hostapd: Fetch multiple radios information from the driver

When a driver consolidates several hardware components under a single
radio, it provides details about these components via the
NL80211_CMD_GET_WIPHY command.

Parse this information and store it. A subsequent change will use this
information for validation in certain scenarios.

Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
This commit is contained in:
Aditya Kumar Singh 2024-07-24 12:38:13 +05:30 committed by Jouni Malinen
parent c43e1e5a5f
commit 15bf093b5b
10 changed files with 268 additions and 0 deletions

View file

@ -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, &params);
}
#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);
}

View file

@ -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 */

View file

@ -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)) {

View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);
};
/**

View file

@ -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,
};

View file

@ -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 */

View file

@ -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;
}