Recovery boot on Rockchip SoCs

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 bootROM
  • 0x472, 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:

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:

  1. 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
  2. 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_merger for 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.

  3. Build rkdeveloptool:
    $ cgit clone https://github.com/rockchip-linux/rkdeveloptool.git
    $ cd rkdeveloptool
    $ ./autogen.sh
    $ ./configure
    $ make
  4. 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.
  5. 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.

Leave a Reply