<commit_before>/*
 * Intel Wireless WiMAX Connection 2400m
 * Miscellaneous control functions for managing the device
 *
 *
 * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * Intel Corporation <<EMAIL>>
 * <NAME> <<EMAIL>>
 *  - Initial implementation
 *
 * This is a collection of functions used to control the device (plus
 * a few helpers).
 *
 * There are utilities for handling TLV buffers, hooks on the device's
 * reports to act on device changes of state [i2400m_report_hook()],
 * on acks to commands [i2400m_msg_ack_hook()], a helper for sending
 * commands to the device and blocking until a reply arrives
 * [i2400m_msg_to_dev()], a few high level commands for manipulating
 * the device state, powersving mode and configuration plus the
 * routines to setup the device once communication is stablished with
 * it [i2400m_dev_initialize()].
 *
 * ROADMAP
 *
 * i2400m_dev_initalize()       Called by i2400m_dev_start()
 *   i2400m_set_init_config()
 *   i2400m_cmd_get_state()
 * i2400m_dev_shutdown()        Called by i2400m_dev_stop()
 *   i2400m->bus_reset()
 *
 * i2400m_{cmd,get,set}_*()
 *   i2400m_msg_to_dev()
 *   i2400m_msg_check_status()
 *
 * i2400m_report_hook()         Called on reception of an event
 *   i2400m_report_state_hook()
 *     i2400m_tlv_buffer_walk()
 *     i2400m_tlv_match()
 *     i2400m_report_tlv_system_state()
 *     i2400m_report_tlv_rf_switches_status()
 *     i2400m_report_tlv_media_status()
 *   i2400m_cmd_enter_powersave()
 *
 * i2400m_msg_ack_hook()        Called on reception of a reply to a
 *                              command, get or set
 */

#include <stdarg.h>
#include "i2400m.h"
#include <linux/kernel.h>
#include <linux/wimax/i2400m.h>


#define D_SUBMODULE control
#include "debug-levels.h"


/*
 * Return if a TLV is of a give type and size
 *
 * @tlv_hdr: pointer to the TLV
 * @tlv_type: type of the TLV we are looking for
 * @tlv_size: expected size of the TLV we are looking for (if -1,
 *            don't check the size). This includes the header
 * Returns: 0 if the TLV matches
 *          < 0 if it doesn't match at all
 *          > 0 total TLV + payload size, if the type matches, but not
 *              the size
 */
static
ssize_t i2400m_tlv_match(const struct i2400m_tlv_hdr *tlv,
		     enum i2400m_tlv tlv_type, ssize_t tlv_size)
{
	if (le16_to_cpu(tlv->type) != tlv_type)	/* Not our type? skip */
		return -1;
	if (tlv_size != -1
	    && le16_to_cpu(tlv->length) + sizeof(*tlv) != tlv_size) {
		size_t size = le16_to_cpu(tlv->length) + sizeof(*tlv);
		printk(KERN_WARNING "W: tlv type 0x%x mismatched because of "
		       "size (got %zu vs %zu expected)\n",
		       tlv_type, size, tlv_size);
		return size;
	}
	return 0;
}


/*
 * Given a buffer of TLVs, iterate over them
 *
 * @i2400m: device instance
 * @tlv_buf: pointer to the beginning of the TLV buffer
 * @buf_size: buffer size in bytes
 * @tlv_pos: seek position; this is assumed to be a pointer returned
 *           by i2400m_tlv_buffer_walk() [and thus, validated]. The
 *           TLV returned will be the one following this one.
 *
 * Usage:
 *
 * tlv_itr = NULL;
 * while (tlv_itr = i2400m_tlv_buffer_walk(i2400m, buf, size, tlv_itr))  {
 *         ...
 *         // Do stuff with tlv_itr, DON'T MODIFY IT
 *         ...
 * }
 */
