前言:以前写驱动程序时候,一般把驱动程序分成两部分platform_device跟platform_driver这两部分,
- platform_device:描述硬件使用的资源;
在前面文章介绍过设备树dts文件最终在linux内核中会转化成platform_device:
dts -> dtb -> device_node -> platform_device
本文章主要解决下面三个问题:
- linux内核如何把device_node转换成platfrom_device?
答:内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device
- 哪些设备树节点可以转换为platform_device。
答:
1、 该节点必须含有compatible属性
2、 根节点的子节点(节点必须含有compatible属性)
3、 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):
这些特殊的compatilbe属性为: "simple-bus","simple-mfd","isa","arm,amba-bus "
根节点是例外的,生成platfrom_device时,即使有compatible属性也不会处理。
- cpu可以访问很多外设,spi控制器 I2c控制器,led
如何在设备树中描述这些硬件?
答:
1、比如以下的节点:
/mytest会被转换为platform_device,
因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device
2、 /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
/i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。
3、类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
/spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。
/ {
mytest {
compatile = "mytest", "simple-bus";
mytest@0 {
compatile = "mytest_0";
};
};
i2c {
compatile = "samsung,i2c";
at24c02 {
compatile = "at24c02";
};
};
spi {
compatile = "samsung,spi";
flash@0 {
compatible = "winbond,w25q32dw";
spi-max-frequency = <25000000>;
reg = <0>;
};
};
};
下面通过源程序对问题进行分析:
首先看下platform_device结构体:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev; ----------------------2
u32 num_resources;
struct resource *resource; ---------------------1
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
1、resource是一个指针,指向动态分配的数组,num_resources表示动态分配数组的下标,这也就意味着platform_device中可以会有n(n>=0)项资源,这些资源来自设备树中reg属性(表示所占用的内存空间)、中断属性(表示所占用的中断号)对于resource(资源)有三种类别:内存资源、IO资源、中断资源,这三种资源都可以在设备树中指定,这些资源可以从device_node结构体转化得到。
2、在设备树中还会有其他的属性譬如 : spi-max-frequency = <25000000>;
它并不对应什么资源,那么这些属性保存在哪里呢?
在platform_device结构体里面有个struct device dev; 在dev中有个of_node(指向device_node结构体),所以我们以后想要得到某个属性时可以从platform_device->dev->of_node里面读取属性值,
struct device {
.........
struct device_node *of_node; /* associated device tree node */
.........
};
- 事实上,如果从C语言的开始函数start_kernel进行追溯,是找不到platform device这一部分转换的源头的,事实上,这个转换过程的函数是of_platform_default_populate_init(),它被调用的方式是这样一个声明:
arch_initcall_sync(of_platform_default_populate_init);
在linux内核源码中,同系列的调用声明还有:
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
这些宏最终都是调用__define_initcall(fn, n),这个数字代表系统启动时被调用的优先级,数字越小,优先级越低,用这一系列宏声明一个新的函数就是将这个函数指针放入内存中一个指定的段内。
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn;
编译内核,section属性的变量(.initcalln.init)会被集中放在一起,放在linux-4.19-rc3/arch/arm/kernel/vmlinux.lds文件中,linux内核启动时会从何里面取出section属性(.initcalln.init,n代表优先级)会依次调用这些段中的函数。
start_kernel // init/main.c
rest_init();
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level); // 比如 do_initcall_level(3)
for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数
2. of_platform_default_populate_init()做什么事情:-----device_node 转化为platform_device
static int __init of_platform_default_populate_init(void)
{
struct device_node *node;
if (!of_have_populated_dt())
return -ENODEV;
/*
* Handle certain compatibles explicitly, since we don't want to create
* platform_devices for every node in /reserved-memory with a
* "compatible",
*/
for_each_matching_node(node, reserved_mem_matches) //处理一些保留的节点
of_platform_device_create(node, NULL, NULL);
node = of_find_node_by_path("/firmware"); //处理/firmware下面的子节点
if (node) {
of_platform_populate(node, NULL, NULL, NULL);
of_node_put(node);
}
/* Populate everything else. 处理所有的信息 */
of_platform_default_populate(NULL, NULL, NULL); --------1
return 0;
}
1、
int of_platform_default_populate(struct device_node *root,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
return of_platform_populate(root, of_default_bus_match_table, lookup,
parent); --------1.1
}
1.1、
- 参考of_default_bus_match_table变量的定义:
const struct of_device_id of_default_bus_match_table[] = { { .compatible = "simple-bus", }, { .compatible = "simple-mfd", }, { .compatible = "isa", }, #ifdef CONFIG_ARM_AMBA { .compatible = "arm,amba-bus", }, #endif /* CONFIG_ARM_AMBA */ {} /* Empty terminated list */ };
如果节点的属性值为 "simple-bus","simple-mfd","isa","arm,amba-bus "之一的话,那么它子节点就可以转化成platform_device。
- of_platform_populate函数定义如下所示:
/**
* of_platform_populate() - Populate platform_devices from device tree data
* @root: parent of the first level to probe or NULL for the root of the tree
* @matches: match table, NULL to use the default
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent to hook devices from, NULL for toplevel
*
* Similar to of_platform_bus_probe(), this function walks the device tree
* and creates devices from nodes. It differs in that it follows the modern
* convention of requiring all device nodes to have a 'compatible' property,
* and it is suitable for creating devices which are children of the root
* node (of_platform_bus_probe will only create children of the root which
* are selected by the @matches argument).
*
* New board support should be using this function instead of
* of_platform_bus_probe().
*
* Returns 0 on success, < 0 on failure.
*/
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/"); //得到根节点
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
for_each_child_of_node(root, child) { //遍历所有的子节点
/* 每一个子节点都会调用of_platform_bus_create函数,根据节点创建对应的
* 总线节点
*/
rc = of_platform_bus_create(child, matches, lookup, parent, true);-----1.1.1
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
1.1.1、of_platform_bus_create函数定义
/**
* of_platform_bus_create() - Create a device for a node and its children.
* @bus: device node of the bus to instantiate
* @matches: match table for bus nodes
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent for new device, or NULL for top level.
* @strict: require compatible property
*
* Creates a platform_device for the provided device_node, and optionally
* recursively create devices for all the child nodes.
*/
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool 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 %pOF, no compatible prop\n",
__func__, bus);
return 0; //如果节点没有compatible属性的话,返回0
}
/* Skip nodes for which we don't want to create devices */
if (unlikely(of_match_node(of_skipped_node_table, bus))) {
pr_debug("%s() - skipping %pOF node\n", __func__, bus);
return 0;
}
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %pOF, already populated\n",
__func__, bus);
return 0;
}
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
/* 根据节点创建出对应的platform_device */
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
----1.1.1.1
/* 如果bus节点属性不含 "simple-bus","simple-mfd","isa","arm,amba-bus "之一的话,
* 返回0(不处理子节点)
*/
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
pr_debug(" create child: %pOF\n", child);
/* 该函数是个递归调用函数,当节点属性含有 "simple-bus","simple-mfd","isa","arm,amba-bus "
* 之一的话,任然把子节点当作对应的总线来对待,
*/
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
1.1.1.1、of_platform_device_create_pdata函数定义:
/**
* of_platform_device_create_pdata - Alloc, initialize and register an of_device
* @np: pointer to node to create device for
* @bus_id: name to assign device
* @platform_data: pointer to populate platform_data pointer with
* @parent: Linux device model parent device.
*
* Returns pointer to created platform device, or NULL if a device was not
* registered. Unavailable devices will not get registered.
*/
static 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) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;
/* 会从device_node中根据中断属性、IO属性构造出resource */
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
对于i2c节点里面的子节点应该交给总线来处理,对于i2c节点它是根目录下面的第一级子节点,它会有对应的platform_device,在linux内核中它也会找到对应的platform_device,找到之后对应的.probe函数就会被调用,
s3c24xx_i2c_probe(struct platform_device *pdev)
i2c_add_numbered_adapter(&i2c->adap); //注册一个adapter(i2c总线控制器)
__i2c_add_numbered_adapter(adap);
i2c_register_adapter(adap);
/* create pre-declared device nodes */
of_i2c_register_devices(adap); //
/* 遍历总线下面每一个可用的子节点 */
for_each_available_child_of_node(bus, node)
{
/* 创建、注册i2c_client结构体 *
client = of_i2c_register_device(adap, node);
}