Rockchip 自定义vendorstorages数据再u-boot通过cmdline给kernel传递数据

简介: Rockchip 自定义vendorstorages数据再u-boot通过cmdline给kernel传递数据

最近在想一些好玩的功能 , 比如自定义logo , 动态更新屏参 , 动态切换dts 等功能(虽然我以前都在mtk平台做过 但平台不一样呢 时间也久远了) , 所以呢 近两天研究了下uboot相关内容。

这篇内容搞了我大半天 , 中间遇到了许多坑 和 莫名其妙的问题 , 但是最终都被我理解消化了。所以别看内容只有一点 需要自己上手才能真正的理解和掌握。

那我们开始吧 , 我会把我花了大半天的成果写出来 希望能帮助到有需要的朋友。

Rockchip系列之VendorStorage 浅浅的介绍(1)

Rockchip系列之VendorStorage uboot/kernel/user space 阶段接口使用介绍(2)

Rockchip系列之VendorStorage 新增framework系统jni+service接口访问(3)

Rockchip系列之VendorStorage 新增framework封装VendorStorageManager访问(4)

Rockchip 自定义vendorstorages数据再u-boot通过cmdline给kernel传递数据

背景

在Rockchip的芯片上,有一个特殊的分区叫做vendor storage,它是一个用于存储用户自定义数据的分区,比如序列号、MAC地址、校准数据等。vendor storage分区是通过MMC命令访问的,不需要挂载文件系统,可以在u-boot和kernel中都使用。vendor storage分区的大小和位置可以在dts文件中配置,一般为4MB。

在某些场景下,可能需要把vendor storage分区中的某些数据在u-boot中读取出来,然后通过cmdline参数传递给kernel,以便kernel中的驱动或应用程序使用。例如,我们可能需要把用户自定义的LED颜色或亮度值存储在vendor storage分区中,然后在u-boot中读取出来,通过cmdline参数传递给kernel中的LED驱动,从而实现用户自定义的LED效果。

参考: Rockchip系列之VendorStorage uboot/kernel/user space 阶段接口使用介绍(2)

实现方法

要实现这个功能,我需要修改u-boot和kernel两个部分的代码。具体步骤如下:

1. 在u-boot中定义两个ID用于存储用户自定义数据

在u-boot中,vendor storage分区中的每个数据项都有一个唯一的ID来标识,这个ID是一个16位的整数。可以在u-boot/arch/arm/include/asm/arch-rockchip/vendor.h文件中定义需要的ID,例如:

#define USER_CUSTOM1_ID                        18
#define USER_CUSTOM2_ID                        19

这里我定义了两个ID,分别为18和19,用于存储用户自定义的两个数据项。

2. 在u-boot中读取vendor storage分区中的用户自定义数据,并设置为环境变量

在u-boot中,我们可以使用vendor_storage_read函数来读取vendor storage分区中的某个数据项,这个函数需要传入三个参数:ID、缓冲区指针和缓冲区大小。我们可以在u-boot/arch/arm/mach-rockchip/board.c文件中添加两个函数来读取我们定义的两个ID对应的数据,并设置为环境变量,例如:

// 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();
*/

这里分别定义了两个函数,rockchip_get_user_custom1_value和rockchip_get_user_custom2_value,用于读取vendor storage分区中的USER_CUSTOM1_ID和USER_CUSTOM2_ID的值,并设置为user_custom1_value和user_custom2_value两个环境变量。我们可以在函数中打印出读取到的值,以便调试。

3. 在u-boot中把环境变量拼接到cmdline参数中,并传递给kernel