static
const struct i2400m_tlv_hdr *i2400m_tlv_buffer_walk(
	struct i2400m *i2400m,
	const void *tlv_buf, size_t buf_size,
	const struct i2400m_tlv_hdr *tlv_pos)
{
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_hdr *tlv_top = tlv_buf + buf_size;
	size_t offset, length, avail_size;
	unsigned type;

	if (tlv_pos == NULL)	/* Take the first one? */
		tlv_pos = tlv_buf;
	else			/* Nope, the next one */
		tlv_pos = (void *) tlv_pos
			+ le16_to_cpu(tlv_pos->length) + sizeof(*tlv_pos);
	if (tlv_pos == tlv_top) {	/* buffer done */
		tlv_pos = NULL;
		goto error_beyond_end;
	}
	if (tlv_pos > tlv_top) {
		tlv_pos = NULL;
		WARN_ON(1);
		goto error_beyond_end;
	}
	offset = (void *) tlv_pos - (void *) tlv_buf;
	avail_size = buf_size - offset;
	if (avail_size < sizeof(*tlv_pos)) {
		dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], tlv @%zu: "
			"short header\n", tlv_buf, buf_size, offset);
		goto error_short_header;
	}
	type = le16_to_cpu(tlv_pos->type);
	length = le16_to_cpu(tlv_pos->length);
	if (avail_size < sizeof(*tlv_pos) + length) {
		dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], "
			"tlv type 0x%04x @%zu: "
			"short data (%zu bytes vs %zu needed)\n",
			tlv_buf, buf_size, type, offset, avail_size,
			sizeof(*tlv_pos) + length);
		goto error_short_header;
	}
error_short_header:
error_beyond_end:
	return tlv_pos;
}


/*
 * Find a TLV in a buffer of sequential TLVs
 *
 * @i2400m: device descriptor
 * @tlv_hdr: pointer to the first TLV in the sequence
 * @size: size of the buffer in bytes; all TLVs are assumed to fit
 *        fully in the buffer (otherwise we'll complain).
 * @tlv_type: type of the TLV we are looking for
 * @tlv_size: expected size of the TLV we are looking for (if -1,
 *            don't check the size). This includes the header
 *
 * Returns: NULL if the TLV is not found, otherwise a pointer to
 *          it. If the sizes don't match, an error is printed and NULL
 *          returned.
 */
static
const struct i2400m_tlv_hdr *i2400m_tlv_find(
	struct i2400m *i2400m,
	const struct i2400m_tlv_hdr *tlv_hdr, size_t size,
	enum i2400m_tlv tlv_type, ssize_t tlv_size)
{
	ssize_t match;
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_hdr *tlv = NULL;
	while ((tlv = i2400m_tlv_buffer_walk(i2400m, tlv_hdr, size, tlv))) {
		match = i2400m_tlv_match(tlv, tlv_type, tlv_size);
		if (match == 0)		/* found it :) */
			break;
		if (match > 0)
			dev_warn(dev, "TLV type 0x%04x found with size "
				 "mismatch (%zu vs %zu needed)\n",
				 tlv_type, match, tlv_size);
	}
	return tlv;
}


static const struct
{
	char *msg;
	int errno;
} ms_to_errno[I2400M_MS_MAX] = {
	[I2400M_MS_DONE_OK] = { "", 0 },
	[I2400M_MS_DONE_IN_PROGRESS] = { "", 0 },
	[I2400M_MS_INVALID_OP] = { "invalid opcode", -ENOSYS },
	[I2400M_MS_BAD_STATE] = { "invalid state", -EILSEQ },
	[I2400M_MS_ILLEGAL_VALUE] = { "illegal value", -EINVAL },
	[I2400M_MS_MISSING_PARAMS] = { "missing parameters", -ENOMSG },
	[I2400M_MS_VERSION_ERROR] = { "bad version", -EIO },
	[I2400M_MS_ACCESSIBILITY_ERROR] = { "accesibility error", -EIO },
	[I2400M_MS_BUSY] = { "busy", -EBUSY },
	[I2400M_MS_CORRUPTED_TLV] = { "corrupted TLV", -EILSEQ },
	[I2400M_MS_UNINITIALIZED] = { "not unitialized", -EILSEQ },
	[I2400M_MS_UNKNOWN_ERROR] = { "unknown error", -EIO },
	[I2400M_MS_PRODUCTION_ERROR] = { "production error", -EIO },
	[I2400M_MS_NO_RF] = { "no RF", -EIO },
	[I2400M_MS_NOT_READY_FOR_POWERSAVE] =
		{ "not ready for powersave", -EACCES },
	[I2400M_MS_THERMAL_CRITICAL] = { "thermal critical", -EL3HLT },
};


/*
 * i2400m_msg_check_status - translate a message's status code
 *
 * @i2400m: device descriptor
 * @l3l4_hdr: message header
 * @strbuf: buffer to place a formatted error message (unless NULL).
 * @strbuf_size: max amount of available space; larger messages will
 * be truncated.
 *
 * Returns: errno code corresponding to the status code in @l3l4_hdr
 *          and a message in @strbuf describing the error.
 */
