Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e8571af authored by James Ketrenos's avatar James Ketrenos Committed by Jeff Garzik
Browse files

[PATCH] ieee80211: Add QoS (WME) support to the ieee80211 subsystem



tree a3ad796273e98036eb0e9fc063225070fa24508a
parent 1b9c0aeb377abf8e4a43a86cff42382f74ca0259
author Mohamed Abbas <mabbas@linux.intel.com> 1124447069 -0500
committer James Ketrenos <jketreno@linux.intel.com> 1127313435 -0500

Add QoS (WME) support to the ieee80211 subsystem.

NOTE: This requires drivers that use the ieee80211 hard_start_xmit
(ipw2100 and ipw2200) to add the priority parameter to their callback.

Signed-off-by: default avatarJames Ketrenos <jketreno@linux.intel.com>
Signed-off-by: default avatarJeff Garzik <jgarzik@pobox.com>
parent 2c0aa2a5
Loading
Loading
Loading
Loading
+99 −4
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@
#define IEEE80211_STYPE_CFACK		0x0050
#define IEEE80211_STYPE_CFPOLL		0x0060
#define IEEE80211_STYPE_CFACKPOLL	0x0070
#define IEEE80211_STYPE_QOS_DATA        0x0080

#define IEEE80211_SCTL_FRAG		0x000F
#define IEEE80211_SCTL_SEQ		0xFFF0
@@ -153,6 +154,7 @@ const char *escape_essid(const char *essid, u8 essid_len);

#define IEEE80211_DL_TX            (1<<8)
#define IEEE80211_DL_RX            (1<<9)
#define IEEE80211_DL_QOS           (1<<31)

#define IEEE80211_ERROR(f, a...) printk(KERN_ERR "ieee80211: " f, ## a)
#define IEEE80211_WARNING(f, a...) printk(KERN_WARNING "ieee80211: " f, ## a)
@@ -166,6 +168,7 @@ const char *escape_essid(const char *essid, u8 essid_len);
#define IEEE80211_DEBUG_DROP(f, a...)  IEEE80211_DEBUG(IEEE80211_DL_DROP, f, ## a)
#define IEEE80211_DEBUG_TX(f, a...)  IEEE80211_DEBUG(IEEE80211_DL_TX, f, ## a)
#define IEEE80211_DEBUG_RX(f, a...)  IEEE80211_DEBUG(IEEE80211_DL_RX, f, ## a)
#define IEEE80211_DEBUG_QOS(f, a...)  IEEE80211_DEBUG(IEEE80211_DL_QOS, f, ## a)
#include <linux/netdevice.h>
#include <linux/wireless.h>
#include <linux/if_arp.h>	/* ARPHRD_ETHER */
@@ -493,6 +496,7 @@ enum ieee80211_mfie {
	MFIE_TYPE_RSN = 48,
	MFIE_TYPE_RATES_EX = 50,
	MFIE_TYPE_GENERIC = 221,
	MFIE_TYPE_QOS_PARAMETER = 222,
};

/* Minimal header; can be used for passing 802.11 frames with sufficient
@@ -540,6 +544,29 @@ struct ieee80211_hdr_4addr {
	u8 payload[0];
} __attribute__ ((packed));

struct ieee80211_hdr_3addrqos {
	u16 frame_ctl;
	u16 duration_id;
	u8 addr1[ETH_ALEN];
	u8 addr2[ETH_ALEN];
	u8 addr3[ETH_ALEN];
	u16 seq_ctl;
	u8 payload[0];
	u16 qos_ctl;
} __attribute__ ((packed));

struct ieee80211_hdr_4addrqos {
	u16 frame_ctl;
	u16 duration_id;
	u8 addr1[ETH_ALEN];
	u8 addr2[ETH_ALEN];
	u8 addr3[ETH_ALEN];
	u16 seq_ctl;
	u8 addr4[ETH_ALEN];
	u8 payload[0];
	u16 qos_ctl;
} __attribute__ ((packed));

struct ieee80211_info_element {
	u8 id;
	u8 len;
@@ -645,6 +672,65 @@ struct ieee80211_txb {
#define NETWORK_HAS_OFDM       (1<<1)
#define NETWORK_HAS_CCK        (1<<2)

/* QoS structure */
#define NETWORK_HAS_QOS_PARAMETERS      (1<<3)
#define NETWORK_HAS_QOS_INFORMATION     (1<<4)
#define NETWORK_HAS_QOS_MASK            (NETWORK_HAS_QOS_PARAMETERS | NETWORK_HAS_QOS_INFORMATION)

