--- zzzz-none-000/linux-3.10.107/drivers/net/wireless/mac80211_hwsim.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/net/wireless/mac80211_hwsim.c 2021-02-04 17:41:59.000000000 +0000 @@ -57,6 +57,10 @@ module_param(rctbl, bool, 0444); MODULE_PARM_DESC(rctbl, "Handle rate control table"); +static bool support_p2p_device = true; +module_param(support_p2p_device, bool, 0444); +MODULE_PARM_DESC(support_p2p_device, "Support P2P-Device interface type"); + /** * enum hwsim_regtest - the type of regulatory tests we offer * @@ -159,14 +163,20 @@ .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), + NL80211_RRF_NO_IR), } }; +static const struct ieee80211_regdomain *hwsim_world_regdom_custom[] = { + &hwsim_world_regdom_custom_01, + &hwsim_world_regdom_custom_02, +}; + struct hwsim_vif_priv { u32 magic; u8 bssid[ETH_ALEN]; bool assoc; + bool bcn_en; u16 aid; }; @@ -320,8 +330,150 @@ { .bitrate = 540 } }; +#define OUI_QCA 0x001374 +#define QCA_NL80211_SUBCMD_TEST 1 +enum qca_nl80211_vendor_subcmds { + QCA_WLAN_VENDOR_ATTR_TEST = 8, + QCA_WLAN_VENDOR_ATTR_MAX = QCA_WLAN_VENDOR_ATTR_TEST +}; + +static const struct nla_policy +hwsim_vendor_test_policy[QCA_WLAN_VENDOR_ATTR_MAX + 1] = { + [QCA_WLAN_VENDOR_ATTR_MAX] = { .type = NLA_U32 }, +}; + +static int mac80211_hwsim_vendor_cmd_test(struct wiphy *wiphy, + struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct sk_buff *skb; + struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_MAX + 1]; + int err; + u32 val; + + err = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_MAX, data, data_len, + hwsim_vendor_test_policy); + if (err) + return err; + if (!tb[QCA_WLAN_VENDOR_ATTR_TEST]) + return -EINVAL; + val = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_TEST]); + wiphy_debug(wiphy, "%s: test=%u\n", __func__, val); + + /* Send a vendor event as a test. Note that this would not normally be + * done within a command handler, but rather, based on some other + * trigger. For simplicity, this command is used to trigger the event + * here. + * + * event_idx = 0 (index in mac80211_hwsim_vendor_commands) + */ + skb = cfg80211_vendor_event_alloc(wiphy, wdev, 100, 0, GFP_KERNEL); + if (skb) { + /* skb_put() or nla_put() will fill up data within + * NL80211_ATTR_VENDOR_DATA. + */ + + /* Add vendor data */ + nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_TEST, val + 1); + + /* Send the event - this will call nla_nest_end() */ + cfg80211_vendor_event(skb, GFP_KERNEL); + } + + /* Send a response to the command */ + skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 10); + if (!skb) + return -ENOMEM; + + /* skb_put() or nla_put() will fill up data within + * NL80211_ATTR_VENDOR_DATA + */ + nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_TEST, val + 2); + + return cfg80211_vendor_cmd_reply(skb); +} + +static struct wiphy_vendor_command mac80211_hwsim_vendor_commands[] = { + { + .info = { .vendor_id = OUI_QCA, + .subcmd = QCA_NL80211_SUBCMD_TEST }, + .flags = WIPHY_VENDOR_CMD_NEED_NETDEV, + .doit = mac80211_hwsim_vendor_cmd_test, + } +}; + +/* Advertise support vendor specific events */ +static const struct nl80211_vendor_cmd_info mac80211_hwsim_vendor_events[] = { + { .vendor_id = OUI_QCA, .subcmd = 1 }, +}; + +static const struct ieee80211_iface_limit hwsim_if_limits[] = { + { .max = 1, .types = BIT(NL80211_IFTYPE_ADHOC) }, + { .max = 2048, .types = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | +#ifdef CONFIG_MAC80211_MESH + BIT(NL80211_IFTYPE_MESH_POINT) | +#endif + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_GO) }, + /* must be last, see hwsim_if_comb */ + { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) } +}; + +static const struct ieee80211_iface_limit hwsim_if_dfs_limits[] = { + { .max = 8, .types = BIT(NL80211_IFTYPE_AP) }, +}; + +static const struct ieee80211_iface_combination hwsim_if_comb[] = { + { + .limits = hwsim_if_limits, + /* remove the last entry which is P2P_DEVICE */ + .n_limits = ARRAY_SIZE(hwsim_if_limits) - 1, + .max_interfaces = 2048, + .num_different_channels = 1, + }, + { + .limits = hwsim_if_dfs_limits, + .n_limits = ARRAY_SIZE(hwsim_if_dfs_limits), + .max_interfaces = 8, + .num_different_channels = 1, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160), + } +}; + +static const struct ieee80211_iface_combination hwsim_if_comb_p2p_dev[] = { + { + .limits = hwsim_if_limits, + .n_limits = ARRAY_SIZE(hwsim_if_limits), + .max_interfaces = 2048, + .num_different_channels = 1, + }, + { + .limits = hwsim_if_dfs_limits, + .n_limits = ARRAY_SIZE(hwsim_if_dfs_limits), + .max_interfaces = 8, + .num_different_channels = 1, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160), + } +}; + static spinlock_t hwsim_radio_lock; static struct list_head hwsim_radios; +static int hwsim_radio_idx; + +static struct platform_driver mac80211_hwsim_driver = { + .driver = { + .name = "mac80211_hwsim", + }, +}; struct mac80211_hwsim_data { struct list_head list; @@ -331,8 +483,16 @@ 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_iface_combination if_combination; struct mac_address addresses[2]; + int channels, idx; + bool use_chanctx; + bool destroy_on_close; + struct work_struct destroy_work; + u32 portid; + char alpha2[2]; + const struct ieee80211_regdomain *regd; struct ieee80211_channel *tmp_chan; struct delayed_work roc_done; @@ -340,6 +500,7 @@ struct cfg80211_scan_request *hw_scan_request; struct ieee80211_vif *hw_scan_vif; int scan_chan_idx; + u8 scan_addr[ETH_ALEN]; struct ieee80211_channel *channel; u64 beacon_int /* beacon interval in us */; @@ -352,16 +513,14 @@ } ps; bool ps_poll_pending; struct dentry *debugfs; - struct dentry *debugfs_ps; struct sk_buff_head pending; /* packets pending */ /* * Only radios in the same group can communicate together (the * channel has to match too). Each bit represents a group. A - * radio can be in more then one group. + * radio can be in more than one group. */ u64 group; - struct dentry *debugfs_group; int power_level; @@ -370,6 +529,14 @@ s64 bcn_delta; /* absolute beacon transmission time. Used to cover up "tx" delay. */ u64 abs_bcn_ts; + + /* Stats */ + u64 tx_pkts; + u64 rx_pkts; + u64 tx_bytes; + u64 rx_bytes; + u64 tx_dropped; + u64 tx_failed; }; @@ -382,6 +549,14 @@ __le16 rt_chbitmask; } __packed; +struct hwsim_radiotap_ack_hdr { + struct ieee80211_radiotap_header hdr; + u8 rt_flags; + u8 pad; + __le16 rt_channel; + __le16 rt_chbitmask; +} __packed; + /* MAC80211_HWSIM netlinf family */ static struct genl_family hwsim_genl_family = { .id = GENL_ID_GENERATE, @@ -391,24 +566,195 @@ .maxattr = HWSIM_ATTR_MAX, }; +enum hwsim_multicast_groups { + HWSIM_MCGRP_CONFIG, +}; + +static const struct genl_multicast_group hwsim_mcgrps[] = { + [HWSIM_MCGRP_CONFIG] = { .name = "config", }, +}; + /* MAC80211_HWSIM netlink policy */ -static struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = { - [HWSIM_ATTR_ADDR_RECEIVER] = { .type = NLA_UNSPEC, - .len = 6*sizeof(u8) }, - [HWSIM_ATTR_ADDR_TRANSMITTER] = { .type = NLA_UNSPEC, - .len = 6*sizeof(u8) }, +static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = { + [HWSIM_ATTR_ADDR_RECEIVER] = { .type = NLA_UNSPEC, .len = ETH_ALEN }, + [HWSIM_ATTR_ADDR_TRANSMITTER] = { .type = NLA_UNSPEC, .len = ETH_ALEN }, [HWSIM_ATTR_FRAME] = { .type = NLA_BINARY, .len = IEEE80211_MAX_DATA_LEN }, [HWSIM_ATTR_FLAGS] = { .type = NLA_U32 }, [HWSIM_ATTR_RX_RATE] = { .type = NLA_U32 }, [HWSIM_ATTR_SIGNAL] = { .type = NLA_U32 }, [HWSIM_ATTR_TX_INFO] = { .type = NLA_UNSPEC, - .len = IEEE80211_TX_MAX_RATES*sizeof( - struct hwsim_tx_rate)}, + .len = IEEE80211_TX_MAX_RATES * + sizeof(struct hwsim_tx_rate)}, [HWSIM_ATTR_COOKIE] = { .type = NLA_U64 }, + [HWSIM_ATTR_CHANNELS] = { .type = NLA_U32 }, + [HWSIM_ATTR_RADIO_ID] = { .type = NLA_U32 }, + [HWSIM_ATTR_REG_HINT_ALPHA2] = { .type = NLA_STRING, .len = 2 }, + [HWSIM_ATTR_REG_CUSTOM_REG] = { .type = NLA_U32 }, + [HWSIM_ATTR_REG_STRICT_REG] = { .type = NLA_FLAG }, + [HWSIM_ATTR_SUPPORT_P2P_DEVICE] = { .type = NLA_FLAG }, + [HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE] = { .type = NLA_FLAG }, + [HWSIM_ATTR_RADIO_NAME] = { .type = NLA_STRING }, + [HWSIM_ATTR_NO_VIF] = { .type = NLA_FLAG }, + [HWSIM_ATTR_FREQ] = { .type = NLA_U32 }, }; +static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw, + struct sk_buff *skb, + struct ieee80211_channel *chan); + +/* sysfs attributes */ +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; + struct sk_buff *skb; + struct ieee80211_pspoll *pspoll; + + if (!vp->assoc) + return; + + wiphy_debug(data->hw->wiphy, + "%s: send PS-Poll to %pM for aid %d\n", + __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); + + rcu_read_lock(); + mac80211_hwsim_tx_frame(data->hw, skb, + rcu_dereference(vif->chanctx_conf)->def.chan); + rcu_read_unlock(); +} + +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; + struct sk_buff *skb; + struct ieee80211_hdr *hdr; + + if (!vp->assoc) + return; + + wiphy_debug(data->hw->wiphy, + "%s: send data::nullfunc to %pM ps=%d\n", + __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); + + rcu_read_lock(); + mac80211_hwsim_tx_frame(data->hw, skb, + rcu_dereference(vif->chanctx_conf)->def.chan); + rcu_read_unlock(); +} + + +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; + + local_bh_disable(); + if (val == PS_MANUAL_POLL) { + ieee80211_iterate_active_interfaces_atomic( + data->hw, IEEE80211_IFACE_ITER_NORMAL, + hwsim_send_ps_poll, data); + data->ps_poll_pending = true; + } else if (old_ps == PS_DISABLED && val != PS_DISABLED) { + ieee80211_iterate_active_interfaces_atomic( + data->hw, IEEE80211_IFACE_ITER_NORMAL, + hwsim_send_nullfunc_ps, data); + } else if (old_ps != PS_DISABLED && val == PS_DISABLED) { + ieee80211_iterate_active_interfaces_atomic( + data->hw, IEEE80211_IFACE_ITER_NORMAL, + hwsim_send_nullfunc_no_ps, data); + } + local_bh_enable(); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_ps, hwsim_fops_ps_read, hwsim_fops_ps_write, + "%llu\n"); + +static int hwsim_write_simulate_radar(void *dat, u64 val) +{ + struct mac80211_hwsim_data *data = dat; + + ieee80211_radar_detected(data->hw); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(hwsim_simulate_radar, NULL, + hwsim_write_simulate_radar, "%llu\n"); + +static int hwsim_fops_group_read(void *dat, u64 *val) +{ + struct mac80211_hwsim_data *data = dat; + *val = data->group; + return 0; +} + +static int hwsim_fops_group_write(void *dat, u64 val) +{ + struct mac80211_hwsim_data *data = dat; + data->group = val; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_group, + hwsim_fops_group_read, hwsim_fops_group_write, + "%llx\n"); + static netdev_tx_t hwsim_mon_xmit(struct sk_buff *skb, struct net_device *dev) { @@ -441,11 +787,16 @@ struct mac80211_hwsim_data *data = hw->priv; u64 now = mac80211_hwsim_get_tsf(hw, vif); u32 bcn_int = data->beacon_int; - s64 delta = tsf - now; + u64 delta = abs(tsf - now); - data->tsf_offset += delta; /* adjust after beaconing with new timestamp at old TBTT */ - data->bcn_delta = do_div(delta, bcn_int); + if (tsf > now) { + data->tsf_offset += delta; + data->bcn_delta = do_div(delta, bcn_int); + } else { + data->tsf_offset -= delta; + data->bcn_delta = -do_div(delta, bcn_int); + } } static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, @@ -499,7 +850,7 @@ const u8 *addr) { struct sk_buff *skb; - struct hwsim_radiotap_hdr *hdr; + struct hwsim_radiotap_ack_hdr *hdr; u16 flags; struct ieee80211_hdr *hdr11; @@ -510,14 +861,14 @@ if (skb == NULL) return; - hdr = (struct hwsim_radiotap_hdr *) skb_put(skb, sizeof(*hdr)); + hdr = (struct hwsim_radiotap_ack_hdr *) skb_put(skb, sizeof(*hdr)); hdr->hdr.it_version = PKTHDR_RADIOTAP_VERSION; hdr->hdr.it_pad = 0; hdr->hdr.it_len = cpu_to_le16(sizeof(*hdr)); hdr->hdr.it_present = cpu_to_le32((1 << IEEE80211_RADIOTAP_FLAGS) | (1 << IEEE80211_RADIOTAP_CHANNEL)); hdr->rt_flags = 0; - hdr->rt_rate = 0; + hdr->pad = 0; hdr->rt_channel = cpu_to_le16(chan->center_freq); flags = IEEE80211_CHAN_2GHZ; hdr->rt_chbitmask = cpu_to_le16(flags); @@ -537,59 +888,32 @@ netif_rx(skb); } - -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; -} - - struct mac80211_hwsim_addr_match_data { + u8 addr[ETH_ALEN]; bool ret; - const u8 *addr; }; static void mac80211_hwsim_addr_iter(void *data, u8 *mac, struct ieee80211_vif *vif) { struct mac80211_hwsim_addr_match_data *md = data; + if (memcmp(mac, md->addr, ETH_ALEN) == 0) md->ret = true; } - static bool mac80211_hwsim_addr_match(struct mac80211_hwsim_data *data, const u8 *addr) { - struct mac80211_hwsim_addr_match_data md; + struct mac80211_hwsim_addr_match_data md = { + .ret = false, + }; - if (memcmp(addr, data->hw->wiphy->perm_addr, ETH_ALEN) == 0) + if (data->scanning && memcmp(addr, data->scan_addr, ETH_ALEN) == 0) return true; - md.ret = false; - md.addr = addr; + memcpy(md.addr, addr, ETH_ALEN); + ieee80211_iterate_active_interfaces_atomic(data->hw, IEEE80211_IFACE_ITER_NORMAL, mac80211_hwsim_addr_iter, @@ -598,6 +922,32 @@ return md.ret; } +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 && + mac80211_hwsim_addr_match(data, skb->data + 4)) { + data->ps_poll_pending = false; + return true; + } + return false; + } + + return true; +} + static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw, struct sk_buff *my_skb, int dst_portid) @@ -616,8 +966,10 @@ /* If the queue contains MAX_QUEUE skb's drop some */ if (skb_queue_len(&data->pending) >= MAX_QUEUE) { /* Droping until WARN_QUEUE level */ - while (skb_queue_len(&data->pending) >= WARN_QUEUE) - skb_dequeue(&data->pending); + while (skb_queue_len(&data->pending) >= WARN_QUEUE) { + ieee80211_free_txskb(hw, skb_dequeue(&data->pending)); + data->tx_dropped++; + } } skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_ATOMIC); @@ -631,8 +983,7 @@ goto nla_put_failure; } - if (nla_put(skb, HWSIM_ATTR_ADDR_TRANSMITTER, - sizeof(struct mac_address), data->addresses[1].addr)) + if (nla_put(skb, HWSIM_ATTR_ADDR_TRANSMITTER, ETH_ALEN, hdr->addr2)) goto nla_put_failure; /* We get the skb->data */ @@ -651,6 +1002,9 @@ if (nla_put_u32(skb, HWSIM_ATTR_FLAGS, hwsim_flags)) goto nla_put_failure; + if (nla_put_u32(skb, HWSIM_ATTR_FREQ, data->channel->center_freq)) + goto nla_put_failure; + /* We get the tx control (rate and retries) info*/ for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { @@ -668,14 +1022,21 @@ goto nla_put_failure; genlmsg_end(skb, msg_head); - genlmsg_unicast(&init_net, skb, dst_portid); + if (genlmsg_unicast(&init_net, skb, dst_portid)) + goto err_free_txskb; /* Enqueue the packet */ skb_queue_tail(&data->pending, my_skb); + data->tx_pkts++; + data->tx_bytes += my_skb->len; return; nla_put_failure: + nlmsg_free(skb); +err_free_txskb: printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); + ieee80211_free_txskb(hw, my_skb); + data->tx_failed++; } static bool hwsim_chans_compat(struct ieee80211_channel *c1, @@ -707,6 +1068,53 @@ data->receive = true; } +static void mac80211_hwsim_add_vendor_rtap(struct sk_buff *skb) +{ + /* + * To enable this code, #define the HWSIM_RADIOTAP_OUI, + * e.g. like this: + * #define HWSIM_RADIOTAP_OUI "\x02\x00\x00" + * (but you should use a valid OUI, not that) + * + * If anyone wants to 'donate' a radiotap OUI/subns code + * please send a patch removing this #ifdef and changing + * the values accordingly. + */ +#ifdef HWSIM_RADIOTAP_OUI + struct ieee80211_vendor_radiotap *rtap; + + /* + * Note that this code requires the headroom in the SKB + * that was allocated earlier. + */ + rtap = (void *)skb_push(skb, sizeof(*rtap) + 8 + 4); + rtap->oui[0] = HWSIM_RADIOTAP_OUI[0]; + rtap->oui[1] = HWSIM_RADIOTAP_OUI[1]; + rtap->oui[2] = HWSIM_RADIOTAP_OUI[2]; + rtap->subns = 127; + + /* + * Radiotap vendor namespaces can (and should) also be + * split into fields by using the standard radiotap + * presence bitmap mechanism. Use just BIT(0) here for + * the presence bitmap. + */ + rtap->present = BIT(0); + /* We have 8 bytes of (dummy) data */ + rtap->len = 8; + /* For testing, also require it to be aligned */ + rtap->align = 8; + /* And also test that padding works, 4 bytes */ + rtap->pad = 4; + /* push the data */ + memcpy(rtap->data, "ABCDEFGH", 8); + /* make sure to clear padding, mac80211 doesn't */ + memset(rtap->data + 8, 0, 4); + + IEEE80211_SKB_RXCB(skb)->flag |= RX_FLAG_RADIOTAP_VENDOR_DATA; +#endif +} + static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw, struct sk_buff *skb, struct ieee80211_channel *chan) @@ -819,34 +1227,13 @@ ack = true; rx_status.mactime = now + data2->tsf_offset; -#if 0 - /* - * Don't enable this code by default as the OUI 00:00:00 - * is registered to Xerox so we shouldn't use it here, it - * might find its way into pcap files. - * Note that this code requires the headroom in the SKB - * that was allocated earlier. - */ - rx_status.vendor_radiotap_oui[0] = 0x00; - rx_status.vendor_radiotap_oui[1] = 0x00; - rx_status.vendor_radiotap_oui[2] = 0x00; - rx_status.vendor_radiotap_subns = 127; - /* - * Radiotap vendor namespaces can (and should) also be - * split into fields by using the standard radiotap - * presence bitmap mechanism. Use just BIT(0) here for - * the presence bitmap. - */ - rx_status.vendor_radiotap_bitmap = BIT(0); - /* We have 8 bytes of (dummy) data */ - rx_status.vendor_radiotap_len = 8; - /* For testing, also require it to be aligned */ - rx_status.vendor_radiotap_align = 8; - /* push the data */ - memcpy(skb_push(nskb, 8), "ABCDEFGH", 8); -#endif memcpy(IEEE80211_SKB_RXCB(nskb), &rx_status, sizeof(rx_status)); + + mac80211_hwsim_add_vendor_rtap(nskb); + + data2->rx_pkts++; + data2->rx_bytes += nskb->len; ieee80211_rx_irqsafe(data2->hw, nskb); } spin_unlock(&hwsim_radio_lock); @@ -867,11 +1254,11 @@ if (WARN_ON(skb->len < 10)) { /* Should not happen; just a sanity check for addr1 use */ - dev_kfree_skb(skb); + ieee80211_free_txskb(hw, skb); return; } - if (channels == 1) { + if (!data->use_chanctx) { channel = data->channel; } else if (txi->hw_queue == 4) { channel = data->tmp_chan; @@ -884,13 +1271,13 @@ } if (WARN(!channel, "TX w/o channel - queue = %d\n", txi->hw_queue)) { - dev_kfree_skb(skb); + ieee80211_free_txskb(hw, skb); return; } if (data->idle && !data->tmp_chan) { wiphy_debug(hw->wiphy, "Trying to TX when idle - reject\n"); - dev_kfree_skb(skb); + ieee80211_free_txskb(hw, skb); return; } @@ -899,7 +1286,7 @@ if (control->sta) hwsim_check_sta_magic(control->sta); - if (rctbl) + if (ieee80211_hw_check(hw, SUPPORTS_RC_TABLE)) ieee80211_get_tx_rates(txi->control.vif, control->sta, skb, txi->control.rates, ARRAY_SIZE(txi->control.rates)); @@ -914,6 +1301,8 @@ return mac80211_hwsim_tx_frame_nl(hw, skb, _portid); /* NO wmediumd detected, perfect medium simulation */ + data->tx_pkts++; + data->tx_bytes += skb->len; ack = mac80211_hwsim_tx_frame_no_nl(hw, skb, channel); if (ack && skb->len >= 16) { @@ -1006,7 +1395,7 @@ { u32 _pid = ACCESS_ONCE(wmediumd_portid); - if (rctbl) { + if (ieee80211_hw_check(hw, SUPPORTS_RC_TABLE)) { struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb); ieee80211_get_tx_rates(txi->control.vif, NULL, skb, txi->control.rates, @@ -1043,7 +1432,7 @@ if (skb == NULL) return; info = IEEE80211_SKB_CB(skb); - if (rctbl) + if (ieee80211_hw_check(hw, SUPPORTS_RC_TABLE)) ieee80211_get_tx_rates(vif, NULL, skb, info->control.rates, ARRAY_SIZE(info->control.rates)); @@ -1059,6 +1448,9 @@ mac80211_hwsim_tx_frame(hw, skb, rcu_dereference(vif->chanctx_conf)->def.chan); + + if (vif->csa_active && ieee80211_csa_is_complete(vif)) + ieee80211_csa_finish(vif); } static enum hrtimer_restart @@ -1134,7 +1526,7 @@ data->channel = conf->chandef.chan; - WARN_ON(data->channel && channels > 1); + WARN_ON(data->channel && data->use_chanctx); data->power_level = conf->power_level; if (!data->started || !data->beacon_int) @@ -1162,14 +1554,22 @@ wiphy_debug(hw->wiphy, "%s\n", __func__); data->rx_filter = 0; - if (*total_flags & FIF_PROMISC_IN_BSS) - data->rx_filter |= FIF_PROMISC_IN_BSS; if (*total_flags & FIF_ALLMULTI) data->rx_filter |= FIF_ALLMULTI; *total_flags = data->rx_filter; } +static void mac80211_hwsim_bcn_en_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + unsigned int *count = data; + struct hwsim_vif_priv *vp = (void *)vif->drv_priv; + + if (vp->bcn_en) + (*count)++; +} + static void mac80211_hwsim_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *info, @@ -1180,7 +1580,8 @@ hwsim_check_magic(vif); - wiphy_debug(hw->wiphy, "%s(changed=0x%x)\n", __func__, changed); + wiphy_debug(hw->wiphy, "%s(changed=0x%x vif->addr=%pM)\n", + __func__, changed, vif->addr); if (changed & BSS_CHANGED_BSSID) { wiphy_debug(hw->wiphy, "%s: BSSID changed: %pM\n", @@ -1195,28 +1596,34 @@ vp->aid = info->aid; } - if (changed & BSS_CHANGED_BEACON_INT) { - wiphy_debug(hw->wiphy, " BCNINT: %d\n", info->beacon_int); - data->beacon_int = info->beacon_int * 1024; - } - if (changed & BSS_CHANGED_BEACON_ENABLED) { - wiphy_debug(hw->wiphy, " BCN EN: %d\n", info->enable_beacon); + wiphy_debug(hw->wiphy, " BCN EN: %d (BI=%u)\n", + info->enable_beacon, info->beacon_int); + vp->bcn_en = info->enable_beacon; if (data->started && !hrtimer_is_queued(&data->beacon_timer.timer) && info->enable_beacon) { u64 tsf, until_tbtt; u32 bcn_int; - if (WARN_ON(!data->beacon_int)) - data->beacon_int = 1000 * 1024; + data->beacon_int = info->beacon_int * 1024; tsf = mac80211_hwsim_get_tsf(hw, vif); bcn_int = data->beacon_int; until_tbtt = bcn_int - do_div(tsf, bcn_int); tasklet_hrtimer_start(&data->beacon_timer, ns_to_ktime(until_tbtt * 1000), HRTIMER_MODE_REL); - } else if (!info->enable_beacon) - tasklet_hrtimer_cancel(&data->beacon_timer); + } else if (!info->enable_beacon) { + unsigned int count = 0; + ieee80211_iterate_active_interfaces_atomic( + data->hw, IEEE80211_IFACE_ITER_NORMAL, + mac80211_hwsim_bcn_en_iter, &count); + wiphy_debug(hw->wiphy, " beaconing vifs remaining: %u", + count); + if (count == 0) { + tasklet_hrtimer_cancel(&data->beacon_timer); + data->beacon_int = 0; + } + } } if (changed & BSS_CHANGED_ERP_CTS_PROT) { @@ -1361,9 +1768,8 @@ [HWSIM_TM_ATTR_PS] = { .type = NLA_U32 }, }; -static int hwsim_fops_ps_write(void *dat, u64 val); - static int mac80211_hwsim_testmode_cmd(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, void *data, int len) { struct mac80211_hwsim_data *hwsim = hw->priv; @@ -1413,7 +1819,7 @@ struct ieee80211_vif *vif, enum ieee80211_ampdu_mlme_action action, struct ieee80211_sta *sta, u16 tid, u16 *ssn, - u8 buf_size) + u8 buf_size, bool amsdu) { switch (action) { case IEEE80211_AMPDU_TX_START: @@ -1436,7 +1842,9 @@ return 0; } -static void mac80211_hwsim_flush(struct ieee80211_hw *hw, u32 queues, bool drop) +static void mac80211_hwsim_flush(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u32 queues, bool drop) { /* Not implemented, queues only on kernel side */ } @@ -1463,7 +1871,7 @@ req->channels[hwsim->scan_chan_idx]->center_freq); hwsim->tmp_chan = req->channels[hwsim->scan_chan_idx]; - if (hwsim->tmp_chan->flags & IEEE80211_CHAN_PASSIVE_SCAN || + if (hwsim->tmp_chan->flags & IEEE80211_CHAN_NO_IR || !req->n_ssids) { dwell = 120; } else { @@ -1473,7 +1881,7 @@ struct sk_buff *probe; probe = ieee80211_probereq_get(hwsim->hw, - hwsim->hw_scan_vif, + hwsim->scan_addr, req->ssids[i].ssid, req->ssids[i].ssid_len, req->ie_len); @@ -1498,9 +1906,10 @@ static int mac80211_hwsim_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, - struct cfg80211_scan_request *req) + struct ieee80211_scan_request *hw_req) { struct mac80211_hwsim_data *hwsim = hw->priv; + struct cfg80211_scan_request *req = &hw_req->req; mutex_lock(&hwsim->mutex); if (WARN_ON(hwsim->tmp_chan || hwsim->hw_scan_request)) { @@ -1510,6 +1919,12 @@ hwsim->hw_scan_request = req; hwsim->hw_scan_vif = vif; hwsim->scan_chan_idx = 0; + if (req->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) + get_random_mask_addr(hwsim->scan_addr, + hw_req->req.mac_addr, + hw_req->req.mac_addr_mask); + else + memcpy(hwsim->scan_addr, vif->addr, ETH_ALEN); mutex_unlock(&hwsim->mutex); wiphy_debug(hw->wiphy, "hwsim hw_scan request\n"); @@ -1536,7 +1951,9 @@ mutex_unlock(&hwsim->mutex); } -static void mac80211_hwsim_sw_scan(struct ieee80211_hw *hw) +static void mac80211_hwsim_sw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) { struct mac80211_hwsim_data *hwsim = hw->priv; @@ -1548,13 +1965,16 @@ } printk(KERN_DEBUG "hwsim sw_scan request, prepping stuff\n"); + + memcpy(hwsim->scan_addr, mac_addr, ETH_ALEN); hwsim->scanning = true; out: mutex_unlock(&hwsim->mutex); } -static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw) +static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) { struct mac80211_hwsim_data *hwsim = hw->priv; @@ -1562,6 +1982,7 @@ printk(KERN_DEBUG "hwsim sw_scan_complete\n"); hwsim->scanning = false; + eth_zero_addr(hwsim->scan_addr); mutex_unlock(&hwsim->mutex); } @@ -1672,8 +2093,58 @@ hwsim_check_chanctx_magic(ctx); } -static struct ieee80211_ops mac80211_hwsim_ops = +static const char mac80211_hwsim_gstrings_stats[][ETH_GSTRING_LEN] = { + "tx_pkts_nic", + "tx_bytes_nic", + "rx_pkts_nic", + "rx_bytes_nic", + "d_tx_dropped", + "d_tx_failed", + "d_ps_mode", + "d_group", + "d_tx_power", +}; + +#define MAC80211_HWSIM_SSTATS_LEN ARRAY_SIZE(mac80211_hwsim_gstrings_stats) + +static void mac80211_hwsim_get_et_strings(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + u32 sset, u8 *data) { + if (sset == ETH_SS_STATS) + memcpy(data, *mac80211_hwsim_gstrings_stats, + sizeof(mac80211_hwsim_gstrings_stats)); +} + +static int mac80211_hwsim_get_et_sset_count(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, int sset) +{ + if (sset == ETH_SS_STATS) + return MAC80211_HWSIM_SSTATS_LEN; + return 0; +} + +static void mac80211_hwsim_get_et_stats(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ethtool_stats *stats, u64 *data) +{ + struct mac80211_hwsim_data *ar = hw->priv; + int i = 0; + + data[i++] = ar->tx_pkts; + data[i++] = ar->tx_bytes; + data[i++] = ar->rx_pkts; + data[i++] = ar->rx_bytes; + data[i++] = ar->tx_dropped; + data[i++] = ar->tx_failed; + data[i++] = ar->ps; + data[i++] = ar->group; + data[i++] = ar->power_level; + + WARN_ON(i != MAC80211_HWSIM_SSTATS_LEN); +} + +static const struct ieee80211_ops mac80211_hwsim_ops = { .tx = mac80211_hwsim_tx, .start = mac80211_hwsim_start, .stop = mac80211_hwsim_stop, @@ -1696,210 +2167,529 @@ .flush = mac80211_hwsim_flush, .get_tsf = mac80211_hwsim_get_tsf, .set_tsf = mac80211_hwsim_set_tsf, -}; - + .get_et_sset_count = mac80211_hwsim_get_et_sset_count, + .get_et_stats = mac80211_hwsim_get_et_stats, + .get_et_strings = mac80211_hwsim_get_et_strings, +}; + +static struct ieee80211_ops mac80211_hwsim_mchan_ops; + +struct hwsim_new_radio_params { + unsigned int channels; + const char *reg_alpha2; + const struct ieee80211_regdomain *regd; + bool reg_strict; + bool p2p_device; + bool use_chanctx; + bool destroy_on_close; + const char *hwname; + bool no_vif; +}; + +static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb, + struct genl_info *info) +{ + if (info) + genl_notify(&hwsim_genl_family, mcast_skb, info, + HWSIM_MCGRP_CONFIG, GFP_KERNEL); + else + genlmsg_multicast(&hwsim_genl_family, mcast_skb, 0, + HWSIM_MCGRP_CONFIG, GFP_KERNEL); +} -static void mac80211_hwsim_free(void) +static int append_radio_msg(struct sk_buff *skb, int id, + struct hwsim_new_radio_params *param) { - struct list_head tmplist, *i, *tmp; - struct mac80211_hwsim_data *data, *tmpdata; + int ret; - INIT_LIST_HEAD(&tmplist); + ret = nla_put_u32(skb, HWSIM_ATTR_RADIO_ID, id); + if (ret < 0) + return ret; - spin_lock_bh(&hwsim_radio_lock); - list_for_each_safe(i, tmp, &hwsim_radios) - list_move(i, &tmplist); - spin_unlock_bh(&hwsim_radio_lock); + if (param->channels) { + ret = nla_put_u32(skb, HWSIM_ATTR_CHANNELS, param->channels); + if (ret < 0) + return ret; + } - list_for_each_entry_safe(data, tmpdata, &tmplist, list) { - debugfs_remove(data->debugfs_group); - debugfs_remove(data->debugfs_ps); - debugfs_remove(data->debugfs); - ieee80211_unregister_hw(data->hw); - device_release_driver(data->dev); - device_unregister(data->dev); - ieee80211_free_hw(data->hw); + if (param->reg_alpha2) { + ret = nla_put(skb, HWSIM_ATTR_REG_HINT_ALPHA2, 2, + param->reg_alpha2); + if (ret < 0) + return ret; } - class_destroy(hwsim_class); -} -static struct platform_driver mac80211_hwsim_driver = { - .driver = { - .name = "mac80211_hwsim", - .owner = THIS_MODULE, - }, -}; + if (param->regd) { + int i; -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, -}; + for (i = 0; i < ARRAY_SIZE(hwsim_world_regdom_custom); i++) { + if (hwsim_world_regdom_custom[i] != param->regd) + continue; -static void hwsim_mon_setup(struct net_device *dev) -{ - dev->netdev_ops = &hwsim_netdev_ops; - dev->destructor = free_netdev; - ether_setup(dev); - dev->tx_queue_len = 0; - dev->type = ARPHRD_IEEE80211_RADIOTAP; - memset(dev->dev_addr, 0, ETH_ALEN); - dev->dev_addr[0] = 0x12; -} + ret = nla_put_u32(skb, HWSIM_ATTR_REG_CUSTOM_REG, i); + if (ret < 0) + return ret; + break; + } + } + if (param->reg_strict) { + ret = nla_put_flag(skb, HWSIM_ATTR_REG_STRICT_REG); + if (ret < 0) + return ret; + } -static void hwsim_send_ps_poll(void *dat, u8 *mac, struct ieee80211_vif *vif) + if (param->p2p_device) { + ret = nla_put_flag(skb, HWSIM_ATTR_SUPPORT_P2P_DEVICE); + if (ret < 0) + return ret; + } + + if (param->use_chanctx) { + ret = nla_put_flag(skb, HWSIM_ATTR_USE_CHANCTX); + if (ret < 0) + return ret; + } + + if (param->hwname) { + ret = nla_put(skb, HWSIM_ATTR_RADIO_NAME, + strlen(param->hwname), param->hwname); + if (ret < 0) + return ret; + } + + return 0; +} + +static void hwsim_mcast_new_radio(int id, struct genl_info *info, + struct hwsim_new_radio_params *param) { - struct mac80211_hwsim_data *data = dat; - struct hwsim_vif_priv *vp = (void *)vif->drv_priv; - struct sk_buff *skb; - struct ieee80211_pspoll *pspoll; + struct sk_buff *mcast_skb; + void *data; - if (!vp->assoc) + mcast_skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!mcast_skb) return; - wiphy_debug(data->hw->wiphy, - "%s: send PS-Poll to %pM for aid %d\n", - __func__, vp->bssid, vp->aid); + data = genlmsg_put(mcast_skb, 0, 0, &hwsim_genl_family, 0, + HWSIM_CMD_NEW_RADIO); + if (!data) + goto out_err; - 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 (append_radio_msg(mcast_skb, id, param) < 0) + goto out_err; - rcu_read_lock(); - mac80211_hwsim_tx_frame(data->hw, skb, - rcu_dereference(vif->chanctx_conf)->def.chan); - rcu_read_unlock(); + genlmsg_end(mcast_skb, data); + + hwsim_mcast_config_msg(mcast_skb, info); + return; + +out_err: + genlmsg_cancel(mcast_skb, data); + nlmsg_free(mcast_skb); } -static void hwsim_send_nullfunc(struct mac80211_hwsim_data *data, u8 *mac, - struct ieee80211_vif *vif, int ps) +static int mac80211_hwsim_new_radio(struct genl_info *info, + struct hwsim_new_radio_params *param) { - struct hwsim_vif_priv *vp = (void *)vif->drv_priv; - struct sk_buff *skb; - struct ieee80211_hdr *hdr; + int err; + u8 addr[ETH_ALEN]; + struct mac80211_hwsim_data *data; + struct ieee80211_hw *hw; + enum ieee80211_band band; + const struct ieee80211_ops *ops = &mac80211_hwsim_ops; + int idx; - if (!vp->assoc) - return; + if (WARN_ON(param->channels > 1 && !param->use_chanctx)) + return -EINVAL; - wiphy_debug(data->hw->wiphy, - "%s: send data::nullfunc to %pM ps=%d\n", - __func__, vp->bssid, ps); + spin_lock_bh(&hwsim_radio_lock); + idx = hwsim_radio_idx++; + spin_unlock_bh(&hwsim_radio_lock); - 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 (param->use_chanctx) + ops = &mac80211_hwsim_mchan_ops; + hw = ieee80211_alloc_hw_nm(sizeof(*data), ops, param->hwname); + if (!hw) { + printk(KERN_DEBUG "mac80211_hwsim: ieee80211_alloc_hw failed\n"); + err = -ENOMEM; + goto failed; + } + data = hw->priv; + data->hw = hw; - rcu_read_lock(); - mac80211_hwsim_tx_frame(data->hw, skb, - rcu_dereference(vif->chanctx_conf)->def.chan); - rcu_read_unlock(); -} + data->dev = device_create(hwsim_class, NULL, 0, hw, "hwsim%d", idx); + if (IS_ERR(data->dev)) { + printk(KERN_DEBUG + "mac80211_hwsim: device_create failed (%ld)\n", + PTR_ERR(data->dev)); + err = -ENOMEM; + goto failed_drvdata; + } + data->dev->driver = &mac80211_hwsim_driver.driver; + err = device_bind_driver(data->dev); + if (err != 0) { + printk(KERN_DEBUG "mac80211_hwsim: device_bind_driver failed (%d)\n", + err); + goto failed_bind; + } + skb_queue_head_init(&data->pending); -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); -} + SET_IEEE80211_DEV(hw, data->dev); + eth_zero_addr(addr); + addr[0] = 0x02; + addr[3] = idx >> 8; + addr[4] = idx; + memcpy(data->addresses[0].addr, addr, ETH_ALEN); + memcpy(data->addresses[1].addr, addr, ETH_ALEN); + data->addresses[1].addr[0] |= 0x40; + hw->wiphy->n_addresses = 2; + hw->wiphy->addresses = data->addresses; + + data->channels = param->channels; + data->use_chanctx = param->use_chanctx; + data->idx = idx; + data->destroy_on_close = param->destroy_on_close; + if (info) + data->portid = info->snd_portid; + + if (data->use_chanctx) { + hw->wiphy->max_scan_ssids = 255; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; + hw->wiphy->max_remain_on_channel_duration = 1000; + /* For channels > 1 DFS is not allowed */ + hw->wiphy->n_iface_combinations = 1; + hw->wiphy->iface_combinations = &data->if_combination; + if (param->p2p_device) + data->if_combination = hwsim_if_comb_p2p_dev[0]; + else + data->if_combination = hwsim_if_comb[0]; + data->if_combination.num_different_channels = data->channels; + } else if (param->p2p_device) { + hw->wiphy->iface_combinations = hwsim_if_comb_p2p_dev; + hw->wiphy->n_iface_combinations = + ARRAY_SIZE(hwsim_if_comb_p2p_dev); + } else { + hw->wiphy->iface_combinations = hwsim_if_comb; + hw->wiphy->n_iface_combinations = ARRAY_SIZE(hwsim_if_comb); + } + INIT_DELAYED_WORK(&data->roc_done, hw_roc_done); + INIT_DELAYED_WORK(&data->hw_scan, hw_scan_work); -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); -} + hw->queues = 5; + hw->offchannel_tx_hw_queue = 4; + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_MESH_POINT); + + if (param->p2p_device) + hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_P2P_DEVICE); + + ieee80211_hw_set(hw, SUPPORT_FAST_XMIT); + ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, SUPPORTS_HT_CCK_RATES); + ieee80211_hw_set(hw, QUEUE_CONTROL); + ieee80211_hw_set(hw, WANT_MONITOR_VIF); + ieee80211_hw_set(hw, AMPDU_AGGREGATION); + ieee80211_hw_set(hw, MFP_CAPABLE); + ieee80211_hw_set(hw, SIGNAL_DBM); + ieee80211_hw_set(hw, TDLS_WIDER_BW); + if (rctbl) + ieee80211_hw_set(hw, SUPPORTS_RC_TABLE); + hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS | + WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | + WIPHY_FLAG_AP_UAPSD | + WIPHY_FLAG_HAS_CHANNEL_SWITCH; + hw->wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR | + NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | + NL80211_FEATURE_STATIC_SMPS | + NL80211_FEATURE_DYNAMIC_SMPS | + NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR; + + /* 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); + hw->chanctx_data_size = sizeof(struct hwsim_chanctx_priv); + + 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)); + + 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); + sband->bitrates = data->rates; + sband->n_bitrates = ARRAY_SIZE(hwsim_rates); + break; + case IEEE80211_BAND_5GHZ: + sband->channels = data->channels_5ghz; + sband->n_channels = ARRAY_SIZE(hwsim_channels_5ghz); + sband->bitrates = data->rates + 4; + sband->n_bitrates = ARRAY_SIZE(hwsim_rates) - 4; -static int hwsim_fops_ps_read(void *dat, u64 *val) -{ - struct mac80211_hwsim_data *data = dat; - *val = data->ps; - return 0; -} + sband->vht_cap.vht_supported = true; + sband->vht_cap.cap = + IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 | + IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ | + IEEE80211_VHT_CAP_RXLDPC | + IEEE80211_VHT_CAP_SHORT_GI_80 | + IEEE80211_VHT_CAP_SHORT_GI_160 | + IEEE80211_VHT_CAP_TXSTBC | + IEEE80211_VHT_CAP_RXSTBC_1 | + IEEE80211_VHT_CAP_RXSTBC_2 | + IEEE80211_VHT_CAP_RXSTBC_3 | + IEEE80211_VHT_CAP_RXSTBC_4 | + IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; + sband->vht_cap.vht_mcs.rx_mcs_map = + cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 | + IEEE80211_VHT_MCS_SUPPORT_0_9 << 14); + sband->vht_cap.vht_mcs.tx_mcs_map = + sband->vht_cap.vht_mcs.rx_mcs_map; + break; + default: + continue; + } -static int hwsim_fops_ps_write(void *dat, u64 val) -{ - struct mac80211_hwsim_data *data = dat; - enum ps_mode old_ps; + 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_20 | + 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; + } + + /* By default all radios belong to the first group */ + data->group = 1; + mutex_init(&data->mutex); + + /* Enable frame retransmissions for lossy channels */ + hw->max_rates = 4; + hw->max_rate_tries = 11; + + hw->wiphy->vendor_commands = mac80211_hwsim_vendor_commands; + hw->wiphy->n_vendor_commands = + ARRAY_SIZE(mac80211_hwsim_vendor_commands); + hw->wiphy->vendor_events = mac80211_hwsim_vendor_events; + hw->wiphy->n_vendor_events = ARRAY_SIZE(mac80211_hwsim_vendor_events); + + if (param->reg_strict) + hw->wiphy->regulatory_flags |= REGULATORY_STRICT_REG; + if (param->regd) { + data->regd = param->regd; + hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; + wiphy_apply_custom_regulatory(hw->wiphy, param->regd); + /* give the regulatory workqueue a chance to run */ + schedule_timeout_interruptible(1); + } - if (val != PS_DISABLED && val != PS_ENABLED && val != PS_AUTO_POLL && - val != PS_MANUAL_POLL) - return -EINVAL; + if (param->no_vif) + ieee80211_hw_set(hw, NO_AUTO_VIF); - old_ps = data->ps; - data->ps = val; + err = ieee80211_register_hw(hw); + if (err < 0) { + printk(KERN_DEBUG "mac80211_hwsim: ieee80211_register_hw failed (%d)\n", + err); + goto failed_hw; + } - if (val == PS_MANUAL_POLL) { - ieee80211_iterate_active_interfaces(data->hw, - IEEE80211_IFACE_ITER_NORMAL, - 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, - IEEE80211_IFACE_ITER_NORMAL, - hwsim_send_nullfunc_ps, - data); - } else if (old_ps != PS_DISABLED && val == PS_DISABLED) { - ieee80211_iterate_active_interfaces(data->hw, - IEEE80211_IFACE_ITER_NORMAL, - hwsim_send_nullfunc_no_ps, - data); + wiphy_debug(hw->wiphy, "hwaddr %pM registered\n", hw->wiphy->perm_addr); + + if (param->reg_alpha2) { + data->alpha2[0] = param->reg_alpha2[0]; + data->alpha2[1] = param->reg_alpha2[1]; + regulatory_hint(hw->wiphy, param->reg_alpha2); } - return 0; + data->debugfs = debugfs_create_dir("hwsim", hw->wiphy->debugfsdir); + debugfs_create_file("ps", 0666, data->debugfs, data, &hwsim_fops_ps); + debugfs_create_file("group", 0666, data->debugfs, data, + &hwsim_fops_group); + if (!data->use_chanctx) + debugfs_create_file("dfs_simulate_radar", 0222, + data->debugfs, + data, &hwsim_simulate_radar); + + tasklet_hrtimer_init(&data->beacon_timer, + mac80211_hwsim_beacon, + CLOCK_MONOTONIC_RAW, HRTIMER_MODE_ABS); + + spin_lock_bh(&hwsim_radio_lock); + list_add_tail(&data->list, &hwsim_radios); + spin_unlock_bh(&hwsim_radio_lock); + + if (idx > 0) + hwsim_mcast_new_radio(idx, info, param); + + return idx; + +failed_hw: + device_release_driver(data->dev); +failed_bind: + device_unregister(data->dev); +failed_drvdata: + ieee80211_free_hw(hw); +failed: + return err; } -DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_ps, hwsim_fops_ps_read, hwsim_fops_ps_write, - "%llu\n"); +static void hwsim_mcast_del_radio(int id, const char *hwname, + struct genl_info *info) +{ + struct sk_buff *skb; + void *data; + int ret; + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb) + return; -static int hwsim_fops_group_read(void *dat, u64 *val) + data = genlmsg_put(skb, 0, 0, &hwsim_genl_family, 0, + HWSIM_CMD_DEL_RADIO); + if (!data) + goto error; + + ret = nla_put_u32(skb, HWSIM_ATTR_RADIO_ID, id); + if (ret < 0) + goto error; + + ret = nla_put(skb, HWSIM_ATTR_RADIO_NAME, strlen(hwname), + hwname); + if (ret < 0) + goto error; + + genlmsg_end(skb, data); + + hwsim_mcast_config_msg(skb, info); + + return; + +error: + nlmsg_free(skb); +} + +static void mac80211_hwsim_del_radio(struct mac80211_hwsim_data *data, + const char *hwname, + struct genl_info *info) +{ + hwsim_mcast_del_radio(data->idx, hwname, info); + debugfs_remove_recursive(data->debugfs); + ieee80211_unregister_hw(data->hw); + device_release_driver(data->dev); + device_unregister(data->dev); + ieee80211_free_hw(data->hw); +} + +static int mac80211_hwsim_get_radio(struct sk_buff *skb, + struct mac80211_hwsim_data *data, + u32 portid, u32 seq, + struct netlink_callback *cb, int flags) { - struct mac80211_hwsim_data *data = dat; - *val = data->group; + void *hdr; + struct hwsim_new_radio_params param = { }; + int res = -EMSGSIZE; + + hdr = genlmsg_put(skb, portid, seq, &hwsim_genl_family, flags, + HWSIM_CMD_GET_RADIO); + if (!hdr) + return -EMSGSIZE; + + if (cb) + genl_dump_check_consistent(cb, hdr, &hwsim_genl_family); + + if (data->alpha2[0] && data->alpha2[1]) + param.reg_alpha2 = data->alpha2; + + param.reg_strict = !!(data->hw->wiphy->regulatory_flags & + REGULATORY_STRICT_REG); + param.p2p_device = !!(data->hw->wiphy->interface_modes & + BIT(NL80211_IFTYPE_P2P_DEVICE)); + param.use_chanctx = data->use_chanctx; + param.regd = data->regd; + param.channels = data->channels; + param.hwname = wiphy_name(data->hw->wiphy); + + res = append_radio_msg(skb, data->idx, ¶m); + if (res < 0) + goto out_err; + + genlmsg_end(skb, hdr); return 0; + +out_err: + genlmsg_cancel(skb, hdr); + return res; } -static int hwsim_fops_group_write(void *dat, u64 val) +static void mac80211_hwsim_free(void) { - struct mac80211_hwsim_data *data = dat; - data->group = val; - return 0; + struct mac80211_hwsim_data *data; + + spin_lock_bh(&hwsim_radio_lock); + while ((data = list_first_entry_or_null(&hwsim_radios, + struct mac80211_hwsim_data, + list))) { + list_del(&data->list); + spin_unlock_bh(&hwsim_radio_lock); + mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), + NULL); + spin_lock_bh(&hwsim_radio_lock); + } + spin_unlock_bh(&hwsim_radio_lock); + class_destroy(hwsim_class); } -DEFINE_SIMPLE_ATTRIBUTE(hwsim_fops_group, - hwsim_fops_group_read, hwsim_fops_group_write, - "%llx\n"); +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->netdev_ops = &hwsim_netdev_ops; + dev->destructor = free_netdev; + ether_setup(dev); + dev->priv_flags |= IFF_NO_QUEUE; + dev->type = ARPHRD_IEEE80211_RADIOTAP; + eth_zero_addr(dev->dev_addr); + dev->dev_addr[0] = 0x12; +} -static struct mac80211_hwsim_data *get_hwsim_data_ref_from_addr( - struct mac_address *addr) +static struct mac80211_hwsim_data *get_hwsim_data_ref_from_addr(const u8 *addr) { struct mac80211_hwsim_data *data; bool _found = false; spin_lock_bh(&hwsim_radio_lock); list_for_each_entry(data, &hwsim_radios, list) { - if (memcmp(data->addresses[1].addr, addr, - sizeof(struct mac_address)) == 0) { + if (mac80211_hwsim_addr_match(data, addr)) { _found = true; break; } @@ -1922,28 +2712,27 @@ struct hwsim_tx_rate *tx_attempts; unsigned long ret_skb_ptr; struct sk_buff *skb, *tmp; - struct mac_address *src; + const u8 *src; unsigned int hwsim_flags; - int i; bool found = false; + if (info->snd_portid != wmediumd_portid) + return -EINVAL; + if (!info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER] || - !info->attrs[HWSIM_ATTR_FLAGS] || - !info->attrs[HWSIM_ATTR_COOKIE] || - !info->attrs[HWSIM_ATTR_SIGNAL] || - !info->attrs[HWSIM_ATTR_TX_INFO]) + !info->attrs[HWSIM_ATTR_FLAGS] || + !info->attrs[HWSIM_ATTR_COOKIE] || + !info->attrs[HWSIM_ATTR_SIGNAL] || + !info->attrs[HWSIM_ATTR_TX_INFO]) goto out; - src = (struct mac_address *)nla_data( - info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); + src = (void *)nla_data(info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); hwsim_flags = nla_get_u32(info->attrs[HWSIM_ATTR_FLAGS]); - ret_skb_ptr = nla_get_u64(info->attrs[HWSIM_ATTR_COOKIE]); data2 = get_hwsim_data_ref_from_addr(src); - - if (data2 == NULL) + if (!data2) goto out; /* look for the skb matching the cookie passed back from user */ @@ -1982,7 +2771,7 @@ (hwsim_flags & HWSIM_TX_STAT_ACK)) { if (skb->len >= 16) { hdr = (struct ieee80211_hdr *) skb->data; - mac80211_hwsim_monitor_ack(txi->rate_driver_data[0], + mac80211_hwsim_monitor_ack(data2->channel, hdr->addr2); } txi->flags |= IEEE80211_TX_STAT_ACK; @@ -1997,41 +2786,39 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, struct genl_info *info) { - struct mac80211_hwsim_data *data2; struct ieee80211_rx_status rx_status; - struct mac_address *dst; + const u8 *dst; int frame_data_len; - char *frame_data; + void *frame_data; struct sk_buff *skb = NULL; + if (info->snd_portid != wmediumd_portid) + return -EINVAL; + if (!info->attrs[HWSIM_ATTR_ADDR_RECEIVER] || !info->attrs[HWSIM_ATTR_FRAME] || !info->attrs[HWSIM_ATTR_RX_RATE] || !info->attrs[HWSIM_ATTR_SIGNAL]) goto out; - dst = (struct mac_address *)nla_data( - info->attrs[HWSIM_ATTR_ADDR_RECEIVER]); - + dst = (void *)nla_data(info->attrs[HWSIM_ATTR_ADDR_RECEIVER]); frame_data_len = nla_len(info->attrs[HWSIM_ATTR_FRAME]); - frame_data = (char *)nla_data(info->attrs[HWSIM_ATTR_FRAME]); + frame_data = (void *)nla_data(info->attrs[HWSIM_ATTR_FRAME]); /* Allocate new skb here */ skb = alloc_skb(frame_data_len, GFP_KERNEL); if (skb == NULL) goto err; - if (frame_data_len <= IEEE80211_MAX_DATA_LEN) { - /* Copy the data */ - memcpy(skb_put(skb, frame_data_len), frame_data, - frame_data_len); - } else + if (frame_data_len > IEEE80211_MAX_DATA_LEN) goto err; - data2 = get_hwsim_data_ref_from_addr(dst); + /* Copy the data */ + memcpy(skb_put(skb, frame_data_len), frame_data, frame_data_len); - if (data2 == NULL) + data2 = get_hwsim_data_ref_from_addr(dst); + if (!data2) goto out; /* check if radio is configured properly */ @@ -2039,20 +2826,24 @@ if (data2->idle || !data2->started) goto out; - /*A frame is received from user space*/ + /* A frame is received from user space */ memset(&rx_status, 0, sizeof(rx_status)); + /* TODO: Check ATTR_FREQ if it exists, and maybe throw away off-channel + * packets? + */ rx_status.freq = data2->channel->center_freq; rx_status.band = data2->channel->band; rx_status.rate_idx = nla_get_u32(info->attrs[HWSIM_ATTR_RX_RATE]); rx_status.signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]); memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + data2->rx_pkts++; + data2->rx_bytes += skb->len; ieee80211_rx_irqsafe(data2->hw, skb); return 0; err: printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); - goto out; out: dev_kfree_skb(skb); return -EINVAL; @@ -2061,8 +2852,24 @@ static int hwsim_register_received_nl(struct sk_buff *skb_2, struct genl_info *info) { - if (info == NULL) - goto out; + struct mac80211_hwsim_data *data; + int chans = 1; + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry(data, &hwsim_radios, list) + chans = max(chans, data->channels); + spin_unlock_bh(&hwsim_radio_lock); + + /* In the future we should revise the userspace API and allow it + * to set a flag that it does support multi-channel, then we can + * let this pass conditionally on the flag. + * For current userspace, prohibit it since it won't work right. + */ + if (chans > 1) + return -EOPNOTSUPP; + + if (wmediumd_portid) + return -EBUSY; wmediumd_portid = info->snd_portid; @@ -2070,13 +2877,154 @@ "switching to wmediumd mode with pid %d\n", info->snd_portid); return 0; -out: - printk(KERN_DEBUG "mac80211_hwsim: error occurred in %s\n", __func__); - return -EINVAL; +} + +static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info) +{ + struct hwsim_new_radio_params param = { 0 }; + + param.reg_strict = info->attrs[HWSIM_ATTR_REG_STRICT_REG]; + param.p2p_device = info->attrs[HWSIM_ATTR_SUPPORT_P2P_DEVICE]; + param.channels = channels; + param.destroy_on_close = + info->attrs[HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE]; + + if (info->attrs[HWSIM_ATTR_CHANNELS]) + param.channels = nla_get_u32(info->attrs[HWSIM_ATTR_CHANNELS]); + + if (info->attrs[HWSIM_ATTR_NO_VIF]) + param.no_vif = true; + + if (info->attrs[HWSIM_ATTR_RADIO_NAME]) + param.hwname = nla_data(info->attrs[HWSIM_ATTR_RADIO_NAME]); + + if (info->attrs[HWSIM_ATTR_USE_CHANCTX]) + param.use_chanctx = true; + else + param.use_chanctx = (param.channels > 1); + + if (info->attrs[HWSIM_ATTR_REG_HINT_ALPHA2]) + param.reg_alpha2 = + nla_data(info->attrs[HWSIM_ATTR_REG_HINT_ALPHA2]); + + if (info->attrs[HWSIM_ATTR_REG_CUSTOM_REG]) { + u32 idx = nla_get_u32(info->attrs[HWSIM_ATTR_REG_CUSTOM_REG]); + + if (idx >= ARRAY_SIZE(hwsim_world_regdom_custom)) + return -EINVAL; + param.regd = hwsim_world_regdom_custom[idx]; + } + + return mac80211_hwsim_new_radio(info, ¶m); +} + +static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info) +{ + struct mac80211_hwsim_data *data; + s64 idx = -1; + const char *hwname = NULL; + + if (info->attrs[HWSIM_ATTR_RADIO_ID]) + idx = nla_get_u32(info->attrs[HWSIM_ATTR_RADIO_ID]); + else if (info->attrs[HWSIM_ATTR_RADIO_NAME]) + hwname = (void *)nla_data(info->attrs[HWSIM_ATTR_RADIO_NAME]); + else + return -EINVAL; + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry(data, &hwsim_radios, list) { + if (idx >= 0) { + if (data->idx != idx) + continue; + } else { + if (strcmp(hwname, wiphy_name(data->hw->wiphy))) + continue; + } + + list_del(&data->list); + spin_unlock_bh(&hwsim_radio_lock); + mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), + info); + return 0; + } + spin_unlock_bh(&hwsim_radio_lock); + + return -ENODEV; +} + +static int hwsim_get_radio_nl(struct sk_buff *msg, struct genl_info *info) +{ + struct mac80211_hwsim_data *data; + struct sk_buff *skb; + int idx, res = -ENODEV; + + if (!info->attrs[HWSIM_ATTR_RADIO_ID]) + return -EINVAL; + idx = nla_get_u32(info->attrs[HWSIM_ATTR_RADIO_ID]); + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry(data, &hwsim_radios, list) { + if (data->idx != idx) + continue; + + skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb) { + res = -ENOMEM; + goto out_err; + } + + res = mac80211_hwsim_get_radio(skb, data, info->snd_portid, + info->snd_seq, NULL, 0); + if (res < 0) { + nlmsg_free(skb); + goto out_err; + } + + genlmsg_reply(skb, info); + break; + } + +out_err: + spin_unlock_bh(&hwsim_radio_lock); + + return res; +} + +static int hwsim_dump_radio_nl(struct sk_buff *skb, + struct netlink_callback *cb) +{ + int idx = cb->args[0]; + struct mac80211_hwsim_data *data = NULL; + int res; + + spin_lock_bh(&hwsim_radio_lock); + + if (idx == hwsim_radio_idx) + goto done; + + list_for_each_entry(data, &hwsim_radios, list) { + if (data->idx < idx) + continue; + + res = mac80211_hwsim_get_radio(skb, data, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, cb, + NLM_F_MULTI); + if (res < 0) + break; + + idx = data->idx + 1; + } + + cb->args[0] = idx; + +done: + spin_unlock_bh(&hwsim_radio_lock); + return skb->len; } /* Generic Netlink operations array */ -static struct genl_ops hwsim_ops[] = { +static const struct genl_ops hwsim_ops[] = { { .cmd = HWSIM_CMD_REGISTER, .policy = hwsim_genl_policy, @@ -2093,8 +3041,49 @@ .policy = hwsim_genl_policy, .doit = hwsim_tx_info_frame_received_nl, }, + { + .cmd = HWSIM_CMD_NEW_RADIO, + .policy = hwsim_genl_policy, + .doit = hwsim_new_radio_nl, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = HWSIM_CMD_DEL_RADIO, + .policy = hwsim_genl_policy, + .doit = hwsim_del_radio_nl, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = HWSIM_CMD_GET_RADIO, + .policy = hwsim_genl_policy, + .doit = hwsim_get_radio_nl, + .dumpit = hwsim_dump_radio_nl, + }, }; +static void destroy_radio(struct work_struct *work) +{ + struct mac80211_hwsim_data *data = + container_of(work, struct mac80211_hwsim_data, destroy_work); + + mac80211_hwsim_del_radio(data, wiphy_name(data->hw->wiphy), NULL); +} + +static void remove_user_radios(u32 portid) +{ + struct mac80211_hwsim_data *entry, *tmp; + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry_safe(entry, tmp, &hwsim_radios, list) { + if (entry->destroy_on_close && entry->portid == portid) { + list_del(&entry->list); + INIT_WORK(&entry->destroy_work, destroy_radio); + schedule_work(&entry->destroy_work); + } + } + spin_unlock_bh(&hwsim_radio_lock); +} + static int mac80211_hwsim_netlink_notify(struct notifier_block *nb, unsigned long state, void *_notify) @@ -2104,6 +3093,8 @@ if (state != NETLINK_URELEASE) return NOTIFY_DONE; + remove_user_radios(notify->portid); + if (notify->portid == wmediumd_portid) { printk(KERN_INFO "mac80211_hwsim: wmediumd released netlink" " socket, switching to perfect channel medium\n"); @@ -2121,20 +3112,19 @@ { int rc; - /* userspace test API hasn't been adjusted for multi-channel */ - if (channels > 1) - return 0; - printk(KERN_INFO "mac80211_hwsim: initializing netlink\n"); - rc = genl_register_family_with_ops(&hwsim_genl_family, - hwsim_ops, ARRAY_SIZE(hwsim_ops)); + rc = genl_register_family_with_ops_groups(&hwsim_genl_family, + hwsim_ops, + hwsim_mcgrps); if (rc) goto failure; rc = netlink_register_notifier(&hwsim_netlink_notifier); - if (rc) + if (rc) { + genl_unregister_family(&hwsim_genl_family); goto failure; + } return 0; @@ -2145,77 +3135,36 @@ static void hwsim_exit_netlink(void) { - int ret; - - /* userspace test API hasn't been adjusted for multi-channel */ - if (channels > 1) - return; - - printk(KERN_INFO "mac80211_hwsim: closing netlink\n"); /* unregister the notifier */ netlink_unregister_notifier(&hwsim_netlink_notifier); /* unregister the family */ - ret = genl_unregister_family(&hwsim_genl_family); - if (ret) - printk(KERN_DEBUG "mac80211_hwsim: " - "unregister family %i\n", ret); + genl_unregister_family(&hwsim_genl_family); } -static const struct ieee80211_iface_limit hwsim_if_limits[] = { - { .max = 1, .types = BIT(NL80211_IFTYPE_ADHOC) }, - { .max = 2048, .types = BIT(NL80211_IFTYPE_STATION) | - BIT(NL80211_IFTYPE_P2P_CLIENT) | -#ifdef CONFIG_MAC80211_MESH - BIT(NL80211_IFTYPE_MESH_POINT) | -#endif - BIT(NL80211_IFTYPE_AP) | - BIT(NL80211_IFTYPE_P2P_GO) }, - { .max = 1, .types = BIT(NL80211_IFTYPE_P2P_DEVICE) }, -}; - -static struct ieee80211_iface_combination hwsim_if_comb = { - .limits = hwsim_if_limits, - .n_limits = ARRAY_SIZE(hwsim_if_limits), - .max_interfaces = 2048, - .num_different_channels = 1, -}; - static int __init init_mac80211_hwsim(void) { - int i, err = 0; - u8 addr[ETH_ALEN]; - struct mac80211_hwsim_data *data; - struct ieee80211_hw *hw; - enum ieee80211_band band; + int i, err; - if (radios < 1 || radios > 100) + if (radios < 0 || radios > 100) return -EINVAL; if (channels < 1) return -EINVAL; - if (channels > 1) { - hwsim_if_comb.num_different_channels = channels; - mac80211_hwsim_ops.hw_scan = mac80211_hwsim_hw_scan; - mac80211_hwsim_ops.cancel_hw_scan = - mac80211_hwsim_cancel_hw_scan; - mac80211_hwsim_ops.sw_scan_start = NULL; - mac80211_hwsim_ops.sw_scan_complete = NULL; - mac80211_hwsim_ops.remain_on_channel = - mac80211_hwsim_roc; - mac80211_hwsim_ops.cancel_remain_on_channel = - mac80211_hwsim_croc; - mac80211_hwsim_ops.add_chanctx = - mac80211_hwsim_add_chanctx; - mac80211_hwsim_ops.remove_chanctx = - mac80211_hwsim_remove_chanctx; - mac80211_hwsim_ops.change_chanctx = - mac80211_hwsim_change_chanctx; - mac80211_hwsim_ops.assign_vif_chanctx = - mac80211_hwsim_assign_vif_chanctx; - mac80211_hwsim_ops.unassign_vif_chanctx = - mac80211_hwsim_unassign_vif_chanctx; - } + mac80211_hwsim_mchan_ops = mac80211_hwsim_ops; + mac80211_hwsim_mchan_ops.hw_scan = mac80211_hwsim_hw_scan; + mac80211_hwsim_mchan_ops.cancel_hw_scan = mac80211_hwsim_cancel_hw_scan; + mac80211_hwsim_mchan_ops.sw_scan_start = NULL; + mac80211_hwsim_mchan_ops.sw_scan_complete = NULL; + mac80211_hwsim_mchan_ops.remain_on_channel = mac80211_hwsim_roc; + mac80211_hwsim_mchan_ops.cancel_remain_on_channel = mac80211_hwsim_croc; + mac80211_hwsim_mchan_ops.add_chanctx = mac80211_hwsim_add_chanctx; + mac80211_hwsim_mchan_ops.remove_chanctx = mac80211_hwsim_remove_chanctx; + mac80211_hwsim_mchan_ops.change_chanctx = mac80211_hwsim_change_chanctx; + mac80211_hwsim_mchan_ops.assign_vif_chanctx = + mac80211_hwsim_assign_vif_chanctx; + mac80211_hwsim_mchan_ops.unassign_vif_chanctx = + mac80211_hwsim_unassign_vif_chanctx; spin_lock_init(&hwsim_radio_lock); INIT_LIST_HEAD(&hwsim_radios); @@ -2227,344 +3176,119 @@ hwsim_class = class_create(THIS_MODULE, "mac80211_hwsim"); if (IS_ERR(hwsim_class)) { err = PTR_ERR(hwsim_class); - goto failed_unregister_driver; + goto out_unregister_driver; } - memset(addr, 0, ETH_ALEN); - addr[0] = 0x02; + err = hwsim_init_netlink(); + if (err < 0) + goto out_unregister_driver; for (i = 0; i < radios; i++) { - printk(KERN_DEBUG "mac80211_hwsim: Initializing radio %d\n", - i); - hw = ieee80211_alloc_hw(sizeof(*data), &mac80211_hwsim_ops); - if (!hw) { - printk(KERN_DEBUG "mac80211_hwsim: ieee80211_alloc_hw " - "failed\n"); - err = -ENOMEM; - goto failed; - } - data = hw->priv; - data->hw = hw; - - data->dev = device_create(hwsim_class, NULL, 0, hw, - "hwsim%d", i); - if (IS_ERR(data->dev)) { - printk(KERN_DEBUG - "mac80211_hwsim: device_create failed (%ld)\n", - PTR_ERR(data->dev)); - err = -ENOMEM; - goto failed_drvdata; - } - data->dev->driver = &mac80211_hwsim_driver.driver; - err = device_bind_driver(data->dev); - if (err != 0) { - printk(KERN_DEBUG - "mac80211_hwsim: device_bind_driver failed (%d)\n", - err); - goto failed_hw; - } - - skb_queue_head_init(&data->pending); - - SET_IEEE80211_DEV(hw, data->dev); - addr[3] = i >> 8; - addr[4] = i; - memcpy(data->addresses[0].addr, addr, ETH_ALEN); - memcpy(data->addresses[1].addr, addr, ETH_ALEN); - data->addresses[1].addr[0] |= 0x40; - hw->wiphy->n_addresses = 2; - hw->wiphy->addresses = data->addresses; + struct hwsim_new_radio_params param = { 0 }; - hw->wiphy->iface_combinations = &hwsim_if_comb; - hw->wiphy->n_iface_combinations = 1; - - if (channels > 1) { - hw->wiphy->max_scan_ssids = 255; - hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; - hw->wiphy->max_remain_on_channel_duration = 1000; - } - - INIT_DELAYED_WORK(&data->roc_done, hw_roc_done); - INIT_DELAYED_WORK(&data->hw_scan, hw_scan_work); - - hw->channel_change_time = 1; - hw->queues = 5; - hw->offchannel_tx_hw_queue = 4; - hw->wiphy->interface_modes = - BIT(NL80211_IFTYPE_STATION) | - BIT(NL80211_IFTYPE_AP) | - BIT(NL80211_IFTYPE_P2P_CLIENT) | - BIT(NL80211_IFTYPE_P2P_GO) | - BIT(NL80211_IFTYPE_ADHOC) | - BIT(NL80211_IFTYPE_MESH_POINT) | - BIT(NL80211_IFTYPE_P2P_DEVICE); - - hw->flags = IEEE80211_HW_MFP_CAPABLE | - IEEE80211_HW_SIGNAL_DBM | - IEEE80211_HW_SUPPORTS_STATIC_SMPS | - IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS | - IEEE80211_HW_AMPDU_AGGREGATION | - IEEE80211_HW_WANT_MONITOR_VIF | - IEEE80211_HW_QUEUE_CONTROL; - if (rctbl) - hw->flags |= IEEE80211_HW_SUPPORTS_RC_TABLE; - - hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS | - WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - - /* 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); - hw->chanctx_data_size = sizeof(struct hwsim_chanctx_priv); - - 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)); - - 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); - sband->bitrates = data->rates; - sband->n_bitrates = ARRAY_SIZE(hwsim_rates); - break; - case IEEE80211_BAND_5GHZ: - sband->channels = data->channels_5ghz; - sband->n_channels = - ARRAY_SIZE(hwsim_channels_5ghz); - sband->bitrates = data->rates + 4; - sband->n_bitrates = ARRAY_SIZE(hwsim_rates) - 4; - break; - default: - continue; - } + param.channels = channels; - 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; - - sband->vht_cap.vht_supported = true; - sband->vht_cap.cap = - IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 | - IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ | - IEEE80211_VHT_CAP_RXLDPC | - IEEE80211_VHT_CAP_SHORT_GI_80 | - IEEE80211_VHT_CAP_SHORT_GI_160 | - IEEE80211_VHT_CAP_TXSTBC | - IEEE80211_VHT_CAP_RXSTBC_1 | - IEEE80211_VHT_CAP_RXSTBC_2 | - IEEE80211_VHT_CAP_RXSTBC_3 | - IEEE80211_VHT_CAP_RXSTBC_4 | - IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; - sband->vht_cap.vht_mcs.rx_mcs_map = - cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_8 << 0 | - IEEE80211_VHT_MCS_SUPPORT_0_8 << 2 | - IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 | - IEEE80211_VHT_MCS_SUPPORT_0_8 << 6 | - IEEE80211_VHT_MCS_SUPPORT_0_8 << 8 | - IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 | - IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 | - IEEE80211_VHT_MCS_SUPPORT_0_8 << 14); - sband->vht_cap.vht_mcs.tx_mcs_map = - sband->vht_cap.vht_mcs.rx_mcs_map; - } - /* By default all radios are belonging to the first group */ - data->group = 1; - mutex_init(&data->mutex); - - /* Enable frame retransmissions for lossy channels */ - hw->max_rates = 4; - hw->max_rate_tries = 11; - - /* 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->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; - wiphy_apply_custom_regulatory(hw->wiphy, - &hwsim_world_regdom_custom_01); - } - break; - case HWSIM_REGTEST_CUSTOM_WORLD: - hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; - wiphy_apply_custom_regulatory(hw->wiphy, - &hwsim_world_regdom_custom_01); - break; - case HWSIM_REGTEST_CUSTOM_WORLD_2: - if (i == 0) { - hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; - wiphy_apply_custom_regulatory(hw->wiphy, - &hwsim_world_regdom_custom_01); - } else if (i == 1) { - hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; - wiphy_apply_custom_regulatory(hw->wiphy, - &hwsim_world_regdom_custom_02); - } - break; - case HWSIM_REGTEST_STRICT_ALL: - hw->wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; - break; - case HWSIM_REGTEST_STRICT_FOLLOW: - case HWSIM_REGTEST_STRICT_AND_DRIVER_REG: - if (i == 0) - hw->wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; - break; - case HWSIM_REGTEST_ALL: - if (i == 0) { - hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; - wiphy_apply_custom_regulatory(hw->wiphy, - &hwsim_world_regdom_custom_01); - } else if (i == 1) { - hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; - wiphy_apply_custom_regulatory(hw->wiphy, - &hwsim_world_regdom_custom_02); - } else if (i == 4) - hw->wiphy->flags |= WIPHY_FLAG_STRICT_REGULATORY; - break; - default: - break; - } - - /* give the regulatory workqueue a chance to run */ - if (regtest) - schedule_timeout_interruptible(1); - err = ieee80211_register_hw(hw); - if (err < 0) { - printk(KERN_DEBUG "mac80211_hwsim: " - "ieee80211_register_hw failed (%d)\n", err); - goto failed_hw; - } - - /* Work to be done after to ieee80211_register_hw() */ - switch (regtest) { - case HWSIM_REGTEST_WORLD_ROAM: - case HWSIM_REGTEST_DISABLED: + if (i < ARRAY_SIZE(hwsim_alpha2s)) + param.reg_alpha2 = hwsim_alpha2s[i]; break; case HWSIM_REGTEST_DRIVER_REG_FOLLOW: if (!i) - regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + param.reg_alpha2 = hwsim_alpha2s[0]; break; - case HWSIM_REGTEST_DRIVER_REG_ALL: case HWSIM_REGTEST_STRICT_ALL: - regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + param.reg_strict = true; + case HWSIM_REGTEST_DRIVER_REG_ALL: + param.reg_alpha2 = hwsim_alpha2s[0]; break; - case HWSIM_REGTEST_DIFF_COUNTRY: - if (i < ARRAY_SIZE(hwsim_alpha2s)) - regulatory_hint(hw->wiphy, hwsim_alpha2s[i]); + case HWSIM_REGTEST_WORLD_ROAM: + if (i == 0) + param.regd = &hwsim_world_regdom_custom_01; break; case HWSIM_REGTEST_CUSTOM_WORLD: + param.regd = &hwsim_world_regdom_custom_01; + break; case HWSIM_REGTEST_CUSTOM_WORLD_2: - /* - * Nothing to be done for custom world regulatory - * domains after to ieee80211_register_hw - */ + if (i == 0) + param.regd = &hwsim_world_regdom_custom_01; + else if (i == 1) + param.regd = &hwsim_world_regdom_custom_02; break; case HWSIM_REGTEST_STRICT_FOLLOW: - if (i == 0) - regulatory_hint(hw->wiphy, hwsim_alpha2s[0]); + if (i == 0) { + param.reg_strict = true; + param.reg_alpha2 = 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]); + if (i == 0) { + param.reg_strict = true; + param.reg_alpha2 = hwsim_alpha2s[0]; + } else if (i == 1) { + param.reg_alpha2 = 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]); + switch (i) { + case 0: + param.regd = &hwsim_world_regdom_custom_01; + break; + case 1: + param.regd = &hwsim_world_regdom_custom_02; + break; + case 2: + param.reg_alpha2 = hwsim_alpha2s[0]; + break; + case 3: + param.reg_alpha2 = hwsim_alpha2s[1]; + break; + case 4: + param.reg_strict = true; + param.reg_alpha2 = hwsim_alpha2s[2]; + break; + } break; default: break; } - wiphy_debug(hw->wiphy, "hwaddr %pm registered\n", - hw->wiphy->perm_addr); + param.p2p_device = support_p2p_device; + param.use_chanctx = channels > 1; - data->debugfs = debugfs_create_dir("hwsim", - hw->wiphy->debugfsdir); - data->debugfs_ps = debugfs_create_file("ps", 0666, - data->debugfs, data, - &hwsim_fops_ps); - data->debugfs_group = debugfs_create_file("group", 0666, - data->debugfs, data, - &hwsim_fops_group); - - tasklet_hrtimer_init(&data->beacon_timer, - mac80211_hwsim_beacon, - CLOCK_REALTIME, HRTIMER_MODE_ABS); - - list_add_tail(&data->list, &hwsim_radios); + err = mac80211_hwsim_new_radio(NULL, ¶m); + if (err < 0) + goto out_free_radios; } - hwsim_mon = alloc_netdev(0, "hwsim%d", hwsim_mon_setup); - if (hwsim_mon == NULL) - goto failed; + hwsim_mon = alloc_netdev(0, "hwsim%d", NET_NAME_UNKNOWN, + hwsim_mon_setup); + if (hwsim_mon == NULL) { + err = -ENOMEM; + goto out_free_radios; + } rtnl_lock(); - err = dev_alloc_name(hwsim_mon, hwsim_mon->name); - if (err < 0) - goto failed_mon; - + if (err < 0) { + rtnl_unlock(); + goto out_free_radios; + } err = register_netdevice(hwsim_mon); - if (err < 0) - goto failed_mon; - + if (err < 0) { + rtnl_unlock(); + goto out_free_mon; + } rtnl_unlock(); - err = hwsim_init_netlink(); - if (err < 0) - goto failed_nl; - return 0; -failed_nl: - printk(KERN_DEBUG "mac_80211_hwsim: failed initializing netlink\n"); - return err; - -failed_mon: - rtnl_unlock(); +out_free_mon: free_netdev(hwsim_mon); +out_free_radios: mac80211_hwsim_free(); - return err; - -failed_hw: - device_unregister(data->dev); -failed_drvdata: - ieee80211_free_hw(hw); -failed: - mac80211_hwsim_free(); -failed_unregister_driver: +out_unregister_driver: platform_driver_unregister(&mac80211_hwsim_driver); return err; }