int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *l3l4_hdr,
			    char *strbuf, size_t strbuf_size)
{
	int result;
	enum i2400m_ms status = le16_to_cpu(l3l4_hdr->status);
	const char *str;

	if (status == 0)
		return 0;
	if (status > ARRAY_SIZE(ms_to_errno)) {
		str = "unknown status code";
		result = -EBADR;
	} else {
		str = ms_to_errno[status].msg;
		result = ms_to_errno[status].errno;
	}
	if (strbuf)
		snprintf(strbuf, strbuf_size, "%s (%d)", str, status);
	return result;
}


/*
 * Act on a TLV System State reported by the device
 *
 * @i2400m: device descriptor
 * @ss: validated System State TLV
 */
static
void i2400m_report_tlv_system_state(struct i2400m *i2400m,
				    const struct i2400m_tlv_system_state *ss)
{
	struct device *dev = i2400m_dev(i2400m);
	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
	enum i2400m_system_state i2400m_state = le32_to_cpu(ss->state);

	d_fnstart(3, dev, "(i2400m %p ss %p [%u])\n", i2400m, ss, i2400m_state);

	if (unlikely(i2400m->ready == 0))	/* act if up */
		goto out;
	if (i2400m->state != i2400m_state) {
		i2400m->state = i2400m_state;
		wake_up_all(&i2400m->state_wq);
	}
	switch (i2400m_state) {
	case I2400M_SS_UNINITIALIZED:
	case I2400M_SS_INIT:
	case I2400M_SS_CONFIG:
	case I2400M_SS_PRODUCTION:
		wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
		break;

	case I2400M_SS_RF_OFF:
	case I2400M_SS_RF_SHUTDOWN:
		wimax_state_change(wimax_dev, WIMAX_ST_RADIO_OFF);
		break;

	case I2400M_SS_READY:
	case I2400M_SS_STANDBY:
	case I2400M_SS_SLEEPACTIVE:
		wimax_state_change(wimax_dev, WIMAX_ST_READY);
		break;

	case I2400M_SS_CONNECTING:
	case I2400M_SS_WIMAX_CONNECTED:
		wimax_state_change(wimax_dev, WIMAX_ST_READY);
		break;

	case I2400M_SS_SCAN:
	case I2400M_SS_OUT_OF_ZONE:
		wimax_state_change(wimax_dev, WIMAX_ST_SCANNING);
		break;

	case I2400M_SS_IDLE:
		d_printf(1, dev, "entering BS-negotiated idle mode\n");
	case I2400M_SS_DISCONNECTING:
	case I2400M_SS_DATA_PATH_CONNECTED:
		wimax_state_change(wimax_dev, WIMAX_ST_CONNECTED);
		break;

	default:
		/* Huh? just in case, shut it down */
		dev_err(dev, "HW BUG? unknown state %u: shutting down\n",
			i2400m_state);
		i2400m->bus_reset(i2400m, I2400M_RT_WARM);
		break;
	};
out:
	d_fnend(3, dev, "(i2400m %p ss %p [%u]) = void\n",
		i2400m, ss, i2400m_state);
}


/*
 * Parse and act on a TLV Media Status sent by the device
 *
 * @i2400m: device descriptor
 * @ms: validated Media Status TLV
 *
 * This will set the carrier up on down based on the device's link
 * report. This is done asides of what the WiMAX stack does based on
 * the device's state as sometimes we need to do a link-renew (the BS
 * wants us to renew a DHCP lease, for example).
 *
 * In fact, doc says that everytime we get a link-up, we should do a
 * DHCP negotiation...
 */
static
void i2400m_report_tlv_media_status(struct i2400m *i2400m,
				    const struct i2400m_tlv_media_status *ms)
{
	struct device *dev = i2400m_dev(i2400m);
	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
	struct net_device *net_dev = wimax_dev->net_dev;
	enum i2400m_media_status status = le32_to_cpu(ms->media_status);

	d_fnstart(3, dev, "(i2400m %p ms %p [%u])\n", i2400m, ms, status);

	if (unlikely(i2400m->ready == 0))	/* act if up */
		goto out;
	switch (status) {
	case I2400M_MEDIA_STATUS_LINK_UP:
		netif_carrier_on(net_dev);
		break;
	case I2400M_MEDIA_STATUS_LINK_DOWN:
		netif_carrier_off(net_dev);
		break;
	/*
	 * This is the network telling us we need to retrain the DHCP
	 * lease -- so far, we are trusting the WiMAX Network Service
	 * in user space to pick this up and poke the DHCP client.
	 */
	case I2400M_MEDIA_STATUS_LINK_RENEW:
		netif_carrier_on(net_dev);
		break;
	default:
		dev_err(dev, "HW BUG? unknown media status %u\n",
			status);
	};
out:
	d_fnend(3, dev, "(i2400m %p ms %p [%u]) = void\n",
		i2400m, ms, status);
}


