Initial commit
Some checks failed
Build Kernel / Build all affected Kernels (push) Has been cancelled
Build all core packages / Build all core packages for selected target (push) Has been cancelled
Build and Push prebuilt tools container / Build and Push all prebuilt containers (push) Has been cancelled
Build Toolchains / Build Toolchains for each target (push) Has been cancelled
Build host tools / Build host tools for linux and macos based systems (push) Has been cancelled
Coverity scan build / Coverity x86/64 build (push) Has been cancelled

This commit is contained in:
domenico
2025-06-24 14:35:53 +02:00
commit c06fb25d1f
9263 changed files with 1750214 additions and 0 deletions

View File

@@ -0,0 +1,420 @@
import * as nl80211 from "nl80211";
import * as rtnl from "rtnl";
import { readfile, glob, basename, readlink, open } from "fs";
const iftypes = {
ap: nl80211.const.NL80211_IFTYPE_AP,
mesh: nl80211.const.NL80211_IFTYPE_MESH_POINT,
sta: nl80211.const.NL80211_IFTYPE_STATION,
adhoc: nl80211.const.NL80211_IFTYPE_ADHOC,
monitor: nl80211.const.NL80211_IFTYPE_MONITOR,
};
const mesh_params = {
mesh_retry_timeout: "retry_timeout",
mesh_confirm_timeout: "confirm_timeout",
mesh_holding_timeout: "holding_timeout",
mesh_max_peer_links: "max_peer_links",
mesh_max_retries: "max_retries",
mesh_ttl: "ttl",
mesh_element_ttl: "element_ttl",
mesh_auto_open_plinks: "auto_open_plinks",
mesh_hwmp_max_preq_retries: "hwmp_max_preq_retries",
mesh_path_refresh_time: "path_refresh_time",
mesh_min_discovery_timeout: "min_discovery_timeout",
mesh_hwmp_active_path_timeout: "hwmp_active_path_timeout",
mesh_hwmp_preq_min_interval: "hwmp_preq_min_interval",
mesh_hwmp_net_diameter_traversal_time: "hwmp_net_diam_trvs_time",
mesh_hwmp_rootmode: "hwmp_rootmode",
mesh_hwmp_rann_interval: "hwmp_rann_interval",
mesh_gate_announcements: "gate_announcements",
mesh_sync_offset_max_neighor: "sync_offset_max_neighbor",
mesh_rssi_threshold: "rssi_threshold",
mesh_hwmp_active_path_to_root_timeout: "hwmp_path_to_root_timeout",
mesh_hwmp_root_interval: "hwmp_root_interval",
mesh_hwmp_confirmation_interval: "hwmp_confirmation_interval",
mesh_awake_window: "awake_window",
mesh_plink_timeout: "plink_timeout",
mesh_fwding: "forwarding",
mesh_power_mode: "power_mode",
mesh_nolearn: "nolearn"
};
function wdev_remove(name)
{
nl80211.request(nl80211.const.NL80211_CMD_DEL_INTERFACE, 0, { dev: name });
}
function __phy_is_fullmac(phyidx)
{
let data = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, 0, { wiphy: phyidx });
return !data.software_iftypes.monitor;
}
function phy_is_fullmac(phy)
{
let phyidx = int(trim(readfile(`/sys/class/ieee80211/${phy}/index`)));
return __phy_is_fullmac(phyidx);
}
function find_reusable_wdev(phyidx)
{
if (!__phy_is_fullmac(phyidx))
return null;
let data = nl80211.request(
nl80211.const.NL80211_CMD_GET_INTERFACE,
nl80211.const.NLM_F_DUMP,
{ wiphy: phyidx });
for (let res in data)
if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) == "down")
return res.ifname;
return null;
}
function wdev_set_radio_mask(name, mask)
{
nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, {
dev: name,
vif_radio_mask: mask
});
}
function wdev_create(phy, name, data)
{
let phyidx = int(readfile(`/sys/class/ieee80211/${phy}/index`));
wdev_remove(name);
if (!iftypes[data.mode])
return `Invalid mode: ${data.mode}`;
let req = {
wiphy: phyidx,
ifname: name,
iftype: iftypes[data.mode],
};
if (data["4addr"])
req["4addr"] = data["4addr"];
if (data.macaddr)
req.mac = data.macaddr;
if (data.radio != null && data.radio >= 0)
req.vif_radio_mask = 1 << data.radio;
nl80211.error();
let reuse_ifname = find_reusable_wdev(phyidx);
if (reuse_ifname &&
(reuse_ifname == name ||
rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false)) {
req.dev = req.ifname;
delete req.ifname;
nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, req);
} else {
nl80211.request(
nl80211.const.NL80211_CMD_NEW_INTERFACE,
nl80211.const.NLM_F_CREATE,
req);
}
let error = nl80211.error();
if (error)
return error;
if (data.powersave != null) {
nl80211.request(nl80211.const.NL80211_CMD_SET_POWER_SAVE, 0,
{ dev: name, ps_state: data.powersave ? 1 : 0});
}
return null;
}
function wdev_set_mesh_params(name, data)
{
let mesh_cfg = {};
for (let key in mesh_params) {
let val = data[key];
if (val == null)
continue;
mesh_cfg[mesh_params[key]] = int(val);
}
if (!length(mesh_cfg))
return null;
nl80211.request(nl80211.const.NL80211_CMD_SET_MESH_CONFIG, 0,
{ dev: name, mesh_params: mesh_cfg });
return nl80211.error();
}
function wdev_set_up(name, up)
{
rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: name, change: 1, flags: up ? 1 : 0 });
}
function phy_sysfs_file(phy, name)
{
return trim(readfile(`/sys/class/ieee80211/${phy}/${name}`));
}
function macaddr_split(str)
{
return map(split(str, ":"), (val) => hex(val));
}
function macaddr_join(addr)
{
return join(":", map(addr, (val) => sprintf("%02x", val)));
}
function wdev_macaddr(wdev)
{
return trim(readfile(`/sys/class/net/${wdev}/address`));
}
const phy_proto = {
macaddr_init: function(used, options) {
this.macaddr_options = options ?? {};
this.macaddr_list = {};
if (type(used) == "object")
for (let addr in used)
this.macaddr_list[addr] = used[addr];
else
for (let addr in used)
this.macaddr_list[addr] = -1;
this.for_each_wdev((wdev) => {
let macaddr = wdev_macaddr(wdev);
this.macaddr_list[macaddr] ??= -1;
});
return this.macaddr_list;
},
macaddr_generate: function(data) {
let phy = this.phy;
let radio_idx = this.radio;
let idx = int(data.id ?? 0);
let mbssid = int(data.mbssid ?? 0) > 0;
let num_global = int(data.num_global ?? 1);
let use_global = !mbssid && idx < num_global;
let base_addr = phy_sysfs_file(phy, "macaddress");
if (!base_addr)
return null;
let base_mask = phy_sysfs_file(phy, "address_mask");
if (!base_mask)
return null;
if (data.macaddr_base)
base_addr = data.macaddr_base;
else if (base_mask == "00:00:00:00:00:00" &&
(radio_idx > 0 || idx >= num_global)) {
let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
if (radio_idx != null) {
if (radio_idx && radio_idx < length(addrs))
base_addr = addrs[radio_idx];
else
idx += radio_idx * 16;
} else {
if (idx < length(addrs))
return addrs[idx];
base_mask = "ff:ff:ff:ff:ff:ff";
}
}
if (!idx && !mbssid)
return base_addr;
let addr = macaddr_split(base_addr);
let mask = macaddr_split(base_mask);
let type;
if (mbssid)
type = "b5";
else if (use_global)
type = "add";
else if (mask[0] > 0)
type = "b1";
else if (mask[5] < 0xff)
type = "b5";
else
type = "add";
switch (type) {
case "b1":
if (!(addr[0] & 2))
idx--;
addr[0] |= 2;
addr[0] ^= idx << 2;
break;
case "b5":
if (mbssid)
addr[0] |= 2;
addr[5] ^= idx;
break;
default:
for (let i = 5; i > 0; i--) {
addr[i] += idx;
if (addr[i] < 256)
break;
addr[i] %= 256;
}
break;
}
return macaddr_join(addr);
},
macaddr_next: function(val) {
let data = this.macaddr_options ?? {};
let list = this.macaddr_list;
for (let i = 0; i < 32; i++) {
data.id = i;
let mac = this.macaddr_generate(data);
if (!mac)
return null;
if (list[mac] != null)
continue;
list[mac] = val != null ? val : -1;
return mac;
}
},
wdev_add: function(name, data) {
let phydev = this;
wdev_create(this.phy, name, {
...data,
radio: this.radio,
});
},
for_each_wdev: function(cb) {
let wdevs = nl80211.request(
nl80211.const.NL80211_CMD_GET_INTERFACE,
nl80211.const.NLM_F_DUMP,
{ wiphy: this.idx }
);
let mac_wdev = {};
for (let wdev in wdevs) {
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)))
continue;
mac_wdev[wdev.mac] = wdev;
}
for (let wdev in wdevs) {
if (!mac_wdev[wdev.mac])
continue;
cb(wdev.ifname);
}
}
};
function phy_open(phy, radio)
{
let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
if (!phyidx)
return null;
let name = phy;
if (radio === "" || radio < 0)
radio = null;
if (radio != null)
name += "." + radio;
return proto({
phy, name, radio,
idx: int(phyidx),
}, phy_proto);
}
const vlist_proto = {
update: function(values, arg) {
let data = this.data;
let cb = this.cb;
let seq = { };
let new_data = {};
let old_data = {};
this.data = new_data;
if (type(values) == "object") {
for (let key in values) {
old_data[key] = data[key];
new_data[key] = values[key];
delete data[key];
}
} else {
for (let val in values) {
let cur_key = val[0];
let cur_obj = val[1];
old_data[cur_key] = data[cur_key];
new_data[cur_key] = val[1];
delete data[cur_key];
}
}
for (let key in data) {
cb(null, data[key], arg);
delete data[key];
}
for (let key in new_data)
cb(new_data[key], old_data[key], arg);
}
};
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;
}
}
function vlist_new(cb) {
return proto({
cb: cb,
data: {}
}, vlist_proto);
}
export { wdev_remove, wdev_create, wdev_set_mesh_params, wdev_set_radio_mask, wdev_set_up, is_equal, vlist_new, phy_is_fullmac, phy_open };

