Secure Boot on Rock 5B

I’m trying to enable Secure Boot on Rock 5B by burning RSA public key to OTP fuse.
Following the Rockchip_Developer_Guide_Secure_Boot_Application_Note_EN.pdf guide I was able sign and upload the signed firmware to the devices. As per the doc, the first time a signed firmware boots it should burn fuses automatically, but apparently it doesn’t happen as I’m still able to boot with unsigned firmware.

Have tried the same approach on other RK3566/RK3568 boards with no luck.

At the same time on RK3399, I was able to burn keys with EFuseTool and everything works.

1 Like

I couldn’t get it to Secure Boot either, I could be wrong but from what I gather it’s the lack of mini loader on my end, and I can’t find any code in the build repository that indicates that the trust files get packed in to the idbloader for any rk35xx build

From chapter “6.6 Programming OTP”:

If OTP program success, serial port print “otp write key success!!!”. If OTP program fail, serial
port print"otp write error: !!!".

I found out only the following loaders have the required logic:

$ grep -r "otp write key" rkbin/bin/rk35/
grep: rkbin/bin/rk35/rk3588_ramboot_v1.06.bin: binary file matches
grep: rkbin/bin/rk35/rk3568_miniloader_spinand_v1.15.bin: binary file matches
grep: rkbin/bin/rk35/rk356x_spl_nand_v1.14.bin: binary file matches

So for RK3588 the only suitable loader for enabling secure boot is rk3588_ramboot_v1.06.bin.

I created idbloader.img and signed it using the following commands:

$ ./uboot/bools/mkimage -n rk3588 -T rksd -d rk3588_ddr_lp4_2112MHz_lp5_2736MHz_v1.08.bin:rk3588_ramboot_v1.06.bin idbloader.img

$ rk_sign_tool ssr --key privateKey.pem --pubkey publicKey.pem --idb idbloader.img

but that doesn’t enable secure boot either (SecureMode = 0):

DDR Version V1.08 20220617
LPDDR4X, 2112MHz
channel[0] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
channel[1] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
channel[2] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
channel[3] BW=16 Col=10 Bk=8 CS0 Row=17 CS=1 Die BW=16 Size=2048MB
Manufacturer ID:0xff 
CH0 RX Vref:27.7%, TX Vref:22.8%,0.0%
CH1 RX Vref:28.7%, TX Vref:23.8%,0.0%
CH2 RX Vref:28.7%, TX Vref:21.8%,0.0%
CH3 RX Vref:27.7%, TX Vref:22.8%,0.0%
change to F1: 528MHz
change to F2: 1068MHz
change to F3: 1560MHz
change to F0: 2112MHz
out
Boot1 Release Time: Feb 24 2022 10:23:56, version: 1.05 USB BOOT
ChipType = 0x32, 481
SecureMode = 0
atags_set_bootdev: ret:(0)
UsbBoot ...1079
powerOn 1340
Usb no Connecte. 6001342
Usb no Connecte. 12001344
Usb no Connecte. 18001349
Usb no Connecte. 24001357
1 Like

@kirgene @ibebarrett Hi! Were you able to get the Secure Boot thing done? I was looking for some documentation and could not get anything more up-to-date than https://github.com/yunzhaoyu2050/rockchip_rv1126_rv1109_docs/blob/main/Kernel/NVM/Rockchip_Developer_Guide_Secure_Boot_Application_Note_EN.pdf

Hi There, thank you for posting your results, good to know!

And did you try flashing it with dd? Something like this?
sudo dd if=./out/u-boot/idbloader.img of=/dev/mtdblock0 bs=512 seek=64
sudo dd if=./out/u-boot/u-boot.itb of=/dev/mtdblock0 bs=512 seek=16384

For those interested, we’ve successfully enabled Secure Boot (on a different board not Rock 5B), along with full disk encryption, and everything seems to be working smoothly. But yes, as @DualTachyon pointed out it was quite a bit of effort. It is even frustrating to realize that while this feature is actually supported by the hardware, yet documentation is scarce, and all board manufacturers we talked to do not support this feature.

2 Likes

@gilbertchen and @DualTachyon can you please say what observation do you get when trying to load any unsigned bootloader after enabling secure boot using rkdeveloptool db xx.bin ? And @DualTachyon do you get a minimal uart output when loading your custom unsigned bootloader on a secured SoC?

We are trying to diagnose a board we have, that embeds RK3588S and refuses to load any bootloader, and while it gets recognized in maskrom, we have no clue why it is the case. Power and current are also in good figures. A different board can load any bootloader, rockchip binaries or DualTachyon’s custom one.