/*
 * Parse a 'state report' and extract carrier on/off information
 *
 * @i2400m: device descriptor
 * @l3l4_hdr: pointer to message; it has been already validated for
 *            consistent size.
 * @size: size of the message (header + payload). The header length
 *        declaration is assumed to be congruent with @size (as in
 *        sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
 *
 * Extract from the report state the system state TLV and infer from
 * there if we have a carrier or not. Update our local state and tell
 * netdev.
 *
 * When setting the carrier, it's fine to set OFF twice (for example),
 * as netif_carrier_off() will not generate two OFF events (just on
 * the transitions).
 */
static
void i2400m_report_state_hook(struct i2400m *i2400m,
			      const struct i2400m_l3l4_hdr *l3l4_hdr,
			      size_t size, const char *tag)
{
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_hdr *tlv;
	const struct i2400m_tlv_system_state *ss;
	const struct i2400m_tlv_rf_switches_status *rfss;
	const struct i2400m_tlv_media_status *ms;
	size_t tlv_size = le16_to_cpu(l3l4_hdr->length);

	d_fnstart(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s)\n",
		  i2400m, l3l4_hdr, size, tag);
	tlv = NULL;

	while ((tlv = i2400m_tlv_buffer_walk(i2400m, &l3l4_hdr->pl,
					     tlv_size, tlv))) {
		if (0 == i2400m_tlv_match(tlv, I2400M_TLV_SYSTEM_STATE,
					  sizeof(*ss))) {
			ss = container_of(tlv, typeof(*ss), hdr);
			d_printf(2, dev, "%s: system state TLV "
				 "found (0x%04x), state 0x%08x\n",
				 tag, I2400M_TLV_SYSTEM_STATE,
				 le32_to_cpu(ss->state));
			i2400m_report_tlv_system_state(i2400m, ss);
		}
		if (0 == i2400m_tlv_match(tlv, I2400M_TLV_RF_STATUS,
					  sizeof(*rfss))) {
			rfss = container_of(tlv, typeof(*rfss), hdr);
			d_printf(2, dev, "%s: RF status TLV "
				 "found (0x%04x), sw 0x%02x hw 0x%02x\n",
				 tag, I2400M_TLV_RF_STATUS,
				 le32_to_cpu(rfss->sw_rf_switch),
				 le32_to_cpu(rfss->hw_rf_switch));
			i2400m_report_tlv_rf_switches_status(i2400m, rfss);
		}
		if (0 == i2400m_tlv_match(tlv, I2400M_TLV_MEDIA_STATUS,
					  sizeof(*ms))) {
			ms = container_of(tlv, typeof(*ms), hdr);
			d_printf(2, dev, "%s: Media Status TLV: %u\n",
				 tag, le32_to_cpu(ms->media_status));
			i2400m_report_tlv_media_status(i2400m, ms);
		}
	}
	d_fnend(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s) = void\n",
		i2400m, l3l4_hdr, size, tag);
}


/*
 * i2400m_report_hook - (maybe) act on a report
 *
 * @i2400m: device descriptor
 * @l3l4_hdr: pointer to message; it has been already validated for
 *            consistent size.
 * @size: size of the message (header + payload). The header length
 *        declaration is assumed to be congruent with @size (as in
 *        sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
 *
 * Extract information we might need (like carrien on/off) from a
 * device report.
 */
