diff --git a/src/Makefile b/src/Makefile index bd5efddc780884372f0d40781aa1060c708ae0fb..c931a4115a16b5a99b8917383cfa3e18e88cd40f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -17,6 +17,7 @@ AGENT_OBJS = \ timer.o \ wifi_ubus.o \ wifi_opclass.o \ + wifi_caps.o \ wifi_scanresults.o \ wifi.o \ steer_rules.o \ diff --git a/src/agent.c b/src/agent.c index 95d5de94423310e6ae3db73ea3460e5e3e2149ad..506905819429ef274088d3b33ac8ebe2e8169919 100644 --- a/src/agent.c +++ b/src/agent.c @@ -66,6 +66,7 @@ #include "utils/liblist.h" #include "utils/utils.h" #include "wifi.h" +#include "wifi_caps.h" struct json_object; @@ -5237,431 +5238,6 @@ static void parse_radio_cac_methods(struct ubus_request *req, int type, } } -static uint8_t caps_ht_max_nss(const uint8_t *mcs_set) -{ - uint8_t nss = 0; - int i; - - for (i = 0; i < 4; i++) { - if (mcs_set[i] != 0) - nss++; - } - - return nss; -} - -static void generate_agent_caps_ht(struct wifi_caps_ht *in, struct tlv_ap_ht_cap *out) -{ - uint8_t rx_nss = 0, tx_nss = 0; - - if (in->byte[0] & BIT(1)) - out->cap |= HT_HT40_MASK; - if (in->byte[0] & BIT(5)) - out->cap |= HT_SGI20_MASK; - if (in->byte[0] & BIT(6)) - out->cap |= HT_SGI40_MASK; - - rx_nss = caps_ht_max_nss(&in->supp_mcs[0]); - - if (!(in->supp_mcs[12] & BIT(4))) - /* tx/rx equal */ - tx_nss = rx_nss; - - if (tx_nss) - tx_nss--; - if (rx_nss) - rx_nss--; - - out->cap |= (tx_nss & 0x3) << 6; - out->cap |= (rx_nss & 0x3) << 4; -} - -static uint8_t caps_vht_he_max_nss(uint16_t mcs_set) -{ - uint8_t max_nss = 0; - uint16_t data; - int i; - - for (i = 0; i < 8; i++) { - data = mcs_set >> i*2; - data &= 0x3; - - if (data != 0x3) - max_nss++; - } - - return max_nss; -} - -static void generate_agent_caps_vht(struct wifi_caps_vht *in, struct tlv_ap_vht_cap *out) -{ - uint16_t *rx_mcs, *tx_mcs; - uint8_t rx_nss, tx_nss; - uint8_t width, nss; - - rx_mcs = (uint16_t *) &in->supp_mcs[0]; - tx_mcs = (uint16_t *) &in->supp_mcs[4]; - out->rx_mcs_supported = BUF_GET_BE16(*rx_mcs); - out->tx_mcs_supported = BUF_GET_BE16(*tx_mcs); - - rx_nss = caps_vht_he_max_nss(*rx_mcs); - tx_nss = caps_vht_he_max_nss(*tx_mcs); - - out->cap[0] |= ((tx_nss - 1) & 0x07) << 5; - out->cap[0] |= ((rx_nss - 1) & 0x07) << 2; - - if (in->byte[0] & BIT(5)) - out->cap[0] |= VHT_SGI80_MASK; - if (in->byte[0] & BIT(5)) - out->cap[0] |= VHT_SGI160_8080_MASK; - if (in->byte[2] & BIT(3)) - out->cap[1] |= VHT_SU_BFR; - if (in->byte[2] & BIT(4)) - out->cap[1] |= VHT_MU_BFR; - - width = in->byte[0] & 0xc; - nss = in->byte[3] & 0xc; - - if (width >= 1) - out->cap[1] |= VHT_160_MASK; - if (width>= 2 || (width == 1 && nss >= 3)) - out->cap[1] |= VHT_8080_MASK; -} - -#if (EASYMESH_VERSION > 2) -static void generate_agent_caps_he(struct wifi_caps_he *in, struct agent_wifi_caps *out) -{ - uint16_t *rx_mcs, *tx_mcs; - uint8_t rx_nss, tx_nss; - int mcs_len = 4; - int i; - - if (in->byte_phy[0] & BIT(3)) { - mcs_len += 4; - out->he_cap.cap[0] |= HE_160_MASK; - } - - if (in->byte_phy[0] & BIT(4)) { - mcs_len += 4; - out->he_cap.cap[0] |= HE_8080_MASK; - } - - for (i = 0; i < mcs_len; i+=2) { - if (i + 1 > sizeof(out->he_cap.mcs)) - break; - /* change order */ - out->he_cap.mcs[i] = in->byte_mcs[i + 1]; - out->he_cap.mcs[i + 1] = in->byte_mcs[i]; - } - out->he_cap.mcs_len = i; - - /* set more bits */ - if (in->byte_phy[3] & BIT(7)) - out->he_cap.cap[1] |= HE_SU_BFR; - if (in->byte_phy[4] & BIT(1)) - out->he_cap.cap[1] |= HE_MU_BFR; - if (in->byte_phy[2] & BIT(6)) - out->he_cap.cap[1] |= HE_UL_MUMIMO; - if (in->byte_phy[2] & BIT(7)) - out->he_cap.cap[1] |= HE_UL_MUMIMO_OFDMA; - if (in->byte_phy[6] & BIT(6)) - out->he_cap.cap[1] |= HE_DL_MUMIMO_OFDMA; - if (in->byte_phy[2] & BIT(7)) - out->he_cap.cap[1] |= HE_UL_OFDMA; - if (in->byte_phy[2] & BIT(7)) - out->he_cap.cap[1] |= HE_DL_OFDMA; - - rx_mcs = (uint16_t *) &in->byte_mcs[0]; - tx_mcs = (uint16_t *) &in->byte_mcs[2]; - - rx_nss = caps_vht_he_max_nss(*rx_mcs); - tx_nss = caps_vht_he_max_nss(*tx_mcs); - - out->he_cap.cap[0] |= (rx_nss - 1) << 5; - out->he_cap.cap[0] |= (tx_nss - 1) << 2; -} - -static void generate_agent_caps_wifi6(struct wifi_caps_he *in, struct agent_wifi_caps *out) -{ - int i; - - /* First copy from caps_he */ - if (out->he_cap.cap[0] & HE_160_MASK) - out->wifi6_cap.caps |= HE160_SUPPORTED; - if (out->he_cap.cap[0] & HE_8080_MASK) - out->wifi6_cap.caps |= HE8080_SUPPORTED; - - out->wifi6_cap.mcs_len = out->he_cap.mcs_len; - out->wifi6_cap.caps |= (out->wifi6_cap.mcs_len & MCS_NSS_LEN_MASK); - - for (i = 0; i < out->he_cap.mcs_len; i++) { - if (i > sizeof(out->wifi6_cap.mcs)) - break; - out->wifi6_cap.mcs[i] = out->he_cap.mcs[i]; - } - - /* Parse wifi_caps phy/mac and set more */ - if (in->byte_phy[3] & BIT(7)) - out->wifi6_cap.beamform_caps |= SU_BEAMFORMER_SUPPORTED; - if (in->byte_phy[4] & BIT(9)) - out->wifi6_cap.beamform_caps |= SU_BEAMFORMEE_SUPPORTED; - if (in->byte_phy[4] & BIT(1)) - out->wifi6_cap.beamform_caps |= MU_B_FORMER_STATUS_SUPPORTED; - if (in->byte_phy[4] & (BIT(2) | BIT(3) | BIT(4))) - out->wifi6_cap.beamform_caps |= B_FORMEE_STS_LE_80_SUPPORTED; - if (in->byte_phy[4] & (BIT(5) | BIT(6) | BIT(7))) - out->wifi6_cap.beamform_caps |= B_FORMEE_STS_GT_80_SUPPORTED; - if (in->byte_phy[2] & BIT(6)) - out->wifi6_cap.beamform_caps |= UL_MU_MIMO_SUPPORTED; - if (in->byte_mac[3] & BIT(2)) - out->wifi6_cap.beamform_caps |= DL_OFDMA_SUPPORTED; - if (in->byte_mac[3] & BIT(2)) - out->wifi6_cap.beamform_caps |= UL_OFDMA_SUPPORTED; - - if (in->byte_mac[0] & BIT(1)) - out->wifi6_cap.other_caps |= TWT_REQUESTER_SUPPORTED; - if (in->byte_mac[0] & BIT(2)) - out->wifi6_cap.other_caps |= TWT_RESPONDER_SUPPORTED; -} -#else -static void generate_agent_caps_wifi6(struct wifi_caps_he *in, struct agent_wifi_caps *out) -{ - UNUSED(in); - UNUSED(out); -} -static void generate_agent_caps_he(struct wifi_caps_he *in, struct agent_wifi_caps *out) -{ - UNUSED(in); - UNUSED(out); -} -#endif - -static int generate_agent_wifi_caps(struct agent_wifi_caps *caps) -{ - if (caps->wifi_caps.valid & WIFI_CAP_HE_VALID) { - generate_agent_caps_he(&caps->wifi_caps.he, caps); - generate_agent_caps_wifi6(&caps->wifi_caps.he, caps); - } - - if (caps->wifi_caps.valid & WIFI_CAP_VHT_VALID) - generate_agent_caps_vht(&caps->wifi_caps.vht, &caps->vht_cap); - - if (caps->wifi_caps.valid & WIFI_CAP_HT_VALID) - generate_agent_caps_ht(&caps->wifi_caps.ht, &caps->ht_cap); - - return 0; -} - -static void parse_radio_wifi_caps(struct blob_attr *msg, struct wifi_caps *caps) -{ - static const struct blobmsg_policy caps_attr[] = { - [0] = { .name = "ht", .type = BLOBMSG_TYPE_TABLE }, - [1] = { .name = "vht", .type = BLOBMSG_TYPE_TABLE }, - [2] = { .name = "he", .type = BLOBMSG_TYPE_TABLE }, - [3] = { .name = "eht", .type = BLOBMSG_TYPE_TABLE }, - [4] = { .name = "ml", .type = BLOBMSG_TYPE_TABLE }, - }; - struct blob_attr *tb[ARRAY_SIZE(caps_attr)]; - - blobmsg_parse(caps_attr, ARRAY_SIZE(caps_attr), tb, blobmsg_data(msg), blob_len(msg)); - - if (tb[0]) { - static const struct blobmsg_policy ht_attr[] = { - [0] = { .name = "caps", .type = BLOBMSG_TYPE_STRING }, - [1] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, - }; - struct blob_attr *tb_ht[ARRAY_SIZE(ht_attr)]; - - blobmsg_parse(ht_attr, ARRAY_SIZE(ht_attr), tb_ht, blobmsg_data(tb[0]), blob_len(tb[0])); - - caps->valid |= WIFI_CAP_HT_VALID; - blobattrtob(tb_ht[0], caps->ht.byte, sizeof(caps->ht.byte)); - blobattrtob(tb_ht[1], caps->ht.supp_mcs, sizeof(caps->ht.supp_mcs)); - } - - if (tb[1]) { - static const struct blobmsg_policy vht_attr[] = { - [0] = { .name = "caps", .type = BLOBMSG_TYPE_STRING }, - [1] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, - }; - struct blob_attr *tb_vht[ARRAY_SIZE(vht_attr)]; - - blobmsg_parse(vht_attr, ARRAY_SIZE(vht_attr), tb_vht, blobmsg_data(tb[1]), blob_len(tb[1])); - - caps->valid |= WIFI_CAP_VHT_VALID; - blobattrtob(tb_vht[0], caps->vht.byte, sizeof(caps->vht.byte)); - blobattrtob(tb_vht[1], caps->vht.supp_mcs, sizeof(caps->vht.supp_mcs)); - } - - if (tb[2]) { - static const struct blobmsg_policy he_attr[] = { - [0] = { .name = "phy_caps", .type = BLOBMSG_TYPE_STRING }, - [1] = { .name = "mac_caps", .type = BLOBMSG_TYPE_STRING }, - [2] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, - [3] = { .name = "ppe", .type = BLOBMSG_TYPE_STRING }, - }; - struct blob_attr *tb_he[ARRAY_SIZE(he_attr)]; - - blobmsg_parse(he_attr, ARRAY_SIZE(he_attr), tb_he, blobmsg_data(tb[2]), blob_len(tb[2])); - - caps->valid |= WIFI_CAP_HE_VALID; - blobattrtob(tb_he[0], caps->he.byte_phy, sizeof(caps->he.byte_phy)); - blobattrtob(tb_he[1], caps->he.byte_mac, sizeof(caps->he.byte_mac)); - blobattrtob(tb_he[2], caps->he.byte_mcs, sizeof(caps->he.byte_mcs)); - blobattrtob(tb_he[3], caps->he.byte_ppe, sizeof(caps->he.byte_ppe)); - } - - if (tb[3]) { - static const struct blobmsg_policy eht_attr[] = { - [0] = { .name = "phy_caps", .type = BLOBMSG_TYPE_STRING }, - [1] = { .name = "mac_caps", .type = BLOBMSG_TYPE_STRING }, - [2] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, - [3] = { .name = "ppe", .type = BLOBMSG_TYPE_STRING }, - }; - struct blob_attr *tb_eht[ARRAY_SIZE(eht_attr)]; - - blobmsg_parse(eht_attr, ARRAY_SIZE(eht_attr), tb_eht, blobmsg_data(tb[3]), blob_len(tb[3])); - - caps->valid |= WIFI_CAP_EHT_VALID; - blobattrtob(tb_eht[0], caps->eht.byte_phy, sizeof(caps->eht.byte_phy)); - blobattrtob(tb_eht[1], caps->eht.byte_mac, sizeof(caps->eht.byte_mac)); - blobattrtob(tb_eht[2], caps->eht.supp_mcs, sizeof(caps->eht.supp_mcs)); - blobattrtob(tb_eht[3], caps->eht.byte_ppe_th, sizeof(caps->eht.byte_ppe_th)); - } - - if (tb[4]) { - static const struct blobmsg_policy ml_attr[] = { - [0] = { .name = "eml", .type = BLOBMSG_TYPE_STRING }, - [1] = { .name = "mld", .type = BLOBMSG_TYPE_STRING }, - [2] = { .name = "emld", .type = BLOBMSG_TYPE_STRING }, - }; - struct blob_attr *tb_ml[ARRAY_SIZE(ml_attr)]; - - blobmsg_parse(ml_attr, ARRAY_SIZE(ml_attr), tb_ml, blobmsg_data(tb[4]), blob_len(tb[4])); - - caps->valid |= WIFI_CAP_ML_VALID; - if (tb_ml[0]) { - caps->ml.valid |= WIFI_CAP_ML_EML_VALID; - blobattrtob(tb_ml[0], caps->ml.eml, sizeof(caps->ml.eml)); - } - - if (tb_ml[1]) { - caps->ml.valid |= WIFI_CAP_ML_MLD_VALID; - blobattrtob(tb_ml[1], caps->ml.mld, sizeof(caps->ml.mld)); - } - - if (tb_ml[2]) { - caps->ml.valid |= WIFI_CAP_ML_EMLD_VALID; - blobattrtob(tb_ml[2], caps->ml.emld, sizeof(caps->ml.emld)); - } - } -} - -static void parse_radio_ap_caps(struct ubus_request *req, int type, - struct blob_attr *msg) -{ - struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; - - parse_radio_wifi_caps(msg, &re->ap_caps.wifi_caps); - generate_agent_wifi_caps(&re->ap_caps); -} - -static void parse_radio_sta_caps(struct ubus_request *req, int type, - struct blob_attr *msg) -{ - struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; - - parse_radio_wifi_caps(msg, &re->sta_caps.wifi_caps); - generate_agent_wifi_caps(&re->sta_caps); -} - -#if (EASYMESH_VERSION >= 6) -static void parse_radio_mlo_ap_caps(struct ubus_request *req, int type, - struct blob_attr *msg) -{ - struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; - static const struct blobmsg_policy stats_attr[] = { - [0] = { .name = "emlsr_supported", .type = BLOBMSG_TYPE_INT8 }, - [1] = { .name = "emlmr_supported", .type = BLOBMSG_TYPE_INT8 }, - [2] = { .name = "nstr", .type = BLOBMSG_TYPE_INT8 }, - [3] = { .name = "str", .type = BLOBMSG_TYPE_INT8 }, - [4] = { .name = "max_links", .type = BLOBMSG_TYPE_INT32 }, - [5] = { .name = "ttlm", .type = BLOBMSG_TYPE_INT32 }, - - }; - struct blob_attr *tb[ARRAY_SIZE(stats_attr)]; - struct agent_config_radio *rcfg; - - blobmsg_parse(stats_attr, ARRAY_SIZE(stats_attr), tb, blobmsg_data(msg), blob_len(msg)); - - rcfg = get_agent_config_radio(&re->agent->cfg, re->name); - if (!rcfg) - return; - - if (rcfg->mlo_capable) { - if (tb[0]) - re->wifi7_caps.ap_emlsr_support = !!blobmsg_get_u8(tb[0]); - - if (tb[1]) - re->wifi7_caps.ap_emlmr_support = !!blobmsg_get_u8(tb[1]); - - if (tb[2]) - re->wifi7_caps.ap_nstr_support = !!blobmsg_get_u8(tb[2]); - - if (tb[3]) - re->wifi7_caps.ap_str_support = !!blobmsg_get_u8(tb[3]); - } - - if (tb[4]) - re->wifi7_caps.max_ap_links = blobmsg_get_u32(tb[4]); - - if (tb[5]) - re->wifi7_caps.ap_ttlm = blobmsg_get_u32(tb[5]); -} - -static void parse_radio_mlo_bsta_caps(struct ubus_request *req, int type, - struct blob_attr *msg) -{ - struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; - static const struct blobmsg_policy stats_attr[] = { - [0] = { .name = "emlsr_supported", .type = BLOBMSG_TYPE_INT8 }, - [1] = { .name = "emlmr_supported", .type = BLOBMSG_TYPE_INT8 }, - [2] = { .name = "nstr", .type = BLOBMSG_TYPE_INT8 }, - [3] = { .name = "str", .type = BLOBMSG_TYPE_INT8 }, - [4] = { .name = "max_links", .type = BLOBMSG_TYPE_INT32 }, - [5] = { .name = "ttlm", .type = BLOBMSG_TYPE_INT32 }, - - }; - struct blob_attr *tb[ARRAY_SIZE(stats_attr)]; - struct agent_config_radio *rcfg; - - blobmsg_parse(stats_attr, ARRAY_SIZE(stats_attr), tb, blobmsg_data(msg), blob_len(msg)); - - rcfg = get_agent_config_radio(&re->agent->cfg, re->name); - if (!rcfg) - return; - - if (rcfg->mlo_capable) { - if (tb[0]) - re->wifi7_caps.bsta_emlsr_support = !!blobmsg_get_u8(tb[0]); - - if (tb[1]) - re->wifi7_caps.bsta_emlmr_support = !!blobmsg_get_u8(tb[1]); - - if (tb[2]) - re->wifi7_caps.bsta_nstr_support = !!blobmsg_get_u8(tb[2]); - - if (tb[3]) - re->wifi7_caps.bsta_str_support = !!blobmsg_get_u8(tb[3]); - } - if (tb[4]) - re->wifi7_caps.max_bsta_links = blobmsg_get_u32(tb[4]); - - if (tb[5]) - re->wifi7_caps.bsta_ttlm = blobmsg_get_u32(tb[5]); -} -#endif - static void parse_radio(struct ubus_request *req, int type, struct blob_attr *msg) { @@ -5780,19 +5356,11 @@ static void parse_radio(struct ubus_request *req, int type, if (tb[15]) parse_radio_cac_methods(req, type, tb[15]); - if (tb[16]) { + if (tb[16]) parse_radio_ap_caps(req, type, tb[16]); -#if (EASYMESH_VERSION >= 6) - parse_radio_mlo_ap_caps(req, type, tb[16]); -#endif - } - if (tb[17]) { + if (tb[17]) parse_radio_sta_caps(req, type, tb[17]); -#if (EASYMESH_VERSION >= 6) - parse_radio_mlo_bsta_caps(req, type, tb[17]); -#endif - } agent_config_opclass(re); } diff --git a/src/wifi_caps.c b/src/wifi_caps.c new file mode 100644 index 0000000000000000000000000000000000000000..fcd477f9408a840d4fe62e2d22ae94d44eecd8ff --- /dev/null +++ b/src/wifi_caps.c @@ -0,0 +1,454 @@ +/* + * wifi_caps.c - agent wifi capabilities + * + * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved. + * + */ + +#include "wifi_caps.h" + +#include <easy/utils.h> +#include <easymesh.h> +#include <libubox/blob.h> +#include <libubox/blobmsg.h> +#include <libubox/blobmsg_json.h> +#include <libubus.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ubusmsg.h> + +#include "agent.h" +#include "config.h" +#include "utils/debug.h" +#include "utils/utils.h" +#include "wifi.h" + + +static uint8_t caps_ht_max_nss(const uint8_t *mcs_set) +{ + uint8_t nss = 0; + int i; + + for (i = 0; i < 4; i++) { + if (mcs_set[i] != 0) + nss++; + } + + return nss; +} + +static void generate_agent_caps_ht(struct wifi_caps_ht *in, struct tlv_ap_ht_cap *out) +{ + uint8_t rx_nss = 0, tx_nss = 0; + + if (in->byte[0] & BIT(1)) + out->cap |= HT_HT40_MASK; + if (in->byte[0] & BIT(5)) + out->cap |= HT_SGI20_MASK; + if (in->byte[0] & BIT(6)) + out->cap |= HT_SGI40_MASK; + + rx_nss = caps_ht_max_nss(&in->supp_mcs[0]); + + if (!(in->supp_mcs[12] & BIT(4))) + /* tx/rx equal */ + tx_nss = rx_nss; + + if (tx_nss) + tx_nss--; + if (rx_nss) + rx_nss--; + + out->cap |= (tx_nss & 0x3) << 6; + out->cap |= (rx_nss & 0x3) << 4; +} + +static uint8_t caps_vht_he_max_nss(uint16_t mcs_set) +{ + uint8_t max_nss = 0; + uint16_t data; + int i; + + for (i = 0; i < 8; i++) { + data = mcs_set >> i*2; + data &= 0x3; + + if (data != 0x3) + max_nss++; + } + + return max_nss; +} + +static void generate_agent_caps_vht(struct wifi_caps_vht *in, struct tlv_ap_vht_cap *out) +{ + uint16_t *rx_mcs, *tx_mcs; + uint8_t rx_nss, tx_nss; + uint8_t width, nss; + + rx_mcs = (uint16_t *) &in->supp_mcs[0]; + tx_mcs = (uint16_t *) &in->supp_mcs[4]; + out->rx_mcs_supported = BUF_GET_BE16(*rx_mcs); + out->tx_mcs_supported = BUF_GET_BE16(*tx_mcs); + + rx_nss = caps_vht_he_max_nss(*rx_mcs); + tx_nss = caps_vht_he_max_nss(*tx_mcs); + + out->cap[0] |= ((tx_nss - 1) & 0x07) << 5; + out->cap[0] |= ((rx_nss - 1) & 0x07) << 2; + + if (in->byte[0] & BIT(5)) + out->cap[0] |= VHT_SGI80_MASK; + if (in->byte[0] & BIT(5)) + out->cap[0] |= VHT_SGI160_8080_MASK; + if (in->byte[2] & BIT(3)) + out->cap[1] |= VHT_SU_BFR; + if (in->byte[2] & BIT(4)) + out->cap[1] |= VHT_MU_BFR; + + width = in->byte[0] & 0xc; + nss = in->byte[3] & 0xc; + + if (width >= 1) + out->cap[1] |= VHT_160_MASK; + if (width>= 2 || (width == 1 && nss >= 3)) + out->cap[1] |= VHT_8080_MASK; +} + +#if (EASYMESH_VERSION > 2) +static void generate_agent_caps_he(struct wifi_caps_he *in, struct agent_wifi_caps *out) +{ + uint16_t *rx_mcs, *tx_mcs; + uint8_t rx_nss, tx_nss; + int mcs_len = 4; + int i; + + if (in->byte_phy[0] & BIT(3)) { + mcs_len += 4; + out->he_cap.cap[0] |= HE_160_MASK; + } + + if (in->byte_phy[0] & BIT(4)) { + mcs_len += 4; + out->he_cap.cap[0] |= HE_8080_MASK; + } + + for (i = 0; i < mcs_len; i+=2) { + if (i + 1 > sizeof(out->he_cap.mcs)) + break; + /* change order */ + out->he_cap.mcs[i] = in->byte_mcs[i + 1]; + out->he_cap.mcs[i + 1] = in->byte_mcs[i]; + } + out->he_cap.mcs_len = i; + + /* set more bits */ + if (in->byte_phy[3] & BIT(7)) + out->he_cap.cap[1] |= HE_SU_BFR; + if (in->byte_phy[4] & BIT(1)) + out->he_cap.cap[1] |= HE_MU_BFR; + if (in->byte_phy[2] & BIT(6)) + out->he_cap.cap[1] |= HE_UL_MUMIMO; + if (in->byte_phy[2] & BIT(7)) + out->he_cap.cap[1] |= HE_UL_MUMIMO_OFDMA; + if (in->byte_phy[6] & BIT(6)) + out->he_cap.cap[1] |= HE_DL_MUMIMO_OFDMA; + if (in->byte_phy[2] & BIT(7)) + out->he_cap.cap[1] |= HE_UL_OFDMA; + if (in->byte_phy[2] & BIT(7)) + out->he_cap.cap[1] |= HE_DL_OFDMA; + + rx_mcs = (uint16_t *) &in->byte_mcs[0]; + tx_mcs = (uint16_t *) &in->byte_mcs[2]; + + rx_nss = caps_vht_he_max_nss(*rx_mcs); + tx_nss = caps_vht_he_max_nss(*tx_mcs); + + out->he_cap.cap[0] |= (rx_nss - 1) << 5; + out->he_cap.cap[0] |= (tx_nss - 1) << 2; +} + +static void generate_agent_caps_wifi6(struct wifi_caps_he *in, struct agent_wifi_caps *out) +{ + int i; + + /* First copy from caps_he */ + if (out->he_cap.cap[0] & HE_160_MASK) + out->wifi6_cap.caps |= HE160_SUPPORTED; + if (out->he_cap.cap[0] & HE_8080_MASK) + out->wifi6_cap.caps |= HE8080_SUPPORTED; + + out->wifi6_cap.mcs_len = out->he_cap.mcs_len; + out->wifi6_cap.caps |= (out->wifi6_cap.mcs_len & MCS_NSS_LEN_MASK); + + for (i = 0; i < out->he_cap.mcs_len; i++) { + if (i > sizeof(out->wifi6_cap.mcs)) + break; + out->wifi6_cap.mcs[i] = out->he_cap.mcs[i]; + } + + /* Parse wifi_caps phy/mac and set more */ + if (in->byte_phy[3] & BIT(7)) + out->wifi6_cap.beamform_caps |= SU_BEAMFORMER_SUPPORTED; + if (in->byte_phy[4] & BIT(9)) + out->wifi6_cap.beamform_caps |= SU_BEAMFORMEE_SUPPORTED; + if (in->byte_phy[4] & BIT(1)) + out->wifi6_cap.beamform_caps |= MU_B_FORMER_STATUS_SUPPORTED; + if (in->byte_phy[4] & (BIT(2) | BIT(3) | BIT(4))) + out->wifi6_cap.beamform_caps |= B_FORMEE_STS_LE_80_SUPPORTED; + if (in->byte_phy[4] & (BIT(5) | BIT(6) | BIT(7))) + out->wifi6_cap.beamform_caps |= B_FORMEE_STS_GT_80_SUPPORTED; + if (in->byte_phy[2] & BIT(6)) + out->wifi6_cap.beamform_caps |= UL_MU_MIMO_SUPPORTED; + if (in->byte_mac[3] & BIT(2)) + out->wifi6_cap.beamform_caps |= DL_OFDMA_SUPPORTED; + if (in->byte_mac[3] & BIT(2)) + out->wifi6_cap.beamform_caps |= UL_OFDMA_SUPPORTED; + + if (in->byte_mac[0] & BIT(1)) + out->wifi6_cap.other_caps |= TWT_REQUESTER_SUPPORTED; + if (in->byte_mac[0] & BIT(2)) + out->wifi6_cap.other_caps |= TWT_RESPONDER_SUPPORTED; +} +#endif + +static int generate_agent_wifi_caps(struct agent_wifi_caps *caps) +{ +#if (EASYMESH_VERSION > 2) + if (caps->wifi_caps.valid & WIFI_CAP_HE_VALID) { + generate_agent_caps_he(&caps->wifi_caps.he, caps); + generate_agent_caps_wifi6(&caps->wifi_caps.he, caps); + } +#endif + + if (caps->wifi_caps.valid & WIFI_CAP_VHT_VALID) + generate_agent_caps_vht(&caps->wifi_caps.vht, &caps->vht_cap); + + if (caps->wifi_caps.valid & WIFI_CAP_HT_VALID) + generate_agent_caps_ht(&caps->wifi_caps.ht, &caps->ht_cap); + + return 0; +} + +static void parse_radio_wifi_caps(struct blob_attr *msg, struct wifi_caps *caps) +{ + static const struct blobmsg_policy caps_attr[] = { + [0] = { .name = "ht", .type = BLOBMSG_TYPE_TABLE }, + [1] = { .name = "vht", .type = BLOBMSG_TYPE_TABLE }, + [2] = { .name = "he", .type = BLOBMSG_TYPE_TABLE }, + [3] = { .name = "eht", .type = BLOBMSG_TYPE_TABLE }, + [4] = { .name = "ml", .type = BLOBMSG_TYPE_TABLE }, + }; + struct blob_attr *tb[ARRAY_SIZE(caps_attr)]; + + blobmsg_parse(caps_attr, ARRAY_SIZE(caps_attr), tb, blobmsg_data(msg), blob_len(msg)); + + if (tb[0]) { + static const struct blobmsg_policy ht_attr[] = { + [0] = { .name = "caps", .type = BLOBMSG_TYPE_STRING }, + [1] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb_ht[ARRAY_SIZE(ht_attr)]; + + blobmsg_parse(ht_attr, ARRAY_SIZE(ht_attr), tb_ht, blobmsg_data(tb[0]), blob_len(tb[0])); + + caps->valid |= WIFI_CAP_HT_VALID; + blobattrtob(tb_ht[0], caps->ht.byte, sizeof(caps->ht.byte)); + blobattrtob(tb_ht[1], caps->ht.supp_mcs, sizeof(caps->ht.supp_mcs)); + } + + if (tb[1]) { + static const struct blobmsg_policy vht_attr[] = { + [0] = { .name = "caps", .type = BLOBMSG_TYPE_STRING }, + [1] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb_vht[ARRAY_SIZE(vht_attr)]; + + blobmsg_parse(vht_attr, ARRAY_SIZE(vht_attr), tb_vht, blobmsg_data(tb[1]), blob_len(tb[1])); + + caps->valid |= WIFI_CAP_VHT_VALID; + blobattrtob(tb_vht[0], caps->vht.byte, sizeof(caps->vht.byte)); + blobattrtob(tb_vht[1], caps->vht.supp_mcs, sizeof(caps->vht.supp_mcs)); + } + +#if (EASYMESH_VERSION > 2) + if (tb[2]) { + static const struct blobmsg_policy he_attr[] = { + [0] = { .name = "phy_caps", .type = BLOBMSG_TYPE_STRING }, + [1] = { .name = "mac_caps", .type = BLOBMSG_TYPE_STRING }, + [2] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, + [3] = { .name = "ppe", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb_he[ARRAY_SIZE(he_attr)]; + + blobmsg_parse(he_attr, ARRAY_SIZE(he_attr), tb_he, blobmsg_data(tb[2]), blob_len(tb[2])); + + caps->valid |= WIFI_CAP_HE_VALID; + blobattrtob(tb_he[0], caps->he.byte_phy, sizeof(caps->he.byte_phy)); + blobattrtob(tb_he[1], caps->he.byte_mac, sizeof(caps->he.byte_mac)); + blobattrtob(tb_he[2], caps->he.byte_mcs, sizeof(caps->he.byte_mcs)); + blobattrtob(tb_he[3], caps->he.byte_ppe, sizeof(caps->he.byte_ppe)); + } +#endif + +#if (EASYMESH_VERSION >= 6) + if (tb[3]) { + static const struct blobmsg_policy eht_attr[] = { + [0] = { .name = "phy_caps", .type = BLOBMSG_TYPE_STRING }, + [1] = { .name = "mac_caps", .type = BLOBMSG_TYPE_STRING }, + [2] = { .name = "mcs", .type = BLOBMSG_TYPE_STRING }, + [3] = { .name = "ppe", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb_eht[ARRAY_SIZE(eht_attr)]; + + blobmsg_parse(eht_attr, ARRAY_SIZE(eht_attr), tb_eht, blobmsg_data(tb[3]), blob_len(tb[3])); + + caps->valid |= WIFI_CAP_EHT_VALID; + blobattrtob(tb_eht[0], caps->eht.byte_phy, sizeof(caps->eht.byte_phy)); + blobattrtob(tb_eht[1], caps->eht.byte_mac, sizeof(caps->eht.byte_mac)); + blobattrtob(tb_eht[2], caps->eht.supp_mcs, sizeof(caps->eht.supp_mcs)); + blobattrtob(tb_eht[3], caps->eht.byte_ppe_th, sizeof(caps->eht.byte_ppe_th)); + } + + if (tb[4]) { + static const struct blobmsg_policy ml_attr[] = { + [0] = { .name = "eml", .type = BLOBMSG_TYPE_STRING }, + [1] = { .name = "mld", .type = BLOBMSG_TYPE_STRING }, + [2] = { .name = "emld", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb_ml[ARRAY_SIZE(ml_attr)]; + + blobmsg_parse(ml_attr, ARRAY_SIZE(ml_attr), tb_ml, blobmsg_data(tb[4]), blob_len(tb[4])); + + caps->valid |= WIFI_CAP_ML_VALID; + if (tb_ml[0]) { + caps->ml.valid |= WIFI_CAP_ML_EML_VALID; + blobattrtob(tb_ml[0], caps->ml.eml, sizeof(caps->ml.eml)); + } + + if (tb_ml[1]) { + caps->ml.valid |= WIFI_CAP_ML_MLD_VALID; + blobattrtob(tb_ml[1], caps->ml.mld, sizeof(caps->ml.mld)); + } + + if (tb_ml[2]) { + caps->ml.valid |= WIFI_CAP_ML_EMLD_VALID; + blobattrtob(tb_ml[2], caps->ml.emld, sizeof(caps->ml.emld)); + } + } +#endif +} + +#if (EASYMESH_VERSION >= 6) +static void parse_radio_mlo_ap_caps(struct ubus_request *req, int type, + struct blob_attr *msg) +{ + struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; + static const struct blobmsg_policy stats_attr[] = { + [0] = { .name = "emlsr_supported", .type = BLOBMSG_TYPE_INT8 }, + [1] = { .name = "emlmr_supported", .type = BLOBMSG_TYPE_INT8 }, + [2] = { .name = "nstr", .type = BLOBMSG_TYPE_INT8 }, + [3] = { .name = "str", .type = BLOBMSG_TYPE_INT8 }, + [4] = { .name = "max_links", .type = BLOBMSG_TYPE_INT32 }, + [5] = { .name = "ttlm", .type = BLOBMSG_TYPE_INT32 }, + + }; + struct blob_attr *tb[ARRAY_SIZE(stats_attr)]; + struct agent_config_radio *rcfg; + + blobmsg_parse(stats_attr, ARRAY_SIZE(stats_attr), tb, blobmsg_data(msg), blob_len(msg)); + + rcfg = get_agent_config_radio(&re->agent->cfg, re->name); + if (!rcfg) + return; + + if (rcfg->mlo_capable) { + if (tb[0]) + re->wifi7_caps.ap_emlsr_support = !!blobmsg_get_u8(tb[0]); + + if (tb[1]) + re->wifi7_caps.ap_emlmr_support = !!blobmsg_get_u8(tb[1]); + + if (tb[2]) + re->wifi7_caps.ap_nstr_support = !!blobmsg_get_u8(tb[2]); + + if (tb[3]) + re->wifi7_caps.ap_str_support = !!blobmsg_get_u8(tb[3]); + } + + if (tb[4]) + re->wifi7_caps.max_ap_links = blobmsg_get_u32(tb[4]); + + if (tb[5]) + re->wifi7_caps.ap_ttlm = blobmsg_get_u32(tb[5]); +} + +static void parse_radio_mlo_bsta_caps(struct ubus_request *req, int type, + struct blob_attr *msg) +{ + struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; + static const struct blobmsg_policy stats_attr[] = { + [0] = { .name = "emlsr_supported", .type = BLOBMSG_TYPE_INT8 }, + [1] = { .name = "emlmr_supported", .type = BLOBMSG_TYPE_INT8 }, + [2] = { .name = "nstr", .type = BLOBMSG_TYPE_INT8 }, + [3] = { .name = "str", .type = BLOBMSG_TYPE_INT8 }, + [4] = { .name = "max_links", .type = BLOBMSG_TYPE_INT32 }, + [5] = { .name = "ttlm", .type = BLOBMSG_TYPE_INT32 }, + + }; + struct blob_attr *tb[ARRAY_SIZE(stats_attr)]; + struct agent_config_radio *rcfg; + + blobmsg_parse(stats_attr, ARRAY_SIZE(stats_attr), tb, blobmsg_data(msg), blob_len(msg)); + + rcfg = get_agent_config_radio(&re->agent->cfg, re->name); + if (!rcfg) + return; + + if (rcfg->mlo_capable) { + if (tb[0]) + re->wifi7_caps.bsta_emlsr_support = !!blobmsg_get_u8(tb[0]); + + if (tb[1]) + re->wifi7_caps.bsta_emlmr_support = !!blobmsg_get_u8(tb[1]); + + if (tb[2]) + re->wifi7_caps.bsta_nstr_support = !!blobmsg_get_u8(tb[2]); + + if (tb[3]) + re->wifi7_caps.bsta_str_support = !!blobmsg_get_u8(tb[3]); + } + if (tb[4]) + re->wifi7_caps.max_bsta_links = blobmsg_get_u32(tb[4]); + + if (tb[5]) + re->wifi7_caps.bsta_ttlm = blobmsg_get_u32(tb[5]); +} +#endif + +void parse_radio_ap_caps(struct ubus_request *req, int type, + struct blob_attr *msg) +{ + struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; + + parse_radio_wifi_caps(msg, &re->ap_caps.wifi_caps); + generate_agent_wifi_caps(&re->ap_caps); + +#if (EASYMESH_VERSION >= 6) + parse_radio_mlo_ap_caps(req, type, msg); +#endif +} + +void parse_radio_sta_caps(struct ubus_request *req, int type, + struct blob_attr *msg) +{ + struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv; + + parse_radio_wifi_caps(msg, &re->sta_caps.wifi_caps); + generate_agent_wifi_caps(&re->sta_caps); + +#if (EASYMESH_VERSION >= 6) + parse_radio_mlo_bsta_caps(req, type, msg); +#endif +} diff --git a/src/wifi_caps.h b/src/wifi_caps.h new file mode 100644 index 0000000000000000000000000000000000000000..5e5d9c073a901b5c4b1b0c10de30126e2432288c --- /dev/null +++ b/src/wifi_caps.h @@ -0,0 +1,19 @@ +/* + * wifi_caps.h + * + * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved. + * + */ + +#ifndef WIFI_CAPS_H +#define WIFI_CAPS_H + +#include <libubox/blobmsg.h> +#include <libubus.h> + +void parse_radio_ap_caps(struct ubus_request *req, int type, + struct blob_attr *msg); +void parse_radio_sta_caps(struct ubus_request *req, int type, + struct blob_attr *msg); + +#endif /* WIFI_CAPS_H */