This introduces a debugfs file (mac80211_hwsim/phy#/ps) that can be used
to force a simulated radio into power save mode. Following value can be
written into this file to change PS mode:
0 = power save disabled (constantly awake)
1 = power save enabled (drop all frames; do not send PS-Poll)
2 = power save enabled (send PS-Poll frames automatically to receive
buffered unicast frames); not yet fully implemented
3 = manual PS-Poll trigger (send a single PS-Poll frame)
Two different behavior for power save mode processing can be tested:
- move between modes 1 and 0 (i.e., receive all buffered frames at a
time)
- move to mode 1 and use manual PS-Poll frames (write 3 to the 'ps'
debugfs file) to fetch power save buffered frames one at a time
Mode 2 (automatic PS-Poll) does not yet parse Beacon frames, but
eventually, it should take a look at TIM IE and send PS-Poll if a
traffic bit is set for our AID.
Signed-off-by: Jouni Malinen <[email protected]>
Index: wireless-testing/drivers/net/wireless/mac80211_hwsim.c
===================================================================
--- wireless-testing.orig/drivers/net/wireless/mac80211_hwsim.c 2008-10-30 16:26:49.000000000 +0200
+++ wireless-testing/drivers/net/wireless/mac80211_hwsim.c 2008-10-30 16:28:27.000000000 +0200
@@ -21,6 +21,7 @@
#include <linux/if_arp.h>
#include <linux/rtnetlink.h>
#include <linux/etherdevice.h>
+#include <linux/debugfs.h>
MODULE_AUTHOR("Jouni Malinen");
MODULE_DESCRIPTION("Software simulator of 802.11 radio(s) for mac80211");
@@ -30,8 +31,13 @@
module_param(radios, int, 0444);
MODULE_PARM_DESC(radios, "Number of simulated radios");
+static struct dentry *hwsim_debugfs;
+
struct hwsim_vif_priv {
u32 magic;
+ u8 bssid[ETH_ALEN];
+ bool assoc;
+ u16 aid;
};
#define HWSIM_VIF_MAGIC 0x69537748
@@ -132,6 +138,12 @@
unsigned int rx_filter;
int started;
struct timer_list beacon_timer;
+ enum ps_mode {
+ PS_DISABLED, PS_ENABLED, PS_AUTO_POLL, PS_MANUAL_POLL
+ } ps;
+ bool ps_poll_pending;
+ struct dentry *debugfs;
+ struct dentry *debugfs_ps;
};
@@ -196,6 +208,34 @@
}
+static bool hwsim_ps_rx_ok(struct mac80211_hwsim_data *data,
+ struct sk_buff *skb)
+{
+ switch (data->ps) {
+ case PS_DISABLED:
+ return true;
+ case PS_ENABLED:
+ return false;
+ case PS_AUTO_POLL:
+ /* TODO: accept (some) Beacons by default and other frames only
+ * if pending PS-Poll has been sent */
+ return true;
+ case PS_MANUAL_POLL:
+ /* Allow unicast frames to own address if there is a pending
+ * PS-Poll */
+ if (data->ps_poll_pending &&
+ memcmp(data->hw->wiphy->perm_addr, skb->data + 4,
+ ETH_ALEN) == 0) {
+ data->ps_poll_pending = false;
+ return true;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+
static bool mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
struct sk_buff *skb)
{
@@ -212,6 +252,9 @@
rx_status.rate_idx = info->control.rates[0].idx;
/* TODO: simulate signal strength (and optional packet drop) */
+ if (data->ps != PS_DISABLED)
+ hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM);
+
/* Copy skb to all enabled radios that are on the current frequency */
spin_lock(&hwsim_radio_lock);
list_for_each_entry(data2, &hwsim_radios, list) {
@@ -221,6 +264,7 @@
continue;
if (!data2->started || !data2->radio_enabled ||
+ !hwsim_ps_rx_ok(data2, skb) ||
data->channel->center_freq != data2->channel->center_freq)
continue;
@@ -406,7 +450,16 @@
struct ieee80211_vif *vif,
struct ieee80211_if_conf *conf)
{
+ struct hwsim_vif_priv *vp = (void *)vif->drv_priv;
+
hwsim_check_magic(vif);
+ if (conf->changed & IEEE80211_IFCC_BSSID) {
+ DECLARE_MAC_BUF(mac);
+ printk(KERN_DEBUG "%s:%s: BSSID changed: %s\n",
+ wiphy_name(hw->wiphy), __func__,
+ print_mac(mac, conf->bssid));
+ memcpy(vp->bssid, conf->bssid, ETH_ALEN);
+ }
return 0;
}
@@ -415,6 +468,8 @@
struct ieee80211_bss_conf *info,
u32 changed)
{
+ struct hwsim_vif_priv *vp = (void *)vif->drv_priv;
+
hwsim_check_magic(vif);
printk(KERN_DEBUG "%s:%s(changed=0x%x)\n",
@@ -423,6 +478,8 @@
if (changed & BSS_CHANGED_ASSOC) {
printk(KERN_DEBUG " %s: ASSOC: assoc=%d aid=%d\n",
wiphy_name(hw->wiphy), info->assoc, info->aid);
+ vp->assoc = info->assoc;
+ vp->aid = info->aid;
}
if (changed & BSS_CHANGED_ERP_CTS_PROT) {
@@ -520,11 +577,15 @@
spin_unlock_bh(&hwsim_radio_lock);
list_for_each_entry(data, &tmplist, list) {
+ debugfs_remove(data->debugfs_ps);
+ debugfs_remove(data->debugfs);
ieee80211_unregister_hw(data->hw);
device_unregister(data->dev);
ieee80211_free_hw(data->hw);
}
class_destroy(hwsim_class);
+
+ debugfs_remove(hwsim_debugfs);
}
@@ -545,6 +606,127 @@
}
+static void hwsim_send_ps_poll(void *dat, u8 *mac, struct ieee80211_vif *vif)
+{
+ struct mac80211_hwsim_data *data = dat;
+ struct hwsim_vif_priv *vp = (void *)vif->drv_priv;
+ DECLARE_MAC_BUF(buf);
+ struct sk_buff *skb;
+ struct ieee80211_pspoll *pspoll;
+
+ if (!vp->assoc)
+ return;
+
+ printk(KERN_DEBUG "%s:%s: send PS-Poll to %s for aid %d\n",
+ wiphy_name(data->hw->wiphy), __func__,
+ print_mac(buf, vp->bssid), vp->aid);
+
+ skb = dev_alloc_skb(sizeof(*pspoll));
+ if (!skb)
+ return;
+ pspoll = (void *) skb_put(skb, sizeof(*pspoll));
+ pspoll->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL |
+ IEEE80211_STYPE_PSPOLL |
+ IEEE80211_FCTL_PM);
+ pspoll->aid = cpu_to_le16(0xc000 | vp->aid);
+ memcpy(pspoll->bssid, vp->bssid, ETH_ALEN);
+ memcpy(pspoll->ta, mac, ETH_ALEN);
+ if (data->radio_enabled &&
+ !mac80211_hwsim_tx_frame(data->hw, skb))
+ printk(KERN_DEBUG "%s: PS-Poll frame not ack'ed\n", __func__);
+ dev_kfree_skb(skb);
+}
+
+
+static void hwsim_send_nullfunc(struct mac80211_hwsim_data *data, u8 *mac,
+ struct ieee80211_vif *vif, int ps)
+{
+ struct hwsim_vif_priv *vp = (void *)vif->drv_priv;
+ DECLARE_MAC_BUF(buf);
+ struct sk_buff *skb;
+ struct ieee80211_hdr *hdr;
+
+ if (!vp->assoc)
+ return;
+
+ printk(KERN_DEBUG "%s:%s: send data::nullfunc to %s ps=%d\n",
+ wiphy_name(data->hw->wiphy), __func__,
+ print_mac(buf, vp->bssid), ps);
+
+ skb = dev_alloc_skb(sizeof(*hdr));
+ if (!skb)
+ return;
+ hdr = (void *) skb_put(skb, sizeof(*hdr) - ETH_ALEN);
+ hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
+ IEEE80211_STYPE_NULLFUNC |
+ (ps ? IEEE80211_FCTL_PM : 0));
+ hdr->duration_id = cpu_to_le16(0);
+ memcpy(hdr->addr1, vp->bssid, ETH_ALEN);
+ memcpy(hdr->addr2, mac, ETH_ALEN);
+ memcpy(hdr->addr3, vp->bssid, ETH_ALEN);
+ if (data->radio_enabled &&
+ !mac80211_hwsim_tx_frame(data->hw, skb))
+ printk(KERN_DEBUG "%s: nullfunc frame not ack'ed\n", __func__);
+ dev_kfree_skb(skb);
+}
+
+
+static void hwsim_send_nullfunc_ps(void *dat, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mac80211_hwsim_data *data = dat;
+ hwsim_send_nullfunc(data, mac, vif, 1);
+}
+
+
+static void hwsim_send_nullfunc_no_ps(void *dat, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mac80211_hwsim_data *data = dat;
+ hwsim_send_nullfunc(data, mac, vif, 0);
+}
+
+
+static int hwsim_fops_ps_read(void *dat, u64 *val)
+{
+ struct mac80211_hwsim_data *data = dat;
+ *val = data->ps;
+ return 0;
+}
+
+static int hwsim_fops_ps_write(void *dat, u64 val)
+{
+ struct mac80211_hwsim_data *data = dat;
+ enum ps_mode old_ps;
+
+ if (val != PS_DISABLED && val != PS_ENABLED && val != PS_AUTO_POLL &&
+ val != PS_MANUAL_POLL)
+ return -EINVAL;
+
+ old_ps = data->ps;
+ data->ps = val;
+
+ if (val == PS_MANUAL_POLL) {
+ ieee80211_iterate_active_interfaces(data->hw,
+ hwsim_send_ps_poll, data);
+ data->ps_poll_pending = true;
+ } else if (data->ps == PS_DISABLED && val != PS_DISABLED) {
+ ieee80211_iterate_active_interfaces(data->hw,
+ hwsim_send_nullfunc_ps,
+ data);
+ } else if (data->ps != PS_DISABLED && val == PS_DISABLED) {
+ ieee80211_iterate_active_interfaces(data->hw,
+ hwsim_send_nullfunc_no_ps,
+ data);
+ }
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_ps, hwsim_fops_ps_read, hwsim_fops_ps_write,
+ "%llu\n");
+
+
static int __init init_mac80211_hwsim(void)
{
int i, err = 0;
@@ -563,6 +745,8 @@
if (IS_ERR(hwsim_class))
return PTR_ERR(hwsim_class);
+ hwsim_debugfs = debugfs_create_dir("mac80211_hwsim", NULL);
+
memset(addr, 0, ETH_ALEN);
addr[0] = 0x02;
@@ -637,6 +821,12 @@
wiphy_name(hw->wiphy),
print_mac(mac, hw->wiphy->perm_addr));
+ data->debugfs = debugfs_create_dir(wiphy_name(hw->wiphy),
+ hwsim_debugfs);
+ data->debugfs_ps = debugfs_create_file("ps", 0666,
+ data->debugfs, data,
+ &hwsim_fops_ps);
+
setup_timer(&data->beacon_timer, mac80211_hwsim_beacon,
(unsigned long) hw);
Index: wireless-testing/include/linux/ieee80211.h
===================================================================
--- wireless-testing.orig/include/linux/ieee80211.h 2008-10-30 16:26:01.000000000 +0200
+++ wireless-testing/include/linux/ieee80211.h 2008-10-30 16:27:57.000000000 +0200
@@ -666,6 +666,13 @@
u8 ra[6];
} __attribute__ ((packed));
+struct ieee80211_pspoll {
+ __le16 frame_control;
+ __le16 aid;
+ u8 bssid[6];
+ u8 ta[6];
+} __attribute__ ((packed));
+
/**
* struct ieee80211_bar - HT Block Ack Request
*
--
Jouni Malinen PGP id EFC895FA
On Thu, 2008-10-30 at 17:04 +0200, Jouni Malinen wrote:
> This introduces a debugfs file (mac80211_hwsim/phy#/ps) that can be
> used
Why not use the cfg80211-provided debugfs dir /ieee80211/phy#/ and maybe
put it below hwsim/ in there because mac80211 has things in there too?
That way you save all the code setting up the directory etc.
> Mode 2 (automatic PS-Poll) does not yet parse Beacon frames, but
> eventually, it should take a look at TIM IE and send PS-Poll if a
> traffic bit is set for our AID.
Out of curiosity, is there any hw that implements this? b43 surely would
need such code too, and then it could be in mac80211? I can see setting
the PM bit be a decision of the driver, but if more than one driver ends
up needing ps-poll, maybe mac80211 should be handling it?
johannes
On Thu, Oct 30, 2008 at 04:15:15PM +0100, Johannes Berg wrote:
> On Thu, 2008-10-30 at 17:04 +0200, Jouni Malinen wrote:
> > This introduces a debugfs file (mac80211_hwsim/phy#/ps) that can be
> > used
>
> Why not use the cfg80211-provided debugfs dir /ieee80211/phy#/ and maybe
> put it below hwsim/ in there because mac80211 has things in there too?
> That way you save all the code setting up the directory etc.
Good point.. I did actually find out about that some time ago, but did
not remember anymore. I'll move the files there.
> > Mode 2 (automatic PS-Poll) does not yet parse Beacon frames, but
> > eventually, it should take a look at TIM IE and send PS-Poll if a
> > traffic bit is set for our AID.
>
> Out of curiosity, is there any hw that implements this? b43 surely would
> need such code too, and then it could be in mac80211? I can see setting
> the PM bit be a decision of the driver, but if more than one driver ends
> up needing ps-poll, maybe mac80211 should be handling it?
Yes, or well, firmware more likely. Anyway, AP will have to support it
and the main purpose of this hwsim code is indeed to allow AP PS
buffering code to be tested. Anyway, should there be other drivers that
would like to do the same, this could of course be eventually moved to
mac80211. I'm just not sure whether I would expect this (Beacon parsing)
to happen in driver. Rather, I would expect the hardware to provide
helper here (i.e., process Beacon frames without indicating them to
host, generate an interrupt on Beacon frame that has TIM IE with our bit
set). Sending the PS-Poll could potentially be done in mac80211. I would
need to take a closer look at how this is done in modern wlan designs..
Then again, many vendors choose not to use PS-Poll at all even.
As far as setting FC PwrMgt bit is concerned, that could possible be in
mac80211. Though, there is some timing consideration and likely
potential race conditions that would need to be understood based on the
exact hardware design.
--
Jouni Malinen PGP id EFC895FA