Skip to content

Commit

Permalink
mac80211: add improved HW queue control
Browse files Browse the repository at this point in the history
mac80211 currently only supports one hardware queue
per AC. This is already problematic for off-channel
uses since if we go off channel while the BE queue
is full and then try to send an off-channel frame
the frame will never go out. This will become worse
when we support multi-channel since then a queue on
one channel might be full, but we have to stop the
software queue for all channels. That is obviously
not desirable.

To address this problem allow drivers to register
more hardware queues, and allow them to map them to
virtual interfaces. When they stop a hardware queue
the corresponding AC software queues on the correct
interfaces will be stopped as well. Additionally,
there's an off-channel queue to solve that problem
and a per-interface after-DTIM beacon queue. This
allows drivers to manage software queues closer to
how the hardware works.

Currently, there's a limit of 16 hardware queues.
This may or may not be sufficient, we can adjust it
as needed.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
  • Loading branch information
Johannes Berg authored and John W. Linville committed Apr 11, 2012
1 parent 4b6f1dd commit 3a25a8c
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 42 deletions.
81 changes: 77 additions & 4 deletions include/net/mac80211.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ struct device;
* @IEEE80211_MAX_QUEUES: Maximum number of regular device queues.
*/
enum ieee80211_max_queues {
IEEE80211_MAX_QUEUES = 4,
IEEE80211_MAX_QUEUES = 16,
};

#define IEEE80211_INVAL_HW_QUEUE 0xff

