wpa_supplicant: add MLO client support
authorFelix Fietkau <[email protected]>
Sun, 21 Sep 2025 13:39:35 +0000 (15:39 +0200)
committerFelix Fietkau <[email protected]>
Wed, 24 Sep 2025 11:45:59 +0000 (13:45 +0200)
Can also be used for a client mode interface that is able to connect on
multiple bands individually, while handling hostapd state for the correct
band.

Signed-off-by: Felix Fietkau <[email protected]>
package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc
package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc
package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
package/network/services/hostapd/files/wpa_supplicant.uc
package/network/services/hostapd/patches/601-ucode_support.patch
package/network/services/hostapd/src/wpa_supplicant/ucode.c
package/network/services/hostapd/src/wpa_supplicant/ucode.h

index 30e196ddce6318566907df67e6235c50415fbef4..3ef150694f7c91ac66cf0bf17868e8d4842e7908 100644 (file)
@@ -248,6 +248,8 @@ export function generate(config_list, data, interface) {
                iface: interface.config.ifname,
                config: file_name,
                '4addr': !!interface.config.wds,
+               mlo: !!interface.config.mlo,
+               freq_list: data.config.scan_list,
                powersave: false
        };
 
index d793ef8bfe9a0c635791a6ca5769d8ece15990d9..fa9a5faafb7d0db779d10ae349f4602cd7bd12e9 100644 (file)
@@ -17,6 +17,16 @@ let wdev_handler = {};
 let wdev_script_task, wdev_script_timeout;
 let handler_timer;
 
+function supplicant_start_mlo()
+{
+       ubus.call({
+               object: "wpa_supplicant",
+               method: "mld_start",
+               return: "ignore",
+               data: { },
+       });
+}
+
 function delete_wdev(name)
 {
        delete netifd.wireless.devices[name];
@@ -216,6 +226,9 @@ function run_next_handler()
 {
        while (!wdev_cur && length(wdev_handler) > 0)
                __run_next_handler();
+
+       if (!wdev_cur && !length(wdev_handler))
+               supplicant_start_mlo();
 }
 
 function run_handler(wdev, op, cb)
index 360fe2503eb0ff90aa81a2b65a9b3bd5f01b6ec9..19c38d11e566cd57a83ac4d190c5cab5d98d621d 100644 (file)
@@ -17,12 +17,12 @@ let wireless = netifd.wireless = {
        path: realpath(netifd.main_path + "/wireless"),
 };
 
-function hostapd_update_mlo()
+function wpad_update_mlo(service, mode)
 {
        let config = {};
 
        for (let ifname, data in wireless.mlo) {
-               if (data.mode != "ap")
+               if (data.mode != mode)
                        continue;
 
                data.phy = find_phy(data.radio_config[0], true);
@@ -33,17 +33,28 @@ function hostapd_update_mlo()
        }
 
        ubus.call({
-               object: "hostapd",
+               object: service,
                method: "mld_set",
                return: "ignore",
                data: { config },
        });
 }
 
+function hostapd_update_mlo()
+{
+       wpad_update_mlo("hostapd", "ap");
+}
+
+function supplicant_update_mlo()
+{
+       wpad_update_mlo("wpa_supplicant", "sta");
+}
+
 function update_config(new_devices, mlo_vifs)
 {
        wireless.mlo = mlo_vifs;
        hostapd_update_mlo();
+       supplicant_update_mlo();
 
        for (let name, dev in wireless.devices)
                if (!new_devices[name])
@@ -516,6 +527,8 @@ wireless.obj = ubus.publish("network.wireless", ubus_obj);
 wireless.listener = ubus.listener("ubus.object.add", (event, msg) => {
        if (msg.path == "hostapd")
                hostapd_update_mlo();
+       else if (msg.path == "wpa_supplicant")
+               supplicant_update_mlo();
 });
 
 return {
index dd371154e1ed9a3ef820c68f08b6e45cfb5241c4..b4cafe80a6602166c28f55a7f884e1d9a989c661 100644 (file)
@@ -13,6 +13,7 @@ function ex_handler(e)
 }
 libubus.guard(ex_handler);
 
+wpas.data.mld = {};
 wpas.data.config = {};
 wpas.data.iface_phy = {};
 wpas.data.macaddr_list = {};
@@ -77,50 +78,39 @@ function prepare_config(config, radio)
        return { config };
 }
 
-function set_config(config_name, phy_name, radio, num_global_macaddr, macaddr_base, config_list)
+function phy_dev_open(phy_name)
 {
-       let phy = wpas.data.config[config_name];
-
-       if (radio < 0)
-               radio = null;
-
+       let phy = wpas.data.config[phy_name];
        if (!phy) {
-               phy = vlist_new(iface_cb, false);
-               phy.name = phy_name;
-               wpas.data.config[config_name] = phy;
+               warn(`Missing phy config for ${phy_name}\n`);
+               return;
        }
 
-       phy.radio = radio;
-       phy.num_global_macaddr = num_global_macaddr;
-       phy.macaddr_base = macaddr_base;
+       let phydev = phy_open(phy.name, phy.radio);
+       if (!phydev)
+               return;
 
-       let values = [];
-       for (let config in config_list)
-               push(values, [ config.iface, prepare_config(config) ]);
+       let macaddr_list = wpas.data.macaddr_list[phy_name];
+       phydev.macaddr_init(macaddr_list, {
+               num_global: phy.num_global_macaddr,
+               macaddr_base: phy.macaddr_base,
+       });
 
-       phy.update(values);
+       return phydev;
 }
 
 function start_pending(phy_name)
 {
        let phy = wpas.data.config[phy_name];
-       let ubus = wpas.data.ubus;
-
        if (!phy || !phy.data)
                return;
 
-       let phydev = phy_open(phy.name, phy.radio);
+       let phydev = phy_dev_open(phy_name);
        if (!phydev) {
                wpas.printf(`Could not open phy ${phy_name}`);
                return;
        }
 
-       let macaddr_list = wpas.data.macaddr_list[phy_name];
-       phydev.macaddr_init(macaddr_list, {
-               num_global: phy.num_global_macaddr,
-               macaddr_base: phy.macaddr_base,
-       });
-
        for (let ifname in phy.data)
                iface_start(phydev, phy.data[ifname]);
 }
@@ -136,6 +126,235 @@ function phy_name(phy, radio)
        return phy;
 }
 
