]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/net/wireless/mac80211_hwsim.c
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/kaber/nf-next-2.6
[linux-2.6-omap-h63xx.git] / drivers / net / wireless / mac80211_hwsim.c
index 1a019e98dac3ee8ef0b7be504a6cee8af6a36369..2368b7f825a233f26f3b1406474f836bdb74208d 100644 (file)
@@ -10,7 +10,6 @@
 /*
  * TODO:
  * - IBSS mode simulation (Beacon transmission with competition for "air time")
- * - IEEE 802.11a and 802.11n modes
  * - RX filtering based on filter configuration (data->rx_filter)
  */
 
@@ -21,6 +20,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 +30,117 @@ static int radios = 2;
 module_param(radios, int, 0444);
 MODULE_PARM_DESC(radios, "Number of simulated radios");
 
+/**
+ * enum hwsim_regtest - the type of regulatory tests we offer
+ *
+ * These are the different values you can use for the regtest
+ * module parameter. This is useful to help test world roaming
+ * and the driver regulatory_hint() call and combinations of these.
+ * If you want to do specific alpha2 regulatory domain tests simply
+ * use the userspace regulatory request as that will be respected as
+ * well without the need of this module parameter. This is designed
+ * only for testing the driver regulatory request, world roaming
+ * and all possible combinations.
+ *
+ * @HWSIM_REGTEST_DISABLED: No regulatory tests are performed,
+ *     this is the default value.
+ * @HWSIM_REGTEST_DRIVER_REG_FOLLOW: Used for testing the driver regulatory
+ *     hint, only one driver regulatory hint will be sent as such the
+ *     secondary radios are expected to follow.
+ * @HWSIM_REGTEST_DRIVER_REG_ALL: Used for testing the driver regulatory
+ *     request with all radios reporting the same regulatory domain.
+ * @HWSIM_REGTEST_DIFF_COUNTRY: Used for testing the drivers calling
+ *     different regulatory domains requests. Expected behaviour is for
+ *     an intersection to occur but each device will still use their
+ *     respective regulatory requested domains. Subsequent radios will
+ *     use the resulting intersection.
+ * @HWSIM_REGTEST_WORLD_ROAM: Used for testing the world roaming. We acomplish
+ *     this by using a custom beacon-capable regulatory domain for the first
+ *     radio. All other device world roam.
+ * @HWSIM_REGTEST_CUSTOM_WORLD: Used for testing the custom world regulatory
+ *     domain requests. All radios will adhere to this custom world regulatory
+ *     domain.
+ * @HWSIM_REGTEST_CUSTOM_WORLD_2: Used for testing 2 custom world regulatory
+ *     domain requests. The first radio will adhere to the first custom world
+ *     regulatory domain, the second one to the second custom world regulatory
+ *     domain. All other devices will world roam.
+ * @HWSIM_REGTEST_STRICT_FOLLOW_: Used for testing strict regulatory domain
+ *     settings, only the first radio will send a regulatory domain request
+ *     and use strict settings. The rest of the radios are expected to follow.
+ * @HWSIM_REGTEST_STRICT_ALL: Used for testing strict regulatory domain
+ *     settings. All radios will adhere to this.
+ * @HWSIM_REGTEST_STRICT_AND_DRIVER_REG: Used for testing strict regulatory
+ *     domain settings, combined with secondary driver regulatory domain
+ *     settings. The first radio will get a strict regulatory domain setting
+ *     using the first driver regulatory request and the second radio will use
+ *     non-strict settings using the second driver regulatory request. All
+ *     other devices should follow the intersection created between the
+ *     first two.
+ * @HWSIM_REGTEST_ALL: Used for testing every possible mix. You will need
+ *     at least 6 radios for a complete test. We will test in this order:
+ *     1 - driver custom world regulatory domain
+ *     2 - second custom world regulatory domain
+ *     3 - first driver regulatory domain request
+ *     4 - second driver regulatory domain request
+ *     5 - strict regulatory domain settings using the third driver regulatory
+ *         domain request
+ *     6 and on - should follow the intersection of the 3rd, 4rth and 5th radio
+ *                regulatory requests.
+ */
+enum hwsim_regtest {
+       HWSIM_REGTEST_DISABLED = 0,
+       HWSIM_REGTEST_DRIVER_REG_FOLLOW = 1,
+       HWSIM_REGTEST_DRIVER_REG_ALL = 2,
+       HWSIM_REGTEST_DIFF_COUNTRY = 3,
+       HWSIM_REGTEST_WORLD_ROAM = 4,
+       HWSIM_REGTEST_CUSTOM_WORLD = 5,
+       HWSIM_REGTEST_CUSTOM_WORLD_2 = 6,
+       HWSIM_REGTEST_STRICT_FOLLOW = 7,
+       HWSIM_REGTEST_STRICT_ALL = 8,
+       HWSIM_REGTEST_STRICT_AND_DRIVER_REG = 9,
+       HWSIM_REGTEST_ALL = 10,
+};
+
+/* Set to one of the HWSIM_REGTEST_* values above */
+static int regtest = HWSIM_REGTEST_DISABLED;
+module_param(regtest, int, 0444);
+MODULE_PARM_DESC(regtest, "The type of regulatory test we want to run");
+
+static const char *hwsim_alpha2s[] = {
+       "FI",
+       "AL",
+       "US",
+       "DE",
+       "JP",
+       "AL",
+};
+
+static const struct ieee80211_regdomain hwsim_world_regdom_custom_01 = {
+       .n_reg_rules = 4,
+       .alpha2 =  "99",
+       .reg_rules = {
+               REG_RULE(2412-10, 2462+10, 40, 0, 20, 0),
+               REG_RULE(2484-10, 2484+10, 40, 0, 20, 0),
+               REG_RULE(5150-10, 5240+10, 40, 0, 30, 0),
+               REG_RULE(5745-10, 5825+10, 40, 0, 30, 0),
+       }
+};
+
+static const struct ieee80211_regdomain hwsim_world_regdom_custom_02 = {
+       .n_reg_rules = 2,
+       .alpha2 =  "99",
+       .reg_rules = {
+               REG_RULE(2412-10, 2462+10, 40, 0, 20, 0),
+               REG_RULE(5725-10, 5850+10, 40, 0, 30,
+                       NL80211_RRF_PASSIVE_SCAN | NL80211_RRF_NO_IBSS),
+       }
+};
+
 struct hwsim_vif_priv {
        u32 magic;
+       u8 bssid[ETH_ALEN];
+       bool assoc;
+       u16 aid;
 };
 
 #define HWSIM_VIF_MAGIC        0x69537748
