1. 前言
使用设备树之前,关于设备的硬件信息都是在arch/arm/plat-xxx和arch/arm/mach-xxx下通过填充结构体的方式进行硬编码,当硬件有小改动时,我们需要修改硬件信息,然后编译内核,在调试阶段反复编译内核是一个很烦、很耗时、没有技术含量的事情;
然后设备树出现了,使用设备树的情况下,当硬件有小改动时,只需要重新编译设备树文件就好了,不用重新编译内核;
按道理来说,设备树的机制已经很完美了,但是现在有这样一个任务,一块设备上预留了两个摄像头芯片的位置,分别是高清摄像头和标清摄像头,实际焊接的时候只会焊接一块芯片,高配设备只焊接高清摄像头芯片,低配设备则反之。需求:在kernel初始化过程中,通过MCU发送过来的消息来选择初始化高清摄像头芯片还是标清摄像头芯片。
2. 分析
2.1 改什么地方
内核将各个device_node转化成各种设备时,会根据其status属性的值来决定是否转化,比如i2c设备,i2c在初始化过程中会遍历i2c节点下的子节点,status属性为okay的子节点就会被注册(i2c_device_register)为i2c设备,设备和驱动匹配后就会进行初始化。因此只需要根据MCU发过来的消息将对应的设备树节点里的status属性值改为okay即可。
2.2 在什么时候改
dtb块-----device_node-----具体的设备结构体-----设备初始化。
四个阶段,很明显修改dtb块(二进制形式)的难度很大,不适合尝试,而设备什么时候初始化也不太好把握(但是是可以知道什么时候开始初始化设备的,最先初始化的是platform设备,这些设备的初始化是集中在某一个段里的,所以在这个段执行之前就可以),所以最好的时机就是内核将dtb块解析完毕以后,立刻使用Linux提供的OF系列函数提供的接口,查找、修改节点,我们且把这种方式称作“动态修改设备树”。
3. 动态修改设备树
3.1 选定修改位置
在博客设备树(三)中有设备树解析的详细流程,从那篇博客可以知道,函数关系如下:
start_kernel--->setup_arch(&command_line)--->unflatten_device_tree();
在unflatten_device_tree()函数中,会将dtb转化为node结构体,所以修改位置选在unflatten_device_tree()函数内部的末尾、或者紧跟unflatten_device_tree()函数调用的地方最佳。
3.2 具体方案
3.2.1 在kernel/drivers/of/fdt.c中添加两个函数接口,如下:
//因为这个修改是在内核启动很早的时候,内存操作比较敏感,稍有不慎就可能导致系统无法启动,所以这里参照内核解析dtb 时申请内存的方式和步骤(unflatten_dt_node函数),封装了一个创建属性的新接口
static struct property *create_new_property(char *name, void *value, int value_len)
{
struct property *new_prop;
void *mem;
int malloc_size = 0;
/* 1. calc the whole malloc memory size */
malloc_size = strlen(name)+1;
malloc_size += value_len;
malloc_size += sizeof(struct property);
/* 2. alloc the memory*/
mem = early_init_dt_alloc_memory_arch(malloc_size + 4, 4);
memset(mem, 0, malloc_size + 4);
new_prop = unflatten_dt_alloc(&mem, sizeof(struct property),
__alignof__(struct property));
new_prop->value = unflatten_dt_alloc(&mem, value_len, 1);
new_prop->name = (char*)unflatten_dt_alloc(&mem, strlen(name)+1, 1);
/* 3. fill the data*/
memcpy(new_prop->value, value, value_len);
new_prop->length = value_len;
strcpy(new_prop->name, name);
return new_prop;
}
static void camera_adaption(void)
{
struct device_node *np = NULL;
struct property *new_prop = NULL;
np = of_find_node_by_name(NULL, "Camera_std"); //通过节点名找到需要修改的节点
new_prop = create_new_property("status", "okay", sizeof("okay"));
of_update_property(np, new_prop); //将status属性修改为okay
}
3.2.2 然后在unflatten_device_tree(void)函数的尾部调用camera_adaption()函数即可(unflatten_device_tree函数在kernel/drivers/of/fdt.c的底部)
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
//加入动态修改设备树的接口
camera_adaption();
}
---------------------------------------------------------------------------------
//或者放在调用unflatten_device_tree函数的地方[arch/arm/kernel/setup.c]
void __init setup_arch(char **cmdline_p)
{
······
unflatten_device_tree();
//加入动态修改设备树的接口
camera_adaption();
}
4. 动态修改设备树的由来
对于修改设备树节点这个事儿,刚开始的时候小编也是没什么思绪,网上相关的资料基本没有(可能是因为这种需求不常见吧,万恶的需求,淦······),还好Linux有提供修改设备树的OF函数接口系列,通过阅读内核中提供的文档,我注意到其中一句话的大意是设备树可以被动态修改,于是小编就去搜索动态修改设备树,感谢万能的互联网,真的让我找到了唯一的一篇相关的博文,在参考那篇博文和分析源码的情况下,终于搞定了这个需求。
4.1 总结
Linux内核自带的文档很有用,很有阅读的必要。
至于那唯一 一篇相关的博文,我现在已经找不到链接了,所以就不贴出来了(主要还是懒得找,哈哈哈······)。