+function mld_remove(data)
+{
+       if (!data.radio_mask_up)
+               return;
+
+       let name = data.name;
+       wpas.printf(`Remove MLD interface ${name}`);
+       wpas.remove_iface(name);
+       wdev_remove(name);
+       data.radio_mask_up = 0;
+}
+
+function mld_first_phy(data)
+{
+       let mask = data.radio_mask_present;
+
+       for (let i = 0; mask; i++, mask >>= 1)
+               if (mask & 1)
+                       return i;
+}
+
+function mld_radio_index(data, freq)
+{
+       let phys = data.phy_config;
+       for (let i = 0; i < length(phys); i++)
+               if (phys[i] && index(phys[i].freq_list, freq) >= 0)
+                       return i;
+}
+
+function mld_add(data, phy_list)
+{
+       let name = data.name;
+       phy_list ??= [];
+
+       wpas.printf(`Add MLD interface ${name}`);
+
+       let radio = mld_first_phy(data);
+       if (radio == null)
+               return;
+
+       let phy_name = data.phy + '.' + radio;
+       let phydev = phy_list[phy_name];
+       if (!phydev) {
+               phydev = phy_dev_open(phy_name);
+               if (!phydev)
+                       return;
+
+               phy_list[phy_name] = phydev;
+       }
+
+       let wdev_config = { ...data.config, radio_mask: data.radio_mask };
+       let ret = phydev.wdev_add(name, wdev_config);
+       if (ret)
+               wpas.printf(`Failed to create device ${name}: ${ret}`);
+
+       let first_config = data.phy_config[radio];
+
+       wdev_set_up(name, true);
+       wpas.add_iface(first_config);
+
+       let iface = wpas.interfaces[name];
+       if (!iface) {
+               wpas.printf(`Interface ${name} not found after adding\n`);
+               wpas.remove_iface(name);
+               wdev_remove(name);
+               return;
+       }
+
+       if (length(data.freq_list) > 0)
+               iface.config('freq_list', data.freq_list);
+
+       data.radio_mask_up = data.radio_mask_present;
+}
+
+function mld_remove_links(data)
+{
+       // TODO
+       mld_remove(data);
+}
+
+function mld_add_links(data)
+{
+       // TODO: incremental update
+       mld_remove(data);
+       mld_add(data);
+}
+
+function mld_set_config(config)
+{
+       let prev_mld = { ...wpas.data.mld };
+       let new_mld = {};
+       let phy_list = {};
+       let new_config = !length(prev_mld);
+
+       wpas.printf(`Set MLD config: ${keys(config)}`);
+
+       for (let name, data in config) {
+               let prev = prev_mld[name];
+               if (prev && is_equal(prev.config, data)) {
+                       new_mld[name] = prev;
+                       delete prev_mld[name];
+                       continue;
+               }
+
+               let radio_mask = 0;
+               for (let r in data.radios)
+                       if (r != null)
+                               radio_mask |= 1 << r;
+
+               new_mld[name] = {
+                       name,
+                       config: data,
+                       phy: data.phy,
+                       phy_config: [],
+                       radio_mask,
+                       radio_mask_up: 0,
+                       radio_mask_present: 0,
+               };
+       }
+
+       for (let name, data in prev_mld)
+               mld_remove(data);
+
+       wpas.data.mld = new_mld;
+
+}
+
+function mld_set_iface_config(name, data, radio, config)
+{
+       wpas.printf(`Set MLD interface ${name} radio ${radio} config: ${keys(config)}`);
+
+       data.phy_config[radio] = config;
+       if (config)
+               data.radio_mask_present |= 1 << radio;
+       else
+               data.radio_mask_present &= ~(1 << radio);
+
+       let freq_list;
+       for (let config in data.phy_config) {
+               if (!config || !config.freq_list)
+                       continue;
+               if (!freq_list)
+                       freq_list = [ ...config.freq_list ];
+               else
+                       push(freq_list, ...config.freq_list);
+       }
+
+       data.freq_list = freq_list;
+}
+
+function mld_update_iface(name, data) {
+       if (!data.radio_mask_up)
+               return;
+
+       if (!data.radio_mask_present) {
+               mld_remove(data);
+               return;
+       }
+
+       let mask = data.radio_mask_up & ~data.radio_mask_present;
+       if (!mask)
+               return;
+
+       mld_remove_links(data);
+}
+
+function mld_update_phy(phy, ifaces) {
+       for (let name, data in wpas.data.mld) {
+               if (data.phy != phy.name)
+                       continue;
+
+               mld_set_iface_config(name, data, phy.radio, ifaces[name]);
+               mld_update_iface(name, data);
+       }
+}
+
+function mld_start() {
+       wpas.printf(`Start pending MLD interfaces\n`);
+
+       let phy_list = {};
+       for (let name, data in wpas.data.mld) {
+               wpas.printf(`MLD interface ${name} present=${data.radio_mask_present} up=${data.radio_mask_up}`);
+               let add_mask = data.radio_mask_present & ~data.radio_mask_up;
+               if (!add_mask)
+                       continue;
+
+               if (!data.radio_mask_up)
+                       mld_add(data, phy_list);
+               else
+                       mld_add_links(data);
+       }
+}
+
+function mld_bss_allowed(data, bss) {
+       if (!data.freq_list)
+               return true;
+
+       return index(data.freq_list, bss.freq) >= 0;
+}
+
+function set_config(config_name, phy_name, radio, num_global_macaddr, macaddr_base, config_list)
+{
+       let phy = wpas.data.config[config_name];
+
+       if (radio < 0)
+               radio = null;
+
+       if (!phy) {
+               phy = vlist_new(iface_cb, false);
+               phy.name = phy_name;
+               wpas.data.config[config_name] = phy;
+       }
+
+       phy.radio = radio;
+       phy.num_global_macaddr = num_global_macaddr;
+       phy.macaddr_base = macaddr_base;
+
+       let values = [];
+       let mlo_ifaces = {};
+       for (let config in config_list)
+               if (config.mlo)
+                       mlo_ifaces[config.iface] = config;
+               else
+                       push(values, [ config.iface, prepare_config(config) ]);
+
+       mld_update_phy(phy, mlo_ifaces);
+       phy.update(values);
+}
+
 let main_obj = {
        phy_set_state: {
                args: {
@@ -214,6 +433,25 @@ let main_obj = {
                        return libubus.STATUS_NOT_FOUND;
                }
        },
+       mld_set: {
+               args: {
+                       config: {}
+               },
+               call: function(req) {
+                       if (!req.args.config)
+                               return libubus.STATUS_INVALID_ARGUMENT;
+
+                       mld_set_config(req.args.config);
+                       return 0;
+               }
+       },
+       mld_start: {
+               args: {},
+               call: function(req) {
+                       mld_start();
+                       return 0;
+               }
+       },
        config_set: {
                args: {
                        phy: "",
@@ -315,12 +553,30 @@ function iface_event(type, name, data) {
        ubus.call("service", "event", { type: `wpa_supplicant.${name}.${type}`, data: {} });
 }
 
-function iface_hostapd_notify(phy, ifname, iface, state)
+function iface_hostapd_fill_radio_link(mld, radio, msg, link)
+{
+       let config = mld.phy_config[radio];
+       if (!config)
+               return;
+
+       let freq_list = config.freq_list;
+       if (!freq_list)
+               return;
+
+       if (!link || index(freq_list, link.frequency) < 0)
+               return;
+
+       msg.frequency = link.frequency;
+       msg.sec_chan_offset = link.sec_chan_offset;
+}
+
+function iface_hostapd_notify(ifname, iface, state)
 {
-       let ubus = wpas.data.ubus;
        let status = iface.status();
-       let msg = { phy: phy };
+       let ubus = wpas.data.ubus;
+       let msg = {};
 
+       let mld = wpas.data.mld[ifname];
        switch (state) {
        case "DISCONNECTED":
        case "AUTHENTICATING":
@@ -333,26 +589,76 @@ function iface_hostapd_notify(phy, ifname, iface, state)
                break;
        case "COMPLETED":
                msg.up = true;
-               msg.frequency = status.frequency;
-               msg.sec_chan_offset = status.sec_chan_offset;
+               if (!mld) {
+                       msg.frequency = status.frequency;
+                       msg.sec_chan_offset = status.sec_chan_offset;
+               }
                break;
        default:
                return;
        }
 
-       ubus.call("hostapd", "apsta_state", msg);
+       if (!mld) {
+               msg.phy = wpas.data.iface_phy[ifname];
+               if (!phy) {
+                       wpas.printf(`no PHY for ifname ${ifname}`);
+                       return;
+               }
+               ubus.call("hostapd", "apsta_state", msg);
+               return;
+       }
+
+       let radio_mask = mld.radio_mask;
+       for (let i = 0; radio_mask; i++, radio_mask >>= 1) {
+               if (!(radio_mask & 1)) {
+                       wpas.printf(`skip radio ${i}`);
+                       continue;
+               }
+
+               let radio_msg = {
+                       ...msg,
+                       phy: mld.phy,
+                       radio: i,
+               };
+
+               if (state == "COMPLETED") {
+                       if (status.links)
+                               for (let link in status.links)
+                                       iface_hostapd_fill_radio_link(mld, i, radio_msg, link);
+                       else
+                               iface_hostapd_fill_radio_link(mld, i, radio_msg, status);
+               }
+
+               ubus.call("hostapd", "apsta_state", radio_msg);
+       }
 }
 
-function iface_channel_switch(phy, ifname, iface, info)
+function iface_channel_switch(ifname, iface, info)
 {
        let msg = {
-               phy: phy,
                up: true,
                csa: true,
                csa_count: info.csa_count ? info.csa_count - 1 : 0,
                frequency: info.frequency,
                sec_chan_offset: info.sec_chan_offset,
        };
+
+       let mld = wpas.data.mld[ifname];
+       if (mld) {
+               msg.phy = mld.phy;
+               msg.radio = mld_radio_index(mld, info.frequency);
+               if (msg.radio == null) {
+                       wpas.printf(`PHY ${mld.phy} radio for frequency ${info.frequency} not found`);
+                       return;
+               }
+       } else {
+               msg.phy = wpas.data.iface_phy[ifname];
+               if (!msg.phy) {
+                       wpas.printf(`no PHY for ifname ${ifname}`);
+                       return;
+               }
+       }
+
        ubus.call("hostapd", "apsta_state", msg);
 }
 
@@ -362,6 +668,13 @@ return {
                        set_config(phy, []);
                wpas.ubus.disconnect();
        },
+       bss_allowed: function(ifname, bss) {
+               let mld = wpas.data.mld[ifname];
+               if (!mld)
+                       return true;
+
+               return mld_bss_allowed(mld, bss);
+       },
        iface_add: function(name, obj) {
                iface_event("add", name);
        },
@@ -369,39 +682,35 @@ return {
                iface_event("remove", name);
        },
        state: function(ifname, iface, state) {
-               let phy = wpas.data.iface_phy[ifname];
-               if (!phy) {
-                       wpas.printf(`no PHY for ifname ${ifname}`);
-                       return;
-               }
+               try {
+                       iface_hostapd_notify(ifname, iface, state);
 
-               iface_hostapd_notify(phy, ifname, iface, state);
+                       if (state != "COMPLETED")
+                               return;
 
-               if (state != "COMPLETED")
-                       return;
+                       let phy = wpas.data.iface_phy[ifname];
+                       if (!phy)
+                               return;
 
-               let phy_data = wpas.data.config[phy];
-               if (!phy_data)
-                       return;
+                       let phy_data = wpas.data.config[phy];
+                       if (!phy_data)
+                               return;
 
-               let iface_data = phy_data.data[ifname];
-               if (!iface_data)
-                       return;
+                       let iface_data = phy_data.data[ifname];
+                       if (!iface_data)
+                               return;
 
-               let wdev_config = iface_data.config;
-               if (!wdev_config || wdev_config.mode != "mesh")
-                       return;
+                       let wdev_config = iface_data.config;
+                       if (!wdev_config || wdev_config.mode != "mesh")
+                               return;
 
-               wdev_set_mesh_params(ifname, wdev_config);
+                       wdev_set_mesh_params(ifname, wdev_config);
+               } catch (e) {
+                       ex_handler(e);
+               }
        },
        event: function(ifname, iface, ev, info) {
-               let phy = wpas.data.iface_phy[ifname];
-               if (!phy) {
-                       wpas.printf(`no PHY for ifname ${ifname}`);
-                       return;
-               }
-
                if (ev == "CH_SWITCH_STARTED")
-                       iface_channel_switch(phy, ifname, iface, info);
+                       iface_channel_switch(ifname, iface, info);
        }
 };
index 30780ca76e7ecf5dc1cc734d608b2e0b6e71179d..436acf922089d43909b17425957dede9dc81481b 100644 (file)
@@ -702,7 +702,28 @@ as adding/removing interfaces.
  CFLAGS += -DEAP_SERVER -DEAP_SERVER_IDENTITY
 --- a/wpa_supplicant/events.c
 +++ b/wpa_supplicant/events.c
-@@ -6293,6 +6293,7 @@ void supplicant_event(void *ctx, enum wp
+@@ -53,6 +53,7 @@
+ #include "wmm_ac.h"
+ #include "nan_usd.h"
+ #include "dpp_supplicant.h"
++#include "ucode.h"
+ #define MAX_OWE_TRANSITION_BSS_SELECT_COUNT 5
+@@ -1706,6 +1707,12 @@ struct wpa_ssid * wpa_scan_res_match(str
+               return NULL;
+       }
++      if (!wpas_ucode_bss_allowed(wpa_s, bss)) {
++              if (debug_print)
++                      wpa_dbg(wpa_s, MSG_DEBUG, "   skip - denied by ucode handler");
++              return NULL;
++      }
++
+       for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) {
+               if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len,
+                                   bss, bssid_ignore_count, debug_print, link))
+@@ -6293,6 +6300,7 @@ void supplicant_event(void *ctx, enum wp
                event_to_string(event), event);
  #endif /* CONFIG_NO_STDOUT_DEBUG */
  
index 88d63e0b21507434a64d6654eaa7d45f58a0dc70..7f0249a423ba5a15cd1584a3180d43c8c8ff870f 100644 (file)
@@ -6,6 +6,7 @@
 #include "wpa_supplicant_i.h"
 #include "wps_supplicant.h"
 #include "ctrl_iface.h"
+#include "config.h"
 #include "bss.h"
 #include "ucode.h"
 
@@ -41,6 +42,21 @@ wpas_ucode_update_interfaces(void)
        ucv_object_add(ucv_prototype_get(global), "interfaces", ifs);
 }
 
+static uc_value_t *
+wpas_ucode_bss_get_uval(struct wpa_bss *bss)
+{
+       uc_value_t *val;
+
+       val = ucv_object_new(vm);
+       ucv_object_add(val, "freq", ucv_int64_new(bss->freq));
+       ucv_object_add(val, "ssid", ucv_string_new_length(bss->ssid, bss->ssid_len));
+       ucv_object_add(val, "snr", ucv_int64_new(bss->snr));
+       ucv_object_add(val, "signal", ucv_int64_new(bss->level));
+       ucv_object_add(val, "noise", ucv_int64_new(bss->noise));
+
+       return val;
+}
+
 void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s)
 {
        uc_value_t *val;
@@ -71,6 +87,25 @@ void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s)
        ucv_put(val);
 }
 
+bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+       uc_value_t *val;
+       bool ret = true;
+
+       if (wpa_ucode_call_prepare("bss_allowed"))
+               return true;
+
+       uc_value_push(ucv_string_new(wpa_s->ifname));
+       uc_value_push(wpas_ucode_bss_get_uval(bss));
+       val = wpa_ucode_call(2);
+
+       if (ucv_type(val) == UC_BOOLEAN)
+               ret = ucv_boolean_get(val);
+       ucv_put(val);
+
+       return ret;
+}
+
 void wpas_ucode_update_state(struct wpa_supplicant *wpa_s)
 {
        const char *state;
@@ -203,6 +238,29 @@ out:
        return ucv_int64_new(ret);
 }
 
+static void
+uc_wpas_iface_status_bss(uc_value_t *ret, struct wpa_bss *bss)
+{
+       int sec_chan = 0;
+       const u8 *ie;
+
+       ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
+       if (ie && ie[1] >= 2) {
+               const struct ieee80211_ht_operation *ht_oper;
+               int sec;
+
+               ht_oper = (const void *) (ie + 2);
+               sec = ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
+               if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
+                       sec_chan = 1;
+               else if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
+                       sec_chan = -1;
+       }
+
+       ucv_object_add(ret, "sec_chan_offset", ucv_int64_new(sec_chan));
+       ucv_object_add(ret, "frequency", ucv_int64_new(bss->freq));
+}
+
 static uc_value_t *
 uc_wpas_iface_status(uc_vm_t *vm, size_t nargs)
 {
@@ -218,25 +276,29 @@ uc_wpas_iface_status(uc_vm_t *vm, size_t nargs)
        ucv_object_add(ret, "state", ucv_string_new(wpa_supplicant_state_txt(wpa_s->wpa_state)));
 
        bss = wpa_s->current_bss;
-       if (bss) {
-               int sec_chan = 0;
-               const u8 *ie;
-
-               ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
-               if (ie && ie[1] >= 2) {
-                       const struct ieee80211_ht_operation *ht_oper;
-                       int sec;
-
-                       ht_oper = (const void *) (ie + 2);
-                       sec = ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
-                       if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
-                               sec_chan = 1;
-                       else if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
-                               sec_chan = -1;
+       if (bss)
+               uc_wpas_iface_status_bss(ret, bss);
+
+       if (wpa_s->valid_links) {
+               unsigned int valid_links = wpa_s->valid_links;
+               uc_value_t *link, *links;
+
+               links = ucv_array_new(vm);
+
+               for (size_t i = 0;
+                    valid_links && i < ARRAY_SIZE(wpa_s->links);
+                        i++, valid_links >>= 1) {
+                       bss = wpa_s->links[i].bss;
+
+                       if (!(valid_links & 1) || !bss)
+                               continue;
+
+                       link = ucv_object_new(vm);
+                       uc_wpas_iface_status_bss(link, bss);
+                       ucv_array_set(links, i, link);
                }
 
-               ucv_object_add(ret, "sec_chan_offset", ucv_int64_new(sec_chan));
-               ucv_object_add(ret, "frequency", ucv_int64_new(bss->freq));
+               ucv_object_add(ret, "links", links);
        }
 
 #ifdef CONFIG_MESH
@@ -276,6 +338,58 @@ uc_wpas_iface_ctrl(uc_vm_t *vm, size_t nargs)
        return ret;
 }
 
+static uc_value_t *
+uc_wpas_iface_config(uc_vm_t *vm, size_t nargs)
+{
+       struct wpa_supplicant *wpa_s = uc_fn_thisval("wpas.iface");
+       uc_value_t *arg = uc_fn_arg(0);
+       uc_value_t *val = uc_fn_arg(1);
+       uc_value_t *ret = NULL;
+       bool get = nargs == 1;
+       const char *name;
+       size_t len = 0;
+
+       if (!wpa_s || ucv_type(arg) != UC_STRING)
+               return NULL;
+
+       name = ucv_string_get(arg);
+       if (!strcmp(name, "freq_list")) {
+               if (get) {
+                       int *cur = wpa_s->conf->freq_list;
+                       if (!cur)
+                               return NULL;
+
+                       ret = ucv_array_new(vm);
+                       while (*cur)
+                               ucv_array_set(ret, len++, ucv_int64_new(*(cur++)));
+               } else {
+                       size_t len = ucv_array_length(val);
+                       int *freq_list;
+
+                       if (ucv_type(val) != UC_ARRAY)
+                               return NULL;
+
+                       freq_list = calloc(len + 1, sizeof(*freq_list));
+                       for (size_t i = 0; i < len; i++) {
+                               uc_value_t *cur = ucv_array_get(val, i);
+
+                               if (ucv_type(cur) != UC_INTEGER) {
+                                       free(freq_list);
+                                       return NULL;
+                               }
+
+                               freq_list[i] = ucv_int64_get(cur);
+                       }
+
+                       free(wpa_s->conf->freq_list);
+                       wpa_s->conf->freq_list = freq_list;
+                       ret = ucv_boolean_new(true);
+               }
+       }
+
+       return ret;
+}
+
 int wpas_ucode_init(struct wpa_global *gl)
 {
        static const uc_function_list_t global_fns[] = {
@@ -288,6 +402,7 @@ int wpas_ucode_init(struct wpa_global *gl)
        static const uc_function_list_t iface_fns[] = {
                { "status", uc_wpas_iface_status },
                { "ctrl", uc_wpas_iface_ctrl },
+               { "config", uc_wpas_iface_config },
        };
        uc_value_t *data, *proto;
 
index a429a0ed87bdcca08eae88cdb0686e2631fbc66a..fd339fa3e9f82892132448f444d34e6b6f630642 100644 (file)
@@ -20,6 +20,7 @@ void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s);
 void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s);
 void wpas_ucode_update_state(struct wpa_supplicant *wpa_s);
 void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_data *data);
+bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss);
 #else
 static inline int wpas_ucode_init(struct wpa_global *gl)
 {
@@ -44,6 +45,10 @@ static inline void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, uni
 {
 }
 
+static inline bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+       return true;
+}
 #endif
 
 #endif