@@ -63,13 +172,13 @@ struct hwsim_sta_priv {
 static inline void hwsim_check_sta_magic(struct ieee80211_sta *sta)
 {
        struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
-       WARN_ON(sp->magic != HWSIM_VIF_MAGIC);
+       WARN_ON(sp->magic != HWSIM_STA_MAGIC);
 }
 
 static inline void hwsim_set_sta_magic(struct ieee80211_sta *sta)
 {
        struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
-       sp->magic = HWSIM_VIF_MAGIC;
+       sp->magic = HWSIM_STA_MAGIC;
 }
 
 static inline void hwsim_clear_sta_magic(struct ieee80211_sta *sta)
@@ -82,22 +191,65 @@ static struct class *hwsim_class;
 
 static struct net_device *hwsim_mon; /* global monitor netdev */
 
+#define CHAN2G(_freq)  { \
+       .band = IEEE80211_BAND_2GHZ, \
+       .center_freq = (_freq), \
+       .hw_value = (_freq), \
+       .max_power = 20, \
+}
 
-static const struct ieee80211_channel hwsim_channels[] = {
-       { .center_freq = 2412 },
-       { .center_freq = 2417 },
-       { .center_freq = 2422 },
-       { .center_freq = 2427 },
-       { .center_freq = 2432 },
-       { .center_freq = 2437 },
-       { .center_freq = 2442 },
-       { .center_freq = 2447 },
-       { .center_freq = 2452 },
-       { .center_freq = 2457 },
-       { .center_freq = 2462 },
-       { .center_freq = 2467 },
-       { .center_freq = 2472 },
-       { .center_freq = 2484 },
+#define CHAN5G(_freq) { \
+       .band = IEEE80211_BAND_5GHZ, \
+       .center_freq = (_freq), \
+       .hw_value = (_freq), \
+       .max_power = 20, \
+}
+
+static const struct ieee80211_channel hwsim_channels_2ghz[] = {
+       CHAN2G(2412), /* Channel 1 */
+       CHAN2G(2417), /* Channel 2 */
+       CHAN2G(2422), /* Channel 3 */
+       CHAN2G(2427), /* Channel 4 */
+       CHAN2G(2432), /* Channel 5 */
+       CHAN2G(2437), /* Channel 6 */
+       CHAN2G(2442), /* Channel 7 */
+       CHAN2G(2447), /* Channel 8 */
+       CHAN2G(2452), /* Channel 9 */
+       CHAN2G(2457), /* Channel 10 */
+       CHAN2G(2462), /* Channel 11 */
+       CHAN2G(2467), /* Channel 12 */
+       CHAN2G(2472), /* Channel 13 */
+       CHAN2G(2484), /* Channel 14 */
+};
+
+static const struct ieee80211_channel hwsim_channels_5ghz[] = {
+       CHAN5G(5180), /* Channel 36 */
+       CHAN5G(5200), /* Channel 40 */
+       CHAN5G(5220), /* Channel 44 */
+       CHAN5G(5240), /* Channel 48 */
+
+       CHAN5G(5260), /* Channel 52 */
+       CHAN5G(5280), /* Channel 56 */
+       CHAN5G(5300), /* Channel 60 */
+       CHAN5G(5320), /* Channel 64 */
+
+       CHAN5G(5500), /* Channel 100 */
+       CHAN5G(5520), /* Channel 104 */
+       CHAN5G(5540), /* Channel 108 */
+       CHAN5G(5560), /* Channel 112 */
+       CHAN5G(5580), /* Channel 116 */
+       CHAN5G(5600), /* Channel 120 */
+       CHAN5G(5620), /* Channel 124 */
+       CHAN5G(5640), /* Channel 128 */
+       CHAN5G(5660), /* Channel 132 */
+       CHAN5G(5680), /* Channel 136 */
+       CHAN5G(5700), /* Channel 140 */
+
+       CHAN5G(5745), /* Channel 149 */
+       CHAN5G(5765), /* Channel 153 */
+       CHAN5G(5785), /* Channel 157 */
+       CHAN5G(5805), /* Channel 161 */
+       CHAN5G(5825), /* Channel 165 */
 };
 
 static const struct ieee80211_rate hwsim_rates[] = {
@@ -122,8 +274,9 @@ struct mac80211_hwsim_data {
        struct list_head list;
        struct ieee80211_hw *hw;
        struct device *dev;
-       struct ieee80211_supported_band band;
-       struct ieee80211_channel channels[ARRAY_SIZE(hwsim_channels)];
+       struct ieee80211_supported_band bands[2];
+       struct ieee80211_channel channels_2ghz[ARRAY_SIZE(hwsim_channels_2ghz)];
+       struct ieee80211_channel channels_5ghz[ARRAY_SIZE(hwsim_channels_5ghz)];
        struct ieee80211_rate rates[ARRAY_SIZE(hwsim_rates)];
 
        struct ieee80211_channel *channel;
@@ -132,6 +285,12 @@ struct mac80211_hwsim_data {
        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 +355,34 @@ static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw,
 }
 
 
+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)
 {
@@ -209,9 +396,12 @@ static bool mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
        /* TODO: set mactime */
        rx_status.freq = data->channel->center_freq;
        rx_status.band = data->channel->band;
-       rx_status.rate_idx = info->tx_rate_idx;
+       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 +411,7 @@ static bool mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
                        continue;
 
                if (!data2->started || !data2->radio_enabled ||
+                   !hwsim_ps_rx_ok(data2, skb) ||
                    data->channel->center_freq != data2->channel->center_freq)
                        continue;
 
@@ -269,13 +460,9 @@ static int mac80211_hwsim_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
        if (txi->control.sta)
                hwsim_check_sta_magic(txi->control.sta);
 
-       memset(&txi->status, 0, sizeof(txi->status));
-       if (!(txi->flags & IEEE80211_TX_CTL_NO_ACK)) {
-               if (ack)
-                       txi->flags |= IEEE80211_TX_STAT_ACK;
-               else
-                       txi->status.excessive_retries = 1;
-       }
+       ieee80211_tx_info_clear_status(txi);
+       if (!(txi->flags & IEEE80211_TX_CTL_NO_ACK) && ack)
+               txi->flags |= IEEE80211_TX_STAT_ACK;
        ieee80211_tx_status_irqsafe(hw, skb);
        return NETDEV_TX_OK;
 }
@@ -294,6 +481,7 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw)
 {
        struct mac80211_hwsim_data *data = hw->priv;
        data->started = 0;
+       del_timer(&data->beacon_timer);
        printk(KERN_DEBUG "%s:%s\n", wiphy_name(hw->wiphy), __func__);
 }
 
