1、linux中的总线-驱动-设备模型。
总线代码由linux内核提供,只需要编写设备和驱动。当向总线注册驱动时总线会从现有的所有设备中查找和此驱动匹配的设备,当向总线注册设备时总线也会在现有的驱动中查找是否有与其匹配的驱动。
1.1 总线
数据类型为bus_type,使用bus_register向内核注册总线,主要就是完成设备与驱动的匹配。注册的总线种类在/sys/bus/下可以看到,如:
/sys/bus$ ls
ac97 event_source memory pci sdio vme
acpi gpio mipi-dsi pci-epf serial workqueue
clockevents hid mmc pci_express serio xen
clocksource i2c nd platform snd_seq xen-backend
container isa node pnp spi
cpu machinecheck nvmem rapidio usb
edac mdio_bus parport scsi virtio
match:当当一个新的设备或驱动注册到总线时,都会调用match进行设备和驱动的匹配。
probe:当match匹配成功就会调用驱动的probe接口。虽然传参是device,实际调用device->driver->probe。
struct bus_type {
const char *name; // bus名称如"mdio_bus"
const char *dev_name;
struct device *dev_root;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);//当一个新的设备/驱动注册到总线时,都会调用match
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
bool need_parent_lock;
};
1.2. 驱动
数据类型为device_driver,使用driver_register向内核注册驱动。驱动跟设备匹配以后驱动里面的 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 */
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;
};
1.3 设备device
会保存bus、driver数据结构,of_node是设备对应的设备树节点。
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
...
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
};
2、为什么出现platform总线
usb总线可以下挂多个相关设备,说明usb总线是管理这类设备的主控制器。当注册usb总线后,usb总线就可以完成usb设备的管理和共用底层驱动框架。(mdio总线用来管理phy设备)但有些外设并没有总线概念又想使用总线驱动设备模型,就出现了platform虚拟总线。platform总线下面可以挂载不同类型设备,usb总线下面挂载的是同一种类型设备。
3、platform平台驱动模型
3.1 platform总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, /*负责设备和驱动匹配*/
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
3.2 platform设备
可以说platform_device继承了device,并添加了一些属性。
struct platform_device {
const char *name;
int id;//由厂商提供
bool id_auto;
struct device dev;
u64 platform_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;
};
//其中resource定义如下:表示设备使用的资源如mem、irq等
struct resource
{
resource_size_t start;// 资源起始地址
resource_size_t end;//资源结束地址
const char *name;
/* 表示 resource类型如IORESOURCE_MEM、IORESOURCE_IRQ */
unsigned long flags;
struct resource *parent, *sibling, *child;
};
3.3 platform驱动:platform_driver继承device_driver。
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;
};
4、platform总线注册
注册一个表示设备链表的root节点platform_bus,注册总线platform_bus_type。
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
5、platform_driver注册
__platform_driver_register
driver_register
->bus_add_driver // add a driver to a bus;
-> driver_attach // 查找bus下所有设备,找与其匹配的
-> bus_for_each_dev(drv->bus,NULL,drv,__driver_attach)
-> __driver_attach // 检测每一个设备
-> driver_match_device // 每个设备都调用此函数,判断是否与当前驱动匹配,执行dev->bus->match
->driver_probe_device
->really_probe
-> drv->probe //驱动与设备匹配时,执行driver->probe
6、platform_device注册流程
6.1 没有设备树时需要自己实现代码注册设备信息,调用platform_device_register。但这种方式应该很少见了。
platform_device_add
->device_add
->bus_add_device
->bus_probe_device
->device_initial_probe
->__device_attach(dd.c)
->ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
->__device_attach_driver//实际查找bus下的每一个驱动执行__device_attach_driver
->driver_match_device(drv, dev) // 判断每一个驱动设备是否匹配,实际调用drv->bus->match
->platform_match
->driver_probe_device
->really_probe
->若bus注册了probe则执行,否则执行driver的probe接口
6.2 使用设备树时,设备树是如何转换为platform_device?
struct platform_driver
中.probe
方法。1、/proc/device-tree
linux内核在启动时会解析设备树中的各个节点,根据节点名字在/proc/device-tree目录下创建不同的文件夹或文件。
/proc/device-tree就是设备树在根文件系统的体现。
dts根节点的属性表现为一个个文件;如compatible。
根节点的子节点表现为文件夹;如cpus。
root@(mt7981):/sys/firmware/devicetree/base# ls
#address-cells psci
#size-cells pwm@10048000
adc@1100d000 regulator-3p3v
ap2woccif@151A5000 reserved-memory
apmixedsys@1001E000 serial@11002000
audio-controller@11210000 serial@11003000
chosen serial@11004000
clkitg snfi@11005000
compatible spi@11009000
consys@10000000 spi@1100a000
cpus spi@1100b000
crypto@10320000 syscon@10060000
dummy_gpt_clk syscon@10070000
dummy_system_clk syscon@15000000
efuse@11f20000 thermal-zones
ethernet@15100000 thermal@1100c800
gsw@0 timer
hnat@15000000 topckgen@1001B000
i2c@11007000 topmisc@11d10000
ice_debug trng@1020f000
infracfg@10001040 usb-phy@11e10000
infracfg_ao@10001000 watchdog@1001c000
interrupt-controller@c000000 wbsys@18000000
interrupt-parent wdma@15104800
memory wed@15010000
mmc@11230000 wed_pcie@10003000
model wocpu0_ilm@151E0000
name wocpu_boot@15194000
oscillator@0 wocpu_dlm@151E8000
pcie@11280000 xhci@11200000
pinctrl@11d00000 yt-8521sc
2、chosen子节点
chosen 节点主要是为了uboot 向linux 内核传递数据,重点是bootargs 参数。如mt7981的chosen节点为:
chosen {
bootargs = "console=ttyS0,115200n1 loglevel=8 \
earlycon=uart8250,mmio32,0x11002000";
};
uboot 在启动Linux 内核的时候会将bootargs 的值传递给Linux 内核,bootargs 会作为Linux 内核的命令行参数,Linux 内核启动的时候会打印出命令行参数。比如:
[ 0.000000] Kernel command line: console=ttyS0,115200n8 loglevel=8 swiotlb=512 root=/dev/mtdblock6 rootfstype=squashfs,jffs2 noinitrd earlycon=uart8250,mmio32,0x11002000
在启动Linux 内核之前,只有uboot 知道bootargs 环境变量的值,并且uboot 也知道.dtb 设备树文件在DRAM 中的位置。
uboot下对chosen节点的处理(fdt_support.c):
int fdt_chosen(void *fdt)
{
int nodeoffset;
int err;
char *str; /* used to set string properties */
err = fdt_check_header(fdt);
if (err < 0) {
printf("fdt_chosen: %s\n", fdt_strerror(err));
return err;
}
/* 从设备树(.dtb) 中找到chosen 节点,如果没有找到的话就会子创建一个chosen 节点。 */
nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
if (nodeoffset < 0)
return nodeoffset;
str = board_fdt_chosen_bootargs();
if (str) {
/* 向chosen 节点添加bootargs 属性,并且bootargs 属性的值就是环境变量bootargs 的内容。 */
err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
strlen(str) + 1);
if (err < 0) {
printf("WARNING: could not set bootargs %s.\n",
fdt_strerror(err));
return err;
}
}
return fdt_fixup_stdout(fdt, nodeoffset);
}
3、内核解析设备树文件
Linux 内核在启动的时候会解析DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树根节点文件。Linux 内核解析DTB 文件的流程如下:
unflatten_device_tree()调用__unflatten_device_tree()继续解析dts文件,并将数据保存到struct device_node结构中。每个dts节点对应一个device_node,父子关系通过device_node的指针来关联。最后将device_node链表赋给of_root,即of_root代表所有的device_node的root,通过它,可以遍历所有的device_node。
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
...
/* 第一轮解析设备树为了计算保存device_node树结构所需内存大小 */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size < 0)
return NULL;
size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p...\n", mem);
/* 第二轮真正解析设备树,从mem中为设备节点分配内存并填充属性 */
unflatten_dt_nodes(blob, mem, dad, mynodes);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
if (detached && mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
pr_debug(" <- unflatten_device_tree()\n");
return mem;
}
至此内核完成dts文件的解析,并将各个设备节点以树结构链接在一起(父子节点之间、兄弟节点之间均使用双向链表),root节点为of_root全局变量。解析后的device_node如何变成platform_device,并注册到platform_bus_type的klist_devices链表中?
4、device_node转换为platform_device
从start_kernel开始是找不到platform device这一部分转换的源头的,这个转换过程的函数是of_platform_default_populate_init(),它被调用的方式是一个声明:arch_initcall_sync(of_platform_default_populate_init);
// 数字表示优先级,数字越小优先级越低, 宏声明一个新的函数就是将这个函数指针放入内存中一个指定的段内
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
// linux内核启动时会依次调用这些段中的函数
start_kernel
arch_call_rest_init
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函数
device_node=>platform_device过程:
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 */
};
of_platform_default_populate_init
of_platform_default_populate
of_platform_populate(root, of_default_bus_match_table, lookup, parent);
of_platform_bus_create
of_platform_device_create_pdata
4.1 of_platform_populate(root, of_default_bus_match_table, lookup, parent);
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) { //遍历所有子节点
rc = of_platform_bus_create(child, matches, lookup, parent, true); //根据child创建对应的platform_device
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
4.2 of_platform_bus_create:根据device_node创建platform_device,并递归遍历子节点
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;
/* 若没有compatible属性则退出 */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %pOF, no compatible prop\n",
__func__, bus);
return 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;
}
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
pr_debug(" create child: %pOF\n", child);
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;
}
4.3 of_platform_device_create_pdata:为platform_device分配内存并进行初始化。调用of_device_add->device_add->之后的过程与无设备树时过程一样。
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;
dev = of_device_alloc(np, bus_id, parent); //为resource分配内存,初始化一些属性
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) { //调用of_device_add将platform_device注册进platform总线
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
5、哪些设备树节点可以转换为platform_device
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 */
};
1、 该节点必须含有compatible属性。
2、 根节点的子节点(节点必须含有compatible属性)。
3、 父节点compatible属性能匹配到of_default_bus_match_table。则其子节点(子节点必须含有compatible属性)可以生成platform_device。
4、根节点是例外的,即使有compatible属性也不会生成platfrom_device。
6、platform_device和platform_driver匹配规则?
of_driver_match_device: 设备树采用的匹配方式。在注册驱动时指定了of_match_table,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否能匹配上,设备和驱动匹配成功以后 probe 函数就会执行。如yt8521:
static const struct of_device_id yt_8521sc_match[] =
{
{ .compatible = "qualcomm,yt-8521sc-phy" },
{},
};
第二种匹配方式,ACPI 匹配方式。
id_table 匹配:每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platform驱动所支持的驱动类型。
比较name:直接比较驱动和设备的 name 字段,如果相等的话就匹配成功。
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);
}