platform_device设备创建
1 dtb文件内存管理
start_kernel // init/main.c
setup_arch // arch/arm/kernel/setup.c
arm_memblock_init(mdesc); // arch/arm/mm/init.c
early_init_fdt_reserve_self();
/* Reserve the dtb region */
early_init_dt_reserve_memory_arch(__pa(initial_boot_params),fdt_totalsize(initial_boot_params),0);
memblock_reserve(base, size);
在引入DTB后,内核会保留DTB占用内存,即使不在dts文件中利用/memreserve/指明区域,内核还是会进行保留
2 设备树的构造
dts文件中每一个{ }代表一个节点,如树型结构一致,存在父子节点、兄弟节点
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
unflatten_device_tree(); // drivers/of/fdt.c
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
{
/* First pass, scan for size */
unflatten_dt_nodes(blob, NULL, dad, NULL); // 扫描节点数量
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes); // 真正分配内存
fpsizes[depth+1] = populate_node(blob, offset, &mem, nps[depth], fpsizes[depth], &nps[depth+1], dryrun);
{
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node));\
np->full_name = fn = ((char *)np) + sizeof(*np);
populate_properties(blob, offset, mem, np, pathp, dryrun); // 属性
{
pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property));
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
}
}
}
可见整个流程中涉及两个结构:struct device_node与struct property
2.1 device_node
struct device_node { \\ include/linux/of.h
const char *name; // 此为节点属性name,没有该属性即为NULL
const char *type; // 此为节点属性device_type,没有该属性即为NULL
phandle phandle;
const char *full_name; // 指向节点名字,在整个结构体之后
struct fwnode_handle fwnode;
struct property *properties; // 节点属性
struct property *deadprops; /* removed properties */
struct device_node *parent; // 节点父节点
struct device_node *child; // 节点子节点
struct device_node *sibling; // 节点兄弟节点
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
device_node节点中绑定着自身的property属性
2.2 property
struct property { \\ include/linux/of.h
char *name; // 属性名字, 指向dtb文件中的字符串
int length; // 属性值的长度
void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
struct property *next;
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
3 device_node转换platform_device
在总线设备驱动模型未使用设备树情况下,对于一个字符设备而言,会创建platform_driver与platform_device,platform_driver为实际的驱动位置,platform_device来设置相应硬件资源,所以对于一个设备树而言,对于一个节点是否需要创建platform_device结构,在3.3节再进行展开。
start_kernel
/* Do the rest non-__init'ed, we're now alive */
rest_init(); \\ init/main.c
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
// kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
do_initcall_level(level);
{
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
这部分代码看似与platform_device转换没有任何关联,一步步分析:
向do_one_initcall函数传入的参数为fn,fn = initcall_levels[level];initcall_levels定义为以下
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start, //
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
这一部分涉及linux的initcall机制(针对编译进内核的驱动)
3.1 initcall机制
参考:https://www.cnblogs.com/downey-blog/p/10486653.html
在接触驱动程序时,通常以module_init()函数接口来启动驱动程序,编译进内核,内核70%以上都是驱动程序,驱动程序如此多,内核启动init程序去访问每一个驱动程序的xxx_init()函数,这并不是一个高效的做法,linux中采取了自定义段方式存放初始化函数地址,通过函数指针取指方式执行。
3.1.1 initcall定义
在include/linux/init.h文件中定义了以下宏
#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 __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
__attribute__机制作为GNU C的一大特色,主要用于设置函数、变量、类型的属性,这里表示将目标符号防止在括号指定的段中
#在宏定义里表示将目标字符串化,##表将多个符号连接成一个符号
__used表编译器不能优化该符号,需保留该符号并放置在内核镜像.initcall0.init段处
3.1.2 函数调用
现在可以继续跟踪do_one_initcall(*fn)函数了,这个函数就是执行所有使用xxx_initcall()声明的函数,fn作为参数传入
typedef int (*initcall_t)(void);
extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
可见initcall_levels为指针数组,levels递增,可见在存在优先级的驱动程序里是可以让优先级高的先加载的,该函数一共执行8次
根据3.1中的符号规则,分解
- initcall_levels[level] -> *__initcall##level##_start
- 取__initcall##level##_start地址的函数指针 = “.initcall##level##.init"段 = xxx_initcall()添加函数
Q:initcall_levels[level]是怎么链接到”.initcall##level##.init"段的?
A:在链接脚本中指定了
#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
KEEP(*(.initcall##level##.init)) \
KEEP(*(.initcall##level##s.init)) \
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
KEEP(*(.initcallearly.init)) \
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7) \
VMLINUX_SYMBOL(__initcall_end) = .;
扩展(想到啥说啥,单纯防止忘了,与本文无关):
加载地址与运行地址:加载地址表示程序加载到哪个地址,运行(链接)地址表示程序在哪里运行
在加载地址≠链接地址时,会碰到程序读取乱码现象,甚至奔溃,主要还是由于代码未重定位,数据信息在加载地址开始的内存,并不在链接地址的内存块中,程序读取时数据会从链接地址开始读,但此时的值并未从加载地址拷贝过来,导致读取错误,重定位表示将数据从加载地址向运行地址拷贝的过程。拷贝的过程由最前面的代码来执行,这也代表着拷贝之前的代码必须是位置无关码
3.2 函数调用
内核中设备树的初始化在of_platform_default_populate_init函数,以arch_initcall_sync(of_platform_default_populate_init);加载
static int __init of_platform_default_populate_init(void)
{
struct device_node *node;
if (!of_have_populated_dt())
return -ENODEV;
/*
* Handle ramoops explicitly, since it is inside /reserved-memory,
* which lacks a "compatible" property.
*/
node = of_find_node_by_path("/reserved-memory");
if (node) {
node = of_find_compatible_node(node, NULL, "ramoops");
if (node)
of_platform_device_create(node, NULL, NULL);
}
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL);
return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
在of_platform_device_create中递归处理每个节点
of_platform_default_populate_init
{
of_platform_default_populate
{
of_platform_populate(root, of_default_bus_match_table, lookup, 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); // 默认为总线节点进行处理
{
if (strict && (!of_get_property(bus, "compatible", NULL))) // 确定节点包含compatible属性
of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 填充platform_device结构
{
of_device_alloc(np, bus_id, parent);
{
// 分配platform_device
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
/* count the io and irq resources */
of_address_to_resource(np, num_reg, &temp_res) // IO与IRQ资源处理
of_find_device_by_node(node)
of_irq_count(np)
kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL)
// 设置具体资源描述
dev->num_resources = num_reg + num_irq;
dev->resource = res;
dev->dev.parent = parent ? : &platform_bus;
// 设置总线名字
if (bus_id) dev_set_name(&dev->dev, "%s", bus_id);
else of_device_make_bus_id(&dev->dev);
}
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_device_add(dev)
device_add(&ofdev->dev)
}
for_each_child_of_node(bus, child) // 为子节点匹配of_default_bus_match_table,进行递归处理
{
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) of_node_put(child);
}
}
}
of_node_set_flag(root, OF_POPULATED_BUS); // 更新标志
of_node_put(root);
}
}
}
从of_platform_default_populate看匹配过程,发现传入了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 */
};
可见处理完根节点下包含compatible属性的子节点后,会根据of_default_bus_match_table进一步判断是否需要进一步处理该子节点下的下一级node
3.3 设备节点转换
对于总线设备而言,节点下包含具体硬件节点,这些挂载的硬件节点如何处理由platform_driver中的.probe函数决定
i2c { // 当前节点包含platform_device <--> platform_driver(.probe处理子节点at24c02)
compatile = "samsung,i2c";
at24c02 { // 总线子节点不会被转换当前节点包含platform_device,对于I2C转换为I2C_Client,SPI会转换为spi_device
compatile = "at24c02";
};
};
根据之前函数调用流程可知,of_platform_default_populate_init函数会遍历device_node树来生成platform_device,但并非所有节点都会转化为oplatform_device,大致有以下规则:
- 该节点必须含有compatible属性
- 根节点的子节点(节点必须含有compatible属性)
- 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):这些特殊的compatilbe属性为:“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”
总结起来就是:kernel为DTB中所有包含compatible的第一级node创建platform_device,并向平台设备总线注册。若第一级node的compatible属性为"simple-bus",“simple-mfd”,“isa”,“arm,amba-bus”,则为当前node的包含compatible属性的第二级node创建platform_device,所有子节点都会注册到该总线上。
例:
(1)I2C总线设备
platform_device匹配到platform_driver之后会调用probe结构体,结合https://www.linuxidc.com/Linux/2017-03/142201.htm 可以确定在创建I2C设备时是通过i2c_add_numbered_adapter添加适配器的
s3c24xx_i2c_probe
i2c_add_numbered_adapter(&i2c->adap) // drivers/i2c/i2c-core-base.c
__i2c_add_numbered_adapter(adap)
i2c_register_adapter(adap)
of_i2c_register_devices(adap) // drivers/i2c/i2c-core-of.c
{
bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
if (!bus)
bus = of_node_get(adap->dev.of_node);
for_each_available_child_of_node(bus, node) {
if (of_node_test_and_set_flag(node, OF_POPULATED))
continue;
client = of_i2c_register_device(adap, node);
if (IS_ERR(client)) {
dev_warn(&adap->dev,
"Failed to create I2C device for %pOF\n",
node);
of_node_clear_flag(node, OF_POPULATED);
}
}
of_node_put(bus);
}
进一步展开of_i2c_register_device
of_i2c_register_device // drivers/i2c/i2c-core-of.c
of_modalias_node(node, info.type, sizeof(info.type)) // drivers/of/base.c
compatible = of_get_property(node, "compatible", &cplen);
addr_be = of_get_property(node, "reg", &len);
addr = be32_to_cpup(addr_be);
of_property_read_bool(node, "host-notify")
result = i2c_new_device(adap, &info);
(2)SPI总线设备
该部分包括I2C部分,后续树莓派系列文章上结合内核及以前的学习再深入分析
stm32_spi_probe
devm_spi_register_master(&pdev->dev, master) = spi_register_controller(_ctlr) // drivers/spi/spi.c
of_register_spi_devices(ctlr)
for_each_available_child_of_node(ctlr->dev.of_node, nc)
{
if (of_node_test_and_set_flag(nc, OF_POPULATED))
continue;
spi = of_register_spi_device(ctlr, nc);
if (IS_ERR(spi)) {
dev_warn(&ctlr->dev,
"Failed to create SPI device for %pOF\n", nc);
of_node_clear_flag(nc, OF_POPULATED);
}
}
4 驱动匹配过程
总线设备驱动模型采取了分离机制,将硬件相对稳定的东西分离出来,形成dev-bus-drv模型。将dev、drv分别放入bus管理的链表中,以bus提供的match函数进行匹配。涉及设备树的匹配结合代码来看:
struct bus_type platform_bus_type = { // drivers/base/platform.c
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, // 匹配函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
从该函数可以明显看到匹配过程有以下几步:
① platform_device.driver_override 与 platform_driver.driver.name
strcmp(pdev->driver_override, drv->name)
② platform_dev.dev.of_node的compatible属性 与 platform_driver.drv->of_match_table
of_driver_match_device(dev, drv)
of_match_device(drv->of_match_table, dev)
of_match_node(matches, dev->of_node)
__of_match_node(matches, node)
__of_device_is_compatible(node, matches->compatible, matches->type, matches->name);
{
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
}
③ platform_dev.name 与 platform_driver.id_table
platform_match_id(pdrv->id_table, pdev)
strcmp(pdev->name, id->name
④ platform_dev.name 与 platform_driver.drv->name
strcmp(pdev->name, drv->name
附:几个结构体
struct platform_device {
const char *name; // 驱动名称
int id;
bool id_auto;
struct device dev;
u32 num_resources; // 资源数组长度
struct resource *resource; // 资源数组,IO与中断资源
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;
};
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
struct device_driver {
const char *name;
struct bus_type *bus; // 定义连接总线
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table; // 匹配表
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
附:设备中查看dtb
a. /sys/firmware/fdt
原始dtb文件
b. /sys/firmware/devicetree
目录结构dtb文件
c. /sys/devices/platform
设备树或传统注册的platform_device,设备树看是否存在of_node
d. /proc/device-tree
链接文件
参考:https://blog.csdn.net/thisway_diy/article/details/84336817