summaryrefslogtreecommitdiffstats
path: root/net/mac80211/wme.c
blob: ac71b38f7cb5cb217180bb2cef3bcfa4b6540b40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/*
 * Copyright 2004, Instant802 Networks, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/module.h>
#include <linux/if_arp.h>
#include <linux/types.h>
#include <net/ip.h>
#include <net/pkt_sched.h>

#include <net/mac80211.h>
#include "ieee80211_i.h"
#include "wme.h"

/* Default mapping in classifier to work with default
 * queue setup.
 */
const int ieee802_1d_to_ac[8] = { 2, 3, 3, 2, 1, 1, 0, 0 };

static const char llc_ip_hdr[8] = {0xAA, 0xAA, 0x3, 0, 0, 0, 0x08, 0};

/* Given a data frame determine the 802.1p/1d tag to use.  */
static unsigned int classify_1d(struct sk_buff *skb)
{
	unsigned int dscp;

	/* skb->priority values from 256->263 are magic values to
	 * directly indicate a specific 802.1d priority.  This is used
	 * to allow 802.1d priority to be passed directly in from VLAN
	 * tags, etc.
	 */
	if (skb->priority >= 256 && skb->priority <= 263)
		return skb->priority - 256;

	switch (skb->protocol) {
	case htons(ETH_P_IP):
		dscp = ip_hdr(skb)->tos & 0xfc;
		break;

	default:
		return 0;
	}

	return dscp >> 5;
}


static int wme_downgrade_ac(struct sk_buff *skb)
{
	switch (skb->priority) {
	case 6:
	case 7:
		skb->priority = 5; /* VO -> VI */
		return 0;
	case 4:
	case 5:
		skb->priority = 3; /* VI -> BE */
		return 0;
	case 0:
	case 3:
		skb->priority = 2; /* BE -> BK */
		return 0;
	default:
		return -1;
	}
}


/* Indicate which queue to use.  */
static u16 classify80211(struct ieee80211_local *local, struct sk_buff *skb)
{
	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;

	if (!ieee80211_is_data(hdr->frame_control)) {
		/* management frames go on AC_VO queue, but are sent
		* without QoS control fields */
		return 0;
	}

	if (0 /* injected */) {
		/* use AC from radiotap */
	}

	if (!ieee80211_is_data_qos(hdr->frame_control)) {
		skb->priority = 0; /* required for correct WPA/11i MIC */
		return ieee802_1d_to_ac[skb->priority];
	}

	/* use the data classifier to determine what 802.1d tag the
	 * data frame has */
	skb->priority = classify_1d(skb);

	/* in case we are a client verify acm is not set for this ac */
	while (unlikely(local->wmm_acm & BIT(skb->priority))) {
		if (wme_downgrade_ac(skb)) {
			/* The old code would drop the packet in this
			 * case.
			 */
			return 0;
		}
	}

	/* look up which queue to use for frames with this 1d tag */
	return ieee802_1d_to_ac[skb->priority];
}

u16 ieee80211_select_queue(struct net_device *dev, struct sk_buff *skb)
{
	struct ieee80211_master_priv *mpriv = netdev_priv(dev);
	struct ieee80211_local *local = mpriv->local;
	struct ieee80211_hw *hw = &local->hw;
	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
	struct sta_info *sta;
	u16 queue;
	u8 tid;

	queue = classify80211(local, skb);
	if (unlikely(queue >= local->hw.queues))
		queue = local->hw.queues - 1;

	if (skb->requeue) {
		if (!hw->ampdu_queues)
			return queue;

		rcu_read_lock();
		sta = sta_info_get(local, hdr->addr1);
		tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
		if (sta) {
			int ampdu_queue = sta->tid_to_tx_q[tid];

			if ((ampdu_queue < ieee80211_num_queues(hw)) &&
			    test_bit(ampdu_queue, local->queue_pool))
				queue = ampdu_queue;
		}
		rcu_read_unlock();

		return queue;
	}

	/* Now we know the 1d priority, fill in the QoS header if
	 * there is one.
	 */
	if (ieee80211_is_data_qos(hdr->frame_control)) {
		u8 *p = ieee80211_get_qos_ctl(hdr);
		u8 ack_policy = 0;
		tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
		if (local->wifi_wme_noack_test)
			ack_policy |= QOS_CONTROL_ACK_POLICY_NOACK <<
					QOS_CONTROL_ACK_POLICY_SHIFT;
		/* qos header is 2 bytes, second reserved */
		*p++ = ack_policy | tid;
		*p = 0;

		if (!hw->ampdu_queues)
			return queue;

		rcu_read_lock();

		sta = sta_info_get(local, hdr->addr1);
		if (sta) {
			int ampdu_queue = sta->tid_to_tx_q[tid];

			if ((ampdu_queue < ieee80211_num_queues(hw)) &&
			    test_bit(ampdu_queue, local->queue_pool))
				queue = ampdu_queue;
		}

		rcu_read_unlock();
	}

	return queue;
}