void i2400m_report_hook(struct i2400m *i2400m,
			const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size)
{
	struct device *dev = i2400m_dev(i2400m);<commit_msg>wimax/i2400m: factor out 'state report's TLV handling to a function<commit_after>/*
 * Intel Wireless WiMAX Connection 2400m
 * Miscellaneous control functions for managing the device
 *
 *
 * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * Intel Corporation <<EMAIL>>
 * <NAME> <<EMAIL>>
 *  - Initial implementation
 *
 * This is a collection of functions used to control the device (plus
 * a few helpers).
 *
 * There are utilities for handling TLV buffers, hooks on the device's
 * reports to act on device changes of state [i2400m_report_hook()],
 * on acks to commands [i2400m_msg_ack_hook()], a helper for sending
 * commands to the device and blocking until a reply arrives
 * [i2400m_msg_to_dev()], a few high level commands for manipulating
 * the device state, powersving mode and configuration plus the
 * routines to setup the device once communication is stablished with
 * it [i2400m_dev_initialize()].
 *
 * ROADMAP
 *
 * i2400m_dev_initalize()       Called by i2400m_dev_start()
 *   i2400m_set_init_config()
 *   i2400m_cmd_get_state()
 * i2400m_dev_shutdown()        Called by i2400m_dev_stop()
 *   i2400m->bus_reset()
 *
 * i2400m_{cmd,get,set}_*()
 *   i2400m_msg_to_dev()
 *   i2400m_msg_check_status()
 *
 * i2400m_report_hook()         Called on reception of an event
 *   i2400m_report_state_hook()
 *     i2400m_tlv_buffer_walk()
 *     i2400m_tlv_match()
 *     i2400m_report_tlv_system_state()
 *     i2400m_report_tlv_rf_switches_status()
 *     i2400m_report_tlv_media_status()
 *   i2400m_cmd_enter_powersave()
 *
 * i2400m_msg_ack_hook()        Called on reception of a reply to a
 *                              command, get or set
 */

#include <stdarg.h>
#include "i2400m.h"
#include <linux/kernel.h>
#include <linux/wimax/i2400m.h>


#define D_SUBMODULE control
#include "debug-levels.h"


/*
 * Return if a TLV is of a give type and size
 *
 * @tlv_hdr: pointer to the TLV
 * @tlv_type: type of the TLV we are looking for
 * @tlv_size: expected size of the TLV we are looking for (if -1,
 *            don't check the size). This includes the header
 * Returns: 0 if the TLV matches
 *          < 0 if it doesn't match at all
 *          > 0 total TLV + payload size, if the type matches, but not
 *              the size
 */
static
ssize_t i2400m_tlv_match(const struct i2400m_tlv_hdr *tlv,
		     enum i2400m_tlv tlv_type, ssize_t tlv_size)
{
	if (le16_to_cpu(tlv->type) != tlv_type)	/* Not our type? skip */
		return -1;
	if (tlv_size != -1
	    && le16_to_cpu(tlv->length) + sizeof(*tlv) != tlv_size) {
		size_t size = le16_to_cpu(tlv->length) + sizeof(*tlv);
		printk(KERN_WARNING "W: tlv type 0x%x mismatched because of "
		       "size (got %zu vs %zu expected)\n",
		       tlv_type, size, tlv_size);
		return size;
	}
	return 0;
}


/*
 * Given a buffer of TLVs, iterate over them
 *
 * @i2400m: device instance
 * @tlv_buf: pointer to the beginning of the TLV buffer
 * @buf_size: buffer size in bytes
 * @tlv_pos: seek position; this is assumed to be a pointer returned
 *           by i2400m_tlv_buffer_walk() [and thus, validated]. The
 *           TLV returned will be the one following this one.
 *
 * Usage:
 *
 * tlv_itr = NULL;
 * while (tlv_itr = i2400m_tlv_buffer_walk(i2400m, buf, size, tlv_itr))  {
 *         ...
 *         // Do stuff with tlv_itr, DON'T MODIFY IT
 *         ...
 * }
 */
static
const struct i2400m_tlv_hdr *i2400m_tlv_buffer_walk(
	struct i2400m *i2400m,
	const void *tlv_buf, size_t buf_size,
	const struct i2400m_tlv_hdr *tlv_pos)
{
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_hdr *tlv_top = tlv_buf + buf_size;
	size_t offset, length, avail_size;
	unsigned type;

	if (tlv_pos == NULL)	/* Take the first one? */
		tlv_pos = tlv_buf;
	else			/* Nope, the next one */
		tlv_pos = (void *) tlv_pos
			+ le16_to_cpu(tlv_pos->length) + sizeof(*tlv_pos);
	if (tlv_pos == tlv_top) {	/* buffer done */
		tlv_pos = NULL;
		goto error_beyond_end;
	}
	if (tlv_pos > tlv_top) {
		tlv_pos = NULL;
		WARN_ON(1);
		goto error_beyond_end;
	}
	offset = (void *) tlv_pos - (void *) tlv_buf;
	avail_size = buf_size - offset;
	if (avail_size < sizeof(*tlv_pos)) {
		dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], tlv @%zu: "
			"short header\n", tlv_buf, buf_size, offset);
		goto error_short_header;
	}
	type = le16_to_cpu(tlv_pos->type);
	length = le16_to_cpu(tlv_pos->length);
	if (avail_size < sizeof(*tlv_pos) + length) {
		dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], "
			"tlv type 0x%04x @%zu: "
			"short data (%zu bytes vs %zu needed)\n",
			tlv_buf, buf_size, type, offset, avail_size,
			sizeof(*tlv_pos) + length);
		goto error_short_header;
	}
