Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Turing RK1

The Turing RK1 is an RK3588 compute module that seats in a Turing Pi 2 cluster board. boot2deb ships it as a small family of recipes over one hardware base — kernel v7.2 (linux-stable), u-boot v2026.07, and the RGA / VEPU / VDPU (and NPU) drivers carried in-kernel via the rk3588-accel patch series. It is a supported configuration in its own right and a good starting point for any RK3588 board.

The variants differ along two independent axes — the Debian suite, and whether the Rockchip media userspace is built in:

RecipeSuiteMedia userspace
turing-rk1/forkyforky— (base)
turing-rk1/trixietrixie— (base)
turing-rk1/media-accel-forkyforkyffmpeg-rk + MPP + RGA + Vulkan
turing-rk1/media-accel-trixietrixieffmpeg-rk + MPP + RGA + Vulkan
turing-rk1/jellyfin-forkyforkyffmpeg-rk + MPP + RGA, plus Jellyfin
turing-rk1/jellyfin-trixietrixieffmpeg-rk + MPP + RGA, plus Jellyfin

The jellyfin-* pair is the media-server build — media-accel plus the Jellyfin server, pre-pointed at ffmpeg-rk; see Accelerated Jellyfin. turing-rk1/util is not an image along these axes at all but a u-boot-only recovery tool — see Writing the NVMe from u-boot.

Every variant carries the same accel kernel: the VEPU / VDPU / RGA and NPU drivers are present in all of them, because the patches and kconfig live on the kernel axis. A base image simply omits the Rockchip media userspace — the hardware blocks are there but dark. A media-accel image adds the media-accel-rockchip feature, which builds and installs ffmpeg-rk, librockchip-mpp1, and librga2 on top. The split is deliberate: because the kernel already carries the capability, those debs can equally be installed onto a running base image later. forky is the RK1’s validated suite.

Two RGA drivers exist; this image builds the out-of-tree one

Kernel 7.2 added an in-tree V4L2 driver for the RK3588’s RGA3 cores, so from that release the SoC has two drivers to choose between, and the choice is a kconfig one — ROCKCHIP_MULTI_RGA for the vendor driver, VIDEO_ROCKCHIP_RGA for the in-tree one. These images build the vendor driver and leave the in-tree one unset, for reasons that are about what reaches FFmpeg rather than about code quality:

  • The ABI. librga, and therefore scale_rkrga, vpp_rkrga and overlay_rkrga, speak the vendor /dev/rga interface. The in-tree driver exposes V4L2 video nodes, and FFmpeg ships no V4L2 mem2mem filter to drive them.
  • The cores. The in-tree driver deliberately exposes one core, to avoid an ABI break when multi-core scheduling is added later. The vendor driver schedules across all three.
  • 10-bit. The in-tree driver implements scaling and colour conversion only; 10-bit YUV is on its own list of what is not done yet. A 10-bit HEVC decode on this hardware produces NV15, and converting that is the one job nothing else on the board does.

If you want a kernel with no out-of-tree code and can live without the FFmpeg filter path, the in-tree driver is a supported thing to switch to: unset CONFIG_ROCKCHIP_MULTI_RGA and set CONFIG_VIDEO_ROCKCHIP_RGA in an overlay fragment, and drop media-accel/kernel/072 from the series so the device-tree nodes keep their mainline compatibles.

The media-accel-* pair also carries the vulkan feature: Mesa’s Vulkan drivers and the loader, which is what makes the Vulkan filters ffmpeg-rk is already built with actually open. On a box driven from the command line those are the fastest scale, tone-map and composite route the hardware has — scale_vulkan measures 53.8 dB against swscale at 18x the efficiency and 2.8x the speed. It costs about 305 MiB installed, two thirds of which is the LLVM that Mesa’s software rasterizer pulls in rather than the Mali driver itself. The jellyfin-* pair deliberately does not carry it: Jellyfin’s HardwareAccelerationType is a closed enum with no Vulkan member, so the server can never emit a Vulkan filter and the packages would be reachable only from a shell. Add it there with turing-rk1/forky+media-accel-rockchip+jellyfin+jellyfin-rockchip+vulkan if you want the command line too — with --image-size 3G, since a feature selection takes the device’s 2G and cannot carry the jellyfin-* recipes’ own larger volume.