int ieee80211_ht_agg_queue_add(struct ieee80211_local *local,
			       struct sta_info *sta, u16 tid)
{
	int i;

	/* XXX: currently broken due to cb/requeue use */
	return -EPERM;

	/* prepare the filter and save it for the SW queue
	 * matching the received HW queue */

	if (!local->hw.ampdu_queues)
		return -EPERM;

	/* try to get a Qdisc from the pool */
	for (i = local->hw.queues; i < ieee80211_num_queues(&local->hw); i++)
		if (!test_and_set_bit(i, local->queue_pool)) {
			ieee80211_stop_queue(local_to_hw(local), i);
			sta->tid_to_tx_q[tid] = i;

			/* IF there are already pending packets
			 * on this tid first we need to drain them
			 * on the previous queue
			 * since HT is strict in order */
#ifdef CONFIG_MAC80211_HT_DEBUG
			if (net_ratelimit())
				printk(KERN_DEBUG "allocated aggregation queue"
					" %d tid %d addr %pM pool=0x%lX\n",
					i, tid, sta->sta.addr,
					local->queue_pool[0]);
#endif /* CONFIG_MAC80211_HT_DEBUG */
			return 0;
		}

	return -EAGAIN;
}

/**
 * the caller needs to hold netdev_get_tx_queue(local->mdev, X)->lock
 */
void ieee80211_ht_agg_queue_remove(struct ieee80211_local *local,
				   struct sta_info *sta, u16 tid,
				   u8 requeue)
{
	int agg_queue = sta->tid_to_tx_q[tid];
	struct ieee80211_hw *hw = &local->hw;

	/* return the qdisc to the pool */
	clear_bit(agg_queue, local->queue_pool);
	sta->tid_to_tx_q[tid] = ieee80211_num_queues(hw);

	if (requeue) {
		ieee80211_requeue(local, agg_queue);
	} else {
		struct netdev_queue *txq;
		spinlock_t *root_lock;
		struct Qdisc *q;

		txq = netdev_get_tx_queue(local->mdev, agg_queue);
		q = rcu_dereference(txq->qdisc);
		root_lock = qdisc_lock(q);

		spin_lock_bh(root_lock);
		qdisc_reset(q);
		spin_unlock_bh(root_lock);
	}
}

void ieee80211_requeue(struct ieee80211_local *local, int queue)
{
	struct netdev_queue *txq = netdev_get_tx_queue(local->mdev, queue);
	struct sk_buff_head list;
	spinlock_t *root_lock;
	struct Qdisc *qdisc;
	u32 len;

	rcu_read_lock_bh();

	qdisc = rcu_dereference(txq->qdisc);
	if (!qdisc || !qdisc->dequeue)
		goto out_unlock;

	skb_queue_head_init(&list);

	root_lock = qdisc_root_lock(qdisc);
	spin_lock(root_lock);
	for (len = qdisc->q.qlen; len > 0; len--) {
		struct sk_buff *skb = qdisc->dequeue(qdisc);

		if (skb)
			__skb_queue_tail(&list, skb);
	}
	spin_unlock(root_lock);

	for (len = list.qlen; len > 0; len--) {
		struct sk_buff *skb = __skb_dequeue(&list);
		u16 new_queue;

		BUG_ON(!skb);
		new_queue = ieee80211_select_queue(local->mdev, skb);
		skb_set_queue_mapping(skb, new_queue);

		txq = netdev_get_tx_queue(local->mdev, new_queue);


		qdisc = rcu_dereference(txq->qdisc);
		root_lock = qdisc_root_lock(qdisc);

		spin_lock(root_lock);
		qdisc_enqueue_root(skb, qdisc);
		spin_unlock(root_lock);
	}

out_unlock:
	rcu_read_unlock_bh();
}