diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/etc/modprobe.d/mt7915e-wed.conf b/board/aarch64/bananapi-bpi-r3/rootfs/etc/modprobe.d/mt7915e-wed.conf new file mode 100644 index 000000000..f6575644f --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/etc/modprobe.d/mt7915e-wed.conf @@ -0,0 +1,2 @@ +# Wireless Ethernet Dispatch, lets the PPE forward offloaded flows to the radios +options mt7915e wed_enable=1 diff --git a/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi b/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi index 6964340f8..39cb3e1eb 100644 --- a/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi +++ b/board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi @@ -13,7 +13,7 @@ fdt_addr_r = "0x43f00000"; kernel_addr_r = "0x44000000"; scriptaddr = "0x48000000"; - ramdisk_addr_r = "0x4A000000"; + ramdisk_addr_r = "0x50000000"; en8811h_fw_part = "0#en8811h_fw"; en8811h_fw_dm_dir = "EthMD32.dm.bin"; diff --git a/board/common/rootfs/usr/libexec/infix/iw.py b/board/common/rootfs/usr/libexec/infix/iw.py index ff6335148..727c3957a 100755 --- a/board/common/rootfs/usr/libexec/infix/iw.py +++ b/board/common/rootfs/usr/libexec/infix/iw.py @@ -486,6 +486,29 @@ def parse_dev(): return result +def parse_wds_ports(ifname): + """ + List the WDS ports of an access point: the AP/VLAN interfaces on the + same PHY that carry its MAC address. + Returns: [ifname, ...] + """ + info = parse_interface_info(ifname) + mac = info.get('mac') + ports = [] + + for phy, ifaces in parse_dev().items(): + if ifname not in ifaces: + continue + for dev in ifaces: + if dev == ifname: + continue + devinfo = parse_interface_info(dev) + if devinfo.get('iftype') == 'AP/VLAN' and devinfo.get('mac') == mac: + ports.append(dev) + + return ports + + def parse_link(ifname): """ Parse 'iw dev link' output for station mode @@ -629,6 +652,7 @@ def main(): 'station': 'Get connected stations in AP mode (requires interface)', 'link': 'Get link info in station mode (requires interface)', 'mesh': 'Get mesh parameters in mesh point mode (requires interface)', + 'wds': 'List the WDS ports of an access point (requires interface)', 'caps': 'Get HT/VHT capability bitmasks (requires PHY/radio)' }, 'examples': [ @@ -677,6 +701,11 @@ def main(): data = {'error': 'mesh command requires interface argument'} else: data = parse_mesh_param(sys.argv[2]) + elif command == 'wds': + if len(sys.argv) < 3: + data = {'error': 'wds command requires interface argument'} + else: + data = parse_wds_ports(sys.argv[2]) elif command == 'survey': if len(sys.argv) < 3: data = {'error': 'survey command requires interface argument'} diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 89de004c4..1fbb27501 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -30,6 +30,26 @@ All notable changes to the project are documented in this file. - LLDP neighbors were listed without their system name, descriptions, and capabilities, in the CLI, the WebUI, and the operational datastore +### Added + +- WiFi 4-address (WDS) links: a station with `wds` enabled can be a bridge + port, and an access point gets a `wds-link` interface per remote station + to bridge it. Together they build wireless bridges and repeaters, see + [WDS Backhaul and Repeaters][wds]. The station leaf `peer-bssid` pins a + station to one access point +- MT7986 boards (Banana Pi BPI-R3, BPI-R3 Mini, Acer Connect Vero W6m): + WiFi hardware offloading is now active, which lowers the CPU load of + WiFi traffic + +### Fixes + +- Fix #1679: a WiFi station set up on an interface that was in scan-only + mode, as in the Raspberry Pi 4 factory configuration, did not connect + until the device was rebooted. Changes to a station's or mesh point's + settings now take effect on commit + +[wds]: https://www.kernelkit.org/infix/latest/wifi/#wds-backhaul-and-repeaters + [v26.09.0][] - 2026-09-30 ------------------------- diff --git a/doc/wifi.md b/doc/wifi.md index 233b07ba4..38ca5c313 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -1,7 +1,8 @@ # Wi-Fi (Wireless LAN) -Infix includes comprehensive Wi-Fi support for both client (Station) and -Access Point modes. When a compatible Wi-Fi adapter is detected, the system +Infix supports Wi-Fi as a client (Station), as an Access Point, as an +802.11s mesh point, and as a 4-address (WDS) link for wireless bridges +and repeaters. When a compatible Wi-Fi adapter is detected, the system automatically creates a WiFi radio (PHY) in factory-config, that can host virtual interfaces. @@ -16,7 +17,7 @@ Infix uses a two-layer WiFi architecture: 2. **WiFi Interface (Network layer)**: Virtual interface on a radio - Configured via `infix-interfaces` module - - Can operate in Station (client) or Access Point mode + - Operates in Station (client), Access Point, Mesh Point or WDS link mode - Each interface references a parent radio ## Naming Conventions @@ -44,7 +45,9 @@ Where `N` is a number (0, 1, 2, ...). - USB hotplug is not supported - adapters must be present at boot - Interface naming may be inconsistent with multiple USB Wi-Fi adapters -- AP and Station modes cannot be mixed on the same radio +- A station and access points on the same radio must share a channel: the + station follows its access point, so pin the radio to that channel on + both ends. See [WDS Backhaul and Repeaters](#wds-backhaul-and-repeaters) ## Supported Wi-Fi Adapters @@ -704,9 +707,8 @@ Repeat for all APs that should participate in the roaming group. IEEE 802.11s is a wireless mesh networking standard operating at Layer 2. Mesh nodes form peer links directly with each other and route traffic -using HWMP (Hybrid Wireless Mesh Protocol), which is built into the -Linux mac80211 subsystem. There is no central controller; nodes -discover peers and find paths on their own. +using HWMP (Hybrid Wireless Mesh Protocol). There is no central +controller; nodes discover peers and find paths on their own. The standard defines two node roles: @@ -718,11 +720,13 @@ The standard defines two node roles: In practice, a node bridging the mesh interface to a LAN acts as a mesh portal. +For a backhaul with a single root, where multi-hop and self-healing are +not needed, see [WDS Backhaul and Repeaters](#wds-backhaul-and-repeaters). +That variant can use WiFi hardware offloading, mesh cannot. + > [!NOTE] -> Not all WiFi hardware supports 802.11s mesh. The driver must implement -> mesh point mode in mac80211. Check your adapter's capabilities with -> `iw phy info` and look for "mesh point" under "Supported interface -> modes". +> Not all WiFi hardware supports 802.11s mesh, see the adapter list +> under [Supported Wi-Fi Adapters](#supported-wi-fi-adapters). ### 802.11s vs EasyMesh @@ -733,10 +737,10 @@ portal. | **Single point of failure** | None | Controller | | **Multi-hop** | True N-hop | Limited (1-2 hops) | | **Vendor lock-in** | None | Common | -| **Linux support** | Kernel-native (mac80211) | Requires proprietary firmware | +| **Vendor software** | None needed | Required | -Infix uses 802.11s because it runs entirely in the kernel with no -proprietary components. +Infix uses 802.11s because it is an open standard that works across +vendors without proprietary components. ### Mesh configuration @@ -808,6 +812,114 @@ With 802.11r/k/v roaming enabled on the APs (same SSID, same passphrase, same mobility domain), clients hand off between nodes while the mesh carries backhaul traffic. +## WDS Backhaul and Repeaters + +A WiFi station normally carries only its own traffic and cannot be a +bridge port. In 4-address mode, also called WDS, it can forward traffic +for the devices behind it. That is what makes a device with a station +and an access point a repeater, and a device with a station and wired +ports a wireless bridge. + +Both ends take part. The satellite enables `wds` on its station. The +root accepts the station on one of its access points and gives it a +`wds-link` interface: a bridge port, one per satellite, created by +configuration and tied to the satellite's MAC address. + +Compared to an [802.11s mesh](#80211s-mesh-point-mode), a WDS backhaul +is a star with one root, without multi-hop or self-healing. In return +it is a plain access point and station link, which WiFi hardware +offloading supports where mesh is not. + +### Root: access point with WDS ports + +On the root, two kinds of interface share the job. The access point is +the one the satellites connect to: it advertises the backhaul SSID and +handles authentication, and there is one per radio. Each `wds-link` +is the port for one satellite: that is where its traffic appears and +what you put in the bridge. With two satellites on `radio1`: + +``` +radio1 + ├── backhaul access point, the SSID the satellites connect to + ├── wds-jaffa port for jaffa, bridge port + └── wds-tauri port for tauri, bridge port +``` + +Give the backhaul its own SSID, advertised by the root only, so the +satellites can land nowhere else. For each satellite, add a `wds-link` +interface naming the access point and the satellite's station MAC +address, and make it a port of the bridge. The interface uses the +access point's radio and MAC address, so it takes neither a `radio` nor +a `custom-phys-address`. + +
admin@root:/config/> edit interface backhaul
+admin@root:/config/interface/backhaul/> set wifi radio radio1
+admin@root:/config/interface/backhaul/> set wifi access-point ssid my-backhaul
+admin@root:/config/interface/backhaul/> set wifi access-point security secret backhaul-key
+admin@root:/config/interface/backhaul/> end
+admin@root:/config/> edit interface wds-jaffa
+admin@root:/config/interface/wds-jaffa/> set type wifi
+admin@root:/config/interface/wds-jaffa/> set wifi wds-link access-point backhaul
+admin@root:/config/interface/wds-jaffa/> set wifi wds-link peer-address 02:13:37:13:37:12
+admin@root:/config/interface/wds-jaffa/> set bridge-port bridge br0
+admin@root:/config/interface/wds-jaffa/> leave
+
+ +VLANs and other bridge port settings are configured on the `wds-link` +interface like on any other port. The port is down until the satellite +connects, and goes down again when it leaves, or within about half a +minute if the satellite disappears without notice: + +
admin@root:/> show interface wds-jaffa
+name               : wds-jaffa
+type               : wifi
+operational status : up
+higher-layer-if    : br0
+mode               : wds-link
+connected          : yes
+signal             : -48 dBm (good)
+
+ +### Satellite: 4-address station + +On the satellite, configure a station for the backhaul SSID with `wds` +enabled and make it a bridge port, next to the wired ports and any local +access points. `peer-bssid` is optional and pins the station to the +root's access point: + +
admin@jaffa:/config/> edit interface uplink
+admin@jaffa:/config/interface/uplink/> set wifi radio radio1
+admin@jaffa:/config/interface/uplink/> set wifi station ssid my-backhaul
+admin@jaffa:/config/interface/uplink/> set wifi station wds true
+admin@jaffa:/config/interface/uplink/> set wifi station peer-bssid 02:13:37:13:37:01
+admin@jaffa:/config/interface/uplink/> set wifi station security secret backhaul-key
+admin@jaffa:/config/interface/uplink/> set bridge-port bridge br0
+admin@jaffa:/config/interface/uplink/> leave
+
+ +A station without `wds` cannot be a bridge port, the configuration is +rejected. + +### Repeater + +A repeater is a satellite that also runs an access point for clients, +bridged with the backhaul station. The station and the access point +can share a radio, but then all radios in the backhaul must use the same +channel: the station follows the root's channel and the local access +point has a fixed one. A channel change on the root, for example from +radar detection, leaves the satellites disconnected until they are +reconfigured. + +Clients on a repeater keep their own MAC addresses, so DHCP reservations +and per-port VLANs work as on a wired network. With the same client +SSID on the root and the repeaters, the [roaming +features](#fast-roaming-between-access-points) apply as usual. Keep the +backhaul SSID separate from the client SSIDs, or a satellite may connect +to another satellite instead of the root. + +A satellite with both a WDS backhaul and a cable to the same LAN forms a +loop, as with any two bridge ports to the same network. + ## Troubleshooting Use `show interface wifi0` to verify signal strength and connection status. diff --git a/package/feature-wifi/wifimedium b/package/feature-wifi/wifimedium index 58aeb4d84..55d335e88 100755 --- a/package/feature-wifi/wifimedium +++ b/package/feature-wifi/wifimedium @@ -13,7 +13,9 @@ This daemon is that process. On each DUT it: * registers on the "mac80211_hwsim" genl family and receives every frame the local radios transmit (HWSIM_CMD_FRAME), * acknowledges each transmit back to the kernel (HWSIM_CMD_TX_INFO_FRAME) so - mac80211's TX path completes, and + mac80211's TX path completes. A unicast frame is only acknowledged if its + receiver has been heard on the medium recently, so a station that vanishes + stops acking like on real RF and the AP's inactivity probing works, and * relays the frame per radio: each radio (phy radioN) is paired by name with a carrier NIC (netdev radioN) that joins one multicast "cell" -- a QEMU socket multicast group shared by every DUT's radioN (see test/virt/quad and @@ -181,9 +183,11 @@ class Netlink: attrs += put_attr(HWSIM_ATTR_FREQ, struct.pack("=I", freq)) self._send(self.family_id, HWSIM_CMD_FRAME, attrs) - def tx_ack(self, transmitter, flags, tx_info, tx_info_flags, cookie): + def tx_ack(self, transmitter, flags, tx_info, tx_info_flags, cookie, ack=True): + if ack: + flags |= HWSIM_TX_STAT_ACK attrs = put_attr(HWSIM_ATTR_ADDR_TRANSMITTER, transmitter) - attrs += put_attr(HWSIM_ATTR_FLAGS, struct.pack("=I", flags | HWSIM_TX_STAT_ACK)) + attrs += put_attr(HWSIM_ATTR_FLAGS, struct.pack("=I", flags)) attrs += put_attr(HWSIM_ATTR_SIGNAL, struct.pack("=i", RX_SIGNAL)) if tx_info: attrs += put_attr(HWSIM_ATTR_TX_INFO, tx_info) @@ -232,7 +236,10 @@ def open_medium(ifname): # The carrier NIC is unconfigured (it is not a real DUT port), so Infix # leaves it administratively down -- a raw packet socket on a down link # carries no frames. Bring it up; wifimedium owns the medium, like hwsim0. - ifup(ifname) + # A full-size data frame is a 1500 byte MSDU plus 802.11 header, mesh + # control, encryption and LLC, and A-MSDUs are larger still, so the + # carrier needs a far larger MTU than the default 1500. + ifup(ifname, mtu=9000) sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_WIFIMEDIUM)) sock.bind((ifname, ETH_P_WIFIMEDIUM)) @@ -251,6 +258,25 @@ def open_medium(ifname): # raw 802.11 frame. WIRE = struct.Struct("=6sI") +# Transmitter addresses heard on the medium and when. mac80211 stations send +# a keep-alive at least every 30 s when idle, so one not heard for twice that +# is gone and frames to it go unacknowledged. +ALIVE_TIMEOUT = 60.0 +seen = {} + + +def heard(frame): + if len(frame) >= 16: + seen[frame[10:16]] = time.monotonic() + + +def acked(frame): + """Would the receiver of this frame acknowledge it?""" + if len(frame) < 10 or frame[4] & 1: + return True # multicast: no ack expected, keep hwsim's default + last = seen.get(frame[4:10]) + return last is not None and time.monotonic() - last < ALIVE_TIMEOUT + def wait_radios_renamed(timeout=20): """Wait until the hwsim phys have been renamed phyN -> radioN. @@ -307,8 +333,10 @@ def discover_radios(): return radios -def ifup(ifname): - """Bring an interface up (best effort).""" +def ifup(ifname, mtu=None): + """Bring an interface up (best effort), optionally with a larger MTU.""" + if mtu: + subprocess.run(["ip", "link", "set", ifname, "mtu", str(mtu)], check=False) subprocess.run(["ip", "link", "set", ifname, "up"], check=False) @@ -364,13 +392,17 @@ def main(): # Complete the kernel TX path regardless of delivery. nl.tx_ack(tx, flags, a.get(HWSIM_ATTR_TX_INFO), a.get(HWSIM_ATTR_TX_INFO_FLAGS), - a.get(HWSIM_ATTR_COOKIE)) + a.get(HWSIM_ATTR_COOKIE), acked(frame)) # Send only onto the transmitting radio's own carrier. r = by_addr1.get(tx) if r: - r["sock"].send(ETH_BROADCAST + r["mac"] + ETYPE + - WIRE.pack(tx, freq) + frame) - dbg(f"tx {r['name']} freq={freq} len={len(frame)}") + try: + r["sock"].send(ETH_BROADCAST + r["mac"] + ETYPE + + WIRE.pack(tx, freq) + frame) + dbg(f"tx {r['name']} freq={freq} len={len(frame)}") + except OSError as e: + # Lost on the air, like a collision would be. + log(f"tx {r['name']} len={len(frame)} dropped: {e}") else: dbg(f"tx from unknown radio {tx.hex()} -- dropped") off += nla_align(mlen) @@ -389,6 +421,7 @@ def main(): continue tx, freq = WIRE.unpack_from(pkt, 14) frame = pkt[14 + WIRE.size:] + heard(frame) # Inject into THIS radio only -- its carrier is its cell. nl.inject(r["addr1"], frame, freq) dbg(f"rx {r['name']} tx={tx.hex()} freq={freq} len={len(frame)}") diff --git a/patches/hostapd/0003-hostapd-Allow-binding-WDS-stations-to-preconfigured-i.patch b/patches/hostapd/0003-hostapd-Allow-binding-WDS-stations-to-preconfigured-i.patch new file mode 100644 index 000000000..dcf2bb56d --- /dev/null +++ b/patches/hostapd/0003-hostapd-Allow-binding-WDS-stations-to-preconfigured-i.patch @@ -0,0 +1,244 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Fri, 2 Oct 2026 10:00:00 +0200 +Subject: [PATCH 3/3] hostapd: Allow binding WDS stations to preconfigured + interfaces +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +A 4-address WDS station is bound to an AP_VLAN interface that hostapd +creates on association, named .sta, and removes again on +disassociation. Nothing outside hostapd can configure such a port +ahead of time, since neither its name nor its lifetime is known. + +Add a per-BSS option mapping a station MAC address to an interface +name: + + wds_sta_ifname= + +A matching station is bound to that interface instead. The interface +must already exist; hostapd brings it up and binds the station on +association, and on disassociation only sets it down. Its bridge +membership is never touched, so an external network manager can +create the port, enslave it and configure it like any other bridge +port before the station ever shows up. + +Stations without a mapping keep the current behaviour. + +Signed-off-by: Mattias Walström +--- +diff -ruN a/hostapd/config_file.c b/hostapd/config_file.c +--- a/hostapd/config_file.c ++++ b/hostapd/config_file.c +@@ -2368,6 +2368,48 @@ + #endif /* CONFIG_TESTING_OPTIONS */ + + ++/* ++ * wds_sta_ifname= ++ * ++ * Bind the 4-address WDS station with the given MAC address to an ++ * existing, externally managed interface instead of creating ++ * .sta. The interface is left in place when the station ++ * disassociates, and its bridge membership is never touched. ++ */ ++static int hostapd_config_parse_wds_sta_ifname(struct hostapd_bss_config *bss, ++ char *pos, int line) ++{ ++ struct hostapd_wds_sta_ifname *e; ++ char *ifname; ++ ++ ifname = os_strchr(pos, ' '); ++ if (!ifname) ++ goto fail; ++ *ifname++ = '\0'; ++ while (*ifname == ' ') ++ ifname++; ++ if (!*ifname || os_strlen(ifname) > IFNAMSIZ) ++ goto fail; ++ ++ e = os_zalloc(sizeof(*e)); ++ if (!e) ++ return 1; ++ if (hwaddr_aton(pos, e->addr)) { ++ os_free(e); ++ goto fail; ++ } ++ os_strlcpy(e->ifname, ifname, sizeof(e->ifname)); ++ e->next = bss->wds_sta_ifname; ++ bss->wds_sta_ifname = e; ++ ++ return 0; ++fail: ++ wpa_printf(MSG_ERROR, "Line %d: invalid wds_sta_ifname '%s'", ++ line, pos); ++ return 1; ++} ++ ++ + static int hostapd_config_fill(struct hostapd_config *conf, + struct hostapd_bss_config *bss, + const char *buf, char *pos, int line) +@@ -2383,6 +2425,9 @@ + os_strlcpy(bss->vlan_bridge, pos, sizeof(bss->vlan_bridge)); + } else if (os_strcmp(buf, "wds_bridge") == 0) { + os_strlcpy(bss->wds_bridge, pos, sizeof(bss->wds_bridge)); ++ } else if (os_strcmp(buf, "wds_sta_ifname") == 0) { ++ if (hostapd_config_parse_wds_sta_ifname(bss, pos, line)) ++ return 1; + } else if (os_strcmp(buf, "driver") == 0) { + int j; + const struct wpa_driver_ops *driver = NULL; +diff -ruN a/src/ap/ap_config.c b/src/ap/ap_config.c +--- a/src/ap/ap_config.c ++++ b/src/ap/ap_config.c +@@ -890,6 +890,12 @@ + os_free(conf->radius); + os_free(conf->radius_das_shared_secret); + hostapd_config_free_vlan(conf); ++ while (conf->wds_sta_ifname) { ++ struct hostapd_wds_sta_ifname *e = conf->wds_sta_ifname; ++ ++ conf->wds_sta_ifname = e->next; ++ os_free(e); ++ } + os_free(conf->time_zone); + os_free(conf->supported_rates); + os_free(conf->basic_rates); +diff -ruN a/src/ap/ap_config.h b/src/ap/ap_config.h +--- a/src/ap/ap_config.h ++++ b/src/ap/ap_config.h +@@ -281,6 +281,12 @@ + /** + * struct hostapd_bss_config - Per-BSS configuration + */ ++struct hostapd_wds_sta_ifname { ++ struct hostapd_wds_sta_ifname *next; ++ u8 addr[ETH_ALEN]; ++ char ifname[IFNAMSIZ + 1]; ++}; ++ + struct hostapd_bss_config { + char iface[IFNAMSIZ + 1]; + char bridge[IFNAMSIZ + 1]; +@@ -357,6 +363,7 @@ + struct mac_acl_entry *deny_mac; + int num_deny_mac; + int wds_sta; ++ struct hostapd_wds_sta_ifname *wds_sta_ifname; + int isolate; + int start_disabled; + +diff -ruN a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c +--- a/src/ap/ap_drv_ops.c ++++ b/src/ap/ap_drv_ops.c +@@ -391,7 +391,9 @@ + int hostapd_set_wds_sta(struct hostapd_data *hapd, char *ifname_wds, + const u8 *addr, int aid, int val) + { ++ struct hostapd_wds_sta_ifname *e; + const char *bridge = NULL; ++ char name[IFNAMSIZ + 1]; + + if (hapd->driver == NULL || hapd->driver->set_wds_sta == NULL) + return -1; +@@ -399,6 +401,20 @@ + bridge = hapd->conf->wds_bridge; + else if (hapd->conf->bridge[0]) + bridge = hapd->conf->bridge; ++ ++ if (!ifname_wds) ++ ifname_wds = name; ++ ifname_wds[0] = '\0'; ++ for (e = hapd->conf->wds_sta_ifname; e; e = e->next) { ++ if (os_memcmp(e->addr, addr, ETH_ALEN) != 0) ++ continue; ++ /* Externally managed interface: hand the driver its name ++ * and keep it out of any bridge handling. */ ++ os_strlcpy(ifname_wds, e->ifname, IFNAMSIZ + 1); ++ bridge = NULL; ++ break; ++ } ++ + return hapd->driver->set_wds_sta(hapd->drv_priv, addr, aid, val, + bridge, ifname_wds); + } +diff -ruN a/src/drivers/driver.h b/src/drivers/driver.h +--- a/src/drivers/driver.h ++++ b/src/drivers/driver.h +@@ -4518,7 +4518,11 @@ + * @bridge_ifname: Bridge interface to use for the WDS station or %NULL + * to indicate that bridge is not to be used + * @ifname_wds: Buffer to return the interface name for the new WDS +- * station or %NULL to indicate name is not returned. ++ * station or %NULL to indicate name is not returned. If the ++ * buffer holds a name on entry, that existing interface is used ++ * as is: it is not created, not added to or removed from a ++ * bridge, and is only set down, not removed, when the station ++ * is unbound. + * Returns: 0 on success, -1 on failure + */ + int (*set_wds_sta)(void *priv, const u8 *addr, int aid, int val, +diff -ruN a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c +--- a/src/drivers/driver_nl80211.c ++++ b/src/drivers/driver_nl80211.c +@@ -9317,22 +9317,35 @@ + char name[IFNAMSIZ + 1]; + union wpa_event_data event; + bool add_br = false; ++ bool external = false; + int ret; + +- ret = os_snprintf(name, sizeof(name), "%s.sta%d", bss->ifname, aid); +- if (ret >= (int) sizeof(name)) +- wpa_printf(MSG_WARNING, +- "nl80211: WDS interface name was truncated"); +- else if (ret < 0) +- return ret; +- +- if (ifname_wds) +- os_strlcpy(ifname_wds, name, IFNAMSIZ + 1); ++ if (ifname_wds && ifname_wds[0]) { ++ os_strlcpy(name, ifname_wds, sizeof(name)); ++ external = true; ++ } else { ++ ret = os_snprintf(name, sizeof(name), "%s.sta%d", bss->ifname, ++ aid); ++ if (ret >= (int) sizeof(name)) ++ wpa_printf(MSG_WARNING, ++ "nl80211: WDS interface name was truncated"); ++ else if (ret < 0) ++ return ret; ++ ++ if (ifname_wds) ++ os_strlcpy(ifname_wds, name, IFNAMSIZ + 1); ++ } + + wpa_printf(MSG_DEBUG, "nl80211: Set WDS STA addr=" MACSTR + " aid=%d val=%d name=%s", MAC2STR(addr), aid, val, name); + if (val) { + if (!if_nametoindex(name)) { ++ if (external) { ++ wpa_printf(MSG_ERROR, ++ "nl80211: WDS STA interface %s does not exist", ++ name); ++ return -1; ++ } + if (nl80211_create_iface(drv, name, + NL80211_IFTYPE_AP_VLAN, + bss->addr, 1, NULL, NULL, 0) < +@@ -9363,6 +9376,13 @@ + return i802_set_sta_vlan(priv, addr, name, 0, + NL80211_DRV_LINK_ID_NA); + } else { ++ if (external) { ++ i802_set_sta_vlan(priv, addr, bss->ifname, 0, ++ NL80211_DRV_LINK_ID_NA); ++ linux_set_iface_flags(drv->global->ioctl_sock, name, 0); ++ return 0; ++ } ++ + if (bridge_ifname && + linux_br_del_if(drv->global->ioctl_sock, bridge_ifname, + name) < 0) diff --git a/patches/linux/6.18.55/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch b/patches/linux/6.18.55/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch index 761e03a8b..1b18d01a4 100644 --- a/patches/linux/6.18.55/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch +++ b/patches/linux/6.18.55/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch @@ -1,7 +1,7 @@ From 1bdc682ca8860bb8299b6c25cc91ebf032a23cfa Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 19 Sep 2023 18:38:10 +0200 -Subject: [PATCH 01/78] net: phy: marvell10g: Support firmware loading on +Subject: [PATCH 01/80] net: phy: marvell10g: Support firmware loading on 88X3310 When probing, if a device is waiting for firmware to be loaded into diff --git a/patches/linux/6.18.55/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch b/patches/linux/6.18.55/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch index 713ec69a7..aac9b3ce0 100644 --- a/patches/linux/6.18.55/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch +++ b/patches/linux/6.18.55/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch @@ -1,7 +1,7 @@ From 920227e8923cf970730a4bed22bd2aa55dc9387e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 21 Nov 2023 20:15:24 +0100 -Subject: [PATCH 02/78] net: phy: marvell10g: Fix power-up when strapped to +Subject: [PATCH 02/80] net: phy: marvell10g: Fix power-up when strapped to start powered down On devices which are hardware strapped to start powered down (PDSTATE diff --git a/patches/linux/6.18.55/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch b/patches/linux/6.18.55/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch index 370716246..ba70a2721 100644 --- a/patches/linux/6.18.55/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch +++ b/patches/linux/6.18.55/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch @@ -1,7 +1,7 @@ From c473017acbed8a778f07f9f1ce55028e85a9ad62 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 15 Nov 2023 20:58:42 +0100 -Subject: [PATCH 03/78] net: phy: marvell10g: Add LED support for 88X3310 +Subject: [PATCH 03/80] net: phy: marvell10g: Add LED support for 88X3310 Pickup the LEDs from the state in which the hardware reset or bootloader left them, but also support further configuration via diff --git a/patches/linux/6.18.55/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch b/patches/linux/6.18.55/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch index 09f301669..3a3cdc386 100644 --- a/patches/linux/6.18.55/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch +++ b/patches/linux/6.18.55/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch @@ -1,7 +1,7 @@ From 7ad37cfa715d86cc20f29432f106cedd457e4f26 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Dec 2023 09:51:05 +0100 -Subject: [PATCH 04/78] net: phy: marvell10g: Support LEDs tied to a single +Subject: [PATCH 04/80] net: phy: marvell10g: Support LEDs tied to a single media side In a combo-port setup, i.e. where both the copper and fiber interface diff --git a/patches/linux/6.18.55/0005-net-phy-Do-not-resume-PHY-when-attaching.patch b/patches/linux/6.18.55/0005-net-phy-Do-not-resume-PHY-when-attaching.patch index 4b8eb49ac..e5c7ee359 100644 --- a/patches/linux/6.18.55/0005-net-phy-Do-not-resume-PHY-when-attaching.patch +++ b/patches/linux/6.18.55/0005-net-phy-Do-not-resume-PHY-when-attaching.patch @@ -1,7 +1,7 @@ From 5f95f31c7e48087019db7d673ff32a31f4e06619 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 10:10:19 +0100 -Subject: [PATCH 05/78] net: phy: Do not resume PHY when attaching +Subject: [PATCH 05/80] net: phy: Do not resume PHY when attaching The PHY should not start negotiating with its link-partner until explicitly instructed to do so. diff --git a/patches/linux/6.18.55/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch b/patches/linux/6.18.55/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch index fa5d143f3..4a90cda2e 100644 --- a/patches/linux/6.18.55/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch +++ b/patches/linux/6.18.55/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch @@ -1,7 +1,7 @@ From c77bae505246ca5585adfa9219077af6fe98efa7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 4 Mar 2024 16:47:28 +0100 -Subject: [PATCH 06/78] net: bridge: avoid classifying unknown multicast as +Subject: [PATCH 06/80] net: bridge: avoid classifying unknown multicast as mrouters_only Unknown multicast, MAC/IPv4/IPv6, should always be flooded according to diff --git a/patches/linux/6.18.55/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch b/patches/linux/6.18.55/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch index fcd288fd8..106490dbf 100644 --- a/patches/linux/6.18.55/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch +++ b/patches/linux/6.18.55/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch @@ -1,7 +1,7 @@ From fe3d0113cb67a4e4287329bc1a3b311f9c707cf1 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 06:44:41 +0100 -Subject: [PATCH 07/78] net: bridge: Ignore router ports when forwarding L2 +Subject: [PATCH 07/80] net: bridge: Ignore router ports when forwarding L2 multicast Multicast router ports are either statically configured or learned from diff --git a/patches/linux/6.18.55/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch b/patches/linux/6.18.55/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch index 4151a986e..a82b18d3d 100644 --- a/patches/linux/6.18.55/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch +++ b/patches/linux/6.18.55/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch @@ -1,7 +1,7 @@ From e00f0142f3eef963aef0a046761fda5a2e7712e8 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 4 Apr 2024 16:36:30 +0200 -Subject: [PATCH 08/78] net: bridge: drop delay for applying strict multicast +Subject: [PATCH 08/80] net: bridge: drop delay for applying strict multicast filtering This *local* patch drops the initial delay before applying strict multicast diff --git a/patches/linux/6.18.55/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch b/patches/linux/6.18.55/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch index f4dad3714..eb2716b31 100644 --- a/patches/linux/6.18.55/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch +++ b/patches/linux/6.18.55/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch @@ -1,7 +1,7 @@ From f24cd427adb4d2f1c56c339085bdce4ee1d61844 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 May 2024 14:51:54 +0200 -Subject: [PATCH 09/78] net: bridge: Differentiate MDB additions from +Subject: [PATCH 09/80] net: bridge: Differentiate MDB additions from modifications Before this change, the reception of an IGMPv3 report (and analogously diff --git a/patches/linux/6.18.55/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch b/patches/linux/6.18.55/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch index 2ad996086..e2f37ec34 100644 --- a/patches/linux/6.18.55/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch +++ b/patches/linux/6.18.55/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch @@ -1,7 +1,7 @@ From e47f23a9767fc22465731ab728215afa25579c20 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 24 Nov 2023 23:29:55 +0100 -Subject: [PATCH 10/78] nvmem: layouts: onie-tlv: Let device probe even when +Subject: [PATCH 10/80] nvmem: layouts: onie-tlv: Let device probe even when TLV is invalid Before this change, probing an NVMEM device, expected to contain a diff --git a/patches/linux/6.18.55/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch b/patches/linux/6.18.55/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch index ab0ad78fd..cdc1b8615 100644 --- a/patches/linux/6.18.55/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch +++ b/patches/linux/6.18.55/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch @@ -1,7 +1,7 @@ From bd5c4068ec34ff0af11b1f52a3c06d43c8af39b1 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 11 Aug 2024 11:27:35 +0200 -Subject: [PATCH 11/78] net: usb: r8152: add r8153b support for link/activity +Subject: [PATCH 11/80] net: usb: r8152: add r8153b support for link/activity LEDs This patch adds support for the link/activity LEDs on the NanoPi R2S diff --git a/patches/linux/6.18.55/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch b/patches/linux/6.18.55/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch index d8140edde..fb23d8063 100644 --- a/patches/linux/6.18.55/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch +++ b/patches/linux/6.18.55/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch @@ -1,7 +1,7 @@ From 7552cf30d52b0798298af29063a44da182e9b6ee Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 10 Aug 2025 18:52:54 +0200 -Subject: [PATCH 12/78] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to +Subject: [PATCH 12/80] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to match case For ref. see: https://wiki.banana-pi.org/File:Bpi-r3_Metal_case.jpg diff --git a/patches/linux/6.18.55/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch b/patches/linux/6.18.55/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch index 94951986d..925df3c86 100644 --- a/patches/linux/6.18.55/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch +++ b/patches/linux/6.18.55/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch @@ -1,7 +1,7 @@ From 34593fc729c0656763733bf93719b8c8c9408f91 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 20 Aug 2025 21:38:24 +0200 -Subject: [PATCH 13/78] drm/panel-simple: Add a timing for the Raspberry Pi 7" +Subject: [PATCH 13/80] drm/panel-simple: Add a timing for the Raspberry Pi 7" panel The Raspberry Pi 7" 800x480 panel uses a Toshiba TC358762 DSI diff --git a/patches/linux/6.18.55/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch b/patches/linux/6.18.55/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch index adab4e5d7..75121da43 100644 --- a/patches/linux/6.18.55/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch +++ b/patches/linux/6.18.55/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch @@ -1,7 +1,7 @@ From e443789978265aac625d474e2ceeebdf62ce5b8e Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 21 Aug 2025 11:20:23 +0200 -Subject: [PATCH 14/78] input:touchscreen:edt-ft5x06: Add polled mode +Subject: [PATCH 14/80] input:touchscreen:edt-ft5x06: Add polled mode Not all hardware has interrupts therefore we need to poll the touchscreen. diff --git a/patches/linux/6.18.55/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch b/patches/linux/6.18.55/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch index 72e36b64f..5ad5ec3d4 100644 --- a/patches/linux/6.18.55/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch +++ b/patches/linux/6.18.55/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch @@ -1,7 +1,7 @@ From dab336b722de649666ef241636207f10745a0f1e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Mar 2024 10:27:24 +0100 -Subject: [PATCH 15/78] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for +Subject: [PATCH 15/80] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for PPU on 6393X In a multi-chip setup, delays of up to 750ms are observed before the diff --git a/patches/linux/6.18.55/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch b/patches/linux/6.18.55/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch index 17d51c3c4..e8a4dd969 100644 --- a/patches/linux/6.18.55/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch +++ b/patches/linux/6.18.55/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch @@ -1,7 +1,7 @@ From fbb87a65e898ae04f9801aa820f586d046ba329c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 15:52:43 +0100 -Subject: [PATCH 16/78] net: dsa: mv88e6xxx: Improve indirect register access +Subject: [PATCH 16/80] net: dsa: mv88e6xxx: Improve indirect register access perf on 6393 When operating in multi-chip mode, the 6393 family maps a subset of diff --git a/patches/linux/6.18.55/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch b/patches/linux/6.18.55/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch index 54b80314d..f5917d980 100644 --- a/patches/linux/6.18.55/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch +++ b/patches/linux/6.18.55/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch @@ -1,7 +1,7 @@ From 32bf97b633ba31514b0b0756a1c0891bc67254ba Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 22 Apr 2024 23:18:01 +0200 -Subject: [PATCH 17/78] net: dsa: mv88e6xxx: Honor ports being managed via +Subject: [PATCH 17/80] net: dsa: mv88e6xxx: Honor ports being managed via in-band-status Keep all link parameters in their unforced states when the port is diff --git a/patches/linux/6.18.55/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch b/patches/linux/6.18.55/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch index a4c117e94..58f61816b 100644 --- a/patches/linux/6.18.55/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch +++ b/patches/linux/6.18.55/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch @@ -1,7 +1,7 @@ From fadc315c8a6f0305edb24d3fbba80aa2e712d2a1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 24 Apr 2024 22:41:04 +0200 -Subject: [PATCH 18/78] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user +Subject: [PATCH 18/80] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user ports on 6393X For packets with a DA in the IEEE reserved L2 group range, originating diff --git a/patches/linux/6.18.55/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch b/patches/linux/6.18.55/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch index 088b22c42..5160321e6 100644 --- a/patches/linux/6.18.55/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch +++ b/patches/linux/6.18.55/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch @@ -1,7 +1,7 @@ From 52728881ff09f7e48fb26145d21b1dd83f6643e7 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 10:38:42 +0200 -Subject: [PATCH 19/78] net: dsa: tag_dsa: Use tag priority as initial +Subject: [PATCH 19/80] net: dsa: tag_dsa: Use tag priority as initial skb->priority Use the 3-bit priority field from the DSA tag as the initial packet diff --git a/patches/linux/6.18.55/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch b/patches/linux/6.18.55/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch index ca0afe350..0fff69c63 100644 --- a/patches/linux/6.18.55/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch +++ b/patches/linux/6.18.55/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch @@ -1,7 +1,7 @@ From 5fe097173ea1563a7f1e3b93fa0347699a94e19e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 16 Jan 2024 16:00:55 +0100 -Subject: [PATCH 20/78] net: dsa: Support MDB memberships whose L2 addresses +Subject: [PATCH 20/80] net: dsa: Support MDB memberships whose L2 addresses overlap Multiple IP multicast groups (32 for v4, 2^80 for v6) map to the same diff --git a/patches/linux/6.18.55/0021-net-dsa-Support-EtherType-based-priority-overrides.patch b/patches/linux/6.18.55/0021-net-dsa-Support-EtherType-based-priority-overrides.patch index 8d18a6a2a..44fb6980d 100644 --- a/patches/linux/6.18.55/0021-net-dsa-Support-EtherType-based-priority-overrides.patch +++ b/patches/linux/6.18.55/0021-net-dsa-Support-EtherType-based-priority-overrides.patch @@ -1,7 +1,7 @@ From b1f583e91f7d447d2a81feb2753b8a483c3ff24f Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 21 Mar 2024 19:12:15 +0100 -Subject: [PATCH 21/78] net: dsa: Support EtherType based priority overrides +Subject: [PATCH 21/80] net: dsa: Support EtherType based priority overrides --- include/net/dsa.h | 4 ++++ diff --git a/patches/linux/6.18.55/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch b/patches/linux/6.18.55/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch index 363882976..df7d16a28 100644 --- a/patches/linux/6.18.55/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch +++ b/patches/linux/6.18.55/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch @@ -1,7 +1,7 @@ From ba6e63577971f172e2dcc46071d1b3471d9d1a12 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 22 Mar 2024 16:15:43 +0100 -Subject: [PATCH 22/78] net: dsa: mv88e6xxx: Support EtherType based priority +Subject: [PATCH 22/80] net: dsa: mv88e6xxx: Support EtherType based priority overrides --- diff --git a/patches/linux/6.18.55/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch b/patches/linux/6.18.55/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch index d8fed0a32..95c43c7bf 100644 --- a/patches/linux/6.18.55/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch +++ b/patches/linux/6.18.55/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch @@ -1,7 +1,7 @@ From b12a958ebe126f89d0bcd2057733a270de63e883 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 11:04:22 +0200 -Subject: [PATCH 23/78] net: dsa: mv88e6xxx: Add mqprio qdisc support +Subject: [PATCH 23/80] net: dsa: mv88e6xxx: Add mqprio qdisc support Add support for attaching mqprio qdisc's to mv88e6xxx ports and use the packet's traffic class as the outgoing priority when no PCP bits diff --git a/patches/linux/6.18.55/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch b/patches/linux/6.18.55/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch index 78c8791f8..b285f639c 100644 --- a/patches/linux/6.18.55/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch +++ b/patches/linux/6.18.55/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch @@ -1,7 +1,7 @@ From 5905be94b871ac828acbae66f6a2c21ea0c48dfd Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 29 May 2024 13:20:41 +0200 -Subject: [PATCH 24/78] net: dsa: mv88e6xxx: Use VLAN prio over IP when both +Subject: [PATCH 24/80] net: dsa: mv88e6xxx: Use VLAN prio over IP when both are available Switch the priority sourcing precdence to prefer VLAN PCP over IP diff --git a/patches/linux/6.18.55/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch b/patches/linux/6.18.55/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch index f87376948..f6b9cb510 100644 --- a/patches/linux/6.18.55/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch +++ b/patches/linux/6.18.55/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch @@ -1,7 +1,7 @@ From 508ff078d94e3083fcf905ff59fcb477a3dbe507 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 26 Nov 2024 19:45:59 +0100 -Subject: [PATCH 25/78] [FIX] net: dsa: mv88e6xxx: Trap locally terminated +Subject: [PATCH 25/80] [FIX] net: dsa: mv88e6xxx: Trap locally terminated VLANs Before this change, in a setup like the following, packets assigned to diff --git a/patches/linux/6.18.55/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch b/patches/linux/6.18.55/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch index 00afddfb0..330674958 100644 --- a/patches/linux/6.18.55/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch +++ b/patches/linux/6.18.55/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch @@ -1,7 +1,7 @@ From c24a806cd5b1728102098a6e3da24ee19f908884 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 12:35:12 +0100 -Subject: [PATCH 26/78] net: dsa: mv88e6xxx: collapse disabled state into +Subject: [PATCH 26/80] net: dsa: mv88e6xxx: collapse disabled state into blocking This patch changes the behavior of switchcore ports wrt. the port state. diff --git a/patches/linux/6.18.55/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch b/patches/linux/6.18.55/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch index d96c4386d..f7a366c25 100644 --- a/patches/linux/6.18.55/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch +++ b/patches/linux/6.18.55/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch @@ -1,7 +1,7 @@ From b1b26dd227ed5099626c1fcb0e843b81d3daae65 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 12 Feb 2025 22:03:14 +0100 -Subject: [PATCH 27/78] net: dsa: mv88e6xxx: Only activate LAG offloading when +Subject: [PATCH 27/80] net: dsa: mv88e6xxx: Only activate LAG offloading when bridged The current port isolation scheme for mv88e6xxx is detailed here: diff --git a/patches/linux/6.18.55/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch b/patches/linux/6.18.55/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch index 159a15a7d..c020140be 100644 --- a/patches/linux/6.18.55/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch +++ b/patches/linux/6.18.55/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch @@ -1,7 +1,7 @@ From 307811da452a9d4b665bc0f06c514d74d8c26ab9 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 14 Jan 2026 18:22:41 +0100 -Subject: [PATCH 28/78] net: dsa: mv88e6xxx: Add LED support for 6393X +Subject: [PATCH 28/80] net: dsa: mv88e6xxx: Add LED support for 6393X Original commit: commit 462277b926140ee2d231317e92afb6cabf640268 diff --git a/patches/linux/6.18.55/0029-wifi-brcmfmac-check-connection-state-before-querying.patch b/patches/linux/6.18.55/0029-wifi-brcmfmac-check-connection-state-before-querying.patch index 1e7ccf9bb..4b8d10ec6 100644 --- a/patches/linux/6.18.55/0029-wifi-brcmfmac-check-connection-state-before-querying.patch +++ b/patches/linux/6.18.55/0029-wifi-brcmfmac-check-connection-state-before-querying.patch @@ -1,7 +1,7 @@ From 59c469b24ce0aee40cde6aa1ea986cf42dfc150c Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 19 Jan 2026 13:06:53 +0100 -Subject: [PATCH 29/78] wifi: brcmfmac: check connection state before querying +Subject: [PATCH 29/80] wifi: brcmfmac: check connection state before querying station info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch b/patches/linux/6.18.55/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch index bb686aac7..af05c0969 100644 --- a/patches/linux/6.18.55/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch +++ b/patches/linux/6.18.55/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch @@ -1,7 +1,7 @@ From 96510a92c5e05d36dfd1b66e4e7719adc3b6aaf1 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:12:10 +0100 -Subject: [PATCH 30/78] wifi: brcmfmac: suppress log spam for +Subject: [PATCH 30/80] wifi: brcmfmac: suppress log spam for regulatory-restricted channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch b/patches/linux/6.18.55/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch index d6eee3ae1..3ed1c4b76 100644 --- a/patches/linux/6.18.55/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch +++ b/patches/linux/6.18.55/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch @@ -1,7 +1,7 @@ From 10cc35ebdae09afe942ce3e054be910b608af6ca Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:18:45 +0100 -Subject: [PATCH 31/78] wifi: brcmfmac: reduce log noise during AP to station +Subject: [PATCH 31/80] wifi: brcmfmac: reduce log noise during AP to station transition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch b/patches/linux/6.18.55/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch index 980f7bbfd..619e3f901 100644 --- a/patches/linux/6.18.55/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch +++ b/patches/linux/6.18.55/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch @@ -1,7 +1,7 @@ From 98a492d3fa5e2472984335bfec5353b293677983 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 17 Feb 2026 21:59:59 +0100 -Subject: [PATCH 32/78] net: phy: air_en8811h: add OF device table for +Subject: [PATCH 32/80] net: phy: air_en8811h: add OF device table for auto-loading mdio_uevent() only emits an OF-style MODALIAS via diff --git a/patches/linux/6.18.55/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch b/patches/linux/6.18.55/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch index d50b355fd..6784a0c25 100644 --- a/patches/linux/6.18.55/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch +++ b/patches/linux/6.18.55/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch @@ -1,7 +1,7 @@ From fe79facb35cd0c40ad8ad042680666351196cb22 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 20 Sep 2024 12:05:18 +0100 -Subject: [PATCH 33/78] drm: vc4: dsi: enable video and then retry failed +Subject: [PATCH 33/80] drm: vc4: dsi: enable video and then retry failed transfers The DSI block appears to be able to come up stuck in a condition where diff --git a/patches/linux/6.18.55/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch b/patches/linux/6.18.55/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch index 59d6dc79e..496726ec7 100644 --- a/patches/linux/6.18.55/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch +++ b/patches/linux/6.18.55/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch @@ -1,7 +1,7 @@ From bd07a3e59ac1c8f8ebfb54f5dc4e96c925f8381d Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 8 Jun 2022 17:23:47 +0100 -Subject: [PATCH 34/78] drm: vc4: dsi: Clocks should be running before reset +Subject: [PATCH 34/80] drm: vc4: dsi: Clocks should be running before reset The initialisation sequence differs slightly from the documentation in that the clocks are meant to be running before resets and diff --git a/patches/linux/6.18.55/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch b/patches/linux/6.18.55/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch index 5dd80c251..89d798807 100644 --- a/patches/linux/6.18.55/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch +++ b/patches/linux/6.18.55/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch @@ -1,7 +1,7 @@ From 95a1f57cf66771a609d4155774264d962a99d297 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 5 Apr 2024 17:51:55 +0100 -Subject: [PATCH 35/78] drm/vc4: Ensure DSI is enabled for FIFO resets +Subject: [PATCH 35/80] drm/vc4: Ensure DSI is enabled for FIFO resets The block must be enabled for the FIFO resets to be actioned, so ensure this is the case. diff --git a/patches/linux/6.18.55/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch b/patches/linux/6.18.55/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch index 4418dc762..a323ed0b4 100644 --- a/patches/linux/6.18.55/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch +++ b/patches/linux/6.18.55/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch @@ -1,7 +1,7 @@ From 2f6f4f9db2f1a65c8b445dc44fce4fe3b7e82755 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 26 May 2022 18:56:19 +0100 -Subject: [PATCH 36/78] drm: vc4: Reset DSI AFE on disable +Subject: [PATCH 36/80] drm: vc4: Reset DSI AFE on disable vc4_dsi_bridge_disable wasn't resetting things during shutdown, so add that in. diff --git a/patches/linux/6.18.55/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch b/patches/linux/6.18.55/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch index b8ce8fd24..48a453713 100644 --- a/patches/linux/6.18.55/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch +++ b/patches/linux/6.18.55/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch @@ -1,7 +1,7 @@ From 985a117c1a98da834b30c3a698675191341b53e1 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 20 Nov 2024 13:58:08 +0000 -Subject: [PATCH 37/78] drm: vc4: dsi: Handle the different command FIFO widths +Subject: [PATCH 37/80] drm: vc4: dsi: Handle the different command FIFO widths DSI0 and DSI1 have different widths for the command FIFO (24bit vs 32bit), but the driver was assuming the 32bit width of DSI1 diff --git a/patches/linux/6.18.55/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch b/patches/linux/6.18.55/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch index e27586d0a..e377b2b4d 100644 --- a/patches/linux/6.18.55/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch +++ b/patches/linux/6.18.55/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch @@ -1,7 +1,7 @@ From 3eb94e911cb22e62435f70b33b515e93a5697e5d Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 17:37:00 +0000 -Subject: [PATCH 38/78] drm/bridge: tc358762: Program the DPI mode into the +Subject: [PATCH 38/80] drm/bridge: tc358762: Program the DPI mode into the chip The autodetection of resolution/timing by the TC358762 can lead diff --git a/patches/linux/6.18.55/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch b/patches/linux/6.18.55/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch index 32e36366b..256df2a2e 100644 --- a/patches/linux/6.18.55/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch +++ b/patches/linux/6.18.55/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch @@ -1,7 +1,7 @@ From e4d2f293250646a8109932ce816acfa1434cfc1c Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 18:44:49 +0000 -Subject: [PATCH 39/78] drm/bridge: tc358762: revert move ops to enable +Subject: [PATCH 39/80] drm/bridge: tc358762: revert move ops to enable Reverts 8a4b2fc9c91a ("drm/bridge: tc358762: Split register programming from pre-enable to enable") as we want the config commands sent before video starts. diff --git a/patches/linux/6.18.55/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch b/patches/linux/6.18.55/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch index 702d2e779..231fe00b4 100644 --- a/patches/linux/6.18.55/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch +++ b/patches/linux/6.18.55/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch @@ -1,7 +1,7 @@ From 0a1c5fc25afd628b5547348a4a65b1764f53f287 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Sat, 4 Apr 2026 18:04:19 +0200 -Subject: [PATCH 40/78] drm/bridge: tc358762: Set pre_enabled on pre_enable to +Subject: [PATCH 40/80] drm/bridge: tc358762: Set pre_enabled on pre_enable to prevent regulator imbalance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch b/patches/linux/6.18.55/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch index fd33eebc3..837da1083 100644 --- a/patches/linux/6.18.55/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch +++ b/patches/linux/6.18.55/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch @@ -1,7 +1,7 @@ From 384c6992b3b1e67d6515d00f20ac03a4deb2d3e9 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 5 Apr 2026 11:33:00 +0200 -Subject: [PATCH 41/78] net/pcs: add standalone PCS registration infrastructure +Subject: [PATCH 41/80] net/pcs: add standalone PCS registration infrastructure Add a simple registration mechanism that allows platform PCS drivers to register their phylink_pcs instances, and consumers (e.g. Ethernet MAC diff --git a/patches/linux/6.18.55/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch b/patches/linux/6.18.55/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch index ae942d64f..47181ae19 100644 --- a/patches/linux/6.18.55/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch +++ b/patches/linux/6.18.55/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch @@ -1,7 +1,7 @@ From 93142130e7b12203fb7b8c421bc5c4f3a6c065e7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:14:23 +0200 -Subject: [PATCH 42/78] net/pcs: add MediaTek MT7988 USXGMII PCS driver +Subject: [PATCH 42/80] net/pcs: add MediaTek MT7988 USXGMII PCS driver Add a PCS driver for the USXGMII subsystem found in the MediaTek MT7988 SoC (usxgmiisys0 at 0x10080000, usxgmiisys1 at 0x10081000). The hardware diff --git a/patches/linux/6.18.55/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch b/patches/linux/6.18.55/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch index 82352f2fd..f14cd30bf 100644 --- a/patches/linux/6.18.55/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch +++ b/patches/linux/6.18.55/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch @@ -1,7 +1,7 @@ From 54ba118cc31f35998a57a2b12809037fc675ad61 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:43 +0200 -Subject: [PATCH 43/78] net: ethernet: mediatek: add USXGMII support for MT7988 +Subject: [PATCH 43/80] net: ethernet: mediatek: add USXGMII support for MT7988 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/patches/linux/6.18.55/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch b/patches/linux/6.18.55/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch index 1d2a19627..1a7cd1b7f 100644 --- a/patches/linux/6.18.55/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch +++ b/patches/linux/6.18.55/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch @@ -1,7 +1,7 @@ From 262f24d9ace1f68b30a1e6f1728be00300addf2e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:56 +0200 -Subject: [PATCH 44/78] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes +Subject: [PATCH 44/80] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes Add device nodes for the two USXGMII subsystem blocks (usxgmiisys0 at 0x10080000 and usxgmiisys1 at 0x10081000), each referencing its clock, diff --git a/patches/linux/6.18.55/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch b/patches/linux/6.18.55/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch index 7b74dcb09..2b434a6b1 100644 --- a/patches/linux/6.18.55/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch +++ b/patches/linux/6.18.55/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch @@ -1,7 +1,7 @@ From 4ef35eea0eaca6789595196ca8399154ba53f034 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:16:11 +0200 -Subject: [PATCH 45/78] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ +Subject: [PATCH 45/80] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ ports and WPS button Enable the SFP+ cages wired to gmac1 and gmac2. The USXGMII PCS nodes diff --git a/patches/linux/6.18.55/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch b/patches/linux/6.18.55/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch index c0c5676a2..e853de0ee 100644 --- a/patches/linux/6.18.55/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch +++ b/patches/linux/6.18.55/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch @@ -1,7 +1,7 @@ From 81a4f7212dc829bf60e7c96bcae2317d5545a428 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 Apr 2026 07:34:52 +0200 -Subject: [PATCH 46/78] net: phy: sfp: add OEM SFP-10G-T-I quirk +Subject: [PATCH 46/80] net: phy: sfp: add OEM SFP-10G-T-I quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/patches/linux/6.18.55/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch b/patches/linux/6.18.55/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch index a42c7ec9c..98a2044d4 100644 --- a/patches/linux/6.18.55/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch +++ b/patches/linux/6.18.55/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch @@ -1,7 +1,7 @@ From d2cf171d24e9d08ad6a3bb43ad6790c4e03b3626 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 17 Apr 2026 09:13:04 +0000 -Subject: [PATCH 47/78] net: dsa: mv88e6xxx: Trap PTP frames on timestamping +Subject: [PATCH 47/80] net: dsa: mv88e6xxx: Trap PTP frames on timestamping ports, on 6393X Similar to the Peridot (6390), the designation of PTP frames as diff --git a/patches/linux/6.18.55/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch b/patches/linux/6.18.55/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch index fc12fd035..b37cc177a 100644 --- a/patches/linux/6.18.55/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch +++ b/patches/linux/6.18.55/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch @@ -1,7 +1,7 @@ From b6ad175129b948b44460af7ef7701e244e3a3b55 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Apr 2026 15:30:01 +0200 -Subject: [PATCH 48/78] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 +Subject: [PATCH 48/80] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 wmac Without MODULE_DEVICE_TABLE(of, ...) the OF compatible alias is never diff --git a/patches/linux/6.18.55/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch b/patches/linux/6.18.55/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch index dddc27bc9..ce1e36619 100644 --- a/patches/linux/6.18.55/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch +++ b/patches/linux/6.18.55/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch @@ -1,7 +1,7 @@ From eb6bf2a9d6967617eea9b242603e0352f3856a00 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 22 Apr 2026 10:24:43 +0200 -Subject: [PATCH 49/78] PCI: mediatek-gen3: Fix PERST# control timing during +Subject: [PATCH 49/80] PCI: mediatek-gen3: Fix PERST# control timing during system startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch b/patches/linux/6.18.55/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch index 0cb957ef3..e9208d9be 100644 --- a/patches/linux/6.18.55/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch +++ b/patches/linux/6.18.55/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch @@ -1,7 +1,7 @@ From ebaca6761deffa22bf18f0dfda287fa0e3210694 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 12 Aug 2026 10:08:53 +0200 -Subject: [PATCH 50/78] net: dsa: mv88e6xxx: Derive LED names from device name +Subject: [PATCH 50/80] net: dsa: mv88e6xxx: Derive LED names from device name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/patches/linux/6.18.55/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch b/patches/linux/6.18.55/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch index 04e64f390..400ebf21d 100644 --- a/patches/linux/6.18.55/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch +++ b/patches/linux/6.18.55/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch @@ -1,7 +1,7 @@ From 4706c45681cda924ab8eaf9015be7a911c15c183 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 31 Oct 2025 13:18:12 +0100 -Subject: [PATCH 51/78] phy: sparx5-serdes: make it selectable for ARCH_LAN969X +Subject: [PATCH 51/80] phy: sparx5-serdes: make it selectable for ARCH_LAN969X LAN969x uses the SparX-5 SERDES driver, so make it selectable for ARCH_LAN969X. diff --git a/patches/linux/6.18.55/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch b/patches/linux/6.18.55/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch index 2ac52662f..0ec896cfb 100644 --- a/patches/linux/6.18.55/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch +++ b/patches/linux/6.18.55/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch @@ -1,7 +1,7 @@ From 30f7485ae40aaad3d66606c49c196c28d65983ae Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:38 +0200 -Subject: [PATCH 52/78] net: sparx5: fix wrong chip ids for TSN SKUs +Subject: [PATCH 52/80] net: sparx5: fix wrong chip ids for TSN SKUs The TSN SKUs in enum spx5_target_chiptype have incorrect IDs: diff --git a/patches/linux/6.18.55/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch b/patches/linux/6.18.55/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch index 63e7c6fab..ea6023f67 100644 --- a/patches/linux/6.18.55/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch +++ b/patches/linux/6.18.55/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch @@ -1,7 +1,7 @@ From 1ac619e910f3911443963cd2359dd89958213313 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:39 +0200 -Subject: [PATCH 53/78] net: sparx5: configure serdes for 1000BASE-X in +Subject: [PATCH 53/80] net: sparx5: configure serdes for 1000BASE-X in sparx5_port_init() sparx5_port_init() only invokes sparx5_serdes_set() and the associated diff --git a/patches/linux/6.18.55/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch b/patches/linux/6.18.55/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch index 856b42976..25fa66bfa 100644 --- a/patches/linux/6.18.55/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch +++ b/patches/linux/6.18.55/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch @@ -1,7 +1,7 @@ From 7fe972637a8ffbdf55391e7f6927649a50caae9a Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:29 +0200 -Subject: [PATCH 54/78] dt-bindings: mmc: atmel,sama5d2-sdhci: add +Subject: [PATCH 54/80] dt-bindings: mmc: atmel,sama5d2-sdhci: add microchip,lan969x-sdhci The LAN969x SDMMC controller has its own base clock divider and, unlike diff --git a/patches/linux/6.18.55/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch b/patches/linux/6.18.55/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch index eabf5693f..ed6dd27eb 100644 --- a/patches/linux/6.18.55/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch +++ b/patches/linux/6.18.55/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From 7b461e9b726584d2d4cc908a6120138504e52127 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:44 +0200 -Subject: [PATCH 55/78] mmc: sdhci-of-at91: add LAN969x support +Subject: [PATCH 55/80] mmc: sdhci-of-at91: add LAN969x support The LAN969x SDMMC controller is an Atmel SDMMC IP block, but the driver has no compatible for it, so the eMMC on LAN969x boards never probes. diff --git a/patches/linux/6.18.55/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch b/patches/linux/6.18.55/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch index eb4ceed8e..ff7300e2b 100644 --- a/patches/linux/6.18.55/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch +++ b/patches/linux/6.18.55/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch @@ -1,7 +1,7 @@ From a55cfe74da903a95ff10f6f3659f6e4d26eb5a13 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:37:48 +0200 -Subject: [PATCH 56/78] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC +Subject: [PATCH 56/80] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC hardware reset The controller is reset, and its signaling mode changed, with SDCLK diff --git a/patches/linux/6.18.55/0057-net-sparx5-lan969x-populate-netdev-of_node.patch b/patches/linux/6.18.55/0057-net-sparx5-lan969x-populate-netdev-of_node.patch index 396a1cb41..d7204764c 100644 --- a/patches/linux/6.18.55/0057-net-sparx5-lan969x-populate-netdev-of_node.patch +++ b/patches/linux/6.18.55/0057-net-sparx5-lan969x-populate-netdev-of_node.patch @@ -1,7 +1,7 @@ From 0f61d1e5c4910ed9cd79fb1b3ebccae2d69498d3 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 10 Nov 2025 13:42:53 +0100 -Subject: [PATCH 57/78] net: sparx5/lan969x: populate netdev of_node +Subject: [PATCH 57/80] net: sparx5/lan969x: populate netdev of_node Populate of_node for the port netdevs, to make the individual ports of_nodes available in sysfs. diff --git a/patches/linux/6.18.55/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch b/patches/linux/6.18.55/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch index db7c9cedb..210eb27af 100644 --- a/patches/linux/6.18.55/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch +++ b/patches/linux/6.18.55/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch @@ -1,7 +1,7 @@ From 3bf84f8d8dcbd2c444ba8249ee5789941def7215 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:11 +0100 -Subject: [PATCH 58/78] arm64: dts: microchip: add LAN969x clock header file +Subject: [PATCH 58/80] arm64: dts: microchip: add LAN969x clock header file LAN969x uses hardware clock indexes, so document theses in a header to make them humanly readable. diff --git a/patches/linux/6.18.55/0059-arm64-dts-microchip-add-LAN969x-support.patch b/patches/linux/6.18.55/0059-arm64-dts-microchip-add-LAN969x-support.patch index 8fa346b04..efa5f279a 100644 --- a/patches/linux/6.18.55/0059-arm64-dts-microchip-add-LAN969x-support.patch +++ b/patches/linux/6.18.55/0059-arm64-dts-microchip-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From 2783fbf634082295b24d8573ce91ea33017bb7b8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:12 +0100 -Subject: [PATCH 59/78] arm64: dts: microchip: add LAN969x support +Subject: [PATCH 59/80] arm64: dts: microchip: add LAN969x support Add support for Microchip LAN969x switch SoC series by adding the SoC DTSI. diff --git a/patches/linux/6.18.55/0060-arm64-dts-microchip-add-EV23X71A-board.patch b/patches/linux/6.18.55/0060-arm64-dts-microchip-add-EV23X71A-board.patch index 84e3ff4d0..d3e91fe96 100644 --- a/patches/linux/6.18.55/0060-arm64-dts-microchip-add-EV23X71A-board.patch +++ b/patches/linux/6.18.55/0060-arm64-dts-microchip-add-EV23X71A-board.patch @@ -1,7 +1,7 @@ From 129276c0d1704ca77d786d6aa300913b28d92e1f Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:14 +0100 -Subject: [PATCH 60/78] arm64: dts: microchip: add EV23X71A board +Subject: [PATCH 60/80] arm64: dts: microchip: add EV23X71A board Microchip EV23X71A is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.55/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch b/patches/linux/6.18.55/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch index 040b0870d..71b284fe1 100644 --- a/patches/linux/6.18.55/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch +++ b/patches/linux/6.18.55/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch @@ -1,7 +1,7 @@ From 6f418f0c5d3dd1ca1d431e23d8149413c5aa8f9a Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 15 May 2026 13:59:09 +0200 -Subject: [PATCH 61/78] arm64: dts: microchip: lan969x: add OTP node +Subject: [PATCH 61/80] arm64: dts: microchip: lan969x: add OTP node Add the required OTP on LAN969x. diff --git a/patches/linux/6.18.55/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch b/patches/linux/6.18.55/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch index fcfa1253d..70cb88c3c 100644 --- a/patches/linux/6.18.55/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch +++ b/patches/linux/6.18.55/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch @@ -1,7 +1,7 @@ From a49eecdbfa8d6daa61c5baa4d2ebf3d006358fcb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 62/78] arm64: dts: microchip: lan969x: add SDMMC nodes +Subject: [PATCH 62/80] arm64: dts: microchip: lan969x: add SDMMC nodes The SoC has two SDMMC controllers, neither of which is described, so boards with eMMC have no way to enable it. Add both, disabled by diff --git a/patches/linux/6.18.55/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch b/patches/linux/6.18.55/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch index 8b2a898d8..5139cb949 100644 --- a/patches/linux/6.18.55/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch +++ b/patches/linux/6.18.55/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch @@ -1,7 +1,7 @@ From 8dd67f639beee3bbe5c8992d0314b40ad46bb726 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 63/78] arm64: dts: microchip: ev23x71a: enable eMMC +Subject: [PATCH 63/80] arm64: dts: microchip: ev23x71a: enable eMMC The board has an 8-bit eMMC on SDMMC0, and defines the emmc_sd pinctrl group for it, but nothing enables the controller. diff --git a/patches/linux/6.18.55/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch b/patches/linux/6.18.55/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch index 8c224bce4..5ac51e6cd 100644 --- a/patches/linux/6.18.55/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch +++ b/patches/linux/6.18.55/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch @@ -1,7 +1,7 @@ From 10d713882efe78b7be0dbda7e469ce7dd1830933 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:42:48 +0200 -Subject: [PATCH 64/78] wifi: brcmfmac: survey the requested interface, not the +Subject: [PATCH 64/80] wifi: brcmfmac: survey the requested interface, not the primary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch b/patches/linux/6.18.55/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch index 6b43d2c04..c0abca748 100644 --- a/patches/linux/6.18.55/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch +++ b/patches/linux/6.18.55/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch @@ -1,7 +1,7 @@ From 0d0351b6cb2d86eab7728fcda9a6a89489e5eb0f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:43:14 +0200 -Subject: [PATCH 65/78] wifi: brcmfmac: honor caller's rtnl lock when stopping +Subject: [PATCH 65/80] wifi: brcmfmac: honor caller's rtnl lock when stopping primary interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch b/patches/linux/6.18.55/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch index 8badcc793..0fa23dbb3 100644 --- a/patches/linux/6.18.55/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch +++ b/patches/linux/6.18.55/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch @@ -1,7 +1,7 @@ From 438486c55109e46bf0227704a6e28e3ccb4c751b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:46:25 +0200 -Subject: [PATCH 66/78] wifi: brcmfmac: let cfg_to_ndev() return NULL and +Subject: [PATCH 66/80] wifi: brcmfmac: let cfg_to_ndev() return NULL and harden its callers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch b/patches/linux/6.18.55/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch index 8adbf256a..2719d697c 100644 --- a/patches/linux/6.18.55/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch +++ b/patches/linux/6.18.55/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch @@ -1,7 +1,7 @@ From 0c6d68f2957c4a73c66a2fbe6524cf54c11ad138 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 16:05:17 +0200 -Subject: [PATCH 67/78] wifi: brcmfmac: support deletion and recreation of the +Subject: [PATCH 67/80] wifi: brcmfmac: support deletion and recreation of the primary interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch b/patches/linux/6.18.55/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch index 4b5aa554c..a770f6ae7 100644 --- a/patches/linux/6.18.55/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch +++ b/patches/linux/6.18.55/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch @@ -1,7 +1,7 @@ From e0016e70627bbd29ad17dfdcf6890d6692ad2679 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 10 Sep 2026 08:29:47 +0200 -Subject: [PATCH 68/78] wifi: brcmfmac: report port authorized after offloaded +Subject: [PATCH 68/80] wifi: brcmfmac: report port authorized after offloaded PSK/SAE handshake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.55/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch b/patches/linux/6.18.55/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch index b4de3473b..1cf61daa0 100644 --- a/patches/linux/6.18.55/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.55/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From 87bb123eb9add21c4ed4bce67313dcc37b367079 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 9 Jan 2026 13:27:00 +0100 -Subject: [PATCH 69/78] dt-bindings: arm: AT91: document Novarq Tactical 1000 +Subject: [PATCH 69/80] dt-bindings: arm: AT91: document Novarq Tactical 1000 Novarq Tactical 1000 is a Microchip LAN9696 based 24x1G + 4x10G SFP switch. diff --git a/patches/linux/6.18.55/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch b/patches/linux/6.18.55/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch index 783c72ff4..f23f33702 100644 --- a/patches/linux/6.18.55/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.55/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From ded124119dbb2ad94c870b56449f12798043e63f Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Sep 2025 13:37:49 +0200 -Subject: [PATCH 70/78] arm64: dts: microchip: add Novarq Tactical 1000 +Subject: [PATCH 70/80] arm64: dts: microchip: add Novarq Tactical 1000 Novarq Tactical 1000 is a LAN9696 based switch featuring 24x1G and 4x10G SFP ports. diff --git a/patches/linux/6.18.55/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch b/patches/linux/6.18.55/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch index 1a8306c2e..9a7bb4836 100644 --- a/patches/linux/6.18.55/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch +++ b/patches/linux/6.18.55/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch @@ -1,7 +1,7 @@ From aa86655cabeb7c303f3acbc59a36754d1b5383c5 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Jun 2026 11:23:47 +0200 -Subject: [PATCH 71/78] arm64: dts: microchip: tactical-1000: add port names +Subject: [PATCH 71/80] arm64: dts: microchip: tactical-1000: add port names Now that driver supports parsing the "label" property, populate port names as they are physically wired up. diff --git a/patches/linux/6.18.55/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch b/patches/linux/6.18.55/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch index 7c61c7250..86a83c0ee 100644 --- a/patches/linux/6.18.55/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch +++ b/patches/linux/6.18.55/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch @@ -1,7 +1,7 @@ From a5fe28d8813c1265151399c88961117e95db9c7d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 20 Sep 2026 12:22:43 +0200 -Subject: [PATCH 72/78] arm64: dts: microchip: tactical-1000: adapt to 6.18 +Subject: [PATCH 72/80] arm64: dts: microchip: tactical-1000: adapt to 6.18 Novarq develop against 7.3, which has three things 6.18 does not: a QSPI controller node, a tmon that also provides the fan PWM, and hence a SoC diff --git a/patches/linux/6.18.55/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch b/patches/linux/6.18.55/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch index 5a53d4404..0d8842c25 100644 --- a/patches/linux/6.18.55/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch +++ b/patches/linux/6.18.55/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch @@ -1,7 +1,7 @@ From b390f0eaf27fd05c7a1436f3941964758b7a4fb4 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:13 +0100 -Subject: [PATCH 73/78] dt-bindings: arm: AT91: document EV23X71A board +Subject: [PATCH 73/80] dt-bindings: arm: AT91: document EV23X71A board Microchip EV23X71A board is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.55/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch b/patches/linux/6.18.55/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch index 6395b557c..d66312e4c 100644 --- a/patches/linux/6.18.55/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch +++ b/patches/linux/6.18.55/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch @@ -1,7 +1,7 @@ From 8fc86e86edc2464d419084beb306cb95cf5946fc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:21 +0200 -Subject: [PATCH 74/78] net: dsa: Skip DCB default priority init on unsupported +Subject: [PATCH 74/80] net: dsa: Skip DCB default priority init on unsupported switches A driver serving several chip generations has one dsa_switch_ops for diff --git a/patches/linux/6.18.55/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch b/patches/linux/6.18.55/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch index 9ed0da249..0de88fdc5 100644 --- a/patches/linux/6.18.55/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch +++ b/patches/linux/6.18.55/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch @@ -1,7 +1,7 @@ From 3d3f7ba164355546fc2188315622356b4f02f681 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:51:07 +0200 -Subject: [PATCH 75/78] net: dsa: Generalise the global DCB APP mirroring +Subject: [PATCH 75/80] net: dsa: Generalise the global DCB APP mirroring helper A switch with one classification table for all its ports programs it diff --git a/patches/linux/6.18.55/0076-net-dsa-Support-the-PCP-APP-selector.patch b/patches/linux/6.18.55/0076-net-dsa-Support-the-PCP-APP-selector.patch index e3d2b348d..a28ff8fb3 100644 --- a/patches/linux/6.18.55/0076-net-dsa-Support-the-PCP-APP-selector.patch +++ b/patches/linux/6.18.55/0076-net-dsa-Support-the-PCP-APP-selector.patch @@ -1,7 +1,7 @@ From 32c1befbedb0f46361960a0d321c743711a87762 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:22 +0200 -Subject: [PATCH 76/78] net: dsa: Support the PCP APP selector +Subject: [PATCH 76/80] net: dsa: Support the PCP APP selector Add port_add_pcp_prio, port_del_pcp_prio and port_get_pcp_prio switch ops and route the DCB_APP_SEL_PCP selector to them, mirroring the DSCP diff --git a/patches/linux/6.18.55/0077-net-dsa-Support-DCB-priority-rewrite.patch b/patches/linux/6.18.55/0077-net-dsa-Support-DCB-priority-rewrite.patch index 34fa95eea..f3fbbac89 100644 --- a/patches/linux/6.18.55/0077-net-dsa-Support-DCB-priority-rewrite.patch +++ b/patches/linux/6.18.55/0077-net-dsa-Support-DCB-priority-rewrite.patch @@ -1,7 +1,7 @@ From b45635ee810e5edadedbc000b737281b2c517695 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:10:32 +0200 -Subject: [PATCH 77/78] net: dsa: Support DCB priority rewrite +Subject: [PATCH 77/80] net: dsa: Support DCB priority rewrite The DCB rewrite table maps a priority back to the PCP and DEI, or the DSCP, that frames are remarked with on egress. DSA has no diff --git a/patches/linux/6.18.55/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch b/patches/linux/6.18.55/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch index adc56c667..cf7238c5f 100644 --- a/patches/linux/6.18.55/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch +++ b/patches/linux/6.18.55/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch @@ -1,7 +1,7 @@ From 621214da6d9ee1c6ebef311b193dab441c53d359 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:53:23 +0200 -Subject: [PATCH 78/78] net: dsa: Support the IEEE ETS managed object +Subject: [PATCH 78/80] net: dsa: Support the IEEE ETS managed object A switch port's transmission selection is configured today through the ets queuing discipline, which numbers its bands the other way round diff --git a/patches/linux/6.18.55/0079-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch b/patches/linux/6.18.55/0079-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch new file mode 100644 index 000000000..6f995f7e5 --- /dev/null +++ b/patches/linux/6.18.55/0079-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch @@ -0,0 +1,45 @@ +From 9f01f451cd49c71b37d15e26fd3b4ae6c381a95b Mon Sep 17 00:00:00 2001 +From: Mattias Walström +Date: Mon, 5 Oct 2026 14:37:56 +0200 +Subject: [PATCH 79/80] net: ethernet: mtk_wed: map WO memory regions without + requesting them +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Since the conversion to of_reserved_mem_region_to_resource_byname(), +the WO firmware regions are mapped with devm_ioremap_resource(), which +also requests the region. On MT7986 that request fails and WED never +attaches: + + platform 15010000.wed: error -EBUSY: can't request region for resource [mem 0x4fd00000-0x4fd3ffff] + platform 15010000.wed: failed to attach wed device + +The wo-data region is in addition shared by both WED instances, so a +request-based mapping can never succeed for the second one. Go back +to a plain devm_ioremap(), as the code did before the conversion and +as qcom_wcnss did for the same regression. + +Fixes: e27dba1951ce ("net: Use of_reserved_mem_region_to_resource{_byname}() for "memory-region"") +Signed-off-by: Mattias Walström +--- + drivers/net/ethernet/mediatek/mtk_wed_mcu.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c +index 0d38183c6ba7..be469c753c71 100644 +--- a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c ++++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c +@@ -246,9 +246,9 @@ mtk_wed_get_memory_region(struct mtk_wed_hw *hw, const char *name, + + region->phy_addr = res.start; + region->size = resource_size(&res); +- region->addr = devm_ioremap_resource(hw->dev, &res); +- if (IS_ERR(region->addr)) +- return PTR_ERR(region->addr); ++ region->addr = devm_ioremap(hw->dev, region->phy_addr, region->size); ++ if (!region->addr) ++ return -ENOMEM; + + return 0; + } diff --git a/patches/linux/6.18.55/0080-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch b/patches/linux/6.18.55/0080-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch new file mode 100644 index 000000000..a572bc06a --- /dev/null +++ b/patches/linux/6.18.55/0080-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch @@ -0,0 +1,45 @@ +From ad7b3b544a57029b682d8c6c422d0313abdb503e Mon Sep 17 00:00:00 2001 +From: Mattias Walström +Date: Mon, 5 Oct 2026 14:37:56 +0200 +Subject: [PATCH 80/80] wifi: mt76: wed: fix kernel panic on non-DBDC MT7986 + +mt76_wed_init_rx_buf() hardcodes MT_RXQ_MAIN, but a non-DBDC MT7986 +uses MT_RXQ_BAND1 for its RX data queue, so MT_RXQ_MAIN has no page +pool and attaching WED dereferences NULL: + + Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 + pc : page_pool_alloc_frag_netmem+0x28/0x260 + lr : page_pool_alloc_frag+0x18/0x50 + mt76_wed_init_rx_buf+0x128/0x240 [mt76] + mtk_wed_start+0x804/0x1310 + mt7915_dma_start+0x278/0x340 [mt7915e] + +Pick the queue from the WED version and the band index instead. + +Upstream: https://ratatoskr.run/linux-mediatek/2026/07/17264034/t +Signed-off-by: Zhi-Jun You
+--- + drivers/net/wireless/mediatek/mt76/wed.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c +index fbd7e59c73aa..2ebf4edc689e 100644 +--- a/drivers/net/wireless/mediatek/mt76/wed.c ++++ b/drivers/net/wireless/mediatek/mt76/wed.c +@@ -33,10 +33,15 @@ u32 mt76_wed_init_rx_buf(struct mtk_wed_device *wed, int size) + { + struct mtk_wed_bm_desc *desc = wed->rx_buf_ring.desc; + struct mt76_dev *dev = mt76_wed_to_dev(wed); +- struct mt76_queue *q = &dev->q_rx[MT_RXQ_MAIN]; + struct mt76_txwi_cache *t = NULL; ++ struct mt76_queue *q; + int i; + ++ if (wed->version == 2 && dev->phy.band_idx) ++ q = &dev->q_rx[MT_RXQ_BAND1]; ++ else ++ q = &dev->q_rx[MT_RXQ_MAIN]; ++ + for (i = 0; i < size; i++) { + dma_addr_t addr; + u32 offset; diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 7ebf70d51..7905fd6cc 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -469,6 +469,17 @@ static confd_dependency_t dep_wifi_interfaces(struct lyd_node **diff, struct lyd radio_node = lydx_get_xpathf(config, "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio", ifname); + if (!radio_node) { + /* A WDS link has no radio of its own, follow its access point */ + struct lyd_node *ap = lydx_get_xpathf(config, + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/wds-link/access-point", + ifname); + + if (ap) + radio_node = lydx_get_xpathf(config, + "/ietf-interfaces:interfaces/interface[name='%s']/infix-interfaces:wifi/radio", + lyd_get_value(ap)); + } if (!radio_node) continue; diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index d883b2c39..aa391b7b6 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -339,6 +339,38 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name, return 0; } +/* Emit the 4-address WDS ports (wds-link interfaces) of an AP */ +static void wifi_gen_wds_ports(FILE *hostapd, struct lyd_node *config, const char *ap_ifname) +{ + struct lyd_node *cifs, *cif; + bool first = true; + + cifs = lydx_get_descendant(config, "interfaces", "interface", NULL); + LYX_LIST_FOR_EACH(cifs, cif, "interface") { + struct lyd_node *wds; + const char *ap; + + wds = lydx_get_descendant(lyd_child(cif), "wifi", "wds-link", NULL); + if (!wds) + continue; + + ap = lydx_get_cattr(wds, "access-point"); + if (!ap || strcmp(ap, ap_ifname)) + continue; + + if (first) { + fprintf(hostapd, "# 4-address WDS ports\n"); + fprintf(hostapd, "wds_sta=1\n"); + /* A deauth from a vanishing peer is easily lost, and the + * default 300 s leaves a dead backhaul port up that long. */ + fprintf(hostapd, "ap_max_inactivity=30\n"); + first = false; + } + fprintf(hostapd, "wds_sta_ifname=%s %s\n", + lydx_get_cattr(wds, "peer-address"), lydx_get_cattr(cif, "name")); + } +} + /* Helper: Write SSID and security configuration (shared between primary and BSS) */ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd_node *config, bool is_bss, const char *band) { @@ -361,6 +393,7 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd if (is_bss) { fprintf(hostapd, "\n# BSS %s\n", ifname); fprintf(hostapd, "bss=%s\n", ifname); + fprintf(hostapd, "ctrl_interface=/run/hostapd\n"); } /* Check 802.11k/r/v configuration */ @@ -505,6 +538,8 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd fprintf(hostapd, "bss_transition=1\n"); } + wifi_gen_wds_ports(hostapd, config, ifname); + /* OKC: Opportunistic Key Caching */ if (roaming) { const char *okc = lydx_get_cattr(roaming, "okc"); @@ -1253,13 +1288,14 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l goto err; } } else if (!strcmp(class, "infix-hardware:wifi")) { - struct lyd_node *interfaces_config, *interfaces_diff; struct lyd_node **wifi_iface_list = NULL; - struct lyd_node *ap; + struct lyd_node *interfaces_config; struct lyd_node *cwifi_radio; int wifi_iface_count = 0; char src[40], dst[40]; + char **ap_list = NULL; int ap_interfaces = 0; + int i; switch (event) { case SR_EV_ABORT: @@ -1267,27 +1303,25 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l case SR_EV_CHANGE: break; case SR_EV_DONE: - interfaces_diff = lydx_get_descendant(diff, "interfaces", "interface", NULL); - - wifi_find_interfaces_on_radio(interfaces_diff, name, - &wifi_iface_list, &wifi_iface_count); - if (wifi_iface_count > 0) { - ap = lydx_get_descendant(wifi_iface_list[0], "interface", "wifi", "access-point", NULL); - if (ap && lydx_get_op(ap) != LYDX_OP_DELETE) { - snprintf(src, sizeof(src), HOSTAPD_CONF_NEXT, name); - snprintf(dst, sizeof(dst), HOSTAPD_CONF, name); - - if (fexistf(HOSTAPD_CONF_NEXT, name)) { - (void)rename(src, dst); - ap_interfaces++; - } - } - } - if (!ap_interfaces) { + /* The changed interface need not be an AP, a + * wds-link or station on the radio also lands + * here, so judge by the APs left in config. */ + interfaces_config = lydx_get_descendant(config, "interfaces", "interface", NULL); + wifi_find_radio_aps(interfaces_config, name, &ap_list, &ap_interfaces); + for (i = 0; i < ap_interfaces; i++) + free(ap_list[i]); + free(ap_list); + + if (ap_interfaces) { + snprintf(src, sizeof(src), HOSTAPD_CONF_NEXT, name); + snprintf(dst, sizeof(dst), HOSTAPD_CONF, name); + + if (fexistf(HOSTAPD_CONF_NEXT, name)) + (void)rename(src, dst); + } else { erasef(HOSTAPD_CONF, name); erasef(HOSTAPD_CONF_NEXT, name); } - free(wifi_iface_list); /* All radios share one hostapd process; the service is * (re)generated after the component loop below. */ wifi_changed = 1; @@ -1432,7 +1466,7 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l } else { fprintf(fp, "# Generated by confd, do not edit.\n"); fprintf(fp, "service name:hostapd \\\n"); - fprintf(fp, "\t[2345] hostapd -P /run/hostapd.pid"); + fprintf(fp, "\t[2345] hostapd -g /run/hostapd.global -P /run/hostapd.pid"); for (i = 0; i < gl.gl_pathc; i++) fprintf(fp, " %s", gl.gl_pathv[i]); fprintf(fp, " \\\n\t-- Wi-Fi Access Points\n"); diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 8b9fca53a..a6e9f1f86 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -109,7 +109,7 @@ int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif) wifi_mode_t wifi_get_mode(struct lyd_node *iface) { - struct lyd_node *ap, *mesh, *wifi; + struct lyd_node *ap, *mesh, *wds, *wifi; wifi = lydx_get_child(iface, "wifi"); if (!wifi) @@ -127,6 +127,12 @@ wifi_mode_t wifi_get_mode(struct lyd_node *iface) return wifi_mesh; } + wds = lydx_get_child(wifi, "wds-link"); + if (wds) { + if (lydx_get_op(wds) != LYDX_OP_DELETE) + return wifi_wds; + } + /* * Need to return station even if "station" also is false, * because station is the default scanning mode. @@ -154,9 +160,78 @@ int wifi_mode_changed(struct lyd_node *wifi) if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE)) return 1; + node = lydx_get_child(wifi, "wds-link"); + if (node) + op = lydx_get_op(node); + if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE)) + return 1; + + /* Scan-only <-> station, and the 4-address flag is set at creation */ + node = lydx_get_child(wifi, "station"); + if (node) + op = lydx_get_op(node); + if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE)) + return 1; + if (node && lydx_get_child(node, "wds")) + return 1; + return 0; } +/* + * A changed wpa_supplicant config only takes effect when the daemon + * reloads it, so nudge the service whenever the wifi subtree changed. + */ +static int wifi_gen_reload(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net) +{ + const char *ifname = lydx_get_cattr(cif, "name"); + const char *svc; + FILE *fp; + + if (!lydx_get_child(dif, "wifi")) + return SR_ERR_OK; + + switch (wifi_get_mode(cif)) { + case wifi_station: + svc = "wifi"; + break; + case wifi_mesh: + svc = "mesh"; + break; + default: + return SR_ERR_OK; + } + + fp = dagger_fopen_net_init(net, ifname, NETDAG_INIT_DAEMON, "wifi-reload.sh"); + if (!fp) + return SR_ERR_INTERNAL; + + fprintf(fp, "initctl -bfq touch %s@%s\n", svc, ifname); + fclose(fp); + + return SR_ERR_OK; +} + +/* Settings of an existing station or mesh point changed */ +int wifi_gen_settings(sr_session_ctx_t *session, struct lyd_node *dif, + struct lyd_node *cif, struct dagger *net) +{ + int rc; + + switch (wifi_get_mode(cif)) { + case wifi_station: + rc = wifi_validate_secret(session, cif) ? : wifi_gen_station(cif); + break; + case wifi_mesh: + rc = wifi_gen_mesh(cif); + break; + default: + return SR_ERR_OK; + } + + return rc ? : wifi_gen_reload(dif, cif, net); +} + /* * Generate wpa_supplicant config for station mode */ @@ -164,6 +239,7 @@ int wifi_gen_station(struct lyd_node *cif) { const char *ifname, *ssid, *secret_name, *security_mode, *radio; struct lyd_node *security, *secret_node, *radio_node, *station, *wifi; + const char *bssid = NULL; unsigned char *secret = NULL; FILE *wpa_supplicant = NULL; char *security_str = NULL; @@ -180,6 +256,7 @@ int wifi_gen_station(struct lyd_node *cif) station = lydx_get_child(wifi, "station"); if (station) { ssid = lydx_get_cattr(station, "ssid"); + bssid = lydx_get_cattr(station, "peer-bssid"); security = lydx_get_child(station, "security"); security_mode = lydx_get_cattr(security, "mode"); secret_name = lydx_get_cattr(security, "secret"); @@ -239,9 +316,13 @@ int wifi_gen_station(struct lyd_node *cif) fprintf(wpa_supplicant, "network={\n" " bgscan=\"\"\n" - " ssid=\"%s\"\n" + " scan_ssid=1\n" + " ssid=\"%s\"\n", ssid); + if (bssid) + fprintf(wpa_supplicant, " bssid=%s\n", bssid); + fprintf(wpa_supplicant, " %s\n" - "}\n", ssid, security_str); + "}\n", security_str); free(security_str); } else { /* Scan-only mode - no station container configured */ @@ -481,6 +562,24 @@ static int wifi_get_probe_timeout(sr_session_ctx_t *session, const char *radio) /* * Add WiFi virtual interface using iw */ +int wifi_add_deps(struct lyd_node *cif) +{ + struct lyd_node *wds; + const char *ap; + int err; + + wds = lydx_get_descendant(lyd_child(cif), "wifi", "wds-link", NULL); + if (!wds) + return 0; + + ap = lydx_get_cattr(wds, "access-point"); + err = dagger_add_dep(&confd.netdag, lydx_get_cattr(cif, "name"), ap); + if (err) + return ERR_IFACE(cif, err, "Unable to depend on \"%s\"", ap); + + return 0; +} + int wifi_add_iface(struct lyd_node *cif, struct dagger *net) { const char *ifname, *radio; @@ -498,10 +597,14 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) return SR_ERR_INVAL_ARG; } - radio = lydx_get_cattr(wifi, "radio"); - if (!radio) { - ERROR("WiFi interface %s: missing radio reference", ifname); - return SR_ERR_INVAL_ARG; + mode = wifi_get_mode(cif); + if (mode == wifi_wds) { + /* A WDS link has no radio of its own, it is a port of its AP */ + const char *ap = lydx_get_cattr(lydx_get_child(wifi, "wds-link"), "access-point"); + + radio = lydx_get_cattr(lydx_get_xpathf(cif, "../interface[name='%s']/wifi", ap), "radio"); + } else { + radio = lydx_get_cattr(wifi, "radio"); } iw = dagger_fopen_net_init(net, ifname, NETDAG_INIT_PRE, "wifi-iface.sh"); @@ -510,12 +613,13 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) return SR_ERR_INTERNAL; } - mode = wifi_get_mode(cif); probe_timeout = wifi_get_probe_timeout(net->session, radio); fprintf(iw, "# Generated by Infix confd - WiFi Interface Creation\n"); fprintf(iw, "# Create %s interface %s on radio %s\n", - mode == wifi_station ? "station" : (mode == wifi_mesh ? "mesh" : "access point"), ifname, radio); + mode == wifi_station ? "station" : + mode == wifi_mesh ? "mesh" : + mode == wifi_wds ? "wds-link" : "access point", ifname, radio); /* Wait for PHY if probe-timeout is set (slow USB dongles) */ if (probe_timeout > 0) { @@ -540,12 +644,29 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) fprintf(iw, "fi\n\n"); switch(mode) { - case wifi_station: - fprintf(iw, "iw phy %s interface add %s type managed\n", radio, ifname); + case wifi_station: { + struct lyd_node *station = lydx_get_child(wifi, "station"); + + fprintf(iw, "iw phy %s interface add %s type managed%s\n", radio, ifname, + station && lydx_is_enabled(station, "wds") ? " 4addr on" : ""); wifi_gen_station(cif); fprintf(iw, "initctl -bfq enable wifi@%s\n", ifname); fprintf(iw, "initctl -bfq touch wifi@%s\n", ifname); + /* A running instance from before the netdev was recreated + * only gets a SIGHUP from the touch and keeps stale driver + * state, so make sure it starts over on the new netdev. */ + fprintf(iw, "initctl -bnq restart wpa_supplicant:%s\n", ifname); break; + } + case wifi_wds: { + const char *ap = lydx_get_cattr(lydx_get_child(wifi, "wds-link"), "access-point"); + + /* An AP_VLAN pairs with the AP of the same MAC address, and + * hostapd brings it up when the station associates. */ + fprintf(iw, "iw dev %s interface add %s type __ap_vlan" + " addr $(cat /sys/class/net/%s/address) 4addr on\n", ap, ifname, ap); + break; + } case wifi_ap: fprintf(iw, "iw phy %s interface add %s type __ap\n", radio, ifname); break; @@ -554,6 +675,7 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) wifi_gen_mesh(cif); fprintf(iw, "initctl -bfq enable mesh@%s\n", ifname); fprintf(iw, "initctl -bfq touch mesh@%s\n", ifname); + fprintf(iw, "initctl -bnq restart wpa_supplicant:%s\n", ifname); break; default: ERROR("WiFi mode %d unknown", mode); @@ -587,6 +709,10 @@ int wifi_del_iface(struct lyd_node *dif, struct dagger *net) fprintf(iw, "initctl -bfq disable mesh@%s\n", ifname); fprintf(iw, "initctl -bfq disable wifi@%s\n", ifname); + /* hostapd adopts a re-added netdev of a name it still holds and + * turns it back into an AP, so make it forget the name first. + * hostapd_cli cannot talk to the global socket, wpa_cli can. */ + fprintf(iw, "wpa_cli -g /run/hostapd.global raw \"REMOVE %s\" >/dev/null 2>&1\n", ifname); fprintf(iw, "ip link set %s down\n", ifname); fprintf(iw, "iw dev %s disconnect 2>/dev/null\n", ifname); fprintf(iw, "iw dev %s del 2>/dev/null || ip link del %s 2>/dev/null || true\n", ifname, ifname); diff --git a/src/confd/src/interfaces.c b/src/confd/src/interfaces.c index 822600426..7dede9e23 100644 --- a/src/confd/src/interfaces.c +++ b/src/confd/src/interfaces.c @@ -453,12 +453,7 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net, case IFT_ETH: return netdag_gen_ethtool(net, cif, dif); case IFT_WIFI: - if (wifi_get_mode(cif) == wifi_station) - return wifi_validate_secret(session, cif) - ? : wifi_gen_station(cif); - if (wifi_get_mode(cif) == wifi_mesh) - return wifi_gen_mesh(cif); - return 0; + return wifi_gen_settings(session, dif, cif, net); case IFT_DUMMY: case IFT_GRE: case IFT_GRETAP: @@ -755,8 +750,10 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net attr = lydx_get_cattr(cif, "description"); fprintf(ip, "link set alias \"%s\" dev %s\n", attr ?: "", ifname); - /* Bring interface back up, if enabled */ - if (lydx_is_enabled(cif, "enabled")) + /* Bring interface back up, if enabled. A wds-link port cannot + * come up before hostapd runs its AP, hostapd does it instead. */ + if (lydx_is_enabled(cif, "enabled") && + !(iftype_from_iface(cif) == IFT_WIFI && wifi_get_mode(cif) == wifi_wds)) fprintf(ip, "link set dev %s up state up\n", ifname); err = err ? : netdag_gen_sysctl(net, cif, dif); @@ -788,6 +785,7 @@ static int netdag_init_iface(struct lyd_node *cif) case IFT_VETH: return veth_add_deps(cif); case IFT_WIFI: + return wifi_add_deps(cif); case IFT_DUMMY: case IFT_ETH: case IFT_GRE: diff --git a/src/confd/src/interfaces.h b/src/confd/src/interfaces.h index 3ff5063e2..193dbd87b 100644 --- a/src/confd/src/interfaces.h +++ b/src/confd/src/interfaces.h @@ -132,13 +132,17 @@ typedef enum wifi_mode_t { wifi_station, wifi_ap, wifi_mesh, + wifi_wds, wifi_unknown } wifi_mode_t; int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif); +int wifi_add_deps(struct lyd_node *cif); int wifi_add_iface(struct lyd_node *cif, struct dagger *net); int wifi_del_iface(struct lyd_node *dif, struct dagger *net); int wifi_mode_changed(struct lyd_node *wifi); +int wifi_gen_settings(sr_session_ctx_t *session, struct lyd_node *dif, + struct lyd_node *cif, struct dagger *net); int wifi_gen_station(struct lyd_node *cif); int wifi_gen_mesh(struct lyd_node *cif); wifi_mode_t wifi_get_mode(struct lyd_node *wifi); diff --git a/src/confd/yang/confd/infix-if-bridge.yang b/src/confd/yang/confd/infix-if-bridge.yang index 7c5eff40b..5b3bba7db 100644 --- a/src/confd/yang/confd/infix-if-bridge.yang +++ b/src/confd/yang/confd/infix-if-bridge.yang @@ -29,6 +29,12 @@ submodule infix-if-bridge { contact "kernelkit@googlegroups.com"; description "Linux bridge extension for ietf-interfaces."; + revision 2026-10-02 { + description + "Allow WDS link interfaces and 4-address stations as bridge ports."; + reference "internal"; + } + revision 2026-04-29 { description "Add operational state for multicast router ports per bridge."; reference "internal"; @@ -939,8 +945,8 @@ submodule infix-if-bridge { must "not(../ip:ipv4/ip:address or ../ip:ipv6/ip:address)" { error-message "Bridge ports cannot have IP addresses configured."; } - must "not(derived-from-or-self(../if:type, 'infix-ift:wifi')) or ../infix-if:wifi/infix-if:access-point or ../infix-if:wifi/infix-if:mesh-point" { - error-message "WiFi interfaces can only be bridge ports when configured as Access Points or Mesh Points."; + must "not(derived-from-or-self(../if:type, 'infix-ift:wifi')) or ../infix-if:wifi/infix-if:access-point or ../infix-if:wifi/infix-if:mesh-point or ../infix-if:wifi/infix-if:wds-link or ../infix-if:wifi/infix-if:station/infix-if:wds = 'true'" { + error-message "WiFi interfaces can only be bridge ports as access point, mesh point, WDS link, or station with wds enabled."; } description "Bridge association and port specific settings."; uses bridge-port-common; diff --git a/src/confd/yang/confd/infix-if-bridge@2026-04-29.yang b/src/confd/yang/confd/infix-if-bridge@2026-10-02.yang similarity index 100% rename from src/confd/yang/confd/infix-if-bridge@2026-04-29.yang rename to src/confd/yang/confd/infix-if-bridge@2026-10-02.yang diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 078e50204..a88518eaa 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -43,11 +43,19 @@ submodule infix-if-wifi { interfaces provide network-layer configuration (SSID, security). Key features: - - Dual mode support: AP and Station + - Modes: Access Point, Station, 802.11s Mesh Point and 4-address + (WDS) link - Multi-SSID: Multiple APs on same radio - Security: WPA2/WPA3 with keystore integration - Operational state: Connection status, RSSI, client lists"; + revision 2026-10-02 { + description + "Add 4-address (WDS) support: wds-link mode for access point side + ports, and the station leaves 'wds' and 'peer-bssid'."; + reference "internal"; + } + revision 2026-09-24 { description "Constrain the character set of mesh-id and nas-identifier."; @@ -132,18 +140,21 @@ submodule infix-if-wifi { The interface must reference a radio defined in infix-wifi-radio module, which provides the physical layer configuration."; + must "radio or wds-link" { + error-message "A WiFi interface must reference a radio"; + } + leaf radio { type leafref { path "/iehw:hardware/iehw:component/iehw:name"; } - mandatory true; must "derived-from-or-self(/iehw:hardware/iehw:component[iehw:name=current()]/iehw:class, 'ih:wifi')" { error-message "Referenced hardware component must be a WiFi radio (class 'ih:wifi')"; } - must "count(/if:interfaces/if:interface[wifi/radio = current()][not(wifi/access-point)]) <= 1" { + must "count(/if:interfaces/if:interface[wifi/radio = current()][not(wifi/access-point)][not(wifi/wds-link)]) <= 1" { error-message "Only one station or scan interface is allowed per radio"; } - must "count(/if:interfaces/if:interface[wifi/radio = current()][not(infix-if:custom-phys-address/*)]) <= 1" { + must "count(/if:interfaces/if:interface[wifi/radio = current()][not(infix-if:custom-phys-address/*)][not(wifi/wds-link)]) <= 1" { error-message "Only one interface per radio can use the default MAC address. Configure custom-phys-address on additional interfaces."; } @@ -152,7 +163,8 @@ submodule infix-if-wifi { References a hardware component with class 'ih:wifi'. The radio must exist and be configured before creating - virtual interfaces. + virtual interfaces. Not set for a WDS link, which uses + the radio of its access point. Example: 'phy0' for the first WiFi radio. @@ -171,9 +183,11 @@ submodule infix-if-wifi { - Station mode: Connect to an existing WiFi network - Access Point mode: Create a WiFi network for clients - Mesh Point mode: Create an 802.11s mesh link + - WDS link: Bridge port for one 4-address station on a local + access point Note: A radio can host either: - - Multiple AP interfaces (multi-SSID), OR + - Multiple AP interfaces (multi-SSID), with WDS links, OR - A single Station interface, OR - A single Mesh Point interface @@ -193,6 +207,28 @@ submodule infix-if-wifi { Example use case: Connect to upstream WiFi network."; + leaf wds { + type boolean; + default false; + description + "4-address (WDS) mode. + + Lets the station forward frames for other devices, which + is what allows it to be a bridge port. Use it to bridge + the station with wired ports or a local access point, as + in a repeater. The access point must accept 4-address + stations, see the wds-link mode."; + } + + leaf peer-bssid { + type yang:mac-address; + description + "Only associate to the access point with this BSSID. + + Without it the station picks any access point advertising + the SSID."; + } + leaf ssid { type string { length "1..32"; @@ -811,6 +847,92 @@ submodule infix-if-wifi { } } } + + case wds-link { + container wds-link { + presence "Configure a 4-address WDS port of a local access point"; + + description + "Bridge port for one 4-address (WDS) station on an access + point of this device. + + The access point binds the station with the given MAC + address to this interface. Add the interface to the access + point's bridge and configure VLANs like for any other port; + one wds-link per repeater or remote bridge. The port is up + while the station is associated. + + The interface uses the access point's radio and MAC + address."; + + must "not(../../custom-phys-address/*)" { + error-message "A wds-link uses the MAC address of its access point"; + } + + must "not(../radio)" { + error-message "A wds-link uses the radio of its access point, do not set radio"; + } + + leaf access-point { + type leafref { + path "/if:interfaces/if:interface/if:name"; + } + mandatory true; + must "/if:interfaces/if:interface[if:name = current()]/wifi/access-point" { + error-message "wds-link must reference an access point interface on this device"; + } + description + "Access point interface this port belongs to."; + } + + leaf peer-address { + type yang:mac-address; + mandatory true; + must "count(/if:interfaces/if:interface[wifi/wds-link/access-point = current()/../access-point][wifi/wds-link/peer-address = current()]) = 1" { + error-message "Only one wds-link per station on an access point"; + } + description + "MAC address of the 4-address station bound to this port."; + } + + /* Operational state */ + + leaf connected { + config false; + type boolean; + description + "True while the station is associated and bound to this + port."; + } + + leaf signal-strength { + config false; + type int16; + units "dBm"; + description + "Signal strength of the station in dBm. Only present + while connected."; + } + + leaf rx-speed { + config false; + type uint32; + units "100 kbps"; + description + "Last received data rate from the station in 100 kbps. + Only present while connected."; + } + + leaf tx-speed { + config false; + type uint32; + units "100 kbps"; + description + "Last transmitted data rate to the station in 100 kbps. + Only present while connected."; + } + } + } } } } diff --git a/src/confd/yang/confd/infix-if-wifi@2026-09-24.yang b/src/confd/yang/confd/infix-if-wifi@2026-10-02.yang similarity index 100% rename from src/confd/yang/confd/infix-if-wifi@2026-09-24.yang rename to src/confd/yang/confd/infix-if-wifi@2026-10-02.yang diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index ad661b556..d4eff3fde 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -1457,6 +1457,16 @@ def pr_proto_wifi(self, pipe=''): peers_data = mesh.get("peers", {}) peers = peers_data.get("peer", []) data_str = f"{mode}, mesh-id: {mesh_id}, peers: {len(peers)}" + elif "wds-link" in self.wifi: + wds = self.wifi["wds-link"] + mode = "WDS" + signal = wds.get("signal-strength") + if wds.get("connected") and signal is not None: + data_str = f"{mode}, connected, signal: {signal_to_status(signal)}" + elif wds.get("connected"): + data_str = f"{mode}, connected" + else: + data_str = f"{mode}, not connected" else: station=self.wifi.get("station", {}) ssid = station.get("ssid", "------") @@ -1764,6 +1774,19 @@ def _addr_lines(addrs): print(f"{'mesh-id':<{19}}: {mesh_id}") print(f"{'connected peers':<{19}}: {len(peers)}") self.pr_wifi_peers() + elif "wds-link" in self.wifi: + wds = self.wifi['wds-link'] + signal = wds.get('signal-strength') + print(f"{'mode':<{19}}: wds-link") + print(f"{'connected':<{19}}: {'yes' if wds.get('connected') else 'no'}") + if signal is not None: + print(f"{'signal':<{19}}: {signal} dBm ({signal_to_status(signal)})") + rx_speed = wds.get('rx-speed') + tx_speed = wds.get('tx-speed') + if rx_speed is not None: + print(f"{'rx bitrate':<{19}}: {rx_speed / 10:.1f} Mbps") + if tx_speed is not None: + print(f"{'tx bitrate':<{19}}: {tx_speed / 10:.1f} Mbps") else: mode = "station" station = self.wifi.get('station', {}) diff --git a/src/statd/python/yanger/ietf_interfaces/link.py b/src/statd/python/yanger/ietf_interfaces/link.py index f5ef6a7ca..6c88da12b 100644 --- a/src/statd/python/yanger/ietf_interfaces/link.py +++ b/src/statd/python/yanger/ietf_interfaces/link.py @@ -111,6 +111,13 @@ def iplink2yang_operstate(iplink): "LOWERLAYERDOWN": "lower-layer-down", "NOTPRESENT": "not-present" } + if iplink["operstate"] == "UNKNOWN": + # Interfaces without carrier handling (AP_VLAN, tunnels) stay + # in UNKNOWN; the link flags tell the real state. + flags = iplink.get("flags", []) + if "UP" not in flags: + return "down" + return "up" if "LOWER_UP" in flags else "lower-layer-down" return xlate.get(iplink["operstate"], "unknown") diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index 06617f4bb..7dc0f69d3 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -28,6 +28,16 @@ def get_iw_stations(ifname): return [] +def get_iw_wds_ports(ifname): + """Get the WDS ports of an AP via iw.py""" + try: + data = HOST.run(('/usr/libexec/infix/iw.py', 'wds', ifname), default='[]') + return json.loads(data) + except Exception: + pass + return [] + + def get_iw_mesh_param(ifname): """Get mesh parameters via iw.py (mesh point mode)""" try: @@ -60,8 +70,11 @@ def wifi_ap(ifname): if info.get('ssid'): ap_data['ssid'] = info['ssid'] - # Get connected stations + # Connected stations, including the 4-address ones the kernel lists + # under the AP's WDS ports rather than under the AP itself stations = get_iw_stations(ifname) + for port in get_iw_wds_ports(ifname): + stations += get_iw_stations(port) if stations: ap_data['stations'] = {'station': stations} @@ -153,6 +166,25 @@ def wifi_station(ifname): return {'station': station_data} if station_data else {} +def wifi_wds(ifname): + """Operational data for a wds-link port (AP_VLAN). + + The station bound to the port is the only entry in its station dump, + so that is both the connected flag and the link quality. + """ + stations = get_iw_stations(ifname) + if not stations: + return {'wds-link': {'connected': False}} + + sta = stations[0] + data = {'connected': True} + for key in ('signal-strength', 'rx-speed', 'tx-speed'): + if sta.get(key) is not None: + data[key] = sta[key] + + return {'wds-link': data} + + def wifi(ifname): """Main entry point - detect mode and return appropriate data""" info = get_iw_info(ifname) @@ -165,6 +197,8 @@ def wifi(ifname): if mode == 'ap': result.update(wifi_ap(ifname)) + elif mode == 'ap/vlan': + result.update(wifi_wds(ifname)) elif mode == 'mesh point': result.update(wifi_mesh(ifname, info)) else: diff --git a/src/webui/internal/handlers/interfaces.go b/src/webui/internal/handlers/interfaces.go index c1dc9ea4e..1acfd4042 100644 --- a/src/webui/internal/handlers/interfaces.go +++ b/src/webui/internal/handlers/interfaces.go @@ -96,6 +96,18 @@ type wifiJSON struct { AccessPoint *wifiAPJSON `json:"access-point"` Station *wifiStationJSON `json:"station"` MeshPoint *wifiMeshJSON `json:"mesh-point"` + WDSLink *wifiWDSJSON `json:"wds-link"` +} + +// wifiWDSJSON mirrors the wds-link container: a 4-address station bound +// to this port of a local access point. +type wifiWDSJSON struct { + AccessPoint string `json:"access-point"` + PeerAddress string `json:"peer-address"` + Connected *bool `json:"connected"` + SignalStrength *int `json:"signal-strength"` + RxSpeed int `json:"rx-speed"` + TxSpeed int `json:"tx-speed"` } type wifiAPJSON struct { @@ -565,6 +577,12 @@ func makeIfaceEntry(iface ifaceJSON, fwdSet map[string]bool) ifaceEntry { } else if mp := iface.WiFi.MeshPoint; mp != nil { n := len(mp.Peers.Peer) e.Detail = fmt.Sprintf("Mesh, mesh-id: %s, peers: %d", mp.MeshID, n) + } else if wds := iface.WiFi.WDSLink; wds != nil { + if wds.Connected != nil && *wds.Connected { + e.Detail = "WDS, connected" + } else { + e.Detail = "WDS, not connected" + } } } @@ -771,6 +789,17 @@ func buildDetailData(r *http.Request, iface *ifaceJSON) ifaceDetailData { for _, p := range mp.Peers.Peer { d.WiFiStations = append(d.WiFiStations, buildWifiStaEntry(p)) } + } else if wds := iface.WiFi.WDSLink; wds != nil { + d.WiFiMode = "WDS Link" + if wds.SignalStrength != nil { + d.WiFiSignal = fmt.Sprintf("%d dBm", *wds.SignalStrength) + } + if wds.RxSpeed > 0 { + d.WiFiRxSpeed = fmt.Sprintf("%.1f Mbps", float64(wds.RxSpeed)/10) + } + if wds.TxSpeed > 0 { + d.WiFiTxSpeed = fmt.Sprintf("%.1f Mbps", float64(wds.TxSpeed)/10) + } } else if st := iface.WiFi.Station; st != nil { d.WiFiMode = "Station" d.WiFiSSID = st.SSID diff --git a/test/case/interfaces/wifi.yaml b/test/case/interfaces/wifi.yaml index 974e35e2b..0e5c28195 100644 --- a/test/case/interfaces/wifi.yaml +++ b/test/case/interfaces/wifi.yaml @@ -10,3 +10,12 @@ - name: WiFi Band Steering across a dual-band Access Point case: wifi_band_steering/test.py + +- name: WiFi 4-address (WDS) link between two bridges + case: wifi_wds_link_2dut/test.py + +- name: WiFi repeater with two SSIDs in VLANs over a 4-address (WDS) backhaul + case: wifi_wds_repeater/test.py + +- name: WiFi station set up from a scan-only interface + case: wifi_station_from_scan/test.py diff --git a/test/case/interfaces/wifi_station_from_scan/Readme.adoc b/test/case/interfaces/wifi_station_from_scan/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/interfaces/wifi_station_from_scan/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/interfaces/wifi_station_from_scan/test.adoc b/test/case/interfaces/wifi_station_from_scan/test.adoc new file mode 100644 index 000000000..3eb98e127 --- /dev/null +++ b/test/case/interfaces/wifi_station_from_scan/test.adoc @@ -0,0 +1,32 @@ +=== WiFi station set up from a scan-only interface + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_station_from_scan] + +==== Description + +A WiFi interface with only a radio, no station or access point, is in +scan-only mode. That is how the factory configuration of boards with a +built-in radio ships, so the usual way to get online is to add the station +settings to that existing interface. The connection has to come up from +that change alone, without a reboot or a service restart. + +Topology: +.... + host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host +.... + +==== Topology + +image::topology.svg[WiFi station set up from a scan-only interface topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to the ap and the station +. Configure the ap with access point 'infix-scan' and a DHCP server on 192.168.21.1 +. Configure wifi0 on the station with only radio0, scan-only mode +. Verify the station sees 'infix-scan' in its scan results +. Add station settings for 'infix-scan' to wifi0 on the station +. Verify the station associates to 'infix-scan' without a restart +. Verify the station leases 192.168.21.100 over wifi + + diff --git a/test/case/interfaces/wifi_station_from_scan/test.py b/test/case/interfaces/wifi_station_from_scan/test.py new file mode 100755 index 000000000..ab4ba6fe5 --- /dev/null +++ b/test/case/interfaces/wifi_station_from_scan/test.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +r""" +WiFi station set up from a scan-only interface + +A WiFi interface with only a radio, no station or access point, is in +scan-only mode. That is how the factory configuration of boards with a +built-in radio ships, so the usual way to get online is to add the station +settings to that existing interface. The connection has to come up from +that change alone, without a reboot or a service restart. + +Topology: +.... + host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host +.... +""" +import infamy +import infamy.iface as iface +import infamy.wifi as wifi +from infamy.util import until, parallel + +SSID = "infix-scan" +PSK = "infixinfix" + +SUBNET = "192.168.21.0/24" +AP_IP = "192.168.21.1" +LEASE = "192.168.21.100" + + +with infamy.Test() as test: + with test.step("Set up topology and attach to the ap and the station"): + env = infamy.Env() + ap, station = parallel( + lambda: env.attach("ap", "mgmt"), + lambda: env.attach("station", "mgmt"), + ) + wifi.skip_unless_supported(test, ap, station) + + with test.step("Configure the ap with access point 'infix-scan' and a DHCP server on 192.168.21.1"): + ap.put_config_dicts({ + "ietf-hardware": {"hardware": {"component": [ + wifi.radio("radio0", band="2.4GHz", channel=1)]}}, + "ietf-keystore": wifi.keystore({"wifi": PSK}), + "ietf-interfaces": {"interfaces": {"interface": [ + wifi.iface("wifi0", "02:00:00:00:00:01", { + "radio": "radio0", + "access-point": { + "ssid": SSID, + "security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"}, + }, + }, ipv4={"address": [{"ip": AP_IP, "prefix-length": 24}]}), + ]}}, + "infix-dhcp-server": {"dhcp-server": {"subnet": [{ + "subnet": SUBNET, + "pool": {"start-address": LEASE, "end-address": LEASE}, + }]}}, + }) + + with test.step("Configure wifi0 on the station with only radio0, scan-only mode"): + station.put_config_dicts({ + "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}}, + "ietf-interfaces": {"interfaces": {"interface": [ + wifi.iface("wifi0", "02:00:00:00:00:02", {"radio": "radio0"}, + ipv4={"infix-dhcp-client:dhcp": {}}), + ]}}, + }) + + with test.step("Verify the station sees 'infix-scan' in its scan results"): + until(lambda: SSID in {n.get("ssid") for n in + wifi.station(station).get("scan-results") or []}, + attempts=60, interval=2) + + with test.step("Add station settings for 'infix-scan' to wifi0 on the station"): + station.put_config_dicts({ + "ietf-keystore": wifi.keystore({"wifi": PSK}), + "ietf-interfaces": {"interfaces": {"interface": [ + {"name": "wifi0", "infix-interfaces:wifi": { + "radio": "radio0", + "station": { + "ssid": SSID, + "security": {"mode": "auto", "secret": "wifi"}, + }, + }}, + ]}}, + }) + + with test.step("Verify the station associates to 'infix-scan' without a restart"): + until(lambda: wifi.associated(station, SSID), attempts=60, interval=2) + + with test.step("Verify the station leases 192.168.21.100 over wifi"): + until(lambda: iface.address_exist(station, "wifi0", LEASE), + attempts=60, interval=2) + + test.succeed() diff --git a/test/case/interfaces/wifi_station_from_scan/topology.dot b/test/case/interfaces/wifi_station_from_scan/topology.dot new file mode 100644 index 000000000..7a05357a1 --- /dev/null +++ b/test/case/interfaces/wifi_station_from_scan/topology.dot @@ -0,0 +1,44 @@ +graph "wifi-station-from-scan" { + layout="neato"; + overlap="false"; + esep="+40"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | mgmt2 }", + pos="0,0!", + requires="controller", + ]; + + ap [ + label="{ mgmt | wifi } | ap", + pos="6,2!", + requires="infix", + ]; + + station [ + label="{ mgmt | wifi } | station", + pos="6,-2!", + requires="infix", + ]; + + // The wireless cell the ap and the station share, modelled as a medium + // node both join (one radio each, same index -> same cell): + // * physical: maps onto a real over-the-air RF cell; + // * virtual (qeneth): maps onto one of the multicast cells over which + // the wifimedium relay bridges mac80211_hwsim frames. + // The mapper guarantees the two DUTs actually share this medium. + cell [ + label="cell", + pos="9,0!", + requires="wifi-radio", + ]; + + host:mgmt1 -- ap:mgmt [requires="mgmt", color="lightgray"] + host:mgmt2 -- station:mgmt [requires="mgmt", color="lightgray"] + + ap:wifi -- cell [requires="wifi2.4GHz", style="dashed"] + station:wifi -- cell [requires="wifi2.4GHz", style="dashed"] +} diff --git a/test/case/interfaces/wifi_station_from_scan/topology.svg b/test/case/interfaces/wifi_station_from_scan/topology.svg new file mode 100644 index 000000000..9c8741aff --- /dev/null +++ b/test/case/interfaces/wifi_station_from_scan/topology.svg @@ -0,0 +1,68 @@ + + + + + + +wifi-station-from-scan + + + +host + +host + +mgmt1 + +mgmt2 + + + +ap + +mgmt + +wifi + +ap + + + +host:mgmt1--ap:mgmt + + + + +station + +mgmt + +wifi + +station + + + +host:mgmt2--station:mgmt + + + + +cell + +cell + + + +ap:wifi--cell + + + + +station:wifi--cell + + + + diff --git a/test/case/interfaces/wifi_wds_link_2dut/Readme.adoc b/test/case/interfaces/wifi_wds_link_2dut/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/interfaces/wifi_wds_link_2dut/test.adoc b/test/case/interfaces/wifi_wds_link_2dut/test.adoc new file mode 100644 index 000000000..fbab6130a --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/test.adoc @@ -0,0 +1,45 @@ +=== WiFi 4-address (WDS) link between two bridges + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_wds_link_2dut] + +==== Description + +Two DUTs: the root runs an access point with a WDS port, the satellite a +4-address station. Both ends are bridge ports, so the radio link joins +the two bridges at layer 2. + +On the root, wds0 is created by configuration before any station shows up +and gets its VLAN membership like any other bridge port. The access point +binds the station with the configured MAC address to it: the port comes up +when the station associates and goes down, but stays, when it leaves. On +the satellite, the station is a bridge port, which needs 4-address mode. + +The DHCP lease over the link is the data-plane check: the request and the +reply cross both bridges and the radio in opposite directions. + +Topology: +.... + host ==(mgmt)== root ))) ~ cell ~ ((( satellite ==(mgmt)== host + br0/vlan10 -- wds0 ~~~~~~~~~~~~~~~~~~~~~~ wifi0 -- br0 +.... + +==== Topology + +image::topology.svg[WiFi 4-address (WDS) link between two bridges topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to the root and the satellite +. Configure the root with access point 'infix-wds' and WDS port wds0 untagged in VLAN 10 on br0 +. Verify wds0 on the root exists, is down and is an untagged member of VLAN 10 +. Configure the satellite with a 4-address station for 'infix-wds' in br0, br0 as DHCP client +. Verify the satellite's wifi0 associates to 'infix-wds' +. Verify wds0 on the root comes up and reports the station connected +. Verify access point wifi0 on the root lists the satellite 02:00:00:00:00:02 as a station +. Verify the satellite leases 192.168.20.100 on br0 over the WDS link +. Disable wifi0 on the satellite +. Verify wds0 on the root goes down but remains a member of VLAN 10 +. Enable wifi0 on the satellite again +. Verify wds0 on the root comes up again + + diff --git a/test/case/interfaces/wifi_wds_link_2dut/test.py b/test/case/interfaces/wifi_wds_link_2dut/test.py new file mode 100755 index 000000000..caee52810 --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/test.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +r""" +WiFi 4-address (WDS) link between two bridges + +Two DUTs: the root runs an access point with a WDS port, the satellite a +4-address station. Both ends are bridge ports, so the radio link joins +the two bridges at layer 2. + +On the root, wds0 is created by configuration before any station shows up +and gets its VLAN membership like any other bridge port. The access point +binds the station with the configured MAC address to it: the port comes up +when the station associates and goes down, but stays, when it leaves. On +the satellite, the station is a bridge port, which needs 4-address mode. + +The DHCP lease over the link is the data-plane check: the request and the +reply cross both bridges and the radio in opposite directions. + +Topology: +.... + host ==(mgmt)== root ))) ~ cell ~ ((( satellite ==(mgmt)== host + br0/vlan10 -- wds0 ~~~~~~~~~~~~~~~~~~~~~~ wifi0 -- br0 +.... +""" +import infamy +import infamy.iface as iface +import infamy.wifi as wifi +from infamy.util import until, parallel + +SSID = "infix-wds" +PSK = "infixinfix" + +ROOT_AP_MAC = "02:00:00:00:00:01" +SAT_MAC = "02:00:00:00:00:02" + +SUBNET = "192.168.20.0/24" +ROOT_IP = "192.168.20.1" +LEASE = "192.168.20.100" + + +def root_config(): + return { + "ietf-hardware": {"hardware": {"component": [ + wifi.radio("radio0", band="2.4GHz", channel=1)]}}, + "ietf-keystore": wifi.keystore({"wifi": PSK}), + "ietf-interfaces": {"interfaces": {"interface": [ + { + "name": "br0", + "type": "infix-if-type:bridge", + "enabled": True, + "bridge": {"vlans": {"vlan": [ + {"vid": 10, "untagged": ["wds0"], "tagged": ["br0"]}, + ]}}, + }, + { + "name": "vlan10", + "type": "infix-if-type:vlan", + "enabled": True, + "vlan": {"id": 10, "lower-layer-if": "br0"}, + "ipv4": {"address": [{"ip": ROOT_IP, "prefix-length": 24}]}, + }, + wifi.iface("wifi0", ROOT_AP_MAC, { + "radio": "radio0", + "access-point": { + "ssid": SSID, + "security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"}, + }, + }), + wifi.wds_link("wds0", "wifi0", SAT_MAC, bridge="br0", pvid=10), + ]}}, + "infix-dhcp-server": {"dhcp-server": {"subnet": [{ + "subnet": SUBNET, + "pool": {"start-address": LEASE, "end-address": LEASE}, + }]}}, + } + + +def satellite_config(): + return { + "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}}, + "ietf-keystore": wifi.keystore({"wifi": PSK}), + "ietf-interfaces": {"interfaces": {"interface": [ + { + "name": "br0", + "type": "infix-if-type:bridge", + "enabled": True, + "ietf-ip:ipv4": {"infix-dhcp-client:dhcp": {}}, + }, + wifi.iface("wifi0", SAT_MAC, { + "radio": "radio0", + "station": { + "ssid": SSID, + "wds": True, + "peer-bssid": ROOT_AP_MAC, + "security": {"mode": "auto", "secret": "wifi"}, + }, + }, bridge="br0"), + ]}}, + } + + +def station_enabled(enabled): + return {"ietf-interfaces": {"interfaces": {"interface": [ + {"name": "wifi0", "enabled": enabled}]}}} + + +with infamy.Test() as test: + with test.step("Set up topology and attach to the root and the satellite"): + env = infamy.Env() + root, satellite = parallel( + lambda: env.attach("root", "mgmt"), + lambda: env.attach("satellite", "mgmt"), + ) + wifi.skip_unless_supported(test, root, satellite) + + with test.step("Configure the root with access point 'infix-wds' and WDS port wds0 untagged in VLAN 10 on br0"): + root.put_config_dicts(root_config()) + + with test.step("Verify wds0 on the root exists, is down and is an untagged member of VLAN 10"): + until(lambda: iface.exist(root, "wds0"), attempts=30) + until(lambda: "wds0" in wifi.bridge_vlan_members(root, "br0", 10), attempts=30) + if iface.is_oper_up(root, "wds0"): + test.fail() + + with test.step("Configure the satellite with a 4-address station for 'infix-wds' in br0, br0 as DHCP client"): + satellite.put_config_dicts(satellite_config()) + + with test.step("Verify the satellite's wifi0 associates to 'infix-wds'"): + until(lambda: wifi.associated(satellite, SSID), attempts=60, interval=2) + + with test.step("Verify wds0 on the root comes up and reports the station connected"): + until(lambda: iface.is_oper_up(root, "wds0"), attempts=30, interval=2) + until(lambda: wifi.wds_connected(root, "wds0"), attempts=30, interval=2) + + with test.step("Verify access point wifi0 on the root lists the satellite 02:00:00:00:00:02 as a station"): + until(lambda: SAT_MAC in wifi.ap_stations(root, "wifi0"), attempts=30, interval=2) + + with test.step("Verify the satellite leases 192.168.20.100 on br0 over the WDS link"): + until(lambda: iface.address_exist(satellite, "br0", LEASE), + attempts=60, interval=2) + + with test.step("Disable wifi0 on the satellite"): + satellite.put_config_dicts(station_enabled(False)) + + with test.step("Verify wds0 on the root goes down but remains a member of VLAN 10"): + until(lambda: not iface.is_oper_up(root, "wds0"), attempts=90, interval=2) + until(lambda: not wifi.wds_connected(root, "wds0"), attempts=30, interval=2) + if "wds0" not in wifi.bridge_vlan_members(root, "br0", 10): + test.fail() + + with test.step("Enable wifi0 on the satellite again"): + satellite.put_config_dicts(station_enabled(True)) + + with test.step("Verify wds0 on the root comes up again"): + until(lambda: iface.is_oper_up(root, "wds0"), attempts=60, interval=2) + until(lambda: wifi.wds_connected(root, "wds0"), attempts=30, interval=2) + + test.succeed() diff --git a/test/case/interfaces/wifi_wds_link_2dut/topology.dot b/test/case/interfaces/wifi_wds_link_2dut/topology.dot new file mode 100644 index 000000000..c626b88ae --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/topology.dot @@ -0,0 +1,40 @@ +graph "wifi-wds-link-2dut" { + layout="neato"; + overlap="false"; + esep="+40"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | mgmt2 }", + pos="0,0!", + requires="controller", + ]; + + root [ + label="{ mgmt | wifi } | root", + pos="6,2!", + requires="infix", + ]; + + satellite [ + label="{ mgmt | wifi } | satellite", + pos="6,-2!", + requires="infix", + ]; + + // The wireless cell the root and the satellite share, see + // wifi_ap_station_2dut for how it maps onto RF and onto hwsim. + cell [ + label="cell", + pos="9,0!", + requires="wifi-radio", + ]; + + host:mgmt1 -- root:mgmt [requires="mgmt", color="lightgray"] + host:mgmt2 -- satellite:mgmt [requires="mgmt", color="lightgray"] + + root:wifi -- cell [requires="wifi2.4GHz", style="dashed"] + satellite:wifi -- cell [requires="wifi2.4GHz", style="dashed"] +} diff --git a/test/case/interfaces/wifi_wds_link_2dut/topology.svg b/test/case/interfaces/wifi_wds_link_2dut/topology.svg new file mode 100644 index 000000000..d17cd1db9 --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/topology.svg @@ -0,0 +1,68 @@ + + + + + + +wifi-wds-link-2dut + + + +host + +host + +mgmt1 + +mgmt2 + + + +root + +mgmt + +wifi + +root + + + +host:mgmt1--root:mgmt + + + + +satellite + +mgmt + +wifi + +satellite + + + +host:mgmt2--satellite:mgmt + + + + +cell + +cell + + + +root:wifi--cell + + + + +satellite:wifi--cell + + + + diff --git a/test/case/interfaces/wifi_wds_repeater/Readme.adoc b/test/case/interfaces/wifi_wds_repeater/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/interfaces/wifi_wds_repeater/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/interfaces/wifi_wds_repeater/test.adoc b/test/case/interfaces/wifi_wds_repeater/test.adoc new file mode 100644 index 000000000..166ee8b46 --- /dev/null +++ b/test/case/interfaces/wifi_wds_repeater/test.adoc @@ -0,0 +1,54 @@ +=== WiFi repeater with two SSIDs in VLANs over a 4-address (WDS) backhaul + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_wds_repeater] + +==== Description + +Four DUTs. The root runs the backhaul access point 'infix-backhaul' and +a VLAN filtering bridge: the WDS port and the wired uplink carry VLAN 10 +and VLAN 20 tagged, and the root serves DHCP in each VLAN. The repeater +has a 4-address station on the backhaul, carrying both VLANs tagged, and +two access points on the same radio as access ports: 'infix-home' +untagged in VLAN 10 and 'infix-guest' untagged in VLAN 20. Two plain +stations join them, one per SSID. + +Each station must lease an address from the DHCP server of its own VLAN +on the root. That proves both that the 4-address backhaul carries the +stations' own MAC addresses and that the VLAN tags survive the trip. +The host behind the root reaches both stations on their VLANs. + +Taking the backhaul down and up again shows it is a transparent bridge +port: the stations lose and regain reach without re-associating. + +Topology: +.... + host ==(lan, VLAN 10+20)== root (AP 'infix-backhaul') + wds0 ))) ~ cell ~ ((( wifi0 repeater wifi1 (AP 'infix-home', VLAN 10) ))) home + wifi2 (AP 'infix-guest', VLAN 20) ))) guest +.... + +==== Topology + +image::topology.svg[WiFi repeater with two SSIDs in VLANs over a 4-address (WDS) backhaul topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to the root, the repeater, home and guest +. Configure the root with access point 'infix-backhaul', WDS port wds0 and the uplink tagged in VLAN 10 and 20, DHCP in each VLAN +. Configure the repeater with a 4-address station tagged in VLAN 10 and 20, access point 'infix-home' untagged in VLAN 10 and 'infix-guest' untagged in VLAN 20 +. Configure home as a DHCP station for 'infix-home' and guest as a DHCP station for 'infix-guest' +. Verify the repeater's wifi0 associates to 'infix-backhaul' +. Verify wds0 on the root is up +. Verify home is on the repeater's 'infix-home' access point, BSSID 02:00:00:00:0a:02 +. Verify guest is on the repeater's 'infix-guest' access point, BSSID 02:00:00:00:0b:02 +. Verify home leases 10.10.0.9 from the root's VLAN 10 DHCP server through the backhaul +. Verify guest leases 10.20.0.9 from the root's VLAN 20 DHCP server through the backhaul +. Verify the host reaches home at 10.10.0.9 on VLAN 10 through the repeater +. Verify the host reaches guest at 10.20.0.9 on VLAN 20 through the repeater +. Disable the repeater's backhaul station wifi0 +. Verify home at 10.10.0.9 and guest at 10.20.0.9 are no longer reachable from the host +. Enable the repeater's backhaul station wifi0 again +. Verify the host reaches home at 10.10.0.9 and guest at 10.20.0.9 again +. Verify home and guest are still on their access points + + diff --git a/test/case/interfaces/wifi_wds_repeater/test.py b/test/case/interfaces/wifi_wds_repeater/test.py new file mode 100755 index 000000000..68311ade1 --- /dev/null +++ b/test/case/interfaces/wifi_wds_repeater/test.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +r""" +WiFi repeater with two SSIDs in VLANs over a 4-address (WDS) backhaul + +Four DUTs. The root runs the backhaul access point 'infix-backhaul' and +a VLAN filtering bridge: the WDS port and the wired uplink carry VLAN 10 +and VLAN 20 tagged, and the root serves DHCP in each VLAN. The repeater +has a 4-address station on the backhaul, carrying both VLANs tagged, and +two access points on the same radio as access ports: 'infix-home' +untagged in VLAN 10 and 'infix-guest' untagged in VLAN 20. Two plain +stations join them, one per SSID. + +Each station must lease an address from the DHCP server of its own VLAN +on the root. That proves both that the 4-address backhaul carries the +stations' own MAC addresses and that the VLAN tags survive the trip. +The host behind the root reaches both stations on their VLANs. + +Taking the backhaul down and up again shows it is a transparent bridge +port: the stations lose and regain reach without re-associating. + +Topology: +.... + host ==(lan, VLAN 10+20)== root (AP 'infix-backhaul') + wds0 ))) ~ cell ~ ((( wifi0 repeater wifi1 (AP 'infix-home', VLAN 10) ))) home + wifi2 (AP 'infix-guest', VLAN 20) ))) guest +.... +""" +import infamy +import infamy.iface as iface +import infamy.wifi as wifi +from infamy.util import until, parallel + +BACKHAUL_SSID = "infix-backhaul" +HOME_SSID = "infix-home" +GUEST_SSID = "infix-guest" +PSK = "infixinfix" + +ROOT_AP_MAC = "02:00:00:00:00:01" +REPEATER_STA_MAC = "02:00:00:00:00:02" +HOME_AP_MAC = "02:00:00:00:0a:02" +GUEST_AP_MAC = "02:00:00:00:0b:02" +HOME_MAC = "02:00:00:00:00:09" +GUEST_MAC = "02:00:00:00:00:0a" + +HOME_HOST_IP = "10.10.0.1" +HOME_ROOT_IP = "10.10.0.2" +HOME_IP = "10.10.0.9" +GUEST_HOST_IP = "10.20.0.1" +GUEST_ROOT_IP = "10.20.0.2" +GUEST_IP = "10.20.0.9" + +SECRETS = {"backhaul": PSK, "home": PSK, "guest": PSK} + + +def root_config(uplink): + return { + "ietf-hardware": {"hardware": {"component": [ + wifi.radio("radio0", band="2.4GHz", channel=1)]}}, + "ietf-keystore": wifi.keystore(SECRETS), + "ietf-interfaces": {"interfaces": {"interface": [ + {"name": "br0", "type": "infix-if-type:bridge", "enabled": True, + "bridge": {"vlans": {"vlan": [ + {"vid": 10, "tagged": [uplink, "wds0", "br0"]}, + {"vid": 20, "tagged": [uplink, "wds0", "br0"]}, + ]}}}, + {"name": "vlan10", "type": "infix-if-type:vlan", "enabled": True, + "vlan": {"id": 10, "lower-layer-if": "br0"}, + "ipv4": {"address": [{"ip": HOME_ROOT_IP, "prefix-length": 24}]}}, + {"name": "vlan20", "type": "infix-if-type:vlan", "enabled": True, + "vlan": {"id": 20, "lower-layer-if": "br0"}, + "ipv4": {"address": [{"ip": GUEST_ROOT_IP, "prefix-length": 24}]}}, + {"name": uplink, "enabled": True, + "infix-interfaces:bridge-port": {"bridge": "br0"}}, + wifi.iface("wifi0", ROOT_AP_MAC, { + "radio": "radio0", + "access-point": { + "ssid": BACKHAUL_SSID, + "security": {"mode": "wpa2-wpa3-personal", "secret": "backhaul"}, + }, + }), + wifi.wds_link("wds0", "wifi0", REPEATER_STA_MAC, bridge="br0"), + ]}}, + "infix-dhcp-server": {"dhcp-server": {"subnet": [ + {"subnet": "10.10.0.0/24", + "pool": {"start-address": "10.10.0.100", "end-address": "10.10.0.100"}, + "host": [{"address": HOME_IP, "match": {"mac-address": HOME_MAC}}]}, + {"subnet": "10.20.0.0/24", + "pool": {"start-address": "10.20.0.100", "end-address": "10.20.0.100"}, + "host": [{"address": GUEST_IP, "match": {"mac-address": GUEST_MAC}}]}, + ]}}, + } + + +def repeater_config(): + return { + "ietf-hardware": {"hardware": {"component": [ + wifi.radio("radio0", band="2.4GHz", channel=1)]}}, + "ietf-keystore": wifi.keystore(SECRETS), + "ietf-interfaces": {"interfaces": {"interface": [ + {"name": "br0", "type": "infix-if-type:bridge", "enabled": True, + "bridge": {"vlans": {"vlan": [ + {"vid": 10, "untagged": ["wifi1"], "tagged": ["wifi0"]}, + {"vid": 20, "untagged": ["wifi2"], "tagged": ["wifi0"]}, + ]}}}, + wifi.iface("wifi0", REPEATER_STA_MAC, { + "radio": "radio0", + "station": { + "ssid": BACKHAUL_SSID, + "wds": True, + "peer-bssid": ROOT_AP_MAC, + "security": {"mode": "auto", "secret": "backhaul"}, + }, + }, bridge="br0"), + wifi.iface("wifi1", HOME_AP_MAC, { + "radio": "radio0", + "access-point": { + "ssid": HOME_SSID, + "security": {"mode": "wpa2-wpa3-personal", "secret": "home"}, + }, + }, bridge="br0", pvid=10), + wifi.iface("wifi2", GUEST_AP_MAC, { + "radio": "radio0", + "access-point": { + "ssid": GUEST_SSID, + "security": {"mode": "wpa2-wpa3-personal", "secret": "guest"}, + }, + }, bridge="br0", pvid=20), + ]}}, + } + + +def station_config(mac, ssid, secret): + return { + "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}}, + "ietf-keystore": wifi.keystore(SECRETS), + "ietf-interfaces": {"interfaces": {"interface": [ + wifi.iface("wifi0", mac, { + "radio": "radio0", + "station": { + "ssid": ssid, + "security": {"mode": "auto", "secret": secret}, + }, + }, ipv4={"infix-dhcp-client:dhcp": {}}), + ]}}, + } + + +def backhaul_enabled(enabled): + return {"ietf-interfaces": {"interfaces": {"interface": [ + {"name": "wifi0", "enabled": enabled}]}}} + + +def reaches(ns, addr): + try: + ns.ping(addr) + return True + except Exception: + return False + + +with infamy.Test() as test: + with test.step("Set up topology and attach to the root, the repeater, home and guest"): + env = infamy.Env() + root, repeater, home, guest = parallel( + lambda: env.attach("root", "mgmt"), + lambda: env.attach("repeater", "mgmt"), + lambda: env.attach("home", "mgmt"), + lambda: env.attach("guest", "mgmt"), + ) + wifi.skip_unless_supported(test, root, repeater, home, guest) + + with test.step("Configure the root with access point 'infix-backhaul', WDS port wds0 and the uplink tagged in VLAN 10 and 20, DHCP in each VLAN"): + _, uplink = env.ltop.xlate("root", "uplink") + root.put_config_dicts(root_config(uplink)) + + with test.step("Configure the repeater with a 4-address station tagged in VLAN 10 and 20, access point 'infix-home' untagged in VLAN 10 and 'infix-guest' untagged in VLAN 20"): + repeater.put_config_dicts(repeater_config()) + + with test.step("Configure home as a DHCP station for 'infix-home' and guest as a DHCP station for 'infix-guest'"): + parallel( + lambda: home.put_config_dicts(station_config(HOME_MAC, HOME_SSID, "home")), + lambda: guest.put_config_dicts(station_config(GUEST_MAC, GUEST_SSID, "guest")), + ) + + with test.step("Verify the repeater's wifi0 associates to 'infix-backhaul'"): + until(lambda: wifi.associated(repeater, BACKHAUL_SSID), attempts=60, interval=2) + + with test.step("Verify wds0 on the root is up"): + until(lambda: iface.is_oper_up(root, "wds0"), attempts=30, interval=2) + + with test.step("Verify home is on the repeater's 'infix-home' access point, BSSID 02:00:00:00:0a:02"): + until(lambda: wifi.station_bssid(home) == HOME_AP_MAC, attempts=60, interval=2) + + with test.step("Verify guest is on the repeater's 'infix-guest' access point, BSSID 02:00:00:00:0b:02"): + until(lambda: wifi.station_bssid(guest) == GUEST_AP_MAC, attempts=60, interval=2) + + with test.step("Verify home leases 10.10.0.9 from the root's VLAN 10 DHCP server through the backhaul"): + until(lambda: iface.address_exist(home, "wifi0", HOME_IP), attempts=60, interval=2) + + with test.step("Verify guest leases 10.20.0.9 from the root's VLAN 20 DHCP server through the backhaul"): + until(lambda: iface.address_exist(guest, "wifi0", GUEST_IP), attempts=60, interval=2) + + _, hlan = env.ltop.xlate("host", "lan") + with infamy.IsolatedMacVlan(hlan) as ns: + ns.runsh(f""" + set -ex + ip link add dev vlan10 link iface up type vlan id 10 + ip link add dev vlan20 link iface up type vlan id 20 + ip addr add {HOME_HOST_IP}/24 dev vlan10 + ip addr add {GUEST_HOST_IP}/24 dev vlan20 + """) + + with test.step("Verify the host reaches home at 10.10.0.9 on VLAN 10 through the repeater"): + until(lambda: reaches(ns, HOME_IP), attempts=30, interval=2) + + with test.step("Verify the host reaches guest at 10.20.0.9 on VLAN 20 through the repeater"): + until(lambda: reaches(ns, GUEST_IP), attempts=30, interval=2) + + with test.step("Disable the repeater's backhaul station wifi0"): + repeater.put_config_dicts(backhaul_enabled(False)) + + with test.step("Verify home at 10.10.0.9 and guest at 10.20.0.9 are no longer reachable from the host"): + until(lambda: not iface.is_oper_up(root, "wds0"), attempts=30, interval=2) + ns.must_not_reach(HOME_IP) + ns.must_not_reach(GUEST_IP) + + with test.step("Enable the repeater's backhaul station wifi0 again"): + repeater.put_config_dicts(backhaul_enabled(True)) + + with test.step("Verify the host reaches home at 10.10.0.9 and guest at 10.20.0.9 again"): + until(lambda: iface.is_oper_up(root, "wds0"), attempts=60, interval=2) + until(lambda: reaches(ns, HOME_IP), attempts=30, interval=2) + until(lambda: reaches(ns, GUEST_IP), attempts=30, interval=2) + + with test.step("Verify home and guest are still on their access points"): + if wifi.station_bssid(home) != HOME_AP_MAC or wifi.station_bssid(guest) != GUEST_AP_MAC: + test.fail() + + test.succeed() diff --git a/test/case/interfaces/wifi_wds_repeater/topology.dot b/test/case/interfaces/wifi_wds_repeater/topology.dot new file mode 100644 index 000000000..3d5489e30 --- /dev/null +++ b/test/case/interfaces/wifi_wds_repeater/topology.dot @@ -0,0 +1,59 @@ +graph "wifi-wds-repeater" { + layout="neato"; + overlap="false"; + esep="+40"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | mgmt2 | mgmt3 | mgmt4 | lan }", + pos="0,0!", + requires="controller", + ]; + + root [ + label="{ mgmt | uplink | wifi } | root", + pos="6,3!", + requires="infix", + ]; + + repeater [ + label="{ mgmt | wifi } | repeater", + pos="9,0!", + requires="infix", + ]; + + home [ + label="{ mgmt | wifi } | home", + pos="6,-3!", + requires="infix", + ]; + + guest [ + label="{ mgmt | wifi } | guest", + pos="12,-3!", + requires="infix", + ]; + + // One cell: the repeater's backhaul station and its two access points + // share a radio, so all four nodes hear each other. Each SSID is + // advertised by exactly one node, which decides who joins whom. + cell [ + label="cell", + pos="12,0!", + requires="wifi-radio", + ]; + + host:mgmt1 -- root:mgmt [requires="mgmt", color="lightgray"] + host:mgmt2 -- repeater:mgmt [requires="mgmt", color="lightgray"] + host:mgmt3 -- home:mgmt [requires="mgmt", color="lightgray"] + host:mgmt4 -- guest:mgmt [requires="mgmt", color="lightgray"] + + host:lan -- root:uplink [color="black"] + + root:wifi -- cell [requires="wifi2.4GHz", style="dashed"] + repeater:wifi -- cell [requires="wifi2.4GHz", style="dashed"] + home:wifi -- cell [requires="wifi2.4GHz", style="dashed"] + guest:wifi -- cell [requires="wifi2.4GHz", style="dashed"] +} diff --git a/test/case/interfaces/wifi_wds_repeater/topology.svg b/test/case/interfaces/wifi_wds_repeater/topology.svg new file mode 100644 index 000000000..5d0ca7f8a --- /dev/null +++ b/test/case/interfaces/wifi_wds_repeater/topology.svg @@ -0,0 +1,121 @@ + + + + + + +wifi-wds-repeater + + + +host + +host + +mgmt1 + +mgmt2 + +mgmt3 + +mgmt4 + +lan + + + +root + +mgmt + +uplink + +wifi + +root + + + +host:mgmt1--root:mgmt + + + + +host:lan--root:uplink + + + + +repeater + +mgmt + +wifi + +repeater + + + +host:mgmt2--repeater:mgmt + + + + +home + +mgmt + +wifi + +home + + + +host:mgmt3--home:mgmt + + + + +guest + +mgmt + +wifi + +guest + + + +host:mgmt4--guest:mgmt + + + + +cell + +cell + + + +root:wifi--cell + + + + +repeater:wifi--cell + + + + +home:wifi--cell + + + + +guest:wifi--cell + + + + diff --git a/test/infamy/wifi.py b/test/infamy/wifi.py index 942c838b0..92eb8473d 100644 --- a/test/infamy/wifi.py +++ b/test/infamy/wifi.py @@ -31,7 +31,7 @@ def keystore(secrets): ]}}} -def iface(name, mac, wifi, ipv4=None, bridge=None): +def iface(name, mac, wifi, ipv4=None, bridge=None, pvid=None): """ietf-interfaces entry for a WiFi VIF. wifi is the infix-interfaces:wifi container: the radio plus one of @@ -48,6 +48,29 @@ def iface(name, mac, wifi, ipv4=None, bridge=None): ifc["ietf-ip:ipv4"] = ipv4 if bridge: ifc["infix-interfaces:bridge-port"] = {"bridge": bridge} + if pvid is not None: + ifc["infix-interfaces:bridge-port"]["pvid"] = pvid + return ifc + + +def wds_link(name, ap, peer, bridge=None, pvid=None): + """ietf-interfaces entry for a wds-link port of access point ap. + + The port inherits the AP's radio and MAC address, so unlike iface() it + takes neither a radio nor a custom-phys-address. + """ + ifc = { + "name": name, + "type": "infix-if-type:wifi", + "enabled": True, + "infix-interfaces:wifi": { + "wds-link": {"access-point": ap, "peer-address": peer}, + }, + } + if bridge: + ifc["infix-interfaces:bridge-port"] = {"bridge": bridge} + if pvid is not None: + ifc["infix-interfaces:bridge-port"]["pvid"] = pvid return ifc @@ -84,6 +107,21 @@ def station_bssid(target, ifname="wifi0"): return (station(target, ifname).get("bssid") or "").lower() +def wds_connected(target, ifname): + """True once the station of the wds-link port ifname is bound to it.""" + return _wifi(target.get_iface(ifname)).get("wds-link", {}).get("connected") is True + + +def bridge_vlan_members(target, bridge, vid, tagging="untagged"): + """Ports listed as tagged/untagged members of vid on bridge, from operational.""" + ifc = target.get_iface(bridge) or {} + br = ifc.get("bridge") or ifc.get("infix-interfaces:bridge") or {} + for vlan in (br.get("vlans") or {}).get("vlan") or []: + if vlan.get("vid") == vid: + return set(vlan.get(tagging) or []) + return set() + + def ap_stations(target, ifname="wifi0"): """MACs of the stations currently associated to this AP BSS, lowercase.""" ap = _wifi(target.get_iface(ifname)).get("access-point") or {}