#define QOS_QUEUE_NUM                   4
#define QOS_OUI_LEN                     3
#define QOS_OUI_TYPE                    2
#define QOS_ELEMENT_ID                  221
#define QOS_OUI_INFO_SUB_TYPE           0
#define QOS_OUI_PARAM_SUB_TYPE          1
#define QOS_VERSION_1                   1
#define QOS_AIFSN_MIN_VALUE             2

struct ieee80211_qos_information_element {
	u8 elementID;
	u8 length;
	u8 qui[QOS_OUI_LEN];
	u8 qui_type;
	u8 qui_subtype;
	u8 version;
	u8 ac_info;
} __attribute__ ((packed));

struct ieee80211_qos_ac_parameter {
	u8 aci_aifsn;
	u8 ecw_min_max;
	u16 tx_op_limit;
} __attribute__ ((packed));

struct ieee80211_qos_parameter_info {
	struct ieee80211_qos_information_element info_element;
	u8 reserved;
	struct ieee80211_qos_ac_parameter ac_params_record[QOS_QUEUE_NUM];
} __attribute__ ((packed));

struct ieee80211_qos_parameters {
	u16 cw_min[QOS_QUEUE_NUM];
	u16 cw_max[QOS_QUEUE_NUM];
	u8 aifs[QOS_QUEUE_NUM];
	u8 flag[QOS_QUEUE_NUM];
	u16 tx_op_limit[QOS_QUEUE_NUM];
} __attribute__ ((packed));

struct ieee80211_qos_data {
	struct ieee80211_qos_parameters parameters;
	int active;
	int supported;
	u8 param_count;
	u8 old_param_count;
};

struct ieee80211_tim_parameters {
	u8 tim_count;
	u8 tim_period;
} __attribute__ ((packed));

/*******************************************************/

