- Shell 34.5%
- Python 34.4%
- Nix 16.4%
- Just 14.5%
- C 0.2%
The phone now also runs as kif, a desktop host of the user's NixOS fleet (~/nix). That host needs the hardware support without the lab's choices, so nixos/cheetah.nix is split: - nixos/hardware.nix (exported as nixosModules.cheetah): boot outside NixOS, the ways into the device, the gs201 services, firmware, Mesa. It works with nixos-26.05 (the fleet's release) as well: a journald setting by option, a Mesa 26.1 variant of the G710 patch. - nixos/cheetah.nix: the lab system on top of it (hostname, no firewall, root niri test, Steam, PipeWire). Its system differs only in the order of system-path's packages. - `just nixos-switch` deploys the system the device runs: a fleet host from ~/nix with the hardware module from this checkout. On kif root's login shell is fish, which broke devsh and with it the confirmation of `just kexec` (two dead man's switch resets): fish has no $?, keeps a line it cannot parse in its buffer, and waits ~10 s at its start for answers to terminal queries. The serial consoles start bash without login now, and devsh sends one line any shell accepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9beSzErEyyXdcmqPbwzUa |
||
|---|---|---|
| configs | ||
| docs | ||
| initramfs | ||
| nixos | ||
| scripts | ||
| tools | ||
| .envrc | ||
| .gitignore | ||
| CLAUDE.md | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
gs201 mainline
Mainline Linux bring-up for Google Tensor G2 (gs201), target device Pixel 7 Pro (cheetah).
Layout
| Path | Contents |
|---|---|
flake.nix |
all tooling (nix develop, or direnv via .envrc) |
linux/ |
linux-next with our branches (not part of this repo); checked out on the integration branch gs201-all, see docs/kernel-branches.md |
wt/ |
worktrees of the feature branches (just wt <branch>, not part of this repo) |
downstream/ |
Google downstream sources for reference (not part of this repo) |
configs/gs201.config |
Kconfig fragment on top of arm64 defconfig |
initramfs/ |
busybox init: display trigger, status report through the bootloader (dssreport.sh), dmesg/pstore on the framebuffer, dead man's switch for kexec boots (deadman.sh) |
scripts/ |
build and analysis scripts |
docs/gs201-vs-gs101.md |
hardware comparison gs101 ↔ gs201 |
out/ |
build output (removed by just clean) |
logs/ |
timestamped bootloader logs and photos (not in the repo, kept by just clean) |
Build and boot
Common tasks are just recipes (justfile); just without arguments lists them. just comes from the flake, so use nix develop, direnv, or nix develop -c just <recipe>.
Kernel work happens on feature branches in worktrees; linux/ builds the
integration branch gs201-all (docs/kernel-branches.md):
just wt gs201-bt # worktree wt/gs201-bt for a feature branch
just integrate # gs201-all = gs201 + the features in configs/kernel-branches
just build # create .config if missing, build Image.lz4 + cheetah DTB
just check # dtbs_check + dt_binding_check
just images # out/boot.img + out/vendor_boot.img
just all # build + images
just kmake <tgt> # any kernel make target through the kmake wrapper
Never call make directly for the kernel, use kmake. The wrapper sets up the LLVM toolchain, the host compiler and rpaths.
Device (GrapheneOS is no longer needed; we use slot A, which holds the current bootloader):
just bootloader # adb reboot bootloader
just getvar # fastboot getvar all -> logs/getvar-<date>.txt
just bl-log # bootloader log (fastboot oem dmesg) -> logs/bl-dmesg-<date>.txt, logs/latest-bl-dmesg.txt
just unlock-ability # 1 = unlocking allowed
just flash # build, get into fastboot, flash boot/vendor_boot/vendor_kernel_boot to slot A, erase init_boot + dtbo, reboot (asks first)
just reboot-bootloader # reboot our running kernel into fastboot
just flash and just reboot-bootloader reach fastboot from our running kernel through the MAX77759 nvmem reboot mode (the bootloader reads it from the PMIC, not from the PMU). The kernel keeps the watchdog running, so a hang resets the device after at most 30 s; just kexec then boots the flashed kernel again.
The bootloader counts boots of slot A that are never marked successful (Android does that, we do not). Once the retry counter runs out it stays in fastboot (AB Decision: force ABL into fastboot in just bl-log); fastboot --set-active=a refills it, which just flash and the recovery in just kexec do.
Getting to fastboot by hand: hold Power + Vol-Down; once the screen goes black keep holding Vol-Down until the fastboot screen shows. To recover, flash a factory image (stock or GrapheneOS) from there.
If the device shows up only as 18d1:4f00 Pixel ROM Recovery (black screen, no fastboot), the BootROM could not boot from UFS: see docs/recovery.md (just recovery-files, just eub-recover).
Never run fastboot flashing lock with our own images. With UFS up, the bootloader chain is visible as /dev/sdb, /dev/sdc and /dev/sdd on the device: never write to them.
Safety nets:
initramfs/protect.shsetssdb,sdc,sddand thesdapartitionspersist,efs,efs_backup,modem_userdata,frp,fipsread-only at boot (blockdev --setro); writes then fail with EPERM.just backupcopies those areas plus the GPT read-only into~/gs201-device-backup/<date>/(outside the repo, they hold IMEIs and calibration), checked with md5 against the device and recorded inSHA256SUMS.scripts/devpull.pydoes the transfers.
NixOS on the device
nixos/hardware.nix is the hardware support (exported as nixosModules.cheetah), nixos/cheetah.nix the lab system built on it for the userdata partition; kernel and initramfs stay ours. The initramfs acts as stage 1 (initramfs/switch-root.sh): if userdata holds NixOS it switches to it, otherwise it stays in the busybox environment.
The phone also runs as kif, a desktop host of the user's NixOS fleet (~/nix, hosts/kif/HOST.md), which imports nixosModules.cheetah as the flake input gs201. just nixos-switch deploys whichever of the two the device runs (its hostname), a fleet host with nixos/hardware.nix from this checkout.
just nixos-install # build .#cheetah-rootfs, write it to userdata (verified), flash kernel + stage 1
just nixos-switch # rebuild the running system (cheetah or kif) and switch the device (nixos-rebuild --target-host)
ssh root@172.16.42.1 # key from nixos/authorized_keys
The deploy key for just nixos-switch lives in out/ssh/deploy (not in the repo); its public half is in nixos/authorized_keys. For kernel bring-up, GS201_NO_SWITCH=1 just kexec boots into the busybox environment from either userland, plain just kexec returns to NixOS.
aarch64 store paths come from cache.nixos.org; the few system-specific ones build through the host's aarch64 binfmt emulation. The first boot registers the store, sets the system profile and grows the file system to the whole partition. To get back to the busybox environment, build with GS201_NO_SWITCH=1 (e.g. GS201_NO_SWITCH=1 just kexec); nixos-install needs userdata unmounted.
Device console over USB
The init sets up a USB gadget (initramfs/usb-gadget.sh, 1d6b:0104) with an ACM serial port and NCM networking:
just console # interactive shell (tio /dev/ttyACM0)
just sh 'dmesg | tail' # run one command, print its output, return its exit status
On the device, usb0 has 172.16.42.1/24 and telnetd runs. The gadget uses fixed MACs, so the host side is configured once with a NetworkManager profile (no root needed for members of the networkmanager group):
nmcli connection add type ethernet con-name gs201-usb \
ethernet.mac-address 02:00:00:20:12:02 \
ipv4.method manual ipv4.addresses 172.16.42.2/24 ipv4.never-default yes \
ipv6.method link-local connection.autoconnect yes
Iterating with kexec
Once a kexec-capable image runs on the device, new kernels, DTBs and initramfs images are booted without fastboot:
just kexec # build, push over USB networking, kexec -c, wait for the new kernel
just kexec 30 # same, with a 30 s dead man's switch instead of 90 s
just deadman-logs # fetch dead man's switch reports from the device into logs/
GS201_TARGET=net:192.168.89.46 just kexec # the same over Wi-Fi (lab AP)
Over Wi-Fi, the device-side steps (scripts/kexec-exec.sh: stop DMA masters, freeze the file systems) and the logs a hung kexec kernel leaves in ramoops (logs/pstore-*.txt): docs/kexec.md.
The recipe takes the memory and secure reserved-memory nodes the bootloader adds from the running kernel's DTB (scripts/kexec-dtb.py), copies the files with scripts/devpush.py and loads them with kexec -c (kexec_file_load would ignore --dtb on arm64). Flash with just flash only when the bootloader path itself needs testing or the device no longer comes up.
A broken test kernel needs no hands. The kexec initramfs carries a dead man's switch (initramfs/deadman.sh, GS201_DEADMAN=<seconds> in scripts/mkinitramfs.sh, never in flashed images): unless just kexec confirms the new kernel (it creates /dev/.gs201-host-ok) before the deadline, the device writes a report with the full kernel log to /var/log/gs201/ on userdata and stops feeding the watchdog. After the reset the flashed kernel boots again; just kexec notices it (no /dev/.gs201-deadman), moves the report to logs/deadman-<date>-<boot id>.txt and fails. Under NixOS the gs201-deadman service takes the deadline over from the initramfs, keeps dmesg the same way and reboots. This catches hangs where the kernel lives on (so the watchdog keeps being fed) but USB is gone, as with an uninitialised USB 3 PHY. GS201_KEXEC_NO_CONFIRM=1 just kexec skips the confirmation to test the whole path.
Buttons
gs201-keys (scripts/keys.py) gives a way in without host, network or UART:
Power 1 s reboot, 3 s fastboot; Vol- short swaps the USB data role; Vol+
short shows a status page on the panel, 3 s toggles dmesg -w there;
Vol+ and Vol- together confirm a kexec boot to the dead man's switch.
Debugging without UART
The debug UART on the USB-C SBU pins is now set up, see docs/uart.md; what follows is the fallback without it.
As long as there is neither UART nor USB, feedback goes through the bootloader. initramfs/dssreport.sh writes one line of text into the debug-snapshot header (0xd8100000, panic string at +0xc00) and sets the reboot reason to 0xbaba. After a reset (hold Vol-Down → fastboot) just bl-log shows it as reset message. This is not reliable: since the MAX77759 came up the bootloader mostly reports its own reason (UVLO (IF-PMIC)) and clears the text, so with USB working the dead man's switch reports on userdata are the better source.
By default the init keeps the console up and refreshes that report every 10 seconds. Build the images with GS201_WDT_RESET=1 just images to have the init report once and then let the watchdog reset the device instead, for when the display does not come up.
The panel runs in command mode. For simpledrm/fbcon to become visible, init unmasks the hardware trigger in DECON0 TRIG_CON (0x1c240030).
Clock driver generator
drivers/clk/samsung/clk-gs201.c and include/dt-bindings/clock/google,gs201.h are generated from the downstream cal-if tables (cmucal-sfr.c, cmucal-node.c) by scripts/clkgen/:
just clkgen # regenerate for TOP,APM,MISC,HSI0,HSI2,PERIC0,PERIC1
just clkgen TOP,MISC,HSI0,HSI2 # other set of CMUs
just clkgen-check # run the generator on gs101 and compare with upstream clk-gs101.c
Names follow the upstream gs101 scheme, derived from the register names (naming.py). Registers, bit fields and parents come from the cal-if data; clock flags (CLK_SET_RATE_PARENT, critical gates, ...) are copied from clk-gs101.c for clocks with the same name. Generated for gs101, the register offsets, bit fields and parents match upstream except where upstream hand-renamed clocks or wires PLL outputs differently.
Analysis helpers
just ds-nodes downstream/devices_google_gs201/dts/gs201-b0.dts # -> out/ds/gs201-b0.nodes (path, compatible, reg, status)