b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | From: Carl Huang <cjhuang@codeaurora.org> |
| 2 | Date: Thu, 3 Dec 2020 05:37:26 -0500 |
| 3 | Subject: [PATCH] nl80211: add common API to configure SAR power limitations |
| 4 | |
| 5 | NL80211_CMD_SET_SAR_SPECS is added to configure SAR from |
| 6 | user space. NL80211_ATTR_SAR_SPEC is used to pass the SAR |
| 7 | power specification when used with NL80211_CMD_SET_SAR_SPECS. |
| 8 | |
| 9 | Wireless driver needs to register SAR type, supported frequency |
| 10 | ranges to wiphy, so user space can query it. The index in |
| 11 | frequency range is used to specify which sub band the power |
| 12 | limitation applies to. The SAR type is for compatibility, so later |
| 13 | other SAR mechanism can be implemented without breaking the user |
| 14 | space SAR applications. |
| 15 | |
| 16 | Normal process is user space queries the SAR capability, and |
| 17 | gets the index of supported frequency ranges and associates the |
| 18 | power limitation with this index and sends to kernel. |
| 19 | |
| 20 | Here is an example of message send to kernel: |
| 21 | 8c 00 00 00 08 00 01 00 00 00 00 00 38 00 2b 81 |
| 22 | 08 00 01 00 00 00 00 00 2c 00 02 80 14 00 00 80 |
| 23 | 08 00 02 00 00 00 00 00 08 00 01 00 38 00 00 00 |
| 24 | 14 00 01 80 08 00 02 00 01 00 00 00 08 00 01 00 |
| 25 | 48 00 00 00 |
| 26 | |
| 27 | NL80211_CMD_SET_SAR_SPECS: 0x8c |
| 28 | NL80211_ATTR_WIPHY: 0x01(phy idx is 0) |
| 29 | NL80211_ATTR_SAR_SPEC: 0x812b (NLA_NESTED) |
| 30 | NL80211_SAR_ATTR_TYPE: 0x00 (NL80211_SAR_TYPE_POWER) |
| 31 | NL80211_SAR_ATTR_SPECS: 0x8002 (NLA_NESTED) |
| 32 | freq range 0 power: 0x38 in 0.25dbm unit (14dbm) |
| 33 | freq range 1 power: 0x48 in 0.25dbm unit (18dbm) |
| 34 | |
| 35 | Signed-off-by: Carl Huang <cjhuang@codeaurora.org> |
| 36 | Reviewed-by: Brian Norris <briannorris@chromium.org> |
| 37 | Reviewed-by: Abhishek Kumar <kuabhs@chromium.org> |
| 38 | Link: https://lore.kernel.org/r/20201203103728.3034-2-cjhuang@codeaurora.org |
| 39 | [minor edits, NLA parse cleanups] |
| 40 | Signed-off-by: Johannes Berg <johannes.berg@intel.com> |
| 41 | --- |
| 42 | |
| 43 | --- a/include/net/cfg80211.h |
| 44 | +++ b/include/net/cfg80211.h |
| 45 | @@ -1737,6 +1737,54 @@ struct station_info { |
| 46 | u8 connected_to_as; |
| 47 | }; |
| 48 | |
| 49 | +/** |
| 50 | + * struct cfg80211_sar_sub_specs - sub specs limit |
| 51 | + * @power: power limitation in 0.25dbm |
| 52 | + * @freq_range_index: index the power limitation applies to |
| 53 | + */ |
| 54 | +struct cfg80211_sar_sub_specs { |
| 55 | + s32 power; |
| 56 | + u32 freq_range_index; |
| 57 | +}; |
| 58 | + |
| 59 | +/** |
| 60 | + * struct cfg80211_sar_specs - sar limit specs |
| 61 | + * @type: it's set with power in 0.25dbm or other types |
| 62 | + * @num_sub_specs: number of sar sub specs |
| 63 | + * @sub_specs: memory to hold the sar sub specs |
| 64 | + */ |
| 65 | +struct cfg80211_sar_specs { |
| 66 | + enum nl80211_sar_type type; |
| 67 | + u32 num_sub_specs; |
| 68 | + struct cfg80211_sar_sub_specs sub_specs[]; |
| 69 | +}; |
| 70 | + |
| 71 | + |
| 72 | +/** |
| 73 | + * @struct cfg80211_sar_chan_ranges - sar frequency ranges |
| 74 | + * @start_freq: start range edge frequency |
| 75 | + * @end_freq: end range edge frequency |
| 76 | + */ |
| 77 | +struct cfg80211_sar_freq_ranges { |
| 78 | + u32 start_freq; |
| 79 | + u32 end_freq; |
| 80 | +}; |
| 81 | + |
| 82 | +/** |
| 83 | + * struct cfg80211_sar_capa - sar limit capability |
| 84 | + * @type: it's set via power in 0.25dbm or other types |
| 85 | + * @num_freq_ranges: number of frequency ranges |
| 86 | + * @freq_ranges: memory to hold the freq ranges. |
| 87 | + * |
| 88 | + * Note: WLAN driver may append new ranges or split an existing |
| 89 | + * range to small ones and then append them. |
| 90 | + */ |
| 91 | +struct cfg80211_sar_capa { |
| 92 | + enum nl80211_sar_type type; |
| 93 | + u32 num_freq_ranges; |
| 94 | + const struct cfg80211_sar_freq_ranges *freq_ranges; |
| 95 | +}; |
| 96 | + |
| 97 | #if IS_ENABLED(CPTCFG_CFG80211) |
| 98 | /** |
| 99 | * cfg80211_get_station - retrieve information about a given station |
| 100 | @@ -4259,6 +4307,8 @@ struct cfg80211_ops { |
| 101 | struct cfg80211_tid_config *tid_conf); |
| 102 | int (*reset_tid_config)(struct wiphy *wiphy, struct net_device *dev, |
| 103 | const u8 *peer, u8 tids); |
| 104 | + int (*set_sar_specs)(struct wiphy *wiphy, |
| 105 | + struct cfg80211_sar_specs *sar); |
| 106 | }; |
| 107 | |
| 108 | /* |
| 109 | @@ -5030,6 +5080,8 @@ struct wiphy { |
| 110 | |
| 111 | u8 max_data_retry_count; |
| 112 | |
| 113 | + const struct cfg80211_sar_capa *sar_capa; |
| 114 | + |
| 115 | char priv[] __aligned(NETDEV_ALIGN); |
| 116 | }; |
| 117 | |
| 118 | --- a/net/wireless/nl80211.c |
| 119 | +++ b/net/wireless/nl80211.c |
| 120 | @@ -405,6 +405,18 @@ nl80211_unsol_bcast_probe_resp_policy[NL |
| 121 | .len = IEEE80211_MAX_DATA_LEN } |
| 122 | }; |
| 123 | |
| 124 | +static const struct nla_policy |
| 125 | +sar_specs_policy[NL80211_SAR_ATTR_SPECS_MAX + 1] = { |
| 126 | + [NL80211_SAR_ATTR_SPECS_POWER] = { .type = NLA_S32 }, |
| 127 | + [NL80211_SAR_ATTR_SPECS_RANGE_INDEX] = {.type = NLA_U32 }, |
| 128 | +}; |
| 129 | + |
| 130 | +static const struct nla_policy |
| 131 | +sar_policy[NL80211_SAR_ATTR_MAX + 1] = { |
| 132 | + [NL80211_SAR_ATTR_TYPE] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_SAR_TYPE), |
| 133 | + [NL80211_SAR_ATTR_SPECS] = NLA_POLICY_NESTED_ARRAY(sar_specs_policy), |
| 134 | +}; |
| 135 | + |
| 136 | static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { |
| 137 | [0] = { .strict_start_type = NL80211_ATTR_HE_OBSS_PD }, |
| 138 | [NL80211_ATTR_WIPHY] = { .type = NLA_U32 }, |
| 139 | @@ -744,6 +756,7 @@ static const struct nla_policy nl80211_p |
| 140 | [NL80211_ATTR_SAE_PWE] = |
| 141 | NLA_POLICY_RANGE(NLA_U8, NL80211_SAE_PWE_HUNT_AND_PECK, |
| 142 | NL80211_SAE_PWE_BOTH), |
| 143 | + [NL80211_ATTR_SAR_SPEC] = NLA_POLICY_NESTED(sar_policy), |
| 144 | [NL80211_ATTR_RECONNECT_REQUESTED] = { .type = NLA_REJECT }, |
| 145 | }; |
| 146 | |
| 147 | @@ -2122,6 +2135,56 @@ fail: |
| 148 | return -ENOBUFS; |
| 149 | } |
| 150 | |
| 151 | +static int |
| 152 | +nl80211_put_sar_specs(struct cfg80211_registered_device *rdev, |
| 153 | + struct sk_buff *msg) |
| 154 | +{ |
| 155 | + struct nlattr *sar_capa, *specs, *sub_freq_range; |
| 156 | + u8 num_freq_ranges; |
| 157 | + int i; |
| 158 | + |
| 159 | + if (!rdev->wiphy.sar_capa) |
| 160 | + return 0; |
| 161 | + |
| 162 | + num_freq_ranges = rdev->wiphy.sar_capa->num_freq_ranges; |
| 163 | + |
| 164 | + sar_capa = nla_nest_start(msg, NL80211_ATTR_SAR_SPEC); |
| 165 | + if (!sar_capa) |
| 166 | + return -ENOSPC; |
| 167 | + |
| 168 | + if (nla_put_u32(msg, NL80211_SAR_ATTR_TYPE, rdev->wiphy.sar_capa->type)) |
| 169 | + goto fail; |
| 170 | + |
| 171 | + specs = nla_nest_start(msg, NL80211_SAR_ATTR_SPECS); |
| 172 | + if (!specs) |
| 173 | + goto fail; |
| 174 | + |
| 175 | + /* report supported freq_ranges */ |
| 176 | + for (i = 0; i < num_freq_ranges; i++) { |
| 177 | + sub_freq_range = nla_nest_start(msg, i + 1); |
| 178 | + if (!sub_freq_range) |
| 179 | + goto fail; |
| 180 | + |
| 181 | + if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_START_FREQ, |
| 182 | + rdev->wiphy.sar_capa->freq_ranges[i].start_freq)) |
| 183 | + goto fail; |
| 184 | + |
| 185 | + if (nla_put_u32(msg, NL80211_SAR_ATTR_SPECS_END_FREQ, |
| 186 | + rdev->wiphy.sar_capa->freq_ranges[i].end_freq)) |
| 187 | + goto fail; |
| 188 | + |
| 189 | + nla_nest_end(msg, sub_freq_range); |
| 190 | + } |
| 191 | + |
| 192 | + nla_nest_end(msg, specs); |
| 193 | + nla_nest_end(msg, sar_capa); |
| 194 | + |
| 195 | + return 0; |
| 196 | +fail: |
| 197 | + nla_nest_cancel(msg, sar_capa); |
| 198 | + return -ENOBUFS; |
| 199 | +} |
| 200 | + |
| 201 | struct nl80211_dump_wiphy_state { |
| 202 | s64 filter_wiphy; |
| 203 | long start; |
| 204 | @@ -2371,6 +2434,8 @@ static int nl80211_send_wiphy(struct cfg |
| 205 | CMD(set_multicast_to_unicast, SET_MULTICAST_TO_UNICAST); |
| 206 | CMD(update_connect_params, UPDATE_CONNECT_PARAMS); |
| 207 | CMD(update_ft_ies, UPDATE_FT_IES); |
| 208 | + if (rdev->wiphy.sar_capa) |
| 209 | + CMD(set_sar_specs, SET_SAR_SPECS); |
| 210 | } |
| 211 | #undef CMD |
| 212 | |
| 213 | @@ -2696,6 +2761,11 @@ static int nl80211_send_wiphy(struct cfg |
| 214 | |
| 215 | if (nl80211_put_tid_config_support(rdev, msg)) |
| 216 | goto nla_put_failure; |
| 217 | + state->split_start++; |
| 218 | + break; |
| 219 | + case 16: |
| 220 | + if (nl80211_put_sar_specs(rdev, msg)) |
| 221 | + goto nla_put_failure; |
| 222 | |
| 223 | /* done */ |
| 224 | state->split_start = 0; |
| 225 | @@ -14744,6 +14814,111 @@ static void nl80211_post_doit(__genl_con |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | +static int nl80211_set_sar_sub_specs(struct cfg80211_registered_device *rdev, |
| 230 | + struct cfg80211_sar_specs *sar_specs, |
| 231 | + struct nlattr *spec[], int index) |
| 232 | +{ |
| 233 | + u32 range_index, i; |
| 234 | + |
| 235 | + if (!sar_specs || !spec) |
| 236 | + return -EINVAL; |
| 237 | + |
| 238 | + if (!spec[NL80211_SAR_ATTR_SPECS_POWER] || |
| 239 | + !spec[NL80211_SAR_ATTR_SPECS_RANGE_INDEX]) |
| 240 | + return -EINVAL; |
| 241 | + |
| 242 | + range_index = nla_get_u32(spec[NL80211_SAR_ATTR_SPECS_RANGE_INDEX]); |
| 243 | + |
| 244 | + /* check if range_index exceeds num_freq_ranges */ |
| 245 | + if (range_index >= rdev->wiphy.sar_capa->num_freq_ranges) |
| 246 | + return -EINVAL; |
| 247 | + |
| 248 | + /* check if range_index duplicates */ |
| 249 | + for (i = 0; i < index; i++) { |
| 250 | + if (sar_specs->sub_specs[i].freq_range_index == range_index) |
| 251 | + return -EINVAL; |
| 252 | + } |
| 253 | + |
| 254 | + sar_specs->sub_specs[index].power = |
| 255 | + nla_get_s32(spec[NL80211_SAR_ATTR_SPECS_POWER]); |
| 256 | + |
| 257 | + sar_specs->sub_specs[index].freq_range_index = range_index; |
| 258 | + |
| 259 | + return 0; |
| 260 | +} |
| 261 | + |
| 262 | +static int nl80211_set_sar_specs(struct sk_buff *skb, struct genl_info *info) |
| 263 | +{ |
| 264 | + struct cfg80211_registered_device *rdev = info->user_ptr[0]; |
| 265 | + struct nlattr *spec[NL80211_SAR_ATTR_SPECS_MAX + 1]; |
| 266 | + struct nlattr *tb[NL80211_SAR_ATTR_MAX + 1]; |
| 267 | + struct cfg80211_sar_specs *sar_spec; |
| 268 | + enum nl80211_sar_type type; |
| 269 | + struct nlattr *spec_list; |
| 270 | + u32 specs; |
| 271 | + int rem, err; |
| 272 | + |
| 273 | + if (!rdev->wiphy.sar_capa || !rdev->ops->set_sar_specs) |
| 274 | + return -EOPNOTSUPP; |
| 275 | + |
| 276 | + if (!info->attrs[NL80211_ATTR_SAR_SPEC]) |
| 277 | + return -EINVAL; |
| 278 | + |
| 279 | + nla_parse_nested(tb, NL80211_SAR_ATTR_MAX, |
| 280 | + info->attrs[NL80211_ATTR_SAR_SPEC], |
| 281 | + NULL, NULL); |
| 282 | + |
| 283 | + if (!tb[NL80211_SAR_ATTR_TYPE] || !tb[NL80211_SAR_ATTR_SPECS]) |
| 284 | + return -EINVAL; |
| 285 | + |
| 286 | + type = nla_get_u32(tb[NL80211_SAR_ATTR_TYPE]); |
| 287 | + if (type != rdev->wiphy.sar_capa->type) |
| 288 | + return -EINVAL; |
| 289 | + |
| 290 | + specs = 0; |
| 291 | + nla_for_each_nested(spec_list, tb[NL80211_SAR_ATTR_SPECS], rem) |
| 292 | + specs++; |
| 293 | + |
| 294 | + if (specs > rdev->wiphy.sar_capa->num_freq_ranges) |
| 295 | + return -EINVAL; |
| 296 | + |
| 297 | + sar_spec = kzalloc(sizeof(*sar_spec) + |
| 298 | + specs * sizeof(struct cfg80211_sar_sub_specs), |
| 299 | + GFP_KERNEL); |
| 300 | + if (!sar_spec) |
| 301 | + return -ENOMEM; |
| 302 | + |
| 303 | + sar_spec->type = type; |
| 304 | + specs = 0; |
| 305 | + nla_for_each_nested(spec_list, tb[NL80211_SAR_ATTR_SPECS], rem) { |
| 306 | + nla_parse_nested(spec, NL80211_SAR_ATTR_SPECS_MAX, |
| 307 | + spec_list, NULL, NULL); |
| 308 | + |
| 309 | + switch (type) { |
| 310 | + case NL80211_SAR_TYPE_POWER: |
| 311 | + if (nl80211_set_sar_sub_specs(rdev, sar_spec, |
| 312 | + spec, specs)) { |
| 313 | + err = -EINVAL; |
| 314 | + goto error; |
| 315 | + } |
| 316 | + break; |
| 317 | + default: |
| 318 | + err = -EINVAL; |
| 319 | + goto error; |
| 320 | + } |
| 321 | + specs++; |
| 322 | + } |
| 323 | + |
| 324 | + sar_spec->num_sub_specs = specs; |
| 325 | + |
| 326 | + rdev->cur_cmd_info = info; |
| 327 | + err = rdev_set_sar_specs(rdev, sar_spec); |
| 328 | + rdev->cur_cmd_info = NULL; |
| 329 | +error: |
| 330 | + kfree(sar_spec); |
| 331 | + return err; |
| 332 | +} |
| 333 | + |
| 334 | static __genl_const struct genl_ops nl80211_ops[] = { |
| 335 | { |
| 336 | .cmd = NL80211_CMD_GET_WIPHY, |
| 337 | @@ -15607,6 +15782,14 @@ static const struct genl_small_ops nl802 |
| 338 | .internal_flags = NL80211_FLAG_NEED_NETDEV | |
| 339 | NL80211_FLAG_NEED_RTNL, |
| 340 | }, |
| 341 | + { |
| 342 | + .cmd = NL80211_CMD_SET_SAR_SPECS, |
| 343 | + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, |
| 344 | + .doit = nl80211_set_sar_specs, |
| 345 | + .flags = GENL_UNS_ADMIN_PERM, |
| 346 | + .internal_flags = NL80211_FLAG_NEED_WIPHY | |
| 347 | + NL80211_FLAG_NEED_RTNL, |
| 348 | + }, |
| 349 | }; |
| 350 | |
| 351 | static struct genl_family nl80211_fam __genl_ro_after_init = { |
| 352 | --- a/net/wireless/rdev-ops.h |
| 353 | +++ b/net/wireless/rdev-ops.h |
| 354 | @@ -1356,4 +1356,16 @@ static inline int rdev_reset_tid_config( |
| 355 | return ret; |
| 356 | } |
| 357 | |
| 358 | +static inline int rdev_set_sar_specs(struct cfg80211_registered_device *rdev, |
| 359 | + struct cfg80211_sar_specs *sar) |
| 360 | +{ |
| 361 | + int ret; |
| 362 | + |
| 363 | + trace_rdev_set_sar_specs(&rdev->wiphy, sar); |
| 364 | + ret = rdev->ops->set_sar_specs(&rdev->wiphy, sar); |
| 365 | + trace_rdev_return_int(&rdev->wiphy, ret); |
| 366 | + |
| 367 | + return ret; |
| 368 | +} |
| 369 | + |
| 370 | #endif /* __CFG80211_RDEV_OPS */ |
| 371 | --- a/net/wireless/trace.h |
| 372 | +++ b/net/wireless/trace.h |
| 373 | @@ -3551,6 +3551,25 @@ TRACE_EVENT(rdev_reset_tid_config, |
| 374 | TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", peer: " MAC_PR_FMT ", tids: 0x%x", |
| 375 | WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(peer), __entry->tids) |
| 376 | ); |
| 377 | + |
| 378 | +TRACE_EVENT(rdev_set_sar_specs, |
| 379 | + TP_PROTO(struct wiphy *wiphy, struct cfg80211_sar_specs *sar), |
| 380 | + TP_ARGS(wiphy, sar), |
| 381 | + TP_STRUCT__entry( |
| 382 | + WIPHY_ENTRY |
| 383 | + __field(u16, type) |
| 384 | + __field(u16, num) |
| 385 | + ), |
| 386 | + TP_fast_assign( |
| 387 | + WIPHY_ASSIGN; |
| 388 | + __entry->type = sar->type; |
| 389 | + __entry->num = sar->num_sub_specs; |
| 390 | + |
| 391 | + ), |
| 392 | + TP_printk(WIPHY_PR_FMT ", Set type:%d, num_specs:%d", |
| 393 | + WIPHY_PR_ARG, __entry->type, __entry->num) |
| 394 | +); |
| 395 | + |
| 396 | #endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */ |
| 397 | |
| 398 | #undef TRACE_INCLUDE_PATH |