# ALARM — Arch Linux ARM for the NVIDIA Tegra X1 (Nintendo Switch) > **⚠️ AI-GENERATED PROJECT / README** > > **This README — and the majority of the code and changes in this repository — > were written by a Large Language Model (LLM) AI assistant**, not by a human, > with human direction and review. Treat everything here as unverified: check > commands before running them, especially anything involving `sudo`, loop > devices, or writing to a block device. Do not assume the code is correct, > safe, or complete just because it is written confidently. Review it. > > **The mainline kernel now boots to userspace.** A 7.2 kernel built by this > project boots on the Switch, mounts the Arch Linux ARM root filesystem, and > gives a root shell over USB serial (`/dev/ttyACM0`). That is the project's > goal: current userspace on a current kernel. The stock L4T 4.9.140 entry is > still there as a known-good fallback, and it also reaches a root shell, with > WiFi and SSH working. > > systemd does not run on either path yet — a small shell script takes its > place as PID 1. See [Status](#status) and > [`patches/README.md`](patches/README.md). A build system that produces an SD-card image for running Arch Linux ARM on the Nintendo Switch (HAC-001), targeting the Tegra X1 (ODNX2-A2) SoC and booting through hekate's L4T loader. > **A full record of the mainline bring-up — the observation channels, every > silent failure, and the reasoning behind each fix — is in > [`BRINGUP.md`](BRINGUP.md).** Start there if you are debugging a boot, or read > §4 of it first if you are about to change anything. > > **Prior art:** switchroot have already done mainline Switch bring-up, including > the panel driver and the PCIe lane configuration this project was missing. See > [`reference/switchroot-mainline/`](reference/switchroot-mainline/README.md) and > §11 of `BRINGUP.md`. > > Published on notdoublehub as > [`alarm-tegra-x1`](https://notdouble.com/p/JG2HQZWB/alarm-tegra-x1). The > manifest is `notdouble.toml`; `.ndignore` excludes the user photos and videos, > the `l4t-kernel-4.9` clone (~930 MB), the switchroot rootfs parts (~4 GB each) > and the `build/`/`output/` trees, so an upload is ~1.3 MB of source. --- ## Status **What works** - **The mainline kernel boots to userspace.** A 7.2 kernel built by this project (clang 23, `--kernel built`) boots on the Switch, mounts the Arch Linux ARM ext4 root filesystem, and provides a **root shell over USB serial** (`/dev/ttyACM0` on the host, `/dev/ttyGS0` on the device). - `alarm-build` CLI (Rust) builds: hekate boot tree, device tree, rootfs, kernel, and a bootable GPT SD-card image. - hekate's L4T loader accepts the generated entry and boots. - U-Boot (`bl33`) loads the kernel and device tree and runs our `boot.scr`. - On the mainline kernel: microSD (SDHCI plus card detect), the Tegra XUDC USB serial gadget, the arch timer, PSCI and SMP all work. - `sshd` and a console shell start at boot. On the L4T entry, WiFi, DHCP, DNS and `pacman` work as well. **What does not work** - **The display works.** The panel is a JDI LPM062M326A, its driver is ported from switchroot's patch 0010, and the whole chain — DSI, panel, init sequence, rails, backlight — is proven by `modetest -M tegra -r` drawing a full 720×1280 image on the Switch's own screen. **There is no boot console on it, and that is an upstream limitation**, not a device-tree one: mainline only ships fbdev backends for GEM-DMA/shmem/TTM, and tegra has its own GEM. A userspace compositor works (that is the route to a desktop on the panel); input drivers are the other half. See [`device-tree/README.md`](device-tree/README.md), "Display". - **WiFi on the mainline kernel.** The PCIe controller is deliberately left disabled. Enabling it now trains the link and enumerates the root port — the MAX77620 `ldo1` rail was the missing piece — but the endpoint never answers config cycles, and the resulting asynchronous SError panics the kernel and costs userspace entirely. See [`device-tree/README.md`](device-tree/README.md), "WiFi: the state of play". - **systemd does not run** on either path. Current Arch Linux ARM ships systemd 261, so a shell script takes its place as PID 1. The cost is no systemd units, no `journalctl`, no `NetworkManager` and no user session management. - **Kernel modules are not built.** The kernel is built with `ALARM_NO_MODULES=1`, so anything configured `=m` is simply absent. That silently broke the *host-side* USB console driver (`CONFIG_USB_ACM`, now built in) and is the reason `REQUIRED_OPTIONS` exists. Both the kernel and the userspace need matching `/lib/modules` + `/lib/firmware`; the build imports them from the switchroot image (`switchroot/install/l4t.00` + `l4t.01`). **systemd is now unblocked in principle.** systemd 261's baseline is kernel 4.15 and this project boots 7.2, so the cgroup v2 and new-mount-API requirements are met — the shell PID 1 is now a choice rather than a necessity. The path to a real systemd boot on the mainline kernel is to point `/sbin/init` back at `/usr/lib/systemd/systemd` and work through what the first boot complains about. On the L4T 4.9.140 entry systemd remains impossible, which is why that entry keeps the shell init. See [Troubleshooting](#troubleshooting) for details. --- ## Hardware specifications | Component | Specification | |-----------|---------------| | SoC | NVIDIA Tegra X1 (ODNX2-A2) | | Process | TSMC 20nm | | CPU (Big) | 4× Cortex-A57 @ 2.2 GHz (ARMv8-A) | | CPU (Little) | 4× Cortex-A53 @ 1.3 GHz (ARMv8-A) | | GPU | GM20B (Maxwell) 256 cores @ 1000 MHz | | Memory | 8 GB LPDDR4 (64-bit, 25.6 GB/s) | | Bootloader | hekate v6.x | --- ## How booting works hekate does **not** boot a raw kernel `Image` + `.dtb` pair. It boots Linux through its **L4T loader**, which reads an entry in `bootloader/ini/*.ini` that points at a boot-prefix directory: ```ini [L4T ALARM] l4t=1 boot_prefixes=/switchroot/alarm/ id=ALARM rootdev=mmcblk0p2 rootlabel_retries=1 r2p_action=self fbconsole=0 bootargs_extra=usb_logging auto_rootdev_disable ``` From that prefix, `bl33` (a U-Boot stage, run by hekate) executes `boot.scr`, which loads: | File | Purpose | |------|---------| | `uImage` | Kernel (U-Boot image, load/entry `0x80200000`) | | `initramfs` | Finds the root partition, mounts it, and starts PID 1 (patched — see [`patches/`](patches/README.md)) | | `nx-plat.dtimg` | Platform device trees (Tegra DC/SOR/DSI panel, regulators, …) | | `boot.scr` | U-Boot boot script that loads the above and `bootm`s | | `bl31.bin` | ARM Trusted Firmware secure monitor (loaded by hekate) | | `bl33.bin` | L4T bootloader stage (loaded by hekate) | Because the Switch panel is a DSI panel that must be brought up by a display driver, the default kernel is the **known-good switchroot L4T kernel** shipped in `switchroot/ubuntu-jammy/`. A vanilla kernel built from `linux.git` does not carry the panel/regulator patch set and will not light up the display; it can still be packaged with `--kernel built` for experimentation. ### hekate runtime files hekate is usually injected over RCM, but it still loads support files from the SD card. The build fetches the official hekate release bundle and copies its whole `bootloader/` folder, which provides: | Path | Purpose | |------|---------| | `bootloader/sys/libsys_lp0.bso` | LP0 sleep support | | `bootloader/sys/libsys_minerva.bso` | RAM training / Minerva | | `bootloader/sys/nyx.bin` | Nyx GUI | | `bootloader/sys/l4t/bpmpfw*.bin`, `mtc_tbl*.bin` | BPMP firmware + RAM tables for L4T | | `bootloader/sys/res.pak`, `thk.bin`, `emummc.kipm` | Resources / emuMMC | Without these hekate refuses to boot L4T with *"missing lp0 (sleep) lib / missing minerva lib / update bootloader folder!"*. Set `HEKATE_BOOTLOADER_DIR` to point at an existing `bootloader/` folder instead of downloading one. --- ## Prerequisites ```bash # Arch Linux sudo pacman -S git make gcc flex bison dtc bc rsync parted dosfstools \ e2fsprogs mtools uboot-tools base-devel # Ubuntu/Debian sudo apt install git make gcc flex bison device-tree-compiler bc rsync parted \ dosfstools e2fsprogs mtools u-boot-tools build-essential # Fedora sudo dnf install git make gcc flex bison dtc bc rsync parted dosfstools \ e2fsprogs mtools uboot-tools ``` Cross compiler (only needed for `--kernel built`): ```bash # Arch: sudo pacman -S aarch64-linux-gnu-gcc # Ubuntu: sudo apt install gcc-aarch64-linux-gnu ``` Rootfs and image creation require **root**. Do **not** prefix the commands with `sudo` — the build tool re-executes its own binary through `sudo` when it needs to (running `sudo cargo` fails because rustup toolchains are per-user). Set `ALARM_NO_ELEVATE=1` to disable this and handle privileges yourself. --- ## Building ```bash # Initialise the build directory and download sources cargo run -- init # Assemble the hekate L4T boot tree under ./output (uses the switchroot kernel) cargo run -- payload # Bootstrap the ALARM root filesystem (prompts for your sudo password) cargo run -- rootfs # Create the bootable image cargo run -- image --size 8 # Flash it to an SD card (auto-sudo; confirms the target is a block device) cargo run -- flash /dev/sdX ``` `cargo run -- image` also runs `payload` and `rootfs`, so a plain `cargo run -- image` is enough once `init` has completed. Use `--kernel built` to package the locally built kernel instead of the switchroot one, and `--desktop` for an XFCE rootfs. ### Step by step ```bash cargo run -- init # create build dirs + config cargo run -- kernel # build the Linux kernel (optional; needs patches) cargo run -- device-tree # compile the fallback device tree cargo run -- payload # assemble bootloader/ini + switchroot/alarm cargo run -- rootfs # bootstrap the ALARM rootfs (auto-sudo) cargo run -- image # create alarm-tegra-x1.img (auto-sudo) cargo run -- flash /dev/sdX # flash (auto-sudo) ``` --- ## Image layout `alarm-tegra-x1.img` is a GPT disk with two partitions: | Partition | Type | Label | Contents | |-----------|------|-------|----------| | 1 | FAT32 (512 MiB) | `BOOT` | `bootloader/`, `switchroot/` (read by hekate) | | 2 | ext4 | `ALARM` | Arch Linux ARM root filesystem | The ext4 label (`ALARM`) must match `id=` in the hekate L4T entry. The ext4 is created with `orphan_file` and `metadata_csum_seed` **disabled**, because the 4.9 L4T kernel cannot mount them. --- ## Booting on the Switch 1. Insert the flashed SD card. 2. Enter RCM (hold **VOL UP** + **POWER**) and inject hekate. 3. In hekate open **More configs…** and select **L4T ALARM**. `fbconsole=0` keeps the built-in display out of the way. The kernel console is the **USB serial gadget**, enabled by `usb_logging` in the hekate entry: plug the Switch into your PC and open the port with `minicom -D /dev/ttyACM0`. A root shell is started there automatically. ### First boot The system does not show a login prompt — a root shell runs as PID 1 and another on `ttyGS0`. Set a root password and the WiFi credentials, then reboot or just run the init script's steps by hand: ```sh passwd root # if PAM refuses, use the /etc/shadow method below wpa_passphrase "YourSSID" "YourPassphrase" > /etc/wpa_supplicant/wpa_supplicant.conf ``` After a reboot, `alarm-init.sh` brings up WiFi, gets a DHCP lease and starts `sshd`, so you can `ssh root@
` instead of using the serial cable. --- ## Configuration `build/config/build.toml` holds the hardware description, image layout (`[image]`) and toolchain settings. The kernel config is generated from the arm64 defconfig with Tegra X1 options applied (`src/kernel.rs`); to customise it, pass your own file: ```bash cargo run -- kernel --custom-config build/config/kernel.config ``` Relevant environment variables: | Variable | Purpose | |----------|---------| | `HEKATE_BOOTLOADER_DIR` | Use a local hekate `bootloader/` folder instead of downloading | | `ALARM_NO_ELEVATE` | Disable automatic `sudo` re-execution | --- ## Project structure ``` alarm/ ├── src/ │ ├── main.rs # CLI entry point │ ├── config.rs # build + image configuration │ ├── kernel.rs # kernel/device-tree build │ ├── payload.rs # hekate L4T boot tree assembly │ ├── rootfs.rs # ALARM rootfs + disk image │ ├── packages.rs # U-Boot download/build │ └── utils.rs # toolchain, flashing, privilege handling ├── device-tree/ # fallback DTS + switch-fb driver ├── switchroot/ # reference L4T boot files (kernel/ATF/dtimg/initramfs) ├── patches/ # non-systemd boot fix + card snapshot (see its README) ├── build/ # build directory (created by `init`) └── output/ # generated boot tree + image ``` --- ## Troubleshooting These are the real failures encountered while bringing this up; most were in the boot chain. - **`missing lp0 (sleep) lib / missing minerva lib / update bootloader folder!`** hekate's own `bootloader/sys/` files are missing from the SD card. The build now copies the full hekate release bundle, so regenerate with `payload` (or set `HEKATE_BOOTLOADER_DIR`). - **Selecting the entry bounces straight back to hekate.** `boot.scr` runs `reset` (which returns to hekate) when it cannot load the kernel/initramfs/dtimg, and hekate's L4T loader returns to the menu on `Path missing` / `Minerva missing` / `BL31 missing` / `BL33 missing` / `loading BPMP-FW` errors. The entry must stay comment-free: hekate treats a `#` line as a comment, but a `;` line inside a section is parsed as a key/value and forwarded into U-Boot's environment. - **`failed to mount rootfs` / `filesystem has errors`.** The ext4 was created with features the 4.9 kernel cannot mount. The build disables `orphan_file` and `metadata_csum_seed`; to fix an existing card: `sudo tune2fs -O ^orphan_file,^metadata_csum_seed /dev/sdX2`. - **`