diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index c47349110..92dbc1653 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -1250,3 +1250,14 @@ int hostapd_drv_set_secure_ranging_ctx(struct hostapd_data *hapd, return hapd->driver->set_secure_ranging_ctx(hapd->drv_priv, ¶ms); } #endif /* CONFIG_PASN */ + + +struct hostapd_multi_hw_info * +hostapd_get_multi_hw_info(struct hostapd_data *hapd, + unsigned int *num_multi_hws) +{ + if (!hapd->driver || !hapd->driver->get_multi_hw_info) + return NULL; + + return hapd->driver->get_multi_hw_info(hapd->drv_priv, num_multi_hws); +} diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index d7e79c840..6b7f02a1f 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -478,4 +478,8 @@ static inline int hostapd_drv_link_sta_remove(struct hostapd_data *hapd, #endif /* CONFIG_IEEE80211BE */ +struct hostapd_multi_hw_info * +hostapd_get_multi_hw_info(struct hostapd_data *hapd, + unsigned int *num_multi_hws); + #endif /* AP_DRV_OPS */ diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 57ae12d4a..bf40f389d 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -712,6 +712,9 @@ void hostapd_cleanup_iface_partial(struct hostapd_iface *iface) ap_list_deinit(iface); sta_track_deinit(iface); airtime_policy_update_deinit(iface); + hostapd_free_multi_hw_info(iface->multi_hw_info); + iface->multi_hw_info = NULL; + iface->current_hw_info = NULL; } @@ -2505,6 +2508,12 @@ static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, hostapd_hw_mode_txt(iface->conf->hw_mode), iface->conf->channel, iface->freq); + if (hostapd_set_current_hw_info(iface, iface->freq)) { + wpa_printf(MSG_ERROR, + "Failed to set current hardware info"); + goto fail; + } + #ifdef NEED_AP_MLME /* Handle DFS only if it is not offloaded to the driver */ if (!(iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)) { diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 278e9c3ca..898dc0d75 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -730,6 +730,10 @@ struct hostapd_iface { bool is_no_ir; bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */ + + struct hostapd_multi_hw_info *multi_hw_info; + unsigned int num_multi_hws; + struct hostapd_multi_hw_info *current_hw_info; }; /* hostapd.c */ diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index c4556603d..02d67593a 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -76,12 +76,15 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) { struct hostapd_data *hapd = iface->bss[0]; int i, j; + unsigned int k; u16 num_modes, flags; struct hostapd_hw_modes *modes; u8 dfs_domain; enum hostapd_hw_mode mode = HOSTAPD_MODE_IEEE80211ANY; bool is_6ghz = false; bool orig_mode_valid = false; + struct hostapd_multi_hw_info *multi_hw_info; + unsigned int num_multi_hws; if (hostapd_drv_none(hapd)) return -1; @@ -168,6 +171,25 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) __func__); } + multi_hw_info = hostapd_get_multi_hw_info(hapd, &num_multi_hws); + if (!multi_hw_info) + return 0; + + hostapd_free_multi_hw_info(iface->multi_hw_info); + iface->multi_hw_info = multi_hw_info; + iface->num_multi_hws = num_multi_hws; + + wpa_printf(MSG_DEBUG, "Multiple underlying hardwares info:"); + + for (k = 0; k < num_multi_hws; k++) { + struct hostapd_multi_hw_info *hw_info = &multi_hw_info[k]; + + wpa_printf(MSG_DEBUG, + " %d. hw_idx=%u, frequency range: %d-%d MHz", + k + 1, hw_info->hw_idx, hw_info->start_freq, + hw_info->end_freq); + } + return 0; } @@ -1391,3 +1413,34 @@ int hostapd_hw_skip_mode(struct hostapd_iface *iface, } return 0; } + + +void hostapd_free_multi_hw_info(struct hostapd_multi_hw_info *multi_hw_info) +{ + os_free(multi_hw_info); +} + + +int hostapd_set_current_hw_info(struct hostapd_iface *iface, int oper_freq) +{ + struct hostapd_multi_hw_info *hw_info; + unsigned int i; + + if (!iface->num_multi_hws) + return 0; + + for (i = 0; i < iface->num_multi_hws; i++) { + hw_info = &iface->multi_hw_info[i]; + + if (hw_info->start_freq <= oper_freq && + hw_info->end_freq >= oper_freq) { + iface->current_hw_info = hw_info; + wpa_printf(MSG_DEBUG, + "Mode: Selected underlying hardware: hw_idx=%u", + iface->current_hw_info->hw_idx); + return 0; + } + } + + return -1; +} diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h index c682c6d20..73663d0af 100644 --- a/src/ap/hw_features.h +++ b/src/ap/hw_features.h @@ -30,6 +30,8 @@ void hostapd_stop_setup_timers(struct hostapd_iface *iface); int hostapd_hw_skip_mode(struct hostapd_iface *iface, struct hostapd_hw_modes *mode); int hostapd_determine_mode(struct hostapd_iface *iface); +void hostapd_free_multi_hw_info(struct hostapd_multi_hw_info *multi_hw_info); +int hostapd_set_current_hw_info(struct hostapd_iface *iface, int oper_freq); #else /* NEED_AP_MLME */ static inline void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, @@ -103,6 +105,16 @@ static inline int hostapd_determine_mode(struct hostapd_iface *iface) return 0; } +static inline +void hostapd_free_multi_hw_info(struct hostapd_multi_hw_info *multi_hw_info) +{ +} + +static inline int hostapd_set_current_hw_info(struct hostapd_iface *iface, + u32 oper_freq) +{ + return 0; +} #endif /* NEED_AP_MLME */ #endif /* HW_FEATURES_H */ diff --git a/src/drivers/driver.h b/src/drivers/driver.h index c4cf46c30..e9c6a8325 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -317,6 +317,27 @@ struct hostapd_hw_modes { }; +/** + * struct hostapd_multi_hw_info: Supported multiple underlying hardware info + */ +struct hostapd_multi_hw_info { + /** + * hw_idx - Hardware index + */ + u8 hw_idx; + + /** + * start_freq - Frequency range start in MHz + */ + int start_freq; + + /** + * end_freq - Frequency range end in MHz + */ + int end_freq; +}; + + #define IEEE80211_CAP_ESS 0x0001 #define IEEE80211_CAP_IBSS 0x0002 #define IEEE80211_CAP_PRIVACY 0x0010 @@ -5224,6 +5245,18 @@ struct wpa_driver_ops { const u8 *match, size_t match_len, bool multicast); #endif /* CONFIG_TESTING_OPTIONS */ + + /** + * get_multi_hw_info - Get multiple underlying hardware information + * (hardware IDx and supported frequency range) + * @priv: Private driver interface data + * @num_multi_hws: Variable for returning the number of returned + * hardware info data + * Returns: Pointer to allocated multiple hardware data on success + * or %NULL on failure. Caller is responsible for freeing this. + */ + struct hostapd_multi_hw_info * + (*get_multi_hw_info)(void *priv, unsigned int *num_multi_hws); }; /** diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index b729b3e5b..a71fbf8b1 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -14036,6 +14036,15 @@ static int testing_nl80211_radio_disable(void *priv, int disabled) #endif /* CONFIG_TESTING_OPTIONS */ +static struct hostapd_multi_hw_info * +wpa_driver_get_multi_hw_info(void *priv, unsigned int *num_multi_hws) +{ + struct i802_bss *bss = priv; + + return nl80211_get_multi_hw_info(bss, num_multi_hws); +} + + const struct wpa_driver_ops wpa_driver_nl80211_ops = { .name = "nl80211", .desc = "Linux nl80211/cfg80211", @@ -14194,4 +14203,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .register_frame = testing_nl80211_register_frame, .radio_disable = testing_nl80211_radio_disable, #endif /* CONFIG_TESTING_OPTIONS */ + .get_multi_hw_info = wpa_driver_get_multi_hw_info, }; diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h index d1e0f195e..87bcc880b 100644 --- a/src/drivers/driver_nl80211.h +++ b/src/drivers/driver_nl80211.h @@ -411,5 +411,7 @@ int wpa_driver_nl80211_abort_scan(void *priv, u64 scan_cookie); int wpa_driver_nl80211_vendor_scan(struct i802_bss *bss, struct wpa_driver_scan_params *params); int nl80211_set_default_scan_ies(void *priv, const u8 *ies, size_t ies_len); +struct hostapd_multi_hw_info * +nl80211_get_multi_hw_info(struct i802_bss *bss, unsigned int *num_multi_hws); #endif /* DRIVER_NL80211_H */ diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c index 26c1f4140..c84a9478c 100644 --- a/src/drivers/driver_nl80211_capa.c +++ b/src/drivers/driver_nl80211_capa.c @@ -2736,3 +2736,133 @@ nl80211_get_hw_feature_data(void *priv, u16 *num_modes, u16 *flags, return NULL; } + + +static int phy_multi_hw_info_parse(struct hostapd_multi_hw_info *hw_info, + struct nlattr *radio_attr) +{ + struct nlattr *tb_freq[NL80211_WIPHY_RADIO_FREQ_ATTR_MAX + 1]; + int start_freq, end_freq; + + switch (nla_type(radio_attr)) { + case NL80211_WIPHY_RADIO_ATTR_INDEX: + hw_info->hw_idx = nla_get_u32(radio_attr); + return NL_OK; + case NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE: + nla_parse_nested(tb_freq, NL80211_WIPHY_RADIO_FREQ_ATTR_MAX, + radio_attr, NULL); + + if (!tb_freq[NL80211_WIPHY_RADIO_FREQ_ATTR_START] || + !tb_freq[NL80211_WIPHY_RADIO_FREQ_ATTR_END]) + return NL_STOP; + + start_freq = nla_get_u32( + tb_freq[NL80211_WIPHY_RADIO_FREQ_ATTR_START]); + end_freq = nla_get_u32( + tb_freq[NL80211_WIPHY_RADIO_FREQ_ATTR_END]); + + /* Convert to MHz and store */ + hw_info->start_freq = start_freq / 1000; + hw_info->end_freq = end_freq / 1000; + return NL_OK; + default: + return NL_OK; + } +} + + +struct phy_multi_hw_info_arg { + bool failed; + unsigned int *num_multi_hws; + struct hostapd_multi_hw_info *multi_hws; +}; + + +static int phy_multi_hw_info_handler(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct phy_multi_hw_info_arg *multi_hw_info = arg; + struct hostapd_multi_hw_info *multi_hws, hw_info; + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + struct nlattr *nl_hw, *radio_attr; + int rem_hw, rem_radio_prop, res; + + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb_msg[NL80211_ATTR_WIPHY_RADIOS]) + return NL_SKIP; + + *multi_hw_info->num_multi_hws = 0; + + nla_for_each_nested(nl_hw, tb_msg[NL80211_ATTR_WIPHY_RADIOS], rem_hw) { + os_memset(&hw_info, 0, sizeof(hw_info)); + + nla_for_each_nested(radio_attr, nl_hw, rem_radio_prop) { + res = phy_multi_hw_info_parse(&hw_info, radio_attr); + if (res != NL_OK) + goto out; + } + + if (hw_info.start_freq == 0 || hw_info.end_freq == 0) + goto out; + + multi_hws = os_realloc_array(multi_hw_info->multi_hws, + *multi_hw_info->num_multi_hws + 1, + sizeof(*multi_hws)); + if (!multi_hws) + goto out; + + multi_hw_info->multi_hws = multi_hws; + os_memcpy(&multi_hws[*(multi_hw_info->num_multi_hws)], + &hw_info, sizeof(struct hostapd_multi_hw_info)); + *(multi_hw_info->num_multi_hws) += 1; + } + + return NL_OK; +out: + multi_hw_info->failed = true; + return NL_STOP; +} + + +struct hostapd_multi_hw_info * +nl80211_get_multi_hw_info(struct i802_bss *bss, unsigned int *num_multi_hws) +{ + u32 feat; + struct wpa_driver_nl80211_data *drv = bss->drv; + int nl_flags = 0; + struct nl_msg *msg; + struct phy_multi_hw_info_arg result = { + .failed = false, + .num_multi_hws = num_multi_hws, + .multi_hws = NULL, + }; + + *num_multi_hws = 0; + + if (!drv->has_capability || !(drv->capa.flags2 & WPA_DRIVER_FLAGS2_MLO)) + return NULL; + + feat = get_nl80211_protocol_features(drv); + if (feat & NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP) + nl_flags = NLM_F_DUMP; + if (!(msg = nl80211_cmd_msg(bss, nl_flags, NL80211_CMD_GET_WIPHY)) || + nla_put_flag(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP)) { + nlmsg_free(msg); + return NULL; + } + + if (send_and_recv_resp(drv, msg, phy_multi_hw_info_handler, + &result) == 0) { + if (result.failed) { + os_free(result.multi_hws); + *num_multi_hws = 0; + return NULL; + } + + return result.multi_hws; + } + + return NULL; +}