Every image here ships a redistributable FFmpeg; see The FFmpeg a build ships is redistributable for the opt-in nonfree flavour and why no hardware path depends on it.

Build the base image as in Getting started:

boot2deb build turing-rk1/forky

or, for a ready hardware-transcode host, the media-accel variant:

boot2deb build turing-rk1/media-accel-forky

Either produces a whole-disk image (GPT, u-boot in the reserved gap ahead of the first partition, then the ext4 rootfs), so a single write lays down everything, bootloader included. Artifacts are named for the whole build point, so turing-rk1/forky writes build/turing-rk1/forky/artifacts/turing-rk1-forky.img.xz and the media-accel variant writes turing-rk1-media-accel-forky.img.xz. The flashing and boot notes below use turing-rk1/forky; they are identical for any variant (the bootloader and disk layout do not change), so substitute your recipe name throughout.

Flash

Press the built artifact into a verified, optionally personalized raw image first — one master, one file per unit:

boot2deb press turing-rk1/forky rk1-03.img \
    --hostname rk1-03 --ssh-key "$(cat ~/.ssh/id_ed25519.pub)"

The RK1 is a compute module, not a board you plug a card reader into, so the usual write path is the Turing Pi’s BMC, which writes the module’s eMMC. Both BMC routes take the raw file press produces:

tpi flash -n 2 -l -i rk1-03.img       # the tpi CLI, node 1-4

or the BMC web UI’s flash upload. For a removable or NVMe/USB medium you write on another machine — the same image boots from any medium the board scans, since u-boot discovers its root device at runtime — use any flasher, dd included:

lsblk    # confirm the device; dd overwrites it whole
sudo dd if=rk1-03.img of=/dev/sdX bs=4M status=progress conv=fsync

See Producing images for verification, the seed keys, and per-site additions. The tpi CLI and web UI evolve; see Turing Pi’s flashing docs for the current specifics.

Streaming to the eMMC over USB mass storage

tpi flash stages the whole image on the BMC before it writes. The BMC has another mode that does not:

tpi advanced msd --node 2

That reboots the node into USB mass-storage mode, after which its eMMC is an ordinary SCSI disk on the BMC — no custom firmware, no u-boot in the loop, no UART. Allow about ten seconds for it to enumerate, then find it by the vendor string the RK1’s eMMC reports rather than by guessing a letter:

ssh root@<bmc> 'grep -l Rockchip /sys/block/*/device/vendor'
# /sys/block/sda/device/vendor   ->  the node's eMMC is /dev/sda

With the disk present, the image streams straight through with nothing staged on either machine:

xzcat rk1-03.img.xz | ssh root@<bmc> 'dd of=/dev/sda bs=4M conv=fsync'

conv=sparse is worth knowing about here and worth understanding before you use it: it makes dd seek over runs of NULs instead of writing them, which is a real saving across a USB mass-storage link, because most of a fresh image is zeros. The catch is that seeking leaves whatever was there before — so it is a faster write, not a clean one. On a node whose eMMC has held another system, the stale bytes can include an old backup GPT or a filesystem signature that blkid will still find. Use it on a disk you do not mind reading as half-overwritten, and leave it off when you want the medium to say exactly what the image says.

The same mode is the route by which an already-flashed node can be edited in place rather than reflashed — mount the exposed rootfs on the BMC and change what you need, which is how a boot2deb seed key can be applied after the fact. Whether that half works is a property of the BMC firmware rather than of this mode: it needs ext4 in the BMC’s kernel and enough userland to be useful. One command tells you before you plan around it:

ssh root@<bmc> 'grep -w ext4 /proc/filesystems && command -v mount'

The write half needs neither, so it stands on its own where the mount half does not.

This exposes the eMMC and nothing else, exactly as tpi flash does. The M.2 disk stays invisible to the BMC in this mode as in every other; see Writing the NVMe from u-boot and Installing to the NVMe from the booted node for the two routes that reach it.

u-boot on eMMC, OS on a separate disk

A common RK1 setup keeps only u-boot on the eMMC and runs the OS from an NVMe or USB disk. The builder produces the two pieces for this directly.