@@ -301,10 +489,9 @@ static void mac80211_hwsim_stop(struct ieee80211_hw *hw)
 static int mac80211_hwsim_add_interface(struct ieee80211_hw *hw,
                                        struct ieee80211_if_init_conf *conf)
 {
-       DECLARE_MAC_BUF(mac);
-       printk(KERN_DEBUG "%s:%s (type=%d mac_addr=%s)\n",
+       printk(KERN_DEBUG "%s:%s (type=%d mac_addr=%pM)\n",
               wiphy_name(hw->wiphy), __func__, conf->type,
-              print_mac(mac, conf->mac_addr));
+              conf->mac_addr);
        hwsim_set_magic(conf->vif);
        return 0;
 }
@@ -313,10 +500,9 @@ static int mac80211_hwsim_add_interface(struct ieee80211_hw *hw,
 static void mac80211_hwsim_remove_interface(
        struct ieee80211_hw *hw, struct ieee80211_if_init_conf *conf)
 {
-       DECLARE_MAC_BUF(mac);
-       printk(KERN_DEBUG "%s:%s (type=%d mac_addr=%s)\n",
+       printk(KERN_DEBUG "%s:%s (type=%d mac_addr=%pM)\n",
               wiphy_name(hw->wiphy), __func__, conf->type,
-              print_mac(mac, conf->mac_addr));
+              conf->mac_addr);
        hwsim_check_magic(conf->vif);
        hwsim_clear_magic(conf->vif);
 }
@@ -331,7 +517,8 @@ static void mac80211_hwsim_beacon_tx(void *arg, u8 *mac,
 
        hwsim_check_magic(vif);
 
-       if (vif->type != NL80211_IFTYPE_AP)
+       if (vif->type != NL80211_IFTYPE_AP &&
+           vif->type != NL80211_IFTYPE_MESH_POINT)
                return;
 
        skb = ieee80211_beacon_get(hw, vif);
@@ -361,10 +548,10 @@ static void mac80211_hwsim_beacon(unsigned long arg)
 }
 
 
-static int mac80211_hwsim_config(struct ieee80211_hw *hw,
-                                struct ieee80211_conf *conf)
+static int mac80211_hwsim_config(struct ieee80211_hw *hw, u32 changed)
 {
        struct mac80211_hwsim_data *data = hw->priv;
+       struct ieee80211_conf *conf = &hw->conf;
 
        printk(KERN_DEBUG "%s:%s (freq=%d radio_enabled=%d beacon_int=%d)\n",
               wiphy_name(hw->wiphy), __func__,
@@ -409,7 +596,16 @@ static int mac80211_hwsim_config_interface(struct ieee80211_hw *hw,
                                           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: %pM\n",
+                      wiphy_name(hw->wiphy), __func__,
+                      conf->bssid);
+               memcpy(vp->bssid, conf->bssid, ETH_ALEN);
+       }
        return 0;
 }
 
@@ -418,7 +614,46 @@ static void mac80211_hwsim_bss_info_changed(struct ieee80211_hw *hw,
                                            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",
+              wiphy_name(hw->wiphy), __func__, changed);
+
+       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) {
+               printk(KERN_DEBUG "  %s: ERP_CTS_PROT: %d\n",
+                      wiphy_name(hw->wiphy), info->use_cts_prot);
+       }
+
+       if (changed & BSS_CHANGED_ERP_PREAMBLE) {
+               printk(KERN_DEBUG "  %s: ERP_PREAMBLE: %d\n",
+                      wiphy_name(hw->wiphy), info->use_short_preamble);
+       }
+
+       if (changed & BSS_CHANGED_ERP_SLOT) {
+               printk(KERN_DEBUG "  %s: ERP_SLOT: %d\n",
+                      wiphy_name(hw->wiphy), info->use_short_slot);
+       }
+
+       if (changed & BSS_CHANGED_HT) {
+               printk(KERN_DEBUG "  %s: HT: op_mode=0x%x\n",
+                      wiphy_name(hw->wiphy),
+                      info->ht.operation_mode);
+       }
+
+       if (changed & BSS_CHANGED_BASIC_RATES) {
+               printk(KERN_DEBUG "  %s: BASIC_RATES: 0x%llx\n",
+                      wiphy_name(hw->wiphy),
+                      (unsigned long long) info->basic_rates);
+       }
 }
 
 static void mac80211_hwsim_sta_notify(struct ieee80211_hw *hw,
@@ -434,6 +669,10 @@ static void mac80211_hwsim_sta_notify(struct ieee80211_hw *hw,
        case STA_NOTIFY_REMOVE:
                hwsim_clear_sta_magic(sta);
                break;
+       case STA_NOTIFY_SLEEP:
+       case STA_NOTIFY_AWAKE:
+               /* TODO: make good use of these flags */
+               break;
        }
 }
 
