有时候linux内核中的驱动程序需要根据一些参数作出对应的调整,例如生产中可能需要适配多种型号LCD屏,如果只需要传入一些参数,就能让驱动程序自动适配驱动参数,这样会给生产调试带来很多方便。
这里介绍的方法是利用uboot传递给内核的bootargs参数。
为了能让内核使用uboot的bootargs参数,在配置内核选项中需要选择:
Boot opthions--->Kernel command line type :
(X) Use bootloader kernel arguments if available
( ) Extend bootloader kernel arguments
( ) Always use the default kernel command string
linux内核代码中已经实现了提取bootargs参数的方法,就是利用__setup宏。
__setup宏在<linux/init.h>定义如下:
/*
* Only for really core code. See moduleparam.h for the normal way.
*
* Force the alignment so the compiler doesn't space elements of the
* obs_kernel_param "array" too far apart in .init.setup.
*/
#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
/* NOTE: fn is as per module_param, not __setup! Emits warning if fn
* returns non-zero. */
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
该宏的作用是在编译时产生一个.init.data初始化段,该初始化段以__setup_start标号为起始,以__setup_end标号为结束。而以__setup宏定义的data则插入在__setup_start与__setup_end之间。这样就形成一张线性表,只需要查找该表就可提取bootargs参数了。
early_param宏的作用与__setup宏的作用类似,区别在于early_param定义的选项会比普通内核选项优先处理。在源码init/main.c中看到首先使用parse_early_param()处理early_param定义的选项,然后使用parse_args()处理__setup定义的选项。这样做的目的是保证处理普通内核参数之前能够预先做一些工作,例如初始化存储环境,设置输入输出环境等。
我们这里以前面的适配LCD的例子讲解一下使用方法。
首先需要在uboot中设置需要的参数,例如我们设置一个lcdtype的参数
uboot#setenv lcdtype 1
然后将该参数插入bootargs参数中,lcdtype = ${lcdtype}
有的linux构建环境buildroot会提供修改bootargs参数的脚本,则需要从脚本中更改bootargs,这样就不用每次重新编译都重新设置该参数了。例如全志A33平台,有个配置文件brandy\pack\chips\sun8iw5p1\configs\linux\default\env.cfg
其内容就是配置uboot引导参数和linux运行环境的:
bootdelay=3
#default bootcmd, will change at runtime according to key press
bootcmd=run setargs_nand boot_normal#default nand boot
#kernel command arguments
console=ttyS0,115200
nand_root=/dev/nandd
mmc_root=/dev/mmcblk0p7
init=/init
loglevel=8
lcdtype=1
#set kernel cmdline if boot.img or recovery.img has no cmdline we will use this
setargs_nand=setenv bootargs console=${console} root=${nand_root} init=${init} loglevel=${loglevel} partitions=${partitions} lcdtype=${lcdtype}
setargs_mmc=setenv bootargs console=${console} root=${mmc_root} init=${init} loglevel=${loglevel} partitions=${partitions} lcdtype=${lcdtype}
#nand command syntax: sunxi_flash read address partition_name read_bytes
#0x40007800 = 0x40008000(kernel entry) - 0x800(boot.img header 2k)
boot_normal=sunxi_flash read 40007800 boot;boota 40007800
boot_recovery=sunxi_flash read 40007800 recovery;boota 40007800
boot_fastboot=fastboot
#recovery key
recovery_key_value_max=0x13
recovery_key_value_min=0x10
#fastboot key
fastboot_key_value_max=0x8
fastboot_key_value_min=0x2
如上所示,已经将lcdtype参数插入到bootargs中了,我们可以使用如下方法提取参数lcdtype,在需要使用参数lcdtype的LCD驱动代码中加入以下代码:
static int lcdtype = 0;
static int __init lcdtype_get(char *str)
{
if (!str)
return 0;
if(!strcmp("1",str)) {
lcdtype = 1;
}
return 1;
}
__setup("lcdtype=", lcdtype_get);
之后就可在LCD驱动代码中使用lcdtype这个变量了。是不是很简单。
这是最简单的使用bootargs的方法,当然如果想手动编代码解析bootargs参数也是可以的,在内核源码init/main.c文件中的数组boot_command_line保存了bootargs字符串,在main.c中写一个接口函数获取它,然后送给解析函数去解析。
char* kernel_get_cmdline_string(void)
{
return boot_command_line;
}
上面是内核中获取bootargs字符串的方法。
在应用程序中需要通过读取
/proc/cmdline
的值获取bootargs字符串。
需要注意,在<linux/init.h>中__setup宏是在MODULE宏无效时起作用的,所以使用__setup宏的源文件在编译时需要使用[*]built-in方式,如果使用[M]module方式编译会报defined but not used错误。
假如需要在MODULE中使用bootargs的env参数,可以改造成如下方式:
static int lcdtype = 0;
int lcdtype_check(void)
{
return lcdtype;
}
EXPORT_SYMBOL(lcdtype_check);
static int __init lcdtype_get(char *str)
{
if (!str)
return 0;
if(!strcmp("1",str)) {
lcdtype = 1;
}
return 1;
}
__setup("lcdtype=", lcdtype_get);
并将以上代码放入非MODULE源码中,如/init/main.c中,因为已经使用EXPORT_SYMBOL宏将lcdtype_check扇出为全局符号呢,所以在应用中只需要声明一下即可:
extern int lcdtype_check();
int type = lcdtype_check();
//......
如果有帮到你,请帮我点个赞:P