diff --git a/include/download.mk b/include/download.mk index 6a3479c1ee..4c026185da 100644 --- a/include/download.mk +++ b/include/download.mk @@ -154,7 +154,17 @@ endef # $(2): "PKG_" if as in Download/ is "default", otherwise "Download/:" # $(3): shell command sequence to do the download define wrap_mirror -$(if $(if $(MIRROR),$(filter-out x,$(MIRROR_HASH))),$(SCRIPT_DIR)/download.pl "$(DL_DIR)" "$(FILE)" "$(MIRROR_HASH)" "" || ( $(3) ),$(3)) \ +$(if $(if $(MIRROR), \ + $(filter-out x,$(MIRROR_HASH))),$(SCRIPT_DIR)/download.pl "$(DL_DIR)" "$(FILE)" "$(MIRROR_HASH)" "" || \ + ( $(3) ) \ + $(if $(filter-out x,$(MIRROR_HASH)), && ( \ + file_hash="$$$$($(MKHASH) sha256 "$(DL_DIR)/$(FILE)")"; \ + [ "$$$$file_hash" = "$(MIRROR_HASH)" ] || { \ + echo "Hash mismatch for file $(FILE): expected $(MIRROR_HASH), got $$$$file_hash"; \ + false; \ + }; \ + )), + $(3)) \ $(if $(filter check,$(1)), \ $(call check_hash,$(FILE),$(MIRROR_HASH),$(2)MIRROR_$(call hash_var,$(MIRROR_MD5SUM))) \ $(call check_md5,$(MIRROR_MD5SUM),$(2)MIRROR_MD5SUM,$(2)MIRROR_HASH) \ diff --git a/package/boot/uboot-mvebu/Makefile b/package/boot/uboot-mvebu/Makefile index c0357de0d6..561963fbe7 100644 --- a/package/boot/uboot-mvebu/Makefile +++ b/package/boot/uboot-mvebu/Makefile @@ -8,10 +8,10 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/kernel.mk -PKG_VERSION:=2025.04 +PKG_VERSION:=2025.07 PKG_RELEASE:=1 -PKG_HASH:=439d3bef296effd54130be6a731c5b118be7fddd7fcc663ccbc5fb18294d8718 +PKG_HASH:=0f933f6c5a426895bf306e93e6ac53c60870e4b54cda56d95211bec99e63bec7 include $(INCLUDE_DIR)/u-boot.mk include $(INCLUDE_DIR)/package.mk diff --git a/package/boot/uboot-mvebu/patches/102-arm-mvebu-add-support-for-MikroTik-RB5009UG-S-IN.patch b/package/boot/uboot-mvebu/patches/102-arm-mvebu-add-support-for-MikroTik-RB5009UG-S-IN.patch index 950e935755..b9c56c7841 100644 --- a/package/boot/uboot-mvebu/patches/102-arm-mvebu-add-support-for-MikroTik-RB5009UG-S-IN.patch +++ b/package/boot/uboot-mvebu/patches/102-arm-mvebu-add-support-for-MikroTik-RB5009UG-S-IN.patch @@ -31,7 +31,7 @@ Signed-off-by: Robert Marko --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile -@@ -165,6 +165,7 @@ dtb-$(CONFIG_ARCH_MVEBU) += \ +@@ -172,6 +172,7 @@ dtb-$(CONFIG_ARCH_MVEBU) += \ armada-3720-uDPU.dtb \ armada-7040-db-nand.dtb \ armada-7040-db.dtb \ diff --git a/package/boot/uboot-tools/uboot-envtools/files/realtek b/package/boot/uboot-tools/uboot-envtools/files/realtek index 49921da151..9b8bf90c27 100644 --- a/package/boot/uboot-tools/uboot-envtools/files/realtek +++ b/package/boot/uboot-tools/uboot-envtools/files/realtek @@ -26,7 +26,8 @@ zyxel,gs1900-24-v1|\ zyxel,gs1900-24e|\ zyxel,gs1900-24ep|\ zyxel,gs1900-24hp-v1|\ -zyxel,gs1900-24hp-v2) +zyxel,gs1900-24hp-v2|\ +zyxel,gs1900-48) ubootenv_add_mtd "u-boot-env" "0x0" "0x400" "0x10000" ubootenv_add_sys_mtd "u-boot-env2" "0x0" "0x1000" "0x10000" ;; diff --git a/package/network/config/netifd/Makefile b/package/network/config/netifd/Makefile index 86a2713e81..2bf01dae76 100644 --- a/package/network/config/netifd/Makefile +++ b/package/network/config/netifd/Makefile @@ -5,9 +5,9 @@ PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL=$(PROJECT_GIT)/project/netifd.git -PKG_SOURCE_DATE:=2025-05-23 -PKG_SOURCE_VERSION:=7901e66c5f273bceee8981bc8a0c8b0e60945f60 -PKG_MIRROR_HASH:=8b85ec64e446ae065b1466c520b2d3aae329b6167221e425af903777278f557e +PKG_SOURCE_DATE:=2025-08-03 +PKG_SOURCE_VERSION:=c3cfd8df02af6b66c7aeceb61ffc2ac948fa008a +PKG_MIRROR_HASH:=09c4422181d1ae1ef5f4a648757693bd912418013fad2840e5b4bcd1bdf0860f PKG_MAINTAINER:=Felix Fietkau PKG_LICENSE:=GPL-2.0 diff --git a/package/network/config/netifd/files/lib/netifd/main.uc b/package/network/config/netifd/files/lib/netifd/main.uc new file mode 100644 index 0000000000..6c2e38fd6a --- /dev/null +++ b/package/network/config/netifd/files/lib/netifd/main.uc @@ -0,0 +1,80 @@ +import * as uci from "uci"; +import * as uloop from "uloop"; +import * as libubus from "ubus"; +import { access, dirname } from "fs"; + +function ex_handler(e) +{ + netifd.log(netifd.L_WARNING, `Exception: ${e}\n${e.stacktrace[0].context}\n`); +} + +uloop.guard(ex_handler); +libubus.guard(ex_handler); + +let ubus = netifd.ubus = libubus.connect(); +let wireless; + +function uci_ctx() +{ + let savedir = netifd.dummy_mode ? "./tmp" : null; + let ctx = uci.cursor(netifd.config_path, savedir, null, { + strict: false + }); + return ctx; +} + +function config_init() +{ + let ctx = uci_ctx(); + + if (wireless) + wireless.config_init(ctx); +} + +function config_start() +{ + if (wireless) + wireless.config_start(); +} + +function check_interfaces() +{ + if (wireless) + wireless.check_interfaces(); +} + +function hotplug(name, add) +{ + if (wireless) + wireless.hotplug(name, add); +} + +function ex_wrap(cb) +{ + let fn = cb; + return (...args) => { + try { + return fn(...args); + } catch (e) { + netifd.log(netifd.L_WARNING, `${e}\n${e.stacktrace[0].context}`); + } + }; +} + +netifd.cb = { + hotplug: ex_wrap(hotplug), + config_init: ex_wrap(config_init), + config_start: ex_wrap(config_start), + check_interfaces: ex_wrap(check_interfaces), +}; + +const wireless_module = dirname(sourcepath()) + "/wireless.uc"; +if (access(wireless_module, "r")) { + try { + wireless = loadfile(wireless_module)(); + } catch (e) { + netifd.log(netifd.L_WARNING, `Error loading wireless module: ${e}\n${e.stacktrace[0].context}\n`); + } +} else { + netifd.log(netifd.L_WARNING, `Wireless module not found\n`); +} diff --git a/package/network/config/netifd/files/lib/netifd/utils.uc b/package/network/config/netifd/files/lib/netifd/utils.uc new file mode 100644 index 0000000000..84db69d2fa --- /dev/null +++ b/package/network/config/netifd/files/lib/netifd/utils.uc @@ -0,0 +1,131 @@ +'use strict'; + +import { glob, basename, realpath, chdir, mkstemp } from "fs"; + +export const TYPE_ARRAY = 1; +export const TYPE_STRING = 3; +export const TYPE_INT = 5; +export const TYPE_BOOL = 7; + +export function parse_bool(val) +{ + switch (val) { + case "1": + case "true": + return true; + case "0": + case "false": + return false; + } +}; + +export function parse_array(val) +{ + if (type(val) != "array") + val = split(val, /\s+/); + return val; +}; + +function __type_parsers() +{ + let ret = []; + + ret[TYPE_ARRAY] = parse_array; + ret[TYPE_STRING] = function(val) { + return val; + }; + ret[TYPE_INT] = function(val) { + return +val; + }; + ret[TYPE_BOOL] = parse_bool; + + return ret; +} +export const type_parser = __type_parsers(); + +export function handler_load(path, cb) +{ + for (let script in glob(path + "/*.sh")) { + script = basename(script); + + let f = mkstemp(); + let prev_dir = realpath("."); + chdir(path); + system(`./${script} "" "dump" >&${f.fileno()}`); + chdir(prev_dir); + f.seek(); + while (!f.error()) { + let data = trim(f.read("line")); + try { + data = json(data); + } catch (e) { + continue; + } + + if (type(data) != "object") + continue; + + cb(script, data); + } + f.close(); + } +}; + +export function handler_attributes(data, extra, validate) +{ + let ret = { ...extra }; + for (let cur in data) { + let name_data = split(cur[0], ":", 2); + let name = name_data[0]; + ret[name] = cur[1]; + if (validate && name_data[1]) + validate[name] = name_data[1]; + } + return ret; +}; + +export function parse_attribute_list(data, spec) +{ + let ret = {}; + + for (let name, type_id in spec) { + if (!(name in data)) + continue; + + let val = data[name]; + let parser = type_parser[type_id]; + if (parser) + val = parser(val); + ret[name] = val; + } + + return ret; +}; + +export function is_equal(val1, val2) { + let t1 = type(val1); + + if (t1 != type(val2)) + return false; + + if (t1 == "array") { + if (length(val1) != length(val2)) + return false; + + for (let i = 0; i < length(val1); i++) + if (!is_equal(val1[i], val2[i])) + return false; + + return true; + } else if (t1 == "object") { + for (let key in val1) + if (!is_equal(val1[key], val2[key])) + return false; + for (let key in val2) + if (val1[key] == null) + return false; + return true; + } else { + return val1 == val2; + } +}; diff --git a/package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh b/package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh index 249c7a4596..8ef2dd9b2b 100755 --- a/package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh +++ b/package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh @@ -157,9 +157,39 @@ function config_add_mesh_params(config, data) { config_add(config, param, data[param]); } +function setup_mlo(data) { + let config = {}; + let idx = 0; + + for (let k, v in data.interfaces) { + let ifname = v.config.ifname; + if (!ifname) + ifname = 'ap-mld' + idx++; + + delete v.config.ifname; + config[ifname] = v.config; + netifd.set_vif(k, ifname); + + v.config.phy = find_phy(v.config.radio_config[0], true); + delete v.config.radio_config; + } + + let ret = ubus.call('hostapd', 'mld_set', { config }); + if (type(ret) != "object") + return netifd.setup_failed('HOSTAPD_START_FAILED'); + + netifd.add_process('/usr/sbin/hostapd', ret.pid, true, true); + netifd.set_up(); + + return 0; +} + function setup() { let data = json(ARGV[3]); + if (ARGV[2] == "#mlo") + return setup_mlo(data); + data.phy = find_phy(data.config, true); if (!data.phy) { log('Bug: PHY is undefined for device'); @@ -200,6 +230,7 @@ function setup() { } switch (mode) { + case 'link': case 'ap': has_ap = true; // fallthrough @@ -210,7 +241,8 @@ function setup() { data.config.noscan = true; validate('iface', v.config); iface.prepare(v.config, data.phy + data.phy_suffix, data.config.num_global_macaddr, data.config.macaddr_base); - netifd.set_vif(k, v.config.ifname); + if (mode != "link") + netifd.set_vif(k, v.config.ifname); break; } @@ -277,6 +309,9 @@ function setup() { function teardown() { let data = json(ARGV[3]); + if (ARGV[2] == "#mlo") + return 0; + if (!data.data?.phy) { log('Bug: PHY is undefined for device'); return 1; diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 47049f30bb..8296f01195 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -4,7 +4,7 @@ import * as libuci from 'uci'; import { md5 } from 'digest'; import * as fs from 'fs'; -import { append, append_raw, append_value, append_vars, comment, push_config, set_default, touch_file } from 'wifi.common'; +import { append, append_raw, append_value, append_vars, append_string_vars, comment, push_config, set_default, touch_file } from 'wifi.common'; import * as netifd from 'wifi.netifd'; import * as iface from 'wifi.iface'; @@ -44,17 +44,19 @@ function iface_setup(config) { config.ap_isolate = 1; append('bssid', config.macaddr); + config.ssid2 = config.ssid; + append_string_vars(config, [ 'ssid2' ]); -append_vars(config, [ + append_vars(config, [ 'ctrl_interface', 'ap_isolate', 'max_num_sta', 'ap_max_inactivity', 'airtime_bss_weight', 'airtime_bss_limit', 'airtime_sta_weight', 'bss_load_update_period', 'chan_util_avg_period', 'disassoc_low_ack', 'skip_inactivity_poll', 'ignore_broadcast_ssid', 'uapsd_advertisement_enabled', - 'utf8_ssid', 'multi_ap', 'ssid', 'tdls_prohibit', 'bridge', 'wds_sta', 'wds_bridge', + 'utf8_ssid', 'multi_ap', 'tdls_prohibit', 'bridge', 'wds_sta', 'wds_bridge', 'snoop_iface', 'vendor_elements', 'nas_identifier', 'radius_acct_interim_interval', 'ocv', 'multicast_to_unicast', 'preamble', 'wmm_enabled', 'proxy_arp', 'per_sta_vif', 'mbo', 'bss_transition', 'wnm_sleep_mode', 'wnm_sleep_mode_no_keys', 'qos_map_set', 'max_listen_int', 'dtim_period', - ]); + ]); } function iface_authentication_server(config) { @@ -102,11 +104,9 @@ function iface_auth_type(config) { config.wps_possible = 1; config.wps_state = 1; - if (config.owe_transition_ssid) - config.owe_transition_ssid = `"${config.owe_transition_ssid}"`; - + append_string_vars(config, [ 'owe_transition_ssid' ]); append_vars(config, [ - 'owe_transition_ssid', 'owe_transition_bssid', 'owe_transition_ifname', + 'owe_transition_bssid', 'owe_transition_ifname', ]); break; @@ -200,7 +200,7 @@ function iface_wps(config) { set_default(config, 'upnp_iface', config.network_bridge); if (config.multi_ap && config.multi_ap_backhaul_ssid) { - append_vars(config, [ 'multi_ap_backhaul_ssid' ]); + append_string_vars(config, [ 'multi_ap_backhaul_ssid' ]); if (length(config.multi_ap_backhaul_key) == 64) append('multi_ap_backhaul_wpa_psk', config.multi_ap_backhaul_key); else if (length(config.multi_ap_backhaul_key) > 8) @@ -488,6 +488,12 @@ export function generate(interface, data, config, vlans, stas, phy_features) { for (let raw in config.hostapd_options) append_raw(raw); + if (config.mode == 'link') { + append_raw('mld_ap=1'); + if (data.config.radio != null) + append_raw('mld_link_id=' + data.config.radio); + } + if (config.default_macaddr) append_raw('#default_macaddr'); }; diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/common.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/common.uc index 2dc11f50b9..0418fe8b76 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/common.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/common.uc @@ -32,11 +32,31 @@ export function append(key, value) { append_raw(key + '=' + value); }; +function escape_string(value) { + let chars = map(split(value, ''), (v) => ord(v)); + if (length(filter(chars, (v) => (v < 32 || v >= 128))) > 0) + return hexenc(value); + + return `"${value}"`; +} + +export function append_string(key, value) { + if (value == null) + return; + + append(key, escape_string(value)); +}; + export function append_vars(dict, keys) { for (let key in keys) append(key, dict[key]); }; +export function append_string_vars(dict, keys) { + for (let key in keys) + append_string(key, dict[key]); +}; + export function network_append_raw(value) { network_data += value + '\n'; }; @@ -57,11 +77,23 @@ export function network_append(key, value) { network_append_raw('\t' + key + '=' + value); }; +export function network_append_string(key, value) { + if (value == null) + return; + + network_append_raw('\t' + key + '=' + escape_string(value)); +}; + export function network_append_vars(dict, keys) { for (let key in keys) network_append(key, dict[key]); }; +export function network_append_string_vars(dict, keys) { + for (let key in keys) + network_append_string(key, dict[key]); +}; + export function set_default(dict, key, value) { if (dict[key] == null) dict[key] = value; diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc index cc174cda50..a31b2955eb 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc @@ -548,7 +548,7 @@ export function setup(data) { append('\n#macaddr_base', data.config.macaddr_base); for (let k, interface in data.interfaces) { - if (interface.config.mode != 'ap') + if (interface.config.mode != 'ap' && interface.config.mode != 'link') continue; interface.config.network_bridge = interface.bridge; diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc index 2942767f0b..49c6888d01 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc @@ -1,7 +1,7 @@ 'use strict'; import { append, append_raw, append_vars, network_append, network_append_raw, network_append_vars, - set_default, dump_network, flush_network } from 'wifi.common'; + network_append_string_vars, set_default, dump_network, flush_network } from 'wifi.common'; import * as netifd from 'wifi.netifd'; import * as iface from 'wifi.iface'; import * as fs from 'fs'; @@ -56,15 +56,14 @@ function setup_sta(data, config) { iface.parse_encryption(config); if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) - set_default(config, 'ieee80211w', 2); + config.ieee80211w = 2; else if (config.auth_type in [ 'psk-sae' ]) - set_default(config, 'ieee80211w', 1); + config.ieee80211w = 1; set_default(config, 'ieee80211r', 0); set_default(config, 'multi_ap', 0); set_default(config, 'default_disabled', 0); -//multiap_flag_file="${_config}.is_multiap" config.scan_ssid = 1; switch(config.mode) { @@ -157,14 +156,14 @@ function setup_sta(data, config) { config.basic_rate = ratelist(config.basic_rate); config.mcast_rate = ratestr(config.mcast_rate); - config.ssid = `"${config.ssid}"`; + network_append_string_vars(config, [ 'ssid' ]); network_append_vars(config, [ 'scan_ssid', 'noscan', 'disabled', 'multi_ap_backhaul_sta', 'ocv', 'key_mgmt', 'psk', 'sae_password', 'pairwise', 'group', 'bssid', 'proto', 'mesh_fwding', 'mesh_rssi_threshold', 'frequency', 'fixed_freq', 'disable_ht', 'disable_ht40', 'disable_vht', 'vht', 'max_oper_chwidth', - 'ht40', 'ssid', 'beacon_int', 'ieee80211w', 'basic_rate', 'mcast_rate', + 'ht40', 'beacon_int', 'ieee80211w', 'basic_rate', 'mcast_rate', 'bssid_blacklist', 'bssid_whitelist', 'erp', 'ca_cert', 'identity', 'anonymous_identity', 'client_cert', 'private_key', 'private_key_passwd', 'subject_match', 'altsubject_match', 'domain_match', 'domain_suffix_match', diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc b/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc new file mode 100644 index 0000000000..8d2ea051fe --- /dev/null +++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc @@ -0,0 +1,639 @@ +'use strict'; +import * as libubus from "ubus"; +import * as uloop from "uloop"; +import { is_equal } from "./utils.uc"; +import { access } from "fs"; + +const NOTIFY_CMD_UP = 0; +const NOTIFY_CMD_SET_DATA = 1; +const NOTIFY_CMD_PROCESS_ADD = 2; +const NOTIFY_CMD_SET_RETRY = 4; + +const DEFAULT_RETRY = 3; +const DEFAULT_SCRIPT_TIMEOUT = 30 * 1000; + +export const mlo_name = "#mlo"; + +let mlo_wdev; +let wdev_cur; +let wdev_handler = {}; +let wdev_script_task, wdev_script_timeout; +let handler_timer; + +function delete_wdev(name) +{ + delete netifd.wireless.devices[name]; + gc(); +} + +function handle_link(dev, data, up) +{ + let config = data.config; + let bridge_isolate; + let ap = false; + if (dev == data.ifname) + ap = data.type == "vlan" || + (data.type == "vif" && config.mode == "ap"); + + let dev_data = { + external: 2, + check_vlan: false, + isolate: !!config.bridge_isolate, + wireless: true, + wireless_ap: ap, + }; + + if (ap && config.multicast_to_unicast != null) + dev_data.multicast_to_unicast = config.multicast_to_unicast; + + if (data.type == "vif" && config.mode == "ap") { + dev_data.wireless_proxyarp = !!config.proxy_arp; + dev_data.wireless_isolate = !!config.isolate; + } + + if (up) + netifd.device_set(dev, dev_data); + + for (let net in config.network) + netifd.interface_handle_link({ + name: net, + ifname: dev, + vlan: config.network_vlan, + link_ext: true, + up, + }); +} + +function wdev_mlo_fixup(config) +{ + if (!mlo_wdev) + return; + + for (let name, iface in config.interfaces) { + let config = iface.config; + + if (config.mode != "link") + continue; + + let mlo_config = mlo_wdev.handler_data[iface.name]; + if (mlo_config && mlo_config.ifname) + config.ifname = mlo_config.ifname; + } +} + +function wdev_config_init(wdev) +{ + let data = wdev.data; + let config = data.config; + let interfaces = {}; + + let vif_idx = 0; + for (let vif in data.vif) { + let vlan_idx = 0, sta_idx = 0; + let vlans = {}, stas = {}; + + if (wdev.disabled_vifs[vif.name]) + continue; + for (let vlan in vif.vlan) { + let vlan_name = sprintf("%02d", ++vlan_idx); + let cur_vlan = vlans[vlan_name] = { + name: vlan.name, + config: vlan.config, + }; + + if (wdev.disabled_vifs[vif.name]) + continue; + for (let net in vlan.config.network) + if (netifd.interface_get_bridge(net, cur_vlan)) + break; + } + + for (let sta in vif.sta) { + let sta_name = sprintf("%02d", ++sta_idx); + stas[sta_name] = { + name: sta.name, + config: sta.config, + }; + } + + let vif_name = sprintf("%02d", ++vif_idx); + let iface = interfaces[vif_name] = { + name: vif.name, + config: vif.config, + vlans, stas, + }; + + for (let net in vif.config.network) + if (netifd.interface_get_bridge(net, iface)) + break; + } + + wdev.handler_config = { + config, + interfaces, + }; + + let prev = wdev.handler_data; + wdev.handler_data = {}; + + if (prev && prev[wdev.name]) + wdev.handler_data[wdev.name] = prev[wdev.name]; +} + +function wdev_setup_cb(wdev) +{ + if (wdev.state != "setup") + return; + + if (wdev.retry > 0) + wdev.retry--; + else + wdev.retry_setup_failed = true; + + wdev.teardown(); +} + +function wdev_teardown_cb(wdev) +{ + for (let section, data in wdev.handler_data) { + if (data.ifname) + handle_link(data.ifname, data, false); + } + + wdev.handler_data = {}; + wdev.state = "down"; + + if (wdev.delete) { + delete_wdev(wdev.data.name); + return; + } + + wdev.setup(); +} + +function run_handler_cb(wdev, cb) +{ + if (wdev != wdev_cur.wdev) + return; + + wdev.dbg("complete " + wdev_cur.op); + if (wdev_script_timeout) + wdev_script_timeout.cancel(); + wdev_script_timeout = null; + wdev_script_task = null; + wdev_cur = null; + handler_timer.set(1); + cb(wdev); +} + +function run_handler_timeout(wdev, cb) +{ + wdev_script_task.cancel(); + run_handler_cb(wdev, cb); +} + +function handler_sort_fn(a, b) +{ + return wdev_handler[a].time - wdev_handler[b].time +} + +function __run_next_handler_name() +{ + if (wdev_handler[mlo_name]) + return mlo_name; + + return sort(keys(wdev_handler), handler_sort_fn)[0]; +} + +function __run_next_handler() +{ + let name = __run_next_handler_name(); + if (!name) + return; + + wdev_cur = wdev_handler[name]; + delete wdev_handler[name]; + + let wdev = wdev_cur.wdev; + let op = wdev_cur.op; + let cb = wdev_cur.cb; + + wdev.dbg("run " + op); + if (name != mlo_name) + wdev_mlo_fixup(wdev.handler_config); + wdev.handler_config.data = wdev.handler_data[wdev.name]; + wdev_script_task = netifd.process({ + cb: () => run_handler_cb(wdev, cb), + dir: netifd.wireless.path, + argv: [ './' + wdev.script, wdev.data.config.type, op, wdev.name, "" + wdev.handler_config ], + log_prefix: wdev.name, + }); + + if (!wdev_script_task) + return run_handler_cb(wdev, cb); + + wdev_script_timeout = uloop.timer(DEFAULT_SCRIPT_TIMEOUT, + () => run_handler_timeout(wdev, cb) + ); +} + +function run_next_handler() +{ + while (!wdev_cur && length(wdev_handler) > 0) + __run_next_handler(); +} + +function run_handler(wdev, op, cb) +{ + wdev.dbg("queue " + op); + wdev_handler[wdev.name] = { + op, wdev, cb, + time: time() + }; + + run_next_handler(); +} + +function wdev_proc_reset(wdev) +{ + if (wdev.proc_timer) { + wdev.proc_timer.cancel(); + delete wdev.proc_timer; + } + + wdev.procs = []; +} + +function __wdev_proc_check(wdev, proc) +{ + if (netifd.process_check(proc.pid, proc.exe)) + return; + + wdev.dbg(`process ${proc.exe}(${proc.pid}) no longer active`); + wdev.teardown(); + return true; +} + +function wdev_proc_check(wdev) +{ + for (let proc in wdev.procs) + if (__wdev_proc_check(wdev, proc)) + break; +} + +function wdev_proc_add(wdev, data) +{ + if (!data.pid || !data.exe) + return; + + push(wdev.procs, data); + + if (!wdev.proc_timer) + wdev.proc_timer = uloop.interval(1000, () => wdev_proc_check(wdev)); +} + + +function setup() +{ + if (this.state != "up" && this.state != "down") + return; + + this.dbg("setup, state=" + this.state); + if (!this.autostart || this.retry_setup_failed || this.data.config.disabled) + return; + + wdev_proc_reset(this); + delete this.config_change; + this.state = "setup"; + run_handler(this, "setup", wdev_setup_cb); +} + +function teardown() +{ + delete this.cancel_setup; + + this.dbg("teardown, state=" + this.state); + if (this.state == "teardown" || this.state == "down") + return; + + wdev_proc_reset(this); + this.state = "teardown"; + run_handler(this, "teardown", wdev_teardown_cb); +} + +function wdev_update_disabled_vifs(wdev) +{ + let cache = wdev.ifindex_cache; + let prev_disabled = wdev.disabled_vifs; + let disabled = wdev.disabled_vifs = {}; + let changed; + + let vifs = []; + for (let vif in wdev.data.vif) + push(vifs, vif, ...vif.vlan); + + for (let vif in vifs) { + let enabled, ifindex; + + for (let net in vif.config.network) { + let state = netifd.interface_get_enabled(net); + if (!state) + continue; + + if (state.enabled) + enabled = true; + else if (enabled == null) + enabled = false; + if (state.ifindex) + ifindex = state.ifindex; + } + + let name = vif.name; + if (enabled == false) + disabled[wdev] = true; + else if (ifindex != cache[name]) + changed = true; + + if (ifindex) + cache[name] = ifindex; + else + delete cache[name]; + } + + if (changed || !is_equal(prev_disabled, disabled)) + wdev.config_change = true; + + return wdev.config_change; +} + +function wdev_reset(wdev) +{ + wdev.retry = DEFAULT_RETRY; + delete wdev.retry_setup_failed; +} + +function update(data) +{ + if (is_equal(this.data, data)) + return; + + if (data) { + this.data = data; + this.ifindex_cache = {}; + delete this.retry_setup_failed; + delete this.delete; + } + + wdev_reset(this); + this.config_change = true; + this.check(); +} + +function start() +{ + if (this.delete || this.data.config.disabled) + return; + + this.dbg("start, state=" + this.state); + this.autostart = true; + wdev_reset(this); + + if (this.state != "down") + return; + + if (wdev_update_disabled_vifs(this)) + wdev_config_init(this); + this.setup(); +} + +function stop() +{ + this.dbg("stop, state=" + this.state); + this.autostart = false; + + switch (this.state) { + case "setup": + this.cancel_setup = true; + break; + case "up": + this.teardown(); + break; + } +} + +function check() +{ + if (!wdev_update_disabled_vifs(this)) + return; + + wdev_config_init(this); + this.setup(); +} + +function wdev_mark_up(wdev) +{ + wdev.dbg("mark up, state=" + wdev.state); + if (wdev.state != "setup") + return; + + if (wdev.name == mlo_name) + mlo_wdev = wdev; + + if (wdev.config_change) { + wdev.setup(); + return; + } + + for (let section, data in wdev.handler_data) { + if (data.ifname) + handle_link(data.ifname, data, true); + } + wdev.state = "up"; + + return 0; +} + +function wdev_set_data(wdev, vif, vlan, data) +{ + let config = wdev.handler_config; + let cur = wdev; + let cur_type = "device"; + if (!config) + return libubus.STATUS_INVALID_ARGUMENT; + + if (vif) { + cur = vif = config.interfaces[vif]; + if (!vif) + return libubus.STATUS_NOT_FOUND; + cur_type = "vif"; + } + + if (vlan) { + if (!vif) + return libubus.STATUS_INVALID_ARGUMENT; + + cur = vlan = vif.vlans[vlan]; + if (!vlan) + return libubus.STATUS_NOT_FOUND; + + cur_type = "vlan"; + } + + wdev.handler_data[cur.name] = { + ...cur, + ...data, + type: cur_type, + config: cur.config, + }; + + return 0; +} + +function notify(req) +{ + let vif = req.args.interface; + let vlan = req.args.vlan; + let data = req.args.data; + + switch (req.args.command) { + case NOTIFY_CMD_UP: + if (vif || vlan || this.state != "setup") + return libubus.STATUS_INVALID_ARGUMENT; + + return wdev_mark_up(this); + case NOTIFY_CMD_SET_DATA: + return wdev_set_data(this, vif, vlan, data); + case NOTIFY_CMD_PROCESS_ADD: + if (this.state != "setup" && this.state != "up") + return 0; + + wdev_proc_add(this, data); + return 0; + case NOTIFY_CMD_SET_RETRY: + if (data.retry != null) + this.retry = data.retry; + else + this.retry = DEFAULT_RETRY; + return 0; + default: + return libubus.STATUS_INVALID_ARGUMENT; + } +} + +function hotplug(name, add) +{ + let dev = name; + let m = match(name, /(.+)\.sta.+/); + if (m) + name = m[1]; + + for (let section, data in this.handler_data) { + if (data.ifname != name || + data.type != "vif" && data.type != "vlan") + continue; + + handle_link(dev, data, add); + } +} + +function get_status_data(wdev, vif) +{ + let hdata = wdev.handler_data[vif.name]; + let data = { + section: vif.name, + config: vif.config + }; + if (hdata && hdata.ifname) + data.ifname = hdata.ifname; + return data; +} + +function get_status_vlans(wdev, vif) +{ + let vlans = []; + for (let vlan in vif.vlan) + push(vlans, get_status_data(wdev, vlan)); + return vlans; +} + +function get_status_stations(wdev, vif) +{ + let vlans = []; + for (let vlan in vif.sta) + push(vlans, get_status_data(wdev, vlan)); + return vlans; +} + +function status() +{ + let interfaces = []; + for (let vif in this.data.vif) { + let vlans = get_status_vlans(this, vif); + let stations = get_status_stations(this, vif); + let data = get_status_data(this, vif); + push(interfaces, { + ...data, + vlans, stations + }); + } + return { + up: this.state == "up", + pending: this.state == "setup" || this.state == "teardown", + autostart: this.autostart, + disabled: !!this.data.config.disabled, + retry_setup_failed: !!this.retry_setup_failed, + config: this.data.config, + interfaces + }; +} + +function destroy() +{ + this.dbg("destroy"); + this.autostart = false; + this.delete = true; + if (this.state != "down") { + this.stop(); + return; + } + + delete_wdev(this.data.name); +} + +function dbg(msg) +{ + netifd.log(netifd.L_DEBUG, `wireless: ${this.name}: ${msg}\n`); +} + +const wdev_proto = { + update, + destroy, + start, + stop, + setup, + status, + teardown, + check, + notify, + hotplug, + dbg, +}; + +export function new(data, script, driver) +{ + let wdev = { + name: data.name, + script, data, + procs: [], + vifs: {}, + disabled_vifs: {}, + ifindex_cache: {}, + + autostart: true, + state: "down", + }; + wdev_update_disabled_vifs(wdev); + wdev_config_init(wdev); + handler_timer = uloop.timer(1, run_next_handler); + return proto(wdev, wdev_proto); +}; diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc b/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc new file mode 100644 index 0000000000..a1ec40d79e --- /dev/null +++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc @@ -0,0 +1,454 @@ +'use strict'; + +import * as libubus from "ubus"; +import { realpath } from "fs"; +import { + handler_load, handler_attributes, + parse_attribute_list, parse_bool, parse_array, + TYPE_ARRAY, TYPE_STRING, TYPE_INT, TYPE_BOOL +} from "./utils.uc"; +import * as wdev from "./wireless-device.uc"; + +let ubus = netifd.ubus; +let wireless = netifd.wireless = { + handlers: {}, + devices: {}, + path: realpath(netifd.main_path + "/wireless"), +}; + +function update_config(new_devices) +{ + for (let name, dev in wireless.devices) + if (!new_devices[name]) + dev.destroy(); + + for (let name, dev in new_devices) { + let cur_dev = wireless.devices[name]; + if (cur_dev) { + cur_dev.update(dev); + continue; + } + + let handler = wireless.handlers[dev.config.type]; + cur_dev = wdev.new(dev, handler.script); + if (!cur_dev) + continue; + + wireless.devices[name] = cur_dev; + } +} + +function config_init(uci) +{ + let config = uci.get_all("wireless"); + + let handlers = {}; + let devices = {}; + let vifs = {}; + let mlo_device; + + let sections = { + device: {}, + iface: {}, + vlan: {}, + station: {}, + }; + let radio_idx = {}; + + for (let name, data in config) { + let type = data[".type"]; + if (parse_bool(data.disabled) && type != "wifi-device") + continue; + + if (substr(type, 0, 5) != "wifi-") + continue; + + let list = sections[substr(type, 5)]; + if (list) + list[name] = data; + + if (type == "wifi-iface" && parse_bool(data.mlo)) + mlo_device = true; + } + + if (mlo_device) { + devices[wdev.mlo_name] = { + name: wdev.mlo_name, + config: { + type: "mac80211", + }, + vif: [], + }; + handlers[wdev.mlo_name] = wireless.handlers.mac80211; + } + + for (let name, data in sections.device) { + if (!data.type) + continue; + + let handler = wireless.handlers[data.type]; + if (!handler) + continue; + + if (data.radio != null) + radio_idx[name] = +data.radio; + + let config = parse_attribute_list(data, handler.device); + devices[name] = { + name, + config, + + vif: [], + }; + handlers[name] = handler; + } + + for (let name, data in sections.iface) { + let dev_names = parse_array(data.device); + let mlo_vif = parse_bool(data.mlo); + let radios = map(dev_names, (v) => radio_idx[v]); + radios = filter(radios, (v) => v != null); + let radio_config = map(dev_names, (v) => devices[v].config); + if (mlo_vif) + dev_names = [ wdev.mlo_name, ...dev_names ]; + for (let dev_name in dev_names) { + let dev = devices[dev_name]; + if (!dev) + continue; + + let handler = handlers[dev_name]; + if (!handler) + continue; + + let config = parse_attribute_list(data, handler.iface); + if (mlo_vif) + if (dev_name == wdev.mlo_name) + config.radio_config = radio_config; + else + config.mode = "link"; + config.radios = radios; + + let vif = { + name, config, + device: dev_name, + + vlan: [], + sta: [], + }; + push(dev.vif, vif); + + vifs[name] ??= []; + push(vifs[name], vif); + } + } + + for (let name, data in sections.vlan) { + if (!data.iface || !vifs[data.iface]) + continue; + + for (let vif in vifs[data.iface]) { + let dev = devices[vif.device]; + let handler = handlers[vif.device]; + if (!dev || !handler) + continue; + + let config = parse_attribute_list(data, handler.vlan); + + let vlan = { + name, + config + }; + push(vif.vlan, vlan); + } + } + + for (let name, data in sections.station) { + if (!data.iface || !vifs[data.iface]) + continue; + + for (let vif in vifs[data.iface]) { + let dev = devices[vif.device]; + let handler = handlers[vif.device]; + if (!dev || !handler) + continue; + + let config = parse_attribute_list(data, handler.station); + + let sta = { + name, + config + }; + push(vif.sta, sta); + } + } + + let udata = ubus.call({ + object: "service", + method: "get_data", + data: { + type: "wifi-iface" + }, + }); + + for (let svcname, svc in udata) { + for (let typename, data in svc) { + for (let radio, vifs in data) { + for (let name, vif in vifs) { + let devs = vif.device; + if (type(devs) != "array") + devs = [ devs ]; + let config = vif.config; + if (!config) + continue; + for (let device in devs) { + let dev = devices[device]; + if (!dev) + continue; + + let vif_data = { + name, device, config, + vlan: [], + sta: [] + }; + if (vif.vlans) + vif_data.vlans = vif.vlans; + if (vif.stations) + vif_data.sta = vif.stations; + vifs[name] ??= []; + push(vifs[name], vif_data); + push(dev.vif, vif_data); + } + } + } + } + } + + update_config(devices); +} + +function config_start() +{ + for (let name, dev in wireless.devices) + if (dev.autostart) + dev.start(); + +} + +function check_interfaces() +{ + for (let name, dev in wireless.devices) + if (dev.autostart) + dev.check(); +} + +function hotplug(ifname, add) +{ + for (let name, dev in wireless.devices) + if (dev.autostart) + dev.hotplug(ifname, add); +} + +const network_config_attr = { + network: TYPE_ARRAY, + network_vlan: TYPE_ARRAY, + bridge_isolate: TYPE_BOOL, + isolate: TYPE_BOOL, + proxy_arp: TYPE_BOOL, + multicast_to_unicast: TYPE_BOOL, +}; + +const default_config_attr = { + device: { + disabled: TYPE_BOOL, + type: TYPE_STRING, + }, + iface: { + ...network_config_attr, + device: TYPE_STRING, + mode: TYPE_STRING, + }, + station: { + iface: TYPE_STRING, + + mac: TYPE_STRING, + key: TYPE_STRING, + vid: TYPE_STRING, + }, + vlan: { + ...network_config_attr, + iface: TYPE_STRING, + name: TYPE_STRING, + vid: TYPE_STRING, + }, +}; + +const wdev_args = { + device: "" +}; + +function wdev_call(req, cb) +{ + let dev = req.args.device; + if (dev) { + dev = wireless.devices[dev]; + if (!dev) + return libubus.STATUS_NOT_FOUND; + + return cb(dev); + } + + for (let name, dev in wireless.devices) { + if (name == wdev.mlo_name) + continue; + cb(dev); + } + + return 0; +} + +function attr_validate(attr_type, validate) +{ + if (validate) + return validate; + switch (attr_type) { + case TYPE_STRING: + return "string"; + case TYPE_ARRAY: + return "list(string)"; + case TYPE_INT: + return "uinteger"; + case TYPE_BOOL: + return "bool"; + } +} + +function get_validate_info(ret, handler) +{ + for (let kind in default_config_attr) { + let cur = ret[kind == "iface" ? "interface" : kind] = {}; + let validate = handler[kind + "_validate"]; + + for (let attr, attr_type in handler[kind]) { + let val = attr_validate(attr_type, validate[attr]); + if (val != null) + cur[attr] = val; + } + } + + return ret; +} + +const ubus_obj = { + up: { + args: wdev_args, + call: function(req) { + let mlo_dev = wireless.devices[wdev.mlo_name]; + if (mlo_dev) + mlo_dev.start(); + + return wdev_call(req, (dev) => { + dev.start(); + return 0; + }); + } + }, + down: { + args: wdev_args, + call: function(req) { + let mlo_dev = wireless.devices[wdev.mlo_name]; + if (mlo_dev) + mlo_dev.config_change = true; + + return wdev_call(req, (dev) => { + dev.stop(); + return 0; + }); + } + }, + reconf: { + args: wdev_args, + call: function(req) { + let mlo_dev = wireless.devices[wdev.mlo_name]; + if (mlo_dev) + mlo_dev.update(); + + return wdev_call(req, (dev) => { + dev.update(); + return 0; + }); + } + }, + status: { + args: wdev_args, + call: function(req) { + let ret = {}; + let err = wdev_call(req, (dev) => { + ret[dev.data.name] = dev.status(); + return 0; + }); + if (err != 0) + return err; + + return ret; + } + }, + notify: { + args: { + ...wdev_args, + command: 0, + interface: "", + vlan: "", + data: {}, + }, + call: function(req) { + let dev = req.args.device; + if (!dev) + return libubus.STATUS_INVALID_ARGUMENT; + + dev = wireless.devices[dev]; + if (!dev) + return libubus.STATUS_NOT_FOUND; + + return dev.notify(req); + } + }, + get_validate: { + args: wdev_args, + call: function(req) { + let ret = {}; + let err = wdev_call(req, (dev) => { + let dev_type = dev.data.config.type; + let cur = ret[dev.data.name] = {}; + get_validate_info(cur, wireless.handlers[dev_type]); + return 0; + }); + if (err != 0) + return err; + + return ret; + } + }, +}; + + +handler_load(wireless.path, (script, data) => { + if (!data.name) + return; + + let handler = wireless.handlers[data.name] = { + script, + }; + for (let kind, attr in default_config_attr) { + let validate = handler[kind + "_validate"] = {}; + handler[kind] = handler_attributes(data[kind], attr, validate); + } +}); + +wireless.obj = ubus.publish("network.wireless", ubus_obj); + +return { + hotplug, + config_init, + config_start, + check_interfaces, +}; diff --git a/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc b/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc index 31b526b6ae..f801c4940c 100644 --- a/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc +++ b/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc @@ -101,7 +101,9 @@ function wdev_create(phy, name, data) req["4addr"] = data["4addr"]; if (data.macaddr) req.mac = data.macaddr; - if (data.radio != null && data.radio >= 0) + if (data.radio_mask > 0) + req.vif_radio_mask = data.radio_mask; + else if (data.radio != null && data.radio >= 0) req.vif_radio_mask = 1 << data.radio; nl80211.error(); @@ -315,7 +317,7 @@ const phy_proto = { if (wdev.iftype == nl80211.const.NL80211_IFTYPE_AP_VLAN) continue; if (this.radio != null && wdev.vif_radio_mask != null && - !(wdev.vif_radio_mask & (1 << this.radio))) + wdev.vif_radio_mask != (1 << this.radio)) continue; mac_wdev[wdev.mac] = wdev; } diff --git a/package/network/services/hostapd/files/hostapd.uc b/package/network/services/hostapd/files/hostapd.uc index 3e941ae415..a28c282493 100644 --- a/package/network/services/hostapd/files/hostapd.uc +++ b/package/network/services/hostapd/files/hostapd.uc @@ -1,6 +1,6 @@ let libubus = require("ubus"); import { open, readfile } from "fs"; -import { wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open, wdev_set_radio_mask } from "common"; +import { wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open, wdev_set_radio_mask, wdev_set_up } from "common"; let ubus = libubus.connect(null, 60); @@ -50,13 +50,16 @@ hostapd.data.bss_info_fields = { owe_transition_ifname: true, }; +hostapd.data.mld = {}; + function iface_remove(cfg) { if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname) return; for (let bss in cfg.bss) - wdev_remove(bss.ifname); + if (!bss.mld_ap) + wdev_remove(bss.ifname); } function iface_gen_config(config, start_disabled) @@ -70,10 +73,12 @@ channel=${config.radio.channel} let bss = config.bss[i]; let type = i > 0 ? "bss" : "interface"; let nasid = bss.nasid ?? replace(bss.bssid, ":", ""); - + let bssid = bss.bssid; + if (bss.mld_ap) + bssid += "\nmld_addr=" + bss.mld_bssid; str += ` ${type}=${bss.ifname} -bssid=${bss.bssid} +bssid=${bssid} ${join("\n", bss.data)} nas_identifier=${nasid} `; @@ -142,6 +147,9 @@ function iface_add(phy, config, phy_status) function iface_config_macaddr_list(config) { let macaddr_list = {}; + for (let name, mld in hostapd.data.mld) + if (mld.macaddr) + macaddr_list[mld.macaddr] = -1; for (let i = 0; i < length(config.bss); i++) { let bss = config.bss[i]; if (!bss.default_macaddr) @@ -154,8 +162,11 @@ function iface_config_macaddr_list(config) function iface_update_supplicant_macaddr(phydev, config) { let macaddr_list = []; - for (let i = 0; i < length(config.bss); i++) - push(macaddr_list, config.bss[i].bssid); + for (let name, mld in hostapd.data.mld) + if (mld.macaddr) + push(macaddr_list, mld.macaddr); + for (let bss in config.bss) + push(macaddr_list, bss.bssid); ubus.defer("wpa_supplicant", "phy_set_macaddr_list", { phy: phydev.name, radio: phydev.radio ?? -1, @@ -178,13 +189,15 @@ function __iface_pending_next(pending, state, ret, data) iface_update_supplicant_macaddr(phydev, config); return "create_bss"; case "create_bss": - let err = phydev.wdev_add(bss.ifname, { - mode: "ap", - radio: phydev.radio, - }); - if (err) { - hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`); - return null; + if (!bss.mld_ap) { + let err = phydev.wdev_add(bss.ifname, { + mode: "ap", + radio: phydev.radio, + }); + if (err) { + hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`); + return null; + } } pending.call("wpa_supplicant", "phy_status", { @@ -471,18 +484,26 @@ function bss_find_existing(config, prev_config, prev_hash) return -1; } -function get_config_bss(config, idx) +function get_config_bss(name, config, idx) { if (!config.bss[idx]) { hostapd.printf(`Invalid bss index ${idx}`); - return null; + return; } let ifname = config.bss[idx].ifname; - if (!ifname) + if (!ifname) { hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`); + return; + } - return hostapd.bss[ifname]; + let if_bss = hostapd.bss[name]; + if (!if_bss) { + hostapd.printf(`Could not find interface ${name} bss list`); + return; + } + + return if_bss[ifname]; } function iface_reload_config(name, phydev, config, old_config) @@ -508,7 +529,7 @@ function iface_reload_config(name, phydev, config, old_config) return false; } - let first_bss = hostapd.bss[iface_name]; + let first_bss = get_config_bss(name, old_config, 0); if (!first_bss) { hostapd.printf(`Could not find bss of previous interface ${iface_name}`); return false; @@ -542,8 +563,12 @@ function iface_reload_config(name, phydev, config, old_config) let cur_config = config.bss[i]; let prev_config = old_config.bss[prev]; + if (prev_config.force_reload) { + delete prev_config.force_reload; + continue; + } - let prev_bss = get_config_bss(old_config, prev); + let prev_bss = get_config_bss(name, old_config, prev); if (!prev_bss) return false; @@ -576,7 +601,7 @@ function iface_reload_config(name, phydev, config, old_config) config.bss[0].bssid = old_config.bss[0].bssid; } - let prev_bss = get_config_bss(old_config, 0); + let prev_bss = get_config_bss(name, old_config, 0); if (!prev_bss) return false; @@ -591,14 +616,15 @@ function iface_reload_config(name, phydev, config, old_config) if (!prev_bss_hash[i]) continue; - let prev_bss = get_config_bss(old_config, i); + let prev_bss = get_config_bss(name, old_config, i); if (!prev_bss) return false; let ifname = old_config.bss[i].ifname; hostapd.printf(`Remove bss '${ifname}' on phy '${name}'`); prev_bss.delete(); - wdev_remove(ifname); + if (!old_config.bss[i].mld_ap) + wdev_remove(ifname); } // Step 4: rename preserved interfaces, use temporary name on duplicates @@ -612,7 +638,7 @@ function iface_reload_config(name, phydev, config, old_config) if (old_ifname == new_ifname) continue; - if (hostapd.bss[new_ifname]) { + if (hostapd.bss[name][new_ifname]) { new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8); push(rename_list, i); } @@ -729,17 +755,78 @@ function iface_reload_config(name, phydev, config, old_config) return true; } +function bss_check_mld(phydev, iface_name, bss) +{ + if (!bss.ifname) + return; + + let mld_data = hostapd.data.mld[bss.ifname]; + if (!mld_data || !mld_data.ifname || !mld_data.macaddr) + return; + + bss.mld_bssid = mld_data.macaddr; + mld_data.iface[iface_name] = true; + if (mld_data.has_wdev) + return true; + + hostapd.printf(`Create MLD interface ${bss.ifname} on phy ${phydev.name}, radio mask: ${mld_data.radio_mask}`); + let err = phydev.wdev_add(bss.ifname, { + mode: "ap", + macaddr: mld_data.macaddr, + radio_mask: mld_data.radio_mask, + }); + wdev_set_up(bss.ifname, true); + if (err) { + hostapd.printf(`Failed to create MLD ${bss.ifname} on phy ${phydev.name}: ${err}`); + delete mld_data.iface[iface_name]; + return; + } + + mld_data.has_wdev = true; + + return true; +} + +function iface_check_mld(phydev, name, config) +{ + phydev = phy_open(phydev.phy); + + for (let mld_name, mld_data in hostapd.data.mld) + delete mld_data.iface[name]; + + for (let i = 0; i < length(config.bss); i++) { + let bss = config.bss[i]; + if (!bss.mld_ap) + continue; + + if (!bss_check_mld(phydev, name, bss)) { + hostapd.printf(`Skip MLD interface ${name} on phy ${phydev.name}`); + splice(config.bss, i--, 1); + } + } + + for (let mld_name, mld_data in hostapd.data.mld) { + if (length(mld_data.iface) > 0) + continue; + + hostapd.printf(`Remove MLD interface ${mld_name}`); + wdev_remove(mld_name); + delete mld_data.has_wdev; + } +} + +function iface_config_remove(name, old_config) +{ + hostapd.remove_iface(name); + return iface_remove(old_config); +} + function iface_set_config(name, config) { let old_config = hostapd.data.config[name]; hostapd.data.config[name] = config; - if (!config) { - hostapd.remove_iface(name); - return iface_remove(old_config); - } - let phy = config.phy; let phydev = phy_open(phy, config.radio_idx); if (!phydev) { @@ -747,6 +834,11 @@ function iface_set_config(name, config) return false; } + config.orig_bss = [ ...config.bss ]; + iface_check_mld(phydev, name, config); + if (!length(config.bss)) + return iface_config_remove(name, old_config); + try { let ret = iface_reload_config(name, phydev, config, old_config); if (ret) { @@ -779,10 +871,6 @@ function config_add_bss(config, name) function iface_load_config(phy, radio, filename) { - let f = open(filename, "r"); - if (!f) - return null; - if (radio < 0) radio = null; @@ -796,6 +884,10 @@ function iface_load_config(phy, radio, filename) orig_file: filename, }; + let f = open(filename, "r"); + if (!f) + return config; + let bss; let line; while ((line = rtrim(f.read("line"), "\n")) != null) { @@ -839,6 +931,9 @@ function iface_load_config(phy, radio, filename) if (val[0] == "nas_identifier") bss.nasid = val[1]; + if (val[0] == "mld_ap") + bss[val[0]] = int(val[1]); + if (val[0] == "bss") { bss = config_add_bss(config, val[1]); continue; @@ -893,6 +988,134 @@ function bss_config(bss_name) { } } +function mld_rename_bss(data, name) +{ + if (data.ifname == name) + return true; + + // TODO: handle rename gracefully + return false; +} + +function mld_add_bss(name, data, phy_list, i) +{ + let config = data.config; + if (!config.phy) + return; + + wdev_remove(name); + let phydev = phy_list[config.phy]; + if (!phydev) { + phydev = phy_open(config.phy, 0); + if (!phydev) + return; + + let macaddr_list = {}; + let phy_config = hostapd.data.config[phy_name(config.phy, 0)]; + if (phy_config) + macaddr_list = iface_config_macaddr_list(phy_config); + iface_macaddr_init(phydev, data.config, macaddr_list); + + phy_list[config.phy] = phydev; + } + + data.macaddr = config.macaddr; + if (!data.macaddr) { + data.macaddr = phydev.macaddr_next(); + data.default_macaddr = true; + } + + let radio_mask = 0; + for (let r in config.radios) + if (r != null) + radio_mask |= 1 << r; + + data.radio_mask = radio_mask; + data.ifname = name; +} + +function mld_find_matching_config(list, config) +{ + for (let name, data in list) + if (is_equal(data.config, config)) + return name; +} + +function mld_reload_interface(name) +{ + let config = hostapd.data.config[name]; + if (!config) + return; + + config = { ...config }; + config.bss = config.orig_bss; + + iface_set_config(name, config); +} + +function mld_set_config(config) +{ + let prev_mld = { ...hostapd.data.mld }; + let new_mld = {}; + let phy_list = {}; + let new_config = !length(prev_mld); + + hostapd.printf(`Set MLD config: ${keys(config)}`); + + // find renamed/new interfaces + for (let name, data in config) { + let prev = mld_find_matching_config(prev_mld, data); + if (prev) { + let data = prev_mld[prev]; + if (mld_rename_bss(data, name)) { + new_mld[name] = data; + delete prev_mld[prev]; + continue; + } + } + + new_mld[name] = { + config: data, + iface: {}, + }; + } + + let reload_iface = {}; + for (let name, data in prev_mld) { + delete hostapd.data.mld[name]; + + if (!data.ifname) + continue; + + for (let iface, bss_list in hostapd.bss) { + if (!bss_list[name]) + continue; + reload_iface[iface] = true; + } + } + + for (let name in reload_iface) + mld_reload_interface(name); + + for (let name, data in prev_mld) { + if (data.ifname) + hostapd.printf(`Remove MLD interface ${name}`); + wdev_remove(name); + } + + // add new interfaces + hostapd.data.mld = new_mld; + for (let name, data in new_mld) + mld_add_bss(name, data, phy_list); + + if (!new_config) + return; + + hostapd.printf(`Reload all interfaces`); + for (let name in hostapd.data.config) + mld_reload_interface(name); +} + let main_obj = { reload: { args: { @@ -980,6 +1203,31 @@ let main_obj = { return ret; }) }, + mld_set: { + args: { + config: {} + }, + call: ex_wrap(function(req) { + if (!req.args.config) + return libubus.STATUS_INVALID_ARGUMENT; + + mld_set_config(req.args.config); + + return { + pid: hostapd.getpid() + }; + }) + }, + config_reset: { + args: { + }, + call: ex_wrap(function(req) { + for (let name in hostapd.data.config) + iface_set_config(name); + mld_set_config({}); + return 0; + }) + }, config_set: { args: { phy: "", diff --git a/package/network/services/hostapd/patches/190-hostapd-Fix-hostapd-crash-if-setup-a-iface-with-.patch b/package/network/services/hostapd/patches/190-hostapd-Fix-hostapd-crash-if-setup-a-iface-with-.patch new file mode 100644 index 0000000000..d05f272afe --- /dev/null +++ b/package/network/services/hostapd/patches/190-hostapd-Fix-hostapd-crash-if-setup-a-iface-with-.patch @@ -0,0 +1,46 @@ +From c14e53ea013415a29e9c493e9dacafb6dc5b31ee Mon Sep 17 00:00:00 2001 +From: Michael-CY Lee +Date: Fri, 8 Nov 2024 10:20:03 +0800 +Subject: [PATCH] hostapd: Fix hostapd crash if setup a iface with a link bss failed + +The crash occurs while some link bsses is traversing all the links by using +for_each_mld_link(), and hostapd access to the link bss which is already +been freed. + +If hostapd setup a link bss failed, the link should be removed from +its hostapd_mld. However, the function hostapd_bss_link_deinit +doesn't remove the link bss correctly if it is the first bss and +hapd->drv_priv is null. Therefore we should refator the remove iface flow +as hostapd_remove_iface (used in wifi down cmd). + +There are some cases that setup a bss may fail (e.g. afc query failed) or +trigger channel switch while hostapd is setting up other links. +The failed link would be add into hostapd_mld while driver_init(). + +Signed-off-by: Allen Ye +Signed-off-by: Michael-CY Lee +Signed-off-by: Felix Fietkau +--- + src/ap/hostapd.c | 5 +++++ + 1 file changed, 5 insertions(+) + +--- a/src/ap/hostapd.c ++++ b/src/ap/hostapd.c +@@ -3878,6 +3878,7 @@ int hostapd_add_iface(struct hapd_interf + } + + if (hostapd_setup_interface(hapd_iface)) { ++ hostapd_bss_link_deinit(hapd_iface->bss[0]); + hostapd_deinit_driver( + hapd_iface->bss[0]->driver, + hapd_iface->bss[0]->drv_priv, +@@ -5135,6 +5136,9 @@ int hostapd_mld_remove_link(struct hosta + if (!mld) + return -1; + ++ if (!hapd->link.next) ++ return 0; ++ + dl_list_del(&hapd->link); + mld->num_links--; + diff --git a/package/network/services/hostapd/patches/191-hostapd-add-support-for-specifying-the-link-id-in-th.patch b/package/network/services/hostapd/patches/191-hostapd-add-support-for-specifying-the-link-id-in-th.patch new file mode 100644 index 0000000000..fa44875aa6 --- /dev/null +++ b/package/network/services/hostapd/patches/191-hostapd-add-support-for-specifying-the-link-id-in-th.patch @@ -0,0 +1,58 @@ +From: Felix Fietkau +Date: Thu, 3 Jul 2025 11:22:26 +0200 +Subject: [PATCH] hostapd: add support for specifying the link id in the config + +Makes it easier to dynamically manage links for a MLD at run time. + +Signed-off-by: Felix Fietkau +--- + +--- a/hostapd/config_file.c ++++ b/hostapd/config_file.c +@@ -4934,6 +4934,8 @@ static int hostapd_config_fill(struct ho + conf->punct_acs_threshold = val; + } else if (os_strcmp(buf, "mld_ap") == 0) { + bss->mld_ap = !!atoi(pos); ++ } else if (os_strcmp(buf, "mld_link_id") == 0) { ++ bss->mld_link_id = atoi(pos); + } else if (os_strcmp(buf, "mld_addr") == 0) { + if (hwaddr_aton(pos, bss->mld_addr)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid mld_addr", +--- a/src/ap/ap_config.c ++++ b/src/ap/ap_config.c +@@ -177,6 +177,10 @@ void hostapd_config_defaults_bss(struct + bss->pasn_comeback_after = 10; + bss->pasn_noauth = 1; + #endif /* CONFIG_PASN */ ++ ++#ifdef CONFIG_IEEE80211BE ++ bss->mld_link_id = -1; ++#endif + } + + +--- a/src/ap/ap_config.h ++++ b/src/ap/ap_config.h +@@ -967,6 +967,8 @@ struct hostapd_bss_config { + /* The AP's MLD MAC address within the AP MLD */ + u8 mld_addr[ETH_ALEN]; + ++ s8 mld_link_id; ++ + #ifdef CONFIG_TESTING_OPTIONS + /* + * If set indicate the AP as disabled in the RNR element included in the +--- a/src/ap/hostapd.c ++++ b/src/ap/hostapd.c +@@ -3105,7 +3105,10 @@ struct hostapd_iface * hostapd_alloc_ifa + #ifdef CONFIG_IEEE80211BE + static void hostapd_bss_alloc_link_id(struct hostapd_data *hapd) + { +- hapd->mld_link_id = hapd->mld->next_link_id++; ++ if (hapd->conf->mld_link_id >= 0) ++ hapd->mld_link_id = hapd->conf->mld_link_id; ++ else ++ hapd->mld_link_id = hapd->mld->next_link_id++; + wpa_printf(MSG_DEBUG, "AP MLD: %s: Link ID %d assigned.", + hapd->mld->name, hapd->mld_link_id); + } diff --git a/package/network/services/hostapd/patches/300-noscan.patch b/package/network/services/hostapd/patches/300-noscan.patch index 6d97691855..314f0eff1b 100644 --- a/package/network/services/hostapd/patches/300-noscan.patch +++ b/package/network/services/hostapd/patches/300-noscan.patch @@ -18,7 +18,7 @@ Subject: [PATCH] Add noscan, no_ht_coex config options } else if (os_strcmp(buf, "ht_capab") == 0) { --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h -@@ -1105,6 +1105,8 @@ struct hostapd_config { +@@ -1107,6 +1107,8 @@ struct hostapd_config { int ht_op_mode_fixed; u16 ht_capab; diff --git a/package/network/services/hostapd/patches/600-ubus_support.patch b/package/network/services/hostapd/patches/600-ubus_support.patch index 0368726c81..b66d61bad5 100644 --- a/package/network/services/hostapd/patches/600-ubus_support.patch +++ b/package/network/services/hostapd/patches/600-ubus_support.patch @@ -159,7 +159,7 @@ probe/assoc/auth requests via object subscribe. if (iface->is_no_ir) { hostapd_set_state(iface, HAPD_IFACE_NO_IR); -@@ -3527,6 +3532,7 @@ void hostapd_interface_deinit_free(struc +@@ -3530,6 +3535,7 @@ void hostapd_interface_deinit_free(struc (unsigned int) iface->conf->num_bss); driver = iface->bss[0]->driver; drv_priv = iface->bss[0]->drv_priv; diff --git a/package/network/services/hostapd/patches/601-ucode_support.patch b/package/network/services/hostapd/patches/601-ucode_support.patch index ec25cd4084..a5aa2e6a79 100644 --- a/package/network/services/hostapd/patches/601-ucode_support.patch +++ b/package/network/services/hostapd/patches/601-ucode_support.patch @@ -113,6 +113,15 @@ as adding/removing interfaces. hostapd_ubus_free_bss(hapd); accounting_deinit(hapd); hostapd_deinit_wpa(hapd); +@@ -625,7 +628,7 @@ void hostapd_free_hapd_data(struct hosta + * If the BSS being removed is the first link, the next link becomes the first + * link. + */ +-static void hostapd_bss_link_deinit(struct hostapd_data *hapd) ++void hostapd_bss_link_deinit(struct hostapd_data *hapd) + { + #ifdef CONFIG_IEEE80211BE + int i; @@ -737,6 +740,7 @@ void hostapd_cleanup_iface_partial(struc static void hostapd_cleanup_iface(struct hostapd_iface *iface) { @@ -139,7 +148,47 @@ as adding/removing interfaces. { struct hostapd_bss_config *conf = hapd->conf; u8 ssid[SSID_MAX_LEN + 1]; -@@ -1518,6 +1522,8 @@ setup_mld: +@@ -1434,7 +1438,13 @@ static int hostapd_setup_bss(struct host + + if (!first || first == -1) { + u8 *addr = hapd->own_addr; ++ bool use_existing = first == -1; + ++#ifdef CONFIG_IEEE80211BE ++ if (hapd->conf->mld_ap) { ++ addr = NULL; ++ } else ++#endif /* CONFIG_IEEE80211BE */ + if (!is_zero_ether_addr(conf->bssid)) { + /* Allocate the configured BSSID. */ + os_memcpy(hapd->own_addr, conf->bssid, ETH_ALEN); +@@ -1469,6 +1479,7 @@ static int hostapd_setup_bss(struct host + hapd->mld_link_id, hapd->conf->iface); + goto setup_mld; + } ++ use_existing = true; + } + #endif /* CONFIG_IEEE80211BE */ + +@@ -1477,7 +1488,7 @@ static int hostapd_setup_bss(struct host + conf->iface, addr, hapd, + &hapd->drv_priv, force_ifname, if_addr, + conf->bridge[0] ? conf->bridge : NULL, +- first == -1)) { ++ use_existing)) { + wpa_printf(MSG_ERROR, "Failed to add BSS (BSSID=" + MACSTR ")", MAC2STR(hapd->own_addr)); + hapd->interface_added = 0; +@@ -1500,7 +1511,7 @@ static int hostapd_setup_bss(struct host + + #ifdef CONFIG_IEEE80211BE + setup_mld: +- if (hapd->conf->mld_ap && !first) { ++ if (hapd->conf->mld_ap && first != 1) { + wpa_printf(MSG_DEBUG, + "MLD: Set link_id=%u, mld_addr=" MACSTR + ", own_addr=" MACSTR, +@@ -1518,6 +1529,8 @@ setup_mld: } #endif /* CONFIG_IEEE80211BE */ @@ -148,7 +197,16 @@ as adding/removing interfaces. if (conf->wmm_enabled < 0) conf->wmm_enabled = hapd->iconf->ieee80211n | hapd->iconf->ieee80211ax; -@@ -2516,7 +2522,7 @@ static int hostapd_owe_iface_iter2(struc +@@ -1843,7 +1856,7 @@ int hostapd_set_acl(struct hostapd_data + } + + +-static int hostapd_set_ctrl_sock_iface(struct hostapd_data *hapd) ++int hostapd_set_ctrl_sock_iface(struct hostapd_data *hapd) + { + #ifdef CONFIG_IEEE80211BE + int ret; +@@ -2516,7 +2529,7 @@ static int hostapd_owe_iface_iter2(struc #endif /* CONFIG_OWE */ @@ -157,7 +215,7 @@ as adding/removing interfaces. { #ifdef CONFIG_OWE /* Check whether the enabled BSS can complete OWE transition mode -@@ -2986,7 +2992,7 @@ hostapd_alloc_bss_data(struct hostapd_if +@@ -2986,7 +2999,7 @@ hostapd_alloc_bss_data(struct hostapd_if } @@ -166,7 +224,16 @@ as adding/removing interfaces. { if (!hapd) return; -@@ -4070,7 +4076,8 @@ int hostapd_remove_iface(struct hapd_int +@@ -3194,7 +3207,7 @@ fail: + } + + +-static void hostapd_cleanup_unused_mlds(struct hapd_interfaces *interfaces) ++void hostapd_cleanup_unused_mlds(struct hapd_interfaces *interfaces) + { + #ifdef CONFIG_IEEE80211BE + struct hostapd_mld *mld, **all_mld; +@@ -4074,7 +4087,8 @@ int hostapd_remove_iface(struct hapd_int hapd_iface = interfaces->iface[i]; if (hapd_iface == NULL) return -1; @@ -213,16 +280,24 @@ as adding/removing interfaces. void *owner; char *config_fname; struct hostapd_config *conf; -@@ -787,6 +794,8 @@ struct hostapd_iface * hostapd_init(stru +@@ -787,11 +794,16 @@ struct hostapd_iface * hostapd_init(stru struct hostapd_iface * hostapd_interface_init_bss(struct hapd_interfaces *interfaces, const char *phy, const char *config_fname, int debug); ++int hostapd_set_ctrl_sock_iface(struct hostapd_data *hapd); +int hostapd_setup_bss(struct hostapd_data *hapd, int first, bool start_beacon); ++void hostapd_bss_link_deinit(struct hostapd_data *hapd); +void hostapd_bss_deinit(struct hostapd_data *hapd); void hostapd_bss_setup_multi_link(struct hostapd_data *hapd, struct hapd_interfaces *interfaces); void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta, -@@ -817,6 +826,7 @@ hostapd_switch_channel_fallback(struct h + int reassoc); + void hostapd_interface_deinit_free(struct hostapd_iface *iface); ++void hostapd_cleanup_unused_mlds(struct hapd_interfaces *interfaces); + int hostapd_enable_iface(struct hostapd_iface *hapd_iface); + int hostapd_reload_iface(struct hostapd_iface *hapd_iface); + int hostapd_reload_bss_only(struct hostapd_data *bss); +@@ -817,6 +829,7 @@ hostapd_switch_channel_fallback(struct h void hostapd_cleanup_cs_params(struct hostapd_data *hapd); void hostapd_periodic_iface(struct hostapd_iface *iface); int hostapd_owe_trans_get_info(struct hostapd_data *hapd); diff --git a/package/network/services/hostapd/patches/701-reload_config_inline.patch b/package/network/services/hostapd/patches/701-reload_config_inline.patch index 9c142d1ab6..b91ff62e05 100644 --- a/package/network/services/hostapd/patches/701-reload_config_inline.patch +++ b/package/network/services/hostapd/patches/701-reload_config_inline.patch @@ -8,7 +8,7 @@ as adding/removing interfaces. --- a/hostapd/config_file.c +++ b/hostapd/config_file.c -@@ -4981,7 +4981,12 @@ struct hostapd_config * hostapd_config_r +@@ -4983,7 +4983,14 @@ struct hostapd_config * hostapd_config_r int errors = 0; size_t i; @@ -19,6 +19,8 @@ as adding/removing interfaces. + } else { + f = fopen(fname, "r"); + } ++ wpa_printf(MSG_INFO, "Configuration file: Reading configuration file '%s'", ++ fname); if (f == NULL) { wpa_printf(MSG_ERROR, "Could not open configuration file '%s' " "for reading.", fname); @@ -39,3 +41,36 @@ as adding/removing interfaces. if (f == NULL) { wpa_printf(MSG_ERROR, "Failed to open config file '%s', " "error: %s", name, strerror(errno)); +--- a/hostapd/main.c ++++ b/hostapd/main.c +@@ -406,7 +406,11 @@ hostapd_interface_init(struct hapd_inter + struct hostapd_iface *iface; + int k; + +- wpa_printf(MSG_DEBUG, "Configuration file: %s", config_fname); ++ if (!strncmp(config_fname, "data:", 5)) { ++ wpa_printf(MSG_DEBUG, "Configuration file: %s", ""); ++ } else { ++ wpa_printf(MSG_DEBUG, "Configuration file: %s", config_fname); ++ } + iface = hostapd_init(interfaces, config_fname); + if (!iface) + return NULL; +--- a/src/ap/hostapd.c ++++ b/src/ap/hostapd.c +@@ -3400,8 +3400,13 @@ hostapd_interface_init_bss(struct hapd_i + } + } + +- wpa_printf(MSG_INFO, "Configuration file: %s (phy %s)%s", +- config_fname, phy, iface ? "" : " --> new PHY"); ++ if (!strncmp(config_fname, "data:", 5)) { ++ wpa_printf(MSG_INFO, "Configuration file: %s (phy %s)%s", ++ "", phy, iface ? "" : " --> new PHY"); ++ } else { ++ wpa_printf(MSG_INFO, "Configuration file: %s (phy %s)%s", ++ config_fname, phy, iface ? "" : " --> new PHY"); ++ } + + conf = interfaces->config_read_cb(config_fname); + if (!conf) diff --git a/package/network/services/hostapd/patches/720-iface_max_num_sta.patch b/package/network/services/hostapd/patches/720-iface_max_num_sta.patch index 5dfe839e5a..f0e3da9f59 100644 --- a/package/network/services/hostapd/patches/720-iface_max_num_sta.patch +++ b/package/network/services/hostapd/patches/720-iface_max_num_sta.patch @@ -25,7 +25,7 @@ full device, e.g. in order to deal with hardware/driver limitations } else if (os_strcmp(buf, "extended_key_id") == 0) { --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h -@@ -1069,6 +1069,8 @@ struct hostapd_config { +@@ -1071,6 +1071,8 @@ struct hostapd_config { unsigned int track_sta_max_num; unsigned int track_sta_max_age; @@ -79,7 +79,7 @@ full device, e.g. in order to deal with hardware/driver limitations { --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h -@@ -828,6 +828,7 @@ void hostapd_periodic_iface(struct hosta +@@ -831,6 +831,7 @@ void hostapd_periodic_iface(struct hosta int hostapd_owe_trans_get_info(struct hostapd_data *hapd); void hostapd_owe_update_trans(struct hostapd_iface *iface);; void hostapd_ocv_check_csa_sa_query(void *eloop_ctx, void *timeout_ctx); diff --git a/package/network/services/hostapd/patches/770-radius_server.patch b/package/network/services/hostapd/patches/770-radius_server.patch index d6fdb167f6..fdf5fed397 100644 --- a/package/network/services/hostapd/patches/770-radius_server.patch +++ b/package/network/services/hostapd/patches/770-radius_server.patch @@ -29,7 +29,7 @@ handle reload. #ifndef CONFIG_NO_HOSTAPD_LOGGER static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module, -@@ -834,6 +835,11 @@ int main(int argc, char *argv[]) +@@ -838,6 +839,11 @@ int main(int argc, char *argv[]) if (os_program_init()) return -1; diff --git a/package/network/services/hostapd/patches/780-Implement-APuP-Access-Point-Micro-Peering.patch b/package/network/services/hostapd/patches/780-Implement-APuP-Access-Point-Micro-Peering.patch index 9528900e46..271b88c7c0 100644 --- a/package/network/services/hostapd/patches/780-Implement-APuP-Access-Point-Micro-Peering.patch +++ b/package/network/services/hostapd/patches/780-Implement-APuP-Access-Point-Micro-Peering.patch @@ -53,7 +53,7 @@ Hotfix-by: Sebastian Gottschall https://github.com/mirror/dd-wrt/commit/0c3001a6 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c -@@ -4974,6 +4974,15 @@ static int hostapd_config_fill(struct ho +@@ -4976,6 +4976,15 @@ static int hostapd_config_fill(struct ho bss->mld_indicate_disabled = atoi(pos); #endif /* CONFIG_TESTING_OPTIONS */ #endif /* CONFIG_IEEE80211BE */ @@ -71,7 +71,7 @@ Hotfix-by: Sebastian Gottschall https://github.com/mirror/dd-wrt/commit/0c3001a6 "Line %d: unknown configuration item '%s'", --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h -@@ -982,6 +982,35 @@ struct hostapd_bss_config { +@@ -984,6 +984,35 @@ struct hostapd_bss_config { int mbssid_index; bool spp_amsdu; diff --git a/package/network/services/hostapd/src/src/ap/ucode.c b/package/network/services/hostapd/src/src/ap/ucode.c index d54ab63d8e..ecd7203590 100644 --- a/package/network/services/hostapd/src/src/ap/ucode.c +++ b/package/network/services/hostapd/src/src/ap/ucode.c @@ -47,41 +47,37 @@ hostapd_ucode_iface_get_uval(struct hostapd_iface *hapd) } static void -hostapd_ucode_update_bss_list(struct hostapd_iface *iface, uc_value_t *if_bss, uc_value_t *bss) +hostapd_ucode_update_bss_list(struct hostapd_iface *iface, uc_value_t *bss) { uc_value_t *list; int i; - list = ucv_array_new(vm); + list = ucv_object_new(vm); for (i = 0; iface->bss && i < iface->num_bss; i++) { struct hostapd_data *hapd = iface->bss[i]; + uc_value_t *uval = hostapd_ucode_bss_get_uval(hapd); - ucv_array_set(list, i, ucv_string_new(hapd->conf->iface)); - ucv_object_add(bss, hapd->conf->iface, hostapd_ucode_bss_get_uval(hapd)); + ucv_object_add(list, hapd->conf->iface, uval); } - ucv_object_add(if_bss, iface->phy, list); + ucv_object_add(bss, iface->phy, list); } static void hostapd_ucode_update_interfaces(void) { uc_value_t *ifs = ucv_object_new(vm); - uc_value_t *if_bss = ucv_array_new(vm); - uc_value_t *bss = ucv_object_new(vm); + uc_value_t *if_bss = ucv_object_new(vm); int i; for (i = 0; i < interfaces->count; i++) { struct hostapd_iface *iface = interfaces->iface[i]; ucv_object_add(ifs, iface->phy, hostapd_ucode_iface_get_uval(iface)); - hostapd_ucode_update_bss_list(iface, if_bss, bss); + hostapd_ucode_update_bss_list(iface, if_bss); } ucv_object_add(ucv_prototype_get(global), "interfaces", ifs); - ucv_object_add(ucv_prototype_get(global), "interface_bss", if_bss); - ucv_object_add(ucv_prototype_get(global), "bss", bss); - - ucv_gc(vm); + ucv_object_add(ucv_prototype_get(global), "bss", if_bss); } static uc_value_t * @@ -205,6 +201,49 @@ bss_reload_vlans(struct hostapd_data *hapd, struct hostapd_bss_config *bss) return 0; } +static void +__uc_hostapd_bss_stop(struct hostapd_data *hapd) +{ + struct hostapd_iface *iface = hapd->iface; + + if (!hapd->started) + return; + + hostapd_bss_deinit_no_free(hapd); + hostapd_drv_stop_ap(hapd); + hostapd_bss_link_deinit(hapd); + +#ifdef CONFIG_IEEE80211BE + if (hapd == iface->bss[0] && hapd->conf->mld_ap) + hostapd_if_link_remove(hapd, WPA_IF_AP_BSS, hapd->conf->iface, + hapd->mld_link_id); +#endif + + hostapd_free_hapd_data(hapd); +} + +static int +__uc_hostapd_bss_start(struct hostapd_data *hapd) +{ + struct hostapd_iface *iface = hapd->iface; + bool first = hapd == iface->bss[0]; + int ret; + + if (hapd->started) + return 0; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + first = false; +#endif + + ret = hostapd_setup_bss(hapd, first, true); + hostapd_neighbor_set_own_report(hapd); + hostapd_owe_update_trans(iface); + + return ret; +} + static uc_value_t * uc_hostapd_bss_set_config(uc_vm_t *vm, size_t nargs) { @@ -217,6 +256,7 @@ uc_hostapd_bss_set_config(uc_vm_t *vm, size_t nargs) uc_value_t *files_only = uc_fn_arg(2); unsigned int i, idx = 0; int ret = -1; + bool started; if (!hapd || ucv_type(file) != UC_STRING) goto out; @@ -245,12 +285,11 @@ uc_hostapd_bss_set_config(uc_vm_t *vm, size_t nargs) swap_field(ssid.wpa_psk_file); ret = bss_reload_vlans(hapd, bss); - goto done; + goto free; } - hostapd_bss_deinit_no_free(hapd); - hostapd_drv_stop_ap(hapd); - hostapd_free_hapd_data(hapd); + started = hapd->started; + __uc_hostapd_bss_stop(hapd); old_bss = hapd->conf; for (i = 0; i < iface->conf->num_bss; i++) @@ -262,13 +301,12 @@ uc_hostapd_bss_set_config(uc_vm_t *vm, size_t nargs) if (hapd == iface->bss[0]) memcpy(hapd->own_addr, hapd->conf->bssid, ETH_ALEN); - hostapd_setup_bss(hapd, hapd == iface->bss[0], true); - hostapd_neighbor_set_own_report(hapd); + if (started) + ret = __uc_hostapd_bss_start(hapd); + else + ret = 0; hostapd_ucode_update_interfaces(); - hostapd_owe_update_trans(iface); -done: - ret = 0; free: hostapd_config_free(conf); out: @@ -330,10 +368,14 @@ uc_hostapd_bss_delete(uc_vm_t *vm, size_t nargs) hostapd_bss_deinit(hapd); hostapd_remove_iface_bss_conf(iface->conf, hapd->conf); hostapd_config_free_bss(hapd->conf); +#ifdef CONFIG_IEEE80211BE + if (hapd->mld) + hapd->mld->refcount--; +#endif os_free(hapd); + hostapd_cleanup_unused_mlds(iface->interfaces); hostapd_ucode_update_interfaces(); - ucv_gc(vm); return NULL; } @@ -370,7 +412,12 @@ uc_hostapd_iface_add_bss(uc_vm_t *vm, size_t nargs) #ifdef CONFIG_IEEE80211BE os_strlcpy(hapd->ctrl_sock_iface, hapd->conf->iface, sizeof(hapd->ctrl_sock_iface)); + if (hapd->conf->mld_ap) { + hostapd_bss_setup_multi_link(hapd, iface->interfaces); + hostapd_set_ctrl_sock_iface(hapd); + } #endif + if (interfaces->ctrl_iface_init && interfaces->ctrl_iface_init(hapd) < 0) goto free_hapd; @@ -678,6 +725,7 @@ uc_hostapd_bss_rename(uc_vm_t *vm, size_t nargs) { struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss"); uc_value_t *ifname_arg = uc_fn_arg(0); + uc_value_t *skip_rename = uc_fn_arg(1); char prev_ifname[IFNAMSIZ + 1]; struct sta_info *sta; const char *ifname; @@ -693,9 +741,11 @@ uc_hostapd_bss_rename(uc_vm_t *vm, size_t nargs) if (interfaces->ctrl_iface_deinit) interfaces->ctrl_iface_deinit(hapd); - ret = hostapd_drv_if_rename(hapd, WPA_IF_AP_BSS, NULL, ifname); - if (ret) - goto out; + if (!ucv_is_truish(skip_rename)) { + ret = hostapd_drv_if_rename(hapd, WPA_IF_AP_BSS, NULL, ifname); + if (ret) + goto out; + } for (sta = hapd->sta_list; sta; sta = sta->next) { char cur_name[IFNAMSIZ + 1], new_name[IFNAMSIZ + 1]; @@ -782,7 +832,6 @@ int hostapd_ucode_sta_auth(struct hostapd_data *hapd, struct sta_info *sta) ret = ucv_int64_get(cur); ucv_put(val); - ucv_gc(vm); return ret; } @@ -908,7 +957,6 @@ int hostapd_ucode_init(struct hapd_interfaces *ifaces) if (wpa_ucode_run(HOSTAPD_UC_PATH "hostapd.uc")) goto free_vm; - ucv_gc(vm); return 0; @@ -942,7 +990,6 @@ void hostapd_ucode_bss_cb(struct hostapd_data *hapd, const char *type) uc_value_push(ucv_get(val)); ucv_put(wpa_ucode_call(3)); ucv_put(val); - ucv_gc(vm); } void hostapd_ucode_free_bss(struct hostapd_data *hapd) @@ -962,7 +1009,6 @@ void hostapd_ucode_free_bss(struct hostapd_data *hapd) ucv_put(wpa_ucode_call(2)); ucv_put(val); - ucv_gc(vm); } #ifdef CONFIG_APUP @@ -979,6 +1025,5 @@ void hostapd_ucode_apup_newpeer(struct hostapd_data *hapd, const char *ifname) uc_value_push(ucv_string_new(ifname)); // APuP peer ifname ucv_put(wpa_ucode_call(2)); ucv_put(val); - ucv_gc(vm); } #endif // def CONFIG_APUP diff --git a/package/network/services/hostapd/src/src/utils/ucode.c b/package/network/services/hostapd/src/src/utils/ucode.c index a7cc2c7059..b5fdf6676c 100644 --- a/package/network/services/hostapd/src/src/utils/ucode.c +++ b/package/network/services/hostapd/src/src/utils/ucode.c @@ -14,7 +14,6 @@ static uc_value_t *registry; static uc_vm_t vm; -static struct uloop_timeout gc_timer; static struct udebug ud; static struct udebug_buf ud_log, ud_nl[3]; static const struct udebug_buf_meta meta_log = { @@ -71,11 +70,6 @@ static struct udebug_ubus_ring udebug_rings[] = { char *udebug_service; struct udebug_ubus ud_ubus; -static void uc_gc_timer(struct uloop_timeout *timeout) -{ - ucv_gc(&vm); -} - uc_value_t *uc_wpa_printf(uc_vm_t *vm, size_t nargs) { uc_value_t *level = uc_fn_arg(0); @@ -254,7 +248,6 @@ uc_vm_t *wpa_ucode_create_vm(void) uc_stdlib_load(uc_vm_scope_get(&vm)); eloop_add_uloop(); - gc_timer.cb = uc_gc_timer; return &vm; } @@ -486,9 +479,6 @@ uc_value_t *wpa_ucode_call(size_t nargs) if (uc_vm_call(&vm, true, nargs) != EXCEPTION_NONE) return NULL; - if (!gc_timer.pending) - uloop_timeout_set(&gc_timer, 10); - return uc_vm_stack_pop(&vm); } diff --git a/package/network/services/hostapd/src/wpa_supplicant/ucode.c b/package/network/services/hostapd/src/wpa_supplicant/ucode.c index 8335a27e89..8ba4db01de 100644 --- a/package/network/services/hostapd/src/wpa_supplicant/ucode.c +++ b/package/network/services/hostapd/src/wpa_supplicant/ucode.c @@ -39,7 +39,6 @@ wpas_ucode_update_interfaces(void) ucv_object_add(ifs, wpa_s->ifname, wpas_ucode_iface_get_uval(wpa_s)); ucv_object_add(ucv_prototype_get(global), "interfaces", ifs); - ucv_gc(vm); } void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s) @@ -52,7 +51,6 @@ void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s) uc_value_push(ucv_string_new(wpa_s->ifname)); uc_value_push(wpas_ucode_iface_get_uval(wpa_s)); ucv_put(wpa_ucode_call(2)); - ucv_gc(vm); } void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s) @@ -71,7 +69,6 @@ void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s) uc_value_push(ucv_get(val)); ucv_put(wpa_ucode_call(2)); ucv_put(val); - ucv_gc(vm); } void wpas_ucode_update_state(struct wpa_supplicant *wpa_s) @@ -91,7 +88,6 @@ void wpas_ucode_update_state(struct wpa_supplicant *wpa_s) uc_value_push(ucv_get(val)); uc_value_push(ucv_string_new(state)); ucv_put(wpa_ucode_call(3)); - ucv_gc(vm); } void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_data *data) @@ -124,7 +120,6 @@ void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_d } ucv_put(wpa_ucode_call(4)); - ucv_gc(vm); } static const char *obj_stringval(uc_value_t *obj, const char *name) @@ -310,7 +305,6 @@ int wpas_ucode_init(struct wpa_global *gl) if (wpa_ucode_run(HOSTAPD_UC_PATH "wpa_supplicant.uc")) goto free_vm; - ucv_gc(vm); return 0; free_vm: diff --git a/package/utils/util-linux/Makefile b/package/utils/util-linux/Makefile index be7c968cdc..52dca78e3d 100644 --- a/package/utils/util-linux/Makefile +++ b/package/utils/util-linux/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=util-linux PKG_VERSION:=2.41.1 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz PKG_SOURCE_URL:=@KERNEL/linux/utils/$(PKG_NAME)/v2.41 @@ -317,6 +317,18 @@ define Package/ipcs/description semaphore arrays. endef +define Package/last +$(call Package/util-linux/Default) + TITLE:=display history of user logins and logout sessions + LICENSE=BSD-4-Clause-UC + LICENSE_FILES:=Documentation/licenses/COPYING.BSD-4-Clause-UC +endef + +define Package/last/description + last utility displays a history of user login and logout sessions, system reboots, + and other events recorded in /var/log/wtmp (or a specified file) +endef + define Package/logger $(call Package/util-linux/Default) TITLE:=a shell command interface to the syslog system log module @@ -706,7 +718,6 @@ MESON_ARGS += \ -Dbuild-rfkill=disabled \ -Dbuild-tunelp=disabled \ -Dbuild-kill=disabled \ - -Dbuild-last=disabled \ -Dbuild-utmpdump=disabled \ -Dbuild-line=disabled \ -Dbuild-mesg=disabled \ @@ -862,6 +873,11 @@ define Package/ipcs/install $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/ipcs $(1)/usr/bin/ endef +define Package/last/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/last $(1)/usr/bin/ +endef + define Package/logger/install $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/logger $(1)/usr/bin/util-linux-logger @@ -1032,6 +1048,7 @@ $(eval $(call BuildPackage,fstrim)) $(eval $(call BuildPackage,getopt)) $(eval $(call BuildPackage,hwclock)) $(eval $(call BuildPackage,ipcs)) +$(eval $(call BuildPackage,last)) $(eval $(call BuildPackage,logger)) $(eval $(call BuildPackage,look)) $(eval $(call BuildPackage,losetup)) diff --git a/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch b/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch new file mode 100644 index 0000000000..64effc5ec8 --- /dev/null +++ b/target/linux/generic/backport-6.12/620-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch @@ -0,0 +1,79 @@ +From: Qingfang Deng +Date: Sat, 1 Mar 2025 21:55:16 +0800 +Subject: [PATCH] ppp: use IFF_NO_QUEUE in virtual interfaces +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For PPPoE, PPTP, and PPPoL2TP, the start_xmit() function directly +forwards packets to the underlying network stack and never returns +anything other than 1. So these interfaces do not require a qdisc, +and the IFF_NO_QUEUE flag should be set. + +Introduces a direct_xmit flag in struct ppp_channel to indicate when +IFF_NO_QUEUE should be applied. The flag is set in ppp_connect_channel() +for relevant protocols. + +While at it, remove the usused latency member from struct ppp_channel. + +Signed-off-by: Qingfang Deng +Reviewed-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20250301135517.695809-1-dqfext@gmail.com +Signed-off-by: Jakub Kicinski +--- + +--- a/drivers/net/ppp/ppp_generic.c ++++ b/drivers/net/ppp/ppp_generic.c +@@ -3500,6 +3500,10 @@ ppp_connect_channel(struct channel *pch, + ret = -ENOTCONN; + goto outl; + } ++ if (pch->chan->direct_xmit) ++ ppp->dev->priv_flags |= IFF_NO_QUEUE; ++ else ++ ppp->dev->priv_flags &= ~IFF_NO_QUEUE; + spin_unlock_bh(&pch->downl); + if (pch->file.hdrlen > ppp->file.hdrlen) + ppp->file.hdrlen = pch->file.hdrlen; +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -693,6 +693,7 @@ static int pppoe_connect(struct socket * + po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2; + po->chan.private = sk; + po->chan.ops = &pppoe_chan_ops; ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(dev_net(dev), &po->chan); + if (error) { +--- a/drivers/net/ppp/pptp.c ++++ b/drivers/net/ppp/pptp.c +@@ -465,6 +465,7 @@ static int pptp_connect(struct socket *s + po->chan.mtu -= PPTP_HEADER_OVERHEAD; + + po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header); ++ po->chan.direct_xmit = true; + error = ppp_register_channel(&po->chan); + if (error) { + pr_err("PPTP: failed to register PPP channel (%d)\n", error); +--- a/include/linux/ppp_channel.h ++++ b/include/linux/ppp_channel.h +@@ -42,8 +42,7 @@ struct ppp_channel { + int hdrlen; /* amount of headroom channel needs */ + void *ppp; /* opaque to channel */ + int speed; /* transfer rate (bytes/second) */ +- /* the following is not used at present */ +- int latency; /* overhead time in milliseconds */ ++ bool direct_xmit; /* no qdisc, xmit directly */ + }; + + #ifdef __KERNEL__ +--- a/net/l2tp/l2tp_ppp.c ++++ b/net/l2tp/l2tp_ppp.c +@@ -806,6 +806,7 @@ static int pppol2tp_connect(struct socke + po->chan.private = sk; + po->chan.ops = &pppol2tp_chan_ops; + po->chan.mtu = pppol2tp_tunnel_mtu(tunnel); ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(sock_net(sk), &po->chan); + if (error) { diff --git a/target/linux/generic/backport-6.6/624-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch b/target/linux/generic/backport-6.6/624-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch new file mode 100644 index 0000000000..ad98807a6f --- /dev/null +++ b/target/linux/generic/backport-6.6/624-v6.15-ppp-use-IFF_NO_QUEUE-in-virtual-interfaces.patch @@ -0,0 +1,79 @@ +From: Qingfang Deng +Date: Sat, 1 Mar 2025 21:55:16 +0800 +Subject: [PATCH] ppp: use IFF_NO_QUEUE in virtual interfaces +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +For PPPoE, PPTP, and PPPoL2TP, the start_xmit() function directly +forwards packets to the underlying network stack and never returns +anything other than 1. So these interfaces do not require a qdisc, +and the IFF_NO_QUEUE flag should be set. + +Introduces a direct_xmit flag in struct ppp_channel to indicate when +IFF_NO_QUEUE should be applied. The flag is set in ppp_connect_channel() +for relevant protocols. + +While at it, remove the usused latency member from struct ppp_channel. + +Signed-off-by: Qingfang Deng +Reviewed-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20250301135517.695809-1-dqfext@gmail.com +Signed-off-by: Jakub Kicinski +--- + +--- a/drivers/net/ppp/ppp_generic.c ++++ b/drivers/net/ppp/ppp_generic.c +@@ -3500,6 +3500,10 @@ ppp_connect_channel(struct channel *pch, + ret = -ENOTCONN; + goto outl; + } ++ if (pch->chan->direct_xmit) ++ ppp->dev->priv_flags |= IFF_NO_QUEUE; ++ else ++ ppp->dev->priv_flags &= ~IFF_NO_QUEUE; + spin_unlock_bh(&pch->downl); + if (pch->file.hdrlen > ppp->file.hdrlen) + ppp->file.hdrlen = pch->file.hdrlen; +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -693,6 +693,7 @@ static int pppoe_connect(struct socket * + po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2; + po->chan.private = sk; + po->chan.ops = &pppoe_chan_ops; ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(dev_net(dev), &po->chan); + if (error) { +--- a/drivers/net/ppp/pptp.c ++++ b/drivers/net/ppp/pptp.c +@@ -465,6 +465,7 @@ static int pptp_connect(struct socket *s + po->chan.mtu -= PPTP_HEADER_OVERHEAD; + + po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header); ++ po->chan.direct_xmit = true; + error = ppp_register_channel(&po->chan); + if (error) { + pr_err("PPTP: failed to register PPP channel (%d)\n", error); +--- a/include/linux/ppp_channel.h ++++ b/include/linux/ppp_channel.h +@@ -42,8 +42,7 @@ struct ppp_channel { + int hdrlen; /* amount of headroom channel needs */ + void *ppp; /* opaque to channel */ + int speed; /* transfer rate (bytes/second) */ +- /* the following is not used at present */ +- int latency; /* overhead time in milliseconds */ ++ bool direct_xmit; /* no qdisc, xmit directly */ + }; + + #ifdef __KERNEL__ +--- a/net/l2tp/l2tp_ppp.c ++++ b/net/l2tp/l2tp_ppp.c +@@ -820,6 +820,7 @@ static int pppol2tp_connect(struct socke + po->chan.private = sk; + po->chan.ops = &pppol2tp_chan_ops; + po->chan.mtu = pppol2tp_tunnel_mtu(tunnel); ++ po->chan.direct_xmit = true; + + error = ppp_register_net_channel(sock_net(sk), &po->chan); + if (error) { diff --git a/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch b/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch new file mode 100644 index 0000000000..d7260296bd --- /dev/null +++ b/target/linux/generic/pending-6.12/650-net-pppoe-implement-GRO-support.patch @@ -0,0 +1,248 @@ +From: Felix Fietkau +Date: Tue, 15 Jul 2025 12:37:45 +0200 +Subject: [PATCH] net: pppoe: implement GRO support + +Only handles packets where the pppoe header length field matches the exact +packet length. Significantly improves rx throughput. + +When running NAT traffic through a MediaTek MT7621 devices from a host +behind PPPoE to a host directly connected via ethernet, the TCP throughput +that the device is able to handle improves from ~130 Mbit/s to ~630 Mbit/s, +using fraglist GRO. + +Signed-off-by: Felix Fietkau +--- + +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -77,6 +77,7 @@ + #include + #include + #include ++#include + + #include + +@@ -435,7 +436,7 @@ static int pppoe_rcv(struct sk_buff *skb + if (skb->len < len) + goto drop; + +- if (pskb_trim_rcsum(skb, len)) ++ if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len)) + goto drop; + + ph = pppoe_hdr(skb); +@@ -1173,6 +1174,161 @@ static struct pernet_operations pppoe_ne + .size = sizeof(struct pppoe_net), + }; + ++static u16 ++compare_pppoe_header(struct pppoe_hdr *phdr, struct pppoe_hdr *phdr2) ++{ ++ return (__force __u16)((phdr->sid ^ phdr2->sid) | ++ (phdr->tag[0].tag_type ^ phdr2->tag[0].tag_type)); ++} ++ ++static __be16 pppoe_hdr_proto(struct pppoe_hdr *phdr) ++{ ++ switch (phdr->tag[0].tag_type) { ++ case cpu_to_be16(PPP_IP): ++ return cpu_to_be16(ETH_P_IP); ++ case cpu_to_be16(PPP_IPV6): ++ return cpu_to_be16(ETH_P_IPV6); ++ default: ++ return 0; ++ } ++ ++} ++ ++static struct sk_buff *pppoe_gro_receive(struct list_head *head, ++ struct sk_buff *skb) ++{ ++ const struct packet_offload *ptype; ++ unsigned int hlen, off_pppoe; ++ struct sk_buff *pp = NULL; ++ struct pppoe_hdr *phdr; ++ struct sk_buff *p; ++ int flush = 1; ++ __be16 type; ++ ++ off_pppoe = skb_gro_offset(skb); ++ hlen = off_pppoe + sizeof(*phdr); ++ phdr = skb_gro_header(skb, hlen + 2, off_pppoe); ++ if (unlikely(!phdr)) ++ goto out; ++ ++ /* ignore packets with padding or invalid length */ ++ if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + hlen) ++ goto out; ++ ++ type = pppoe_hdr_proto(phdr); ++ if (!type) ++ goto out; ++ ++ ptype = gro_find_receive_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ flush = 0; ++ ++ list_for_each_entry(p, head, list) { ++ struct pppoe_hdr *phdr2; ++ ++ if (!NAPI_GRO_CB(p)->same_flow) ++ continue; ++ ++ phdr2 = (struct pppoe_hdr *)(p->data + off_pppoe); ++ if (compare_pppoe_header(phdr, phdr2)) ++ NAPI_GRO_CB(p)->same_flow = 0; ++ } ++ ++ skb_gro_pull(skb, sizeof(*phdr) + 2); ++ skb_gro_postpull_rcsum(skb, phdr, sizeof(*phdr) + 2); ++ ++ pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, ++ ipv6_gro_receive, inet_gro_receive, ++ head, skb); ++ ++out: ++ skb_gro_flush_final(skb, pp, flush); ++ ++ return pp; ++} ++ ++static int pppoe_gro_complete(struct sk_buff *skb, int nhoff) ++{ ++ struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff); ++ __be16 type = pppoe_hdr_proto(phdr); ++ struct packet_offload *ptype; ++ int len, err; ++ ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ return -ENOENT; ++ ++ err = INDIRECT_CALL_INET(ptype->callbacks.gro_complete, ++ ipv6_gro_complete, inet_gro_complete, ++ skb, nhoff + sizeof(*phdr) + 2); ++ if (err) ++ return err; ++ ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ ++ return 0; ++} ++ ++static struct sk_buff *pppoe_gso_segment(struct sk_buff *skb, ++ netdev_features_t features) ++{ ++ unsigned int pppoe_hlen = sizeof(struct pppoe_hdr) + 2; ++ struct sk_buff *segs = ERR_PTR(-EINVAL); ++ u16 mac_offset = skb->mac_header; ++ struct packet_offload *ptype; ++ u16 mac_len = skb->mac_len; ++ struct pppoe_hdr *phdr; ++ __be16 orig_type, type; ++ int len, nhoff; ++ ++ skb_reset_network_header(skb); ++ nhoff = skb_network_header(skb) - skb_mac_header(skb); ++ ++ if (unlikely(!pskb_may_pull(skb, pppoe_hlen))) ++ goto out; ++ ++ phdr = (struct pppoe_hdr *)skb_network_header(skb); ++ type = pppoe_hdr_proto(phdr); ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ orig_type = skb->protocol; ++ __skb_pull(skb, pppoe_hlen); ++ segs = ptype->callbacks.gso_segment(skb, features); ++ if (IS_ERR_OR_NULL(segs)) { ++ skb_gso_error_unwind(skb, orig_type, pppoe_hlen, mac_offset, ++ mac_len); ++ goto out; ++ } ++ ++ skb = segs; ++ do { ++ phdr = (struct pppoe_hdr *)(skb_mac_header(skb) + nhoff); ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ skb->network_header = (u8 *)phdr - skb->head; ++ skb->protocol = orig_type; ++ skb_reset_mac_len(skb); ++ } while ((skb = skb->next)); ++ ++out: ++ return segs; ++} ++ ++static struct packet_offload pppoe_packet_offload __read_mostly = { ++ .type = cpu_to_be16(ETH_P_PPP_SES), ++ .priority = 20, ++ .callbacks = { ++ .gro_receive = pppoe_gro_receive, ++ .gro_complete = pppoe_gro_complete, ++ .gso_segment = pppoe_gso_segment, ++ }, ++}; ++ + static int __init pppoe_init(void) + { + int err; +@@ -1189,6 +1345,7 @@ static int __init pppoe_init(void) + if (err) + goto out_unregister_pppoe_proto; + ++ dev_add_offload(&pppoe_packet_offload); + dev_add_pack(&pppoes_ptype); + dev_add_pack(&pppoed_ptype); + register_netdevice_notifier(&pppoe_notifier); +@@ -1208,6 +1365,7 @@ static void __exit pppoe_exit(void) + unregister_netdevice_notifier(&pppoe_notifier); + dev_remove_pack(&pppoed_ptype); + dev_remove_pack(&pppoes_ptype); ++ dev_remove_offload(&pppoe_packet_offload); + unregister_pppox_proto(PX_PROTO_OE); + proto_unregister(&pppoe_sk_proto); + unregister_pernet_device(&pppoe_net_ops); +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -1546,6 +1546,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_receive); + + static struct sk_buff *ipip_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -1631,6 +1632,7 @@ int inet_gro_complete(struct sk_buff *sk + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_complete); + + static int ipip_gro_complete(struct sk_buff *skb, int nhoff) + { +--- a/net/ipv6/ip6_offload.c ++++ b/net/ipv6/ip6_offload.c +@@ -304,6 +304,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive); + + static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -386,6 +387,7 @@ INDIRECT_CALLABLE_SCOPE int ipv6_gro_com + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete); + + static int sit_gro_complete(struct sk_buff *skb, int nhoff) + { diff --git a/target/linux/generic/pending-6.6/650-net-pppoe-implement-GRO-support.patch b/target/linux/generic/pending-6.6/650-net-pppoe-implement-GRO-support.patch new file mode 100644 index 0000000000..1c6849bf22 --- /dev/null +++ b/target/linux/generic/pending-6.6/650-net-pppoe-implement-GRO-support.patch @@ -0,0 +1,248 @@ +From: Felix Fietkau +Date: Tue, 15 Jul 2025 12:37:45 +0200 +Subject: [PATCH] net: pppoe: implement GRO support + +Only handles packets where the pppoe header length field matches the exact +packet length. Significantly improves rx throughput. + +When running NAT traffic through a MediaTek MT7621 devices from a host +behind PPPoE to a host directly connected via ethernet, the TCP throughput +that the device is able to handle improves from ~130 Mbit/s to ~630 Mbit/s, +using fraglist GRO. + +Signed-off-by: Felix Fietkau +--- + +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -77,6 +77,7 @@ + #include + #include + #include ++#include + + #include + +@@ -435,7 +436,7 @@ static int pppoe_rcv(struct sk_buff *skb + if (skb->len < len) + goto drop; + +- if (pskb_trim_rcsum(skb, len)) ++ if (!skb_is_gso(skb) && pskb_trim_rcsum(skb, len)) + goto drop; + + ph = pppoe_hdr(skb); +@@ -1173,6 +1174,161 @@ static struct pernet_operations pppoe_ne + .size = sizeof(struct pppoe_net), + }; + ++static u16 ++compare_pppoe_header(struct pppoe_hdr *phdr, struct pppoe_hdr *phdr2) ++{ ++ return (__force __u16)((phdr->sid ^ phdr2->sid) | ++ (phdr->tag[0].tag_type ^ phdr2->tag[0].tag_type)); ++} ++ ++static __be16 pppoe_hdr_proto(struct pppoe_hdr *phdr) ++{ ++ switch (phdr->tag[0].tag_type) { ++ case cpu_to_be16(PPP_IP): ++ return cpu_to_be16(ETH_P_IP); ++ case cpu_to_be16(PPP_IPV6): ++ return cpu_to_be16(ETH_P_IPV6); ++ default: ++ return 0; ++ } ++ ++} ++ ++static struct sk_buff *pppoe_gro_receive(struct list_head *head, ++ struct sk_buff *skb) ++{ ++ const struct packet_offload *ptype; ++ unsigned int hlen, off_pppoe; ++ struct sk_buff *pp = NULL; ++ struct pppoe_hdr *phdr; ++ struct sk_buff *p; ++ int flush = 1; ++ __be16 type; ++ ++ off_pppoe = skb_gro_offset(skb); ++ hlen = off_pppoe + sizeof(*phdr); ++ phdr = skb_gro_header(skb, hlen + 2, off_pppoe); ++ if (unlikely(!phdr)) ++ goto out; ++ ++ /* ignore packets with padding or invalid length */ ++ if (skb_gro_len(skb) != be16_to_cpu(phdr->length) + hlen) ++ goto out; ++ ++ type = pppoe_hdr_proto(phdr); ++ if (!type) ++ goto out; ++ ++ ptype = gro_find_receive_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ flush = 0; ++ ++ list_for_each_entry(p, head, list) { ++ struct pppoe_hdr *phdr2; ++ ++ if (!NAPI_GRO_CB(p)->same_flow) ++ continue; ++ ++ phdr2 = (struct pppoe_hdr *)(p->data + off_pppoe); ++ if (compare_pppoe_header(phdr, phdr2)) ++ NAPI_GRO_CB(p)->same_flow = 0; ++ } ++ ++ skb_gro_pull(skb, sizeof(*phdr) + 2); ++ skb_gro_postpull_rcsum(skb, phdr, sizeof(*phdr) + 2); ++ ++ pp = indirect_call_gro_receive_inet(ptype->callbacks.gro_receive, ++ ipv6_gro_receive, inet_gro_receive, ++ head, skb); ++ ++out: ++ skb_gro_flush_final(skb, pp, flush); ++ ++ return pp; ++} ++ ++static int pppoe_gro_complete(struct sk_buff *skb, int nhoff) ++{ ++ struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb->data + nhoff); ++ __be16 type = pppoe_hdr_proto(phdr); ++ struct packet_offload *ptype; ++ int len, err; ++ ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ return -ENOENT; ++ ++ err = INDIRECT_CALL_INET(ptype->callbacks.gro_complete, ++ ipv6_gro_complete, inet_gro_complete, ++ skb, nhoff + sizeof(*phdr) + 2); ++ if (err) ++ return err; ++ ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ ++ return 0; ++} ++ ++static struct sk_buff *pppoe_gso_segment(struct sk_buff *skb, ++ netdev_features_t features) ++{ ++ unsigned int pppoe_hlen = sizeof(struct pppoe_hdr) + 2; ++ struct sk_buff *segs = ERR_PTR(-EINVAL); ++ u16 mac_offset = skb->mac_header; ++ struct packet_offload *ptype; ++ u16 mac_len = skb->mac_len; ++ struct pppoe_hdr *phdr; ++ __be16 orig_type, type; ++ int len, nhoff; ++ ++ skb_reset_network_header(skb); ++ nhoff = skb_network_header(skb) - skb_mac_header(skb); ++ ++ if (unlikely(!pskb_may_pull(skb, pppoe_hlen))) ++ goto out; ++ ++ phdr = (struct pppoe_hdr *)skb_network_header(skb); ++ type = pppoe_hdr_proto(phdr); ++ ptype = gro_find_complete_by_type(type); ++ if (!ptype) ++ goto out; ++ ++ orig_type = skb->protocol; ++ __skb_pull(skb, pppoe_hlen); ++ segs = ptype->callbacks.gso_segment(skb, features); ++ if (IS_ERR_OR_NULL(segs)) { ++ skb_gso_error_unwind(skb, orig_type, pppoe_hlen, mac_offset, ++ mac_len); ++ goto out; ++ } ++ ++ skb = segs; ++ do { ++ phdr = (struct pppoe_hdr *)(skb_mac_header(skb) + nhoff); ++ len = skb->len - (nhoff + sizeof(*phdr)); ++ phdr->length = cpu_to_be16(len); ++ skb->network_header = (u8 *)phdr - skb->head; ++ skb->protocol = orig_type; ++ skb_reset_mac_len(skb); ++ } while ((skb = skb->next)); ++ ++out: ++ return segs; ++} ++ ++static struct packet_offload pppoe_packet_offload __read_mostly = { ++ .type = cpu_to_be16(ETH_P_PPP_SES), ++ .priority = 20, ++ .callbacks = { ++ .gro_receive = pppoe_gro_receive, ++ .gro_complete = pppoe_gro_complete, ++ .gso_segment = pppoe_gso_segment, ++ }, ++}; ++ + static int __init pppoe_init(void) + { + int err; +@@ -1189,6 +1345,7 @@ static int __init pppoe_init(void) + if (err) + goto out_unregister_pppoe_proto; + ++ dev_add_offload(&pppoe_packet_offload); + dev_add_pack(&pppoes_ptype); + dev_add_pack(&pppoed_ptype); + register_netdevice_notifier(&pppoe_notifier); +@@ -1208,6 +1365,7 @@ static void __exit pppoe_exit(void) + unregister_netdevice_notifier(&pppoe_notifier); + dev_remove_pack(&pppoed_ptype); + dev_remove_pack(&pppoes_ptype); ++ dev_remove_offload(&pppoe_packet_offload); + unregister_pppox_proto(PX_PROTO_OE); + proto_unregister(&pppoe_sk_proto); + unregister_pernet_device(&pppoe_net_ops); +--- a/net/ipv4/af_inet.c ++++ b/net/ipv4/af_inet.c +@@ -1587,6 +1587,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_receive); + + static struct sk_buff *ipip_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -1672,6 +1673,7 @@ int inet_gro_complete(struct sk_buff *sk + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(inet_gro_complete); + + static int ipip_gro_complete(struct sk_buff *skb, int nhoff) + { +--- a/net/ipv6/ip6_offload.c ++++ b/net/ipv6/ip6_offload.c +@@ -319,6 +319,7 @@ out: + + return pp; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive); + + static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head, + struct sk_buff *skb) +@@ -401,6 +402,7 @@ INDIRECT_CALLABLE_SCOPE int ipv6_gro_com + out: + return err; + } ++EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete); + + static int sit_gro_complete(struct sk_buff *skb, int nhoff) + { diff --git a/target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch b/target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch index 6a9bd44ab1..38e8cc1e0e 100644 --- a/target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch +++ b/target/linux/ipq40xx/patches-6.12/999-atm-mpoa-intel-dsl-phy-support.patch @@ -27,7 +27,7 @@ Subject: [PATCH] UGW_SW-29163: ATM oam support /* * Disconnect a channel from the generic layer. -@@ -3624,6 +3640,7 @@ EXPORT_SYMBOL(ppp_unregister_channel); +@@ -3628,6 +3644,7 @@ EXPORT_SYMBOL(ppp_unregister_channel); EXPORT_SYMBOL(ppp_channel_index); EXPORT_SYMBOL(ppp_unit_number); EXPORT_SYMBOL(ppp_dev_name); @@ -37,7 +37,7 @@ Subject: [PATCH] UGW_SW-29163: ATM oam support EXPORT_SYMBOL(ppp_output_wakeup); --- a/include/linux/ppp_channel.h +++ b/include/linux/ppp_channel.h -@@ -76,6 +76,9 @@ extern int ppp_unit_number(struct ppp_ch +@@ -75,6 +75,9 @@ extern int ppp_unit_number(struct ppp_ch /* Get the device name associated with a channel, or NULL if none */ extern char *ppp_dev_name(struct ppp_channel *); diff --git a/target/linux/ipq40xx/patches-6.6/999-atm-mpoa-intel-dsl-phy-support.patch b/target/linux/ipq40xx/patches-6.6/999-atm-mpoa-intel-dsl-phy-support.patch index 6a9bd44ab1..38e8cc1e0e 100644 --- a/target/linux/ipq40xx/patches-6.6/999-atm-mpoa-intel-dsl-phy-support.patch +++ b/target/linux/ipq40xx/patches-6.6/999-atm-mpoa-intel-dsl-phy-support.patch @@ -27,7 +27,7 @@ Subject: [PATCH] UGW_SW-29163: ATM oam support /* * Disconnect a channel from the generic layer. -@@ -3624,6 +3640,7 @@ EXPORT_SYMBOL(ppp_unregister_channel); +@@ -3628,6 +3644,7 @@ EXPORT_SYMBOL(ppp_unregister_channel); EXPORT_SYMBOL(ppp_channel_index); EXPORT_SYMBOL(ppp_unit_number); EXPORT_SYMBOL(ppp_dev_name); @@ -37,7 +37,7 @@ Subject: [PATCH] UGW_SW-29163: ATM oam support EXPORT_SYMBOL(ppp_output_wakeup); --- a/include/linux/ppp_channel.h +++ b/include/linux/ppp_channel.h -@@ -76,6 +76,9 @@ extern int ppp_unit_number(struct ppp_ch +@@ -75,6 +75,9 @@ extern int ppp_unit_number(struct ppp_ch /* Get the device name associated with a channel, or NULL if none */ extern char *ppp_dev_name(struct ppp_channel *); diff --git a/target/linux/realtek/dts/rtl8393_zyxel_gs1900-48.dts b/target/linux/realtek/dts/rtl8393_zyxel_gs1900-48.dts index 8790c3997b..d267cd28fd 100644 --- a/target/linux/realtek/dts/rtl8393_zyxel_gs1900-48.dts +++ b/target/linux/realtek/dts/rtl8393_zyxel_gs1900-48.dts @@ -54,7 +54,7 @@ #size-cells = <0>; }; - sfp0: sfp-p9 { + sfp0: sfp-p49 { compatible = "sff,sfp"; i2c-bus = <&i2c0>; los-gpio = <&gpio1 27 GPIO_ACTIVE_HIGH>; @@ -73,7 +73,7 @@ #size-cells = <0>; }; - sfp1: sfp-p10 { + sfp1: sfp-p50 { compatible = "sff,sfp"; i2c-bus = <&i2c1>; los-gpio = <&gpio1 33 GPIO_ACTIVE_HIGH>; @@ -121,12 +121,10 @@ partition@40000 { label = "u-boot-env"; reg = <0x40000 0x10000>; - read-only; }; partition@50000 { label = "u-boot-env2"; reg = <0x50000 0x10000>; - read-only; }; partition@60000 { label = "jffs"; @@ -214,8 +212,8 @@ EXTERNAL_PHY(47) /* RTL8393 Internal SerDes */ - INTERNAL_PHY(48) - INTERNAL_PHY(49) + INTERNAL_PHY_SDS(48, 12) + INTERNAL_PHY_SDS(49, 13) }; }; @@ -282,31 +280,19 @@ port@48 { reg = <48>; label = "lan49"; - phy-mode = "sgmii"; + phy-mode = "1000base-x"; phy-handle = <&phy48>; + managed = "in-band-status"; sfp = <&sfp0>; - - fixed-link { - speed = <1000>; - full-duplex; - pause; - }; - }; port@49 { reg = <49>; label = "lan50"; - phy-mode = "sgmii"; + phy-mode = "1000base-x"; phy-handle = <&phy49>; + managed = "in-band-status"; sfp = <&sfp1>; - - fixed-link { - speed = <1000>; - full-duplex; - pause; - }; - }; /* CPU-Port */ diff --git a/target/linux/realtek/files-6.12/drivers/net/ethernet/rtl838x_eth.c b/target/linux/realtek/files-6.12/drivers/net/ethernet/rtl838x_eth.c index 5c6d79d19b..c6e03ba571 100644 --- a/target/linux/realtek/files-6.12/drivers/net/ethernet/rtl838x_eth.c +++ b/target/linux/realtek/files-6.12/drivers/net/ethernet/rtl838x_eth.c @@ -83,6 +83,8 @@ extern int rtl931x_write_sds_phy(int phy_addr, int page, int phy_reg, u16 v); #define RTMDIO_ABS BIT(2) #define RTMDIO_PKG BIT(3) +#define RTMDIO_838X_BASE (0xe780) + struct p_hdr { uint8_t *buf; uint16_t reserved; @@ -1647,7 +1649,7 @@ struct rtmdio_bus_priv { bool raw[RTMDIO_MAX_PORT]; int smi_bus[RTMDIO_MAX_PORT]; u8 smi_addr[RTMDIO_MAX_PORT]; - u32 sds_id[RTMDIO_MAX_PORT]; + int sds_id[RTMDIO_MAX_PORT]; bool smi_bus_isc45[RTMDIO_MAX_SMI_BUS]; bool phy_is_internal[RTMDIO_MAX_PORT]; phy_interface_t interfaces[RTMDIO_MAX_PORT]; @@ -1655,6 +1657,8 @@ struct rtmdio_bus_priv { int (*write_mmd_phy)(u32 port, u32 addr, u32 reg, u32 val); int (*read_phy)(u32 port, u32 page, u32 reg, u32 *val); int (*write_phy)(u32 port, u32 page, u32 reg, u32 val); + int (*read_sds_phy)(int sds, int page, int regnum); + int (*write_sds_phy)(int sds, int page, int regnum, u16 val); }; /* @@ -1750,18 +1754,43 @@ int phy_port_read_paged(struct phy_device *phydev, int port, int page, u32 regnu /* SerDes reader/writer functions for the ports without external phy. */ -static int rtmdio_838x_read_sds(int addr, int regnum) -{ - int offset = addr == 26 ? 0x100 : 0x0; +/* + * The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped directly into + * 32 bit memory addresses. High 16 bits are always empty. A "lower" memory block serves pages 0/3 + * a "higher" memory block pages 1/2. + */ - return sw_r32(RTL838X_SDS4_FIB_REG0 + offset + (regnum << 2)) & 0xffff; +static int rtmdio_838x_reg_offset(int sds, int page, int regnum) +{ + if (sds < 0 || sds > 5) + return -EINVAL; + + if (page == 0 || page == 3) + return (sds << 9) + (page << 7) + (regnum << 2); + else if (page == 1 || page == 2) + return 0xb80 + (sds << 8) + (page << 7) + (regnum << 2); + + return -EINVAL; } -static int rtmdio_838x_write_sds(int addr, int regnum, u16 val) +static int rtmdio_838x_read_sds_phy(int sds, int page, int regnum) { - int offset = addr == 26 ? 0x100 : 0x0; + int offset = rtmdio_838x_reg_offset(sds, page, regnum); - sw_w32(val, RTL838X_SDS4_FIB_REG0 + offset + (regnum << 2)); + if (offset < 0) + return offset; + + return sw_r32(RTMDIO_838X_BASE + offset) & GENMASK(15, 0); +} + +static int rtmdio_838x_write_sds_phy(int sds, int page, int regnum, u16 val) +{ + int offset = rtmdio_838x_reg_offset(sds, page, regnum); + + if (offset < 0) + return offset; + + sw_w32(val, RTMDIO_838X_BASE + offset); return 0; } @@ -1950,6 +1979,58 @@ static int rtmdio_read_c45(struct mii_bus *bus, int addr, int devnum, int regnum return err ? err : val; } +static int rtmdio_map_sds_register(int page, int regnum, int *sds_page, int *sds_regnum) +{ + /* + * For the SerDes PHY simulate a register mapping like common RealTek PHYs do. Always + * keep the common registers 0x00-0x0f in place and map the SerDes registers into the + * upper vendor specific registers 0x10-0x17 according to the page select register + * (0x1f). That gives a register mapping as follows: + * + * +-----------------------+-----------------------+---------------+-----------------+ + * | reg 0x00-0x0f | reg 0x10-0x17 | reg 0x18-0x1e | reg 0x1f | + * +-----------------------+-----------------------+---------------+-----------------+ + * | SerDes fiber page (2) | real SerDes registers | zero | SerDes page | + * | registers 0x00-0x0f | in packages of 8 | | select register | + * +-----------------------+-----------------------+---------------+-----------------+ + */ + + if (regnum < 16) { + *sds_page = 2; + *sds_regnum = regnum; + } else if (regnum < 24) { + *sds_page = page / 4; + *sds_regnum = 8 * (page % 4) + (regnum - 16); + } else + return 0; + + return 1; +} + +static int rtmdio_read_sds_phy(struct rtmdio_bus_priv *priv, int sds, int page, int regnum) +{ + int ret, sds_page, sds_regnum; + + ret = rtmdio_map_sds_register(page, regnum, &sds_page, &sds_regnum); + if (ret) + ret = priv->read_sds_phy(sds, sds_page, sds_regnum); + pr_debug("rd_SDS(sds=%d, pag=%d, reg=%d) = %d\n", sds, page, regnum, ret); + + return ret; +} + +static int rtmdio_write_sds_phy(struct rtmdio_bus_priv *priv, int sds, int page, int regnum, u16 val) +{ + int ret, sds_page, sds_regnum; + + ret = rtmdio_map_sds_register(page, regnum, &sds_page, &sds_regnum); + if (ret) + ret = priv->write_sds_phy(sds, sds_page, sds_regnum, val); + pr_debug("wr_SDS(sds=%d, pag=%d, reg=%d, val=%d) err = %d\n", sds, page, regnum, val, ret); + + return ret; +} + static int rtmdio_83xx_read(struct mii_bus *bus, int addr, int regnum) { struct rtmdio_bus_priv *priv = bus->priv; @@ -1961,16 +2042,18 @@ static int rtmdio_83xx_read(struct mii_bus *bus, int addr, int regnum) if (addr >= priv->cpu_port) return -ENODEV; - if (addr >= 24 && addr <= 27 && priv->id == 0x8380) - return rtmdio_838x_read_sds(addr, regnum); - - if (priv->family_id == RTL8390_FAMILY_ID && priv->phy_is_internal[addr]) - return rtl839x_read_sds_phy(addr, regnum); - if (regnum == RTMDIO_PAGE_SELECT && priv->page[addr] != priv->rawpage) return priv->page[addr]; priv->raw[addr] = (priv->page[addr] == priv->rawpage); + if ((priv->phy_is_internal[addr]) && (priv->sds_id[addr] >=0)) { + if (priv->family_id == RTL8380_FAMILY_ID) + return rtmdio_read_sds_phy(priv, priv->sds_id[addr], + priv->page[addr], regnum); + else + return rtl839x_read_sds_phy(addr, regnum); + } + err = (*priv->read_phy)(addr, priv->page[addr], regnum, &val); pr_debug("rd_PHY(adr=%d, pag=%d, reg=%d) = %d, err = %d\n", addr, priv->page[addr], regnum, val, err); @@ -2042,17 +2125,19 @@ static int rtmdio_83xx_write(struct mii_bus *bus, int addr, int regnum, u16 val) page = priv->page[addr]; - if (addr >= 24 && addr <= 27 && priv->id == 0x8380) - return rtmdio_838x_write_sds(addr, regnum, val); - - if (priv->family_id == RTL8390_FAMILY_ID && priv->phy_is_internal[addr]) - return rtl839x_write_sds_phy(addr, regnum, val); - if (regnum == RTMDIO_PAGE_SELECT) priv->page[addr] = val; if (!priv->raw[addr] && (regnum != RTMDIO_PAGE_SELECT || page == priv->rawpage)) { priv->raw[addr] = (page == priv->rawpage); + if (priv->phy_is_internal[addr] && priv->sds_id[addr] >=0 ) { + if (priv->family_id == RTL8380_FAMILY_ID) + return rtmdio_write_sds_phy(priv, priv->sds_id[addr], + priv->page[addr], regnum, val); + else + return rtl839x_write_sds_phy(addr, regnum, val); + } + err = (*priv->write_phy)(addr, page, regnum, val); pr_debug("wr_PHY(adr=%d, pag=%d, reg=%d, val=%d) err = %d\n", addr, page, regnum, val, err); @@ -2377,6 +2462,8 @@ static int rtl838x_mdio_init(struct rtl838x_eth_priv *priv) priv->mii_bus->read = rtmdio_83xx_read; priv->mii_bus->write = rtmdio_83xx_write; priv->mii_bus->reset = rtmdio_838x_reset; + bus_priv->read_sds_phy = rtmdio_838x_read_sds_phy; + bus_priv->write_sds_phy = rtmdio_838x_write_sds_phy; bus_priv->read_mmd_phy = rtmdio_838x_read_mmd_phy; bus_priv->write_mmd_phy = rtmdio_838x_write_mmd_phy; bus_priv->read_phy = rtmdio_838x_read_phy; diff --git a/target/linux/rockchip/image/armv8.mk b/target/linux/rockchip/image/armv8.mk index ad7084b08d..4a314827dc 100644 --- a/target/linux/rockchip/image/armv8.mk +++ b/target/linux/rockchip/image/armv8.mk @@ -381,7 +381,7 @@ define Device/radxa_rock-pi-e SOC := rk3328 SUPPORTED_DEVICES := radxa,rockpi-e BOOT_FLOW := pine64-bin - DEVICE_PACKAGES := kmod-rtw88-8723du kmod-usb-net-cdc-ncm kmod-usb-net-rndis wpad-openssl + DEVICE_PACKAGES := kmod-rtw88-8723du kmod-rtw88-8821cu kmod-usb-net-cdc-ncm kmod-usb-net-rndis wpad-openssl endef TARGET_DEVICES += radxa_rock-pi-e diff --git a/tools/firmware-utils/Makefile b/tools/firmware-utils/Makefile index 691505c36d..96146637b3 100644 --- a/tools/firmware-utils/Makefile +++ b/tools/firmware-utils/Makefile @@ -11,9 +11,9 @@ PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL=$(PROJECT_GIT)/project/firmware-utils.git -PKG_SOURCE_DATE:=2025-07-24 -PKG_SOURCE_VERSION:=f29de74ecd7d952ee1f9a20577fec9f37c0d6f4a -PKG_MIRROR_HASH:=6abf06a2243acb1a9820a8c4d7c9a1f03f5b920fecf86ac2353d257e7963f7bb +PKG_SOURCE_DATE:=2025-08-03 +PKG_SOURCE_VERSION:=950f83405a935395492d61c9972b5e5ca826eeee +PKG_MIRROR_HASH:=19f5b7547dc6f9460e25183c83d55aca9d1f18148466c09b64504a64379a34ae include $(INCLUDE_DIR)/host-build.mk include $(INCLUDE_DIR)/cmake.mk diff --git a/tools/gnulib/patches/190-stdc-m4-no-cache.patch b/tools/gnulib/patches/190-stdc-m4-no-cache.patch new file mode 100644 index 0000000000..1505da8402 --- /dev/null +++ b/tools/gnulib/patches/190-stdc-m4-no-cache.patch @@ -0,0 +1,59 @@ +--- a/m4/std-gnu11.m4 ++++ b/m4/std-gnu11.m4 +@@ -185,8 +185,7 @@ AC_LANG_POP(C++)dnl + # else ACTION-IF-UNAVAILABLE. + AC_DEFUN([_AC_C_STD_TRY], + [AC_MSG_CHECKING([for $CC option to enable ]m4_translit($1, [c], [C])[ features]) +-AC_CACHE_VAL(ac_cv_prog_cc_$1, +-[ac_cv_prog_cc_$1=no ++ac_cv_prog_cc_$1=no + ac_save_CC=$CC + AC_LANG_CONFTEST([AC_LANG_PROGRAM([$2], [$3])]) + for ac_arg in '' $4 +@@ -197,7 +196,6 @@ do + done + rm -f conftest.$ac_ext + CC=$ac_save_CC +-])# AC_CACHE_VAL + ac_prog_cc_stdc_options= + case "x$ac_cv_prog_cc_$1" in + x) +@@ -523,8 +521,7 @@ fi + AC_DEFUN([_AC_CXX_STD_TRY], + [AC_MSG_CHECKING([for $CXX option to enable ]m4_translit(m4_translit($1, [x], [+]), [a-z], [A-Z])[ features]) + AC_LANG_PUSH(C++)dnl +-AC_CACHE_VAL(ac_cv_prog_cxx_$1, +-[ac_cv_prog_cxx_$1=no ++ac_cv_prog_cxx_$1=no + ac_save_CXX=$CXX + AC_LANG_CONFTEST([AC_LANG_PROGRAM([$2], [$3])]) + for ac_arg in '' $4 +@@ -535,7 +532,6 @@ do + done + rm -f conftest.$ac_ext + CXX=$ac_save_CXX +-])# AC_CACHE_VAL + ac_prog_cxx_stdcxx_options= + case "x$ac_cv_prog_cxx_$1" in + x) +--- a/m4/std-gnu23.m4 ++++ b/m4/std-gnu23.m4 +@@ -696,8 +696,7 @@ AC_DEFUN([_AC_PROG_CC_STDC_EDITION_TRY], + [AC_REQUIRE([_AC_C_C$1_TEST_PROGRAM])]dnl + [AS_IF([test x$ac_prog_cc_stdc = xno], + [AC_MSG_CHECKING([for $CC option to enable C$1 features]) +-AC_CACHE_VAL([ac_cv_prog_cc_c$1], +-[ac_cv_prog_cc_c$1=no ++ac_cv_prog_cc_c$1=no + ac_save_CC=$CC + AC_LANG_CONFTEST([AC_LANG_DEFINES_PROVIDED][$][ac_c_conftest_c$1_program]) + for ac_arg in '' m4_normalize(m4_defn([_AC_C_C$1_OPTIONS])) +@@ -707,7 +706,7 @@ do + test "x$ac_cv_prog_cc_c$1" != "xno" && break + done + rm -f conftest.$ac_ext +-CC=$ac_save_CC]) ++CC=$ac_save_CC + AS_IF([test "x$ac_cv_prog_cc_c$1" = xno], + [AC_MSG_RESULT([unsupported])], + [AS_IF([test "x$ac_cv_prog_cc_c$1" = x],