原文网址:http://www.cnblogs.com/biglucky/p/4057495.htmlhtml
Linux kernel 是怎么将 devicetree中的内容生成plateform_device
1,实现场景(以Versatile Express V2M为例说明其过程)
以arch/arm/mach-vexpress/v2m.c 为例,在该文件中的v2m_dt_init函数的做用就是利用 dt(device tree)结构初始化 platform device。
static void __init v2m_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把全部的platform device加入到kernel中。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_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;
这应该所就是那个所谓的“device tree 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 __init setup_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)
{
struct boot_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。
struct boot_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 fields below */
__be32 boot_cpuid_phys; /* Physical CPU id we're booting on */
/* version 3 fields below */
__be32 dt_strings_size; /* size of the DT strings block */
/* version 17 fields below */
__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 __init unflatten_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链接起来
struct device_node *child;
struct device_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,将device node被组织成:
(1)global list。全局变量struct device_node *allnodes就是指向设备树的global list
(2)tree。
static void __unflatten_device_tree(struct boot_param_header *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
//此处删除了health check代码,例如检查DTB header的magic,确认blob的确指向一个DTB。
/* scan过程分红两轮,第一轮主要是肯定device-tree structure的长度,保存在size变量中 */
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
size = unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = (size | 3) + 1;
/* 初始化的时候,并非扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了全部的struct device_node、node name、struct property所须要的内存。*/
mem = (unsigned long)
dt_alloc(size + 4, __alignof__(struct device_node));
((__be32 *)mem)[size / 4] = cpu_to_be32(0xdeadbeef);
/* 这是第二轮的scan,第一次scan是为了获得保存全部node和property所须要的内存size,第二次就是实打实的要构建device node tree了 */
start = ((unsigned long)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,具体建立platform device的过程
接着第一部分的描述:重点剖析 of_platform_bus_create()函数
of_platform_populate 实如今 drivers/of/platform.c,是 OF 的标准函数。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_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()。
static int of_platform_bus_create(struct device_node *bus, ------要建立的device node
const struct of_device_id *matches, ------要匹配的list
const struct of_dev_auxdata *lookup, ------附属数据
struct device *parent, bool strict) ------parent指向父节点
------strict是否要求彻底匹配
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* Make sure it has a 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公司提供了CPU core,除此以外,它设计了AMBA的总线来链接SOC内的各个block。符合这个总线标准的SOC上的外设叫作ARM Primecell Peripherals。若是一个device node的compatible属性值是arm,primecell的话,能够调用of_amba_device_create来向amba总线上增长一个amba device。*/
if (of_device_is_compatible(bus, "arm,primecell")) {
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
//若是不是ARM Primecell Peripherals,那么咱们就须要向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;
/* 一个device node多是一个桥设备,所以要重复调用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;
}
具体增长platform device的代码在of_platform_device_create_pdata中,代码以下:
struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;
if (!of_device_is_available(np)) //check status属性,确保是enable或者OK的。
return NULL;
/*of_device_alloc除了分配struct platform_device的内存,还分配了该platform device须要的resource的内存。固然,这就须要解析该device node的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 fill the DMA ops for platform devices by default.
* This is currently the 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;
}
至此,Linux kernel已经彻底把Device Tree中的内容生成了相对应的platform device。node