error_short_header:
error_beyond_end:
	return tlv_pos;
}


/*
 * Find a TLV in a buffer of sequential TLVs
 *
 * @i2400m: device descriptor
 * @tlv_hdr: pointer to the first TLV in the sequence
 * @size: size of the buffer in bytes; all TLVs are assumed to fit
 *        fully in the buffer (otherwise we'll complain).
 * @tlv_type: type of the TLV we are looking for
 * @tlv_size: expected size of the TLV we are looking for (if -1,
 *            don't check the size). This includes the header
 *
 * Returns: NULL if the TLV is not found, otherwise a pointer to
 *          it. If the sizes don't match, an error is printed and NULL
 *          returned.
 */
static
const struct i2400m_tlv_hdr *i2400m_tlv_find(
	struct i2400m *i2400m,
	const struct i2400m_tlv_hdr *tlv_hdr, size_t size,
	enum i2400m_tlv tlv_type, ssize_t tlv_size)
{
	ssize_t match;
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_hdr *tlv = NULL;
	while ((tlv = i2400m_tlv_buffer_walk(i2400m, tlv_hdr, size, tlv))) {
		match = i2400m_tlv_match(tlv, tlv_type, tlv_size);
		if (match == 0)		/* found it :) */
			break;
		if (match > 0)
			dev_warn(dev, "TLV type 0x%04x found with size "
				 "mismatch (%zu vs %zu needed)\n",
				 tlv_type, match, tlv_size);
	}
	return tlv;
}


static const struct
{
	char *msg;
	int errno;
} ms_to_errno[I2400M_MS_MAX] = {
	[I2400M_MS_DONE_OK] = { "", 0 },
	[I2400M_MS_DONE_IN_PROGRESS] = { "", 0 },
	[I2400M_MS_INVALID_OP] = { "invalid opcode", -ENOSYS },
	[I2400M_MS_BAD_STATE] = { "invalid state", -EILSEQ },
	[I2400M_MS_ILLEGAL_VALUE] = { "illegal value", -EINVAL },
	[I2400M_MS_MISSING_PARAMS] = { "missing parameters", -ENOMSG },
	[I2400M_MS_VERSION_ERROR] = { "bad version", -EIO },
	[I2400M_MS_ACCESSIBILITY_ERROR] = { "accesibility error", -EIO },
	[I2400M_MS_BUSY] = { "busy", -EBUSY },
	[I2400M_MS_CORRUPTED_TLV] = { "corrupted TLV", -EILSEQ },
	[I2400M_MS_UNINITIALIZED] = { "not unitialized", -EILSEQ },
	[I2400M_MS_UNKNOWN_ERROR] = { "unknown error", -EIO },
	[I2400M_MS_PRODUCTION_ERROR] = { "production error", -EIO },
	[I2400M_MS_NO_RF] = { "no RF", -EIO },
	[I2400M_MS_NOT_READY_FOR_POWERSAVE] =
		{ "not ready for powersave", -EACCES },
	[I2400M_MS_THERMAL_CRITICAL] = { "thermal critical", -EL3HLT },
};


/*
 * i2400m_msg_check_status - translate a message's status code
 *
 * @i2400m: device descriptor
 * @l3l4_hdr: message header
 * @strbuf: buffer to place a formatted error message (unless NULL).
 * @strbuf_size: max amount of available space; larger messages will
 * be truncated.
 *
 * Returns: errno code corresponding to the status code in @l3l4_hdr
 *          and a message in @strbuf describing the error.
 */
