diff --git a/default.nix b/default.nix index 2bd91c5..acfce7c 100644 --- a/default.nix +++ b/default.nix @@ -3,6 +3,7 @@ , liminix-config ? , 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 new file mode 100644 index 0000000..6c75b0b --- /dev/null +++ b/devices/zyxel-nwa50ax/a_image/mt7621_zyxel_nwa-ax-for-ab.dtsi @@ -0,0 +1,155 @@ +#include "mt7621.dtsi" + +#include +#include + +/ { + 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 new file mode 100644 index 0000000..c35d362 --- /dev/null +++ b/devices/zyxel-nwa50ax/b_image/mt7621_zyxel_nwa-ax-for-ab.dtsi @@ -0,0 +1,155 @@ +#include "mt7621.dtsi" + +#include +#include + +/ { + 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 new file mode 100644 index 0000000..23a394a --- /dev/null +++ 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: + 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=` 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 new file mode 100644 index 0000000..02b6ba4 --- /dev/null +++ b/devices/zyxel-nwa50ax/mt7621_zyxel_nwa50ax.dtsi @@ -0,0 +1,56 @@ +#include "mt7621_zyxel_nwa-ax-for-ab.dtsi" + +#include +#include + +/ { + 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 = ; + }; + }; +}; + +ð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 new file mode 100644 index 0000000..6a3989a --- /dev/null +++ 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 index 6b432ce..f6a8908 100644 --- 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 index 352fa82..8f53df2 100644 --- 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 index 2f21f11..718cafd 100644 --- 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 index 598038d..44afc3a 100644 --- 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 index f60e146..84199a2 100644 --- 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 index cfa27b9..b7a7c61 100644 --- 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 new file mode 100644 index 0000000..45aa889 --- /dev/null +++ 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 new file mode 100644 index 0000000..a68dd39 --- /dev/null +++ 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 new file mode 100644 index 0000000..91ed48e --- /dev/null +++ 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 ` 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 new file mode 100644 index 0000000..3edd642 --- /dev/null +++ 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 index 558e266..e2257b6 100644 --- 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 index eca50d5..96123a4 100644 --- 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 new file mode 100644 index 0000000..4b72ccf --- /dev/null +++ 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"; + }; +}