- Go 80%
- C 12.7%
- Nix 5.3%
- Shell 2%
The first trailer URL Jellyfin stores is often a dead link, so playing only trailers[0] failed for a working movie. Try up to three remote URLs in order, stopping at the first that plays, and report the last error if none do. |
||
|---|---|---|
| doc | ||
| keys | ||
| submission | ||
| tools | ||
| .gitignore | ||
| AGENTS.md | ||
| build.sh | ||
| cecctl.nix | ||
| configuration.nix | ||
| deploy.sh | ||
| faysa.nix | ||
| flake.lock | ||
| flake.nix | ||
| glplayer.nix | ||
| libva-v4l2-request-cedrus-ordering.patch | ||
| libva-v4l2-request-format-reprogram.patch | ||
| libva-v4l2-request-scaling-matrix.patch | ||
| libva-v4l2-request.nix | ||
| mesa-lima-linear-stride.patch | ||
| mpv-kmsro-render-node.patch | ||
| mpv-kmsro-vaapi-render-node.patch | ||
| mpv-rg8-texture-rg.patch | ||
| README.md | ||
| remotectl.nix | ||
| sd-image.nix | ||
| simple-card-tdm-slot-width.patch | ||
| uboot-sunxi-larger-kernel-region.patch | ||
Faysa
NixOS SD-card image for the Orange Pi One (Allwinner H3, armv7l).
The image is cross-compiled from an x86_64 host. U-Boot (with the upstream
orangepi_one_defconfig), the mainline kernel, the H3 device tree and an
extlinux boot entry are all installed into a raw SD-card image.
Build
nix build .#nixosConfigurations.orangepi.config.system.build.sdImage
# or: ./build.sh
The raw image lands in result/sd-image/*.img (~2.9 GiB). On first boot the
root partition is grown to fill the SD card.
Flakes only see Git-tracked files. If you add or edit a
.nixfile and Nix complains that a path "is not tracked by Git", rungit add <file>.
How the cross-compilation works
flake.nix sets:
nixpkgs.hostPlatform = "armv7l-linux";
nixpkgs.buildPlatform = "x86_64-linux";
Nixpkgs then builds a cross toolchain (runs on x86_64, emits armv7l). The
toolchain, glibc, systemd, rustc/cargo, etc. are available from
cache.nixos.org, so only a few hundred packages (kernel, U-Boot, NetworkManager,
…) are built locally. A from-scratch build took roughly an hour on 16 cores.
Do not use system = "armv7l-linux" on its own: that asks for a native
armv7l build, which your x86_64 machine cannot execute. The alternative is
boot.binfmt.emulatedSystems = [ "armv7l-linux" ], which runs armv7l build
tools through QEMU — reliable, but there is no armv7l binary cache, so
everything (including the C toolchain) bootstraps from source under emulation.
That is dramatically slower.
Flash
Write the whole image to the raw device, not to a partition:
sudo dd if=result/sd-image/nixos-image-sd-card-*.img of=/dev/sdX bs=4M status=progress conv=fsync
sync
Replace /dev/sdX with the SD card device (check carefully with lsblk).
First boot
- Serial console on
ttyS0at 115200 8N1 (alsotty0/HDMI). - Login: user
pi, passwordorangepi(change it!). - SSH is enabled;
PermitRootLoginisno, sudo needs no password forwheel. - Any
keys/*.pubfiles added before the build are authorised forpi. - Wired Ethernet is managed by NetworkManager.
- The device is named
faysaand advertises itself with Avahi, sofaysa.localresolves on the LAN. faysastarts automatically on the HDMI display (faysa.service, amediauseruser service) and shows the setup checks: network connectivity, then a Jellyfin sign-in by remote (Quick Connect) or by scanning a QR code from a phone. It needs no SSH and no hand-written config. Running inside themediauserlogin session is what gives it the session's PipeWire and D-Bus for playback. The getty autologin shell stays underneath;systemctl --user stop faysa(asmediauser) hands the console back to it.
Fast iteration: deploy over SSH
Once the device is running you do not need to reflash to test a change. The
image runs sshd and makes the default user a Nix trusted user, so the
closure can be cross-built on this host and pushed straight to the Pi:
./deploy.sh pi@<device-ip>
deploy.sh is a thin wrapper around:
nixos-rebuild switch \
--flake .#orangepi \
--target-host pi@<device-ip> \
--sudo
It builds on this x86_64 host by default (omit --build-host, or it will
try to SSH to that host instead), nix copys the resulting closure to the
device and runs its activation script there. No image, no SD-card swap, no
reboot.
One-time setup
SSH key auth is expected — otherwise the push has to prompt for a password on
every connection. Drop your public key into keys/ before building:
cp ~/.ssh/id_ed25519.pub keys/
git add keys/id_ed25519.pub # flakes only see Git-tracked files
Every keys/*.pub file is installed as an authorised key for pi (see
keys/README.md). If the device still runs the previous image, flash once
more so it gains the key and the trusted-users setting; after that, updates
are pushed in place.
deploy.sh defaults to pi@orangepi.local and passes
-o StrictHostKeyChecking=accept-new (the host key changes on every reflash).
Why the push is allowed
nixos-rebuild --target-host uploads store paths that were built locally and
are therefore unsigned. Nix refuses to import unsigned paths unless the
uploading user is trusted, so configuration.nix sets:
nix.settings.trusted-users = [ "root" "@wheel" ];
pi is in wheel. Activation (switch-to-configuration, the system profile
update) then runs as root through sudo, which is passwordless for wheel.
This is convenient for a throwaway dev image; on a shared machine note that
trusted users can change Nix daemon settings, so scope the list down if that
matters.
Video playback (mpv)
mpv is installed for the mediauser account and is the baseline test for
whether the image can put video on screen. There is no X server and no Wayland
compositor, so mpv is cross-built with only the DRM/KMS backends (plus VA-API
and a headless ffmpeg).
Log in on the serial console or over SSH as mediauser, then:
# GPU output through mesa's lima + the sun4i DRM/KMS driver, over HDMI.
# mpv picks the DRM/EGL context automatically when X11/Wayland are absent.
mpv file.mp4
# CPU-only output straight to a DRM dumb buffer, as a fallback.
mpv --vo=drm file.mp4
hardware.graphics.enable installs Mesa (with the lima gallium driver for
the H3's Mali-400) into /run/opengl-driver, which is where mpv's RUNPATH
looks for EGL/GBM. Mesa is the largest single addition to the closure (~1 GiB),
so the image is now ~4.7 GiB rather than ~2.9 GiB.
Hardware decoding
Status: H.264 decodes correctly with both stacks and mpv --hwdec=vaapi
plays it correctly with zero-copy dmabuf import. After the kernel fix and the
libva fixes described below, H.264 decodes byte-for-byte identically to
software with no nondeterministic failures, in both ffmpeg -hwaccel vaapi and
GStreamer. mpv's zero-copy NV12 display had a chroma bug (blue rendered
magenta) because mpv sampled the imported DRM_FORMAT_GR88 chroma plane as
luminance-alpha on its GL 2.1/ES2 context; mpv-rg8-texture-rg.patch
fixes it by enabling rg8 via GL_ARB/EXT_texture_rg. A minimal lima
reproducer (tools/cedrus/lima-r8g8-probe.c) proved lima itself was fine. HEVC has a small residual
difference and MPEG-2 has a separate GStreamer-only problem; see
doc/cedrus-investigation.md §13–14. The GStreamer zero-copy KMS path needs
one extra kmssink option to be visible on this board — the kernel console
owns the plane above the video layer; see below.
These pieces are in place:
- Kernel.
sd-image.nixturns onCONFIG_VIDEO_SUNXI_CEDRUS(gated behindCONFIG_VIDEO_SUNXI), so mainline'ssunxi-cedrusdriver registers/dev/video0as a stateless V4L2 decoder. It is loaded explicitly viaboot.kernelModules. - CMA. cedrus allocates every decoded frame from the contiguous memory
pool (~3 MiB for 1080p). The 32 MiB default fills (and fragments) almost
immediately, which shows up as
cedrus ... dma alloc of size N failedand dropped frames, soboot.kernelParamssetscma=256M. - VA-API driver.
libva-v4l2-request.nixpackages the driver that turns VA-API calls into V4L2 request API calls. It installs throughhardware.graphics.extraPackagesand is selected withLIBVA_DRIVER_NAME=v4l2_request(set inconfiguration.nix). - mpv. built with the DRM/EGL GPU context, plus a patch for kmsro render-node discovery.
What works, verified on an Orange Pi One:
v4l2-ctl --list-devices # cedrus /dev/video0, H264+HEVC+MPEG2 formats
vainfo # driver v4l2-request, H264/HEVC/MPEG2 profiles
Frames decode byte-for-byte identically to software
(mean-absolute-difference 0.00 for both Y and UV), and repeated runs give
identical output.
The failures this section used to describe turned out to be a nondeterministic
VPU decode error, not bad reference handling. The H3 VPU is not
cache-coherent, but cedrus allocated its auxiliary buffers with
DMA_ATTR_NO_KERNEL_MAPPING; on 32-bit ARM that flag leaves the CMA pages
cacheable in the kernel linear map, so stale cache lines corrupted the buffers
and the VPU failed to decode the first slice of a stream with
VE_H264_STATUS_DECODE_ERR_INT. tools/cedrus/cedrus-aux-coherency.patch
(wired in via boot.kernelPatches in sd-image.nix) allocates those buffers
coherently and zeroes them. Full write-up: doc/cedrus-investigation.md §13.
hwdec is requested explicitly (it is off by default):
mpv --hwdec=vaapi file.mp4 # zero-copy dmabuf import via lima
mpv --hwdec=vaapi-copy file.mp4 # cedrus decodes, frames copied back
--hwdec=vaapi fixes the pacing completely (a 1080p24 sample that dropped
~135 frames in software drops 1-3), and the libva decode is now bit-exact: the
wrong-luma bug was the fork's Inter-Y 8x8 scaling-list mapping, fixed by
libva-v4l2-request-scaling-matrix.patch (§14.9). mpv's zero-copy NV12 chroma
was then wrong (blue rendered magenta) because, on a GL 2.1 context without
GL_ARB_texture_float, mpv picked its GL_LUMINANCE_ALPHA fallback for the
two-component chroma plane and sampled .ra, while the imported
DRM_FORMAT_GR88 binds as GL_RG. mpv-rg8-texture-rg.patch enables
mpv's rg8 format via GL_ARB/EXT_texture_rg (§14.12). The full sample plays
with hardware decode, zero-copy and 1-3 dropped frames (the display-sync
artefact).
The equivalent GStreamer KMS path (also correct and zero-copy):
gst-launch-1.0 -e filesrc location=file.mkv ! matroskademux ! h264parse ! \
v4l2slh264dec ! kmssink plane-properties=s,zpos=3
plane-properties=s,zpos=3 is required to make the picture appear, not just
cosmetic: sun4i-drm exposes the mixer's RGB "UI" layer as the DRM primary
plane and the YUV/NV12 "VI" layer as an overlay, the kernel framebuffer
console occupies the primary, and the driver composites the primary above
the overlay. kmssink defaults to the VI overlay (the only plane that
accepts the decoder's NV12 dmabufs), so without the zpos bump the console
hides the video and the pipeline runs to EOS with no picture. Raising the VI
plane above the UI plane keeps the path zero-copy. The syntax needs a
structure name — s,zpos=3, not zpos=3. A full 1080p24 clip then plays in
real time with a couple of seconds of CPU total and no dropped buffers; see
doc/cedrus-investigation.md §14.8.
HEVC still shows a small residual and MPEG-2 is still wrong under GStreamer.
cedrus only covers H.264/H.265/MPEG-2; anything else falls back to software.
Cross-checking with GStreamer
gstreamer and gst-plugins-base/good/bad are installed, providing the
mainline v4l2codecs plugin (v4l2slh264dec, v4l2slh265dec,
v4l2slmpeg2dec) and kmssink. It is an independent implementation of the
same stateless V4L2 API, which is what lets us tell a kernel problem apart from
a userspace-driver problem.
Gotcha:
gst_all_1.gstreamer's default output isbin, and the core plugins (filesrc,fakesink, …) live inout, so theGST_PLUGIN_SYSTEM_PATH_1_0inconfiguration.nixnamesgstreamer.outexplicitly. Without that,gst-launchfinds the codec plugins but not the core elements.
# Decode-only smoke test.
gst-launch-1.0 -e filesrc location=file.mp4 ! qtdemux ! h264parse ! \
v4l2slh264dec ! fakesink
# Zero-copy to the screen: the decoder emits NV12_32L32 (cedrus's tiled format)
# backed by dmabufs, which kmssink imports directly. plane-properties=s,zpos=3
# raises the video plane above the kernel console plane so the picture is
# actually visible (see "Hardware decoding" and §14.8 of the investigation doc).
gst-launch-1.0 -e filesrc location=file.mp4 ! qtdemux ! h264parse ! \
v4l2slh264dec ! kmssink plane-properties=s,zpos=3
# Dump frames to compare against a software decode. Forcing linear NV12 avoids
# having to de-tile; the native NV12_32L32 also works.
gst-launch-1.0 -e filesrc location=file.mp4 ! qtdemux ! h264parse ! \
v4l2slh264dec ! video/x-raw,format=NV12 ! filesink location=out.nv12
Patches carried
mainline cedrus and the libva fork do not agree on call ordering, the fork's format handling is too conservative for mpv, it mis-maps one H.264 scaling list, and mpv mishandles the two-plane NV12 format the fork exports, so five patches are carried (three on the fork, two on mpv):
libva-v4l2-request-cedrus-ordering.patch. The driver programs the OUTPUT (compressed) format invaCreateContext, i.e. aftervaCreateSurfaceshas already allocated CAPTURE buffers. cedrus derives the CAPTURE format from OUTPUT and rejectsVIDIOC_S_FMTon OUTPUT once CAPTURE buffers exist (vb2_is_busy(peer_vq)→-EBUSY), so decoding never starts. The patch moves the OUTPUT format program tovaCreateSurfaces, where the picture dimensions are available.libva-v4l2-request-format-reprogram.patch. The fork programs the V4L2 formats only on the firstvaCreateSurfacescall, so mpv's surface-format probe (128x128, with the MPEG-2 profile) latched the device toMG2S 128x128and the real H.264 decode failed withV4L2_BUF_FLAG_ERROR, making mpv fall back to software. The patch re-programs the OUTPUT/CAPTURE formats when the codec changes or the requested surface grows, and gates the driver's per-framerequest_log()behindV4L2_REQUEST_DEBUG.libva-v4l2-request-scaling-matrix.patch. The fork copied H.264's Inter-Y 8x8 scaling list toscaling_list_8x8[3](Inter Cb) instead of[1], leaving the Inter-Y slot zero. cedrus only reads lists 0 and 1, so High-profile inter macroblocks reconstructed wrong luma while chroma (which uses the 4x4 lists) stayed exact — the "wrong luma on B-frame streams" symptom. The patch places the list at index 1.mpv-rg8-texture-rg.patch. mpv's VA-API dmabuf interop imports the chroma plane withDRM_FORMAT_GR88(which binds asGL_RG), but on its GL 2.1 context mpv'srg8format is gated behind GL3/ES3/F_GL2F, so it falls back to theGL_LUMINANCE_ALPHAformat and samples.ra— whose.ais 1.0 for the importedGL_RGtexture. The patch enablesrg8viaGL_ARB/EXT_texture_rg, which lima provides. Seedoc/cedrus-investigation.md§14.12.mpv-kmsro-render-node.patch. The H3 is a kmsro setup: display issun4i-drm(the display card) and the GPU islima(renderD128), two separate DRM devices. mpv's DRM/EGL windower usesdrmGetRenderDeviceNameFromFd(), which only finds a render node belonging to the primary device, so it comes up empty and the VA-API interop cannot create a display. The patch falls back to the first usable render node.mesa-lima-linear-stride.patch. lima validates an imported linear buffer againstALIGN(width, 16)for both the stride and the size. The 16-texel alignment is a tiled-layout rule, but applying it to linear buffers rejects the chroma plane of any NV12 frame whose width is not a multiple of 32: the plane iswidth/2texels wide and shares the luma stride, so at 720x356 its 720 B stride is below lima's computed 736 B minimum, and at 720x480 its 174592-byte buffer is below the computed 176640-byte minimum. Either way the EGL import fails on every frame (linear imported buffer stride is smaller than minimal/imported bo size is smaller than expected). The patch uses the unaligned width and the buffer's real stride for the linear checks, so 720-wide video can use zero-copy. Seedoc/cedrus-investigation.md§14.13.
Notes on the driver package
- Upstream Bootlin
libva-v4l2-requestis unmaintained (last commit 2019) and still uses the pre-upstream privateV4L2_CID_MPEG_VIDEO_H264_*control ABI, so it does not even compile against current kernel headers. The package here tracks theCn8001fork, which uses the stableV4L2_CID_STATELESS_*controls (Linux >= 6.0) that mainlinecedrusimplements. - ffmpeg's
v4l2requesthwaccel is not in mainline ffmpeg (n9.0 only ships the statefulv4l2_m2m, which cedrus does not implement), so there is no--enable-v4l2-requestbuild flag to turn on. VA-API is the way to drive cedrus from mpv. - Audio over HDMI now works; see "Audio playback (HDMI)" below and
doc/audio-investigation.md.
Audio playback (HDMI)
Status: working. ALSA card 0 is named HDMI and plays 48 kHz stereo over
HDMI (verified audibly). mpv uses it automatically:
mpv file.mp4 # audio + video
mpv --no-video file.flac # audio only
The Orange Pi One has no analogue audio jack, so HDMI is the only onboard
output. Mainline ships no Allwinner board with an HDMI sound card, so
sd-image.nix adds one with hardware.deviceTree.overlays:
- enable
&i2s2(the H3 I2S instance wired to the HDMI; the Allwinner BSP drives HDMI audio from the same register base,0x01c22800), - add
#sound-dai-cells = <0>to&hdmi, so the DW-HDMI core's already auto-registeredhdmi-codeccan be referenced assound-dai = <&hdmi>, - link them with a
playback-onlysimple-audio-card.
The DW-HDMI I2S input expects a 64·fs BCLK, but sun4i-i2s derives BCLK from
the physical sample width (16-bit ⇒ 32·fs). The overlay requests 32-bit slots
(dai-tdm-slot-width = <32>), which keeps 16-bit samples — and therefore
IEC958/AC3/DTS passthrough — intact while raising BCLK to 64·fs.
simple-card parses those properties but drops them unless a slot-width map is
present, so simple-card-tdm-slot-width.patch is carried. Full write-up,
evidence (including the I2S clock-divider register readback) and next steps:
doc/audio-investigation.md.
services.pipewire (ALSA and PulseAudio compatibility) is enabled and mpv is
built with native PipeWire output, so media playback and UI cues can mix; two
simultaneous streams work. WirePlumber selects the HDMI card as the default
sink. security.rtkit is enabled for real-time scheduling.
Input / wireless remote
Status: working. A 2.4 GHz air-mouse remote (XING WEI, 1915:1025)
enumerates as a USB composite device and creates five evdev nodes under
/dev/input/. With no X server or Wayland compositor, raw evdev is the control
interface; mediauser can read the nodes (input group), and remotectl
reads and decodes them all at once:
sudo evtest # interactive picker, one device at a time
remotectl # decoded events from every remote node
The remote is split across several nodes: a keyboard (typing, arrows, OK is
KEY_ENTER), a relative pointer (REL_X/REL_Y only), a Consumer Control
node (volume/mute/back/home/voice) and a System Control node (power). The
pointer node emits BTN_LEFT/BTN_RIGHT and relative motion but no wheel
events, and a fifth, unnamed node never emits anything at all. eventN numbers
are assigned in registration order and move between boots, so remotectl
matches devices by name; the full signal map, capability dumps and the
permission/policy details are in doc/input-investigation.md.
Two shapes of this remote are deliberately neutered:
services.logind.settings.Login.Handle{Power,Suspend,Hibernate}Key = "ignore"stops the power/sleep buttons from acting on the headless box.systemd.ctrlAltDelUnitpoints at an emptyctrl-alt-del-blocked.target, so the Fn combination that synthesises Ctrl+Alt+Del no longer reboots the board. The keystrokes are still delivered to applications.
remotectl is a small libevdev reader (tools/input/remotectl.c) that matches
the remote by device name, polls all matching nodes and prints decoded events;
it is the seed of the real control layer.
HDMI-CEC
Status: working end to end against a Denon AVR-S960H. The Pi sits behind a
Denon AVR-S960H on the way to a TV; the receiver answers CEC fully (Audio
System, logical address 5) and discovery, power on/off, volume/mute, audio
status, system-audio mode and input routing are all verified. The TV itself
ACKs polls but answers no queries, and the two PC monitors tested implement no
CEC. Full write-up, protocol background and evidence:
doc/cec-investigation.md.
The H3's DesignWare HDMI block has an integrated CEC controller; mainline's
dw-hdmi-cec registers it as /dev/cec0 and needs no device-tree change.
cecctl (tools/cec/cecctl.c) claims a logical address and sends/receives
messages:
cecctl info # adapter, physical + logical address
cecctl topology # which logical addresses answer
cecctl discover 0 # version, PA, vendor, OSD name, power, features
cecctl power on|off|toggle|status # TV power
cecctl volume up|down|mute|status # TV / audio-system volume
cecctl input 2.0.0.0 # switch the TV to a physical address
cecctl system-audio on|off|status # AVR audio routing; `on` powers it on
cecctl ui play|up|enter|... # any User Control button (`ui list`)
cecctl send 40 04 # raw message
systemd.services.cec-adapter waits for the sink EDID (cecctl wait) and
claims a playback LA named Faysa with RC passthrough, retrying until the HDMI
link is up. With passthrough on, the TV remote's buttons are decoded by the
kernel (rc-cec) and arrive on a dw_hdmi evdev node, which the frontend can
read with input.Open("dw_hdmi") to drive the UI just like the USB remote.
The Go frontend drives the same control path directly: tools/ui/cec speaks
the CEC UAPI with no cgo or helper binary, and the browser routes the USB
remote's volume/mute buttons to the audio system (falling back to mpv) and its
power button to a system power toggle, while playback start performs One Touch
Play and a System Audio Mode Request so a receiver powers on and takes over
audio. See tools/ui/README.md.
Layout
| Path | Purpose |
|---|---|
flake.nix |
Inputs, host/build platform, nixosConfigurations.orangepi |
configuration.nix |
Hostname, users, SSH, NetworkManager, packages |
sd-image.nix |
Orange Pi One board support: U-Boot offset, DTB, extlinux, cedrus |
libva-v4l2-request.nix |
VA-API driver for the stateless V4L2 decoder (cedrus) |
libva-v4l2-request-cedrus-ordering.patch |
Fixes the VA driver's OUTPUT/CAPTURE format ordering |
libva-v4l2-request-scaling-matrix.patch |
Fixes the Inter-Y 8x8 scaling-list mapping (wrong luma) |
mpv-rg8-texture-rg.patch |
Enables mpv's rg8 format on GL2.1/ES2 via GL_ARB/EXT_texture_rg |
mpv-kmsro-render-node.patch |
Makes mpv find the lima render node on kmsro |
mesa-lima-linear-stride.patch |
Lets lima import the NV12 chroma plane of non-32-aligned widths (720) |
mpv-kmsro-vaapi-render-node.patch |
Makes VA-API zero-copy work under the libmpv render API on kmsro |
simple-card-tdm-slot-width.patch |
Makes simple-card apply dai-tdm-slot-* so HDMI gets 64·fs BCLK |
remotectl.nix |
libevdev package for the remote's input-validation tool |
cecctl.nix |
HDMI-CEC control tool (kernel UAPI, no dependencies) |
tools/input/remotectl.c |
Multi-device evdev reader/decoder for the remote |
tools/cec/cecctl.c |
Userspace CEC: claim, discover, power, volume, input, monitor |
doc/cec-investigation.md |
CEC protocol, discovery/error model, monitor verdict, usage |
doc/frontend-investigation.md |
Go full-screen frontend options (KMS, software 2D, video handoff) |
doc/jellyfin-investigation.md |
Jellyfin discovery, auth, listing, streaming and transcode fallback |
tools/ui/ |
Pure-Go frontend toolkit: DRM/KMS (page flips), evdev input, mpv handoff |
tools/ui/jellyfin/ |
Jellyfin API client: discovery, Quick Connect/password auth, sessions, listing, stream URLs |
tools/ui/cmd/jellyfin/ |
CLI to discover servers, authenticate, list items and resolve streams |
uboot-sunxi-larger-kernel-region.patch |
Moves U-Boot's FDT/ramdisk load addresses clear of the kernel |
build.sh |
Convenience wrapper around nix build |
deploy.sh |
Cross-build and push the system to a running device over SSH |
submission/ |
Prepared upstream patch series (ASoC fix + Allwinner DT) and sending instructions |
keys/ |
Public keys authorised to log in as pi (see keys/README.md) |
What sd-image.nix adds
- Reuses
ubootOrangePiPcwith theorangepi_one_defconfigdefconfig (nixpkgs ships no Orange Pi One package). - Writes
u-boot-sunxi-with-spl.binat byte offset 8192 (bs=1024 seek=8), where the Allwinner BROM expects theeGON.BT0SPL. - Sets
hardware.deviceTree.name = "sun8i-h3-orangepi-one.dtb"so extlinux emits an explicitFDTline. - Enables
generic-extlinux-compatible, which U-Boot's distro-boot reads from/boot/extlinux/extlinux.confon the ext4 root partition. - Patches U-Boot's sunxi memory layout (see below).
- Enables the H3 VPU (
CONFIG_VIDEO_SUNXI_CEDRUS) and loads it viaboot.kernelModules(see "Hardware decoding" above). - Adds the HDMI sound card via a device-tree overlay, plus the
simple-card-tdm-slot-width.patchkernel fix (see "Audio playback (HDMI)").
Why U-Boot is patched
U-Boot's default layout for sunxi boards with at least 256 MiB of DRAM loads
the kernel at 0x42000000 and the device tree at 0x43000000 — only 16 MiB
apart. The armv7l kernel Image is larger than that, so U-Boot aborts before
booting:
ERROR: FDT image overlaps OS image (OS=42000000..4313f200)
EXTLINUX FAILED: continuing...
uboot-sunxi-larger-kernel-region.patch moves the FDT, script, PXE, overlay
and ramdisk addresses up to 0x4B000000+ (mirroring the ARM64 sunxi layout),
giving the kernel ~144 MiB of room. The addresses stay below 0x50000000, so
they are valid on 256 MiB boards too. The patch is applied through the
ubootOrangePiOne overlay in sd-image.nix.
Notes / possible tweaks
- The 30 MiB FAT "FIRMWARE" partition is unused on sunxi (it exists only for
Raspberry Pi firmware). Shrink it via
sdImage.firmwareSizeif desired. sdImage.compressImage = falsekeeps iteration fast; set it totrueto get a.img.zst.sd-image.nixpulls inhardware.enableAllHardware(via the base SD module), which drags in a lot of firmware. Sethardware.enableAllHardware = falseplus the specific firmware you need to slim the closure.- Enable
services.avahi.nssmdns4 = trueif you wantorangepi.localto resolve without having to look up the DHCP-assigned IP.