Rockchip customizes vendorstorages data and then u-boot passes the data to the kernel through cmdline

Recently I have been thinking about some interesting functions, such as customizing logos, dynamically updating screen parameters, dynamically switching dts and other functions (although I have done it on the mtk platform before, but the platform is different and it has been a long time ago), so I have been researching it for the past two days. Check out uboot related content.

This content took me a long time, and I encountered many pitfalls and inexplicable problems, but I finally understood and digested them. So don’t read the content. There are only a few points that you need to get started to truly understand and master.

Then let's get started, I will write down the results that I spent most of the day on, hoping to help friends in need.

background

On Rockchip's chip, there is a special partition called vendor storage, which is a partition used to store user-defined data, such as serial numbers, MAC addresses, calibration data, etc. The vendor storage partition is accessed through the MMC command. There is no need to mount the file system and can be used in both u-boot and kernel. The size and location of the vendor storage partition can be configured in the dts file, usually 4MB.

In some scenarios, it may be necessary to read some data in the vendor storage partition in u-boot and then pass it to the kernel through the cmdline parameter so that it can be used by the driver or application in the kernel. For example, we may need to store the user-defined LED color or brightness value in the vendor storage partition, then read it in u-boot, and pass it to the LED driver in the kernel through the cmdline parameter to achieve user-defined LED Effect.

Reference: Rockchip series VendorStorage uboot/kernel/user space stage interface usage introduction (2)

Implementation

To implement this function, I need to modify the code of u-boot and kernel. Specific steps are as follows:

1. Define two IDs in u-boot to store user-defined data

In u-boot, each data item in the vendor storage partition is identified by a unique ID, which is a 16-bit integer. The required ID can be defined in the u-boot/arch/arm/include/asm/arch-rockchip/vendor.h file, for example:

#define USER_CUSTOM1_ID                        18
#define USER_CUSTOM2_ID                        19

Here I have defined two IDs, 18 and 19 respectively, for storing two user-defined data items.

2. Read the user-defined data in the vendor storage partition in u-boot and set it as an environment variable

In u-boot, we can use the vendor_storage_read function to read a certain data item in the vendor storage partition. This function needs to pass in three parameters: ID, buffer pointer and buffer size. We can add two functions to the u-boot/arch/arm/mach-rockchip/board.c file to read the data corresponding to the two IDs we defined and set them as environment variables, for example:

// USER_CUSTOM1_ID 和  USER_CUSTOM2_ID 已经在某个头文件中定义了,如果没有,请定义它


static int rockchip_get_user_custom1_value(void)
{
    
    

#ifdef CONFIG_ROCKCHIP_VENDOR_PARTITION
    char custom_value[VENDOR_SN_MAX + 20] = {
    
    0};  // 用于存储USER_CUSTOM1_ID的值
    //char key[VENDOR_SN_MAX] = "user_custom1_value";
    //char buf[VENDOR_SN_MAX + 20];  // 为键和值预留足够的空间
    // 读取 USER_CUSTOM1_ID 的值
    int custom_ret = vendor_storage_read(USER_CUSTOM1_ID, custom_value, sizeof(custom_value) - 1);
    if (custom_ret > 0) {
    
    
        printf("--3> USER_CUSTOM1_ID value: %s\n", custom_value);
        env_set("user_custom1_value", custom_value);  // 设置为U-Boot环境变量
        
        //sprintf(buf, "%s", custom_value);
        //env_set(key, buf);
    } else {
    
    
        printf("%s: vendor_storage_read for USER_CUSTOM1_ID failed %d\n", __func__, custom_ret);
    }
#endif
    return 0;
}
static int rockchip_get_user_custom2_value(void)
{
    
    
#ifdef CONFIG_ROCKCHIP_VENDOR_PARTITION
    char custom2_value[VENDOR_SN_MAX] = {
    
    0};  // 用于存储USER_CUSTOM2_ID的值

    // 读取 USER_CUSTOM2_ID 的值
    int custom2_ret = vendor_storage_read(USER_CUSTOM2_ID, custom2_value, sizeof(custom2_value) - 1);
    if (custom2_ret > 0) {
    
    
        printf("--> USER_CUSTOM2_ID value: %s\n", custom2_value);
        env_set("user_custom2_value", custom2_value);  // 设置为U-Boot环境变量
    } else {
    
    
        printf("%s: vendor_storage_read for USER_CUSTOM2_ID failed %d\n", __func__, custom2_ret);
    }
#endif

    return 0;
}
/*
需要增加到
int board_late_init(void)
{
  printf("%s: leon->board_late_init enter2 \n",__func__);
  rockchip_get_user_custom2_value();
  rockchip_get_user_custom1_value();
*/

