# What this project carries on top of the pinned kernel Required by the `system/kernel` requirement: *"every patch this project carries on top SHALL be recorded with what it does and why it is needed"*, and *"so a later kernel bump can tell whether it is still required"*. **Pin:** `ruyisdk/linux-xuantie-kernel` at `7d4e1f444f461dbe3833bd99a4640e7b6c2cd529` (6.6.36), `k230_defconfig`. This is the revision and defconfig `k230_canmv_v3_defconfig` builds in `kendryte/k230_linux_sdk`. Why not mainline: `docs/evidence/why-xuantie-kernel.txt`. Everything below lives in `nix/kernel.nix`. All of it is applied through `applyPatches` on `src`, **not** through the derivation's `postPatch` — `buildLinux` does not forward `postPatch`, so setting it there is a silent no-op that returns the same store path with none of the changes applied. That cost a debugging cycle and was only caught because the derivation hash did not change. ## 1. Three device trees the vendor Makefile omits | Added to `arch/riscv/boot/dts/canaan/Makefile` | Why | | --- | --- | | `k230-canmv-v3.dtb` | The reference board's DTB. The vendor Makefile genuinely does not list it, so the tree cannot build the board its own defconfig is named after. | | `k230-canmv-v3-lcd.dtb` | The reference board's LCD variant, kept as the comparison point for our divergence record. | **Drop when:** upstream lists them itself. `k230-tdisplay.dtb` used to be a third row here. It is not built by the kernel any more — see below. ## 2. This board's device tree — NO LONGER IN THE KERNEL `nix/dts/k230-tdisplay.dts` and `nix/dts/display-rm69a10-568x1232.dtsi` were copied into `arch/riscv/boot/dts/canaan/` from `applyPatches`' `postPatch`, which put them in the kernel's `src`. Correct, and very slow: a one-byte edit to `panel-init-sequence` — the thing actively being iterated on to bring the panel up — invalidated the whole kernel and cost a ~20 minute cross-compile. A DTB needs the kernel's *headers*, not a built kernel. So the pin moved to `nix/kernel-src.nix` and the DTS is preprocessed and compiled on its own in `nix/device-tree.nix`, exposed as `nix build --impure .#deviceTree`. That takes about 1.5 seconds, and `nix/sd-image.nix` takes the DTB from there instead of from `${kernel}/dtbs/canaan/`. The output is **byte-identical** to what the kernel build produced: same preprocessor flags as `scripts/Makefile.lib`'s `cmd_dtc`, plus the `-@` that nixpkgs' kernel builder adds to every `make dtbs` (`pkgs/os-specific/linux/kernel/build.nix`). Verified by `cmp` against `${kernel}/dtbs/canaan/k230-tdisplay.dtb` from the previous build. Configuration rather than a code patch: `panel-canaan-universal` is already in the pinned tree and is driven entirely from the device tree, so the RM69A10 is described rather than coded. Divergences from the `k230-canmv-v3-lcd` reference — which describes a different panel, an ST7701 at 480x800 — are recorded in `docs/evidence/dts-divergence.md`. **Drop when:** never, unless the board is upstreamed. ## 3. `goodix_berlin`, backported from v6.12 Source in `nix/patches/goodix-berlin/`, copied into `drivers/input/touchscreen/`, with a Kconfig and Makefile stanza appended. The pinned 6.6 tree has only the older GT9xx `goodix.c`, which does not speak the Berlin protocol at all. **This driver does not match the GT9895 upstream either** — see `gt9895-touch.md`; mainline has no GT9895 support at any version. The device tree therefore declares `"goodix,gt9895", "goodix,gt9916"` so the backport can attach via the fallback. Whether a GT9895 answers the Berlin protocol is an open experiment, not an established fact. One edit to the backported source: ``` sed -i 's|#include |#include |' ``` v6.12 moved `asm/unaligned.h` to `linux/unaligned.h`; 6.6 predates the move. **Drop when:** the pinned kernel reaches a version that carries `goodix_berlin` itself (v6.12+). The `unaligned.h` sed drops at the same time. If the experiment fails, this whole item is replaced by a port of LilyGO's RT-Smart `gt9895.c` instead. ## 4. `autoModules = false` Not a patch, but it changes what is built and belongs in this record. nixpkgs defaults `autoModules` to true, which enables every module it can on top of the defconfig. Against a vendor tree that is actively harmful: it turns on drivers the vendor never compiles and whose bugs have therefore never been hit. The first build died in `drivers/rpmsg/th1520_rpmsg.c` with `redefinition of init_module` — a driver for the **TH1520**, an unrelated SoC that `k230_defconfig` does not enable. Greybus was compiling too. `RPMSG_TH1520` is additionally forced off with `lib.mkForce no`. **Drop when:** never, while building against a vendor tree. ## 5. Config that NixOS needs and a vendor defconfig does not set `DEVTMPFS`, `DEVTMPFS_MOUNT`, `CGROUPS`, `INOTIFY_USER`, `SIGNALFD`, `TIMERFD`, `EPOLL`, `NET`, `SYSFS`, `PROC_FS`, `FHANDLE`, `CRYPTO_USER_API_HASH`, `CRYPTO_HMAC`, `CRYPTO_SHA256`, `TMPFS`, `TMPFS_POSIX_ACL`, `SECCOMP`, `BLK_DEV_INITRD`, `RD_GZIP`, `RD_ZSTD`. systemd refuses to boot without most of these, and the failure mode is an initrd panic that says nothing about the real cause. `TOUCHSCREEN_GOODIX_BERLIN_CORE` and `..._I2C` turn on item 3. **Drop when:** never, while running NixOS. ## Not carried, and worth knowing - **No patch for the panel driver.** `panel-canaan-universal` is stock. - **No patch for thermal.** `canaan_thermal` is stock and built in, and its limitation — a tripless zone, so temperature is readable but nothing acts on it — is a property of the vendor driver, not of anything we changed. See `docs/thermal.md`. - **No SMP patch.** Linux runs on one hart here because no K230 device tree declares a `cpu@1`. See `hardware-userspace.md`. ## Update after the first display bring-up (2026-09-20) **Task 4.1b resolved to "no patch".** The GT9895 answers the Berlin protocol. The v6.12 `goodix_berlin` backport already in item 3 binds it through a `"goodix,gt9895", "goodix,gt9916"` fallback in the device tree, and registers a touchscreen input device. Neither branch of 4.1b was taken: no chip entry (there is no chip table), and no port of LilyGO's `gt9895.c`. Evidence: `docs/evidence/touch-probe.txt`. **One patch added: the DRM fbdev colour depth.** ``` sed -i 's|drm_fbdev_generic_setup(drm_dev, 32);|drm_fbdev_generic_setup(drm_dev, 16);|' \ drivers/gpu/drm/canaan/canaan_drv.c ``` `canaan_drv.c:272` asks DRM's generic fbdev emulation for 32 bpp, which means `XRGB8888`. The driver's own RGB planes advertise `AR24 AR12 AR15 RG24 RG16 BG24` — `XR24` is **not** among them — so format negotiation fails and no `/dev/fb0` is created: ``` [drm] bpp/depth value of 32/24 not supported [drm] No compatible format found [drm] *ERROR* fbdev: Failed to setup generic emulation (ret=-22) ``` Observed on hardware, `docs/evidence/panel-probe.txt`. 16 bpp maps to `RGB565`, which those planes do advertise. This affects the fbdev console only. DRM clients negotiate format for themselves and already work — `modetest -s 48:568x1232@AR24` sets a mode on this panel with the unpatched driver. **Drop when:** the vendor driver either advertises `XR24` on its RGB planes or stops hardcoding 32 in the fbdev setup call. Worth re-checking on any kernel bump, since a one-line constant is easy for upstream to change. ## Stage-1 splash preservation The U-Boot handoff patch supplies an empty runtime `/chosen` property, `canaan,stage1-splash`, only after its image load and RM69A10 initialization succeed. The kernel does not treat that as a board property: an absent flag keeps the ordinary reset, initialization, and fbdev setup path. `panel-canaan-universal.c` reads the flag at probe into per-panel state. On a flagged boot it requests `dsi_reset` with `GPIOD_ASIS`, leaving GPIO22 at the stage-1 level instead of the vendor driver's `GPIOD_OUT_LOW`. Its first `prepare()` logs `canaan_panel_prepare: left as stage 1 set it`, clears that per-panel state, and returns before both the project reset pulses and the DCS init sequence. A later prepare therefore runs the complete existing reset and init path. The change is one-shot and does not persist through any later boot. `canaan_drv.c` reads the same runtime property and skips `drm_fbdev_generic_setup()` only when it is present. That avoids the initial fbdev hotplug/modeset from scanning out a zeroed buffer over the U-Boot frame; `/dev/fb0` is consequently absent on a flagged boot. A boot with no stage-1 success flag still creates the existing RGB565 fbdev console. The source was checked before making this change. In the pinned tree, `canaan_vo_enable_crtc()` initializes VO, programs timing/background, and enables register load; it does **not** call the display reset. The `0x91101090` write of zero then `0xffffffff` is in `canaan_vo_disable_crtc()` only. Earlier text that placed that reset on the enable path was wrong. This task intentionally does not change VO reset or DSI/VO reprogramming; the first-modeset hardware task decides whether either still causes a visible dark interval. **Drop when:** stage 1 no longer owns a pre-kernel panel image, or a later kernel handoff implementation adopts the running display state without the fbdev modeset and has hardware evidence for both splash and no-splash boots. If the first-modeset measurement shows a dark interval, restore the reset and init work in `prepare()` as task 4.4 requires; do not hide that fallback. ## First-VO/DSI handoff preservation — opt-in splash path The first stage-1 preservation patch deliberately stopped at the panel reset, DCS sequence, and fbdev. The first real DRM client still runs the Canaan runtime-PM commit order: CRTC enable calls `canaan_vo_enable_crtc()`, then the encoder calls `canaan_dsi_encoder_enable()`, then the active plane update writes the Linux framebuffer and `canaan_vo_flush_config()` loads it. In the pinned source this is `drm_atomic_helper_commit_tail_rpm()`, which enables modesets before committing active planes. The diagnostic implementation is carried in `nix/kernel.nix`. Each VO and DSI driver reads the runtime `/chosen/canaan,stage1-splash` boolean into an independent one-shot flag. It bypasses its first hardware reprogramming only when the adjusted mode exactly matches the U-Boot RM69A10 mode: 49.5 MHz, 568x1232, hsync 668..708 of 748, and vsync 1236..1252 of 1268. On that path the DSI still calls `drm_panel_prepare()` and `drm_panel_enable()`: the panel's existing one-shot bookkeeping consumes its flag without reset or DCS writes. The plane update and register load in that same atomic commit remain the handoff point; this is not framebuffer adoption. The VO bypass writes only the IRQ1 timing value which `canaan_vo_set_timing()` would calculate, leaving visual registers alone; normal DRM vblank enable remains responsible for its interrupt-enable bit. A timing mismatch logs a warning and uses the ordinary VO/DSI setup. The panel sees that the DSI did not authorize preservation, clears its flag, then explicitly restores the reset GPIO to output-high before its normal reset pulses; a failure to set that direction is returned from `prepare()`. A disable before the first enable clears all three one-shot states, so a later enable is also ordinary. The exact kernel cross-build and physical comparison are now recorded in `docs/evidence/splash-preserve-trial/README.md`: three automatic warm boots ended with the expected portrait shell; successive color updates were visible; removing the logo exercised full initialization and the framebuffer console; and display off/on exercised later full prepare with visible shell recovery. The first preservation-only image still had an approximately 1.1–1.2 second compositor startup dark gap. The kernel patch alone does not solve that gap, and the camera did not resolve the default keyboard well enough for complete keyboard geometry acceptance. Cold/battery boot and remaining real-touch acceptance are still open. The daily `k230.panelConsole=true` choice omits the logo and does not select the preservation path. The source does not independently read back or prove every inherited U-Boot register. The runtime flag and exact mode are the contract for this vendor path, with the normal initialization fallback retained. The separate opt-in Sway scene seed addresses the measured empty first compositor frame. **Drop when:** the physical first-frame evidence shows that ordinary VO/DSI programming is the correct handoff, or a supported DRM handoff replaces this one-shot vendor-driver experiment. Do not retain it merely because the stage-1 flag is present. ## Mainline kernel: stage-1 splash handoff (already ported; no new patch) Laptop evidence only (source reading and a build; no board). Task 1.2 of `the-mainline-kernel-keeps-the-boot-splash`. The vendor sed patches above do not apply to the mainline tree; the mainline panel/DRM sources under `nix/patches/mainline/drm/` already carry the same behaviour, so no driver was changed. Each row names the file, the flag, the log string, and when to drop it. | Behaviour | File and lines | Log string | Drop when | |---|---|---|---| | Flag read: `/chosen/canaan,stage1-splash` (set by `nix/patches/uboot-k230/0005-k230-stage1-splash-readiness.patch`) | `panel-canaan-universal.c:458`, `canaan_dsi.c:913-914`, `canaan_vo.c:835-836`, `canaan_drv.c:277` | none | stage 1 stops owning a pre-kernel panel image | | Panel reset line requested `GPIOD_ASIS` on a flagged boot; normal boot keeps `GPIOD_OUT_LOW` and the reset pulses | `panel-canaan-universal.c:460-466` | none | same | | First `prepare()` returns before reset pulses and DCS init when DSI preserved the mode; one-shot | `panel-canaan-universal.c:139-143` | `canaan_panel_prepare: left as stage 1 set it` | same | | Mode mismatch or disable before first enable: flag cleared, full init, reset pin driven by `gpiod_direction_output` on the ordinary path | `panel-canaan-universal.c:183-186`, `:251-254`, `:166-176` | `stage 1 splash: mode mismatch; reinitializing panel`, `stage 1 splash: panel disabled before handoff; reinitializing later` | same | | DSI first enable skipped only when the adjusted mode equals the U-Boot mode | `canaan_dsi.c:412-451`, `:492-496` | `stage 1 splash: preserving DSI to first plane update`, `stage 1 splash: DSI mode differs; reinitializing`, `stage 1 splash: DSI disabled before handoff; reinitializing later` | same | | VO first enable writes only the vblank timing value when the mode matches | `canaan_vo.c:654-683`, `:701-703` | `stage 1 splash: preserving VO to first plane update`, `stage 1 splash: VO mode differs; reinitializing`, `stage 1 splash: VO disabled before handoff; reinitializing later` | same | | No fbdev/DRM client set up over the logo on a flagged boot | `canaan_drv.c:277-281` (`drm_client_setup_with_fourcc()` is in the `else`) | `stage 1 splash: leaving fbdev unset` | a handoff that adopts the running display state without a client modeset, with hardware evidence for splash and no-splash boots | Fbdev-client question (is a client created by another route?). In the pinned source (`/nix/store/4jrzy64mg619y75lgrlihz78darglas6-linux-mainline-k230-drm-src`), `drivers/gpu/drm/clients/drm_client_setup.c` is the only place the in-kernel clients (fbdev, DRM log) are registered, and its doc comment states drivers call it after `drm_dev_register()`; `drm_drv.c` and `drm_probe_helper.c` do not call it. Within `nix/patches/mainline/` the only `drm_client_setup*` caller is `canaan_drv.c:280`. The configuration is `CONFIG_DRM_FBDEV_EMULATION=y`, `CONFIG_DRM_CLIENT_DEFAULT_FBDEV=y`, `CONFIG_FRAMEBUFFER_CONSOLE=y`, `CONFIG_VT=y` (the kernel `.config` is the `.configfile` of the kernel below), so the fbdev client exists in the image but is only instantiated by that skipped call; there is no DRM-log client (`DRM_CLIENT_LOG` absent). `CONFIG_FB_EFI=y` needs an EFI framebuffer which this boot path does not provide. Source reading cannot exclude a registration from a driver outside this tree, so whether `/dev/fb0` is really absent and the panel really stays lit is hardware gate 2.3. Build proof: `flock /tmp/k230-nix-build.lock nix build .#nixosConfigurations.k230-mainline-drm-shell.config.boot.kernelPackages.kernel --max-jobs 1 --cores 4` produced `/nix/store/f3rnipvbc5vqam8kwdmn3wgcl5rbz9yx-linux-riscv64-unknown-linux-gnu-7.3.0-rc5`, the kernel `.#sdImage` and the splash variant both use (the `panelConsole` option does not change the kernel derivation). **Drop when:** the vendor and mainline panel paths are unified, or a kernel bump removes the driver; re-check `canaan_drv.c:277-281` and the `drm_client_setup` callers on every kernel bump.