From 2e4d39d00bfa93f1255e3d7c15d95cc855724869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 10:24:42 +0200 Subject: [PATCH 01/45] gitignore: Ignore __pycache__ directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 40975fc54..8af2d4f84 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ AGENTS.md /test/.log /local.mk /test/spec/Readme.adoc +__pycache__/ From b3cafec7bb9b01809b10ac62af3b4322174e68b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 10:03:07 +0200 Subject: [PATCH 02/45] test: Regenerate the OSPF BFD specifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- test/case/routing/ospf_bfd/ospfv2.adoc | 6 +++--- test/case/routing/ospf_bfd/ospfv3.adoc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/case/routing/ospf_bfd/ospfv2.adoc b/test/case/routing/ospf_bfd/ospfv2.adoc index 78e548c64..a5e31f327 100644 --- a/test/case/routing/ospf_bfd/ospfv2.adoc +++ b/test/case/routing/ospf_bfd/ospfv2.adoc @@ -12,9 +12,9 @@ This can typically happen when one logical link, from OSPF's perspective, is made up of multiple physical links containing media converters without link fault forwarding. -Note: OSPFv3 next-hops are IPv6 link-local addresses, so the active path is -verified with traceroute rather than by matching a RIB next-hop, and its BFD -peers are only known by their session count. +Note: OSPFv3 next-hops and BFD peers are IPv6 link-local addresses, unknown in +advance, so both versions verify the active path with traceroute rather than by +matching a RIB next-hop, and count BFD sessions rather than name their peers. ==== Topology diff --git a/test/case/routing/ospf_bfd/ospfv3.adoc b/test/case/routing/ospf_bfd/ospfv3.adoc index 65aa96443..5fbe3a0de 100644 --- a/test/case/routing/ospf_bfd/ospfv3.adoc +++ b/test/case/routing/ospf_bfd/ospfv3.adoc @@ -12,9 +12,9 @@ This can typically happen when one logical link, from OSPF's perspective, is made up of multiple physical links containing media converters without link fault forwarding. -Note: OSPFv3 next-hops are IPv6 link-local addresses, so the active path is -verified with traceroute rather than by matching a RIB next-hop, and its BFD -peers are only known by their session count. +Note: OSPFv3 next-hops and BFD peers are IPv6 link-local addresses, unknown in +advance, so both versions verify the active path with traceroute rather than by +matching a RIB next-hop, and count BFD sessions rather than name their peers. ==== Topology From 0ce465b7aebee0cc85e7a792b900d8908ecd5dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:14 +0200 Subject: [PATCH 03/45] board: bpi-r3: Move ramdisk above the MT7986 WiFi reserved memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rootfs image loaded at 0x4A000000 covered the WiFi firmware and WED regions at 0x4fc00000, so the kernel could not reserve them. Signed-off-by: Mattias Walström --- board/aarch64/bananapi-bpi-r3/uboot/mt7986-env.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"; From f48f0558ad9a2203889bcfd7d9075943e86ece15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:25 +0200 Subject: [PATCH 04/45] confd: wifi: Create a hostapd control socket for every BSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hostapd only creates the socket for the BSS with a ctrl_interface line, so hostapd_cli could not reach the secondary SSIDs on a radio. Signed-off-by: Mattias Walström --- src/confd/src/hardware.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index d883b2c39..5d5c2bfec 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -361,6 +361,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 */ From e64f161e8c68d2f8e0eaa49839b88619de5f24d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:42 +0200 Subject: [PATCH 05/45] hostapd: Allow binding WDS stations to preconfigured interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds wds_sta_ifname= , so a 4-address station can be bound to a port that already exists and is bridged by someone else. Signed-off-by: Mattias Walström --- ...ding-WDS-stations-to-preconfigured-i.patch | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 patches/hostapd/0003-hostapd-Allow-binding-WDS-stations-to-preconfigured-i.patch 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..98650c753 --- /dev/null +++ b/patches/hostapd/0003-hostapd-Allow-binding-WDS-stations-to-preconfigured-i.patch @@ -0,0 +1,268 @@ +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. + +A name of IFNAMSIZ characters or a second line for the same address is +rejected, and a port the kernel refuses to bind the station to is set +down again. + +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,57 @@ + #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, *dup; ++ 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; ++ } ++ for (dup = bss->wds_sta_ifname; dup; dup = dup->next) { ++ if (os_memcmp(dup->addr, e->addr, ETH_ALEN) == 0) { ++ os_free(e); ++ wpa_printf(MSG_ERROR, ++ "Line %d: duplicate wds_sta_ifname for %s", ++ line, pos); ++ return 1; ++ } ++ } ++ 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 +2434,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) < +@@ -9360,9 +9373,22 @@ + bridge_ifname, name) < 0) + return -1; + +- return i802_set_sta_vlan(priv, addr, name, 0, +- NL80211_DRV_LINK_ID_NA); ++ ret = i802_set_sta_vlan(priv, addr, name, 0, ++ NL80211_DRV_LINK_ID_NA); ++ /* The kernel only binds a station to an AP_VLAN of this ++ * BSS, so a refused external interface is not ours to keep ++ * up. */ ++ if (ret < 0 && external) ++ linux_set_iface_flags(drv->global->ioctl_sock, name, 0); ++ return ret; + } 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) From b1b7e3248635f91660626a2a4d7befa9917025e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:42 +0200 Subject: [PATCH 06/45] confd: wifi: Add 4-address WDS link and station modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A wds-link interface is a bridge port of a local access point for one remote 4-address station, created as an AP_VLAN up front and bound by hostapd via wds_sta_ifname. A station with wds enabled may be a bridge port. Also judge hostapd config by the APs left in config on commit, since the changed interface on a radio need not be an AP anymore. Signed-off-by: Mattias Walström --- src/confd/src/core.c | 11 ++ src/confd/src/hardware.c | 73 ++++++--- src/confd/src/if-wifi.c | 76 ++++++++-- src/confd/src/interfaces.c | 16 +- src/confd/src/interfaces.h | 2 + src/confd/yang/confd/infix-if-bridge.yang | 10 +- ...9.yang => infix-if-bridge@2026-10-02.yang} | 0 src/confd/yang/confd/infix-if-wifi.yang | 139 +++++++++++++++++- ...-24.yang => infix-if-wifi@2026-10-02.yang} | 0 9 files changed, 284 insertions(+), 43 deletions(-) rename src/confd/yang/confd/{infix-if-bridge@2026-04-29.yang => infix-if-bridge@2026-10-02.yang} (100%) rename src/confd/yang/confd/{infix-if-wifi@2026-09-24.yang => infix-if-wifi@2026-10-02.yang} (100%) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 69433e568..aa26b1ac9 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -506,6 +506,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 5d5c2bfec..4398b4861 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) { @@ -506,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"); @@ -1254,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: @@ -1268,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; diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 8b9fca53a..5a43ed2ff 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,6 +160,12 @@ 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; + return 0; } @@ -164,6 +176,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 +193,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 +253,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 +499,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 +534,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 +550,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 +581,25 @@ 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); 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; diff --git a/src/confd/src/interfaces.c b/src/confd/src/interfaces.c index 4f085c2a3..a01856d55 100644 --- a/src/confd/src/interfaces.c +++ b/src/confd/src/interfaces.c @@ -665,9 +665,9 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net bool pppd_owned; /* link state and MTU */ const char *attr; int err = 0; + bool wds; FILE *ip; - err = netdag_gen_iface_timeout(net, ifname, iftype); if (err) goto err; @@ -729,7 +729,14 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net } pppd_owned = iftype_from_iface(cif) == IFT_PPPOE; - fprintf(ip, "link set dev %s%s", ifname, pppd_owned ? "" : " down"); + /* A wds-link port is raised by hostapd when its station associates + * and must not be bounced for a change of its settings, the + * station would not notice and the port would stay down. */ + wds = iftype_from_iface(cif) == IFT_WIFI && wifi_get_mode(cif) == wifi_wds; + if (pppd_owned || (wds && lydx_is_enabled(cif, "enabled"))) + fprintf(ip, "link set dev %s", ifname); + else + fprintf(ip, "link set dev %s down", ifname); /* Set generic link attributes */ err = err ? : netdag_gen_ipv4_autoconf(net, cif, dif); @@ -769,8 +776,8 @@ 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") && !pppd_owned) + /* Bring interface back up, if enabled, unless pppd or hostapd owns it */ + if (lydx_is_enabled(cif, "enabled") && !pppd_owned && !wds) fprintf(ip, "link set dev %s up state up\n", ifname); err = err ? : netdag_gen_sysctl(net, cif, dif); @@ -802,6 +809,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 aa7743f39..c9087d11e 100644 --- a/src/confd/src/interfaces.h +++ b/src/confd/src/interfaces.h @@ -133,10 +133,12 @@ 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); 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..fa255b618 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,97 @@ 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. + + Give the access point an SSID and secret of its own, for + the WDS links only: a station that knows them and uses + the MAC address below is bound to this port, with every + VLAN the port carries. + + 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 From 3459edd56821b78bc9da4aae743034b950b6bb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:42 +0200 Subject: [PATCH 07/45] statd: wifi: Report wds-link state in operational data and CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AP_VLAN ports never leave operstate UNKNOWN, so oper-status is now derived from the UP and LOWER_UP flags for such interfaces. Signed-off-by: Mattias Walström --- board/common/rootfs/usr/libexec/infix/iw.py | 29 +++++++++++++++ src/statd/python/cli_pretty/cli_pretty.py | 23 ++++++++++++ .../python/yanger/ietf_interfaces/link.py | 7 ++++ .../python/yanger/ietf_interfaces/wifi.py | 36 ++++++++++++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) 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/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 1cbcdbc3a..9d1635fef 100644 --- a/src/statd/python/yanger/ietf_interfaces/link.py +++ b/src/statd/python/yanger/ietf_interfaces/link.py @@ -114,6 +114,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: From 73dd986cdfa5017bf35f9b0789460c99ad45a822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:42 +0200 Subject: [PATCH 08/45] webui: Show wds-link WiFi interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- src/webui/internal/handlers/interfaces.go | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) 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 From d426daa5cb65a924722dcf860f60ccc5262a1a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:42 +0200 Subject: [PATCH 09/45] test: Add WiFi WDS link and repeater tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The virtual medium now only acknowledges unicast frames to peers heard within the last minute, so a vanished station is detected by the AP's inactivity probing like on real RF. Signed-off-by: Mattias Walström --- package/feature-wifi/wifimedium | 32 ++- test/case/interfaces/wifi.yaml | 6 + .../interfaces/wifi_wds_link_2dut/Readme.adoc | 1 + .../interfaces/wifi_wds_link_2dut/test.adoc | 47 ++++ .../interfaces/wifi_wds_link_2dut/test.py | 166 ++++++++++++ .../wifi_wds_link_2dut/topology.dot | 40 +++ .../wifi_wds_link_2dut/topology.svg | 68 +++++ .../interfaces/wifi_wds_repeater/Readme.adoc | 1 + .../interfaces/wifi_wds_repeater/test.adoc | 54 ++++ .../case/interfaces/wifi_wds_repeater/test.py | 239 ++++++++++++++++++ .../interfaces/wifi_wds_repeater/topology.dot | 59 +++++ .../interfaces/wifi_wds_repeater/topology.svg | 121 +++++++++ test/infamy/wifi.py | 40 ++- 13 files changed, 869 insertions(+), 5 deletions(-) create mode 120000 test/case/interfaces/wifi_wds_link_2dut/Readme.adoc create mode 100644 test/case/interfaces/wifi_wds_link_2dut/test.adoc create mode 100755 test/case/interfaces/wifi_wds_link_2dut/test.py create mode 100644 test/case/interfaces/wifi_wds_link_2dut/topology.dot create mode 100644 test/case/interfaces/wifi_wds_link_2dut/topology.svg create mode 120000 test/case/interfaces/wifi_wds_repeater/Readme.adoc create mode 100644 test/case/interfaces/wifi_wds_repeater/test.adoc create mode 100755 test/case/interfaces/wifi_wds_repeater/test.py create mode 100644 test/case/interfaces/wifi_wds_repeater/topology.dot create mode 100644 test/case/interfaces/wifi_wds_repeater/topology.svg diff --git a/package/feature-wifi/wifimedium b/package/feature-wifi/wifimedium index 58aeb4d84..25d07c686 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) @@ -251,6 +255,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. @@ -364,7 +387,7 @@ 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: @@ -389,6 +412,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/test/case/interfaces/wifi.yaml b/test/case/interfaces/wifi.yaml index 974e35e2b..245499dd8 100644 --- a/test/case/interfaces/wifi.yaml +++ b/test/case/interfaces/wifi.yaml @@ -10,3 +10,9 @@ - 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 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..9d2bfdf02 --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/test.adoc @@ -0,0 +1,47 @@ +=== 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 +. Set a description on wds0 on the root +. Verify wds0 on the root stays up with the station connected +. 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..42e578f23 --- /dev/null +++ b/test/case/interfaces/wifi_wds_link_2dut/test.py @@ -0,0 +1,166 @@ +#!/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("Set a description on wds0 on the root"): + root.put_config_dicts({"ietf-interfaces": {"interfaces": {"interface": [ + {"name": "wds0", "description": "satellite"}]}}}) + + with test.step("Verify wds0 on the root stays up with the station connected"): + until(lambda: iface.is_oper_up(root, "wds0") and wifi.wds_connected(root, "wds0"), + attempts=10, interval=1) + + 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 {} From e84779e076bd631da21b332076e9742845228f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:42 +0200 Subject: [PATCH 10/45] doc: Document WDS backhaul and repeaters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 5 ++ doc/wifi.md | 151 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 142 insertions(+), 14 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 3991d8829..2c1bdab79 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -9,6 +9,10 @@ All notable changes to the project are documented in this file. ### Changes - Upgrade Linux kernel to 6.18.56 (LTS) +- WiFi 4-address (WDS) links for wireless bridges and repeaters: a station + with `wds` enabled can be a bridge port, and an access point bridges + each remote station through a `wds-link` interface, see + [WDS Backhaul and Repeaters][wds] - Add IPv6 dynamic routing: RIPng and OSPFv3. Both reuse the existing ietf-rip and ietf-ospf models, selected per control-plane-protocol by the `ripng`/`ospfv3` type and the IPv6 address-family @@ -61,6 +65,7 @@ All notable changes to the project are documented in this file. like a DHCPv4 route. Before, the route preference setting was ignored [pppoe]: https://www.kernelkit.org/infix/latest/pppoe/ +[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..f40a500df 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,125 @@ 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-garage port for the garage satellite, bridge port + └── wds-attic port for the attic satellite, bridge port +``` + +A 4-address station forwards traffic for the devices behind it, so it +cannot share the access point's interface with the ordinary clients. +The `wds-link` is that separate port. Any access point can take WDS +stations, even one that serves clients, but 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-garage
+admin@root:/config/interface/wds-garage/> set type wifi
+admin@root:/config/interface/wds-garage/> set wifi wds-link access-point backhaul
+admin@root:/config/interface/wds-garage/> set wifi wds-link peer-address 02:13:37:13:37:12
+admin@root:/config/interface/wds-garage/> set bridge-port bridge br0
+admin@root:/config/interface/wds-garage/> 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-garage
+name               : wds-garage
+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@garage:/config/> edit interface uplink
+admin@garage:/config/interface/uplink/> set wifi radio radio1
+admin@garage:/config/interface/uplink/> set wifi station ssid my-backhaul
+admin@garage:/config/interface/uplink/> set wifi station wds true
+admin@garage:/config/interface/uplink/> set wifi station peer-bssid 02:13:37:13:37:01
+admin@garage:/config/interface/uplink/> set wifi station security secret backhaul-key
+admin@garage:/config/interface/uplink/> set bridge-port bridge br0
+admin@garage:/config/interface/uplink/> leave
+
+ +A station without `wds` cannot be a bridge port, the configuration is +rejected. + +The access point at the other end must accept 4-address stations. A +root with a `wds-link` for the satellite does, and so do most access +points with a WDS or 4-address option. One that does not still lets +the station connect and authenticate, then drops all its traffic. +Behind such an access point, use a plain station with an address of its +own and route or masquerade the network behind it instead, see +[Firewall](firewall.md). + +### 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. From 49079b3911baae95038d107e1f15c8388ed7ac4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sat, 3 Oct 2026 23:03:04 +0200 Subject: [PATCH 11/45] test: wifimedium: Raise the carrier MTU and drop oversized frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A full-size data frame with 802.11 headers and encryption exceeds the 1500 byte carrier MTU, and the send error took the whole relay down. Signed-off-by: Mattias Walström --- package/feature-wifi/wifimedium | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/package/feature-wifi/wifimedium b/package/feature-wifi/wifimedium index 25d07c686..55d335e88 100755 --- a/package/feature-wifi/wifimedium +++ b/package/feature-wifi/wifimedium @@ -236,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)) @@ -330,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) @@ -391,9 +396,13 @@ def main(): # 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) From 89b985e352b5f8b9c54bb4059a8fd26049bfa7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 13:44:21 +0200 Subject: [PATCH 12/45] confd: wifi: Detach a WiFi interface from hostapd before deleting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hostapd adopts a re-added netdev of a name it still holds and turns it back into an AP, which broke a station created right after an AP of the same name was removed. Run hostapd with a global control socket and tell it to drop the interface first. Signed-off-by: Mattias Walström --- src/confd/src/hardware.c | 2 +- src/confd/src/if-wifi.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 4398b4861..aa391b7b6 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -1466,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 5a43ed2ff..303b5d3ad 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -641,6 +641,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); From 556b93a50f03ccaf1c0aea7127ddfd41aad69c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 14:14:13 +0200 Subject: [PATCH 13/45] confd: wifi: Apply station and mesh changes on commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding station settings to a scan-only interface, or changing them, only rewrote the wpa_supplicant config; nothing told the daemon. Reload it on config changes and start it over when the netdev is recreated. Fix #1679 Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 2 + src/confd/src/if-wifi.c | 68 ++++++++++++++ src/confd/src/interfaces.c | 7 +- src/confd/src/interfaces.h | 2 + test/case/interfaces/wifi.yaml | 3 + .../wifi_station_from_scan/Readme.adoc | 1 + .../wifi_station_from_scan/test.adoc | 32 +++++++ .../interfaces/wifi_station_from_scan/test.py | 93 +++++++++++++++++++ .../wifi_station_from_scan/topology.dot | 44 +++++++++ .../wifi_station_from_scan/topology.svg | 68 ++++++++++++++ 10 files changed, 314 insertions(+), 6 deletions(-) create mode 120000 test/case/interfaces/wifi_station_from_scan/Readme.adoc create mode 100644 test/case/interfaces/wifi_station_from_scan/test.adoc create mode 100755 test/case/interfaces/wifi_station_from_scan/test.py create mode 100644 test/case/interfaces/wifi_station_from_scan/topology.dot create mode 100644 test/case/interfaces/wifi_station_from_scan/topology.svg diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 2c1bdab79..a7cb0b79e 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -63,6 +63,8 @@ All notable changes to the project are documented in this file. - Fix #1423: the default route from a DHCPv6 client, learned from router advertisements, is now a static route with the DHCPv6 route preference, like a DHCPv4 route. Before, the route preference setting was ignored +- Fix #1679: a WiFi station added to a scan-only interface, as in the + Raspberry Pi 4 factory configuration, did not connect until a reboot [pppoe]: https://www.kernelkit.org/infix/latest/pppoe/ [wds]: https://www.kernelkit.org/infix/latest/wifi/#wds-backhaul-and-repeaters diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 303b5d3ad..a6e9f1f86 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -166,9 +166,72 @@ int wifi_mode_changed(struct lyd_node *wifi) 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 */ @@ -589,6 +652,10 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) 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: { @@ -608,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); diff --git a/src/confd/src/interfaces.c b/src/confd/src/interfaces.c index a01856d55..f84ec4edf 100644 --- a/src/confd/src/interfaces.c +++ b/src/confd/src/interfaces.c @@ -457,12 +457,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_PPPOE: return ppp_gen(session, dif, cif, net); case IFT_DUMMY: diff --git a/src/confd/src/interfaces.h b/src/confd/src/interfaces.h index c9087d11e..fc8b0f4df 100644 --- a/src/confd/src/interfaces.h +++ b/src/confd/src/interfaces.h @@ -142,6 +142,8 @@ 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/test/case/interfaces/wifi.yaml b/test/case/interfaces/wifi.yaml index 245499dd8..0e5c28195 100644 --- a/test/case/interfaces/wifi.yaml +++ b/test/case/interfaces/wifi.yaml @@ -16,3 +16,6 @@ - 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 + + + + From 49775705ebe4d41357fd2522976ac1974ccd59d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 5 Oct 2026 16:02:12 +0200 Subject: [PATCH 14/45] confd: wifi: Collect the channel survey on request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The survey container under the radio only ever held the operating channel once the radio was connected or serving clients, the kernel has no data for channels it never visits, and every hardware GET paid for an iw call per radio to learn that. Replace it with a channel-survey action that scans all channels first. Going off channel pauses traffic for a few seconds, so this is something an operator asks for. The scan is triggered and then waited for over iw event, the combined iw scan spins on its netlink socket for minutes on the test kernel. Signed-off-by: Mattias Walström --- board/common/rootfs/usr/libexec/infix/iw.py | 181 ++++++++++++++++++ src/confd/src/core.c | 4 + src/confd/src/core.h | 9 + src/confd/src/hardware.c | 122 ++++++++++++ src/confd/src/system-software.c | 7 - src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-hardware.yang | 156 ++++++++------- ...04.yang => infix-hardware@2026-10-05.yang} | 0 src/statd/python/yanger/ietf_hardware.py | 47 ----- 9 files changed, 403 insertions(+), 125 deletions(-) rename src/confd/yang/confd/{infix-hardware@2026-10-04.yang => infix-hardware@2026-10-05.yang} (100%) diff --git a/board/common/rootfs/usr/libexec/infix/iw.py b/board/common/rootfs/usr/libexec/infix/iw.py index 727c3957a..b702357ea 100755 --- a/board/common/rootfs/usr/libexec/infix/iw.py +++ b/board/common/rootfs/usr/libexec/infix/iw.py @@ -7,12 +7,18 @@ iw.py dev - List all interfaces grouped by PHY iw.py info - Get PHY or interface information iw.py survey - Get channel survey data + iw.py survey-scan [passive] + - Scan all channels, then get survey data """ +import os import sys import json import subprocess import re +import secrets +import select +import time def decode_iw_ssid(ssid): """Decode iw escaped SSID (\\xHH) to UTF-8, stripping non-printable chars.""" try: @@ -22,6 +28,9 @@ def decode_iw_ssid(ssid): return ''.join(c for c in ssid if c.isprintable()) +SCAN_BUDGET = 40 # seconds for one channel survey, scan included + + def run_iw(*args): """Run iw command and return output""" try: @@ -486,6 +495,169 @@ def parse_dev(): return result +def parse_dev_types(): + """ + Parse 'iw dev' output + Returns: dict mapping PHY numbers to list of (interface, type) tuples + """ + output = run_iw('dev') + if not output: + return {} + + result = {} + current_phy = None + current_if = None + + for line in output.splitlines(): + stripped = line.strip() + if line.startswith('phy#'): + current_phy = line.replace('phy#', '').strip() + result.setdefault(current_phy, []) + current_if = None + elif current_phy and stripped.startswith('Interface '): + current_if = stripped.split(None, 1)[1] + result[current_phy].append([current_if, None]) + elif current_if and stripped.startswith('type '): + result[current_phy][-1][1] = stripped.split(None, 1)[1] + + return {phy: [tuple(e) for e in entries] for phy, entries in result.items()} + + +def wait_scan_done(events, ifname, deadline): + """ + Read 'iw event' output until the scan on ifname finishes or aborts. + Returns 'finished', 'aborted' or None at the deadline. + + The pipe is read unbuffered: a second line that arrives in the same + read would otherwise sit in a buffer that select() knows nothing of. + """ + fd = events.stdout.fileno() + buf = getattr(events, 'leftover', b'') + while True: + while b'\n' in buf: + line, buf = buf.split(b'\n', 1) + events.leftover = buf + line = line.decode(errors='replace') + if not line.startswith(f'{ifname} '): + continue + if 'scan finished' in line: + return 'finished' + if 'scan aborted' in line: + return 'aborted' + remaining = deadline - time.monotonic() + if remaining <= 0: + return None + ready, _, _ = select.select([fd], [], [], remaining) + if not ready: + return None + chunk = os.read(fd, 4096) + if not chunk: + return None + buf += chunk + + +def survey_scan(radio, passive=False): + """ + Scan every channel on the radio, then dump the survey. + + Returns the parse_survey() list, or {'error': ...}. The scan runs + on one interface of the radio, preferring a station or mesh point + over an access point since those can scan without extra flags. + + The scan is triggered and then waited for over 'iw event', the + combined 'iw scan' spins on the netlink socket for minutes on some + kernels. A trigger fails with EBUSY while wpa_supplicant runs its + own scan, so wait for that one to finish and try again. + """ + try: + with open(f'/sys/class/ieee80211/{radio}/index') as f: + phy = f.read().strip() + except OSError: + return {'error': f'no such radio: {radio}'} + + ifaces = parse_dev_types().get(phy, []) + order = ['managed', 'mesh point', 'AP'] + ifaces = sorted((i for i in ifaces if i[1] != 'AP/VLAN'), + key=lambda i: order.index(i[1]) if i[1] in order else len(order)) + + # A radio nobody has configured yet has no interface to scan with, + # which is exactly when a survey helps pick a band and channel. + # Borrow one for the duration of the scan, under a name no one + # would configure. + tmp = None + if not ifaces: + tmp = 'survey-' + secrets.token_hex(3) + try: + subprocess.run(['iw', 'phy', radio, 'interface', 'add', tmp, 'type', 'managed'], + capture_output=True, text=True, timeout=5, check=True) + subprocess.run(['ip', 'link', 'set', tmp, 'up'], + capture_output=True, text=True, timeout=5, check=True) + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: + subprocess.run(['iw', 'dev', tmp, 'del'], capture_output=True) + err = getattr(e, 'stderr', '') or '' + return {'error': f'no interface on {radio} to scan with: {err.strip() or e}'} + ifaces = [(tmp, 'managed')] + + try: + return survey_scan_on(ifaces[0], passive) + finally: + if tmp: + subprocess.run(['iw', 'dev', tmp, 'del'], capture_output=True) + + +def survey_scan_on(iface, passive): + """Scan on one (ifname, iftype), then dump the survey, see survey_scan().""" + ifname, iftype = iface + args = ['iw', 'dev', ifname, 'scan', 'trigger'] + if iftype == 'AP': + args.append('ap-force') + if passive: + args.append('passive') + + # The action runs on the configuration daemon's thread, which has a + # minute per request, so the whole scan gets well under that. + deadline = time.monotonic() + SCAN_BUDGET + events = subprocess.Popen(['iw', 'event'], stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + try: + err = 'scan failed' + while True: + remaining = deadline - time.monotonic() + if remaining <= 0: + return {'error': f'scan on {ifname} timed out'} + result = subprocess.run(args, capture_output=True, text=True, + timeout=min(10, remaining)) + if result.returncode == 0: + break + err = result.stderr.strip() or err + if '(-16)' not in err: + return {'error': f'scan on {ifname} failed: {err}'} + # Another scan is running, wait for it to end and try again + if wait_scan_done(events, ifname, deadline) is None: + return {'error': f'scan on {ifname} failed: {err}'} + + state = wait_scan_done(events, ifname, deadline) + if state is None: + return {'error': f'scan on {ifname} timed out'} + if state == 'aborted': + return {'error': f'scan on {ifname} was aborted'} + except subprocess.TimeoutExpired: + return {'error': f'scan on {ifname} timed out'} + finally: + events.kill() + events.wait() + + channels = parse_survey(ifname) + if not any(ch['in_use'] for ch in channels): + # Not every driver flags the operating channel, mac80211_hwsim + # does not, take it from the interface instead. + freq = parse_interface_info(ifname).get('frequency') + for ch in channels: + ch['in_use'] = ch['frequency'] == freq + + return channels + + def parse_wds_ports(ifname): """ List the WDS ports of an access point: the AP/VLAN interfaces on the @@ -649,6 +821,7 @@ def main(): 'dev': 'List all interfaces grouped by PHY', 'info': 'Get PHY or interface information (requires device)', 'survey': 'Get channel survey data (requires interface)', + 'survey-scan': 'Scan all channels, then get survey data (requires radio)', '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)', @@ -664,6 +837,7 @@ def main(): 'iw.py link wlan0', 'iw.py mesh wifi0', 'iw.py survey wlan0', + 'iw.py survey-scan radio0 passive', 'iw.py caps radio0' ] }, indent=2)) @@ -711,6 +885,11 @@ def main(): data = {'error': 'survey command requires interface argument'} else: data = parse_survey(sys.argv[2]) + elif command == 'survey-scan': + if len(sys.argv) < 3: + data = {'error': 'survey-scan command requires radio argument'} + else: + data = survey_scan(sys.argv[2], 'passive' in sys.argv[3:]) elif command == 'caps': if len(sys.argv) < 3: data = {'error': 'caps command requires PHY/radio argument'} @@ -720,6 +899,8 @@ def main(): data = {'error': f'Unknown command: {command}'} print(json.dumps(data, indent=2, ensure_ascii=False)) + if command == 'survey-scan' and isinstance(data, dict) and 'error' in data: + sys.exit(1) except Exception as e: print(json.dumps({'error': str(e)})) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index aa26b1ac9..935e74c16 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -1029,6 +1029,10 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv) if (rc) goto err; + rc = hardware_rpc_init(&confd); + if (rc) + goto err; + rc = support_rpc_init(&confd); if (rc) goto err; diff --git a/src/confd/src/core.h b/src/confd/src/core.h index e784e6a5b..7cafc5550 100644 --- a/src/confd/src/core.h +++ b/src/confd/src/core.h @@ -196,6 +196,14 @@ static inline int register_rpc(sr_session_ctx_t *session, const char *xpath, return rc; } +/* Fail an RPC/action with MSG as the NETCONF error message */ +static inline int rpc_failed(sr_session_ctx_t *session, const char *msg) +{ + sr_session_set_netconf_error(session, "application", "operation-failed", + NULL, NULL, msg, 0); + return SR_ERR_OPERATION_FAILED; +} + static inline int register_rpc_tree(sr_session_ctx_t *session, const char *xpath, sr_rpc_tree_cb cb, void *arg, sr_subscription_ctx_t **sub) { @@ -288,6 +296,7 @@ int services_change(sr_session_ctx_t *session, struct lyd_node *config, struct l /* hardware.c */ int hardware_candidate_init(struct confd *confd); +int hardware_rpc_init(struct confd *confd); int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd); /* keystore.c */ diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index aa391b7b6..8c3a241f9 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -1485,6 +1485,128 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l return rc; } + +/* + * Scan all channels on a radio and report how busy each one is, the + * channel-survey action in infix-hardware.yang. The scan takes the + * radio off its operating channel for a few seconds, which is why this + * is an action and not operational data. The helper does the scan and + * the parsing, see iw.py survey-scan. + */ +static int wifi_channel_survey(sr_session_ctx_t *session, uint32_t sub_id, const char *op_path, + const struct lyd_node *input, sr_event_t event, uint32_t request_id, + struct lyd_node *output, void *priv) +{ + static const struct { const char *json, *yang; } times[] = { + { "noise", "noise" }, + { "active_time", "active-time" }, + { "busy_time", "busy-time" }, + { "receive_time", "receive-time" }, + { "transmit_time", "transmit-time" }, + }; + struct lyd_node *component, *chan; + const char *radio, *passive; + json_error_t jerr; + json_t *root, *entry; + size_t index; + FILE *pp; + + if (event != SR_EV_RPC) + return SR_ERR_OK; + + component = lyd_parent(lyd_parent(input)); + radio = component ? lydx_get_cattr(component, "name") : NULL; + if (!radio) + return rpc_failed(session, "Cannot tell which radio to survey"); + + passive = lydx_get_cattr((struct lyd_node *)input, "passive"); + pp = popenf("r", "/usr/libexec/infix/iw.py survey-scan %s %s", radio, + passive && !strcmp(passive, "true") ? "passive" : ""); + if (!pp) + return rpc_failed(session, "Failed starting channel survey"); + + root = json_loadf(pp, 0, &jerr); + pclose(pp); + if (!root) + return rpc_failed(session, "Channel survey returned no data"); + + if (!json_is_array(root)) { + json_t *err = json_object_get(root, "error"); + char msg[256]; + + snprintf(msg, sizeof(msg), "Channel survey failed: %s", + json_is_string(err) ? json_string_value(err) : "unknown error"); + json_decref(root); + return rpc_failed(session, msg); + } + + json_array_foreach(root, index, entry) { + json_t *freq = json_object_get(entry, "frequency"); + char val[32]; + + if (!json_is_integer(freq)) + continue; + + snprintf(val, sizeof(val), "%lld", json_integer_value(freq)); + if (lyd_new_list(output, NULL, "channel", LYD_NEW_VAL_OUTPUT, &chan, val)) { + ERROR("channel-survey: failed adding channel %s", val); + continue; + } + + lyd_new_term(chan, NULL, "in-use", + json_is_true(json_object_get(entry, "in_use")) ? "true" : "false", + 0, NULL); + + for (size_t i = 0; i < NELEMS(times); i++) { + json_t *v = json_object_get(entry, times[i].json); + + if (!json_is_integer(v)) + continue; + + snprintf(val, sizeof(val), "%lld", json_integer_value(v)); + lyd_new_term(chan, NULL, times[i].yang, val, 0, NULL); + } + } + json_decref(root); + + return SR_ERR_OK; +} + +/* The action only exists when the wifi feature is enabled, see wifi.inc */ +static bool wifi_feature_enabled(struct confd *confd) +{ + const struct lys_module *mod; + const struct ly_ctx *ctx; + bool enabled = false; + + ctx = sr_acquire_context(confd->conn); + if (!ctx) + return false; + + mod = ly_ctx_get_module_implemented(ctx, "infix-hardware"); + if (mod) + enabled = lys_feature_value(mod, "wifi") == LY_SUCCESS; + sr_release_context(confd->conn); + + return enabled; +} + +int hardware_rpc_init(struct confd *confd) +{ + int rc = 0; + + if (!wifi_feature_enabled(confd)) + return SR_ERR_OK; + + REGISTER_RPC_TREE(confd->session, XPATH_BASE_ "/component/infix-hardware:wifi-radio/channel-survey", + wifi_channel_survey, NULL, &confd->sub); + + return SR_ERR_OK; +fail: + ERROR("Init hardware rpc failed: %s", sr_strerror(rc)); + return rc; +} + int hardware_candidate_init(struct confd *confd) { int rc = 0; diff --git a/src/confd/src/system-software.c b/src/confd/src/system-software.c index a714944c5..f184a283a 100644 --- a/src/confd/src/system-software.c +++ b/src/confd/src/system-software.c @@ -93,13 +93,6 @@ static int infix_system_sw_set_boot_order(sr_session_ctx_t *session, uint32_t su return SR_ERR_OK; } -static int rpc_failed(sr_session_ctx_t *session, const char *msg) -{ - sr_session_set_netconf_error(session, "application", "operation-failed", - NULL, NULL, msg, 0); - return SR_ERR_OPERATION_FAILED; -} - /* Append output leaf PATH/LEAF from the state file, skipped when absent. */ static int add_output(sr_val_t **output, size_t *cnt, const char *path, const char *leaf, json_t *val) diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index bad5753a0..a00da3f9f 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -27,7 +27,7 @@ MODULES=( "infix-syslog@2026-09-24.yang" "iana-hardware@2018-03-13.yang" "ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor" - "infix-hardware@2026-10-04.yang" + "infix-hardware@2026-10-05.yang" "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2026-04-28.yang" "infix-if-type@2026-10-03.yang" diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang index a35c38c23..56e3f0e2e 100644 --- a/src/confd/yang/confd/infix-hardware.yang +++ b/src/confd/yang/confd/infix-hardware.yang @@ -21,6 +21,12 @@ module infix-hardware { contact "kernelkit@googlegroups.com"; description "Vital Product Data augmentation of ieee-hardware and deviations."; + revision 2026-10-05 { + description "Replace the WiFi radio survey container with the channel-survey + action, survey data is collected on request only."; + reference "internal"; + } + revision 2026-10-04 { description "Add locate RPC, blinks LEDs to identify the chassis."; reference "internal"; @@ -533,98 +539,108 @@ module infix-hardware { } /* - * Channel survey data (operational state) + * Channel survey, on demand */ - container survey { - config false; + action channel-survey { description - "WiFi channel survey data providing channel utilization - and interference information. - - This data is collected from the WiFi driver and provides - insights into channel occupancy, noise levels, and RF activity."; - - list channel { - key "frequency"; - description - "Per-channel survey information. + "Scan all channels the radio supports and report how busy each + one is. - Includes utilization metrics for all channels scanned by - the radio, not just the currently active channel."; + The radio leaves its operating channel for the duration of + the scan, a few seconds, so traffic on the radio pauses. + Survey data is not collected in the background, this action + is the only way to get it."; - leaf frequency { - type uint32; - units "MHz"; + input { + leaf passive { + type boolean; + default false; description - "Channel center frequency in MHz. - - Examples: - - 2412 MHz (2.4 GHz channel 1) - - 5180 MHz (5 GHz channel 36) - - 5955 MHz (6 GHz channel 1)"; + "Listen only, do not send probe requests. A passive scan + dwells longer on each channel and gives a better busy-time + sample, at the cost of a longer pause in traffic."; } + } - leaf in-use { - type boolean; + output { + list channel { + key "frequency"; description - "Whether this channel is currently in use by the radio. + "Survey of one channel. Channel utilization is + busy-time / active-time."; + + leaf frequency { + type uint32; + units "MHz"; + description + "Channel center frequency in MHz. + + Examples: + - 2412 MHz (2.4 GHz channel 1) + - 5180 MHz (5 GHz channel 36) + - 5955 MHz (6 GHz channel 1)"; + } - Only one channel will have this set to true at a time."; - } + leaf in-use { + type boolean; + description + "Whether this is the radio's operating channel."; + } - leaf noise { - type int16; - units "dBm"; - description - "Background noise level on this channel in dBm. + leaf noise { + type int16; + units "dBm"; + description + "Background noise level on this channel in dBm. - Lower (more negative) values indicate cleaner RF environment. + Lower (more negative) values indicate cleaner RF environment. - Typical values: - - -95 to -100 dBm: Very low noise (excellent) - - -85 to -95 dBm: Low noise (good) - - -75 to -85 dBm: Moderate noise - - -65 to -75 dBm: High noise (congested)"; - } + Typical values: + - -95 to -100 dBm: Very low noise (excellent) + - -85 to -95 dBm: Low noise (good) + - -75 to -85 dBm: Moderate noise + - -65 to -75 dBm: High noise (congested)"; + } - leaf active-time { - type uint32; - units "milliseconds"; - description - "Total time the radio was active on this channel. + leaf active-time { + type uint32; + units "milliseconds"; + description + "Total time the radio was active on this channel. - This is the survey measurement period for this channel."; - } + This is the survey measurement period for this channel."; + } - leaf busy-time { - type uint32; - units "milliseconds"; - description - "Time the channel was detected as busy. + leaf busy-time { + type uint32; + units "milliseconds"; + description + "Time the channel was detected as busy. - Includes time spent receiving frames, transmitting frames, - and time the channel was busy due to other sources. + Includes time spent receiving frames, transmitting frames, + and time the channel was busy due to other sources. - Channel utilization = (busy-time / active-time) * 100%"; - } + Channel utilization = (busy-time / active-time) * 100%"; + } - leaf receive-time { - type uint32; - units "milliseconds"; - description - "Time spent receiving frames on this channel. + leaf receive-time { + type uint32; + units "milliseconds"; + description + "Time spent receiving frames on this channel. - Subset of busy-time spent on frame reception."; - } + Subset of busy-time spent on frame reception."; + } - leaf transmit-time { - type uint32; - units "milliseconds"; - description - "Time spent transmitting frames on this channel. + leaf transmit-time { + type uint32; + units "milliseconds"; + description + "Time spent transmitting frames on this channel. - Subset of busy-time spent on frame transmission."; + Subset of busy-time spent on frame transmission."; + } } } } diff --git a/src/confd/yang/confd/infix-hardware@2026-10-04.yang b/src/confd/yang/confd/infix-hardware@2026-10-05.yang similarity index 100% rename from src/confd/yang/confd/infix-hardware@2026-10-04.yang rename to src/confd/yang/confd/infix-hardware@2026-10-05.yang diff --git a/src/statd/python/yanger/ietf_hardware.py b/src/statd/python/yanger/ietf_hardware.py index 548bd1b1a..b34be178f 100644 --- a/src/statd/python/yanger/ietf_hardware.py +++ b/src/statd/python/yanger/ietf_hardware.py @@ -606,39 +606,6 @@ def thermal_sensor_components(): return components -def get_survey_data(ifname): - """Get channel survey data using iw.py script""" - channels = [] - - try: - survey_data = HOST.run_json(("/usr/libexec/infix/iw.py", "survey", ifname), default=[]) - - for entry in survey_data: - channel = { - "frequency": entry.get("frequency"), - "in-use": entry.get("in_use", False) - } - - # Add optional fields if present - if "noise" in entry: - channel["noise"] = entry["noise"] - if "active_time" in entry: - channel["active-time"] = entry["active_time"] - if "busy_time" in entry: - channel["busy-time"] = entry["busy_time"] - if "receive_time" in entry: - channel["receive-time"] = entry["receive_time"] - if "transmit_time" in entry: - channel["transmit-time"] = entry["transmit_time"] - - channels.append(channel) - - except Exception: - pass - - return channels - - def get_phy_info(phy_name): """Get complete PHY information using iw.py script""" try: @@ -757,20 +724,6 @@ def wifi_radio_components(): num_ifaces = iw_info.get('num_virtual_interfaces', 0) wifi_radio_data['num-virtual-interfaces'] = num_ifaces - # Get survey data if we have an interface - iface = phy_data.get("iface") - if iface: - try: - channels = get_survey_data(iface) - - if channels: - wifi_radio_data["survey"] = { - "channel": channels - } - except Exception: - # If survey fails, continue without survey data - pass - # Add wifi-radio data to component if wifi_radio_data: component["infix-hardware:wifi-radio"] = wifi_radio_data From 7a98e72ffdbd6eb5ace46cd6db7eba004ebed549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 5 Oct 2026 16:02:12 +0200 Subject: [PATCH 15/45] cli: Add show hardware survey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calls the channel-survey action on the radio component and feeds the channel map renderer, which until now had no command at all. The rpc tool gains -j to print an RPC output tree as JSON, the value printer cannot render a list. Signed-off-by: Mattias Walström --- .../usr/libexec/infix/wifi-channel-map.py | 218 ++++++++++-------- src/bin/copy.c | 83 ++++++- src/bin/show/__init__.py | 52 +++++ src/klish-plugin-infix/src/infix.c | 8 + src/klish-plugin-infix/xml/infix.xml | 27 ++- 5 files changed, 287 insertions(+), 101 deletions(-) diff --git a/board/common/rootfs/usr/libexec/infix/wifi-channel-map.py b/board/common/rootfs/usr/libexec/infix/wifi-channel-map.py index 706058517..01781d570 100755 --- a/board/common/rootfs/usr/libexec/infix/wifi-channel-map.py +++ b/board/common/rootfs/usr/libexec/infix/wifi-channel-map.py @@ -54,6 +54,17 @@ def get_channel_frequency(channel, band='2.4'): return None +def freq_to_band(freq): + """Band name for a frequency in MHz, or None""" + if 2400 <= freq <= 2500: + return '2.4 GHz' + if 5000 <= freq <= 5900: + return '5 GHz' + if 5925 <= freq <= 7125: + return '6 GHz' + return None + + def get_busy_percentage(channel_data): """Calculate channel busy percentage""" active = channel_data.get('active-time', 0) @@ -84,7 +95,7 @@ def draw_channel_graph_2_4ghz(survey_data): for ch_data in survey_data: freq = ch_data.get('frequency') ch_num = freq_to_channel(freq) - if ch_num and 1 <= ch_num <= 14: + if ch_num and freq_to_band(freq) == '2.4 GHz': busy_pct = get_busy_percentage(ch_data) channels[ch_num] = { 'freq': freq, @@ -107,14 +118,27 @@ def draw_channel_graph_2_4ghz(survey_data): print(f"Non-overlapping channels: 1, 6, 11 (shown in {Colors.GREEN}green{Colors.RESET})") print() - # Draw frequency scale + # 80 columns span 2400-2500 MHz, so one column is 1.25 MHz and a + # 20 MHz channel is 16 columns wide, centred on its frequency. + width = 80 + cols_per_mhz = width / 100 + + def col(freq): + return int(round((freq - 2400) * cols_per_mhz)) + + ruler = [' '] * width + ticks = [' '] * width + for mhz in range(2400, 2500, 20): + label = str(mhz) + for i, c in enumerate(label): + if col(mhz) + i < width: + ruler[col(mhz) + i] = c + ticks[col(mhz)] = '|' print("Frequency (MHz):") - print("2400 2420 2440 2460 2480") - print("|-----------|-----------|-----------|-----------|") + print(" " + ''.join(ruler)) + print(" " + ''.join(ticks).replace(' ', '-')) - # Draw each channel as a bar showing its 20 MHz width - # Each channel occupies ~4 adjacent channels worth of space - for ch in range(1, 14): + for ch in range(1, 15): if ch not in channels: continue @@ -123,7 +147,6 @@ def draw_channel_graph_2_4ghz(survey_data): is_in_use = data['in_use'] noise = data['noise'] - # Determine color based on status if is_in_use: color = Colors.BG_BLUE marker = '█' @@ -133,49 +156,25 @@ def draw_channel_graph_2_4ghz(survey_data): elif busy_pct >= 25: color = Colors.YELLOW marker = '▒' - elif busy_pct > 0: + elif busy_pct >= 1: color = Colors.CYAN marker = '░' else: color = Colors.GRAY marker = '·' - # Non-overlapping channels get green color if ch in [1, 6, 11] and not is_in_use and busy_pct < 10: color = Colors.GREEN - # Calculate position (each channel is offset by 5 MHz = 1 position) - # Channel 1 is at 2412 MHz, base is 2400 - offset = ((data['freq'] - 2400) // 5) - - # Draw channel bar (20 MHz = 4 positions wide) - line = ' ' * 80 - line_arr = list(line) - - # Mark the channel span (20 MHz width) - for i in range(4): - pos = offset + i - 2 # Center the 20 MHz around channel - if 0 <= pos < len(line_arr): - line_arr[pos] = marker - - # Add channel label - label_pos = offset - if 0 <= label_pos < len(line_arr) - 5: - # Clear space for label - for i in range(5): - if label_pos + i < len(line_arr): - line_arr[label_pos + i] = ' ' - - line = ''.join(line_arr) - - # Status indicators - status = "" - if is_in_use: - status = f" {Colors.BOLD}[IN USE]{Colors.RESET}" + line = [' '] * width + for pos in range(col(data['freq'] - 10), col(data['freq'] + 10)): + if 0 <= pos < width: + line[pos] = marker + status = f" {Colors.BOLD}[IN USE]{Colors.RESET}" if is_in_use else "" busy_color = get_utilization_color(busy_pct) - print(f"{color}Ch{ch:2d}{Colors.RESET} {color}{line}{Colors.RESET} " + print(f"{color}Ch{ch:2d}{Colors.RESET} {color}{''.join(line)}{Colors.RESET} " f"{busy_color}{busy_pct:5.1f}%{Colors.RESET} " f"{noise:4d}dBm{status}") @@ -196,12 +195,17 @@ def draw_channel_list(survey_data): print(f"{'Ch':<4} {'Freq':<6} {'Noise':<8} {'Busy%':<8} {'Utilization Bar':<40}") print("-" * 80) + band = None for ch_data in sorted(survey_data, key=lambda x: x.get('frequency', 0)): freq = ch_data.get('frequency') ch_num = freq_to_channel(freq) if not ch_num: continue + if freq_to_band(freq) != band: + band = freq_to_band(freq) + print(f"{Colors.BOLD}{band}{Colors.RESET}") + noise = ch_data.get('noise', -100) busy_pct = get_busy_percentage(ch_data) is_in_use = ch_data.get('in-use', False) @@ -237,7 +241,7 @@ def draw_overlap_pie(survey_data): for ch_data in survey_data: freq = ch_data.get('frequency') ch_num = freq_to_channel(freq) - if ch_num and 1 <= ch_num <= 13: + if ch_num and freq_to_band(freq) == '2.4 GHz': busy_pct = get_busy_percentage(ch_data) channels[ch_num] = { 'busy': busy_pct, @@ -549,78 +553,108 @@ def busy_to_height(busy_pct): def draw_recommendations(survey_data, json_output=False): - """Analyze channels and provide recommendations""" - # Parse channel data - channels = {} - in_use_channel = None + """Analyze channels and provide recommendations, per band""" + bands = {} + current = None for ch_data in survey_data: freq = ch_data.get('frequency') ch_num = freq_to_channel(freq) - if ch_num: - busy_pct = get_busy_percentage(ch_data) - channels[ch_num] = { - 'busy': busy_pct, - 'noise': ch_data.get('noise', -100), - 'in_use': ch_data.get('in-use', False) - } - if ch_data.get('in-use'): - in_use_channel = ch_num - - # Find least congested non-overlapping channels - best_channels = [] - for ch in [1, 6, 11]: - if ch in channels: - best_channels.append((ch, channels[ch]['busy'])) + band = freq_to_band(freq) if freq else None + if not ch_num or not band: + continue - best_channels.sort(key=lambda x: x[1]) + entry = { + 'channel': ch_num, + 'frequency': freq, + 'busy': get_busy_percentage(ch_data), + 'noise': ch_data.get('noise', -100), + } + bands.setdefault(band, []).append(entry) + if ch_data.get('in-use'): + current = dict(entry, band=band) + + # Least busy channels per band. On 2.4 GHz only the three channels + # that do not overlap are worth recommending. + best = {} + for band, entries in bands.items(): + if band == '2.4 GHz': + entries = [e for e in entries if e['channel'] in (1, 6, 11)] + best[band] = sorted(entries, key=lambda e: e['busy'])[:3] - # JSON output if json_output: output = { - "recommended_channels": [ - {"channel": ch, "busy_percent": round(busy, 1)} - for ch, busy in best_channels - ] + "recommended_channels": { + band: [{"channel": e['channel'], "busy_percent": round(e['busy'], 1)} + for e in entries] + for band, entries in best.items() + } } - if in_use_channel: - output["current_channel"] = in_use_channel - output["current_busy_percent"] = round(channels.get(in_use_channel, {}).get('busy', 0), 1) + if current: + output["current_channel"] = current['channel'] + output["current_band"] = current['band'] + output["current_busy_percent"] = round(current['busy'], 1) print(json.dumps(output, indent=2)) return - # Text output print(f"\n{Colors.BOLD}Channel Recommendations{Colors.RESET}") print("=" * 80) - if in_use_channel: - print(f"Current channel: {Colors.BOLD}{in_use_channel}{Colors.RESET}") - current_busy = channels.get(in_use_channel, {}).get('busy', 0) - if current_busy > 50: - print(f" {Colors.RED}⚠{Colors.RESET} High congestion detected ({current_busy:.1f}% busy)") - elif current_busy > 25: - print(f" {Colors.YELLOW}⚠{Colors.RESET} Moderate congestion ({current_busy:.1f}% busy)") + if current: + print(f"Current channel: {Colors.BOLD}{current['channel']}{Colors.RESET} ({current['band']})") + busy = current['busy'] + if busy > 50: + print(f" {Colors.RED}⚠{Colors.RESET} High congestion detected ({busy:.1f}% busy)") + elif busy > 25: + print(f" {Colors.YELLOW}⚠{Colors.RESET} Moderate congestion ({busy:.1f}% busy)") else: - print(f" {Colors.GREEN}✓{Colors.RESET} Good channel utilization ({current_busy:.1f}% busy)") + print(f" {Colors.GREEN}✓{Colors.RESET} Good channel utilization ({busy:.1f}% busy)") - print(f"\nRecommended non-overlapping channels (2.4 GHz):") - for i, (ch, busy) in enumerate(best_channels[:3], 1): - color = get_utilization_color(busy) - marker = "★" if i == 1 else " " - print(f" {marker} Channel {ch:2d}: {color}{busy:5.1f}% busy{Colors.RESET}") + for band in ('2.4 GHz', '5 GHz', '6 GHz'): + if band not in best: + continue + what = "non-overlapping channels" if band == '2.4 GHz' else "channels" + print(f"\nLeast busy {what} ({band}):") + for i, e in enumerate(best[band], 1): + color = get_utilization_color(e['busy']) + marker = "★" if i == 1 else " " + print(f" {marker} Channel {e['channel']:3d}: {color}{e['busy']:5.1f}% busy{Colors.RESET}") print() +def find_channels(data): + """ + Collect every survey channel list in the input. + + The channel-survey action output is wrapped in its path, with + 'rpc -j' that is hardware/component/wifi-radio/channel-survey, with + RESTCONF it is 'infix-hardware:output'. Dig for the lists rather + than assume one wrapping. + """ + found = [] + if isinstance(data, dict): + for key, value in data.items(): + if key == 'channel' and isinstance(value, list) and \ + all(isinstance(ch, dict) and 'frequency' in ch for ch in value): + found.extend(value) + else: + found.extend(find_channels(value)) + elif isinstance(data, list): + for item in data: + found.extend(find_channels(item)) + return found + + def main(): parser = argparse.ArgumentParser( description='Visualize WiFi channel overlap and utilization', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Examples: - # Read from yanger output (show all sections) - yanger -x "ixll -A ssh host sudo" ietf-hardware | %(prog)s + # Read the channel-survey action output (show all sections) + rpc -j "/ietf-hardware:hardware/component[name='radio0']/infix-hardware:wifi-radio/channel-survey" | %(prog)s # Read from file %(prog)s survey_data.json @@ -670,22 +704,10 @@ def main(): else: data = json.load(sys.stdin) - # Extract survey data from hardware components - survey_data = [] - hardware = data.get('ietf-hardware:hardware', {}) - components = hardware.get('component', []) - - for component in components: - if component.get('class') == 'infix-hardware:wifi': - wifi_radio = component.get('infix-hardware:wifi-radio', {}) - survey = wifi_radio.get('survey', {}) - channels = survey.get('channel', []) - if channels: - survey_data.extend(channels) - + survey_data = find_channels(data) if not survey_data: print("No WiFi survey data found in input", file=sys.stderr) - print("Expected format: yanger ietf-hardware output with wifi-radio survey data", file=sys.stderr) + print("Expected format: output of the infix-hardware channel-survey action", file=sys.stderr) sys.exit(1) # Generate SVG if requested (exclusive mode) diff --git a/src/bin/copy.c b/src/bin/copy.c index e4cacaaef..8fd47f550 100644 --- a/src/bin/copy.c +++ b/src/bin/copy.c @@ -47,6 +47,7 @@ static char *xpath = "/*"; static int debug; static int force; static int timeout; +static int json_out; static int dry_run; static int sanitize; static int redact; @@ -952,6 +953,7 @@ static int usage_rpc(int rc) "Options:\n" " -d Enable debug mode, verbose output on stderr\n" " -h This help text\n" + " -j Print RPC output as JSON\n" " -t SEC Timeout for the operation, or default %d sec\n" " -v Show version\n" "\n" @@ -985,6 +987,77 @@ static bool is_leaflist(sr_conn_ctx_t *conn, const char *rpc_xpath, const char * return rc; } +/* + * Tree variant of rpc_exec(), prints the output as JSON. Used by show + * for actions with list output, which sr_print_val() cannot render. + */ +static int rpc_exec_json(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, + const char *rpc_xpath, int argc, char *argv[]) +{ + struct lyd_node *tree = NULL, *op = NULL; + const struct ly_ctx *ctx; + sr_data_t *output = NULL; + char *str = NULL; + int rc = 1, err = 0, i; + + ctx = sr_acquire_context(conn); + if (!ctx) { + warnx("failed acquiring libyang context"); + return 1; + } + + if (lyd_new_path(NULL, ctx, rpc_xpath, NULL, 0, &tree) || + lyd_find_path(tree, rpc_xpath, 0, &op)) { + warnx("invalid RPC xpath %s", rpc_xpath); + goto cleanup; + } + + for (i = 0; i < argc - 1; i += 2) { + const char *key = argv[i]; + char *val, *token, *saveptr; + + val = strdup(argv[i + 1]); + if (!val) { + warnx("Memory allocation failed"); + goto cleanup; + } + + if (strchr(val, ',') && is_leaflist(conn, rpc_xpath, key)) { + for (token = strtok_r(val, ",", &saveptr); token; + token = strtok_r(NULL, ",", &saveptr)) + err = lyd_new_path(op, NULL, key, token, 0, NULL); + } else + err = lyd_new_path(op, NULL, key, val, 0, NULL); + free(val); + + if (err) { + warnx("invalid RPC argument %s = %s", key, argv[i + 1]); + goto cleanup; + } + } + + dbg("Sending RPC %s (timeout: %d ms)", rpc_xpath, timeout * 1000); + err = sr_rpc_send_tree(sess, tree, timeout * 1000, &output); + if (err != SR_ERR_OK) { + sysrepo_print_error(sess); + warnx("RPC execution failed: %s", sr_strerror(err)); + goto cleanup; + } + + if (output && output->tree && + !lyd_print_mem(&str, output->tree, LYD_JSON, LYD_PRINT_SIBLINGS)) + puts(str); + free(str); + rc = 0; + +cleanup: + sr_release_data(output); + lyd_free_all(tree); + sr_release_context(conn); + + return rc; +} + /* Execute RPC from CLI arguments: xpath and key-value pairs */ static int rpc_exec(const char *rpc_xpath, int argc, char *argv[]) { @@ -1002,6 +1075,11 @@ static int rpc_exec(const char *rpc_xpath, int argc, char *argv[]) if (err != SR_ERR_OK) return 1; + if (json_out) { + rc = rpc_exec_json(conn, sess, rpc_xpath, argc, argv); + goto cleanup; + } + for (i = 0; i < argc - 1; i += 2) { const char *key = argv[i]; const char *val = argv[i + 1]; @@ -1125,13 +1203,16 @@ static int rpc_main(int argc, char *argv[]) timeout = fgetint("/etc/default/confd", "=", "CONFD_TIMEOUT"); - while ((c = getopt(argc, argv, "dht:v")) != EOF) { + while ((c = getopt(argc, argv, "dhjt:v")) != EOF) { switch(c) { case 'd': debug = 1; break; case 'h': return usage_rpc(0); + case 'j': + json_out = 1; + break; case 't': timeout = atoi(optarg); break; diff --git a/src/bin/show/__init__.py b/src/bin/show/__init__.py index e45e9e3a8..46eac8f2f 100755 --- a/src/bin/show/__init__.py +++ b/src/bin/show/__init__.py @@ -2,6 +2,7 @@ import re import subprocess +import sys import json from typing import List import os @@ -82,17 +83,68 @@ def tftp(args: List[str]) -> None: def hardware(args: List[str]) -> None: + """show hardware [ [survey [passive]]]""" + if len(args) >= 2 and args[1] == "survey": + channel_survey(args[0], "passive" in args[2:]) + return + data = get_json("/ietf-hardware:hardware") if not data: print("No hardware data retrieved.") return + if args: + hw = data.get("ietf-hardware:hardware", {}) + hw["component"] = [c for c in hw.get("component", []) if c.get("name") == args[0]] + if not hw["component"]: + print(f"No hardware component named {args[0]}.") + return + if RAW_OUTPUT: print(json.dumps(data, indent=2)) return cli_pretty(data, "show-hardware") +def channel_survey(radio: str, passive: bool) -> None: + """Run the channel-survey action on a WiFi radio and render the channel map""" + # Same character set as the hardware component name in the model + if not re.fullmatch(r"[a-zA-Z0-9_][a-zA-Z0-9_.:+@-]*", radio): + print(f"No WiFi radio named {radio}.") + return + + xpath = f"/ietf-hardware:hardware/component[name='{radio}']/infix-hardware:wifi-radio/channel-survey" + cmd = ["rpc", "-j", xpath] + if passive: + cmd += ["passive", "true"] + + # Progress goes to stderr, past the pager, which would otherwise + # print the chart twice when it arrives piecemeal after the scan + print(f"Scanning channels on {radio}, this takes a few seconds ...", + file=sys.stderr, flush=True) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + if "does not exist" in result.stderr: + print(f"No WiFi radio named {radio}.") + return + # The rpc tool prints the device's reason, then its own verdict + for line in result.stderr.splitlines(): + if line.startswith("rpc: ") and "RPC execution failed" not in line: + print(re.sub(r" \(\d+\)$", "", line[len("rpc: "):])) + return + print(result.stderr.strip() or "Channel survey failed") + return + + if RAW_OUTPUT: + print(result.stdout) + return + + chart = subprocess.run(["/usr/libexec/infix/wifi-channel-map.py"], + input=result.stdout, capture_output=True, text=True) + sys.stdout.write(chart.stdout or chart.stderr) + sys.stdout.flush() + + def ntp(args: List[str]) -> None: # Create argument parser for ntp subcommands parser = argparse.ArgumentParser(prog='show ntp', add_help=False) diff --git a/src/klish-plugin-infix/src/infix.c b/src/klish-plugin-infix/src/infix.c index c301f3678..dd58c012d 100644 --- a/src/klish-plugin-infix/src/infix.c +++ b/src/klish-plugin-infix/src/infix.c @@ -353,6 +353,13 @@ int infix_ifaces(kcontext_t *ctx) return 0; } +int infix_wifi_radios(kcontext_t *ctx) +{ + (void)ctx; + system("ls /sys/class/ieee80211/ 2>/dev/null"); + return 0; +} + /* Note: uses shellf() for pipes, but all arguments are hardcoded by callers */ static int firewall_dbus_completion(const char *interface, const char *method, const char *parser) { @@ -954,6 +961,7 @@ int kplugin_infix_init(kcontext_t *ctx) kplugin_add_syms(plugin, ksym_new("path", infix_path)); kplugin_add_syms(plugin, ksym_new("rename", infix_rename)); kplugin_add_syms(plugin, ksym_new("ifaces", infix_ifaces)); + kplugin_add_syms(plugin, ksym_new("wifi_radios", infix_wifi_radios)); kplugin_add_syms(plugin, ksym_new("users", infix_users)); kplugin_add_syms(plugin, ksym_new("groups", infix_groups)); kplugin_add_syms(plugin, ksym_new("sym_keys", infix_sym_keys)); diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index 1f897bf5d..bcca3206e 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -122,6 +122,13 @@ + + + + + + + @@ -684,9 +691,25 @@ echo "Public: $pub" show mdns - + + + + + + show hardware "$KLISH_PARAM_component" survey |pager + + + + + show hardware "$KLISH_PARAM_component" survey passive |pager + + + + + + - show hardware |pager + show hardware ${KLISH_PARAM_component:+"$KLISH_PARAM_component"} |pager From 752370d7a3471a7df42de8852b7166a18169fa6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 5 Oct 2026 16:02:12 +0200 Subject: [PATCH 16/45] webui: Scan channels on request from the WiFi page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The always-on survey card showed a single bar on a busy radio. Replace it with a Scan channels button per radio that runs the channel-survey action and draws the result. Signed-off-by: Mattias Walström --- src/webui/internal/handlers/interfaces.go | 11 +- src/webui/internal/handlers/wifi.go | 72 ++++++++---- .../internal/handlers/wifi_survey_test.go | 103 ++++++++++++++++++ src/webui/internal/server/server.go | 1 + src/webui/templates/pages/wifi.html | 26 ++++- 5 files changed, 178 insertions(+), 35 deletions(-) create mode 100644 src/webui/internal/handlers/wifi_survey_test.go diff --git a/src/webui/internal/handlers/interfaces.go b/src/webui/internal/handlers/interfaces.go index 1acfd4042..3a690ff40 100644 --- a/src/webui/internal/handlers/interfaces.go +++ b/src/webui/internal/handlers/interfaces.go @@ -189,15 +189,8 @@ type wifiScanResultJSON struct { Encryption []string `json:"encryption"` } -// WiFi radio survey RESTCONF structures (from ietf-hardware:hardware). - -type wifiRadioJSON struct { - Survey *wifiSurveyJSON `json:"survey"` -} - -type wifiSurveyJSON struct { - Channel []surveyChanJSON `json:"channel"` -} +// WiFi channel survey, the output of the infix-hardware channel-survey +// action on a radio. type surveyChanJSON struct { Frequency int `json:"frequency"` diff --git a/src/webui/internal/handlers/wifi.go b/src/webui/internal/handlers/wifi.go index e1ce23097..5695bd161 100644 --- a/src/webui/internal/handlers/wifi.go +++ b/src/webui/internal/handlers/wifi.go @@ -4,19 +4,18 @@ package handlers import ( "context" + "errors" "fmt" "html/template" "log" "net/http" + "net/url" "sync" + "time" "infix/webui/internal/restconf" ) -// wifiRadioHWJSON extends the hardware component wifi-radio container with -// operational fields from infix-hardware YANG that are not in wifiRadioJSON. -// wifiRadioJSON (defined in interfaces.go) only covers survey data; this -// struct captures the full operational state returned by RESTCONF. // wifiMaxIfJSON maps the max-interfaces container from infix-hardware YANG. type wifiMaxIfJSON struct { AP int `json:"ap"` @@ -33,7 +32,6 @@ type wifiRadioHWJSON struct { Driver string `json:"driver"` Bands []wifiBandJSON `json:"bands"` MaxInterfaces *wifiMaxIfJSON `json:"max-interfaces"` - Survey *wifiSurveyJSON `json:"survey"` } type wifiBandJSON struct { @@ -74,8 +72,8 @@ type WiFiRadio struct { VHTCapable bool HECapable bool Bands []WiFiBand - SurveySVG template.HTML Interfaces []WiFiInterface + Survey wifiSurveyData } type WiFiBand struct { @@ -86,17 +84,6 @@ type WiFiBand struct { HECapable bool } -// ChannelSurvey holds processed survey data for one channel. -type ChannelSurvey struct { - Frequency int - Channel int - InUse bool - Noise int - ActiveTime int64 - BusyTime int64 - UtilPct int // BusyTime/ActiveTime * 100 -} - // WiFiInterface is the template data for a virtual WiFi interface. type WiFiInterface struct { Name string @@ -207,6 +194,51 @@ func (h *WiFiHandler) Overview(w http.ResponseWriter, r *http.Request) { } } +// wifiSurveyData is the template data for the channel survey fragment of +// one radio: the chart after a scan, or why there is none. +type wifiSurveyData struct { + Radio string + SVG template.HTML + Error string +} + +// Survey runs the channel-survey action on a radio and renders the result +// as the survey fragment of the radio's card. The scan takes the radio off +// its channel for a few seconds, so it only runs on request. +// POST /wifi/{name}/survey +func (h *WiFiHandler) Survey(w http.ResponseWriter, r *http.Request) { + data := wifiSurveyData{Radio: r.PathValue("name")} + + var reply struct { + Output struct { + Channel []surveyChanJSON `json:"channel"` + } `json:"infix-hardware:output"` + } + + ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) + defer cancel() + + path := "/data/ietf-hardware:hardware/component=" + url.PathEscape(data.Radio) + + "/infix-hardware:wifi-radio/channel-survey" + if err := h.RC.CallRPC(ctx, path, nil, &reply); err != nil { + log.Printf("wifi: channel survey %s: %v", data.Radio, err) + data.Error = "Channel survey failed" + var re *restconf.Error + if errors.As(err, &re) && re.Message != "" { + data.Error = re.Message + } + } else if len(reply.Output.Channel) == 0 { + data.Error = "The radio reported no survey data" + } else { + data.SVG = renderSurveySVG(reply.Output.Channel) + } + + if err := h.Template.ExecuteTemplate(w, "wifi-survey", data); err != nil { + log.Printf("wifi: template error: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} + // buildWiFiRadios assembles the WiFiRadio slice from hardware components // and interface data, matching interfaces to their radio by name. func buildWiFiRadios(components []hwComponentWiFiJSON, ifaces []ifaceJSON) []WiFiRadio { @@ -226,6 +258,7 @@ func buildWiFiRadios(components []hwComponentWiFiJSON, ifaces []ifaceJSON) []WiF Driver: r.Driver, Channel: wifiChannelString(r.Channel), Manufacturer: c.MfgName, + Survey: wifiSurveyData{Radio: c.Name}, } // Capability flags: check per-band capabilities; if any band supports @@ -277,11 +310,6 @@ func buildWiFiRadios(components []hwComponentWiFiJSON, ifaces []ifaceJSON) []WiF radio.MaxAP = fmt.Sprintf("%d", r.MaxInterfaces.AP) } - // Generate channel survey SVG if survey data exists. - if r.Survey != nil && len(r.Survey.Channel) > 0 { - radio.SurveySVG = renderSurveySVG(r.Survey.Channel) - } - // Attach wifi interfaces that reference this radio. radio.Interfaces = buildWiFiInterfaces(c.Name, ifaces) diff --git a/src/webui/internal/handlers/wifi_survey_test.go b/src/webui/internal/handlers/wifi_survey_test.go new file mode 100644 index 000000000..9937ca7b6 --- /dev/null +++ b/src/webui/internal/handlers/wifi_survey_test.go @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +package handlers + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "infix/webui/internal/restconf" +) + +const surveyActionPath = "/data/ietf-hardware:hardware/component=radio0/infix-hardware:wifi-radio/channel-survey" + +// fakeSurveyAction serves the channel-survey action on radio0 with reply. +func fakeSurveyAction(t *testing.T, reply string, status int) *httptest.Server { + t.Helper() + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != surveyActionPath || r.Method != http.MethodPost { + t.Errorf("unexpected request %s %s", r.Method, r.URL.Path) + http.Error(w, "unexpected", http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/yang-data+json") + w.WriteHeader(status) + io.WriteString(w, reply) //nolint:errcheck + })) + t.Cleanup(srv.Close) + return srv +} + +func surveyRequest(t *testing.T, srv *httptest.Server) string { + t.Helper() + h := &WiFiHandler{ + Template: realTemplates(t, nil, "layouts/*.html", "pages/wifi.html"), + RC: restconf.NewClient(srv.URL, true), + } + req := httptest.NewRequest(http.MethodPost, "/wifi/radio0/survey", nil) + req.SetPathValue("name", "radio0") + req = req.WithContext(restconf.ContextWithCredentials(req.Context(), + restconf.Credentials{Username: "admin", Password: "secret"})) + rec := httptest.NewRecorder() + h.Survey(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status %d, want 200", rec.Code) + } + return rec.Body.String() +} + +func TestWiFiSurveyRendersChart(t *testing.T) { + reply := `{"infix-hardware:output":{"channel":[ + {"frequency":2412,"in-use":false,"noise":-92,"active-time":120,"busy-time":15}, + {"frequency":2437,"in-use":true,"noise":-92,"active-time":1000,"busy-time":125, + "receive-time":60,"transmit-time":30}]}}` + out := surveyRequest(t, fakeSurveyAction(t, reply, http.StatusOK)) + + for _, want := range []string{`class="survey-chart"`, "Scan again", `hx-post="/wifi/radio0/survey"`} { + if !strings.Contains(out, want) { + t.Errorf("missing %q in\n%s", want, out) + } + } + if strings.Contains(out, "alert-error") { + t.Errorf("unexpected error in\n%s", out) + } +} + +func TestWiFiSurveyShowsDeviceError(t *testing.T) { + reply := `{"ietf-restconf:errors":{"error":[{"error-type":"application", + "error-tag":"operation-failed", + "error-message":"Channel survey failed: no interface on radio0 to scan with"}]}}` + out := surveyRequest(t, fakeSurveyAction(t, reply, http.StatusInternalServerError)) + + for _, want := range []string{"alert-error", "no interface on radio0", "Scan channels"} { + if !strings.Contains(out, want) { + t.Errorf("missing %q in\n%s", want, out) + } + } + if strings.Contains(out, "survey-chart") { + t.Errorf("chart rendered on error:\n%s", out) + } +} + +func TestWiFiPageOffersSurveyScan(t *testing.T) { + tmpl := realTemplates(t, nil, "layouts/*.html", "pages/wifi.html") + comps := []hwComponentWiFiJSON{{Name: "radio0", WiFiRadio: &wifiRadioHWJSON{Band: "2.4GHz"}}} + data := wifiData{Radios: buildWiFiRadios(comps, nil)} + + var buf strings.Builder + if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil { + t.Fatalf("render: %v", err) + } + out := buf.String() + for _, want := range []string{`id="wifi-survey-radio0"`, `hx-post="/wifi/radio0/survey"`, "Scan channels"} { + if !strings.Contains(out, want) { + t.Errorf("missing %q in\n%s", want, out) + } + } + if strings.Contains(out, "survey-chart") { + t.Errorf("chart rendered before any scan:\n%s", out) + } +} diff --git a/src/webui/internal/server/server.go b/src/webui/internal/server/server.go index 61aee687a..3d4c11a5d 100644 --- a/src/webui/internal/server/server.go +++ b/src/webui/internal/server/server.go @@ -308,6 +308,7 @@ func New( mux.HandleFunc("POST /maintenance/system/datetime", sys.SetDatetime) mux.HandleFunc("GET /routing", routing.Overview) mux.HandleFunc("GET /wifi", wifi.Overview) + mux.HandleFunc("POST /wifi/{name}/survey", wifi.Survey) mux.HandleFunc("GET /hardware", hw.Overview) mux.HandleFunc("POST /hardware/locate", hw.Locate) mux.HandleFunc("GET /vpn", vpn.Overview) diff --git a/src/webui/templates/pages/wifi.html b/src/webui/templates/pages/wifi.html index 9cfdab16c..79d71d4c2 100644 --- a/src/webui/templates/pages/wifi.html +++ b/src/webui/templates/pages/wifi.html @@ -49,14 +49,12 @@ - {{if .SurveySVG}}
Channel Survey
-
- {{.SurveySVG}} +
+ {{template "wifi-survey" .Survey}}
- {{end}} {{range .Interfaces}} @@ -160,3 +158,23 @@ {{end}} {{end}} + +{{define "wifi-survey"}} +
+ {{if .SVG}} + {{.SVG}} + {{else if .Error}} +
{{.Error}}
+ {{else}} +

Scans every channel the radio supports and shows how busy each one is. + The radio leaves its channel for a few seconds, so traffic pauses during the scan.

+ {{end}} +
+
+ +
+{{end}} From de54120a0eef56163b9dd744ab823dfa547159c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 5 Oct 2026 16:02:12 +0200 Subject: [PATCH 17/45] doc: Document the WiFi channel survey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 3 +++ doc/wifi.md | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index a7cb0b79e..5bcd3266c 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -42,6 +42,9 @@ All notable changes to the project are documented in this file. the keystore. Its default route and DNS servers are used like those from a DHCP server, and the TCP MSS of forwarded connections is clamped to the session MTU, see [PPPoE Client][pppoe] +- WiFi channel survey is on request: `show hardware survey`, the + Scan channels button on the WebUI WiFi page, or the `channel-survey` + action. The always-on `survey` container under the radio is gone ### Fixes diff --git a/doc/wifi.md b/doc/wifi.md index f40a500df..1a1ff20bc 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -328,6 +328,32 @@ station is associated to. It appears only while connected. When several access points share one SSID (a roaming network), the `bssid` is what tells them apart, and it changes as the station roams between them. +### Channel Survey + +A channel survey shows how busy each channel is, which helps when picking a +channel for an access point or when a link performs worse than its signal +strength suggests. The radio has to leave its operating channel to measure +the others, so traffic on it pauses for a few seconds. Because of that the +survey only runs when asked for, it is not collected in the background. + +
admin@example:/> show hardware radio0 survey
+
+ +The output lists every channel the radio supports with its noise floor and +utilization, marks the operating channel, and suggests the least busy +channels per band. A radio that has no interface yet can be surveyed too, +which helps when picking a band and channel for it. Add `passive` to listen longer on each channel without sending +probe requests, which gives a steadier utilization reading at the cost of a +longer pause. + +In the WebUI, each radio on the WiFi page has a **Scan channels** button that +draws the same survey as a chart. + +Over NETCONF or RESTCONF the survey is the `channel-survey` action on the +radio's hardware component. Its output is a list of channels keyed by +frequency, with the busy, receive and transmit time out of the total time +the radio spent listening on each. + ## Passphrase Requirements To ensure your connection is secure and compatible with all network From 3e07731cf539efd6b0b94a9c227a5c658f923e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 5 Oct 2026 16:02:12 +0200 Subject: [PATCH 18/45] test: Add WiFi channel survey test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs the channel-survey action on a station associated to an AP and on the AP itself. Both transports get a call_action_output helper that returns the action output as a dict, lists included. Signed-off-by: Mattias Walström --- test/case/interfaces/wifi.yaml | 3 + .../wifi_channel_survey/Readme.adoc | 1 + .../interfaces/wifi_channel_survey/test.adoc | 34 ++++++++ .../interfaces/wifi_channel_survey/test.py | 86 +++++++++++++++++++ .../wifi_channel_survey/topology.dot | 44 ++++++++++ .../wifi_channel_survey/topology.svg | 68 +++++++++++++++ test/infamy/netconf.py | 29 +++++++ test/infamy/restconf.py | 12 +++ test/infamy/transport.py | 10 +++ test/infamy/wifi.py | 20 +++++ 10 files changed, 307 insertions(+) create mode 120000 test/case/interfaces/wifi_channel_survey/Readme.adoc create mode 100644 test/case/interfaces/wifi_channel_survey/test.adoc create mode 100755 test/case/interfaces/wifi_channel_survey/test.py create mode 100644 test/case/interfaces/wifi_channel_survey/topology.dot create mode 100644 test/case/interfaces/wifi_channel_survey/topology.svg diff --git a/test/case/interfaces/wifi.yaml b/test/case/interfaces/wifi.yaml index 0e5c28195..55efbeaf4 100644 --- a/test/case/interfaces/wifi.yaml +++ b/test/case/interfaces/wifi.yaml @@ -19,3 +19,6 @@ - name: WiFi station set up from a scan-only interface case: wifi_station_from_scan/test.py + +- name: WiFi channel survey on a connected radio + case: wifi_channel_survey/test.py diff --git a/test/case/interfaces/wifi_channel_survey/Readme.adoc b/test/case/interfaces/wifi_channel_survey/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/interfaces/wifi_channel_survey/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/interfaces/wifi_channel_survey/test.adoc b/test/case/interfaces/wifi_channel_survey/test.adoc new file mode 100644 index 000000000..44da16e43 --- /dev/null +++ b/test/case/interfaces/wifi_channel_survey/test.adoc @@ -0,0 +1,34 @@ +=== WiFi channel survey on a connected radio + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_channel_survey] + +==== Description + +A channel survey tells how busy each channel is. Collecting it means +leaving the operating channel, so it is not done in the background but +on request, with the channel-survey action on the radio. The action has +to work on a radio that is in use: here on the ap, which is serving a +station, and on the station, which is associated to the ap. + +Topology: +.... + host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host +.... + +==== Topology + +image::topology.svg[WiFi channel survey on a connected radio topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to the ap and the station +. Configure the ap as an Access Point on channel 1 and the station on radio0 +. Verify the station associates to the ap over the wifi link +. Run a channel survey on radio0 of the station +. Verify the station's survey reports 2412 MHz as the channel in use +. Verify the station's survey covers more channels than the one in use +. Run a channel survey on radio0 of the ap +. Verify the ap's survey reports 2412 MHz as the channel in use +. Verify the station is still associated to the ap after the surveys + + diff --git a/test/case/interfaces/wifi_channel_survey/test.py b/test/case/interfaces/wifi_channel_survey/test.py new file mode 100755 index 000000000..97d624b1c --- /dev/null +++ b/test/case/interfaces/wifi_channel_survey/test.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +r""" +WiFi channel survey on a connected radio + +A channel survey tells how busy each channel is. Collecting it means +leaving the operating channel, so it is not done in the background but +on request, with the channel-survey action on the radio. The action has +to work on a radio that is in use: here on the ap, which is serving a +station, and on the station, which is associated to the ap. + +Topology: +.... + host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host +.... +""" +import infamy +import infamy.wifi as wifi +from infamy.util import until, parallel + +SSID = "infix-survey" +PSK = "infixinfix" +FREQ = 2412 # channel 1 + + +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 as an Access Point on channel 1 and the station on radio0"): + parallel( + lambda: 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"}, + }, + }), + ]}}, + }), + lambda: station.put_config_dicts({ + "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}}, + "ietf-keystore": wifi.keystore({"wifi": PSK}), + "ietf-interfaces": {"interfaces": {"interface": [ + wifi.iface("wifi0", "02:00:00:00:00:02", { + "radio": "radio0", + "station": { + "ssid": SSID, + "security": {"mode": "auto", "secret": "wifi"}, + }, + }), + ]}}, + }), + ) + + with test.step("Verify the station associates to the ap over the wifi link"): + until(lambda: wifi.associated(station, SSID), attempts=60, interval=2) + + with test.step("Run a channel survey on radio0 of the station"): + channels = wifi.channel_survey(station, "radio0") + + with test.step("Verify the station's survey reports 2412 MHz as the channel in use"): + assert wifi.in_use_frequency(channels) == FREQ, channels + + with test.step("Verify the station's survey covers more channels than the one in use"): + assert len(channels) > 1, channels + + with test.step("Run a channel survey on radio0 of the ap"): + channels = wifi.channel_survey(ap, "radio0") + + with test.step("Verify the ap's survey reports 2412 MHz as the channel in use"): + assert wifi.in_use_frequency(channels) == FREQ, channels + + with test.step("Verify the station is still associated to the ap after the surveys"): + until(lambda: wifi.associated(station, SSID), attempts=30, interval=2) + + test.succeed() diff --git a/test/case/interfaces/wifi_channel_survey/topology.dot b/test/case/interfaces/wifi_channel_survey/topology.dot new file mode 100644 index 000000000..4e8fd2123 --- /dev/null +++ b/test/case/interfaces/wifi_channel_survey/topology.dot @@ -0,0 +1,44 @@ +graph "wifi-channel-survey" { + 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_channel_survey/topology.svg b/test/case/interfaces/wifi_channel_survey/topology.svg new file mode 100644 index 000000000..f78f26411 --- /dev/null +++ b/test/case/interfaces/wifi_channel_survey/topology.svg @@ -0,0 +1,68 @@ + + + + + + +wifi-channel-survey + + + +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/infamy/netconf.py b/test/infamy/netconf.py index 23242aebe..566d7a62a 100644 --- a/test/infamy/netconf.py +++ b/test/infamy/netconf.py @@ -441,6 +441,35 @@ def call_action(self, xpath, input_data=None): xml = "" + lyd.print_mem("xml", with_siblings=True, pretty=False) + "" return self.ncc.dispatch(xml) + def call_action_output(self, xpath, input_data=None): + """Call NETCONF action, returning the output as a nested dict""" + reply = self.call_action(xpath, input_data) + xml = reply.xml + if isinstance(xml, str): + xml = xml.encode() + + def to_dict(elem): + if not len(elem): + return (elem.text or "").strip() + out = {} + for child in elem: + name = lxml.etree.QName(child).localname + value = to_dict(child) + if name in out: + if not isinstance(out[name], list): + out[name] = [out[name]] + out[name].append(value) + else: + out[name] = value + return out + + output = to_dict(fromstring(xml)) + # Lists with one entry come back as a dict, lift them to a list + for key, value in output.items(): + if isinstance(value, dict) and key not in ("ok",): + output[key] = [value] + return output + def get_schemas_list(self): schemas = [] data = self.get_dict("/netconf-state") diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py index b23a90690..0df048900 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -542,6 +542,18 @@ def call_action(self, xpath, input_data=None): return response.content + def call_action_output(self, xpath, input_data=None): + """Call RESTCONF action, returning the output as a nested dict""" + content = self.call_action(xpath, input_data) + if not content: + return {} + + data = json.loads(content) + for key, value in data.items(): + if key.endswith(":output"): + return value + return data + def delete_xpath(self, xpath): """Delete XPath from running config""" coverage.track_xpath(xpath) diff --git a/test/infamy/transport.py b/test/infamy/transport.py index 8866d6f4b..d8d36a2d1 100644 --- a/test/infamy/transport.py +++ b/test/infamy/transport.py @@ -75,6 +75,16 @@ def call_action(self, xpath, input_data=None): """ pass + @abstractmethod + def call_action_output(self, xpath, input_data=None): + """Invoke a YANG action at `xpath`, returning its output. + + The output is a nested dict of the action's output nodes, lists + as Python lists. Leaf values are strings on NETCONF and typed + on RESTCONF, compare with str(). + """ + pass + def __getitem__(self, key): if key in self.mapping: return self.mapping[key] diff --git a/test/infamy/wifi.py b/test/infamy/wifi.py index 92eb8473d..c670542ba 100644 --- a/test/infamy/wifi.py +++ b/test/infamy/wifi.py @@ -74,6 +74,26 @@ def wds_link(name, ap, peer, bridge=None, pvid=None): return ifc +def channel_survey(target, radio="radio0", passive=False): + """Run the channel-survey action on radio, return its channel list. + + Each entry has at least a frequency, the operating channel has + in-use set. Values are strings on NETCONF, compare with str(). + """ + xpath = f"/ietf-hardware:hardware/component[name='{radio}']" \ + "/infix-hardware:wifi-radio/channel-survey" + output = target.call_action_output(xpath, {"passive": passive} if passive else None) + return output.get("channel", []) + + +def in_use_frequency(channels): + """Frequency in MHz of the channel marked in-use, or None.""" + for ch in channels: + if str(ch.get("in-use")).lower() == "true": + return int(ch["frequency"]) + return None + + def skip_unless_supported(test, *targets): """Skip the test unless every target advertises the wifi feature.""" for target in targets: From e15713f9def60b45883b355e2c2d89076d75e652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 6 Oct 2026 10:56:13 +0200 Subject: [PATCH 19/45] yang: Limit WiFi country codes to the regulatory database MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel ignores a country code the wireless regulatory database has no rules for, so 69 of the ISO codes validated fine and then left the radios in the world domain without a word. Keep the 181 countries the database knows plus '00', say what the world domain means, and drop the CAPWAP references that never applied. Signed-off-by: Mattias Walström --- .../yang/confd/infix-wifi-country-codes.yang | 107 ++++-------------- ... infix-wifi-country-codes@2026-10-06.yang} | 0 2 files changed, 23 insertions(+), 84 deletions(-) rename src/confd/yang/confd/{infix-wifi-country-codes@2025-11-28.yang => infix-wifi-country-codes@2026-10-06.yang} (100%) diff --git a/src/confd/yang/confd/infix-wifi-country-codes.yang b/src/confd/yang/confd/infix-wifi-country-codes.yang index 9c797e42a..5b6d01f74 100644 --- a/src/confd/yang/confd/infix-wifi-country-codes.yang +++ b/src/confd/yang/confd/infix-wifi-country-codes.yang @@ -1,6 +1,6 @@ module infix-wifi-country-codes { yang-version 1.1; - namespace "urn:infix:wifi-country-codes"; + namespace "urn:infix:wifi-country-codes:ns:yang:1.0"; prefix iwcc; organization "KernelKit"; @@ -8,46 +8,48 @@ module infix-wifi-country-codes { contact "kernelkit@googlegroups.com"; description - "This module defines country codes for WiFi regulatory domain - configuration based on ISO 3166-1 alpha-2 standard. + "Country codes for the WiFi regulatory domain, ISO 3166-1 alpha-2, + limited to the countries the Linux wireless regulatory database + has rules for."; - This model provides country code definitions for use in - 802.11 wireless LAN regulatory compliance configuration. + revision 2026-10-06 { + description + "Limit the list to the countries the Linux regulatory database has + rules for, a code without rules is silently ignored by the kernel + and leaves the radios in the world domain."; + reference + "wireless-regdb 2026.09.03"; + } - The regulatory domain configuration follows the principles - established in IETF RFCs for wireless access point management."; revision 2025-11-28 { description "Add support for 00 - World regulatory domain."; reference - "Internal"; - } + "Internal"; + } + revision 2025-06-02 { description "Initial revision for WiFi country code support."; reference - "RFC 5415: Control And Provisioning of Wireless Access Points (CAPWAP) Protocol Specification - RFC 5416: Control and Provisioning of Wireless Access Points (CAPWAP) Protocol Binding for IEEE 802.11"; + "ISO 3166-1:2020 Codes for the representation of names of countries + and their subdivisions -- Part 1: Country codes"; } typedef country-code { type enumeration { - enum "00" { description "World regulatory domain (no country restrictions)"; } + enum "00" { description "World regulatory domain, the most restrictive one: no 6 GHz, listen-only on 5 GHz, no access point"; } enum "AD" { description "Andorra"; } enum "AE" { description "United Arab Emirates"; } enum "AF" { description "Afghanistan"; } - enum "AG" { description "Antigua and Barbuda"; } enum "AI" { description "Anguilla"; } enum "AL" { description "Albania"; } enum "AM" { description "Armenia"; } - enum "AO" { description "Angola"; } - enum "AQ" { description "Antarctica"; } enum "AR" { description "Argentina"; } enum "AS" { description "American Samoa"; } enum "AT" { description "Austria"; } enum "AU" { description "Australia"; } enum "AW" { description "Aruba"; } - enum "AX" { description "Åland Islands"; } enum "AZ" { description "Azerbaijan"; } enum "BA" { description "Bosnia and Herzegovina"; } enum "BB" { description "Barbados"; } @@ -56,41 +58,29 @@ module infix-wifi-country-codes { enum "BF" { description "Burkina Faso"; } enum "BG" { description "Bulgaria"; } enum "BH" { description "Bahrain"; } - enum "BI" { description "Burundi"; } - enum "BJ" { description "Benin"; } enum "BL" { description "Saint Barthélemy"; } enum "BM" { description "Bermuda"; } enum "BN" { description "Brunei Darussalam"; } enum "BO" { description "Bolivia"; } - enum "BQ" { description "Bonaire, Sint Eustatius and Saba"; } enum "BR" { description "Brazil"; } enum "BS" { description "Bahamas"; } enum "BT" { description "Bhutan"; } - enum "BV" { description "Bouvet Island"; } enum "BW" { description "Botswana"; } enum "BY" { description "Belarus"; } enum "BZ" { description "Belize"; } enum "CA" { description "Canada"; } - enum "CC" { description "Cocos (Keeling) Islands"; } - enum "CD" { description "Congo, Democratic Republic of the"; } enum "CF" { description "Central African Republic"; } - enum "CG" { description "Congo"; } enum "CH" { description "Switzerland"; } enum "CI" { description "Côte d'Ivoire"; } - enum "CK" { description "Cook Islands"; } enum "CL" { description "Chile"; } - enum "CM" { description "Cameroon"; } enum "CN" { description "China"; } enum "CO" { description "Colombia"; } enum "CR" { description "Costa Rica"; } enum "CU" { description "Cuba"; } - enum "CV" { description "Cabo Verde"; } - enum "CW" { description "Curaçao"; } enum "CX" { description "Christmas Island"; } enum "CY" { description "Cyprus"; } enum "CZ" { description "Czechia"; } enum "DE" { description "Germany"; } - enum "DJ" { description "Djibouti"; } enum "DK" { description "Denmark"; } enum "DM" { description "Dominica"; } enum "DO" { description "Dominican Republic"; } @@ -98,37 +88,25 @@ module infix-wifi-country-codes { enum "EC" { description "Ecuador"; } enum "EE" { description "Estonia"; } enum "EG" { description "Egypt"; } - enum "EH" { description "Western Sahara"; } - enum "ER" { description "Eritrea"; } enum "ES" { description "Spain"; } enum "ET" { description "Ethiopia"; } enum "FI" { description "Finland"; } - enum "FJ" { description "Fiji"; } - enum "FK" { description "Falkland Islands (Malvinas)"; } enum "FM" { description "Micronesia"; } enum "FO" { description "Faroe Islands"; } enum "FR" { description "France"; } - enum "GA" { description "Gabon"; } enum "GB" { description "United Kingdom"; } enum "GD" { description "Grenada"; } enum "GE" { description "Georgia"; } enum "GF" { description "French Guiana"; } - enum "GG" { description "Guernsey"; } enum "GH" { description "Ghana"; } enum "GI" { description "Gibraltar"; } enum "GL" { description "Greenland"; } - enum "GM" { description "Gambia"; } - enum "GN" { description "Guinea"; } enum "GP" { description "Guadeloupe"; } - enum "GQ" { description "Equatorial Guinea"; } enum "GR" { description "Greece"; } - enum "GS" { description "South Georgia and the South Sandwich Islands"; } enum "GT" { description "Guatemala"; } enum "GU" { description "Guam"; } - enum "GW" { description "Guinea-Bissau"; } enum "GY" { description "Guyana"; } enum "HK" { description "Hong Kong"; } - enum "HM" { description "Heard Island and McDonald Islands"; } enum "HN" { description "Honduras"; } enum "HR" { description "Croatia"; } enum "HT" { description "Haiti"; } @@ -138,71 +116,52 @@ module infix-wifi-country-codes { enum "IL" { description "Israel"; } enum "IM" { description "Isle of Man"; } enum "IN" { description "India"; } - enum "IO" { description "British Indian Ocean Territory"; } - enum "IQ" { description "Iraq"; } enum "IR" { description "Iran"; } enum "IS" { description "Iceland"; } enum "IT" { description "Italy"; } - enum "JE" { description "Jersey"; } enum "JM" { description "Jamaica"; } enum "JO" { description "Jordan"; } enum "JP" { description "Japan"; } enum "KE" { description "Kenya"; } - enum "KG" { description "Kyrgyzstan"; } enum "KH" { description "Cambodia"; } - enum "KI" { description "Kiribati"; } - enum "KM" { description "Comoros"; } enum "KN" { description "Saint Kitts and Nevis"; } enum "KP" { description "Korea, Democratic People's Republic of"; } enum "KR" { description "Korea, Republic of"; } enum "KW" { description "Kuwait"; } enum "KY" { description "Cayman Islands"; } enum "KZ" { description "Kazakhstan"; } - enum "LA" { description "Lao People's Democratic Republic"; } enum "LB" { description "Lebanon"; } enum "LC" { description "Saint Lucia"; } enum "LI" { description "Liechtenstein"; } enum "LK" { description "Sri Lanka"; } - enum "LR" { description "Liberia"; } enum "LS" { description "Lesotho"; } enum "LT" { description "Lithuania"; } enum "LU" { description "Luxembourg"; } enum "LV" { description "Latvia"; } - enum "LY" { description "Libya"; } enum "MA" { description "Morocco"; } enum "MC" { description "Monaco"; } enum "MD" { description "Moldova"; } enum "ME" { description "Montenegro"; } enum "MF" { description "Saint Martin (French part)"; } - enum "MG" { description "Madagascar"; } enum "MH" { description "Marshall Islands"; } enum "MK" { description "North Macedonia"; } - enum "ML" { description "Mali"; } - enum "MM" { description "Myanmar"; } enum "MN" { description "Mongolia"; } enum "MO" { description "Macao"; } enum "MP" { description "Northern Mariana Islands"; } enum "MQ" { description "Martinique"; } enum "MR" { description "Mauritania"; } - enum "MS" { description "Montserrat"; } enum "MT" { description "Malta"; } enum "MU" { description "Mauritius"; } enum "MV" { description "Maldives"; } enum "MW" { description "Malawi"; } enum "MX" { description "Mexico"; } enum "MY" { description "Malaysia"; } - enum "MZ" { description "Mozambique"; } enum "NA" { description "Namibia"; } - enum "NC" { description "New Caledonia"; } - enum "NE" { description "Niger"; } - enum "NF" { description "Norfolk Island"; } enum "NG" { description "Nigeria"; } enum "NI" { description "Nicaragua"; } enum "NL" { description "Netherlands"; } enum "NO" { description "Norway"; } enum "NP" { description "Nepal"; } - enum "NR" { description "Nauru"; } - enum "NU" { description "Niue"; } enum "NZ" { description "New Zealand"; } enum "OM" { description "Oman"; } enum "PA" { description "Panama"; } @@ -213,9 +172,7 @@ module infix-wifi-country-codes { enum "PK" { description "Pakistan"; } enum "PL" { description "Poland"; } enum "PM" { description "Saint Pierre and Miquelon"; } - enum "PN" { description "Pitcairn"; } enum "PR" { description "Puerto Rico"; } - enum "PS" { description "Palestine, State of"; } enum "PT" { description "Portugal"; } enum "PW" { description "Palau"; } enum "PY" { description "Paraguay"; } @@ -226,52 +183,33 @@ module infix-wifi-country-codes { enum "RU" { description "Russian Federation"; } enum "RW" { description "Rwanda"; } enum "SA" { description "Saudi Arabia"; } - enum "SB" { description "Solomon Islands"; } - enum "SC" { description "Seychelles"; } - enum "SD" { description "Sudan"; } enum "SE" { description "Sweden"; } enum "SG" { description "Singapore"; } - enum "SH" { description "Saint Helena, Ascension and Tristan da Cunha"; } enum "SI" { description "Slovenia"; } - enum "SJ" { description "Svalbard and Jan Mayen"; } enum "SK" { description "Slovakia"; } - enum "SL" { description "Sierra Leone"; } enum "SM" { description "San Marino"; } enum "SN" { description "Senegal"; } - enum "SO" { description "Somalia"; } enum "SR" { description "Suriname"; } - enum "SS" { description "South Sudan"; } - enum "ST" { description "Sao Tome and Principe"; } enum "SV" { description "El Salvador"; } enum "SX" { description "Sint Maarten (Dutch part)"; } enum "SY" { description "Syrian Arab Republic"; } - enum "SZ" { description "Eswatini"; } enum "TC" { description "Turks and Caicos Islands"; } enum "TD" { description "Chad"; } - enum "TF" { description "French Southern Territories"; } enum "TG" { description "Togo"; } enum "TH" { description "Thailand"; } - enum "TJ" { description "Tajikistan"; } - enum "TK" { description "Tokelau"; } - enum "TL" { description "Timor-Leste"; } - enum "TM" { description "Turkmenistan"; } enum "TN" { description "Tunisia"; } - enum "TO" { description "Tonga"; } enum "TR" { description "Turkey"; } enum "TT" { description "Trinidad and Tobago"; } - enum "TV" { description "Tuvalu"; } enum "TW" { description "Taiwan"; } enum "TZ" { description "Tanzania"; } enum "UA" { description "Ukraine"; } enum "UG" { description "Uganda"; } - enum "UM" { description "United States Minor Outlying Islands"; } enum "US" { description "United States of America"; } enum "UY" { description "Uruguay"; } enum "UZ" { description "Uzbekistan"; } enum "VA" { description "Holy See (Vatican City State)"; } enum "VC" { description "Saint Vincent and the Grenadines"; } enum "VE" { description "Venezuela"; } - enum "VG" { description "Virgin Islands, British"; } enum "VI" { description "Virgin Islands, U.S."; } enum "VN" { description "Viet Nam"; } enum "VU" { description "Vanuatu"; } @@ -280,14 +218,15 @@ module infix-wifi-country-codes { enum "YE" { description "Yemen"; } enum "YT" { description "Mayotte"; } enum "ZA" { description "South Africa"; } - enum "ZM" { description "Zambia"; } enum "ZW" { description "Zimbabwe"; } } description - "Complete list of ISO 3166-1 alpha-2 country codes for - regulatory domain configuration."; + "ISO 3166-1 alpha-2 country codes for the WiFi regulatory domain, + limited to the countries the Linux wireless regulatory database + has rules for, plus '00' for the world domain."; reference "ISO 3166-1:2020 Codes for the representation of names of countries - and their subdivisions -- Part 1: Country codes"; + and their subdivisions -- Part 1: Country codes + https://git.kernel.org/pub/scm/linux/kernel/git/wens/wireless-regdb.git"; } } diff --git a/src/confd/yang/confd/infix-wifi-country-codes@2025-11-28.yang b/src/confd/yang/confd/infix-wifi-country-codes@2026-10-06.yang similarity index 100% rename from src/confd/yang/confd/infix-wifi-country-codes@2025-11-28.yang rename to src/confd/yang/confd/infix-wifi-country-codes@2026-10-06.yang From 03ef83f254876fa06e8d4cd80d498ecdc5a326b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 6 Oct 2026 10:37:38 +0200 Subject: [PATCH 20/45] confd: wifi: Move the country code to a box-wide setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regulatory domain is one setting in the kernel, yet the model asked for a country code per radio, and it only ever reached the kernel once a radio had an interface, through hostapd or wpa_supplicant. A radio without one sat in the world domain, with no 6 GHz and a listen-only 5 GHz band, which is exactly when a channel survey is wanted. Replace the per-radio leaf with hardware/wifi/country-code, default "00", and apply the domain from confd on every change. Access points and mesh points require a real country. Bump confd to 1.11 and migrate existing configurations: the first radio naming a country wins, radios left at "00" set nothing. Signed-off-by: Mattias Walström --- .../etc/factory-config.cfg | 10 ++-- .../bananapi,bpi-r3/etc/factory-config.cfg | 9 ++-- .../etc/factory-config.cfg | 9 ++-- .../etc/factory-config.cfg | 2 +- .../bananapi,bpi-r4/etc/factory-config.cfg | 2 +- .../bananapi,bpi-r64/etc/factory-config.cfg | 8 +-- .../etc/factory-config.cfg | 2 +- board/aarch64/raspberrypi-rpi64/README.md | 10 ++-- .../etc/factory-config.cfg | 36 +------------ .../raspberrypi,400/etc/factory-config.cfg | 7 +-- .../lemaker,bananapi/etc/factory-config.cfg | 2 +- .../etc/factory-config.cfg | 2 +- doc/ChangeLog.md | 3 ++ doc/wifi.md | 41 +++++++------- src/confd/bin/gen-hardware | 1 - src/confd/configure.ac | 3 +- .../migrate/1.11/10-wifi-country-code.sh | 52 ++++++++++++++++++ src/confd/share/migrate/1.11/Makefile.am | 2 + src/confd/share/migrate/Makefile.am | 2 +- src/confd/src/hardware.c | 36 +++++++++++-- src/confd/src/if-wifi.c | 13 +++-- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-hardware.yang | 54 ++++++++++++------- ...05.yang => infix-hardware@2026-10-06.yang} | 0 src/confd/yang/confd/infix-if-wifi.yang | 15 ++++-- ...-02.yang => infix-if-wifi@2026-10-06.yang} | 0 .../interfaces/wifi_ap_multi_station/test.py | 5 +- .../interfaces/wifi_ap_station_2dut/test.py | 5 +- .../interfaces/wifi_band_steering/test.py | 7 ++- .../interfaces/wifi_channel_survey/test.py | 5 +- .../case/interfaces/wifi_mesh_roaming/test.py | 8 ++- .../interfaces/wifi_station_from_scan/test.py | 5 +- .../interfaces/wifi_wds_link_2dut/test.py | 5 +- .../case/interfaces/wifi_wds_repeater/test.py | 8 ++- test/infamy/wifi.py | 12 ++++- 35 files changed, 231 insertions(+), 152 deletions(-) create mode 100644 src/confd/share/migrate/1.11/10-wifi-country-code.sh create mode 100644 src/confd/share/migrate/1.11/Makefile.am rename src/confd/yang/confd/{infix-hardware@2026-10-05.yang => infix-hardware@2026-10-06.yang} (100%) rename src/confd/yang/confd/{infix-if-wifi@2026-10-02.yang => infix-if-wifi@2026-10-06.yang} (100%) diff --git a/board/aarch64/acer-connect-vero-w6m/rootfs/usr/share/product/acer,connect-vero-w/etc/factory-config.cfg b/board/aarch64/acer-connect-vero-w6m/rootfs/usr/share/product/acer,connect-vero-w/etc/factory-config.cfg index d8d42b8f6..eb1e5b9da 100644 --- a/board/aarch64/acer-connect-vero-w6m/rootfs/usr/share/product/acer,connect-vero-w/etc/factory-config.cfg +++ b/board/aarch64/acer-connect-vero-w6m/rootfs/usr/share/product/acer,connect-vero-w/etc/factory-config.cfg @@ -22,7 +22,6 @@ "name": "radio0", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "2.4GHz", "channel": "auto" } @@ -31,7 +30,6 @@ "name": "radio1", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "6GHz", "channel": "auto" } @@ -40,12 +38,14 @@ "name": "radio2", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "5GHz", "channel": "auto" } } - ] + ], + "infix-hardware:wifi": { + "country-code": "DE" + } }, "ietf-interfaces:interfaces": { "interface": [ @@ -457,7 +457,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg index 799bb7aef..c018183b3 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg @@ -22,7 +22,6 @@ "name": "radio0", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "2.4GHz", "channel": "auto" } @@ -31,12 +30,14 @@ "name": "radio1", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "5GHz", "channel": "auto" } } - ] + ], + "infix-hardware:wifi": { + "country-code": "DE" + } }, "ietf-interfaces:interfaces": { "interface": [ @@ -450,7 +451,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg index a4b6256cb..182c6f535 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/factory-config.cfg @@ -22,7 +22,6 @@ "name": "radio0", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "2.4GHz", "channel": "auto" } @@ -31,12 +30,14 @@ "name": "radio1", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "5GHz", "channel": "auto" } } - ] + ], + "infix-hardware:wifi": { + "country-code": "DE" + } }, "ietf-interfaces:interfaces": { "interface": [ @@ -416,7 +417,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg index 78c9a69bd..c95c464d5 100644 --- a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4-2g5/etc/factory-config.cfg @@ -374,7 +374,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg index d5fa27042..7b6d66926 100644 --- a/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r4/rootfs/usr/share/product/bananapi,bpi-r4/etc/factory-config.cfg @@ -366,7 +366,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg index af616e23c..632787cbd 100644 --- a/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r64/rootfs/usr/share/product/bananapi,bpi-r64/etc/factory-config.cfg @@ -15,12 +15,14 @@ "name": "radio0", "class": "infix-hardware:wifi", "infix-hardware:wifi-radio": { - "country-code": "DE", "band": "2.4GHz", "channel": "auto" } } - ] + ], + "infix-hardware:wifi": { + "country-code": "DE" + } }, "ietf-interfaces:interfaces": { "interface": [ @@ -408,7 +410,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg index ea01bc806..3ef1d99aa 100644 --- a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg +++ b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg @@ -350,7 +350,7 @@ ] }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/raspberrypi-rpi64/README.md b/board/aarch64/raspberrypi-rpi64/README.md index 711bc2eb4..68151d68b 100644 --- a/board/aarch64/raspberrypi-rpi64/README.md +++ b/board/aarch64/raspberrypi-rpi64/README.md @@ -118,19 +118,19 @@ Then configure the WiFi interface using the keystore reference: ``` admin@infix:/> configure +admin@infix:/config/> set hardware wifi country-code US admin@infix:/config/> edit interface wifi0 admin@infix:/config/interface/wifi0/> set ipv4 dhcp-client -admin@infix:/config/interface/wifi0/> set wifi ssid YourNetworkName -admin@infix:/config/interface/wifi0/> set wifi secret mywifi -admin@infix:/config/interface/wifi0/> set wifi country-code US +admin@infix:/config/interface/wifi0/> set wifi station ssid YourNetworkName +admin@infix:/config/interface/wifi0/> set wifi station security secret mywifi admin@infix:/config/interface/wifi0/> leave ``` > [!NOTE] > The WiFi password (8-63 characters) is stored securely in the keystore as > `mywifi` (or any name you choose), which is then referenced in the WiFi -> configuration. The country-code must match your location for regulatory -> compliance (e.g., US, SE, DE, JP). +> configuration. The country code is one setting for all radios and must +> match your location for regulatory compliance (e.g., US, SE, DE, JP). ### Touch Screen Support diff --git a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg index 33998c5d0..09f87bfd0 100644 --- a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg +++ b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg @@ -13,10 +13,7 @@ }, { "name": "radio0", - "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "00" - } + "class": "infix-hardware:wifi" } ] }, @@ -245,35 +242,6 @@ } ] }, - "infix-schedule:schedules": { - "schedule": [ - { - "name": "nightly", - "description": "Every night at 03:00", - "recurrence": { - "frequency": "ietf-schedule:daily", - "byhour": [ - 3 - ] - } - }, - { - "name": "weekly", - "description": "Sunday nights at 03:00", - "recurrence": { - "frequency": "ietf-schedule:weekly", - "byday": [ - { - "weekday": "sunday" - } - ], - "byhour": [ - 3 - ] - } - } - ] - }, "ntp": { "enabled": true, "server": [ @@ -298,7 +266,7 @@ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg index 0094635ba..4a4f80e5f 100644 --- a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg +++ b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,400/etc/factory-config.cfg @@ -27,10 +27,7 @@ }, { "name": "radio0", - "class": "infix-hardware:wifi", - "infix-hardware:wifi-radio": { - "country-code": "00" - } + "class": "infix-hardware:wifi" } ] }, @@ -283,7 +280,7 @@ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg index e39e37c1d..fef2ca84d 100644 --- a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg +++ b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg @@ -272,7 +272,7 @@ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg b/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg index 75cca7917..4b4eb4cc0 100644 --- a/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg +++ b/board/arm/raspberrypi-rpi2/rootfs/usr/share/product/raspberrypi,2-model-b/etc/factory-config.cfg @@ -254,7 +254,7 @@ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, "infix-meta:meta": { - "version": "1.10" + "version": "1.11" }, "infix-services:mdns": { "enabled": true diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 5bcd3266c..6aa119d03 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -42,6 +42,9 @@ All notable changes to the project are documented in this file. the keystore. Its default route and DNS servers are used like those from a DHCP server, and the TCP MSS of forwarded connections is clamped to the session MTU, see [PPPoE Client][pppoe] +- The WiFi country code is one setting for the whole system, `hardware + wifi country-code`, instead of one per radio, and applies as soon as it + is set. Existing configurations are migrated - WiFi channel survey is on request: `show hardware survey`, the Scan channels button on the WebUI WiFi page, or the `channel-survey` action. The always-on `survey` container under the radio is gone diff --git a/doc/wifi.md b/doc/wifi.md index 1a1ff20bc..515e10323 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -95,14 +95,21 @@ Radios are automatically discovered and named `radio0`, `radio1`, etc. ### Country Code ⚠ -The radio defaults to "00" for World domain, but some systems may ship with a -factory default country code (typically "DE" for the BPi-R3). +The country code is one setting for all radios in the system, since the +regulatory domain is one setting in the kernel. It defaults to "00", the +world domain: no 6 GHz, listen-only on 5 GHz, and no access point or mesh +point can be configured. Some systems ship with a factory default +(typically "DE" for the BPi-R3). + +
admin@example:/> configure
+admin@example:/config/> set hardware wifi country-code DE
+admin@example:/config/> leave
+
> [!IMPORTANT] Legal notice! -> The `country-code` setting is **legally required** and determines -> which WiFi channels and power levels are permitted in your -> location. Using an incorrect country code may violate local wireless -> regulations. +> The country code is **legally required** and determines which WiFi +> channels and power levels are permitted in your location. Using an +> incorrect country code may violate local wireless regulations. **Common country codes, see [ISO 3166-1 alpha-2][1] for the complete list**: @@ -123,13 +130,13 @@ factory default country code (typically "DE" for the BPi-R3). ### Basic Radio Setup -Configure the radio with channel, power, and regulatory domain. +Configure the radio with band and channel. The country code above covers +all radios. **For Station (client) mode:**
admin@example:/> configure
 admin@example:/config/> edit hardware component radio0 wifi-radio
-admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
 admin@example:/config/hardware/component/radio0/wifi-radio/> leave
 
@@ -137,7 +144,6 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave
admin@example:/> configure
 admin@example:/config/> edit hardware component radio0 wifi-radio
-admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
 admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz
 admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36
 admin@example:/config/hardware/component/radio0/wifi-radio/> set channel-width 80MHz
@@ -146,8 +152,6 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave
 
 **Key radio parameters:**
 
-- `country-code`: Two-letter [ISO 3166-1 alpha-2][1] code, determines allowed
-  channels and maximum power. Examples: US, DE, GB, SE, FR, JP.  
   **⚠ Must match your physical location for legal compliance! ⚠**
 - `band`: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Automatically enables
   appropriate WiFi standards:
@@ -273,7 +277,6 @@ interface referencing it:
 
 
admin@example:/> configure
 admin@example:/config/> edit hardware component radio0 wifi-radio
-admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
 admin@example:/config/hardware/component/radio0/wifi-radio/> leave
 
@@ -499,8 +502,8 @@ IoT devices, or segregating traffic into different VLANs. **Step 1: Configure the radio** (shared by all APs)
admin@example:/> configure
+admin@example:/config/> set hardware wifi country-code DE
 admin@example:/config/> edit hardware component radio0 wifi-radio
-admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
 admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz
 admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36
 admin@example:/config/hardware/component/radio0/wifi-radio/> leave
@@ -770,15 +773,15 @@ vendors without proprietary components.
 
 ### Mesh configuration
 
-A mesh point requires the radio to have `band`, `channel`, and a valid
-`country-code` configured. Mesh and AP modes cannot coexist on the same
-radio.
+A mesh point requires the radio to have `band` and `channel` configured,
+and the system a country code.  Mesh and AP modes cannot coexist on the
+same radio.
 
 **Step 1: Configure the radio**
 
 
admin@example:/> configure
+admin@example:/config/> set hardware wifi country-code DE
 admin@example:/config/> edit hardware component radio1 wifi-radio
-admin@example:/config/hardware/component/radio1/wifi-radio/> set country-code DE
 admin@example:/config/hardware/component/radio1/wifi-radio/> set band 5GHz
 admin@example:/config/hardware/component/radio1/wifi-radio/> set channel 36
 admin@example:/config/hardware/component/radio1/wifi-radio/> leave
@@ -968,8 +971,8 @@ If issues arise, try the following troubleshooting steps:
    the passphrase matches the network password
 3. **Review logs**: Check system logs with `show log` for Wi-Fi related
    errors
-4. **Regulatory compliance**: Ensure the country-code on the radio
-   matches your location
+4. **Regulatory compliance**: Ensure the WiFi country code matches your
+   location
 5. **Hardware detection**: Confirm the WiFi radio appears in `show
    hardware`
 
diff --git a/src/confd/bin/gen-hardware b/src/confd/bin/gen-hardware
index 8679bc730..3870a7302 100755
--- a/src/confd/bin/gen-hardware
+++ b/src/confd/bin/gen-hardware
@@ -53,7 +53,6 @@ gen_radio()
     "name": "$radio",
     "class": "infix-hardware:wifi",
     "infix-hardware:wifi-radio": {
-        "country-code": "00",
         "band": "$band",
         "channel": "auto"
     }
diff --git a/src/confd/configure.ac b/src/confd/configure.ac
index 28f0c6e2f..1a13a1ffb 100644
--- a/src/confd/configure.ac
+++ b/src/confd/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ(2.61)
 # confd version is same as system YANG model version, step on breaking changes
-AC_INIT([confd], [1.10], [https://github.com/kernelkit/infix/issues])
+AC_INIT([confd], [1.11], [https://github.com/kernelkit/infix/issues])
 AM_INIT_AUTOMAKE(1.11 foreign subdir-objects)
 AM_SILENT_RULES(yes)
 
@@ -24,6 +24,7 @@ AC_CONFIG_FILES([
 	share/migrate/1.8/Makefile
 	share/migrate/1.9/Makefile
 	share/migrate/1.10/Makefile
+	share/migrate/1.11/Makefile
 	yang/Makefile
 	yang/confd/Makefile
 	yang/test-mode/Makefile
diff --git a/src/confd/share/migrate/1.11/10-wifi-country-code.sh b/src/confd/share/migrate/1.11/10-wifi-country-code.sh
new file mode 100644
index 000000000..b3c3f1028
--- /dev/null
+++ b/src/confd/share/migrate/1.11/10-wifi-country-code.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# Move the WiFi country code from each radio to the box-wide
+# hardware/wifi/country-code leaf.
+#
+# The regulatory domain is one setting in the kernel, so a code per radio
+# was misleading, and it was only ever applied once the radio had an
+# interface.  The first radio naming a real country wins.  A radio left
+# at the world domain ("00") sets nothing, that is the default without
+# the leaf.
+#
+# The leaf only takes the codes the regulatory database knows.  The old
+# per-radio leaf accepted any ISO code, and a code the database lacks
+# never did anything, so such a code becomes the world domain here.
+
+file=$1
+temp=${file}.tmp
+yang=${WIFI_COUNTRY_CODES_YANG:-$(ls /usr/share/yang/modules/confd/infix-wifi-country-codes@*.yang 2>/dev/null | tail -1)}
+
+codes=$(jq -r '[ (.["ietf-hardware:hardware"].component // [])[]
+                 | .["infix-hardware:wifi-radio"]?["country-code"]? // empty
+                 | select(. != "00") ] | join(" ")' "$file")
+
+# The radios could disagree, the kernel has only one domain, so whichever
+# daemon started last won.  Say which one the migration keeps.
+case $(echo "$codes" | tr ' ' '\n' | sort -u | wc -l) in
+    0|1) ;;
+    *)
+        logger -t migrate -p user.warning \
+            "$file: radios have different country codes ($codes), keeping the first radio's"
+        ;;
+esac
+
+cc=
+for code in $codes; do
+    if [ -n "$yang" ] && ! grep -qF "enum \"$code\"" "$yang"; then
+        logger -t migrate -p user.warning \
+            "$file: WiFi country code $code is not in the regulatory database, using the world domain"
+        continue
+    fi
+    cc=$code
+    break
+done
+
+jq --arg cc "$cc" '
+  if $cc != "" then
+    .["ietf-hardware:hardware"]["infix-hardware:wifi"] = {"country-code": $cc}
+  else . end
+  | if .["ietf-hardware:hardware"].component then
+      .["ietf-hardware:hardware"].component |=
+          [ .[] | del(.["infix-hardware:wifi-radio"]["country-code"]) ]
+    else . end
+' "$file" > "$temp" && mv "$temp" "$file"
diff --git a/src/confd/share/migrate/1.11/Makefile.am b/src/confd/share/migrate/1.11/Makefile.am
new file mode 100644
index 000000000..f1a52eed3
--- /dev/null
+++ b/src/confd/share/migrate/1.11/Makefile.am
@@ -0,0 +1,2 @@
+migratedir          = $(pkgdatadir)/migrate/1.11
+dist_migrate_DATA   = 10-wifi-country-code.sh
diff --git a/src/confd/share/migrate/Makefile.am b/src/confd/share/migrate/Makefile.am
index 755ac16a4..2f73b3c33 100644
--- a/src/confd/share/migrate/Makefile.am
+++ b/src/confd/share/migrate/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS             = 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10
+SUBDIRS             = 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11
 migratedir          = $(pkgdatadir)/migrate
diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c
index 8c3a241f9..051c2585c 100644
--- a/src/confd/src/hardware.c
+++ b/src/confd/src/hardware.c
@@ -900,9 +900,18 @@ static void wifi_build_vht_capab(char *out, size_t sz, unsigned int vht_cap, int
 	}
 }
 
+/* The box-wide WiFi country code, NULL when none is configured */
+static const char *wifi_country_code(struct lyd_node *config)
+{
+	struct lyd_node *wifi;
+
+	wifi = lydx_get_descendant(config, "hardware", "wifi", NULL);
+	return lydx_get_cattr(wifi, "country-code");
+}
+
 /* Helper: Write radio-specific configuration */
 static void wifi_gen_radio_config(FILE *hostapd, const char *radio_name,
-				  struct lyd_node *radio_node)
+				  struct lyd_node *radio_node, struct lyd_node *config)
 {
 	const char *country, *channel, *band, *width;
 	unsigned int ht_cap = 0, vht_cap = 0;
@@ -911,7 +920,7 @@ static void wifi_gen_radio_config(FILE *hostapd, const char *radio_name,
 	int ch = 0;
 	bool legacy_rates, he = false;
 
-	country = lydx_get_cattr(radio_node, "country-code");
+	country = wifi_country_code(config);
 	band = lydx_get_cattr(radio_node, "band");
 	channel = lydx_get_cattr(radio_node, "channel");
 	width = lydx_get_cattr(radio_node, "channel-width");
@@ -1168,7 +1177,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
 	fprintf(hostapd, "\n");
 
 	/* Radio-specific configuration */
-	wifi_gen_radio_config(hostapd, radio_name, radio_node);
+	wifi_gen_radio_config(hostapd, radio_name, radio_node, config);
 
 	/* Add BSS sections for secondary APs (multi-SSID) */
 	for (i = 1; i < ap_count; i++) {
@@ -1243,6 +1252,7 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
 		    sr_event_t event, struct confd *confd)
 {
 	struct lyd_node  *difs = NULL, *dif = NULL;
+	int country_changed = 0;
 	int rc = SR_ERR_OK;
 	int gps_changed = 0;
 	int wifi_changed = 0;
@@ -1250,7 +1260,22 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
 	if (!lydx_find_xpathf(diff, XPATH_BASE_))
 		return SR_ERR_OK;
 
-	difs = lydx_get_descendant(diff, "hardware", "component", NULL);
+	/*
+	 * The country code is one setting for every radio.  Apply the
+	 * regulatory domain here, not from hostapd, so a radio without an
+	 * interface is in the right domain too.  Every radio's hostapd
+	 * config carries the code, so visit them all when it changes.
+	 */
+	if (lydx_get_xpathf(diff, XPATH_BASE_ "/infix-hardware:wifi/country-code")) {
+		country_changed = 1;
+		if (event == SR_EV_DONE)
+			systemf("iw reg set %s", wifi_country_code(config) ?: "00");
+	}
+
+	if (country_changed)
+		difs = lydx_get_descendant(config, "hardware", "component", NULL);
+	else
+		difs = lydx_get_descendant(diff, "hardware", "component", NULL);
 
 	LYX_LIST_FOR_EACH(difs, dif, "component") {
 		enum lydx_op op;
@@ -1267,6 +1292,9 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
 			continue;
 
 		class = lydx_get_cattr(cif, "class");
+		if (country_changed && strcmp(class, "infix-hardware:wifi") &&
+		    !lydx_get_xpathf(diff, XPATH_BASE_ "/component[name='%s']", name))
+			continue;
 
 		/* Handle USB components */
 		if (!strcmp(class, "infix-hardware:usb")) {
diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c
index a6e9f1f86..7a5697a1e 100644
--- a/src/confd/src/if-wifi.c
+++ b/src/confd/src/if-wifi.c
@@ -237,8 +237,8 @@ int wifi_gen_settings(sr_session_ctx_t *session, struct lyd_node *dif,
  */
 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 *ifname, *ssid, *secret_name, *security_mode;
+	struct lyd_node *security, *secret_node, *station, *wifi;
 	const char *bssid = NULL;
 	unsigned char *secret = NULL;
 	FILE *wpa_supplicant = NULL;
@@ -252,7 +252,6 @@ int wifi_gen_station(struct lyd_node *cif)
 	if (!wifi)
 		return SR_ERR_OK;
 
-	radio = lydx_get_cattr(wifi, "radio");
 	station = lydx_get_child(wifi, "station");
 	if (station) {
 		ssid = lydx_get_cattr(station, "ssid");
@@ -268,9 +267,8 @@ int wifi_gen_station(struct lyd_node *cif)
 		secret_name = NULL;
 	}
 
-	radio_node = lydx_get_xpathf(cif,
-		"/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio);
-	country = lydx_get_cattr(radio_node, "country-code");
+	country = lydx_get_cattr(lydx_get_xpathf(cif, "/ietf-hardware:hardware/infix-hardware:wifi"),
+				 "country-code");
 
 	if (secret_name && strcmp(security_mode, "disabled") != 0) {
 		const char *b64;
@@ -439,7 +437,8 @@ int wifi_gen_mesh(struct lyd_node *cif)
 
 	radio_node = lydx_get_xpathf(cif,
 		"/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio);
-	country = lydx_get_cattr(radio_node, "country-code");
+	country = lydx_get_cattr(lydx_get_xpathf(cif, "/ietf-hardware:hardware/infix-hardware:wifi"),
+				 "country-code");
 	band = lydx_get_cattr(radio_node, "band");
 	width = lydx_get_cattr(radio_node, "channel-width");
 	channel = atoi(lydx_get_cattr(radio_node, "channel") ? : "0");
diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc
index a00da3f9f..f6a55600f 100644
--- a/src/confd/yang/confd.inc
+++ b/src/confd/yang/confd.inc
@@ -27,7 +27,7 @@ MODULES=(
 	"infix-syslog@2026-09-24.yang"
 	"iana-hardware@2018-03-13.yang"
 	"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
-	"infix-hardware@2026-10-05.yang"
+	"infix-hardware@2026-10-06.yang"
 	"ieee802-dot1q-types@2022-10-29.yang"
 	"infix-ip@2026-04-28.yang"
 	"infix-if-type@2026-10-03.yang"
diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang
index 56e3f0e2e..44a52c98a 100644
--- a/src/confd/yang/confd/infix-hardware.yang
+++ b/src/confd/yang/confd/infix-hardware.yang
@@ -21,6 +21,12 @@ module infix-hardware {
   contact      "kernelkit@googlegroups.com";
   description  "Vital Product Data augmentation of ieee-hardware and deviations.";
 
+  revision 2026-10-06 {
+    description "Move the WiFi country code from each radio to the box-wide
+                 hardware/wifi container, the regulatory domain is one setting.";
+    reference "internal";
+  }
+
   revision 2026-10-05 {
     description "Replace the WiFi radio survey container with the channel-survey
                  action, survey data is collected on request only.";
@@ -209,6 +215,34 @@ module infix-hardware {
   deviation "/iehw:hardware/iehw:component/iehw:asset-id" {
     deviate not-supported;
   }
+  augment "/iehw:hardware" {
+    description
+      "Settings shared by every WiFi radio in the system.";
+
+    container wifi {
+      if-feature wifi;
+      description
+        "WiFi settings that apply to all radios.";
+
+      leaf country-code {
+        type iwcc:country-code;
+        default "00";
+        description
+          "Two-letter ISO 3166-1 country code, the regulatory domain for
+           every WiFi radio in the system.  It decides which channels
+           and transmit power levels the radios may use.
+
+           The default '00' is the world domain: no 6 GHz, the 5 GHz
+           band listen-only, and no access point or mesh point can be
+           configured.
+
+           Examples: 'US', 'DE', 'JP'.
+
+           WARNING: Incorrect values may violate local laws and regulations.";
+      }
+    }
+  }
+
   augment "/iehw:hardware/iehw:component" {
     leaf phys-address {
       type yang:phys-address;
@@ -297,22 +331,6 @@ module infix-hardware {
          a WiFi radio (class 'ih:wifi'). WiFi radios are physical devices
          that can host multiple virtual WiFi interfaces (APs or Stations).";
 
-      leaf country-code {
-        type iwcc:country-code;
-        mandatory true;
-        description
-          "Two-letter ISO 3166-1 country code for regulatory compliance.
-
-           Sets the regulatory domain for this radio, determining:
-           - Allowed channels and frequencies
-           - Maximum transmit power
-           - DFS (Dynamic Frequency Selection) requirements
-
-           Examples: 'US', 'DE', 'JP'.
-
-           WARNING: Incorrect values may violate local laws and regulations.";
-      }
-
       leaf channel {
         type union {
           type uint16 {
@@ -333,7 +351,7 @@ module infix-hardware {
 
            Channel availability depends on:
            - Configured band (2.4/5/6 GHz)
-           - Regulatory domain (country-code)
+           - Regulatory domain (hardware wifi country-code)
            - Hardware capabilities
 
            Common channels:
@@ -450,7 +468,7 @@ module infix-hardware {
 
            Channels depend on:
            - Hardware capabilities
-           - Configured country-code
+           - The system's WiFi country code
            - Band selection
 
            This list reflects actual usable channels after applying
diff --git a/src/confd/yang/confd/infix-hardware@2026-10-05.yang b/src/confd/yang/confd/infix-hardware@2026-10-06.yang
similarity index 100%
rename from src/confd/yang/confd/infix-hardware@2026-10-05.yang
rename to src/confd/yang/confd/infix-hardware@2026-10-06.yang
diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang
index fa255b618..2f4163525 100644
--- a/src/confd/yang/confd/infix-if-wifi.yang
+++ b/src/confd/yang/confd/infix-if-wifi.yang
@@ -49,6 +49,13 @@ submodule infix-if-wifi {
      - Security: WPA2/WPA3 with keystore integration
      - Operational state: Connection status, RSSI, client lists";
 
+  revision 2026-10-06 {
+    description
+      "Access point and mesh point require the box-wide WiFi country code,
+       the per-radio country-code leaf is gone.";
+    reference "internal";
+  }
+
   revision 2026-10-02 {
     description
       "Add 4-address (WDS) support: wds-link mode for access point side
@@ -411,8 +418,8 @@ submodule infix-if-wifi {
               error-message "Parent radio must have 'channel' configured for Access Point mode";
             }
 
-            must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:country-code != '00'" {
-              error-message "Country code '00' (world regulatory domain) is not allowed for Access Point mode. Please configure a specific country code on the radio.";
+            must "/iehw:hardware/ih:wifi/ih:country-code != '00'" {
+              error-message "Set the WiFi country code (hardware wifi country-code) before configuring an access point, the world domain '00' is not allowed.";
             }
 
             leaf ssid {
@@ -718,8 +725,8 @@ submodule infix-if-wifi {
               error-message "Parent radio must have 'channel' configured for mesh mode";
             }
 
-            must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:country-code != '00'" {
-              error-message "Country code '00' is not allowed for mesh mode.";
+            must "/iehw:hardware/ih:wifi/ih:country-code != '00'" {
+              error-message "Set the WiFi country code (hardware wifi country-code) before configuring a mesh point, the world domain '00' is not allowed.";
             }
 
             must "not(/if:interfaces/if:interface[wifi/access-point][wifi/radio = current()/../radio])" {
diff --git a/src/confd/yang/confd/infix-if-wifi@2026-10-02.yang b/src/confd/yang/confd/infix-if-wifi@2026-10-06.yang
similarity index 100%
rename from src/confd/yang/confd/infix-if-wifi@2026-10-02.yang
rename to src/confd/yang/confd/infix-if-wifi@2026-10-06.yang
diff --git a/test/case/interfaces/wifi_ap_multi_station/test.py b/test/case/interfaces/wifi_ap_multi_station/test.py
index 6d2bb6e88..f9c19aa65 100755
--- a/test/case/interfaces/wifi_ap_multi_station/test.py
+++ b/test/case/interfaces/wifi_ap_multi_station/test.py
@@ -78,8 +78,7 @@ def leased(dut):
 
     with test.step("Configure the ap as an Access Point on radio0"):
         ap.put_config_dicts({
-            "ietf-hardware": {"hardware": {"component": [
-                wifi.radio("radio0", band="2.4GHz", channel=1)]}},
+            "ietf-hardware": wifi.hardware(wifi.radio("radio0", band="2.4GHz", channel=1)),
             "ietf-keystore": wifi.keystore({"wifi": PSK}),
             "ietf-interfaces": {"interfaces": {"interface": [
                 wifi.iface("wifi0", AP_MAC, {
@@ -99,7 +98,7 @@ def leased(dut):
     with test.step("Configure the stations on radio0"):
         def configure_station(mac, dut):
             dut.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}},
+                "ietf-hardware": wifi.hardware(wifi.radio("radio0")),
                 "ietf-keystore": wifi.keystore({"wifi": PSK}),
                 "ietf-interfaces": {"interfaces": {"interface": [
                     wifi.iface("wifi0", mac, {
diff --git a/test/case/interfaces/wifi_ap_station_2dut/test.py b/test/case/interfaces/wifi_ap_station_2dut/test.py
index 8a596f501..d7f2130bc 100755
--- a/test/case/interfaces/wifi_ap_station_2dut/test.py
+++ b/test/case/interfaces/wifi_ap_station_2dut/test.py
@@ -53,8 +53,7 @@
     with test.step("Configure the ap as an Access Point and the station on radio0"):
         parallel(
             lambda: ap.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [
-                    wifi.radio("radio0", band="2.4GHz", channel=1)]}},
+                "ietf-hardware": wifi.hardware(wifi.radio("radio0", band="2.4GHz", channel=1)),
                 "ietf-keystore": wifi.keystore({"wifi": PSK}),
                 "ietf-interfaces": {"interfaces": {"interface": [
                     # hwsim defaults every radio0 to 02:00:00:00:00:00, so the AP
@@ -74,7 +73,7 @@
                 }]}},
             }),
             lambda: station.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}},
+                "ietf-hardware": wifi.hardware(wifi.radio("radio0")),
                 "ietf-keystore": wifi.keystore({"wifi": PSK}),
                 "ietf-interfaces": {"interfaces": {"interface": [
                     wifi.iface("wifi0", "02:00:00:00:00:02", {
diff --git a/test/case/interfaces/wifi_band_steering/test.py b/test/case/interfaces/wifi_band_steering/test.py
index 3eab7d0f5..cb93b3a58 100755
--- a/test/case/interfaces/wifi_band_steering/test.py
+++ b/test/case/interfaces/wifi_band_steering/test.py
@@ -83,10 +83,9 @@ def ap_bss(name, radio_name, bssid):
         # dedicated band-steering cell (cell2) in test/virt/quad.
         parallel(
             lambda: ap.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [
+                "ietf-hardware": wifi.hardware(
                     wifi.radio("radio2", band="2.4GHz", channel=1),
-                    wifi.radio("radio3", band="5GHz", channel=36),
-                ]}},
+                    wifi.radio("radio3", band="5GHz", channel=36)),
                 "ietf-keystore": wifi.keystore({"wifi": PSK}),
                 "ietf-interfaces": {"interfaces": {"interface": [
                     {"name": "br0", "type": "infix-if-type:bridge", "enabled": True,
@@ -104,7 +103,7 @@ def ap_bss(name, radio_name, bssid):
             # (cell2).  No band/channel pinned: the one radio scans both bands and
             # lets band steering decide where it lands.
             lambda: client.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [wifi.radio("radio2")]}},
+                "ietf-hardware": wifi.hardware(wifi.radio("radio2")),
                 "ietf-keystore": wifi.keystore({"wifi": PSK}),
                 "ietf-interfaces": {"interfaces": {"interface": [
                     wifi.iface("wifi0", CLIENT_MAC, {
diff --git a/test/case/interfaces/wifi_channel_survey/test.py b/test/case/interfaces/wifi_channel_survey/test.py
index 97d624b1c..94091bdc1 100755
--- a/test/case/interfaces/wifi_channel_survey/test.py
+++ b/test/case/interfaces/wifi_channel_survey/test.py
@@ -34,8 +34,7 @@
     with test.step("Configure the ap as an Access Point on channel 1 and the station on radio0"):
         parallel(
             lambda: ap.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [
-                    wifi.radio("radio0", band="2.4GHz", channel=1)]}},
+                "ietf-hardware": wifi.hardware(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", {
@@ -48,7 +47,7 @@
                 ]}},
             }),
             lambda: station.put_config_dicts({
-                "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}},
+                "ietf-hardware": wifi.hardware(wifi.radio("radio0")),
                 "ietf-keystore": wifi.keystore({"wifi": PSK}),
                 "ietf-interfaces": {"interfaces": {"interface": [
                     wifi.iface("wifi0", "02:00:00:00:00:02", {
diff --git a/test/case/interfaces/wifi_mesh_roaming/test.py b/test/case/interfaces/wifi_mesh_roaming/test.py
index a2c1ecc06..c85c3c1ff 100755
--- a/test/case/interfaces/wifi_mesh_roaming/test.py
+++ b/test/case/interfaces/wifi_mesh_roaming/test.py
@@ -96,10 +96,9 @@ def gw_config(mesh_mac, ap_mac, uplink=None):
             "infix-interfaces:bridge-port": {"bridge": "br0"},
         })
     return {
-        "ietf-hardware": {"hardware": {"component": [
+        "ietf-hardware": wifi.hardware(
             wifi.radio("radio0", band="5GHz", channel=36),
-            wifi.radio("radio1", band="2.4GHz", channel=1),
-        ]}},
+            wifi.radio("radio1", band="2.4GHz", channel=1)),
         "ietf-keystore": wifi.keystore(SECRETS),
         "ietf-interfaces": {"interfaces": {"interface": interfaces}},
     }
@@ -136,8 +135,7 @@ def gw_config(mesh_mac, ap_mac, uplink=None):
         # associates to live in the same cell only when they share an index.
         # See doc/wifi.md and test/virt/quad.
         client.put_config_dicts({
-            "ietf-hardware": {"hardware": {"component": [
-                wifi.radio("radio1", band="2.4GHz", channel=1)]}},
+            "ietf-hardware": wifi.hardware(wifi.radio("radio1", band="2.4GHz", channel=1)),
             "ietf-keystore": wifi.keystore(SECRETS),
             "ietf-interfaces": {"interfaces": {"interface": [
                 wifi.iface("wifi0", CLIENT_MAC, {
diff --git a/test/case/interfaces/wifi_station_from_scan/test.py b/test/case/interfaces/wifi_station_from_scan/test.py
index ab4ba6fe5..9af9a916f 100755
--- a/test/case/interfaces/wifi_station_from_scan/test.py
+++ b/test/case/interfaces/wifi_station_from_scan/test.py
@@ -37,8 +37,7 @@
 
     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-hardware": wifi.hardware(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", {
@@ -57,7 +56,7 @@
 
     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-hardware": wifi.hardware(wifi.radio("radio0")),
             "ietf-interfaces": {"interfaces": {"interface": [
                 wifi.iface("wifi0", "02:00:00:00:00:02", {"radio": "radio0"},
                            ipv4={"infix-dhcp-client:dhcp": {}}),
diff --git a/test/case/interfaces/wifi_wds_link_2dut/test.py b/test/case/interfaces/wifi_wds_link_2dut/test.py
index 42e578f23..c44d194c4 100755
--- a/test/case/interfaces/wifi_wds_link_2dut/test.py
+++ b/test/case/interfaces/wifi_wds_link_2dut/test.py
@@ -40,8 +40,7 @@
 
 def root_config():
     return {
-        "ietf-hardware": {"hardware": {"component": [
-            wifi.radio("radio0", band="2.4GHz", channel=1)]}},
+        "ietf-hardware": wifi.hardware(wifi.radio("radio0", band="2.4GHz", channel=1)),
         "ietf-keystore": wifi.keystore({"wifi": PSK}),
         "ietf-interfaces": {"interfaces": {"interface": [
             {
@@ -77,7 +76,7 @@ def root_config():
 
 def satellite_config():
     return {
-        "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}},
+        "ietf-hardware": wifi.hardware(wifi.radio("radio0")),
         "ietf-keystore": wifi.keystore({"wifi": PSK}),
         "ietf-interfaces": {"interfaces": {"interface": [
             {
diff --git a/test/case/interfaces/wifi_wds_repeater/test.py b/test/case/interfaces/wifi_wds_repeater/test.py
index 68311ade1..6cb525a8a 100755
--- a/test/case/interfaces/wifi_wds_repeater/test.py
+++ b/test/case/interfaces/wifi_wds_repeater/test.py
@@ -54,8 +54,7 @@
 
 def root_config(uplink):
     return {
-        "ietf-hardware": {"hardware": {"component": [
-            wifi.radio("radio0", band="2.4GHz", channel=1)]}},
+        "ietf-hardware": wifi.hardware(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,
@@ -93,8 +92,7 @@ def root_config(uplink):
 
 def repeater_config():
     return {
-        "ietf-hardware": {"hardware": {"component": [
-            wifi.radio("radio0", band="2.4GHz", channel=1)]}},
+        "ietf-hardware": wifi.hardware(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,
@@ -131,7 +129,7 @@ def repeater_config():
 
 def station_config(mac, ssid, secret):
     return {
-        "ietf-hardware": {"hardware": {"component": [wifi.radio("radio0")]}},
+        "ietf-hardware": wifi.hardware(wifi.radio("radio0")),
         "ietf-keystore": wifi.keystore(SECRETS),
         "ietf-interfaces": {"interfaces": {"interface": [
             wifi.iface("wifi0", mac, {
diff --git a/test/infamy/wifi.py b/test/infamy/wifi.py
index c670542ba..dcf5046d3 100644
--- a/test/infamy/wifi.py
+++ b/test/infamy/wifi.py
@@ -6,9 +6,9 @@
 import base64
 
 
-def radio(name, country="SE", band=None, channel=None):
+def radio(name, band=None, channel=None):
     """ietf-hardware component for a WiFi radio."""
-    settings = {"country-code": country}
+    settings = {}
     if band:
         settings["band"] = band
     if channel is not None:
@@ -20,6 +20,14 @@ def radio(name, country="SE", band=None, channel=None):
     }
 
 
+def hardware(*radios, country="SE"):
+    """ietf-hardware config: the radios plus the box-wide WiFi country code."""
+    return {"hardware": {
+        "infix-hardware:wifi": {"country-code": country},
+        "component": list(radios),
+    }}
+
+
 def keystore(secrets):
     """ietf-keystore config with a passphrase entry per {name: psk}."""
     return {"keystore": {"symmetric-keys": {"symmetric-key": [

From 8eece1999edc8e7bc8128ef534a37eb0290ca216 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= 
Date: Tue, 6 Oct 2026 10:37:38 +0200
Subject: [PATCH 21/45] webui: Use the box-wide WiFi country code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The country moves from each radio's form to a WiFi card on the hardware
page.  The interface editor and the wizard still offer it next to the
radio fields, so a first radio can be set up in one go, but write it to
the shared leaf.

Signed-off-by: Mattias Walström 
---
 .../internal/handlers/configure_hardware.go   | 71 +++++++++++++------
 .../internal/handlers/configure_interfaces.go | 71 +++++++++++--------
 src/webui/internal/handlers/dashboard.go      | 16 +++++
 src/webui/internal/handlers/wifi.go           |  1 -
 src/webui/internal/handlers/wifi_save_test.go | 20 ++++++
 src/webui/internal/server/server.go           |  1 +
 src/webui/static/js/app.js                    |  9 +--
 .../templates/pages/configure-hardware.html   | 40 ++++++-----
 .../templates/pages/configure-interfaces.html | 10 +--
 9 files changed, 155 insertions(+), 84 deletions(-)

diff --git a/src/webui/internal/handlers/configure_hardware.go b/src/webui/internal/handlers/configure_hardware.go
index bb4a4d94c..621907cfb 100644
--- a/src/webui/internal/handlers/configure_hardware.go
+++ b/src/webui/internal/handlers/configure_hardware.go
@@ -31,6 +31,9 @@ import (
 const hwRoot = "/ietf-hardware:hardware"
 const hwCandPath = candidatePath + hwRoot
 
+// hwWiFiCountryPath is the schema path of the box-wide WiFi country code.
+const hwWiFiCountryPath = hwRoot + "/infix-hardware:wifi/country-code"
+
 // hwCompCfgRow is one configured component in the main table. Per-class
 // fields are populated only for the matching Class. IsUSB/IsWiFi/IsGPS
 // spare the template from dispatching on stringly-typed Class slugs.
@@ -47,18 +50,15 @@ type hwCompCfgRow struct {
 	Unlocked bool // admin-state == "unlocked"
 
 	// WiFi-specific.
-	CountryCode string
-	Channel     string
-	Band        string
+	Channel string
+	Band    string
 
 	// Schema descriptions carried per-row so the fold-out forms are
 	// self-contained — Go templates can't pass extra arguments through
 	// {{template}}.
-	CountryOptions  []schema.IdentityOption
 	BandOptions     []schema.IdentityOption
 	DescDescription string
 	DescAdminState  string
-	DescCountry     string
 	DescBand        string
 	DescChannel     string
 }
@@ -79,7 +79,10 @@ type cfgHardwarePageData struct {
 	AvailableUSB   []hwAvailable
 	AvailableWiFi  []hwAvailable
 	AvailableGPS   []hwAvailable
+	// Box-wide WiFi country code, one setting for every radio.
+	CountryCode    string
 	CountryOptions []schema.IdentityOption
+	DescCountry    string
 	BandOptions    []schema.IdentityOption
 	Desc           map[string]string
 	Error          string
@@ -112,13 +115,13 @@ func (h *ConfigureHardwareHandler) Overview(w http.ResponseWriter, r *http.Reque
 		compPath := "/ietf-hardware:hardware/component"
 		radioPath := compPath + "/infix-hardware:wifi-radio"
 		data.Desc = map[string]string{
-			"description":  schema.DescriptionOf(mgr, compPath+"/description"),
-			"admin-state":  schema.DescriptionOf(mgr, compPath+"/state/admin-state"),
-			"country-code": schema.DescriptionOf(mgr, radioPath+"/country-code"),
-			"channel":      schema.DescriptionOf(mgr, radioPath+"/channel"),
-			"band":         schema.DescriptionOf(mgr, radioPath+"/band"),
+			"description": schema.DescriptionOf(mgr, compPath+"/description"),
+			"admin-state": schema.DescriptionOf(mgr, compPath+"/state/admin-state"),
+			"channel":     schema.DescriptionOf(mgr, radioPath+"/channel"),
+			"band":        schema.DescriptionOf(mgr, radioPath+"/band"),
 		}
-		data.CountryOptions = schema.OptionsFor(mgr, radioPath+"/country-code")
+		data.DescCountry = schema.DescriptionOf(mgr, hwWiFiCountryPath)
+		data.CountryOptions = schema.OptionsFor(mgr, hwWiFiCountryPath)
 		data.BandOptions = schema.OptionsFor(mgr, radioPath+"/band")
 	}
 
@@ -146,6 +149,7 @@ func (h *ConfigureHardwareHandler) Overview(w http.ResponseWriter, r *http.Reque
 		log.Printf("configure hardware: operational fetch: %v", operErr)
 	}
 
+	data.CountryCode = cfgWrap.wifiCountryCode()
 	configured := make(map[string]bool, len(cfgWrap.Hardware.Component))
 	for _, c := range cfgWrap.Hardware.Component {
 		configured[c.Name] = true
@@ -209,13 +213,10 @@ func (h *ConfigureHardwareHandler) buildRow(c hwComponentJSON, class string, dat
 		row.Unlocked = c.State != nil && c.State.AdminState == adminStateUnlocked
 	case classWiFi:
 		row.IsWiFi = true
-		row.CountryOptions = data.CountryOptions
 		row.BandOptions = data.BandOptions
-		row.DescCountry = data.Desc["country-code"]
 		row.DescBand = data.Desc["band"]
 		row.DescChannel = data.Desc["channel"]
 		if c.WiFiRadio != nil {
-			row.CountryCode = c.WiFiRadio.CountryCode
 			row.Band = c.WiFiRadio.Band
 			row.Channel = wifiChannelString(c.WiFiRadio.Channel)
 		}
@@ -341,6 +342,35 @@ func (h *ConfigureHardwareHandler) CreateHardware(w http.ResponseWriter, r *http
 	renderSavedRedirect(w, name+" added", "/configure/hardware")
 }
 
+// SaveWiFiCountry writes or clears the box-wide WiFi country code.
+// POST /configure/hardware/wifi
+func (h *ConfigureHardwareHandler) SaveWiFiCountry(w http.ResponseWriter, r *http.Request) {
+	if err := r.ParseForm(); err != nil {
+		http.Error(w, "bad request", http.StatusBadRequest)
+		return
+	}
+	if err := h.putWiFiCountry(r.Context(), r.FormValue("country-code")); err != nil {
+		log.Printf("configure hardware wifi country: %v", err)
+		renderSaveError(w, err)
+		return
+	}
+	renderSaved(w, "WiFi country code saved")
+}
+
+// putWiFiCountry sets the box-wide country code, or removes it when
+// country is empty so the radios fall back to the world domain default.
+func (h *ConfigureHardwareHandler) putWiFiCountry(ctx context.Context, country string) error {
+	country = strings.TrimSpace(country)
+	path := hwCandPath + "/infix-hardware:wifi/country-code"
+	if country == "" {
+		if err := h.RC.Delete(ctx, path); err != nil && !restconf.IsNotFound(err) {
+			return err
+		}
+		return nil
+	}
+	return h.RC.Put(ctx, path, map[string]any{"infix-hardware:country-code": country})
+}
+
 func (h *ConfigureHardwareHandler) putAdminState(ctx context.Context, name, state string) error {
 	return h.RC.Put(ctx, hwComponentPath(name)+"/state/admin-state",
 		map[string]any{"ietf-hardware:admin-state": state})
@@ -351,15 +381,12 @@ func (h *ConfigureHardwareHandler) putWiFiRadio(ctx context.Context, name string
 		map[string]any{"infix-hardware:wifi-radio": radio})
 }
 
-// parseWiFiRadio builds the wifi-radio body from form fields. Country
-// code is mandatory; band and channel are optional and only included
-// when non-empty so we don't clobber YANG defaults with empty strings.
+// parseWiFiRadio builds the wifi-radio body from form fields. Band and
+// channel are optional and only included when non-empty so we don't
+// clobber YANG defaults with empty strings; the result may be empty,
+// which still creates the presence container.
 func parseWiFiRadio(r *http.Request) (map[string]any, error) {
-	country := strings.TrimSpace(r.FormValue("country-code"))
-	if country == "" {
-		return nil, fmt.Errorf("country code is required")
-	}
-	radio := map[string]any{"country-code": country}
+	radio := map[string]any{}
 	if band := strings.TrimSpace(r.FormValue("band")); band != "" {
 		radio["band"] = band
 	}
diff --git a/src/webui/internal/handlers/configure_interfaces.go b/src/webui/internal/handlers/configure_interfaces.go
index 86c8e12f1..44474ae6a 100644
--- a/src/webui/internal/handlers/configure_interfaces.go
+++ b/src/webui/internal/handlers/configure_interfaces.go
@@ -351,7 +351,7 @@ func (h *ConfigureInterfacesHandler) Overview(w http.ResponseWriter, r *http.Req
 		// by the inline "+ New radio" form in the WiFi fieldset and by
 		// the WiFi interface row's mirrored radio editor.
 		const radioSchemaPath = "/ietf-hardware:hardware/component/infix-hardware:wifi-radio"
-		data.WizardCountryOptions = schema.OptionsFor(mgr, radioSchemaPath+"/country-code")
+		data.WizardCountryOptions = schema.OptionsFor(mgr, hwWiFiCountryPath)
 		data.WizardBandOptions = schema.OptionsFor(mgr, radioSchemaPath+"/band")
 		data.CountryOptions = data.WizardCountryOptions
 		data.BandOptions = data.WizardBandOptions
@@ -481,7 +481,7 @@ func (h *ConfigureInterfacesHandler) Overview(w http.ResponseWriter, r *http.Req
 	// Configured WiFi radios live in candidate (so the picker reflects
 	// uncommitted edits the user is in the middle of). Available radios
 	// = detected (operational class=wifi) - those already in candidate.
-	data.WizardWifiRadios = buildWifiRadioOptions(hwCand.Hardware.Component)
+	data.WizardWifiRadios = buildWifiRadioOptions(hwCand.Hardware.Component, hwCand.wifiCountryCode())
 	configuredRadioNames := make(map[string]bool, len(data.WizardWifiRadios))
 	for _, r := range data.WizardWifiRadios {
 		configuredRadioNames[r.Name] = true
@@ -507,7 +507,7 @@ func (h *ConfigureInterfacesHandler) Overview(w http.ResponseWriter, r *http.Req
 	}
 	// Populate the mirrored radio editor for WiFi interface rows from
 	// the already-fetched candidate hardware tree (no extra fetch).
-	radios := indexWifiRadios(hwCand.Hardware.Component)
+	radios := indexWifiRadios(hwCand.Hardware.Component, hwCand.wifiCountryCode())
 	for i := range data.Interfaces {
 		row := &data.Interfaces[i]
 		if !row.IsWifi || row.WiFi == nil || row.WiFi.Radio == "" {
@@ -1074,11 +1074,7 @@ func (h *ConfigureInterfacesHandler) WizardCreateRadio(w http.ResponseWriter, r
 		renderSaveError(w, fmt.Errorf("radio name is required"))
 		return
 	}
-	if country == "" {
-		renderSaveError(w, fmt.Errorf("country code is required"))
-		return
-	}
-	radio := map[string]any{"country-code": country}
+	radio := map[string]any{}
 	if band != "" {
 		radio["band"] = band
 	}
@@ -1100,6 +1096,16 @@ func (h *ConfigureInterfacesHandler) WizardCreateRadio(w http.ResponseWriter, r
 		"class":                     "infix-hardware:wifi",
 		"infix-hardware:wifi-radio": radio,
 	}
+	// The country code is one setting for every radio; the form offers
+	// it here so a first radio can be set up in one go.
+	if country != "" {
+		cc := map[string]any{"infix-hardware:country-code": country}
+		if err := h.RC.Put(r.Context(), candidatePath+hwWiFiCountryPath, cc); err != nil {
+			log.Printf("wizard create radio %q: country: %v", name, err)
+			renderSaveError(w, err)
+			return
+		}
+	}
 	body := map[string]any{"ietf-hardware:component": []map[string]any{comp}}
 	path := candidatePath + "/ietf-hardware:hardware/component=" + url.PathEscape(name)
 	if err := h.RC.Put(r.Context(), path, body); err != nil {
@@ -1115,7 +1121,7 @@ func (h *ConfigureInterfacesHandler) renderRadioPicker(w http.ResponseWriter, r
 	if err := h.RC.Get(r.Context(), candidatePath+"/ietf-hardware:hardware", &hwCand); err != nil {
 		log.Printf("wizard radio refresh: %v", err)
 	}
-	radios := buildWifiRadioOptions(hwCand.Hardware.Component)
+	radios := buildWifiRadioOptions(hwCand.Hardware.Component, hwCand.wifiCountryCode())
 	if !containsRadioName(radios, selected) {
 		// Race / fetch failure — surface the new radio anyway.
 		radios = append([]wifiRadioOption{{Name: selected, Label: selected}}, radios...)
@@ -1804,25 +1810,29 @@ func (h *ConfigureInterfacesHandler) SaveWifi(w http.ResponseWriter, r *http.Req
 	// Both halves go in one patch, so a rejected save leaves the
 	// candidate untouched.
 	p := restconf.NewYangPatch(candidatePath)
-	// Radio half, only when the form actually carried a country (the
-	// wifi-radio container's mandatory leaf). Without it parseWiFiRadio
-	// would reject a form whose user only touched the WiFi side and left
-	// the radio fields untouched-empty.
+	// Hardware half: the radio's band and channel when the form carried
+	// them, and the box-wide country code when it was picked.  A form
+	// whose user only touched the WiFi side leaves hardware alone.
+	hw := map[string]any{}
 	if strings.TrimSpace(r.FormValue("country-code")) != "" {
+		hw["infix-hardware:wifi"] = map[string]any{
+			"country-code": strings.TrimSpace(r.FormValue("country-code")),
+		}
+	}
+	if strings.TrimSpace(r.FormValue("band")) != "" || strings.TrimSpace(r.FormValue("channel")) != "" {
 		rc, err := parseWiFiRadio(r)
 		if err != nil {
 			renderSaveError(w, err)
 			return
 		}
-		p.Merge(hwRoot, map[string]any{
-			"ietf-hardware:hardware": map[string]any{
-				"component": []map[string]any{{
-					"name":                      radio,
-					"class":                     "infix-hardware:wifi",
-					"infix-hardware:wifi-radio": rc,
-				}},
-			},
-		})
+		hw["component"] = []map[string]any{{
+			"name":                      radio,
+			"class":                     "infix-hardware:wifi",
+			"infix-hardware:wifi-radio": rc,
+		}}
+	}
+	if len(hw) > 0 {
+		p.Merge(hwRoot, map[string]any{"ietf-hardware:hardware": hw})
 	}
 	wifi := map[string]any{"radio": radio, mode: leaf}
 	p.Replace(ifaceTarget(name)+"/infix-interfaces:wifi", map[string]any{"infix-interfaces:wifi": wifi})
@@ -1977,9 +1987,9 @@ func ifaceTarget(name string) string {
 
 // indexWifiRadios picks WiFi radio components out of the hardware
 // candidate tree and returns the minimal subset the WiFi interface
-// editor mirrors (name, country, band, channel). Components without
-// a wifi-radio container are skipped.
-func indexWifiRadios(comps []hwComponentJSON) map[string]*ifaceRadioMirror {
+// editor mirrors (name, band, channel, plus the box-wide country).
+// Components without a wifi-radio container are skipped.
+func indexWifiRadios(comps []hwComponentJSON, country string) map[string]*ifaceRadioMirror {
 	out := make(map[string]*ifaceRadioMirror, len(comps))
 	for _, c := range comps {
 		if c.WiFiRadio == nil {
@@ -1987,7 +1997,7 @@ func indexWifiRadios(comps []hwComponentJSON) map[string]*ifaceRadioMirror {
 		}
 		m := &ifaceRadioMirror{
 			Name:        c.Name,
-			CountryCode: c.WiFiRadio.CountryCode,
+			CountryCode: country,
 			Band:        c.WiFiRadio.Band,
 		}
 		if ch, ok := c.WiFiRadio.Channel.(float64); ok && ch > 0 {
@@ -2658,9 +2668,10 @@ type wifiRadioOption struct {
 // configured WiFi radios (class=wifi with a wifi-radio container present
 // in running config) and returns picker entries with a label that hints
 // at band/channel/country. APReady reflects the YANG must-clauses on
-// access-point — band, channel, and country-code all set, with country
-// != "00" (world regulatory domain is rejected for AP mode).
-func buildWifiRadioOptions(comps []hwComponentJSON) []wifiRadioOption {
+// access-point — band and channel set on the radio and the box-wide
+// country code set to something other than "00" (the world regulatory
+// domain is rejected for AP mode).
+func buildWifiRadioOptions(comps []hwComponentJSON, country string) []wifiRadioOption {
 	var out []wifiRadioOption
 	for _, c := range comps {
 		if shortClass(c.Class) != classWiFi || c.WiFiRadio == nil {
@@ -2669,7 +2680,7 @@ func buildWifiRadioOptions(comps []hwComponentJSON) []wifiRadioOption {
 		ch := wifiChannelString(c.WiFiRadio.Channel)
 		opt := wifiRadioOption{
 			Name:    c.Name,
-			Country: c.WiFiRadio.CountryCode,
+			Country: country,
 			Band:    c.WiFiRadio.Band,
 			Channel: ch,
 		}
diff --git a/src/webui/internal/handlers/dashboard.go b/src/webui/internal/handlers/dashboard.go
index 2e476e640..7f78a0553 100644
--- a/src/webui/internal/handlers/dashboard.go
+++ b/src/webui/internal/handlers/dashboard.go
@@ -410,10 +410,26 @@ const (
 
 type hardwareWrapper struct {
 	Hardware struct {
+		WiFi      *hwWiFiJSON       `json:"infix-hardware:wifi"`
 		Component []hwComponentJSON `json:"component"`
 	} `json:"ietf-hardware:hardware"`
 }
 
+// hwWiFiJSON is the box-wide WiFi container, one country code for every
+// radio.
+type hwWiFiJSON struct {
+	CountryCode string `json:"country-code"`
+}
+
+// wifiCountryCode returns the box-wide WiFi country code, "00" (the
+// world domain, the YANG default) when none is configured.
+func (w hardwareWrapper) wifiCountryCode() string {
+	if w.Hardware.WiFi == nil || w.Hardware.WiFi.CountryCode == "" {
+		return "00"
+	}
+	return w.Hardware.WiFi.CountryCode
+}
+
 type hwComponentJSON struct {
 	Name        string           `json:"name"`
 	Class       string           `json:"class"`
diff --git a/src/webui/internal/handlers/wifi.go b/src/webui/internal/handlers/wifi.go
index 5695bd161..e2aba1072 100644
--- a/src/webui/internal/handlers/wifi.go
+++ b/src/webui/internal/handlers/wifi.go
@@ -24,7 +24,6 @@ type wifiMaxIfJSON struct {
 }
 
 type wifiRadioHWJSON struct {
-	CountryCode   string          `json:"country-code"` // ISO 3166-1, rw
 	Channel       interface{}     `json:"channel"`      // uint16 or "auto", rw
 	Band          string          `json:"band"`         // rw
 	Frequency     int             `json:"frequency"`    // MHz, operational
diff --git a/src/webui/internal/handlers/wifi_save_test.go b/src/webui/internal/handlers/wifi_save_test.go
index ede183884..230bb5ac9 100644
--- a/src/webui/internal/handlers/wifi_save_test.go
+++ b/src/webui/internal/handlers/wifi_save_test.go
@@ -77,3 +77,23 @@ func TestSaveWifiBadRadioWritesNothing(t *testing.T) {
 		t.Fatalf("candidate written despite form error: %+v", rc.Patches)
 	}
 }
+
+func TestSaveWifiCountryGoesToBoxWideLeaf(t *testing.T) {
+	w, rc := postSaveWifi(t, url.Values{
+		"mode": {"access-point"}, "radio": {"radio0"}, "ssid": {"lab"},
+		"sec-mode": {"wpa2-wpa3-personal"}, "secret": {"psk"},
+		"country-code": {"SE"}, "band": {"5GHz"}, "channel": {"36"},
+	})
+	if w.Code != http.StatusOK {
+		t.Fatalf("status %d: %s", w.Code, w.Body.String())
+	}
+	hw := rc.Patches[0].Edits[0].Value.(map[string]any)["ietf-hardware:hardware"].(map[string]any)
+	wifi, _ := hw["infix-hardware:wifi"].(map[string]any)
+	if wifi["country-code"] != "SE" {
+		t.Errorf("country not on the box-wide leaf: %v", hw)
+	}
+	comps := hw["component"].([]map[string]any)
+	if _, has := comps[0]["infix-hardware:wifi-radio"].(map[string]any)["country-code"]; has {
+		t.Errorf("country still on the radio: %v", comps[0])
+	}
+}
diff --git a/src/webui/internal/server/server.go b/src/webui/internal/server/server.go
index 3d4c11a5d..02c951de6 100644
--- a/src/webui/internal/server/server.go
+++ b/src/webui/internal/server/server.go
@@ -410,6 +410,7 @@ func New(
 	mux.HandleFunc("GET /configure/hardware",                       cfgHw.Overview)
 	mux.HandleFunc("POST /configure/hardware",                      cfgHw.CreateHardware)
 	mux.HandleFunc("POST /configure/hardware/usb/{name}",           cfgHw.SaveUSBPort)
+	mux.HandleFunc("POST /configure/hardware/wifi",                 cfgHw.SaveWiFiCountry)
 	mux.HandleFunc("POST /configure/hardware/wifi/{name}",          cfgHw.SaveWiFiRadio)
 	mux.HandleFunc("POST /configure/hardware/gps/{name}",           cfgHw.SaveGPS)
 	mux.HandleFunc("DELETE /configure/hardware/{name}",             cfgHw.DeleteComponent)
diff --git a/src/webui/static/js/app.js b/src/webui/static/js/app.js
index b946bb495..934121459 100644
--- a/src/webui/static/js/app.js
+++ b/src/webui/static/js/app.js
@@ -1411,8 +1411,7 @@ function setBlockEnabled(el, on) {
   });
 
   // Configure > Hardware "+ Add hardware" picker: sync the hidden class
-  // input from the selected option's data-class and reveal class-specific
-  // fields (currently WiFi country-code).
+  // input from the selected option's data-class.
   document.addEventListener('change', function (e) {
     var sel = e.target.closest && e.target.closest('#add-hw-picker');
     if (!sel) return;
@@ -1420,12 +1419,6 @@ function setBlockEnabled(el, on) {
     var cls = (opt && opt.getAttribute('data-class')) || '';
     var classInput = document.getElementById('add-hw-class');
     if (classInput) classInput.value = cls;
-    var wifi = document.getElementById('add-hw-wifi-fields');
-    // setBlockEnabled, not .hidden: the country select is inside this
-    // nested hidden span, so opening the row left it disabled.
-    if (wifi) setBlockEnabled(wifi, cls === 'wifi');
-    var country = document.getElementById('add-hw-wifi-country');
-    if (country) country.required = (cls === 'wifi');
   });
 })();
 
diff --git a/src/webui/templates/pages/configure-hardware.html b/src/webui/templates/pages/configure-hardware.html
index 7096185f4..0ca6e44d1 100644
--- a/src/webui/templates/pages/configure-hardware.html
+++ b/src/webui/templates/pages/configure-hardware.html
@@ -20,6 +20,27 @@
 {{end}}
 
 
+ {{if .CountryOptions}} +
+
WiFi
+
+ + + + + + +
Country code{{template "field-info" .DescCountry}} + + + + +
+
+
+ {{end}}
Hardware unlocked {{else}}locked{{end}} {{else if $c.IsWiFi}} - {{$c.CountryCode}}{{if $c.Band}} · {{$c.Band}}{{end}} + {{if $c.Band}}{{$c.Band}}{{else}}configured{{end}} {{else if $c.IsGPS}} configured {{else}}—{{end}} @@ -109,13 +130,6 @@ {{end}} - - @@ -199,16 +213,6 @@ hx-confirm="Reset description to its YANG default?">{{template "icon-reset"}} - - Country code{{template "field-info" .DescCountry}} - - - - - Band{{template "field-info" .DescBand}} diff --git a/src/webui/templates/pages/configure-interfaces.html b/src/webui/templates/pages/configure-interfaces.html index 5e4109ee7..45fd18cc1 100644 --- a/src/webui/templates/pages/configure-interfaces.html +++ b/src/webui/templates/pages/configure-interfaces.html @@ -437,8 +437,8 @@

WiFi

Country code - + {{range $.WizardCountryOptions}}{{end}} @@ -1544,10 +1544,10 @@

Add Interface

- Country code{{template "field-info" "ISO 3166-1 country code — required. Regulators tie the radio's channel/power allowance to this; '00' (world) cannot be used in Access Point mode."}} + Country code{{template "field-info" "ISO 3166-1 country code, one setting shared by every radio in the system. Regulators tie the channel/power allowance to this; '00' (world) cannot be used in Access Point mode."}} - + {{range .WizardCountryOptions}}{{end}} From 87ff7b22f402efc88adb60eb3ef447ba608b0aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 00:02:33 +0200 Subject: [PATCH 22/45] confd: wifi: Enable management frame protection on stations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A WPA3-only access point requires it, and on 6 GHz every access point does. wpa_supplicant silently skipped those networks as candidates, so the station saw them in the scan but never tried to associate. Set it as capable rather than required, WPA2 networks without it still work. Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 2 ++ src/confd/src/if-wifi.c | 4 ++++ test/case/interfaces/wifi_wds_link_2dut/test.py | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 6aa119d03..6ac6e7ec1 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -71,6 +71,8 @@ All notable changes to the project are documented in this file. like a DHCPv4 route. Before, the route preference setting was ignored - Fix #1679: a WiFi station added to a scan-only interface, as in the Raspberry Pi 4 factory configuration, did not connect until a reboot +- A WiFi station never connected to a WPA3-only access point, which + includes every access point on 6 GHz [pppoe]: https://www.kernelkit.org/infix/latest/pppoe/ [wds]: https://www.kernelkit.org/infix/latest/wifi/#wds-backhaul-and-repeaters diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 7a5697a1e..15b860305 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -304,8 +304,12 @@ int wifi_gen_station(struct lyd_node *cif) if (!strcmp(security_mode, "disabled")) asprintf(&security_str, "key_mgmt=NONE"); else if (secret) + /* ieee80211w=1: MFP capable. WPA3-only APs, and every + * AP on 6 GHz, require it and are skipped as candidates + * without it; WPA2 APs without MFP still work. */ asprintf(&security_str, "key_mgmt=FT-SAE FT-PSK SAE WPA-PSK\n" + " ieee80211w=1\n" " psk=\"%s\"", secret); /* bgscan="" disables background scanning once associated: on a diff --git a/test/case/interfaces/wifi_wds_link_2dut/test.py b/test/case/interfaces/wifi_wds_link_2dut/test.py index c44d194c4..58e523edf 100755 --- a/test/case/interfaces/wifi_wds_link_2dut/test.py +++ b/test/case/interfaces/wifi_wds_link_2dut/test.py @@ -62,7 +62,7 @@ def root_config(): "radio": "radio0", "access-point": { "ssid": SSID, - "security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"}, + "security": {"mode": "wpa3-personal", "secret": "wifi"}, }, }), wifi.wds_link("wds0", "wifi0", SAT_MAC, bridge="br0", pvid=10), From d96fa2d04d34387ac8e8be4599344517325d59ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 00:02:33 +0200 Subject: [PATCH 23/45] statd: wifi: Tell WPA3-only networks apart in scan results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wpa_supplicant labels every RSN network WPA2, so an SAE-only network showed up as WPA2-Personal. Classify by key management instead. Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 1 + src/statd/python/yanger/ietf_interfaces/wifi.py | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 6ac6e7ec1..1ea8184b0 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -73,6 +73,7 @@ All notable changes to the project are documented in this file. Raspberry Pi 4 factory configuration, did not connect until a reboot - A WiFi station never connected to a WPA3-only access point, which includes every access point on 6 GHz +- WiFi scan results listed WPA3-only networks as WPA2-Personal [pppoe]: https://www.kernelkit.org/infix/latest/pppoe/ [wds]: https://www.kernelkit.org/infix/latest/wifi/#wds-backhaul-and-repeaters diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index 7dc0f69d3..39615a8d1 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -287,12 +287,15 @@ def extract_encryption(flags): 'auth_type': 'Unknown' } - # Extract WPA protocols - if 'WPA3' in flags: + # wpa_supplicant labels every RSN network WPA2, so WPA3 is told + # apart by its key management: SAE only is WPA3, SAE next to PSK + # is a WPA2/WPA3 transition network. + rsn = 'WPA2-' in flags + if rsn and 'SAE' in flags: encryption_info['protocols'].append('WPA3') - if 'WPA2' in flags: + if rsn and ('PSK' in flags or 'EAP' in flags): encryption_info['protocols'].append('WPA2') - if 'WPA-' in flags and 'WPA2' not in flags and 'WPA3' not in flags: + if 'WPA-' in flags and not rsn: encryption_info['protocols'].append('WPA') # Extract key management methods From 26f87e568a31e7cff866648e9cf78f25e7e40f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 00:02:33 +0200 Subject: [PATCH 24/45] webui: Click the survey chart to open it in an overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chart is capped at 220 px in the radio card, too small to read on a radio with many channels. A click opens a copy in a dialog sized to the window, closed with the button, the backdrop or Escape. Signed-off-by: Mattias Walström --- src/webui/static/css/style.css | 31 +++++++++++++++++++++++++++++ src/webui/static/js/app.js | 22 ++++++++++++++++++++ src/webui/templates/pages/wifi.html | 13 +++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/webui/static/css/style.css b/src/webui/static/css/style.css index c69ae3b9a..a44b4f1cb 100644 --- a/src/webui/static/css/style.css +++ b/src/webui/static/css/style.css @@ -2205,6 +2205,37 @@ details[open] > .cfg-multi-summary { width: auto; } +/* Click the chart to open it in an overlay sized to the window. */ +.survey-zoom { + display: block; + width: 100%; + padding: 0; + border: 0; + background: none; + cursor: zoom-in; +} + +.survey-zoom .survey-chart { + margin: 0 auto; +} + +.survey-dialog { + min-width: 0; + width: min(96vw, 1400px); +} + +.survey-dialog .iface-modal-body { + padding: 1rem; +} + +.survey-dialog .survey-chart { + display: block; + width: 100%; + height: auto; + max-height: 82vh; + margin: 0 auto; +} + .wifi-ssid { font-weight: 400; color: var(--fg-muted); diff --git a/src/webui/static/js/app.js b/src/webui/static/js/app.js index 934121459..126e982ef 100644 --- a/src/webui/static/js/app.js +++ b/src/webui/static/js/app.js @@ -1100,6 +1100,28 @@ function setBlockEnabled(el, on) { }); }); + // WiFi channel survey: click the chart to open a copy of it in the + // page's survey dialog, sized to the window. Delegated so it survives + // the htmx swap that replaces the chart on every scan. A click on the + // backdrop closes the dialog, like the close button. + document.addEventListener('click', function (e) { + var zoom = e.target.closest && e.target.closest('[data-survey-zoom]'); + if (!zoom) return; + var dlg = document.getElementById('survey-dialog'); + var svg = zoom.querySelector('svg'); + if (!dlg || !dlg.showModal || !svg) return; + var body = document.getElementById('survey-dialog-body'); + var radio = document.getElementById('survey-dialog-radio'); + body.innerHTML = ''; + body.appendChild(svg.cloneNode(true)); + if (radio) radio.textContent = zoom.getAttribute('data-survey-zoom') || ''; + dlg.showModal(); + }); + document.addEventListener('click', function (e) { + var dlg = e.target; + if (dlg && dlg.id === 'survey-dialog' && dlg.open) dlg.close(); + }); + // Add Interface modal — open via data-show-modal, close via // data-close-modal. Mirrors the existing data-show/data-hide vocabulary // for action-on-target attributes. Native .showModal() gives us diff --git a/src/webui/templates/pages/wifi.html b/src/webui/templates/pages/wifi.html index 79d71d4c2..a97527ac5 100644 --- a/src/webui/templates/pages/wifi.html +++ b/src/webui/templates/pages/wifi.html @@ -157,12 +157,23 @@

No WiFi radios detected.

{{end}} + +
+

Channel Survey

+ +
+
+
{{end}} {{define "wifi-survey"}}
{{if .SVG}} - {{.SVG}} + {{else if .Error}}
{{.Error}}
{{else}} From 34521dec7b2d8b3632fbb7b765c945f2f30b3cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 08:15:21 +0200 Subject: [PATCH 25/45] webui: Survive a broken YANG schema cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A module file cut short, left by a restart in the middle of a download, made goyang dereference nil at every start. Write cached files through a temporary name, turn a parser panic into an error, drop a cache that fails to load so the next refresh fetches it again, and prune files the device no longer lists so two revisions of a module never load together. Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 2 + .../internal/schema/cache_broken_test.go | 109 ++++++++++++++++++ src/webui/internal/schema/fetch.go | 46 +++++++- src/webui/internal/schema/manager.go | 11 +- src/webui/internal/schema/refresh.go | 24 +++- 5 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 src/webui/internal/schema/cache_broken_test.go diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 1ea8184b0..09c65b0c6 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -54,6 +54,8 @@ All notable changes to the project are documented in this file. - WebUI: uploading a software bundle failed on slow storage - WebUI: the Overview page showed the hostname template, e.g. `rpi-%m`, and never showed the boot partition +- WebUI: a damaged schema cache, e.g. after a restart during download, + crashed the WebUI at every start - LLDP neighbors were listed without their system name, descriptions, and capabilities, in the CLI, the WebUI, and the operational datastore - The manufacturer from a VPD was not shown as `mfg-name` of its diff --git a/src/webui/internal/schema/cache_broken_test.go b/src/webui/internal/schema/cache_broken_test.go new file mode 100644 index 000000000..c173e84b1 --- /dev/null +++ b/src/webui/internal/schema/cache_broken_test.go @@ -0,0 +1,109 @@ +package schema + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "infix/webui/internal/restconf" +) + +// A module cut short, next to a submodule that uses a type from it, makes +// goyang dereference nil instead of reporting an error. +func writeBrokenCache(t *testing.T, dir string) { + t.Helper() + files := map[string]string{ + "m@2026-01-01.yang": "module m { yang-version 1.1; namespace \"urn:m\"; prefix m; include s; typedef t { ", + "s@2026-01-01.yang": "submodule s { yang-version 1.1; belongs-to m { prefix m; } leaf x { type t; } }", + } + for name, body := range files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0640); err != nil { + t.Fatal(err) + } + } +} + +func TestLoadRejectsBrokenModule(t *testing.T) { + dir := t.TempDir() + writeBrokenCache(t, dir) + if _, err := Load(dir); err == nil { + t.Fatal("Load accepted a cut-short module") + } +} + +func TestLoadFromCacheDropsBrokenCache(t *testing.T) { + dir := t.TempDir() + writeBrokenCache(t, dir) + if err := os.WriteFile(filepath.Join(dir, ".version"), []byte("v1\n"), 0640); err != nil { + t.Fatal(err) + } + + if err := NewCache(nil, dir, "v1").LoadFromCache(); err == nil { + t.Fatal("broken cache loaded without error") + } + entries, _ := os.ReadDir(dir) + for _, e := range entries { + if strings.HasSuffix(e.Name(), ".yang") { + t.Errorf("%s kept in a broken cache", e.Name()) + } + } + if b, _ := os.ReadFile(filepath.Join(dir, ".version")); string(b) != "v1\n" { + t.Errorf("stamp = %q", b) + } +} + +// fakeFetcher serves a modules-state listing and the module files from a +// map. Other Fetcher methods are never called. +type fakeFetcher struct { + restconf.Fetcher + modules map[string]string // name@revision -> body +} + +func (f fakeFetcher) Get(_ context.Context, path string, target any) error { + ms := target.(*rfc7895ModulesState) + for key := range f.modules { + name, rev, _ := strings.Cut(key, "@") + ms.ModulesState.Module = append(ms.ModulesState.Module, struct { + Name string `json:"name"` + Revision string `json:"revision"` + Submodule []struct { + Name string `json:"name"` + Revision string `json:"revision"` + } `json:"submodule"` + }{Name: name, Revision: rev}) + } + return nil +} + +func (f fakeFetcher) GetYANG(_ context.Context, name, revision string) ([]byte, error) { + return []byte(f.modules[name+"@"+revision]), nil +} + +func TestFetchModulesPrunesStaleRevisions(t *testing.T) { + dir := t.TempDir() + stale := filepath.Join(dir, "example@2025-01-01.yang") + if err := os.WriteFile(stale, []byte("module example { namespace x; prefix x; }"), 0640); err != nil { + t.Fatal(err) + } + + rc := fakeFetcher{modules: map[string]string{ + "example@2026-01-01": "module example { namespace x; prefix x; }", + }} + if _, err := FetchModules(context.Background(), rc, dir); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(stale); !os.IsNotExist(err) { + t.Errorf("stale revision kept: %v", err) + } + if _, err := os.Stat(filepath.Join(dir, "example@2026-01-01.yang")); err != nil { + t.Errorf("current revision missing: %v", err) + } + entries, _ := os.ReadDir(dir) + for _, e := range entries { + if strings.HasPrefix(e.Name(), ".example") { + t.Errorf("temporary file left behind: %s", e.Name()) + } + } +} diff --git a/src/webui/internal/schema/fetch.go b/src/webui/internal/schema/fetch.go index 41a55fbc1..940a47287 100644 --- a/src/webui/internal/schema/fetch.go +++ b/src/webui/internal/schema/fetch.go @@ -6,6 +6,7 @@ import ( "log" "os" "path/filepath" + "strings" "infix/webui/internal/restconf" ) @@ -71,9 +72,32 @@ func FetchModules(ctx context.Context, rc restconf.Fetcher, cacheDir string) ([] } downloaded = append(downloaded, m) } + prune(cacheDir, modules) return downloaded, nil } +// prune removes cached YANG files the device no longer lists, such as an +// earlier revision of a module, so that two revisions never load together. +func prune(cacheDir string, modules []ModuleInfo) { + keep := make(map[string]bool, len(modules)) + for _, m := range modules { + keep[m.filename()] = true + } + entries, err := os.ReadDir(cacheDir) + if err != nil { + return + } + for _, e := range entries { + name := e.Name() + if e.IsDir() || !strings.HasSuffix(name, ".yang") || keep[name] { + continue + } + if err := os.Remove(filepath.Join(cacheDir, name)); err == nil { + log.Printf("schema: dropped %s, no longer on the device", name) + } + } +} + // listModules queries the device for the list of implemented YANG modules. func listModules(ctx context.Context, rc restconf.Fetcher) ([]ModuleInfo, error) { // Try RFC 7895 modules-state first. @@ -117,7 +141,27 @@ func downloadIfMissing(ctx context.Context, rc restconf.Fetcher, cacheDir string return fmt.Errorf("GET /yang/%s: %w", m.filename(), err) } - if err := os.WriteFile(dest, data, 0640); err != nil { + // Write through a temporary name so that a restart in the middle of + // the download never leaves a cut-short module behind. + tmp, err := os.CreateTemp(cacheDir, "."+m.Name+".*") + if err != nil { + return fmt.Errorf("write %s: %w", dest, err) + } + if _, err := tmp.Write(data); err != nil { + tmp.Close() + os.Remove(tmp.Name()) + return fmt.Errorf("write %s: %w", dest, err) + } + if err := tmp.Close(); err != nil { + os.Remove(tmp.Name()) + return fmt.Errorf("write %s: %w", dest, err) + } + if err := os.Chmod(tmp.Name(), 0640); err != nil { + os.Remove(tmp.Name()) + return fmt.Errorf("write %s: %w", dest, err) + } + if err := os.Rename(tmp.Name(), dest); err != nil { + os.Remove(tmp.Name()) return fmt.Errorf("write %s: %w", dest, err) } log.Printf("schema: cached %s", m.filename()) diff --git a/src/webui/internal/schema/manager.go b/src/webui/internal/schema/manager.go index b888cf744..8095e1fd4 100644 --- a/src/webui/internal/schema/manager.go +++ b/src/webui/internal/schema/manager.go @@ -23,7 +23,16 @@ type Manager struct { // Load parses all .yang files in yangDir and returns a Manager. // Errors from Process() that are non-fatal (e.g. unresolved augments for // modules that were not downloaded) are logged but do not abort loading. -func Load(yangDir string) (*Manager, error) { +func Load(yangDir string) (mgr *Manager, err error) { + // goyang dereferences nil on some malformed input, for example a + // module file cut short, instead of returning an error. The cache is + // rebuilt from the device on failure, so turn that into an error. + defer func() { + if r := recover(); r != nil { + mgr, err = nil, fmt.Errorf("schema: parse %s: %v", yangDir, r) + } + }() + ms := yang.NewModules() ms.Path = []string{yangDir} diff --git a/src/webui/internal/schema/refresh.go b/src/webui/internal/schema/refresh.go index 31279d460..18243f617 100644 --- a/src/webui/internal/schema/refresh.go +++ b/src/webui/internal/schema/refresh.go @@ -41,13 +41,23 @@ func (c *Cache) dropStale() error { if b, err := os.ReadFile(stamp); err == nil && strings.TrimSpace(string(b)) == c.version { return nil } + log.Printf("schema: cache in %s is for another image, dropped", c.dir) + return c.drop() +} + +// drop empties the cache directory and stamps it for this image, so the +// next Refresh downloads every module again. +func (c *Cache) drop() error { if err := os.RemoveAll(c.dir); err != nil { return err } if err := os.MkdirAll(c.dir, 0750); err != nil { return err } - log.Printf("schema: cache in %s is for another image, dropped", c.dir) + if c.version == "" { + return nil + } + stamp := filepath.Join(c.dir, ".version") return os.WriteFile(stamp, []byte(c.version+"\n"), 0640) } @@ -80,7 +90,12 @@ func (c *Cache) LoadFromCache() error { mgr, err := Load(c.dir) if err != nil { - return fmt.Errorf("schema: load from cache: %w", err) + // A broken file would fail every start, drop the lot and let + // the next Refresh fetch a fresh copy. + if derr := c.drop(); derr != nil { + return fmt.Errorf("schema: load from cache: %w (and dropping it: %v)", err, derr) + } + return fmt.Errorf("schema: load from cache: %w, cache dropped", err) } c.mu.Lock() c.manager = mgr @@ -122,7 +137,10 @@ func (c *Cache) Refresh(ctx context.Context) error { mgr, err := Load(c.dir) if err != nil { - return fmt.Errorf("schema refresh: load: %w", err) + if derr := c.drop(); derr != nil { + return fmt.Errorf("schema refresh: load: %w (and dropping the cache: %v)", err, derr) + } + return fmt.Errorf("schema refresh: load: %w, cache dropped", err) } c.mu.Lock() From be8fe356608e723ba8cf8988f707dca17ada5f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 09:12:15 +0200 Subject: [PATCH 26/45] confd: Run the hardware handler after the interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hostapd binds to WiFi netdevs. With the hardware handler first, a commit that recreates an access point netdev, e.g. for a new MAC address, restarted hostapd before the interface pipeline rebuilt the netdev. hostapd bound to the one about to be deleted and kept reporting the access point enabled while the new netdev sat idle. Run the interfaces first, then the hardware. Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 2 ++ src/confd/src/core.c | 10 ++++--- src/confd/src/hardware.c | 6 +++- .../interfaces/wifi_wds_repeater/test.adoc | 11 ++++++- .../case/interfaces/wifi_wds_repeater/test.py | 29 +++++++++++++++++-- 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 09c65b0c6..d8cf9c866 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -76,6 +76,8 @@ All notable changes to the project are documented in this file. - A WiFi station never connected to a WPA3-only access point, which includes every access point on 6 GHz - WiFi scan results listed WPA3-only networks as WPA2-Personal +- Changing the MAC address of a WiFi access point could leave the access + points on that radio down until the WiFi service was restarted [pppoe]: https://www.kernelkit.org/infix/latest/pppoe/ [wds]: https://www.kernelkit.org/infix/latest/wifi/#wds-backhaul-and-repeaters diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 935e74c16..2aa0b3b75 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -734,14 +734,16 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod } } - /* ietf-hardware */ - if ((rc = hardware_change(session, config, diff, event, confd))) - goto free_diff; - /* ietf-interfaces */ if ((rc = interfaces_change(session, config, diff, event, confd))) goto free_diff; + /* ietf-hardware, after the interfaces: hostapd binds to WiFi + * netdevs, so it must be (re)started only once the interface + * pipeline has created or recreated them. */ + if ((rc = hardware_change(session, config, diff, event, confd))) + goto free_diff; + /* infix-dhcp-client*/ if ((rc = dhcp_client_change(session, config, diff, event, confd))) goto free_diff; diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 051c2585c..adba498bd 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -249,7 +249,7 @@ static const char *wifi_find_higher_band_twin(struct lyd_node *config, if (!wifi) continue; ap = lydx_get_child(wifi, "access-point"); - if (!ap) + if (!ap || !lydx_is_enabled(cif, "enabled")) continue; ssid = lydx_get_cattr(ap, "ssid"); if (!ssid || strcmp(ssid, current_ssid)) @@ -317,6 +317,10 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name, ap = lydx_get_child(wifi, "access-point"); if (!ap) continue; + /* hostapd brings every BSS it is given up, a disabled one + * must not be in its config at all. */ + if (!lydx_is_enabled(cif, "enabled")) + continue; list = realloc(list, sizeof(char *) * (n + 1)); ifname = lydx_get_cattr(cif, "name"); diff --git a/test/case/interfaces/wifi_wds_repeater/test.adoc b/test/case/interfaces/wifi_wds_repeater/test.adoc index 166ee8b46..252c468db 100644 --- a/test/case/interfaces/wifi_wds_repeater/test.adoc +++ b/test/case/interfaces/wifi_wds_repeater/test.adoc @@ -20,6 +20,11 @@ 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. +Finally the guest access point gets a new MAC address, which recreates +its netdev. The home access point on the same radio must not be +disturbed, and the guest access point has to come back at the new +address with its client. + Topology: .... host ==(lan, VLAN 10+20)== root (AP 'infix-backhaul') @@ -49,6 +54,10 @@ image::topology.svg[WiFi repeater with two SSIDs in VLANs over a 4-address (WDS) . 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 +. Change the address of the repeater's 'infix-guest' access point to 02:00:00:00:0b:12 +. Verify wifi2 on the repeater reports the new address and both access points are up +. Verify guest associates to 'infix-guest' at the new address, BSSID 02:00:00:00:0b:12 +. Verify the host reaches home at 10.10.0.9 and guest at 10.20.0.9 after the address change +. Verify home is still on its access point diff --git a/test/case/interfaces/wifi_wds_repeater/test.py b/test/case/interfaces/wifi_wds_repeater/test.py index 6cb525a8a..bc92e981f 100755 --- a/test/case/interfaces/wifi_wds_repeater/test.py +++ b/test/case/interfaces/wifi_wds_repeater/test.py @@ -18,6 +18,11 @@ Taking the backhaul down and up again shows it is a transparent bridge port: the stations lose and regain reach without re-associating. +Finally the guest access point gets a new MAC address, which recreates +its netdev. The home access point on the same radio must not be +disturbed, and the guest access point has to come back at the new +address with its client. + Topology: .... host ==(lan, VLAN 10+20)== root (AP 'infix-backhaul') @@ -39,6 +44,7 @@ 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" +GUEST_AP_MAC_NEW = "02:00:00:00:0b:12" HOME_MAC = "02:00:00:00:00:09" GUEST_MAC = "02:00:00:00:00:0a" @@ -230,8 +236,27 @@ def reaches(ns, addr): 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: + with test.step("Change the address of the repeater's 'infix-guest' access point to 02:00:00:00:0b:12"): + repeater.put_config_dicts({"ietf-interfaces": {"interfaces": {"interface": [{ + "name": "wifi2", + "custom-phys-address": {"static": GUEST_AP_MAC_NEW}, + }]}}}) + + with test.step("Verify wifi2 on the repeater reports the new address and both access points are up"): + until(lambda: (iface.get_phys_address(repeater, "wifi2") or "").lower() == GUEST_AP_MAC_NEW, + attempts=30, interval=2) + until(lambda: iface.is_oper_up(repeater, "wifi2"), attempts=45, interval=2) + until(lambda: iface.is_oper_up(repeater, "wifi1"), attempts=30, interval=2) + + with test.step("Verify guest associates to 'infix-guest' at the new address, BSSID 02:00:00:00:0b:12"): + until(lambda: wifi.station_bssid(guest) == GUEST_AP_MAC_NEW, attempts=60, interval=2) + + with test.step("Verify the host reaches home at 10.10.0.9 and guest at 10.20.0.9 after the address change"): + 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 is still on its access point"): + if wifi.station_bssid(home) != HOME_AP_MAC: test.fail() test.succeed() From 1a49699cb48cef3acd32c6f3695d01ab2132ff6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 15:13:26 +0200 Subject: [PATCH 27/45] hostapd: Add ft_iface for the 802.11r key holder exchange MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exchange is sent on the bridge the BSS is a port of. On a VLAN filtering bridge a frame from the bridge device lands in the bridge's own untagged VLAN, if any, not in the access points' VLAN, and naming another interface as the bridge makes hostapd move the BSS into it. Add a per-BSS ft_iface option for the interface to use instead. Signed-off-by: Mattias Walström --- ...ace-for-the-802.11r-key-holder-excha.patch | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 patches/hostapd/0004-hostapd-Add-ft_iface-for-the-802.11r-key-holder-excha.patch diff --git a/patches/hostapd/0004-hostapd-Add-ft_iface-for-the-802.11r-key-holder-excha.patch b/patches/hostapd/0004-hostapd-Add-ft_iface-for-the-802.11r-key-holder-excha.patch new file mode 100644 index 000000000..8c76a3077 --- /dev/null +++ b/patches/hostapd/0004-hostapd-Add-ft_iface-for-the-802.11r-key-holder-excha.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Tue, 7 Oct 2026 15:00:00 +0200 +Subject: [PATCH 4/4] hostapd: Add ft_iface for the 802.11r key holder exchange +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The R0KH/R1KH exchange is sent on the bridge the BSS is a port of, or +on the BSS interface itself. On a VLAN filtering bridge a frame sent +from the bridge device belongs to the bridge's own untagged VLAN, if it +has one, which need not be the VLAN of the access points. Nor can the +bridge option name another interface: hostapd then moves the BSS into +that bridge. + +Add a per-BSS option for the interface to use for the exchange, for +example the VLAN interface of the access points' VLAN: + + ft_iface= + +Signed-off-by: Mattias Walström +--- + hostapd/config_file.c | 2 ++ + hostapd/hostapd.conf | 5 +++++ + src/ap/ap_config.h | 1 + + src/ap/wpa_auth_glue.c | 3 ++- + 4 files changed, 10 insertions(+), 1 deletion(-) + +diff -ruN a/hostapd/config_file.c b/hostapd/config_file.c +--- a/hostapd/config_file.c ++++ b/hostapd/config_file.c +@@ -2419,6 +2419,8 @@ + sizeof(conf->bss[0]->iface)); + } else if (os_strcmp(buf, "bridge") == 0) { + os_strlcpy(bss->bridge, pos, sizeof(bss->bridge)); ++ } else if (os_strcmp(buf, "ft_iface") == 0) { ++ os_strlcpy(bss->ft_iface, pos, sizeof(bss->ft_iface)); + } else if (os_strcmp(buf, "bridge_hairpin") == 0) { + bss->bridge_hairpin = atoi(pos); + } else if (os_strcmp(buf, "vlan_bridge") == 0) { +diff -ruN a/hostapd/hostapd.conf b/hostapd/hostapd.conf +--- a/hostapd/hostapd.conf ++++ b/hostapd/hostapd.conf +@@ -19,6 +19,11 @@ + # has been started to change the interface mode). If needed, the bridge + # interface is also created. + #bridge=br0 ++ ++# Interface to send and receive the 802.11r R0KH/R1KH exchange on. Defaults ++# to the bridge, or to the BSS interface when no bridge is configured. Use ++# it to name the VLAN interface of the access points on a VLAN aware bridge. ++#ft_iface=br0.1 + + # Driver interface type (wired/none/nl80211/bsd); + # default: nl80211). nl80211 is used with all Linux mac80211 drivers. +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 +@@ -290,6 +290,7 @@ + struct hostapd_bss_config { + char iface[IFNAMSIZ + 1]; + char bridge[IFNAMSIZ + 1]; ++ char ft_iface[IFNAMSIZ + 1]; + char vlan_bridge[IFNAMSIZ + 1]; + char wds_bridge[IFNAMSIZ + 1]; + int bridge_hairpin; /* hairpin_mode on bridge members */ +diff -ruN a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c +--- a/src/ap/wpa_auth_glue.c ++++ b/src/ap/wpa_auth_glue.c +@@ -1918,7 +1918,8 @@ + wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt)) { + const char *ft_iface; + +- ft_iface = hapd->conf->bridge[0] ? hapd->conf->bridge : ++ ft_iface = hapd->conf->ft_iface[0] ? hapd->conf->ft_iface : ++ hapd->conf->bridge[0] ? hapd->conf->bridge : + hapd->conf->iface; + hapd->l2 = l2_packet_init(ft_iface, NULL, ETH_P_RRB, + hostapd_rrb_receive, hapd, 1); From 6eadecfcbd3d0b4846674729f420c43bc2e1c903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 09:49:12 +0200 Subject: [PATCH 28/45] confd: wifi: Exchange 802.11r keys between access points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fast transition to an access point on another device failed for WPA3 clients with status 53, invalid PMKID: their PMK comes from the SAE handshake, so the target cannot regenerate it from the passphrase like ft_psk_generate_local does for WPA2. Give every access point of the SSID wildcard R0KH/R1KH entries with a key derived from the mobility domain and a shared secret, so the target fetches the PMK-R1 from the access point the client came from, and tell hostapd about the bridge so that exchange reaches the other devices. The secret is the passphrase unless an optional key-holder-secret, a keystore reference under dot11r, is set. Every client knows the passphrase and whoever holds the key is handed every client's keys, so a shared network wants a key of its own. Signed-off-by: Mattias Walström --- doc/ChangeLog.md | 5 + doc/wifi.md | 30 +++++- src/confd/src/hardware.c | 132 +++++++++++++++++++++--- src/confd/yang/confd/infix-if-wifi.yang | 19 +++- 4 files changed, 165 insertions(+), 21 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index d8cf9c866..6ac20ec40 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -45,6 +45,9 @@ All notable changes to the project are documented in this file. - The WiFi country code is one setting for the whole system, `hardware wifi country-code`, instead of one per radio, and applies as soon as it is set. Existing configurations are migrated +- 802.11r access points can be given a `key-holder-secret`, a keystore + key of their own to protect the key exchange with instead of the WiFi + password, see [WiFi documentation](wifi.md#80211r---fast-bss-transition) - WiFi channel survey is on request: `show hardware survey`, the Scan channels button on the WebUI WiFi page, or the `channel-survey` action. The always-on `survey` container under the radio is gone @@ -76,6 +79,8 @@ All notable changes to the project are documented in this file. - A WiFi station never connected to a WPA3-only access point, which includes every access point on 6 GHz - WiFi scan results listed WPA3-only networks as WPA2-Personal +- Fast roaming (802.11r) to an access point on another device failed for + WPA3 clients - Changing the MAC address of a WiFi access point could leave the access points on that radio down until the WiFi service was restarted diff --git a/doc/wifi.md b/doc/wifi.md index 515e10323..f0b28380d 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -604,14 +604,38 @@ your requirements. Enable 802.11r for fast handoff (<50ms) between APs with the same SSID: ``` -admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r +admin@example:/config/> edit interface wifi0 admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11r mobility-domain 4f57 ``` +The access points hand each other a roaming client's keys, protected +with a secret they all share. By default that is the WiFi password, +which is fine for a home or small office network. Anyone who knows the +password can then also read the other clients' keys, so on a network +shared with people you do not trust, give the access points a secret of +their own, the key holder secret. Create it in the keystore on every +device, with the same value: + +``` +admin@example:/config/> edit keystore symmetric-keys symmetric-key roaming-key +admin@example:/config/keystore/symmetric-keys/symmetric-key/roaming-key/> set key-format passphrase-key-format +admin@example:/config/keystore/symmetric-keys/symmetric-key/roaming-key/> set cleartext-symmetric-key
+admin@example:/config/keystore/symmetric-keys/symmetric-key/roaming-key/> end +admin@example:/config/> set interface wifi0 wifi access-point roaming dot11r key-holder-secret roaming-key +``` + **Requirements:** - All APs in roaming group must have **identical** SSID - All APs must have **identical** passphrase (same keystore secret) +- All APs must have the **same key holder secret**, if one is set - All APs must use the **same mobility-domain** identifier +- APs on different devices must be ports of bridges that are connected + to each other, over a cable, a mesh or a WDS backhaul. The APs hand a + roaming client's keys to each other over that network, which WPA3 + clients need for a fast transition. On a bridge with VLAN filtering + the keys travel in the APs' VLAN, so each device needs a VLAN + interface on the bridge for that VLAN, the one carrying its IP address + there is enough **Mobility Domain Options:** - Explicit 4-character hex value (e.g., `4f57`) - default if not specified @@ -838,8 +862,8 @@ AP interfaces for clients on another: ![802.11s mesh backhaul with roaming-enabled access points](img/wifi-mesh-roaming.svg) 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. +passphrase, same mobility domain), clients hand +off between nodes while the mesh carries backhaul traffic. ## WDS Backhaul and Repeaters diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index adba498bd..4d2656c74 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -221,6 +221,75 @@ static const char *resolve_mobility_domain(const char *mobility_domain, const ch return hash_result; } +/* + * Cleartext of a keystore symmetric key, NULL when unset. Caller frees. + */ +static unsigned char *wifi_keystore_secret(struct lyd_node *config, const char *name) +{ + struct lyd_node *node; + const char *b64; + + if (!name) + return NULL; + + node = lydx_get_xpathf(config, "/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key", name); + if (!node) + return NULL; + + b64 = lyd_get_value(node); + if (!b64) + return NULL; + + return base64_decode((const unsigned char *)b64, strlen(b64), NULL); +} + +/* + * Key for the 802.11r key holder exchange between access points, hex + * encoded SHA-256 over the mobility domain and the key holder secret. + * Every AP of the mobility domain derives the same key, so the wildcard + * R0KH/R1KH entries let any of them fetch a roaming client's PMK-R1 from + * the AP it came from, which is what fast transition needs for WPA3 + * (SAE) clients: their PMK comes from the SAE handshake and cannot be + * regenerated locally the way a WPA2 PSK can. + * + * The secret is the dot11r key-holder-secret when one is set, else the + * WiFi password. A holder of the key can fetch any client's keys and is + * pushed them, and the key tags the announcements wifi-neighbors.py + * sends on the APs' network, so with the password as the secret every + * client can read the others' keys and anyone on the network has an + * offline oracle for the password. Hence the opt-in key of its own. + */ +static int wifi_ft_key(const char *mobility_domain, const unsigned char *secret, char *out, size_t len) +{ + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int dlen = 0; + EVP_MD_CTX *ctx; + size_t i; + + if (len < 2 * 32 + 1) + return -1; + + ctx = EVP_MD_CTX_new(); + if (!ctx) + return -1; + + if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1 || + EVP_DigestUpdate(ctx, "infix-ft:", 9) != 1 || + EVP_DigestUpdate(ctx, mobility_domain, strlen(mobility_domain)) != 1 || + EVP_DigestUpdate(ctx, ":", 1) != 1 || + EVP_DigestUpdate(ctx, secret, strlen((const char *)secret)) != 1 || + EVP_DigestFinal_ex(ctx, digest, &dlen) != 1 || dlen < 32) { + EVP_MD_CTX_free(ctx); + return -1; + } + EVP_MD_CTX_free(ctx); + + for (i = 0; i < 32; i++) + snprintf(out + 2 * i, 3, "%02x", digest[i]); + + return 0; +} + /* * Find an AP interface on a higher-band radio (5/6 GHz) advertising the * same SSID as the caller's 2.4 GHz BSS, for no_probe_resp_if_seen_on=. @@ -378,9 +447,9 @@ static void wifi_gen_wds_ports(FILE *hostapd, struct lyd_node *config, const cha /* 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) { - struct lyd_node *wifi, *ap, *security, *secret_node, *roaming; + struct lyd_node *wifi, *ap, *security, *roaming; struct lyd_node *dot11k, *dot11r, *dot11v; - const char *ssid, *hidden, *security_mode, *secret_name; + const char *ssid, *hidden, *security_mode; const char *mobility_domain, *mobility_domain_raw; const char *nas_identifier_cfg; bool enable_80211k, enable_80211r, enable_80211v, enable_mbo; @@ -464,21 +533,8 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd security_mode = "open"; /* Get secret from keystore if needed */ - if (strcmp(security_mode, "open") != 0) { - secret_name = lydx_get_cattr(security, "secret"); - if (secret_name) { - const char *b64; - - secret_node = lydx_get_xpathf(config, - "/keystore/symmetric-keys/symmetric-key[name='%s']/cleartext-symmetric-key", - secret_name); - if (secret_node) { - b64 = lyd_get_value(secret_node); - if (b64) - secret = base64_decode((const unsigned char *)b64, strlen(b64), NULL); - } - } - } + if (strcmp(security_mode, "open") != 0) + secret = wifi_keystore_secret(config, lydx_get_cattr(security, "secret")); /* Resolve "auto" to concrete mode based on band */ if (!strcmp(security_mode, "auto")) @@ -519,6 +575,11 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd /* 802.11r: Fast BSS Transition */ if (enable_80211r) { + const char *bridge = lydx_get_cattr(lydx_get_child(cif, "bridge-port"), "bridge"); + unsigned char *kh_secret; + const char *kh_name; + char ft_key[65]; + fprintf(hostapd, "# Fast BSS Transition (802.11r)\n"); fprintf(hostapd, "mobility_domain=%s\n", mobility_domain); /* Over-the-air FT: the client authenticates directly with the @@ -527,6 +588,43 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd fprintf(hostapd, "ft_over_ds=0\n"); fprintf(hostapd, "ft_psk_generate_local=1\n"); fprintf(hostapd, "nas_identifier=%s\n", nas_identifier_cfg); + /* The key holders of all APs on the SSID reach each other over + * the network the APs are bridged to, see wifi_ft_key(). On a + * VLAN filtering bridge that is the APs' VLAN: use its VLAN + * interface when there is one, the bridge device itself has + * no say in which VLAN its frames end up in. */ + kh_name = lydx_get_cattr(dot11r, "key-holder-secret"); + if (!kh_name) + kh_name = lydx_get_cattr(security, "secret"); + kh_secret = wifi_keystore_secret(config, kh_name); + if (!kh_secret) + ERROR("%s: 802.11r key holder secret not found in keystore, no key exchange", ifname); + if (kh_secret && !wifi_ft_key(mobility_domain, kh_secret, ft_key, sizeof(ft_key))) { + if (bridge) { + const char *pvid = lydx_get_cattr(lydx_get_child(cif, "bridge-port"), "pvid"); + const char *ft_iface = bridge; + + if (pvid) { + struct lyd_node *vif; + + vif = lydx_get_xpathf(config, "/interfaces/interface[vlan/id='%s' and vlan/lower-layer-if='%s']/name", + pvid, bridge); + if (vif) + ft_iface = lyd_get_value(vif); + } + fprintf(hostapd, "ft_iface=%s\n", ft_iface); + } + fprintf(hostapd, "r0kh=ff:ff:ff:ff:ff:ff * %s\n", ft_key); + fprintf(hostapd, "r1kh=00:00:00:00:00:00 00:00:00:00:00:00 %s\n", ft_key); + /* A client roaming away from a node that is going down + * asks for a key that node can no longer hand out. + * Give up on the fetch quickly and reject, the client + * then logs in the normal way; waiting in silence makes + * it blacklist the target instead. */ + fprintf(hostapd, "rkh_pull_timeout=300\n"); + fprintf(hostapd, "rkh_pull_retries=1\n"); + } + free(kh_secret); } /* 802.11k: Radio Resource Management */ diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 2f4163525..96804b2af 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -52,7 +52,10 @@ submodule infix-if-wifi { revision 2026-10-06 { description "Access point and mesh point require the box-wide WiFi country code, - the per-radio country-code leaf is gone."; + the per-radio country-code leaf is gone. 802.11r gets an optional + key-holder-secret, a key of its own for the access points of a + mobility domain to hand each other a client's keys with, instead + of the WiFi password."; reference "internal"; } @@ -551,6 +554,20 @@ submodule infix-if-wifi { Reduces handoff latency from ~1s to <50ms through pre-authentication. Required for seamless roaming."; + leaf key-holder-secret { + type ks:central-symmetric-key-ref; + description + "Secret the access points of the mobility domain use + to hand each other a roaming client's keys. + + References a symmetric key in the keystore. Set the + same key on every device with an access point in the + mobility domain. Without it the WiFi password is + used, which lets anyone who knows the password read + the other clients' keys. Give it a value of its own + on networks where that matters."; + } + leaf mobility-domain { type mobility-domain; default "4f57"; From 015ad8c9729c0cd465c24ebc0d8d1b22a718b080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 09:41:42 +0200 Subject: [PATCH 29/45] confd: wifi: Hand clients over before hostapd stops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A client with a good signal has no reason to roam, so when its access point goes away it only notices once the beacons stop, and then scans for a new network. Run hostapd through a wrapper that, when stopped, sends every station an 802.11v BSS transition request with disassociation imminent and waits for them to leave, so clients that support it roam while the radio is still up. Give finit ten seconds before SIGKILL to make room for that. Signed-off-by: Mattias Walström --- .../rootfs/usr/libexec/infix/hostapd.sh | 92 +++++++++++++++++++ doc/ChangeLog.md | 4 + doc/wifi.md | 7 ++ src/confd/src/hardware.c | 7 +- .../interfaces/wifi_mesh_roaming/test.adoc | 5 + .../case/interfaces/wifi_mesh_roaming/test.py | 36 +++++++- 6 files changed, 148 insertions(+), 3 deletions(-) create mode 100755 board/common/rootfs/usr/libexec/infix/hostapd.sh diff --git a/board/common/rootfs/usr/libexec/infix/hostapd.sh b/board/common/rootfs/usr/libexec/infix/hostapd.sh new file mode 100755 index 000000000..8c302f6e4 --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/hostapd.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# Run hostapd and, when stopped, hand the clients over first. +# +# A client with a good signal has no reason to roam, so when its access +# point goes away it only notices once the beacons stop, and then has to +# scan for a new network. An 802.11v BSS transition request with +# disassociation imminent makes it roam to another access point while +# the radio is still up. Clients without 802.11v are deauthenticated +# by hostapd on exit, as before. + +# The request names no candidate, a node does not know the other nodes' +# access points, so the client has to scan for one: a few seconds on a +# real radio across three bands. A client that roams never tells the old +# access point either, hostapd drops it from its station list when the +# timer runs out, so wait a little longer than that before giving up, +# but stay well inside the ten seconds the service gets to stop before +# it is killed. +TIMER=50 # beacon intervals (100 ms) until hostapd disassociates a client that stays +WAIT=7 # seconds to wait for the clients to leave + +# The hostapd configs among the arguments, which also carry options +confs() +{ + for arg in $CONFS; do + case $arg in + *.conf) echo "$arg" ;; + esac + done +} + +bsses() +{ + for sock in /run/hostapd/*; do + [ -S "$sock" ] && echo "${sock##*/}" + done +} + +stations() +{ + for bss in $(bsses); do + hostapd_cli -i "$bss" list_sta 2>/dev/null + done +} + +handover() +{ + # Only clients that do 802.11v can be asked to move, and hostapd only + # does so with bss_transition on + grep -qs '^bss_transition=1' $(confs) || return 0 + + num=0 + for bss in $(bsses); do + for sta in $(hostapd_cli -i "$bss" list_sta 2>/dev/null); do + hostapd_cli -i "$bss" disassoc_imminent "$sta" $TIMER >/dev/null 2>&1 + num=$((num + 1)) + done + done + if [ $num -eq 0 ]; then + logger -t hostapd -p daemon.notice "stop: no stations on $(bsses | tr '\n' ' ')" + return 0 + fi + + end=$(($(date +%s) + WAIT)) + while [ "$(date +%s)" -lt "$end" ]; do + [ -z "$(stations)" ] && break + sleep 0.2 + done + + left=$(stations | grep -c .) + if [ "$left" -eq 0 ]; then + logger -t hostapd -p daemon.notice "stop: asked $num station(s) to move, all left" + else + logger -t hostapd -p daemon.notice "stop: asked $num station(s) to move, $left still here" + fi +} + +stop() +{ + handover + kill -TERM "$pid" 2>/dev/null +} + +CONFS=$* +hostapd "$@" & +pid=$! +trap stop TERM INT +rc=0 +while kill -0 "$pid" 2>/dev/null; do + wait "$pid" + rc=$? +done +exit $rc diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 6ac20ec40..5ce77738e 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -45,6 +45,10 @@ All notable changes to the project are documented in this file. - The WiFi country code is one setting for the whole system, `hardware wifi country-code`, instead of one per radio, and applies as soon as it is set. Existing configurations are migrated +- WiFi clients are moved to another access point before theirs stops for + a reboot, an upgrade or a configuration change, instead of losing the + connection. Access points on different devices now know of each other + and share a client's keys ahead of a roam - 802.11r access points can be given a `key-holder-secret`, a keystore key of their own to protect the key exchange with instead of the WiFi password, see [WiFi documentation](wifi.md#80211r---fast-bss-transition) diff --git a/doc/wifi.md b/doc/wifi.md index f0b28380d..da030795c 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -689,6 +689,13 @@ admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v Allows APs to suggest better APs to clients, improving roaming decisions. +An access point about to stop, because the device reboots, is upgraded, +or its WiFi configuration changes, also uses 802.11v to ask its clients +to move first. Clients that support it roam to another access point +with the same SSID while the radio is still up, instead of noticing the +loss afterwards and scanning for a new network. Clients without +802.11v are disconnected as before. + #### Band Steering (MBO) Enabling `dot11v` also turns on MBO (Multi-Band Operation), advertised in diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 4d2656c74..a900aaeb7 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -1595,8 +1595,11 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l rc = SR_ERR_INTERNAL; } else { fprintf(fp, "# Generated by confd, do not edit.\n"); - fprintf(fp, "service name:hostapd \\\n"); - fprintf(fp, "\t[2345] hostapd -g /run/hostapd.global -P /run/hostapd.pid"); + /* The wrapper hands the clients over to another + * access point before hostapd stops, give it time. */ + fprintf(fp, "service name:hostapd kill:10 \\\n"); + fprintf(fp, "\t[2345] /usr/libexec/infix/hostapd.sh" + " -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/test/case/interfaces/wifi_mesh_roaming/test.adoc b/test/case/interfaces/wifi_mesh_roaming/test.adoc index 77290bed3..630bb2fd8 100644 --- a/test/case/interfaces/wifi_mesh_roaming/test.adoc +++ b/test/case/interfaces/wifi_mesh_roaming/test.adoc @@ -60,5 +60,10 @@ image::topology.svg[WiFi Mesh backhaul with roaming Access Points topology, alig . Take down the client's current AP to force a roam . Verify the client roams to another node's AP . Verify connectivity is restored after roaming +. Stop the WiFi service on the client's current node +. Verify the client roams to the remaining node's AP +. Verify the client was asked to move and roamed without disconnecting +. Verify connectivity is restored after the handover +. Start the WiFi service again on the stopped node diff --git a/test/case/interfaces/wifi_mesh_roaming/test.py b/test/case/interfaces/wifi_mesh_roaming/test.py index c85c3c1ff..34d786bfb 100755 --- a/test/case/interfaces/wifi_mesh_roaming/test.py +++ b/test/case/interfaces/wifi_mesh_roaming/test.py @@ -110,13 +110,18 @@ def gw_config(mesh_mac, ap_mac, uplink=None): # Connect to all four nodes concurrently -- each attach probes the # node and downloads its YANG models, so doing them in parallel cuts # the setup time roughly four-fold. - gw1, gw2, gw3, client = parallel( + gw1, gw2, gw3, client, gw1sh, gw2sh, gw3sh, clientsh = parallel( lambda: env.attach("gw1", "mgmt"), lambda: env.attach("gw2", "mgmt"), lambda: env.attach("gw3", "mgmt"), lambda: env.attach("client", "mgmt"), + lambda: env.attach("gw1", "mgmt", "ssh"), + lambda: env.attach("gw2", "mgmt", "ssh"), + lambda: env.attach("gw3", "mgmt", "ssh"), + lambda: env.attach("client", "mgmt", "ssh"), ) gw_duts = [gw1, gw2, gw3] + shells = {"gw1": gw1sh, "gw2": gw2sh, "gw3": gw3sh} gws = [(name, dut, mesh, ap) for (name, mesh, ap), dut in zip(GWS, gw_duts)] wifi.skip_unless_supported(test, client, *gw_duts) @@ -198,4 +203,33 @@ def gw_config(mesh_mac, ap_mac, uplink=None): with test.step("Verify connectivity is restored after roaming"): ns.must_reach(CLIENT_IP) + # A node going down for a reboot or an upgrade asks its clients + # to move first, so a client with a strong signal roams instead + # of waiting for the beacons to stop. + second_bssid = wifi.station_bssid(client) + second_ap, _ = aps[second_bssid] + mark = int(clientsh.runsh("wc -l < /var/log/syslog").stdout) + + with test.step("Stop the WiFi service on the client's current node"): + shells[second_ap].runsh("initctl stop hostapd") + + with test.step("Verify the client roams to the remaining node's AP"): + until(lambda: wifi.station_bssid(client) in aps and + wifi.station_bssid(client) not in (first_bssid, second_bssid), + attempts=30, interval=1) + third_ap, _ = aps[wifi.station_bssid(client)] + print(f"client roamed from {second_ap} to {third_ap}") + + with test.step("Verify the client was asked to move and roamed without disconnecting"): + def log(): + return clientsh.runsh(f"tail -n +{mark + 1} /var/log/syslog").stdout + until(lambda: "Disassociation Imminent" in log(), attempts=10, interval=1) + assert "CTRL-EVENT-DISCONNECTED" not in log(), log() + + with test.step("Verify connectivity is restored after the handover"): + ns.must_reach(CLIENT_IP) + + with test.step("Start the WiFi service again on the stopped node"): + shells[second_ap].runsh("initctl start hostapd") + test.succeed() From 81b4913d324af0e321261c98a0e5dc3997077079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 11:16:04 +0200 Subject: [PATCH 30/45] confd: wifi: Steer dual-band clients to the higher band MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Withholding probe responses only sways a client that is choosing a network, a client already connected on 2.4 GHz stays there. Ask such clients to move with an 802.11v request naming the higher-band twin, from a steering loop the hostapd wrapper runs per pair, as long as the twin is up and the client's 2.4 GHz signal makes the move worthwhile; a client that shrugs off a couple of requests is left alone for an hour. Refusing authentication on 2.4 GHz for clients the twin has seen was tried and dropped: it locks a client out when the twin cannot take it. Keep the seen-on list to one minute. Signed-off-by: Mattias Walström --- .../rootfs/usr/libexec/infix/hostapd.sh | 36 ++++++- .../rootfs/usr/libexec/infix/wifi-steer.sh | 96 +++++++++++++++++++ doc/ChangeLog.md | 2 + doc/wifi.md | 27 +++++- src/confd/src/hardware.c | 25 +++-- src/confd/yang/confd/infix-if-wifi.yang | 6 ++ .../interfaces/wifi_band_steering/test.adoc | 5 + .../interfaces/wifi_band_steering/test.py | 36 ++++++- 8 files changed, 216 insertions(+), 17 deletions(-) create mode 100755 board/common/rootfs/usr/libexec/infix/wifi-steer.sh diff --git a/board/common/rootfs/usr/libexec/infix/hostapd.sh b/board/common/rootfs/usr/libexec/infix/hostapd.sh index 8c302f6e4..bbfc56987 100755 --- a/board/common/rootfs/usr/libexec/infix/hostapd.sh +++ b/board/common/rootfs/usr/libexec/infix/hostapd.sh @@ -1,5 +1,6 @@ #!/bin/sh -# Run hostapd and, when stopped, hand the clients over first. +# Run hostapd, steer dual-band clients to 5 GHz while it runs, and hand +# the clients over when stopped. # # A client with a good signal has no reason to roam, so when its access # point goes away it only notices once the beacons stop, and then has to @@ -74,16 +75,49 @@ handover() fi } +# Band steering pairs, a 2.4 GHz BSS and the 5/6 GHz twin it defers to, +# from the no_probe_resp_if_seen_on directives in the radio configs. +pairs() +{ + for conf in "$@"; do + case $conf in + *.conf) ;; + *) continue ;; + esac + awk -F= '/^(interface|bss)=/ { cur = $2 } + /^no_probe_resp_if_seen_on=/ { print cur, $2 }' "$conf" + done +} + +steer() +{ + pairs "$@" | while read -r bss twin; do + i=0 + while [ ! -S /run/hostapd/$bss ] && [ $i -lt 50 ]; do + sleep 0.2 + i=$((i + 1)) + done + [ -S /run/hostapd/$bss ] || continue + /usr/libexec/infix/wifi-steer.sh "$bss" "$twin" & + echo $! >> /run/wifi-steer/pids + done +} + stop() { + [ -f /run/wifi-steer/pids ] && kill $(cat /run/wifi-steer/pids) 2>/dev/null + rm -rf /run/wifi-steer handover kill -TERM "$pid" 2>/dev/null } +rm -rf /run/wifi-steer +mkdir -p /run/wifi-steer CONFS=$* hostapd "$@" & pid=$! trap stop TERM INT +steer "$@" & rc=0 while kill -0 "$pid" 2>/dev/null; do wait "$pid" diff --git a/board/common/rootfs/usr/libexec/infix/wifi-steer.sh b/board/common/rootfs/usr/libexec/infix/wifi-steer.sh new file mode 100755 index 000000000..0f9cd64db --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/wifi-steer.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# Move dual-band clients from a 2.4 GHz access point to its 5/6 GHz twin. +# +# Usage: wifi-steer.sh <2.4 GHz bss> +# +# hostapd keeps a list of the clients each radio has seen lately. A +# client connected on 2.4 GHz that the twin has seen is dual-band and in +# range of the twin, so ask it to move with an 802.11v BSS transition +# request naming the twin. Clients that ignore the request stay: a +# client is asked again only after a cooldown, and one that has shrugged +# off a couple of requests is left alone for an hour. A client with a +# weak 2.4 GHz signal is left alone too, 5 GHz would be worse, and +# nobody is sent to a twin that is down or still checking for radar. +bss=$1 +twin=$2 + +PERIOD=15 # seconds between rounds +COOLDOWN=120 # seconds before asking the same client again +GIVEUP=2 # requests a client may shrug off before it is left alone ... +LONG=3600 # ... for this long +MIN_SIGNAL=-65 # dBm on 2.4 GHz below which a client is left alone + +state=/run/wifi-steer/$bss +mkdir -p "$state" + +# Neighbor report candidate for the twin: BSSID, BSSID information, +# operating class, channel and PHY type. Taken from the twin's own +# neighbor entry when it keeps one (802.11k), else built from its status +# with the 20 MHz operating class of the channel, enough for the client +# to find the BSS and learn the rest from its beacons. +candidate() +{ + bssid=$(hostapd_cli -i "$twin" get_config 2>/dev/null | sed -n 's/^bssid=//p') + [ -n "$bssid" ] || return 1 + + nr=$(hostapd_cli -i "$twin" show_neighbor 2>/dev/null | \ + awk -v b="$bssid" 'tolower($1) == tolower(b) { for (i = 2; i <= NF; i++) if ($i ~ /^nr=/) print substr($i, 4) }') + if [ ${#nr} -ge 26 ]; then + info=$(echo "$nr" | cut -c13-20) + info=$(printf '%d' "0x$(echo "$info" | cut -c7-8)$(echo "$info" | cut -c5-6)$(echo "$info" | cut -c3-4)$(echo "$info" | cut -c1-2)") + op=$(printf '%d' "0x$(echo "$nr" | cut -c21-22)") + chan=$(printf '%d' "0x$(echo "$nr" | cut -c23-24)") + phy=$(printf '%d' "0x$(echo "$nr" | cut -c25-26)") + echo "$bssid,$info,$op,$chan,$phy" + return 0 + fi + + status=$(hostapd_cli -i "$twin" status 2>/dev/null) + freq=$(echo "$status" | sed -n 's/^freq=//p') + chan=$(echo "$status" | sed -n 's/^channel=//p') + [ -n "$freq" ] && [ -n "$chan" ] || return 1 + if [ "$freq" -ge 5925 ]; then + op=131 + elif [ "$freq" -ge 5745 ]; then + op=124 + elif [ "$freq" -ge 5500 ]; then + op=121 + elif [ "$freq" -ge 5260 ]; then + op=118 + elif [ "$freq" -ge 5180 ]; then + op=115 + else + op=81 + fi + # BSSID information: AP reachable, same security and key scope. + echo "$bssid,1151,$op,$chan,9" +} + +while sleep $PERIOD; do + stas=$(hostapd_cli -i "$bss" list_sta 2>/dev/null) + [ -n "$stas" ] || continue + # Nothing to move to while the twin is down or checking for radar + hostapd_cli -i "$twin" status 2>/dev/null | grep -q '^state=ENABLED' || continue + # hostapd_cli has no shorthand for the seen-on list, ask hostapd directly + seen=$(hostapd_cli -i "$twin" raw TRACK_STA_LIST 2>/dev/null) + [ -n "$seen" ] || continue + cand=$(candidate) || continue + now=$(date +%s) + + for sta in $stas; do + echo "$seen" | grep -qi "^$sta " || continue + read -r last tries < "$state/$sta" 2>/dev/null || { last=0; tries=0; } + wait=$COOLDOWN + [ "${tries:-0}" -lt $GIVEUP ] || wait=$LONG + [ $((now - last)) -ge $wait ] || continue + [ "${tries:-0}" -lt $GIVEUP ] || tries=0 + signal=$(hostapd_cli -i "$bss" sta "$sta" 2>/dev/null | sed -n 's/^signal=//p') + if [ -n "$signal" ] && [ "$signal" -lt $MIN_SIGNAL ]; then + continue + fi + hostapd_cli -i "$bss" bss_tm_req "$sta" pref=1 abridged=1 valid_int=255 \ + "neighbor=$cand" >/dev/null 2>&1 + echo "$now $((tries + 1))" > "$state/$sta" + logger -t hostapd -p daemon.notice "$bss: asked $sta to move to $twin" + done +done diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 5ce77738e..e61e68c15 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -45,6 +45,8 @@ All notable changes to the project are documented in this file. - The WiFi country code is one setting for the whole system, `hardware wifi country-code`, instead of one per radio, and applies as soon as it is set. Existing configurations are migrated +- WiFi band steering also moves dual-band clients already connected on + 2.4 GHz to the 5 or 6 GHz access point of the same SSID - WiFi clients are moved to another access point before theirs stops for a reboot, an upgrade or a configuration change, instead of losing the connection. Access points on different devices now know of each other diff --git a/doc/wifi.md b/doc/wifi.md index da030795c..4228da50f 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -704,11 +704,28 @@ the same SSID exists on another band and decide for itself when to move, while 802.11v BSS Transition Management lets the AP suggest a better target. -On top of the client-cooperative hints, the AP applies active steering: -on a 2.4 GHz access-point it suppresses probe responses to clients that -were recently seen on the same SSID on the 5/6 GHz band, nudging -dual-band clients onto the higher band. MBO is **enabled by default** -whenever `dot11v` is enabled: +On top of these hints, the device steers dual-band clients to 5 or 6 GHz +itself. Each radio remembers the clients it has seen during the last +minute. A 2.4 GHz access point whose SSID also exists on a higher band +of the same device treats a client that higher band has seen as +dual-band and in range of it: + +- it does not answer the client's probe requests, so a client choosing + a network tends to pick the higher band, +- a client that is connected on 2.4 GHz anyway, for example because it + joined while the higher band was down, is asked to move with an + 802.11v request naming the higher band, as long as its 2.4 GHz signal + is good enough for the move to make sense and the higher band is up. + A client that shrugs off a couple of requests is left alone for an + hour. + +A client is never refused on 2.4 GHz, so one that cannot get in on the +higher band still has a way in. Clients that only support 2.4 GHz are +never seen on the higher band and are not affected, and clients that +ignore 802.11v requests stay where they are. Band +steering is **enabled by default** whenever `dot11v` is enabled. The +setting is read on the 2.4 GHz access point, the one that defers and +moves clients; on a 5 or 6 GHz access point it has no effect: ``` admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11v diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index a900aaeb7..4e675ff6c 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -658,16 +658,14 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd fprintf(hostapd, "mbo=1\n"); - /* Required for no_probe_resp_if_seen_on below: without it - * hostapd keeps no sta_track list, so the twin radio never - * knows which clients it has seen. Radio-level, emit once - * in the main section, never per BSS. */ - if (!is_bss) - fprintf(hostapd, "track_sta_max_num=100\n"); - - /* Active band steering: on a 2.4 GHz BSS, suppress probe - * responses to clients recently seen on the same-SSID 5/6 - * GHz BSS, nudging dual-band clients to the higher band. */ + /* Band steering on a 2.4 GHz BSS with a same-SSID twin on 5/6 + * GHz: a client the twin has seen lately gets no probe response + * here, so it tends to join the twin instead. The seen-on list + * is kept per radio, see the radio section. Clients already + * connected are moved by wifi-steer.sh, which finds the pairs + * by this directive. Refusing authentication as well would + * lock a client out when the twin cannot take it, e.g. during + * its radar check, so that is deliberately not done. */ twin = wifi_find_higher_band_twin(config, band, ssid); if (twin) fprintf(hostapd, "no_probe_resp_if_seen_on=%s\n", twin); @@ -1054,6 +1052,13 @@ static void wifi_gen_radio_config(FILE *hostapd, const char *radio_name, /* Use short preamble for better throughput on modern clients */ fprintf(hostapd, "preamble=1\n"); + /* Remember the clients this radio has seen, for band steering on + * the other radios. A dual-band client is refused on 2.4 GHz + * while it is on this list, so keep the list short-lived: that is + * the longest a client that cannot get in on 5 GHz has to wait. */ + fprintf(hostapd, "track_sta_max_num=100\n"); + fprintf(hostapd, "track_sta_max_age=60\n"); + if (band) { if (!strcmp(band, "2.4GHz")) { /* hw_mode=g: 2.4GHz with 802.11g (OFDM) as baseline */ diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index 96804b2af..ce44c5065 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -625,6 +625,12 @@ submodule infix-if-wifi { encouraging dual-band devices to prefer 5GHz over 2.4GHz when signal quality permits. + Acts on a 2.4GHz access point whose SSID is also + served on 5 or 6GHz by this device: it defers + dual-band clients to that access point, and moves + the ones already connected. On a 5 or 6GHz + access point the setting has no effect. + MBO steers clients via 802.11v BSS Transition Management, hence its placement under dot11v; enabling dot11v enables band steering by default. diff --git a/test/case/interfaces/wifi_band_steering/test.adoc b/test/case/interfaces/wifi_band_steering/test.adoc index af7edd2dd..5f0242b12 100644 --- a/test/case/interfaces/wifi_band_steering/test.adoc +++ b/test/case/interfaces/wifi_band_steering/test.adoc @@ -45,5 +45,10 @@ image::topology.svg[WiFi Band Steering across a dual-band Access Point topology, . Verify the client associates to the 'campus' SSID . Verify band steering put the client on the 5GHz BSS . Verify the client leases an address over 5GHz +. Disable the 5GHz BSS +. Verify the client falls back to the 2.4GHz BSS +. Enable the 5GHz BSS again +. Verify a scanning client is steered back to the 5GHz BSS +. Verify the client still has its address after the move diff --git a/test/case/interfaces/wifi_band_steering/test.py b/test/case/interfaces/wifi_band_steering/test.py index cb93b3a58..4f43f3aba 100755 --- a/test/case/interfaces/wifi_band_steering/test.py +++ b/test/case/interfaces/wifi_band_steering/test.py @@ -72,9 +72,10 @@ def ap_bss(name, radio_name, bssid): with infamy.Test() as test: with test.step("Set up topology and attach to the ap and the client"): env = infamy.Env() - ap, client = parallel( + ap, client, clientsh = parallel( lambda: env.attach("ap", "mgmt"), lambda: env.attach("client", "mgmt"), + lambda: env.attach("client", "mgmt", "ssh"), ) wifi.skip_unless_supported(test, ap, client) @@ -135,4 +136,37 @@ def ap_bss(name, radio_name, bssid): iface.address_exist(client, "wifi0", POOL_END), attempts=60, interval=2) + # The 5GHz BSS goes away: the client falls back to 2.4GHz once the + # 5GHz radio has not seen it for a minute. When the 5GHz BSS comes + # back, the client is connected on the wrong band. A client that + # scans is seen on 5GHz again and gets asked to move there. Real + # clients scan in the background on their own, this one is told to. + with test.step("Disable the 5GHz BSS"): + ap.put_config_dicts({"ietf-interfaces": {"interfaces": { + "interface": [{"name": "wifi1", "enabled": False}]}}}) + + with test.step("Verify the client falls back to the 2.4GHz BSS"): + until(lambda: CLIENT_MAC in wifi.ap_stations(ap, "wifi0"), + attempts=90, interval=2) + + with test.step("Enable the 5GHz BSS again"): + ap.put_config_dicts({"ietf-interfaces": {"interfaces": { + "interface": [{"name": "wifi1", "enabled": True}]}}}) + until(lambda: iface.is_oper_up(ap, "wifi1"), attempts=60, interval=2) + + with test.step("Verify a scanning client is steered back to the 5GHz BSS"): + def steered(): + if CLIENT_MAC in wifi.ap_stations(ap, "wifi1"): + return True + clientsh.runsh("sudo wpa_cli -i wifi0 scan >/dev/null") + return False + until(steered, attempts=20, interval=10) + assert CLIENT_MAC not in wifi.ap_stations(ap, "wifi0"), \ + "client still associated on 2.4GHz" + + with test.step("Verify the client still has its address after the move"): + until(lambda: iface.address_exist(client, "wifi0", POOL_START) or + iface.address_exist(client, "wifi0", POOL_END), + attempts=30, interval=2) + test.succeed() From 26f6224ac077df4087d52bf0a34bcde8cf7aa2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 7 Oct 2026 15:33:25 +0200 Subject: [PATCH 31/45] confd: wifi: Tell the other nodes about our access points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A client asked to move needs to know where to, wpa_supplicant ignores a transition request without candidates, and a node does not know the other nodes' access points. Every 30 seconds a node announces its access points in one frame per network they are bridged to, the network the 802.11r key exchange uses, and listens for the other nodes' frames. What it hears goes to hostapd as 802.11k neighbors of every access point with the same SSID, and into the handover request. No radio leaves its channel for it. Anything on that network can send such a frame, so each line is tagged with the 802.11r key of its SSID and checked before use. The frame starts with a version; a node drops frames of another version and logs it once per sender, so bump the version when the format changes. Signed-off-by: Mattias Walström --- .../rootfs/usr/libexec/infix/hostapd.sh | 78 +++-- .../usr/libexec/infix/wifi-neighbors.py | 309 ++++++++++++++++++ doc/wifi.md | 18 +- .../interfaces/wifi_mesh_roaming/test.adoc | 2 +- .../case/interfaces/wifi_mesh_roaming/test.py | 17 +- 5 files changed, 395 insertions(+), 29 deletions(-) create mode 100755 board/common/rootfs/usr/libexec/infix/wifi-neighbors.py diff --git a/board/common/rootfs/usr/libexec/infix/hostapd.sh b/board/common/rootfs/usr/libexec/infix/hostapd.sh index bbfc56987..69631868a 100755 --- a/board/common/rootfs/usr/libexec/infix/hostapd.sh +++ b/board/common/rootfs/usr/libexec/infix/hostapd.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Run hostapd, steer dual-band clients to 5 GHz while it runs, and hand -# the clients over when stopped. +# Run hostapd, learn the other nodes' access points, steer dual-band +# clients to 5 GHz while it runs, and hand the clients over when stopped. # # A client with a good signal has no reason to roam, so when its access # point goes away it only notices once the beacons stop, and then has to @@ -9,13 +9,12 @@ # the radio is still up. Clients without 802.11v are deauthenticated # by hostapd on exit, as before. -# The request names no candidate, a node does not know the other nodes' -# access points, so the client has to scan for one: a few seconds on a -# real radio across three bands. A client that roams never tells the old -# access point either, hostapd drops it from its station list when the -# timer runs out, so wait a little longer than that before giving up, -# but stay well inside the ten seconds the service gets to stop before -# it is killed. +# A client given candidates roams in well under a second, one without +# has to scan first, a few seconds on a real radio across three bands. +# A client that roams never tells the old access point either, hostapd +# drops it from its station list when the timer runs out, so wait a +# little longer than that before giving up, but stay well inside the +# ten seconds the service gets to stop before it is killed. TIMER=50 # beacon intervals (100 ms) until hostapd disassociates a client that stays WAIT=7 # seconds to wait for the clients to leave @@ -43,6 +42,28 @@ stations() done } +# Ask a station to leave, naming the other access points of the SSID +# wifi-neighbors.py has heard of: a client does not look for a new +# access point on a request without candidates. +ask_to_move() +{ + bss=$1 + sta=$2 + cands="" + if [ -s /run/wifi-neighbors/$bss ]; then + for cand in $(grep -E '^[0-9a-f]{2}(:[0-9a-f]{2}){5}(,[0-9]+){4}$' /run/wifi-neighbors/$bss); do + cands="$cands neighbor=$cand" + done + fi + if [ -n "$cands" ]; then + # shellcheck disable=SC2086 + hostapd_cli -i "$bss" bss_tm_req "$sta" disassoc_imminent=1 disassoc_timer=$TIMER \ + pref=1 abridged=1 $cands >/dev/null 2>&1 + else + hostapd_cli -i "$bss" disassoc_imminent "$sta" $TIMER >/dev/null 2>&1 + fi +} + handover() { # Only clients that do 802.11v can be asked to move, and hostapd only @@ -52,7 +73,7 @@ handover() num=0 for bss in $(bsses); do for sta in $(hostapd_cli -i "$bss" list_sta 2>/dev/null); do - hostapd_cli -i "$bss" disassoc_imminent "$sta" $TIMER >/dev/null 2>&1 + ask_to_move "$bss" "$sta" num=$((num + 1)) done done @@ -89,15 +110,31 @@ pairs() done } -steer() +# Wait for the control socket of a BSS, up to ten seconds +wait_bss() { + i=0 + while [ ! -S /run/hostapd/$1 ] && [ $i -lt 50 ]; do + sleep 0.2 + i=$((i + 1)) + done + [ -S /run/hostapd/$1 ] +} + +helpers() +{ + # Exchange access point lists with the other nodes once hostapd is up + for conf in "$@"; do + case $conf in *.conf) ;; *) continue ;; esac + wait_bss "$(sed -n 's/^interface=//p' "$conf")" || continue + /usr/libexec/infix/wifi-neighbors.py "$@" & + echo $! >> /run/wifi-steer/pids + break + done + + # Steer dual-band clients to the higher band, one loop per pair pairs "$@" | while read -r bss twin; do - i=0 - while [ ! -S /run/hostapd/$bss ] && [ $i -lt 50 ]; do - sleep 0.2 - i=$((i + 1)) - done - [ -S /run/hostapd/$bss ] || continue + wait_bss "$bss" || continue /usr/libexec/infix/wifi-steer.sh "$bss" "$twin" & echo $! >> /run/wifi-steer/pids done @@ -111,13 +148,14 @@ stop() kill -TERM "$pid" 2>/dev/null } +CONFS=$* rm -rf /run/wifi-steer mkdir -p /run/wifi-steer -CONFS=$* +trap stop TERM INT hostapd "$@" & pid=$! -trap stop TERM INT -steer "$@" & +helpers "$@" & +echo $! >> /run/wifi-steer/pids rc=0 while kill -0 "$pid" 2>/dev/null; do wait "$pid" diff --git a/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py b/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py new file mode 100755 index 000000000..a51d12261 --- /dev/null +++ b/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +""" +Tell the other nodes about our access points, learn about theirs. + +Usage: wifi-neighbors.py ... + +A client asked to move needs to know where to, and a node knows nothing +about the other nodes' access points. Every 30 seconds each node sends +one frame per network its access points are bridged to, listing them: +BSSID, frequency, PHY type and SSID. The network is the one the 802.11r +key exchange uses, ft_iface in the hostapd config, so the announcements +reach exactly the nodes a client can roam to. Bridges flood the frames +over the backhaul like any multicast, no IP address is needed and no +radio ever leaves its channel. + +Anything on that network can send such a frame, so every line carries a +tag made with the 802.11r key of its SSID, which the nodes serving the +SSID already share. A line whose tag does not check out, or that names +an SSID this node does not serve, is dropped. An access point without +an 802.11r key is neither announced nor learned. + +The first line of a frame is 'infix-wifi '. Nodes +only listen to their own version, a frame of another version is logged +once per sender and dropped, so a mixed set of nodes shows up in the +log instead of as a silent lack of neighbors. Bump VERSION when the +frame format changes. + +What is heard is kept for 90 seconds and handed to hostapd as 802.11k +neighbors of every access point with the same SSID, for its neighbor +reports and for the transition requests sent by hostapd.sh and +wifi-steer.sh, which read the candidate list of a BSS from +/run/wifi-neighbors/, one bss_tm_req neighbor= argument per line. +""" +import hmac +import hashlib +import os +import re +import select +import socket +import struct +import subprocess +import sys +import time + +ETHERTYPE = 0x88B5 # IEEE 802 local experimental 1 +GROUP = bytes.fromhex('034b4b000001') # locally administered group address +MAGIC = b'infix-wifi' +VERSION = 2 +PERIOD = 30 +EXPIRE = 95 +MAX_NEIGHBORS = 64 +DIR = '/run/wifi-neighbors' +MAC = re.compile(r'^([0-9a-f]{2}:){5}[0-9a-f]{2}$') + + +def log(msg): + subprocess.run(['logger', '-t', 'hostapd', '-p', 'daemon.notice', msg], + capture_output=True) + + +def hostapd_cli(bss, *args): + try: + res = subprocess.run(['hostapd_cli', '-i', bss, *args], + capture_output=True, text=True, timeout=5) + return res.stdout if res.returncode == 0 else '' + except (OSError, subprocess.SubprocessError): + return '' + + +def parse_configs(paths): + """{bss: (ft_iface, key)} for every BSS, both None without 802.11r.""" + bsses = {} + for path in paths: + if not path.endswith('.conf'): + continue + cur = None + try: + lines = open(path).read().splitlines() + except OSError: + continue + for line in lines: + if line.startswith(('interface=', 'bss=')): + cur = line.split('=', 1)[1].strip() + bsses.setdefault(cur, [None, None]) + elif line.startswith('ft_iface=') and cur: + bsses[cur][0] = line.split('=', 1)[1].strip() + elif line.startswith('r0kh=') and cur: + # r0kh= , the key is what every node derives + # from the mobility domain and the secret + bsses[cur][1] = line.split()[-1].encode() + return {bss: tuple(v) for bss, v in bsses.items()} + + +def opclass(freq): + """20 MHz operating class of a frequency.""" + if freq >= 5925: + return 131 + if freq >= 5745: + return 124 + if freq >= 5500: + return 121 + if freq >= 5260: + return 118 + if freq >= 5180: + return 115 + return 81 + + +def channel(freq): + if freq == 5935: + return 2 + if freq >= 5955: + return (freq - 5950) // 5 + if freq >= 5000: + return (freq - 5000) // 5 + if freq == 2484: + return 14 + return (freq - 2407) // 5 + + +def valid(bssid, freq, phy, ssid): + """A line is only used if every field fits in a neighbor report.""" + return (MAC.match(bssid) is not None + and (2412 <= freq <= 2484 or 5180 <= freq <= 5885 + or freq == 5935 or 5955 <= freq <= 7115) + and 1 <= channel(freq) <= 233 + and 0 <= phy <= 255 + and 1 <= len(ssid.encode()) <= 32) + + +def tag(key, bssid, freq, phy, ssid): + msg = f'{bssid} {freq} {phy} {ssid}'.encode() + return hmac.new(key, msg, hashlib.sha256).hexdigest()[:32] + + +def own_bsses(bsses): + """{bss: (bssid, freq, phy, ssid)} from hostapd, for the keyed BSSes up.""" + out = {} + for bss, (_, key) in bsses.items(): + if not key: + continue + cfg = hostapd_cli(bss, 'get_config') + status = hostapd_cli(bss, 'status') + bssid = re.search(r'^bssid=(\S+)', cfg, re.M) + ssid = re.search(r'^ssid=(.*)$', cfg, re.M) + freq = re.search(r'^freq=(\d+)', status, re.M) + if not (bssid and ssid and freq): + continue + # dot11PHYType: OFDM, HT, VHT, HE + if 'ieee80211ax=1' in status: + phy = 14 + elif 'ieee80211ac=1' in status: + phy = 9 + elif 'ieee80211n=1' in status: + phy = 7 + else: + phy = 4 + entry = (bssid.group(1).lower(), int(freq.group(1)), phy, ssid.group(1)) + if valid(*entry): + out[bss] = entry + return out + + +def announcement(host, own, bsses): + lines = [b'%s %d %s' % (MAGIC, VERSION, host.encode())] + for bss, (bssid, freq, phy, ssid) in own.items(): + key = bsses[bss][1] + lines.append(f'{bssid} {freq} {phy} {tag(key, bssid, freq, phy, ssid)} {ssid}'.encode()) + return b'\n'.join(lines) + b'\n' + + +def parse_announcement(data, keys, seen): + """[(bssid, freq, phy, ssid)] for the lines tagged with a key of ours.""" + try: + text = data.decode() + except UnicodeDecodeError: + return [] + lines = text.split('\n') + head = lines[0].split(' ', 2) if lines else [] + if len(head) != 3 or head[0] != MAGIC.decode(): + return [] + if head[1] != str(VERSION): + if head[2] not in seen: + seen.add(head[2]) + log(f'wifi-neighbors: {head[2][:64]} announces version {head[1][:8]}, ' + f'ours is {VERSION}, ignoring it') + return [] + out = [] + for line in lines[1:]: + parts = line.split(' ', 4) + if len(parts) < 5: + continue + bssid, ssid = parts[0].lower(), parts[4] + try: + freq, phy = int(parts[1]), int(parts[2]) + except ValueError: + continue + key = keys.get(ssid) + if not key or not valid(bssid, freq, phy, ssid): + continue + if not hmac.compare_digest(parts[3], tag(key, bssid, freq, phy, ssid)): + continue + out.append((bssid, freq, phy, ssid)) + return out + + +def candidate(bssid, freq, phy): + # BSSID information: AP reachable, same security and key scope + return f'{bssid},1151,{opclass(freq)},{channel(freq)},{phy}' + + +def nr_hex(bssid, freq, phy): + info = 1151 + return (bssid.replace(':', '') + struct.pack(' (bssid, freq, phy, ssid, last seen) + published = {} + next_send = 0.0 + own = {} + keys = {} # ssid -> 802.11r key, for the SSIDs we serve + other = set() # nodes heard announcing another version + while True: + now = time.monotonic() + if now >= next_send: + own = own_bsses(bsses) + keys = {ssid: bsses[bss][1] for bss, (_, _, _, ssid) in own.items()} + payload = struct.pack('!H', ETHERTYPE) + announcement(host, own, bsses) + for s, ifc in socks.items(): + try: + s.send(GROUP + s.getsockname()[4] + payload) + except OSError: + pass + next_send = now + PERIOD + + ready, _, _ = select.select(list(socks), [], [], max(0.1, next_send - now)) + for s in ready: + try: + data = s.recv(2048) + except OSError: + continue + stamp = time.monotonic() + for bssid, freq, phy, ssid in parse_announcement(data[14:], keys, other): + if any(bssid == o[0] for o in own.values()): + continue + if bssid not in neighbors and len(neighbors) >= MAX_NEIGHBORS: + continue + neighbors[bssid] = (bssid, freq, phy, ssid, stamp) + + cutoff = time.monotonic() - EXPIRE + for bssid in [b for b, n in neighbors.items() if n[4] < cutoff]: + del neighbors[bssid] + if own: + try: + publish(own, {b: n[:4] for b, n in neighbors.items()}, published) + except OSError as err: + log(f'wifi-neighbors: {err}') + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + pass diff --git a/doc/wifi.md b/doc/wifi.md index 4228da50f..94e071dc4 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -679,6 +679,16 @@ admin@example:/config/interface/wifi0/> set wifi access-point roaming dot11k Enables neighbor reports and beacon reports, allowing clients to discover nearby APs before roaming. + +Devices with access points on the same network tell each other about +them, over that network, so every access point knows the other access +points of its SSID on the other devices and lists them in its neighbor +reports. A client asking where else its network exists gets the real +answer, and a client asked to move, see below, is told where to. A +device that comes up later is known to the others within a minute. +The exchange is protected with the 802.11r key, so it needs fast +roaming on the access points, see above. + ### 802.11v - BSS Transition Management Enable 802.11v for network-assisted roaming: @@ -691,10 +701,10 @@ Allows APs to suggest better APs to clients, improving roaming decisions. An access point about to stop, because the device reboots, is upgraded, or its WiFi configuration changes, also uses 802.11v to ask its clients -to move first. Clients that support it roam to another access point -with the same SSID while the radio is still up, instead of noticing the -loss afterwards and scanning for a new network. Clients without -802.11v are disconnected as before. +to move first, naming the other access points of the SSID it knows of. +Clients that support it roam to one of them while the radio is still +up, instead of noticing the loss afterwards and scanning for a new +network. Clients without 802.11v are disconnected as before. #### Band Steering (MBO) diff --git a/test/case/interfaces/wifi_mesh_roaming/test.adoc b/test/case/interfaces/wifi_mesh_roaming/test.adoc index 630bb2fd8..b74b724f2 100644 --- a/test/case/interfaces/wifi_mesh_roaming/test.adoc +++ b/test/case/interfaces/wifi_mesh_roaming/test.adoc @@ -62,7 +62,7 @@ image::topology.svg[WiFi Mesh backhaul with roaming Access Points topology, alig . Verify connectivity is restored after roaming . Stop the WiFi service on the client's current node . Verify the client roams to the remaining node's AP -. Verify the client was asked to move and roamed without disconnecting +. Verify the client was asked to move, given candidates, and roamed without disconnecting . Verify connectivity is restored after the handover . Start the WiFi service again on the stopped node diff --git a/test/case/interfaces/wifi_mesh_roaming/test.py b/test/case/interfaces/wifi_mesh_roaming/test.py index 34d786bfb..56989fed8 100755 --- a/test/case/interfaces/wifi_mesh_roaming/test.py +++ b/test/case/interfaces/wifi_mesh_roaming/test.py @@ -186,8 +186,16 @@ def gw_config(mesh_mac, ap_mac, uplink=None): with infamy.IsolatedMacVlan(hlan) as ns: ns.addip(HOST_IP) + # Give a ping a few tries, the radios have just come up. + def reaches(addr): + try: + ns.ping(addr) + return True + except Exception: + return False + with test.step("Verify the client is reachable across the mesh"): - ns.must_reach(CLIENT_IP) + until(lambda: reaches(CLIENT_IP), attempts=10, interval=2) with test.step("Take down the client's current AP to force a roam"): first_dut.put_config_dicts({"ietf-interfaces": {"interfaces": { @@ -201,7 +209,7 @@ def gw_config(mesh_mac, ap_mac, uplink=None): print(f"client roamed from {first_ap} to {new_ap}") with test.step("Verify connectivity is restored after roaming"): - ns.must_reach(CLIENT_IP) + until(lambda: reaches(CLIENT_IP), attempts=15, interval=2) # A node going down for a reboot or an upgrade asks its clients # to move first, so a client with a strong signal roams instead @@ -220,14 +228,15 @@ def gw_config(mesh_mac, ap_mac, uplink=None): third_ap, _ = aps[wifi.station_bssid(client)] print(f"client roamed from {second_ap} to {third_ap}") - with test.step("Verify the client was asked to move and roamed without disconnecting"): + with test.step("Verify the client was asked to move, given candidates, and roamed without disconnecting"): def log(): return clientsh.runsh(f"tail -n +{mark + 1} /var/log/syslog").stdout until(lambda: "Disassociation Imminent" in log(), attempts=10, interval=1) + assert "Preferred List Available" in log(), log() assert "CTRL-EVENT-DISCONNECTED" not in log(), log() with test.step("Verify connectivity is restored after the handover"): - ns.must_reach(CLIENT_IP) + until(lambda: reaches(CLIENT_IP), attempts=15, interval=2) with test.step("Start the WiFi service again on the stopped node"): shells[second_ap].runsh("initctl start hostapd") From c8aa85af4f607f52dc2201ce2bdb1c8f3828dc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:14 +0200 Subject: [PATCH 32/45] patches: linux: Map the WED firmware regions without requesting them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- ...0g-Support-firmware-loading-on-88X33.patch | 5 +-- ...0g-Fix-power-up-when-strapped-to-sta.patch | 5 +-- ...rvell10g-Add-LED-support-for-88X3310.patch | 5 +-- ...0g-Support-LEDs-tied-to-a-single-med.patch | 5 +-- ...phy-Do-not-resume-PHY-when-attaching.patch | 5 +-- ...-classifying-unknown-multicast-as-mr.patch | 17 ++++--- ...e-router-ports-when-forwarding-L2-mu.patch | 5 +-- ...delay-for-applying-strict-multicast-.patch | 7 ++- ...rentiate-MDB-additions-from-modifica.patch | 9 ++-- ...ie-tlv-Let-device-probe-even-when-TL.patch | 5 +-- ...d-r8153b-support-for-link-activity-L.patch | 5 +-- ...ek-mt7986a-rename-BPi-R3-ports-to-ma.patch | 5 +-- ...-Add-a-timing-for-the-Raspberry-Pi-7.patch | 5 +-- ...uchscreen-edt-ft5x06-Add-polled-mode.patch | 5 +-- ...e6xxx-Fix-timeout-on-waiting-for-PPU.patch | 5 +-- ...x-Improve-indirect-register-access-p.patch | 13 +++--- ...x-Honor-ports-being-managed-via-in-b.patch | 5 +-- ...x-Limit-rsvd2cpu-policy-to-user-port.patch | 5 +-- ...Use-tag-priority-as-initial-skb-prio.patch | 5 +-- ...MDB-memberships-whose-L2-addresses-o.patch | 5 +-- ...t-EtherType-based-priority-overrides.patch | 7 ++- ...x-Support-EtherType-based-priority-o.patch | 15 +++---- ...a-mv88e6xxx-Add-mqprio-qdisc-support.patch | 7 ++- ...x-Use-VLAN-prio-over-IP-when-both-ar.patch | 5 +-- ...8e6xxx-Trap-locally-terminated-VLANs.patch | 9 ++-- ...x-collapse-disabled-state-into-block.patch | 5 +-- ...x-Only-activate-LAG-offloading-when-.patch | 5 +-- ...-mv88e6xxx-Add-LED-support-for-6393X.patch | 11 +++-- ...eck-connection-state-before-querying.patch | 5 +-- ...ppress-log-spam-for-regulatory-restr.patch | 5 +-- ...duce-log-noise-during-AP-to-station-.patch | 5 +-- ...11h-add-OF-device-table-for-auto-loa.patch | 5 +-- ...le-video-and-then-retry-failed-trans.patch | 5 +-- ...locks-should-be-running-before-reset.patch | 5 +-- ...nsure-DSI-is-enabled-for-FIFO-resets.patch | 5 +-- ...036-drm-vc4-Reset-DSI-AFE-on-disable.patch | 5 +-- ...le-the-different-command-FIFO-widths.patch | 5 +-- ...762-Program-the-DPI-mode-into-the-ch.patch | 5 +-- ...e-tc358762-revert-move-ops-to-enable.patch | 5 +-- ...762-Set-pre_enabled-on-pre_enable-to.patch | 5 +-- ...dalone-PCS-registration-infrastructu.patch | 11 +++-- ...d-MediaTek-MT7988-USXGMII-PCS-driver.patch | 9 ++-- ...iatek-add-USXGMII-support-for-MT7988.patch | 9 ++-- ...diatek-mt7988a-add-USXGMII-PCS-nodes.patch | 5 +-- ...ek-bananapi-bpi-r4-enable-SFP-ports-.patch | 7 ++- ...et-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch | 5 +-- ...x-Trap-PTP-frames-on-timestamping-po.patch | 17 ++++--- ...-add-MODULE_DEVICE_TABLE-for-mt7622-.patch | 5 +-- ...3-Fix-PERST-control-timing-during-sy.patch | 5 +-- ...xx-Derive-LED-names-from-device-name.patch | 5 +-- ...s-make-it-selectable-for-ARCH_LAN969.patch | 5 +-- ...arx5-fix-wrong-chip-ids-for-TSN-SKUs.patch | 5 +-- ...gure-serdes-for-1000BASE-X-in-sparx5.patch | 5 +-- ...atmel-sama5d2-sdhci-add-microchip-la.patch | 5 +-- ...mc-sdhci-of-at91-add-LAN969x-support.patch | 5 +-- ...1-stop-SDCLK-on-reset-and-add-eMMC-h.patch | 5 +-- ...arx5-lan969x-populate-netdev-of_node.patch | 5 +-- ...rochip-add-LAN969x-clock-header-file.patch | 5 +-- ...64-dts-microchip-add-LAN969x-support.patch | 5 +-- ...m64-dts-microchip-add-EV23X71A-board.patch | 7 ++- ...4-dts-microchip-lan969x-add-OTP-node.patch | 5 +-- ...ts-microchip-lan969x-add-SDMMC-nodes.patch | 5 +-- ...4-dts-microchip-ev23x71a-enable-eMMC.patch | 5 +-- ...rvey-the-requested-interface-not-the.patch | 5 +-- ...nor-caller-s-rtnl-lock-when-stopping.patch | 5 +-- ...t-cfg_to_ndev-return-NULL-and-harden.patch | 13 +++--- ...pport-deletion-and-recreation-of-the.patch | 7 ++- ...port-port-authorized-after-offloaded.patch | 5 +-- ...m-AT91-document-Novarq-Tactical-1000.patch | 5 +-- ...s-microchip-add-Novarq-Tactical-1000.patch | 7 ++- ...crochip-tactical-1000-add-port-names.patch | 5 +-- ...icrochip-tactical-1000-adapt-to-6.18.patch | 5 +-- ...ngs-arm-AT91-document-EV23X71A-board.patch | 5 +-- ...-default-priority-init-on-unsupporte.patch | 5 +-- ...se-the-global-DCB-APP-mirroring-help.patch | 5 +-- ...net-dsa-Support-the-PCP-APP-selector.patch | 7 ++- ...net-dsa-Support-DCB-priority-rewrite.patch | 7 ++- ...-Support-the-IEEE-ETS-managed-object.patch | 7 ++- ...leep-in-atomic-context-in-MAC-table-.patch | 7 ++- ...ymreg-symreg-debugfs-driver-DBB-1045.patch | 11 +++-- ..._wed-map-WO-memory-regions-without-r.patch | 45 +++++++++++++++++++ 81 files changed, 258 insertions(+), 293 deletions(-) create mode 100644 patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch diff --git a/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch b/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch index 8612a64b0..448e534f1 100644 --- a/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch +++ b/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch @@ -1,9 +1,8 @@ From ca34ebe2b40b53b52a5ea6fb00b7bb36eeeabb5f Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 19 Sep 2023 18:38:10 +0200 -Subject: [PATCH 01/80] net: phy: marvell10g: Support firmware loading on +Subject: [PATCH 01/81] net: phy: marvell10g: Support firmware loading on 88X3310 -Organization: Wires When probing, if a device is waiting for firmware to be loaded into its RAM, ask userspace for the binary and load it over XMDIO. @@ -16,7 +15,7 @@ fall back. 1 file changed, 161 insertions(+) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c -index 8fd42131cdbf..44294519231e 100644 +index 8fd42131cdbf9..44294519231ed 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -25,6 +25,7 @@ diff --git a/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch b/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch index 597cade49..717c09f94 100644 --- a/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch +++ b/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch @@ -1,9 +1,8 @@ From 59532ff26cccf476b4e6f8eeaf2e733c93106a78 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 21 Nov 2023 20:15:24 +0100 -Subject: [PATCH 02/80] net: phy: marvell10g: Fix power-up when strapped to +Subject: [PATCH 02/81] net: phy: marvell10g: Fix power-up when strapped to start powered down -Organization: Wires On devices which are hardware strapped to start powered down (PDSTATE == 1), make sure that we clear the power-down bit on all units @@ -13,7 +12,7 @@ affected by this setting. 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c -index 44294519231e..40439db49601 100644 +index 44294519231ed..40439db49601d 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -322,11 +322,23 @@ static int mv3310_power_down(struct phy_device *phydev) diff --git a/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch b/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch index 46c231564..e9c309892 100644 --- a/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch +++ b/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch @@ -1,8 +1,7 @@ From 2881f5a7008a4b7a214f63960f1d199f55a7a78a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 15 Nov 2023 20:58:42 +0100 -Subject: [PATCH 03/80] net: phy: marvell10g: Add LED support for 88X3310 -Organization: Wires +Subject: [PATCH 03/81] 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 @@ -22,7 +21,7 @@ Trigger support: 1 file changed, 422 insertions(+) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c -index 40439db49601..7ae4744f147b 100644 +index 40439db49601d..7ae4744f147bb 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -28,6 +28,7 @@ diff --git a/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch b/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch index fceff7ad6..a9eb8a755 100644 --- a/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch +++ b/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch @@ -1,9 +1,8 @@ From 67a62752bdb5e1e45982289b2eda1f5df4ca6f47 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Dec 2023 09:51:05 +0100 -Subject: [PATCH 04/80] net: phy: marvell10g: Support LEDs tied to a single +Subject: [PATCH 04/81] net: phy: marvell10g: Support LEDs tied to a single media side -Organization: Wires In a combo-port setup, i.e. where both the copper and fiber interface are available to the user, the LEDs may be physically located either @@ -19,7 +18,7 @@ versa for the SFP cage. 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c -index 7ae4744f147b..5e48355ffcef 100644 +index 7ae4744f147bb..5e48355ffcef4 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -192,6 +192,9 @@ struct mv3310_chip { diff --git a/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch b/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch index 2c1419b7d..91bc3344a 100644 --- a/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch +++ b/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch @@ -1,8 +1,7 @@ From a60255d4ba8ea888ecb86e92a2c65334a26f6384 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 10:10:19 +0100 -Subject: [PATCH 05/80] net: phy: Do not resume PHY when attaching -Organization: Wires +Subject: [PATCH 05/81] net: phy: Do not resume PHY when attaching The PHY should not start negotiating with its link-partner until explicitly instructed to do so. @@ -20,7 +19,7 @@ administratively down. 1 file changed, 1 deletion(-) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c -index 26b08e3dbd1d..7597308534a0 100644 +index 26b08e3dbd1de..7597308534a0a 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1753,7 +1753,6 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, diff --git a/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch b/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch index 0fd79097f..0a2243138 100644 --- a/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch +++ b/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch @@ -1,9 +1,8 @@ From 3fc8e2a80589a5a5ae02fac7b3f97f195cb90930 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 4 Mar 2024 16:47:28 +0100 -Subject: [PATCH 06/80] net: bridge: avoid classifying unknown multicast as +Subject: [PATCH 06/81] net: bridge: avoid classifying unknown multicast as mrouters_only -Organization: Wires Unknown multicast, MAC/IPv4/IPv6, should always be flooded according to the per-port mcast_flood setting, as well as to detected and configured @@ -27,7 +26,7 @@ Signed-off-by: Joachim Wiberg 7 files changed, 57 insertions(+), 14 deletions(-) diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h -index e52f8207ab27..306fa8a94c81 100644 +index e52f8207ab278..306fa8a94c819 100644 --- a/include/uapi/linux/if_bridge.h +++ b/include/uapi/linux/if_bridge.h @@ -835,6 +835,7 @@ enum br_boolopt_id { @@ -39,7 +38,7 @@ index e52f8207ab27..306fa8a94c81 100644 }; diff --git a/net/bridge/br.c b/net/bridge/br.c -index c37e52e2f29a..0759bc37d6f0 100644 +index c37e52e2f29ad..0759bc37d6f0f 100644 --- a/net/bridge/br.c +++ b/net/bridge/br.c @@ -312,6 +312,9 @@ int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on, @@ -62,7 +61,7 @@ index c37e52e2f29a..0759bc37d6f0 100644 /* shouldn't be called with unsupported options */ WARN_ON(1); diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c -index 3055a72959cb..0e5e7821d44c 100644 +index 3055a72959cb6..0e5e7821d44cd 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -89,10 +89,10 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev) @@ -102,7 +101,7 @@ index 3055a72959cb..0e5e7821d44c 100644 br_stp_timer_init(br); br_multicast_init(br); diff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c -index dea09096ad0f..f0a613238e0d 100644 +index dea09096ad0fb..f0a613238e0d5 100644 --- a/net/bridge/br_forward.c +++ b/net/bridge/br_forward.c @@ -199,15 +199,20 @@ static struct net_bridge_port *maybe_deliver( @@ -141,7 +140,7 @@ index dea09096ad0f..f0a613238e0d 100644 continue; break; diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c -index ac0f56076e93..9af38febff25 100644 +index ac0f56076e93b..9af38febff25d 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c @@ -186,9 +186,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb @@ -171,7 +170,7 @@ index ac0f56076e93..9af38febff25 100644 br_multicast_flood(mdst, skb, brmctx, local_rcv, false); } diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c -index fda1f89ee22a..db837e27ed8a 100644 +index fda1f89ee22ae..db837e27ed8a2 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -3838,6 +3838,11 @@ static void br_multicast_err_count(const struct net_bridge *br, @@ -207,7 +206,7 @@ index fda1f89ee22a..db837e27ed8a 100644 ipv6_addr_is_all_snoopers(&ipv6_hdr(skb)->daddr)) br_ip6_multicast_mrd_rcv(brmctx, pmctx, skb); diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h -index e4c8bc8576df..b99aa1d6a260 100644 +index e4c8bc8576df6..b99aa1d6a2608 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -491,6 +491,7 @@ enum net_bridge_opts { diff --git a/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch b/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch index 94978c7a9..668c5da85 100644 --- a/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch +++ b/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch @@ -1,9 +1,8 @@ From 8f5cf3a448d6e7a1d00043debd0bf1fcaf706680 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 06:44:41 +0100 -Subject: [PATCH 07/80] net: bridge: Ignore router ports when forwarding L2 +Subject: [PATCH 07/81] net: bridge: Ignore router ports when forwarding L2 multicast -Organization: Wires Multicast router ports are either statically configured or learned from control protocol traffic (IGMP/MLD/PIM). These protocols regulate IP @@ -17,7 +16,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h -index b99aa1d6a260..93f3bbc37590 100644 +index b99aa1d6a2608..93f3bbc37590a 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -1088,7 +1088,10 @@ br_multicast_get_first_rport_node(struct net_bridge_mcast *brmctx, diff --git a/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch b/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch index ab6d68dd6..5357abce7 100644 --- a/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch +++ b/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch @@ -1,9 +1,8 @@ From a42722822b7ff8ea8ac5e65a8449bb72b1ef8c05 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 4 Apr 2024 16:36:30 +0200 -Subject: [PATCH 08/80] net: bridge: drop delay for applying strict multicast +Subject: [PATCH 08/81] net: bridge: drop delay for applying strict multicast filtering -Organization: Wires This *local* patch drops the initial delay before applying strict multicast filtering, introduced in [1] and recently updated in [2]. @@ -25,7 +24,7 @@ Signed-off-by: Joachim Wiberg 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c -index db837e27ed8a..2ccfe3c7b20e 100644 +index db837e27ed8a2..2ccfe3c7b20e3 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -1761,10 +1761,6 @@ static void br_ip6_multicast_querier_expired(struct timer_list *t) @@ -164,7 +163,7 @@ index db837e27ed8a..2ccfe3c7b20e 100644 #endif diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h -index 93f3bbc37590..ec6a85023b5a 100644 +index 93f3bbc37590a..ec6a85023b5ac 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -80,7 +80,6 @@ struct bridge_mcast_own_query { diff --git a/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch b/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch index 32fedf98f..612760b34 100644 --- a/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch +++ b/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch @@ -1,9 +1,8 @@ From 2722df35856114c70f9896452d155b660f804ed8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 May 2024 14:51:54 +0200 -Subject: [PATCH 09/80] net: bridge: Differentiate MDB additions from +Subject: [PATCH 09/81] net: bridge: Differentiate MDB additions from modifications -Organization: Wires Before this change, the reception of an IGMPv3 report (and analogously for MLDv2) that adds a new group, would trigger two MDB RTM_NEWMDB @@ -28,7 +27,7 @@ introducing a RTM_SETMDB events to be used in the latter scenario. 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h -index dab9493c791b..40a3534f0f1d 100644 +index dab9493c791b8..40a3534f0f1d7 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -142,6 +142,8 @@ enum { @@ -41,7 +40,7 @@ index dab9493c791b..40a3534f0f1d 100644 RTM_NEWNSID = 88, #define RTM_NEWNSID RTM_NEWNSID diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c -index a80ec51fe18d..4cbf3381564d 100644 +index a80ec51fe18d1..4cbf3381564d7 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c @@ -752,7 +752,7 @@ static int br_mdb_replace_group_sg(const struct br_mdb_config *cfg, @@ -63,7 +62,7 @@ index a80ec51fe18d..4cbf3381564d 100644 if (br_multicast_should_handle_mode(brmctx, cfg->group.proto)) br_multicast_star_g_handle_mode(pg, cfg->filter_mode); diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c -index 2ccfe3c7b20e..aad576e4af65 100644 +index 2ccfe3c7b20e3..aad576e4af65d 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -616,7 +616,7 @@ static void br_multicast_fwd_src_handle(struct net_bridge_group_src *src) diff --git a/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch b/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch index bd653df54..3e93a454b 100644 --- a/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch +++ b/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch @@ -1,9 +1,8 @@ From 17f03931f23a3ed1dbe6f77a7e48bfdc11f17140 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 24 Nov 2023 23:29:55 +0100 -Subject: [PATCH 10/80] nvmem: layouts: onie-tlv: Let device probe even when +Subject: [PATCH 10/81] nvmem: layouts: onie-tlv: Let device probe even when TLV is invalid -Organization: Wires Before this change, probing an NVMEM device, expected to contain a valid TLV, would fail if it had not been provisioned yet. But an @@ -18,7 +17,7 @@ simply refrain from registering any cells in those cases. 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c -index 8b0f3c1b8a0e..de85690aaa30 100644 +index 8b0f3c1b8a0e9..de85690aaa303 100644 --- a/drivers/nvmem/layouts/onie-tlv.c +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -198,7 +198,7 @@ static int onie_tlv_parse_table(struct nvmem_layout *layout) diff --git a/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch b/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch index 0a2a275c6..e2d588a30 100644 --- a/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch +++ b/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch @@ -1,9 +1,8 @@ From cbc993b90307a45db570d20bce5e651100bafaa7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 11 Aug 2024 11:27:35 +0200 -Subject: [PATCH 11/80] net: usb: r8152: add r8153b support for link/activity +Subject: [PATCH 11/81] net: usb: r8152: add r8153b support for link/activity LEDs -Organization: Wires This patch adds support for the link/activity LEDs on the NanoPi R2S and OrangePi R1 Plus. @@ -19,7 +18,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 8 insertions(+) diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c -index 8cf4e81f8f88..1bb93efb96c6 100644 +index 8cf4e81f8f882..1bb93efb96c68 100644 --- a/drivers/net/usb/r8152.c +++ b/drivers/net/usb/r8152.c @@ -41,6 +41,11 @@ diff --git a/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch b/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch index 0049db9a7..d6123223e 100644 --- a/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch +++ b/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch @@ -1,9 +1,8 @@ From 0c1a54359f6989eedad98678e030fc1d8fb44da0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 10 Aug 2025 18:52:54 +0200 -Subject: [PATCH 12/80] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to +Subject: [PATCH 12/81] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to match case -Organization: Wires For ref. see: https://wiki.banana-pi.org/File:Bpi-r3_Metal_case.jpg @@ -16,7 +15,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts b/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts -index e7654dc9a1c9..4cc6aff07225 100644 +index e7654dc9a1c9b..4cc6aff072256 100644 --- a/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts +++ b/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts @@ -46,7 +46,7 @@ fan: pwm-fan { diff --git a/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch b/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch index 2038a8ee2..8aa5a854d 100644 --- a/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch +++ b/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch @@ -1,9 +1,8 @@ From 2cbb47d51538a83315595715405a328552b9a753 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 20 Aug 2025 21:38:24 +0200 -Subject: [PATCH 13/80] drm/panel-simple: Add a timing for the Raspberry Pi 7" +Subject: [PATCH 13/81] drm/panel-simple: Add a timing for the Raspberry Pi 7" panel -Organization: Wires The Raspberry Pi 7" 800x480 panel uses a Toshiba TC358762 DSI to DPI bridge chip, so there is a requirement for the timings @@ -47,7 +46,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c -index 878a5dc7748f..d5da86abea70 100644 +index 878a5dc7748fb..d5da86abea700 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -400,7 +400,8 @@ static int panel_simple_get_modes(struct drm_panel *panel, diff --git a/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch b/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch index 64b7d90e8..ba7ab2d41 100644 --- a/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch +++ b/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch @@ -1,8 +1,7 @@ From fed97704c224902b2d79c81492c5a50ccd97793b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 21 Aug 2025 11:20:23 +0200 -Subject: [PATCH 14/80] input:touchscreen:edt-ft5x06: Add polled mode -Organization: Wires +Subject: [PATCH 14/81] input:touchscreen:edt-ft5x06: Add polled mode Not all hardware has interrupts therefore we need to poll the touchscreen. @@ -11,7 +10,7 @@ to poll the touchscreen. 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c -index 4efdb467b6c6..6ec1078081d5 100644 +index 4efdb467b6c61..6ec1078081d58 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -77,6 +77,9 @@ diff --git a/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch b/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch index 808292f1e..8b00ec54a 100644 --- a/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch +++ b/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch @@ -1,9 +1,8 @@ From 11f9f49b62a6eb3a73a523541b3f26f6958e0d49 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Mar 2024 10:27:24 +0100 -Subject: [PATCH 15/80] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for +Subject: [PATCH 15/81] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for PPU on 6393X -Organization: Wires In a multi-chip setup, delays of up to 750ms are observed before the device (6393X) signals completion of PPU initialization (Global 1, @@ -13,7 +12,7 @@ register 0, bit 15). Therefore, increase the timeout threshold to 1s. 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index e9270c266884..37cd099180d6 100644 +index e9270c266884c..37cd099180d64 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -87,7 +87,7 @@ int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val) diff --git a/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch b/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch index 0bdf20fb2..f640b7fa6 100644 --- a/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch +++ b/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch @@ -1,9 +1,8 @@ From 6aed16ecedd6e8d3594474b00b4f80a6a75372c1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 15:52:43 +0100 -Subject: [PATCH 16/80] net: dsa: mv88e6xxx: Improve indirect register access +Subject: [PATCH 16/81] net: dsa: mv88e6xxx: Improve indirect register access perf on 6393 -Organization: Wires When operating in multi-chip mode, the 6393 family maps a subset of commonly used global registers to the outermost address space (in @@ -22,7 +21,7 @@ other accesses use the regular indirect interface. 5 files changed, 119 insertions(+) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index 37cd099180d6..e8980609b45d 100644 +index 37cd099180d64..e8980609b45da 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -6554,6 +6554,13 @@ static int mv88e6xxx_detect(struct mv88e6xxx_chip *chip) @@ -40,7 +39,7 @@ index 37cd099180d6..e8980609b45d 100644 chip->info->prod_num, chip->info->name, rev); diff --git a/drivers/net/dsa/mv88e6xxx/global1.h b/drivers/net/dsa/mv88e6xxx/global1.h -index 3dbb7a1b8fe1..5ea956aadecc 100644 +index 3dbb7a1b8fe11..5ea956aadecc9 100644 --- a/drivers/net/dsa/mv88e6xxx/global1.h +++ b/drivers/net/dsa/mv88e6xxx/global1.h @@ -218,6 +218,9 @@ @@ -54,7 +53,7 @@ index 3dbb7a1b8fe1..5ea956aadecc 100644 #define MV88E6XXX_G1_CTL2 0x1c #define MV88E6185_G1_CTL2_CASCADE_PORT_MASK 0xf000 diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h -index 82f9b410de0b..3663645621c9 100644 +index 82f9b410de0b8..3663645621c9d 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.h +++ b/drivers/net/dsa/mv88e6xxx/global2.h @@ -143,6 +143,9 @@ @@ -68,7 +67,7 @@ index 82f9b410de0b..3663645621c9 100644 #define MV88E6XXX_G2_EEPROM_CMD 0x14 #define MV88E6XXX_G2_EEPROM_CMD_BUSY 0x8000 diff --git a/drivers/net/dsa/mv88e6xxx/smi.c b/drivers/net/dsa/mv88e6xxx/smi.c -index a990271b7482..f54bb0e79030 100644 +index a990271b74823..f54bb0e79030c 100644 --- a/drivers/net/dsa/mv88e6xxx/smi.c +++ b/drivers/net/dsa/mv88e6xxx/smi.c @@ -8,6 +8,8 @@ @@ -169,7 +168,7 @@ index a990271b7482..f54bb0e79030 100644 chip->smi_ops = &mv88e6xxx_smi_indirect_ops; else diff --git a/drivers/net/dsa/mv88e6xxx/smi.h b/drivers/net/dsa/mv88e6xxx/smi.h -index c6c71d5757f5..788cf68b7b33 100644 +index c6c71d5757f5d..788cf68b7b334 100644 --- a/drivers/net/dsa/mv88e6xxx/smi.h +++ b/drivers/net/dsa/mv88e6xxx/smi.h @@ -31,6 +31,36 @@ diff --git a/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch b/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch index 1873f0e3b..d5d860a99 100644 --- a/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch +++ b/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch @@ -1,9 +1,8 @@ From 627e2902f0b11f9994e5c8c92627791b60069cdb Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 22 Apr 2024 23:18:01 +0200 -Subject: [PATCH 17/80] net: dsa: mv88e6xxx: Honor ports being managed via +Subject: [PATCH 17/81] net: dsa: mv88e6xxx: Honor ports being managed via in-band-status -Organization: Wires Keep all link parameters in their unforced states when the port is declared as being managed via in-band-status, and let the MAC @@ -18,7 +17,7 @@ when in-band-status is being used. 1 file changed, 6 insertions(+) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index e8980609b45d..0a535dc0a48c 100644 +index e8980609b45da..0a535dc0a48c1 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -965,6 +965,9 @@ static void mv88e6xxx_mac_link_down(struct phylink_config *config, diff --git a/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch b/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch index 70fc05377..d3d3efdaa 100644 --- a/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch +++ b/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch @@ -1,9 +1,8 @@ From 1ad63ed23d72ed2429bcb7393aac0e82affde9e3 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 24 Apr 2024 22:41:04 +0200 -Subject: [PATCH 18/80] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user +Subject: [PATCH 18/81] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user ports on 6393X -Organization: Wires For packets with a DA in the IEEE reserved L2 group range, originating from a CPU, forward it as normal, rather than classifying it as @@ -34,7 +33,7 @@ forwarded like any other. 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c -index 66b1b7277281..df19dfa5cc3d 100644 +index 66b1b72772810..df19dfa5cc3d8 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -1434,6 +1434,23 @@ static int mv88e6393x_port_policy_write_all(struct mv88e6xxx_chip *chip, diff --git a/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch b/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch index 5fb65cefd..453be1f3e 100644 --- a/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch +++ b/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch @@ -1,9 +1,8 @@ From 29b6583d7463dfddbfbb0e30a70de7707f5c86b0 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 10:38:42 +0200 -Subject: [PATCH 19/80] net: dsa: tag_dsa: Use tag priority as initial +Subject: [PATCH 19/81] net: dsa: tag_dsa: Use tag priority as initial skb->priority -Organization: Wires Use the 3-bit priority field from the DSA tag as the initial packet priority on ingress to the CPU. @@ -24,7 +23,7 @@ implemented, support the setup that is likely to be the most common; a 1 file changed, 7 insertions(+) diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c -index 2a2c4fb61a65..a00ae6bf2971 100644 +index 2a2c4fb61a65c..a00ae6bf29717 100644 --- a/net/dsa/tag_dsa.c +++ b/net/dsa/tag_dsa.c @@ -323,6 +323,13 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev, diff --git a/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch b/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch index c92863ee5..732e35d14 100644 --- a/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch +++ b/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch @@ -1,9 +1,8 @@ From 6cebfb46fd0a75b04f2481dcbd43cb9442bb1e12 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 16 Jan 2024 16:00:55 +0100 -Subject: [PATCH 20/80] net: dsa: Support MDB memberships whose L2 addresses +Subject: [PATCH 20/81] net: dsa: Support MDB memberships whose L2 addresses overlap -Organization: Wires Multiple IP multicast groups (32 for v4, 2^80 for v6) map to the same L2 address. This means that switchdev drivers may receive multiple MDB @@ -34,7 +33,7 @@ which previously skipped reference countung on user ports. 1 file changed, 16 deletions(-) diff --git a/net/dsa/switch.c b/net/dsa/switch.c -index 3d2feeea897b..628e8a884dde 100644 +index 3d2feeea897b6..628e8a884dde5 100644 --- a/net/dsa/switch.c +++ b/net/dsa/switch.c @@ -164,14 +164,6 @@ static int dsa_port_do_mdb_add(struct dsa_port *dp, diff --git a/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch b/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch index 651625049..9cde3a5d3 100644 --- a/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch +++ b/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch @@ -1,8 +1,7 @@ From 2b39a3958e00546ff1c6f0cc523970d19355d796 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 21 Mar 2024 19:12:15 +0100 -Subject: [PATCH 21/80] net: dsa: Support EtherType based priority overrides -Organization: Wires +Subject: [PATCH 21/81] net: dsa: Support EtherType based priority overrides --- include/net/dsa.h | 4 ++++ @@ -10,7 +9,7 @@ Organization: Wires 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/include/net/dsa.h b/include/net/dsa.h -index 5cb456bf4639..c31d4e910f07 100644 +index 5cb456bf46394..c31d4e910f075 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -958,6 +958,10 @@ struct dsa_switch_ops { @@ -25,7 +24,7 @@ index 5cb456bf4639..c31d4e910f07 100644 /* * Suspend and resume diff --git a/net/dsa/user.c b/net/dsa/user.c -index f59d66f0975d..58174ad612d2 100644 +index f59d66f0975d7..58174ad612d21 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2336,6 +2336,34 @@ dsa_user_dcbnl_add_dscp_prio(struct net_device *dev, struct dcb_app *app) diff --git a/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch b/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch index bcb014ba6..ab7639986 100644 --- a/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch +++ b/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch @@ -1,9 +1,8 @@ From 3769055203bee35077c685d850f0debbac52459e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 22 Mar 2024 16:15:43 +0100 -Subject: [PATCH 22/80] net: dsa: mv88e6xxx: Support EtherType based priority +Subject: [PATCH 22/81] net: dsa: mv88e6xxx: Support EtherType based priority overrides -Organization: Wires --- drivers/net/dsa/mv88e6xxx/chip.c | 64 +++++++++++++++++++++++++++++ @@ -15,7 +14,7 @@ Organization: Wires 6 files changed, 207 insertions(+), 4 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index 0a535dc0a48c..c708f1cce9b3 100644 +index 0a535dc0a48c1..c708f1cce9b30 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -1688,6 +1688,11 @@ static int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip) @@ -118,7 +117,7 @@ index 0a535dc0a48c..c708f1cce9b3 100644 static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip) diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h -index e073446ee7d0..5d5e7ab63fca 100644 +index e073446ee7d02..5d5e7ab63fca3 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -301,6 +301,11 @@ struct mv88e6xxx_port { @@ -171,7 +170,7 @@ index e073446ee7d0..5d5e7ab63fca 100644 struct mv88e6xxx_bus_ops { diff --git a/drivers/net/dsa/mv88e6xxx/global2.c b/drivers/net/dsa/mv88e6xxx/global2.c -index 30a6ffa7817b..3f16a7ef3fa2 100644 +index 30a6ffa7817b0..3f16a7ef3fa28 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.c +++ b/drivers/net/dsa/mv88e6xxx/global2.c @@ -315,7 +315,7 @@ int mv88e6xxx_g2_atu_stats_get(struct mv88e6xxx_chip *chip, u16 *stats) @@ -245,7 +244,7 @@ index 30a6ffa7817b..3f16a7ef3fa2 100644 * Offset 0x15: EEPROM Data (for 16-bit data access) * Offset 0x15: EEPROM Addr (for 8-bit data access) diff --git a/drivers/net/dsa/mv88e6xxx/global2.h b/drivers/net/dsa/mv88e6xxx/global2.h -index 3663645621c9..496269c26c50 100644 +index 3663645621c9d..496269c26c508 100644 --- a/drivers/net/dsa/mv88e6xxx/global2.h +++ b/drivers/net/dsa/mv88e6xxx/global2.h @@ -138,6 +138,7 @@ @@ -267,7 +266,7 @@ index 3663645621c9..496269c26c50 100644 int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num, bool hash, u16 mask); diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c -index df19dfa5cc3d..e1fee09eae2f 100644 +index df19dfa5cc3d8..e1fee09eae2fc 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -1560,6 +1560,52 @@ int mv88e6351_port_set_ether_type(struct mv88e6xxx_chip *chip, int port, @@ -324,7 +323,7 @@ index df19dfa5cc3d..e1fee09eae2f 100644 * Offset 0x19: Port IEEE Priority Remapping Registers [4-7] */ diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h -index c1d2f99efb1c..d2ba9ed1f23e 100644 +index c1d2f99efb1c6..d2ba9ed1f23ea 100644 --- a/drivers/net/dsa/mv88e6xxx/port.h +++ b/drivers/net/dsa/mv88e6xxx/port.h @@ -286,7 +286,18 @@ diff --git a/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch b/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch index 6a9368b2d..53fd825e1 100644 --- a/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch +++ b/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch @@ -1,8 +1,7 @@ From bf8849ce4ee5e95f33c435914a5d7c8e6e16e416 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 11:04:22 +0200 -Subject: [PATCH 23/80] net: dsa: mv88e6xxx: Add mqprio qdisc support -Organization: Wires +Subject: [PATCH 23/81] 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 @@ -30,7 +29,7 @@ handle the mapping down to the "real" number. 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index c708f1cce9b3..2dd0573e101e 100644 +index c708f1cce9b30..2dd0573e101eb 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -32,6 +32,7 @@ @@ -132,7 +131,7 @@ index c708f1cce9b3..2dd0573e101e 100644 return dsa_register_switch(ds); diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c -index a00ae6bf2971..55c296e0b5b0 100644 +index a00ae6bf29717..55c296e0b5b0e 100644 --- a/net/dsa/tag_dsa.c +++ b/net/dsa/tag_dsa.c @@ -180,8 +180,10 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev, diff --git a/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch b/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch index 2a5c3b479..11f537e99 100644 --- a/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch +++ b/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch @@ -1,9 +1,8 @@ From 73b39094d7cdd592ec6fb18164590678789ab37a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 29 May 2024 13:20:41 +0200 -Subject: [PATCH 24/80] net: dsa: mv88e6xxx: Use VLAN prio over IP when both +Subject: [PATCH 24/81] net: dsa: mv88e6xxx: Use VLAN prio over IP when both are available -Organization: Wires Switch the priority sourcing precdence to prefer VLAN PCP over IP DSCP, when both are available. @@ -27,7 +26,7 @@ main reasons for choosing the new default: 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index 2dd0573e101e..15c80e66aefd 100644 +index 2dd0573e101eb..15c80e66aefd2 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -3430,9 +3430,13 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) diff --git a/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch b/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch index cf9bd45f2..15b2afe91 100644 --- a/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch +++ b/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch @@ -1,9 +1,8 @@ From 320983cf57f27f4138a4736388ad384ea9754d37 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 26 Nov 2024 19:45:59 +0100 -Subject: [PATCH 25/80] [FIX] net: dsa: mv88e6xxx: Trap locally terminated +Subject: [PATCH 25/81] [FIX] net: dsa: mv88e6xxx: Trap locally terminated VLANs -Organization: Wires Before this change, in a setup like the following, packets assigned to VLAN 10 were forwarded between the switch ports, even though the @@ -31,7 +30,7 @@ cause all packets assigned to these VLANs to properly terminated. 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index 15c80e66aefd..397ae2b78df8 100644 +index 15c80e66aefd2..397ae2b78df84 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -2619,7 +2619,7 @@ static int mv88e6xxx_port_broadcast_sync(struct mv88e6xxx_chip *chip, int port, @@ -118,7 +117,7 @@ index 15c80e66aefd..397ae2b78df8 100644 return err; diff --git a/include/net/switchdev.h b/include/net/switchdev.h -index 8346b0d29542..764cd3dd4645 100644 +index 8346b0d29542c..764cd3dd46454 100644 --- a/include/net/switchdev.h +++ b/include/net/switchdev.h @@ -104,6 +104,10 @@ struct switchdev_obj_port_vlan { @@ -133,7 +132,7 @@ index 8346b0d29542..764cd3dd4645 100644 #define SWITCHDEV_OBJ_PORT_VLAN(OBJ) \ diff --git a/net/dsa/user.c b/net/dsa/user.c -index 58174ad612d2..c0019d0c1172 100644 +index 58174ad612d21..c0019d0c1172d 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -1796,6 +1796,8 @@ static int dsa_user_vlan_rx_add_vid(struct net_device *dev, __be16 proto, diff --git a/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch b/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch index 0580d2cc2..e9cb0ecc3 100644 --- a/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch +++ b/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch @@ -1,9 +1,8 @@ From 95d919b20d95e7d33432d450e2caafa42105d645 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 12:35:12 +0100 -Subject: [PATCH 26/80] net: dsa: mv88e6xxx: collapse disabled state into +Subject: [PATCH 26/81] net: dsa: mv88e6xxx: collapse disabled state into blocking -Organization: Wires This patch changes the behavior of switchcore ports wrt. the port state. Instead of disabling the port, the driver now treats the disabled state @@ -23,7 +22,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 2 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c -index e1fee09eae2f..e28fdd21de38 100644 +index e1fee09eae2fc..e28fdd21de38e 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -793,8 +793,6 @@ int mv88e6xxx_port_set_state(struct mv88e6xxx_chip *chip, int port, u8 state) diff --git a/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch b/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch index a89e04a5c..83e0fcdac 100644 --- a/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch +++ b/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch @@ -1,9 +1,8 @@ From 4a67cefaa3e4d69034afee21162133191863e841 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 12 Feb 2025 22:03:14 +0100 -Subject: [PATCH 27/80] net: dsa: mv88e6xxx: Only activate LAG offloading when +Subject: [PATCH 27/81] net: dsa: mv88e6xxx: Only activate LAG offloading when bridged -Organization: Wires The current port isolation scheme for mv88e6xxx is detailed here: https://lore.kernel.org/netdev/20220203101657.990241-1-tobias@waldekranz.com/ @@ -51,7 +50,7 @@ CPU; egress traffic always relies on software hashing. 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index 397ae2b78df8..71458342a274 100644 +index 397ae2b78df84..71458342a274c 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -3066,6 +3066,7 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port, diff --git a/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch b/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch index e9fff66d0..c6748afec 100644 --- a/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch +++ b/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch @@ -1,8 +1,7 @@ From de1f05b6ee9af9d4f7deb7cde43e28d81e1b7f7f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 14 Jan 2026 18:22:41 +0100 -Subject: [PATCH 28/80] net: dsa: mv88e6xxx: Add LED support for 6393X -Organization: Wires +Subject: [PATCH 28/81] net: dsa: mv88e6xxx: Add LED support for 6393X Original commit: commit 462277b926140ee2d231317e92afb6cabf640268 @@ -23,7 +22,7 @@ Date: Thu Nov 16 21:59:35 2023 +0100 4 files changed, 450 insertions(+) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index 71458342a274..f0a662e9b847 100644 +index 71458342a274c..f0a662e9b8477 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -5669,6 +5669,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = { @@ -35,7 +34,7 @@ index 71458342a274..f0a662e9b847 100644 .port_set_upstream_port = mv88e6393x_port_set_upstream_port, .stats_snapshot = mv88e6390_g1_stats_snapshot, diff --git a/drivers/net/dsa/mv88e6xxx/leds.c b/drivers/net/dsa/mv88e6xxx/leds.c -index ab3bc645da56..7c5904300849 100644 +index ab3bc645da566..7c59043008494 100644 --- a/drivers/net/dsa/mv88e6xxx/leds.c +++ b/drivers/net/dsa/mv88e6xxx/leds.c @@ -102,6 +102,58 @@ struct mv88e6xxx_led_hwconfig { @@ -453,7 +452,7 @@ index ab3bc645da56..7c5904300849 100644 + return ret; +} diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c -index e28fdd21de38..1985b8553611 100644 +index e28fdd21de38e..1985b85536112 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -1604,6 +1604,39 @@ int mv88e6xxx_port_relinquish_ether_type(struct mv88e6xxx_chip *chip, int port) @@ -497,7 +496,7 @@ index e28fdd21de38..1985b8553611 100644 * Offset 0x19: Port IEEE Priority Remapping Registers [4-7] */ diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h -index d2ba9ed1f23e..f4a56671fc4c 100644 +index d2ba9ed1f23ea..f4a56671fc4c2 100644 --- a/drivers/net/dsa/mv88e6xxx/port.h +++ b/drivers/net/dsa/mv88e6xxx/port.h @@ -444,6 +444,9 @@ diff --git a/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch b/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch index 8b4de6dce..3744b78cc 100644 --- a/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch +++ b/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch @@ -1,12 +1,11 @@ From 5ba4ebc2cbcc3efdb8c68e64de2e3e1d36f700ca Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 19 Jan 2026 13:06:53 +0100 -Subject: [PATCH 29/80] wifi: brcmfmac: check connection state before querying +Subject: [PATCH 29/81] wifi: brcmfmac: check connection state before querying station info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires In station mode, brcmf_cfg80211_get_station() queries the firmware for station info even when not connected to an AP. This results in error @@ -24,7 +23,7 @@ Signed-off-by: Mattias Walström 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index 1fc9ecd86d5d..4189a127f4b3 100644 +index 1fc9ecd86d5d9..4189a127f4b37 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -3156,6 +3156,11 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev, diff --git a/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch b/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch index 87983e004..97e86baf1 100644 --- a/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch +++ b/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch @@ -1,12 +1,11 @@ From e714d2bb8736f8befb0e223b4de054981125010e Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:12:10 +0100 -Subject: [PATCH 30/80] wifi: brcmfmac: suppress log spam for +Subject: [PATCH 30/81] wifi: brcmfmac: suppress log spam for regulatory-restricted channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires When scanning, the driver attempts to set each channel and logs an error if the firmware rejects it. For regulatory-restricted channels, @@ -21,7 +20,7 @@ Signed-off-by: Mattias Walström 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index 4189a127f4b3..88dac288a398 100644 +index 4189a127f4b37..88dac288a3986 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -8133,7 +8133,12 @@ brcmf_set_channel(struct brcmf_cfg80211_info *cfg, struct ieee80211_channel *cha diff --git a/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch b/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch index 6a245f952..ec0a97518 100644 --- a/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch +++ b/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch @@ -1,12 +1,11 @@ From 09696680be45dbeb24e8a722c3d4030b7737e11a Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:18:45 +0100 -Subject: [PATCH 31/80] wifi: brcmfmac: reduce log noise during AP to station +Subject: [PATCH 31/81] wifi: brcmfmac: reduce log noise during AP to station transition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires When transitioning from AP mode to station mode (e.g., hostapd stopping and wpa_supplicant starting), several non-fatal errors can occur: @@ -28,7 +27,7 @@ Signed-off-by: Mattias Walström 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index 88dac288a398..0081ceb6c782 100644 +index 88dac288a3986..0081ceb6c7829 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -1030,13 +1030,15 @@ static void brcmf_scan_config_mpc(struct brcmf_if *ifp, int mpc) diff --git a/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch b/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch index f3bced2d9..501d6453f 100644 --- a/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch +++ b/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch @@ -1,9 +1,8 @@ From 28322c174df66701c2f4283bf338a3032a8121bc Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 17 Feb 2026 21:59:59 +0100 -Subject: [PATCH 32/80] net: phy: air_en8811h: add OF device table for +Subject: [PATCH 32/81] net: phy: air_en8811h: add OF device table for auto-loading -Organization: Wires mdio_uevent() only emits an OF-style MODALIAS via of_device_uevent_modalias(), never the mdio: binary format that @@ -21,7 +20,7 @@ rejects drivers that provide an of_match_table. 1 file changed, 6 insertions(+) diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c -index badd65f0ccee..b609465247a2 100644 +index badd65f0ccee2..b609465247a28 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -1184,6 +1184,12 @@ static int en8811h_suspend(struct phy_device *phydev) diff --git a/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch b/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch index 9e4158b1d..7d974a0f2 100644 --- a/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch +++ b/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch @@ -1,9 +1,8 @@ From 53aba25643ef8e41021eab9b61242f3151fa3a56 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 20 Sep 2024 12:05:18 +0100 -Subject: [PATCH 33/80] drm: vc4: dsi: enable video and then retry failed +Subject: [PATCH 33/81] drm: vc4: dsi: enable video and then retry failed transfers -Organization: Wires The DSI block appears to be able to come up stuck in a condition where it leaves the lanes in HS mode or just jabbering. This stops LP @@ -19,7 +18,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c -index 458e5d987964..439d7a04127d 100644 +index 458e5d9879645..439d7a04127d8 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -286,6 +286,8 @@ diff --git a/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch b/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch index 15de47038..c0d162eac 100644 --- a/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch +++ b/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch @@ -1,8 +1,7 @@ From b145fb6117a42c835d69cee8d01b7679396541e6 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 8 Jun 2022 17:23:47 +0100 -Subject: [PATCH 34/80] drm: vc4: dsi: Clocks should be running before reset -Organization: Wires +Subject: [PATCH 34/81] 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 @@ -14,7 +13,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c -index 439d7a04127d..1eba498d276f 100644 +index 439d7a04127d8..1eba498d276f8 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -926,6 +926,21 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge, diff --git a/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch b/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch index 1ce1a25e6..072673f11 100644 --- a/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch +++ b/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch @@ -1,8 +1,7 @@ From 394a21d586f7f13a5cb822ed95ef986dfb17ca87 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 5 Apr 2024 17:51:55 +0100 -Subject: [PATCH 35/80] drm/vc4: Ensure DSI is enabled for FIFO resets -Organization: Wires +Subject: [PATCH 35/81] 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. @@ -13,7 +12,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c -index 1eba498d276f..de963edefb76 100644 +index 1eba498d276f8..de963edefb760 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -400,7 +400,8 @@ diff --git a/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch b/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch index ed792a262..20ff1a89d 100644 --- a/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch +++ b/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch @@ -1,8 +1,7 @@ From 5b807e23d91f3d90697f74d484276e574670aa2c Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 26 May 2022 18:56:19 +0100 -Subject: [PATCH 36/80] drm: vc4: Reset DSI AFE on disable -Organization: Wires +Subject: [PATCH 36/81] drm: vc4: Reset DSI AFE on disable vc4_dsi_bridge_disable wasn't resetting things during shutdown, so add that in. @@ -13,7 +12,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 20 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c -index de963edefb76..9756821ebf27 100644 +index de963edefb760..9756821ebf27c 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -360,6 +360,16 @@ diff --git a/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch b/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch index 7d8311eaf..1b560872c 100644 --- a/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch +++ b/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch @@ -1,8 +1,7 @@ From 1610998f5338234b29c335d18ffeb00c2dcec73a Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 20 Nov 2024 13:58:08 +0000 -Subject: [PATCH 37/80] drm: vc4: dsi: Handle the different command FIFO widths -Organization: Wires +Subject: [PATCH 37/81] 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 @@ -18,7 +17,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c -index 9756821ebf27..b1cc30b8aa79 100644 +index 9756821ebf27c..b1cc30b8aa797 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -44,7 +44,6 @@ diff --git a/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch b/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch index 01ca9154c..6f698596c 100644 --- a/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch +++ b/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch @@ -1,9 +1,8 @@ From 0c2f36c6efa65abc8d2c5179903c3fb5afac9874 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 17:37:00 +0000 -Subject: [PATCH 38/80] drm/bridge: tc358762: Program the DPI mode into the +Subject: [PATCH 38/81] drm/bridge: tc358762: Program the DPI mode into the chip -Organization: Wires The autodetection of resolution/timing by the TC358762 can lead to the display being shifted by a pixel or two. @@ -17,7 +16,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 15 insertions(+) diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c -index 98df3e667d4a..73b169026e63 100644 +index 98df3e667d4aa..73b169026e63b 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -53,6 +53,12 @@ diff --git a/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch b/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch index 11deb5aa7..0cde615a9 100644 --- a/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch +++ b/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch @@ -1,8 +1,7 @@ From 91f9e48ff0405e6b8ec9d046b7f37d96f99483c5 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 18:44:49 +0000 -Subject: [PATCH 39/80] drm/bridge: tc358762: revert move ops to enable -Organization: Wires +Subject: [PATCH 39/81] 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. @@ -13,7 +12,7 @@ Signed-off-by: Dave Stevenson 1 file changed, 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c -index 73b169026e63..6164932edfec 100644 +index 73b169026e63b..6164932edfec9 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -202,15 +202,6 @@ static void tc358762_pre_enable(struct drm_bridge *bridge, diff --git a/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch b/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch index 247ff26b1..c62e74b9c 100644 --- a/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch +++ b/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch @@ -1,12 +1,11 @@ From 18d15d554c004a795771c8adb8abb6ef54f22985 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Sat, 4 Apr 2026 18:04:19 +0200 -Subject: [PATCH 40/80] drm/bridge: tc358762: Set pre_enabled on pre_enable to +Subject: [PATCH 40/81] drm/bridge: tc358762: Set pre_enabled on pre_enable to prevent regulator imbalance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires The RPi cherry-pick moved tc358762_init() to pre_enable but dropped the pre_enabled = true assignment. Without it, post_disable always bails out early and never disables the regulator. @@ -17,7 +16,7 @@ Signed-off-by: Mattias Walström 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c -index 6164932edfec..8da66b06d6b6 100644 +index 6164932edfec9..8da66b06d6b63 100644 --- a/drivers/gpu/drm/bridge/tc358762.c +++ b/drivers/gpu/drm/bridge/tc358762.c @@ -205,6 +205,8 @@ static void tc358762_pre_enable(struct drm_bridge *bridge, diff --git a/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch b/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch index c5243d73c..96a06608b 100644 --- a/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch +++ b/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch @@ -1,8 +1,7 @@ From a2fc20fe20ad386cff232905d1858f9af21ecaf2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 5 Apr 2026 11:33:00 +0200 -Subject: [PATCH 41/80] net/pcs: add standalone PCS registration infrastructure -Organization: Wires +Subject: [PATCH 41/81] 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 @@ -23,7 +22,7 @@ Based on work by Daniel Golle . create mode 100644 include/linux/pcs/pcs-standalone.h diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig -index ecbc3530e780..f6b4de7f972c 100644 +index ecbc3530e780c..f6b4de7f972cc 100644 --- a/drivers/net/pcs/Kconfig +++ b/drivers/net/pcs/Kconfig @@ -5,6 +5,10 @@ @@ -38,7 +37,7 @@ index ecbc3530e780..f6b4de7f972c 100644 tristate "Synopsys DesignWare Ethernet XPCS" select PHYLINK diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile -index 4f7920618b90..0cb0057f2b8e 100644 +index 4f7920618b900..0cb0057f2b8e6 100644 --- a/drivers/net/pcs/Makefile +++ b/drivers/net/pcs/Makefile @@ -4,6 +4,7 @@ @@ -51,7 +50,7 @@ index 4f7920618b90..0cb0057f2b8e 100644 obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o diff --git a/drivers/net/pcs/pcs-standalone.c b/drivers/net/pcs/pcs-standalone.c new file mode 100644 -index 000000000000..f7b46de59636 +index 0000000000000..f7b46de59636c --- /dev/null +++ b/drivers/net/pcs/pcs-standalone.c @@ -0,0 +1,95 @@ @@ -152,7 +151,7 @@ index 000000000000..f7b46de59636 +MODULE_LICENSE("GPL"); diff --git a/include/linux/pcs/pcs-standalone.h b/include/linux/pcs/pcs-standalone.h new file mode 100644 -index 000000000000..520835cdeee8 +index 0000000000000..520835cdeee83 --- /dev/null +++ b/include/linux/pcs/pcs-standalone.h @@ -0,0 +1,26 @@ diff --git a/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch b/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch index 0be7dcc0a..29b3e6400 100644 --- a/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch +++ b/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch @@ -1,8 +1,7 @@ From 6c0e1262a95027d0ef8fb263353e5a4382529437 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:14:23 +0200 -Subject: [PATCH 42/80] net/pcs: add MediaTek MT7988 USXGMII PCS driver -Organization: Wires +Subject: [PATCH 42/81] 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 @@ -28,7 +27,7 @@ Daniel Golle . create mode 100644 drivers/net/pcs/pcs-mtk-usxgmii.c diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig -index f6b4de7f972c..563c12d6aa9c 100644 +index f6b4de7f972cc..563c12d6aa9cf 100644 --- a/drivers/net/pcs/Kconfig +++ b/drivers/net/pcs/Kconfig @@ -29,6 +29,16 @@ config PCS_MTK_LYNXI @@ -49,7 +48,7 @@ index f6b4de7f972c..563c12d6aa9c 100644 tristate "Renesas RZ/N1, RZ/N2H, RZ/T2H MII converter" depends on OF diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile -index 0cb0057f2b8e..b876cb2157b1 100644 +index 0cb0057f2b8e6..b876cb2157b15 100644 --- a/drivers/net/pcs/Makefile +++ b/drivers/net/pcs/Makefile @@ -8,4 +8,5 @@ obj-$(CONFIG_PCS_STANDALONE) += pcs-standalone.o @@ -60,7 +59,7 @@ index 0cb0057f2b8e..b876cb2157b1 100644 obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o diff --git a/drivers/net/pcs/pcs-mtk-usxgmii.c b/drivers/net/pcs/pcs-mtk-usxgmii.c new file mode 100644 -index 000000000000..f0e7d418db38 +index 0000000000000..f0e7d418db388 --- /dev/null +++ b/drivers/net/pcs/pcs-mtk-usxgmii.c @@ -0,0 +1,394 @@ diff --git a/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch b/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch index c8ef7da05..b32b5dd1e 100644 --- a/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch +++ b/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch @@ -1,11 +1,10 @@ From 7a3a934b66b5a38e732f6c28f76c45b43cea4e15 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:43 +0200 -Subject: [PATCH 43/80] net: ethernet: mediatek: add USXGMII support for MT7988 +Subject: [PATCH 43/81] net: ethernet: mediatek: add USXGMII support for MT7988 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires Add support for the USXGMII path used by gmac1 and gmac2 on the MT7988 SoC (BananaPi BPI-R4). Changes: @@ -34,7 +33,7 @@ Daniel Golle . 3 files changed, 139 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_path.c b/drivers/net/ethernet/mediatek/mtk_eth_path.c -index b4c01e2878f6..57380776032a 100644 +index b4c01e2878f6f..57380776032a7 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_path.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_path.c @@ -37,6 +37,12 @@ static const char *mtk_eth_path_name(u64 path) @@ -127,7 +126,7 @@ index b4c01e2878f6..57380776032a 100644 +} + diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c -index 162a17a87c7f..9609e812033b 100644 +index 162a17a87c7f5..9609e812033b3 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -22,6 +22,7 @@ @@ -212,7 +211,7 @@ index 162a17a87c7f..9609e812033b 100644 mac_ops = &rt5350_phylink_ops; diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/drivers/net/ethernet/mediatek/mtk_eth_soc.h -index 88a9b3b23bea..340ecb824f59 100644 +index 88a9b3b23bea5..340ecb824f59a 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h @@ -567,6 +567,7 @@ diff --git a/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch b/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch index 9acdd764b..785a4128b 100644 --- a/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch +++ b/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch @@ -1,8 +1,7 @@ From d320f311c3e132191b319db4d7f7e3e3f5ed58df Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:56 +0200 -Subject: [PATCH 44/80] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes -Organization: Wires +Subject: [PATCH 44/81] 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, @@ -15,7 +14,7 @@ property, so the Ethernet driver can discover the PCS at probe time. 1 file changed, 20 insertions(+) diff --git a/arch/arm64/boot/dts/mediatek/mt7988a.dtsi b/arch/arm64/boot/dts/mediatek/mt7988a.dtsi -index 366203a72d6d..5ae43390f6c2 100644 +index 366203a72d6d2..5ae43390f6c2c 100644 --- a/arch/arm64/boot/dts/mediatek/mt7988a.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt7988a.dtsi @@ -708,6 +708,24 @@ xfi_pll: clock-controller@11f40000 { diff --git a/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch b/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch index f453c1cbb..dbc73bf94 100644 --- a/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch +++ b/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch @@ -1,9 +1,8 @@ From d83185ab2d0bc45de5c33b6fc50006e166f39417 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:16:11 +0200 -Subject: [PATCH 45/80] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ +Subject: [PATCH 45/81] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ ports and WPS button -Organization: Wires Enable the SFP+ cages wired to gmac1 and gmac2. The USXGMII PCS nodes and xfi_tphy SerDes are wired up in mt7988a.dtsi; only status = "okay" @@ -26,7 +25,7 @@ Signed-off-by: Joachim Wiberg 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts -index 4b3796ba82e3..499f0c91c213 100644 +index 4b3796ba82e36..499f0c91c2137 100644 --- a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts +++ b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dts @@ -27,6 +27,7 @@ &gmac1 { @@ -38,7 +37,7 @@ index 4b3796ba82e3..499f0c91c213 100644 &pca9545 { diff --git a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi -index 0ff69dae45d3..2a904a76f3fe 100644 +index 0ff69dae45d32..2a904a76f3fe3 100644 --- a/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt7988a-bananapi-bpi-r4.dtsi @@ -3,9 +3,9 @@ diff --git a/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch b/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch index eb7f10d37..5030e7d35 100644 --- a/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch +++ b/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch @@ -1,11 +1,10 @@ From fa401f49e78eeefa6ef1752733c87b1d67655125 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 Apr 2026 07:34:52 +0200 -Subject: [PATCH 46/80] net: phy: sfp: add OEM SFP-10G-T-I quirk +Subject: [PATCH 46/81] 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 -Organization: Wires The industrial-temperature variant of the OEM SFP-10G-T copper module reports vendor PN "SFP-10G-T-I". Unlike the base "SFP-10G-T" which is a @@ -27,7 +26,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 11 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c -index af43530ffb2c..15a4b67ece2c 100644 +index af43530ffb2c3..15a4b67ece2cb 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -451,6 +451,16 @@ static void sfp_fixup_rollball_cc(struct sfp *sfp) diff --git a/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch b/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch index d719c8eae..51d9f1c47 100644 --- a/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch +++ b/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch @@ -1,9 +1,8 @@ From 79c898ca16e60f10eafaf89110b5d2c24bd9ac2c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 17 Apr 2026 09:13:04 +0000 -Subject: [PATCH 47/80] net: dsa: mv88e6xxx: Trap PTP frames on timestamping +Subject: [PATCH 47/81] net: dsa: mv88e6xxx: Trap PTP frames on timestamping ports, on 6393X -Organization: Wires Similar to the Peridot (6390), the designation of PTP frames as management (and the destination port) must be explicitly @@ -24,7 +23,7 @@ a port. 7 files changed, 67 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c -index f0a662e9b847..54bf531fcc35 100644 +index f0a662e9b8477..54bf531fcc35c 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -5700,7 +5700,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = { @@ -37,7 +36,7 @@ index f0a662e9b847..54bf531fcc35 100644 .pcs_ops = &mv88e6393x_pcs_ops, }; diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.c b/drivers/net/dsa/mv88e6xxx/hwtstamp.c -index 6e6472a3b75a..446e1c616752 100644 +index 6e6472a3b75ad..446e1c616752d 100644 --- a/drivers/net/dsa/mv88e6xxx/hwtstamp.c +++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.c @@ -13,6 +13,7 @@ @@ -81,7 +80,7 @@ index 6e6472a3b75a..446e1c616752 100644 { const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops; diff --git a/drivers/net/dsa/mv88e6xxx/hwtstamp.h b/drivers/net/dsa/mv88e6xxx/hwtstamp.h -index c359821d5a6e..c51cd1ab2978 100644 +index c359821d5a6ea..c51cd1ab29788 100644 --- a/drivers/net/dsa/mv88e6xxx/hwtstamp.h +++ b/drivers/net/dsa/mv88e6xxx/hwtstamp.h @@ -129,6 +129,8 @@ int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip); @@ -94,7 +93,7 @@ index c359821d5a6e..c51cd1ab2978 100644 int mv88e6165_global_disable(struct mv88e6xxx_chip *chip); diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c -index 1985b8553611..843972077576 100644 +index 1985b85536112..843972077576c 100644 --- a/drivers/net/dsa/mv88e6xxx/port.c +++ b/drivers/net/dsa/mv88e6xxx/port.c @@ -1484,6 +1484,16 @@ int mv88e6393x_port_set_upstream_port(struct mv88e6xxx_chip *chip, int port, @@ -115,7 +114,7 @@ index 1985b8553611..843972077576 100644 { u16 ptr; diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h -index f4a56671fc4c..5f733f84b348 100644 +index f4a56671fc4c2..5f733f84b3481 100644 --- a/drivers/net/dsa/mv88e6xxx/port.h +++ b/drivers/net/dsa/mv88e6xxx/port.h @@ -283,6 +283,8 @@ @@ -137,7 +136,7 @@ index f4a56671fc4c..5f733f84b348 100644 int mv88e6xxx_port_set_message_port(struct mv88e6xxx_chip *chip, int port, bool message_port); diff --git a/drivers/net/dsa/mv88e6xxx/ptp.c b/drivers/net/dsa/mv88e6xxx/ptp.c -index f7603573d3a9..327a9f786437 100644 +index f7603573d3a98..327a9f786437a 100644 --- a/drivers/net/dsa/mv88e6xxx/ptp.c +++ b/drivers/net/dsa/mv88e6xxx/ptp.c @@ -444,6 +444,29 @@ const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops = { @@ -171,7 +170,7 @@ index f7603573d3a9..327a9f786437 100644 { struct mv88e6xxx_chip *chip = cc_to_chip(cc); diff --git a/drivers/net/dsa/mv88e6xxx/ptp.h b/drivers/net/dsa/mv88e6xxx/ptp.h -index 95bdddb0bf39..54dce1fbf1aa 100644 +index 95bdddb0bf39f..54dce1fbf1aa2 100644 --- a/drivers/net/dsa/mv88e6xxx/ptp.h +++ b/drivers/net/dsa/mv88e6xxx/ptp.h @@ -75,6 +75,7 @@ void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip); diff --git a/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch b/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch index ffe2a3185..e9e9f3551 100644 --- a/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch +++ b/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch @@ -1,9 +1,8 @@ From 0f8cdee2b15e4d5c36520e274ebc74371ec8de68 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Apr 2026 15:30:01 +0200 -Subject: [PATCH 48/80] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 +Subject: [PATCH 48/81] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 wmac -Organization: Wires Without MODULE_DEVICE_TABLE(of, ...) the OF compatible alias is never exported to modules.alias, so udev cannot autoload mt7615e when the @@ -16,7 +15,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/soc.c b/drivers/net/wireless/mediatek/mt76/mt7615/soc.c -index 06a0f2a141e8..538ea1dc333b 100644 +index 06a0f2a141e8c..538ea1dc333b0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/soc.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/soc.c @@ -56,6 +56,7 @@ static const struct of_device_id mt7622_wmac_of_match[] = { diff --git a/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch b/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch index 222c7b325..7fa9e5406 100644 --- a/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch +++ b/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch @@ -1,12 +1,11 @@ From 5f9aa8845b841fcada5fa58f92ceaa9bb9fabcb4 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 22 Apr 2026 10:24:43 +0200 -Subject: [PATCH 49/80] PCI: mediatek-gen3: Fix PERST# control timing during +Subject: [PATCH 49/81] PCI: mediatek-gen3: Fix PERST# control timing during system startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires Some of MediaTek's chips (mt7986 amongst them) stop generating REFCLK if the PCIE_PHY_RSTB signal of the PCIe controller is asserted. @@ -31,7 +30,7 @@ PERST# control timing during system startup"). 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c -index e45c43ccc84c..af84bc63bcad 100644 +index e45c43ccc84c2..af84bc63bcad5 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -479,16 +479,35 @@ static int mtk_pcie_startup_port(struct mtk_gen3_pcie *pcie) diff --git a/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch b/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch index fd20557d0..59d9af05a 100644 --- a/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch +++ b/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch @@ -1,11 +1,10 @@ From 024fb8fd29a48bb85f355b5a4a558c8ab6875b3c Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 12 Aug 2026 10:08:53 +0200 -Subject: [PATCH 50/80] net: dsa: mv88e6xxx: Derive LED names from device name +Subject: [PATCH 50/81] net: dsa: mv88e6xxx: Derive LED names from device name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires LED devices were named ":0:0", e.g. "mv88e6390:01:00:port:lednum". Since chip->info->name is the model name, two @@ -17,7 +16,7 @@ Signed-off-by: Mattias Walström 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/mv88e6xxx/leds.c b/drivers/net/dsa/mv88e6xxx/leds.c -index 7c5904300849..2c09c37be077 100644 +index 7c59043008494..2c09c37be077b 100644 --- a/drivers/net/dsa/mv88e6xxx/leds.c +++ b/drivers/net/dsa/mv88e6xxx/leds.c @@ -874,8 +874,8 @@ int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port) diff --git a/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch b/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch index 48f34182d..e353d58f4 100644 --- a/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch +++ b/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch @@ -1,8 +1,7 @@ From f42698c6d686cabca0fced226a63e09c438fbc78 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 31 Oct 2025 13:18:12 +0100 -Subject: [PATCH 51/80] phy: sparx5-serdes: make it selectable for ARCH_LAN969X -Organization: Wires +Subject: [PATCH 51/81] phy: sparx5-serdes: make it selectable for ARCH_LAN969X LAN969x uses the SparX-5 SERDES driver, so make it selectable for ARCH_LAN969X. @@ -19,7 +18,7 @@ Signed-off-by: Vinod Koul 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/microchip/Kconfig b/drivers/phy/microchip/Kconfig -index 2f0045e874ac..2e6d1224711e 100644 +index 2f0045e874ac8..2e6d1224711e3 100644 --- a/drivers/phy/microchip/Kconfig +++ b/drivers/phy/microchip/Kconfig @@ -6,7 +6,7 @@ diff --git a/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch b/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch index 98d2b545a..93351d9ec 100644 --- a/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch +++ b/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch @@ -1,8 +1,7 @@ From da55a0660bc7a764abd39b28d1b588d58cfafcf7 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:38 +0200 -Subject: [PATCH 52/80] net: sparx5: fix wrong chip ids for TSN SKUs -Organization: Wires +Subject: [PATCH 52/81] net: sparx5: fix wrong chip ids for TSN SKUs The TSN SKUs in enum spx5_target_chiptype have incorrect IDs: @@ -31,7 +30,7 @@ Signed-off-by: Jakub Kicinski 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h -index a5675f13a11e..f7d86fc6aa12 100644 +index a5675f13a11e2..f7d86fc6aa125 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h @@ -31,11 +31,11 @@ enum spx5_target_chiptype { diff --git a/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch b/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch index b2d792169..7236de0a3 100644 --- a/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch +++ b/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch @@ -1,9 +1,8 @@ From 13292f9168073d35dfa05f2c3e8402e18bf3f9ce Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:39 +0200 -Subject: [PATCH 53/80] net: sparx5: configure serdes for 1000BASE-X in +Subject: [PATCH 53/81] net: sparx5: configure serdes for 1000BASE-X in sparx5_port_init() -Organization: Wires sparx5_port_init() only invokes sparx5_serdes_set() and the associated shadow-device enable and low-speed device switch for SGMII and QSGMII. @@ -29,7 +28,7 @@ Signed-off-by: Jakub Kicinski 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c -index 04bc8fffaf96..62c49893de3c 100644 +index 04bc8fffaf961..62c49893de3c3 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c @@ -1128,7 +1128,8 @@ int sparx5_port_init(struct sparx5 *sparx5, diff --git a/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch b/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch index b2257c8f7..dfe3cec22 100644 --- a/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch +++ b/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch @@ -1,9 +1,8 @@ From 704c64713d2d2d34a22e0d2f8066996bc62d9120 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:29 +0200 -Subject: [PATCH 54/80] dt-bindings: mmc: atmel,sama5d2-sdhci: add +Subject: [PATCH 54/81] dt-bindings: mmc: atmel,sama5d2-sdhci: add microchip,lan969x-sdhci -Organization: Wires The LAN969x SDMMC controller has its own base clock divider and, unlike the SAMA5D2 and SAM9X60 variants, supports HS200 and caps SDR104 at @@ -16,7 +15,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml b/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml -index ba75623b7778..860e6736e2eb 100644 +index ba75623b7778b..860e6736e2eb9 100644 --- a/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml +++ b/Documentation/devicetree/bindings/mmc/atmel,sama5d2-sdhci.yaml @@ -18,6 +18,7 @@ properties: diff --git a/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch b/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch index 3c40b60d1..8fbbec365 100644 --- a/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch +++ b/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch @@ -1,8 +1,7 @@ From ae98994e45aaa55ecff09321aa65601561aefab4 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:44 +0200 -Subject: [PATCH 55/80] mmc: sdhci-of-at91: add LAN969x support -Organization: Wires +Subject: [PATCH 55/81] 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. @@ -19,7 +18,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c -index 7c4ac65f247d..5dcb88cba0d9 100644 +index 7c4ac65f247d3..5dcb88cba0d9c 100644 --- a/drivers/mmc/host/sdhci-of-at91.c +++ b/drivers/mmc/host/sdhci-of-at91.c @@ -39,6 +39,9 @@ struct sdhci_at91_soc_data { diff --git a/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch b/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch index 403ec7426..5c7cec95e 100644 --- a/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch +++ b/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch @@ -1,9 +1,8 @@ From 4a50546ae4abb37070703d1ce20149b65fdd1a51 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:37:48 +0200 -Subject: [PATCH 56/80] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC +Subject: [PATCH 56/81] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC hardware reset -Organization: Wires The controller is reset, and its signaling mode changed, with SDCLK still running. Both require the clock to be stopped, otherwise the @@ -21,7 +20,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 50 insertions(+) diff --git a/drivers/mmc/host/sdhci-of-at91.c b/drivers/mmc/host/sdhci-of-at91.c -index 5dcb88cba0d9..6a9e4a7fa705 100644 +index 5dcb88cba0d9c..6a9e4a7fa705f 100644 --- a/drivers/mmc/host/sdhci-of-at91.c +++ b/drivers/mmc/host/sdhci-of-at91.c @@ -25,6 +25,7 @@ diff --git a/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch b/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch index da2fdecc7..5fafe3f00 100644 --- a/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch +++ b/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch @@ -1,8 +1,7 @@ From ba2f77ed48aae661eed6cb213162a612f4792931 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 10 Nov 2025 13:42:53 +0100 -Subject: [PATCH 57/80] net: sparx5/lan969x: populate netdev of_node -Organization: Wires +Subject: [PATCH 57/81] net: sparx5/lan969x: populate netdev of_node Populate of_node for the port netdevs, to make the individual ports of_nodes available in sysfs. @@ -16,7 +15,7 @@ Signed-off-by: Jakub Kicinski 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c -index 46837e7efbd0..787dcdc996cd 100644 +index 46837e7efbd07..787dcdc996cdc 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c @@ -395,6 +395,8 @@ static int sparx5_create_port(struct sparx5 *sparx5, diff --git a/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch b/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch index 9caf18ae7..9b43f6bfc 100644 --- a/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch +++ b/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch @@ -1,8 +1,7 @@ From b8f745bf145aed656a87edcf2cee16caef2950e2 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:11 +0100 -Subject: [PATCH 58/80] arm64: dts: microchip: add LAN969x clock header file -Organization: Wires +Subject: [PATCH 58/81] arm64: dts: microchip: add LAN969x clock header file LAN969x uses hardware clock indexes, so document theses in a header to make them humanly readable. @@ -19,7 +18,7 @@ Signed-off-by: Claudiu Beznea diff --git a/arch/arm64/boot/dts/microchip/clk-lan9691.h b/arch/arm64/boot/dts/microchip/clk-lan9691.h new file mode 100644 -index 000000000000..0f2d7a0f881e +index 0000000000000..0f2d7a0f881ef --- /dev/null +++ b/arch/arm64/boot/dts/microchip/clk-lan9691.h @@ -0,0 +1,24 @@ diff --git a/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch b/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch index b19053548..e27e2246a 100644 --- a/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch +++ b/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch @@ -1,8 +1,7 @@ From c3fc7872ff9176197161d2695a4d3fbbe2d90f90 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:12 +0100 -Subject: [PATCH 59/80] arm64: dts: microchip: add LAN969x support -Organization: Wires +Subject: [PATCH 59/81] arm64: dts: microchip: add LAN969x support Add support for Microchip LAN969x switch SoC series by adding the SoC DTSI. @@ -19,7 +18,7 @@ Signed-off-by: Claudiu Beznea diff --git a/arch/arm64/boot/dts/microchip/lan9691.dtsi b/arch/arm64/boot/dts/microchip/lan9691.dtsi new file mode 100644 -index 000000000000..235e56bebbdb +index 0000000000000..235e56bebbdbb --- /dev/null +++ b/arch/arm64/boot/dts/microchip/lan9691.dtsi @@ -0,0 +1,488 @@ diff --git a/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch b/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch index 76200a8fd..13e1c2b8a 100644 --- a/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch +++ b/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch @@ -1,8 +1,7 @@ From b10c04d81bb96ab0b09484ee2a2373482b56f26d Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:14 +0100 -Subject: [PATCH 60/80] arm64: dts: microchip: add EV23X71A board -Organization: Wires +Subject: [PATCH 60/81] arm64: dts: microchip: add EV23X71A board Microchip EV23X71A is an LAN9696 based evaluation board. @@ -20,7 +19,7 @@ Signed-off-by: Claudiu Beznea create mode 100644 arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts diff --git a/arch/arm64/boot/dts/microchip/Makefile b/arch/arm64/boot/dts/microchip/Makefile -index c6e0313eea0f..09d16fc1ce9a 100644 +index c6e0313eea0f9..09d16fc1ce9ac 100644 --- a/arch/arm64/boot/dts/microchip/Makefile +++ b/arch/arm64/boot/dts/microchip/Makefile @@ -1,4 +1,5 @@ @@ -31,7 +30,7 @@ index c6e0313eea0f..09d16fc1ce9a 100644 dtb-$(CONFIG_ARCH_SPARX5) += sparx5_pcb135.dtb sparx5_pcb135_emmc.dtb diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts new file mode 100644 -index 000000000000..4012ea7d07bb +index 0000000000000..4012ea7d07bbd --- /dev/null +++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts @@ -0,0 +1,756 @@ diff --git a/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch b/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch index 1712e368f..25f44ad15 100644 --- a/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch +++ b/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch @@ -1,8 +1,7 @@ From c6af26aff045284fc98aae066d46385e74ab82c8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 15 May 2026 13:59:09 +0200 -Subject: [PATCH 61/80] arm64: dts: microchip: lan969x: add OTP node -Organization: Wires +Subject: [PATCH 61/81] arm64: dts: microchip: lan969x: add OTP node Add the required OTP on LAN969x. @@ -16,7 +15,7 @@ Signed-off-by: Claudiu Beznea 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/microchip/lan9691.dtsi b/arch/arm64/boot/dts/microchip/lan9691.dtsi -index 235e56bebbdb..ed997d87dd09 100644 +index 235e56bebbdbb..ed997d87dd09f 100644 --- a/arch/arm64/boot/dts/microchip/lan9691.dtsi +++ b/arch/arm64/boot/dts/microchip/lan9691.dtsi @@ -100,6 +100,11 @@ usb: usb@300000 { diff --git a/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch b/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch index ec0538498..678310fc8 100644 --- a/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch +++ b/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch @@ -1,8 +1,7 @@ From 7df6f654874cc48a7bea2d8d2d78fe8e9c1c90c7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 62/80] arm64: dts: microchip: lan969x: add SDMMC nodes -Organization: Wires +Subject: [PATCH 62/81] 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 @@ -14,7 +13,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 22 insertions(+) diff --git a/arch/arm64/boot/dts/microchip/lan9691.dtsi b/arch/arm64/boot/dts/microchip/lan9691.dtsi -index ed997d87dd09..59a19462a5db 100644 +index ed997d87dd09f..59a19462a5db5 100644 --- a/arch/arm64/boot/dts/microchip/lan9691.dtsi +++ b/arch/arm64/boot/dts/microchip/lan9691.dtsi @@ -396,6 +396,28 @@ clks: clock-controller@e00c00b4 { diff --git a/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch b/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch index 8a5e07efa..189e508c8 100644 --- a/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch +++ b/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch @@ -1,8 +1,7 @@ From b41259b09f91a140bc0350d7f473d9fee3fbe8b3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 63/80] arm64: dts: microchip: ev23x71a: enable eMMC -Organization: Wires +Subject: [PATCH 63/81] 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. @@ -13,7 +12,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 11 insertions(+) diff --git a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts -index 4012ea7d07bb..7f12fc5c321f 100644 +index 4012ea7d07bbd..7f12fc5c321fe 100644 --- a/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts +++ b/arch/arm64/boot/dts/microchip/lan9696-ev23x71a.dts @@ -463,6 +463,17 @@ phy27: phy@27 { diff --git a/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch b/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch index e01db1afb..546cb59f1 100644 --- a/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch +++ b/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch @@ -1,12 +1,11 @@ From 54b38afac5c46299c8a7ebe594b35774d134389b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:42:48 +0200 -Subject: [PATCH 64/80] wifi: brcmfmac: survey the requested interface, not the +Subject: [PATCH 64/81] wifi: brcmfmac: survey the requested interface, not the primary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires brcmf_cfg80211_dump_survey() and its helper brcmf_set_channel() always operated on the primary interface (cfg_to_ndev()), ignoring the netdev @@ -29,7 +28,7 @@ Signed-off-by: Mattias Walström 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index 0081ceb6c782..8b245460262d 100644 +index 0081ceb6c7829..8b245460262d9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -8124,11 +8124,11 @@ brcmf_dump_obss(struct brcmf_if *ifp, struct cca_msrmnt_query req, diff --git a/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch b/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch index 0718a6d1b..e32ad25f6 100644 --- a/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch +++ b/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch @@ -1,12 +1,11 @@ From b2c521013e15b485455469fa4a263de2cf04dd02 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:43:14 +0200 -Subject: [PATCH 65/80] wifi: brcmfmac: honor caller's rtnl lock when stopping +Subject: [PATCH 65/81] wifi: brcmfmac: honor caller's rtnl lock when stopping primary interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires brcmf_del_if() already takes a 'locked' flag that tells it whether the caller holds the rtnl lock, and it passes that flag down to @@ -27,7 +26,7 @@ Signed-off-by: Mattias Walström 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c -index 048b5d500215..b0c79b50dc9a 100644 +index 048b5d500215e..b0c79b50dc9a2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -935,9 +935,11 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx, diff --git a/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch b/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch index b10af056c..b1eb50e5b 100644 --- a/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch +++ b/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch @@ -1,12 +1,11 @@ From 0e082428a5013b6d35567cf122119af2772f909f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:46:25 +0200 -Subject: [PATCH 66/80] wifi: brcmfmac: let cfg_to_ndev() return NULL and +Subject: [PATCH 66/81] wifi: brcmfmac: let cfg_to_ndev() return NULL and harden its callers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires cfg_to_ndev() dereferenced the result of brcmf_get_ifp(cfg->pub, 0) unconditionally, even though brcmf_get_ifp() can return NULL. Make the @@ -48,7 +47,7 @@ Signed-off-by: Mattias Walström 5 files changed, 78 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c -index 67c0c5a92f99..265f3f5d5307 100644 +index 67c0c5a92f998..265f3f5d53079 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/btcoex.c @@ -141,6 +141,9 @@ static void brcmf_btcoex_boost_wifi(struct brcmf_btcoex_info *btci, @@ -72,7 +71,7 @@ index 67c0c5a92f99..265f3f5d5307 100644 case BRCMF_BTCOEX_DISABLED: brcmf_dbg(INFO, "DHCP session starts\n"); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index 8b245460262d..fd8c39254d46 100644 +index 8b245460262d9..fd8c39254d466 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -822,11 +822,18 @@ struct wireless_dev *brcmf_apsta_add_vif(struct wiphy *wiphy, const char *name, @@ -215,7 +214,7 @@ index 8b245460262d..fd8c39254d46 100644 if (err) { bphy_err(drvr, "Country code iovar returned err = %d\n", err); diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h -index 273c80f2d483..ee7e9dc17dce 100644 +index 273c80f2d483a..ee7e9dc17dce0 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -433,7 +433,9 @@ static inline struct brcmf_cfg80211_vif *wdev_to_vif(struct wireless_dev *wdev) @@ -230,7 +229,7 @@ index 273c80f2d483..ee7e9dc17dce 100644 static inline struct brcmf_cfg80211_info *ndev_to_cfg(struct net_device *ndev) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c -index b0c79b50dc9a..28e677660865 100644 +index b0c79b50dc9a2..28e6776608658 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1028,8 +1028,11 @@ static int brcmf_inetaddr_changed(struct notifier_block *nb, @@ -247,7 +246,7 @@ index b0c79b50dc9a..28e677660865 100644 /* retrieve the table from firmware */ ret = brcmf_fil_iovar_data_get(ifp, "arp_hostip", addr_table, diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c -index e1752a513c73..5b550e5a4e77 100644 +index e1752a513c733..5b550e5a4e77e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -2115,8 +2115,12 @@ static int brcmf_p2p_disable_p2p_if(struct brcmf_cfg80211_vif *vif) diff --git a/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch b/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch index 1ce7bbd39..2d9c810bb 100644 --- a/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch +++ b/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch @@ -1,12 +1,11 @@ From 102750f26d8cbf5d58936b4c9c6da801433c4314 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 16:05:17 +0200 -Subject: [PATCH 67/80] wifi: brcmfmac: support deletion and recreation of the +Subject: [PATCH 67/81] wifi: brcmfmac: support deletion and recreation of the primary interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires The Broadcom FullMAC firmware never deletes the primary interface (bsscfgidx 0); it always exists. brcmfmac therefore rejected any attempt @@ -67,7 +66,7 @@ Signed-off-by: Mattias Walström 2 files changed, 174 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index fd8c39254d46..c17930471845 100644 +index fd8c39254d466..c179304718455 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -124,6 +124,9 @@ struct cca_msrmnt_query { @@ -283,7 +282,7 @@ index fd8c39254d46..c17930471845 100644 case NL80211_IFTYPE_ADHOC: case NL80211_IFTYPE_AP_VLAN: diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c -index 5b550e5a4e77..b1b85da83371 100644 +index 5b550e5a4e77e..b1b85da83371f 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -1156,6 +1156,8 @@ static s32 brcmf_p2p_af_searching_channel(struct brcmf_p2p_info *p2p) diff --git a/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch b/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch index cf6925f41..c2c2134f5 100644 --- a/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch +++ b/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch @@ -1,12 +1,11 @@ From b965ae0c551ae3e2df6937933b6bdd1c42753a43 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 10 Sep 2026 08:29:47 +0200 -Subject: [PATCH 68/80] wifi: brcmfmac: report port authorized after offloaded +Subject: [PATCH 68/81] wifi: brcmfmac: report port authorized after offloaded PSK/SAE handshake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Wires When the 4-way handshake is offloaded to firmware (BRCMF_PROFILE_FWSUP_PSK or _SAE), brcmf_is_linkup() only declares the link up once the firmware @@ -59,7 +58,7 @@ Signed-off-by: Mattias Walström 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c -index c17930471845..064daf8c42ec 100644 +index c179304718455..064daf8c42ec9 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -6705,6 +6705,23 @@ brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, diff --git a/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch b/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch index 195e02968..9aef75685 100644 --- a/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch @@ -1,8 +1,7 @@ From f5f7599f8af9db56b09f26d22b20ee75defe1717 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 9 Jan 2026 13:27:00 +0100 -Subject: [PATCH 69/80] dt-bindings: arm: AT91: document Novarq Tactical 1000 -Organization: Wires +Subject: [PATCH 69/81] dt-bindings: arm: AT91: document Novarq Tactical 1000 Novarq Tactical 1000 is a Microchip LAN9696 based 24x1G + 4x10G SFP switch. @@ -13,7 +12,7 @@ Signed-off-by: Robert Marko 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/atmel-at91.yaml b/Documentation/devicetree/bindings/arm/atmel-at91.yaml -index 3a34b7a2e8d4..49666e92c04a 100644 +index 3a34b7a2e8d4e..49666e92c04a2 100644 --- a/Documentation/devicetree/bindings/arm/atmel-at91.yaml +++ b/Documentation/devicetree/bindings/arm/atmel-at91.yaml @@ -241,6 +241,12 @@ properties: diff --git a/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch b/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch index cc3ae5c93..a53fa1b65 100644 --- a/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch @@ -1,8 +1,7 @@ From 8b4b41c449758451c7dbcdd9092b9bd0e0a2b082 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Sep 2025 13:37:49 +0200 -Subject: [PATCH 70/80] arm64: dts: microchip: add Novarq Tactical 1000 -Organization: Wires +Subject: [PATCH 70/81] arm64: dts: microchip: add Novarq Tactical 1000 Novarq Tactical 1000 is a LAN9696 based switch featuring 24x1G and 4x10G SFP ports. @@ -16,7 +15,7 @@ Signed-off-by: Robert Marko create mode 100644 arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts diff --git a/arch/arm64/boot/dts/microchip/Makefile b/arch/arm64/boot/dts/microchip/Makefile -index 09d16fc1ce9a..0ba970d308ec 100644 +index 09d16fc1ce9ac..0ba970d308ec4 100644 --- a/arch/arm64/boot/dts/microchip/Makefile +++ b/arch/arm64/boot/dts/microchip/Makefile @@ -1,5 +1,6 @@ @@ -28,7 +27,7 @@ index 09d16fc1ce9a..0ba970d308ec 100644 dtb-$(CONFIG_ARCH_SPARX5) += sparx5_pcb135.dtb sparx5_pcb135_emmc.dtb diff --git a/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts b/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts new file mode 100644 -index 000000000000..e9fa26e05804 +index 0000000000000..e9fa26e058047 --- /dev/null +++ b/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts @@ -0,0 +1,886 @@ diff --git a/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch b/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch index 34dbebf4a..78aac2d66 100644 --- a/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch +++ b/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch @@ -1,8 +1,7 @@ From f973058544e1b44fd551db6a230659dbce67313e Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Jun 2026 11:23:47 +0200 -Subject: [PATCH 71/80] arm64: dts: microchip: tactical-1000: add port names -Organization: Wires +Subject: [PATCH 71/81] 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. @@ -14,7 +13,7 @@ Signed-off-by: Robert Marko 1 file changed, 29 insertions(+) diff --git a/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts b/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts -index e9fa26e05804..e7e4654af98f 100644 +index e9fa26e058047..e7e4654af98fc 100644 --- a/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts +++ b/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts @@ -626,6 +626,7 @@ port0: port@0 { diff --git a/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch b/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch index 08b045d8b..1ca1b085f 100644 --- a/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch +++ b/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch @@ -1,8 +1,7 @@ From d49813a338d7c9b9d69e38e1fcc1c937c588a071 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 20 Sep 2026 12:22:43 +0200 -Subject: [PATCH 72/80] arm64: dts: microchip: tactical-1000: adapt to 6.18 -Organization: Wires +Subject: [PATCH 72/81] 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 @@ -21,7 +20,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts b/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts -index e7e4654af98f..bd6953fc9d4a 100644 +index e7e4654af98fc..bd6953fc9d4a3 100644 --- a/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts +++ b/arch/arm64/boot/dts/microchip/lan9696-tactical-1000.dts @@ -5,7 +5,6 @@ diff --git a/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch b/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch index a224d0801..b799219b1 100644 --- a/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch +++ b/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch @@ -1,8 +1,7 @@ From b5afb74688659804bbed4bca3928c98975daf2c8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:13 +0100 -Subject: [PATCH 73/80] dt-bindings: arm: AT91: document EV23X71A board -Organization: Wires +Subject: [PATCH 73/81] dt-bindings: arm: AT91: document EV23X71A board Microchip EV23X71A board is an LAN9696 based evaluation board. @@ -17,7 +16,7 @@ Signed-off-by: Claudiu Beznea 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/atmel-at91.yaml b/Documentation/devicetree/bindings/arm/atmel-at91.yaml -index 49666e92c04a..b7c37a1cece0 100644 +index 49666e92c04a2..b7c37a1cece03 100644 --- a/Documentation/devicetree/bindings/arm/atmel-at91.yaml +++ b/Documentation/devicetree/bindings/arm/atmel-at91.yaml @@ -241,6 +241,12 @@ properties: diff --git a/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch b/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch index 0fb626dad..7db3cbd31 100644 --- a/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch +++ b/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch @@ -1,9 +1,8 @@ From 45fd43a305c595ea24b0de5cf090affeffc4029f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:21 +0200 -Subject: [PATCH 74/80] net: dsa: Skip DCB default priority init on unsupported +Subject: [PATCH 74/81] net: dsa: Skip DCB default priority init on unsupported switches -Organization: Wires A driver serving several chip generations has one dsa_switch_ops for all of them, so port_get_default_prio is set even for chips that have @@ -18,7 +17,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/net/dsa/user.c b/net/dsa/user.c -index c0019d0c1172..b881c177628c 100644 +index c0019d0c1172d..b881c177628ce 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2511,12 +2511,13 @@ static int dsa_user_dcbnl_init(struct net_device *dev) diff --git a/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch b/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch index 27456e792..e73d3ee9d 100644 --- a/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch +++ b/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch @@ -1,9 +1,8 @@ From cb31150945889f02373febe89f618afee36c6e62 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:51:07 +0200 -Subject: [PATCH 75/80] net: dsa: Generalise the global DCB APP mirroring +Subject: [PATCH 75/81] net: dsa: Generalise the global DCB APP mirroring helper -Organization: Wires A switch with one classification table for all its ports programs it once, and the core keeps every other port's APP table in step so each @@ -20,7 +19,7 @@ Signed-off-by: Joachim Wiberg 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/net/dsa/user.c b/net/dsa/user.c -index b881c177628c..193e54b7d85e 100644 +index b881c177628ce..193e54b7d85ea 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2243,11 +2243,13 @@ dsa_user_dcbnl_set_default_prio(struct net_device *dev, struct dcb_app *app) diff --git a/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch b/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch index 5b1e8668d..3181a574b 100644 --- a/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch +++ b/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch @@ -1,8 +1,7 @@ From e1a8005d2fc1813b71e65c79e700da5635e639e0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:22 +0200 -Subject: [PATCH 76/80] net: dsa: Support the PCP APP selector -Organization: Wires +Subject: [PATCH 76/81] 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 @@ -28,7 +27,7 @@ Signed-off-by: Joachim Wiberg 2 files changed, 206 insertions(+) diff --git a/include/net/dsa.h b/include/net/dsa.h -index c31d4e910f07..f271376b7758 100644 +index c31d4e910f075..f271376b77582 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -447,6 +447,10 @@ struct dsa_switch { @@ -56,7 +55,7 @@ index c31d4e910f07..f271376b7758 100644 /* * Suspend and resume diff --git a/net/dsa/user.c b/net/dsa/user.c -index 193e54b7d85e..85b06f3a4337 100644 +index 193e54b7d85ea..85b06f3a43370 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2342,6 +2342,172 @@ dsa_user_dcbnl_add_dscp_prio(struct net_device *dev, struct dcb_app *app) diff --git a/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch b/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch index 6ab583997..8cda5ba84 100644 --- a/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch +++ b/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch @@ -1,8 +1,7 @@ From fb3ca7803ea876f7d96514696615143835bc78f9 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:10:32 +0200 -Subject: [PATCH 77/80] net: dsa: Support DCB priority rewrite -Organization: Wires +Subject: [PATCH 77/81] 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 @@ -27,7 +26,7 @@ Signed-off-by: Joachim Wiberg 2 files changed, 279 insertions(+), 1 deletion(-) diff --git a/include/net/dsa.h b/include/net/dsa.h -index f271376b7758..3a36904e57f8 100644 +index f271376b77582..3a36904e57f81 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -451,6 +451,12 @@ struct dsa_switch { @@ -61,7 +60,7 @@ index f271376b7758..3a36904e57f8 100644 /* * Suspend and resume diff --git a/net/dsa/user.c b/net/dsa/user.c -index 85b06f3a4337..349bacfebb08 100644 +index 85b06f3a43370..349bacfebb086 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -2665,6 +2665,266 @@ static int __maybe_unused dsa_user_dcbnl_ieee_delapp(struct net_device *dev, diff --git a/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch b/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch index bc9ebd384..31c154004 100644 --- a/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch +++ b/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch @@ -1,8 +1,7 @@ From 46e44f7a4aba62dc94958e01638e8e93ea62e59b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:53:23 +0200 -Subject: [PATCH 78/80] net: dsa: Support the IEEE ETS managed object -Organization: Wires +Subject: [PATCH 78/81] 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 @@ -33,7 +32,7 @@ Signed-off-by: Joachim Wiberg 2 files changed, 82 insertions(+) diff --git a/include/net/dsa.h b/include/net/dsa.h -index 3a36904e57f8..fa47901fa9a9 100644 +index 3a36904e57f81..fa47901fa9a98 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -849,6 +849,8 @@ static inline bool dsa_port_tree_same(const struct dsa_port *a, @@ -65,7 +64,7 @@ index 3a36904e57f8..fa47901fa9a9 100644 * Suspend and resume */ diff --git a/net/dsa/user.c b/net/dsa/user.c -index 349bacfebb08..e9809fda8d89 100644 +index 349bacfebb086..e9809fda8d89e 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -3040,9 +3040,77 @@ static const struct ethtool_ops dsa_user_ethtool_ops = { diff --git a/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch b/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch index 6ba9afb2b..e586b4fac 100644 --- a/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch +++ b/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch @@ -1,9 +1,8 @@ From 308c9dbab764bef06cceb03e412b491ea57234b5 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 7 Oct 2026 11:42:12 +0200 -Subject: [PATCH 79/80] net: sparx5: fix sleep in atomic context in MAC table +Subject: [PATCH 79/81] net: sparx5: fix sleep in atomic context in MAC table access -Organization: Wires Adding or removing a multicast address on an unbridged switch port triggers: @@ -49,7 +48,7 @@ Signed-off-by: Joachim Wiberg 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c b/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c -index 2bf9c5f64151..30acb2127797 100644 +index 2bf9c5f64151c..30acb21277974 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_mactable.c @@ -50,7 +50,7 @@ static int sparx5_mact_wait_for_completion(struct sparx5 *sparx5) @@ -161,7 +160,7 @@ index 2bf9c5f64151..30acb2127797 100644 /* Flush MAC table */ spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_CLEAR_ALL) | diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h -index f7d86fc6aa12..317c4ca0d29b 100644 +index f7d86fc6aa125..317c4ca0d29b2 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h @@ -373,7 +373,7 @@ struct sparx5 { diff --git a/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch b/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch index 503f7d1a7..2545180ad 100644 --- a/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch +++ b/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch @@ -1,8 +1,7 @@ From c6f8b6a050e69c951ce0ce56467b6e9fd9a638f1 Mon Sep 17 00:00:00 2001 From: Steen Hegelund Date: Wed, 21 Jan 2026 10:09:45 +0100 -Subject: [PATCH 80/80] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) -Organization: Wires +Subject: [PATCH 80/81] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) Expose the switch register space of Microchip Sparx5 family SoCs through /sys/kernel/debug/symreg/mem, for the symreg tool, which @@ -23,7 +22,7 @@ Signed-off-by: Joachim Wiberg diff --git a/Documentation/devicetree/bindings/arm/microchip,sparx5-symreg.yaml b/Documentation/devicetree/bindings/arm/microchip,sparx5-symreg.yaml new file mode 100644 -index 000000000000..fe6f9e7fcc67 +index 0000000000000..fe6f9e7fcc67c --- /dev/null +++ b/Documentation/devicetree/bindings/arm/microchip,sparx5-symreg.yaml @@ -0,0 +1,62 @@ @@ -90,7 +89,7 @@ index 000000000000..fe6f9e7fcc67 + status = "okay"; + }; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index b9c11f67315f..8e164b0fba97 100644 +index b9c11f67315f0..8e164b0fba975 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -644,6 +644,12 @@ config MCHP_LAN966X_PCI @@ -107,7 +106,7 @@ index b9c11f67315f..8e164b0fba97 100644 source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index b32a2597d246..9286bd91a995 100644 +index b32a2597d2467..9286bd91a9954 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -75,3 +75,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o @@ -117,7 +116,7 @@ index b32a2597d246..9286bd91a995 100644 +obj-$(CONFIG_SPARX5_SYMREG) += sparx5_symreg_debugfs.o diff --git a/drivers/misc/sparx5_symreg_debugfs.c b/drivers/misc/sparx5_symreg_debugfs.c new file mode 100644 -index 000000000000..17f7bcc6fd68 +index 0000000000000..17f7bcc6fd682 --- /dev/null +++ b/drivers/misc/sparx5_symreg_debugfs.c @@ -0,0 +1,267 @@ diff --git a/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch b/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch new file mode 100644 index 000000000..cfb3edfe3 --- /dev/null +++ b/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch @@ -0,0 +1,45 @@ +From e0e6e024e5e5866ff61b68bf9790fd24e542d7e2 Mon Sep 17 00:00:00 2001 +From: Mattias Walström +Date: Mon, 5 Oct 2026 14:37:56 +0200 +Subject: [PATCH 81/81] 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 0d38183c6ba70..be469c753c71b 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; + } From a91a7f34a12b0ea962c9094b22de9050463afc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:14 +0200 Subject: [PATCH 33/45] patches: linux: Fix WED attach panic on non-DBDC MT7986 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Walström --- ...0g-Support-firmware-loading-on-88X33.patch | 2 +- ...0g-Fix-power-up-when-strapped-to-sta.patch | 2 +- ...rvell10g-Add-LED-support-for-88X3310.patch | 2 +- ...0g-Support-LEDs-tied-to-a-single-med.patch | 2 +- ...phy-Do-not-resume-PHY-when-attaching.patch | 2 +- ...-classifying-unknown-multicast-as-mr.patch | 2 +- ...e-router-ports-when-forwarding-L2-mu.patch | 2 +- ...delay-for-applying-strict-multicast-.patch | 2 +- ...rentiate-MDB-additions-from-modifica.patch | 2 +- ...ie-tlv-Let-device-probe-even-when-TL.patch | 2 +- ...d-r8153b-support-for-link-activity-L.patch | 2 +- ...ek-mt7986a-rename-BPi-R3-ports-to-ma.patch | 2 +- ...-Add-a-timing-for-the-Raspberry-Pi-7.patch | 2 +- ...uchscreen-edt-ft5x06-Add-polled-mode.patch | 2 +- ...e6xxx-Fix-timeout-on-waiting-for-PPU.patch | 2 +- ...x-Improve-indirect-register-access-p.patch | 2 +- ...x-Honor-ports-being-managed-via-in-b.patch | 2 +- ...x-Limit-rsvd2cpu-policy-to-user-port.patch | 2 +- ...Use-tag-priority-as-initial-skb-prio.patch | 2 +- ...MDB-memberships-whose-L2-addresses-o.patch | 2 +- ...t-EtherType-based-priority-overrides.patch | 2 +- ...x-Support-EtherType-based-priority-o.patch | 2 +- ...a-mv88e6xxx-Add-mqprio-qdisc-support.patch | 2 +- ...x-Use-VLAN-prio-over-IP-when-both-ar.patch | 2 +- ...8e6xxx-Trap-locally-terminated-VLANs.patch | 2 +- ...x-collapse-disabled-state-into-block.patch | 2 +- ...x-Only-activate-LAG-offloading-when-.patch | 2 +- ...-mv88e6xxx-Add-LED-support-for-6393X.patch | 2 +- ...eck-connection-state-before-querying.patch | 2 +- ...ppress-log-spam-for-regulatory-restr.patch | 2 +- ...duce-log-noise-during-AP-to-station-.patch | 2 +- ...11h-add-OF-device-table-for-auto-loa.patch | 2 +- ...le-video-and-then-retry-failed-trans.patch | 2 +- ...locks-should-be-running-before-reset.patch | 2 +- ...nsure-DSI-is-enabled-for-FIFO-resets.patch | 2 +- ...036-drm-vc4-Reset-DSI-AFE-on-disable.patch | 2 +- ...le-the-different-command-FIFO-widths.patch | 2 +- ...762-Program-the-DPI-mode-into-the-ch.patch | 2 +- ...e-tc358762-revert-move-ops-to-enable.patch | 2 +- ...762-Set-pre_enabled-on-pre_enable-to.patch | 2 +- ...dalone-PCS-registration-infrastructu.patch | 2 +- ...d-MediaTek-MT7988-USXGMII-PCS-driver.patch | 2 +- ...iatek-add-USXGMII-support-for-MT7988.patch | 2 +- ...diatek-mt7988a-add-USXGMII-PCS-nodes.patch | 2 +- ...ek-bananapi-bpi-r4-enable-SFP-ports-.patch | 2 +- ...et-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch | 2 +- ...x-Trap-PTP-frames-on-timestamping-po.patch | 2 +- ...-add-MODULE_DEVICE_TABLE-for-mt7622-.patch | 2 +- ...3-Fix-PERST-control-timing-during-sy.patch | 2 +- ...xx-Derive-LED-names-from-device-name.patch | 2 +- ...s-make-it-selectable-for-ARCH_LAN969.patch | 2 +- ...arx5-fix-wrong-chip-ids-for-TSN-SKUs.patch | 2 +- ...gure-serdes-for-1000BASE-X-in-sparx5.patch | 2 +- ...atmel-sama5d2-sdhci-add-microchip-la.patch | 2 +- ...mc-sdhci-of-at91-add-LAN969x-support.patch | 2 +- ...1-stop-SDCLK-on-reset-and-add-eMMC-h.patch | 2 +- ...arx5-lan969x-populate-netdev-of_node.patch | 2 +- ...rochip-add-LAN969x-clock-header-file.patch | 2 +- ...64-dts-microchip-add-LAN969x-support.patch | 2 +- ...m64-dts-microchip-add-EV23X71A-board.patch | 2 +- ...4-dts-microchip-lan969x-add-OTP-node.patch | 2 +- ...ts-microchip-lan969x-add-SDMMC-nodes.patch | 2 +- ...4-dts-microchip-ev23x71a-enable-eMMC.patch | 2 +- ...rvey-the-requested-interface-not-the.patch | 2 +- ...nor-caller-s-rtnl-lock-when-stopping.patch | 2 +- ...t-cfg_to_ndev-return-NULL-and-harden.patch | 2 +- ...pport-deletion-and-recreation-of-the.patch | 2 +- ...port-port-authorized-after-offloaded.patch | 2 +- ...m-AT91-document-Novarq-Tactical-1000.patch | 2 +- ...s-microchip-add-Novarq-Tactical-1000.patch | 2 +- ...crochip-tactical-1000-add-port-names.patch | 2 +- ...icrochip-tactical-1000-adapt-to-6.18.patch | 2 +- ...ngs-arm-AT91-document-EV23X71A-board.patch | 2 +- ...-default-priority-init-on-unsupporte.patch | 2 +- ...se-the-global-DCB-APP-mirroring-help.patch | 2 +- ...net-dsa-Support-the-PCP-APP-selector.patch | 2 +- ...net-dsa-Support-DCB-priority-rewrite.patch | 2 +- ...-Support-the-IEEE-ETS-managed-object.patch | 2 +- ...leep-in-atomic-context-in-MAC-table-.patch | 2 +- ...ymreg-symreg-debugfs-driver-DBB-1045.patch | 2 +- ..._wed-map-WO-memory-regions-without-r.patch | 2 +- ...-fix-kernel-panic-on-non-DBDC-MT7986.patch | 111 ++++++++++++++++++ 82 files changed, 192 insertions(+), 81 deletions(-) create mode 100644 patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch diff --git a/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch b/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch index 448e534f1..2017a443d 100644 --- a/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch +++ b/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch @@ -1,7 +1,7 @@ From ca34ebe2b40b53b52a5ea6fb00b7bb36eeeabb5f Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 19 Sep 2023 18:38:10 +0200 -Subject: [PATCH 01/81] net: phy: marvell10g: Support firmware loading on +Subject: [PATCH 01/82] 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.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch b/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch index 717c09f94..989d015b7 100644 --- a/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch +++ b/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch @@ -1,7 +1,7 @@ From 59532ff26cccf476b4e6f8eeaf2e733c93106a78 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 21 Nov 2023 20:15:24 +0100 -Subject: [PATCH 02/81] net: phy: marvell10g: Fix power-up when strapped to +Subject: [PATCH 02/82] 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.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch b/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch index e9c309892..bc4decb01 100644 --- a/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch +++ b/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch @@ -1,7 +1,7 @@ From 2881f5a7008a4b7a214f63960f1d199f55a7a78a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 15 Nov 2023 20:58:42 +0100 -Subject: [PATCH 03/81] net: phy: marvell10g: Add LED support for 88X3310 +Subject: [PATCH 03/82] 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.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch b/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch index a9eb8a755..09b8f6d57 100644 --- a/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch +++ b/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch @@ -1,7 +1,7 @@ From 67a62752bdb5e1e45982289b2eda1f5df4ca6f47 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Dec 2023 09:51:05 +0100 -Subject: [PATCH 04/81] net: phy: marvell10g: Support LEDs tied to a single +Subject: [PATCH 04/82] 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.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch b/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch index 91bc3344a..fab2ecce8 100644 --- a/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch +++ b/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch @@ -1,7 +1,7 @@ From a60255d4ba8ea888ecb86e92a2c65334a26f6384 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 10:10:19 +0100 -Subject: [PATCH 05/81] net: phy: Do not resume PHY when attaching +Subject: [PATCH 05/82] 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.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch b/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch index 0a2243138..c9b37aee9 100644 --- a/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch +++ b/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch @@ -1,7 +1,7 @@ From 3fc8e2a80589a5a5ae02fac7b3f97f195cb90930 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 4 Mar 2024 16:47:28 +0100 -Subject: [PATCH 06/81] net: bridge: avoid classifying unknown multicast as +Subject: [PATCH 06/82] 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.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch b/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch index 668c5da85..2eadbd7fb 100644 --- a/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch +++ b/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch @@ -1,7 +1,7 @@ From 8f5cf3a448d6e7a1d00043debd0bf1fcaf706680 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 06:44:41 +0100 -Subject: [PATCH 07/81] net: bridge: Ignore router ports when forwarding L2 +Subject: [PATCH 07/82] 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.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch b/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch index 5357abce7..049d87c8a 100644 --- a/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch +++ b/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch @@ -1,7 +1,7 @@ From a42722822b7ff8ea8ac5e65a8449bb72b1ef8c05 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 4 Apr 2024 16:36:30 +0200 -Subject: [PATCH 08/81] net: bridge: drop delay for applying strict multicast +Subject: [PATCH 08/82] 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.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch b/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch index 612760b34..ab448ab63 100644 --- a/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch +++ b/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch @@ -1,7 +1,7 @@ From 2722df35856114c70f9896452d155b660f804ed8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 May 2024 14:51:54 +0200 -Subject: [PATCH 09/81] net: bridge: Differentiate MDB additions from +Subject: [PATCH 09/82] 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.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch b/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch index 3e93a454b..230b1227d 100644 --- a/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch +++ b/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch @@ -1,7 +1,7 @@ From 17f03931f23a3ed1dbe6f77a7e48bfdc11f17140 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 24 Nov 2023 23:29:55 +0100 -Subject: [PATCH 10/81] nvmem: layouts: onie-tlv: Let device probe even when +Subject: [PATCH 10/82] 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.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch b/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch index e2d588a30..772262c38 100644 --- a/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch +++ b/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch @@ -1,7 +1,7 @@ From cbc993b90307a45db570d20bce5e651100bafaa7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 11 Aug 2024 11:27:35 +0200 -Subject: [PATCH 11/81] net: usb: r8152: add r8153b support for link/activity +Subject: [PATCH 11/82] 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.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch b/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch index d6123223e..e1c8a26bc 100644 --- a/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch +++ b/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch @@ -1,7 +1,7 @@ From 0c1a54359f6989eedad98678e030fc1d8fb44da0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 10 Aug 2025 18:52:54 +0200 -Subject: [PATCH 12/81] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to +Subject: [PATCH 12/82] 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.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch b/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch index 8aa5a854d..1c2242121 100644 --- a/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch +++ b/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch @@ -1,7 +1,7 @@ From 2cbb47d51538a83315595715405a328552b9a753 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 20 Aug 2025 21:38:24 +0200 -Subject: [PATCH 13/81] drm/panel-simple: Add a timing for the Raspberry Pi 7" +Subject: [PATCH 13/82] 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.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch b/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch index ba7ab2d41..32946370b 100644 --- a/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch +++ b/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch @@ -1,7 +1,7 @@ From fed97704c224902b2d79c81492c5a50ccd97793b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 21 Aug 2025 11:20:23 +0200 -Subject: [PATCH 14/81] input:touchscreen:edt-ft5x06: Add polled mode +Subject: [PATCH 14/82] 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.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch b/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch index 8b00ec54a..22507ce84 100644 --- a/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch +++ b/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch @@ -1,7 +1,7 @@ From 11f9f49b62a6eb3a73a523541b3f26f6958e0d49 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Mar 2024 10:27:24 +0100 -Subject: [PATCH 15/81] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for +Subject: [PATCH 15/82] [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.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch b/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch index f640b7fa6..15981bcaf 100644 --- a/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch +++ b/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch @@ -1,7 +1,7 @@ From 6aed16ecedd6e8d3594474b00b4f80a6a75372c1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 15:52:43 +0100 -Subject: [PATCH 16/81] net: dsa: mv88e6xxx: Improve indirect register access +Subject: [PATCH 16/82] 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.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch b/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch index d5d860a99..8a105694a 100644 --- a/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch +++ b/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch @@ -1,7 +1,7 @@ From 627e2902f0b11f9994e5c8c92627791b60069cdb Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 22 Apr 2024 23:18:01 +0200 -Subject: [PATCH 17/81] net: dsa: mv88e6xxx: Honor ports being managed via +Subject: [PATCH 17/82] 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.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch b/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch index d3d3efdaa..492d5eb8c 100644 --- a/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch +++ b/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch @@ -1,7 +1,7 @@ From 1ad63ed23d72ed2429bcb7393aac0e82affde9e3 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 24 Apr 2024 22:41:04 +0200 -Subject: [PATCH 18/81] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user +Subject: [PATCH 18/82] 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.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch b/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch index 453be1f3e..6f2e82239 100644 --- a/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch +++ b/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch @@ -1,7 +1,7 @@ From 29b6583d7463dfddbfbb0e30a70de7707f5c86b0 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 10:38:42 +0200 -Subject: [PATCH 19/81] net: dsa: tag_dsa: Use tag priority as initial +Subject: [PATCH 19/82] 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.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch b/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch index 732e35d14..05781141a 100644 --- a/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch +++ b/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch @@ -1,7 +1,7 @@ From 6cebfb46fd0a75b04f2481dcbd43cb9442bb1e12 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 16 Jan 2024 16:00:55 +0100 -Subject: [PATCH 20/81] net: dsa: Support MDB memberships whose L2 addresses +Subject: [PATCH 20/82] 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.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch b/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch index 9cde3a5d3..9a6ff1291 100644 --- a/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch +++ b/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch @@ -1,7 +1,7 @@ From 2b39a3958e00546ff1c6f0cc523970d19355d796 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 21 Mar 2024 19:12:15 +0100 -Subject: [PATCH 21/81] net: dsa: Support EtherType based priority overrides +Subject: [PATCH 21/82] net: dsa: Support EtherType based priority overrides --- include/net/dsa.h | 4 ++++ diff --git a/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch b/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch index ab7639986..5f80da7dd 100644 --- a/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch +++ b/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch @@ -1,7 +1,7 @@ From 3769055203bee35077c685d850f0debbac52459e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 22 Mar 2024 16:15:43 +0100 -Subject: [PATCH 22/81] net: dsa: mv88e6xxx: Support EtherType based priority +Subject: [PATCH 22/82] net: dsa: mv88e6xxx: Support EtherType based priority overrides --- diff --git a/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch b/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch index 53fd825e1..c0d36b025 100644 --- a/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch +++ b/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch @@ -1,7 +1,7 @@ From bf8849ce4ee5e95f33c435914a5d7c8e6e16e416 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 11:04:22 +0200 -Subject: [PATCH 23/81] net: dsa: mv88e6xxx: Add mqprio qdisc support +Subject: [PATCH 23/82] 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.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch b/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch index 11f537e99..a8e6ec129 100644 --- a/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch +++ b/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch @@ -1,7 +1,7 @@ From 73b39094d7cdd592ec6fb18164590678789ab37a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 29 May 2024 13:20:41 +0200 -Subject: [PATCH 24/81] net: dsa: mv88e6xxx: Use VLAN prio over IP when both +Subject: [PATCH 24/82] 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.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch b/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch index 15b2afe91..52fbdc5af 100644 --- a/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch +++ b/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch @@ -1,7 +1,7 @@ From 320983cf57f27f4138a4736388ad384ea9754d37 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 26 Nov 2024 19:45:59 +0100 -Subject: [PATCH 25/81] [FIX] net: dsa: mv88e6xxx: Trap locally terminated +Subject: [PATCH 25/82] [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.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch b/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch index e9cb0ecc3..aad5ce03a 100644 --- a/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch +++ b/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch @@ -1,7 +1,7 @@ From 95d919b20d95e7d33432d450e2caafa42105d645 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 12:35:12 +0100 -Subject: [PATCH 26/81] net: dsa: mv88e6xxx: collapse disabled state into +Subject: [PATCH 26/82] 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.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch b/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch index 83e0fcdac..5899acb33 100644 --- a/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch +++ b/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch @@ -1,7 +1,7 @@ From 4a67cefaa3e4d69034afee21162133191863e841 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 12 Feb 2025 22:03:14 +0100 -Subject: [PATCH 27/81] net: dsa: mv88e6xxx: Only activate LAG offloading when +Subject: [PATCH 27/82] 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.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch b/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch index c6748afec..f192fd9d8 100644 --- a/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch +++ b/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch @@ -1,7 +1,7 @@ From de1f05b6ee9af9d4f7deb7cde43e28d81e1b7f7f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 14 Jan 2026 18:22:41 +0100 -Subject: [PATCH 28/81] net: dsa: mv88e6xxx: Add LED support for 6393X +Subject: [PATCH 28/82] net: dsa: mv88e6xxx: Add LED support for 6393X Original commit: commit 462277b926140ee2d231317e92afb6cabf640268 diff --git a/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch b/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch index 3744b78cc..3ca518d33 100644 --- a/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch +++ b/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch @@ -1,7 +1,7 @@ From 5ba4ebc2cbcc3efdb8c68e64de2e3e1d36f700ca Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 19 Jan 2026 13:06:53 +0100 -Subject: [PATCH 29/81] wifi: brcmfmac: check connection state before querying +Subject: [PATCH 29/82] 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.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch b/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch index 97e86baf1..d7fbb6b0d 100644 --- a/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch +++ b/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch @@ -1,7 +1,7 @@ From e714d2bb8736f8befb0e223b4de054981125010e Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:12:10 +0100 -Subject: [PATCH 30/81] wifi: brcmfmac: suppress log spam for +Subject: [PATCH 30/82] 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.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch b/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch index ec0a97518..a32d2ef25 100644 --- a/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch +++ b/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch @@ -1,7 +1,7 @@ From 09696680be45dbeb24e8a722c3d4030b7737e11a Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:18:45 +0100 -Subject: [PATCH 31/81] wifi: brcmfmac: reduce log noise during AP to station +Subject: [PATCH 31/82] 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.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch b/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch index 501d6453f..9be7be1eb 100644 --- a/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch +++ b/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch @@ -1,7 +1,7 @@ From 28322c174df66701c2f4283bf338a3032a8121bc Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 17 Feb 2026 21:59:59 +0100 -Subject: [PATCH 32/81] net: phy: air_en8811h: add OF device table for +Subject: [PATCH 32/82] 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.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch b/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch index 7d974a0f2..db68cd8dc 100644 --- a/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch +++ b/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch @@ -1,7 +1,7 @@ From 53aba25643ef8e41021eab9b61242f3151fa3a56 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 20 Sep 2024 12:05:18 +0100 -Subject: [PATCH 33/81] drm: vc4: dsi: enable video and then retry failed +Subject: [PATCH 33/82] 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.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch b/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch index c0d162eac..40bf46547 100644 --- a/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch +++ b/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch @@ -1,7 +1,7 @@ From b145fb6117a42c835d69cee8d01b7679396541e6 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 8 Jun 2022 17:23:47 +0100 -Subject: [PATCH 34/81] drm: vc4: dsi: Clocks should be running before reset +Subject: [PATCH 34/82] 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.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch b/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch index 072673f11..de6d0ee37 100644 --- a/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch +++ b/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch @@ -1,7 +1,7 @@ From 394a21d586f7f13a5cb822ed95ef986dfb17ca87 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 5 Apr 2024 17:51:55 +0100 -Subject: [PATCH 35/81] drm/vc4: Ensure DSI is enabled for FIFO resets +Subject: [PATCH 35/82] 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.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch b/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch index 20ff1a89d..4529117f6 100644 --- a/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch +++ b/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch @@ -1,7 +1,7 @@ From 5b807e23d91f3d90697f74d484276e574670aa2c Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 26 May 2022 18:56:19 +0100 -Subject: [PATCH 36/81] drm: vc4: Reset DSI AFE on disable +Subject: [PATCH 36/82] 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.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch b/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch index 1b560872c..0360e64ae 100644 --- a/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch +++ b/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch @@ -1,7 +1,7 @@ From 1610998f5338234b29c335d18ffeb00c2dcec73a Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 20 Nov 2024 13:58:08 +0000 -Subject: [PATCH 37/81] drm: vc4: dsi: Handle the different command FIFO widths +Subject: [PATCH 37/82] 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.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch b/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch index 6f698596c..8a34df8a8 100644 --- a/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch +++ b/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch @@ -1,7 +1,7 @@ From 0c2f36c6efa65abc8d2c5179903c3fb5afac9874 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 17:37:00 +0000 -Subject: [PATCH 38/81] drm/bridge: tc358762: Program the DPI mode into the +Subject: [PATCH 38/82] 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.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch b/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch index 0cde615a9..ecfdb8852 100644 --- a/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch +++ b/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch @@ -1,7 +1,7 @@ From 91f9e48ff0405e6b8ec9d046b7f37d96f99483c5 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 18:44:49 +0000 -Subject: [PATCH 39/81] drm/bridge: tc358762: revert move ops to enable +Subject: [PATCH 39/82] 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.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch b/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch index c62e74b9c..72a5851f5 100644 --- a/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch +++ b/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch @@ -1,7 +1,7 @@ From 18d15d554c004a795771c8adb8abb6ef54f22985 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Sat, 4 Apr 2026 18:04:19 +0200 -Subject: [PATCH 40/81] drm/bridge: tc358762: Set pre_enabled on pre_enable to +Subject: [PATCH 40/82] 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.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch b/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch index 96a06608b..291e30d92 100644 --- a/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch +++ b/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch @@ -1,7 +1,7 @@ From a2fc20fe20ad386cff232905d1858f9af21ecaf2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 5 Apr 2026 11:33:00 +0200 -Subject: [PATCH 41/81] net/pcs: add standalone PCS registration infrastructure +Subject: [PATCH 41/82] 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.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch b/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch index 29b3e6400..799dc5320 100644 --- a/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch +++ b/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch @@ -1,7 +1,7 @@ From 6c0e1262a95027d0ef8fb263353e5a4382529437 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:14:23 +0200 -Subject: [PATCH 42/81] net/pcs: add MediaTek MT7988 USXGMII PCS driver +Subject: [PATCH 42/82] 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.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch b/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch index b32b5dd1e..48e21a65f 100644 --- a/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch +++ b/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch @@ -1,7 +1,7 @@ From 7a3a934b66b5a38e732f6c28f76c45b43cea4e15 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:43 +0200 -Subject: [PATCH 43/81] net: ethernet: mediatek: add USXGMII support for MT7988 +Subject: [PATCH 43/82] 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.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch b/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch index 785a4128b..2a2155827 100644 --- a/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch +++ b/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch @@ -1,7 +1,7 @@ From d320f311c3e132191b319db4d7f7e3e3f5ed58df Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:56 +0200 -Subject: [PATCH 44/81] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes +Subject: [PATCH 44/82] 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.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch b/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch index dbc73bf94..c0172c326 100644 --- a/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch +++ b/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch @@ -1,7 +1,7 @@ From d83185ab2d0bc45de5c33b6fc50006e166f39417 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:16:11 +0200 -Subject: [PATCH 45/81] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ +Subject: [PATCH 45/82] 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.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch b/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch index 5030e7d35..7c441dcc4 100644 --- a/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch +++ b/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch @@ -1,7 +1,7 @@ From fa401f49e78eeefa6ef1752733c87b1d67655125 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 Apr 2026 07:34:52 +0200 -Subject: [PATCH 46/81] net: phy: sfp: add OEM SFP-10G-T-I quirk +Subject: [PATCH 46/82] 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.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch b/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch index 51d9f1c47..ad6a2ee8a 100644 --- a/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch +++ b/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch @@ -1,7 +1,7 @@ From 79c898ca16e60f10eafaf89110b5d2c24bd9ac2c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 17 Apr 2026 09:13:04 +0000 -Subject: [PATCH 47/81] net: dsa: mv88e6xxx: Trap PTP frames on timestamping +Subject: [PATCH 47/82] 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.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch b/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch index e9e9f3551..20b60c468 100644 --- a/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch +++ b/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch @@ -1,7 +1,7 @@ From 0f8cdee2b15e4d5c36520e274ebc74371ec8de68 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Apr 2026 15:30:01 +0200 -Subject: [PATCH 48/81] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 +Subject: [PATCH 48/82] 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.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch b/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch index 7fa9e5406..852c2e98f 100644 --- a/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch +++ b/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch @@ -1,7 +1,7 @@ From 5f9aa8845b841fcada5fa58f92ceaa9bb9fabcb4 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 22 Apr 2026 10:24:43 +0200 -Subject: [PATCH 49/81] PCI: mediatek-gen3: Fix PERST# control timing during +Subject: [PATCH 49/82] 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.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch b/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch index 59d9af05a..d0b6d1933 100644 --- a/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch +++ b/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch @@ -1,7 +1,7 @@ From 024fb8fd29a48bb85f355b5a4a558c8ab6875b3c Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 12 Aug 2026 10:08:53 +0200 -Subject: [PATCH 50/81] net: dsa: mv88e6xxx: Derive LED names from device name +Subject: [PATCH 50/82] 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.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch b/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch index e353d58f4..13fec3ac1 100644 --- a/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch +++ b/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch @@ -1,7 +1,7 @@ From f42698c6d686cabca0fced226a63e09c438fbc78 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 31 Oct 2025 13:18:12 +0100 -Subject: [PATCH 51/81] phy: sparx5-serdes: make it selectable for ARCH_LAN969X +Subject: [PATCH 51/82] 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.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch b/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch index 93351d9ec..550be861a 100644 --- a/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch +++ b/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch @@ -1,7 +1,7 @@ From da55a0660bc7a764abd39b28d1b588d58cfafcf7 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:38 +0200 -Subject: [PATCH 52/81] net: sparx5: fix wrong chip ids for TSN SKUs +Subject: [PATCH 52/82] 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.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch b/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch index 7236de0a3..662582d83 100644 --- a/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch +++ b/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch @@ -1,7 +1,7 @@ From 13292f9168073d35dfa05f2c3e8402e18bf3f9ce Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:39 +0200 -Subject: [PATCH 53/81] net: sparx5: configure serdes for 1000BASE-X in +Subject: [PATCH 53/82] 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.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch b/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch index dfe3cec22..ed019fbc3 100644 --- a/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch +++ b/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch @@ -1,7 +1,7 @@ From 704c64713d2d2d34a22e0d2f8066996bc62d9120 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:29 +0200 -Subject: [PATCH 54/81] dt-bindings: mmc: atmel,sama5d2-sdhci: add +Subject: [PATCH 54/82] 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.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch b/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch index 8fbbec365..f2415b49e 100644 --- a/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch +++ b/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From ae98994e45aaa55ecff09321aa65601561aefab4 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:44 +0200 -Subject: [PATCH 55/81] mmc: sdhci-of-at91: add LAN969x support +Subject: [PATCH 55/82] 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.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch b/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch index 5c7cec95e..1e8fd681d 100644 --- a/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch +++ b/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch @@ -1,7 +1,7 @@ From 4a50546ae4abb37070703d1ce20149b65fdd1a51 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:37:48 +0200 -Subject: [PATCH 56/81] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC +Subject: [PATCH 56/82] 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.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch b/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch index 5fafe3f00..25d3ab26e 100644 --- a/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch +++ b/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch @@ -1,7 +1,7 @@ From ba2f77ed48aae661eed6cb213162a612f4792931 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 10 Nov 2025 13:42:53 +0100 -Subject: [PATCH 57/81] net: sparx5/lan969x: populate netdev of_node +Subject: [PATCH 57/82] 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.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch b/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch index 9b43f6bfc..dfa9ba218 100644 --- a/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch +++ b/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch @@ -1,7 +1,7 @@ From b8f745bf145aed656a87edcf2cee16caef2950e2 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:11 +0100 -Subject: [PATCH 58/81] arm64: dts: microchip: add LAN969x clock header file +Subject: [PATCH 58/82] 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.56/0059-arm64-dts-microchip-add-LAN969x-support.patch b/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch index e27e2246a..6ef71bd73 100644 --- a/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch +++ b/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From c3fc7872ff9176197161d2695a4d3fbbe2d90f90 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:12 +0100 -Subject: [PATCH 59/81] arm64: dts: microchip: add LAN969x support +Subject: [PATCH 59/82] 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.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch b/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch index 13e1c2b8a..562445a07 100644 --- a/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch +++ b/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch @@ -1,7 +1,7 @@ From b10c04d81bb96ab0b09484ee2a2373482b56f26d Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:14 +0100 -Subject: [PATCH 60/81] arm64: dts: microchip: add EV23X71A board +Subject: [PATCH 60/82] arm64: dts: microchip: add EV23X71A board Microchip EV23X71A is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch b/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch index 25f44ad15..aa8cef4bd 100644 --- a/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch +++ b/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch @@ -1,7 +1,7 @@ From c6af26aff045284fc98aae066d46385e74ab82c8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 15 May 2026 13:59:09 +0200 -Subject: [PATCH 61/81] arm64: dts: microchip: lan969x: add OTP node +Subject: [PATCH 61/82] arm64: dts: microchip: lan969x: add OTP node Add the required OTP on LAN969x. diff --git a/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch b/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch index 678310fc8..4e477e62a 100644 --- a/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch +++ b/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch @@ -1,7 +1,7 @@ From 7df6f654874cc48a7bea2d8d2d78fe8e9c1c90c7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 62/81] arm64: dts: microchip: lan969x: add SDMMC nodes +Subject: [PATCH 62/82] 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.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch b/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch index 189e508c8..1bf7707d0 100644 --- a/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch +++ b/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch @@ -1,7 +1,7 @@ From b41259b09f91a140bc0350d7f473d9fee3fbe8b3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 63/81] arm64: dts: microchip: ev23x71a: enable eMMC +Subject: [PATCH 63/82] 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.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch b/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch index 546cb59f1..bde436c94 100644 --- a/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch +++ b/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch @@ -1,7 +1,7 @@ From 54b38afac5c46299c8a7ebe594b35774d134389b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:42:48 +0200 -Subject: [PATCH 64/81] wifi: brcmfmac: survey the requested interface, not the +Subject: [PATCH 64/82] 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.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch b/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch index e32ad25f6..9f5cce8c4 100644 --- a/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch +++ b/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch @@ -1,7 +1,7 @@ From b2c521013e15b485455469fa4a263de2cf04dd02 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:43:14 +0200 -Subject: [PATCH 65/81] wifi: brcmfmac: honor caller's rtnl lock when stopping +Subject: [PATCH 65/82] 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.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch b/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch index b1eb50e5b..f503412c8 100644 --- a/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch +++ b/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch @@ -1,7 +1,7 @@ From 0e082428a5013b6d35567cf122119af2772f909f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:46:25 +0200 -Subject: [PATCH 66/81] wifi: brcmfmac: let cfg_to_ndev() return NULL and +Subject: [PATCH 66/82] 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.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch b/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch index 2d9c810bb..6a9dca004 100644 --- a/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch +++ b/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch @@ -1,7 +1,7 @@ From 102750f26d8cbf5d58936b4c9c6da801433c4314 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 16:05:17 +0200 -Subject: [PATCH 67/81] wifi: brcmfmac: support deletion and recreation of the +Subject: [PATCH 67/82] 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.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch b/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch index c2c2134f5..c8e3a65d7 100644 --- a/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch +++ b/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch @@ -1,7 +1,7 @@ From b965ae0c551ae3e2df6937933b6bdd1c42753a43 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 10 Sep 2026 08:29:47 +0200 -Subject: [PATCH 68/81] wifi: brcmfmac: report port authorized after offloaded +Subject: [PATCH 68/82] 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.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch b/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch index 9aef75685..6e75eb686 100644 --- a/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From f5f7599f8af9db56b09f26d22b20ee75defe1717 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 9 Jan 2026 13:27:00 +0100 -Subject: [PATCH 69/81] dt-bindings: arm: AT91: document Novarq Tactical 1000 +Subject: [PATCH 69/82] 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.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch b/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch index a53fa1b65..93dba7bd2 100644 --- a/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From 8b4b41c449758451c7dbcdd9092b9bd0e0a2b082 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Sep 2025 13:37:49 +0200 -Subject: [PATCH 70/81] arm64: dts: microchip: add Novarq Tactical 1000 +Subject: [PATCH 70/82] 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.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch b/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch index 78aac2d66..9d2b02ac8 100644 --- a/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch +++ b/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch @@ -1,7 +1,7 @@ From f973058544e1b44fd551db6a230659dbce67313e Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Jun 2026 11:23:47 +0200 -Subject: [PATCH 71/81] arm64: dts: microchip: tactical-1000: add port names +Subject: [PATCH 71/82] 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.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch b/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch index 1ca1b085f..6b8cd5c9e 100644 --- a/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch +++ b/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch @@ -1,7 +1,7 @@ From d49813a338d7c9b9d69e38e1fcc1c937c588a071 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 20 Sep 2026 12:22:43 +0200 -Subject: [PATCH 72/81] arm64: dts: microchip: tactical-1000: adapt to 6.18 +Subject: [PATCH 72/82] 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.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch b/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch index b799219b1..a4c365abf 100644 --- a/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch +++ b/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch @@ -1,7 +1,7 @@ From b5afb74688659804bbed4bca3928c98975daf2c8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:13 +0100 -Subject: [PATCH 73/81] dt-bindings: arm: AT91: document EV23X71A board +Subject: [PATCH 73/82] dt-bindings: arm: AT91: document EV23X71A board Microchip EV23X71A board is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch b/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch index 7db3cbd31..051cdc942 100644 --- a/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch +++ b/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch @@ -1,7 +1,7 @@ From 45fd43a305c595ea24b0de5cf090affeffc4029f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:21 +0200 -Subject: [PATCH 74/81] net: dsa: Skip DCB default priority init on unsupported +Subject: [PATCH 74/82] 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.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch b/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch index e73d3ee9d..066055a92 100644 --- a/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch +++ b/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch @@ -1,7 +1,7 @@ From cb31150945889f02373febe89f618afee36c6e62 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:51:07 +0200 -Subject: [PATCH 75/81] net: dsa: Generalise the global DCB APP mirroring +Subject: [PATCH 75/82] 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.56/0076-net-dsa-Support-the-PCP-APP-selector.patch b/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch index 3181a574b..328d668f7 100644 --- a/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch +++ b/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch @@ -1,7 +1,7 @@ From e1a8005d2fc1813b71e65c79e700da5635e639e0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:22 +0200 -Subject: [PATCH 76/81] net: dsa: Support the PCP APP selector +Subject: [PATCH 76/82] 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.56/0077-net-dsa-Support-DCB-priority-rewrite.patch b/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch index 8cda5ba84..62cb65da0 100644 --- a/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch +++ b/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch @@ -1,7 +1,7 @@ From fb3ca7803ea876f7d96514696615143835bc78f9 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:10:32 +0200 -Subject: [PATCH 77/81] net: dsa: Support DCB priority rewrite +Subject: [PATCH 77/82] 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.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch b/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch index 31c154004..79e3c394c 100644 --- a/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch +++ b/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch @@ -1,7 +1,7 @@ From 46e44f7a4aba62dc94958e01638e8e93ea62e59b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:53:23 +0200 -Subject: [PATCH 78/81] net: dsa: Support the IEEE ETS managed object +Subject: [PATCH 78/82] 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.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch b/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch index e586b4fac..f73200a0e 100644 --- a/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch +++ b/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch @@ -1,7 +1,7 @@ From 308c9dbab764bef06cceb03e412b491ea57234b5 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 7 Oct 2026 11:42:12 +0200 -Subject: [PATCH 79/81] net: sparx5: fix sleep in atomic context in MAC table +Subject: [PATCH 79/82] net: sparx5: fix sleep in atomic context in MAC table access Adding or removing a multicast address on an unbridged switch port diff --git a/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch b/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch index 2545180ad..b8bb033b9 100644 --- a/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch +++ b/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch @@ -1,7 +1,7 @@ From c6f8b6a050e69c951ce0ce56467b6e9fd9a638f1 Mon Sep 17 00:00:00 2001 From: Steen Hegelund Date: Wed, 21 Jan 2026 10:09:45 +0100 -Subject: [PATCH 80/81] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) +Subject: [PATCH 80/82] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) Expose the switch register space of Microchip Sparx5 family SoCs through /sys/kernel/debug/symreg/mem, for the symreg tool, which diff --git a/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch b/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch index cfb3edfe3..01c8f54a4 100644 --- a/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch +++ b/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch @@ -1,7 +1,7 @@ From e0e6e024e5e5866ff61b68bf9790fd24e542d7e2 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 5 Oct 2026 14:37:56 +0200 -Subject: [PATCH 81/81] net: ethernet: mtk_wed: map WO memory regions without +Subject: [PATCH 81/82] net: ethernet: mtk_wed: map WO memory regions without requesting them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch b/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch new file mode 100644 index 000000000..37c42e754 --- /dev/null +++ b/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch @@ -0,0 +1,111 @@ +From 8c40b743cd6f8117c177291988ffe1980e9595cd Mon Sep 17 00:00:00 2001 +From: Zhi-Jun You +Date: Wed, 15 Jul 2026 23:21:12 +0800 +Subject: [PATCH 82/82] wifi: mt76: wed: fix kernel panic on non-DBDC MT7986 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +In mt76_wed_init_rx_buf, it's hardcoded to use MT_RXQ_MAIN. +But for non-DBDC MT7986 MT_RXQ_BAND1 is used for RX data queue which +leads to kernel panic when attaching WED. + +Use the correct RX queue by checking WED version and band_idx. + +v2 and band 1 -> MT_RXQ_BAND1 +Others -> MT_RXQ_MAIN + +Kernel panic: + +Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000000000000000 +Mem abort info: + ESR = 0x0000000096000005 + EC = 0x25: DABT (current EL), IL = 32 bits + SET = 0, FnV = 0 + EA = 0, S1PTW = 0 + FSC = 0x05: level 1 translation fault +Data abort info: + ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000 + CM = 0, WnR = 0, TnD = 0, TagAccess = 0 + GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 +Internal error: Oops: 0000000096000005 [#1] SMP +CPU: 1 UID: 0 PID: 925 Comm: kmodloader Tainted: G O 6.18.26 #0 NONE +Tainted: [O]=OOT_MODULE +Hardware name: Acer Connect Vero W6m (DT) +pstate: 40400005 (nZcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) +pc : page_pool_alloc_frag_netmem+0x1c/0x1bc +lr : page_pool_alloc_frag+0xc/0x34 +sp : ffffffc081dab660 +x29: ffffffc081dab660 x28: ffffffc081dabc60 x27: ffffff80091af040 +x26: 0000008000000000 x25: ffffff80091a8898 x24: ffffff80091a5440 +x23: 0000000000001000 x22: 0000000140000000 x21: ffffff80091a2040 +x20: ffffff8003f1d780 x19: 0000000000000000 x18: 0000000000000020 +x17: ffffffbfbf0ac000 x16: ffffffc080ee0000 x15: ffffff80049d47ca +x14: 000000000000037b x13: 000000000000037b x12: 0000000000000001 +x11: 0000000000000000 x10: 0000000000000000 x9 : 0000000000000000 +x8 : ffffff8003f1d7c0 x7 : 0000000000000000 x6 : ffffff8003f1d780 +x5 : 0000000000000680 x4 : 0000000000000000 x3 : 0000000000002824 +x2 : 0000000000000000 x1 : ffffffc081dab71c x0 : 0000000000000000 +Call trace: + page_pool_alloc_frag_netmem+0x1c/0x1bc (P) + page_pool_alloc_frag+0xc/0x34 + mt76_wed_init_rx_buf+0xf8/0x2ac [mt76] + mtk_wed_start+0x79c/0x12ac + mt7915_dma_start+0x274/0x63c [mt7915e] + mt7915_dma_start+0x5b4/0x63c [mt7915e] + mt7915_dma_init+0x49c/0x81c [mt7915e] + mt7915_register_device+0x24c/0x530 [mt7915e] + mt7915_mmio_probe+0x91c/0x1980 [mt7915e] + platform_probe+0x58/0xa0 + really_probe+0xb8/0x2a8 + __driver_probe_device+0x74/0x118 + driver_probe_device+0x3c/0xe0 + __driver_attach+0x88/0x154 + bus_for_each_dev+0x60/0xb0 + driver_attach+0x20/0x28 + bus_add_driver+0xdc/0x200 + driver_register+0x64/0x118 + __platform_driver_register+0x20/0x30 + init_module+0x74/0x1000 [mt7915e] + do_one_initcall+0x4c/0x1f8 + do_init_module+0x50/0x210 + load_module+0x15f8/0x1b10 + __do_sys_init_module+0x1a8/0x260 + __arm64_sys_init_module+0x18/0x20 + invoke_syscall.constprop.0+0x4c/0xd0 + do_el0_svc+0x3c/0xd0 + el0_svc+0x18/0x60 + el0t_64_sync_handler+0x98/0xdc + el0t_64_sync+0x158/0x15c +Code: aa0003f3 a9025bf5 a90363f7 d2820017 (b9400000) + +Signed-off-by: Zhi-Jun You +Link: https://patch.msgid.link/20260715152113.553-1-hujy652@gmail.com +Signed-off-by: Felix Fietkau +(cherry picked from commit 8a177dabeeead47dda4d4f91331282790eed0097) +Signed-off-by: Mattias Walström +--- + 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 fbd7e59c73aaf..2ebf4edc689e9 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; From 73dbee0fd51bf40c387c2159ee9b0998e6957c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Thu, 8 Oct 2026 21:21:23 +0200 Subject: [PATCH 34/45] patches: linux: Fix WED on the single-band MT7986 radio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With WED on, the built-in radio of a BPI-R3 or W6m stalled its WPDMA RX ring after a few thousand frames: clients associated and nothing else arrived. The radio is bound to band 1 and never set up ring 0, which the WED drives alike. Give it an empty ring 0. Also pull in the WDS with WED support, the WED v2 reserve buffer and the wcid publish order from upstream. Signed-off-by: Mattias Walström --- ...0g-Support-firmware-loading-on-88X33.patch | 2 +- ...0g-Fix-power-up-when-strapped-to-sta.patch | 2 +- ...rvell10g-Add-LED-support-for-88X3310.patch | 2 +- ...0g-Support-LEDs-tied-to-a-single-med.patch | 2 +- ...phy-Do-not-resume-PHY-when-attaching.patch | 2 +- ...-classifying-unknown-multicast-as-mr.patch | 2 +- ...e-router-ports-when-forwarding-L2-mu.patch | 2 +- ...delay-for-applying-strict-multicast-.patch | 2 +- ...rentiate-MDB-additions-from-modifica.patch | 2 +- ...ie-tlv-Let-device-probe-even-when-TL.patch | 2 +- ...d-r8153b-support-for-link-activity-L.patch | 2 +- ...ek-mt7986a-rename-BPi-R3-ports-to-ma.patch | 2 +- ...-Add-a-timing-for-the-Raspberry-Pi-7.patch | 2 +- ...uchscreen-edt-ft5x06-Add-polled-mode.patch | 2 +- ...e6xxx-Fix-timeout-on-waiting-for-PPU.patch | 2 +- ...x-Improve-indirect-register-access-p.patch | 2 +- ...x-Honor-ports-being-managed-via-in-b.patch | 2 +- ...x-Limit-rsvd2cpu-policy-to-user-port.patch | 2 +- ...Use-tag-priority-as-initial-skb-prio.patch | 2 +- ...MDB-memberships-whose-L2-addresses-o.patch | 2 +- ...t-EtherType-based-priority-overrides.patch | 2 +- ...x-Support-EtherType-based-priority-o.patch | 2 +- ...a-mv88e6xxx-Add-mqprio-qdisc-support.patch | 2 +- ...x-Use-VLAN-prio-over-IP-when-both-ar.patch | 2 +- ...8e6xxx-Trap-locally-terminated-VLANs.patch | 2 +- ...x-collapse-disabled-state-into-block.patch | 2 +- ...x-Only-activate-LAG-offloading-when-.patch | 2 +- ...-mv88e6xxx-Add-LED-support-for-6393X.patch | 2 +- ...eck-connection-state-before-querying.patch | 2 +- ...ppress-log-spam-for-regulatory-restr.patch | 2 +- ...duce-log-noise-during-AP-to-station-.patch | 2 +- ...11h-add-OF-device-table-for-auto-loa.patch | 2 +- ...le-video-and-then-retry-failed-trans.patch | 2 +- ...locks-should-be-running-before-reset.patch | 2 +- ...nsure-DSI-is-enabled-for-FIFO-resets.patch | 2 +- ...036-drm-vc4-Reset-DSI-AFE-on-disable.patch | 2 +- ...le-the-different-command-FIFO-widths.patch | 2 +- ...762-Program-the-DPI-mode-into-the-ch.patch | 2 +- ...e-tc358762-revert-move-ops-to-enable.patch | 2 +- ...762-Set-pre_enabled-on-pre_enable-to.patch | 2 +- ...dalone-PCS-registration-infrastructu.patch | 2 +- ...d-MediaTek-MT7988-USXGMII-PCS-driver.patch | 2 +- ...iatek-add-USXGMII-support-for-MT7988.patch | 2 +- ...diatek-mt7988a-add-USXGMII-PCS-nodes.patch | 2 +- ...ek-bananapi-bpi-r4-enable-SFP-ports-.patch | 2 +- ...et-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch | 2 +- ...x-Trap-PTP-frames-on-timestamping-po.patch | 2 +- ...-add-MODULE_DEVICE_TABLE-for-mt7622-.patch | 2 +- ...3-Fix-PERST-control-timing-during-sy.patch | 2 +- ...xx-Derive-LED-names-from-device-name.patch | 2 +- ...s-make-it-selectable-for-ARCH_LAN969.patch | 2 +- ...arx5-fix-wrong-chip-ids-for-TSN-SKUs.patch | 2 +- ...gure-serdes-for-1000BASE-X-in-sparx5.patch | 2 +- ...atmel-sama5d2-sdhci-add-microchip-la.patch | 2 +- ...mc-sdhci-of-at91-add-LAN969x-support.patch | 2 +- ...1-stop-SDCLK-on-reset-and-add-eMMC-h.patch | 2 +- ...arx5-lan969x-populate-netdev-of_node.patch | 2 +- ...rochip-add-LAN969x-clock-header-file.patch | 2 +- ...64-dts-microchip-add-LAN969x-support.patch | 2 +- ...m64-dts-microchip-add-EV23X71A-board.patch | 2 +- ...4-dts-microchip-lan969x-add-OTP-node.patch | 2 +- ...ts-microchip-lan969x-add-SDMMC-nodes.patch | 2 +- ...4-dts-microchip-ev23x71a-enable-eMMC.patch | 2 +- ...rvey-the-requested-interface-not-the.patch | 2 +- ...nor-caller-s-rtnl-lock-when-stopping.patch | 2 +- ...t-cfg_to_ndev-return-NULL-and-harden.patch | 2 +- ...pport-deletion-and-recreation-of-the.patch | 2 +- ...port-port-authorized-after-offloaded.patch | 2 +- ...m-AT91-document-Novarq-Tactical-1000.patch | 2 +- ...s-microchip-add-Novarq-Tactical-1000.patch | 2 +- ...crochip-tactical-1000-add-port-names.patch | 2 +- ...icrochip-tactical-1000-adapt-to-6.18.patch | 2 +- ...ngs-arm-AT91-document-EV23X71A-board.patch | 2 +- ...-default-priority-init-on-unsupporte.patch | 2 +- ...se-the-global-DCB-APP-mirroring-help.patch | 2 +- ...net-dsa-Support-the-PCP-APP-selector.patch | 2 +- ...net-dsa-Support-DCB-priority-rewrite.patch | 2 +- ...-Support-the-IEEE-ETS-managed-object.patch | 2 +- ...leep-in-atomic-context-in-MAC-table-.patch | 2 +- ...ymreg-symreg-debugfs-driver-DBB-1045.patch | 2 +- ..._wed-map-WO-memory-regions-without-r.patch | 2 +- ...-fix-kernel-panic-on-non-DBDC-MT7986.patch | 2 +- ...-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch | 44 ++ ...-add-WDS-support-when-WED-is-enabled.patch | 431 ++++++++++++++++++ ..._wed-increase-WED-v2-WDMA-RESV_BUFF-.patch | 49 ++ ...-publish-wcid-before-MCU-add-command.patch | 54 +++ 86 files changed, 660 insertions(+), 82 deletions(-) create mode 100644 patches/linux/6.18.56/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch create mode 100644 patches/linux/6.18.56/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch create mode 100644 patches/linux/6.18.56/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch create mode 100644 patches/linux/6.18.56/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch diff --git a/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch b/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch index 2017a443d..63f024651 100644 --- a/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch +++ b/patches/linux/6.18.56/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch @@ -1,7 +1,7 @@ From ca34ebe2b40b53b52a5ea6fb00b7bb36eeeabb5f Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 19 Sep 2023 18:38:10 +0200 -Subject: [PATCH 01/82] net: phy: marvell10g: Support firmware loading on +Subject: [PATCH 01/86] 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.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch b/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch index 989d015b7..9a48713b7 100644 --- a/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch +++ b/patches/linux/6.18.56/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch @@ -1,7 +1,7 @@ From 59532ff26cccf476b4e6f8eeaf2e733c93106a78 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 21 Nov 2023 20:15:24 +0100 -Subject: [PATCH 02/82] net: phy: marvell10g: Fix power-up when strapped to +Subject: [PATCH 02/86] 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.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch b/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch index bc4decb01..1b45c4c0a 100644 --- a/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch +++ b/patches/linux/6.18.56/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch @@ -1,7 +1,7 @@ From 2881f5a7008a4b7a214f63960f1d199f55a7a78a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 15 Nov 2023 20:58:42 +0100 -Subject: [PATCH 03/82] net: phy: marvell10g: Add LED support for 88X3310 +Subject: [PATCH 03/86] 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.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch b/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch index 09b8f6d57..13027abff 100644 --- a/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch +++ b/patches/linux/6.18.56/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch @@ -1,7 +1,7 @@ From 67a62752bdb5e1e45982289b2eda1f5df4ca6f47 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Dec 2023 09:51:05 +0100 -Subject: [PATCH 04/82] net: phy: marvell10g: Support LEDs tied to a single +Subject: [PATCH 04/86] 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.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch b/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch index fab2ecce8..370ff0d79 100644 --- a/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch +++ b/patches/linux/6.18.56/0005-net-phy-Do-not-resume-PHY-when-attaching.patch @@ -1,7 +1,7 @@ From a60255d4ba8ea888ecb86e92a2c65334a26f6384 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 10:10:19 +0100 -Subject: [PATCH 05/82] net: phy: Do not resume PHY when attaching +Subject: [PATCH 05/86] 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.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch b/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch index c9b37aee9..722348bd3 100644 --- a/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch +++ b/patches/linux/6.18.56/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch @@ -1,7 +1,7 @@ From 3fc8e2a80589a5a5ae02fac7b3f97f195cb90930 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 4 Mar 2024 16:47:28 +0100 -Subject: [PATCH 06/82] net: bridge: avoid classifying unknown multicast as +Subject: [PATCH 06/86] 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.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch b/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch index 2eadbd7fb..ac60d4c16 100644 --- a/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch +++ b/patches/linux/6.18.56/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch @@ -1,7 +1,7 @@ From 8f5cf3a448d6e7a1d00043debd0bf1fcaf706680 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 06:44:41 +0100 -Subject: [PATCH 07/82] net: bridge: Ignore router ports when forwarding L2 +Subject: [PATCH 07/86] 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.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch b/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch index 049d87c8a..46e829866 100644 --- a/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch +++ b/patches/linux/6.18.56/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch @@ -1,7 +1,7 @@ From a42722822b7ff8ea8ac5e65a8449bb72b1ef8c05 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 4 Apr 2024 16:36:30 +0200 -Subject: [PATCH 08/82] net: bridge: drop delay for applying strict multicast +Subject: [PATCH 08/86] 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.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch b/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch index ab448ab63..6a3937d3a 100644 --- a/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch +++ b/patches/linux/6.18.56/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch @@ -1,7 +1,7 @@ From 2722df35856114c70f9896452d155b660f804ed8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 May 2024 14:51:54 +0200 -Subject: [PATCH 09/82] net: bridge: Differentiate MDB additions from +Subject: [PATCH 09/86] 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.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch b/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch index 230b1227d..1e5f830a6 100644 --- a/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch +++ b/patches/linux/6.18.56/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch @@ -1,7 +1,7 @@ From 17f03931f23a3ed1dbe6f77a7e48bfdc11f17140 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 24 Nov 2023 23:29:55 +0100 -Subject: [PATCH 10/82] nvmem: layouts: onie-tlv: Let device probe even when +Subject: [PATCH 10/86] 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.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch b/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch index 772262c38..55dfcebe6 100644 --- a/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch +++ b/patches/linux/6.18.56/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch @@ -1,7 +1,7 @@ From cbc993b90307a45db570d20bce5e651100bafaa7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 11 Aug 2024 11:27:35 +0200 -Subject: [PATCH 11/82] net: usb: r8152: add r8153b support for link/activity +Subject: [PATCH 11/86] 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.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch b/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch index e1c8a26bc..5726a3f79 100644 --- a/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch +++ b/patches/linux/6.18.56/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch @@ -1,7 +1,7 @@ From 0c1a54359f6989eedad98678e030fc1d8fb44da0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 10 Aug 2025 18:52:54 +0200 -Subject: [PATCH 12/82] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to +Subject: [PATCH 12/86] 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.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch b/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch index 1c2242121..1068b1f89 100644 --- a/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch +++ b/patches/linux/6.18.56/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch @@ -1,7 +1,7 @@ From 2cbb47d51538a83315595715405a328552b9a753 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 20 Aug 2025 21:38:24 +0200 -Subject: [PATCH 13/82] drm/panel-simple: Add a timing for the Raspberry Pi 7" +Subject: [PATCH 13/86] 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.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch b/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch index 32946370b..70094938d 100644 --- a/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch +++ b/patches/linux/6.18.56/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch @@ -1,7 +1,7 @@ From fed97704c224902b2d79c81492c5a50ccd97793b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 21 Aug 2025 11:20:23 +0200 -Subject: [PATCH 14/82] input:touchscreen:edt-ft5x06: Add polled mode +Subject: [PATCH 14/86] 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.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch b/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch index 22507ce84..0d8c07724 100644 --- a/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch +++ b/patches/linux/6.18.56/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch @@ -1,7 +1,7 @@ From 11f9f49b62a6eb3a73a523541b3f26f6958e0d49 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Mar 2024 10:27:24 +0100 -Subject: [PATCH 15/82] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for +Subject: [PATCH 15/86] [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.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch b/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch index 15981bcaf..aef78ec81 100644 --- a/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch +++ b/patches/linux/6.18.56/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch @@ -1,7 +1,7 @@ From 6aed16ecedd6e8d3594474b00b4f80a6a75372c1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 15:52:43 +0100 -Subject: [PATCH 16/82] net: dsa: mv88e6xxx: Improve indirect register access +Subject: [PATCH 16/86] 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.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch b/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch index 8a105694a..914a05225 100644 --- a/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch +++ b/patches/linux/6.18.56/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch @@ -1,7 +1,7 @@ From 627e2902f0b11f9994e5c8c92627791b60069cdb Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 22 Apr 2024 23:18:01 +0200 -Subject: [PATCH 17/82] net: dsa: mv88e6xxx: Honor ports being managed via +Subject: [PATCH 17/86] 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.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch b/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch index 492d5eb8c..25c027ed6 100644 --- a/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch +++ b/patches/linux/6.18.56/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch @@ -1,7 +1,7 @@ From 1ad63ed23d72ed2429bcb7393aac0e82affde9e3 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 24 Apr 2024 22:41:04 +0200 -Subject: [PATCH 18/82] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user +Subject: [PATCH 18/86] 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.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch b/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch index 6f2e82239..51365c7dc 100644 --- a/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch +++ b/patches/linux/6.18.56/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch @@ -1,7 +1,7 @@ From 29b6583d7463dfddbfbb0e30a70de7707f5c86b0 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 10:38:42 +0200 -Subject: [PATCH 19/82] net: dsa: tag_dsa: Use tag priority as initial +Subject: [PATCH 19/86] 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.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch b/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch index 05781141a..ffeed9746 100644 --- a/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch +++ b/patches/linux/6.18.56/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch @@ -1,7 +1,7 @@ From 6cebfb46fd0a75b04f2481dcbd43cb9442bb1e12 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 16 Jan 2024 16:00:55 +0100 -Subject: [PATCH 20/82] net: dsa: Support MDB memberships whose L2 addresses +Subject: [PATCH 20/86] 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.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch b/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch index 9a6ff1291..c4d543ad5 100644 --- a/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch +++ b/patches/linux/6.18.56/0021-net-dsa-Support-EtherType-based-priority-overrides.patch @@ -1,7 +1,7 @@ From 2b39a3958e00546ff1c6f0cc523970d19355d796 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 21 Mar 2024 19:12:15 +0100 -Subject: [PATCH 21/82] net: dsa: Support EtherType based priority overrides +Subject: [PATCH 21/86] net: dsa: Support EtherType based priority overrides --- include/net/dsa.h | 4 ++++ diff --git a/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch b/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch index 5f80da7dd..fa0860976 100644 --- a/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch +++ b/patches/linux/6.18.56/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch @@ -1,7 +1,7 @@ From 3769055203bee35077c685d850f0debbac52459e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 22 Mar 2024 16:15:43 +0100 -Subject: [PATCH 22/82] net: dsa: mv88e6xxx: Support EtherType based priority +Subject: [PATCH 22/86] net: dsa: mv88e6xxx: Support EtherType based priority overrides --- diff --git a/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch b/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch index c0d36b025..ade6f51f6 100644 --- a/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch +++ b/patches/linux/6.18.56/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch @@ -1,7 +1,7 @@ From bf8849ce4ee5e95f33c435914a5d7c8e6e16e416 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 11:04:22 +0200 -Subject: [PATCH 23/82] net: dsa: mv88e6xxx: Add mqprio qdisc support +Subject: [PATCH 23/86] 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.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch b/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch index a8e6ec129..11cf8db9d 100644 --- a/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch +++ b/patches/linux/6.18.56/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch @@ -1,7 +1,7 @@ From 73b39094d7cdd592ec6fb18164590678789ab37a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 29 May 2024 13:20:41 +0200 -Subject: [PATCH 24/82] net: dsa: mv88e6xxx: Use VLAN prio over IP when both +Subject: [PATCH 24/86] 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.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch b/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch index 52fbdc5af..a8da9951c 100644 --- a/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch +++ b/patches/linux/6.18.56/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch @@ -1,7 +1,7 @@ From 320983cf57f27f4138a4736388ad384ea9754d37 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 26 Nov 2024 19:45:59 +0100 -Subject: [PATCH 25/82] [FIX] net: dsa: mv88e6xxx: Trap locally terminated +Subject: [PATCH 25/86] [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.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch b/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch index aad5ce03a..e3cd5c084 100644 --- a/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch +++ b/patches/linux/6.18.56/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch @@ -1,7 +1,7 @@ From 95d919b20d95e7d33432d450e2caafa42105d645 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 12:35:12 +0100 -Subject: [PATCH 26/82] net: dsa: mv88e6xxx: collapse disabled state into +Subject: [PATCH 26/86] 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.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch b/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch index 5899acb33..ec77c2f60 100644 --- a/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch +++ b/patches/linux/6.18.56/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch @@ -1,7 +1,7 @@ From 4a67cefaa3e4d69034afee21162133191863e841 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 12 Feb 2025 22:03:14 +0100 -Subject: [PATCH 27/82] net: dsa: mv88e6xxx: Only activate LAG offloading when +Subject: [PATCH 27/86] 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.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch b/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch index f192fd9d8..ba7c735f1 100644 --- a/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch +++ b/patches/linux/6.18.56/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch @@ -1,7 +1,7 @@ From de1f05b6ee9af9d4f7deb7cde43e28d81e1b7f7f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 14 Jan 2026 18:22:41 +0100 -Subject: [PATCH 28/82] net: dsa: mv88e6xxx: Add LED support for 6393X +Subject: [PATCH 28/86] net: dsa: mv88e6xxx: Add LED support for 6393X Original commit: commit 462277b926140ee2d231317e92afb6cabf640268 diff --git a/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch b/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch index 3ca518d33..61a591407 100644 --- a/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch +++ b/patches/linux/6.18.56/0029-wifi-brcmfmac-check-connection-state-before-querying.patch @@ -1,7 +1,7 @@ From 5ba4ebc2cbcc3efdb8c68e64de2e3e1d36f700ca Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 19 Jan 2026 13:06:53 +0100 -Subject: [PATCH 29/82] wifi: brcmfmac: check connection state before querying +Subject: [PATCH 29/86] 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.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch b/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch index d7fbb6b0d..2749a592e 100644 --- a/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch +++ b/patches/linux/6.18.56/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch @@ -1,7 +1,7 @@ From e714d2bb8736f8befb0e223b4de054981125010e Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:12:10 +0100 -Subject: [PATCH 30/82] wifi: brcmfmac: suppress log spam for +Subject: [PATCH 30/86] 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.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch b/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch index a32d2ef25..30aecd068 100644 --- a/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch +++ b/patches/linux/6.18.56/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch @@ -1,7 +1,7 @@ From 09696680be45dbeb24e8a722c3d4030b7737e11a Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:18:45 +0100 -Subject: [PATCH 31/82] wifi: brcmfmac: reduce log noise during AP to station +Subject: [PATCH 31/86] 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.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch b/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch index 9be7be1eb..145a6655e 100644 --- a/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch +++ b/patches/linux/6.18.56/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch @@ -1,7 +1,7 @@ From 28322c174df66701c2f4283bf338a3032a8121bc Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 17 Feb 2026 21:59:59 +0100 -Subject: [PATCH 32/82] net: phy: air_en8811h: add OF device table for +Subject: [PATCH 32/86] 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.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch b/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch index db68cd8dc..d2717e199 100644 --- a/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch +++ b/patches/linux/6.18.56/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch @@ -1,7 +1,7 @@ From 53aba25643ef8e41021eab9b61242f3151fa3a56 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 20 Sep 2024 12:05:18 +0100 -Subject: [PATCH 33/82] drm: vc4: dsi: enable video and then retry failed +Subject: [PATCH 33/86] 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.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch b/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch index 40bf46547..128b97ca5 100644 --- a/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch +++ b/patches/linux/6.18.56/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch @@ -1,7 +1,7 @@ From b145fb6117a42c835d69cee8d01b7679396541e6 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 8 Jun 2022 17:23:47 +0100 -Subject: [PATCH 34/82] drm: vc4: dsi: Clocks should be running before reset +Subject: [PATCH 34/86] 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.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch b/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch index de6d0ee37..d1130efce 100644 --- a/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch +++ b/patches/linux/6.18.56/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch @@ -1,7 +1,7 @@ From 394a21d586f7f13a5cb822ed95ef986dfb17ca87 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 5 Apr 2024 17:51:55 +0100 -Subject: [PATCH 35/82] drm/vc4: Ensure DSI is enabled for FIFO resets +Subject: [PATCH 35/86] 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.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch b/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch index 4529117f6..de5adcbf3 100644 --- a/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch +++ b/patches/linux/6.18.56/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch @@ -1,7 +1,7 @@ From 5b807e23d91f3d90697f74d484276e574670aa2c Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 26 May 2022 18:56:19 +0100 -Subject: [PATCH 36/82] drm: vc4: Reset DSI AFE on disable +Subject: [PATCH 36/86] 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.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch b/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch index 0360e64ae..742c69a38 100644 --- a/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch +++ b/patches/linux/6.18.56/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch @@ -1,7 +1,7 @@ From 1610998f5338234b29c335d18ffeb00c2dcec73a Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 20 Nov 2024 13:58:08 +0000 -Subject: [PATCH 37/82] drm: vc4: dsi: Handle the different command FIFO widths +Subject: [PATCH 37/86] 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.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch b/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch index 8a34df8a8..fc94b38fc 100644 --- a/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch +++ b/patches/linux/6.18.56/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch @@ -1,7 +1,7 @@ From 0c2f36c6efa65abc8d2c5179903c3fb5afac9874 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 17:37:00 +0000 -Subject: [PATCH 38/82] drm/bridge: tc358762: Program the DPI mode into the +Subject: [PATCH 38/86] 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.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch b/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch index ecfdb8852..168a87fe3 100644 --- a/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch +++ b/patches/linux/6.18.56/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch @@ -1,7 +1,7 @@ From 91f9e48ff0405e6b8ec9d046b7f37d96f99483c5 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 18:44:49 +0000 -Subject: [PATCH 39/82] drm/bridge: tc358762: revert move ops to enable +Subject: [PATCH 39/86] 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.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch b/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch index 72a5851f5..a4d092208 100644 --- a/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch +++ b/patches/linux/6.18.56/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch @@ -1,7 +1,7 @@ From 18d15d554c004a795771c8adb8abb6ef54f22985 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Sat, 4 Apr 2026 18:04:19 +0200 -Subject: [PATCH 40/82] drm/bridge: tc358762: Set pre_enabled on pre_enable to +Subject: [PATCH 40/86] 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.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch b/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch index 291e30d92..acc2764e2 100644 --- a/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch +++ b/patches/linux/6.18.56/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch @@ -1,7 +1,7 @@ From a2fc20fe20ad386cff232905d1858f9af21ecaf2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 5 Apr 2026 11:33:00 +0200 -Subject: [PATCH 41/82] net/pcs: add standalone PCS registration infrastructure +Subject: [PATCH 41/86] 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.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch b/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch index 799dc5320..07a19f206 100644 --- a/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch +++ b/patches/linux/6.18.56/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch @@ -1,7 +1,7 @@ From 6c0e1262a95027d0ef8fb263353e5a4382529437 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:14:23 +0200 -Subject: [PATCH 42/82] net/pcs: add MediaTek MT7988 USXGMII PCS driver +Subject: [PATCH 42/86] 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.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch b/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch index 48e21a65f..50c0ae6e6 100644 --- a/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch +++ b/patches/linux/6.18.56/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch @@ -1,7 +1,7 @@ From 7a3a934b66b5a38e732f6c28f76c45b43cea4e15 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:43 +0200 -Subject: [PATCH 43/82] net: ethernet: mediatek: add USXGMII support for MT7988 +Subject: [PATCH 43/86] 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.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch b/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch index 2a2155827..366630365 100644 --- a/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch +++ b/patches/linux/6.18.56/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch @@ -1,7 +1,7 @@ From d320f311c3e132191b319db4d7f7e3e3f5ed58df Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:56 +0200 -Subject: [PATCH 44/82] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes +Subject: [PATCH 44/86] 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.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch b/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch index c0172c326..51a2e9c26 100644 --- a/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch +++ b/patches/linux/6.18.56/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch @@ -1,7 +1,7 @@ From d83185ab2d0bc45de5c33b6fc50006e166f39417 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:16:11 +0200 -Subject: [PATCH 45/82] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ +Subject: [PATCH 45/86] 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.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch b/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch index 7c441dcc4..59cc38f06 100644 --- a/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch +++ b/patches/linux/6.18.56/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch @@ -1,7 +1,7 @@ From fa401f49e78eeefa6ef1752733c87b1d67655125 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 Apr 2026 07:34:52 +0200 -Subject: [PATCH 46/82] net: phy: sfp: add OEM SFP-10G-T-I quirk +Subject: [PATCH 46/86] 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.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch b/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch index ad6a2ee8a..169a5b9e4 100644 --- a/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch +++ b/patches/linux/6.18.56/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch @@ -1,7 +1,7 @@ From 79c898ca16e60f10eafaf89110b5d2c24bd9ac2c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 17 Apr 2026 09:13:04 +0000 -Subject: [PATCH 47/82] net: dsa: mv88e6xxx: Trap PTP frames on timestamping +Subject: [PATCH 47/86] 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.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch b/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch index 20b60c468..7feb07b15 100644 --- a/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch +++ b/patches/linux/6.18.56/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch @@ -1,7 +1,7 @@ From 0f8cdee2b15e4d5c36520e274ebc74371ec8de68 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Apr 2026 15:30:01 +0200 -Subject: [PATCH 48/82] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for mt7622 +Subject: [PATCH 48/86] 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.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch b/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch index 852c2e98f..46fa15d0d 100644 --- a/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch +++ b/patches/linux/6.18.56/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch @@ -1,7 +1,7 @@ From 5f9aa8845b841fcada5fa58f92ceaa9bb9fabcb4 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 22 Apr 2026 10:24:43 +0200 -Subject: [PATCH 49/82] PCI: mediatek-gen3: Fix PERST# control timing during +Subject: [PATCH 49/86] 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.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch b/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch index d0b6d1933..cf0df843b 100644 --- a/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch +++ b/patches/linux/6.18.56/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch @@ -1,7 +1,7 @@ From 024fb8fd29a48bb85f355b5a4a558c8ab6875b3c Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 12 Aug 2026 10:08:53 +0200 -Subject: [PATCH 50/82] net: dsa: mv88e6xxx: Derive LED names from device name +Subject: [PATCH 50/86] 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.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch b/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch index 13fec3ac1..449a6df46 100644 --- a/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch +++ b/patches/linux/6.18.56/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch @@ -1,7 +1,7 @@ From f42698c6d686cabca0fced226a63e09c438fbc78 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 31 Oct 2025 13:18:12 +0100 -Subject: [PATCH 51/82] phy: sparx5-serdes: make it selectable for ARCH_LAN969X +Subject: [PATCH 51/86] 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.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch b/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch index 550be861a..745d75541 100644 --- a/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch +++ b/patches/linux/6.18.56/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch @@ -1,7 +1,7 @@ From da55a0660bc7a764abd39b28d1b588d58cfafcf7 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:38 +0200 -Subject: [PATCH 52/82] net: sparx5: fix wrong chip ids for TSN SKUs +Subject: [PATCH 52/86] 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.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch b/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch index 662582d83..0d8dfbf32 100644 --- a/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch +++ b/patches/linux/6.18.56/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch @@ -1,7 +1,7 @@ From 13292f9168073d35dfa05f2c3e8402e18bf3f9ce Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:39 +0200 -Subject: [PATCH 53/82] net: sparx5: configure serdes for 1000BASE-X in +Subject: [PATCH 53/86] 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.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch b/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch index ed019fbc3..bd6b3e02a 100644 --- a/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch +++ b/patches/linux/6.18.56/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch @@ -1,7 +1,7 @@ From 704c64713d2d2d34a22e0d2f8066996bc62d9120 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:29 +0200 -Subject: [PATCH 54/82] dt-bindings: mmc: atmel,sama5d2-sdhci: add +Subject: [PATCH 54/86] 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.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch b/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch index f2415b49e..fb62bbd5c 100644 --- a/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch +++ b/patches/linux/6.18.56/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From ae98994e45aaa55ecff09321aa65601561aefab4 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:44 +0200 -Subject: [PATCH 55/82] mmc: sdhci-of-at91: add LAN969x support +Subject: [PATCH 55/86] 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.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch b/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch index 1e8fd681d..36f60a3d2 100644 --- a/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch +++ b/patches/linux/6.18.56/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch @@ -1,7 +1,7 @@ From 4a50546ae4abb37070703d1ce20149b65fdd1a51 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:37:48 +0200 -Subject: [PATCH 56/82] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC +Subject: [PATCH 56/86] 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.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch b/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch index 25d3ab26e..3b692f39f 100644 --- a/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch +++ b/patches/linux/6.18.56/0057-net-sparx5-lan969x-populate-netdev-of_node.patch @@ -1,7 +1,7 @@ From ba2f77ed48aae661eed6cb213162a612f4792931 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 10 Nov 2025 13:42:53 +0100 -Subject: [PATCH 57/82] net: sparx5/lan969x: populate netdev of_node +Subject: [PATCH 57/86] 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.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch b/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch index dfa9ba218..d0fa8eb3c 100644 --- a/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch +++ b/patches/linux/6.18.56/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch @@ -1,7 +1,7 @@ From b8f745bf145aed656a87edcf2cee16caef2950e2 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:11 +0100 -Subject: [PATCH 58/82] arm64: dts: microchip: add LAN969x clock header file +Subject: [PATCH 58/86] 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.56/0059-arm64-dts-microchip-add-LAN969x-support.patch b/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch index 6ef71bd73..835c902c4 100644 --- a/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch +++ b/patches/linux/6.18.56/0059-arm64-dts-microchip-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From c3fc7872ff9176197161d2695a4d3fbbe2d90f90 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:12 +0100 -Subject: [PATCH 59/82] arm64: dts: microchip: add LAN969x support +Subject: [PATCH 59/86] 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.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch b/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch index 562445a07..cf54bc54f 100644 --- a/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch +++ b/patches/linux/6.18.56/0060-arm64-dts-microchip-add-EV23X71A-board.patch @@ -1,7 +1,7 @@ From b10c04d81bb96ab0b09484ee2a2373482b56f26d Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:14 +0100 -Subject: [PATCH 60/82] arm64: dts: microchip: add EV23X71A board +Subject: [PATCH 60/86] arm64: dts: microchip: add EV23X71A board Microchip EV23X71A is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch b/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch index aa8cef4bd..1a593defe 100644 --- a/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch +++ b/patches/linux/6.18.56/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch @@ -1,7 +1,7 @@ From c6af26aff045284fc98aae066d46385e74ab82c8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 15 May 2026 13:59:09 +0200 -Subject: [PATCH 61/82] arm64: dts: microchip: lan969x: add OTP node +Subject: [PATCH 61/86] arm64: dts: microchip: lan969x: add OTP node Add the required OTP on LAN969x. diff --git a/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch b/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch index 4e477e62a..5a143e36d 100644 --- a/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch +++ b/patches/linux/6.18.56/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch @@ -1,7 +1,7 @@ From 7df6f654874cc48a7bea2d8d2d78fe8e9c1c90c7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 62/82] arm64: dts: microchip: lan969x: add SDMMC nodes +Subject: [PATCH 62/86] 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.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch b/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch index 1bf7707d0..7e5db38be 100644 --- a/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch +++ b/patches/linux/6.18.56/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch @@ -1,7 +1,7 @@ From b41259b09f91a140bc0350d7f473d9fee3fbe8b3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 63/82] arm64: dts: microchip: ev23x71a: enable eMMC +Subject: [PATCH 63/86] 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.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch b/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch index bde436c94..bd1609210 100644 --- a/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch +++ b/patches/linux/6.18.56/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch @@ -1,7 +1,7 @@ From 54b38afac5c46299c8a7ebe594b35774d134389b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:42:48 +0200 -Subject: [PATCH 64/82] wifi: brcmfmac: survey the requested interface, not the +Subject: [PATCH 64/86] 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.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch b/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch index 9f5cce8c4..a4b45e637 100644 --- a/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch +++ b/patches/linux/6.18.56/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch @@ -1,7 +1,7 @@ From b2c521013e15b485455469fa4a263de2cf04dd02 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:43:14 +0200 -Subject: [PATCH 65/82] wifi: brcmfmac: honor caller's rtnl lock when stopping +Subject: [PATCH 65/86] 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.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch b/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch index f503412c8..30f45c859 100644 --- a/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch +++ b/patches/linux/6.18.56/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch @@ -1,7 +1,7 @@ From 0e082428a5013b6d35567cf122119af2772f909f Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:46:25 +0200 -Subject: [PATCH 66/82] wifi: brcmfmac: let cfg_to_ndev() return NULL and +Subject: [PATCH 66/86] 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.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch b/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch index 6a9dca004..c31f3cb56 100644 --- a/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch +++ b/patches/linux/6.18.56/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch @@ -1,7 +1,7 @@ From 102750f26d8cbf5d58936b4c9c6da801433c4314 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 16:05:17 +0200 -Subject: [PATCH 67/82] wifi: brcmfmac: support deletion and recreation of the +Subject: [PATCH 67/86] 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.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch b/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch index c8e3a65d7..cf1ee0196 100644 --- a/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch +++ b/patches/linux/6.18.56/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch @@ -1,7 +1,7 @@ From b965ae0c551ae3e2df6937933b6bdd1c42753a43 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 10 Sep 2026 08:29:47 +0200 -Subject: [PATCH 68/82] wifi: brcmfmac: report port authorized after offloaded +Subject: [PATCH 68/86] 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.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch b/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch index 6e75eb686..e1668444d 100644 --- a/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.56/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From f5f7599f8af9db56b09f26d22b20ee75defe1717 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 9 Jan 2026 13:27:00 +0100 -Subject: [PATCH 69/82] dt-bindings: arm: AT91: document Novarq Tactical 1000 +Subject: [PATCH 69/86] 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.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch b/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch index 93dba7bd2..e6e34024e 100644 --- a/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.56/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From 8b4b41c449758451c7dbcdd9092b9bd0e0a2b082 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Sep 2025 13:37:49 +0200 -Subject: [PATCH 70/82] arm64: dts: microchip: add Novarq Tactical 1000 +Subject: [PATCH 70/86] 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.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch b/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch index 9d2b02ac8..bbef8699f 100644 --- a/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch +++ b/patches/linux/6.18.56/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch @@ -1,7 +1,7 @@ From f973058544e1b44fd551db6a230659dbce67313e Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Jun 2026 11:23:47 +0200 -Subject: [PATCH 71/82] arm64: dts: microchip: tactical-1000: add port names +Subject: [PATCH 71/86] 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.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch b/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch index 6b8cd5c9e..5c5071bfd 100644 --- a/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch +++ b/patches/linux/6.18.56/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch @@ -1,7 +1,7 @@ From d49813a338d7c9b9d69e38e1fcc1c937c588a071 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 20 Sep 2026 12:22:43 +0200 -Subject: [PATCH 72/82] arm64: dts: microchip: tactical-1000: adapt to 6.18 +Subject: [PATCH 72/86] 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.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch b/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch index a4c365abf..66b1c907b 100644 --- a/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch +++ b/patches/linux/6.18.56/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch @@ -1,7 +1,7 @@ From b5afb74688659804bbed4bca3928c98975daf2c8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:13 +0100 -Subject: [PATCH 73/82] dt-bindings: arm: AT91: document EV23X71A board +Subject: [PATCH 73/86] dt-bindings: arm: AT91: document EV23X71A board Microchip EV23X71A board is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch b/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch index 051cdc942..e696bec02 100644 --- a/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch +++ b/patches/linux/6.18.56/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch @@ -1,7 +1,7 @@ From 45fd43a305c595ea24b0de5cf090affeffc4029f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:21 +0200 -Subject: [PATCH 74/82] net: dsa: Skip DCB default priority init on unsupported +Subject: [PATCH 74/86] 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.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch b/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch index 066055a92..33b956935 100644 --- a/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch +++ b/patches/linux/6.18.56/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch @@ -1,7 +1,7 @@ From cb31150945889f02373febe89f618afee36c6e62 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:51:07 +0200 -Subject: [PATCH 75/82] net: dsa: Generalise the global DCB APP mirroring +Subject: [PATCH 75/86] 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.56/0076-net-dsa-Support-the-PCP-APP-selector.patch b/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch index 328d668f7..605823822 100644 --- a/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch +++ b/patches/linux/6.18.56/0076-net-dsa-Support-the-PCP-APP-selector.patch @@ -1,7 +1,7 @@ From e1a8005d2fc1813b71e65c79e700da5635e639e0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:22 +0200 -Subject: [PATCH 76/82] net: dsa: Support the PCP APP selector +Subject: [PATCH 76/86] 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.56/0077-net-dsa-Support-DCB-priority-rewrite.patch b/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch index 62cb65da0..e3760a847 100644 --- a/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch +++ b/patches/linux/6.18.56/0077-net-dsa-Support-DCB-priority-rewrite.patch @@ -1,7 +1,7 @@ From fb3ca7803ea876f7d96514696615143835bc78f9 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:10:32 +0200 -Subject: [PATCH 77/82] net: dsa: Support DCB priority rewrite +Subject: [PATCH 77/86] 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.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch b/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch index 79e3c394c..a6cd10f78 100644 --- a/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch +++ b/patches/linux/6.18.56/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch @@ -1,7 +1,7 @@ From 46e44f7a4aba62dc94958e01638e8e93ea62e59b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:53:23 +0200 -Subject: [PATCH 78/82] net: dsa: Support the IEEE ETS managed object +Subject: [PATCH 78/86] 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.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch b/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch index f73200a0e..1de8c4805 100644 --- a/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch +++ b/patches/linux/6.18.56/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch @@ -1,7 +1,7 @@ From 308c9dbab764bef06cceb03e412b491ea57234b5 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 7 Oct 2026 11:42:12 +0200 -Subject: [PATCH 79/82] net: sparx5: fix sleep in atomic context in MAC table +Subject: [PATCH 79/86] net: sparx5: fix sleep in atomic context in MAC table access Adding or removing a multicast address on an unbridged switch port diff --git a/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch b/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch index b8bb033b9..91e66a5b8 100644 --- a/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch +++ b/patches/linux/6.18.56/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch @@ -1,7 +1,7 @@ From c6f8b6a050e69c951ce0ce56467b6e9fd9a638f1 Mon Sep 17 00:00:00 2001 From: Steen Hegelund Date: Wed, 21 Jan 2026 10:09:45 +0100 -Subject: [PATCH 80/82] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) +Subject: [PATCH 80/86] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) Expose the switch register space of Microchip Sparx5 family SoCs through /sys/kernel/debug/symreg/mem, for the symreg tool, which diff --git a/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch b/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch index 01c8f54a4..4119c93b2 100644 --- a/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch +++ b/patches/linux/6.18.56/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch @@ -1,7 +1,7 @@ From e0e6e024e5e5866ff61b68bf9790fd24e542d7e2 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 5 Oct 2026 14:37:56 +0200 -Subject: [PATCH 81/82] net: ethernet: mtk_wed: map WO memory regions without +Subject: [PATCH 81/86] net: ethernet: mtk_wed: map WO memory regions without requesting them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch b/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch index 37c42e754..e9b8b07da 100644 --- a/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch +++ b/patches/linux/6.18.56/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch @@ -1,7 +1,7 @@ From 8c40b743cd6f8117c177291988ffe1980e9595cd Mon Sep 17 00:00:00 2001 From: Zhi-Jun You Date: Wed, 15 Jul 2026 23:21:12 +0800 -Subject: [PATCH 82/82] wifi: mt76: wed: fix kernel panic on non-DBDC MT7986 +Subject: [PATCH 82/86] wifi: mt76: wed: fix kernel panic on non-DBDC MT7986 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git a/patches/linux/6.18.56/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch b/patches/linux/6.18.56/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch new file mode 100644 index 000000000..e91a7477f --- /dev/null +++ b/patches/linux/6.18.56/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch @@ -0,0 +1,44 @@ +From 9b5a788cc9a12f1b23ad0e84dfaa78008a399b49 Mon Sep 17 00:00:00 2001 +From: Mattias Walström +Date: Wed, 7 Oct 2026 22:20:00 +0200 +Subject: [PATCH 83/86] wifi: mt76: mt7915: set up WPDMA RX ring 0 for WED on a + band1-only device +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +On a single-band MT7986 wmac bound to band 1, next to a PCIe card for +the other bands, the band0 RX data queue is never allocated, so the +WED is attached with WPDMA RX ring 0 unconfigured while it drives ring +0 and 1 alike. On a BPI-R3 that ends in a stall of ring 1 after some +thousand frames: no descriptor armed, the WED's index two ahead of the +WPDMA's, the MAC counting RX FIFO full, and clients associating without +any data getting through. Allocate the band0 queue when WED does RX, +so the WED finds a valid, idle ring 0. + +Signed-off-by: Mattias Walström +--- + drivers/net/wireless/mediatek/mt76/mt7915/dma.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/dma.c b/drivers/net/wireless/mediatek/mt76/mt7915/dma.c +index 009ef713f4379..851931ae55530 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/dma.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/dma.c +@@ -506,8 +506,14 @@ int mt7915_dma_init(struct mt7915_dev *dev, struct mt7915_phy *phy2) + if (ret) + return ret; + +- /* rx data queue for band0 */ +- if (!dev->phy.mt76->band_idx) { ++ /* rx data queue for band0. Also on a device whose only phy is ++ * band1 when WED does RX: the WED drives WPDMA RX ring 0 and 1 ++ * alike, and with ring 0 never set up it stalls on ring 1 after ++ * a while, the ring sits with no descriptor armed and the MAC ++ * counts RX FIFO full. Give it an empty but valid ring 0. */ ++ if (!dev->phy.mt76->band_idx || ++ (mtk_wed_device_active(&mdev->mmio.wed) && ++ mtk_wed_get_rx_capa(&mdev->mmio.wed))) { + if (mtk_wed_device_active(&mdev->mmio.wed) && + mtk_wed_get_rx_capa(&mdev->mmio.wed)) { + mdev->q_rx[MT_RXQ_MAIN].flags = diff --git a/patches/linux/6.18.56/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch b/patches/linux/6.18.56/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch new file mode 100644 index 000000000..e390eb26d --- /dev/null +++ b/patches/linux/6.18.56/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch @@ -0,0 +1,431 @@ +From a39e22f32c005cb7096e66789000b2a9209444d0 Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Mon, 20 Oct 2025 15:59:12 +0200 +Subject: [PATCH 84/86] wifi: mt76: mt7915: add WDS support when WED is enabled + +The current WED only supports 256 wcid, whereas mt7986 can support up to +512 entries, so firmware provides a rule to get sta_info by DA when wcid +is set to 0x3ff by txd. Also, WED provides a register to overwrite txd +wcid, that is, wcid[9:8] can be overwritten by 0x3 and wcid[7:0] is set +to 0xff by host driver. + +However, firmware is unable to get sta_info from DA as DA != RA for +4addr cases, so firmware and wifi host driver both use wcid (256 - 271) +and (768 ~ 783) for sync up to get correct sta_info. + +Currently WDS+WED config is completely broken on MT7986/7981 devices if +without this patch. + +Signed-off-by: Bo Jiao +Signed-off-by: Sujuan Chen +Signed-off-by: Shengyu Qu +Signed-off-by: Felix Fietkau +--- + drivers/net/wireless/mediatek/mt76/mt76.h | 3 + + .../wireless/mediatek/mt76/mt7915/debugfs.c | 2 +- + .../net/wireless/mediatek/mt76/mt7915/mac.c | 2 +- + .../net/wireless/mediatek/mt76/mt7915/main.c | 56 +++++++++++++++++-- + .../net/wireless/mediatek/mt76/mt7915/mcu.c | 55 +++++++++++------- + .../net/wireless/mediatek/mt76/mt7915/mcu.h | 1 + + .../wireless/mediatek/mt76/mt7915/mt7915.h | 16 +++++- + drivers/net/wireless/mediatek/mt76/util.c | 13 ++++- + drivers/net/wireless/mediatek/mt76/util.h | 7 ++- + 9 files changed, 122 insertions(+), 33 deletions(-) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h +index 2a186b47659b6..9cb7e2da472fc 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt76.h ++++ b/drivers/net/wireless/mediatek/mt76/mt76.h +@@ -29,6 +29,9 @@ + + #define MT76_TOKEN_FREE_THR 64 + ++#define MT76_WED_WDS_MIN 256 ++#define MT76_WED_WDS_MAX 272 ++ + #define MT_QFLAG_WED_RING GENMASK(1, 0) + #define MT_QFLAG_WED_TYPE GENMASK(4, 2) + #define MT_QFLAG_WED BIT(5) +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c +index b287b7d9394e2..ec2287459587e 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c +@@ -1385,7 +1385,7 @@ static ssize_t mt7915_sta_fixed_rate_set(struct file *file, + + out: + vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv); +- ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &phy, field); ++ ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &msta->wcid, &phy, field); + if (ret) + return -EFAULT; + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +index dda4e7d179536..b431467c0eeee 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +@@ -1972,7 +1972,7 @@ void mt7915_mac_sta_rc_work(struct work_struct *work) + if (changed & (IEEE80211_RC_SUPP_RATES_CHANGED | + IEEE80211_RC_NSS_CHANGED | + IEEE80211_RC_BW_CHANGED)) +- mt7915_mcu_add_rate_ctrl(dev, vif, sta, true); ++ mt7915_mcu_add_rate_ctrl(dev, vif, sta, &msta->wcid, true); + + if (changed & IEEE80211_RC_SMPS_CHANGED) + mt7915_mcu_add_smps(dev, vif, sta); +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c +index 9d546eb8022ac..eb2dc410bba25 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c +@@ -758,7 +758,14 @@ int mt7915_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, + bool ext_phy = mvif->phy != &dev->phy; + int idx; + +- idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7915_WTBL_STA); ++ if (mtk_wed_device_active(&dev->mt76.mmio.wed) && ++ !is_mt7915(&dev->mt76) && ++ test_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags)) ++ idx = __mt76_wcid_alloc(mdev->wcid_mask, MT76_WED_WDS_MIN, ++ MT76_WED_WDS_MAX); ++ else ++ idx = mt76_wcid_alloc(mdev->wcid_mask, MT7915_WTBL_STA); ++ + if (idx < 0) + return -ENOSPC; + +@@ -847,7 +854,7 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, + addr = mt7915_mac_wtbl_lmac_addr(dev, msta->wcid.idx, 30); + mt76_rmw_field(dev, addr, GENMASK(7, 0), 0xa0); + +- ret = mt7915_mcu_add_rate_ctrl(dev, vif, sta, false); ++ ret = mt7915_mcu_add_rate_ctrl(dev, vif, sta, &msta->wcid, false); + if (ret) + return ret; + +@@ -1278,6 +1285,40 @@ mt7915_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + return 0; + } + ++static void mt7915_sta_wed_set_4addr(struct mt7915_dev *dev, struct ieee80211_vif *vif, ++ struct ieee80211_sta *sta) ++{ ++ struct mt7915_sta *msta = (struct mt7915_sta *)sta->drv_priv; ++ int min = MT76_WED_WDS_MIN, max = MT76_WED_WDS_MAX; ++ int idx, prev_idx = msta->wcid.idx; ++ struct mt76_wcid wcid = msta->wcid; ++ int state; ++ ++ if (!is_mt7915(&dev->mt76)) ++ return; ++ ++ if (msta->wcid.idx >= min && msta->wcid.idx < max) ++ return; ++ ++ idx = __mt76_wcid_alloc(dev->mt76.wcid_mask, min, max); ++ if (idx < 0) ++ return; ++ ++ wcid.idx = idx; ++ state = msta->wcid.sta ? CONN_STATE_PORT_SECURE : CONN_STATE_DISCONNECT; ++ __mt7915_mcu_add_sta(dev, vif, sta, &wcid, state, true); ++ mt7915_mcu_add_rate_ctrl(dev, vif, sta, &wcid, false); ++ rcu_assign_pointer(dev->mt76.wcid[idx], &msta->wcid); ++ msta->wcid.idx = idx; ++ ++ synchronize_rcu(); ++ ++ rcu_assign_pointer(dev->mt76.wcid[prev_idx], NULL); ++ mt76_wcid_mask_clear(dev->mt76.wcid_mask, prev_idx); ++ wcid.idx = prev_idx; ++ __mt7915_mcu_add_sta(dev, vif, sta, &wcid, CONN_STATE_DISCONNECT, false); ++} ++ + static void mt7915_sta_set_4addr(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, +@@ -1291,6 +1332,9 @@ static void mt7915_sta_set_4addr(struct ieee80211_hw *hw, + else + clear_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags); + ++ if (mtk_wed_device_active(&dev->mt76.mmio.wed) && enabled) ++ mt7915_sta_wed_set_4addr(dev, vif, sta); ++ + if (!msta->wcid.sta) + return; + +@@ -1740,15 +1784,19 @@ mt7915_net_fill_forward_path(struct ieee80211_hw *hw, + if (!mtk_wed_device_active(wed)) + return -ENODEV; + +- if (msta->wcid.idx > 0xff) ++ if (msta->wcid.idx > MT7915_WTBL_STA) + return -EIO; + + path->type = DEV_PATH_MTK_WDMA; + path->dev = ctx->dev; + path->mtk_wdma.wdma_idx = wed->wdma_idx; + path->mtk_wdma.bss = mvif->mt76.idx; +- path->mtk_wdma.wcid = is_mt7915(&dev->mt76) ? msta->wcid.idx : 0x3ff; + path->mtk_wdma.queue = phy->mt76->band_idx; ++ if (test_bit(MT_WCID_FLAG_4ADDR, &msta->wcid.flags) || ++ is_mt7915(&dev->mt76)) ++ path->mtk_wdma.wcid = msta->wcid.idx; ++ else ++ path->mtk_wdma.wcid = 0x3ff; + + ctx->dev = NULL; + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +index 29b3f0cda1ce7..1ecce494bb09c 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +@@ -1367,6 +1367,7 @@ mt7915_mcu_get_mmps_mode(enum ieee80211_smps_mode smps) + int mt7915_mcu_set_fixed_rate_ctrl(struct mt7915_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, ++ struct mt76_wcid *wcid, + void *data, u32 field) + { + struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; +@@ -1438,7 +1439,7 @@ int mt7915_mcu_add_smps(struct mt7915_dev *dev, struct ieee80211_vif *vif, + if (ret) + return ret; + +- return mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, NULL, ++ return mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &msta->wcid, NULL, + RATE_PARAM_MMPS_UPDATE); + } + +@@ -1447,17 +1448,19 @@ mt7915_mcu_set_spe_idx(struct mt7915_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) + { + struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; ++ struct mt7915_sta *msta = (struct mt7915_sta *)sta->drv_priv; + struct mt76_phy *mphy = mvif->phy->mt76; + u8 spe_idx = mt76_connac_spe_idx(mphy->antenna_mask); + +- return mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &spe_idx, +- RATE_PARAM_SPE_UPDATE); ++ return mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &msta->wcid, ++ &spe_idx, RATE_PARAM_SPE_UPDATE); + } + + static int + mt7915_mcu_add_rate_ctrl_fixed(struct mt7915_dev *dev, + struct ieee80211_vif *vif, +- struct ieee80211_sta *sta) ++ struct ieee80211_sta *sta, ++ struct mt76_wcid *wcid) + { + struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; + struct cfg80211_chan_def *chandef = &mvif->phy->mt76->chandef; +@@ -1505,7 +1508,7 @@ mt7915_mcu_add_rate_ctrl_fixed(struct mt7915_dev *dev, + + /* fixed single rate */ + if (nrates == 1) { +- ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &phy, ++ ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, wcid, &phy, + RATE_PARAM_FIXED_MCS); + if (ret) + return ret; +@@ -1527,7 +1530,7 @@ mt7915_mcu_add_rate_ctrl_fixed(struct mt7915_dev *dev, + else + mt76_rmw_field(dev, addr, GENMASK(15, 12), phy.sgi); + +- ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &phy, ++ ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, wcid, &phy, + RATE_PARAM_FIXED_GI); + if (ret) + return ret; +@@ -1535,7 +1538,7 @@ mt7915_mcu_add_rate_ctrl_fixed(struct mt7915_dev *dev, + + /* fixed HE_LTF */ + if (mask->control[band].he_ltf != GENMASK(7, 0)) { +- ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, &phy, ++ ret = mt7915_mcu_set_fixed_rate_ctrl(dev, vif, sta, wcid, &phy, + RATE_PARAM_FIXED_HE_LTF); + if (ret) + return ret; +@@ -1648,15 +1651,14 @@ mt7915_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7915_dev *dev, + } + + int mt7915_mcu_add_rate_ctrl(struct mt7915_dev *dev, struct ieee80211_vif *vif, +- struct ieee80211_sta *sta, bool changed) ++ struct ieee80211_sta *sta, struct mt76_wcid *wcid, ++ bool changed) + { + struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; +- struct mt7915_sta *msta = (struct mt7915_sta *)sta->drv_priv; + struct sk_buff *skb; + int ret; + +- skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76, +- &msta->wcid); ++ skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76, wcid); + if (IS_ERR(skb)) + return PTR_ERR(skb); + +@@ -1681,7 +1683,7 @@ int mt7915_mcu_add_rate_ctrl(struct mt7915_dev *dev, struct ieee80211_vif *vif, + * and updates as peer fixed rate parameters, which overrides + * sta_rec_ra and firmware rate control algorithm. + */ +- return mt7915_mcu_add_rate_ctrl_fixed(dev, vif, sta); ++ return mt7915_mcu_add_rate_ctrl_fixed(dev, vif, sta, wcid); + } + + static int +@@ -1712,8 +1714,9 @@ mt7915_mcu_add_group(struct mt7915_dev *dev, struct ieee80211_vif *vif, + sizeof(req), true); + } + +-int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, +- struct ieee80211_sta *sta, int conn_state, bool newly) ++int __mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, ++ struct ieee80211_sta *sta, struct mt76_wcid *wcid, ++ int conn_state, bool newly) + { + struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; + struct ieee80211_link_sta *link_sta; +@@ -1724,8 +1727,10 @@ int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, + msta = sta ? (struct mt7915_sta *)sta->drv_priv : &mvif->sta; + link_sta = sta ? &sta->deflink : NULL; + +- skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76, +- &msta->wcid); ++ if (!wcid) ++ wcid = &msta->wcid; ++ ++ skb = mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76, wcid); + if (IS_ERR(skb)) + return PTR_ERR(skb); + +@@ -2423,10 +2428,20 @@ int mt7915_mcu_init_firmware(struct mt7915_dev *dev) + + mt76_connac_mcu_del_wtbl_all(&dev->mt76); + +- if ((mtk_wed_device_active(&dev->mt76.mmio.wed) && +- is_mt7915(&dev->mt76)) || +- !mtk_wed_get_rx_capa(&dev->mt76.mmio.wed)) +- mt7915_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(CAPABILITY), 0, 0, 0); ++#if IS_ENABLED(CONFIG_NET_MEDIATEK_SOC_WED) ++ if (mtk_wed_device_active(&dev->mt76.mmio.wed)) { ++ if (is_mt7915(&dev->mt76) || ++ !mtk_wed_get_rx_capa(&dev->mt76.mmio.wed)) ++ ret = mt7915_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(CAPABILITY), ++ 0, 0, 0); ++ else ++ ret = mt7915_mcu_wa_cmd(dev, MCU_WA_PARAM_CMD(SET), ++ MCU_WA_PARAM_WED_VERSION, ++ dev->mt76.mmio.wed.rev_id, 0); ++ if (ret) ++ return ret; ++ } ++#endif + + ret = mt7915_mcu_set_mwds(dev, 1); + if (ret) +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h +index 4c25abb0297de..6ffa5ea5bc0d6 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h +@@ -278,6 +278,7 @@ enum { + MCU_WA_PARAM_PDMA_RX = 0x04, + MCU_WA_PARAM_CPU_UTIL = 0x0b, + MCU_WA_PARAM_RED = 0x0e, ++ MCU_WA_PARAM_WED_VERSION = 0x32, + MCU_WA_PARAM_RED_SETTING = 0x40, + }; + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +index 27857347f05f4..cc56ef27308c0 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +@@ -475,8 +475,16 @@ int mt7915_mcu_add_dev_info(struct mt7915_phy *phy, + struct ieee80211_vif *vif, bool enable); + int mt7915_mcu_add_bss_info(struct mt7915_phy *phy, + struct ieee80211_vif *vif, int enable); +-int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, +- struct ieee80211_sta *sta, int conn_state, bool newly); ++int __mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, ++ struct ieee80211_sta *sta, struct mt76_wcid *wcid, ++ int conn_state, bool newly); ++static inline int ++mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, ++ struct ieee80211_sta *sta, int conn_state, bool newly) ++{ ++ return __mt7915_mcu_add_sta(dev, vif, sta, NULL, conn_state, newly); ++} ++ + int mt7915_mcu_add_tx_ba(struct mt7915_dev *dev, + struct ieee80211_ampdu_params *params, + bool add); +@@ -494,7 +502,8 @@ int mt7915_mcu_set_protection(struct mt7915_phy *phy, struct ieee80211_vif *vif, + int mt7915_mcu_add_obss_spr(struct mt7915_phy *phy, struct ieee80211_vif *vif, + struct ieee80211_he_obss_pd *he_obss_pd); + int mt7915_mcu_add_rate_ctrl(struct mt7915_dev *dev, struct ieee80211_vif *vif, +- struct ieee80211_sta *sta, bool changed); ++ struct ieee80211_sta *sta, struct mt76_wcid *wcid, ++ bool changed); + int mt7915_mcu_add_smps(struct mt7915_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); + int mt7915_set_channel(struct mt76_phy *mphy); +@@ -504,6 +513,7 @@ int mt7915_mcu_update_edca(struct mt7915_dev *dev, void *req); + int mt7915_mcu_set_fixed_rate_ctrl(struct mt7915_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, ++ struct mt76_wcid *wcid, + void *data, u32 field); + int mt7915_mcu_set_eeprom(struct mt7915_dev *dev); + int mt7915_mcu_get_eeprom(struct mt7915_dev *dev, u32 offset, u8 *read_buf); +diff --git a/drivers/net/wireless/mediatek/mt76/util.c b/drivers/net/wireless/mediatek/mt76/util.c +index 97249ebb4bc8f..6ac7cfdd8786f 100644 +--- a/drivers/net/wireless/mediatek/mt76/util.c ++++ b/drivers/net/wireless/mediatek/mt76/util.c +@@ -42,12 +42,19 @@ bool ____mt76_poll_msec(struct mt76_dev *dev, u32 offset, u32 mask, u32 val, + } + EXPORT_SYMBOL_GPL(____mt76_poll_msec); + +-int mt76_wcid_alloc(u32 *mask, int size) ++int __mt76_wcid_alloc(u32 *mask, int min, int size) + { ++ u32 min_mask = ~0; + int i, idx = 0, cur; + ++ mask += min / 32; ++ min %= 32; ++ if (min > 0) ++ min_mask = ~((1 << min) - 1); ++ + for (i = 0; i < DIV_ROUND_UP(size, 32); i++) { +- idx = ffs(~mask[i]); ++ idx = ffs(~mask[i] & min_mask); ++ min_mask = ~0; + if (!idx) + continue; + +@@ -62,7 +69,7 @@ int mt76_wcid_alloc(u32 *mask, int size) + + return -1; + } +-EXPORT_SYMBOL_GPL(mt76_wcid_alloc); ++EXPORT_SYMBOL_GPL(__mt76_wcid_alloc); + + int mt76_get_min_avg_rssi(struct mt76_dev *dev, u8 phy_idx) + { +diff --git a/drivers/net/wireless/mediatek/mt76/util.h b/drivers/net/wireless/mediatek/mt76/util.h +index 260965dde94cf..68432fce2f56a 100644 +--- a/drivers/net/wireless/mediatek/mt76/util.h ++++ b/drivers/net/wireless/mediatek/mt76/util.h +@@ -27,7 +27,12 @@ enum { + #define MT76_INCR(_var, _size) \ + (_var = (((_var) + 1) % (_size))) + +-int mt76_wcid_alloc(u32 *mask, int size); ++int __mt76_wcid_alloc(u32 *mask, int min, int size); ++ ++static inline int mt76_wcid_alloc(u32 *mask, int size) ++{ ++ return __mt76_wcid_alloc(mask, 0, size); ++} + + static inline void + mt76_wcid_mask_set(u32 *mask, int idx) diff --git a/patches/linux/6.18.56/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch b/patches/linux/6.18.56/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch new file mode 100644 index 000000000..cceaca7b9 --- /dev/null +++ b/patches/linux/6.18.56/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch @@ -0,0 +1,49 @@ +From 3d86eb80be2c4367e3293e783d26b6b452840287 Mon Sep 17 00:00:00 2001 +From: Shiji Yang +Date: Wed, 19 Aug 2026 21:07:07 +0800 +Subject: [PATCH 85/86] net: ethernet: mtk_wed: increase WED v2 WDMA RESV_BUFF + to 0x80 + +Change WDMA RESV_BUFF from 0x40 to 0x80 to avoid CDM TX FIFO overflow. +Without this patch mt7986 and mt7981 may have WDMA TX hang issue. This +patch was pulled from mtk-openwrt-feeds GPL open source project. + +Link: https://github.com/mediatek/mtk-openwrt-feeds/commit/07c87502e854b68b48544d101b6fe17ec059b97b +Signed-off-by: Shiji Yang +Reviewed-by: Simon Horman +Acked-by: Lorenzo Bianconi +Link: https://patch.msgid.link/OSZPR01MB779537889255E2F606E47EABBCA52@OSZPR01MB7795.jpnprd01.prod.outlook.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/ethernet/mediatek/mtk_wed.c | 5 +++++ + drivers/net/ethernet/mediatek/mtk_wed_regs.h | 1 + + 2 files changed, 6 insertions(+) + +diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c +index 1ed1f88dd7f8b..fdb487e5d46e1 100644 +--- a/drivers/net/ethernet/mediatek/mtk_wed.c ++++ b/drivers/net/ethernet/mediatek/mtk_wed.c +@@ -2071,6 +2071,11 @@ mtk_wed_dma_enable(struct mtk_wed_device *dev) + wdma_set(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN); + } + ++ if (mtk_wed_is_v2(dev->hw)) ++ wdma_m32(dev, MTK_WDMA_GLO_CFG, ++ MTK_WDMA_GLO_CFG_RESV_BUFF, ++ FIELD_PREP(MTK_WDMA_GLO_CFG_RESV_BUFF, 0x80)); ++ + wed_set(dev, MTK_WED_GLO_CFG, + MTK_WED_GLO_CFG_TX_DMA_EN | + MTK_WED_GLO_CFG_RX_DMA_EN); +diff --git a/drivers/net/ethernet/mediatek/mtk_wed_regs.h b/drivers/net/ethernet/mediatek/mtk_wed_regs.h +index c71190924816c..e5f83100ded52 100644 +--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h ++++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h +@@ -433,6 +433,7 @@ struct mtk_wdma_desc { + #define MTK_WDMA_GLO_CFG_TX_DMA_BUSY BIT(1) + #define MTK_WDMA_GLO_CFG_RX_DMA_EN BIT(2) + #define MTK_WDMA_GLO_CFG_RX_DMA_BUSY BIT(3) ++#define MTK_WDMA_GLO_CFG_RESV_BUFF GENMASK(23, 16) + #define MTK_WDMA_GLO_CFG_RX_INFO3_PRERES BIT(26) + #define MTK_WDMA_GLO_CFG_RX_INFO2_PRERES BIT(27) + #define MTK_WDMA_GLO_CFG_RX_INFO1_PRERES BIT(28) diff --git a/patches/linux/6.18.56/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch b/patches/linux/6.18.56/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch new file mode 100644 index 000000000..6a67d8d83 --- /dev/null +++ b/patches/linux/6.18.56/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch @@ -0,0 +1,54 @@ +From 57ac316f70b79c0e2709e6221aca6b9026f8ce1c Mon Sep 17 00:00:00 2001 +From: Ryan Leung +Date: Fri, 21 Aug 2026 09:00:01 +0000 +Subject: [PATCH 86/86] wifi: mt76: mt7915: publish wcid before MCU add + commands + +mt7915_add_interface() enables the BSS/STA in firmware via +mt7915_mcu_add_bss_info() and mt7915_mcu_add_sta() before publishing +dev->mt76.wcid[idx] with rcu_assign_pointer(). Firmware can start +generating tx-status/tx-free events referencing that wcid as soon as it +processes those MCU commands, but mt76's rx/tx-free handlers (e.g. +mt7915_mac_tx_free()) look up dev->mt76.wcid[idx] to service them, and +won't find it published yet. This can lead to memory corruption and +kernel crashes shortly after an AP interface is brought up. + +This ordering was introduced by commit 8e3e7567b8c1 ("mt76: mt7915: add +sta_rec with EXTRA_INFO_NEW for the first time only"): +mt7915_mcu_add_sta() derived its "newly added" flag from +!rcu_access_pointer(dev->mt76.wcid[idx]), so the publish had to happen +after that call. Commit 33eb14f10290 ("wifi: mt76: mt7915: use mac80211 +.sta_state op") later replaced that flag with a caller-supplied `newly` +argument to mt7915_mcu_add_sta(), now simply hardcoded to true, so the +ordering is no longer required. + +Move the rcu_assign_pointer() before the MCU add_bss_info/add_sta +calls, so that the wcid is visible to lookups before firmware is told +it's live. This makes the ordering symmetric with +mt7915_remove_interface(), which keeps the pointer published until +after the MCU disable commands have been issued. + +Closes: https://github.com/openwrt/openwrt/issues/24594 +Fixes: 8e3e7567b8c1 ("mt76: mt7915: add sta_rec with EXTRA_INFO_NEW for the first time only") +Signed-off-by: Ryan Leung +Link: https://patch.msgid.link/20260821-mt7915-wcid-publish-order-v2-1-acf0b7f32cbd@protonmail.com +Signed-off-by: Felix Fietkau +--- + drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c +index eb2dc410bba25..1f91e56b288f4 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c +@@ -273,9 +273,9 @@ static int mt7915_add_interface(struct ieee80211_hw *hw, + mt7915_init_bitrate_mask(vif); + memset(&mvif->cap, -1, sizeof(mvif->cap)); + ++ rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid); + mt7915_mcu_add_bss_info(phy, vif, true); + mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_PORT_SECURE, true); +- rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid); + + mutex_unlock(&dev->mt76.mutex); + From 5f75bdfe2a7960e7e478291683aaf54a6ac9bc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 2 Oct 2026 10:47:14 +0200 Subject: [PATCH 35/45] board: bpi-r3: Enable WED in mt7915e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies to every MT7986 board built from this BSP: BPI-R3, BPI-R3 Mini and the Acer Connect Vero W6m. Signed-off-by: Mattias Walström --- .../bananapi-bpi-r3/rootfs/etc/modprobe.d/mt7915e-wed.conf | 2 ++ doc/ChangeLog.md | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 board/aarch64/bananapi-bpi-r3/rootfs/etc/modprobe.d/mt7915e-wed.conf 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..2d323064b --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/etc/modprobe.d/mt7915e-wed.conf @@ -0,0 +1,2 @@ +# Wireless Ethernet Dispatch, the WiFi hardware offload path +options mt7915e wed_enable=1 diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index e61e68c15..b54ecc335 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -13,6 +13,8 @@ All notable changes to the project are documented in this file. with `wds` enabled can be a bridge port, and an access point bridges each remote station through a `wds-link` interface, see [WDS Backhaul and Repeaters][wds] +- MT7986 boards (Banana Pi BPI-R3, BPI-R3 Mini, Acer Connect Vero W6m): + WiFi hardware offloading is now active - Add IPv6 dynamic routing: RIPng and OSPFv3. Both reuse the existing ietf-rip and ietf-ospf models, selected per control-plane-protocol by the `ripng`/`ospfv3` type and the IPv6 address-family From 661df01d1cf0ad004ac93139bf3dc6c44425a4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 9 Oct 2026 21:00:54 +0200 Subject: [PATCH 36/45] onieprom: Fix encoding JSON to a TLV The encode and decode functions were never imported in the CLI entry point. --- src/onieprom/onieprom/__main__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/onieprom/onieprom/__main__.py b/src/onieprom/onieprom/__main__.py index f3d581b4d..b08206d6e 100644 --- a/src/onieprom/onieprom/__main__.py +++ b/src/onieprom/onieprom/__main__.py @@ -4,6 +4,8 @@ def main(): import os import sys + from onieprom import into_tlv, from_tlv + parser = argparse.ArgumentParser(prog='onieprom') parser.add_argument("infile", nargs="?", default=sys.stdin, type=argparse.FileType("rb", 0)) @@ -30,9 +32,15 @@ def main(): sys.exit(1) if args.encode: - args.outfile.buffer.write(into_tlv(json.load(args.infile))) + out = getattr(args.outfile, "buffer", args.outfile) + out.write(into_tlv(json.load(args.infile))) else: - args.outfile.write(json.dumps(from_tlv(args.infile))) + out = args.outfile + data = json.dumps(from_tlv(args.infile)) + if hasattr(out, "buffer"): + out.write(data) + else: + out.write(data.encode()) if __name__ == "__main__": main() From 9d6717210344719a1b84000e4ba081a0c5e6b45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 9 Oct 2026 21:00:54 +0200 Subject: [PATCH 37/45] probe: Read the product VPD from a factory partition SBCs have no VPD EEPROM, so they boot with a random base MAC and a new hostname every time. Fall back to an ONIE TLV at the start of the partition labelled factory, never trusted since anyone with the disk can write it. --- board/aarch64/bananapi-bpi-r3/README.md | 30 ++++++++++++++++ .../rootfs/usr/libexec/infix/init.d/00-probe | 35 +++++++++++++++++++ doc/ChangeLog.md | 3 ++ doc/vpd.md | 23 ++++++++++++ 4 files changed, 91 insertions(+) diff --git a/board/aarch64/bananapi-bpi-r3/README.md b/board/aarch64/bananapi-bpi-r3/README.md index 91a1aaf47..b21fda14f 100644 --- a/board/aarch64/bananapi-bpi-r3/README.md +++ b/board/aarch64/bananapi-bpi-r3/README.md @@ -212,6 +212,36 @@ sync 2. Set boot switches to eMMC mode (see image above) 3. Power on +## Giving the Board a Permanent MAC Address + +The BPI-R3 family has no EEPROM with a factory-programmed MAC address. +Out of the box every Ethernet port therefore gets a random address on +each boot, and the hostname, which is derived from the base address, +changes with it. Worse, both WiFi radios carry the same MediaTek +default address on every board, so two boards cannot even connect to +each other over WiFi: a client refuses to authenticate to an access +point that has its own address. + +The eMMC image carries an empty `factory` partition for this purpose. +Write a product record to it once, from the Infix shell, and the board +keeps that identity across reboots and upgrades: + +``` +echo '{"mac-address":"02:00:00:ba:01:00","serial-number":"banana1","vendor":"Bananapi","product-name":"BPI-R3"}' \ + | onieprom -e | dd of=/dev/disk/by-partlabel/factory +``` + +Give each board its own base address and serial number, then reboot. +The base address becomes the hostname suffix, the wired ports get base ++ 1, base + 2, and so on in name order, and the WiFi interfaces get +addresses derived from the base. With the record above the board is +named `bpi-ba-01-00`, `lan1` is `02:00:00:ba:01:02` and `wifi0` is +`06:00:00:ba:01:00`. + +A locally administered address like `02:xx:xx:xx:xx:xx` is fine for a +lab. For boards that will share a network with other equipment, use a +range you own. See [Vital Product Data](../../../doc/vpd.md#factory-partition) +for the record format and what else it can carry. ## Troubleshooting diff --git a/board/common/rootfs/usr/libexec/infix/init.d/00-probe b/board/common/rootfs/usr/libexec/infix/init.d/00-probe index e2054cefb..d41c196de 100755 --- a/board/common/rootfs/usr/libexec/infix/init.d/00-probe +++ b/board/common/rootfs/usr/libexec/infix/init.d/00-probe @@ -480,11 +480,46 @@ def probe_dmisystem(out): return 0 +FACTORY_PART = "/dev/disk/by-partlabel/factory" + + +def factory_partition_vpd(): + """Product VPD from an ONIE TLV at the start of the factory partition. + + Boards without a VPD EEPROM, e.g. SBCs, can be given an identity by + writing a TLV to a partition labelled "factory". Anyone with write + access to the disk can change it, so it is never trusted. + """ + if not os.path.exists(FACTORY_PART): + return None + + try: + with open(FACTORY_PART, "rb", 0) as f: + data = onieprom.from_tlv(f) + except Exception: # pylint: disable=broad-except + return None + + if not data: + return None + + return { + "board": "product", + "available": True, + "trusted": False, + "data": data, + } + + def probe_dtsystem(out): """Probe DTS based system, expects a VPD in ONIE PROM format.""" dtsys = DTSystem() vpds = dtsys.infix_vpds() + if not any(vpd["board"] == "product" and vpd["available"] for vpd in vpds): + vpd = factory_partition_vpd() + if vpd: + vpds.append(vpd) + model = dtsys.base.str("model") if model: out["product-name"] = model diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index b54ecc335..bd0447221 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -9,6 +9,9 @@ All notable changes to the project are documented in this file. ### Changes - Upgrade Linux kernel to 6.18.56 (LTS) +- Boards without a VPD EEPROM can be given a fixed identity, base MAC + included, by writing an ONIE TLV to a disk partition labelled `factory`, + see [VPD documentation](vpd.md#factory-partition) - WiFi 4-address (WDS) links for wireless bridges and repeaters: a station with `wds` enabled can be a bridge port, and an access point bridges each remote station through a `wds-link` interface, see diff --git a/doc/vpd.md b/doc/vpd.md index 6adf21be8..7c0e756e2 100644 --- a/doc/vpd.md +++ b/doc/vpd.md @@ -54,6 +54,29 @@ Every VPD is listed in the _ietf-hardware_ model as a component named `vpd-`, e.g., `vpd-product` for the example above. +## Factory Partition + +Boards without a VPD EEPROM, such as single board computers, boot with +a random MAC address on every port and the vendor's default address on +every WiFi radio. Two such boards of the same model cannot even talk +to each other over WiFi, since both radios claim the same address. + +Such a board can be given an identity by writing an ONIE TLV to the +start of a disk partition labelled `factory`. The system reads it +when the device tree provides no product VPD. The `onieprom` tool +encodes the JSON form described [below](#json-encoding): + +``` +echo '{"mac-address":"02:00:00:ba:01:00","serial-number":"banana1","vendor":"Bananapi","product-name":"BPI-R3"}' \ + | onieprom -e | dd of=/dev/disk/by-partlabel/factory +``` + +A VPD read this way is never trusted, so it can only provide identity, +never the factory password. + +The image for the Banana Pi BPI-R3 family already carries an empty +`factory` partition for this purpose. + ## Product VPD The VPD named `product` describes the device as a whole. Its From c8579c61774da3a239f8f8f2ed678f7451f05b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 9 Oct 2026 21:00:54 +0200 Subject: [PATCH 38/45] bpi-r3: Give ports a chassis-derived address at boot The board has no MAC EEPROM, so ports come up random and the switch ports inherit the conduit's address. A product init script hands each port base + N from the VPD, in interface name order. --- .../rootfs/usr/libexec/bpi-r3/macaddr | 86 +++++++++++++++++++ .../etc/product/init.d/S22-macaddr | 1 + .../etc/product/init.d/S22-macaddr | 1 + doc/ChangeLog.md | 2 + doc/vpd.md | 5 +- 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100755 board/aarch64/bananapi-bpi-r3/rootfs/usr/libexec/bpi-r3/macaddr create mode 120000 board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/product/init.d/S22-macaddr create mode 120000 board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/product/init.d/S22-macaddr diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/libexec/bpi-r3/macaddr b/board/aarch64/bananapi-bpi-r3/rootfs/usr/libexec/bpi-r3/macaddr new file mode 100755 index 000000000..3e5efb239 --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/libexec/bpi-r3/macaddr @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Give ports without a hardware MAC address a chassis-derived one. + +The BPI-R3 family has no MAC EEPROM, so every port boots with a random +address, and the switch ports inherit that from their conduit. When +the product VPD provides a base MAC, hand each such port a stable +address derived from it: base + 1, base + 2, ... in interface name +order. This mirrors what products with real hardware addresses get +from their device tree. Ports with a permanent address are left alone. + +Runs from /etc/product/init.d, before the DSA conduit is renamed, so +it sorts as eth0 and takes base + 1. +""" +import json +import os +import subprocess +import sys + +SYSTEM_JSON = "/run/system.json" +NET = "/sys/class/net" +NET_ADDR_PERM = 0 + + +def log(msg): + subprocess.run(["logger", "-k", "-p", "user.notice", "-t", "macaddr", msg], + check=False) + + +def vpd_base_mac(): + try: + with open(SYSTEM_JSON, encoding="ascii") as f: + system = json.load(f) + except (OSError, ValueError): + return None + + product = system.get("vpd", {}).get("product", {}) + return product.get("data", {}).get("mac-address") + + +def mac_add(mac, offset): + num = int(mac.replace(":", ""), 16) + offset + num %= 1 << 48 + return ":".join(f"{(num >> 8 * i) & 0xff:02x}" for i in range(5, -1, -1)) + + +def read(path): + with open(path, encoding="ascii") as f: + return f.read().strip() + + +def ports(): + """Wired ports whose address the kernel did not get from hardware.""" + for name in sorted(os.listdir(NET)): + path = os.path.join(NET, name) + if not os.path.exists(os.path.join(path, "device")): + continue + if os.path.exists(os.path.join(path, "phy80211")): + continue + try: + if int(read(os.path.join(path, "addr_assign_type"))) == NET_ADDR_PERM: + continue + except (OSError, ValueError): + continue + + yield name + + +def main(): + base = vpd_base_mac() + if not base: + return 0 + + for offset, name in enumerate(ports(), start=1): + mac = mac_add(base, offset) + rc = subprocess.run(["ip", "link", "set", "dev", name, "address", mac], + check=False) + if rc.returncode: + log(f"failed setting {name} address {mac}") + continue + log(f"{name}: {mac} (chassis + {offset})") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/product/init.d/S22-macaddr b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/product/init.d/S22-macaddr new file mode 120000 index 000000000..f44f78b9d --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/product/init.d/S22-macaddr @@ -0,0 +1 @@ +/usr/libexec/bpi-r3/macaddr \ No newline at end of file diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/product/init.d/S22-macaddr b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/product/init.d/S22-macaddr new file mode 120000 index 000000000..f44f78b9d --- /dev/null +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3mini/etc/product/init.d/S22-macaddr @@ -0,0 +1 @@ +/usr/libexec/bpi-r3/macaddr \ No newline at end of file diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index bd0447221..4048641d5 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -12,6 +12,8 @@ All notable changes to the project are documented in this file. - Boards without a VPD EEPROM can be given a fixed identity, base MAC included, by writing an ONIE TLV to a disk partition labelled `factory`, see [VPD documentation](vpd.md#factory-partition) +- Banana Pi BPI-R3: ports get stable addresses derived from the chassis + MAC instead of random ones at every boot - WiFi 4-address (WDS) links for wireless bridges and repeaters: a station with `wds` enabled can be a bridge port, and an access point bridges each remote station through a `wds-link` interface, see diff --git a/doc/vpd.md b/doc/vpd.md index 7c0e756e2..5e1ed666f 100644 --- a/doc/vpd.md +++ b/doc/vpd.md @@ -72,7 +72,10 @@ echo '{"mac-address":"02:00:00:ba:01:00","serial-number":"banana1","vendor":"Ban ``` A VPD read this way is never trusted, so it can only provide identity, -never the factory password. +never the factory password. On the Banana Pi BPI-R3 family the base +MAC address it provides is also handed out to the ports at boot: every +wired port without a hardware address gets base + 1, base + 2, and so +on, in interface name order. The image for the Banana Pi BPI-R3 family already carries an empty `factory` partition for this purpose. From 68d77e3b343c0a511190a190fb1ee20c97df693e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 9 Oct 2026 21:00:54 +0200 Subject: [PATCH 39/45] confd: wifi: Derive VIF addresses from the chassis MAC Every VIF inherited the radio's address, which on boards with the default EEPROM is the same on every unit, so two BPI-R3 could not even authenticate to each other: mac80211 rejects a peer with the local address with EINVAL. Encode the interface number in the first octet so the result never overlaps the chassis + N port addresses. --- doc/ChangeLog.md | 3 +++ doc/vpd.md | 2 ++ doc/wifi.md | 25 +++++++---------- src/confd/src/if-wifi.c | 59 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 4048641d5..e585de412 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -14,6 +14,9 @@ All notable changes to the project are documented in this file. see [VPD documentation](vpd.md#factory-partition) - Banana Pi BPI-R3: ports get stable addresses derived from the chassis MAC instead of random ones at every boot +- WiFi interfaces get a unique address derived from the chassis MAC, so + several APs on one radio, or two devices of the same model, no longer + need `custom-phys-address` - WiFi 4-address (WDS) links for wireless bridges and repeaters: a station with `wds` enabled can be a bridge port, and an access point bridges each remote station through a `wds-link` interface, see diff --git a/doc/vpd.md b/doc/vpd.md index 5e1ed666f..6c32b3982 100644 --- a/doc/vpd.md +++ b/doc/vpd.md @@ -76,6 +76,8 @@ never the factory password. On the Banana Pi BPI-R3 family the base MAC address it provides is also handed out to the ports at boot: every wired port without a hardware address gets base + 1, base + 2, and so on, in interface name order. +WiFi interfaces derive their addresses from the base MAC as described +in [WiFi](wifi.md#multi-ssid-configuration). The image for the Banana Pi BPI-R3 family already carries an empty `factory` partition for this purpose. diff --git a/doc/wifi.md b/doc/wifi.md index 94e071dc4..89650e3a2 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -546,7 +546,6 @@ admin@example:/config/interface/wifi1/> set wifi radio radio0 admin@example:/config/interface/wifi1/> set wifi access-point ssid GuestNetwork admin@example:/config/interface/wifi1/> set wifi access-point security mode wpa2-wpa3-personal admin@example:/config/interface/wifi1/> set wifi access-point security secret guest-secret -admin@example:/config/interface/wifi1/> set custom-phys-address static 00:0c:43:26:60:01 # IoT AP - IoT devices (WPA2 for older device compatibility) admin@example:/config/> edit interface wifi2 @@ -554,23 +553,19 @@ admin@example:/config/interface/wifi2/> set wifi radio radio0 admin@example:/config/interface/wifi2/> set wifi access-point ssid IoT-Devices admin@example:/config/interface/wifi2/> set wifi access-point security mode wpa2-personal admin@example:/config/interface/wifi2/> set wifi access-point security secret iot-secret -admin@example:/config/interface/wifi2/> set custom-phys-address static 00:0c:43:26:60:02 admin@example:/config/interface/wifi2/> leave
-> [!IMPORTANT] -> **MAC Address Requirement for Multi-SSID:** -> When creating multiple AP interfaces on the same radio, you **must** configure -> a unique MAC address for each secondary interface (wifi1, wifi2, etc.) using -> `set custom-phys-address static `. All interfaces on the same radio inherit -> the radio's hardware MAC address by default, which causes network conflicts. Only -> the primary interface (alphabetically first, e.g., wifi0) should use the default -> hardware MAC address. -> -> Choose MAC addresses from the same locally-administered range: -> - Primary (wifi0): Uses hardware MAC (e.g., `00:0c:43:26:60:00`) -> - Secondary (wifi1): `00:0c:43:26:60:01` (increment last octet) -> - Tertiary (wifi2): `00:0c:43:26:60:02` (increment last octet) +> [!NOTE] +> Each WiFi interface gets its own MAC address, derived from the chassis +> MAC by changing only the first octet: the locally administered bit is +> set and the interface number plus one is stored in the upper bits. On +> a device with chassis MAC `00:53:00:c0:ff:ee`, `wifi0` becomes +> `06:53:00:c0:ff:ee` and `wifi1` becomes `0a:53:00:c0:ff:ee`. Several +> APs on one radio therefore never share an address, and two devices of +> the same model never collide. To use a specific address instead, set +> `custom-phys-address` on the interface, see [Common Interface +> Settings](iface.md#custom-mac-address). **Result:** Three SSIDs broadcasting simultaneously on radio0: diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 15b860305..88e49107d 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -8,6 +8,8 @@ */ #include +#include +#include #include #include @@ -583,12 +585,57 @@ int wifi_add_deps(struct lyd_node *cif) return 0; } +/* + * Default address for a VIF without custom-phys-address. Every VIF + * on a radio would otherwise inherit the radio's own address, and on + * boards without a per-unit EEPROM that address is the vendor default, + * shared by every unit of the model. Derive from the chassis MAC + * instead, encoded in the first octet so it never meets the port + * addresses, which are chassis + N: set the locally administered bit + * and add the interface number plus one in bits 2-7, e.g. wifi0 on + * chassis 00:53:00:c0:ff:ee becomes 06:53:00:c0:ff:ee and wifi1 + * 0a:53:00:c0:ff:ee. Names without a number use a hash of the name. + */ +static int wifi_default_addr(const char *ifname, char *buf, size_t len) +{ + const char *chassis = get_chassis_addr(); + unsigned int n = 0; + uint8_t mac[6]; + const char *p; + + if (!chassis) + return -1; + + if (sscanf(chassis, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) + return -1; + + for (p = ifname; *p && !isdigit((unsigned char)*p); p++) + ; + if (*p) { + n = strtoul(p, NULL, 10); + } else { + for (p = ifname; *p; p++) + n = n * 33 + (unsigned char)*p; + } + n = (n + 1) & 0x3f; + + /* Locally administered, unicast whatever the chassis says */ + mac[0] = 0x02 | (n << 2); + + snprintf(buf, len, " addr %02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return 0; +} + int wifi_add_iface(struct lyd_node *cif, struct dagger *net) { const char *ifname, *radio; struct lyd_node *wifi; wifi_mode_t mode; int probe_timeout; + char addr[32] = ""; + char custom[18]; FILE *iw; int rc = SR_ERR_OK; @@ -646,12 +693,16 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) fprintf(iw, " exit 0\n"); fprintf(iw, "fi\n\n"); + /* A WDS link is created with its AP's address, see below */ + if (mode != wifi_wds && interface_get_phys_addr(cif, custom)) + wifi_default_addr(ifname, addr, sizeof(addr)); + switch(mode) { 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" : ""); + fprintf(iw, "iw phy %s interface add %s type managed%s%s\n", radio, ifname, + station && lydx_is_enabled(station, "wds") ? " 4addr on" : "", addr); wifi_gen_station(cif); fprintf(iw, "initctl -bfq enable wifi@%s\n", ifname); fprintf(iw, "initctl -bfq touch wifi@%s\n", ifname); @@ -671,10 +722,10 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) break; } case wifi_ap: - fprintf(iw, "iw phy %s interface add %s type __ap\n", radio, ifname); + fprintf(iw, "iw phy %s interface add %s type __ap%s\n", radio, ifname, addr); break; case wifi_mesh: - fprintf(iw, "iw phy %s interface add %s type mesh\n", radio, ifname); + fprintf(iw, "iw phy %s interface add %s type mesh%s\n", radio, ifname, addr); wifi_gen_mesh(cif); fprintf(iw, "initctl -bfq enable mesh@%s\n", ifname); fprintf(iw, "initctl -bfq touch mesh@%s\n", ifname); From 8d25af3018be5fa72373e4ee886aea962faa95ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 9 Oct 2026 21:04:32 +0200 Subject: [PATCH 40/45] hostapd: Push PMK-R1 to the R1KHs added by RELOAD_RXKHS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hostapd pushes a client's key to the key holders known when it authenticated and never again, so an access point learned later has nothing for that client until it re-authenticates. Signed-off-by: Mattias Walström --- ...-R1-to-the-R1KHs-added-by-RELOAD_RXK.patch | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 patches/hostapd/0005-hostapd-Push-PMK-R1-to-the-R1KHs-added-by-RELOAD_RXK.patch diff --git a/patches/hostapd/0005-hostapd-Push-PMK-R1-to-the-R1KHs-added-by-RELOAD_RXK.patch b/patches/hostapd/0005-hostapd-Push-PMK-R1-to-the-R1KHs-added-by-RELOAD_RXK.patch new file mode 100644 index 000000000..08f3a0565 --- /dev/null +++ b/patches/hostapd/0005-hostapd-Push-PMK-R1-to-the-R1KHs-added-by-RELOAD_RXK.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Thu, 9 Oct 2026 18:30:00 +0200 +Subject: [PATCH 5/5] hostapd: Push PMK-R1 to the R1KHs added by RELOAD_RXKHS +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With pmk_r1_push the R0KH hands a PMK-R1 to every configured R1KH when +a station authenticates, and never again. An R1KH configured later, +through RELOAD_RXKHS, gets nothing for the stations already here, so a +roam to it still has to pull from this R0KH, which fails once this +access point has gone down. + +Push the keys of every station to the R1KHs again after a reload. An +R1KH that already has a key just replaces it. + +Signed-off-by: Mattias Walström +--- + hostapd/ctrl_iface.c | 5 +++++ + src/ap/wpa_auth.h | 1 + + src/ap/wpa_auth_ft.c | 21 +++++++++++++++++++++ + 3 files changed, 27 insertions(+) + +diff -ruN a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c +--- a/hostapd/ctrl_iface.c ++++ b/hostapd/ctrl_iface.c +@@ -1527,6 +1527,11 @@ + return -1; + } + ++ /* R1KHs added now have not seen the keys of the stations already ++ * authenticated here, hand them over so a roam to them does not ++ * depend on this R0KH still being around. */ ++ wpa_ft_push_pmk_r1_all(hapd->wpa_auth); ++ + return 0; + } + +diff -ruN a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c +--- a/src/ap/wpa_auth_ft.c ++++ b/src/ap/wpa_auth_ft.c +@@ -4976,4 +4976,25 @@ + } + } + ++ ++/* ++ * Push the PMK-R1 of every station this R0KH holds a PMK-R0 for, to ++ * all configured R1KHs. For use when the R1KH list has changed: the ++ * push at authentication only reached the R1KHs known at the time. ++ */ ++void wpa_ft_push_pmk_r1_all(struct wpa_authenticator *wpa_auth) ++{ ++ struct wpa_ft_pmk_cache *cache; ++ struct wpa_ft_pmk_r0_sa *r0; ++ ++ if (!wpa_auth || !wpa_auth->conf.pmk_r1_push || !wpa_auth->ft_pmk_cache) ++ return; ++ ++ cache = wpa_auth->ft_pmk_cache; ++ dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) { ++ r0->pmk_r1_pushed = 0; ++ wpa_ft_push_pmk_r1(wpa_auth, r0->spa); ++ } ++} ++ + #endif /* CONFIG_IEEE80211R_AP */ +diff -ruN a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h +--- a/src/ap/wpa_auth.h ++++ b/src/ap/wpa_auth.h +@@ -594,6 +594,7 @@ + const u8 *dst_addr, u8 oui_suffix, const u8 *data, + size_t data_len); + void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr); ++void wpa_ft_push_pmk_r1_all(struct wpa_authenticator *wpa_auth); + void wpa_ft_deinit(struct wpa_authenticator *wpa_auth); + void wpa_ft_sta_deinit(struct wpa_state_machine *sm); + int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth, From 732f6d93533a6d1cf1198779d477adaaca407050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 9 Oct 2026 21:04:32 +0200 Subject: [PATCH 41/45] confd: wifi: Push the 802.11r keys to the other nodes' access points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A roam to an access point that has to fetch the client's key fails once the access point the client first connected to is gone. Enable pmk_r1_push and keep the key holders in a file that wifi-neighbors.py fills with the access points it hears of and has hostapd reload, so every node holds the key before it is needed. Signed-off-by: Mattias Walström --- .../usr/libexec/infix/wifi-neighbors.py | 79 +++++++++++++++---- doc/wifi.md | 6 +- src/confd/src/hardware.c | 36 ++++++++- 3 files changed, 101 insertions(+), 20 deletions(-) diff --git a/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py b/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py index a51d12261..05fa709e9 100755 --- a/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py +++ b/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py @@ -25,11 +25,20 @@ log instead of as a silent lack of neighbors. Bump VERSION when the frame format changes. -What is heard is kept for 90 seconds and handed to hostapd as 802.11k -neighbors of every access point with the same SSID, for its neighbor -reports and for the transition requests sent by hostapd.sh and +What is heard is kept for a few periods and handed to hostapd as +802.11k neighbors of every access point with the same SSID, for its +neighbor reports and for the transition requests sent by hostapd.sh and wifi-steer.sh, which read the candidate list of a BSS from /run/wifi-neighbors/, one bss_tm_req neighbor= argument per line. + +The same access points are also the 802.11r key holders a client can +roam to. They go into the BSS's key holder file, rxkh_file in the +hostapd config, and hostapd is told to reload it, so that it pushes the +key of every client to them right away rather than having them fetch it +at the roam, from a node that may be gone by then. + +A node that hears a new neighbor announces itself at once, so two nodes +know each other within a second of the backhaul coming up. """ import hmac import hashlib @@ -46,8 +55,8 @@ GROUP = bytes.fromhex('034b4b000001') # locally administered group address MAGIC = b'infix-wifi' VERSION = 2 -PERIOD = 30 -EXPIRE = 95 +PERIOD = 10 +EXPIRE = 35 MAX_NEIGHBORS = 64 DIR = '/run/wifi-neighbors' MAC = re.compile(r'^([0-9a-f]{2}:){5}[0-9a-f]{2}$') @@ -67,8 +76,21 @@ def hostapd_cli(bss, *args): return '' +def rxkh_key(path): + """The 802.11r key from a key holder file, None without one.""" + try: + for line in open(path).read().splitlines(): + # r0kh= , the key is what every node + # derives from the mobility domain and the secret + if line.startswith('r0kh='): + return line.split()[-1].encode() + except OSError: + pass + return None + + def parse_configs(paths): - """{bss: (ft_iface, key)} for every BSS, both None without 802.11r.""" + """{bss: (ft_iface, key, rxkh)} for every BSS, all None without 802.11r.""" bsses = {} for path in paths: if not path.endswith('.conf'): @@ -81,13 +103,12 @@ def parse_configs(paths): for line in lines: if line.startswith(('interface=', 'bss=')): cur = line.split('=', 1)[1].strip() - bsses.setdefault(cur, [None, None]) + bsses.setdefault(cur, [None, None, None]) elif line.startswith('ft_iface=') and cur: bsses[cur][0] = line.split('=', 1)[1].strip() - elif line.startswith('r0kh=') and cur: - # r0kh= , the key is what every node derives - # from the mobility domain and the secret - bsses[cur][1] = line.split()[-1].encode() + elif line.startswith('rxkh_file=') and cur: + bsses[cur][2] = line.split('=', 1)[1].strip() + bsses[cur][1] = rxkh_key(bsses[cur][2]) return {bss: tuple(v) for bss, v in bsses.items()} @@ -136,7 +157,7 @@ def tag(key, bssid, freq, phy, ssid): def own_bsses(bsses): """{bss: (bssid, freq, phy, ssid)} from hostapd, for the keyed BSSes up.""" out = {} - for bss, (_, key) in bsses.items(): + for bss, (_, key, _) in bsses.items(): if not key: continue cfg = hostapd_cli(bss, 'get_config') @@ -215,8 +236,23 @@ def nr_hex(bssid, freq, phy): + bytes([opclass(freq), channel(freq), phy]).hex()) -def publish(own, neighbors, published): - """Write the candidate files and sync hostapd's neighbor database.""" +def write_rxkh(path, key, bssids): + """Rewrite a key holder file: the wildcards, then one R1KH per neighbor. + + The R1KH-ID of an access point is its BSSID, and so is the address + its key holder is reached at over the backhaul. + """ + key = key.decode() + with open(f'{path}.tmp', 'w') as f: + f.write(f'r0kh=ff:ff:ff:ff:ff:ff * {key}\n') + f.write(f'r1kh=00:00:00:00:00:00 00:00:00:00:00:00 {key}\n') + for bssid in sorted(bssids): + f.write(f'r1kh={bssid} {bssid} {key}\n') + os.replace(f'{path}.tmp', path) + + +def publish(own, neighbors, published, bsses): + """Write the candidate and key holder files, sync hostapd's lists.""" for bss, (mybssid, _, _, myssid) in own.items(): mine = {n[0]: n for n in neighbors.values() if n[3] == myssid and n[0] != mybssid} @@ -233,14 +269,20 @@ def publish(own, neighbors, published): continue hostapd_cli(bss, 'set_neighbor', bssid, f'ssid="{ssid}"', f'nr={nr_hex(bssid, freq, phy)}') + _, key, rxkh = bsses[bss] + if rxkh and key and set(published.get(bss, {})) != set(mine): + write_rxkh(rxkh, key, mine) + hostapd_cli(bss, 'reload_rxkhs') published[bss] = mine log(f"{bss}: {len(mine)} neighbor(s) for '{myssid}'") def main(): + # The key holder files carry the 802.11r key, keep them root-only + os.umask(0o077) os.makedirs(DIR, exist_ok=True) bsses = parse_configs(sys.argv[1:]) - ifaces = sorted({ifc for ifc, key in bsses.values() if ifc and key}) + ifaces = sorted({ifc for ifc, key, _ in bsses.values() if ifc and key}) if not ifaces: return host = socket.gethostname() @@ -262,6 +304,7 @@ def main(): neighbors = {} # bssid -> (bssid, freq, phy, ssid, last seen) published = {} next_send = 0.0 + last_send = 0.0 own = {} keys = {} # ssid -> 802.11r key, for the SSIDs we serve other = set() # nodes heard announcing another version @@ -277,6 +320,7 @@ def main(): except OSError: pass next_send = now + PERIOD + last_send = now ready, _, _ = select.select(list(socks), [], [], max(0.1, next_send - now)) for s in ready: @@ -290,6 +334,9 @@ def main(): continue if bssid not in neighbors and len(neighbors) >= MAX_NEIGHBORS: continue + # Answer a newcomer right away, at most once a second + if bssid not in neighbors and stamp - last_send > 1: + next_send = stamp neighbors[bssid] = (bssid, freq, phy, ssid, stamp) cutoff = time.monotonic() - EXPIRE @@ -297,7 +344,7 @@ def main(): del neighbors[bssid] if own: try: - publish(own, {b: n[:4] for b, n in neighbors.items()}, published) + publish(own, {b: n[:4] for b, n in neighbors.items()}, published, bsses) except OSError as err: log(f'wifi-neighbors: {err}') diff --git a/doc/wifi.md b/doc/wifi.md index 89650e3a2..a12608f17 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -626,8 +626,10 @@ admin@example:/config/> set interface wifi0 wifi access-point roaming dot11r key - All APs must use the **same mobility-domain** identifier - APs on different devices must be ports of bridges that are connected to each other, over a cable, a mesh or a WDS backhaul. The APs hand a - roaming client's keys to each other over that network, which WPA3 - clients need for a fast transition. On a bridge with VLAN filtering + client's keys to each other over that network as soon as it connects, + which WPA3 clients need for a fast transition, so a client can keep + roaming after the AP it first connected to has gone down. On a bridge + with VLAN filtering the keys travel in the APs' VLAN, so each device needs a VLAN interface on the bridge for that VLAN, the one carrying its IP address there is enough diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 4e675ff6c..b3eec62a3 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -20,6 +20,8 @@ #define XPATH_BASE_ "/ietf-hardware:hardware" #define HOSTAPD_CONF "/etc/hostapd-%s.conf" #define HOSTAPD_CONF_NEXT HOSTAPD_CONF"+" +#define WIFI_RXKH_DIR "/run/wifi-neighbors" +#define WIFI_RXKH_FILE WIFI_RXKH_DIR "/%s.rxkh" #define HOSTAPD_SERVICE "/etc/finit.d/available/hostapd.conf" #define GPSD_CONF "/etc/finit.d/available/gpsd.conf" #define GPSD_CONF_NEXT GPSD_CONF"+" @@ -413,6 +415,28 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name, } /* Emit the 4-address WDS ports (wds-link interfaces) of an AP */ +/* + * The 802.11r key holder list of a BSS, read by hostapd at start and on + * RELOAD_RXKHS. wifi-neighbors.py rewrites it with the other nodes' + * access points as R1KHs, keeping these wildcard entries. + */ +static int wifi_write_rxkh(const char *ifname, const char *ft_key) +{ + FILE *fp; + + if (fmkpath(0755, WIFI_RXKH_DIR)) + return -1; + + fp = fopenf("w", WIFI_RXKH_FILE, ifname); + if (!fp) + return -1; + + fprintf(fp, "r0kh=ff:ff:ff:ff:ff:ff * %s\n", ft_key); + fprintf(fp, "r1kh=00:00:00:00:00:00 00:00:00:00:00:00 %s\n", ft_key); + + return fclose(fp); +} + static void wifi_gen_wds_ports(FILE *hostapd, struct lyd_node *config, const char *ap_ifname) { struct lyd_node *cifs, *cif; @@ -614,8 +638,16 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd } fprintf(hostapd, "ft_iface=%s\n", ft_iface); } - fprintf(hostapd, "r0kh=ff:ff:ff:ff:ff:ff * %s\n", ft_key); - fprintf(hostapd, "r1kh=00:00:00:00:00:00 00:00:00:00:00:00 %s\n", ft_key); + /* The key holders live in a file so that wifi-neighbors.py + * can add the other nodes' access points as it hears of + * them and have hostapd reload the list. Start with the + * wildcards, which accept any node holding the key. */ + if (!wifi_write_rxkh(ifname, ft_key)) + fprintf(hostapd, "rxkh_file=" WIFI_RXKH_FILE "\n", ifname); + /* Hand every R1KH the PMK-R1 as soon as a client + * authenticates: a roam must not depend on this node, + * the R0KH, still being up to pull the key from. */ + fprintf(hostapd, "pmk_r1_push=1\n"); /* A client roaming away from a node that is going down * asks for a key that node can no longer hand out. * Give up on the fetch quickly and reject, the client From 22c6413535160acf248471bec826e8d752a0b0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sat, 10 Oct 2026 22:39:31 +0200 Subject: [PATCH 42/45] bpi-r3: dts: Drop the ethernet aliases U-Boot patches its per-boot random ethaddr into the ethernet0 node, so the kernel reports gmac0 and all switch ports as having a permanent address and 22-macaddr leaves them alone. Without the aliases the ports come up random and get their chassis-derived address. --- .../dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi index abe2870cc..ef1b24659 100644 --- a/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi +++ b/board/aarch64/bananapi-bpi-r3/dts/mediatek/mt7986a-bananapi-bpi-r3.dtsi @@ -1,4 +1,18 @@ / { + /* + * U-Boot writes its ethaddr, which it generates at random on + * every boot, into the node behind the ethernet0 alias. The + * kernel then reports that port, and every switch port that + * inherits from it, as having a permanent hardware address. + * Drop the aliases so the ports come up with a random address + * instead, which init.d/22-macaddr replaces with a chassis- + * derived one when a product VPD is present. + */ + aliases { + /delete-property/ ethernet0; + /delete-property/ ethernet1; + }; + chosen { infix { /* Default admin user password: 'admin' */ From 98841b66e716ae2c7164e0273cbdc28db7e4c910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Sun, 11 Oct 2026 10:15:55 +0200 Subject: [PATCH 43/45] webui: Add the 802.11r key holder secret to the interface editor Optional, the WiFi password is used when none is chosen. --- .../internal/handlers/configure_interfaces.go | 6 +++++- src/webui/internal/handlers/interfaces.go | 5 +++-- src/webui/internal/handlers/wifi_mesh_test.go | 19 +++++++++++++------ .../templates/pages/configure-interfaces.html | 16 +++++++++++++--- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/webui/internal/handlers/configure_interfaces.go b/src/webui/internal/handlers/configure_interfaces.go index 44474ae6a..2df2f7cf1 100644 --- a/src/webui/internal/handlers/configure_interfaces.go +++ b/src/webui/internal/handlers/configure_interfaces.go @@ -296,7 +296,8 @@ func (h *ConfigureInterfacesHandler) Overview(w http.ResponseWriter, r *http.Req "wifi-forwarding": descOr(mgr, ifPath+"/infix-interfaces:wifi/mesh-point/forwarding", "Layer-2 mesh forwarding. Leave on to let this node relay traffic for other mesh points and to bridge the mesh interface into a LAN (mesh portal). Off means only locally destined traffic is received."), "wifi-mesh-secret": descOr(mgr, ifPath+"/infix-interfaces:wifi/mesh-point/security/secret", "Pre-shared key reference for the WPA3-SAE mesh. All mesh points in the same mesh must share the same key."), "wifi-dot11k": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11k", "802.11k Radio Resource Management: neighbor and beacon reports let clients discover nearby APs before roaming."), - "wifi-dot11r": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11r", "802.11r Fast BSS Transition: pre-authentication cuts handoff time to under 50 ms. Requires WPA2/WPA3 security, plus identical SSID, passphrase and mobility domain on all APs in the group."), + "wifi-dot11r": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11r", "802.11r Fast BSS Transition: pre-authentication cuts handoff time to under 50 ms. Requires WPA2/WPA3 security, plus identical SSID, passphrase, key holder secret and mobility domain on all APs in the group."), + "wifi-dot11r-key": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11r/key-holder-secret", "Keystore key the access points of the mobility domain hand each other a roaming client's keys with, the same on every device. Without it the WiFi password is used, which lets anyone who knows the password read the other clients' keys."), "wifi-dot11r-md": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11r/mobility-domain", "802.11r mobility domain: four hex digits shared by every AP clients roam between, or 'hash' to derive it from the SSID (OpenWrt-compatible)."), "wifi-dot11r-nas": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11r/nas-identifier", "NAS-Identifier for 802.11r key lookup, unique per AP. 'auto' derives -.."), "wifi-dot11v": descOr(mgr, ifPath+"/infix-interfaces:wifi/access-point/roaming/dot11v", "802.11v BSS Transition Management: lets the AP suggest a better AP to clients (network-assisted roaming)."), @@ -1881,6 +1882,9 @@ func wifiRoamingFromForm(r *http.Request) (map[string]any, error) { } if r.FormValue("dot11r") == "on" { dot11r := map[string]any{} + if key := strings.TrimSpace(r.FormValue("key-holder-secret")); key != "" { + dot11r["key-holder-secret"] = key + } md := strings.ToLower(strings.TrimSpace(r.FormValue("mobility-domain"))) if md != "" { if md != "hash" && !isHex4(md) { diff --git a/src/webui/internal/handlers/interfaces.go b/src/webui/internal/handlers/interfaces.go index 3a690ff40..2a6c06b0d 100644 --- a/src/webui/internal/handlers/interfaces.go +++ b/src/webui/internal/handlers/interfaces.go @@ -131,8 +131,9 @@ type wifiRoamingJSON struct { } type wifiDot11rJSON struct { - MobilityDomain string `json:"mobility-domain"` - NASIdentifier string `json:"nas-identifier"` + KeyHolderSecret string `json:"key-holder-secret"` + MobilityDomain string `json:"mobility-domain"` + NASIdentifier string `json:"nas-identifier"` } type wifiDot11vJSON struct { diff --git a/src/webui/internal/handlers/wifi_mesh_test.go b/src/webui/internal/handlers/wifi_mesh_test.go index c5c303761..b74e41a17 100644 --- a/src/webui/internal/handlers/wifi_mesh_test.go +++ b/src/webui/internal/handlers/wifi_mesh_test.go @@ -40,7 +40,7 @@ const wifiCfgFixture = `{"ietf-interfaces:interfaces":{"interface":[ "security":{"secret":"mesh-psk"}}}}, {"name":"wifi1-ap","type":"infix-if-type:wifi", "infix-interfaces:wifi":{"radio":"phy1","access-point":{"ssid":"office","security":{"mode":"wpa3-personal","secret":"psk"}, - "roaming":{"dot11k":{},"dot11r":{"mobility-domain":"hash"},"dot11v":{"band-steering":false},"okc":false}}}}, + "roaming":{"dot11k":{},"dot11r":{"key-holder-secret":"ft-key","mobility-domain":"hash"},"dot11v":{"band-steering":false},"okc":false}}}}, {"name":"wifi2","type":"infix-if-type:wifi", "infix-interfaces:wifi":{"radio":"phy2","station":{"ssid":"upstream","security":{"secret":"psk"}}}}, {"name":"wifi3-mesh","type":"infix-if-type:wifi", @@ -149,14 +149,14 @@ func roamingForm(t *testing.T, v url.Values) *http.Request { func TestWiFiRoamingFromForm(t *testing.T) { roaming, err := wifiRoamingFromForm(roamingForm(t, url.Values{ - "dot11k": {"on"}, "dot11r": {"on"}, "mobility-domain": {"AB12"}, "nas-identifier": {"auto"}, + "dot11k": {"on"}, "dot11r": {"on"}, "key-holder-secret": {"ft-key"}, "mobility-domain": {"AB12"}, "nas-identifier": {"auto"}, "dot11v": {"on"}, "band-steering": {"on"}, "okc": {"on"}, })) if err != nil { t.Fatal(err) } r := roaming["dot11r"].(map[string]any) - if r["mobility-domain"] != "ab12" || r["nas-identifier"] != "auto" { + if r["key-holder-secret"] != "ft-key" || r["mobility-domain"] != "ab12" || r["nas-identifier"] != "auto" { t.Errorf("dot11r = %v", r) } // okc and band-steering are on: both at their YANG default, so absent. @@ -172,7 +172,7 @@ func TestWiFiRoamingFromForm(t *testing.T) { // Unticked presence containers and blank sub-fields are simply absent; // SaveWifi PUTs the whole container so that is enough to clear them. - roaming, err = wifiRoamingFromForm(roamingForm(t, url.Values{"dot11r": {"on"}, "mobility-domain": {"hash"}})) + roaming, err = wifiRoamingFromForm(roamingForm(t, url.Values{"dot11r": {"on"}, "key-holder-secret": {"ft-key"}, "mobility-domain": {"hash"}})) if err != nil { t.Fatal(err) } @@ -205,9 +205,16 @@ func TestWiFiRoamingFromForm(t *testing.T) { t.Error("blank nas-identifier should be absent") } - if _, err := wifiRoamingFromForm(roamingForm(t, url.Values{"dot11r": {"on"}, "mobility-domain": {"xyz"}})); err == nil { + if _, err := wifiRoamingFromForm(roamingForm(t, url.Values{"dot11r": {"on"}, "key-holder-secret": {"ft-key"}, "mobility-domain": {"xyz"}})); err == nil { t.Error("expected error for bad mobility domain") } + roaming, err = wifiRoamingFromForm(roamingForm(t, url.Values{"dot11r": {"on"}, "mobility-domain": {"hash"}})) + if err != nil { + t.Fatalf("dot11r without key holder secret: %v", err) + } + if _, ok := roaming["dot11r"].(map[string]any)["key-holder-secret"]; ok { + t.Error("key holder secret should be absent when not chosen") + } } // realTemplates parses the on-disk templates the way server.go does, so @@ -329,7 +336,7 @@ func TestConfigureInterfacesRendersMeshAndRoamingEditors(t *testing.T) { for _, want := range []string{ `value="mesh-point" checked`, `name="mesh-id"`, `value="backhaul"`, `name="forwarding"`, `Roaming (802.11k/r/v)`, - `name="dot11k" checked`, `name="mobility-domain"`, `value="hash"`, `data-fold-target="wifi-row-wifi1-ap-dot11r-md wifi-row-wifi1-ap-dot11r-nas"`, + `name="dot11k" checked`, `name="mobility-domain"`, `value="hash"`, `name="key-holder-secret"`, `data-fold-target="wifi-row-wifi1-ap-dot11r-key wifi-row-wifi1-ap-dot11r-md wifi-row-wifi1-ap-dot11r-nas"`, `add-iface-wifi-mode-mesh`, `add-iface-wifi-meshid-row`, `roaming`, } { diff --git a/src/webui/templates/pages/configure-interfaces.html b/src/webui/templates/pages/configure-interfaces.html index 45fd18cc1..0522822cf 100644 --- a/src/webui/templates/pages/configure-interfaces.html +++ b/src/webui/templates/pages/configure-interfaces.html @@ -616,14 +616,14 @@

WiFi

form validation. */}} {{$rm := ""}}{{if $ap}}{{$rm = $ap.Roaming}}{{end}} {{$hasK := false}}{{$hasR := false}}{{$hasV := false}}{{$okc := true}}{{$bs := true}} - {{$md := "4f57"}}{{$nas := "auto"}} + {{$md := "4f57"}}{{$nas := "auto"}}{{$khs := ""}} {{if $rm}} {{if $rm.Dot11k}}{{$hasK = true}}{{end}} - {{if $rm.Dot11r}}{{$hasR = true}}{{if $rm.Dot11r.MobilityDomain}}{{$md = $rm.Dot11r.MobilityDomain}}{{end}}{{if $rm.Dot11r.NASIdentifier}}{{$nas = $rm.Dot11r.NASIdentifier}}{{end}}{{end}} + {{if $rm.Dot11r}}{{$hasR = true}}{{$khs = $rm.Dot11r.KeyHolderSecret}}{{if $rm.Dot11r.MobilityDomain}}{{$md = $rm.Dot11r.MobilityDomain}}{{end}}{{if $rm.Dot11r.NASIdentifier}}{{$nas = $rm.Dot11r.NASIdentifier}}{{end}}{{end}} {{if $rm.Dot11v}}{{$hasV = true}}{{if $rm.Dot11v.BandSteering}}{{$bs = deref $rm.Dot11v.BandSteering}}{{end}}{{end}} {{if $rm.OKC}}{{$okc = deref $rm.OKC}}{{end}} {{end}} - {{$dot11rRows := printf "wifi-row-%s-dot11r-md wifi-row-%s-dot11r-nas" $ifname $ifname}} + {{$dot11rRows := printf "wifi-row-%s-dot11r-key wifi-row-%s-dot11r-md wifi-row-%s-dot11r-nas" $ifname $ifname $ifname}} {{$dot11vRows := printf "wifi-row-%s-dot11v-bs" $ifname}}
WiFi + + Key holder secret{{template "field-info" (index $d "wifi-dot11r-key")}} + + + + Mobility domain{{template "field-info" (index $d "wifi-dot11r-md")}} Date: Sun, 11 Oct 2026 10:15:55 +0200 Subject: [PATCH 44/45] hostapd: Cancel the key holder timers before RELOAD_RXKHS frees them A key holder learned through a wildcard entry is freed by a timer, and every entry may hold sequence state with a timer of its own. The reload freed the lists under both, so a timer could later fire on whatever entry had taken the address. --- ...e-key-holder-timers-before-RELOAD_RX.patch | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 patches/hostapd/0006-hostapd-Cancel-the-key-holder-timers-before-RELOAD_RX.patch diff --git a/patches/hostapd/0006-hostapd-Cancel-the-key-holder-timers-before-RELOAD_RX.patch b/patches/hostapd/0006-hostapd-Cancel-the-key-holder-timers-before-RELOAD_RX.patch new file mode 100644 index 000000000..ab35172c7 --- /dev/null +++ b/patches/hostapd/0006-hostapd-Cancel-the-key-holder-timers-before-RELOAD_RX.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Sat, 11 Oct 2026 10:00:00 +0200 +Subject: [PATCH 6/6] hostapd: Cancel the key holder timers before RELOAD_RXKHS + frees them +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +A key holder learned through a wildcard R0KH or R1KH entry is freed by +a timer, and every entry may hold sequence number state with a timer of +its own. RELOAD_RXKHS freed the lists without cancelling either, so a +delete timer later fired on whatever entry had been allocated at the +old address and removed it, and the sequence state leaked. + +Tear the timers and sequence state down first, the way wpa_deinit does. + +Signed-off-by: Mattias Walström +--- +diff -ruN a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c +--- a/hostapd/ctrl_iface.c ++++ b/hostapd/ctrl_iface.c +@@ -1518,6 +1518,12 @@ + struct hostapd_bss_config *conf = hapd->conf; + int err; + ++ /* Key holders learned through the wildcard entries carry a delete ++ * timer, and every entry may hold sequence number state. Drop ++ * both before the lists are freed under them, or the timer fires ++ * on whatever entry has since taken the address. */ ++ if (hapd->wpa_auth) ++ wpa_ft_deinit(hapd->wpa_auth); + hostapd_config_clear_rxkhs(conf); + + err = hostapd_config_read_rxkh_file(conf, conf->rxkh_file); From d6e23cc7963d95cae0d01a2aa1bd2576b02b269c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 5 Oct 2026 13:18:47 +0200 Subject: [PATCH 45/45] confd: firewall: Re-apply dynamic address-set entries after reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dynamic entries were baked into the generated ipset XML to survive a firewalld reload, which resurrected entries removed while a reload was in flight. The XML now holds static entries only, and 'firewall reload' re-applies the dynamic ones from confd's shadow files once firewalld is back, dropping any it rejects. Signed-off-by: Mattias Walström --- .../etc/finit.d/available/firewalld.conf | 2 +- src/confd/bin/firewall | 39 +++++++++ src/confd/src/firewall.c | 84 ++++--------------- 3 files changed, 55 insertions(+), 70 deletions(-) diff --git a/board/common/rootfs/etc/finit.d/available/firewalld.conf b/board/common/rootfs/etc/finit.d/available/firewalld.conf index 18581c052..439395090 100644 --- a/board/common/rootfs/etc/finit.d/available/firewalld.conf +++ b/board/common/rootfs/etc/finit.d/available/firewalld.conf @@ -1,3 +1,3 @@ -service [2345] reload:'firewall-cmd -q --reload' \ +service [2345] reload:'firewall reload' \ firewalld --nofork --log-target syslog \ -- Firewall daemon diff --git a/src/confd/bin/firewall b/src/confd/bin/firewall index 28a9834d3..8c76f053e 100755 --- a/src/confd/bin/firewall +++ b/src/confd/bin/firewall @@ -6,6 +6,7 @@ DEST="org.fedoraproject.FirewallD1" OBJECT="/org/fedoraproject/FirewallD1" INTERFACE="org.fedoraproject.FirewallD1" +ADDRSET_DIR="/run/confd/address-sets" VERBOSE=0 print() { @@ -117,6 +118,42 @@ ipset_call() fi } +# Dynamic address-set entries only exist in the runtime config; a reload +# rebuilds the sets from the generated ipset XML, which holds static +# entries only. Re-apply the dynamic entries tracked by confd's +# add/remove action handlers, so they survive the reload without being +# baked into the XML (which would resurrect entries removed while the +# reload was in flight). +# +# An entry firewalld rejects as invalid, e.g., one now overlapping a +# static entry, is dropped from the shadow file, or it could never be +# removed with the remove action again. +addrset_resync() +{ + for file in "$ADDRSET_DIR"/*; do + case "$file" in *.resync) continue ;; esac + [ -f "$file" ] || continue + name=$(basename "$file") + keep="$file.resync" + : > "$keep" + + while IFS= read -r entry; do + [ -n "$entry" ] || continue + if ! ipset_call addEntry "$name" "$entry"; then + case "$output" in + *INVALID_ENTRY*) + logger -t firewall -p daemon.warn "ipset $name: dropping rejected dynamic entry $entry" + continue + ;; + esac + fi + printf '%s\n' "$entry" >> "$keep" + done < "$file" + + mv "$keep" "$file" + done +} + panic_status() { if is_panic_enabled; then @@ -377,6 +414,8 @@ main() exit 1 fi fi + + addrset_resync ;; panic) if ! check_firewalld; then diff --git a/src/confd/src/firewall.c b/src/confd/src/firewall.c index be6a18311..ec4dc9a8e 100644 --- a/src/confd/src/firewall.c +++ b/src/confd/src/firewall.c @@ -106,29 +106,6 @@ static int prefix_parse(const char *str, struct prefix *p) return -1; } -static bool prefix_overlap(const char *a, const char *b) -{ - struct prefix pa, pb; - int len, i; - - if (prefix_parse(a, &pa) || prefix_parse(b, &pb) || pa.af != pb.af) - return false; - - len = pa.len < pb.len ? pa.len : pb.len; - for (i = 0; i < len / 8; i++) { - if (pa.addr[i] != pb.addr[i]) - return false; - } - if (len % 8) { - uint8_t mask = 0xff << (8 - len % 8); - - if ((pa.addr[i] & mask) != (pb.addr[i] & mask)) - return false; - } - - return true; -} - static bool shadow_has(const char *name, const char *entry) { char line[ENTRY_STRLEN]; @@ -371,47 +348,12 @@ static int generate_zone(struct lyd_node *cfg, const char *name, char **ifaces) } /* - * Dynamic entries, added at runtime with the add action, are folded - * into the generated ipset as regular entries so they survive the - * firewalld reload triggered by configuration changes. Entries that - * overlap new static configuration are dropped -- config wins, and - * nftables refuses overlapping elements in interval sets. + * Only static entries go into the generated ipset. Dynamic entries, + * added at runtime with the add action, are re-applied from the shadow + * files by 'firewall reload' after firewalld has reloaded. Baking them + * into the XML would resurrect entries removed while a reload was in + * flight -- the reload is asynchronous to the action handlers. */ -static void merge_dynamic(FILE *fp, struct lyd_node *cfg, const char *name) -{ - char line[ENTRY_STRLEN]; - FILE *sf; - - sf = fopenf("r", ADDRSET_RUNDIR "/%s", name); - if (!sf) - return; - - while (fgets(line, sizeof(line), sf)) { - struct lyd_node *node; - bool skip = false; - - chomp(line); - if (!line[0]) - continue; - - LYX_LIST_FOR_EACH(lyd_child(cfg), node, "entry") { - if (prefix_overlap(line, lyd_get_value(node))) { - skip = true; - break; - } - } - - if (skip) { - NOTE("address-set %s: dropping dynamic entry %s, overlaps static entry", - name, line); - continue; - } - - fprintf(fp, " %s\n", line); - } - fclose(sf); -} - static int generate_ipset(struct lyd_node *cfg, const char *name) { const char *family, *timeout, *desc; @@ -441,9 +383,6 @@ static int generate_ipset(struct lyd_node *cfg, const char *name) LYX_LIST_FOR_EACH(lyd_child(cfg), node, "entry") fprintf(fp, " %s\n", lyd_get_value(node)); - if (!timeout) - merge_dynamic(fp, cfg, name); - fprintf(fp, "\n"); return close_file(fp); @@ -745,10 +684,17 @@ int firewall_change(sr_session_ctx_t *session, struct lyd_node *config, struct l return SR_ERR_OK; } - /* Drop dynamic state of deleted address-sets */ + /* + * Drop dynamic state of deleted address-sets, and of sets + * that got a timeout: their entries expire on their own and + * must not be re-applied on reload. + */ clist = lydx_get_descendant(diff, "firewall", "address-set", NULL); LYX_LIST_FOR_EACH(clist, cnode, "address-set") { - if (lydx_get_op(cnode) == LYDX_OP_DELETE) + struct lyd_node *timeout = lydx_get_child(cnode, "timeout"); + + if (lydx_get_op(cnode) == LYDX_OP_DELETE || + (timeout && lydx_get_op(timeout) != LYDX_OP_DELETE)) erasef(ADDRSET_RUNDIR "/%s", lydx_get_cattr(cnode, "name")); } @@ -861,7 +807,7 @@ int firewall_change(sr_session_ctx_t *session, struct lyd_node *config, struct l LYX_LIST_FOR_EACH(clist, cnode, "service") generate_service(cnode, lydx_get_cattr(cnode, "name")); - /* Regenerate all address-sets, incl. dynamic entries */ + /* Regenerate all address-sets (static entries only) */ clist = lydx_get_descendant(tree, "firewall", "address-set", NULL); LYX_LIST_FOR_EACH(clist, cnode, "address-set") generate_ipset(cnode, lydx_get_cattr(cnode, "name"));