| From: Carl Huang <cjhuang@codeaurora.org> |
| Date: Thu, 3 Dec 2020 05:37:26 -0500 |
| Subject: [PATCH] nl80211: add common API to configure SAR power limitations |
| |
| NL80211_CMD_SET_SAR_SPECS is added to configure SAR from |
| user space. NL80211_ATTR_SAR_SPEC is used to pass the SAR |
| power specification when used with NL80211_CMD_SET_SAR_SPECS. |
| |
| Wireless driver needs to register SAR type, supported frequency |
| ranges to wiphy, so user space can query it. The index in |
| frequency range is used to specify which sub band the power |
| limitation applies to. The SAR type is for compatibility, so later |
| other SAR mechanism can be implemented without breaking the user |
| space SAR applications. |
| |
| Normal process is user space queries the SAR capability, and |
| gets the index of supported frequency ranges and associates the |
| power limitation with this index and sends to kernel. |
| |
| Here is an example of message send to kernel: |
| 8c 00 00 00 08 00 01 00 00 00 00 00 38 00 2b 81 |
| 08 00 01 00 00 00 00 00 2c 00 02 80 14 00 00 80 |
| 08 00 02 00 00 00 00 00 08 00 01 00 38 00 00 00 |
| 14 00 01 80 08 00 02 00 01 00 00 00 08 00 01 00 |
| 48 00 00 00 |
| |
| NL80211_CMD_SET_SAR_SPECS: 0x8c |
| NL80211_ATTR_WIPHY: 0x01(phy idx is 0) |
| NL80211_ATTR_SAR_SPEC: 0x812b (NLA_NESTED) |
| NL80211_SAR_ATTR_TYPE: 0x00 (NL80211_SAR_TYPE_POWER) |
| NL80211_SAR_ATTR_SPECS: 0x8002 (NLA_NESTED) |
| freq range 0 power: 0x38 in 0.25dbm unit (14dbm) |
| freq range 1 power: 0x48 in 0.25dbm unit (18dbm) |
| |
| Signed-off-by: Carl Huang <cjhuang@codeaurora.org> |
| Reviewed-by: Brian Norris <briannorris@chromium.org> |
| Reviewed-by: Abhishek Kumar <kuabhs@chromium.org> |
| Link: https://lore.kernel.org/r/20201203103728.3034-2-cjhuang@codeaurora.org |
| [minor edits, NLA parse cleanups] |
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> |
| --- |
| |
| --- a/include/net/cfg80211.h |
| +++ b/include/net/cfg80211.h |
| @@ -1737,6 +1737,54 @@ struct station_info { |
| u8 connected_to_as; |
| }; |
| |
| +/** |
| + * struct cfg80211_sar_sub_specs - sub specs limit |
| + * @power: power limitation in 0.25dbm |
| + * @freq_range_index: index the power limitation applies to |
| + */ |
| +struct cfg80211_sar_sub_specs { |
| + s32 power; |
| + u32 freq_range_index; |
| +}; |
| + |
| +/** |
| + * struct cfg80211_sar_specs - sar limit specs |
| + * @type: it's set with power in 0.25dbm or other types |
| + * @num_sub_specs: number of sar sub specs |
| + * @sub_specs: memory to hold the sar sub specs |
| + */ |
| +struct cfg80211_sar_specs { |
| + enum nl80211_sar_type type; |
| + u32 num_sub_specs; |
| + struct cfg80211_sar_sub_specs sub_specs[]; |
| +}; |
| + |
| + |
| +/** |
| + * @struct cfg80211_sar_chan_ranges - sar frequency ranges |
| + * @start_freq: start range edge frequency |
| + * @end_freq: end range edge frequency |
| + */ |
| +struct cfg80211_sar_freq_ranges { |
| + u32 start_freq; |
| + u32 end_freq; |
| +}; |
| + |
| +/** |
| + * struct cfg80211_sar_capa - sar limit capability |
| + * @type: it's set via power in 0.25dbm or other types |
| + * @num_freq_ranges: number of frequency ranges |
| + * @freq_ranges: memory to hold the freq ranges. |
| + * |
| + * Note: WLAN driver may append new ranges or split an existing |
| + * range to small ones and then append them. |
| + */ |
| +struct cfg80211_sar_capa { |
| + enum nl80211_sar_type type; |
| + u32 num_freq_ranges; |
| + const struct cfg80211_sar_freq_ranges *freq_ranges; |
| +}; |
| + |
| #if IS_ENABLED(CPTCFG_CFG80211) |
| /** |
| * cfg80211_get_station - retrieve information about a given station |
| @@ -4259,6 +4307,8 @@ struct cfg80211_ops { |
| struct cfg80211_tid_config *tid_conf); |
| int (*reset_tid_config)(struct wiphy *wiphy, struct net_device *dev, |
| const u8 *peer, u8 tids); |
| + int (*set_sar_specs)(struct wiphy *wiphy, |
| + struct cfg80211_sar_specs *sar); |
| }; |
| |
| /* |
| @@ -5030,6 +5080,8 @@ struct wiphy { |
| |
| u8 max_data_retry_count; |
| |
| + const struct cfg80211_sar_capa *sar_capa; |
| + |
| char priv[] __aligned(NETDEV_ALIGN); |
| }; |
| |
| --- a/net/wireless/nl80211.c |
| +++ b/net/wireless/nl80211.c |
| @@ -405,6 +405,18 @@ nl80211_unsol_bcast_probe_resp_policy[NL |
| .len = IEEE80211_MAX_DATA_LEN } |
| }; |
| |
| +static const struct nla_policy |
| +sar_specs_policy[NL80211_SAR_ATTR_SPECS_MAX + 1] = { |
| + [NL80211_SAR_ATTR_SPECS_POWER] = { .type = NLA_S32 }, |
| + [NL80211_SAR_ATTR_SPECS_RANGE_INDEX] = {.type = NLA_U32 }, |
| +}; |
| + |
| +static const struct nla_policy |
| +sar_policy[NL80211_SAR_ATTR_MAX + 1] = { |
| + [NL80211_SAR_ATTR_TYPE] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_SAR_TYPE), |
| + [NL80211_SAR_ATTR_SPECS] = NLA_POLICY_NESTED_ARRAY(sar_specs_policy), |
| +}; |
| + |
| static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { |
| [0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD }, |
| [NL80211_ATTR_WIPHY] = { .type = NLA_U32 }, |
| @@ -744,6 +756,7 @@ static const struct nla_policy nl80211_p |
| [NL80211_ATTR_SAE_PWE] = |
| NLA_POLICY_RANGE(NLA_U8, NL80211_SAE_PWE_HUNT_AND_PECK, |
| NL80211_SAE_PWE_BOTH), |
| + [NL80211_ATTR_SAR_SPEC] = NLA_POLICY_NESTED(sar_policy), |
| [NL80211_ATTR_RECONNECT_REQUESTED] = { .type = NLA_REJECT }, |
| }; |
| |
| @@ -2122,6 +2135,56 @@ fail: |
| return -ENOBUFS; |
| } |
| |
| +static int |
| +nl80211_put_sar_specs(struct cfg80211_registered_device *rdev, |
| + struct sk_buff *msg) |
| +{ |
| + struct nlattr *sar_capa, *specs, *sub_freq_range; |
| + u8 num_freq_ranges; |
| + int i; |
| + |
| + if (!rdev->wiphy.sar_capa) |
| + return 0; |
| + |
| + num_freq_ranges = rdev->wiphy.sar_capa->num_freq_ranges; |
| + |
| + sar_capa = nla_nest_start(msg, NL80211_ATTR_SAR_SPEC); |
| + if (!sar_capa) |
| + return -ENOSPC; |
| + |
| + if (nla_put_u32(msg, NL80211_SAR_ATTR_TYPE, rdev->wiphy.sar_capa->type)) |
| + goto fail; |
| + |
| + specs = nla_nest_start(msg, NL80211_SAR_ATTR_SPECS); |
| + if (!specs) |
| + goto fail; |
| + |
| + /* report supported freq_ranges */ |
| + for (i = 0; i < num_freq_ranges; i++) { |
| + sub_freq_range = nla_nest_start(msg, i + 1); |
| + if (!sub_freq_range) |
| + goto fail; |
| + |
| + if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_START_FREQ, |
| + rdev->wiphy.sar_capa->freq_ranges[i].start_freq)) |
| + goto fail; |
| + |
| + if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_END_FREQ, |
| + rdev->wiphy.sar_capa->freq_ranges[i].end_freq)) |
| + goto fail; |
| + |
| + nla_nest_end(msg, sub_freq_range); |
| + } |
| + |
| + nla_nest_end(msg, specs); |
| + nla_nest_end(msg, sar_capa); |
| + |
| + return 0; |
| +fail: |
| + nla_nest_cancel(msg, sar_capa); |
| + return -ENOBUFS; |
| +} |
| + |
| struct nl80211_dump_wiphy_state { |
| s64 filter_wiphy; |
| long start; |
| @@ -2371,6 +2434,8 @@ static int nl80211_send_wiphy(struct cfg |
| CMD(set_multicast_to_unicast, SET_MULTICAST_TO_UNICAST); |
| CMD(update_connect_params, UPDATE_CONNECT_PARAMS); |
| CMD(update_ft_ies, UPDATE_FT_IES); |
| + if (rdev->wiphy.sar_capa) |
| + CMD(set_sar_specs, SET_SAR_SPECS); |
| } |
| #undef CMD |
| |
| @@ -2696,6 +2761,11 @@ static int nl80211_send_wiphy(struct cfg |
| |
| if (nl80211_put_tid_config_support(rdev, msg)) |
| goto nla_put_failure; |
| + state->split_start++; |
| + break; |
| + case 16: |
| + if (nl80211_put_sar_specs(rdev, msg)) |
| + goto nla_put_failure; |
| |
| /* done */ |
| state->split_start = 0; |
| @@ -14744,6 +14814,111 @@ static void nl80211_post_doit(__genl_con |
| } |
| } |
| |
| +static int nl80211_set_sar_sub_specs(struct cfg80211_registered_device *rdev, |
| + struct cfg80211_sar_specs *sar_specs, |
| + struct nlattr *spec[], int index) |
| +{ |
| + u32 range_index, i; |
| + |
| + if (!sar_specs || !spec) |
| + return -EINVAL; |
| + |
| + if (!spec[NL80211_SAR_ATTR_SPECS_POWER] || |
| + !spec[NL80211_SAR_ATTR_SPECS_RANGE_INDEX]) |
| + return -EINVAL; |
| + |
| + range_index = nla_get_u32(spec[NL80211_SAR_ATTR_SPECS_RANGE_INDEX]); |
| + |
| + /* check if range_index exceeds num_freq_ranges */ |
| + if (range_index >= rdev->wiphy.sar_capa->num_freq_ranges) |
| + return -EINVAL; |
| + |
| + /* check if range_index duplicates */ |
| + for (i = 0; i < index; i++) { |
| + if (sar_specs->sub_specs[i].freq_range_index == range_index) |
| + return -EINVAL; |
| + } |
| + |
| + sar_specs->sub_specs[index].power = |
| + nla_get_s32(spec[NL80211_SAR_ATTR_SPECS_POWER]); |
| + |
| + sar_specs->sub_specs[index].freq_range_index = range_index; |
| + |
| + return 0; |
| +} |
| + |
| +static int nl80211_set_sar_specs(struct sk_buff *skb, struct genl_info *info) |
| +{ |
| + struct cfg80211_registered_device *rdev = info->user_ptr[0]; |
| + struct nlattr *spec[NL80211_SAR_ATTR_SPECS_MAX + 1]; |
| + struct nlattr *tb[NL80211_SAR_ATTR_MAX + 1]; |
| + struct cfg80211_sar_specs *sar_spec; |
| + enum nl80211_sar_type type; |
| + struct nlattr *spec_list; |
| + u32 specs; |
| + int rem, err; |
| + |
| + if (!rdev->wiphy.sar_capa || !rdev->ops->set_sar_specs) |
| + return -EOPNOTSUPP; |
| + |
| + if (!info->attrs[NL80211_ATTR_SAR_SPEC]) |
| + return -EINVAL; |
| + |
| + nla_parse_nested(tb, NL80211_SAR_ATTR_MAX, |
| + info->attrs[NL80211_ATTR_SAR_SPEC], |
| + NULL, NULL); |
| + |
| + if (!tb[NL80211_SAR_ATTR_TYPE] || !tb[NL80211_SAR_ATTR_SPECS]) |
| + return -EINVAL; |
| + |
| + type = nla_get_u32(tb[NL80211_SAR_ATTR_TYPE]); |
| + if (type != rdev->wiphy.sar_capa->type) |
| + return -EINVAL; |
| + |
| + specs = 0; |
| + nla_for_each_nested(spec_list, tb[NL80211_SAR_ATTR_SPECS], rem) |
| + specs++; |
| + |
| + if (specs > rdev->wiphy.sar_capa->num_freq_ranges) |
| + return -EINVAL; |
| + |
| + sar_spec = kzalloc(sizeof(*sar_spec) + |
| + specs * sizeof(struct cfg80211_sar_sub_specs), |
| + GFP_KERNEL); |
| + if (!sar_spec) |
| + return -ENOMEM; |
| + |
| + sar_spec->type = type; |
| + specs = 0; |
| + nla_for_each_nested(spec_list, tb[NL80211_SAR_ATTR_SPECS], rem) { |
| + nla_parse_nested(spec, NL80211_SAR_ATTR_SPECS_MAX, |
| + spec_list, NULL, NULL); |
| + |
| + switch (type) { |
| + case NL80211_SAR_TYPE_POWER: |
| + if (nl80211_set_sar_sub_specs(rdev, sar_spec, |
| + spec, specs)) { |
| + err = -EINVAL; |
| + goto error; |
| + } |
| + break; |
| + default: |
| + err = -EINVAL; |
| + goto error; |
| + } |
| + specs++; |
| + } |
| + |
| + sar_spec->num_sub_specs = specs; |
| + |
| + rdev->cur_cmd_info = info; |
| + err = rdev_set_sar_specs(rdev, sar_spec); |
| + rdev->cur_cmd_info = NULL; |
| +error: |
| + kfree(sar_spec); |
| + return err; |
| +} |
| + |
| static __genl_const struct genl_ops nl80211_ops[] = { |
| { |
| .cmd = NL80211_CMD_GET_WIPHY, |
| @@ -15607,6 +15782,14 @@ static const struct genl_small_ops nl802 |
| .internal_flags = NL80211_FLAG_NEED_NETDEV | |
| NL80211_FLAG_NEED_RTNL, |
| }, |
| + { |
| + .cmd = NL80211_CMD_SET_SAR_SPECS, |
| + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| + .doit = nl80211_set_sar_specs, |
| + .flags = GENL_UNS_ADMIN_PERM, |
| + .internal_flags = NL80211_FLAG_NEED_WIPHY | |
| + NL80211_FLAG_NEED_RTNL, |
| + }, |
| }; |
| |
| static struct genl_family nl80211_fam __genl_ro_after_init = { |
| --- a/net/wireless/rdev-ops.h |
| +++ b/net/wireless/rdev-ops.h |
| @@ -1356,4 +1356,16 @@ static inline int rdev_reset_tid_config( |
| return ret; |
| } |
| |
| +static inline int rdev_set_sar_specs(struct cfg80211_registered_device *rdev, |
| + struct cfg80211_sar_specs *sar) |
| +{ |
| + int ret; |
| + |
| + trace_rdev_set_sar_specs(&rdev->wiphy, sar); |
| + ret = rdev->ops->set_sar_specs(&rdev->wiphy, sar); |
| + trace_rdev_return_int(&rdev->wiphy, ret); |
| + |
| + return ret; |
| +} |
| + |
| #endif /* __CFG80211_RDEV_OPS */ |
| --- a/net/wireless/trace.h |
| +++ b/net/wireless/trace.h |
| @@ -3551,6 +3551,25 @@ TRACE_EVENT(rdev_reset_tid_config, |
| TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", peer: " MAC_PR_FMT ", tids: 0x%x", |
| WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(peer), __entry->tids) |
| ); |
| + |
| +TRACE_EVENT(rdev_set_sar_specs, |
| + TP_PROTO(struct wiphy *wiphy, struct cfg80211_sar_specs *sar), |
| + TP_ARGS(wiphy, sar), |
| + TP_STRUCT__entry( |
| + WIPHY_ENTRY |
| + __field(u16, type) |
| + __field(u16, num) |
| + ), |
| + TP_fast_assign( |
| + WIPHY_ASSIGN; |
| + __entry->type = sar->type; |
| + __entry->num = sar->num_sub_specs; |
| + |
| + ), |
| + TP_printk(WIPHY_PR_FMT ", Set type:%d, num_specs:%d", |
| + WIPHY_PR_ARG, __entry->type, __entry->num) |
| +); |
| + |
| #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */ |
| |
| #undef TRACE_INCLUDE_PATH |