Rockchip is a well-known silicon vendor whose System-on-Chips (SoCs) are integrated in a wide range of embedded Linux systems. For example, Pine64, a company manufacturing embedded devices targeted for the Open Source community uses the RK3399 for its PinePhone Pro, and the RK3566 for its PineTab 2. Rockchip SoCs implement a rather powerful recovery bootflow.
Unfortunately, that capability is poorly documented, and the tooling available is fragmented and only partially open source.
The aim of this article is to present this capability and give a step-by-step guide to using it.
The Rockchip BootROM
Like all SoCs, the ones Rockchip produces have a well-defined boot sequence whose goal is to load the bootloader and firmwares from storage into main memory. This is implemented by Rockchip in the SoCs’ bootROM, and described in this Rockchip wiki page.
A clearer description is provided for the RK3399 on the Pine64 wiki. Importantly, this wiki mentions “USB gadget boot”, which is more commonly referred to as “MaskROM mode”, and is common to a large selection of Rockchip SoCs.
MaskROM mode
As mentioned in the Pine64 wiki, the Rockchip bootROM defaults to MaskROM mode once it has failed loading the second stage from any of its hardcoded boot locations. In this mode, it will expose a USB device over its USB OTG interface, identifiable through Rockchip’s Vendor ID, which, as can be seen in this list from the USB Implementers Forum, is 8711/0x2207:
lsusb -d '2207:' Bus 003 Device 063: ID 2207:330c Fuzhou Rockchip Electronics Company RK3399 in Mask ROM mode
That interface exposes 2 Vendor control requests:
0x471, which will load code into the SRAM and execute it before returning to the bootROM0x472, which will load code into the DRAM and jump to it
The first is usually used to load a DRAM initialization binary, while the second one is used to start the rest of the boot process. This is no different from a normal boot, the binaries are simply being loaded into RAM over the USB interface using the capability exposed by the bootROM. For each supported SoC, Rockchip provides binaries that can be used for each step:
-
<chip>_ddr_<frequency>_<version>.binfor0x471e. g. rk3399_ddr_800MHz_v1.30.bin<chip>_usbplug_<version>.binfor0x472e.g. rk3399_usbplug_v1.30.bin
As explained above, the <chip>_ddr_<frequency>_<version>.bin binaries are responsible for DRAM initialization.
<chip>_usbplug_<version>.bin is the more interesting of the two, and the binary that implements the next stage of the recovery boot.
Usbplug & the rockUSB protocol
The next stage in the Rockchip recovery boot flow continues to happens over USB, but uses opCodes to issue commands in accordance with the vendor-specific rockUSB protocol, which is summarily described on the Rockchip wiki. This protocol is not implemented in the ROM code, but in the custom usbplug program, distributed by Rockchip in binary form. This is documented in a readme in the Rockchip client for rockUSB, rkdeveloptool:
sudo ./rkdeveloptool db RKXXLoader.bin //download usbplug to device
The “download usbplug to device” is terse and slightly misleading, but let’s look at the implementation. In short, the software will parse the binary for an ENTRY471 object, then use USB vendor request 0x471 to load it, wait, then do the same for an ENTRY472 object and load it with the 0x472 USB vendor request.
The rockUSB clients
Rockchip has open-sourced rkdeveloptool, which serves as a client both for the USB Vendor requests, and the rockUSB protocol. An alternative client called xrock is more actively maintained and slightly better documented.
Neither of these clients implement the full list of opcodes they embed in their own source code. Because rkdeveloptool is the client officially supported by Rockchip and probably the one most used, this post will focus on using it rather than xrock.
Using rkdeveloptool
rkdeveloptool refers to “RKXXLoader.bin” as its first argument, but this binary is absent from the Rockchip rkbin repo. From what we’ve read before, we can infer that it is generated from the DRAM initialization binary, and usbplug. Looking a bit though the repo shows that the RKBOOT directory contains .ini files that look like recipes to generate the loader:
[CHIP_NAME] NAME=RK330C [VERSION] MAJOR=1 MINOR=30 [CODE471_OPTION] NUM=1 Path1=bin/rk33/rk3399_ddr_800MHz_v1.30.bin Sleep=1 [CODE472_OPTION] NUM=1 Path1=bin/rk33/rk3399_usbplug_v1.30.bin [LOADER_OPTION] NUM=2 LOADER1=FlashData LOADER2=FlashBoot FlashData=bin/rk33/rk3399_ddr_800MHz_v1.30.bin FlashBoot=bin/rk33/rk3399_miniloader_v1.30.bin [OUTPUT] PATH=rk3399_loader_v1.30.130.bin
This is a configuration file for rkbin/tools/boot_merger, the sources of which can be found in Rockchip’s U-Boot tree, under tools/rockchip/boot_merger.c.
Putting it all together
In summary, here’s how to get an RK3399-based device ready to receive rockUSB commands from scratch:
- Get the device into MaskROM mode by having it fail to load the second stage bootloader (e.g. by electrically disabling the SPI flash)
$ lsusb -d '2207:' Bus 003 Device 063: ID 2207:330c Fuzhou Rockchip Electronics Company RK3399 in Mask ROM mode
- Build the loader by combining DDR init and
usbplug:$ git clone https://github.com/rockchip-linux/rkbin.git $ cd rkbin $ ./tools/boot_merger RKBOOT/RK3399MINIALL.ini $ cd ..
Here, we use the pre-built
boot_mergerfor simplicity, but we recommend using one built from the U-Boot source, especially if you build U-Boot tools as part of your build process. - Build
rkdeveloptool:$ cgit clone https://github.com/rockchip-linux/rkdeveloptool.git $ cd rkdeveloptool $ ./autogen.sh $ ./configure $ make
- Enable rockUSB by downloading the previously built loader:
$ sudo ./rkdeveloptool rci Read Chip Info failed!
$ sudo ./rkdeveloptool db ../rkbin/rk3399_loader_v1.30.130.bin Downloading bootloader succeeded.
- Run some rockUSB commands. Below, an example using
rci(read chip info):$ sudo ./rkdeveloptool rci Chip Info: 43 30 33 33 E9 F9 FF 7F F1 D1 BF EF FC F5 3E DB
This prints out some information about the chip embedded by the manufacturer. Many more commands are available, including ones to write files to the boot medium.
Recovery boot flow
The interesting aspect of MaskROM mode is the ability to boot over USB, even when all boot locations are corrupted. That is a perfect candidate to have a recovery mechanism when a device is bricked or during manufacturing or provisioning of new devices. Indeed, this has been proposed in snagboot to support recovery on Rockchip-based devices, by:
- Loading the U-Boot Tertiary Program Loader (TPL), built from the Rockchip closed-source DDR init blob, as entry
0x471 - Loading the U-Boot Secondary Program Loader (SPL) + U-Boot’s FIT image as entry
0x472.
or:
- Loading the U-Boot Tertiary Program Loader (TPL), built from the Rockchip closed-source DDR init blob, as entry
0x471 - Loading a Direct Firmware Update (DFU)-enabled SPL as entry
0x472 - Using DFU to load U-Boot proper into RAM
Conclusion
Rockchip’s SoCs embed a great recovery boot capability over USB, directly in their bootROM, as well as an interesting toolkit in the form of the rockUSB protocol and its implementation, usbplug. The former could be useful e.g. for automatic bootstrapping of Rockchip SoC-based devices from scratch over USB.