int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *l3l4_hdr,
			    char *strbuf, size_t strbuf_size)
{
	int result;
	enum i2400m_ms status = le16_to_cpu(l3l4_hdr->status);
	const char *str;

	if (status == 0)
		return 0;
	if (status > ARRAY_SIZE(ms_to_errno)) {
		str = "unknown status code";
		result = -EBADR;
	} else {
		str = ms_to_errno[status].msg;
		result = ms_to_errno[status].errno;
	}
	if (strbuf)
		snprintf(strbuf, strbuf_size, "%s (%d)", str, status);
	return result;
}


/*
 * Act on a TLV System State reported by the device
 *
 * @i2400m: device descriptor
 * @ss: validated System State TLV
 */
static
void i2400m_report_tlv_system_state(struct i2400m *i2400m,
				    const struct i2400m_tlv_system_state *ss)
{
	struct device *dev = i2400m_dev(i2400m);
	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
	enum i2400m_system_state i2400m_state = le32_to_cpu(ss->state);

	d_fnstart(3, dev, "(i2400m %p ss %p [%u])\n", i2400m, ss, i2400m_state);

	if (unlikely(i2400m->ready == 0))	/* act if up */
		goto out;
	if (i2400m->state != i2400m_state) {
		i2400m->state = i2400m_state;
		wake_up_all(&i2400m->state_wq);
	}
	switch (i2400m_state) {
	case I2400M_SS_UNINITIALIZED:
	case I2400M_SS_INIT:
	case I2400M_SS_CONFIG:
	case I2400M_SS_PRODUCTION:
		wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
		break;

	case I2400M_SS_RF_OFF:
	case I2400M_SS_RF_SHUTDOWN:
		wimax_state_change(wimax_dev, WIMAX_ST_RADIO_OFF);
		break;

	case I2400M_SS_READY:
	case I2400M_SS_STANDBY:
	case I2400M_SS_SLEEPACTIVE:
		wimax_state_change(wimax_dev, WIMAX_ST_READY);
		break;

	case I2400M_SS_CONNECTING:
	case I2400M_SS_WIMAX_CONNECTED:
		wimax_state_change(wimax_dev, WIMAX_ST_READY);
		break;

	case I2400M_SS_SCAN:
	case I2400M_SS_OUT_OF_ZONE:
		wimax_state_change(wimax_dev, WIMAX_ST_SCANNING);
		break;

	case I2400M_SS_IDLE:
		d_printf(1, dev, "entering BS-negotiated idle mode\n");
	case I2400M_SS_DISCONNECTING:
	case I2400M_SS_DATA_PATH_CONNECTED:
		wimax_state_change(wimax_dev, WIMAX_ST_CONNECTED);
		break;

	default:
		/* Huh? just in case, shut it down */
		dev_err(dev, "HW BUG? unknown state %u: shutting down\n",
			i2400m_state);
		i2400m->bus_reset(i2400m, I2400M_RT_WARM);
		break;
	};
out:
	d_fnend(3, dev, "(i2400m %p ss %p [%u]) = void\n",
		i2400m, ss, i2400m_state);
}


/*
 * Parse and act on a TLV Media Status sent by the device
 *
 * @i2400m: device descriptor
 * @ms: validated Media Status TLV
 *
 * This will set the carrier up on down based on the device's link
 * report. This is done asides of what the WiMAX stack does based on
 * the device's state as sometimes we need to do a link-renew (the BS
 * wants us to renew a DHCP lease, for example).
 *
 * In fact, doc says that everytime we get a link-up, we should do a
 * DHCP negotiation...
 */
static
void i2400m_report_tlv_media_status(struct i2400m *i2400m,
				    const struct i2400m_tlv_media_status *ms)
{
	struct device *dev = i2400m_dev(i2400m);
	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
	struct net_device *net_dev = wimax_dev->net_dev;
	enum i2400m_media_status status = le32_to_cpu(ms->media_status);

	d_fnstart(3, dev, "(i2400m %p ms %p [%u])\n", i2400m, ms, status);

	if (unlikely(i2400m->ready == 0))	/* act if up */
		goto out;
	switch (status) {
	case I2400M_MEDIA_STATUS_LINK_UP:
		netif_carrier_on(net_dev);
		break;
	case I2400M_MEDIA_STATUS_LINK_DOWN:
		netif_carrier_off(net_dev);
		break;
	/*
	 * This is the network telling us we need to retrain the DHCP
	 * lease -- so far, we are trusting the WiMAX Network Service
	 * in user space to pick this up and poke the DHCP client.
	 */
	case I2400M_MEDIA_STATUS_LINK_RENEW:
		netif_carrier_on(net_dev);
		break;
	default:
		dev_err(dev, "HW BUG? unknown media status %u\n",
			status);
	};
out:
	d_fnend(3, dev, "(i2400m %p ms %p [%u]) = void\n",
		i2400m, ms, status);
}