View File

@@ -0,0 +1,183 @@
#!/usr/bin/env ucode
'use strict';
import { vlist_new, is_equal, wdev_set_mesh_params, wdev_remove, wdev_set_up, phy_open } from "/usr/share/hostap/common.uc";
import { readfile, writefile, basename, readlink, glob } from "fs";
let libubus = require("ubus");
let keep_devices = {};
let phy_name = shift(ARGV);
let command = shift(ARGV);
let phy, phydev;
function iface_stop(wdev)
{
if (keep_devices[wdev.ifname])
return;
wdev_remove(wdev.ifname);
}
function iface_start(wdev)
{
let ifname = wdev.ifname;
if (readfile(`/sys/class/net/${ifname}/ifindex`)) {
wdev_set_up(ifname, false);
wdev_remove(ifname);
}
let wdev_config = {};
for (let key in wdev)
wdev_config[key] = wdev[key];
if (!wdev_config.macaddr && wdev.mode != "monitor")
wdev_config.macaddr = phydev.macaddr_next();
phydev.wdev_add(ifname, wdev_config);
wdev_set_up(ifname, true);
let htmode = wdev.htmode || "NOHT";
if (wdev.freq)
system(`iw dev ${ifname} set freq ${wdev.freq} ${htmode}`);
if (wdev.mode == "adhoc") {
let cmd = ["iw", "dev", ifname, "ibss", "join", wdev.ssid, wdev.freq, htmode, "fixed-freq" ];
if (wdev.bssid)
push(cmd, wdev.bssid);
for (let key in [ "beacon-interval", "basic-rates", "mcast-rate", "keys" ])
if (wdev[key])
push(cmd, key, wdev[key]);
system(cmd);
} else if (wdev.mode == "mesh") {
let cmd = [ "iw", "dev", ifname, "mesh", "join", wdev.ssid, "freq", wdev.freq, htmode ];
for (let key in [ "mcast-rate", "beacon-interval" ])
if (wdev[key])
push(cmd, key, wdev[key]);
system(cmd);
wdev_set_mesh_params(ifname, wdev);
}
}
function iface_cb(new_if, old_if)
{
if (old_if && new_if && is_equal(old_if, new_if))
return;
if (old_if)
iface_stop(old_if);
if (new_if)
iface_start(new_if);
}
function drop_inactive(config)
{
for (let key in config) {
if (!readfile(`/sys/class/net/${key}/ifindex`))
delete config[key];
}
}
function add_ifname(config)
{
for (let key in config)
config[key].ifname = key;
}
function delete_ifname(config)
{
for (let key in config)
delete config[key].ifname;
}
function add_existing(phydev, config)
{
phydev.for_each_wdev((wdev) => {
if (config[wdev])
return;
if (trim(readfile(`/sys/class/net/${wdev}/operstate`)) == "down")
config[wdev] = {};
});
}
function usage()
{
warn(`Usage: ${basename(sourcepath())} <phy> <command> [<arguments>]
Commands:
set_config <config> [<device]...] - set phy configuration
get_macaddr <id> - get phy MAC address for vif index <id>
`);
exit(1);
}
const commands = {
set_config: function(args) {
let statefile = `/var/run/wdev-${phy_name}.json`;
let new_config = shift(args);
for (let dev in ARGV)
keep_devices[dev] = true;
if (!new_config)
usage();
new_config = json(new_config);
if (!new_config) {
warn("Invalid configuration\n");
exit(1);
}
let old_config = readfile(statefile);
if (old_config)
old_config = json(old_config);
let config = vlist_new(iface_cb);
if (type(old_config) == "object")
config.data = old_config;
add_existing(phydev, config.data);
add_ifname(config.data);
drop_inactive(config.data);
let ubus = libubus.connect();
let data = ubus.call("hostapd", "config_get_macaddr_list", { phy: phydev.name, radio: phydev.radio ?? -1 });
let macaddr_list = [];
if (type(data) == "object" && data.macaddr)
macaddr_list = data.macaddr;
ubus.disconnect();
phydev.macaddr_init(macaddr_list);
add_ifname(new_config);
config.update(new_config);
drop_inactive(config.data);
delete_ifname(config.data);
writefile(statefile, sprintf("%J", config.data));
},
get_macaddr: function(args) {
let data = {};
for (let arg in args) {
arg = split(arg, "=", 2);
data[arg[0]] = arg[1];
}
let macaddr = phydev.macaddr_generate(data);
if (!macaddr) {
warn(`Could not get MAC address for phy ${phy_name}\n`);
exit(1);
}
print(macaddr + "\n");
},
};
if (!phy_name || !command | !commands[command])
usage();
let phy_split = split(phy_name, ":");
phydev = phy_open(phy_split[0], phy_split[1]);
phy = phydev.phy;
if (!phydev) {
warn(`PHY ${phy_name} does not exist\n`);
exit(1);
}
commands[command](ARGV);