The whole split at once — build the split layout, which emits two images instead of one:

boot2deb build turing-rk1/forky --layout split
  • turing-rk1-forky-boot.img — u-boot only (idbloader + u-boot.itb at their offsets, no GPT), for the eMMC.
  • turing-rk1-forky-rootfs.img — GPT + rootfs, for the NVMe/USB disk.

press emits the same pair as personalized copies — press turing-rk1/forky --layout split --boot-out emmc.img --rootfs-out nvme.img --hostname rk1-03 — with the seed riding the rootfs half.

Just the bootloader — if you only need the eMMC u-boot image (e.g. to re-flash the bootloader across nodes) without building a whole OS, the u-boot stage emits it on its own:

boot2deb build turing-rk1/forky --stage uboot

This writes turing-rk1-forky-boot.img (a few MiB, gap-sized) alongside the raw turing-rk1-forky-idbloader.img and turing-rk1-forky-u-boot.itb. Flash the -boot.img to the eMMC with tpi/web UI; write the rootfs image to the target disk.

Because tpi/web UI flash the eMMC only, the rootfs image goes onto the NVMe/USB disk by another route. The bootloader itself is the shortest one — see below.

Installing to the NVMe from the booted node

The shortest way onto the M.2 disk uses no serial console and no bootloader prompt at all: flash the eMMC with the BMC — which it can do in one step — carrying the image inside itself, boot that, and let the node write its own NVMe.

boot2deb press turing-rk1/forky rk1-03.img --embed-image \
    --hostname rk1-03 --ssh-key "$(cat ~/.ssh/id_ed25519.pub)"
tpi flash -n 2 -l -i rk1-03.img

--embed-image carries the recipe’s own compressed artifact inside the pressed image at /var/lib/boot2deb/install/. Power the node on, let first boot finish, then from an ssh session:

sudo boot2deb-install-to /dev/nvme0n1

boot2deb-install-to ships in every image. It refuses anything that is not a whole disk, refuses the disk the system is running from, refuses a disk with anything mounted on it, and requires you to type the device’s name — then writes the embedded artifact and syncs. Power off, and the node boots from the NVMe; the rootfs grows to fill it on that first boot. Re-running it is safe and still writes, so an interrupted write is repaired by repeating it.

That is one BMC flash and one ssh session. The eMMC keeps a complete, bootable copy of the same system, which is a useful thing to have on a node whose OS disk you are about to replace.

Writing the NVMe from u-boot

The BMC writes eMMC and nothing else: the loader it streams into the module speaks eMMC, so the M.2 disk is invisible to tpi flash, to the web UI, and to gadget mode. The RK1’s own u-boot has no such limit — it enumerates the disk over pcie3x4 — so the shipped bootloader carries the two commands that let a host reach it.

This route needs a UART session and interrupting the boot countdown, so reach for it when you want the disk written from outside the node — a bare M.2 with no system on it yet, or a node whose OS will not boot. To install onto the NVMe of a node that boots, the previous section does it with no console at all. Build the tool variant for the full set:

boot2deb build turing-rk1/util --stage uboot     # writes turing-rk1-util-boot.img

Flash that to the eMMC with tpi, open the node’s UART, and interrupt the countdown. Two routes from the prompt:

Export the disk to the BMC. ums presents any block device u-boot can see as USB mass storage, so with the node’s USB in device mode the BMC sees the NVMe as a normal disk:

=> nvme scan
=> ums 0 nvme 0

then, from your machine, stream the image through the BMC — nothing is staged on the node or the BMC:

xzcat turing-rk1-forky-rootfs.img.xz | ssh root@<bmc> 'dd of=/dev/sdX bs=4M'

Or pull the image in over the network and let u-boot write it. This needs a gzip image, since u-boot has no xz decompressor:

boot2deb build turing-rk1/forky --layout split --compress gz
=> dhcp
=> tftp ${loadaddr} turing-rk1-forky-rootfs.img.gz
=> gzwrite nvme 0 ${loadaddr} ${filesize}

gzwrite decompresses and writes in one pass. Hash the image first with md5sum if the link is one you do not trust — several GiB over TFTP has no integrity check of its own. Images at or above 4 GiB uncompressed need gzwrite’s explicit outsize argument; the shipped recipes are well under it.

