最近在想一些好玩的功能 , 比如自定义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效果、屏参等。
希望这篇博客对你有所帮助,如果你有任何问题或建议,请在评论区留言。谢谢!
如果写的不错 三连支持下!