We have the feeling that the non-working one was locked up during manufacturing, or accidentally pre secured. So we’re trying to compare similar patterns to confirm. Ultimately we will try to enable secureboot on the working SoC and compare.
Lack of documentation and support from rockchip doesn’t make this easier.

No it won’t accept any loader or custom loader. I am not even trying to get to DDR training. I used your custom bootloader to just setup a uart and display a test message. I also doubt that the uart subsystem is not working as the SoC manages to also setup the pull-ups and pull-downs on the GPIOs within the same PMU and I could measure voltage on them. Also the fact that the USB enumeration and entering maskrom work fine, indicate that the CPU is working to a certain extent.

One behaviour that can give a hint here, is that when uploading the “471” binary part, the uploading would work the first time (always no uart output), but the second time it just hangs until I have to reset the board. Is a RK3588(S) with secure boot enabled, behaves the same way?

First @DualTachyon, thanks for the work on the bootloader, it helped a lot with debugging. We are talking about SPL loaders. Now, I followed your instructions and we managed to connect through SWD to the booting board in both forced maskrom and also when loading the custom bootloader, which initializes the SWD interface.

However, the two boards that are not booting do not respond via SWD and openocd shows that it cannot connect to the target. While these boards are powered up and able to enumerate USB and enter maskrom, we believe that they are alive. But no SWD access strengthens the indication of being boot secured.

One remark here about sending 471 multiple times before reset. Yes it would work once and needs reset for the booting boards. But for the non booting ones, it always succeeds in uploading the 471 data without reset and libusb never returns any error. Do you observe that when loading the wrong unsigned loader to the boot secured SoC on your end?

Regarding the problem of reading and writing OTP failure, perhaps the enable bl32 part of this document can be solved: https://docs.radxa.com/en/rock5/rock5b/low-level-dev/rk_anti_copy_board

We contacted the distributor. Otherwise, we will replace the SoC and try again. I will update about the results. Thank you.

Update: We managed to replace the SoC with a new one, this time from a trusted source. This one worked, JTAG access and booting SPL loader work fine. Therefore we can confirm that the non-booting ones had a locked-up processor. It’s highly likely we got either some sort of engineering samples or something of an unknown channel.

Pay attention to whom you buy crucial parts from. This, for example, was a heavy price. But to be fair, we had a useful experience. @DualTachyon thanks again.

Hi , OTP write documentation is not available in RK3588’s both TRMs. I wanted to program non - secure OTP. Could you help me with documentation ?

thank you, will try and let you know

Thank you for your reply! Can I program it from user space using your code ? or should I add write function in kernel’s driver ?
#define OTP_S_PROG_DATA_ADDR (OTP_S_BASE + 0x0010)
#define OTP_S_PROG_DATA ((volatileuint32_t)OTP_S_PROG_DATA_ADDR)
are all these similar offsets for NS ?

thanks a lot, will let you know

RK3588 Data sheet says: Support 32Kbit space and higher 4k address space is non-secure part.

But according to dtb:

