diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 8c06252002..f5768c0de9 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -16,7 +16,7 @@ on: - bananapi-bpi-r64 - friendlyarm-nanopi-r2s - microchip-sama7g54-ek - - acer-connect-vero-w + - acer-connect-vero-w6m default: 'raspberrypi-rpi64' jobs: @@ -93,7 +93,7 @@ jobs: bootloader_sdcard=bpi-r64-sd-boot bootloader_emmc=bpi-r64-emmc-boot ;; - acer-connect-vero-w) + acer-connect-vero-w6m) arch=aarch64 targets="emmc" bootloader_emmc=bpi-r3-emmc-boot diff --git a/board/aarch64/acer-connect-vero-w6m/README.md b/board/aarch64/acer-connect-vero-w6m/README.md index b0f67e525b..7905cbdcf0 100644 --- a/board/aarch64/acer-connect-vero-w6m/README.md +++ b/board/aarch64/acer-connect-vero-w6m/README.md @@ -49,23 +49,6 @@ U-Boot from the first partition after fip. installing. Interrupt autoboot and run `printenv` to note down: `2gMAC`, `5gMAC`, `6gMAC`, `LANMAC`, and `WANMAC`. -## Giving the Unit a Permanent MAC Address - -Infix does not read the stock U-Boot environment, so the unit boots -with random Ethernet addresses until it is given a product record. -Store one as `/mnt/aux/vpd.bin`, from the Infix shell, using the -`WANMAC` noted down above as the base address: - -``` -echo '{"mac-address":"","serial-number":"","vendor":"Acer","product-name":"Connect Vero W6m"}' \ - | onieprom -e > /mnt/aux/vpd.bin -``` - -After a reboot the ports get fixed offsets from the base address and -the WiFi interfaces derive theirs from it. See -[Vital Product Data](../../../doc/vpd.md#vpd-file-on-the-aux-partition) -for the record format. - ## Required Files Prepare these files on a host reachable from the Vero: @@ -89,15 +72,39 @@ host. ``` setenv serverip setenv ipaddr - setenv bootmenu_default 7 + setenv bootmenu_default tftpboot 0x46000000 u-boot.bin go 0x46000000 ``` `serverip` must point to your TFTP server and `ipaddr` must be a free - address for the Vero on the same subnet. `bootmenu_default 7` - bypasses secure boot verification. The Infix U-Boot will - automatically netboot the Infix system. + address for the Vero on the same subnet. `bootmenu_default ` + makes *U-Boot console* the default boot menu entry, which bypasses + secure boot verification. The Infix U-Boot will automatically + netboot the Infix system. + + The right value for `` depends on the stock U-Boot version, it is + usually 7 or 9. Type `bootmenu` at the prompt to list the entries, + then count down from the top, starting at 0, until you reach + *U-Boot console*. Do not use the number shown in the menu. In + this example the console is the tenth entry, so `` is 9: + + ``` + *** U-Boot Boot Menu *** + + 1. Startup system (Default) + 2. Upgrade firmware + 3. Upgrade ATF BL2 + 4. Upgrade ATF FIP + 5. Upgrade U-boot only + 6. Upgrade partition table + 7. Upgrade single image + 8. Load image + 9. Start Web failsafe + 0. U-Boot console + ``` + + Press Esc or Ctrl-C to leave the menu and get back to the prompt. 2. **From running Infix**, stream the image directly to eMMC: @@ -134,14 +141,70 @@ host. interrupt stock U-Boot again: ``` - setenv bootmenu_default 7 + setenv bootmenu_default setenv bootcmd 'mmc read 0x46000000 0x4400 0x10000; go 0x46000000' saveenv reset ``` The `bootcmd` reads the Infix U-Boot (at sector 0x4400/17408) - into RAM and jumps to it. After `reset`, Infix boots from eMMC. + into RAM and jumps to it. Use the same `` as in step 1. After + `reset`, Infix boots from eMMC. + +## Giving the Unit a Permanent MAC Address + +Infix does not read the stock U-Boot environment, so the unit boots +with random Ethernet addresses until it is given a product record. + +Acer allocates one address per interface (`LANMAC`, `WANMAC`, `2gMAC`, +`5gMAC` and `6gMAC`), and they are not a consecutive range. Infix +derives every address from a single base, so using one of the Acer +addresses as the base would hand out addresses Acer never assigned to +this unit. Instead, give the product record a private (locally +administered) base address, then set the real Acer addresses on the +interfaces themselves. + +1. **Create the product record** from the Infix shell, with a random + private base address: + + ``` + set -- $(od -An -N4 -tx1 /dev/urandom) + echo "{\"mac-address\":\"02:$1:$2:$3:$4:00\",\"serial-number\":\"\",\"vendor\":\"Acer\",\"product-name\":\"Connect Vero W6m\"}" \ + | onieprom -e > /mnt/aux/vpd.bin + ``` + + The leading `02` marks the address as locally administered, so it + can never clash with a vendor-assigned one. See [Vital Product + Data](../../../doc/vpd.md#vpd-file-on-the-aux-partition) for the + record format. + +2. **Set the Acer addresses** with `custom-phys-address`, using the + values noted down from the stock U-Boot environment: + + | Interface | Address | + |------------|----------| + | `br0` | `LANMAC` | + | `internet` | `WANMAC` | + | `wifi0-ap` | `2gMAC` | + | `wifi1-ap` | `6gMAC` | + | `wifi2-ap` | `5gMAC` | + + ``` + admin@vero:/> configure + admin@vero:/config/> set interface br0 custom-phys-address static + admin@vero:/config/> set interface internet custom-phys-address static + admin@vero:/config/> set interface wifi0-ap custom-phys-address static <2gMAC> + admin@vero:/config/> set interface wifi1-ap custom-phys-address static <6gMAC> + admin@vero:/config/> set interface wifi2-ap custom-phys-address static <5gMAC> + admin@vero:/config/> leave + admin@vero:/> copy running-config startup-config + ``` + + Interfaces without a `custom-phys-address`, such as `lan1`-`lan3`, + keep addresses derived from the private base. See [Custom MAC + Address](../../../doc/iface.md#custom-mac-address) for details. + +Reboot after step 1 so the new base address takes effect. [vero-openwrt]: https://openwrt.org/toh/acer/predator_vero_w6m [1]: https://github.com/kernelkit/infix/releases/download/latest-boot/infix-vero-w-emmc.img diff --git a/board/aarch64/raspberrypi-rpi64/README.md b/board/aarch64/raspberrypi-rpi64/README.md index 68151d68b4..b2513844d3 100644 --- a/board/aarch64/raspberrypi-rpi64/README.md +++ b/board/aarch64/raspberrypi-rpi64/README.md @@ -108,7 +108,7 @@ To configure WiFi as a client, first store your WiFi password in the keystore: admin@infix:/> configure admin@infix:/config/> edit keystore symmetric-key mywifi admin@infix:/config/keystore/…/mywifi/> set key-format passphrase-key-format -admin@infix:/config/keystore/…/mywifi/> change cleartext-symmetric-key +admin@infix:/config/keystore/…/mywifi/> edit cleartext-symmetric-key Passphrase: ************ Retype passphrase: ************ admin@infix:/config/keystore/…/mywifi/> leave @@ -120,6 +120,7 @@ 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 wifi radio radio0 admin@infix:/config/interface/wifi0/> set ipv4 dhcp-client admin@infix:/config/interface/wifi0/> set wifi station ssid YourNetworkName admin@infix:/config/interface/wifi0/> set wifi station security secret mywifi 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 09f87bfd0f..325ce52336 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 @@ -75,13 +75,6 @@ ] } } - }, - { - "name": "wifi0", - "type": "infix-if-type:wifi", - "infix-interfaces:wifi": { - "radio": "radio0" - } } ] }, 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 4a4f80e5f2..4ec7bdc130 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 @@ -89,13 +89,6 @@ ] } } - }, - { - "name": "wifi0", - "type": "infix-if-type:wifi", - "infix-interfaces:wifi": { - "radio": "radio0" - } } ] }, diff --git a/board/common/rootfs/usr/libexec/infix/iw.py b/board/common/rootfs/usr/libexec/infix/iw.py index b702357ea9..c942c81c36 100755 --- a/board/common/rootfs/usr/libexec/infix/iw.py +++ b/board/common/rootfs/usr/libexec/infix/iw.py @@ -9,6 +9,7 @@ iw.py survey - Get channel survey data iw.py survey-scan [passive] - Scan all channels, then get survey data + iw.py scan - Scan all channels, list the networks heard """ import os @@ -556,57 +557,207 @@ def wait_scan_done(events, ifname, deadline): buf += chunk -def survey_scan(radio, passive=False): +def scan_iface(radio): """ - 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. + Pick an interface of the radio to scan with, (ifname, iftype), and + the name of a temporary one to delete afterwards, or None. + + A station or mesh point is preferred over an access point since + those can scan without extra flags. A radio nobody has configured + yet has no interface to scan with, which is exactly when a scan or + survey helps pick a network, band and channel. Borrow one for the + duration, under a name no one would configure. """ try: with open(f'/sys/class/ieee80211/{radio}/index') as f: phy = f.read().strip() except OSError: - return {'error': f'no such radio: {radio}'} + raise RuntimeError(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)) + if ifaces: + return ifaces[0], None - # 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: + 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 '' + raise RuntimeError(f'no interface on {radio} to scan with: {err.strip() or e}') + return (tmp, 'managed'), tmp + + +def survey_scan(radio, passive=False): + """ + Scan every channel on the radio, then dump the survey. + + Returns the parse_survey() list, or {'error': ...}. + """ + try: + iface, tmp = scan_iface(radio) + except RuntimeError as e: + return {'error': str(e)} + + try: + return survey_scan_on(iface, passive) + finally: + if tmp: 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')] + + +def network_scan(radio): + """ + Scan every channel on the radio and list the networks heard. + + Returns a list of networks, best signal first, or {'error': ...}. + """ + try: + iface, tmp = scan_iface(radio) + except RuntimeError as e: + return {'error': str(e)} try: - return survey_scan_on(ifaces[0], passive) + err = trigger_scan(iface, False) + if err: + return err + return parse_scan_dump(iface[0]) 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().""" +def frequency_to_channel(freq): + """Channel number of a center frequency in MHz, 0 if unknown""" + if 2412 <= freq <= 2484: + return 14 if freq == 2484 else (freq - 2412) // 5 + 1 + if 5170 <= freq <= 5895: + return (freq - 5000) // 5 + if 5955 <= freq <= 7115: + return (freq - 5950) // 5 + return 0 + + +def scan_encryption(rsn, wpa, privacy): + """ + Name the security of a scanned network from the authentication + suites of its RSN and WPA information elements, in the terms a + user knows: 'WPA2-Personal', 'WPA3-Personal', 'WPA2-Enterprise', + 'WPA-Personal', 'Enhanced-Open', 'WEP' or 'Open'. + """ + result = [] + + if rsn is not None: + if 'SAE' in rsn: + result.append('WPA3-Personal') + if 'PSK' in rsn: + result.append('WPA2-Personal') + if '802.1X' in rsn: + result.append('WPA2-Enterprise') + if 'OWE' in rsn: + result.append('Enhanced-Open') + if wpa is not None: + if 'PSK' in wpa: + result.append('WPA-Personal') + if '802.1X' in wpa: + result.append('WPA-Enterprise') + + if result: + return result + if privacy: + return ['WEP'] + return ['Open'] + + +def parse_scan_dump(ifname): + """ + Parse 'iw dev scan dump' into a list of networks, best + signal first. Hidden networks have no ssid. + """ + output = run_iw('dev', ifname, 'scan', 'dump') + if output is None: + return {'error': f'scan dump on {ifname} failed'} + + networks = [] + bss = None + ie = None + + for line in output.splitlines(): + stripped = line.strip() + if line.startswith('BSS '): + match = re.match(r'BSS ([0-9a-f:]{17})', line) + if not match: + bss = None + continue + bss = {'bssid': match.group(1), 'privacy': False, 'rsn': None, 'wpa': None} + networks.append(bss) + ie = None + continue + if bss is None: + continue + + if stripped.startswith('freq:'): + try: + bss['frequency'] = int(float(stripped.split(':', 1)[1].strip())) + except ValueError: + pass + elif stripped.startswith('signal:'): + match = re.search(r'(-?[0-9.]+) dBm', stripped) + if match: + bss['signal'] = int(float(match.group(1))) + elif stripped.startswith('capability:'): + bss['privacy'] = ' Privacy' in stripped or stripped.startswith('capability: Privacy') + elif stripped.startswith('SSID:'): + ssid = decode_iw_ssid(stripped[5:].strip()) + if ssid: + bss['ssid'] = ssid + elif stripped.startswith('RSN:'): + ie = 'rsn' + bss[ie] = '' + elif stripped.startswith('WPA:'): + ie = 'wpa' + bss[ie] = '' + elif ie is not None and stripped.startswith('* Authentication suites:'): + bss[ie] = stripped.split(':', 1)[1].strip() + elif not stripped.startswith('*'): + ie = None + + result = [] + for bss in networks: + if 'frequency' not in bss: + continue + net = { + 'bssid': bss['bssid'], + 'frequency': bss['frequency'], + 'channel': frequency_to_channel(bss['frequency']), + 'encryption': scan_encryption(bss['rsn'], bss['wpa'], bss['privacy']), + } + if 'ssid' in bss: + net['ssid'] = bss['ssid'] + if 'signal' in bss: + net['signal'] = bss['signal'] + result.append(net) + + result.sort(key=lambda n: n.get('signal', -1000), reverse=True) + return result + + +def trigger_scan(iface, passive): + """ + Scan on one (ifname, iftype) and wait for it to finish. Returns + None when done, else {'error': ...}. + + 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. + """ ifname, iftype = iface args = ['iw', 'dev', ifname, 'scan', 'trigger'] if iftype == 'AP': @@ -647,6 +798,16 @@ def survey_scan_on(iface, passive): events.kill() events.wait() + return None + + +def survey_scan_on(iface, passive): + """Scan on one (ifname, iftype), then dump the survey, see survey_scan().""" + ifname = iface[0] + err = trigger_scan(iface, passive) + if err: + return err + channels = parse_survey(ifname) if not any(ch['in_use'] for ch in channels): # Not every driver flags the operating channel, mac80211_hwsim @@ -822,6 +983,7 @@ def main(): '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)', + 'scan': 'Scan all channels, list the networks heard (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)', @@ -838,6 +1000,7 @@ def main(): 'iw.py mesh wifi0', 'iw.py survey wlan0', 'iw.py survey-scan radio0 passive', + 'iw.py scan radio0', 'iw.py caps radio0' ] }, indent=2)) @@ -890,6 +1053,11 @@ def main(): data = {'error': 'survey-scan command requires radio argument'} else: data = survey_scan(sys.argv[2], 'passive' in sys.argv[3:]) + elif command == 'scan': + if len(sys.argv) < 3: + data = {'error': 'Radio name required for scan command'} + else: + data = network_scan(sys.argv[2]) elif command == 'caps': if len(sys.argv) < 3: data = {'error': 'caps command requires PHY/radio argument'} @@ -899,7 +1067,7 @@ 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: + if command in ('survey-scan', 'scan') and isinstance(data, dict) and 'error' in data: sys.exit(1) except Exception as e: diff --git a/board/common/rootfs/usr/libexec/infix/hostapd.sh b/board/common/rootfs/usr/libexec/infix/wifi-handover.sh similarity index 52% rename from board/common/rootfs/usr/libexec/infix/hostapd.sh rename to board/common/rootfs/usr/libexec/infix/wifi-handover.sh index 69631868a6..5c6c645c76 100755 --- a/board/common/rootfs/usr/libexec/infix/hostapd.sh +++ b/board/common/rootfs/usr/libexec/infix/wifi-handover.sh @@ -1,6 +1,6 @@ #!/bin/sh -# 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. +# Hand the clients over to another access point, then stop hostapd. +# Called by finit as the stop:script of hostapd, with $MAINPID set. # # 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 @@ -18,14 +18,9 @@ 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() +log() { - for arg in $CONFS; do - case $arg in - *.conf) echo "$arg" ;; - esac - done + logger -t wifi-handover -p daemon.notice "$*" } bsses() @@ -56,9 +51,12 @@ ask_to_move() done fi if [ -n "$cands" ]; then + # An MBO transition reason makes a client take a candidate rather + # than weigh it against this access point, which older clients do + # even with disassociation imminent # 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 + pref=1 abridged=1 mbo=0:0:1 $cands >/dev/null 2>&1 else hostapd_cli -i "$bss" disassoc_imminent "$sta" $TIMER >/dev/null 2>&1 fi @@ -68,7 +66,7 @@ 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 + grep -qs '^bss_transition=1' /etc/hostapd-*.conf || return 0 num=0 for bss in $(bsses); do @@ -78,7 +76,7 @@ handover() done done if [ $num -eq 0 ]; then - logger -t hostapd -p daemon.notice "stop: no stations on $(bsses | tr '\n' ' ')" + log "no stations on $(bsses | tr '\n' ' ')" return 0 fi @@ -90,75 +88,11 @@ handover() left=$(stations | grep -c .) if [ "$left" -eq 0 ]; then - logger -t hostapd -p daemon.notice "stop: asked $num station(s) to move, all left" + log "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" + log "asked $num station(s) to move, $left still here" 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 -} - -# 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 - wait_bss "$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 -} - -CONFS=$* -rm -rf /run/wifi-steer -mkdir -p /run/wifi-steer -trap stop TERM INT -hostapd "$@" & -pid=$! -helpers "$@" & -echo $! >> /run/wifi-steer/pids -rc=0 -while kill -0 "$pid" 2>/dev/null; do - wait "$pid" - rc=$? -done -exit $rc +handover +kill -TERM "$MAINPID" diff --git a/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py b/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py index 05fa709e9e..899d93d1aa 100755 --- a/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py +++ b/board/common/rootfs/usr/libexec/infix/wifi-neighbors.py @@ -27,15 +27,18 @@ 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 +neighbor reports and for the transition requests sent by wifi-handover.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. +at the roam, from a node that may be gone by then. This node's other +access points on the SSID are key holders too, a client moves between +the bands of one node, but they are no candidates: a node asking its +clients to leave is going down with all of them. 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. @@ -63,7 +66,7 @@ def log(msg): - subprocess.run(['logger', '-t', 'hostapd', '-p', 'daemon.notice', msg], + subprocess.run(['logger', '-t', 'wifi-neighbors', '-p', 'daemon.notice', msg], capture_output=True) @@ -203,7 +206,7 @@ def parse_announcement(data, keys, seen): 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]}, ' + log(f'{head[2][:64]} announces version {head[1][:8]}, ' f'ours is {VERSION}, ignoring it') return [] out = [] @@ -251,11 +254,18 @@ def write_rxkh(path, key, bssids): os.replace(f'{path}.tmp', path) -def publish(own, neighbors, published, bsses): +def publish(own, neighbors, published, holders, 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} + _, key, rxkh = bsses[bss] + mykh = set(mine) | {b for b, _, _, s in own.values() + if s == myssid and b != mybssid} + if rxkh and key and holders.get(bss) != mykh: + write_rxkh(rxkh, key, mykh) + hostapd_cli(bss, 'reload_rxkhs') + holders[bss] = mykh if published.get(bss) == mine: continue with open(f'{DIR}/{bss}.tmp', 'w') as f: @@ -269,10 +279,6 @@ def publish(own, neighbors, published, bsses): 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}'") @@ -298,11 +304,12 @@ def main(): except OSError: time.sleep(0.2) if not socks: - log('wifi-neighbors: no interface to announce on') + log('no interface to announce on') return neighbors = {} # bssid -> (bssid, freq, phy, ssid, last seen) published = {} + holders = {} # bss -> key holder BSSIDs written to its file next_send = 0.0 last_send = 0.0 own = {} @@ -344,9 +351,9 @@ def main(): del neighbors[bssid] if own: try: - publish(own, {b: n[:4] for b, n in neighbors.items()}, published, bsses) + publish(own, {b: n[:4] for b, n in neighbors.items()}, published, holders, bsses) except OSError as err: - log(f'wifi-neighbors: {err}') + log(str(err)) if __name__ == '__main__': diff --git a/board/common/rootfs/usr/libexec/infix/wifi-steer.sh b/board/common/rootfs/usr/libexec/infix/wifi-steer.sh index 0f9cd64db0..c141c2621e 100755 --- a/board/common/rootfs/usr/libexec/infix/wifi-steer.sh +++ b/board/common/rootfs/usr/libexec/infix/wifi-steer.sh @@ -21,6 +21,7 @@ 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 +rm -rf "$state" mkdir -p "$state" # Neighbor report candidate for the twin: BSSID, BSSID information, @@ -91,6 +92,6 @@ while sleep $PERIOD; do 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" + logger -t wifi-steer -p daemon.notice "$bss: asked $sta to move to $twin" done done diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 55d7ca5c8c..63d9713141 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -71,6 +71,12 @@ All notable changes to the project are documented in this file. - 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 +- WiFi network scan is on request too: `show hardware scan`, the + Scan networks button on the WebUI WiFi page, or the `scan` action on + the radio, which works before any WiFi interface exists. A WiFi + interface with only a radio no longer scans in the background, and the + `scan-results` list under a station is gone, see + [WiFi documentation](wifi.md#discovering-available-networks) - Configurable OSPF auto-cost reference bandwidth, for OSPFv2 and OSPFv3, issue #1576. The default, 100000 Mbit/s, is the value used before, so link costs do not change on upgrade. The CLI shows it in `show ip ospf` diff --git a/doc/cli/configure.md b/doc/cli/configure.md index 5d3e997f82..7e72f1887e 100644 --- a/doc/cli/configure.md +++ b/doc/cli/configure.md @@ -133,13 +133,13 @@ User management, including passwords, is also a part of `ietf-system`. ``` admin@host:/config/> edit system authentication user admin -admin@host:/config/system/authentication/user/admin/> change password +admin@host:/config/system/authentication/user/admin/> edit password New password: Retype password: admin@host:/config/system/authentication/user/admin/> leave ``` -The `change password` command starts an interactive dialogue that asks +The `edit password` command starts an interactive dialogue that asks for the new password, with a confirmation, and then salts and encrypts the password with sha512crypt. diff --git a/doc/ntp.md b/doc/ntp.md index 100e955e60..ac7a742b3e 100644 --- a/doc/ntp.md +++ b/doc/ntp.md @@ -169,6 +169,7 @@ admin@example:/config/ntp/…/0.pool.ntp.org/type/uc-server/> end Poll intervals are specified as powers of 2: + - `minpoll 4` = poll every 2^4 = 16 seconds (minimum polling rate) - `maxpoll 10` = poll every 2^10 = 1024 seconds (maximum polling rate) - Defaults: minpoll 6 (64 seconds), maxpoll 10 (1024 seconds) diff --git a/doc/syslog.md b/doc/syslog.md index db6a68f419..d3ba9dc95a 100644 --- a/doc/syslog.md +++ b/doc/syslog.md @@ -325,6 +325,7 @@ admin@example:/> Available properties: + - `msg`: Message body - `msgid`: RFC5424 message identifier - `programname`: Program/tag name @@ -333,6 +334,7 @@ Available properties: - `data`: RFC5424 structured data Available operators: + - `contains`: Substring match - `isequal`: Exact equality - `startswith`: Prefix match diff --git a/doc/system.md b/doc/system.md index 181e80a703..14330a737d 100644 --- a/doc/system.md +++ b/doc/system.md @@ -23,13 +23,13 @@ User management, including passwords, SSH keys, remote authentication is available in the system authentication configuration context.
admin@example:/config/> edit system authentication user admin
-admin@example:/config/system/…/user/admin/> change password
+admin@example:/config/system/…/user/admin/> edit password
 New password:
 Retype password:
 admin@example:/config/system/…/user/admin/> leave
 
-The `change password` command starts an interactive dialogue that asks +The `edit password` command starts an interactive dialogue that asks for the new password, with a confirmation, and then salts and encrypts the password with sha512crypt. @@ -200,7 +200,7 @@ For detailed rules, use: show nacm group <name> Creating a new user starts with defining the user account in the system:
admin@example:/config/> edit system authentication user jacky
-admin@example:/config/system/…/user/jacky/> change password
+admin@example:/config/system/…/user/jacky/> edit password
 New password:
 Retype password:
 admin@example:/config/system/…/user/jacky/> leave
diff --git a/doc/vpn-wireguard.md b/doc/vpn-wireguard.md
index a3d41b97d4..5b3971108e 100644
--- a/doc/vpn-wireguard.md
+++ b/doc/vpn-wireguard.md
@@ -192,6 +192,7 @@ admin@example:/>
 