Two functions are defined here, rockchip_get_user_custom1_value and rockchip_get_user_custom2_value, which are used to read the values ​​​​of USER_CUSTOM1_ID and USER_CUSTOM2_ID in the vendor storage partition, and set them to the two environment variables user_custom1_value and user_custom2_value. We can print out the read value in the function for debugging.

3. Splice the environment variables into the cmdline parameters in u-boot and pass them to the kernel

In u-boot, we can use the env_get function to obtain the value of the environment variable, and then use the snprintf function to splice it into the cmdline parameter. We can modify the boot_prep_linux function in the u-boot/arch/arm/lib/bootm.c file, for example:

static void boot_prep_linux(bootm_headers_t *images)
{
    
    
//-----------------star
#ifdef CONFIG_ROCKCHIP_VENDOR_PARTITION
       char *commandline = NULL;
       //这里有个坑,512 会出问题 所以搞大一点。
       char new_bootargs[2048] = {
    
    0};  // 增加缓冲区大小以容纳更多的内容
       char* old_bootargs = env_get("bootargs");
       char *user_custom1_value = env_get("user_custom1_value");
       char *user_custom2_value = env_get("user_custom2_value");

       strncpy(new_bootargs, old_bootargs, sizeof(new_bootargs) - 1);

       if(user_custom1_value){
    
    
           snprintf(new_bootargs + strlen(new_bootargs), sizeof(new_bootargs) - strlen(new_bootargs), " user_custom1_value=%s", user_custom1_value);
       }

       if(user_custom2_value){
    
    
           snprintf(new_bootargs + strlen(new_bootargs), sizeof(new_bootargs) - strlen(new_bootargs), " user_custom2_value=%s", user_custom2_value);
       }

       env_set("bootargs", new_bootargs);
       commandline = env_get("bootargs");
#else
       char *commandline  = env_get("bootargs");
#endif
//-----------------end

	if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
    
    

Here, the original cmdline parameters are first obtained, and then it is determined whether there are environment variables for user-defined data. If so, they are spliced ​​into the cmdline parameters, and then the bootargs environment variables are reset and passed to the commandline pointer. In this way, when u-boot starts the kernel, these user-defined data will be passed to the kernel as cmdline parameters.

4. Parse cmdline parameters in the kernel and obtain user-defined data

In the kernel, we can use the strstr function to find whether the cmdline parameter contains the key-value pairs of the required user-defined data. If so, parse them out and store them in the corresponding variables. We can add a function to the kernel/drivers/leds/led_control.c file ( the driver I wrote myself, any is fine. I just verify whether the printing is correct ) to print out the value of user-defined data, for example:

/*不要用这个 这个不会截取的,
[    5.256936] user_custom1_value: CUSTOM_20230826 earlycon=uart8250,mmio32,0xfe660000 androidboot.boot_devices=fe310000.sdhci,fe330000.nandc
[    5.256953] user_custom2_value not found
*/
/*static void printk_custom_cmdline(void)
{
    //-----------------------打印commandline参数
    char *custom1_ptr = NULL;
    char *custom2_ptr = NULL;

    custom1_ptr = strstr(boot_command_line, "user_custom1_value=");
    custom2_ptr = strstr(boot_command_line, "user_custom2_value=");

    if (custom1_ptr) {
        custom1_ptr += strlen("user_custom1_value=");
        printk(KERN_INFO "user_custom1_value: %s\n", custom1_ptr);
    } else {
        printk(KERN_INFO "user_custom1_value not found\n");
    }

    if (custom2_ptr) {
        custom2_ptr += strlen("user_custom2_value=");
        printk(KERN_INFO "user_custom2_value: %s\n", custom2_ptr);
    } else {
        printk(KERN_INFO "user_custom2_value not found\n");
    }
    //-----------------------打印commandline参数
}*/
/*
这个打印就是正常的
[    5.257462] user_custom1_value: CUSTOM_20230826
[    5.257479] user_custom2_value not found
*/
static void printk_custom_cmdline(void)
{
    
    
    //-----------------------打印commandline参数
    char *custom1_ptr = NULL;
    char *custom1_end = NULL;
    char *custom2_ptr = NULL;
    char *custom2_end = NULL;

    custom1_ptr = strstr(boot_command_line, "user_custom1_value=");
    custom2_ptr = strstr(boot_command_line, "user_custom2_value=");

    if (custom1_ptr) {
    
    
        custom1_ptr += strlen("user_custom1_value=");
        custom1_end = strchr(custom1_ptr, ' ');  // 查找空格,表示参数的结束
        if (!custom1_end)  // 如果没有找到空格,使用字符串结束作为结束
            custom1_end = custom1_ptr + strlen(custom1_ptr);
        printk(KERN_INFO "user_custom1_value: %.*s\n", (int)(custom1_end - custom1_ptr), custom1_ptr);
    } else {
    
    
        printk(KERN_INFO "user_custom1_value not found\n");
    }

    if (custom2_ptr) {
    
    
        custom2_ptr += strlen("user_custom2_value=");
        custom2_end = strchr(custom2_ptr, ' ');
        if (!custom2_end)
            custom2_end = custom2_ptr + strlen(custom2_ptr);
        printk(KERN_INFO "user_custom2_value: %.*s\n", (int)(custom2_end - custom2_ptr), custom2_ptr);
    } else {
    
    
        printk(KERN_INFO "user_custom2_value not found\n");
    }
    //-----------------------打印commandline参数
}

5. Use user-defined data in the kernel to control the color and brightness of the LED

In the kernel, we can set the color and brightness of the LED according to the value of user-defined data to achieve user-defined LED effects. We can modify the led_init_state function in the kernel/drivers/leds/led_control.c file, for example:
(This step is no longer important. After getting the data in step 4, what should you do according to your own situation? This is just my own logic. )

static void led_init_state(void)
{
    
    
    int i,j;
    char *custom1_ptr = NULL;
    char *custom2_ptr = NULL;
    int custom1_value = 0;
    int custom2_value = 0;

    // 获取用户自定义数据的值
    custom1_ptr = strstr(boot_command_line, "user_custom1_value=");
    custom2_ptr = strstr(boot_command_line, "user_custom2_value=");

    if (custom1_ptr) {
    
    
        custom1_ptr += strlen("user_custom1_value=");
        custom1_value = simple_strtol(custom1_ptr, NULL, 10); // 转换为整数
    }

    if (custom2_ptr) {
    
    
        custom2_ptr += strlen("user_custom2_value=");
        custom2_value = simple_strtol(custom2_ptr, NULL, 10); // 转换为整数
    }

    // 根据用户自定义数据的值来设置LED的颜色和亮度
    for(i=0;i<MAX_LED;i++)
    {
    
    
        for(j=0;j<MAX_COLOR;j++)
        {
    
    
            if(j == 0) // 红色
            {
    
    
                led_state[i][j] = (custom1_value >> 16) & 0xff; // 取用户自定义数据的高8位作为红色值
            }
            else if(j == 1) // 绿色
            {
    
    
                led_state[i][j] = (custom1_value >> 8) & 0xff; // 取用户自定义数据的中8位作为绿色值
            }
            else if(j == 2) // 蓝色
            {
    
    
                led_state[i][j] = custom1_value & 0xff; // 取用户自定义数据的低8位作为蓝色值
            }
            else if(j == 3) // 亮度
            {
    
    
                led_state[i][j] = custom2_value; // 取用户自定义数据的另一个值作为亮度值
            }
            
            printk("led_init %d,%d,%d\n",i,j,led_state[i][j]);
        }
    }

}

Here we first get the values ​​of the user-defined data, and then set the color and brightness of the LED based on them. Assume that the first value of the user-defined data is a 24-bit integer, representing the RGB color. Take out the high 8 bits, the middle 8 bits and the low 8 bits as the values ​​of red, green and blue. Assume that the second value of user-defined data is an 8-bit integer, representing brightness, and use it directly as the value of brightness. This allows the color and brightness of the LED to be controlled based on the data stored by the user in the vendor storage partition.

6. Problems encountered

Question 1: Stuck Enter fastboot...OK, this is because the cmdline space is not enough, plus our customization, I initially wrote 512 which is not enough, and later changed it to 2048.

board_late_init: leon->board_late_init enter2 
USER_CUSTOM1_ID value: CUSTOM_20230826
rockchip_set_serialno: leon->rockchip_set_serialno enter4 
enter fastboot!
load_bmp_logo 尝试从MMC加载 logo.bmp...
512 bytes read in 40 ms (11.7 KiB/s)
170326 bytes read in 45 ms (3.6 MiB/s)
Rockchip UBOOT DRM driver version: v1.0.1
VOP have 1 active VP
vp0 have layer nr:6[0 2 4 1 3 5 ], primary plane: 4
vp1 have layer nr:0[], primary plane: 0
vp2 have layer nr:0[], primary plane: 0
disp info 0, type:11, id:0
xfer: num: 2, addr: 0x50
[dw_hdmi_i2c_read] i2c read reg[0x01] no interrupt
xfer: num: 2, addr: 0x50
[dw_hdmi_i2c_read] i2c read reg[0x01] no interrupt
xfer: num: 2, addr: 0x50
[dw_hdmi_i2c_read] i2c read reg[0x01] no interrupt
xfer: num: 2, addr: 0x50
[dw_hdmi_i2c_read] i2c read reg[0x01] no interrupt
xfer: num: 2, addr: 0x50
[dw_hdmi_i2c_read] i2c read reg[0x01] no interrupt
can't get edid block:0
failed to get edid
base_parameter.mode:1920x1080
mode:1920x1080
hdmi@fe0a0000:  detailed mode clock 148500 kHz, flags[5]
    H: 1920 2008 2052 2200
    V: 1080 1084 1089 1125
bus_format: 100a
VOP update mode to: 1920x1080p0, type: HDMI0 for VP0
VOP VP0 enable Smart0[654x258->654x258@633x411] fmt[2] addr[0x7df2a000]
CEA mode used vic=16
final pixclk = 148500000 tmdsclk = 148500000
PHY powered down in 0 iterations
PHY PLL locked 1 iterations
PHY powered down in 0 iterations
PHY PLL locked 1 iterations
sink has audio support
hdmi_set_clk_regenerator: fs=48000Hz ftdms=148.500MHz N=6144 cts=148500
CLK: (sync kernel. arm: enter 816000 KHz, init 816000 KHz, kernel 0N/A)
  apll 1416000 KHz
  dpll 780000 KHz
  gpll 1188000 KHz
  cpll 1000000 KHz
  npll 1200000 KHz
  vpll 24000 KHz
  hpll 148000 KHz
  ppll 200000 KHz
  armclk 1416000 KHz
  aclk_bus 150000 KHz
  pclk_bus 100000 KHz
  aclk_top_high 500000 KHz
  aclk_top_low 400000 KHz
  hclk_top 150000 KHz
  pclk_top 100000 KHz
  aclk_perimid 300000 KHz
  hclk_perimid 150000 KHz
  pclk_pmu 100000 KHz
Net:   eth1: ethernet@fe010000, eth0: ethernet@fe2a0000
switch to partitions #0, OK
mmc0(part 0) is current device
Enter fastboot...OK

Question 2: I was confused at first when debugging. env_set("bt_mac", "112233445566");This method will not work and will cause uboot to crash.

static int rockchip_get_bt_mac(void)
{
    
    
  
#ifdef CONFIG_ROCKCHIP_VENDOR_PARTITION

       char buf[ARP_HLEN_ASCII + 1];
       u8 ethaddr[ARP_HLEN];
       int ret;
        //不能直接这么写,哎 写java写习惯了。会导致uboot 崩掉
       //env_set("test_var", "112233445566");
       printf("%s: leon->rockchip_get_bt_mac enter 3 \n",__func__);
       ret = vendor_storage_read(BT_MAC_ID, ethaddr, sizeof(ethaddr));

       if (ret > 0 && is_valid_ethaddr(ethaddr)) {
    
    
               sprintf(buf, "%pM", ethaddr);
               env_set("bt_mac", buf);
       }
#endif
       return 0;

}
............
load_bmp_logo 尝试从MMC加载 logo_kernel.bmp...
512 bytes read in 45 ms (10.7 KiB/s)
170326 bytes read in 49 ms (3.3 MiB/s)
vp0, plane_mask:0x3f, primary-id:4, curser-id:-1
vp1, plane_mask:0x0, primary-id:0, curser-id:-1
vp2, plane_mask:0x0, primary-id:0, curser-id:-1
Adding bank: 0x00200000 - 0x08400000 (size: 0x08200000)
Adding bank: 0x09400000 - 0x80000000 (size: 0x76c00000)
"Synchronous Abort" handler, esr 0x86000004

* Reason:        Exception from an Instruction abort
* PC         =   61705f77e4380e70
* LR         =   61705f77e4380e70
* SP         =   000000007b9f7800
* ESR_EL2    =   0000000086000004
* Reloc Off  =   000000007d352000

x0 : 0000000000000000 x1 : 000000007b9f76f8
x2 : 0000000000000008 x3 : 0000000000000008
x4 : 000000000a117a6c x5 : 0000000000000029
x6 : 000000000a11f935 x7 : 000000000a117a74
x8 : 00000000000179fc x9 : 0000000000000008
x10: 000000007b9f75ac x11: 000000000a100000
x12: 00000000000179fc x13: 000000007b9f766c
x14: 000000000a100000 x15: 00000000ffffffff
x16: 0000000000000003 x17: 000000000c563004
x18: 000000007b9ffcf8 x19: 646e6120373d7472
x20: 746f6f6264696f72 x21: 78756e696c65732e
x22: 000000007b9f7b18 x23: 0000000000000000
x24: 0000000000000200 x25: 0000000000000000
x26: 000000007dd5431c x27: 0000000000280000
x28: 0000000000000000 x29: 6f6f6c206f722074


Call trace:
  PC:	[< 61705f77e4380e70 >]
  LR:	[< 61705f77e4380e70 >]

Stack:
	[< 61705f77e4380e70 >]

Copy info from "Call trace..." to a file(eg. dump.txt), and run
command in your U-Boot project: ./scripts/stacktrace.sh dump.txt 

Resetting CPU ...

### ERROR ### Please RESET the board ###

verify

Under simple settings,
set the RK DeviInfoWriteTool tool to Custom 1, change it to manual, and change the ID to 18 to correspond to the ID we added (#define USER_CUSTOM1_ID 18).
Insert image description here
Set Custom 2, change it to manual, and change the ID to 19 to correspond to the ID we added (#define USER_CUSTOM2_ID 19).
Insert image description here
Write data
Custom 1 data write: lcd_x: 112233
Custom 2 data write: lcd_y: 445566
Insert image description here

Restart to see serial port log printing

# ---->uboot 阶段打印
board_late_init: leon->board_late_init enter3 
--3> USER_CUSTOM2_ID value: lcd_y:445566
--3> USER_CUSTOM1_ID value: lcd_x:112233
# ---->kernel 阶段打印
[    0.000000] Kernel command line: storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal  androidboot.dtb_idx=0 androidboot.dtbo_idx=0  androidboot.verifiedbootstate=orange androidboot.slot_suffix= androidboot.serialno=SN20230826005 console=ttyFIQ0 androidboot.baseband=N/A androidboot.wificountrycode=CN androidboot.veritymode=enforcing androidboot.hardware=rk30board androidboot.console=ttyFIQ0 androidboot.verifiedbootstate=orange firmware_class.path=/vendor/etc/firmware init=/init rootwait ro loop.max_part=7 androidboot.selinux=permissive buildvariant=userdebug user_custom1_value=lcd_x:112233 user_custom2_value=lcd_y:445566 earlycon=uart8250,mmio32,0xfe660000 androidboot.boot_devices=fe310000.sdhci,fe330000.nandc


[    5.229307] user_custom1_value: lcd_x:112233
[    5.229324] user_custom2_value: lcd_y:445566

Summarize

Through the above steps, we have realized the function of Rockchip's custom vendorstorages data and then u-boot transmits the data to the kernel through cmdline. In this way, we can store some user-defined data in the vendor storage partition, then read it in u-boot and pass it to the kernel through the cmdline parameter so that it can be used by the driver or application in the kernel. This method can easily implement some user-defined functions, such as LED effects, screen parameters, etc.

I hope this blog is helpful to you. If you have any questions or suggestions, please leave them in the comment area. Thanks!
If your writing is good, please support me!

Guess you like

Origin blog.csdn.net/SHH_1064994894/article/details/132508787