otp: otp@fecc0000 {
compatible = “rockchip,rk3588-otp”;
reg = <0x0 0xfecc0000 0x0 0x400>;
its size is only 0x400 bytes , right?

and we start read/write from NO_SECURE_OFFSET i.e 0x300

does this mean there are 0x400 otps each of 4 bytes ?

static int rk3588_otp_write(void *context, unsigned int offset,
void *val, size_t bytes)
{
struct rockchip_otp *otp = context;
unsigned int addr_start, addr_end, addr_offset, addr_len;
int ret = 0;// i = 0;
u32 in_value;
u8 *buf;
OTP_NS_t ns_otp=(OTP_NS_t)otp->base;
dev_info(otp->dev, “inside rk3588_otp_write()\n”);

if (offset >= otp->data->size)
	return -ENOMEM;
if (offset + bytes > otp->data->size)
	bytes = otp->data->size - offset;

addr_start = rounddown(offset, RK3588_NBYTES) / RK3588_NBYTES;
addr_end = roundup(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
addr_offset = offset % RK3588_NBYTES;
addr_len = addr_end - addr_start;
addr_start += RK3588_NO_SECURE_OFFSET;

printk(KERN_INFO "rk3588_otp_write(): addr_start: %d, addr_end: %d, addr_len: %d, addr_offset: %d\n",addr_start,addr_end,addr_len,addr_offset);

buf = kzalloc(array3_size(addr_len, RK3588_NBYTES, sizeof(*buf)), GFP_KERNEL);
if (!buf)
	return -ENOMEM;

ret = clk_bulk_prepare_enable(otp->num_clks, otp->clks);
if (ret < 0) {
	dev_err(otp->dev, "failed to enable clocks\n");
	return -1;
}

//dtOtpWriteNonSecure(ns_otp,val,addr_start,addr_len);

//Copy input to buffer, considering offset inside the first word
memcpy(buf + addr_offset, val, bytes);

while (addr_len--) {
	memcpy(&in_value, &buf[i], RK3588_NBYTES);

	// Write the data to be programmed
	writel(in_value, otp->base + OTP_NS_PROG_DATA_ADDR);

	// Set AUTO_CTRL with address and write command
	writel((addr_start << RK3588_ADDR_SHIFT) |
	       (RK3588_BURST_NUM << RK3588_BURST_SHIFT) |
	       RK3588_CMD_WRITE,
	       otp->base + RK3588_OTPC_AUTO_CTRL);

	// Enable the command
	writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN);

	// Wait for write to complete
	// ret = rk3588_otp_wait_status(otp, RK3588_WR_DONE);
	// if (ret < 0) {
	// 	dev_err(otp->dev, "timeout during write operation\n");
	// 	goto write_end;
	// }
	udelay(10000);
	udelay(10000);
	printk(KERN_INFO "for i = %d ,addr_start = %d \n",i,addr_start);

	addr_start++;
	i += RK3588_NBYTES;
}

write_end:
clk_bulk_disable_unprepare(otp->num_clks, otp->clks);
out:
kfree(buf);
return ret;
}

is this the right code ? I just used writel() instead of your volatile objects

my macros:

#define RK3588_OTPC_AUTO_CTRL 0x04
#define RK3588_OTPC_AUTO_EN 0x08
#define RK3588_OTPC_INT_ST 0x84
#define RK3588_OTPC_DOUT0 0x20
#define RK3588_NO_SECURE_OFFSET 0x300
#define RK3588_NBYTES 4
#define RK3588_BURST_NUM 1
#define RK3588_BURST_SHIFT 8
#define RK3588_ADDR_SHIFT 16
#define OTP_NS_PROG_DATA_ADDR 0x0010
#define RK3588_AUTO_EN BIT(0)
#define RK3588_RD_DONE BIT(1)
#define RK3588_CMD_WRITE 0x2
#define RK3588_WR_DONE BIT(3)

my_function
static int rk3588_otp_write(void* context, unsigned int offset,
void val, size_t bytes)
{
struct rockchip_otp
otp=(struct rockchip_otp*)context;

unsigned int addr_start, addr_end, addr_offset, addr_len;
unsigned int in_value;
int ret = 0, i = 0;
u8 *buf;


if (offset >= otp->data->size)
    return -EINVAL;
if (offset + bytes > otp->data->size)
    bytes = otp->data->size - offset;


addr_start = rounddown(offset, RK3588_NBYTES) / RK3588_NBYTES;
addr_end = roundup(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES;
addr_offset = offset % RK3588_NBYTES;
addr_len = addr_end - addr_start;
addr_start += RK3588_NO_SECURE_OFFSET;


buf = kzalloc(addr_len * RK3588_NBYTES, GFP_KERNEL);
if (!buf)
    return -ENOMEM;

// Copying user data into buffer with offset
memcpy(buf + addr_offset, val, bytes);


ret = clk_bulk_prepare_enable(otp->num_clks, otp->clks);
if (ret < 0) {
    dev_err(otp->dev, "Failed to enable clocks\n");
    goto out_free;
}

// Word-by-word programming
for (i = 0; i < addr_len * RK3588_NBYTES; i += RK3588_NBYTES) {
    memcpy(&in_value, &buf[i], RK3588_NBYTES);

    writel(in_value, otp->base + OTP_NS_PROG_DATA_ADDR);

    writel(((addr_start << RK3588_ADDR_SHIFT) |
           (RK3588_BURST_NUM << RK3588_BURST_SHIFT) |
           RK3588_CMD_WRITE),
           otp->base + RK3588_OTPC_AUTO_CTRL);

    writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN);
	
	udelay(10000); // 10ms delay for write
	ret = rk3588_otp_wait_status(otp, RK3588_WR_DONE);
	if (ret < 0) {
		dev_err(otp->dev, "timeout during read setup\n");
		goto out_free;
	}
    dev_info(otp->dev, "Wrote word: 0x%08x at word %u\n", in_value, addr_start);

    addr_start++;
}

clk_bulk_disable_unprepare(otp->num_clks, otp->clks);

out_free:
kfree(buf);
return ret;
}

here is my function

yes I know it, sorry to bother you ,but the file had predefined
#define RK3588_NO_SECURE_OFFSET 0x300

as per your advice I made OFFSET to 0x0

then both read and write fail.

Also i’m doing this in linux, not baremetal

Can you go through code once and let me know ?

Hope you’re doing good. I request you to look at my query . I have a few boards and I’m trying very best for it. I sincerely need your help. Sorry for the trouble.