In this example: + - Both peers inherit `endpoint-port 51820` and `persistent-keepalive 25` from the bag - `office-west` overrides the keepalive to 10 seconds while `office-east` uses the default 25 - Each peer has its own `endpoint` and `allowed-ips` configuration diff --git a/doc/wifi.md b/doc/wifi.md index 7a781cb52a..65a2398f45 100644 --- a/doc/wifi.md +++ b/doc/wifi.md @@ -152,12 +152,13 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave **Key radio parameters:** - **⚠ Must match your physical location for legal compliance! ⚠** +**⚠ Must match your physical location for legal compliance! ⚠** + - `band`: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Automatically enables appropriate WiFi standards: - - 2.4GHz: 802.11n/ax - - 5GHz: 802.11n/ac/ax - - 6GHz: 802.11ax + - 2.4GHz: 802.11n/ax + - 5GHz: 802.11n/ac/ax + - 6GHz: 802.11ax - `channel`: Channel number (1-233) or "auto". When set to "auto", defaults to channel 6 for 2.4GHz, channel 36 for 5GHz, or channel 37 for 6GHz - `channel-width`: AP channel bandwidth. Supported values are `auto`, `20MHz`, @@ -264,72 +265,29 @@ performance through features like OFDMA, BSS Coloring, and beamforming. ## Discovering Available Networks -Before connecting to a WiFi network, you need to discover which networks -are available. Infix automatically scans for networks when a WiFi interface -is created with a radio reference. - -### Enable Background Scanning - -To enable scanning without connecting, configure the radio and create a WiFi -interface referencing it: - -**Step 1: Configure the radio** - -
admin@example:/> configure
-admin@example:/config/> edit hardware component radio0 wifi-radio
-admin@example:/config/hardware/component/radio0/wifi-radio/> leave
+Before connecting to a WiFi network you need to know which networks are in
+range.  A scan lists them, with signal strength and security, and works on
+any radio, whether or not it has an interface yet.  The radio leaves its
+channel for a few seconds during the scan, so traffic on it pauses.
+
+
admin@example:/> show hardware radio0 scan
+SSID                  BSSID              SECURITY       SIGNAL   CHANNEL
+MyNetwork             b4:fb:e4:17:b6:a7  WPA2-Personal  -45 dBm        6
+MyNetwork-6G          b4:fb:e4:17:b6:a8  WPA3-Personal  -58 dBm       37
+GuestWiFi             c8:3a:35:12:34:56  WPA2-Personal  -62 dBm       11
+CoffeeShop            00:1a:2b:3c:4d:5e  Open           -78 dBm        1
 
-**Step 2: Create WiFi interface with radio reference only** - -
admin@example:/> configure
-admin@example:/config/> edit interface wifi0
-admin@example:/config/interface/wifi0/> set wifi radio radio0
-admin@example:/config/interface/wifi0/> leave
-
- -The system will now start scanning in the background. The interface will -operate in scan-only mode until you configure a specific mode (station or -access-point). - -### View Available Networks - -Use `show interface` to see discovered networks and their signal strength: - -
admin@example:/> show interface wifi0
-name               : wifi0
-type               : wifi
-index              : 3
-mtu                : 1500
-operational status : up
-ip forwarding      : enabled
-physical address   : f0:09:0d:36:5f:86
-ipv4 addresses     : 192.168.1.100/24 (dhcp)
-ipv6 addresses     :
-in-octets          : 148388
-out-octets         : 24555
-mode               : station
-ssid               : MyNetwork
-bssid              : b4:fb:e4:17:b6:a7
-signal             : -45 dBm (good)
-rx bitrate         : 72.2 Mbps
-tx bitrate         : 86.6 Mbps
-──────────────────────────────────────────────────────────────────────
-Available Networks
-SSID                 BSSID              SECURITY       SIGNAL  CHANNEL
-MyNetwork            b4:fb:e4:17:b6:a7  WPA2-Personal  good          6
-GuestWiFi            c8:3a:35:12:34:56  WPA2-Personal  fair         11
-CoffeeShop           00:1a:2b:3c:4d:5e  Open           bad           1
-
+A hidden network shows up without a name. Several entries with the same +SSID are the access points of one roaming network, the BSSID tells them +apart. -In the CLI, signal strength is reported as: excellent, good, fair or bad. -For precise signal strength values in dBm, use NETCONF or RESTCONF to access -the `signal-strength` leaf in the operational datastore. +In the WebUI, each radio on the WiFi page has a **Scan networks** button +that lists the same networks. -When connected, `bssid` shows the MAC address of the access point the -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. +Over NETCONF or RESTCONF the scan is the `scan` action on the radio's +hardware component. Its output is a list of networks keyed by BSSID, with +the SSID, frequency, channel, signal strength and encryption of each. ### Channel Survey @@ -377,8 +335,8 @@ hardware, your passphrase must meet the following criteria: ## Station Mode (Client) Station mode connects to an existing Wi-Fi network. Before configuring station -mode, follow the "Discovering Available Networks (Scanning)" section above to -scan for available networks and identify the SSID you want to connect to. +mode, follow the "Discovering Available Networks" section above to scan for +available networks and identify the SSID you want to connect to. ### Step 1: Configure Password @@ -402,6 +360,7 @@ Configure station mode with the SSID and password to connect:
admin@example:/> configure
 admin@example:/config/> edit interface wifi0
+admin@example:/config/interface/wifi0/> set wifi radio radio0
 admin@example:/config/interface/wifi0/> set wifi station ssid MyHomeNetwork
 admin@example:/config/interface/wifi0/> set wifi station security secret my-wifi-key
 admin@example:/config/interface/wifi0/> leave
@@ -422,7 +381,7 @@ signal             : -52 dBm (good)
 
 **Station configuration parameters:**
 
-- `radio`: Reference to the WiFi radio (mandatory) - already set during scanning
+- `radio`: Reference to the WiFi radio (mandatory)
 - `station ssid`: Network name to connect to (mandatory)
 - `station security mode`:
       - `auto`: default, WPA2/WPA3 auto-negotiation
@@ -433,6 +392,8 @@ signal             : -52 dBm (good)
 > [!NOTE]
 > The `auto` security mode automatically selects WPA3-SAE or WPA2-PSK based on
 > what the access point supports, prioritizing WPA3 for better security.
+> The station offers management frame protection on every connection, so
+> WPA3-only networks, and any network on 6 GHz, accept it.
 > Certificate-based authentication (802.1X/EAP) is not yet supported.
 
 ## Access Point Mode
@@ -451,7 +412,7 @@ create a keystore entry for your WiFi password and configure the AP interface:
 
admin@example:/> configure
 admin@example:/config/> edit keystore symmetric-key my-wifi-secret
 admin@example:/config/keystore/…/my-wifi-secret/> set key-format passphrase-key-format
-admin@example:/config/keystore/…/my-wifi-secret/> change cleartext-symmetric-key
+admin@example:/config/keystore/…/my-wifi-secret/> edit cleartext-symmetric-key
 Passphrase: ************
 Retype passphrase: ************
 admin@example:/config/keystore/…/my-wifi-secret/> end
@@ -514,17 +475,17 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave
 
admin@example:/> configure
 admin@example:/config/> edit keystore symmetric-key main-secret
 admin@example:/config/keystore/…/main-secret/> set key-format passphrase-key-format
-admin@example:/config/keystore/…/main-secret/> change cleartext-symmetric-key
+admin@example:/config/keystore/…/main-secret/> edit cleartext-symmetric-key
 Passphrase: ************
 Retype passphrase: ************
 admin@example:/config/> edit keystore symmetric-key guest-secret
 admin@example:/config/keystore/…/guest-secret/> set key-format passphrase-key-format
-admin@example:/config/keystore/…/guest-secret/> change cleartext-symmetric-key
+admin@example:/config/keystore/…/guest-secret/> edit cleartext-symmetric-key
 Passphrase: ************
 Retype passphrase: ************
 admin@example:/config/> edit keystore symmetric-key iot-secret
 admin@example:/config/keystore/…/iot-secret/> set key-format passphrase-key-format
-admin@example:/config/keystore/…/iot-secret/> change cleartext-symmetric-key
+admin@example:/config/keystore/…/iot-secret/> edit cleartext-symmetric-key
 Passphrase: ************
 Retype passphrase: ************
 admin@example:/config/keystore/…/iot-secret/> leave
@@ -561,11 +522,12 @@ admin@example:/config/interface/wifi2/> leave
 > 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).
+> `06:53:00:c0:ff:ee` and `wifi1` becomes `0a:53:00:c0:ff:ee`.  An
+> interface whose name has no number, such as `backhaul`, gets its number
+> from a hash of the name.  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:
 
@@ -581,12 +543,13 @@ All APs on the same radio share the same channel and physical layer settings
 - Hidden/visible SSID setting
 - Bridge membership
 
-You can verify the configuration with `show hardware component radio0` to see
+You can verify the configuration with `show hardware radio0` to see
 radio settings, and `show interface` to see all active AP interfaces.
 