/*
 * Process a TLV from a 'state report'
 *
 * @i2400m: device descriptor
 * @tlv: pointer to the TLV header; it has been already validated for
 *     consistent size.
 * @tag: for error messages
 *
 * Act on the TLVs from a 'state report'.
 */
static
void i2400m_report_state_parse_tlv(struct i2400m *i2400m,
				   const struct i2400m_tlv_hdr *tlv,
				   const char *tag)
{
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_media_status *ms;
	const struct i2400m_tlv_system_state *ss;
	const struct i2400m_tlv_rf_switches_status *rfss;

	if (0 == i2400m_tlv_match(tlv, I2400M_TLV_SYSTEM_STATE, sizeof(*ss))) {
		ss = container_of(tlv, typeof(*ss), hdr);
		d_printf(2, dev, "%s: system state TLV "
			 "found (0x%04x), state 0x%08x\n",
			 tag, I2400M_TLV_SYSTEM_STATE,
			 le32_to_cpu(ss->state));
		i2400m_report_tlv_system_state(i2400m, ss);
	}
	if (0 == i2400m_tlv_match(tlv, I2400M_TLV_RF_STATUS, sizeof(*rfss))) {
		rfss = container_of(tlv, typeof(*rfss), hdr);
		d_printf(2, dev, "%s: RF status TLV "
			 "found (0x%04x), sw 0x%02x hw 0x%02x\n",
			 tag, I2400M_TLV_RF_STATUS,
			 le32_to_cpu(rfss->sw_rf_switch),
			 le32_to_cpu(rfss->hw_rf_switch));
		i2400m_report_tlv_rf_switches_status(i2400m, rfss);
	}
	if (0 == i2400m_tlv_match(tlv, I2400M_TLV_MEDIA_STATUS, sizeof(*ms))) {
		ms = container_of(tlv, typeof(*ms), hdr);
		d_printf(2, dev, "%s: Media Status TLV: %u\n",
			 tag, le32_to_cpu(ms->media_status));
		i2400m_report_tlv_media_status(i2400m, ms);
	}
}


/*
 * Parse a 'state report' and extract information
 *
 * @i2400m: device descriptor
 * @l3l4_hdr: pointer to message; it has been already validated for
 *            consistent size.
 * @size: size of the message (header + payload). The header length
 *        declaration is assumed to be congruent with @size (as in
 *        sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
 *
 * Walk over the TLVs in a report state and act on them.
 */
static
void i2400m_report_state_hook(struct i2400m *i2400m,
			      const struct i2400m_l3l4_hdr *l3l4_hdr,
			      size_t size, const char *tag)
{
	struct device *dev = i2400m_dev(i2400m);
	const struct i2400m_tlv_hdr *tlv;
	size_t tlv_size = le16_to_cpu(l3l4_hdr->length);

	d_fnstart(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s)\n",
		  i2400m, l3l4_hdr, size, tag);
	tlv = NULL;

	while ((tlv = i2400m_tlv_buffer_walk(i2400m, &l3l4_hdr->pl,
					     tlv_size, tlv)))
		i2400m_report_state_parse_tlv(i2400m, tlv, tag);
	d_fnend(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s) = void\n",
		i2400m, l3l4_hdr, size, tag);
}


/*
 * i2400m_report_hook - (maybe) act on a report
 *
 * @i2400m: device descriptor
 * @l3l4_hdr: pointer to message; it has been already validated for
 *            consistent size.
 * @size: size of the message (header + payload). The header length
 *        declaration is assumed to be congruent with @size (as in
 *        sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
 *
 * Extract information we might need (like carrien on/off) from a
 * device report.
 */
void i2400m_report_hook(struct i2400m *i2400m,
			const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size)
{
	struct device *dev = i2400m_dev(i2400m);
{'fraction_non_alphanumeric': 0.10559635350721702, 'fraction_numerical': 0.053874398581919475, 'mean_word_length': 3.8717039321511177, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2471811', 'n_tokens_mistral': 13601, 'n_tokens_neox': 12198, 'n_words': 3402}