View File

@@ -0,0 +1,260 @@
#!/usr/bin/env ucode
'use strict';
import { readfile, writefile, realpath, glob, basename, unlink, open, rename } from "fs";
import { is_equal } from "/usr/share/hostap/common.uc";
let nl = require("nl80211");
let board_file = "/etc/board.json";
let prev_board_data = json(readfile(board_file));
let board_data = json(readfile(board_file));
function phy_idx(name) {
return +rtrim(readfile(`/sys/class/ieee80211/${name}/index`));
}
function phy_path(name) {
let devpath = realpath(`/sys/class/ieee80211/${name}/device`);
devpath = replace(devpath, /^\/sys\/devices\//, "");
if (match(devpath, /^platform\/.*\/pci/))
devpath = replace(devpath, /^platform\//, "");
let dev_phys = map(glob(`/sys/class/ieee80211/${name}/device/ieee80211/*`), basename);
sort(dev_phys, (a, b) => phy_idx(a) - phy_idx(b));
let ofs = index(dev_phys, name);
if (ofs > 0)
devpath += `+${ofs}`;
return devpath;
}
function cleanup() {
let wlan = board_data.wlan;
for (let name in wlan)
if (substr(name, 0, 3) == "phy")
delete wlan[name];
else
delete wlan[name].info;
}
function wiphy_get_entry(phy, path) {
board_data.wlan ??= {};
let wlan = board_data.wlan;
for (let name in wlan)
if (wlan[name].path == path)
return wlan[name];
wlan[phy] = {
path: path
};
return wlan[phy];
}
function freq_to_channel(freq) {
if (freq < 1000)
return 0;
if (freq == 2484)
return 14;
if (freq == 5935)
return 2;
if (freq < 2484)
return (freq - 2407) / 5;
if (freq >= 4910 && freq <= 4980)
return (freq - 4000) / 5;
if (freq < 5950)
return (freq - 5000) / 5;
if (freq <= 45000)
return (freq - 5950) / 5;
if (freq >= 58320 && freq <= 70200)
return (freq - 56160) / 2160;
return 0;
}
function freq_range_match(ranges, freq) {
freq *= 1000;
for (let range in ranges) {
if (freq >= range[0] && freq <= range[1])
return true;
}
return false;
}
function wiphy_detect() {
let phys = nl.request(nl.const.NL80211_CMD_GET_WIPHY, nl.const.NLM_F_DUMP, { split_wiphy_dump: true });
if (!phys)
return;
for (let phy in phys) {
if (!phy)
continue;
let name = phy.wiphy_name;
let path = phy_path(name);
let info = {
antenna_rx: phy.wiphy_antenna_avail_rx,
antenna_tx: phy.wiphy_antenna_avail_tx,
bands: {},
radios: []
};
for (let radio in phy.radios) {
// S1G is not supported yet
radio.freq_ranges = filter(radio.freq_ranges,
(range) => range.end > 2000000
);
if (!length(radio.freq_ranges))
continue;
push(info.radios, {
index: radio.index,
freq_ranges: map(radio.freq_ranges,
(range) => [ range.start, range.end ]
),
bands: {}
});
}
let bands = info.bands;
for (let band in phy.wiphy_bands) {
if (!band || !band.freqs)
continue;
let freq = band.freqs[0].freq;
let band_info = {};
let band_name;
if (freq > 50000)
band_name = "60G";
else if (freq > 5900)
band_name = "6G";
else if (freq > 4000)
band_name = "5G";
else if (freq > 2000)
band_name = "2G";
else
continue;
bands[band_name] = band_info;
if (band.ht_capa > 0)
band_info.ht = true;
if (band.vht_capa > 0)
band_info.vht = true;
let he_phy_cap = 0;
let eht_phy_cap = 0;
for (let ift in band.iftype_data) {
if (!ift.he_cap_phy)
continue;
band_info.he = true;
he_phy_cap |= ift.he_cap_phy[0];
if (!ift.eht_cap_phy)
continue;
band_info.eht = true;
eht_phy_cap |= ift.eht_cap_phy[0];
}
if (band_name != "2G" &&
(he_phy_cap & 0x18) || ((band.vht_capa >> 2) & 0x3))
band_info.max_width = 160;
else if (band_name != "2G" &&
(he_phy_cap & 4) || band.vht_capa > 0)
band_info.max_width = 80;
else if ((band.ht_capa & 0x2) || (he_phy_cap & 0x2))
band_info.max_width = 40;
else
band_info.max_width = 20;
let modes = band_info.modes = [ "NOHT" ];
if (band_info.ht)
push(modes, "HT20");
if (band_info.vht)
push(modes, "VHT20");
if (band_info.he)
push(modes, "HE20");
if (band_info.eht)
push(modes, "EHT20");
if (band.ht_capa & 0x2) {
push(modes, "HT40");
if (band_info.vht)
push(modes, "VHT40")
}
if (he_phy_cap & 2)
push(modes, "HE40");
if (eht_phy_cap && he_phy_cap & 2)
push(modes, "EHT40");
for (let radio in info.radios) {
let freq_match = filter(band.freqs,
(freq) => freq_range_match(radio.freq_ranges, freq.freq)
);
if (!length(freq_match))
continue;
let radio_band = {};
radio.bands[band_name] = radio_band;
freq_match = filter(freq_match,
(freq) => !freq.disabled
);
let freq = freq_match[0];
if (freq)
radio_band.default_channel = freq_to_channel(freq.freq);
}
for (let freq in band.freqs) {
if (freq.disabled)
continue;
let chan = freq_to_channel(freq.freq);
if (!chan)
continue;
band_info.default_channel = chan;
break;
}
if (band_name == "2G")
continue;
if (he_phy_cap & 4)
push(modes, "HE40");
if (eht_phy_cap && he_phy_cap & 4)
push(modes, "EHT40");
if (band_info.vht)
push(modes, "VHT80");
if (he_phy_cap & 4)
push(modes, "HE80");
if (eht_phy_cap && he_phy_cap & 4)
push(modes, "EHT80");
if ((band.vht_capa >> 2) & 0x3)
push(modes, "VHT160");
if (he_phy_cap & 0x18)
push(modes, "HE160");
if (eht_phy_cap && he_phy_cap & 0x18)
push(modes, "EHT160");
if (eht_phy_cap & 2)
push(modes, "EHT320");
}
let entry = wiphy_get_entry(name, path);
entry.info = info;
}
}
cleanup();
wiphy_detect();
if (!is_equal(prev_board_data, board_data)) {
let new_file = board_file + ".new";
unlink(new_file);
let f = open(new_file, "wx");
if (!f)
exit(1);
f.write(sprintf("%.J\n", board_data));
f.close();
rename(new_file, board_file);
}