-> [!IMPORTANT]
-> AP and Station modes cannot be mixed on the same radio. All virtual interfaces
-> on a radio must be the same mode (all APs or all Stations).
+> [!NOTE]
+> A radio hosts any number of access points but only one station, and
+> the station follows its access point's channel, see [Current
+> Limitations](#current-limitations).
 
 ## Fast Roaming Between Access Points
 
@@ -611,15 +574,17 @@ 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
-```
+
admin@example:/config/> edit keystore symmetric-key roaming-key
+admin@example:/config/keystore/…/roaming-key/> set key-format passphrase-key-format
+admin@example:/config/keystore/…/roaming-key/> edit cleartext-symmetric-key
+Passphrase: ************
+Retype passphrase: ************
+admin@example:/config/keystore/…/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 @@ -635,6 +600,7 @@ admin@example:/config/> set interface wifi0 wifi access-point roaming dot11r key there is enough **Mobility Domain Options:** + - Explicit 4-character hex value (e.g., `4f57`) - default if not specified - `hash` - Automatically derive from SSID using MD5 (OpenWrt-compatible) @@ -849,7 +815,7 @@ network must share the same passphrase:
admin@example:/> configure
 admin@example:/config/> edit keystore symmetric-key mesh-secret
 admin@example:/config/keystore/…/mesh-secret/> set key-format passphrase-key-format
-admin@example:/config/keystore/…/mesh-secret/> change cleartext-symmetric-key
+admin@example:/config/keystore/…/mesh-secret/> edit cleartext-symmetric-key
 Passphrase: ************
 Retype passphrase: ************
 admin@example:/config/keystore/…/mesh-secret/> end
@@ -982,8 +948,9 @@ 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. +A station without `wds` cannot be a bridge port. The configuration is +accepted, but the port is refused when it is applied and never comes +up. 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 @@ -1018,8 +985,8 @@ loop, as with any two bridge ports to the same network. Use `show interface wifi0` to verify signal strength and connection status. If issues arise, try the following troubleshooting steps: -1. **Verify signal strength**: Check that the target network shows - "good" or "excellent" signal in scan results +1. **Verify signal strength**: Check the target network's signal with + `show hardware radio0 scan`, -65 dBm or better is good 2. **Check credentials**: Use `show keystore symmetric ` to verify the passphrase matches the network password 3. **Review logs**: Check system logs with `show log` for Wi-Fi related diff --git a/patches/linux/6.18.57/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch b/patches/linux/6.18.57/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch index 8d67c3df60..14d9f09ffd 100644 --- a/patches/linux/6.18.57/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch +++ b/patches/linux/6.18.57/0001-net-phy-marvell10g-Support-firmware-loading-on-88X33.patch @@ -1,7 +1,7 @@ From a3d812890f51189fc70b419a37f2fe038fe2395d Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 19 Sep 2023 18:38:10 +0200 -Subject: [PATCH 001/102] net: phy: marvell10g: Support firmware loading on +Subject: [PATCH 001/105] 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.57/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch b/patches/linux/6.18.57/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch index 37b285f2a1..eebd100d55 100644 --- a/patches/linux/6.18.57/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch +++ b/patches/linux/6.18.57/0002-net-phy-marvell10g-Fix-power-up-when-strapped-to-sta.patch @@ -1,7 +1,7 @@ From 2a59f9d7a2c7d3c847db7ee4e5bf2f4ddf7b4702 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 21 Nov 2023 20:15:24 +0100 -Subject: [PATCH 002/102] net: phy: marvell10g: Fix power-up when strapped to +Subject: [PATCH 002/105] 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.57/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch b/patches/linux/6.18.57/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch index 74cdd4fd5e..c5541acb73 100644 --- a/patches/linux/6.18.57/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch +++ b/patches/linux/6.18.57/0003-net-phy-marvell10g-Add-LED-support-for-88X3310.patch @@ -1,7 +1,7 @@ From a43ab064f5f15038c30db9b6d9f3ccdc7eaf57ab Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 15 Nov 2023 20:58:42 +0100 -Subject: [PATCH 003/102] net: phy: marvell10g: Add LED support for 88X3310 +Subject: [PATCH 003/105] 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.57/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch b/patches/linux/6.18.57/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch index 2ff7e9dbe6..1b1577e0c4 100644 --- a/patches/linux/6.18.57/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch +++ b/patches/linux/6.18.57/0004-net-phy-marvell10g-Support-LEDs-tied-to-a-single-med.patch @@ -1,7 +1,7 @@ From c2c0be4516d1c42574bf5ec923cd1b28749ed079 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Dec 2023 09:51:05 +0100 -Subject: [PATCH 004/102] net: phy: marvell10g: Support LEDs tied to a single +Subject: [PATCH 004/105] 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.57/0005-net-phy-Do-not-resume-PHY-when-attaching.patch b/patches/linux/6.18.57/0005-net-phy-Do-not-resume-PHY-when-attaching.patch index f3c7377df7..ca96793e8d 100644 --- a/patches/linux/6.18.57/0005-net-phy-Do-not-resume-PHY-when-attaching.patch +++ b/patches/linux/6.18.57/0005-net-phy-Do-not-resume-PHY-when-attaching.patch @@ -1,7 +1,7 @@ From 531ee82493ae9b2419a0d00891cab20fa1c99751 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 10:10:19 +0100 -Subject: [PATCH 005/102] net: phy: Do not resume PHY when attaching +Subject: [PATCH 005/105] 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.57/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch b/patches/linux/6.18.57/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch index 563ab50e75..bfb2497f11 100644 --- a/patches/linux/6.18.57/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch +++ b/patches/linux/6.18.57/0006-net-bridge-avoid-classifying-unknown-multicast-as-mr.patch @@ -1,7 +1,7 @@ From 9d9a85e79b3ed5844e47efbab43c854b5cd50695 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 4 Mar 2024 16:47:28 +0100 -Subject: [PATCH 006/102] net: bridge: avoid classifying unknown multicast as +Subject: [PATCH 006/105] 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.57/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch b/patches/linux/6.18.57/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch index 0886ae84bb..4a8085833c 100644 --- a/patches/linux/6.18.57/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch +++ b/patches/linux/6.18.57/0007-net-bridge-Ignore-router-ports-when-forwarding-L2-mu.patch @@ -1,7 +1,7 @@ From 8650a96e792f68b3fe6b0e48a56bc1dbe478b899 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 5 Mar 2024 06:44:41 +0100 -Subject: [PATCH 007/102] net: bridge: Ignore router ports when forwarding L2 +Subject: [PATCH 007/105] 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.57/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch b/patches/linux/6.18.57/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch index 3f6f2d3f4e..37a51d8b9c 100644 --- a/patches/linux/6.18.57/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch +++ b/patches/linux/6.18.57/0008-net-bridge-drop-delay-for-applying-strict-multicast-.patch @@ -1,7 +1,7 @@ From 3168dbb99768d0967714da20f1d59ba2b039e287 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 4 Apr 2024 16:36:30 +0200 -Subject: [PATCH 008/102] net: bridge: drop delay for applying strict multicast +Subject: [PATCH 008/105] 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.57/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch b/patches/linux/6.18.57/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch index c98cf44308..1bdab8dfd7 100644 --- a/patches/linux/6.18.57/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch +++ b/patches/linux/6.18.57/0009-net-bridge-Differentiate-MDB-additions-from-modifica.patch @@ -1,7 +1,7 @@ From 80187190309db1097de8442aa2bce72276af00b2 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 16 May 2024 14:51:54 +0200 -Subject: [PATCH 009/102] net: bridge: Differentiate MDB additions from +Subject: [PATCH 009/105] 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.57/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch b/patches/linux/6.18.57/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch index 178dfee78d..4a2e059ebd 100644 --- a/patches/linux/6.18.57/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch +++ b/patches/linux/6.18.57/0010-nvmem-layouts-onie-tlv-Let-device-probe-even-when-TL.patch @@ -1,7 +1,7 @@ From cfd0d7f5f1762efebab226a88910c9852f0b71f1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 24 Nov 2023 23:29:55 +0100 -Subject: [PATCH 010/102] nvmem: layouts: onie-tlv: Let device probe even when +Subject: [PATCH 010/105] 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.57/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch b/patches/linux/6.18.57/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch index b50b5b2d8a..7dc3402a92 100644 --- a/patches/linux/6.18.57/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch +++ b/patches/linux/6.18.57/0011-net-usb-r8152-add-r8153b-support-for-link-activity-L.patch @@ -1,7 +1,7 @@ From 1fa2ebb08f708cd6c002dbdd7b2061ed1e011764 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 11 Aug 2024 11:27:35 +0200 -Subject: [PATCH 011/102] net: usb: r8152: add r8153b support for link/activity +Subject: [PATCH 011/105] 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.57/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch b/patches/linux/6.18.57/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch index 5782f77e82..9287c85e67 100644 --- a/patches/linux/6.18.57/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch +++ b/patches/linux/6.18.57/0012-arm64-dts-mediatek-mt7986a-rename-BPi-R3-ports-to-ma.patch @@ -1,7 +1,7 @@ From 70da24ec1e91985b5c039082bfc1fa2bac974f63 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 10 Aug 2025 18:52:54 +0200 -Subject: [PATCH 012/102] arm64: dts: mediatek: mt7986a: rename BPi R3 ports to +Subject: [PATCH 012/105] 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.57/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch b/patches/linux/6.18.57/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch index 653e02341b..f3c5571f7e 100644 --- a/patches/linux/6.18.57/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch +++ b/patches/linux/6.18.57/0013-drm-panel-simple-Add-a-timing-for-the-Raspberry-Pi-7.patch @@ -1,7 +1,7 @@ From e12c89125b2d6a8cda9004f11f0851e443cd1723 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 20 Aug 2025 21:38:24 +0200 -Subject: [PATCH 013/102] drm/panel-simple: Add a timing for the Raspberry Pi +Subject: [PATCH 013/105] 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.57/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch b/patches/linux/6.18.57/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch index e1fea2ef8d..ead58a8f14 100644 --- a/patches/linux/6.18.57/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch +++ b/patches/linux/6.18.57/0014-input-touchscreen-edt-ft5x06-Add-polled-mode.patch @@ -1,7 +1,7 @@ From dc8a3b2bfa67af9ef458571b69a0614375deefa2 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 21 Aug 2025 11:20:23 +0200 -Subject: [PATCH 014/102] input:touchscreen:edt-ft5x06: Add polled mode +Subject: [PATCH 014/105] 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.57/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch b/patches/linux/6.18.57/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch index 044a0f0e70..507c29aa62 100644 --- a/patches/linux/6.18.57/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch +++ b/patches/linux/6.18.57/0015-FIX-net-dsa-mv88e6xxx-Fix-timeout-on-waiting-for-PPU.patch @@ -1,7 +1,7 @@ From 10a5c6575f2fd6c1534c88171e6de89a25e07ae3 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 12 Mar 2024 10:27:24 +0100 -Subject: [PATCH 015/102] [FIX] net: dsa: mv88e6xxx: Fix timeout on waiting for +Subject: [PATCH 015/105] [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.57/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch b/patches/linux/6.18.57/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch index 99007915bd..162120a1ea 100644 --- a/patches/linux/6.18.57/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch +++ b/patches/linux/6.18.57/0016-net-dsa-mv88e6xxx-Improve-indirect-register-access-p.patch @@ -1,7 +1,7 @@ From 3d4aa3d2ef925c1048c99b31760b3acdba4450e1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 27 Mar 2024 15:52:43 +0100 -Subject: [PATCH 016/102] net: dsa: mv88e6xxx: Improve indirect register access +Subject: [PATCH 016/105] 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.57/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch b/patches/linux/6.18.57/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch index ce6281e06d..f7b4f104d5 100644 --- a/patches/linux/6.18.57/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch +++ b/patches/linux/6.18.57/0017-net-dsa-mv88e6xxx-Honor-ports-being-managed-via-in-b.patch @@ -1,7 +1,7 @@ From 2b2ba8743e3bd4405502d30b459bcf55d567e6a1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 22 Apr 2024 23:18:01 +0200 -Subject: [PATCH 017/102] net: dsa: mv88e6xxx: Honor ports being managed via +Subject: [PATCH 017/105] 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.57/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch b/patches/linux/6.18.57/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch index 2046e327ee..242cf5bb05 100644 --- a/patches/linux/6.18.57/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch +++ b/patches/linux/6.18.57/0018-net-dsa-mv88e6xxx-Limit-rsvd2cpu-policy-to-user-port.patch @@ -1,7 +1,7 @@ From 78b6eab5fff4b3d733a62120da3b8c1866fcc783 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 24 Apr 2024 22:41:04 +0200 -Subject: [PATCH 018/102] net: dsa: mv88e6xxx: Limit rsvd2cpu policy to user +Subject: [PATCH 018/105] 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.57/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch b/patches/linux/6.18.57/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch index 3179ab3243..77e648c3f4 100644 --- a/patches/linux/6.18.57/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch +++ b/patches/linux/6.18.57/0019-net-dsa-tag_dsa-Use-tag-priority-as-initial-skb-prio.patch @@ -1,7 +1,7 @@ From 3be8bf956b25b37a99ba802351bfd4c260ab44a0 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 10:38:42 +0200 -Subject: [PATCH 019/102] net: dsa: tag_dsa: Use tag priority as initial +Subject: [PATCH 019/105] 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.57/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch b/patches/linux/6.18.57/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch index 9e57137664..bc73ad210d 100644 --- a/patches/linux/6.18.57/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch +++ b/patches/linux/6.18.57/0020-net-dsa-Support-MDB-memberships-whose-L2-addresses-o.patch @@ -1,7 +1,7 @@ From 1193f95e9eb34da7a8056c676aeda694212e31fa Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 16 Jan 2024 16:00:55 +0100 -Subject: [PATCH 020/102] net: dsa: Support MDB memberships whose L2 addresses +Subject: [PATCH 020/105] 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.57/0021-net-dsa-Support-EtherType-based-priority-overrides.patch b/patches/linux/6.18.57/0021-net-dsa-Support-EtherType-based-priority-overrides.patch index e82ffa3b95..70f2329ef8 100644 --- a/patches/linux/6.18.57/0021-net-dsa-Support-EtherType-based-priority-overrides.patch +++ b/patches/linux/6.18.57/0021-net-dsa-Support-EtherType-based-priority-overrides.patch @@ -1,7 +1,7 @@ From 7f07711bf7200141a3963592126a5395cf50483c Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 21 Mar 2024 19:12:15 +0100 -Subject: [PATCH 021/102] net: dsa: Support EtherType based priority overrides +Subject: [PATCH 021/105] net: dsa: Support EtherType based priority overrides --- include/net/dsa.h | 4 ++++ diff --git a/patches/linux/6.18.57/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch b/patches/linux/6.18.57/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch index d3518e8cb0..843e062570 100644 --- a/patches/linux/6.18.57/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch +++ b/patches/linux/6.18.57/0022-net-dsa-mv88e6xxx-Support-EtherType-based-priority-o.patch @@ -1,7 +1,7 @@ From e15ceaac9b2ebe24422e42c6b7fa9f2b7daee2f8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 22 Mar 2024 16:15:43 +0100 -Subject: [PATCH 022/102] net: dsa: mv88e6xxx: Support EtherType based priority +Subject: [PATCH 022/105] net: dsa: mv88e6xxx: Support EtherType based priority overrides --- diff --git a/patches/linux/6.18.57/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch b/patches/linux/6.18.57/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch index e0ac0a5027..4f4f3e8760 100644 --- a/patches/linux/6.18.57/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch +++ b/patches/linux/6.18.57/0023-net-dsa-mv88e6xxx-Add-mqprio-qdisc-support.patch @@ -1,7 +1,7 @@ From 94fa7cbb59c02912a1ec8d203ea2923c422d48c1 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 28 May 2024 11:04:22 +0200 -Subject: [PATCH 023/102] net: dsa: mv88e6xxx: Add mqprio qdisc support +Subject: [PATCH 023/105] 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.57/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch b/patches/linux/6.18.57/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch index 1bcfccdfe5..d20bede6b2 100644 --- a/patches/linux/6.18.57/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch +++ b/patches/linux/6.18.57/0024-net-dsa-mv88e6xxx-Use-VLAN-prio-over-IP-when-both-ar.patch @@ -1,7 +1,7 @@ From 57ed0e3c9767f4b57a0643d207f062a7fb776190 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 29 May 2024 13:20:41 +0200 -Subject: [PATCH 024/102] net: dsa: mv88e6xxx: Use VLAN prio over IP when both +Subject: [PATCH 024/105] 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.57/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch b/patches/linux/6.18.57/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch index a00f160f4d..bdff0cb3f2 100644 --- a/patches/linux/6.18.57/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch +++ b/patches/linux/6.18.57/0025-FIX-net-dsa-mv88e6xxx-Trap-locally-terminated-VLANs.patch @@ -1,7 +1,7 @@ From 0cba0f7b7ed4589b57ddb43c2e310104aec83766 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 26 Nov 2024 19:45:59 +0100 -Subject: [PATCH 025/102] [FIX] net: dsa: mv88e6xxx: Trap locally terminated +Subject: [PATCH 025/105] [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.57/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch b/patches/linux/6.18.57/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch index 4270701d35..79d9f8d0e4 100644 --- a/patches/linux/6.18.57/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch +++ b/patches/linux/6.18.57/0026-net-dsa-mv88e6xxx-collapse-disabled-state-into-block.patch @@ -1,7 +1,7 @@ From 2e7660ff03b93bf10fbe2fa7a0f3cb6daf515966 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 16 Jan 2025 12:35:12 +0100 -Subject: [PATCH 026/102] net: dsa: mv88e6xxx: collapse disabled state into +Subject: [PATCH 026/105] 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.57/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch b/patches/linux/6.18.57/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch index 5351f0e75b..92f22412db 100644 --- a/patches/linux/6.18.57/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch +++ b/patches/linux/6.18.57/0027-net-dsa-mv88e6xxx-Only-activate-LAG-offloading-when-.patch @@ -1,7 +1,7 @@ From 3a7e7ca03128893fa0f4e879c726e46c88730dd6 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 12 Feb 2025 22:03:14 +0100 -Subject: [PATCH 027/102] net: dsa: mv88e6xxx: Only activate LAG offloading +Subject: [PATCH 027/105] 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.57/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch b/patches/linux/6.18.57/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch index c87c6d3fd4..584dcbaba9 100644 --- a/patches/linux/6.18.57/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch +++ b/patches/linux/6.18.57/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch @@ -1,7 +1,7 @@ From fc9673985eb18fb570381aafe73919b5f4964a86 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 14 Jan 2026 18:22:41 +0100 -Subject: [PATCH 028/102] net: dsa: mv88e6xxx: Add LED support for 6393X +Subject: [PATCH 028/105] net: dsa: mv88e6xxx: Add LED support for 6393X Original commit: commit 462277b926140ee2d231317e92afb6cabf640268 diff --git a/patches/linux/6.18.57/0029-wifi-brcmfmac-check-connection-state-before-querying.patch b/patches/linux/6.18.57/0029-wifi-brcmfmac-check-connection-state-before-querying.patch index 5830945c0a..634243cf8a 100644 --- a/patches/linux/6.18.57/0029-wifi-brcmfmac-check-connection-state-before-querying.patch +++ b/patches/linux/6.18.57/0029-wifi-brcmfmac-check-connection-state-before-querying.patch @@ -1,7 +1,7 @@ From 708cc18d4ebb8ab95cb998a8b9b0966f17329fa7 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 19 Jan 2026 13:06:53 +0100 -Subject: [PATCH 029/102] wifi: brcmfmac: check connection state before +Subject: [PATCH 029/105] 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.57/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch b/patches/linux/6.18.57/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch index 05ac7db3f3..cb23c55bce 100644 --- a/patches/linux/6.18.57/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch +++ b/patches/linux/6.18.57/0030-wifi-brcmfmac-suppress-log-spam-for-regulatory-restr.patch @@ -1,7 +1,7 @@ From 71c8a7ad6583d59f611f4b2a14c66570ec2d73fb Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:12:10 +0100 -Subject: [PATCH 030/102] wifi: brcmfmac: suppress log spam for +Subject: [PATCH 030/105] 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.57/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch b/patches/linux/6.18.57/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch index 87278995de..5dc94c0594 100644 --- a/patches/linux/6.18.57/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch +++ b/patches/linux/6.18.57/0031-wifi-brcmfmac-reduce-log-noise-during-AP-to-station-.patch @@ -1,7 +1,7 @@ From 2e7e5bd36b3618cfa2f5aa299100154aa789ee57 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 20 Jan 2026 20:18:45 +0100 -Subject: [PATCH 031/102] wifi: brcmfmac: reduce log noise during AP to station +Subject: [PATCH 031/105] 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.57/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch b/patches/linux/6.18.57/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch index 006b3a7639..d074c62ae1 100644 --- a/patches/linux/6.18.57/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch +++ b/patches/linux/6.18.57/0032-net-phy-air_en8811h-add-OF-device-table-for-auto-loa.patch @@ -1,7 +1,7 @@ From a5fc393818f3b0784538aaa169fb19cd7b7a7af4 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 17 Feb 2026 21:59:59 +0100 -Subject: [PATCH 032/102] net: phy: air_en8811h: add OF device table for +Subject: [PATCH 032/105] 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.57/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch b/patches/linux/6.18.57/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch index ae787536dd..e0d9c1e499 100644 --- a/patches/linux/6.18.57/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch +++ b/patches/linux/6.18.57/0033-drm-vc4-dsi-enable-video-and-then-retry-failed-trans.patch @@ -1,7 +1,7 @@ From 73fdfca5d09d2a9784eb453065d6f2bb0aea2e52 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 20 Sep 2024 12:05:18 +0100 -Subject: [PATCH 033/102] drm: vc4: dsi: enable video and then retry failed +Subject: [PATCH 033/105] 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.57/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch b/patches/linux/6.18.57/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch index d7ba9435b2..bc1975b475 100644 --- a/patches/linux/6.18.57/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch +++ b/patches/linux/6.18.57/0034-drm-vc4-dsi-Clocks-should-be-running-before-reset.patch @@ -1,7 +1,7 @@ From ac4c2819f28196619f842bd72a92c372f69f3ff2 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 8 Jun 2022 17:23:47 +0100 -Subject: [PATCH 034/102] drm: vc4: dsi: Clocks should be running before reset +Subject: [PATCH 034/105] 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.57/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch b/patches/linux/6.18.57/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch index ac6d5b5156..d8327cec9e 100644 --- a/patches/linux/6.18.57/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch +++ b/patches/linux/6.18.57/0035-drm-vc4-Ensure-DSI-is-enabled-for-FIFO-resets.patch @@ -1,7 +1,7 @@ From 4083619702e09ef4fb60273bf788dcf092cfd3d1 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Fri, 5 Apr 2024 17:51:55 +0100 -Subject: [PATCH 035/102] drm/vc4: Ensure DSI is enabled for FIFO resets +Subject: [PATCH 035/105] 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.57/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch b/patches/linux/6.18.57/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch index fcc6fae513..05cebeabff 100644 --- a/patches/linux/6.18.57/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch +++ b/patches/linux/6.18.57/0036-drm-vc4-Reset-DSI-AFE-on-disable.patch @@ -1,7 +1,7 @@ From 58d4282e5dd75669d962b9538962e74fa534742a Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 26 May 2022 18:56:19 +0100 -Subject: [PATCH 036/102] drm: vc4: Reset DSI AFE on disable +Subject: [PATCH 036/105] 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.57/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch b/patches/linux/6.18.57/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch index fcbf79bee0..e10c0d73f5 100644 --- a/patches/linux/6.18.57/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch +++ b/patches/linux/6.18.57/0037-drm-vc4-dsi-Handle-the-different-command-FIFO-widths.patch @@ -1,7 +1,7 @@ From 6ad4174eafa120b81cc9b6aa500c3a798fcdb03b Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 20 Nov 2024 13:58:08 +0000 -Subject: [PATCH 037/102] drm: vc4: dsi: Handle the different command FIFO +Subject: [PATCH 037/105] drm: vc4: dsi: Handle the different command FIFO widths DSI0 and DSI1 have different widths for the command FIFO (24bit diff --git a/patches/linux/6.18.57/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch b/patches/linux/6.18.57/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch index f92453f65f..1b171c0718 100644 --- a/patches/linux/6.18.57/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch +++ b/patches/linux/6.18.57/0038-drm-bridge-tc358762-Program-the-DPI-mode-into-the-ch.patch @@ -1,7 +1,7 @@ From cb7f2812a0484abf0c0140a7b9f686b6e772f28b Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 17:37:00 +0000 -Subject: [PATCH 038/102] drm/bridge: tc358762: Program the DPI mode into the +Subject: [PATCH 038/105] 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.57/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch b/patches/linux/6.18.57/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch index e2c89dc4e1..4d80fe6ee3 100644 --- a/patches/linux/6.18.57/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch +++ b/patches/linux/6.18.57/0039-drm-bridge-tc358762-revert-move-ops-to-enable.patch @@ -1,7 +1,7 @@ From bdebf5183c87e4e023e08a252556e79053ecc997 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Jan 2024 18:44:49 +0000 -Subject: [PATCH 039/102] drm/bridge: tc358762: revert move ops to enable +Subject: [PATCH 039/105] 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.57/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch b/patches/linux/6.18.57/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch index b91b3e320a..30a85ca7ef 100644 --- a/patches/linux/6.18.57/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch +++ b/patches/linux/6.18.57/0040-drm-bridge-tc358762-Set-pre_enabled-on-pre_enable-to.patch @@ -1,7 +1,7 @@ From a7a7bfa99431333b70b791e41477b691c70ecb56 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Sat, 4 Apr 2026 18:04:19 +0200 -Subject: [PATCH 040/102] drm/bridge: tc358762: Set pre_enabled on pre_enable +Subject: [PATCH 040/105] 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.57/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch b/patches/linux/6.18.57/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch index 3e858155f2..590442ef1c 100644 --- a/patches/linux/6.18.57/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch +++ b/patches/linux/6.18.57/0041-net-pcs-add-standalone-PCS-registration-infrastructu.patch @@ -1,7 +1,7 @@ From bfdeabb27eb5e9b40eaa9605ee5d49d4a44be648 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 5 Apr 2026 11:33:00 +0200 -Subject: [PATCH 041/102] net/pcs: add standalone PCS registration +Subject: [PATCH 041/105] net/pcs: add standalone PCS registration infrastructure Add a simple registration mechanism that allows platform PCS drivers to diff --git a/patches/linux/6.18.57/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch b/patches/linux/6.18.57/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch index 7ae2bb8d85..3e9fc52d82 100644 --- a/patches/linux/6.18.57/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch +++ b/patches/linux/6.18.57/0042-net-pcs-add-MediaTek-MT7988-USXGMII-PCS-driver.patch @@ -1,7 +1,7 @@ From 372e882a4d8eba6dc3bf3523a2a6f854aec7a277 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:14:23 +0200 -Subject: [PATCH 042/102] net/pcs: add MediaTek MT7988 USXGMII PCS driver +Subject: [PATCH 042/105] 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.57/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch b/patches/linux/6.18.57/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch index cd75fad633..5e29ed3231 100644 --- a/patches/linux/6.18.57/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch +++ b/patches/linux/6.18.57/0043-net-ethernet-mediatek-add-USXGMII-support-for-MT7988.patch @@ -1,7 +1,7 @@ From b15db9efa791d47f2d39beff1a25d5f795caf1f4 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:43 +0200 -Subject: [PATCH 043/102] net: ethernet: mediatek: add USXGMII support for +Subject: [PATCH 043/105] net: ethernet: mediatek: add USXGMII support for MT7988 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.57/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch b/patches/linux/6.18.57/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch index abe48fb5ad..382dc9065e 100644 --- a/patches/linux/6.18.57/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch +++ b/patches/linux/6.18.57/0044-arm64-dts-mediatek-mt7988a-add-USXGMII-PCS-nodes.patch @@ -1,7 +1,7 @@ From 96b51720c66858d5ad611c6d287775ae5a209153 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:15:56 +0200 -Subject: [PATCH 044/102] arm64: dts: mediatek: mt7988a: add USXGMII PCS nodes +Subject: [PATCH 044/105] 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.57/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch b/patches/linux/6.18.57/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch index 63da9bf593..905255cb9c 100644 --- a/patches/linux/6.18.57/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch +++ b/patches/linux/6.18.57/0045-arm64-dts-mediatek-bananapi-bpi-r4-enable-SFP-ports-.patch @@ -1,7 +1,7 @@ From 036272e6796ba961028a1a99a8eeebc40403cbeb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 6 Apr 2026 14:16:11 +0200 -Subject: [PATCH 045/102] arm64: dts: mediatek: bananapi-bpi-r4: enable SFP+ +Subject: [PATCH 045/105] 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.57/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch b/patches/linux/6.18.57/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch index 1067b96aee..92811f7f75 100644 --- a/patches/linux/6.18.57/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch +++ b/patches/linux/6.18.57/0046-net-phy-sfp-add-OEM-SFP-10G-T-I-quirk.patch @@ -1,7 +1,7 @@ From b9a7c9f7a0a95a0fba5a652007eb596f6010b155 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 Apr 2026 07:34:52 +0200 -Subject: [PATCH 046/102] net: phy: sfp: add OEM SFP-10G-T-I quirk +Subject: [PATCH 046/105] 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.57/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch b/patches/linux/6.18.57/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch index a69056d257..dc802104cd 100644 --- a/patches/linux/6.18.57/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch +++ b/patches/linux/6.18.57/0047-net-dsa-mv88e6xxx-Trap-PTP-frames-on-timestamping-po.patch @@ -1,7 +1,7 @@ From 54ac0d29323338b43bbde3d894540ae80951d90b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 17 Apr 2026 09:13:04 +0000 -Subject: [PATCH 047/102] net: dsa: mv88e6xxx: Trap PTP frames on timestamping +Subject: [PATCH 047/105] 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.57/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch b/patches/linux/6.18.57/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch index 92c5a74a3a..de87d1a73f 100644 --- a/patches/linux/6.18.57/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch +++ b/patches/linux/6.18.57/0048-wifi-mt76-mt7615-add-MODULE_DEVICE_TABLE-for-mt7622-.patch @@ -1,7 +1,7 @@ From ad6aefc83c5592f3d02b8d3f48d50d486b871abc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Apr 2026 15:30:01 +0200 -Subject: [PATCH 048/102] wifi: mt76: mt7615: add MODULE_DEVICE_TABLE for +Subject: [PATCH 048/105] 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.57/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch b/patches/linux/6.18.57/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch index 842f5df4b2..c0373d4989 100644 --- a/patches/linux/6.18.57/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch +++ b/patches/linux/6.18.57/0049-PCI-mediatek-gen3-Fix-PERST-control-timing-during-sy.patch @@ -1,7 +1,7 @@ From ba2b448ecb61c6ddd3a4227bdc577d74e3a4cba1 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 22 Apr 2026 10:24:43 +0200 -Subject: [PATCH 049/102] PCI: mediatek-gen3: Fix PERST# control timing during +Subject: [PATCH 049/105] 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.57/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch b/patches/linux/6.18.57/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch index 6ca04401be..870f77e3ca 100644 --- a/patches/linux/6.18.57/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch +++ b/patches/linux/6.18.57/0050-net-dsa-mv88e6xxx-Derive-LED-names-from-device-name.patch @@ -1,7 +1,7 @@ From a70a80b94fe0737ff3b8d092713984ff1ffbdf8b Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 12 Aug 2026 10:08:53 +0200 -Subject: [PATCH 050/102] net: dsa: mv88e6xxx: Derive LED names from device +Subject: [PATCH 050/105] net: dsa: mv88e6xxx: Derive LED names from device name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.57/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch b/patches/linux/6.18.57/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch index aa1f8e388e..61463a1534 100644 --- a/patches/linux/6.18.57/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch +++ b/patches/linux/6.18.57/0051-phy-sparx5-serdes-make-it-selectable-for-ARCH_LAN969.patch @@ -1,7 +1,7 @@ From 7b587fadb354fc4bafc4a441cded3cc9ef0aa160 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 31 Oct 2025 13:18:12 +0100 -Subject: [PATCH 051/102] phy: sparx5-serdes: make it selectable for +Subject: [PATCH 051/105] phy: sparx5-serdes: make it selectable for ARCH_LAN969X LAN969x uses the SparX-5 SERDES driver, so make it selectable for diff --git a/patches/linux/6.18.57/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch b/patches/linux/6.18.57/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch index 6227400a2e..fcfc3a04c0 100644 --- a/patches/linux/6.18.57/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch +++ b/patches/linux/6.18.57/0052-net-sparx5-fix-wrong-chip-ids-for-TSN-SKUs.patch @@ -1,7 +1,7 @@ From 97735a9198026c9ffa6c991d25eab0f5bc0d9698 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:38 +0200 -Subject: [PATCH 052/102] net: sparx5: fix wrong chip ids for TSN SKUs +Subject: [PATCH 052/105] 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.57/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch b/patches/linux/6.18.57/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch index 3ce78d44df..e709156348 100644 --- a/patches/linux/6.18.57/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch +++ b/patches/linux/6.18.57/0053-net-sparx5-configure-serdes-for-1000BASE-X-in-sparx5.patch @@ -1,7 +1,7 @@ From c3ea33818e99e769ca3b33323df09cae87b18800 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 6 May 2026 09:25:39 +0200 -Subject: [PATCH 053/102] net: sparx5: configure serdes for 1000BASE-X in +Subject: [PATCH 053/105] 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.57/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch b/patches/linux/6.18.57/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch index 1af7983ce8..bc9eb98a9c 100644 --- a/patches/linux/6.18.57/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch +++ b/patches/linux/6.18.57/0054-dt-bindings-mmc-atmel-sama5d2-sdhci-add-microchip-la.patch @@ -1,7 +1,7 @@ From f7003e2bb12d95583101d4ef55eab6f21eb9cd6f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:29 +0200 -Subject: [PATCH 054/102] dt-bindings: mmc: atmel,sama5d2-sdhci: add +Subject: [PATCH 054/105] 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.57/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch b/patches/linux/6.18.57/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch index 5de2180916..3494cc1567 100644 --- a/patches/linux/6.18.57/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch +++ b/patches/linux/6.18.57/0055-mmc-sdhci-of-at91-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From cac6b74294ffda4f256e520bc9d3d0e6849ab9a7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:35:44 +0200 -Subject: [PATCH 055/102] mmc: sdhci-of-at91: add LAN969x support +Subject: [PATCH 055/105] 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.57/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch b/patches/linux/6.18.57/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch index cad2ad8edd..3fac234a11 100644 --- a/patches/linux/6.18.57/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch +++ b/patches/linux/6.18.57/0056-mmc-sdhci-of-at91-stop-SDCLK-on-reset-and-add-eMMC-h.patch @@ -1,7 +1,7 @@ From dd296a20b251c66bbb82a71e746b1a59a43a4b0f Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:37:48 +0200 -Subject: [PATCH 056/102] mmc: sdhci-of-at91: stop SDCLK on reset and add eMMC +Subject: [PATCH 056/105] 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.57/0057-net-sparx5-lan969x-populate-netdev-of_node.patch b/patches/linux/6.18.57/0057-net-sparx5-lan969x-populate-netdev-of_node.patch index 266f97e81e..6982dc6e51 100644 --- a/patches/linux/6.18.57/0057-net-sparx5-lan969x-populate-netdev-of_node.patch +++ b/patches/linux/6.18.57/0057-net-sparx5-lan969x-populate-netdev-of_node.patch @@ -1,7 +1,7 @@ From 104588391db5e1d6506fc02b8dae79e01b95656a Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 10 Nov 2025 13:42:53 +0100 -Subject: [PATCH 057/102] net: sparx5/lan969x: populate netdev of_node +Subject: [PATCH 057/105] 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.57/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch b/patches/linux/6.18.57/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch index 2957194506..890bd0386e 100644 --- a/patches/linux/6.18.57/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch +++ b/patches/linux/6.18.57/0058-arm64-dts-microchip-add-LAN969x-clock-header-file.patch @@ -1,7 +1,7 @@ From 684a31e6deb1e1037ffaba5af86a7a1298fb6256 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:11 +0100 -Subject: [PATCH 058/102] arm64: dts: microchip: add LAN969x clock header file +Subject: [PATCH 058/105] 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.57/0059-arm64-dts-microchip-add-LAN969x-support.patch b/patches/linux/6.18.57/0059-arm64-dts-microchip-add-LAN969x-support.patch index e6f53851ed..35b427adf0 100644 --- a/patches/linux/6.18.57/0059-arm64-dts-microchip-add-LAN969x-support.patch +++ b/patches/linux/6.18.57/0059-arm64-dts-microchip-add-LAN969x-support.patch @@ -1,7 +1,7 @@ From dc944c2dac81be7148374122391cb1390224d329 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:12 +0100 -Subject: [PATCH 059/102] arm64: dts: microchip: add LAN969x support +Subject: [PATCH 059/105] 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.57/0060-arm64-dts-microchip-add-EV23X71A-board.patch b/patches/linux/6.18.57/0060-arm64-dts-microchip-add-EV23X71A-board.patch index 116cde43d3..6059d6b756 100644 --- a/patches/linux/6.18.57/0060-arm64-dts-microchip-add-EV23X71A-board.patch +++ b/patches/linux/6.18.57/0060-arm64-dts-microchip-add-EV23X71A-board.patch @@ -1,7 +1,7 @@ From e1d6da07c42ab6d9e2765a16afba208898abb8b3 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:14 +0100 -Subject: [PATCH 060/102] arm64: dts: microchip: add EV23X71A board +Subject: [PATCH 060/105] arm64: dts: microchip: add EV23X71A board Microchip EV23X71A is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.57/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch b/patches/linux/6.18.57/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch index 70a9fff1c4..74975da719 100644 --- a/patches/linux/6.18.57/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch +++ b/patches/linux/6.18.57/0061-arm64-dts-microchip-lan969x-add-OTP-node.patch @@ -1,7 +1,7 @@ From a707e5cdc92248cae91c53b3d1f9b41366f52b05 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 15 May 2026 13:59:09 +0200 -Subject: [PATCH 061/102] arm64: dts: microchip: lan969x: add OTP node +Subject: [PATCH 061/105] arm64: dts: microchip: lan969x: add OTP node Add the required OTP on LAN969x. diff --git a/patches/linux/6.18.57/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch b/patches/linux/6.18.57/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch index e5cdc7bb58..d69cc6b383 100644 --- a/patches/linux/6.18.57/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch +++ b/patches/linux/6.18.57/0062-arm64-dts-microchip-lan969x-add-SDMMC-nodes.patch @@ -1,7 +1,7 @@ From 0b6e2ad8e9c69ea465dfcaf87dd6878a96a8f464 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 062/102] arm64: dts: microchip: lan969x: add SDMMC nodes +Subject: [PATCH 062/105] 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.57/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch b/patches/linux/6.18.57/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch index 85d49d7414..c79864f752 100644 --- a/patches/linux/6.18.57/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch +++ b/patches/linux/6.18.57/0063-arm64-dts-microchip-ev23x71a-enable-eMMC.patch @@ -1,7 +1,7 @@ From 8f5529fc1894b4fa292d10191a3b7267af126c81 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 19 Aug 2026 11:38:37 +0200 -Subject: [PATCH 063/102] arm64: dts: microchip: ev23x71a: enable eMMC +Subject: [PATCH 063/105] 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.57/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch b/patches/linux/6.18.57/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch index 7d9d026b85..6a3082524a 100644 --- a/patches/linux/6.18.57/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch +++ b/patches/linux/6.18.57/0064-wifi-brcmfmac-survey-the-requested-interface-not-the.patch @@ -1,7 +1,7 @@ From 3f11119521883f53a0fc2141820e756192245044 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:42:48 +0200 -Subject: [PATCH 064/102] wifi: brcmfmac: survey the requested interface, not +Subject: [PATCH 064/105] 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.57/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch b/patches/linux/6.18.57/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch index e8b43e0d7b..b8b5a6a9f9 100644 --- a/patches/linux/6.18.57/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch +++ b/patches/linux/6.18.57/0065-wifi-brcmfmac-honor-caller-s-rtnl-lock-when-stopping.patch @@ -1,7 +1,7 @@ From 54f5351143c719f752d94e0e393f21a4fe7e35a3 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:43:14 +0200 -Subject: [PATCH 065/102] wifi: brcmfmac: honor caller's rtnl lock when +Subject: [PATCH 065/105] 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.57/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch b/patches/linux/6.18.57/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch index 56d1ad0d90..cd0de81d0f 100644 --- a/patches/linux/6.18.57/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch +++ b/patches/linux/6.18.57/0066-wifi-brcmfmac-let-cfg_to_ndev-return-NULL-and-harden.patch @@ -1,7 +1,7 @@ From 158bd44aa0b69481a405d90e238d95bdd017b989 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 15:46:25 +0200 -Subject: [PATCH 066/102] wifi: brcmfmac: let cfg_to_ndev() return NULL and +Subject: [PATCH 066/105] 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.57/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch b/patches/linux/6.18.57/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch index 38d5ef6c7b..9d7c0d6cd0 100644 --- a/patches/linux/6.18.57/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch +++ b/patches/linux/6.18.57/0067-wifi-brcmfmac-support-deletion-and-recreation-of-the.patch @@ -1,7 +1,7 @@ From 4d4beb465e67a38707ee515719f2acd93734ce1d Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 30 Jun 2026 16:05:17 +0200 -Subject: [PATCH 067/102] wifi: brcmfmac: support deletion and recreation of +Subject: [PATCH 067/105] 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.57/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch b/patches/linux/6.18.57/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch index 5979cd5b1f..617a90c588 100644 --- a/patches/linux/6.18.57/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch +++ b/patches/linux/6.18.57/0068-wifi-brcmfmac-report-port-authorized-after-offloaded.patch @@ -1,7 +1,7 @@ From f73a221a67e6e43fb76ce294773e037d669f44b0 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Thu, 10 Sep 2026 08:29:47 +0200 -Subject: [PATCH 068/102] wifi: brcmfmac: report port authorized after +Subject: [PATCH 068/105] 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.57/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch b/patches/linux/6.18.57/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch index 5d0b8141cd..37506e41b2 100644 --- a/patches/linux/6.18.57/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.57/0069-dt-bindings-arm-AT91-document-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From 0efbdf46b65578f1432ab62b282242fa7b6c2cd8 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 9 Jan 2026 13:27:00 +0100 -Subject: [PATCH 069/102] dt-bindings: arm: AT91: document Novarq Tactical 1000 +Subject: [PATCH 069/105] 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.57/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch b/patches/linux/6.18.57/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch index 420fa1230f..8779a73bd0 100644 --- a/patches/linux/6.18.57/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch +++ b/patches/linux/6.18.57/0070-arm64-dts-microchip-add-Novarq-Tactical-1000.patch @@ -1,7 +1,7 @@ From 4ea0c290a941df0d055be5974db76cd050010205 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Sep 2025 13:37:49 +0200 -Subject: [PATCH 070/102] arm64: dts: microchip: add Novarq Tactical 1000 +Subject: [PATCH 070/105] 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.57/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch b/patches/linux/6.18.57/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch index 544ca36119..3c32dd3b02 100644 --- a/patches/linux/6.18.57/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch +++ b/patches/linux/6.18.57/0071-arm64-dts-microchip-tactical-1000-add-port-names.patch @@ -1,7 +1,7 @@ From 1a800417bef77bbce9724da04d54f1b34459e0e5 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 Jun 2026 11:23:47 +0200 -Subject: [PATCH 071/102] arm64: dts: microchip: tactical-1000: add port names +Subject: [PATCH 071/105] 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.57/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch b/patches/linux/6.18.57/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch index 7c81daa8fa..20725b5f42 100644 --- a/patches/linux/6.18.57/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch +++ b/patches/linux/6.18.57/0072-arm64-dts-microchip-tactical-1000-adapt-to-6.18.patch @@ -1,7 +1,7 @@ From 12cf129fd63364ce6aa8d1b865505493a7e4dcd2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 20 Sep 2026 12:22:43 +0200 -Subject: [PATCH 072/102] arm64: dts: microchip: tactical-1000: adapt to 6.18 +Subject: [PATCH 072/105] 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.57/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch b/patches/linux/6.18.57/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch index 9e732ba476..bfa1f6de97 100644 --- a/patches/linux/6.18.57/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch +++ b/patches/linux/6.18.57/0073-dt-bindings-arm-AT91-document-EV23X71A-board.patch @@ -1,7 +1,7 @@ From 097bfa1b554e425775024a0c3b5daedab5a293e4 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 2 Mar 2026 12:20:13 +0100 -Subject: [PATCH 073/102] dt-bindings: arm: AT91: document EV23X71A board +Subject: [PATCH 073/105] dt-bindings: arm: AT91: document EV23X71A board Microchip EV23X71A board is an LAN9696 based evaluation board. diff --git a/patches/linux/6.18.57/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch b/patches/linux/6.18.57/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch index 32d72187a3..27ea1dac23 100644 --- a/patches/linux/6.18.57/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch +++ b/patches/linux/6.18.57/0074-net-dsa-Skip-DCB-default-priority-init-on-unsupporte.patch @@ -1,7 +1,7 @@ From 46334fce5a274b6640c1801b144e721f9a89970d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:21 +0200 -Subject: [PATCH 074/102] net: dsa: Skip DCB default priority init on +Subject: [PATCH 074/105] 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.57/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch b/patches/linux/6.18.57/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch index 63fb355d1d..9b444d764c 100644 --- a/patches/linux/6.18.57/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch +++ b/patches/linux/6.18.57/0075-net-dsa-Generalise-the-global-DCB-APP-mirroring-help.patch @@ -1,7 +1,7 @@ From b7aab315dfe05f936a0344b10e0f998002bcb43e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:51:07 +0200 -Subject: [PATCH 075/102] net: dsa: Generalise the global DCB APP mirroring +Subject: [PATCH 075/105] 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.57/0076-net-dsa-Support-the-PCP-APP-selector.patch b/patches/linux/6.18.57/0076-net-dsa-Support-the-PCP-APP-selector.patch index da78633075..1fca2c6289 100644 --- a/patches/linux/6.18.57/0076-net-dsa-Support-the-PCP-APP-selector.patch +++ b/patches/linux/6.18.57/0076-net-dsa-Support-the-PCP-APP-selector.patch @@ -1,7 +1,7 @@ From c9576e0f132ba51462c0a088451cf183c975fbe9 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:04:22 +0200 -Subject: [PATCH 076/102] net: dsa: Support the PCP APP selector +Subject: [PATCH 076/105] 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.57/0077-net-dsa-Support-DCB-priority-rewrite.patch b/patches/linux/6.18.57/0077-net-dsa-Support-DCB-priority-rewrite.patch index 28ecc15b58..9faa7ccb65 100644 --- a/patches/linux/6.18.57/0077-net-dsa-Support-DCB-priority-rewrite.patch +++ b/patches/linux/6.18.57/0077-net-dsa-Support-DCB-priority-rewrite.patch @@ -1,7 +1,7 @@ From d78d08175f4f8a37b3ef3b67e6fb526f6fee43aa Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 9 Sep 2026 17:10:32 +0200 -Subject: [PATCH 077/102] net: dsa: Support DCB priority rewrite +Subject: [PATCH 077/105] 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.57/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch b/patches/linux/6.18.57/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch index 3d4729f413..a59eb2bce3 100644 --- a/patches/linux/6.18.57/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch +++ b/patches/linux/6.18.57/0078-net-dsa-Support-the-IEEE-ETS-managed-object.patch @@ -1,7 +1,7 @@ From 65c40a578d8d76adbcc7d7bb2b98ad43c5fd7171 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 22 Sep 2026 13:53:23 +0200 -Subject: [PATCH 078/102] net: dsa: Support the IEEE ETS managed object +Subject: [PATCH 078/105] 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.57/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch b/patches/linux/6.18.57/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch index dbd0fd6d34..14a6beadea 100644 --- a/patches/linux/6.18.57/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch +++ b/patches/linux/6.18.57/0079-net-sparx5-fix-sleep-in-atomic-context-in-MAC-table-.patch @@ -1,7 +1,7 @@ From 5b9f6fe0ad79285bff06dddb7eef60c9377bab67 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 7 Oct 2026 11:42:12 +0200 -Subject: [PATCH 079/102] net: sparx5: fix sleep in atomic context in MAC table +Subject: [PATCH 079/105] 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.57/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch b/patches/linux/6.18.57/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch index ecd76faede..dbb64c9a1f 100644 --- a/patches/linux/6.18.57/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch +++ b/patches/linux/6.18.57/0080-misc-sparx5-symreg-symreg-debugfs-driver-DBB-1045.patch @@ -1,7 +1,7 @@ From 9eb7532602b5ad1c766c0cb8b4f0c43272da1ddd Mon Sep 17 00:00:00 2001 From: Steen Hegelund Date: Wed, 21 Jan 2026 10:09:45 +0100 -Subject: [PATCH 080/102] misc: sparx5-symreg; symreg debugfs driver (DBB-1045) +Subject: [PATCH 080/105] 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.57/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch b/patches/linux/6.18.57/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch index 0157b6b55c..f2915efd40 100644 --- a/patches/linux/6.18.57/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch +++ b/patches/linux/6.18.57/0081-net-ethernet-mtk_wed-map-WO-memory-regions-without-r.patch @@ -1,7 +1,7 @@ From 17958613375144f4d059777987e6ab034acf2ab1 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Mon, 5 Oct 2026 14:37:56 +0200 -Subject: [PATCH 081/102] net: ethernet: mtk_wed: map WO memory regions without +Subject: [PATCH 081/105] 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.57/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch b/patches/linux/6.18.57/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch index 61593ac9ac..80e6278f13 100644 --- a/patches/linux/6.18.57/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch +++ b/patches/linux/6.18.57/0082-wifi-mt76-wed-fix-kernel-panic-on-non-DBDC-MT7986.patch @@ -1,7 +1,7 @@ From 3144fdeb67f73640c6194435bdfe1d011d318d67 Mon Sep 17 00:00:00 2001 From: Zhi-Jun You Date: Wed, 15 Jul 2026 23:21:12 +0800 -Subject: [PATCH 082/102] wifi: mt76: wed: fix kernel panic on non-DBDC MT7986 +Subject: [PATCH 082/105] 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.57/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch b/patches/linux/6.18.57/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch index 6dc6019f27..0d7ba9c701 100644 --- a/patches/linux/6.18.57/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch +++ b/patches/linux/6.18.57/0083-wifi-mt76-mt7915-set-up-WPDMA-RX-ring-0-for-WED-on-a.patch @@ -1,7 +1,7 @@ From 78214f7ced0ae7022257f880621d85865523660d Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Wed, 7 Oct 2026 22:20:00 +0200 -Subject: [PATCH 083/102] wifi: mt76: mt7915: set up WPDMA RX ring 0 for WED on +Subject: [PATCH 083/105] 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 diff --git a/patches/linux/6.18.57/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch b/patches/linux/6.18.57/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch index a8822d998c..3af9fd2069 100644 --- a/patches/linux/6.18.57/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch +++ b/patches/linux/6.18.57/0084-wifi-mt76-mt7915-add-WDS-support-when-WED-is-enabled.patch @@ -1,7 +1,7 @@ From 08357c0c00268d987d3f615735b1e6c6056547e9 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 20 Oct 2025 15:59:12 +0200 -Subject: [PATCH 084/102] wifi: mt76: mt7915: add WDS support when WED is +Subject: [PATCH 084/105] wifi: mt76: mt7915: add WDS support when WED is enabled The current WED only supports 256 wcid, whereas mt7986 can support up to diff --git a/patches/linux/6.18.57/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch b/patches/linux/6.18.57/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch index fd21159893..3624cddf5b 100644 --- a/patches/linux/6.18.57/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch +++ b/patches/linux/6.18.57/0085-net-ethernet-mtk_wed-increase-WED-v2-WDMA-RESV_BUFF-.patch @@ -1,7 +1,7 @@ From 59f60ef5023bee3c659ed50a34e0a6a4d5a18a79 Mon Sep 17 00:00:00 2001 From: Shiji Yang Date: Wed, 19 Aug 2026 21:07:07 +0800 -Subject: [PATCH 085/102] net: ethernet: mtk_wed: increase WED v2 WDMA +Subject: [PATCH 085/105] 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. diff --git a/patches/linux/6.18.57/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch b/patches/linux/6.18.57/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch index c54ea48ccc..285c13ee6d 100644 --- a/patches/linux/6.18.57/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch +++ b/patches/linux/6.18.57/0086-wifi-mt76-mt7915-publish-wcid-before-MCU-add-command.patch @@ -1,7 +1,7 @@ From 0f22a4d31ffe1ec1ee02192fe2007d4fdab7e6e0 Mon Sep 17 00:00:00 2001 From: Ryan Leung Date: Fri, 21 Aug 2026 09:00:01 +0000 -Subject: [PATCH 086/102] wifi: mt76: mt7915: publish wcid before MCU add +Subject: [PATCH 086/105] wifi: mt76: mt7915: publish wcid before MCU add commands mt7915_add_interface() enables the BSS/STA in firmware via diff --git a/patches/linux/6.18.57/0087-riscv-dts-spacemit-add-UART-pinctrl-combinations.patch b/patches/linux/6.18.57/0087-riscv-dts-spacemit-add-UART-pinctrl-combinations.patch index f8306de6b3..14f734d133 100644 --- a/patches/linux/6.18.57/0087-riscv-dts-spacemit-add-UART-pinctrl-combinations.patch +++ b/patches/linux/6.18.57/0087-riscv-dts-spacemit-add-UART-pinctrl-combinations.patch @@ -1,7 +1,7 @@ From d8ac79ff39f12f88c497bcb6c9644877b8a63642 Mon Sep 17 00:00:00 2001 From: Hendrik Hamerlinck Date: Wed, 17 Sep 2025 08:59:07 +0200 -Subject: [PATCH 087/102] riscv: dts: spacemit: add UART pinctrl combinations +Subject: [PATCH 087/105] riscv: dts: spacemit: add UART pinctrl combinations Add UART pinctrl configurations based on the SoC datasheet and the downstream Bianbu Linux tree. The drive strength values were taken from diff --git a/patches/linux/6.18.57/0088-riscv-dts-spacemit-enable-the-i2c8-adapter.patch b/patches/linux/6.18.57/0088-riscv-dts-spacemit-enable-the-i2c8-adapter.patch index 69f2bcbe98..c3df9a90d6 100644 --- a/patches/linux/6.18.57/0088-riscv-dts-spacemit-enable-the-i2c8-adapter.patch +++ b/patches/linux/6.18.57/0088-riscv-dts-spacemit-enable-the-i2c8-adapter.patch @@ -1,7 +1,7 @@ From 7e3084b4fbbefb845e30e5068e58bcae956d6e8c Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 25 Aug 2025 12:20:54 -0500 -Subject: [PATCH 088/102] riscv: dts: spacemit: enable the i2c8 adapter +Subject: [PATCH 088/105] riscv: dts: spacemit: enable the i2c8 adapter Define properties for the I2C adapter that provides access to the SpacemiT P1 PMIC. Enable this adapter on the Banana Pi BPI-F3. diff --git a/patches/linux/6.18.57/0089-riscv-dts-spacemit-define-fixed-regulators.patch b/patches/linux/6.18.57/0089-riscv-dts-spacemit-define-fixed-regulators.patch index 94313170f1..6dc69ff078 100644 --- a/patches/linux/6.18.57/0089-riscv-dts-spacemit-define-fixed-regulators.patch +++ b/patches/linux/6.18.57/0089-riscv-dts-spacemit-define-fixed-regulators.patch @@ -1,7 +1,7 @@ From 7f01d556a1e42225f97f0153fa76a7f2b7387251 Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 25 Aug 2025 12:20:55 -0500 -Subject: [PATCH 089/102] riscv: dts: spacemit: define fixed regulators +Subject: [PATCH 089/105] riscv: dts: spacemit: define fixed regulators Define the DC power input and the 4v power as fixed supplies in the Banana Pi BPI-F3. diff --git a/patches/linux/6.18.57/0090-riscv-dts-spacemit-define-regulator-constraints.patch b/patches/linux/6.18.57/0090-riscv-dts-spacemit-define-regulator-constraints.patch index 75400aac49..ee9c04b06d 100644 --- a/patches/linux/6.18.57/0090-riscv-dts-spacemit-define-regulator-constraints.patch +++ b/patches/linux/6.18.57/0090-riscv-dts-spacemit-define-regulator-constraints.patch @@ -1,7 +1,7 @@ From f2b9631efd5b8e96a484f5bbfdd23154f6fd784a Mon Sep 17 00:00:00 2001 From: Alex Elder Date: Mon, 25 Aug 2025 12:20:56 -0500 -Subject: [PATCH 090/102] riscv: dts: spacemit: define regulator constraints +Subject: [PATCH 090/105] riscv: dts: spacemit: define regulator constraints Define basic constraints for the regulators in the SpacemiT P1 PMIC, as implemented in the Banana Pi BPI-F3. diff --git a/patches/linux/6.18.57/0091-riscv-dts-spacemit-pinctrl-update-register-and-IO-po.patch b/patches/linux/6.18.57/0091-riscv-dts-spacemit-pinctrl-update-register-and-IO-po.patch index 357ab74b64..5d6dd00c48 100644 --- a/patches/linux/6.18.57/0091-riscv-dts-spacemit-pinctrl-update-register-and-IO-po.patch +++ b/patches/linux/6.18.57/0091-riscv-dts-spacemit-pinctrl-update-register-and-IO-po.patch @@ -1,7 +1,7 @@ From bbff952346fd57b379625607a0c60108200153b4 Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Thu, 8 Jan 2026 14:42:40 +0800 -Subject: [PATCH 091/102] riscv: dts: spacemit: pinctrl: update register and IO +Subject: [PATCH 091/105] riscv: dts: spacemit: pinctrl: update register and IO power Change the size of the reg register to 0x1000 to match the hardware. diff --git a/patches/linux/6.18.57/0092-riscv-dts-spacemit-Update-PMIC-supply-properties-for.patch b/patches/linux/6.18.57/0092-riscv-dts-spacemit-Update-PMIC-supply-properties-for.patch index e6c7ee3872..6b91ed263d 100644 --- a/patches/linux/6.18.57/0092-riscv-dts-spacemit-Update-PMIC-supply-properties-for.patch +++ b/patches/linux/6.18.57/0092-riscv-dts-spacemit-Update-PMIC-supply-properties-for.patch @@ -1,7 +1,7 @@ From 020a7249aaf089217f93a40c6fdacb4a995c28e4 Mon Sep 17 00:00:00 2001 From: Guodong Xu Date: Fri, 6 Feb 2026 10:32:04 +0800 -Subject: [PATCH 092/102] riscv: dts: spacemit: Update PMIC supply properties +Subject: [PATCH 092/105] riscv: dts: spacemit: Update PMIC supply properties for BPI-F3 and Jupiter Use per-regulator supply names in pmic "spacemit,p1" node to specify diff --git a/patches/linux/6.18.57/0093-riscv-dts-spacemit-k1-add-SD-card-controller-and-pin.patch b/patches/linux/6.18.57/0093-riscv-dts-spacemit-k1-add-SD-card-controller-and-pin.patch index 4f3308d2c0..7a53b533b2 100644 --- a/patches/linux/6.18.57/0093-riscv-dts-spacemit-k1-add-SD-card-controller-and-pin.patch +++ b/patches/linux/6.18.57/0093-riscv-dts-spacemit-k1-add-SD-card-controller-and-pin.patch @@ -1,7 +1,7 @@ From 83bd92bb4d97b92241804ced2967f5b0bf1d01e6 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 15 May 2026 12:48:59 +0200 -Subject: [PATCH 093/102] riscv: dts: spacemit: k1: add SD card controller and +Subject: [PATCH 093/105] riscv: dts: spacemit: k1: add SD card controller and pinctrl support Add SD card controller infrastructure for SpacemiT K1 SoC with complete diff --git a/patches/linux/6.18.57/0094-riscv-dts-spacemit-k1-bananapi-f3-add-SD-card-suppor.patch b/patches/linux/6.18.57/0094-riscv-dts-spacemit-k1-bananapi-f3-add-SD-card-suppor.patch index 053aa48d3c..9a3a1a612a 100644 --- a/patches/linux/6.18.57/0094-riscv-dts-spacemit-k1-bananapi-f3-add-SD-card-suppor.patch +++ b/patches/linux/6.18.57/0094-riscv-dts-spacemit-k1-bananapi-f3-add-SD-card-suppor.patch @@ -1,7 +1,7 @@ From 18b19b95243b1e70d1ed6c2b36ad95950b9b7b8a Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Fri, 15 May 2026 12:49:01 +0200 -Subject: [PATCH 094/102] riscv: dts: spacemit: k1-bananapi-f3: add SD card +Subject: [PATCH 094/105] riscv: dts: spacemit: k1-bananapi-f3: add SD card support with UHS modes Add complete SD card controller support with UHS high-speed modes. diff --git a/patches/linux/6.18.57/0095-mmc-sdhci-of-k1-add-reset-support.patch b/patches/linux/6.18.57/0095-mmc-sdhci-of-k1-add-reset-support.patch index 7852a618df..af9aa50704 100644 --- a/patches/linux/6.18.57/0095-mmc-sdhci-of-k1-add-reset-support.patch +++ b/patches/linux/6.18.57/0095-mmc-sdhci-of-k1-add-reset-support.patch @@ -1,7 +1,7 @@ From faf9f545c4466ea17618388c5aaa6f8ca42bae98 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Tue, 23 Dec 2025 10:24:50 +0800 -Subject: [PATCH 095/102] mmc: sdhci-of-k1: add reset support +Subject: [PATCH 095/105] mmc: sdhci-of-k1: add reset support The SDHCI controller of SpacemiT K1 SoC requires two resets, add support to explicitly request the reset line and deassert during diff --git a/patches/linux/6.18.57/0096-mmc-sdhci-of-k1-enable-essential-clock-infrastructur.patch b/patches/linux/6.18.57/0096-mmc-sdhci-of-k1-enable-essential-clock-infrastructur.patch index c87f0d7712..1ec0031d72 100644 --- a/patches/linux/6.18.57/0096-mmc-sdhci-of-k1-enable-essential-clock-infrastructur.patch +++ b/patches/linux/6.18.57/0096-mmc-sdhci-of-k1-enable-essential-clock-infrastructur.patch @@ -1,7 +1,7 @@ From f18a38a760707550435a91643cf51d4e8739e0f0 Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 11 May 2026 10:53:57 +0200 -Subject: [PATCH 096/102] mmc: sdhci-of-k1: enable essential clock +Subject: [PATCH 096/105] mmc: sdhci-of-k1: enable essential clock infrastructure for SD operation Ensure SD card pins receive clock signals by enabling pad clock diff --git a/patches/linux/6.18.57/0097-mmc-sdhci-of-k1-add-regulator-and-pinctrl-voltage-sw.patch b/patches/linux/6.18.57/0097-mmc-sdhci-of-k1-add-regulator-and-pinctrl-voltage-sw.patch index b89026def9..7ee212198d 100644 --- a/patches/linux/6.18.57/0097-mmc-sdhci-of-k1-add-regulator-and-pinctrl-voltage-sw.patch +++ b/patches/linux/6.18.57/0097-mmc-sdhci-of-k1-add-regulator-and-pinctrl-voltage-sw.patch @@ -1,7 +1,7 @@ From 6b6ee1f575037a3daee550a0225f8f8d10f126bc Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 11 May 2026 10:53:58 +0200 -Subject: [PATCH 097/102] mmc: sdhci-of-k1: add regulator and pinctrl voltage +Subject: [PATCH 097/105] mmc: sdhci-of-k1: add regulator and pinctrl voltage switching support Add voltage switching infrastructure for UHS-I modes by integrating both diff --git a/patches/linux/6.18.57/0098-mmc-sdhci-of-k1-add-comprehensive-SDR-tuning-support.patch b/patches/linux/6.18.57/0098-mmc-sdhci-of-k1-add-comprehensive-SDR-tuning-support.patch index bf24dfc796..9ab7e37f44 100644 --- a/patches/linux/6.18.57/0098-mmc-sdhci-of-k1-add-comprehensive-SDR-tuning-support.patch +++ b/patches/linux/6.18.57/0098-mmc-sdhci-of-k1-add-comprehensive-SDR-tuning-support.patch @@ -1,7 +1,7 @@ From 658bc011b09c32a0731b9a11edfa542b955c025d Mon Sep 17 00:00:00 2001 From: Iker Pedrosa Date: Mon, 11 May 2026 10:53:59 +0200 -Subject: [PATCH 098/102] mmc: sdhci-of-k1: add comprehensive SDR tuning +Subject: [PATCH 098/105] mmc: sdhci-of-k1: add comprehensive SDR tuning support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.57/0099-driver-reset-spacemit-p1-add-driver-for-poweroff-reb.patch b/patches/linux/6.18.57/0099-driver-reset-spacemit-p1-add-driver-for-poweroff-reb.patch index 44dc4a8c16..a85dd26068 100644 --- a/patches/linux/6.18.57/0099-driver-reset-spacemit-p1-add-driver-for-poweroff-reb.patch +++ b/patches/linux/6.18.57/0099-driver-reset-spacemit-p1-add-driver-for-poweroff-reb.patch @@ -1,7 +1,7 @@ From 856831f83fc0466e356b6620540eacdaa2fb6c2a Mon Sep 17 00:00:00 2001 From: Aurelien Jarno Date: Mon, 3 Nov 2025 00:01:59 +0100 -Subject: [PATCH 099/102] driver: reset: spacemit-p1: add driver for +Subject: [PATCH 099/105] driver: reset: spacemit-p1: add driver for poweroff/reboot This driver implements poweroff/reboot support for the SpacemiT P1 PMIC diff --git a/patches/linux/6.18.57/0100-mfd-simple-mfd-i2c-Add-a-reboot-cell-for-the-Spacemi.patch b/patches/linux/6.18.57/0100-mfd-simple-mfd-i2c-Add-a-reboot-cell-for-the-Spacemi.patch index 173f32fbf2..fbf407727b 100644 --- a/patches/linux/6.18.57/0100-mfd-simple-mfd-i2c-Add-a-reboot-cell-for-the-Spacemi.patch +++ b/patches/linux/6.18.57/0100-mfd-simple-mfd-i2c-Add-a-reboot-cell-for-the-Spacemi.patch @@ -1,7 +1,7 @@ From 1e5a95c0a6210aeff898ef1e85873632951b23a7 Mon Sep 17 00:00:00 2001 From: Aurelien Jarno Date: Mon, 3 Nov 2025 00:02:00 +0100 -Subject: [PATCH 100/102] mfd: simple-mfd-i2c: Add a reboot cell for the +Subject: [PATCH 100/105] mfd: simple-mfd-i2c: Add a reboot cell for the SpacemiT P1 chip Add a "spacemit-p1-reboot" cell for the SpacemiT P1 chip. diff --git a/patches/linux/6.18.57/0101-nvmem-core-do-not-defer-on-a-cell-missing-from-a-bou.patch b/patches/linux/6.18.57/0101-nvmem-core-do-not-defer-on-a-cell-missing-from-a-bou.patch index f2366ba708..86c5a78680 100644 --- a/patches/linux/6.18.57/0101-nvmem-core-do-not-defer-on-a-cell-missing-from-a-bou.patch +++ b/patches/linux/6.18.57/0101-nvmem-core-do-not-defer-on-a-cell-missing-from-a-bou.patch @@ -1,7 +1,7 @@ From e37bdae94135a99a05ac92b7869a7730709a0663 Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 13 Oct 2026 10:52:54 +0200 -Subject: [PATCH 101/102] nvmem: core: do not defer on a cell missing from a +Subject: [PATCH 101/105] nvmem: core: do not defer on a cell missing from a bound layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.57/0102-net-dsa-mt7530-lock-the-MDIO-bus-around-regmap-irq-a.patch b/patches/linux/6.18.57/0102-net-dsa-mt7530-lock-the-MDIO-bus-around-regmap-irq-a.patch index 834864ec34..eaaa4ff486 100644 --- a/patches/linux/6.18.57/0102-net-dsa-mt7530-lock-the-MDIO-bus-around-regmap-irq-a.patch +++ b/patches/linux/6.18.57/0102-net-dsa-mt7530-lock-the-MDIO-bus-around-regmap-irq-a.patch @@ -1,7 +1,7 @@ From e86b215ec661c443e2d50ee8bb3d4d5008e8845e Mon Sep 17 00:00:00 2001 From: Mattias Walström Date: Tue, 13 Oct 2026 15:48:00 +0200 -Subject: [PATCH 102/102] net: dsa: mt7530: lock the MDIO bus around regmap-irq +Subject: [PATCH 102/105] net: dsa: mt7530: lock the MDIO bus around regmap-irq accesses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 diff --git a/patches/linux/6.18.57/0103-net-phy-mediatek-ge-break-the-MT7531-master-slave-ti.patch b/patches/linux/6.18.57/0103-net-phy-mediatek-ge-break-the-MT7531-master-slave-ti.patch new file mode 100644 index 0000000000..6be96cc3d8 --- /dev/null +++ b/patches/linux/6.18.57/0103-net-phy-mediatek-ge-break-the-MT7531-master-slave-ti.patch @@ -0,0 +1,219 @@ +From 37627894d92b5c2547f2aec35519aea1858f0704 Mon Sep 17 00:00:00 2001 +From: Mattias Walström +Date: Wed, 14 Oct 2026 19:56:12 +0200 +Subject: [PATCH 103/105] net: phy: mediatek-ge: break the MT7531 master/slave + tie after a resume + +Two MT7531 PHYs connected back to back fail to link up again when both +are powered down and resumed at the same time, which is what happens +when a management system bounces both ends of a link. Both ends then +sit with autonegotiation enabled and never completing, the 1000BASE-T +status register shows both resolved as master and eventually the +master/slave configuration fault bit, and the exchange repeats forever. +Measured on a BPI-R3 pair with synchronized ip link bounces, the link +either comes up after 3.0 s or never: 3 of 10 never, with the status +register cycling 0x4000, 0x0800, 0x4800, 0x8800 for as long as one +cares to watch. + +The PHY draws the same master/slave seed on every attempt after it +leaves BMCR power-down, so two PHYs leaving it together keep drawing +the same seed. No register exposes the seed. Keeping the PHY out of +power-down is not an option either: the switch is not reset by a warm +reset of the SoC and firmware only clears power-down, so any other +quiet state survives a crash into the bootloader, and a PHY left in +loopback there echoes every frame back into the switch and breaks +netboot until a cold boot. + +A plain autonegotiation restart on one end does not reliably break the +tie, a power-down pulse on one end does. So watch each resume: when +the port has received pages from its partner but has no link after the +normal link-up time, pulse power-down, at a moment derived from the +port's MAC address so that the two ends never pulse together. A port +with nobody on the other end is left alone. + +With this in place the same pair went through 1000 synchronized +bounces: 356 of them tied, the first pulse broke 353 and the other 3 +resolved on their own before it was due. No round needed a second +pulse and the link was up within 11.9 s in the worst case instead of +never. +--- + drivers/net/phy/mediatek/mtk-ge.c | 146 +++++++++++++++++++++++++++++- + 1 file changed, 144 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/phy/mediatek/mtk-ge.c b/drivers/net/phy/mediatek/mtk-ge.c +index 96d8ac5154e5e..92b29f51c1a92 100644 +--- a/drivers/net/phy/mediatek/mtk-ge.c ++++ b/drivers/net/phy/mediatek/mtk-ge.c +@@ -2,6 +2,10 @@ + #include + #include + #include ++#include ++#include ++#include ++#include + + #include "mtk.h" + +@@ -124,6 +128,142 @@ static int mt7531_phy_config_init(struct phy_device *phydev) + return 0; + } + ++/* Two MT7531 PHYs facing each other that leave BMCR power-down at the ++ * same time, as happens when a management system brings both ends of a ++ * link down and up together, draw the same 1000BASE-T master/slave seed ++ * on every autonegotiation attempt, both resolve as master and never ++ * link up. Only a power-down cycle restarts the seed sequence, no ++ * register exposes it, and a plain autonegotiation restart on one end ++ * does not always break the tie, so break it from software with a ++ * power-down pulse on one end: when a resumed port has received pages ++ * from its partner but has no link after the normal link-up time, ++ * pulse power-down, at a moment derived from the port's MAC address so ++ * that the two ends never do it together. ++ */ ++#define MT7531_LINK_WAIT msecs_to_jiffies(5000) ++#define MT7531_PULSE msecs_to_jiffies(300) ++#define MT7531_BACKOFF_STEP 250 ++#define MT7531_MAX_ROUNDS 16 ++ ++struct mt7531_phy_priv { ++ struct phy_device *phydev; ++ struct delayed_work watch; ++ unsigned int round; ++ bool in_pulse; ++}; ++ ++static unsigned long mt7531_phy_backoff(struct phy_device *phydev, ++ unsigned int round) ++{ ++ struct net_device *ndev = phydev->attached_dev; ++ unsigned int nibble; ++ u32 key; ++ ++ if (ndev) ++ key = crc32_le(~0, ndev->dev_addr, ETH_ALEN); ++ else ++ key = phydev->mdio.addr; ++ ++ nibble = (key >> (4 * (round & 7))) & 0xf; ++ ++ return msecs_to_jiffies(MT7531_BACKOFF_STEP * nibble); ++} ++ ++static void mt7531_phy_watch(struct work_struct *work) ++{ ++ struct mt7531_phy_priv *priv = container_of(work, struct mt7531_phy_priv, ++ watch.work); ++ struct phy_device *phydev = priv->phydev; ++ int bmsr, exp; ++ ++ mutex_lock(&phydev->lock); ++ ++ if (priv->in_pulse) { ++ priv->in_pulse = false; ++ genphy_resume(phydev); ++ schedule_delayed_work(&priv->watch, MT7531_LINK_WAIT + ++ mt7531_phy_backoff(phydev, priv->round)); ++ goto out; ++ } ++ ++ if (phydev->state < PHY_UP || phydev->autoneg != AUTONEG_ENABLE) ++ goto out; ++ ++ /* Link status is latched low, read it twice. */ ++ bmsr = phy_read(phydev, MII_BMSR); ++ if (bmsr >= 0) ++ bmsr = phy_read(phydev, MII_BMSR); ++ exp = phy_read(phydev, MII_EXPANSION); ++ if (bmsr < 0 || exp < 0) ++ goto out; ++ ++ if (bmsr & BMSR_LSTATUS) ++ goto out; ++ ++ /* Nobody on the other end, nothing to break. */ ++ if (!(exp & EXPANSION_LCWP)) ++ goto out; ++ ++ if (priv->round >= MT7531_MAX_ROUNDS) ++ goto out; ++ ++ priv->round++; ++ priv->in_pulse = true; ++ genphy_suspend(phydev); ++ schedule_delayed_work(&priv->watch, MT7531_PULSE); ++out: ++ mutex_unlock(&phydev->lock); ++} ++ ++static int mt7531_phy_resume(struct phy_device *phydev) ++{ ++ struct mt7531_phy_priv *priv = phydev->priv; ++ int ret; ++ ++ ret = genphy_resume(phydev); ++ if (ret) ++ return ret; ++ ++ priv->round = 0; ++ priv->in_pulse = false; ++ mod_delayed_work(system_wq, &priv->watch, ++ MT7531_LINK_WAIT + mt7531_phy_backoff(phydev, 0)); ++ ++ return 0; ++} ++ ++static int mt7531_phy_suspend(struct phy_device *phydev) ++{ ++ struct mt7531_phy_priv *priv = phydev->priv; ++ ++ cancel_delayed_work_sync(&priv->watch); ++ priv->in_pulse = false; ++ ++ return genphy_suspend(phydev); ++} ++ ++static int mt7531_phy_probe(struct phy_device *phydev) ++{ ++ struct mt7531_phy_priv *priv; ++ ++ priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->phydev = phydev; ++ INIT_DELAYED_WORK(&priv->watch, mt7531_phy_watch); ++ phydev->priv = priv; ++ ++ return 0; ++} ++ ++static void mt7531_phy_remove(struct phy_device *phydev) ++{ ++ struct mt7531_phy_priv *priv = phydev->priv; ++ ++ cancel_delayed_work_sync(&priv->watch); ++} ++ + static struct phy_driver mtk_gephy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7530), +@@ -143,14 +283,16 @@ static struct phy_driver mtk_gephy_driver[] = { + { + PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7531), + .name = "MediaTek MT7531 PHY", ++ .probe = mt7531_phy_probe, ++ .remove = mt7531_phy_remove, + .config_init = mt7531_phy_config_init, + /* Interrupts are handled by the switch, not the PHY + * itself. + */ + .config_intr = genphy_no_config_intr, + .handle_interrupt = genphy_handle_interrupt_no_ack, +- .suspend = genphy_suspend, +- .resume = genphy_resume, ++ .suspend = mt7531_phy_suspend, ++ .resume = mt7531_phy_resume, + .read_page = mtk_phy_read_page, + .write_page = mtk_phy_write_page, + }, diff --git a/patches/linux/6.18.57/0104-net-dsa-mt7530-untag-VLAN-aware-bridge-PVID.patch b/patches/linux/6.18.57/0104-net-dsa-mt7530-untag-VLAN-aware-bridge-PVID.patch new file mode 100644 index 0000000000..39eb401cb5 --- /dev/null +++ b/patches/linux/6.18.57/0104-net-dsa-mt7530-untag-VLAN-aware-bridge-PVID.patch @@ -0,0 +1,59 @@ +From 12bac91e802cc2648a9947d7052def6ea7b0f5e6 Mon Sep 17 00:00:00 2001 +From: Edward Parker +Date: Thu, 14 May 2026 15:05:12 +0100 +Subject: [PATCH 104/105] net: dsa: mt7530: untag VLAN-aware bridge PVID +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With bridge VLAN filtering enabled on a port configured as untagged +member of the bridge PVID, ingress untagged frames do not reach the +corresponding bridge VLAN upper interface (br-lan.). ARP and +similar traffic is visible on the physical port but not delivered +to the VLAN sub-interface. + +The MT7530/MT7531 forwards frames to the CPU port with the user +port's PVID tag applied even when the frame ingressed untagged on +the wire, because the CPU port is set to MT7530_VLAN_EG_CONSISTENT +and is a tagged member of the VLAN entry created for the bridge +VLAN. The DSA core then sees a hwaccel-tagged frame whose VID +matches the port's PVID, which the bridge does not treat as the +untagged-on-the-wire frame that the user expects. + +Set ds->untag_vlan_aware_bridge_pvid in the mt7530 and mt7531 +setup paths so the DSA core strips that hwaccel tag in software +when the parsed VID matches the bridge port's PVID, restoring the +on-the-wire frame as the bridge expects to see it. + +Link: https://github.com/openwrt/openwrt/issues/18576 +Fixes: 83163f7dca56 ("net: dsa: mediatek: add VLAN support for MT7530") +Signed-off-by: Edward Parker +[daniel@makrotopia.org: improve commit message] +Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/85d25ea1b26d3c907f815649f2e0bde6560282a3.1778766629.git.daniel@makrotopia.org +Signed-off-by: Paolo Abeni +Signed-off-by: Mattias Walström +--- + drivers/net/dsa/mt7530.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c +index 9e06b79e64e8e..e8f4c1e4663b4 100644 +--- a/drivers/net/dsa/mt7530.c ++++ b/drivers/net/dsa/mt7530.c +@@ -2417,6 +2417,7 @@ mt7530_setup(struct dsa_switch *ds) + } + + ds->assisted_learning_on_cpu_port = true; ++ ds->untag_vlan_aware_bridge_pvid = true; + ds->mtu_enforcement_ingress = true; + ds->ageing_time_min = 2 * 1000; + ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000; +@@ -2608,6 +2609,7 @@ mt7531_setup_common(struct dsa_switch *ds) + int ret, i; + + ds->assisted_learning_on_cpu_port = true; ++ ds->untag_vlan_aware_bridge_pvid = true; + ds->mtu_enforcement_ingress = true; + ds->ageing_time_min = 2 * 1000; + ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000; diff --git a/patches/linux/6.18.57/0105-net-dsa-mt7530-error-out-on-failed-reads-in-ATC-VTCR.patch b/patches/linux/6.18.57/0105-net-dsa-mt7530-error-out-on-failed-reads-in-ATC-VTCR.patch new file mode 100644 index 0000000000..e7045b16a3 --- /dev/null +++ b/patches/linux/6.18.57/0105-net-dsa-mt7530-error-out-on-failed-reads-in-ATC-VTCR.patch @@ -0,0 +1,103 @@ +From 2acc858fe6f0afd488eac07ab4d2c3ef7f1424ce Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 28 Jul 2026 05:52:21 +0100 +Subject: [PATCH 105/105] net: dsa: mt7530: error out on failed reads in + ATC/VTCR command polling +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +[ Upstream commit ed9adac35b8fac635f40e28461505e2e5b6c8fcc ] + +mt7530_fdb_cmd() and mt7530_vlan_cmd() poll the command register +through a helper which returns 0 when the underlying read fails. A +failed bus transaction thus clears ATC_BUSY/VTCR_BUSY and is treated +as successful command completion, and the subsequent ATC_INVALID and +VTCR_INVALID checks are defeated the same way. + +Poll using regmap_read_poll_timeout(), which stops on read errors and +propagates them, and check the completion status read as well. Take +the MDIO bus lock across the sequence as the switch regmap is set up +with locking disabled. + +Fixes: b8f126a8d543 ("net-next: dsa: add dsa support for Mediatek MT7530 switch") +Fixes: 83163f7dca56 ("net: dsa: mediatek: add VLAN support for MT7530") +Signed-off-by: Daniel Golle +Reviewed-by: Andrew Lunn +Link: https://patch.msgid.link/eea1d8f15c54375b3770c23e09fb3217df487169.1785213071.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Mattias Walström +--- + drivers/net/dsa/mt7530.c | 28 ++++++++++++++++++---------- + 1 file changed, 18 insertions(+), 10 deletions(-) + +diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c +index e8f4c1e4663b4..3ad2ecbb02b4b 100644 +--- a/drivers/net/dsa/mt7530.c ++++ b/drivers/net/dsa/mt7530.c +@@ -242,15 +242,20 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp) + { + u32 val; + int ret; +- struct mt7530_dummy_poll p; + + /* Set the command operating upon the MAC address entries */ + val = ATC_BUSY | ATC_MAT(0) | cmd; + mt7530_write(priv, MT7530_ATC, val); + +- INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_ATC); +- ret = readx_poll_timeout(_mt7530_read, &p, val, +- !(val & ATC_BUSY), 20, 20000); ++ mt7530_mutex_lock(priv); ++ ++ ret = regmap_read_poll_timeout(priv->regmap, MT7530_ATC, val, ++ !(val & ATC_BUSY), 20, 20000); ++ if (!ret) ++ ret = regmap_read(priv->regmap, MT7530_ATC, &val); ++ ++ mt7530_mutex_unlock(priv); ++ + if (ret < 0) { + dev_err(priv->dev, "reset timeout\n"); + return ret; +@@ -259,7 +264,6 @@ mt7530_fdb_cmd(struct mt7530_priv *priv, enum mt7530_fdb_cmd cmd, u32 *rsp) + /* Additional sanity for read command if the specified + * entry is invalid + */ +- val = mt7530_read(priv, MT7530_ATC); + if ((cmd == MT7530_FDB_READ) && (val & ATC_INVALID)) + return -EINVAL; + +@@ -1838,22 +1842,26 @@ mt7530_port_mdb_del(struct dsa_switch *ds, int port, + static int + mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) + { +- struct mt7530_dummy_poll p; + u32 val; + int ret; + + val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; + mt7530_write(priv, MT7530_VTCR, val); + +- INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR); +- ret = readx_poll_timeout(_mt7530_read, &p, val, +- !(val & VTCR_BUSY), 20, 20000); ++ mt7530_mutex_lock(priv); ++ ++ ret = regmap_read_poll_timeout(priv->regmap, MT7530_VTCR, val, ++ !(val & VTCR_BUSY), 20, 20000); ++ if (!ret) ++ ret = regmap_read(priv->regmap, MT7530_VTCR, &val); ++ ++ mt7530_mutex_unlock(priv); ++ + if (ret < 0) { + dev_err(priv->dev, "poll timeout\n"); + return ret; + } + +- val = mt7530_read(priv, MT7530_VTCR); + if (val & VTCR_INVALID) { + dev_err(priv->dev, "read VTCR invalid\n"); + return -EINVAL; diff --git a/src/bin/show/__init__.py b/src/bin/show/__init__.py index 9c3eb272d8..0401bfccaf 100755 --- a/src/bin/show/__init__.py +++ b/src/bin/show/__init__.py @@ -83,10 +83,13 @@ def tftp(args: List[str]) -> None: def hardware(args: List[str]) -> None: - """show hardware [ [survey [passive]]]""" + """show hardware [ [survey [passive] | scan]]""" if len(args) >= 2 and args[1] == "survey": channel_survey(args[0], "passive" in args[2:]) return + if len(args) >= 2 and args[1] == "scan": + wifi_scan(args[0]) + return data = get_json("/ietf-hardware:hardware") if not data: @@ -111,41 +114,101 @@ def hardware(args: List[str]) -> None: 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""" +def radio_action(radio: str, action: str, what: str, *args: str): + """Run an action on a WiFi radio, return its stdout or None after printing why""" # 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 + return None - xpath = f"/ietf-hardware:hardware/component[name='{radio}']/infix-hardware:wifi-radio/channel-survey" - cmd = ["rpc", "-j", xpath] - if passive: - cmd += ["passive", "true"] + xpath = f"/ietf-hardware:hardware/component[name='{radio}']/infix-hardware:wifi-radio/{action}" + cmd = ["rpc", "-j", xpath, *args] # Progress goes to stderr, past the pager, which would otherwise - # print the chart twice when it arrives piecemeal after the scan + # print the result 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 + return None # 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 None + print(result.stderr.strip() or f"{what} failed") + return None + + return result.stdout + + +def find_lists(data, name: str, key: str) -> list: + """Collect every list called name whose entries carry key, however the output is wrapped""" + found = [] + if isinstance(data, dict): + for k, v in data.items(): + if k == name and isinstance(v, list) and \ + all(isinstance(e, dict) and key in e for e in v): + found.extend(v) + else: + found.extend(find_lists(v, name, key)) + elif isinstance(data, list): + for item in data: + found.extend(find_lists(item, name, key)) + return found + + +def wifi_scan(radio: str) -> None: + """Run the scan action on a WiFi radio and list the networks heard""" + out = radio_action(radio, "scan", "Scan") + if out is None: + return + + if RAW_OUTPUT: + print(out) + return + + try: + networks = find_lists(json.loads(out), "network", "bssid") + except json.JSONDecodeError: + print("Scan returned no data") + return + + if not networks: + print(f"No networks heard on {radio}.") + return + + rows = [] + for net in networks: + signal = net.get("signal-strength") + rows.append((net.get("ssid", ""), + net.get("bssid", ""), + ", ".join(net.get("encryption", [])) or "Open", + f"{signal} dBm" if signal is not None else "", + str(net.get("channel", "")))) + + width = max(20, min(32, max(len(r[0]) for r in rows))) + sec = max(8, max(len(r[2]) for r in rows)) + print(f"{'SSID':<{width}} {'BSSID':<17} {'SECURITY':<{sec}} {'SIGNAL':>8} CHANNEL") + for ssid, bssid, security, signal, channel in rows: + print(f"{ssid:<{width}.{width}} {bssid:<17} {security:<{sec}} {signal:>8} {channel:>7}") + + +def channel_survey(radio: str, passive: bool) -> None: + """Run the channel-survey action on a WiFi radio and render the channel map""" + out = radio_action(radio, "channel-survey", "Channel survey", + *(["passive", "true"] if passive else [])) + if out is None: return if RAW_OUTPUT: - print(result.stdout) + print(out) return chart = subprocess.run(["/usr/libexec/infix/wifi-channel-map.py"], - input=result.stdout, capture_output=True, text=True) + input=out, capture_output=True, text=True) sys.stdout.write(chart.stdout or chart.stderr) sys.stdout.flush() diff --git a/src/confd/src/core.c b/src/confd/src/core.c index a5899107e3..fbc758d757 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -550,6 +550,31 @@ static confd_dependency_t dep_wifi_interfaces(struct lyd_node **diff, struct lyd } DEBUG("Added radio %s to diff for WiFi interface %s", radio_name, ifname); + + /* The band steering directive of a 2.4 GHz radio names the + * same-SSID access point on another radio, so a change to + * any access point regenerates every radio that has one. */ + if (lydx_get_descendant(lyd_child(dif), "wifi", "access-point", NULL) || + (cif && lydx_get_descendant(lyd_child(cif), "wifi", "access-point", NULL))) { + struct ly_set *radios; + uint32_t i; + + radios = lydx_find_xpathf(config, + "/ietf-interfaces:interfaces/interface/infix-interfaces:wifi[access-point]/radio"); + for (i = 0; radios && i < radios->count; i++) { + const char *name = lyd_get_value(radios->dnodes[i]); + + snprintf(xpath, sizeof(xpath), + "/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", name); + result = add_dependencies(diff, xpath, name); + if (result == CONFD_DEP_ERROR) { + ERROR("Failed to add radio %s to diff for access point %s", name, ifname); + ly_set_free(radios, NULL); + return result; + } + } + ly_set_free(radios, NULL); + } } return result; diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index f5d800f264..6e6a7a50b0 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -482,8 +483,30 @@ static void wifi_gen_wds_ports(FILE *hostapd, struct lyd_node *config, const cha } } +/* + * The hostname of the default nas-identifier, from the configuration + * rather than the kernel. The hostname is applied last in a transaction, + * so at boot gethostname() still answers with the previous one, "infix" + * on every box, and the access points of different nodes end up with the + * same R0KH-ID. hostapd routes a key pull by that name, so a roaming + * client's key is then fetched from the wrong node and the roam fails. + */ +static void wifi_nas_hostname(struct confd *confd, struct lyd_node *config, char *hostname, size_t len) +{ + struct lyd_node *node; + + node = lydx_get_xpathf(config, "/ietf-system:system/hostname"); + if (node && !hostnamefmt(confd, lyd_get_value(node), hostname, len, NULL, 0) && hostname[0]) + return; + + if (gethostname(hostname, len) != 0) + strlcpy(hostname, "localhost", len); + hostname[len - 1] = '\0'; +} + /* 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) +static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd_node *config, bool is_bss, + const char *band, struct confd *confd) { struct lyd_node *wifi, *ap, *security, *roaming; struct lyd_node *dot11k, *dot11r, *dot11v; @@ -538,9 +561,7 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd mobility_domain = resolve_mobility_domain(mobility_domain_raw, ssid); nas_identifier_cfg = lydx_get_cattr(dot11r, "nas-identifier"); if (!nas_identifier_cfg || !strcmp(nas_identifier_cfg, "auto")) { - if (gethostname(hostname, sizeof(hostname)) != 0) - snprintf(hostname, sizeof(hostname), "localhost"); - hostname[sizeof(hostname) - 1] = '\0'; + wifi_nas_hostname(confd, config, hostname, sizeof(hostname)); snprintf(nas_identifier_default, sizeof(nas_identifier_default), "%s-%s.%s", ifname, hostname, mobility_domain); nas_identifier_cfg = nas_identifier_default; @@ -708,8 +729,8 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd * 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 + * connected are moved by a wifi-steer.sh for every pair with + * 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); @@ -1268,7 +1289,8 @@ static void wifi_gen_radio_config(FILE *hostapd, const char *radio_name, /* Generate hostapd config for all APs on a radio (multi-SSID support) */ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, - struct lyd_node *radio_node, struct lyd_node *config) + struct lyd_node *radio_node, struct lyd_node *config, + struct confd *confd) { struct lyd_node *primary_cif, *cif; const char *primary_ifname; @@ -1326,7 +1348,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, fprintf(hostapd, "ctrl_interface=/run/hostapd\n\n"); /* Primary AP SSID and security configuration */ - wifi_gen_ssid_config(hostapd, primary_cif, config, false, lydx_get_cattr(radio_node, "band")); + wifi_gen_ssid_config(hostapd, primary_cif, config, false, lydx_get_cattr(radio_node, "band"), confd); fprintf(hostapd, "\n"); /* Radio-specific configuration */ @@ -1351,7 +1373,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs, } DEBUG("Adding BSS section for secondary AP %s", ap_list[i]); - wifi_gen_ssid_config(hostapd, bss_cif, config, true, lydx_get_cattr(radio_node, "band")); + wifi_gen_ssid_config(hostapd, bss_cif, config, true, lydx_get_cattr(radio_node, "band"), confd); } fclose(hostapd); @@ -1659,6 +1681,49 @@ static int modem_gen_rules(struct lyd_node *config) return unlocked; } +/* + * The helpers that run alongside hostapd, as services of their own that + * follow it: the access point exchange with the other nodes, for the + * BSSes that do 802.11r over a bridge, and one band steering loop per + * 2.4 GHz BSS with a 5/6 GHz twin, read back from the radio configs. + */ +static void wifi_gen_helpers(FILE *fp, glob_t *gl) +{ + bool neighbors = false; + size_t i; + + for (i = 0; i < gl->gl_pathc; i++) { + char line[256], bss[IFNAMSIZ] = "", twin[IFNAMSIZ]; + FILE *conf; + + conf = fopen(gl->gl_pathv[i], "r"); + if (!conf) + continue; + while (fgets(line, sizeof(line), conf)) { + if (sscanf(line, "interface=%15s", bss) == 1 || + sscanf(line, "bss=%15s", bss) == 1) + continue; + if (!strncmp(line, "ft_iface=", 9)) + neighbors = true; + if (bss[0] && sscanf(line, "no_probe_resp_if_seen_on=%15s", twin) == 1) { + fprintf(fp, "\nservice name:wifi-steer :%s \\\n", bss); + fprintf(fp, "\t[2345] /usr/libexec/infix/wifi-steer.sh %s %s \\\n", bss, twin); + fprintf(fp, "\t-- Wi-Fi band steering %s\n", bss); + } + } + fclose(conf); + } + + if (!neighbors) + return; + + fprintf(fp, "\nservice name:wifi-neighbors \\\n"); + fprintf(fp, "\t[2345] /usr/libexec/infix/wifi-neighbors.py"); + for (i = 0; i < gl->gl_pathc; i++) + fprintf(fp, " %s", gl->gl_pathv[i]); + fprintf(fp, " \\\n\t-- Wi-Fi neighbor exchange\n"); +} + int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) { @@ -1766,7 +1831,7 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l if (wifi_iface_list) { if (wifi_iface_count) { /* Generate AP config (hostapd) for all APs on this radio */ - rc = wifi_gen_aps_on_radio(name, interfaces_config, cwifi_radio, config); + rc = wifi_gen_aps_on_radio(name, interfaces_config, cwifi_radio, config, confd); if (rc != SR_ERR_OK) ERROR("Failed to generate AP config for radio %s", name); } @@ -1970,14 +2035,17 @@ int hardware_wifi_change(sr_session_ctx_t *session, struct lyd_node *config, str rc = SR_ERR_INTERNAL; } else { fprintf(fp, "# Generated by confd, do not edit.\n"); - /* 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"); + /* The stop script hands the clients over to another + * access point before hostapd stops, give it time. + * hostapd only writes a PID file when it forks, so + * finit writes it, for the helpers' . */ + fprintf(fp, "service name:hostapd pid:/run/hostapd.pid kill:10" + " stop:/usr/libexec/infix/wifi-handover.sh \\\n"); + fprintf(fp, "\t[2345] hostapd -g /run/hostapd.global"); for (i = 0; i < gl.gl_pathc; i++) fprintf(fp, " %s", gl.gl_pathv[i]); fprintf(fp, " \\\n\t-- Wi-Fi Access Points\n"); + wifi_gen_helpers(fp, &gl); fclose(fp); finit_enable("hostapd"); @@ -2079,7 +2147,97 @@ static int wifi_channel_survey(sr_session_ctx_t *session, uint32_t sub_id, const return SR_ERR_OK; } -/* The action only exists when the wifi feature is enabled, see wifi.inc */ +/* + * Scan for networks in range of a radio, the scan action in + * infix-hardware.yang. Like the survey it takes the radio off its + * channel for a few seconds, so it only runs on request. The helper + * does the scan and the parsing, see iw.py scan. + */ +static int wifi_scan(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; } nums[] = { + { "frequency", "frequency" }, + { "channel", "channel" }, + { "signal", "signal-strength" }, + }; + struct lyd_node *component, *net; + json_t *root, *entry; + const char *radio; + json_error_t jerr; + 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 scan"); + + pp = popenf("r", "/usr/libexec/infix/iw.py scan %s", radio); + if (!pp) + return rpc_failed(session, "Failed starting scan"); + + root = json_loadf(pp, 0, &jerr); + pclose(pp); + if (!root) + return rpc_failed(session, "Scan returned no data"); + + if (!json_is_array(root)) { + json_t *err = json_object_get(root, "error"); + char msg[256]; + + snprintf(msg, sizeof(msg), "Scan 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 *bssid = json_object_get(entry, "bssid"); + json_t *val, *enc; + char buf[32]; + size_t i; + + if (!json_is_string(bssid)) + continue; + + if (lyd_new_list(output, NULL, "network", LYD_NEW_VAL_OUTPUT, &net, + json_string_value(bssid))) { + ERROR("scan: failed adding network %s", json_string_value(bssid)); + continue; + } + + val = json_object_get(entry, "ssid"); + if (json_is_string(val)) + lyd_new_term(net, NULL, "ssid", json_string_value(val), + LYD_NEW_VAL_OUTPUT, NULL); + + for (i = 0; i < NELEMS(nums); i++) { + val = json_object_get(entry, nums[i].json); + if (!json_is_integer(val)) + continue; + + snprintf(buf, sizeof(buf), "%lld", json_integer_value(val)); + lyd_new_term(net, NULL, nums[i].yang, buf, LYD_NEW_VAL_OUTPUT, NULL); + } + + enc = json_object_get(entry, "encryption"); + json_array_foreach(enc, i, val) { + if (json_is_string(val)) + lyd_new_term(net, NULL, "encryption", json_string_value(val), + LYD_NEW_VAL_OUTPUT, NULL); + } + } + json_decref(root); + + return SR_ERR_OK; +} + +/* The actions only exist when the wifi feature is enabled, see wifi.inc */ static bool wifi_feature_enabled(struct confd *confd) { const struct lys_module *mod; @@ -2107,6 +2265,8 @@ int hardware_rpc_init(struct confd *confd) REGISTER_RPC_TREE(confd->session, XPATH_BASE_ "/component/infix-hardware:wifi-radio/channel-survey", wifi_channel_survey, NULL, &confd->sub); + REGISTER_RPC_TREE(confd->session, XPATH_BASE_ "/component/infix-hardware:wifi-radio/scan", + wifi_scan, NULL, &confd->sub); return SR_ERR_OK; fail: diff --git a/src/confd/src/if-wifi.c b/src/confd/src/if-wifi.c index 8e71b073ee..1af5ba1872 100644 --- a/src/confd/src/if-wifi.c +++ b/src/confd/src/if-wifi.c @@ -136,8 +136,8 @@ wifi_mode_t wifi_get_mode(struct lyd_node *iface) } /* - * Need to return station even if "station" also is false, - * because station is the default scanning mode. + * An interface without a mode is a managed netdev with nothing + * running on it, handled with the stations. */ return wifi_station; } @@ -168,7 +168,7 @@ 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 */ + /* No mode <-> station, and the 4-address flag is set at creation */ node = lydx_get_child(wifi, "station"); if (node) op = lydx_get_op(node); @@ -195,6 +195,8 @@ static int wifi_gen_reload(struct lyd_node *dif, struct lyd_node *cif, struct da switch (wifi_get_mode(cif)) { case wifi_station: + if (!lydx_get_descendant(lyd_child(cif), "wifi", "station", NULL)) + return SR_ERR_OK; svc = "wifi"; break; case wifi_mesh: @@ -255,20 +257,18 @@ int wifi_gen_station(struct lyd_node *cif) return SR_ERR_OK; 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"); - } else { - /* If station is NULL, we're in scan-only mode (no station container) */ - ssid = NULL; - security = NULL; - security_mode = "disabled"; - secret_name = NULL; + if (!station) { + /* No mode configured, nothing for wpa_supplicant to do */ + erasef(WPA_SUPPLICANT_CONF, ifname); + return SR_ERR_OK; } + 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"); + country = lydx_get_cattr(lydx_get_xpathf(cif, "/ietf-hardware:hardware/infix-hardware:wifi"), "country-code"); @@ -300,51 +300,31 @@ int wifi_gen_station(struct lyd_node *cif) if (country) fprintf(wpa_supplicant, "country=%s\n", country); - /* If SSID is present, create network block. Otherwise, scan-only mode */ - if (ssid) { - /* Station mode with network configured */ - 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 - * fixed backhaul link the periodic off-channel sweep stalls - * traffic and buys no roaming benefit. */ - fprintf(wpa_supplicant, - "network={\n" - " bgscan=\"\"\n" - " scan_ssid=1\n" - " ssid=\"%s\"\n", ssid); - if (bssid) - fprintf(wpa_supplicant, " bssid=%s\n", bssid); - fprintf(wpa_supplicant, - " %s\n" - "}\n", security_str); - free(security_str); - } else { - /* Scan-only mode - no station container configured */ - fprintf(wpa_supplicant, "# Scan-only - need dummy network for state machine\n"); - /* - * This prevents the daemon from actually trying to associate and fail, - * 'bssid' with a reserved/impossible MAC address prevents it from ever - * actually \"finding\" and joining a random open AP - */ - fprintf(wpa_supplicant, - "network={\n" - " ssid=\"\"\n" - " key_mgmt=NONE\n" - " disabled=0\n" - " bssid=00:00:00:00:00:01\n" - " scan_ssid=1\n" - "}\n"); - } + 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 + * fixed backhaul link the periodic off-channel sweep stalls + * traffic and buys no roaming benefit. */ + fprintf(wpa_supplicant, + "network={\n" + " bgscan=\"\"\n" + " scan_ssid=1\n" + " ssid=\"%s\"\n", ssid); + if (bssid) + fprintf(wpa_supplicant, " bssid=%s\n", bssid); + fprintf(wpa_supplicant, + " %s\n" + "}\n", security_str); + free(security_str); out: free(secret); @@ -704,6 +684,8 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net) /* iw takes addr before 4addr, whatever its usage text says */ fprintf(iw, "iw phy %s interface add %s type managed%s%s\n", radio, ifname, addr, station && lydx_is_enabled(station, "wds") ? " 4addr on" : ""); + if (!station) + break; /* No mode yet, the netdev is created but idle */ wifi_gen_station(cif); fprintf(iw, "initctl -bfq enable wifi@%s\n", ifname); fprintf(iw, "initctl -bfq touch wifi@%s\n", ifname); diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang index 430cc4ce12..abcca42712 100644 --- a/src/confd/yang/confd/infix-hardware.yang +++ b/src/confd/yang/confd/infix-hardware.yang @@ -1005,6 +1005,66 @@ module infix-hardware { } } } + + action scan { + description + "Scan for WiFi networks in range of the radio and report what + was found. + + The radio leaves its operating channel for the duration of + the scan, a few seconds, so traffic on the radio pauses. A + radio without any interface can be scanned too. Networks are + not collected in the background, this action is the only way + to find them."; + + output { + list network { + key "bssid"; + description + "One access point (BSS) heard during the scan. Several + entries can share an SSID, one per access point of a + roaming network."; + + leaf bssid { + type yang:mac-address; + description "BSSID (MAC address) of the access point."; + } + + leaf ssid { + type string; + description + "Network name. Absent for a hidden network, which + does not announce its name."; + } + + leaf frequency { + type uint32; + units "MHz"; + description "Channel center frequency in MHz."; + } + + leaf channel { + type uint16; + description "Channel number on the network's band."; + } + + leaf signal-strength { + type int16; + units "dBm"; + description "Signal strength of the access point."; + } + + leaf-list encryption { + ordered-by user; + type string; + description + "Security offered by the access point. + + Examples: 'WPA2-Personal', 'WPA3-Personal', 'Open'"; + } + } + } + } } /* diff --git a/src/confd/yang/confd/infix-if-wifi.yang b/src/confd/yang/confd/infix-if-wifi.yang index ea73e06db6..1996cf0065 100644 --- a/src/confd/yang/confd/infix-if-wifi.yang +++ b/src/confd/yang/confd/infix-if-wifi.yang @@ -56,7 +56,8 @@ submodule infix-if-wifi { 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. Interfaces without a custom-phys-address - are no longer limited to one per radio."; + are no longer limited to one per radio. An open access point is + rejected on 6 GHz, as the error message always said."; reference "internal"; } @@ -163,7 +164,7 @@ submodule infix-if-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)][not(wifi/wds-link)]) <= 1" { - error-message "Only one station or scan interface is allowed per radio"; + error-message "Only one station interface is allowed per radio"; } description @@ -184,10 +185,10 @@ submodule infix-if-wifi { description "WiFi interface operating mode. - When no mode is configured, the interface operates in scan-only - mode, allowing discovery of available WiFi networks. + An interface without a mode is created but idle. To find + the networks in range, use the scan action on the radio. - Once you've identified a network, configure either: + Configure one of: - 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 @@ -354,45 +355,6 @@ submodule infix-if-wifi { - 65 = 6.5 Mbps - 866 = 86.6 Mbps"; } - - list scan-results { - config false; - key ssid; - description - "List of discovered WiFi networks. - - Updated periodically by background scanning."; - - leaf ssid { - type string; - description "SSID of the discovered network."; - } - - leaf bssid { - type yang:mac-address; - description "BSSID (MAC address) of the AP."; - } - - leaf signal-strength { - type int16; - units "dBm"; - description "Signal strength of the network."; - } - - leaf channel { - type uint16; - description "Channel on which the network was detected."; - } - - leaf-list encryption { - ordered-by user; - type string; - description - "Human-readable security information. - - Examples: 'WPA2-Personal', 'WPA3-SAE', 'Open'"; - } - } } } @@ -492,7 +454,7 @@ submodule infix-if-wifi { } } default auto; - must ". = 'auto' or . = 'open' or . = 'wpa3-personal' or " + must ". = 'auto' or . = 'wpa3-personal' or " + "/iehw:hardware/iehw:component[iehw:name = current()/../../../radio]/ih:wifi-radio/ih:band != '6GHz'" { error-message "6GHz band requires 'auto' or 'wpa3-personal' security mode."; } diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index c3455287d0..0d4e322c2c 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -730,6 +730,11 @@ echo "Public: $pub" + + + show hardware "$KLISH_PARAM_component" scan |pager + + show hardware "$KLISH_PARAM_component" survey |pager diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index a6fa6ca26c..4b86a78292 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -98,14 +98,6 @@ def compress_interface_list(interfaces): return ", ".join(result_parts) -class Pad: - flags = 2 - iface = 16 - proto = 14 - state = 12 - data = 41 - - class PadMdb: bridge = 7 vlan = 6 @@ -232,11 +224,11 @@ def visible_width(text): clean_text = re.sub(ansi_pattern, '', str(text)) return len(clean_text) - def row(self, *values): - """Store row data for later formatting""" + def row(self, *values, style=None): + """Store row data for later formatting, style decorates the whole row""" if len(values) != len(self.columns): raise ValueError(f"Expected {len(self.columns)} values, got {len(values)}") - self.rows.append(values) + self.rows.append((values, style)) def width(self): """Calculate and return total table width""" @@ -288,14 +280,18 @@ def print(self, styled=True): self.adjust_padding(self.min_width) print(self._format_header(self._column_widths, styled)) - for row_data in self.rows: - print(self._format_row(row_data, self._column_widths)) + for row_data, style in self.rows: + line = self._format_row(row_data, self._column_widths) + if style: + line += " " * (self.width() - self.visible_width(line)) + line = style(line) + print(line) def _calculate_column_widths(self): """Calculate maximum width needed for each column""" widths = [self.visible_width(col.name) for col in self.columns] - for row_data in self.rows: + for row_data, _ in self.rows: for i, (value, column) in enumerate(zip(row_data, self.columns)): formatted_value = column.formatter(value) if column.formatter else value value_width = self.visible_width(str(formatted_value)) @@ -1219,30 +1215,33 @@ def oper(self, detail=False): return "lower-down" return self.oper_status - def pr_name(self, pipe=""): - flag = "⇅" if self.name in Iface._routing_ifaces else " " - print(f"{flag:<{Pad.flags}}", end="") - print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="") + def flag(self): + return "⇅" if self.name in Iface._routing_ifaces else "" - def pr_proto_ipv4(self, pipe=''): - for addr in self.ipv4_addr: - origin = f"({addr['origin']})" if addr.get('origin') else "" + def _state(self): + dec = Decore.green if self.oper() == "up" else Decore.red + return dec(self.oper().upper()) - row = f"{'':<{Pad.flags}}" - row += f"{pipe:<{Pad.iface}}" - row += f"{'ipv4':<{Pad.proto}}" - row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}" - print(row) + def add_row(self, table, pipe, cells, first=False): + """Add an overview row, cells is (protocol, state, data). - def pr_proto_ipv6(self, pipe=''): - for addr in self.ipv6_addr: - origin = f"({addr['origin']})" if addr.get('origin') else "" + The first row of an interface carries its flag and name, the + rows below only a tree marker in the name column: '│' under a + parent with members, '└ ' or '├ ' in front of a member. + """ + proto, state, data = cells + if first: + table.row(self.flag(), f"{pipe}{self.name}", proto, state, data) + else: + table.row("", pipe, proto, state, data) - row = f"{'':<{Pad.flags}}" - row += f"{pipe:<{Pad.iface}}" - row += f"{'ipv6':<{Pad.proto}}" - row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}" - print(row) + def add_addrs(self, table, pipe=""): + for proto, addrs in (("ipv4", self.ipv4_addr), ("ipv6", self.ipv6_addr)): + for addr in addrs: + data = f"{addr['ip']}/{addr['prefix-length']}" + if addr.get('origin'): + data += f" ({addr['origin']})" + table.row("", pipe, proto, "", data) @staticmethod def _pr_label_list(label, values): @@ -1290,71 +1289,49 @@ def _phy_label(self): """ return self._pmd_label(self.pmd_type or self.phy_type) - def _pr_proto_common(self, name, pipe='', data='', show_state=True): - # pipe="" means this row follows pr_name() on the same line, so col 1-2 - # are already on screen. A non-empty pipe (" ", "│", "└ ") prints col - # 1-2 with that marker. show_state=False blanks the STATE column for - # sub-rows whose state was already reflected in the row above. - row = "" - if len(pipe) > 0: - row = f"{'':<{Pad.flags}}" - row += f"{pipe:<{Pad.iface}}" - - row += f"{name:<{Pad.proto}}" - if show_state: - dec = Decore.green if self.oper() == "up" else Decore.red - row += dec(f"{self.oper().upper():<{Pad.state}}") - else: - row += f"{'':<{Pad.state}}" - if data: - row += f"{data:<{Pad.data}}" - return row + def cells(self, name, data=""): + """Overview cells (protocol, state, data) for a protocol row""" + return (name, self._state(), data) - def _pr_phy_row(self, pipe=''): - """Emit the physical-medium row when applicable. Return True if printed. - - Skipped when pmd-type is unknown or the link is not up — keeping the - common 'boring port' cases quiet and falling back to the ethernet row. - """ + def cells_phy(self): + """Physical-medium cells, or None when the link is down or the + medium unknown, which keeps the common cases on the ethernet row.""" label = self._phy_label() if not label or self.oper() != "up": - return False - data = f"duplex: {self.duplex}" if self.duplex else "" - print(self._pr_proto_common(label, pipe, data)) - return True + return None + return self.cells(label, f"duplex: {self.duplex}" if self.duplex else "") - def pr_proto_eth(self, pipe=''): - print(self._pr_proto_common("ethernet", pipe, self.phys_address or "")) + def cells_eth(self): + return self.cells("ethernet", self.phys_address or "") - def pr_proto_eth_subrow(self, pipe=' '): - """Ethernet row beneath another protocol row: bare MAC, no STATE column.""" - print(self._pr_proto_common("ethernet", pipe, self.phys_address or "", - show_state=False)) + def cells_eth_sub(self): + """Ethernet row beneath another protocol row: bare MAC, no state""" + return ("ethernet", "", self.phys_address or "") - def pr_proto_veth(self, pipe=''): + def cells_veth(self): parts = [] if self.phys_address: parts.append(self.phys_address) if self.lower_if: parts.append(f"peer: {self.lower_if}") - print(self._pr_proto_common("veth", pipe, " ".join(parts))) + return self.cells("veth", " ".join(parts)) - def pr_proto_gre(self, pipe=''): + def cells_gre(self): data = "" if self.gre and (remote := self.gre.get('remote')): data = f"remote: {remote}" - print(self._pr_proto_common("gre", pipe, data)) + return self.cells("gre", data) - def pr_proto_vxlan(self, pipe=''): + def cells_vxlan(self): parts = [] if self.vxlan: if (vni := self.vxlan.get('vni')) is not None: parts.append(f"vni: {vni}") if remote := self.vxlan.get('remote'): parts.append(f"remote: {remote}") - print(self._pr_proto_common("vxlan", pipe, " ".join(parts))) + return self.cells("vxlan", " ".join(parts)) - def pr_proto_wireguard(self, pipe=''): + def cells_wireguard(self): data = "" if self.wireguard: peer_status = self.wireguard.get('peer-status', {}) @@ -1364,33 +1341,10 @@ def pr_proto_wireguard(self, pipe=''): if total_peers > 0: plural = "s" if total_peers != 1 else "" data = f"{total_peers} peer{plural} ({up_peers} up)" - print(self._pr_proto_common("wireguard", pipe, data)) - - def pr_proto_loopack(self, pipe=''): - print(self._pr_proto_common("loopback", pipe)) - - def pr_wifi_ssids(self): - width = 70 - Decore.title("Available Networks", width) - ssid_table = SimpleTable([ - Column('SSID', flexible=True), - Column('BSSID'), - Column('SECURITY'), - Column('SIGNAL'), - Column('CHANNEL', 'right') - ], min_width=width) - - station = self.wifi.get("station", {}) - results = station.get("scan-results", {}) - for result in results: - ssid = result.get('ssid', 'Hidden') - bssid = result.get("bssid", "unknown") - encstr = ", ".join(result.get("encryption", ["Unknown"])) - status = signal_to_status(result.get("signal-strength", -100)) - channel = result.get("channel", "?") + return self.cells("wireguard", data) - ssid_table.row(ssid, bssid, encstr, status, channel) - ssid_table.print() + def cells_loopback(self): + return self.cells("loopback") def pr_wifi_stations(self): """Display connected stations for AP mode""" @@ -1490,7 +1444,7 @@ def pr_wifi_peers(self): peers_table.print() - def pr_proto_wifi(self, pipe=''): + def cells_wifi(self): ssid = None signal = None mode = None @@ -1534,18 +1488,14 @@ def pr_proto_wifi(self, pipe=''): else: data_str = "ssid: ------" - print(self._pr_proto_common("wifi", pipe, data_str)) - - def pr_proto_br(self, br_vlans): - data_str = "" - - row = f"{'bridge':<{Pad.proto}}" + return self.cells("wifi", data_str) + def cells_br(self, br_vlans): if self.oper() == "up": dec = Decore.green if self.stp_state == "forwarding" else Decore.yellow - row += dec(f"{self.stp_state.upper():<{Pad.state}}") + state = dec(self.stp_state.upper()) else: - row += Decore.red(f"{self.oper().upper():<{Pad.state}}") + state = Decore.red(self.oper().upper()) vlans = [] for vlan in br_vlans: @@ -1558,166 +1508,83 @@ def pr_proto_br(self, br_vlans): tokens.append(f"vlan: {','.join(vlans)}") if self.pvid: tokens.append(f"pvid: {self.pvid}") - data_str = " ".join(tokens) - if data_str: - row += f"{data_str:<{Pad.data}}" + return ("bridge", state, " ".join(tokens)) - print(row) - - def pr_bridge(self, _ifaces): - self.pr_name(pipe="") - self.pr_proto_br(self.br_vlans) + def add_bridge(self, table, _ifaces): + lowers = [iface for iface in map(Iface, _ifaces) if iface.bridge == self.name] - lowers = [] - for _iface in [Iface(data) for data in _ifaces]: - if _iface.bridge and _iface.bridge == self.name: - lowers.append(_iface) - - if lowers: - self.pr_proto_eth_subrow(pipe='│') - self.pr_proto_ipv4(pipe='│') - self.pr_proto_ipv6(pipe='│') - else: - self.pr_proto_eth_subrow() - self.pr_proto_ipv4() - self.pr_proto_ipv6() + self.add_row(table, "", self.cells_br(self.br_vlans), first=True) + pipe = "│" if lowers else "" + self.add_row(table, pipe, self.cells_eth_sub()) + self.add_addrs(table, pipe) for i, lower in enumerate(lowers): - pipe = '└ ' if (i == len(lowers) -1) else '├ ' - lower.pr_name(pipe) - lower.pr_proto_br(self.br_vlans) + pipe = "└ " if i == len(lowers) - 1 else "├ " + lower.add_row(table, pipe, lower.cells_br(self.br_vlans), first=True) - def pr_loopback(self): - self.pr_name(pipe="") - self.pr_proto_loopack() - self.pr_proto_ipv4() - self.pr_proto_ipv6() + def add_loopback(self, table): + self.add_row(table, "", self.cells_loopback(), first=True) + self.add_addrs(table) - def pr_proto_lag(self, member=True): - data_str = "" - - row = f"{'lag':<{Pad.proto}}" + def cells_lag(self, member=True): + dec = Decore.green if self.oper() == "up" else Decore.yellow if member: - state = self.lag_state.upper() - if self.oper() == "up": - row += Decore.green(f"{state:<{Pad.state}}") - else: - row += Decore.yellow(f"{state:<{Pad.state}}") - if self.lacp_state: - lacp = ', '.join(self.lacp_state) - data_str += lacp + state = dec(self.lag_state.upper()) + data = ", ".join(self.lacp_state) if self.lacp_state else "" else: - dec = Decore.green if self.oper() == "up" else Decore.yellow - row += dec(f"{self.oper().upper():<{Pad.state}}") - data_str += f"{self.lag_mode}" + state = dec(self.oper().upper()) + data = f"{self.lag_mode}" if self.lag_mode == "lacp": - data_str += f": {self.lacp_mode}" - data_str += f", rate: {self.lacp_rate}" - data_str += f", hash: {self.lag_hash}" + data += f": {self.lacp_mode}, rate: {self.lacp_rate}, hash: {self.lag_hash}" else: - data_str += f": {self.lag_type}" - data_str += f", hash: {self.lag_hash}" - - if data_str: - row += f"{data_str:<{Pad.data}}" + data += f": {self.lag_type}, hash: {self.lag_hash}" - print(row) + return ("lag", state, data) - def pr_lag(self, _ifaces): - self.pr_name(pipe="") - self.pr_proto_lag(member=False) + def add_lag(self, table, _ifaces): + lowers = [iface for iface in map(Iface, _ifaces) if iface.lag == self.name] - lowers = [] - for _iface in [Iface(data) for data in _ifaces]: - if _iface.lag and _iface.lag == self.name: - lowers.append(_iface) + self.add_row(table, "", self.cells_lag(member=False), first=True) + pipe = "│" if lowers else "" + self.add_row(table, pipe, self.cells_eth_sub()) + self.add_addrs(table, pipe) - if lowers: - self.pr_proto_eth_subrow(pipe='│') - self.pr_proto_ipv4(pipe='│') - self.pr_proto_ipv6(pipe='│') + for i, lower in enumerate(lowers): + pipe = "└ " if i == len(lowers) - 1 else "├ " + lower.add_row(table, pipe, lower.cells_lag(), first=True) + + def add_plain(self, table, cells, eth_sub=False): + """One protocol row, an optional bare ethernet row, and the addresses""" + self.add_row(table, "", cells, first=True) + if eth_sub: + self.add_row(table, "", self.cells_eth_sub()) + self.add_addrs(table) + + def add_ethernet(self, table): + if cells := self.cells_phy(): + self.add_plain(table, cells, eth_sub=True) else: - self.pr_proto_eth_subrow() - self.pr_proto_ipv4() - self.pr_proto_ipv6() + self.add_plain(table, self.cells_eth()) - for i, lower in enumerate(lowers): - pipe = '└ ' if (i == len(lowers) -1) else '├ ' - lower.pr_name(pipe) - lower.pr_proto_lag() - - def pr_veth(self): - self.pr_name(pipe="") - self.pr_proto_veth() - self.pr_proto_ipv4() - self.pr_proto_ipv6() - - def pr_gre(self): - self.pr_name(pipe="") - self.pr_proto_gre() - self.pr_proto_ipv4() - self.pr_proto_ipv6() - - def pr_gretap(self): - self.pr_name(pipe="") - self.pr_proto_gre() - self.pr_proto_eth_subrow() - self.pr_proto_ipv4() - self.pr_proto_ipv6() - - def pr_vxlan(self): - self.pr_name(pipe="") - self.pr_proto_vxlan() - self.pr_proto_eth_subrow() - self.pr_proto_ipv4() - self.pr_proto_ipv6() - - def pr_wireguard(self): - self.pr_name(pipe="") - self.pr_proto_wireguard() - self.pr_proto_ipv4() - self.pr_proto_ipv6() - - def pr_wifi(self): - self.pr_name(pipe="") - self.pr_proto_wifi() - self.pr_proto_eth_subrow() - self.pr_proto_ipv4() - self.pr_proto_ipv6() - - def pr_vlan(self, _ifaces): - self.pr_name(pipe="") + def add_vlan(self, table, _ifaces): data = f"vid: {self.vid}" if self.vid is not None else "" - print(self._pr_proto_common("vlan", "", data)) + self.add_row(table, "", self.cells("vlan", data), first=True) - if self.lower_if: - self.pr_proto_ipv4(pipe='│') - self.pr_proto_ipv6(pipe='│') - else: - self.pr_proto_ipv4() - self.pr_proto_ipv6() + if not self.lower_if: + self.add_addrs(table) return + self.add_addrs(table, "│") parent = find_iface(_ifaces, self.lower_if) if not parent: print(f"Error, didn't find parent interface for vlan {self.name}") sys.exit(1) - parent.pr_name(pipe='└ ') - print() - - def pr_container(self): - # Add ⇅ flag for interfaces with IP forwarding enabled - flag = "⇅" if self.name in Iface._routing_ifaces else " " - # Flag column is outside the gray background - print(f"{flag:<{Pad.flags}}", end="") - - row = f"{self.name:<{Pad.iface}}" - row += f"{'container':<{Pad.proto}}" - row += f"{'':<{Pad.state}}" - row += f"{', ' . join(self.containers):<{Pad.data}}" + table.row(parent.flag(), f"└ {parent.name}", "", "", "") - print(Decore.gray_bg(row)) + def add_container(self, table): + table.row(self.flag(), self.name, "container", "", ", ".join(self.containers), + style=Decore.gray_bg) def pr_iface(self): if self.is_in_container(): @@ -1860,8 +1727,6 @@ def _addr_lines(addrs): 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") - if "scan-results" in station: - self.pr_wifi_ssids() if self.gre: print(f"{'local address':<{19}}: {self.gre['local']}") @@ -2015,24 +1880,14 @@ def brport_sort(iface): return version_sort(brname) + version_sort(iface["name"]) -def print_interface(iface): - iface.pr_name() - if iface._pr_phy_row(): - iface.pr_proto_eth_subrow() - else: - iface.pr_proto_eth() - iface.pr_proto_ipv4() - iface.pr_proto_ipv6() - - def pr_interface_list(json): - hdr = (f"{'⚑':<{Pad.flags}}" - f"{'INTERFACE':<{Pad.iface}}" - f"{'PROTOCOL':<{Pad.proto}}" - f"{'STATE':<{Pad.state}}" - f"{'DATA':<{Pad.data}}") - - print(Decore.invert(hdr)) + table = SimpleTable([ + Column('⚑'), + Column('INTERFACE'), + Column('PROTOCOL'), + Column('STATE'), + Column('DATA') + ]) # Set class variable with routing-enabled interfaces (with IP forwarding) if "ietf-routing:routing" in json: @@ -2045,61 +1900,39 @@ def pr_interface_list(json): key=ifname_sort) iface = find_iface(ifaces, "lo") if iface: - iface.pr_loopback() + iface.add_loopback(table) for iface in [Iface(data) for data in ifaces]: if iface.name == "lo": continue if iface.is_in_container(): - iface.pr_container() - continue - - if iface.is_bridge(): - iface.pr_bridge(ifaces) - continue - - if iface.is_lag(): - iface.pr_lag(ifaces) - continue - - if iface.is_veth(): - iface.pr_veth() - continue - - if iface.is_gre(): - iface.pr_gre() - continue - - if iface.is_gretap(): - iface.pr_gretap() - continue - - if iface.is_vxlan(): - iface.pr_vxlan() - continue - - if iface.is_wireguard(): - iface.pr_wireguard() - continue - - if iface.is_wifi(): - iface.pr_wifi() - continue - - if iface.is_vlan(): - iface.pr_vlan(ifaces) - continue - - # These interfaces are printed by their parent, such as bridge - if iface.lower_if: - continue - if iface.bridge: - continue - if iface.lag: + iface.add_container(table) + elif iface.is_bridge(): + iface.add_bridge(table, ifaces) + elif iface.is_lag(): + iface.add_lag(table, ifaces) + elif iface.is_veth(): + iface.add_plain(table, iface.cells_veth()) + elif iface.is_gre(): + iface.add_plain(table, iface.cells_gre()) + elif iface.is_gretap(): + iface.add_plain(table, iface.cells_gre(), eth_sub=True) + elif iface.is_vxlan(): + iface.add_plain(table, iface.cells_vxlan(), eth_sub=True) + elif iface.is_wireguard(): + iface.add_plain(table, iface.cells_wireguard()) + elif iface.is_wifi(): + iface.add_plain(table, iface.cells_wifi(), eth_sub=True) + elif iface.is_vlan(): + iface.add_vlan(table, ifaces) + elif iface.lower_if or iface.bridge or iface.lag: + # Printed by their parent, such as a bridge continue + else: + iface.add_ethernet(table) - print_interface(iface) + table.print() def show_interfaces(json, name): diff --git a/src/statd/python/yanger/ietf_interfaces/wifi.py b/src/statd/python/yanger/ietf_interfaces/wifi.py index 39615a8d16..7dbf30d086 100644 --- a/src/statd/python/yanger/ietf_interfaces/wifi.py +++ b/src/statd/python/yanger/ietf_interfaces/wifi.py @@ -3,7 +3,6 @@ Scanning still uses wpa_supplicant for better compatibility. """ import json -import re from ..host import HOST @@ -135,7 +134,7 @@ def wifi_mesh(ifname, info=None): return {'mesh-point': mesh_data} def wifi_station(ifname): - """Get operational data for Station mode using iw + wpa_cli for scanning""" + """Get operational data for Station mode""" station_data = {} # Get link info (includes SSID and signal strength when connected) @@ -153,16 +152,6 @@ def wifi_station(ifname): if link.get('tx-speed') is not None: station_data['tx-speed'] = link['tx-speed'] - # Get scan results from wpa_supplicant (better scan support) - try: - data = HOST.run(('wpa_cli', '-i', ifname, 'scan_result'), default='FAIL') - if data and data != 'FAIL': - scan_results = parse_wpa_scan_result(data) - if scan_results: - station_data['scan-results'] = scan_results - except Exception: - pass - return {'station': station_data} if station_data else {} @@ -207,128 +196,3 @@ def wifi(ifname): return result -def parse_wpa_scan_result(scan_output): - """Parse wpa_cli scan_result output""" - networks = {} - - for line in scan_output.strip().split('\n'): - try: - line = line.strip() - if not line or 'bssid / frequency' in line.lower(): - continue - - # Split by tabs or multiple spaces - parts = re.split(r'\t+|\s{2,}', line) - - if len(parts) >= 5: - bssid = parts[0].strip() - try: - frequency = int(parts[1].strip()) - rssi = int(parts[2].strip()) - except ValueError: - continue - - flags = parts[3].strip() - ssid = decode_wpa_ssid(parts[4].strip() if len(parts) > 4 else "") - - # Skip hidden SSIDs (empty or null-filled) - if not ssid or ssid.isspace(): - continue - - encryption = extract_encryption(flags) - channel = frequency_to_channel(frequency) - - # Keep best signal per SSID - if ssid not in networks or rssi > networks[ssid]['signal-strength']: - networks[ssid] = { - 'bssid': bssid, - 'ssid': ssid, - 'signal-strength': rssi, - 'encryption': encryption, - 'channel': channel - } - except Exception: - continue - - # Sort by signal strength (best first) - result = list(networks.values()) - result.sort(key=lambda x: x['signal-strength'], reverse=True) - - return result - - -def frequency_to_channel(frequency): - """Convert frequency (MHz) to WiFi channel number""" - freq = int(frequency) - - # 2.4 GHz band (channels 1-14) - if 2412 <= freq <= 2484: - if freq == 2484: - return 14 - return (freq - 2412) // 5 + 1 - - # 5 GHz band (channels 36-165) - elif 5170 <= freq <= 5825: - return (freq - 5000) // 5 - - # 6 GHz band (channels 1-233) - elif 5955 <= freq <= 7115: - return (freq - 5950) // 5 - - return 0 - - -def extract_encryption(flags): - """Extract encryption information from flags string""" - flags = flags.upper() - encryption_info = { - 'protocols': [], - 'key_mgmt': [], - 'auth_type': 'Unknown' - } - - # 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 rsn and ('PSK' in flags or 'EAP' in flags): - encryption_info['protocols'].append('WPA2') - if 'WPA-' in flags and not rsn: - encryption_info['protocols'].append('WPA') - - # Extract key management methods - if 'PSK' in flags: - encryption_info['key_mgmt'].append('PSK') - encryption_info['auth_type'] = 'Personal' - if 'EAP' in flags: - encryption_info['key_mgmt'].append('EAP') - encryption_info['auth_type'] = 'Enterprise' - if 'SAE' in flags: - encryption_info['key_mgmt'].append('SAE') - encryption_info['auth_type'] = 'Personal' - if 'OWE' in flags: - encryption_info['key_mgmt'].append('OWE') - encryption_info['auth_type'] = 'Enhanced Open' - - # Handle special cases - if 'WEP' in flags: - return ['WEP'] - - if not encryption_info['protocols'] and 'ESS' in flags: - return ['Open'] - - # Return protocols with auth type - result = [] - for protocol in encryption_info['protocols']: - if encryption_info['auth_type'] == 'Enterprise': - result.append(f"{protocol}-Enterprise") - elif encryption_info['auth_type'] == 'Personal': - result.append(f"{protocol}-Personal") - elif encryption_info['auth_type'] == 'Enhanced Open': - result.append(f"{protocol}-Enhanced-Open") - else: - result.append(protocol) - - return result if result else ['Unknown'] diff --git a/src/test-mode/src/test-mode.c b/src/test-mode/src/test-mode.c index 1db989b21b..39629ac0b7 100644 --- a/src/test-mode/src/test-mode.c +++ b/src/test-mode/src/test-mode.c @@ -19,6 +19,7 @@ sr_subscription_ctx_t *sub = NULL; sr_conn_ctx_t *conn; #define TEST_CONFIG_PATH "/etc/test-config.cfg" +#define DNSMASQ_LEASES "/var/lib/misc/dnsmasq.leases" static int test_reset(sr_session_ctx_t *session, uint32_t sub_id, const char *path, const sr_val_t *input, const size_t input_cnt, @@ -55,6 +56,9 @@ static int test_reset(sr_session_ctx_t *session, uint32_t sub_id, const char *pa ERROR("Failed to replace configuration: %s", sr_strerror(rc)); goto release_ctx; } + + /* The DHCP server leases survive reconfiguration on purpose */ + erase(DNSMASQ_LEASES); release_ctx: sr_release_context(conn); return rc; diff --git a/src/webui/README.md b/src/webui/README.md index b4df0146f9..ed7aeb2724 100644 --- a/src/webui/README.md +++ b/src/webui/README.md @@ -13,7 +13,7 @@ a browser-friendly format. with bridge member grouping - **Interfaces** -- list with status, addresses, and per-type detail; click through to a detail page with live-updating counters, WiFi - station and mesh peer tables, scan results, WireGuard peers, and + station and mesh peer tables, WireGuard peers, and ethernet frame statistics - **Firewall** -- zone-to-zone policy matrix - **Keystore** -- symmetric and asymmetric key display diff --git a/src/webui/internal/handlers/interfaces.go b/src/webui/internal/handlers/interfaces.go index 2a6c06b0da..391892e559 100644 --- a/src/webui/internal/handlers/interfaces.go +++ b/src/webui/internal/handlers/interfaces.go @@ -179,14 +179,17 @@ type wifiStationJSON struct { SignalStrength *int `json:"signal-strength"` RxSpeed yangInt64 `json:"rx-speed"` TxSpeed yangInt64 `json:"tx-speed"` - ScanResults []wifiScanResultJSON `json:"scan-results"` } -type wifiScanResultJSON struct { - SSID string `json:"ssid"` +// WiFi network scan, the output of the infix-hardware scan action on a +// radio: one entry per access point heard. + +type wifiNetworkJSON struct { BSSID string `json:"bssid"` - SignalStrength *int `json:"signal-strength"` + SSID string `json:"ssid"` + Frequency int `json:"frequency"` Channel int `json:"channel"` + SignalStrength *int `json:"signal-strength"` Encryption []string `json:"encryption"` } @@ -631,7 +634,6 @@ type ifaceDetailData struct { EthFrameStats []kvEntry WGPeers []wgPeerEntry WiFiStations []wifiStaEntry - ScanResults []wifiScanEntry Desc map[string]string } @@ -678,15 +680,6 @@ type wifiStaEntry struct { TxSpeed string } -type wifiScanEntry struct { - SSID string - BSSID string - Signal string - SignalCSS string - Channel string - Encryption string -} - // fetchInterface retrieves a single interface by name from RESTCONF. func (h *InterfacesHandler) fetchInterface(r *http.Request, name string) (*ifaceJSON, error) { var all interfacesWrapper @@ -807,9 +800,6 @@ func buildDetailData(r *http.Request, iface *ifaceJSON) ifaceDetailData { if st.TxSpeed > 0 { d.WiFiTxSpeed = fmt.Sprintf("%.1f Mbps", float64(st.TxSpeed)/10) } - for _, sr := range st.ScanResults { - d.ScanResults = append(d.ScanResults, buildWifiScanEntry(sr)) - } } } @@ -1018,24 +1008,6 @@ func buildWifiStaEntry(s wifiStaJSON) wifiStaEntry { return e } -func buildWifiScanEntry(sr wifiScanResultJSON) wifiScanEntry { - e := wifiScanEntry{ - SSID: sr.SSID, - BSSID: sr.BSSID, - Channel: fmt.Sprintf("%d", sr.Channel), - } - if len(sr.Encryption) > 0 { - e.Encryption = strings.Join(sr.Encryption, ", ") - } else { - e.Encryption = "Open" - } - if sr.SignalStrength != nil { - e.Signal = fmt.Sprintf("%d dBm", *sr.SignalStrength) - e.SignalCSS = wifiSignalCSS(*sr.SignalStrength) - } - return e -} - // formatLastChange renders a YANG date-and-time as local time with its age. func formatLastChange(s string) string { if s == "" { diff --git a/src/webui/internal/handlers/wifi.go b/src/webui/internal/handlers/wifi.go index e2aba10724..3a2e497c4a 100644 --- a/src/webui/internal/handlers/wifi.go +++ b/src/webui/internal/handlers/wifi.go @@ -73,6 +73,7 @@ type WiFiRadio struct { Bands []WiFiBand Interfaces []WiFiInterface Survey wifiSurveyData + Scan wifiScanData } type WiFiBand struct { @@ -100,7 +101,6 @@ type WiFiInterface struct { SignalCSS string RxSpeed string TxSpeed string - ScanResults []WiFiScan // Mesh mode Forwarding string } @@ -117,7 +117,7 @@ type WiFiClient struct { TxSpeed string } -// WiFiScan is the template data for a scan result entry. +// WiFiScan is the template data for one network heard in a scan. type WiFiScan struct { SSID string BSSID string @@ -238,6 +238,53 @@ func (h *WiFiHandler) Survey(w http.ResponseWriter, r *http.Request) { } } +// wifiScanData is the template data for the network scan fragment of one +// radio: the networks heard after a scan, or why there are none. +type wifiScanData struct { + Radio string + Networks []WiFiScan + Scanned bool + Error string +} + +// Scan runs the scan action on a radio and renders the networks heard as +// the scan 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}/scan +func (h *WiFiHandler) Scan(w http.ResponseWriter, r *http.Request) { + data := wifiScanData{Radio: r.PathValue("name")} + + var reply struct { + Output struct { + Network []wifiNetworkJSON `json:"network"` + } `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/scan" + if err := h.RC.CallRPC(ctx, path, nil, &reply); err != nil { + log.Printf("wifi: scan %s: %v", data.Radio, err) + data.Error = "Scan failed" + var re *restconf.Error + if errors.As(err, &re) && re.Message != "" { + data.Error = re.Message + } + } else { + data.Scanned = true + for _, n := range reply.Output.Network { + data.Networks = append(data.Networks, buildWiFiScan(n)) + } + } + + if err := h.Template.ExecuteTemplate(w, "wifi-scan", 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 { @@ -258,6 +305,7 @@ func buildWiFiRadios(components []hwComponentWiFiJSON, ifaces []ifaceJSON) []WiF Channel: wifiChannelString(r.Channel), Manufacturer: c.MfgName, Survey: wifiSurveyData{Radio: c.Name}, + Scan: wifiScanData{Radio: c.Name}, } // Capability flags: check per-band capabilities; if any band supports @@ -363,9 +411,6 @@ func buildWiFiInterfaces(radioName string, ifaces []ifaceJSON) []WiFiInterface { if st.TxSpeed > 0 { wi.TxSpeed = fmt.Sprintf("%.1f Mbps", float64(st.TxSpeed)/10) } - for _, sr := range st.ScanResults { - wi.ScanResults = append(wi.ScanResults, buildWiFiScan(sr)) - } } result = append(result, wi) @@ -392,8 +437,8 @@ func buildWiFiClient(s wifiStaJSON) WiFiClient { return c } -// buildWiFiScan converts a wifiScanResultJSON to a WiFiScan template entry. -func buildWiFiScan(sr wifiScanResultJSON) WiFiScan { +// buildWiFiScan converts a network from the scan action to a WiFiScan entry. +func buildWiFiScan(sr wifiNetworkJSON) WiFiScan { enc := "Open" if len(sr.Encryption) > 0 { parts := make([]string, 0, len(sr.Encryption)) diff --git a/src/webui/internal/handlers/wifi_scan_test.go b/src/webui/internal/handlers/wifi_scan_test.go new file mode 100644 index 0000000000..a7af5c7104 --- /dev/null +++ b/src/webui/internal/handlers/wifi_scan_test.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT + +package handlers + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "infix/webui/internal/restconf" +) + +const scanActionPath = "/data/ietf-hardware:hardware/component=radio0/infix-hardware:wifi-radio/scan" + +// fakeScanAction serves the scan action on radio0 with reply. +func fakeScanAction(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 != scanActionPath || 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 scanRequest(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/scan", nil) + req.SetPathValue("name", "radio0") + req = req.WithContext(restconf.ContextWithCredentials(req.Context(), + restconf.Credentials{Username: "admin", Password: "secret"})) + rec := httptest.NewRecorder() + h.Scan(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("status %d, want 200", rec.Code) + } + return rec.Body.String() +} + +func TestWiFiScanListsNetworks(t *testing.T) { + reply := `{"infix-hardware:output":{"network":[ + {"bssid":"b4:fb:e4:17:b6:a7","ssid":"MyNetwork","frequency":2437,"channel":6, + "signal-strength":-45,"encryption":["WPA2-Personal"]}, + {"bssid":"0a:a0:85:00:01:00","frequency":5180,"channel":36,"signal-strength":-70, + "encryption":["WPA3-Personal","WPA2-Personal"]}]}}` + out := scanRequest(t, fakeScanAction(t, reply, http.StatusOK)) + + for _, want := range []string{"MyNetwork", "b4:fb:e4:17:b6:a7", "-45 dBm", "WPA2-Personal", + "Hidden", "WPA3-Personal, WPA2-Personal", "Scan again", `hx-post="/wifi/radio0/scan"`} { + 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 TestWiFiScanShowsDeviceError(t *testing.T) { + reply := `{"ietf-restconf:errors":{"error":[{"error-type":"application", + "error-tag":"operation-failed", + "error-message":"Scan failed: no interface on radio0 to scan with"}]}}` + out := scanRequest(t, fakeScanAction(t, reply, http.StatusInternalServerError)) + + for _, want := range []string{"alert-error", "no interface on radio0", "Scan networks"} { + if !strings.Contains(out, want) { + t.Errorf("missing %q in\n%s", want, out) + } + } + if strings.Contains(out, "data-table") { + t.Errorf("table rendered on error:\n%s", out) + } +} + +func TestWiFiPageOffersNetworkScan(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-scan-radio0"`, `hx-post="/wifi/radio0/scan"`, "Scan networks"} { + if !strings.Contains(out, want) { + t.Errorf("missing %q in\n%s", want, out) + } + } +} diff --git a/src/webui/internal/server/server.go b/src/webui/internal/server/server.go index 02c951de6a..f05b62b733 100644 --- a/src/webui/internal/server/server.go +++ b/src/webui/internal/server/server.go @@ -309,6 +309,7 @@ func New( mux.HandleFunc("GET /routing", routing.Overview) mux.HandleFunc("GET /wifi", wifi.Overview) mux.HandleFunc("POST /wifi/{name}/survey", wifi.Survey) + mux.HandleFunc("POST /wifi/{name}/scan", wifi.Scan) 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/configure-interfaces.html b/src/webui/templates/pages/configure-interfaces.html index 0522822cf7..ead83fdc53 100644 --- a/src/webui/templates/pages/configure-interfaces.html +++ b/src/webui/templates/pages/configure-interfaces.html @@ -488,9 +488,9 @@

WiFi

Mesh Point (802.11s) - {{/* An interface with no mode configured is in scan-only - mode; it shows no mode-specific field until one of - the mode radios is picked. */}} + {{/* An interface with no mode configured yet shows no + mode-specific field until one of the mode radios + is picked. */}} {{$named := or (eq .WifiMode "station") (eq .WifiMode "access-point")}} SSID{{template "field-info" (index $d "wifi-ssid")}} diff --git a/src/webui/templates/pages/iface-detail.html b/src/webui/templates/pages/iface-detail.html index fb3a3814b4..e42315bb0a 100644 --- a/src/webui/templates/pages/iface-detail.html +++ b/src/webui/templates/pages/iface-detail.html @@ -118,36 +118,6 @@

Addresses

{{end}} -{{if .ScanResults}} -
-
Scan Results
-
- - - - - - - - - - - - {{range .ScanResults}} - - - - - - - - {{end}} - -
SSIDBSSIDSignalChannelSecurity
{{if .SSID}}{{.SSID}}{{else}}Hidden{{end}}{{.BSSID}}{{if .Signal}}{{.Signal}}{{end}}{{.Channel}}{{.Encryption}}
-
-
-{{end}} - {{if .WGPeers}}
WireGuard Peers
diff --git a/src/webui/templates/pages/wifi.html b/src/webui/templates/pages/wifi.html index a97527ac56..ab00dae48c 100644 --- a/src/webui/templates/pages/wifi.html +++ b/src/webui/templates/pages/wifi.html @@ -55,6 +55,13 @@ {{template "wifi-survey" .Survey}}
+ +
+
Networks
+
+ {{template "wifi-scan" .Scan}} +
+
{{range .Interfaces}} @@ -121,33 +128,6 @@ {{else if .ClientsEmpty}}

{{.ClientsEmpty}}

{{end}} - - {{if and (eq .Mode "station") .ScanResults}} -
- - - - - - - - - - - - {{range .ScanResults}} - - - - - - - - {{end}} - -
SSIDBSSIDSignalChannelSecurity
{{if .SSID}}{{.SSID}}{{else}}Hidden{{end}}{{.BSSID}}{{if .Signal}}{{.Signal}}{{else}}—{{end}}{{.Channel}}{{.Encryption}}
-
- {{end}} {{end}} {{end}} @@ -189,3 +169,48 @@

Channel Survey < hx-disabled-elt="this">{{if .SVG}}Scan again{{else}}Scan channels{{end}} {{end}} + +{{define "wifi-scan"}} +{{if .Networks}} +
+ + + + + + + + + + + + {{range .Networks}} + + + + + + + + {{end}} + +
SSIDBSSIDSignalChannelSecurity
{{if .SSID}}{{.SSID}}{{else}}Hidden{{end}}{{.BSSID}}{{if .Signal}}{{.Signal}}{{else}}—{{end}}{{.Channel}}{{.Encryption}}
+
+{{else if .Error}} +
{{.Error}}
+{{else if .Scanned}} +

No networks heard.

+{{else}} +
+

Lists the networks in range, with signal strength and security. + The radio leaves its channel for a few seconds, so traffic pauses during the scan.

+
+{{end}} +
+ +
+{{end}} diff --git a/test/case/interfaces/wifi_band_steering/test.adoc b/test/case/interfaces/wifi_band_steering/test.adoc index 5f0242b122..a00d7abea6 100644 --- a/test/case/interfaces/wifi_band_steering/test.adoc +++ b/test/case/interfaces/wifi_band_steering/test.adoc @@ -25,7 +25,14 @@ still answered and works normally. See doc/wifi.md. The test asserts the client associates to "campus", that band steering lands it on the 5GHz BSS (not the 2.4GHz one), and that it leases an address -- a completed association plus DHCP lease is real bidirectional traffic over the -(relayed) radio link. +(relayed) radio link. A client that got in on 2.4GHz before the 5GHz BSS was +up is asked to move once it scans and the 5GHz BSS has seen it; real clients +scan in the background on their own, this one is told to. With 802.11r on +both BSSes, a client steered from 2.4GHz to 5GHz moves by a fast transition: +the 5GHz BSS fetches the client's key from its 2.4GHz sibling, so the client +is neither rejected nor disconnected on the way. That is checked with the +5GHz BSS stopped and started in hostapd alone; a configuration change +restarts hostapd and with it every BSS, so the client then logs in afresh. Topology: .... @@ -45,6 +52,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 +. Stop the 5GHz BSS in hostapd +. Verify the client falls back to the 2.4GHz BSS and logs in there +. Start the 5GHz BSS in hostapd again +. Verify a scanning client is steered to the 5GHz BSS by a fast transition . Disable the 5GHz BSS . Verify the client falls back to the 2.4GHz BSS . Enable the 5GHz BSS again diff --git a/test/case/interfaces/wifi_band_steering/test.py b/test/case/interfaces/wifi_band_steering/test.py index 1cbc01a521..e69aafd157 100755 --- a/test/case/interfaces/wifi_band_steering/test.py +++ b/test/case/interfaces/wifi_band_steering/test.py @@ -23,7 +23,14 @@ The test asserts the client associates to "campus", that band steering lands it on the 5GHz BSS (not the 2.4GHz one), and that it leases an address -- a completed association plus DHCP lease is real bidirectional traffic over the -(relayed) radio link. +(relayed) radio link. A client that got in on 2.4GHz before the 5GHz BSS was +up is asked to move once it scans and the 5GHz BSS has seen it; real clients +scan in the background on their own, this one is told to. With 802.11r on +both BSSes, a client steered from 2.4GHz to 5GHz moves by a fast transition: +the 5GHz BSS fetches the client's key from its 2.4GHz sibling, so the client +is neither rejected nor disconnected on the way. That is checked with the +5GHz BSS stopped and started in hostapd alone; a configuration change +restarts hostapd and with it every BSS, so the client then logs in afresh. Topology: .... @@ -48,6 +55,14 @@ POOL_START = "192.168.30.100" POOL_END = "192.168.30.101" +def steered(ap, client, client_mac): + """True once the client is on the 5GHz BSS, else tell it to scan.""" + if client_mac in wifi.ap_stations(ap, "wifi1"): + return True + wifi.scan(client, "radio2") + return False + + def ap_bss(name, radio_name): """A band-steering AP BSS on the given radio, bridged into br0.""" return wifi.iface(name, { @@ -57,7 +72,8 @@ def ap_bss(name, radio_name): "security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"}, # dot11v enables MBO band steering by default; the two BSSes # share the SSID so band steering has another band to point at. - "roaming": {"dot11v": {}}, + # dot11r makes the move between them a fast transition. + "roaming": {"dot11v": {}, "dot11r": {"mobility-domain": "hash"}}, }, }, bridge="br0") @@ -65,9 +81,10 @@ def ap_bss(name, radio_name): with infamy.Test() as test: with test.step("Set up topology and attach to the ap and the client"): env = infamy.Env() - ap, client, clientsh = parallel( + ap, client, apsh, clientsh = parallel( lambda: env.attach("ap", "mgmt"), lambda: env.attach("client", "mgmt"), + lambda: env.attach("ap", "mgmt", "ssh"), lambda: env.attach("client", "mgmt", "ssh"), ) wifi.skip_unless_supported(test, ap, client) @@ -118,9 +135,8 @@ def ap_bss(name, radio_name): with test.step("Verify band steering put the client on the 5GHz BSS"): # Steered: the client shows up on the 5GHz BSS (wifi1) and not on the # 2.4GHz BSS (wifi0) -- the 2.4GHz BSS suppressed its probe responses - # once it had been seen on 5GHz. - until(lambda: client_mac in wifi.ap_stations(ap, "wifi1"), - attempts=90, interval=2) + # once it had been seen on 5GHz, or asked the client to move there. + until(lambda: steered(ap, client, client_mac), attempts=20, interval=10) assert client_mac not in wifi.ap_stations(ap, "wifi0"), \ "client associated on 2.4GHz; band steering did not steer it to 5GHz" @@ -130,6 +146,35 @@ def ap_bss(name, radio_name): iface.address_exist(client, "wifi0", POOL_END), attempts=60, interval=2) + # Stopping the 5GHz BSS in hostapd, without a configuration change, + # leaves the 2.4GHz BSS running: the client falls back to it and logs + # in there, so that BSS now holds its 802.11r key. Once the 5GHz BSS + # is back the client is steered to it again, and the move is a fast + # transition only if the 5GHz BSS gets the key from its sibling. + with test.step("Stop the 5GHz BSS in hostapd"): + apsh.runsh("sudo hostapd_cli -i wifi1 disable") + + with test.step("Verify the client falls back to the 2.4GHz BSS and logs in there"): + until(lambda: client_mac in wifi.ap_stations(ap, "wifi0"), + attempts=90, interval=2) + + mark = int(clientsh.runsh("wc -l < /var/log/syslog").stdout) + + with test.step("Start the 5GHz BSS in hostapd again"): + apsh.runsh("sudo hostapd_cli -i wifi1 enable") + + with test.step("Verify a scanning client is steered to the 5GHz BSS by a fast transition"): + def client_log(): + return clientsh.runsh(f"tail -n +{mark + 1} /var/log/syslog").stdout + try: + until(lambda: steered(ap, client, client_mac), attempts=25, interval=10) + except Exception as err: + raise AssertionError(client_log()) from err + log = client_log() + assert "CTRL-EVENT-AUTH-REJECT" not in log, log + assert "CTRL-EVENT-ASSOC-REJECT" not in log, log + assert "CTRL-EVENT-DISCONNECTED" not in log, log + # 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 @@ -149,12 +194,7 @@ def ap_bss(name, radio_name): 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) + until(lambda: steered(ap, client, client_mac), attempts=20, interval=10) assert client_mac not in wifi.ap_stations(ap, "wifi0"), \ "client still associated on 2.4GHz" diff --git a/test/case/interfaces/wifi_channel_survey/test.adoc b/test/case/interfaces/wifi_channel_survey/test.adoc index 44da16e432..5e17221f9b 100644 --- a/test/case/interfaces/wifi_channel_survey/test.adoc +++ b/test/case/interfaces/wifi_channel_survey/test.adoc @@ -4,11 +4,12 @@ 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. +A scan lists the networks in range, a channel survey tells how busy each +channel is. Both mean leaving the operating channel, so neither is done +in the background but on request, with the scan and channel-survey +actions on the radio. The actions have 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: .... @@ -24,11 +25,13 @@ image::topology.svg[WiFi channel survey on a connected radio topology, align=cen . 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 +. Scan for networks on radio0 of the station +. Verify the station's scan lists the ap's SSID on 2412 MHz . 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 +. Verify the station is still associated to the ap after the scans diff --git a/test/case/interfaces/wifi_channel_survey/test.py b/test/case/interfaces/wifi_channel_survey/test.py index a6ec76ac40..321e1f20bb 100755 --- a/test/case/interfaces/wifi_channel_survey/test.py +++ b/test/case/interfaces/wifi_channel_survey/test.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 r""" -WiFi channel survey on a connected radio +WiFi scan and 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. +A scan lists the networks in range, a channel survey tells how busy each +channel is. Both mean leaving the operating channel, so neither is done +in the background but on request, with the scan and channel-survey +actions on the radio. The actions have 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: .... @@ -64,6 +65,16 @@ 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("Scan for networks on radio0 of the station"): + networks = wifi.scan(station, "radio0") + + with test.step("Verify the station's scan lists the ap's SSID on 2412 MHz"): + ap_mac = wifi.phys_address(ap, "wifi0") + found = [n for n in networks if n.get("bssid", "").lower() == ap_mac] + assert found, networks + assert found[0].get("ssid") == SSID, found + assert int(found[0].get("frequency", 0)) == FREQ, found + with test.step("Run a channel survey on radio0 of the station"): channels = wifi.channel_survey(station, "radio0") @@ -79,7 +90,7 @@ 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"): + with test.step("Verify the station is still associated to the ap after the scans"): until(lambda: wifi.associated(station, SSID), attempts=30, interval=2) test.succeed() diff --git a/test/case/statd/containers/cli/show-interfaces b/test/case/statd/containers/cli/show-interfaces index 12ed8c334b..680a805941 100644 --- a/test/case/statd/containers/cli/show-interfaces +++ b/test/case/statd/containers/cli/show-interfaces @@ -1,41 +1,41 @@ -⚑ INTERFACE PROTOCOL STATE DATA  - lo loopback UP  - ipv4 127.0.0.1/8 (static) - ipv6 ::1/128 (static) - br0 bridge  vlan: 1u pvid: 1 - │ ethernet 00:a0:85:00:03:00 - │ ipv4 169.254.1.1/16 (random) - │ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer) - ├ e3 bridge FORWARDING vlan: 1u pvid: 1 - ├ e4 bridge FORWARDING vlan: 1u pvid: 1 - ├ veth0b bridge FORWARDING vlan: 1u pvid: 1 - └ veth2b bridge FORWARDING vlan: 1u pvid: 1 - br1 bridge   - │ ethernet 00:a0:85:00:03:00 - ├ veth1b bridge FORWARDING vlan: 6u pvid: 6 - └ veth3b bridge FORWARDING  - e1 ethernet UP 00:a0:85:00:03:01 - ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer) - e2 ethernet UP 00:a0:85:00:03:02 - ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer) - e3.8 vlan UP vid: 8 - │ ipv4 10.1.1.1/32 (static) - └ e3 - e4.8 vlan UP vid: 8 - │ ipv4 10.1.1.1/32 (static) - └ e4 - e5 ethernet UP 00:a0:85:00:03:05 - ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer) - e6 ethernet UP 00:a0:85:00:03:06 - ipv4 10.1.1.101/24 (static) - ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer) - e7 ethernet UP 00:a0:85:00:03:07 - ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer) - veth0a container container-A  - veth0b veth UP e6:19:75:86:80:dd - veth1a container container-A  - veth1b veth UP 3e:67:0f:15:24:14 - veth2a container container-B  - veth2b veth UP b2:fa:30:63:98:f0 - veth3a container container-B  - veth3b veth UP b2:1c:fa:a7:45:eb +⚑ INTERFACE PROTOCOL STATE DATA  + lo loopback UP + ipv4 127.0.0.1/8 (static) + ipv6 ::1/128 (static) + br0 bridge  vlan: 1u pvid: 1 + │ ethernet 00:a0:85:00:03:00 + │ ipv4 169.254.1.1/16 (random) + │ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer) + ├ e3 bridge FORWARDING vlan: 1u pvid: 1 + ├ e4 bridge FORWARDING vlan: 1u pvid: 1 + ├ veth0b bridge FORWARDING vlan: 1u pvid: 1 + └ veth2b bridge FORWARDING vlan: 1u pvid: 1 + br1 bridge  + │ ethernet 00:a0:85:00:03:00 + ├ veth1b bridge FORWARDING vlan: 6u pvid: 6 + └ veth3b bridge FORWARDING + e1 ethernet UP 00:a0:85:00:03:01 + ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer) + e2 ethernet UP 00:a0:85:00:03:02 + ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer) + e3.8 vlan UP vid: 8 + │ ipv4 10.1.1.1/32 (static) + └ e3 + e4.8 vlan UP vid: 8 + │ ipv4 10.1.1.1/32 (static) + └ e4 + e5 ethernet UP 00:a0:85:00:03:05 + ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer) + e6 ethernet UP 00:a0:85:00:03:06 + ipv4 10.1.1.101/24 (static) + ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer) + e7 ethernet UP 00:a0:85:00:03:07 + ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer) + veth0a container container-A  + veth0b veth UP e6:19:75:86:80:dd + veth1a container container-A  + veth1b veth UP 3e:67:0f:15:24:14 + veth2a container container-B  + veth2b veth UP b2:fa:30:63:98:f0 + veth3a container container-B  + veth3b veth UP b2:1c:fa:a7:45:eb diff --git a/test/case/statd/interfaces-all/cli/show-interfaces b/test/case/statd/interfaces-all/cli/show-interfaces index e992c2e304..7959a21815 100644 --- a/test/case/statd/interfaces-all/cli/show-interfaces +++ b/test/case/statd/interfaces-all/cli/show-interfaces @@ -1,55 +1,55 @@ -⚑ INTERFACE PROTOCOL STATE DATA  - lo loopback UP  - ipv4 127.0.0.1/8 (static) - ipv6 ::1/128 (static) - br-0 bridge DOWN  - ethernet 00:a0:85:00:03:00 - br-D bridge   - │ ethernet 00:a0:85:00:03:00 - │ ipv4 10.0.0.1/8 (static) - │ ipv4 192.168.20.1/24 (static) - │ ipv6 2001:db8::1/64 (static) - │ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer) - └ veth0a.20 bridge FORWARDING  - br-Q bridge  vlan: 20u,30u,40t - │ ethernet 00:a0:85:00:03:00 - ├ e3 bridge FORWARDING vlan: 20t,30t,40t - └ veth0b bridge FORWARDING vlan: 20t,30t,40t - br-Q.40 vlan UP vid: 40 - └ br-Q - br-X bridge   - │ ethernet 00:a0:85:00:03:00 - └ e2.30 bridge FORWARDING  - e1 ethernet UP 00:a0:85:00:03:01 - ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer) - e2 ethernet UP 00:a0:85:00:03:02 - ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer) - e2.30 vlan UP vid: 30 - └ e2 - e3.10 vlan UP vid: 10 - └ e3 - e4 ethernet UP 00:a0:85:00:03:04 - ipv6 fe80::2a0:85ff:fe00:304/64 (link-layer) - e5 ethernet UP 00:a0:85:00:03:05 - ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer) - e6 ethernet UP 00:a0:85:00:03:06 - ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer) - e7 ethernet UP 00:a0:85:00:03:07 - ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer) - gre-v4 gre UP remote: 192.168.20.2 - gre-v6 gre UP remote: 2001:db8::2 - ipv4 192.168.50.2/16 (static) - gretap-v4 gre UP remote: 192.168.20.2 - ethernet f6:d4:96:d8:9f:96 - gretap-v6 gre UP remote: 2001:db8::2 - ethernet 7a:46:13:bd:fa:e6 - veth0a veth UP 6e:d0:98:c4:e7:ef - veth0a.20 vlan UP vid: 20 - └ veth0a - veth0b veth UP 36:da:80:06:7f:99 - vxlan-v4 vxlan UP vni: 4 remote: 192.168.20.200 - ethernet 8a:ea:59:32:df:70 - ipv4 192.168.30.2/24 (static) - vxlan-v6 vxlan UP vni: 6 remote: 2001:db8::200 - ethernet 3e:30:c6:a1:71:64 - ipv4 192.168.40.2/24 (static) +⚑ INTERFACE PROTOCOL STATE DATA  + lo loopback UP + ipv4 127.0.0.1/8 (static) + ipv6 ::1/128 (static) + br-0 bridge DOWN + ethernet 00:a0:85:00:03:00 + br-D bridge  + │ ethernet 00:a0:85:00:03:00 + │ ipv4 10.0.0.1/8 (static) + │ ipv4 192.168.20.1/24 (static) + │ ipv6 2001:db8::1/64 (static) + │ ipv6 fe80::2a0:85ff:fe00:300/64 (link-layer) + └ veth0a.20 bridge FORWARDING + br-Q bridge  vlan: 20u,30u,40t + │ ethernet 00:a0:85:00:03:00 + ├ e3 bridge FORWARDING vlan: 20t,30t,40t + └ veth0b bridge FORWARDING vlan: 20t,30t,40t + br-Q.40 vlan UP vid: 40 + └ br-Q + br-X bridge  + │ ethernet 00:a0:85:00:03:00 + └ e2.30 bridge FORWARDING + e1 ethernet UP 00:a0:85:00:03:01 + ipv6 fe80::2a0:85ff:fe00:301/64 (link-layer) + e2 ethernet UP 00:a0:85:00:03:02 + ipv6 fe80::2a0:85ff:fe00:302/64 (link-layer) + e2.30 vlan UP vid: 30 + └ e2 + e3.10 vlan UP vid: 10 + └ e3 + e4 ethernet UP 00:a0:85:00:03:04 + ipv6 fe80::2a0:85ff:fe00:304/64 (link-layer) + e5 ethernet UP 00:a0:85:00:03:05 + ipv6 fe80::2a0:85ff:fe00:305/64 (link-layer) + e6 ethernet UP 00:a0:85:00:03:06 + ipv6 fe80::2a0:85ff:fe00:306/64 (link-layer) + e7 ethernet UP 00:a0:85:00:03:07 + ipv6 fe80::2a0:85ff:fe00:307/64 (link-layer) + gre-v4 gre UP remote: 192.168.20.2 + gre-v6 gre UP remote: 2001:db8::2 + ipv4 192.168.50.2/16 (static) + gretap-v4 gre UP remote: 192.168.20.2 + ethernet f6:d4:96:d8:9f:96 + gretap-v6 gre UP remote: 2001:db8::2 + ethernet 7a:46:13:bd:fa:e6 + veth0a veth UP 6e:d0:98:c4:e7:ef + veth0a.20 vlan UP vid: 20 + └ veth0a + veth0b veth UP 36:da:80:06:7f:99 + vxlan-v4 vxlan UP vni: 4 remote: 192.168.20.200 + ethernet 8a:ea:59:32:df:70 + ipv4 192.168.30.2/24 (static) + vxlan-v6 vxlan UP vni: 6 remote: 2001:db8::200 + ethernet 3e:30:c6:a1:71:64 + ipv4 192.168.40.2/24 (static) diff --git a/test/infamy/netconf.py b/test/infamy/netconf.py index 566d7a62af..4caf191050 100644 --- a/test/infamy/netconf.py +++ b/test/infamy/netconf.py @@ -17,6 +17,7 @@ import netconf_client.connect import netconf_client.ncclient import netconf_client.session +import paramiko from infamy.transport import Transport,infer_put_dict from netconf_client.error import RpcError from . import env, netutil, coverage @@ -119,30 +120,34 @@ def __init__(self, self.location = location self.mapping = mapping self.location = location - self.ly = libyang.Context(yangdir) + self._ly = libyang.Context(yangdir) + self._ly_loaded = True self._ncc_init(location) # self.ncc._fetch_connection_ip() # self.ncc._debug() self.modules = {} self._ly_bootstrap(yangdir) - - del self.ly - self.ly = libyang.Context(yangdir) self._ly_init(yangdir) + @property + def ly(self): + """Loads the device's modules on first use, a skipped test never pays for it""" + if not self._ly_loaded: + self._ly_load() + return self._ly + def __str__(self): nm = f"{self.name}" if env.ENV.ltop: nm += f"({env.ENV.ltop.xlate(self.name)})" return nm + " [NETCONF]" - def _ncc_init(self, location): + def _ncc_connect(self, location): ai = socket.getaddrinfo(location.host, location.port, 0, 0, socket.SOL_TCP) sock = socket.socket(ai[0][0], ai[0][1], 0) sock.settimeout(60) - print(f"Connecting to mgmt IP {location.host}:{location.port} ...") try: sock.connect(ai[0][4]) except InterruptedError as err: @@ -153,9 +158,36 @@ def _ncc_init(self, location): raise err sock.settimeout(None) - session = netconf_client.connect.connect_ssh(sock=sock, - username=location.username, - password=location.password) + try: + return netconf_client.connect.connect_ssh(sock=sock, + username=location.username, + password=location.password) + except BaseException: + sock.close() + raise + + def _ncc_init(self, location, tries=10): + """Open the NETCONF session, retrying while the server settles + + The server accepts the TCP connection as soon as it listens, + but drops the SSH session without a hello while it is loading + or replacing its host key, which happens right after boot and + after every test reset. The ssh transport retries the same + window as exit code 255. + """ + print(f"Connecting to mgmt IP {location.host}:{location.port} ...") + for attempt in range(1, tries + 1): + try: + session = self._ncc_connect(location) + break + except (StopIteration, EOFError, ConnectionError, + paramiko.SSHException) as err: + if attempt == tries: + raise + print(f"NETCONF session setup failed ({type(err).__name__}), " + f"attempt {attempt}/{tries}") + time.sleep(1) + self.ncc = Manager(session) def _ly_bootstrap(self, yangdir): @@ -174,7 +206,7 @@ def _ly_bootstrap(self, yangdir): print("YANG models downloaded.") def _ly_init(self, yangdir): - self.ly = libyang.Context(yangdir) + self._ly = libyang.Context(yangdir) lib = self.ly.load_module("ietf-yang-library") ns = libyang.util.c2str(lib.cdata.ns) @@ -188,7 +220,10 @@ def _ly_init(self, yangdir): xml, parse_only=True).print_dict() self.modules = {m["name"]: m for m in data["modules-state"]["module"]} + self._ly_loaded = False + def _ly_load(self): + self._ly_loaded = True for ms in self.modules.values(): if ms["conformance-type"] != "implement": continue @@ -430,6 +465,8 @@ def call_action(self, xpath, input_data=None): actions that take no parameters. """ coverage.track_xpath(xpath) + if not input_data and (xml := self._plain_action_xml(xpath)): + return self.ncc.dispatch(xml) action={} pattern = r"^/(?P[^:]+):(?P[^/]+)" match = re.search(pattern, xpath) @@ -470,6 +507,19 @@ def to_dict(elem): output[key] = [value] return output + def _plain_action_xml(self, xpath): + """Action without input within one module, e.g. /infix-test:test/reset, + built without libyang so the context need not be loaded""" + match = re.fullmatch(r"/([\w.-]+):([\w.-]+)((?:/[\w.-]+)+)", xpath) + ns = match and self.modules.get(match[1], {}).get("namespace") + if not ns: + return None + names = match[3].split("/")[1:] + inner = "".join(f"<{n}>" for n in names[:-1]) + f"<{names[-1]}/>" + \ + "".join(f"" for n in reversed(names[:-1])) + return (f'' + f'<{match[2]} xmlns="{ns}">{inner}') + 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 0df0489008..dfef2866f2 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -142,9 +142,17 @@ def __init__(self, self.auth = HTTPBasicAuth(location.username, location.password) self.modules = {} - self.lyctx = libyang.Context(yangdir) + self._lyctx = libyang.Context(yangdir) + self._ly_loaded = True self._ly_bootstrap(yangdir) - self._ly_init(yangdir) + self._ly_loaded = False + + @property + def lyctx(self): + """Loads the device's modules on first use, a skipped test never pays for it""" + if not self._ly_loaded: + self._ly_load() + return self._lyctx def __str__(self): nm = f"{self.name}" @@ -186,7 +194,8 @@ def _ly_bootstrap(self, yangdir): print("YANG models downloaded.") - def _ly_init(self, yangdir): + def _ly_load(self): + self._ly_loaded = True for ms in self.modules.values(): if ms["conformance-type"] != "implement": continue diff --git a/test/infamy/wifi.py b/test/infamy/wifi.py index 8dcf3ea327..1161f0ad22 100644 --- a/test/infamy/wifi.py +++ b/test/infamy/wifi.py @@ -98,6 +98,18 @@ def channel_survey(target, radio="radio0", passive=False): return output.get("channel", []) +def scan(target, radio="radio0"): + """Run the scan action on radio, return the networks heard. + + Each entry has a bssid, a frequency and, unless hidden, an ssid. + Values are strings on NETCONF, compare with str(). + """ + xpath = f"/ietf-hardware:hardware/component[name='{radio}']" \ + "/infix-hardware:wifi-radio/scan" + output = target.call_action_output(xpath, None) + return output.get("network", []) + + def in_use_frequency(channels): """Frequency in MHz of the channel marked in-use, or None.""" for ch in channels: