* frame on another channel
*
* @testmode_cmd: run a test mode command
+ * @testmode_dump: Implement a test mode dump. The cb->args[2] and up may be
+ * used by the function, but 0 and 1 must not be touched. Additionally,
+ * return error codes other than -ENOBUFS and -ENOENT will terminate the
+ * dump and return to userspace with an error, so be careful. If any data
+ * was passed in from userspace then the data/len arguments will be present
+ * and point to the data contained in %NL80211_ATTR_TESTDATA.
*
* @set_bitrate_mask: set the bitrate mask configuration
*
#ifdef CONFIG_NL80211_TESTMODE
int (*testmode_cmd)(struct wiphy *wiphy, void *data, int len);
+ int (*testmode_dump)(struct wiphy *wiphy, struct sk_buff *skb,
+ struct netlink_callback *cb,
+ void *data, int len);
#endif
int (*set_bitrate_mask)(struct wiphy *wiphy,
void cfg80211_testmode_event(struct sk_buff *skb, gfp_t gfp);
#define CFG80211_TESTMODE_CMD(cmd) .testmode_cmd = (cmd),
+#define CFG80211_TESTMODE_DUMP(cmd) .testmode_dump = (cmd),
#else
#define CFG80211_TESTMODE_CMD(cmd)
+#define CFG80211_TESTMODE_DUMP(cmd)
#endif
/**
*
* @testmode_cmd: Implement a cfg80211 test mode command.
* The callback can sleep.
+ * @testmode_dump: Implement a cfg80211 test mode dump. The callback can sleep.
*
* @flush: Flush all pending frames from the hardware queue, making sure
* that the hardware queues are empty. If the parameter @drop is set
void (*set_coverage_class)(struct ieee80211_hw *hw, u8 coverage_class);
#ifdef CONFIG_NL80211_TESTMODE
int (*testmode_cmd)(struct ieee80211_hw *hw, void *data, int len);
+ int (*testmode_dump)(struct ieee80211_hw *hw, struct sk_buff *skb,
+ struct netlink_callback *cb,
+ void *data, int len);
#endif
void (*flush)(struct ieee80211_hw *hw, bool drop);
void (*channel_switch)(struct ieee80211_hw *hw,
return local->ops->testmode_cmd(&local->hw, data, len);
}
+
+static int ieee80211_testmode_dump(struct wiphy *wiphy,
+ struct sk_buff *skb,
+ struct netlink_callback *cb,
+ void *data, int len)
+{
+ struct ieee80211_local *local = wiphy_priv(wiphy);
+
+ if (!local->ops->testmode_dump)
+ return -EOPNOTSUPP;
+
+ return local->ops->testmode_dump(&local->hw, skb, cb, data, len);
+}
#endif
int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata,
.set_wds_peer = ieee80211_set_wds_peer,
.rfkill_poll = ieee80211_rfkill_poll,
CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
+ CFG80211_TESTMODE_DUMP(ieee80211_testmode_dump)
.set_power_mgmt = ieee80211_set_power_mgmt,
.set_bitrate_mask = ieee80211_set_bitrate_mask,
.remain_on_channel = ieee80211_remain_on_channel,
return err;
}
+static int nl80211_testmode_dump(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct cfg80211_registered_device *dev;
+ int err;
+ long phy_idx;
+ void *data = NULL;
+ int data_len = 0;
+
+ if (cb->args[0]) {
+ /*
+ * 0 is a valid index, but not valid for args[0],
+ * so we need to offset by 1.
+ */
+ phy_idx = cb->args[0] - 1;
+ } else {
+ err = nlmsg_parse(cb->nlh, GENL_HDRLEN + nl80211_fam.hdrsize,
+ nl80211_fam.attrbuf, nl80211_fam.maxattr,
+ nl80211_policy);
+ if (err)
+ return err;
+ if (!nl80211_fam.attrbuf[NL80211_ATTR_WIPHY])
+ return -EINVAL;
+ phy_idx = nla_get_u32(nl80211_fam.attrbuf[NL80211_ATTR_WIPHY]);
+ if (nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA])
+ cb->args[1] =
+ (long)nl80211_fam.attrbuf[NL80211_ATTR_TESTDATA];
+ }
+
+ if (cb->args[1]) {
+ data = nla_data((void *)cb->args[1]);
+ data_len = nla_len((void *)cb->args[1]);
+ }
+
+ mutex_lock(&cfg80211_mutex);
+ dev = cfg80211_rdev_by_wiphy_idx(phy_idx);
+ if (!dev) {
+ mutex_unlock(&cfg80211_mutex);
+ return -ENOENT;
+ }
+ cfg80211_lock_rdev(dev);
+ mutex_unlock(&cfg80211_mutex);
+
+ if (!dev->ops->testmode_dump) {
+ err = -EOPNOTSUPP;
+ goto out_err;
+ }
+
+ while (1) {
+ void *hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI,
+ NL80211_CMD_TESTMODE);
+ struct nlattr *tmdata;
+
+ if (nla_put_u32(skb, NL80211_ATTR_WIPHY, dev->wiphy_idx) < 0) {
+ genlmsg_cancel(skb, hdr);
+ break;
+ }
+
+ tmdata = nla_nest_start(skb, NL80211_ATTR_TESTDATA);
+ if (!tmdata) {
+ genlmsg_cancel(skb, hdr);
+ break;
+ }
+ err = dev->ops->testmode_dump(&dev->wiphy, skb, cb,
+ data, data_len);
+ nla_nest_end(skb, tmdata);
+
+ if (err == -ENOBUFS || err == -ENOENT) {
+ genlmsg_cancel(skb, hdr);
+ break;
+ } else if (err) {
+ genlmsg_cancel(skb, hdr);
+ goto out_err;
+ }
+
+ genlmsg_end(skb, hdr);
+ }
+
+ err = skb->len;
+ /* see above */
+ cb->args[0] = phy_idx + 1;
+ out_err:
+ cfg80211_unlock_rdev(dev);
+ return err;
+}
+
static struct sk_buff *
__cfg80211_testmode_alloc_skb(struct cfg80211_registered_device *rdev,
int approxlen, u32 pid, u32 seq, gfp_t gfp)
{
.cmd = NL80211_CMD_TESTMODE,
.doit = nl80211_testmode_do,
+ .dumpit = nl80211_testmode_dump,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_WIPHY |