@@ -445,6 +684,17 @@ static int mac80211_hwsim_set_tim(struct ieee80211_hw *hw,
        return 0;
 }
 
+static int mac80211_hwsim_conf_tx(
+       struct ieee80211_hw *hw, u16 queue,
+       const struct ieee80211_tx_queue_params *params)
+{
+       printk(KERN_DEBUG "%s:%s (queue=%d txop=%d cw_min=%d cw_max=%d "
+              "aifs=%d)\n",
+              wiphy_name(hw->wiphy), __func__, queue,
+              params->txop, params->cw_min, params->cw_max, params->aifs);
+       return 0;
+}
+
 static const struct ieee80211_ops mac80211_hwsim_ops =
 {
        .tx = mac80211_hwsim_tx,
@@ -458,6 +708,7 @@ static const struct ieee80211_ops mac80211_hwsim_ops =
        .bss_info_changed = mac80211_hwsim_bss_info_changed,
        .sta_notify = mac80211_hwsim_sta_notify,
        .set_tim = mac80211_hwsim_set_tim,
+       .conf_tx = mac80211_hwsim_conf_tx,
 };
 
 
@@ -474,6 +725,8 @@ static void mac80211_hwsim_free(void)
        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);
@@ -486,10 +739,16 @@ static struct device_driver mac80211_hwsim_driver = {
        .name = "mac80211_hwsim"
 };
 
