diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 0852e1082..fca7d4b45 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2392,6 +2392,11 @@ struct wpa_signal_info { int center_frq2; }; +struct wpa_mlo_signal_info { + u16 valid_links; + struct wpa_signal_info links[MAX_NUM_MLD_LINKS]; +}; + /** * struct wpa_channel_info - Information about the current channel * @frequency: Center frequency of the primary 20 MHz channel @@ -3986,6 +3991,14 @@ struct wpa_driver_ops { */ int (*signal_poll)(void *priv, struct wpa_signal_info *signal_info); + /** + * mlo_signal_poll - Get current MLO connection information + * @priv: Private driver interface data + * @mlo_signal_info: MLO connection info structure + */ + int (*mlo_signal_poll)(void *priv, + struct wpa_mlo_signal_info *mlo_signal_info); + /** * channel_info - Get parameters of the current operating channel * @priv: Private driver interface data diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 00879abea..531699138 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -8775,6 +8775,163 @@ static int nl80211_signal_poll(void *priv, struct wpa_signal_info *si) } +static int get_links_noise(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1]; + static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = { + [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 }, + [NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 }, + }; + struct wpa_mlo_signal_info *mlo_sig = arg; + int i; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_SURVEY_INFO]) { + wpa_printf(MSG_DEBUG, "nl80211: Survey data missing"); + return NL_SKIP; + } + + if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX, + tb[NL80211_ATTR_SURVEY_INFO], + survey_policy)) { + wpa_printf(MSG_DEBUG, + "nl80211: Failed to parse nested attributes"); + return NL_SKIP; + } + + if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY]) + return NL_SKIP; + + if (!sinfo[NL80211_SURVEY_INFO_NOISE]) + return NL_SKIP; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(mlo_sig->valid_links & BIT(i))) + continue; + + if (nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]) != + mlo_sig->links[i].frequency) + continue; + + mlo_sig->links[i].current_noise = + (s8) nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]); + break; + } + + return NL_SKIP; +} + + +static int nl80211_get_links_noise(struct wpa_driver_nl80211_data *drv, + struct wpa_mlo_signal_info *mlo_sig) +{ + struct nl_msg *msg; + + msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SURVEY); + return send_and_recv_msgs(drv, msg, get_links_noise, mlo_sig, + NULL, NULL); +} + + +static int get_links_channel_width(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct wpa_mlo_signal_info *mlo_sig = arg; + struct nlattr *link; + int rem_links; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_MLO_LINKS]) + return NL_SKIP; + + nla_for_each_nested(link, tb[NL80211_ATTR_MLO_LINKS], rem_links) { + struct nlattr *tb2[NL80211_ATTR_MAX + 1]; + int link_id; + + nla_parse(tb2, NL80211_ATTR_MAX, nla_data(link), nla_len(link), + NULL); + + if (!tb2[NL80211_ATTR_MLO_LINK_ID]) + continue; + + link_id = nla_get_u8(tb2[NL80211_ATTR_MLO_LINK_ID]); + if (link_id >= MAX_NUM_MLD_LINKS) + continue; + + if (!tb2[NL80211_ATTR_CHANNEL_WIDTH]) + continue; + mlo_sig->links[link_id].chanwidth = convert2width( + nla_get_u32(tb2[NL80211_ATTR_CHANNEL_WIDTH])); + if (tb2[NL80211_ATTR_CENTER_FREQ1]) + mlo_sig->links[link_id].center_frq1 = + nla_get_u32(tb2[NL80211_ATTR_CENTER_FREQ1]); + if (tb2[NL80211_ATTR_CENTER_FREQ2]) + mlo_sig->links[link_id].center_frq2 = + nla_get_u32(tb2[NL80211_ATTR_CENTER_FREQ2]); + } + + return NL_SKIP; +} + + +static int nl80211_get_links_channel_width(struct wpa_driver_nl80211_data *drv, + struct wpa_mlo_signal_info *mlo_sig) +{ + struct nl_msg *msg; + + msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_INTERFACE); + return send_and_recv_msgs(drv, msg, get_links_channel_width, mlo_sig, + NULL, NULL); +} + + +static int nl80211_mlo_signal_poll(void *priv, + struct wpa_mlo_signal_info *mlo_si) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + int res; + int i; + + if (drv->nlmode != NL80211_IFTYPE_STATION || + !drv->sta_mlo_info.valid_links) + return -1; + + os_memset(mlo_si, 0, sizeof(*mlo_si)); + mlo_si->valid_links = drv->sta_mlo_info.valid_links; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(mlo_si->valid_links & BIT(i))) + continue; + + res = nl80211_get_link_signal(drv, + drv->sta_mlo_info.links[i].bssid, + &mlo_si->links[i]); + if (res != 0) + return res; + + mlo_si->links[i].center_frq1 = -1; + mlo_si->links[i].center_frq2 = -1; + mlo_si->links[i].chanwidth = CHAN_WIDTH_UNKNOWN; + mlo_si->links[i].current_noise = WPA_INVALID_NOISE; + mlo_si->links[i].frequency = drv->sta_mlo_info.links[i].freq; + } + + res = nl80211_get_links_channel_width(drv, mlo_si); + if (res != 0) + return res; + + return nl80211_get_links_noise(drv, mlo_si); +} + + static int nl80211_set_param(void *priv, const char *param) { struct i802_bss *bss = priv; @@ -12710,6 +12867,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .resume = wpa_driver_nl80211_resume, .signal_monitor = nl80211_signal_monitor, .signal_poll = nl80211_signal_poll, + .mlo_signal_poll = nl80211_mlo_signal_poll, .channel_info = nl80211_channel_info, .set_param = nl80211_set_param, .get_radio_name = nl80211_get_radio_name, diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index ca2971389..90eb67f2c 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -11540,6 +11540,86 @@ static int wpas_ctrl_iface_send_dscp_query(struct wpa_supplicant *wpa_s, } +static int wpas_ctrl_iface_mlo_signal_poll(struct wpa_supplicant *wpa_s, + char *buf, size_t buflen) +{ + int ret, i; + char *pos, *end; + struct wpa_mlo_signal_info mlo_si; + + if (!wpa_s->valid_links) + return -1; + + ret = wpa_drv_mlo_signal_poll(wpa_s, &mlo_si); + if (ret) + return -1; + + pos = buf; + end = buf + buflen; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (!(mlo_si.valid_links & BIT(i))) + continue; + + ret = os_snprintf(pos, end - pos, + "LINK_ID=%d\nRSSI=%d\nLINKSPEED=%d\n" + "NOISE=%d\nFREQUENCY=%u\n", + i, mlo_si.links[i].current_signal, + mlo_si.links[i].current_txrate / 1000, + mlo_si.links[i].current_noise, + mlo_si.links[i].frequency); + if (os_snprintf_error(end - pos, ret)) + return -1; + pos += ret; + + if (mlo_si.links[i].chanwidth != CHAN_WIDTH_UNKNOWN) { + ret = os_snprintf(pos, end - pos, "WIDTH=%s\n", + channel_width_to_string( + mlo_si.links[i].chanwidth)); + if (os_snprintf_error(end - pos, ret)) + return -1; + pos += ret; + } + + if (mlo_si.links[i].center_frq1 > 0) { + ret = os_snprintf(pos, end - pos, "CENTER_FRQ1=%d\n", + mlo_si.links[i].center_frq1); + if (os_snprintf_error(end - pos, ret)) + return -1; + pos += ret; + } + + if (mlo_si.links[i].center_frq2 > 0) { + ret = os_snprintf(pos, end - pos, "CENTER_FRQ2=%d\n", + mlo_si.links[i].center_frq2); + if (os_snprintf_error(end - pos, ret)) + return -1; + pos += ret; + } + + if (mlo_si.links[i].avg_signal) { + ret = os_snprintf(pos, end - pos, + "AVG_RSSI=%d\n", + mlo_si.links[i].avg_signal); + if (os_snprintf_error(end - pos, ret)) + return -1; + pos += ret; + } + + if (mlo_si.links[i].avg_beacon_signal) { + ret = os_snprintf(pos, end - pos, + "AVG_BEACON_RSSI=%d\n", + mlo_si.links[i].avg_beacon_signal); + if (os_snprintf_error(end - pos, ret)) + return -1; + pos += ret; + } + } + + return pos - buf; +} + + static int wpas_ctrl_iface_mlo_status(struct wpa_supplicant *wpa_s, char *buf, size_t buflen) { @@ -12588,6 +12668,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strcmp(buf, "MLO_STATUS") == 0) { reply_len = wpas_ctrl_iface_mlo_status(wpa_s, reply, reply_size); + } else if (os_strcmp(buf, "MLO_SIGNAL_POLL") == 0) { + reply_len = wpas_ctrl_iface_mlo_signal_poll(wpa_s, reply, + reply_size); } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 41d353f91..6be117c1d 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -523,6 +523,14 @@ static inline int wpa_drv_signal_monitor(struct wpa_supplicant *wpa_s, int wpa_drv_signal_poll(struct wpa_supplicant *wpa_s, struct wpa_signal_info *si); +static inline int wpa_drv_mlo_signal_poll(struct wpa_supplicant *wpa_s, + struct wpa_mlo_signal_info *mlo_si) +{ + if (wpa_s->driver->mlo_signal_poll) + return wpa_s->driver->mlo_signal_poll(wpa_s->drv_priv, mlo_si); + return -1; +} + static inline int wpa_drv_channel_info(struct wpa_supplicant *wpa_s, struct wpa_channel_info *ci) { diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c index d4d09adc2..197efe0b7 100644 --- a/wpa_supplicant/wpa_cli.c +++ b/wpa_supplicant/wpa_cli.c @@ -419,6 +419,12 @@ static int wpa_cli_cmd_mlo_status(struct wpa_ctrl *ctrl, int argc, char *argv[]) } +static int wpa_cli_cmd_mlo_signal_poll(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + return wpa_ctrl_command(ctrl, "MLO_SIGNAL_POLL"); +} + + static int wpa_cli_cmd_set(struct wpa_ctrl *ctrl, int argc, char *argv[]) { char cmd[256]; @@ -4046,6 +4052,9 @@ static const struct wpa_cli_cmd wpa_cli_commands[] = { { "mlo_status", wpa_cli_cmd_mlo_status, NULL, cli_cmd_flag_none, "= get MLO status" }, + { "mlo_signal_poll", wpa_cli_cmd_mlo_signal_poll, NULL, + cli_cmd_flag_none, + "= get mlo signal parameters" }, { NULL, NULL, NULL, cli_cmd_flag_none, NULL } };