Either way the eMMC still needs a bootloader afterwards. boot2deb build turing-rk1/forky --stage uboot emits the shipping one, which also carries ums — so a node that boots from NVMe keeps a route back to its disks without reflashing the tool.

Or keep the OS on eMMC and use the NVMe for data

Often the better answer, and it makes the whole errand above unnecessary: flash the entire system to the eMMC — which the BMC can do in one step — and let the M.2 disk hold data only. Reimaging then never touches the data, because the new image finds the volume by label and adopts it.

The RK1’s 29 GB eMMC has room for any of the shipped images several times over, so nothing is given up by keeping the OS there. No shipped recipe assumes this layout — where the data lives is an installation’s choice, not the board’s — so you add it to your own recipe. See Data volumes.

Serial console

To watch u-boot and the kernel come up, open the node’s UART from the BMC:

tpi uart --node <n> get
# or, on the BMC directly:
picocom /dev/ttyS<n> -b 115200

On BMC firmware 2.1.0 and newer the node number maps 1:1 to the ttyS number (node 1 → ttyS1, node 2 → ttyS2, …). On 2.0.5 and older the mapping was offset (node 1 → ttyS2, node 2 → ttyS1, …), so check your firmware version. The baud rate is 115200. See Turing Pi’s UART docs.

Forcing one boot from a chosen medium

A node carrying a system on both its eMMC and its M.2 disk boots whichever its boot_targets list reaches first. To boot the other one once — to check that a freshly written eMMC copy comes up, say, without disturbing a node that normally runs from NVMe — override the list at the prompt instead of writing it:

tpi uart --node 2 get          # watch this while the node powers on
tpi power on --node 2

Interrupt the countdown at Hit any key to stop autoboot: with any key, then:

=> printenv boot_targets        # the shipped order, whatever it is on your build
=> setenv boot_targets mmc0     # or nvme0, or "mmc0 nvme0" to try both
=> boot

setenv without saveenv lives until the next reset, so this cannot strand the node: power-cycle it and the shipped order is back, unchanged. That is the whole reason to prefer it over editing the environment.

Driving that conversation for you — powering the node and answering the prompt in one command — is device tooling rather than an image builder’s job, and boot2deb does not do it; the same boundary that keeps it from writing devices.

First boot

Power the node on. On first boot the image:

  • regenerates its SSH host keys, and
  • grows the rootfs to fill the whole medium (the 2 GB image expands to the disk’s capacity), online, in the same boot — no reboot involved.

Log in as user debian with the password the build printed. It is expired, so you are required to set a new one immediately. The debian account has passwordless sudo, and the hostname is turing-rk1.

That is a booted Debian system. The kernel’s transcode devices come up on every variant — check for /dev/dri and /dev/rga. A media-accel image also installs the ffmpeg-rk userspace, so you can exercise the rkmpp / rkrga paths directly; on a base image the blocks are present but idle until you install the media-accel debs (or build a turing-rk1/media-accel-* image).

Running the accelerated FFmpeg

ffmpeg-rk installs under /opt/ffmpeg-rk and is on PATH as ffmpeg-rk (and ffprobe-rk). The suffix is deliberate: the build ships the same library sonames as Debian’s own FFmpeg, so it is kept out of the system’s paths and out of the loader’s search path, and the plain ffmpeg name stays with the distro package.

ffmpeg-rk -hide_banner -filters  | grep rkrga      # scale_rkrga, vpp_rkrga, overlay_rkrga
ffmpeg-rk -hide_banner -encoders | grep rkmpp      # h264_rkmpp, hevc_rkmpp

Hardware decode is reached with -hwaccel v4l2request, not with the *_rkmpp decoders — those are compiled in but do not open on a mainline kernel, where rkvdec is a V4L2 stateless driver rather than an MPP service. A transcode that scales looks like this, and scales on the CPU:

ffmpeg-rk -hwaccel v4l2request -i in.mkv \
          -vf "hwdownload,format=nv12,scale=1280:720" \
          -c:v hevc_rkmpp out.mp4

Both of those limits are stated as caveats on the media-accel-rockchip feature, so they print at the end of a build of any recipe composing it; see Support matrix.