|
@@ -403,6 +403,14 @@ nl80211_wowlan_tcp_policy[NUM_NL80211_WOWLAN_TCP] = {
|
|
|
[NL80211_WOWLAN_TCP_WAKE_MASK] = { .len = 1 },
|
|
|
};
|
|
|
|
|
|
+/* policy for coalesce rule attributes */
|
|
|
+static const struct nla_policy
|
|
|
+nl80211_coalesce_policy[NUM_NL80211_ATTR_COALESCE_RULE] = {
|
|
|
+ [NL80211_ATTR_COALESCE_RULE_DELAY] = { .type = NLA_U32 },
|
|
|
+ [NL80211_ATTR_COALESCE_RULE_CONDITION] = { .type = NLA_U32 },
|
|
|
+ [NL80211_ATTR_COALESCE_RULE_PKT_PATTERN] = { .type = NLA_NESTED },
|
|
|
+};
|
|
|
+
|
|
|
/* policy for GTK rekey offload attributes */
|
|
|
static const struct nla_policy
|
|
|
nl80211_rekey_policy[NUM_NL80211_REKEY_DATA] = {
|
|
@@ -995,6 +1003,27 @@ static int nl80211_send_wowlan(struct sk_buff *msg,
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
+static int nl80211_send_coalesce(struct sk_buff *msg,
|
|
|
+ struct cfg80211_registered_device *dev)
|
|
|
+{
|
|
|
+ struct nl80211_coalesce_rule_support rule;
|
|
|
+
|
|
|
+ if (!dev->wiphy.coalesce)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ rule.max_rules = dev->wiphy.coalesce->n_rules;
|
|
|
+ rule.max_delay = dev->wiphy.coalesce->max_delay;
|
|
|
+ rule.pat.max_patterns = dev->wiphy.coalesce->n_patterns;
|
|
|
+ rule.pat.min_pattern_len = dev->wiphy.coalesce->pattern_min_len;
|
|
|
+ rule.pat.max_pattern_len = dev->wiphy.coalesce->pattern_max_len;
|
|
|
+ rule.pat.max_pkt_offset = dev->wiphy.coalesce->max_pkt_offset;
|
|
|
+
|
|
|
+ if (nla_put(msg, NL80211_ATTR_COALESCE_RULE, sizeof(rule), &rule))
|
|
|
+ return -ENOBUFS;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int nl80211_send_band_rateinfo(struct sk_buff *msg,
|
|
|
struct ieee80211_supported_band *sband)
|
|
|
{
|
|
@@ -1513,6 +1542,12 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
|
|
|
dev->wiphy.vht_capa_mod_mask))
|
|
|
goto nla_put_failure;
|
|
|
|
|
|
+ state->split_start++;
|
|
|
+ break;
|
|
|
+ case 10:
|
|
|
+ if (nl80211_send_coalesce(msg, dev))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
/* done */
|
|
|
state->split_start = 0;
|
|
|
break;
|
|
@@ -8043,6 +8078,264 @@ static int nl80211_set_wowlan(struct sk_buff *skb, struct genl_info *info)
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
+static int nl80211_send_coalesce_rules(struct sk_buff *msg,
|
|
|
+ struct cfg80211_registered_device *rdev)
|
|
|
+{
|
|
|
+ struct nlattr *nl_pats, *nl_pat, *nl_rule, *nl_rules;
|
|
|
+ int i, j, pat_len;
|
|
|
+ struct cfg80211_coalesce_rules *rule;
|
|
|
+
|
|
|
+ if (!rdev->coalesce->n_rules)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ nl_rules = nla_nest_start(msg, NL80211_ATTR_COALESCE_RULE);
|
|
|
+ if (!nl_rules)
|
|
|
+ return -ENOBUFS;
|
|
|
+
|
|
|
+ for (i = 0; i < rdev->coalesce->n_rules; i++) {
|
|
|
+ nl_rule = nla_nest_start(msg, i + 1);
|
|
|
+ if (!nl_rule)
|
|
|
+ return -ENOBUFS;
|
|
|
+
|
|
|
+ rule = &rdev->coalesce->rules[i];
|
|
|
+ if (nla_put_u32(msg, NL80211_ATTR_COALESCE_RULE_DELAY,
|
|
|
+ rule->delay))
|
|
|
+ return -ENOBUFS;
|
|
|
+
|
|
|
+ if (nla_put_u32(msg, NL80211_ATTR_COALESCE_RULE_CONDITION,
|
|
|
+ rule->condition))
|
|
|
+ return -ENOBUFS;
|
|
|
+
|
|
|
+ nl_pats = nla_nest_start(msg,
|
|
|
+ NL80211_ATTR_COALESCE_RULE_PKT_PATTERN);
|
|
|
+ if (!nl_pats)
|
|
|
+ return -ENOBUFS;
|
|
|
+
|
|
|
+ for (j = 0; j < rule->n_patterns; j++) {
|
|
|
+ nl_pat = nla_nest_start(msg, j + 1);
|
|
|
+ if (!nl_pat)
|
|
|
+ return -ENOBUFS;
|
|
|
+ pat_len = rule->patterns[j].pattern_len;
|
|
|
+ if (nla_put(msg, NL80211_PKTPAT_MASK,
|
|
|
+ DIV_ROUND_UP(pat_len, 8),
|
|
|
+ rule->patterns[j].mask) ||
|
|
|
+ nla_put(msg, NL80211_PKTPAT_PATTERN, pat_len,
|
|
|
+ rule->patterns[j].pattern) ||
|
|
|
+ nla_put_u32(msg, NL80211_PKTPAT_OFFSET,
|
|
|
+ rule->patterns[j].pkt_offset))
|
|
|
+ return -ENOBUFS;
|
|
|
+ nla_nest_end(msg, nl_pat);
|
|
|
+ }
|
|
|
+ nla_nest_end(msg, nl_pats);
|
|
|
+ nla_nest_end(msg, nl_rule);
|
|
|
+ }
|
|
|
+ nla_nest_end(msg, nl_rules);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_get_coalesce(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ struct sk_buff *msg;
|
|
|
+ void *hdr;
|
|
|
+
|
|
|
+ if (!rdev->wiphy.coalesce)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
|
|
+ if (!msg)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0,
|
|
|
+ NL80211_CMD_GET_COALESCE);
|
|
|
+ if (!hdr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ if (rdev->coalesce && nl80211_send_coalesce_rules(msg, rdev))
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ genlmsg_end(msg, hdr);
|
|
|
+ return genlmsg_reply(msg, info);
|
|
|
+
|
|
|
+nla_put_failure:
|
|
|
+ nlmsg_free(msg);
|
|
|
+ return -ENOBUFS;
|
|
|
+}
|
|
|
+
|
|
|
+void cfg80211_rdev_free_coalesce(struct cfg80211_registered_device *rdev)
|
|
|
+{
|
|
|
+ struct cfg80211_coalesce *coalesce = rdev->coalesce;
|
|
|
+ int i, j;
|
|
|
+ struct cfg80211_coalesce_rules *rule;
|
|
|
+
|
|
|
+ if (!coalesce)
|
|
|
+ return;
|
|
|
+
|
|
|
+ for (i = 0; i < coalesce->n_rules; i++) {
|
|
|
+ rule = &coalesce->rules[i];
|
|
|
+ for (j = 0; j < rule->n_patterns; j++)
|
|
|
+ kfree(rule->patterns[j].mask);
|
|
|
+ kfree(rule->patterns);
|
|
|
+ }
|
|
|
+ kfree(coalesce->rules);
|
|
|
+ kfree(coalesce);
|
|
|
+ rdev->coalesce = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_parse_coalesce_rule(struct cfg80211_registered_device *rdev,
|
|
|
+ struct nlattr *rule,
|
|
|
+ struct cfg80211_coalesce_rules *new_rule)
|
|
|
+{
|
|
|
+ int err, i;
|
|
|
+ const struct wiphy_coalesce_support *coalesce = rdev->wiphy.coalesce;
|
|
|
+ struct nlattr *tb[NUM_NL80211_ATTR_COALESCE_RULE], *pat;
|
|
|
+ int rem, pat_len, mask_len, pkt_offset, n_patterns = 0;
|
|
|
+ struct nlattr *pat_tb[NUM_NL80211_PKTPAT];
|
|
|
+
|
|
|
+ err = nla_parse(tb, NL80211_ATTR_COALESCE_RULE_MAX, nla_data(rule),
|
|
|
+ nla_len(rule), nl80211_coalesce_policy);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ if (tb[NL80211_ATTR_COALESCE_RULE_DELAY])
|
|
|
+ new_rule->delay =
|
|
|
+ nla_get_u32(tb[NL80211_ATTR_COALESCE_RULE_DELAY]);
|
|
|
+ if (new_rule->delay > coalesce->max_delay)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (tb[NL80211_ATTR_COALESCE_RULE_CONDITION])
|
|
|
+ new_rule->condition =
|
|
|
+ nla_get_u32(tb[NL80211_ATTR_COALESCE_RULE_CONDITION]);
|
|
|
+ if (new_rule->condition != NL80211_COALESCE_CONDITION_MATCH &&
|
|
|
+ new_rule->condition != NL80211_COALESCE_CONDITION_NO_MATCH)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!tb[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ nla_for_each_nested(pat, tb[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN],
|
|
|
+ rem)
|
|
|
+ n_patterns++;
|
|
|
+ if (n_patterns > coalesce->n_patterns)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ new_rule->patterns = kcalloc(n_patterns, sizeof(new_rule->patterns[0]),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!new_rule->patterns)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ new_rule->n_patterns = n_patterns;
|
|
|
+ i = 0;
|
|
|
+
|
|
|
+ nla_for_each_nested(pat, tb[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN],
|
|
|
+ rem) {
|
|
|
+ nla_parse(pat_tb, MAX_NL80211_PKTPAT, nla_data(pat),
|
|
|
+ nla_len(pat), NULL);
|
|
|
+ if (!pat_tb[NL80211_PKTPAT_MASK] ||
|
|
|
+ !pat_tb[NL80211_PKTPAT_PATTERN])
|
|
|
+ return -EINVAL;
|
|
|
+ pat_len = nla_len(pat_tb[NL80211_PKTPAT_PATTERN]);
|
|
|
+ mask_len = DIV_ROUND_UP(pat_len, 8);
|
|
|
+ if (nla_len(pat_tb[NL80211_PKTPAT_MASK]) != mask_len)
|
|
|
+ return -EINVAL;
|
|
|
+ if (pat_len > coalesce->pattern_max_len ||
|
|
|
+ pat_len < coalesce->pattern_min_len)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!pat_tb[NL80211_PKTPAT_OFFSET])
|
|
|
+ pkt_offset = 0;
|
|
|
+ else
|
|
|
+ pkt_offset = nla_get_u32(pat_tb[NL80211_PKTPAT_OFFSET]);
|
|
|
+ if (pkt_offset > coalesce->max_pkt_offset)
|
|
|
+ return -EINVAL;
|
|
|
+ new_rule->patterns[i].pkt_offset = pkt_offset;
|
|
|
+
|
|
|
+ new_rule->patterns[i].mask =
|
|
|
+ kmalloc(mask_len + pat_len, GFP_KERNEL);
|
|
|
+ if (!new_rule->patterns[i].mask)
|
|
|
+ return -ENOMEM;
|
|
|
+ new_rule->patterns[i].pattern =
|
|
|
+ new_rule->patterns[i].mask + mask_len;
|
|
|
+ memcpy(new_rule->patterns[i].mask,
|
|
|
+ nla_data(pat_tb[NL80211_PKTPAT_MASK]), mask_len);
|
|
|
+ new_rule->patterns[i].pattern_len = pat_len;
|
|
|
+ memcpy(new_rule->patterns[i].pattern,
|
|
|
+ nla_data(pat_tb[NL80211_PKTPAT_PATTERN]), pat_len);
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int nl80211_set_coalesce(struct sk_buff *skb, struct genl_info *info)
|
|
|
+{
|
|
|
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
|
+ const struct wiphy_coalesce_support *coalesce = rdev->wiphy.coalesce;
|
|
|
+ struct cfg80211_coalesce new_coalesce = {};
|
|
|
+ struct cfg80211_coalesce *n_coalesce;
|
|
|
+ int err, rem_rule, n_rules = 0, i, j;
|
|
|
+ struct nlattr *rule;
|
|
|
+ struct cfg80211_coalesce_rules *tmp_rule;
|
|
|
+
|
|
|
+ if (!rdev->wiphy.coalesce || !rdev->ops->set_coalesce)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+
|
|
|
+ if (!info->attrs[NL80211_ATTR_COALESCE_RULE]) {
|
|
|
+ cfg80211_rdev_free_coalesce(rdev);
|
|
|
+ rdev->ops->set_coalesce(&rdev->wiphy, NULL);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ nla_for_each_nested(rule, info->attrs[NL80211_ATTR_COALESCE_RULE],
|
|
|
+ rem_rule)
|
|
|
+ n_rules++;
|
|
|
+ if (n_rules > coalesce->n_rules)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ new_coalesce.rules = kcalloc(n_rules, sizeof(new_coalesce.rules[0]),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!new_coalesce.rules)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ new_coalesce.n_rules = n_rules;
|
|
|
+ i = 0;
|
|
|
+
|
|
|
+ nla_for_each_nested(rule, info->attrs[NL80211_ATTR_COALESCE_RULE],
|
|
|
+ rem_rule) {
|
|
|
+ err = nl80211_parse_coalesce_rule(rdev, rule,
|
|
|
+ &new_coalesce.rules[i]);
|
|
|
+ if (err)
|
|
|
+ goto error;
|
|
|
+
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = rdev->ops->set_coalesce(&rdev->wiphy, &new_coalesce);
|
|
|
+ if (err)
|
|
|
+ goto error;
|
|
|
+
|
|
|
+ n_coalesce = kmemdup(&new_coalesce, sizeof(new_coalesce), GFP_KERNEL);
|
|
|
+ if (!n_coalesce) {
|
|
|
+ err = -ENOMEM;
|
|
|
+ goto error;
|
|
|
+ }
|
|
|
+ cfg80211_rdev_free_coalesce(rdev);
|
|
|
+ rdev->coalesce = n_coalesce;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+error:
|
|
|
+ for (i = 0; i < new_coalesce.n_rules; i++) {
|
|
|
+ tmp_rule = &new_coalesce.rules[i];
|
|
|
+ for (j = 0; j < tmp_rule->n_patterns; j++)
|
|
|
+ kfree(tmp_rule->patterns[j].mask);
|
|
|
+ kfree(tmp_rule->patterns);
|
|
|
+ }
|
|
|
+ kfree(new_coalesce.rules);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
static int nl80211_set_rekey_data(struct sk_buff *skb, struct genl_info *info)
|
|
|
{
|
|
|
struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
|
@@ -9050,6 +9343,21 @@ static struct genl_ops nl80211_ops[] = {
|
|
|
.flags = GENL_ADMIN_PERM,
|
|
|
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
|
|
NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_GET_COALESCE,
|
|
|
+ .doit = nl80211_get_coalesce,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WIPHY |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .cmd = NL80211_CMD_SET_COALESCE,
|
|
|
+ .doit = nl80211_set_coalesce,
|
|
|
+ .policy = nl80211_policy,
|
|
|
+ .flags = GENL_ADMIN_PERM,
|
|
|
+ .internal_flags = NL80211_FLAG_NEED_WIPHY |
|
|
|
+ NL80211_FLAG_NEED_RTNL,
|
|
|
}
|
|
|
};
|
|
|
|