前言
在Linux内核驱动开发中,通常会以设备的probe()函数为入口,而在这入口函数,经常会遇到平台资源的情况,导致阅读者不清楚它的来历,本文目的即为分析平台设备的平台资源的来龙去脉。
一、Linux platform 数据结构
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 dma_mask;
u32 num_resources;
struct resource *resource;
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 of_dev_auxdata {
char *compatible;
resource_size_t phys_addr;
char *name;
void *platform_data;
}
二、驱动示例
1. 设备树示例
/ {
compatible = "chipname";
device_father : device-father {
compatible = "father";
info = "this is father info";
device_child : device-child {
compatible = "child";
}
};
}
2. 设备树入口
对于每一个平台,通常都会定义一个这样的设备树根节点的入口,他会把符合platform device的节点,添加到platform总线中
#define DT_MACHINE_START(_type, _namestr) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__ ((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _namestr, \
#define MACHINE_END \
};
DT_MACHINE_START(EXAMPLE_DT, "Board example, Inc.")
.init_machine = board_init,
.dt_compat = board_dt_match,
MACHINE_END
static void __init board_init(void)
{
board_dt_populate(NULL);
}
static const char *board_dt_mach[] __initconst = {
"chipname",
NULL
};
3. 父设备驱动入口
这个父设备是直接挂载在根节点之下的,因此会被识别为platform_device,并添加到了总线之中,当父设备驱动也被加载到platform总线之中时,会进行兼容性匹配,匹配成功便会执行设备的probe函数
static int father_probe(struct platform_device *pdev)
{
char *father_info = NULL;
struct device_node *child_node = NULL;
struct platform_device *child_device = NULL;
of_property_read_string(pdev->dev.of_node, "info", &father_info);
child_node = of_parse_phandle(pdev->dev.of_node, "device-child", 0);
child_device = of_platform_device_create(child_node, NULL, &pdev->dev);
child_device->dev.platform_data = father_info;
}
static const struct of_device_id father_dt_match[] = {
{.compatible = "father"},
{}
}
static struct platform_driver father_driver = {
.probe = father_probe,
.driver = {
.of_match_table = father_dt_match;
}
}
static int __init father_driver_init(void)
{
platform_driver_register(father_driver);
}
module_init(father_driver_init);
4. 子设备驱动
这个子设备节点,并不符合platform设备的条件,因此不会被自动添加到platform总线之中。
从父设备的probe函数中我们可以看到,他主动找到了子设备的节点,并用它创建了一个platform设备,然后将其添加到platform总线中。最后把自身的信息作为平台信息添加到子设备platform_data中,此时父设备就作为了子设备的平台使用了。
static int child_probe(struct platform_device *pdev)
{
struct father_info *pdata;
pdata = dev_get_platdata(&pdev->dev);
printk("%s, platform_data : %s", __func__, pdata);
}
static const struct of_device_id child_dt_match[] = {
{.compatible = "child"},
{}
}
static struct platform_driver child_driver = {
.probe = child_probe,
.driver = {
.of_match_table = child_dt_match;
}
}
static int __init child_driver_init(void)
{
platform_driver_register(child_driver);
}
module_init(child_driver_init);
子设备驱动在加载时,匹配到父设备中添加的子设备节点,便会进行子设备的probe,这里访问了从父设备那里获取而来的platform_data。到此我们便清楚platform_data的来历了
总结
并非所有的设备树中的device_node都会自动变成device,除了常规的三大总线(platform, i2c, spi)的节点,也可能存在不会自动加载的节点。这些节点我们可以在驱动中自行进行添加,并做灵活的处理。