diff options
author | Johannes Berg <johannes.berg@intel.com> | 2010-11-25 04:02:30 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2010-11-29 15:24:35 -0500 |
commit | f30221e4ec62d905b56d5e8f7ccab6b406a97cf5 (patch) | |
tree | 07d3a4cf6da15a3622dd79c1d72e0e5c9201566a /net/mac80211 | |
parent | f7ca38dfe58c20cb1aa2ed9643187e8b194b5bae (diff) |
mac80211: implement off-channel mgmt TX
This implements the new off-channel TX API
in mac80211 with a new work item type. The
operation doesn't add a new work item when
we're on the right channel and there's no
wait time so that for example p2p probe
responses will be transmitted without delay.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/mac80211')
-rw-r--r-- | net/mac80211/cfg.c | 93 | ||||
-rw-r--r-- | net/mac80211/ieee80211_i.h | 5 | ||||
-rw-r--r-- | net/mac80211/status.c | 15 | ||||
-rw-r--r-- | net/mac80211/work.c | 22 |
4 files changed, 129 insertions, 6 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index aac2d7de828e..db134b500caa 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c | |||
@@ -1551,6 +1551,28 @@ static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, | |||
1551 | return ieee80211_wk_cancel_remain_on_channel(sdata, cookie); | 1551 | return ieee80211_wk_cancel_remain_on_channel(sdata, cookie); |
1552 | } | 1552 | } |
1553 | 1553 | ||
1554 | static enum work_done_result | ||
1555 | ieee80211_offchan_tx_done(struct ieee80211_work *wk, struct sk_buff *skb) | ||
1556 | { | ||
1557 | /* | ||
1558 | * Use the data embedded in the work struct for reporting | ||
1559 | * here so if the driver mangled the SKB before dropping | ||
1560 | * it (which is the only way we really should get here) | ||
1561 | * then we don't report mangled data. | ||
1562 | * | ||
1563 | * If there was no wait time, then by the time we get here | ||
1564 | * the driver will likely not have reported the status yet, | ||
1565 | * so in that case userspace will have to deal with it. | ||
1566 | */ | ||
1567 | |||
1568 | if (wk->offchan_tx.wait && wk->offchan_tx.frame) | ||
1569 | cfg80211_mgmt_tx_status(wk->sdata->dev, | ||
1570 | (unsigned long) wk->offchan_tx.frame, | ||
1571 | wk->ie, wk->ie_len, false, GFP_KERNEL); | ||
1572 | |||
1573 | return WORK_DONE_DESTROY; | ||
1574 | } | ||
1575 | |||
1554 | static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, | 1576 | static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, |
1555 | struct ieee80211_channel *chan, bool offchan, | 1577 | struct ieee80211_channel *chan, bool offchan, |
1556 | enum nl80211_channel_type channel_type, | 1578 | enum nl80211_channel_type channel_type, |
@@ -1561,20 +1583,22 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, | |||
1561 | struct ieee80211_local *local = sdata->local; | 1583 | struct ieee80211_local *local = sdata->local; |
1562 | struct sk_buff *skb; | 1584 | struct sk_buff *skb; |
1563 | struct sta_info *sta; | 1585 | struct sta_info *sta; |
1586 | struct ieee80211_work *wk; | ||
1564 | const struct ieee80211_mgmt *mgmt = (void *)buf; | 1587 | const struct ieee80211_mgmt *mgmt = (void *)buf; |
1565 | u32 flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX | | 1588 | u32 flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX | |
1566 | IEEE80211_TX_CTL_REQ_TX_STATUS; | 1589 | IEEE80211_TX_CTL_REQ_TX_STATUS; |
1567 | 1590 | bool is_offchan = false; | |
1568 | if (offchan) | ||
1569 | return -EOPNOTSUPP; | ||
1570 | 1591 | ||
1571 | /* Check that we are on the requested channel for transmission */ | 1592 | /* Check that we are on the requested channel for transmission */ |
1572 | if (chan != local->tmp_channel && | 1593 | if (chan != local->tmp_channel && |
1573 | chan != local->oper_channel) | 1594 | chan != local->oper_channel) |
1574 | return -EBUSY; | 1595 | is_offchan = true; |
1575 | if (channel_type_valid && | 1596 | if (channel_type_valid && |
1576 | (channel_type != local->tmp_channel_type && | 1597 | (channel_type != local->tmp_channel_type && |
1577 | channel_type != local->_oper_channel_type)) | 1598 | channel_type != local->_oper_channel_type)) |
1599 | is_offchan = true; | ||
1600 | |||
1601 | if (is_offchan && !offchan) | ||
1578 | return -EBUSY; | 1602 | return -EBUSY; |
1579 | 1603 | ||
1580 | switch (sdata->vif.type) { | 1604 | switch (sdata->vif.type) { |
@@ -1608,12 +1632,70 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev, | |||
1608 | IEEE80211_SKB_CB(skb)->flags = flags; | 1632 | IEEE80211_SKB_CB(skb)->flags = flags; |
1609 | 1633 | ||
1610 | skb->dev = sdata->dev; | 1634 | skb->dev = sdata->dev; |
1611 | ieee80211_tx_skb(sdata, skb); | ||
1612 | 1635 | ||
1613 | *cookie = (unsigned long) skb; | 1636 | *cookie = (unsigned long) skb; |
1637 | |||
1638 | /* | ||
1639 | * Can transmit right away if the channel was the | ||
1640 | * right one and there's no wait involved... If a | ||
1641 | * wait is involved, we might otherwise not be on | ||
1642 | * the right channel for long enough! | ||
1643 | */ | ||
1644 | if (!is_offchan && !wait && !sdata->vif.bss_conf.idle) { | ||
1645 | ieee80211_tx_skb(sdata, skb); | ||
1646 | return 0; | ||
1647 | } | ||
1648 | |||
1649 | wk = kzalloc(sizeof(*wk) + len, GFP_KERNEL); | ||
1650 | if (!wk) { | ||
1651 | kfree_skb(skb); | ||
1652 | return -ENOMEM; | ||
1653 | } | ||
1654 | |||
1655 | wk->type = IEEE80211_WORK_OFFCHANNEL_TX; | ||
1656 | wk->chan = chan; | ||
1657 | wk->sdata = sdata; | ||
1658 | wk->done = ieee80211_offchan_tx_done; | ||
1659 | wk->offchan_tx.frame = skb; | ||
1660 | wk->offchan_tx.wait = wait; | ||
1661 | wk->ie_len = len; | ||
1662 | memcpy(wk->ie, buf, len); | ||
1663 | |||
1664 | ieee80211_add_work(wk); | ||
1614 | return 0; | 1665 | return 0; |
1615 | } | 1666 | } |
1616 | 1667 | ||
1668 | static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy, | ||
1669 | struct net_device *dev, | ||
1670 | u64 cookie) | ||
1671 | { | ||
1672 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | ||
1673 | struct ieee80211_local *local = sdata->local; | ||
1674 | struct ieee80211_work *wk; | ||
1675 | int ret = -ENOENT; | ||
1676 | |||
1677 | mutex_lock(&local->mtx); | ||
1678 | list_for_each_entry(wk, &local->work_list, list) { | ||
1679 | if (wk->sdata != sdata) | ||
1680 | continue; | ||
1681 | |||
1682 | if (wk->type != IEEE80211_WORK_OFFCHANNEL_TX) | ||
1683 | continue; | ||
1684 | |||
1685 | if (cookie != (unsigned long) wk->offchan_tx.frame) | ||
1686 | continue; | ||
1687 | |||
1688 | wk->timeout = jiffies; | ||
1689 | |||
1690 | ieee80211_queue_work(&local->hw, &local->work_work); | ||
1691 | ret = 0; | ||
1692 | break; | ||
1693 | } | ||
1694 | mutex_unlock(&local->mtx); | ||
1695 | |||
1696 | return ret; | ||
1697 | } | ||
1698 | |||
1617 | static void ieee80211_mgmt_frame_register(struct wiphy *wiphy, | 1699 | static void ieee80211_mgmt_frame_register(struct wiphy *wiphy, |
1618 | struct net_device *dev, | 1700 | struct net_device *dev, |
1619 | u16 frame_type, bool reg) | 1701 | u16 frame_type, bool reg) |
@@ -1698,6 +1780,7 @@ struct cfg80211_ops mac80211_config_ops = { | |||
1698 | .remain_on_channel = ieee80211_remain_on_channel, | 1780 | .remain_on_channel = ieee80211_remain_on_channel, |
1699 | .cancel_remain_on_channel = ieee80211_cancel_remain_on_channel, | 1781 | .cancel_remain_on_channel = ieee80211_cancel_remain_on_channel, |
1700 | .mgmt_tx = ieee80211_mgmt_tx, | 1782 | .mgmt_tx = ieee80211_mgmt_tx, |
1783 | .mgmt_tx_cancel_wait = ieee80211_mgmt_tx_cancel_wait, | ||
1701 | .set_cqm_rssi_config = ieee80211_set_cqm_rssi_config, | 1784 | .set_cqm_rssi_config = ieee80211_set_cqm_rssi_config, |
1702 | .mgmt_frame_register = ieee80211_mgmt_frame_register, | 1785 | .mgmt_frame_register = ieee80211_mgmt_frame_register, |
1703 | .set_antenna = ieee80211_set_antenna, | 1786 | .set_antenna = ieee80211_set_antenna, |
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 5bc0745368fe..66b0b52b828d 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -260,6 +260,7 @@ enum ieee80211_work_type { | |||
260 | IEEE80211_WORK_ASSOC_BEACON_WAIT, | 260 | IEEE80211_WORK_ASSOC_BEACON_WAIT, |
261 | IEEE80211_WORK_ASSOC, | 261 | IEEE80211_WORK_ASSOC, |
262 | IEEE80211_WORK_REMAIN_ON_CHANNEL, | 262 | IEEE80211_WORK_REMAIN_ON_CHANNEL, |
263 | IEEE80211_WORK_OFFCHANNEL_TX, | ||
263 | }; | 264 | }; |
264 | 265 | ||
265 | /** | 266 | /** |
@@ -320,6 +321,10 @@ struct ieee80211_work { | |||
320 | struct { | 321 | struct { |
321 | u32 duration; | 322 | u32 duration; |
322 | } remain; | 323 | } remain; |
324 | struct { | ||
325 | struct sk_buff *frame; | ||
326 | u32 wait; | ||
327 | } offchan_tx; | ||
323 | }; | 328 | }; |
324 | 329 | ||
325 | int ie_len; | 330 | int ie_len; |
diff --git a/net/mac80211/status.c b/net/mac80211/status.c index bed7e32ed908..4958710a7d92 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c | |||
@@ -321,10 +321,23 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb) | |||
321 | msecs_to_jiffies(10)); | 321 | msecs_to_jiffies(10)); |
322 | } | 322 | } |
323 | 323 | ||
324 | if (info->flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX) | 324 | if (info->flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX) { |
325 | struct ieee80211_work *wk; | ||
326 | |||
327 | rcu_read_lock(); | ||
328 | list_for_each_entry_rcu(wk, &local->work_list, list) { | ||
329 | if (wk->type != IEEE80211_WORK_OFFCHANNEL_TX) | ||
330 | continue; | ||
331 | if (wk->offchan_tx.frame != skb) | ||
332 | continue; | ||
333 | wk->offchan_tx.frame = NULL; | ||
334 | break; | ||
335 | } | ||
336 | rcu_read_unlock(); | ||
325 | cfg80211_mgmt_tx_status( | 337 | cfg80211_mgmt_tx_status( |
326 | skb->dev, (unsigned long) skb, skb->data, skb->len, | 338 | skb->dev, (unsigned long) skb, skb->data, skb->len, |
327 | !!(info->flags & IEEE80211_TX_STAT_ACK), GFP_ATOMIC); | 339 | !!(info->flags & IEEE80211_TX_STAT_ACK), GFP_ATOMIC); |
340 | } | ||
328 | 341 | ||
329 | /* this was a transmitted frame, but now we want to reuse it */ | 342 | /* this was a transmitted frame, but now we want to reuse it */ |
330 | skb_orphan(skb); | 343 | skb_orphan(skb); |
diff --git a/net/mac80211/work.c b/net/mac80211/work.c index ae344d1ba056..2b5c3f267198 100644 --- a/net/mac80211/work.c +++ b/net/mac80211/work.c | |||
@@ -561,6 +561,25 @@ ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk) | |||
561 | } | 561 | } |
562 | 562 | ||
563 | static enum work_action __must_check | 563 | static enum work_action __must_check |
564 | ieee80211_offchannel_tx(struct ieee80211_work *wk) | ||
565 | { | ||
566 | if (!wk->started) { | ||
567 | wk->timeout = jiffies + msecs_to_jiffies(wk->offchan_tx.wait); | ||
568 | |||
569 | /* | ||
570 | * After this, offchan_tx.frame remains but now is no | ||
571 | * longer a valid pointer -- we still need it as the | ||
572 | * cookie for canceling this work. | ||
573 | */ | ||
574 | ieee80211_tx_skb(wk->sdata, wk->offchan_tx.frame); | ||
575 | |||
576 | return WORK_ACT_NONE; | ||
577 | } | ||
578 | |||
579 | return WORK_ACT_TIMEOUT; | ||
580 | } | ||
581 | |||
582 | static enum work_action __must_check | ||
564 | ieee80211_assoc_beacon_wait(struct ieee80211_work *wk) | 583 | ieee80211_assoc_beacon_wait(struct ieee80211_work *wk) |
565 | { | 584 | { |
566 | if (wk->started) | 585 | if (wk->started) |
@@ -955,6 +974,9 @@ static void ieee80211_work_work(struct work_struct *work) | |||
955 | case IEEE80211_WORK_REMAIN_ON_CHANNEL: | 974 | case IEEE80211_WORK_REMAIN_ON_CHANNEL: |
956 | rma = ieee80211_remain_on_channel_timeout(wk); | 975 | rma = ieee80211_remain_on_channel_timeout(wk); |
957 | break; | 976 | break; |
977 | case IEEE80211_WORK_OFFCHANNEL_TX: | ||
978 | rma = ieee80211_offchannel_tx(wk); | ||
979 | break; | ||
958 | case IEEE80211_WORK_ASSOC_BEACON_WAIT: | 980 | case IEEE80211_WORK_ASSOC_BEACON_WAIT: |
959 | rma = ieee80211_assoc_beacon_wait(wk); | 981 | rma = ieee80211_assoc_beacon_wait(wk); |
960 | break; | 982 | break; |