在u-boot中,我们可以使用env_get函数来获取环境变量的值,然后使用snprintf函数来拼接到cmdline参数中。我们可以在u-boot/arch/arm/lib/bootm.c文件中修改boot_prep_linux函数,例如:

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) {

这里首先获取了原来的cmdline参数,然后判断是否有用户自定义数据的环境变量,如果有,就把它们拼接到cmdline参数中,然后重新设置bootargs环境变量,并传递给commandline指针。这样,当u-boot启动kernel时,就会把这些用户自定义数据作为cmdline参数传递给kernel。

4. 在kernel中解析cmdline参数,并获取用户自定义数据

在kernel中,我们可以使用strstr函数来查找cmdline参数中是否包含需要的用户自定义数据的键值对,如果有,就把它们解析出来,并存储到相应的变量中。我们可以在kernel/drivers/leds/led_control.c文件(我自己写的驱动 , 任意都可以 我只是验证打印是否正确 )中添加一个函数来打印出用户自定义数据的值,例如:

/*不要用这个 这个不会截取的,
[    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. 在kernel中使用用户自定义数据来控制LED的颜色和亮度

在kernel中,我们可以根据用户自定义数据的值来设置LED的颜色和亮度,从而实现用户自定义的LED效果。我们可以在kernel/drivers/leds/led_control.c文件中修改led_init_state函数,例如:

(这个步骤已经不重要了, 在步骤4拿到数据之后 根据你们自己情况该干嘛干嘛 这里只是我自己的逻辑)

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]);
        }
    }
}

这里我们首先获取了用户自定义数据的值,然后根据它们来设置LED的颜色和亮度。假设用户自定义数据的第一个值是一个24位的整数,表示RGB颜色,把它分别取出高8位、中8位和低8位,作为红色、绿色和蓝色的值。假设用户自定义数据的第二个值是一个8位的整数,表示亮度,直接用它作为亮度的值。这样就可以根据用户在vendor storage分区中存储的数据来控制LED的颜色和亮度。

6. 碰到的问题

问题1: 卡在Enter fastboot...OK , 这个是以为cmdline的空间不够了, 加上我们z自定义的我最开始写的是512 是不够的 , 后面改成来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

问题2: 最开始调试一脸懵逼 , 直接env_set("bt_mac", "112233445566"); 这种方式是不行的 会导致uboot崩溃。

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 ###

验证

简单设置下RK DeviInfoWriteTool工具

设置自定义1 , 改成手动 , ID改为18 和 我们增加的ID对应(#define USER_CUSTOM1_ID 18)。

设置自定义2 , 改成手动 , ID改为19 和 我们增加的ID对应(#define USER_CUSTOM2_ID 19)。

写入数据

自定义1数据写入:lcd_x:112233

自定义2数据写入:lcd_y:445566

重启看串口日志打印

# ---->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

总结

通过以上的步骤,我们就实现了Rockchip自定义vendorstorages数据再u-boot通过cmdline给kernel传递数据的功能。这样,我们就可以在vendor storage分区中存储一些用户自定义的数据,然后在u-boot中读取出来,通过cmdline参数传递给kernel,以便kernel中的驱动或应用程序使用。这种方法可以方便地实现一些用户自定义的功能,比如LED效果、屏参等。

希望这篇博客对你有所帮助,如果你有任何问题或建议,请在评论区留言。谢谢!

如果写的不错 三连支持下!

相关文章
|
7月前
|
存储 Linux Android开发
Rockchip系列之VendorStorage uboot/kernel/user space 阶段接口使用介绍(2)
Rockchip系列之VendorStorage uboot/kernel/user space 阶段接口使用介绍(2)
393 0
|
7月前
|
Linux
linux驱动层输出dev_dbg打印信息
linux驱动层输出dev_dbg打印信息
151 0
|
7月前
|
Linux
cmdline(二):uboot cmdline怎么传?&&cmdline kernel怎么用?
cmdline(二):uboot cmdline怎么传?&&cmdline kernel怎么用?
268 0
|
传感器 算法 Linux
media设备节点初始化与Video4Linux初始化
media设备节点初始化与Video4Linux初始化
213 0
|
Linux 编译器
内核是如何运行ko文件的--insmod命令
内核是如何运行ko文件的--insmod命令
633 0
|
Unix Linux
cat /proc/bus/input/devices 文件内容详解
cat /proc/bus/input/devices 文件内容详解
511 0
cat /proc/bus/input/devices 文件内容详解
ALSA驱动源码之devm_snd_soc_register_component源码分析
ALSA驱动源码之devm_snd_soc_register_component源码分析