]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
wifi-scripts: ucode: add MLO interface support
authorFelix Fietkau <nbd@nbd.name>
Fri, 27 Jun 2025 20:42:30 +0000 (22:42 +0200)
committerFelix Fietkau <nbd@nbd.name>
Sat, 2 Aug 2025 14:46:59 +0000 (16:46 +0200)
MLO can be enabled by configuring a wifi-iface section with multiple
radios, like this:

  config wifi-iface
    list radio 'radio0'
    list radio 'radio1'
    option mlo '1'
    option ssid 'OpenWrt'
    option mode 'ap'
    option network 'lan'
    ...

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh
package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc
package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc
package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
package/network/config/wifi-scripts/files/usr/share/hostap/common.uc

index 249c7a4596dc4f31f5d8339447d3e81b14580c79..8ef2dd9b2b0e7ecad4e1557b775342a1c867e994 100755 (executable)
@@ -157,9 +157,39 @@ function config_add_mesh_params(config, data) {
                config_add(config, param, data[param]);
 }
 
+function setup_mlo(data) {
+       let config = {};
+       let idx = 0;
+
+       for (let k, v in data.interfaces) {
+               let ifname = v.config.ifname;
+               if (!ifname)
+                       ifname = 'ap-mld' + idx++;
+
+               delete v.config.ifname;
+               config[ifname] = v.config;
+               netifd.set_vif(k, ifname);
+
+               v.config.phy = find_phy(v.config.radio_config[0], true);
+               delete v.config.radio_config;
+       }
+
+       let ret = ubus.call('hostapd', 'mld_set', { config });
+       if (type(ret) != "object")
+               return netifd.setup_failed('HOSTAPD_START_FAILED');
+
+       netifd.add_process('/usr/sbin/hostapd', ret.pid, true, true);
+       netifd.set_up();
+
+       return 0;
+}
+
 function setup() {
        let data = json(ARGV[3]);
 
+       if (ARGV[2] == "#mlo")
+               return setup_mlo(data);
+
        data.phy = find_phy(data.config, true);
        if (!data.phy) {
                log('Bug: PHY is undefined for device');
@@ -200,6 +230,7 @@ function setup() {
                }
 
                switch (mode) {
+               case 'link':
                case 'ap':
                        has_ap = true;
                        // fallthrough
@@ -210,7 +241,8 @@ function setup() {
                                data.config.noscan = true;
                        validate('iface', v.config);
                        iface.prepare(v.config, data.phy + data.phy_suffix, data.config.num_global_macaddr, data.config.macaddr_base);
-                       netifd.set_vif(k, v.config.ifname);
+                       if (mode != "link")
+                               netifd.set_vif(k, v.config.ifname);
                        break;
                }
 
@@ -277,6 +309,9 @@ function setup() {
 function teardown() {
        let data = json(ARGV[3]);
 
+       if (ARGV[2] == "#mlo")
+               return 0;
+
        if (!data.data?.phy) {
                log('Bug: PHY is undefined for device');
                return 1;
index 47049f30bb13afa931691dc6f67eadec81733c9c..62e3ec0afb2c0cbf3d7f3d4ab5f360ee5761cb75 100644 (file)
@@ -488,6 +488,12 @@ export function generate(interface, data, config, vlans, stas, phy_features) {
        for (let raw in config.hostapd_options)
                append_raw(raw);
 
+       if (config.mode == 'link') {
+               append_raw('mld_ap=1');
+               if (data.config.radio != null)
+                       append_raw('mld_link_id=' + data.config.radio);
+       }
+
        if (config.default_macaddr)
                append_raw('#default_macaddr');
 };
index cc174cda5027829bfbc39b76c8c87c33f1e716ec..a31b2955ebe79340ee7a74e91401162e7deec147 100644 (file)
@@ -548,7 +548,7 @@ export function setup(data) {
                append('\n#macaddr_base', data.config.macaddr_base);
 
        for (let k, interface in data.interfaces) {
-               if (interface.config.mode != 'ap')
+               if (interface.config.mode != 'ap' && interface.config.mode != 'link')
                        continue;
 
                interface.config.network_bridge = interface.bridge;
index de67bd5d94cc76481eeb09e6083f20d4d7d12050..b63ec239c71c888ffaaa016d412fb74879700266 100644 (file)
@@ -108,6 +108,7 @@ function config_init(uci)
                let mlo_vif = parse_bool(data.mlo);
                let radios = map(dev_names, (v) => radio_idx[v]);
                radios = filter(radios, (v) => v != null);
+               let radio_config = map(dev_names, (v) => devices[v].config);
                if (mlo_vif)
                        dev_names = [ wdev.mlo_name, ...dev_names ];
                for (let dev_name in dev_names) {
@@ -120,8 +121,11 @@ function config_init(uci)
                                continue;
 
                        let config = parse_attribute_list(data, handler.iface);
-                       if (mlo_vif && dev_name != wdev.mlo_name)
-                               config.mode = "link";
+                       if (mlo_vif)
+                               if (dev_name == wdev.mlo_name)
+                                       config.radio_config = radio_config;
+                               else
+                                       config.mode = "link";
                        config.radios = radios;
 
                        let vif = {
index caab14bcab1264b824f3bb476f4f704e405f9b3e..f801c4940c2b76947d34705e80305463b609768a 100644 (file)
@@ -101,7 +101,9 @@ function wdev_create(phy, name, data)
                req["4addr"] = data["4addr"];
        if (data.macaddr)
                req.mac = data.macaddr;
-       if (data.radio != null && data.radio >= 0)
+       if (data.radio_mask > 0)
+               req.vif_radio_mask = data.radio_mask;
+       else if (data.radio != null && data.radio >= 0)
                req.vif_radio_mask = 1 << data.radio;
 
        nl80211.error();