/**
* enum ieee80211_ac_numbers - AC numbers as used in mac80211
* @IEEE80211_AC_VO: voice
Expand Down Expand Up @@ -522,7 +524,7 @@ struct ieee80211_tx_rate {
*
* @flags: transmit info flags, defined above
* @band: the band to transmit on (use for checking for races)
* @reserved: reserved for future use
* @hw_queue: HW queue to put the frame on, skb_get_queue_mapping() gives the AC
* @ack_frame_id: internal frame ID for TX status, used internally
* @control: union for control data
* @status: union for status data
Expand All @@ -538,7 +540,7 @@ struct ieee80211_tx_info {
u32 flags;
u8 band;

u8 reserved;
u8 hw_queue;

u16 ack_frame_id;

Expand Down Expand Up @@ -889,6 +891,8 @@ enum ieee80211_vif_flags {
* these need to be set (or cleared) when the interface is added
* or, if supported by the driver, the interface type is changed
* at runtime, mac80211 will never touch this field
* @hw_queue: hardware queue for each AC
* @cab_queue: content-after-beacon (DTIM beacon really) queue, AP mode only
* @drv_priv: data area for driver use, will always be aligned to
* sizeof(void *).
*/
Expand All @@ -897,7 +901,12 @@ struct ieee80211_vif {
struct ieee80211_bss_conf bss_conf;
u8 addr[ETH_ALEN];
bool p2p;

u8 cab_queue;
u8 hw_queue[IEEE80211_NUM_ACS];

u32 driver_flags;

/* must be last */
u8 drv_priv[0] __attribute__((__aligned__(sizeof(void *))));
};
Expand Down Expand Up @@ -1179,6 +1188,11 @@ enum sta_notify_cmd {
* @IEEE80211_HW_WANT_MONITOR_VIF: The driver would like to be informed of
* a virtual monitor interface when monitor interfaces are the only
* active interfaces.
*
* @IEEE80211_HW_QUEUE_CONTROL: The driver wants to control per-interface
* queue mapping in order to use different queues (not just one per AC)
* for different virtual interfaces. See the doc section on HW queue
* control for more details.
*/
enum ieee80211_hw_flags {
IEEE80211_HW_HAS_RATE_CONTROL = 1<<0,
Expand All @@ -1201,7 +1215,7 @@ enum ieee80211_hw_flags {
IEEE80211_HW_SUPPORTS_UAPSD = 1<<17,
IEEE80211_HW_REPORTS_TX_ACK_STATUS = 1<<18,
IEEE80211_HW_CONNECTION_MONITOR = 1<<19,
/* reuse bit 20 */
IEEE80211_HW_QUEUE_CONTROL = 1<<20,
IEEE80211_HW_SUPPORTS_PER_STA_GTK = 1<<21,
IEEE80211_HW_AP_LINK_PS = 1<<22,
IEEE80211_HW_TX_AMPDU_SETUP_IN_HW = 1<<23,
Expand Down Expand Up @@ -1271,6 +1285,9 @@ enum ieee80211_hw_flags {
* @max_tx_aggregation_subframes: maximum number of subframes in an
* aggregate an HT driver will transmit, used by the peer as a
* hint to size its reorder buffer.
*
* @offchannel_tx_hw_queue: HW queue ID to use for offchannel TX
* (if %IEEE80211_HW_QUEUE_CONTROL is set)
*/
struct ieee80211_hw {
struct ieee80211_conf conf;
Expand All @@ -1291,6 +1308,7 @@ struct ieee80211_hw {
u8 max_rate_tries;
u8 max_rx_aggregation_subframes;
u8 max_tx_aggregation_subframes;
u8 offchannel_tx_hw_queue;
};

/**
Expand Down Expand Up @@ -1698,6 +1716,61 @@ void ieee80211_free_txskb(struct ieee80211_hw *hw, struct sk_buff *skb);
* The driver may also use ieee80211_sta_eosp_irqsafe() in this case.
*/

/**
* DOC: HW queue control
*
* Before HW queue control was introduced, mac80211 only had a single static
* assignment of per-interface AC software queues to hardware queues. This
* was problematic for a few reasons:
* 1) off-channel transmissions might get stuck behind other frames
* 2) multiple virtual interfaces couldn't be handled correctly
* 3) after-DTIM frames could get stuck behind other frames
*
* To solve this, hardware typically uses multiple different queues for all
* the different usages, and this needs to be propagated into mac80211 so it
* won't have the same problem with the software queues.
*
* Therefore, mac80211 now offers the %IEEE80211_HW_QUEUE_CONTROL capability
* flag that tells it that the driver implements its own queue control. To do
* so, the driver will set up the various queues in each &struct ieee80211_vif
* and the offchannel queue in &struct ieee80211_hw. In response, mac80211 will
* use those queue IDs in the hw_queue field of &struct ieee80211_tx_info and
* if necessary will queue the frame on the right software queue that mirrors
* the hardware queue.
* Additionally, the driver has to then use these HW queue IDs for the queue
* management functions (ieee80211_stop_queue() et al.)
*
* The driver is free to set up the queue mappings as needed, multiple virtual
* interfaces may map to the same hardware queues if needed. The setup has to
* happen during add_interface or change_interface callbacks. For example, a
* driver supporting station+station and station+AP modes might decide to have
* 10 hardware queues to handle different scenarios:
*
* 4 AC HW queues for 1st vif: 0, 1, 2, 3
* 4 AC HW queues for 2nd vif: 4, 5, 6, 7
* after-DTIM queue for AP: 8
* off-channel queue: 9
*
* It would then set up the hardware like this:
* hw.offchannel_tx_hw_queue = 9
*
* and the first virtual interface that is added as follows:
* vif.hw_queue[IEEE80211_AC_VO] = 0
* vif.hw_queue[IEEE80211_AC_VI] = 1
* vif.hw_queue[IEEE80211_AC_BE] = 2
* vif.hw_queue[IEEE80211_AC_BK] = 3
* vif.cab_queue = 8 // if AP mode, otherwise %IEEE80211_INVAL_HW_QUEUE
* and the second virtual interface with 4-7.
*
* If queue 6 gets full, for example, mac80211 would only stop the second
* virtual interface's BE queue since virtual interface queues are per AC.
*
* Note that the vif.cab_queue value should be set to %IEEE80211_INVAL_HW_QUEUE
* whenever the queue is not used (i.e. the interface is not in AP mode) if the
* queue could potentially be shared since mac80211 will look at cab_queue when
* a queue is stopped/woken even if the interface is not in AP mode.
*/

/**
* enum ieee80211_filter_flags - hardware filter flags
*
Expand Down
39 changes: 20 additions & 19 deletions net/mac80211/agg-tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,25 +286,25 @@ static inline int ieee80211_ac_from_tid(int tid)
* a global "agg_queue_stop" refcount.
*/
static void __acquires(agg_queue)
ieee80211_stop_queue_agg(struct ieee80211_local *local, int tid)
ieee80211_stop_queue_agg(struct ieee80211_sub_if_data *sdata, int tid)
{
int queue = ieee80211_ac_from_tid(tid);
int queue = sdata->vif.hw_queue[ieee80211_ac_from_tid(tid)];

if (atomic_inc_return(&local->agg_queue_stop[queue]) == 1)
if (atomic_inc_return(&sdata->local->agg_queue_stop[queue]) == 1)
ieee80211_stop_queue_by_reason(
&local->hw, queue,
&sdata->local->hw, queue,
IEEE80211_QUEUE_STOP_REASON_AGGREGATION);
__acquire(agg_queue);
}

static void __releases(agg_queue)
ieee80211_wake_queue_agg(struct ieee80211_local *local, int tid)
ieee80211_wake_queue_agg(struct ieee80211_sub_if_data *sdata, int tid)
{
int queue = ieee80211_ac_from_tid(tid);
int queue = sdata->vif.hw_queue[ieee80211_ac_from_tid(tid)];

if (atomic_dec_return(&local->agg_queue_stop[queue]) == 0)
if (atomic_dec_return(&sdata->local->agg_queue_stop[queue]) == 0)
ieee80211_wake_queue_by_reason(
&local->hw, queue,
&sdata->local->hw, queue,
IEEE80211_QUEUE_STOP_REASON_AGGREGATION);
__release(agg_queue);
}
Expand All @@ -314,13 +314,14 @@ ieee80211_wake_queue_agg(struct ieee80211_local *local, int tid)
* requires a call to ieee80211_agg_splice_finish later
*/
static void __acquires(agg_queue)
ieee80211_agg_splice_packets(struct ieee80211_local *local,
ieee80211_agg_splice_packets(struct ieee80211_sub_if_data *sdata,
struct tid_ampdu_tx *tid_tx, u16 tid)
{
int queue = ieee80211_ac_from_tid(tid);
struct ieee80211_local *local = sdata->local;
int queue = sdata->vif.hw_queue[ieee80211_ac_from_tid(tid)];
unsigned long flags;

ieee80211_stop_queue_agg(local, tid);
ieee80211_stop_queue_agg(sdata, tid);

if (WARN(!tid_tx, "TID %d gone but expected when splicing aggregates"
" from the pending queue\n", tid))
Expand All @@ -336,9 +337,9 @@ ieee80211_agg_splice_packets(struct ieee80211_local *local,
}

static void __releases(agg_queue)
ieee80211_agg_splice_finish(struct ieee80211_local *local, u16 tid)
ieee80211_agg_splice_finish(struct ieee80211_sub_if_data *sdata, u16 tid)
{
ieee80211_wake_queue_agg(local, tid);
ieee80211_wake_queue_agg(sdata, tid);
}

void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
Expand Down Expand Up @@ -376,9 +377,9 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
" tid %d\n", tid);
#endif
spin_lock_bh(&sta->lock);
ieee80211_agg_splice_packets(local, tid_tx, tid);
ieee80211_agg_splice_packets(sdata, tid_tx, tid);
ieee80211_assign_tid_tx(sta, tid, NULL);
ieee80211_agg_splice_finish(local, tid);
ieee80211_agg_splice_finish(sdata, tid);
spin_unlock_bh(&sta->lock);

kfree_rcu(tid_tx, rcu_head);
Expand Down Expand Up @@ -598,14 +599,14 @@ static void ieee80211_agg_tx_operational(struct ieee80211_local *local,
*/
spin_lock_bh(&sta->lock);

ieee80211_agg_splice_packets(local, tid_tx, tid);
ieee80211_agg_splice_packets(sta->sdata, tid_tx, tid);
/*
* Now mark as operational. This will be visible
* in the TX path, and lets it go lock-free in
* the common case.
*/
set_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state);
ieee80211_agg_splice_finish(local, tid);
ieee80211_agg_splice_finish(sta->sdata, tid);

spin_unlock_bh(&sta->lock);
}
Expand Down Expand Up @@ -790,12 +791,12 @@ void ieee80211_stop_tx_ba_cb(struct ieee80211_vif *vif, u8 *ra, u8 tid)
* more.
*/

ieee80211_agg_splice_packets(local, tid_tx, tid);
ieee80211_agg_splice_packets(sta->sdata, tid_tx, tid);

/* future packets must not find the tid_tx struct any more */
ieee80211_assign_tid_tx(sta, tid, NULL);

ieee80211_agg_splice_finish(local, tid);
ieee80211_agg_splice_finish(sta->sdata, tid);

kfree_rcu(tid_tx, rcu_head);

Expand Down
6 changes: 6 additions & 0 deletions net/mac80211/cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,10 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,

IEEE80211_SKB_CB(skb)->flags = flags;

if (flags & IEEE80211_TX_CTL_TX_OFFCHAN)
IEEE80211_SKB_CB(skb)->hw_queue =
local->hw.offchannel_tx_hw_queue;

skb->dev = sdata->dev;

*cookie = (unsigned long) skb;
Expand Down Expand Up @@ -2147,6 +2151,8 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
/* modify cookie to prevent API mismatches */
*cookie ^= 2;
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN;
IEEE80211_SKB_CB(skb)->hw_queue =
local->hw.offchannel_tx_hw_queue;
local->hw_roc_skb = skb;
local->hw_roc_skb_for_status = skb;
mutex_unlock(&local->mtx);
Expand Down
1 change: 1 addition & 0 deletions net/mac80211/ieee80211_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,7 @@ void ieee80211_wake_queue_by_reason(struct ieee80211_hw *hw, int queue,
enum queue_stop_reason reason);
void ieee80211_stop_queue_by_reason(struct ieee80211_hw *hw, int queue,
enum queue_stop_reason reason);
void ieee80211_propagate_queue_wake(struct ieee80211_local *local, int queue);
void ieee80211_add_pending_skb(struct ieee80211_local *local,
struct sk_buff *skb);
void ieee80211_add_pending_skbs_fn(struct ieee80211_local *local,
Expand Down
Loading

0 comments on commit 3a25a8c

Please sign in to comment.