+static const struct net_device_ops hwsim_netdev_ops = {
+       .ndo_start_xmit         = hwsim_mon_xmit,
+       .ndo_change_mtu         = eth_change_mtu,
+       .ndo_set_mac_address    = eth_mac_addr,
+       .ndo_validate_addr      = eth_validate_addr,
+};
 
 static void hwsim_mon_setup(struct net_device *dev)
 {
-       dev->hard_start_xmit = hwsim_mon_xmit;
+       dev->netdev_ops = &hwsim_netdev_ops;
        dev->destructor = free_netdev;
        ether_setup(dev);
        dev->tx_queue_len = 0;
@@ -499,13 +758,132 @@ static void hwsim_mon_setup(struct net_device *dev)
 }
 
 
+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 %pM for aid %d\n",
+              wiphy_name(data->hw->wiphy), __func__, 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 %pM ps=%d\n",
+              wiphy_name(data->hw->wiphy), __func__, 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 (old_ps == PS_DISABLED && val != PS_DISABLED) {
+               ieee80211_iterate_active_interfaces(data->hw,
+                                                   hwsim_send_nullfunc_ps,
+                                                   data);
+       } else if (old_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;
        u8 addr[ETH_ALEN];
        struct mac80211_hwsim_data *data;
        struct ieee80211_hw *hw;
-       DECLARE_MAC_BUF(mac);
+       enum ieee80211_band band;
 
        if (radios < 1 || radios > 100)
                return -EINVAL;
@@ -553,33 +931,115 @@ static int __init init_mac80211_hwsim(void)
                hw->queues = 4;
                hw->wiphy->interface_modes =
                        BIT(NL80211_IFTYPE_STATION) |
-                       BIT(NL80211_IFTYPE_AP);
+                       BIT(NL80211_IFTYPE_AP) |
+                       BIT(NL80211_IFTYPE_MESH_POINT);
                hw->ampdu_queues = 1;
 
+               hw->flags = IEEE80211_HW_MFP_CAPABLE;
+
                /* ask mac80211 to reserve space for magic */
                hw->vif_data_size = sizeof(struct hwsim_vif_priv);
                hw->sta_data_size = sizeof(struct hwsim_sta_priv);
 
-               memcpy(data->channels, hwsim_channels, sizeof(hwsim_channels));
+               memcpy(data->channels_2ghz, hwsim_channels_2ghz,
+                       sizeof(hwsim_channels_2ghz));
+               memcpy(data->channels_5ghz, hwsim_channels_5ghz,
+                       sizeof(hwsim_channels_5ghz));
                memcpy(data->rates, hwsim_rates, sizeof(hwsim_rates));
-               data->band.channels = data->channels;
-               data->band.n_channels = ARRAY_SIZE(hwsim_channels);
-               data->band.bitrates = data->rates;
-               data->band.n_bitrates = ARRAY_SIZE(hwsim_rates);
-               data->band.ht_info.ht_supported = 1;
-               data->band.ht_info.cap = IEEE80211_HT_CAP_SUP_WIDTH |
-                       IEEE80211_HT_CAP_GRN_FLD |
-                       IEEE80211_HT_CAP_SGI_40 |
-                       IEEE80211_HT_CAP_DSSSCCK40;
-               data->band.ht_info.ampdu_factor = 0x3;
-               data->band.ht_info.ampdu_density = 0x6;
-               memset(data->band.ht_info.supp_mcs_set, 0,
-                      sizeof(data->band.ht_info.supp_mcs_set));
-               data->band.ht_info.supp_mcs_set[0] = 0xff;
-               data->band.ht_info.supp_mcs_set[1] = 0xff;
-               data->band.ht_info.supp_mcs_set[12] =
-                       IEEE80211_HT_CAP_MCS_TX_DEFINED;
-               hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &data->band;
+
+               for (band = IEEE80211_BAND_2GHZ; band < IEEE80211_NUM_BANDS; band++) {
+                       struct ieee80211_supported_band *sband = &data->bands[band];
+                       switch (band) {
+                       case IEEE80211_BAND_2GHZ:
+                               sband->channels = data->channels_2ghz;
+                               sband->n_channels =
+                                       ARRAY_SIZE(hwsim_channels_2ghz);
+                               break;
+                       case IEEE80211_BAND_5GHZ:
+                               sband->channels = data->channels_5ghz;
+                               sband->n_channels =
+                                       ARRAY_SIZE(hwsim_channels_5ghz);
+                               break;
+                       default:
+                               break;
+                       }
+
+                       sband->bitrates = data->rates;
+                       sband->n_bitrates = ARRAY_SIZE(hwsim_rates);
+
+                       sband->ht_cap.ht_supported = true;
+                       sband->ht_cap.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+                               IEEE80211_HT_CAP_GRN_FLD |
+                               IEEE80211_HT_CAP_SGI_40 |
+                               IEEE80211_HT_CAP_DSSSCCK40;
+                       sband->ht_cap.ampdu_factor = 0x3;
+                       sband->ht_cap.ampdu_density = 0x6;
+                       memset(&sband->ht_cap.mcs, 0,
+                              sizeof(sband->ht_cap.mcs));
+                       sband->ht_cap.mcs.rx_mask[0] = 0xff;
+                       sband->ht_cap.mcs.rx_mask[1] = 0xff;
+                       sband->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
+
+                       hw->wiphy->bands[band] = sband;
+               }
+
+               /* Work to be done prior to ieee80211_register_hw() */
+               switch (regtest) {
+               case HWSIM_REGTEST_DISABLED:
+               case HWSIM_REGTEST_DRIVER_REG_FOLLOW:
+               case HWSIM_REGTEST_DRIVER_REG_ALL:
+               case HWSIM_REGTEST_DIFF_COUNTRY:
+                       /*
+                        * Nothing to be done for driver regulatory domain
+                        * hints prior to ieee80211_register_hw()
+                        */
+                       break;
+               case HWSIM_REGTEST_WORLD_ROAM:
+                       if (i == 0) {
+                               hw->wiphy->custom_regulatory = true;
+                               wiphy_apply_custom_regulatory(hw->wiphy,
+                                       &hwsim_world_regdom_custom_01);
+                       }
+                       break;
+               case HWSIM_REGTEST_CUSTOM_WORLD:
+                       hw->wiphy->custom_regulatory = true;
+                       wiphy_apply_custom_regulatory(hw->wiphy,
+                               &hwsim_world_regdom_custom_01);
+                       break;
+               case HWSIM_REGTEST_CUSTOM_WORLD_2:
+                       if (i == 0) {
+                               hw->wiphy->custom_regulatory = true;
+                               wiphy_apply_custom_regulatory(hw->wiphy,
+                                       &hwsim_world_regdom_custom_01);
+                       } else if (i == 1) {
+                               hw->wiphy->custom_regulatory = true;
+                               wiphy_apply_custom_regulatory(hw->wiphy,
+                                       &hwsim_world_regdom_custom_02);
+                       }
+                       break;
+               case HWSIM_REGTEST_STRICT_ALL:
+                       hw->wiphy->strict_regulatory = true;
+                       break;
+               case HWSIM_REGTEST_STRICT_FOLLOW:
+               case HWSIM_REGTEST_STRICT_AND_DRIVER_REG:
+                       if (i == 0)
+                               hw->wiphy->strict_regulatory = true;
+                       break;
+               case HWSIM_REGTEST_ALL:
+                       if (i == 0) {
+                               hw->wiphy->custom_regulatory = true;
+                               wiphy_apply_custom_regulatory(hw->wiphy,
+                                       &hwsim_world_regdom_custom_01);
+                       } else if (i == 1) {
+                               hw->wiphy->custom_regulatory = true;
+                               wiphy_apply_custom_regulatory(hw->wiphy,
+                                       &hwsim_world_regdom_custom_02);
+                       } else if (i == 4)
+                               hw->wiphy->strict_regulatory = true;
+                       break;
+               default:
+                       break;
+               }
 
                err = ieee80211_register_hw(hw);
                if (err < 0) {
@@ -588,9 +1048,61 @@ static int __init init_mac80211_hwsim(void)
                        goto failed_hw;
                }
 
-               printk(KERN_DEBUG "%s: hwaddr %s registered\n",
+               /* Work to be done after to ieee80211_register_hw() */
+               switch (regtest) {
+               case HWSIM_REGTEST_WORLD_ROAM:
+               case HWSIM_REGTEST_DISABLED:
+                       break;
+               case HWSIM_REGTEST_DRIVER_REG_FOLLOW:
+                       if (!i)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[0]);
+                       break;
+               case HWSIM_REGTEST_DRIVER_REG_ALL:
+               case HWSIM_REGTEST_STRICT_ALL:
+                       regulatory_hint(hw->wiphy, hwsim_alpha2s[0]);
+                       break;
+               case HWSIM_REGTEST_DIFF_COUNTRY:
+                       if (i < ARRAY_SIZE(hwsim_alpha2s))
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[i]);
+                       break;
+               case HWSIM_REGTEST_CUSTOM_WORLD:
+               case HWSIM_REGTEST_CUSTOM_WORLD_2:
+                       /*
+                        * Nothing to be done for custom world regulatory
+                        * domains after to ieee80211_register_hw
+                        */
+                       break;
+               case HWSIM_REGTEST_STRICT_FOLLOW:
+                       if (i == 0)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[0]);
+                       break;
+               case HWSIM_REGTEST_STRICT_AND_DRIVER_REG:
+                       if (i == 0)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[0]);
+                       else if (i == 1)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[1]);
+                       break;
+               case HWSIM_REGTEST_ALL:
+                       if (i == 2)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[0]);
+                       else if (i == 3)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[1]);
+                       else if (i == 4)
+                               regulatory_hint(hw->wiphy, hwsim_alpha2s[2]);
+                       break;
+               default:
+                       break;
+               }
+
+               printk(KERN_DEBUG "%s: hwaddr %pM registered\n",
                       wiphy_name(hw->wiphy),
-                      print_mac(mac, hw->wiphy->perm_addr));
+                      hw->wiphy->perm_addr);
+
+               data->debugfs = debugfs_create_dir("hwsim",
+                                                  hw->wiphy->debugfsdir);
+               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);