struct ieee80211_network {
	/* These entries are used to identify a unique network */
	u8 bssid[ETH_ALEN];
@@ -653,6 +739,8 @@ struct ieee80211_network {
	u8 ssid[IW_ESSID_MAX_SIZE + 1];
	u8 ssid_len;

	struct ieee80211_qos_data qos_data;

	/* These are network statistics */
	struct ieee80211_rx_stats stats;
	u16 capability;
@@ -672,6 +760,7 @@ struct ieee80211_network {
	size_t wpa_ie_len;
	u8 rsn_ie[MAX_WPA_IE_LEN];
	size_t rsn_ie_len;
	struct ieee80211_tim_parameters tim;
	struct list_head list;
};

@@ -769,10 +858,13 @@ struct ieee80211_device {
	void (*set_security) (struct net_device * dev,
			      struct ieee80211_security * sec);
	int (*hard_start_xmit) (struct ieee80211_txb * txb,
				struct net_device * dev);
				struct net_device * dev, int pri);
	int (*reset_port) (struct net_device * dev);
	int (*is_queue_full) (struct net_device * dev, int pri);

	int (*handle_management) (struct net_device * dev,
				  struct ieee80211_network * network, u16 type);

	/* Typical STA methods */
	int (*handle_auth) (struct net_device * dev,
			    struct ieee80211_auth * auth);
@@ -854,11 +946,14 @@ extern inline int ieee80211_is_valid_mode(struct ieee80211_device *ieee,
extern inline int ieee80211_get_hdrlen(u16 fc)
{
	int hdrlen = IEEE80211_3ADDR_LEN;
	u16 stype = WLAN_FC_GET_STYPE(fc);

	switch (WLAN_FC_GET_TYPE(fc)) {
	case IEEE80211_FTYPE_DATA:
		if ((fc & IEEE80211_FCTL_FROMDS) && (fc & IEEE80211_FCTL_TODS))
			hdrlen = IEEE80211_4ADDR_LEN;
		if (stype & IEEE80211_STYPE_QOS_DATA)
			hdrlen += 2;
		break;
	case IEEE80211_FTYPE_CTL:
		switch (WLAN_FC_GET_STYPE(fc)) {
+315 −19
Original line number Diff line number Diff line
@@ -534,6 +534,9 @@ int ieee80211_rx(struct ieee80211_device *ieee, struct sk_buff *skb,

	/* Nullfunc frames may have PS-bit set, so they must be passed to
	 * hostap_handle_sta_rx() before being dropped here. */

	stype &= ~IEEE80211_STYPE_QOS_DATA;

	if (stype != IEEE80211_STYPE_DATA &&
	    stype != IEEE80211_STYPE_DATA_CFACK &&
	    stype != IEEE80211_STYPE_DATA_CFPOLL &&
@@ -758,6 +761,264 @@ int ieee80211_rx(struct ieee80211_device *ieee, struct sk_buff *skb,

#define MGMT_FRAME_FIXED_PART_LENGTH		0x24

static u8 qos_oui[QOS_OUI_LEN] = { 0x00, 0x50, 0xF2 };

/*
* Make ther structure we read from the beacon packet has
* the right values
*/
static int ieee80211_verify_qos_info(struct ieee80211_qos_information_element
				     *info_element, int sub_type)
{

	if (info_element->qui_subtype != sub_type)
		return -1;
	if (memcmp(info_element->qui, qos_oui, QOS_OUI_LEN))
		return -1;
	if (info_element->qui_type != QOS_OUI_TYPE)
		return -1;
	if (info_element->version != QOS_VERSION_1)
		return -1;

	return 0;
}

/*
 * Parse a QoS parameter element
 */
static int ieee80211_read_qos_param_element(struct ieee80211_qos_parameter_info
					    *element_param, struct ieee80211_info_element
					    *info_element)
{
	int ret = 0;
	u16 size = sizeof(struct ieee80211_qos_parameter_info) - 2;

	if ((info_element == NULL) || (element_param == NULL))
		return -1;

	if (info_element->id == QOS_ELEMENT_ID && info_element->len == size) {
		memcpy(element_param->info_element.qui, info_element->data,
		       info_element->len);
		element_param->info_element.elementID = info_element->id;
		element_param->info_element.length = info_element->len;
	} else
		ret = -1;
	if (ret == 0)
		ret = ieee80211_verify_qos_info(&element_param->info_element,
						QOS_OUI_PARAM_SUB_TYPE);
	return ret;
}

/*
 * Parse a QoS information element
 */
static int ieee80211_read_qos_info_element(struct
					   ieee80211_qos_information_element
					   *element_info, struct ieee80211_info_element
					   *info_element)
{
	int ret = 0;
	u16 size = sizeof(struct ieee80211_qos_information_element) - 2;

	if (element_info == NULL)
		return -1;
	if (info_element == NULL)
		return -1;

	if ((info_element->id == QOS_ELEMENT_ID) && (info_element->len == size)) {
		memcpy(element_info->qui, info_element->data,
		       info_element->len);
		element_info->elementID = info_element->id;
		element_info->length = info_element->len;
	} else
		ret = -1;

	if (ret == 0)
		ret = ieee80211_verify_qos_info(element_info,
						QOS_OUI_INFO_SUB_TYPE);
	return ret;
}

/*
 * Write QoS parameters from the ac parameters.
 */
static int ieee80211_qos_convert_ac_to_parameters(struct
						  ieee80211_qos_parameter_info
						  *param_elm, struct
						  ieee80211_qos_parameters
						  *qos_param)
{
	int rc = 0;
	int i;
	struct ieee80211_qos_ac_parameter *ac_params;
	u32 txop;
	u8 cw_min;
	u8 cw_max;

	for (i = 0; i < QOS_QUEUE_NUM; i++) {
		ac_params = &(param_elm->ac_params_record[i]);

		qos_param->aifs[i] = (ac_params->aci_aifsn) & 0x0F;
		qos_param->aifs[i] -= (qos_param->aifs[i] < 2) ? 0 : 2;

		cw_min = ac_params->ecw_min_max & 0x0F;
		qos_param->cw_min[i] = (u16) ((1 << cw_min) - 1);

		cw_max = (ac_params->ecw_min_max & 0xF0) >> 4;
		qos_param->cw_max[i] = (u16) ((1 << cw_max) - 1);

		qos_param->flag[i] =
		    (ac_params->aci_aifsn & 0x10) ? 0x01 : 0x00;

		txop = le16_to_cpu(ac_params->tx_op_limit) * 32;
		qos_param->tx_op_limit[i] = (u16) txop;
	}
	return rc;
}

/*
 * we have a generic data element which it may contain QoS information or
 * parameters element. check the information element length to decide
 * which type to read
 */
static int ieee80211_parse_qos_info_param_IE(struct ieee80211_info_element
					     *info_element,
					     struct ieee80211_network *network)
{
	int rc = 0;
	struct ieee80211_qos_parameters *qos_param = NULL;
	struct ieee80211_qos_information_element qos_info_element;

	rc = ieee80211_read_qos_info_element(&qos_info_element, info_element);

	if (rc == 0) {
		network->qos_data.param_count = qos_info_element.ac_info & 0x0F;
		network->flags |= NETWORK_HAS_QOS_INFORMATION;
	} else {
		struct ieee80211_qos_parameter_info param_element;

		rc = ieee80211_read_qos_param_element(&param_element,
						      info_element);
		if (rc == 0) {
			qos_param = &(network->qos_data.parameters);
			ieee80211_qos_convert_ac_to_parameters(&param_element,
							       qos_param);
			network->flags |= NETWORK_HAS_QOS_PARAMETERS;
			network->qos_data.param_count =
			    param_element.info_element.ac_info & 0x0F;
		}
	}

	if (rc == 0) {
		IEEE80211_DEBUG_QOS("QoS is supported\n");
		network->qos_data.supported = 1;
	}
	return rc;
}

static int ieee80211_handle_assoc_resp(struct ieee80211_device *ieee, struct ieee80211_assoc_response
				       *frame, struct ieee80211_rx_stats *stats)
{
	struct ieee80211_network network_resp;
	struct ieee80211_network *network = &network_resp;
	struct ieee80211_info_element *info_element;
	struct net_device *dev = ieee->dev;
	u16 left;

	network->flags = 0;
	network->qos_data.active = 0;
	network->qos_data.supported = 0;
	network->qos_data.param_count = 0;
	network->qos_data.old_param_count = 0;

	//network->atim_window = le16_to_cpu(frame->aid) & (0x3FFF);
	network->atim_window = le16_to_cpu(frame->aid);
	network->listen_interval = le16_to_cpu(frame->status);

	info_element = frame->info_element;
	left = stats->len - sizeof(*frame);

	while (left >= sizeof(struct ieee80211_info_element)) {
		if (sizeof(struct ieee80211_info_element) +
		    info_element->len > left) {
			IEEE80211_DEBUG_QOS("ASSOC RESP: parse failed: "
					    "info_element->len + 2 > left : "
					    "info_element->len+2=%zd left=%d, id=%d.\n",
					    info_element->len +
					    sizeof(struct
						   ieee80211_info_element),
					    left, info_element->id);
			return 1;
		}

		switch (info_element->id) {
		case MFIE_TYPE_SSID:
			if (ieee80211_is_empty_essid(info_element->data,
						     info_element->len)) {
				network->flags |= NETWORK_EMPTY_ESSID;
				break;
			}

			network->ssid_len = min(info_element->len,
						(u8) IW_ESSID_MAX_SIZE);
			memcpy(network->ssid, info_element->data,
			       network->ssid_len);
			if (network->ssid_len < IW_ESSID_MAX_SIZE)
				memset(network->ssid + network->ssid_len, 0,
				       IW_ESSID_MAX_SIZE - network->ssid_len);

			IEEE80211_DEBUG_QOS("MFIE_TYPE_SSID: '%s' len=%d.\n",
					    network->ssid, network->ssid_len);
			break;

		case MFIE_TYPE_TIM:
			IEEE80211_DEBUG_QOS("MFIE_TYPE_TIM: ignored\n");
			break;

		case MFIE_TYPE_IBSS_SET:
			IEEE80211_DEBUG_QOS("MFIE_TYPE_IBSS_SET: ignored\n");
			break;

		case MFIE_TYPE_CHALLENGE:
			IEEE80211_DEBUG_QOS("MFIE_TYPE_CHALLENGE: ignored\n");
			break;

		case MFIE_TYPE_GENERIC:
			IEEE80211_DEBUG_QOS("MFIE_TYPE_GENERIC: %d bytes\n",
					    info_element->len);
			ieee80211_parse_qos_info_param_IE(info_element,
							  network);
			break;

		case MFIE_TYPE_RSN:
			IEEE80211_DEBUG_QOS("MFIE_TYPE_RSN: %d bytes\n",
					    info_element->len);
			break;

		case MFIE_TYPE_QOS_PARAMETER:
			printk("QoS Error need to parse QOS_PARAMETER IE\n");
			break;

		default:
			IEEE80211_DEBUG_QOS("unsupported IE %d\n",
					    info_element->id);
			break;
		}

		left -= sizeof(struct ieee80211_info_element) +
		    info_element->len;
		info_element = (struct ieee80211_info_element *)
		    &info_element->data[info_element->len];
	}

	if (ieee->handle_assoc_response != NULL)
		ieee->handle_assoc_response(dev, frame, network);

	return 0;
}

/***************************************************/

static inline int ieee80211_is_ofdm_rate(u8 rate)
{
	switch (rate & ~IEEE80211_BASIC_RATE_MASK) {
@@ -786,6 +1047,9 @@ static inline int ieee80211_network_init(struct ieee80211_device *ieee, struct i
	struct ieee80211_info_element *info_element;
	u16 left;
	u8 i;
	network->qos_data.active = 0;
	network->qos_data.supported = 0;
	network->qos_data.param_count = 0;

	/* Pull out fixed field data */
	memcpy(network->bssid, beacon->header.addr3, ETH_ALEN);
@@ -813,13 +1077,11 @@ static inline int ieee80211_network_init(struct ieee80211_device *ieee, struct i

	info_element = beacon->info_element;
	left = stats->len - sizeof(*beacon);
	while (left >= sizeof(struct ieee80211_info_element)) {
		if (sizeof(struct ieee80211_info_element) + info_element->len >
		    left) {
	while (left >= sizeof(*info_element)) {
		if (sizeof(*info_element) + info_element->len > left) {
			IEEE80211_DEBUG_SCAN
			    ("SCAN: parse failed: info_element->len + 2 > left : info_element->len+2=%Zd left=%d.\n",
			     info_element->len +
			     sizeof(struct ieee80211_info_element), left);
			     info_element->len + sizeof(*info_element), left);
			return 1;
		}

@@ -847,15 +1109,14 @@ static inline int ieee80211_network_init(struct ieee80211_device *ieee, struct i
#ifdef CONFIG_IEEE80211_DEBUG
			p = rates_str;
#endif
			network->rates_len =
			    min(info_element->len, MAX_RATES_LENGTH);
			network->rates_len = min(info_element->len,
						 MAX_RATES_LENGTH);
			for (i = 0; i < network->rates_len; i++) {
				network->rates[i] = info_element->data[i];
#ifdef CONFIG_IEEE80211_DEBUG
				p += snprintf(p,
					      sizeof(rates_str) - (p -
								   rates_str),
					      "%02X ", network->rates[i]);
				p += snprintf(p, sizeof(rates_str) -
					      (p - rates_str), "%02X ",
					      network->rates[i]);
#endif
				if (ieee80211_is_ofdm_rate
				    (info_element->data[i])) {
@@ -875,15 +1136,14 @@ static inline int ieee80211_network_init(struct ieee80211_device *ieee, struct i
#ifdef CONFIG_IEEE80211_DEBUG
			p = rates_str;
#endif
			network->rates_ex_len =
			    min(info_element->len, MAX_RATES_EX_LENGTH);
			network->rates_ex_len = min(info_element->len,
						    MAX_RATES_EX_LENGTH);
			for (i = 0; i < network->rates_ex_len; i++) {
				network->rates_ex[i] = info_element->data[i];
#ifdef CONFIG_IEEE80211_DEBUG
				p += snprintf(p,
					      sizeof(rates_str) - (p -
								   rates_str),
					      "%02X ", network->rates[i]);
				p += snprintf(p, sizeof(rates_str) -
					      (p - rates_str), "%02X ",
					      network->rates[i]);
#endif
				if (ieee80211_is_ofdm_rate
				    (info_element->data[i])) {
@@ -929,6 +1189,10 @@ static inline int ieee80211_network_init(struct ieee80211_device *ieee, struct i
		case MFIE_TYPE_GENERIC:
			IEEE80211_DEBUG_SCAN("MFIE_TYPE_GENERIC: %d bytes\n",
					     info_element->len);
			if (!ieee80211_parse_qos_info_param_IE(info_element,
							       network))
				break;

			if (info_element->len >= 4 &&
			    info_element->data[0] == 0x00 &&
			    info_element->data[1] == 0x50 &&
@@ -950,14 +1214,18 @@ static inline int ieee80211_network_init(struct ieee80211_device *ieee, struct i
			       network->rsn_ie_len);
			break;

		case MFIE_TYPE_QOS_PARAMETER:
			printk(KERN_ERR
			       "QoS Error need to parse QOS_PARAMETER IE\n");
			break;

		default:
			IEEE80211_DEBUG_SCAN("unsupported IE %d\n",
					     info_element->id);
			break;
		}

		left -= sizeof(struct ieee80211_info_element) +
		    info_element->len;
		left -= sizeof(*info_element) + info_element->len;
		info_element = (struct ieee80211_info_element *)
		    &info_element->data[info_element->len];
	}
@@ -1004,6 +1272,9 @@ static inline int is_same_network(struct ieee80211_network *src,
static inline void update_network(struct ieee80211_network *dst,
				  struct ieee80211_network *src)
{
	int qos_active;
	u8 old_param;

	memcpy(&dst->stats, &src->stats, sizeof(struct ieee80211_rx_stats));
	dst->capability = src->capability;
	memcpy(dst->rates, src->rates, src->rates_len);
@@ -1026,6 +1297,28 @@ static inline void update_network(struct ieee80211_network *dst,
	dst->rsn_ie_len = src->rsn_ie_len;

	dst->last_scanned = jiffies;
	qos_active = src->qos_data.active;
	old_param = dst->qos_data.old_param_count;
	if (dst->flags & NETWORK_HAS_QOS_MASK)
		memcpy(&dst->qos_data, &src->qos_data,
		       sizeof(struct ieee80211_qos_data));
	else {
		dst->qos_data.supported = src->qos_data.supported;
		dst->qos_data.param_count = src->qos_data.param_count;
	}

	if (dst->qos_data.supported == 1) {
		if (dst->ssid_len)
			IEEE80211_DEBUG_QOS
			    ("QoS the network %s is QoS supported\n",
			     dst->ssid);
		else
			IEEE80211_DEBUG_QOS
			    ("QoS the network is QoS supported\n");
	}
	dst->qos_data.active = qos_active;
	dst->qos_data.old_param_count = old_param;

	/* dst->last_associate is not overwritten */
}

@@ -1167,6 +1460,9 @@ void ieee80211_rx_mgt(struct ieee80211_device *ieee,
		IEEE80211_DEBUG_MGMT("received ASSOCIATION RESPONSE (%d)\n",
				     WLAN_FC_GET_STYPE(le16_to_cpu
						       (header->frame_ctl)));
		ieee80211_handle_assoc_resp(ieee,
					    (struct ieee80211_assoc_response *)
					    header, stats);
		break;

	case IEEE80211_STYPE_REASSOC_RESP:
+3 −2
Original line number Diff line number Diff line
@@ -465,7 +465,7 @@ int ieee80211_xmit(struct sk_buff *skb, struct net_device *dev)
	dev_kfree_skb_any(skb);

	if (txb) {
		int ret = (*ieee->hard_start_xmit) (txb, dev);
		int ret = (*ieee->hard_start_xmit) (txb, dev, priority);
		if (ret == 0) {
			stats->tx_packets++;
			stats->tx_bytes += txb->payload_size;
@@ -500,6 +500,7 @@ int ieee80211_tx_frame(struct ieee80211_device *ieee,
	unsigned long flags;
	struct net_device_stats *stats = &ieee->stats;
	struct sk_buff *skb_frag;
	int priority = -1;

	spin_lock_irqsave(&ieee->lock, flags);

@@ -540,7 +541,7 @@ int ieee80211_tx_frame(struct ieee80211_device *ieee,
	spin_unlock_irqrestore(&ieee->lock, flags);

	if (txb) {
		if ((*ieee->hard_start_xmit) (txb, ieee->dev) == 0) {
		if ((*ieee->hard_start_xmit) (txb, ieee->dev, priority) == 0) {
			stats->tx_packets++;
			stats->tx_bytes += txb->payload_size;
			return 0;