昨晚,一网友问我如何把LCD反转一下输出,顿时不会,只会去看之前的dm3730的linux2.6.32的内核。偶然发现直接设置bootargs的参数到内核也许就可以啦。
Linux以内核模块为核心,自动编译如系统后,所以的类似init_call等都会存放在Init的section中。类似的,一个模块中经过module_param()和module_param_named()设置的参数等也会有自己的一片section。这让我觉得这和dsp的内存段分配很类似。
1。下面我以 linux/drivers/video/omap2/omapfb-main.c这个驱动模块中的参数为列来做一个分析内核如何接收参数使得模块加载时,该参数已经初始化完毕。
在该文件下可以找到这组对应的模块参数:
module_param_named(mode, def_mode, charp, 0);
module_param_named(vram, def_vram, charp, 0);
module_param_named(rotate, def_rotate, int, 0);
module_param_named(vrfb, def_vrfb, bool, 0);
module_param_named(mirror, def_mirror, bool, 0);
从本质上来说module_param_named等其实就类似与dsp中的#pargam section()的功能,不信可以看下这些宏的实现,上述的在模块参数表中对应的名字都会加上当前模块名.name,MODULE_NAME.param参数:
#define MODULE_PARAM_PREFIX KBUILD_MODNAME "."
#define __module_param_call(prefix, name, set, get, arg, isbool, perm) \
/* Default value instead of permissions? */ \
static int __param_perm_check_##name __attribute__((unused)) = \
BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)) \
+ BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN); \
static const char __param_str_##name[] = prefix #name; \
static struct kernel_param __moduleparam_const __param_##name \
__used \
__attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
= { __param_str_##name, perm, isbool ? KPARAM_ISBOOL : 0, \
set, get, { arg } }
#define module_param_call(name, set, get, arg, perm) \
__module_param_call(MODULE_PARAM_PREFIX, \
name, set, get, arg, \
__same_type(*(arg), bool), perm)
/* Helper functions: type is byte, short, ushort, int, uint, long,
ulong, charp, bool or invbool, or XXX if you define param_get_XXX,
param_set_XXX and param_check_XXX. */
#define module_param_named(name, value, type, perm) \
param_check_##type(name, &(value)); \
module_param_call(name, param_set_##type, param_get_##type, &value, perm); \
__MODULE_PARM_TYPE(name, #type)
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
上述的宏调用过程,说明了最终哦会将模块参数的value取地址存放入_attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *))))这个section中去。可以猜想的是,最终都会访问这个模块参数指针列表,将uboot中的Bootargs等参数传入到内核时,对相关的模块参数区域进行初始化,其主要通过name来进行相对应的查找与匹配。
这个查找的过程在init/main.c函数下的start_kernel处:
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
int parse_args(const char *name,
char *args,
struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
{
while (*args) {
int ret;
int irq_was_disabled;
args = next_arg(args, ¶m, &val);//提取一个参数的名字到param,如果该参数有=的就赋地址值给val
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, params, num, unknown);//提取一个参数到param,并对该参数进行解析,params是_param参数段的首地址
if (irq_was_disabled && !irqs_disabled()) {
printk(KERN_WARNING "parse_args(): option '%s' enabled "
"irq's!\n", param);
}
...
}
。
。
。
static int parse_one(char *param,
char *val,
struct kernel_param *params,
unsigned num_params,
int (*handle_unknown)(char *param, char *val))
{
unsigned int i;
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {//论循传入的参数名字是否在模块参数表有匹配!
DEBUGP("They are equal! Calling %p\n",
params[i].set);
return params[i].set(val, ¶ms[i]);//将传入的value根据模块参数的数据类型,转化后赋值给参数表指针所指向的模块参数,而这些参赛在模块
//中往往是static的形似,故参数表存的是参数地址!
}
}
通过以上各部分可以完全的将bootargs参数进行解析,而各模块在加载时也就可以使用到自己模块内部已经初始化的参数。
如本列中的framebuffer驱动,需要用到的omapfb.mode就是如此传入的。
bootargs 'console=ttyS2,115200n8 root=/dev/mmcblk0p2 rw ip=off mem=55M@0x80000000 mpurate=1000 omap_vout.vid1_static_vrfb_alloc=y omapfb.vram=0:8M mem=384M@0x88000000 omapfb.mode=lcd omapfb.rotate=2 omapfb.vrfb=y omapdss.def_disp=lcd rootwait vram=8M rootdelay=3'来实现旋转
此外补充一点是内核模块参数可以在insmod或者modprobe时,制定内核模块的参数值,如rotate = 2, vrfb = y即可。因为已经指定了模块名故不需要再参数前加前缀。bootargs中加入的前缀目的是区分不同模块的相同name的参数。