commit f9ab0590a67483cc9668557fca6d2ce1f5829f45
parent 84fa8d65f4d4fc1acf96e3883693237b0ed1aea1
Author: Daniel Barlow <dan@telent.net>
Date: Wed, 21 Feb 2024 19:27:23 +0000
Merge remote-tracking branch 'raito/nwa50ax'
Diffstat:
19 files changed, 1172 insertions(+), 23 deletions(-)
diff --git a/default.nix b/default.nix
@@ -3,6 +3,7 @@
, liminix-config ? <liminix-config>
, nixpkgs ? <nixpkgs>
, borderVmConf ? ./bordervm.conf.nix
+, imageType ? "primary"
}:
let
@@ -30,6 +31,9 @@ let
./modules/s6
./modules/users.nix
./modules/outputs.nix
+ {
+ boot.imageType = imageType;
+ }
];
};
config = eval.config;
diff --git a/devices/zyxel-nwa50ax/a_image/mt7621_zyxel_nwa-ax-for-ab.dtsi b/devices/zyxel-nwa50ax/a_image/mt7621_zyxel_nwa-ax-for-ab.dtsi
@@ -0,0 +1,155 @@
+#include "mt7621.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+/ {
+ aliases {
+ label-mac-device = &gmac0;
+ };
+};
+
+&nand {
+ status = "okay";
+
+ mediatek,nmbm;
+ mediatek,bmt-max-ratio = <15>;
+ mediatek,bmt-max-reserved-blocks = <64>;
+ mediatek,bmt-remap-range =
+ <0x0 0x980000>,
+ <0x2980000 0x7800000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x80000>;
+ read-only;
+ };
+
+ partition@80000 {
+ label = "u-boot-env";
+ reg = <0x80000 0x80000>;
+ read-only;
+ };
+
+ factory: partition@100000 {
+ label = "factory";
+ reg = <0x100000 0x80000>;
+ read-only;
+ };
+
+ partition@180000 {
+ label = "firmware_a";
+ reg = <0x180000 0x2800000>;
+
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "kernel_a";
+ reg = <0x0 0x800000>;
+ };
+
+ partition@400000 {
+ label = "ubi";
+ reg = <0x800000 0x2000000>;
+ };
+ };
+
+ partition@2980000 {
+ label = "firmware_b";
+ reg = <0x2980000 0x2800000>;
+
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "kernel_b";
+ reg = <0x0 0x800000>;
+ };
+
+ partition@400000 {
+ label = "ubi_b";
+ reg = <0x800000 0x2000000>;
+ };
+ };
+
+ partition@5180000 {
+ label = "rootfs_data";
+ reg = <0x5180000 0x1400000>;
+ };
+
+ partition@6580000 {
+ label = "logs";
+ reg = <0x6580000 0xd00000>;
+ };
+
+ partition@7280000 {
+ label = "vendor-myzyxel";
+ reg = <0x7280000 0x480000>;
+ read-only;
+ };
+
+ partition@7700000 {
+ label = "bootconfig";
+ reg = <0x7700000 0x80000>;
+ };
+
+ mrd: partition@7780000 {
+ label = "mrd";
+ reg = <0x7780000 0x80000>;
+ read-only;
+
+ nvmem-layout {
+ compatible = "fixed-layout";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ macaddr_mrd_1fff8: macaddr@1fff8 {
+ reg = <0x1fff8 0x6>;
+ };
+ };
+ };
+ };
+};
+
+&pcie {
+ status = "okay";
+};
+
+&pcie1 {
+ wlan_5g: wifi@0,0 {
+ reg = <0x0 0 0 0 0>;
+ compatible = "mediatek,mt76";
+
+ mediatek,mtd-eeprom = <&factory 0x0>;
+ /* MAC-Address set in userspace */
+ };
+};
+
+&gmac0 {
+ nvmem-cells = <&macaddr_mrd_1fff8>;
+ nvmem-cell-names = "mac-address";
+};
+
+&switch0 {
+ ports {
+ port@4 {
+ status = "okay";
+ label = "lan";
+ };
+ };
+};
+
+&state_default {
+ gpio {
+ groups = "uart3";
+ function = "gpio";
+ };
+};
diff --git a/devices/zyxel-nwa50ax/b_image/mt7621_zyxel_nwa-ax-for-ab.dtsi b/devices/zyxel-nwa50ax/b_image/mt7621_zyxel_nwa-ax-for-ab.dtsi
@@ -0,0 +1,155 @@
+#include "mt7621.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+/ {
+ aliases {
+ label-mac-device = &gmac0;
+ };
+};
+
+&nand {
+ status = "okay";
+
+ mediatek,nmbm;
+ mediatek,bmt-max-ratio = <15>;
+ mediatek,bmt-max-reserved-blocks = <64>;
+ mediatek,bmt-remap-range =
+ <0x0 0x980000>,
+ <0x2980000 0x7800000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x80000>;
+ read-only;
+ };
+
+ partition@80000 {
+ label = "u-boot-env";
+ reg = <0x80000 0x80000>;
+ read-only;
+ };
+
+ factory: partition@100000 {
+ label = "factory";
+ reg = <0x100000 0x80000>;
+ read-only;
+ };
+
+ partition@2980000 {
+ label = "firmware_b";
+ reg = <0x2980000 0x2800000>;
+
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "kernel_b";
+ reg = <0x0 0x800000>;
+ };
+
+ partition@400000 {
+ label = "ubi";
+ reg = <0x800000 0x2000000>;
+ };
+ };
+
+ partition@180000 {
+ label = "firmware_a";
+ reg = <0x180000 0x2800000>;
+
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "kernel_a";
+ reg = <0x0 0x800000>;
+ };
+
+ partition@400000 {
+ label = "ubi_a";
+ reg = <0x800000 0x2000000>;
+ };
+ };
+
+ partition@5180000 {
+ label = "rootfs_data";
+ reg = <0x5180000 0x1400000>;
+ };
+
+ partition@6580000 {
+ label = "logs";
+ reg = <0x6580000 0xd00000>;
+ };
+
+ partition@7280000 {
+ label = "vendor-myzyxel";
+ reg = <0x7280000 0x480000>;
+ read-only;
+ };
+
+ partition@7700000 {
+ label = "bootconfig";
+ reg = <0x7700000 0x80000>;
+ };
+
+ mrd: partition@7780000 {
+ label = "mrd";
+ reg = <0x7780000 0x80000>;
+ read-only;
+
+ nvmem-layout {
+ compatible = "fixed-layout";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ macaddr_mrd_1fff8: macaddr@1fff8 {
+ reg = <0x1fff8 0x6>;
+ };
+ };
+ };
+ };
+};
+
+&pcie {
+ status = "okay";
+};
+
+&pcie1 {
+ wlan_5g: wifi@0,0 {
+ reg = <0x0 0 0 0 0>;
+ compatible = "mediatek,mt76";
+
+ mediatek,mtd-eeprom = <&factory 0x0>;
+ /* MAC-Address set in userspace */
+ };
+};
+
+&gmac0 {
+ nvmem-cells = <&macaddr_mrd_1fff8>;
+ nvmem-cell-names = "mac-address";
+};
+
+&switch0 {
+ ports {
+ port@4 {
+ status = "okay";
+ label = "lan";
+ };
+ };
+};
+
+&state_default {
+ gpio {
+ groups = "uart3";
+ function = "gpio";
+ };
+};
diff --git a/devices/zyxel-nwa50ax/default.nix b/devices/zyxel-nwa50ax/default.nix
@@ -0,0 +1,367 @@
+{
+ system = {
+ crossSystem = {
+ config = "mipsel-unknown-linux-musl";
+ gcc = {
+ abi = "32";
+ arch = "mips32"; # mips32r2?
+ };
+ };
+ };
+
+ description = ''
+ Zyxel NWA50AX
+ ********************
+
+ Zyxel NWA50AX is quite close to the GL-MT300N-v2 "Mango" device, but it is based on the MT7621
+ chipset instead of the MT7628.
+
+ Installation
+ ============
+
+ This device is pretty, but, due to its A/B capabilities, can be a bit hard
+ to use completely.
+
+ The stock vendor firmware is a downstream fork of U-Boot: <https://github.com/RaitoBezarius/uboot-nwa50ax>
+ with restricted boot commands. Fortunately, OpenWrt folks figured out trivial command injections,
+ so you can use most of the OpenWrt commands without trouble by just command injecting
+ atns, atna or atnf, e.g. atns "; $real_command".
+
+ From factory web UI, you can upload the result of the zyxel-nwa-fit output.
+ From another operating system, you need to `dumpimage -T flat_dt -p 0 $zyxel-nwa-fit -o firmware.bin`,
+ `flash_erase $(mtd partition of the target partition firmware or zy_firmware) 0 0`, then you complete by
+ `nandwrite -p $(mtd partition of the target partition firmware or zy_firmware) firmware.bin`.
+
+ How to put the firmware.bin on the machine is left to you as an exercise, e.g. SSH, TFTP, whatever.
+
+ From serial, you have two choices:
+
+ - Flash this system via U-Boot:
+ same reasoning as from an existing Linux system, two choices:
+ - ymodem the binary, perform the write manually, you can inspire yourself
+ from the `script` contained in the vendor firmware, those are just a FIT containing a script.
+ - prepare a FIT containing a script executing your commands, tftpboot this.
+
+ - boot from an existing Liminix system, e.g. TFTPBOOT image.
+ - boot from an OpenWrt system, i.e. follow OpenWrt steps.
+
+ Once you are in a Linux system, understand that this device has A/B boot.
+
+ OpenWrt provides you with `zyxel-bootconfig` to set/unset the image status and choice.
+
+ The kernel is booted with `bootImage=<number>` which tells you which slot are you on.
+
+ You should find yourself with 10ish MTD partitions, the most interesting ones are two:
+
+ - firmware: 40MB
+ - firmware_1: 40MB
+
+ In the current setup, they are split further into kernel (8MB) and ubi (32MB).
+
+ Once you are done with first installation, note that if you want to use the A/B feature,
+ you need to write a _secondary_ image on the slot B. There is no proper flashing code
+ that will set the being-updated slot to `new` and boot on it to verify if it's working.
+ This is a WIP.
+
+ Upgrading your system can be achieved via:
+
+ - `liminix-rebuild` for the userspace.
+ - `flash_erase` + `nandwrite` for the kernelspace to the other slot than the one you are booted on,
+ note that you can just nandwrite the mtd partition corresponding to the *kernel* and not the whole firmware.
+
+ If you soft-bricked your AP, i.e. you cannot boot anything in U-Boot, no worries, just plug the serial console,
+ prepare a TFTP server (via `tufted` for example), download vendor firmware, set up `atns`, `atnf`, etc. and run `atnz`.
+
+ This will reflash everything back to normal via TFTP.
+
+ If you hard-bricked your AP, i.e. U-Boot is telling you to transfer a valid bootloader via ymodem, just extract
+ a U-Boot from the vendor OS, send it via ymodem and use the previous operations to perform a full flash this time
+ of all partitions.
+
+ Note that if you erased your MRD partition, you lost your serial and MAC address. There's no way to recover the original one
+ except by reading the physical label on your… device!
+
+ If you super-hard-bricked your AP, i.e. no output on serial console, congratulations, you reached one of the rare state
+ of this device. You need an external NAND flasher to repair it and write the first stage from Mediatek to continue the previous
+ recovery operations.
+
+ Development TODO list:
+
+ - Better support for upgrade automation w.r.t. to A/B, e.g. automagic scripts.
+ - Mount the logs partition, mount / as overlayfs of firmware ? rootfs and rootfs_data for extended data.
+ - Jitter-based entropy injection? Device can be slow to initialize its CRNG and hostapd will reject few clients at the start because of that.
+ - Defaults for hostapd based on MT7915 capabilities? See the example for one possible list.
+ - Remove primary/secondary hack and put it in preinit.
+ - Offer ways to reflash the *bootloader* itself to support direct boot via UBI and kernel upgrades via filesystem rewrite.
+
+ Vendor web page: https://www.zyxel.com/fr/fr/products/wireless/ax1800-wifi-6-dual-radio-nebulaflex-access-point-nwa50ax
+
+ OpenWrt web page: https://openwrt.org/inbox/toh/zyxel/nwa50ax
+ OpenWrt tech data: https://openwrt.org/toh/hwdata/zyxel/zyxel_nwa50ax
+
+ '';
+
+ module = { pkgs, config, lib, lim, ...}:
+ let
+ inherit (pkgs.liminix.networking) interface;
+ inherit (pkgs.liminix.services) oneshot;
+ inherit (pkgs.pseudofile) dir symlink;
+ inherit (pkgs) openwrt;
+
+ mac80211 = pkgs.mac80211.override {
+ drivers = [ "mt7915e" ];
+ klibBuild = config.system.outputs.kernel.modulesupport;
+ };
+ # v204520220929
+ wlan_firmware = pkgs.fetchurl {
+ url = "https://github.com/openwrt/mt76/raw/1b88dd07f153b202e57fe29734806744ed006b0e/firmware/mt7915_wa.bin";
+ hash = "sha256-wooyefzb0i8640+lwq3vNhcBXRFCtGuo+jiL7afZaKA=";
+ };
+ wlan_firmware' = pkgs.fetchurl {
+ url = "https://github.com/openwrt/mt76/raw/1b88dd07f153b202e57fe29734806744ed006b0e/firmware/mt7915_wm.bin";
+ hash = "sha256-k62nQewRuKjBLd5R3RxU4F74YKnQx5zr6gqMMImqVQw=";
+ };
+ wlan_firmware'' = pkgs.fetchurl {
+ url = "https://github.com/openwrt/mt76/raw/1b88dd07f153b202e57fe29734806744ed006b0e/firmware/mt7915_rom_patch.bin";
+ hash = "sha256-ifriAjWzFACrxVWCANZpUaEZgB/0pdbhnTVQytx6ddg=";
+ };
+ in {
+ imports = [
+ # We include it to ensure the bridge functionality
+ # is available on the target kernel.
+ ../../modules/bridge
+ ../../modules/arch/mipsel.nix
+ ../../modules/outputs/tftpboot.nix
+ ../../modules/outputs/zyxel-nwa-fit.nix
+ ../../modules/zyxel-dual-image
+ ];
+
+ filesystem = dir {
+ lib = dir {
+ firmware = dir {
+ mediatek = dir {
+ "mt7915_wa.bin" = symlink wlan_firmware;
+ "mt7915_wm.bin" = symlink wlan_firmware';
+ "mt7915_rom_patch.bin" = symlink wlan_firmware'';
+ };
+ };
+ };
+ };
+
+ rootfsType = "ubifs";
+ hardware = {
+ # Taken from OpenWRT
+ # root@OpenWrt:/# ubinfo /dev/ubi0
+ # ubi0
+ # Volumes count: 2
+ # Logical eraseblock size: 126976 bytes, 124.0 KiB
+ # Total amount of logical eraseblocks: 256 (32505856 bytes, 31.0 MiB)
+ # Amount of available logical eraseblocks: 0 (0 bytes)
+ # Maximum count of volumes 128
+ # Count of bad physical eraseblocks: 0
+ # Count of reserved physical eraseblocks: 19
+ # Current maximum erase counter value: 2
+ # Minimum input/output unit size: 2048 bytes
+ # Character device major/minor: 250:0
+ # Present volumes: 0, 1
+ ubi = {
+ minIOSize = "2048";
+ logicalEraseBlockSize = "126976";
+ physicalEraseBlockSize = "128KiB";
+ maxLEBcount = "256";
+ };
+
+ # This is a FIT containing a kernel padded and
+ # a UBI volume rootfs.
+ defaultOutput = "zyxel-nwa-fit";
+
+ loadAddress = lim.parseInt "0x80001000";
+ entryPoint = lim.parseInt "0x80001000";
+ # Aligned on 2kb.
+ alignment = 2048;
+
+ rootDevice = "ubi:rootfs";
+
+ dts = {
+ # Actually, this is not what we want.
+ # This DTS is insufficient.
+ src = ./mt7621_zyxel_nwa50ax.dtsi;
+ includes = [
+ # Here's one weird trick to make `ubi` detection
+ # out of the box.
+ # We will write ubi on /dev/firmware_a:rootfs location
+ # and same for /dev/firmware_b:rootfs.
+ # How do we distinguish both?
+ # We can just use the DTS to point ubi at A or B.
+ # This, unfortunately, means that we have "two images".
+ # But they are really just 1 image with 2 different DTS.
+ # TODO: improve this hack in preinit?
+ (if config.boot.imageType == "primary" then "${./a_image}" else "${./b_image}")
+ "${openwrt.src}/target/linux/ramips/dts"
+ ];
+ };
+ networkInterfaces =
+ let
+ inherit (config.system.service.network) link;
+ in {
+ eth = link.build { ifname = "eth0"; };
+ lan = link.build { ifname = "lan"; };
+ wlan0 = link.build {
+ ifname = "wlan0";
+ dependencies = [ mac80211 ];
+ };
+ wlan1 = link.build {
+ ifname = "wlan1";
+ dependencies = [ mac80211 ];
+ };
+ };
+ };
+
+ boot = {
+ # Critical because NWA50AX will extend your cmdline with the image number booted.
+ # and some bootloader version.
+ # You don't want to find yourself being overridden.
+ commandLineDtbNode = "bootargs-override";
+
+ imageFormat = "fit";
+ tftp = {
+ # 5MB is nice.
+ freeSpaceBytes = 5 * 1024 * 1024;
+ loadAddress = lim.parseInt "0x2000000";
+ };
+ };
+
+ # Dual image management service in userspace.
+ services.zyxel-dual-image = config.boot.zyxel-dual-image.build {
+ ensureActiveImage = "primary";
+ # TODO: use mtd names rather…
+ # primary and secondary are always /dev/mtd3 by virtue of the
+ # dtb being not too wrong…
+ # TODO: remove this hack.
+ primaryMtdPartition = "/dev/mtd3";
+ secondaryMtdPartition = "/dev/mtd3";
+ bootConfigurationMtdPartition = "/dev/mtd12";
+ };
+
+ # DEVICE_VENDOR := ZyXEL
+ # KERNEL_SIZE := 8192k
+ # DEVICE_PACKAGES := kmod-mt7915-firmware zyxel-bootconfig
+ # KERNEL := kernel-bin | lzma | fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb
+ # IMAGES += factory.bin ramboot-factory.bin
+ # IMAGE/factory.bin := append-kernel | pad-to $$(KERNEL_SIZE) | append-ubi | zyxel-nwa-fit
+ # IMAGE/ramboot-factory.bin := append-kernel | pad-to $$(KERNEL_SIZE) | append-ubi
+
+ kernel = {
+ src = pkgs.fetchurl {
+ name = "linux.tar.gz";
+ url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz";
+ hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ=";
+ };
+ extraPatchPhase = ''
+ ${openwrt.applyPatches.ramips}
+
+ '';
+ config = {
+
+ RALINK = "y";
+ PCI = "y";
+ PHY_MT7621_PCI = "y";
+ PCIE_MT7621 = "y";
+ SOC_MT7621 = "y";
+ CLK_MT7621 = "y";
+ CLOCKSOURCE_WATCHDOG = "y";
+
+ SERIAL_8250_CONSOLE = "y";
+ SERIAL_8250 = "y";
+ SERIAL_CORE_CONSOLE = "y";
+ SERIAL_OF_PLATFORM = "y";
+ SERIAL_8250_NR_UARTS = "3";
+ SERIAL_8250_RUNTIME_UARTS = "3";
+ SERIAL_MCTRL_GPIO = "y";
+
+ CONSOLE_LOGLEVEL_DEFAULT = "8";
+ CONSOLE_LOGLEVEL_QUIET = "4";
+
+ # MTD_UBI_BEB_LIMIT = "20";
+ # MTD_UBI_WL_THRESHOLD = "4096";
+
+ MTD = "y";
+ MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_dev
+ MTD_RAW_NAND = "y";
+ MTD_NAND_MT7621 = "y";
+ MTD_NAND_MTK_BMT = "y"; # Bad-block Management Table
+ MTD_NAND_ECC_SW_HAMMING= "y";
+ MTD_SPI_NAND= "y";
+ MTD_OF_PARTS = "y";
+ MTD_NAND_CORE= "y";
+ MTD_SPLIT_FIRMWARE= "y";
+ MTD_SPLIT_FIT_FW= "y";
+
+ PINCTRL = "y";
+ PINCTRL_MT7621 = "y";
+
+ I2C = "y";
+ I2C_MT7621 = "y";
+
+ SPI = "y";
+ MTD_SPI_NOR = "y";
+ SPI_MT7621 = "y";
+ SPI_MASTER = "y";
+ SPI_MEM = "y";
+
+ REGULATOR = "y";
+ REGULATOR_FIXED_VOLTAGE = "y";
+ RESET_CONTROLLER = "y";
+ POWER_RESET = "y";
+ POWER_RESET_GPIO = "y";
+ POWER_SUPPLY = "y";
+ LED_TRIGGER_PHY = "y";
+
+ PCI_DISABLE_COMMON_QUIRKS = "y";
+ PCI_DOMAINS = "y";
+ PCI_DOMAINS_GENERIC = "y";
+ PCI_DRIVERS_GENERIC = "y";
+ PCS_MTK_LYNXI = "y";
+
+ SOC_BUS = "y";
+
+ NET = "y";
+ ETHERNET = "y";
+ WLAN = "y";
+
+ PHYLIB = "y";
+ AT803X_PHY = "y";
+ FIXED_PHY = "y";
+ GENERIC_PHY = "y";
+ NET_DSA = "y";
+ NET_DSA_MT7530 = "y";
+ NET_DSA_MT7530_MDIO = "y";
+ NET_DSA_TAG_MTK = "y";
+ NET_MEDIATEK_SOC = "y";
+ NET_SWITCHDEV = "y";
+ NET_VENDOR_MEDIATEK = "y";
+
+ SWPHY = "y";
+
+ GPIOLIB = "y";
+ GPIO_MT7621 = "y";
+ OF_GPIO = "y";
+
+ EARLY_PRINTK = "y";
+
+ NEW_LEDS = "y";
+ LEDS_TRIGGERS = "y";
+ LEDS_CLASS = "y"; # required by rt2x00lib
+ LEDS_CLASS_MULTICOLOR = "y";
+ LEDS_BRIGHTNESS_HW_CHANGED = "y";
+
+ PRINTK_TIME = "y";
+ } // lib.optionalAttrs (config.system.service ? vlan) {
+ SWCONFIG = "y";
+ } // lib.optionalAttrs (config.system.service ? watchdog) {
+ RALINK_WDT = "y"; # watchdog
+ MT7621_WDT = "y"; # or it might be this one
+ };
+ };
+ };
+}
diff --git a/devices/zyxel-nwa50ax/mt7621_zyxel_nwa50ax.dtsi b/devices/zyxel-nwa50ax/mt7621_zyxel_nwa50ax.dtsi
@@ -0,0 +1,56 @@
+#include "mt7621_zyxel_nwa-ax-for-ab.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+/ {
+ compatible = "zyxel,nwa50ax", "mediatek,mt7621-soc";
+ model = "ZyXEL NWA50AX";
+
+ aliases {
+ led-boot = &led_system_green;
+ led-failsafe = &led_system_red;
+ led-running = &led_system_green;
+ led-upgrade = &led_system_red;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led_system_red: system_red {
+ label = "red:system";
+ gpios = <&gpio 6 GPIO_ACTIVE_HIGH>;
+ };
+
+ led_system_green: system_green {
+ label = "green:system";
+ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+ };
+
+ system_blue {
+ label = "blue:system";
+ gpios = <&gpio 8 GPIO_ACTIVE_HIGH>;
+ };
+ };
+
+ keys {
+ compatible = "gpio-keys";
+
+ reset {
+ label = "reset";
+ gpios = <&gpio 30 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_RESTART>;
+ };
+ };
+};
+
+ðernet {
+ pinctrl-0 = <&mdio_pins>, <&rgmii1_pins>;
+};
+
+&state_default {
+ gpio {
+ groups = "uart3", "rgmii2";
+ function = "gpio";
+ };
+};
diff --git a/examples/nwa50ax-ap.nix b/examples/nwa50ax-ap.nix
@@ -0,0 +1,120 @@
+{ config, pkgs, ... } :
+let
+ inherit (pkgs.liminix.services) oneshot longrun bundle target;
+ inherit (pkgs) writeText;
+ svc = config.system.service;
+ secrets-1 = {
+ ssid = "Zyxel 2G (N)";
+ wpa_passphrase = "diamond dogs";
+ };
+ secrets-2 = {
+ ssid = "Zyxel 5G (AX)";
+ wpa_passphrase = "diamond dogs";
+ };
+ baseParams = {
+ country_code = "FR";
+ hw_mode = "g";
+ channel = 6;
+ wmm_enabled = 1;
+ ieee80211n = 1;
+ ht_capab = "[LDPC][GF][HT40-][HT40+][SHORT-GI-40][MAX-AMSDU-7935][TX-STBC]";
+ auth_algs = 1;
+ wpa = 2;
+ wpa_key_mgmt = "WPA-PSK";
+ wpa_pairwise = "TKIP CCMP";
+ rsn_pairwise = "CCMP";
+ };
+
+ modernParams = {
+ hw_mode = "a";
+ he_su_beamformer = 1;
+ he_su_beamformee = 1;
+ he_mu_beamformer = 1;
+ preamble = 1;
+ # Allow radar detection.
+ ieee80211d = 1;
+ ieee80211h = 1;
+ ieee80211ac = 1;
+ ieee80211ax = 1;
+ vht_capab = "[MAX-MPDU-7991][SU-BEAMFORMEE][SU-BEAMFORMER][RXLDPC][SHORT-GI-80][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN][TX-STBC-2BY1][RX-STBC-1][MU-BEAMFORMER]";
+ vht_oper_chwidth = 1;
+ he_oper_chwidth = 1;
+ channel = 36;
+ vht_oper_centr_freq_seg0_idx = 42;
+ he_oper_centr_freq_seg0_idx = 42;
+ require_vht = 1;
+ };
+ mkWifiSta = params: interface: secrets: svc.hostapd.build {
+ inherit interface;
+ params = params // {
+ inherit (secrets) ssid wpa_passphrase;
+ };
+ };
+in rec {
+ imports = [
+ ../modules/wlan.nix
+ ../modules/network
+ ../modules/hostapd
+ ../modules/ssh
+ ../modules/ntp
+ ../modules/vlan
+ ../modules/bridge
+ ];
+
+ hostname = "zyxel";
+
+ users.root = {
+ # EDIT: choose a root password and then use
+ # "mkpasswd -m sha512crypt" to determine the hash.
+ # It should start wirh $6$.
+ passwd = "$y$j9T$f8GhLiqYmr3lc58eKhgyD0$z7P/7S9u.kq/cANZExxhS98bze/6i7aBxU6tbl7RMi.";
+ openssh.authorizedKeys.keys = [
+ # EDIT: you can add your ssh pubkey here
+ # "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
+ ];
+ };
+
+ services.int = svc.bridge.primary.build {
+ ifname = "int";
+ };
+
+ services.bridge = svc.bridge.members.build {
+ primary = services.int;
+ members = with config.hardware.networkInterfaces; [
+ lan
+ wlan0
+ wlan1
+ ];
+ };
+
+ services.dhcpv4 =
+ let iface = services.int;
+ in svc.network.dhcp.client.build { interface = iface; };
+
+ services.defaultroute4 = svc.network.route.build {
+ via = "$(output ${services.dhcpv4} address)";
+ target = "default";
+ dependencies = [ services.dhcpv4 ];
+ };
+
+ services.packet_forwarding = svc.network.forward.build { };
+ services.sshd = svc.ssh.build {
+ allowRoot = true;
+ };
+
+ services.ntp = config.system.service.ntp.build {
+ pools = { "pool.ntp.org" = ["iburst"] ; };
+ };
+
+ boot.tftp = {
+ serverip = "192.0.2.10";
+ ipaddr = "192.0.2.12";
+ };
+
+ # wlan0 is the 2.4GHz interface.
+ services.hostap-1 = mkWifiSta baseParams config.hardware.networkInterfaces.wlan0 secrets-1;
+ # wlan1 is the 5GHz interface, e.g. AX capable.
+ services.hostap-2 = mkWifiSta (baseParams // modernParams) config.hardware.networkInterfaces.wlan1 secrets-2;
+
+ defaultProfile.packages = with pkgs; [ zyxel-bootconfig iw min-collect-garbage mtdutils ];
+}
diff --git a/modules/all-modules.nix b/modules/all-modules.nix
@@ -31,6 +31,8 @@
./ssh
./outputs/tftpboot.nix
./outputs/ubifs.nix
+ ./ubifs.nix
+ ./ubinize.nix
./users.nix
./vlan
./watchdog
diff --git a/modules/base.nix b/modules/base.nix
@@ -58,6 +58,15 @@ in {
default = [];
description = "Kernel command line";
};
+ commandLineDtbNode = mkOption {
+ type = types.enum [ "bootargs" "bootargs-override" ];
+ default = "bootargs";
+ description = "Kernel command line's devicetree node";
+ };
+ imageType = mkOption {
+ type = types.enum [ "primary" "secondary" ];
+ default = "primary";
+ };
imageFormat = mkOption {
type = types.enum ["fit" "uimage"];
default = "uimage";
diff --git a/modules/busybox.nix b/modules/busybox.nix
@@ -32,23 +32,21 @@ let
(a: symlink "${busybox}/bin/busybox");
minimalApplets = [
# this is probably less minimal than it could be
- "arch" "ash" "base64" "basename" "bc" "brctl" "bunzip2" "bzcat"
- "bzip2" "cal" "cat" "chattr" "chgrp" "chmod" "chown" "chpst"
- "chroot" "clear" "cmp" "comm" "cp" "cpio" "cut" "date" "dd" "df"
- "dirname" "dmesg" "du" "echo" "egrep" "env" "expand" "expr"
- "false" "fdisk" "fgrep" "find" "free" "fuser" "grep" "gunzip"
- "gzip" "head" "hexdump" "hostname" "hwclock" "ifconfig" "ip"
- "ipaddr" "iplink" "ipneigh" "iproute" "iprule" "kill" "killall"
- "killall5" "less" "ln" "ls" "lsattr" "lsof" "md5sum" "mkdir"
- "mknod" "mktemp" "mount" "mv" "nc" "netstat" "nohup" "od" "pgrep"
- "pidof" "ping" "ping6" "pkill" "pmap" "printenv" "printf" "ps"
- "pwd" "readlink" "realpath" "reset" "rm" "rmdir" "route" "sed"
- "seq" "setsid" "sha1sum" "sha256sum" "sha512sum" "sleep" "sort"
- "stat" "strings" "stty" "su" "sum" "swapoff" "swapon" "sync"
- "tail" "tee" "test" "time" "touch" "tr" "traceroute" "traceroute6"
- "true" "truncate" "tty" "udhcpc" "umount" "uname"
- "unexpand" "uniq" "unlink" "unlzma" "unxz" "unzip" "uptime"
- "watch" "wc" "whoami" "xargs" "xxd" "xz" "xzcat" "yes" "zcat"
+ "arch" "ash" "base64" "basename" "bc" "brctl" "bunzip2" "bzcat" "bzip2"
+ "cal" "cat" "chattr" "chgrp" "chmod" "chown" "chpst" "chroot" "clear" "cmp"
+ "comm" "cp" "cpio" "cut" "date" "dhcprelay" "dd" "df" "dirname" "dmesg"
+ "du" "echo" "egrep" "env" "expand" "expr" "false" "fdisk" "fgrep" "find"
+ "free" "fuser" "grep" "gunzip" "gzip" "head" "hexdump" "hostname" "hwclock"
+ "ifconfig" "ip" "ipaddr" "iplink" "ipneigh" "iproute" "iprule" "kill"
+ "killall" "killall5" "less" "ln" "ls" "lsattr" "lsof" "md5sum" "mkdir"
+ "mknod" "mktemp" "mount" "mv" "nc" "netstat" "nohup" "od" "pgrep" "pidof"
+ "ping" "ping6" "pkill" "pmap" "printenv" "printf" "ps" "pwd" "readlink"
+ "realpath" "reset" "rm" "rmdir" "route" "sed" "seq" "setsid" "sha1sum"
+ "sha256sum" "sha512sum" "sleep" "sort" "stat" "strings" "stty" "su" "sum"
+ "swapoff" "swapon" "sync" "tail" "tee" "test" "time" "touch" "tr"
+ "traceroute" "traceroute6" "true" "truncate" "tty" "udhcpc" "umount"
+ "uname" "unexpand" "uniq" "unlink" "unlzma" "unxz" "unzip" "uptime" "watch"
+ "wc" "whoami" "xargs" "xxd" "xz" "xzcat" "yes" "zcat"
];
in {
options = {
diff --git a/modules/hardware.nix b/modules/hardware.nix
@@ -67,6 +67,7 @@ in {
};
loadAddress = mkOption { type = types.ints.unsigned; default = null; };
entryPoint = mkOption { type = types.ints.unsigned; };
+ alignment = mkOption { type = types.ints.unsigned; default = null; description = "Alignment passed to `mkimage` for FIT"; };
radios = mkOption {
description = ''
Kernel modules (from mac80211 package) required for the
diff --git a/modules/outputs.nix b/modules/outputs.nix
@@ -111,7 +111,8 @@ in
};
uimage = liminix.builders.uimage {
commandLine = concatStringsSep " " config.boot.commandLine;
- inherit (config.hardware) loadAddress entryPoint;
+ inherit (config.boot) commandLineDtbNode;
+ inherit (config.hardware) loadAddress entryPoint alignment;
inherit (config.boot) imageFormat;
inherit (o) kernel dtb;
};
diff --git a/modules/outputs/ubifs.nix b/modules/outputs/ubifs.nix
@@ -12,9 +12,16 @@ in
imports = [
./initramfs.nix
];
+
+ options.system.outputs.rootubifs = mkOption {
+ type = types.package;
+ internal = true;
+ };
+
options.hardware.ubi = {
minIOSize = mkOption { type = types.str; };
- eraseBlockSize = mkOption { type = types.str; }; # LEB
+ logicalEraseBlockSize = mkOption { type = types.str; }; # LEB
+ physicalEraseBlockSize = mkOption { type = types.str; }; # PEB
maxLEBcount = mkOption { type = types.str; }; # LEB
};
@@ -26,7 +33,7 @@ in
};
boot.initramfs.enable = true;
system.outputs = {
- rootfs =
+ rootubifs =
let
inherit (pkgs.pkgsBuildBuild) runCommand mtdutils;
cfg = config.hardware.ubi;
@@ -35,7 +42,7 @@ in
} ''
mkdir tmp
tree=${o.bootablerootdir}
- mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.eraseBlockSize} -y -r $tree --output $out --squash-uids -o $out
+ mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.logicalEraseBlockSize} -y -r $tree --output $out --squash-uids -o $out
'';
};
};
diff --git a/modules/outputs/ubivolume.nix b/modules/outputs/ubivolume.nix
@@ -0,0 +1,91 @@
+{
+ config
+, pkgs
+, lib
+, ...
+}:
+let
+ inherit (pkgs) liminix;
+ inherit (lib) mkIf mkOption types concatStringsSep optionalString;
+in
+ {
+ imports = [
+ ./initramfs.nix
+ ./ubifs.nix
+ ];
+
+ options.hardware.ubi = {
+ minIOSize = mkOption { type = types.str; };
+ eraseBlockSize = mkOption { type = types.str; }; # LEB
+ maxLEBcount = mkOption { type = types.str; }; # LEB
+ };
+
+ config = mkIf (config.rootfsType == "ubifs") {
+ kernel.config = {
+ MTD_UBI="y";
+
+ UBIFS_FS = "y";
+ UBIFS_FS_SECURITY = "n";
+ };
+ boot.initramfs.enable = true;
+
+ system.outputs.rootfs =
+ let
+ inherit (pkgs.pkgsBuildBuild) runCommand;
+ ubiVolume = ({ name, volumeId, image, flags ? [] }:
+ ''
+ [${name}]
+ mode=ubi
+ vol_id=${toString volumeId}
+ vol_type=dynamic
+ vol_name=${name}
+ vol_alignment=1
+ ${optionalString (image != null) ''
+ image=${image}
+ ''}
+ ${optionalString (image == null) ''
+ vol_size=1MiB
+ ''}
+ ${optionalString (flags != []) ''
+ vol_flags=${concatStringsSep "," flags}
+ ''}
+ '');
+
+ ubiImage = (volumes:
+ let
+ ubinizeConfig = pkgs.writeText "ubinize.conf" (concatStringsSep "\n" volumes);
+ inherit (pkgs.pkgsBuildBuild) mtdutils;
+ in
+ runCommand "ubinize" {
+ depsBuildBuild = [ mtdutils ];
+ # block size := 128kb
+ # page size := 2048
+ # ubninize opts := -E 5
+ } ''
+ ubinize -Q "$SOURCE_DATE_EPOCH" -o $out \
+ -p ${config.hardware.ubi.physicalEraseBlockSize} -m ${config.hardware.ubi.minIOSize} \
+ -e ${config.hardware.ubi.logicalEraseBlockSize} \
+ ${ubinizeConfig}
+ '');
+
+ ubiDisk = ({ initramfs }:
+ let
+ initramfsUbi = ubiVolume {
+ name = "rootfs";
+ volumeId = 0;
+ image = initramfs;
+ flags = [ "autoresize" ];
+ };
+ in
+ ubiImage [
+ initramfsUbi
+ ]);
+
+ disk = ubiDisk {
+ initramfs = config.system.outputs.rootubifs; # liminix.builders.squashfs config.filesystem.contents; # # assert this is a proper FIT.
+ };
+
+ in
+ disk;
+ };
+}
diff --git a/modules/outputs/zyxel-nwa-fit.nix b/modules/outputs/zyxel-nwa-fit.nix
@@ -0,0 +1,71 @@
+{
+ config
+, pkgs
+, lib
+, ...
+}:
+let
+ inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
+ models = "6b e1 6f e1 ff ff ff ff ff ff";
+in {
+ options.system.outputs = {
+ zyxel-nwa-fit = mkOption {
+ type = types.package;
+ description = ''
+zyxel-nwa-fit
+*************
+
+This output provides a FIT image for Zyxel NWA series
+containing a kernel image and an UBIFS rootfs.
+
+It can usually be used as a factory image to install Liminix
+on a system with pre-existing firmware and OS.
+ '';
+ };
+ };
+
+ imports = [
+ ./ubivolume.nix
+ ];
+
+ config = mkIf (config.rootfsType == "ubifs") {
+
+ system.outputs.zyxel-nwa-fit =
+ let
+ o = config.system.outputs;
+ # 8129kb padding.
+ paddedKernel = pkgs.runCommand "padded-kernel" {} ''
+ cp --no-preserve=mode ${o.uimage} $out
+ dd if=/dev/zero of=$out bs=1 count=1 seek=8388607
+ '';
+ firmwareImage = pkgs.runCommand "firmware-image" {} ''
+ cat ${paddedKernel} ${o.rootfs} > $out
+ '';
+ dts = pkgs.writeText "image.its" ''
+ /dts-v1/;
+
+ / {
+ description = "Zyxel FIT (Flattened Image Tree)";
+ compat-models = [${models}];
+ #address-cells = <1>;
+
+ images {
+ firmware {
+ data = /incbin/("${firmwareImage}");
+ type = "firmware";
+ compression = "none";
+ hash@1 {
+ algo = "sha1";
+ };
+ };
+ };
+ };
+ '';
+ in
+ pkgs.runCommand "zyxel-nwa-fit-${config.boot.imageType}" {
+ nativeBuildInputs = [ pkgs.pkgsBuildBuild.ubootTools pkgs.pkgsBuildBuild.dtc ];
+ } ''
+ mkimage -f ${dts} $out
+ '';
+ };
+}
diff --git a/modules/zyxel-dual-image/default.nix b/modules/zyxel-dual-image/default.nix
@@ -0,0 +1,60 @@
+## Boot blessing via Zyxel
+## =======================
+## Boot blessing is the process to bless a particular boot configuration
+## It is commonly encountered in devices with redundant partitions
+## for automatic recovery of broken upgrades.
+## This is also known as A/B schemas, where A represents the primary partition
+## and B the secondary partition used for recovery.
+## To use boot blessing on Liminix, you need to have the support of
+## your bootloader to help you boot on the secondary partition in case of
+## failure on the primary partition. The exact details are specifics to your device.
+## See the Zyxel NWA50AX for an example.
+## TODO: generalize this module.
+{ config, lib, pkgs, ... }:
+let
+ inherit (lib) mkOption types;
+ inherit (pkgs) liminix;
+in
+{
+ options.boot.zyxel-dual-image = mkOption {
+ type = liminix.lib.types.serviceDefn;
+ };
+
+ config.boot.zyxel-dual-image = liminix.callService ./service.nix {
+ ensureActiveImage = mkOption {
+ type = types.enum [ "primary" "secondary" ];
+ default = "primary";
+ description = ''At boot, ensure that the active image is the one specified.
+
+ If you are already on a broken image, you need to manually boot
+ into the right image via `atgo <image index>` in U-Boot.
+ '';
+ };
+
+ kernelCommandLineSource = mkOption {
+ type = types.enum [ "/proc/cmdline" "/proc/device-tree/chosen/bootargs" ];
+ default = "/proc/device-tree/chosen/bootargs";
+ description = ''Kernel command line arguments source file.
+ On MIPS, Liminix embeds the kernel command line in /proc/device-tree/chosen/bootargs-override.
+
+ In this instance, it does not get concatenated with `/proc/cmdline`.
+ Therefore you may prefer to source it from another place, like `/proc/device-tree/chosen/bootargs`.
+ '';
+ };
+
+ primaryMtdPartition = mkOption {
+ type = types.str;
+ description = "Primary MTD partition device node, i.e. for image 0.";
+ };
+
+ secondaryMtdPartition = mkOption {
+ type = types.str;
+ description = "Secondary MTD partition device node, i.e. for image 1.";
+ };
+
+ bootConfigurationMtdPartition = mkOption {
+ type = types.str;
+ description = "Boot configuration MTD partition device node.";
+ };
+ };
+}
diff --git a/modules/zyxel-dual-image/service.nix b/modules/zyxel-dual-image/service.nix
@@ -0,0 +1,33 @@
+{
+ liminix
+, lib
+, zyxel-bootconfig
+}:
+{ ensureActiveImage, primaryMtdPartition, secondaryMtdPartition, bootConfigurationMtdPartition, kernelCommandLineSource }:
+let
+ inherit (liminix.services) oneshot;
+ activeImageIndex = if ensureActiveImage == "primary" then 0 else 1;
+in oneshot {
+ name = "zyxel-boot-configure";
+ up = ''
+ set -- $(cat /proc/device-tree/chosen/bootargs)
+ for x in "$@"; do
+ case "$x" in
+ bootImage=*)
+ BOOT_IMAGE="''${x#bootImage=}"
+ echo "Current boot image is $BOOT_IMAGE."
+ ;;
+ esac
+ done
+
+ if test -z "$BOOT_IMAGE"; then
+ echo "No valid image was provided in the kernel command line."
+ exit 1
+ else
+ ${lib.getExe zyxel-bootconfig} ${bootConfigurationMtdPartition} set-image-status "$BOOT_IMAGE" valid
+ ${lib.getExe zyxel-bootconfig} ${bootConfigurationMtdPartition} set-active-image ${toString activeImageIndex}
+
+ echo "Active image is now ${ensureActiveImage}"
+ fi
+ '';
+}
diff --git a/pkgs/default.nix b/pkgs/default.nix
@@ -79,6 +79,7 @@ in {
lzma = callPackage ./lzma {};
mac80211 = callPackage ./mac80211 {};
+ zyxel-bootconfig = callPackage ./zyxel-bootconfig {};
min-collect-garbage = callPackage ./min-collect-garbage {};
min-copy-closure = callPackage ./min-copy-closure {};
netlink-lua = callPackage ./netlink-lua {};
diff --git a/pkgs/kernel/uimage.nix b/pkgs/kernel/uimage.nix
@@ -15,10 +15,12 @@ let
in {
kernel
, commandLine
+, commandLineDtbNode ? "bootargs"
, entryPoint
, extraName ? "" # e.g. socFamily
, loadAddress
, imageFormat
+, alignment ? null
, dtb ? null
} : stdenv.mkDerivation {
name = "kernel.image";
@@ -39,7 +41,7 @@ in {
'';
mungeDtbPhase = ''
dtc -I dtb -O dts -o tmp.dts ${dtb}
- echo '/{ chosen { bootargs = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
+ echo '/{ chosen { ${commandLineDtbNode} = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
dtc -I dts -O dtb -o tmp.dtb tmp.dts
'';
@@ -69,7 +71,7 @@ in {
};
};
_VARS
- mkimage -f mkimage.its kernel.uimage
+ mkimage -f mkimage.its ${lib.optionalString (alignment != null) "-B 0x${lib.toHexString alignment}"} kernel.uimage
mkimage -l kernel.uimage
'';
diff --git a/pkgs/zyxel-bootconfig/default.nix b/pkgs/zyxel-bootconfig/default.nix
@@ -0,0 +1,16 @@
+{
+ stdenv
+, openwrt
+}:
+stdenv.mkDerivation {
+ name = "zyxel-bootconfig";
+ inherit (openwrt) src;
+ sourceRoot = "openwrt-source/package/utils/zyxel-bootconfig/src";
+ installPhase = ''
+ mkdir -p $out/bin
+ install -Dm544 zyxel-bootconfig $out/bin/zyxel-bootconfig
+ '';
+ meta = {
+ mainProgram = "zyxel-bootconfig";
+ };
+}