Linux kernel 是怎么将devicetree中的内容生成plateform_device
1,实现场景(以VersatileExpress V2M为例说明其过程)
以arch/arm/mach-vexpress/v2m.c为例,在该文件中的v2m_dt_init函数的作用就是利用 dt(device tree)结构初始化 platform device。
static void __initv2m_dt_init(void)
{
of_platform_populate(NULL,of_default_bus_match_table,
v2m_dt_auxdata_lookup,NULL);
…...
}
of_platform_populate实现在 drivers/of/platform.c,是 OF 的标准函数。调用of_platform_populate把所有的platformdevice加入到kernel中。
intof_platform_populate(struct device_node *root,
const structof_device_id *matches,
const structof_dev_auxdata *lookup,
struct device*parent)
{
root = root ?of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root,child) {
rc =of_platform_bus_create(child, matches, lookup, parent, true);
}
…...
}
在of_platform_populate()中如果root 为 NULL,则将 root 赋值为根节点,这个根节点是用of_find_node_by_path()取到的。
struct device_node*of_find_node_by_path(const char *path)
{
struct device_node*np = allnodes;
read_lock(&devtree_lock);
for (; np; np =np->allnext) {
if (np->full_name&& (of_node_cmp(np->full_name, path) == 0)
&& of_node_get(np))
break;
}
read_unlock(&devtree_lock);
return np;
}
在这个函数中有一个很关键的全局变量:allnodes,它的定义是在drivers/of/base.c 里面:struct device_node *allnodes;
这应该所就是那个所谓的“devicetree data”了。它应该指向了 device tree 的根节点。问题又来了,这个 allnodes 又是咋来的呢?我们知道 device tree是由 DTC(Device Tree Compiler)编译成二进制文件DTB(Ddevice Tree Blob)的,然后在系统上电之后由bootloader 加载到内存中去,这个时候还没有device tree,而在内存中只有一个所谓的 DTB,这只是一个以某个内存地址开始的一堆原始的 dt数据,没有树结构。kernel 的任务需要把这些数据转换成一个树结构然后再把这棵树的根节点的地址赋值给allnodes就行了。这个过程一定是非常重要,因为没有这个 device tree 那所有的设备就没办法初始化,所以这个 dt 树的形成一定在 kernel刚刚启动的时候就完成了。
既然如此,我们来看看 kernel初始化的代码(init/main.c)。
2,铺垫(初始化device tree)
Kernel/init/main.c
asmlinkage void__init start_kernel(void)
{
setup_arch(&command_line);
}
这个 setup_arch就是各个架构自己的设置函数,哪个参与了编译就调用哪个,在本文中应当是arch/arm/kernel/setup.c 中的setup_arch()。
Kernel/arch/arm/setup.c
void __initsetup_arch(char **cmdline_p)
{
mdesc =setup_machine_fdt(__atags_pointer);
unflatten_device_tree();
}
这个时候 DTB 只是加载到内存中的.dtb 文件而已,这个文件中不仅包含数据结构,还包含了一些文件头等信息,kernel 需要从这些信息中获取到数据结构相关的信息,然后再生成设备树。
struct machine_desc* __init setup_machine_fdt(unsigned int dt_phys)
{
structboot_param_header *devtree;
devtree =phys_to_virt(dt_phys);
initial_boot_params= devtree;
}
phys_to_virt字面上的意思是物理地址转换成虚拟地址,那就是说__atags_pointer是一个物理地址,即__atags_pointer 的确是一个指针,再看变量devtree它指向了一个struct boot_param_header 结构体。随后 kernel把这个指针赋给了全局变量initial_boot_params。也就是说以后 kernel 会是用这个指针指向的数据去初始化 device tree。
structboot_param_header {
__be32 magic; /*magic word OF_DT_HEADER */
__be32 totalsize; /*total size of DT block */
__be32 off_dt_struct; /*offset to structure */
__be32 off_dt_strings; /*offset to strings */
__be32 off_mem_rsvmap; /*offset to memory reserve map */
__be32 version; /*format version */
__be32 last_comp_version; /* last compatible version */
/* version 2 fieldsbelow */
__be32 boot_cpuid_phys; /* Physical CPU id we're booting on */
/* version 3 fieldsbelow */
__be32 dt_strings_size; /* size of the DT strings block */
/* version 17 fieldsbelow */
__be32 dt_struct_size; /*size of the DT structure block */
};
看这个结构体,很像之前所说的文件头,有魔数、大小、数据结构偏移量、版本等等,kernel就应该通过这个结构获取数据,并最终生成设备树。现在回到setup_arch,果然在随后的代码中有这么一个函数:将DTB转换成device node的结构的节点
在系统初始化的过程中,我们需要将DTB转换成节点是device_node的树状结构,以便后续方便操作。具体的代码位于setup_arch->unflatten_device_tree中。
void __initunflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params,&allnodes,
early_init_dt_alloc_memory_arch);
}
可以看到,allnodes就是在这里赋值的,device tree 也是在这里正式开始建立的。
//device node 结构
struct device_node {
const char *name;----------------------device node name
const char *type;-----------------------对应device_type的属性
phandle phandle;-----------------------对应该节点的phandle属性
const char *full_name; ----------------从“/”开始的,表示该node的full path
struct property *properties;-------------该节点的属性列表
struct property *deadprops; ----------如果需要,删除某些属性,并挂入到deadprops的列表
struct device_node *parent;------parent、child以及sibling将所有的device node连接起来
structdevice_node *child;
structdevice_node *sibling;
struct device_node *next; --------通过该指针可以获取相同类型的下一个node
struct device_node *allnext;-------通过该指针可以获取node global list下一个node
struct proc_dir_entry *pde;--------开放到userspace的proc接口信息
struct kref kref;-------------该node的reference count
unsigned long _flags;
void*data;
};
unflatten_device_tree函数的主要功能就是扫描DTB,将devicenode被组织成:
(1)globallist。全局变量struct device_node *allnodes就是指向设备树的global list
(2)tree。
static void__unflatten_device_tree(struct boot_param_header *blob,
structdevice_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align))
{
//此处删除了healthcheck代码,例如检查DTB header的magic,确认blob的确指向一个DTB。
/* scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中 */
start = ((unsignedlong)blob) +
be32_to_cpu(blob->off_dt_struct);
size =unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = (size | 3) +1;
/*初始化的时候,并不是扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了所有的structdevice_node、node name、struct property所需要的内存。*/
mem = (unsignedlong)
dt_alloc(size + 4,__alignof__(struct device_node));
((__be32 *)mem)[size/ 4] = cpu_to_be32(0xdeadbeef);
/*这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device nodetree了 */
start = ((unsignedlong)blob) +
be32_to_cpu(blob->off_dt_struct);
unflatten_dt_node(blob,mem, &start, NULL, &allnextp, 0);
//此处略去校验溢出和校验OF_DT_END。
}
到此为止,device tree的初始化就算完成了,在以后的启动过程中,kernel 就会依据这个 dt 来初始化各个设备。
3,具体创建platformdevice的过程
接着第一部分的描述:重点剖析of_platform_bus_create()函数
of_platform_populate实现在 drivers/of/platform.c,是 OF 的标准函数。
intof_platform_populate(struct device_node *root,
const structof_device_id *matches,
const structof_dev_auxdata *lookup,
struct device*parent)
{
root = root ?of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root,child) {
rc =of_platform_bus_create(child, matches, lookup, parent, true);
}
…...
}
第一部分和第二部分总共完成了of_find_node_by_path("/")。这里开始分析函数of_platform_bus_create()。
staticint of_platform_bus_create(struct device_node *bus, ------要创建的device node
conststruct of_device_id *matches, ------要匹配的list
conststruct of_dev_auxdata *lookup, ------附属数据
structdevice *parent, bool strict) ------parent指向父节点
------strict是否要求完全匹配
{
const structof_dev_auxdata *auxdata;
struct device_node*child;
structplatform_device *dev;
const char *bus_id =NULL;
void *platform_data= NULL;
int rc = 0;
/* Make sure it hasa compatible property */
if (strict&& (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s()- skipping %s, no compatible prop\n",
__func__,bus->full_name);
return 0;
}
auxdata =of_dev_lookup(lookup, bus);//在传入lookup table寻找和该device node匹配的附加数据
if (auxdata) {
bus_id= auxdata->name;//如果找到,那么就用附加数据中的静态定义的内容
platform_data =auxdata->platform_data;
}
/*ARM公司提供了CPUcore,除此之外,它设计了AMBA的总线来连接SOC内的各个block。符合这个总线标准的SOC上的外设叫做ARM PrimecellPeripherals。如果一个devicenode的compatible属性值是arm,primecell的话,可以调用of_amba_device_create来向amba总线上增加一个ambadevice。*/
if(of_device_is_compatible(bus, "arm,primecell")) {
of_amba_device_create(bus,bus_id, platform_data, parent);
return 0;
}
//如果不是ARM PrimecellPeripherals,那么我们就需要向platform bus上增加一个platform device了。
dev =of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev ||!of_match_node(matches, bus))
return 0;
/* 一个devicenode可能是一个桥设备,因此要重复调用of_platform_bus_create来把所有的device node处理掉。*/
for_each_child_of_node(bus,child) {
pr_debug(" create child: %s\n", child->full_name);
rc =of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
return rc;
}
具体增加platformdevice的代码在of_platform_device_create_pdata中,代码如下:
structplatform_device *of_platform_device_create_pdata(
struct device_node*np,
const char *bus_id,
void *platform_data,
struct device*parent)
{
structplatform_device *dev;
if(!of_device_is_available(np)) //checkstatus属性,确保是enable或者OK的。
return NULL;
/*of_device_alloc除了分配structplatform_device的内存,还分配了该platform device需要的resource的内存。当然,这就需要解析该devicenode的interrupt资源以及memory address资源。*/
dev =of_device_alloc(np, bus_id, parent);
//设定platform_device中的其他成员
dev->dev.coherent_dma_mask= DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
dev->dev.bus =&platform_bus_type;
dev->dev.platform_data= platform_data;
/* We do not fillthe DMA ops for platform devices by default.
* This is currentlythe responsibility of the platform code
* to do such,possibly using a device notifier
*/
if(of_device_add(dev) != 0) {
platform_device_put(dev); //把这个platform device加入统一设备模型系统中
return NULL;
}
return dev;
}
至此,Linuxkernel已经完全把Device Tree中的内容生成了相对应的platform device。
源文档 <http://blog.youkuaiyun.com/lichengtongxiazai/article/details/38942033>