Linux设备驱动模型之platform总线深入浅出(加入设备树)
2017-06-25 17:03
417 查看
在Linux2.6以后的设备驱动模型中,需关心总线,设备和驱动这三种实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
对于依附在USB、PCI、I2C、SPI等物理总线来 这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。
相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。
platform总线相关代码
4000
:driver\base\platform.c 文件
相关结构体定义:include\linux\platform_device.h 文件中
platform总线管理下最重要的两个结构体是platform_device和platform_driver
分别表示设备和驱动
在Linux中的定义如下
一:platform_driver
二:platform_device
resource结构体也是描述platform_device的一个重要结构体 该元素存入了最为重要的设备资源信息
我们通常关心start,end,flags。它们分别标明了资源的开始值,结束值和类型,flags可以为IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等,start,end的含义会随着flags变更,如当flags为IORESOURCE_MEM时,start,end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为,IORESOURCE_IRQ时,start,end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,例如某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。
对resource的定义也通常在BSP的板文件中运行,而在具体的设备驱动中通过platform_get_resource()这样的API来获取,此API的原型为:
例如在\arch\arm\mach-at91\Board-sam9261ek.c板文件中为DM9000网卡定义了如下的resource
在DM9000网卡驱动中则是通过如下办法拿到这3份资源
对于irq而言platform_get_resource()还有一个进行了封装的变体platform_get_irq(),
api原型是
实际上这个函数也是调用platform_get_resource(dev, IORESOURCE_IRQ, num)来获取资源
设备除了可以在BSP中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断,内存等标准资源以外,可能还会有一些配置信息,这些配置信息也依赖于板,不适宜直接放置在设备驱动上,因此,platform也提供了platform_data的支持,例如对于dm9000
获取platform_data的API是dev_get_platdata()
三:platform总线
系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下
最重要的是match函数,真是此成员函数确定了platform_device和platform_driver之间如何匹配的
可知有3中情况下platform_device和platform_driver匹配
1.基于设备树风格的匹配
2.匹配ID表(即platform_device设备名是否出现在platform_driver的id表内)
3.匹配platform_device设备名和驱动的名字
在4.0还有一种情况是基于ACPI风格的匹配
对于设备驱动的开发
其设计顺序为定义 platform_device -> 注册 platform_device-> 定义 platform_driver-> 注册 platform_driver 。
这里选用的例子是q40kbd,在/drivers/input/serio目录里。这是一个键盘控制器驱动
这里直接分析初始化函数
驱动注册函数是 platform_driver_register()函数
为总线增加一个驱动
执行驱动加载
driver_probe_device() 这个函数实际上做了两件事
1.调用总线提供的match函数。如果检查通过 说明设备和驱动是匹配的 设备所指的驱动指针要赋值为当前驱动
2.探测(probe) 首先调用总线提供的probe函数,如果驱动有自己的Probe函数,还要调用驱动的probe函数
platform总线的match函数在上文有介绍 三种情况均可匹配
platform总线的probe函数就是platform_drv_probe() 这个函数是个封装函数 它只是简单的调用了驱动的Probe函数 驱动的Probe函数就是q40kbd_probe
综上所述,platform总线驱动注册就是遍历各个设备 检查是否和驱动匹配
接下来分析platform设备的注册
遍历驱动 为设备找到合适的驱动
但是在Linux加入了设备树的时候 就不再需要大量的板级信息了 譬如/arch/arm/plat-xxx和arch/arm/mach-xxx中platform的修改部分
原先的
之类的注册platform_device,绑定resource,即内存,irq等板级信息 现在都不再需要 其中platform_device会由内核自动展开。而这些resource实际来源于.dts中设备节点的reg,interrups属性
再者就是platform_data这些平台数据属性化
比如\linux-3.4.2\arch\arm\mach-at91\Board-sam9263ek.c下用如下代码注册gpio_keys设备 通过gpio_keys_platform_data来定义platform_data
设备驱动drivers/input/keyboard/gpio_keys.c通过以下方法取得这个platform_data
转移到设备树后 platform_data就不再arch/arm/mach-xxx中 它需要从设备树中获取,比如一个电路板上有gpio_keys 则只需要在设备树中添加类似arch/arm/boot/dts/exyons4210-origen.dts的代码
而在驱动中通过of_开头的读属性的API来读取这些信息 并组织处gpio_keys_platform_data结构体
对于依附在USB、PCI、I2C、SPI等物理总线来 这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。
相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。
platform总线相关代码
4000
:driver\base\platform.c 文件
相关结构体定义:include\linux\platform_device.h 文件中
platform总线管理下最重要的两个结构体是platform_device和platform_driver
分别表示设备和驱动
在Linux中的定义如下
一:platform_driver
//include\linux\platform_device.h 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 (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *);//恢复函数,在开机时被调用 struct device_driver driver;//设备驱动结构 };
struct device_driver { const char * name; struct bus_type * bus; struct kobject kobj; struct klist klist_devices; struct klist_node knode_bus; struct module * owner; const char * mod_name; /* used for built-in modules */ struct module_kobject * mkobj; 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); };
二:platform_device
struct platform_device { const char * name;//设备名称 u32 id;//取-1 struct device dev;//设备结构 u32 num_resources;// resource结构个数 struct resource * resource;//设备资源 };
resource结构体也是描述platform_device的一个重要结构体 该元素存入了最为重要的设备资源信息
struct resource { resource_size_t start; resource_size_t end; const char *name; unsigned long flags; struct resource *parent, *sibling, *child; };
我们通常关心start,end,flags。它们分别标明了资源的开始值,结束值和类型,flags可以为IORESOURCE_IO,IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA等,start,end的含义会随着flags变更,如当flags为IORESOURCE_MEM时,start,end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为,IORESOURCE_IRQ时,start,end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,例如某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。
对resource的定义也通常在BSP的板文件中运行,而在具体的设备驱动中通过platform_get_resource()这样的API来获取,此API的原型为:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type,unsigned int num)
例如在\arch\arm\mach-at91\Board-sam9261ek.c板文件中为DM9000网卡定义了如下的resource
static struct resource at91sam9261_dm9000_resource[] = { [0] = { .start = AT91_CHIPSELECT_2, .end = AT91_CHIPSELECT_2 + 3, .flags = IORESOURCE_MEM }, [1] = { .start = AT91_CHIPSELECT_2 + 0x44, .end = AT91_CHIPSELECT_2 + 0xFF, .flags = IORESOURCE_MEM }, [2] = { .start = AT91_PIN_PC11, .end = AT91_PIN_PC11, .flags = IORESOURCE_IRQ } };
在DM9000网卡驱动中则是通过如下办法拿到这3份资源
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
对于irq而言platform_get_resource()还有一个进行了封装的变体platform_get_irq(),
api原型是
int platform_get_irq(struct platform_device *dev, unsigned int num) { struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num); return r ? r->start : -ENXIO; }
实际上这个函数也是调用platform_get_resource(dev, IORESOURCE_IRQ, num)来获取资源
设备除了可以在BSP中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断,内存等标准资源以外,可能还会有一些配置信息,这些配置信息也依赖于板,不适宜直接放置在设备驱动上,因此,platform也提供了platform_data的支持,例如对于dm9000
static struct dm9000_plat_data dm9000_platdata = { .flags = DM9000_PLATF_16BITONLY, }; static struct platform_device at91sam9261_dm9000_device = { .name = "dm9000", .id = 0, .num_resources = ARRAY_SIZE(at91sam9261_dm9000_resource), .resource = at91sam9261_dm9000_resource, .dev = { .platform_data = &dm9000_platdata, } };
获取platform_data的API是dev_get_platdata()
struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);
三:platform总线
系统为platform总线定义了一个bus_type的实例platform_bus_type,其定义位于drivers/base/platform.c下
struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, };
最重要的是match函数,真是此成员函数确定了platform_device和platform_driver之间如何匹配的
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); /* Attempt an OF style match first */ if (of_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); }
可知有3中情况下platform_device和platform_driver匹配
1.基于设备树风格的匹配
2.匹配ID表(即platform_device设备名是否出现在platform_driver的id表内)
3.匹配platform_device设备名和驱动的名字
在4.0还有一种情况是基于ACPI风格的匹配
对于设备驱动的开发
其设计顺序为定义 platform_device -> 注册 platform_device-> 定义 platform_driver-> 注册 platform_driver 。
这里选用的例子是q40kbd,在/drivers/input/serio目录里。这是一个键盘控制器驱动
这里直接分析初始化函数
static int __init q40kbd_init(void) { int error; if (!MACH_IS_Q40) return -EIO; /* platform总线驱动的注册 */ error = platform_driver_register(&q40kbd_driver); if (error) return error; /* 分配一个platform设备 */ q40kbd_device = platform_device_alloc("q40kbd", -1); if (!q40kbd_device) goto err_unregister_driver; /* platform设备注册 */ error = platform_device_add(q40kbd_device); if (error) goto err_free_device; return 0; err_free_device: platform_device_put(q40kbd_device); err_unregister_driver: platform_driver_unregister(&q40kbd_driver); return error; }
驱动注册函数是 platform_driver_register()函数
int platform_driver_register(struct platform_driver *drv) { //把驱动的总线设置为platform总线 drv->driver.bus = &platform_bus_type; //依次设置驱动的各个函数指针 if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; if (drv->suspend) drv->driver.suspend = platform_drv_suspend; if (drv->resume) drv->driver.resume = platform_drv_resume; //调用driver_register注册驱动 return driver_register(&drv->driver); }
/* 初始化后调用bus_add_driver函数执行注册过程 */ int driver_register(struct device_driver * drv) { if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) { printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name); } klist_init(&drv->klist_devices, NULL, NULL); return bus_add_driver(drv); }
为总线增加一个驱动
int bus_add_driver(struct device_driver *drv) { struct bus_type * bus = get_bus(drv->bus); int error = 0; if (!bus) return -EINVAL; pr_debug("bus %s: add driver %s\n", bus->name, drv->name); /* 为kobject结构设置名字 */ error = kobject_set_name(&drv->kobj, "%s", drv->name); if (error) goto out_put_bus; drv->kobj.kset = &bus->drivers; /* 为sysfs文件系统创建设备的相关文件 */ if ((error = kobject_register(&drv->kobj))) goto out_put_bus; if (drv->bus->drivers_autoprobe) { /* 驱动加入总线的驱动列表 */ error = driver_attach(drv); if (error) goto out_unregister; } /* 在sysfs创建驱动的属性文件和模块 */ klist_add_tail(&drv->knode_bus, &bus->klist_drivers); module_add_driver(drv->owner, drv); error = driver_add_attrs(bus, drv); if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n", __FUNCTION__, drv->name); } error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed\n", __FUNCTION__, drv->name); } return error; out_unregister: kobject_unregister(&drv->kobj); out_put_bus: put_bus(bus); return error; }
执行驱动加载
int driver_attach(struct device_driver * drv) { return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *)) { struct klist_iter i; struct device * dev; int error = 0; if (!bus) return -EINVAL; /* 初始化一个klist_iter结构 目的是在双向链表中定位一个成员 */ klist_iter_init_node(&bus->klist_devices, &i, (start ? &start->knode_bus : NULL)); while ((dev = next_device(&i)) && !error) //对每个设备都调用fn函数指针 fn就是传入的函数指针__driver_attach error = fn(dev, data); klist_iter_exit(&i); return error; }
static int __driver_attach(struct device * dev, void * data) { struct device_driver * drv = data; /* * Lock device and try to bind to it. We drop the error * here and always return 0, because we need to keep trying * to bind to devices and some drivers will return an error * simply if it didn't support the device. * * driver_probe_device() will spit a warning if there * is an error. */ if (dev->parent) /* Needed for USB */ down(&dev->parent->sem); /* 获取设备的锁 */ down(&dev->sem); if (!dev->driver) driver_probe_device(drv, dev); up(&dev->sem); if (dev->parent) up(&dev->parent->sem); return 0; }
int driver_probe_device(struct device_driver * drv, struct device * dev) { int ret = 0; if (!device_is_registered(dev)) return -ENODEV; /* 调用总线配置的match函数 */ if (drv->bus->match && !drv->bus->match(dev, drv)) goto done; pr_debug("%s: Matched Device %s with Driver %s\n", drv->bus->name, dev->bus_id, drv->name); /* 调用really_probe函数 */ ret = really_probe(dev, drv); done: return ret; } static int really_probe(struct device *dev, struct device_driver *drv) { int ret = 0; atomic_inc(&probe_count); pr_debug("%s: Probing driver %s with device %s\n", drv->bus->name, drv->name, dev->bus_id); WARN_ON(!list_empty(&dev->devres_head)); dev->driver = drv; if (driver_sysfs_add(dev)) { printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", __FUNCTION__, dev->bus_id); goto probe_failed; } /* 调用总线的probe函数 */ if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) goto probe_failed; /* 如果驱动提供了Probe函数,则调用驱动的probe函数 */ } else if (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; } /* 设备发现了驱动 通过sysfs创建一些文件 和设备做符号链接 */ driver_bound(dev); ret = 1; pr_debug("%s: Bound Device %s to Driver %s\n", drv->bus->name, dev->bus_id, drv->name); goto done; probe_failed: devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; if (ret != -ENODEV && ret != -ENXIO) { /* driver matched but the probe failed */ printk(KERN_WARNING "%s: probe of %s failed with error %d\n", drv->name, dev->bus_id, ret); } /* * Ignore errors returned by ->probe so that the next driver can try * its luck. */ ret = 0; done: atomic_dec(&probe_count); wake_up(&probe_waitqueue); return ret; }
driver_probe_device() 这个函数实际上做了两件事
1.调用总线提供的match函数。如果检查通过 说明设备和驱动是匹配的 设备所指的驱动指针要赋值为当前驱动
2.探测(probe) 首先调用总线提供的probe函数,如果驱动有自己的Probe函数,还要调用驱动的probe函数
platform总线的match函数在上文有介绍 三种情况均可匹配
platform总线的probe函数就是platform_drv_probe() 这个函数是个封装函数 它只是简单的调用了驱动的Probe函数 驱动的Probe函数就是q40kbd_probe
static int __devinit q40kbd_probe(struct platform_device *dev) { q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL); if (!q40kbd_port) return -ENOMEM; q40kbd_port->id.type = SERIO_8042; q40kbd_port->open = q40kbd_open; q40kbd_port->close = q40kbd_close; q40kbd_port->dev.parent = &dev->dev; strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name)); strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys)); //注册serio结构变量 serio_register_port(q40kbd_port); printk(KERN_INFO "serio: Q40 kbd registered\n"); return 0; }
综上所述,platform总线驱动注册就是遍历各个设备 检查是否和驱动匹配
接下来分析platform设备的注册
int platform_device_add(struct platform_device *pdev) { int i, ret = 0; if (!pdev) return -EINVAL; /* 设置设备的父类型 */ if (!pdev->dev.parent) pdev->dev.parent = &platform_bus; /* 设置设备的总线类型为platform_bus_type */ pdev->dev.bus = &platform_bus_type; if (pdev->id != -1) snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id); else strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE); /* 把设备IO端口和IO内存资源注册到系统 */ for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; if (r->name == NULL) r->name = pdev->dev.bus_id; p = r->parent; if (!p) { if (r->flags & IORESOURCE_MEM) p = &iomem_resource; else if (r->flags & IORESOURCE_IO) p = &ioport_resource; } if (p && insert_resource(p, r)) { printk(KERN_ERR "%s: failed to claim resource %d\n", pdev->dev.bus_id, i); ret = -EBUSY; goto failed; } } pr_debug("Registering platform device '%s'. Parent at %s\n", pdev->dev.bus_id, pdev->dev.parent->bus_id); ret = device_add(&pdev->dev); if (ret == 0) return ret; failed: while (--i >= 0) if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO)) release_resource(&pdev->resource[i]); return ret; }
int device_add(struct device *dev) { struct device *parent = NULL; char *class_name = NULL; struct class_interface *class_intf; int error = -EINVAL; dev = get_device(dev); if (!dev || !strlen(dev->bus_id)) goto Error; pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id); /* 获得父设备 */ parent = get_device(dev->parent); error = setup_parent(dev, parent); if (error) goto Error; /* first, register with generic layer. */ /* 这是kobject的名字 */ kobject_set_name(&dev->kobj, "%s", dev->bus_id); /* 在sys目录生成设备目录 */ error = kobject_add(&dev->kobj); if (error) goto Error; /* notify platform of device entry */ if (platform_notify) platform_notify(dev); /* notify clients of device entry (new way) */ if (dev->bus) blocking_notifier_call_chain(&dev->bus->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev); /* 设置uevent属性 */ dev->uevent_attr.attr.name = "uevent"; dev->uevent_attr.attr.mode = S_IRUGO | S_IWUSR; if (dev->driver) dev->uevent_attr.attr.owner = dev->driver->owner; dev->uevent_attr.store = store_uevent; dev->uevent_attr.show = show_uevent; error = device_create_file(dev, &dev->uevent_attr); if (error) goto attrError; /* 设备的属性文件 */ if (MAJOR(dev->devt)) { struct device_attribute *attr; attr = kzalloc(sizeof(*attr), GFP_KERNEL); if (!attr) { error = -ENOMEM; goto ueventattrError; } attr->attr.name = "dev"; attr->attr.mode = S_IRUGO; if (dev->driver) attr->attr.owner = dev->driver->owner; attr->show = show_dev; error = device_create_file(dev, attr); if (error) { kfree(attr); goto ueventattrError; } dev->devt_attr = attr; } /* 创建设备类的符号链接 */ if (dev->class) { sysfs_create_link(&dev->kobj, &dev->class->subsys.kobj, "subsystem"); /* If this is not a "fake" compatible device, then create the * symlink from the class to the device. */ if (dev->kobj.parent != &dev->class->subsys.kobj) sysfs_create_link(&dev->class->subsys.kobj, &dev->kobj, dev->bus_id); if (parent) { sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device"); #ifdef CONFIG_SYSFS_DEPRECATED class_name = make_class_name(dev->class->name, &dev->kobj); if (class_name) sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name); #endif } } /* 设备的能源管理 */ if ((error = device_add_attrs(dev))) goto AttrsError; if ((error = device_pm_add(dev))) goto PMError; if ((error = bus_add_device(dev))) goto BusError; kobject_uevent(&dev->kobj, KOBJ_ADD); bus_attach_device(dev); /* 设备加入父设备的链表 */ if (parent) klist_add_tail(&dev->knode_parent, &parent->klist_children); if (dev->class) { down(&dev->class->sem); /* tie the class to the device */ list_add_tail(&dev->node, &dev->class->devices); /* notify any interfaces that the device is here */ list_for_each_entry(class_intf, &dev->class->interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev, class_intf); up(&dev->class->sem); } Done: kfree(class_name); put_device(dev); return error; BusError: device_pm_remove(dev); PMError: if (dev->bus) blocking_notifier_call_chain(&dev->bus->bus_notifier, BUS_NOTIFY_DEL_DEVICE, dev); device_remove_attrs(dev); AttrsError: if (dev->devt_attr) { device_remove_file(dev, dev->devt_attr); kfree(dev->devt_attr); } if (dev->class) { sysfs_remove_link(&dev->kobj, "subsystem"); /* If this is not a "fake" compatible device, remove the * symlink from the class to the device. */ if (dev->kobj.parent != &dev->class->subsys.kobj) sysfs_remove_link(&dev->class->subsys.kobj, dev->bus_id); if (parent) { #ifdef CONFIG_SYSFS_DEPRECATED char *class_name = make_class_name(dev->class->name, &dev->kobj); if (class_name) sysfs_remove_link(&dev->parent->kobj, class_name); kfree(class_name); #endif sysfs_remove_link(&dev->kobj, "device"); } } ueventattrError: device_remove_file(dev, &dev->uevent_attr); attrError: kobject_uevent(&dev->kobj, KOBJ_REMOVE); kobject_del(&dev->kobj); Error: if (parent) put_device(parent); goto Done; }
遍历驱动 为设备找到合适的驱动
int device_attach(struct device * dev) { int ret = 0; down(&dev->sem); if (dev->driver) { ret = device_bind_driver(dev); if (ret == 0) ret = 1; else { dev->driver = NULL; ret = 0; } } else { ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); } up(&dev->sem); return ret; }
但是在Linux加入了设备树的时候 就不再需要大量的板级信息了 譬如/arch/arm/plat-xxx和arch/arm/mach-xxx中platform的修改部分
原先的
static struct resource xxx_resource[] = { [0] = { .start = ..., .end = ..., .flags = ..., }, [1] = { .start = ..., .end = ..., .flags = ..., }, [2] = { .start =..., .end = ..., .flags = ..., } }; static struct platform_device xxx_device = { .name = "xxx", .id = -1, .num_resources = ARRAY_SIZE(xxx_resource), .resource = xxx_resource, .dev = { .platform_data = &xxx_platdata, } };
之类的注册platform_device,绑定resource,即内存,irq等板级信息 现在都不再需要 其中platform_device会由内核自动展开。而这些resource实际来源于.dts中设备节点的reg,interrups属性
再者就是platform_data这些平台数据属性化
比如\linux-3.4.2\arch\arm\mach-at91\Board-sam9263ek.c下用如下代码注册gpio_keys设备 通过gpio_keys_platform_data来定义platform_data
static struct gpio_keys_button ek_buttons[] = { { /* BP1, "leftclic" */ .code = BTN_LEFT, .gpio = AT91_PIN_PC5, .active_low = 1, .desc = "left_click", .wakeup = 1, }, { /* BP2, "rightclic" */ .code = BTN_RIGHT, .gpio = AT91_PIN_PC4, .active_low = 1, .desc = "right_click", .wakeup = 1, } }; static struct gpio_keys_platform_data ek_button_data = { .buttons = ek_buttons, .nbuttons = ARRAY_SIZE(ek_buttons), }; static struct platform_device ek_button_device = { .name = "gpio-keys", .id = -1, .num_resources = 0, .dev = { .platform_data = &ek_button_data, } };
设备驱动drivers/input/keyboard/gpio_keys.c通过以下方法取得这个platform_data
struct device *dev = &pdev->dev; const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
转移到设备树后 platform_data就不再arch/arm/mach-xxx中 它需要从设备树中获取,比如一个电路板上有gpio_keys 则只需要在设备树中添加类似arch/arm/boot/dts/exyons4210-origen.dts的代码
gpio_keys { compatible = "gpio-keys"; #address-cells = <1>; #size-cells = <0>; up { label = "Up"; gpios = <&gpx2 0 0 0 2>; linux,code = <103>; }; down { label = "Down"; gpios = <&gpx2 1 0 0 2>; linux,code = <108>; };
而在驱动中通过of_开头的读属性的API来读取这些信息 并组织处gpio_keys_platform_data结构体
gpio_keys_get_devtree_pdata(struct device *dev) { struct gpio_keys_platform_data *pdata; struct gpio_keys_button *button; struct fwnode_handle *child; int nbuttons; nbuttons = device_get_child_node_count(dev); if (nbuttons == 0) return ERR_PTR(-ENODEV); pdata = devm_kzalloc(dev, sizeof(*pdata) + nbuttons * sizeof(*button), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); button = (struct gpio_keys_button *)(pdata + 1); pdata->buttons = button; pdata->nbuttons = nbuttons; pdata->rep = device_property_read_bool(dev, "autorepeat"); device_property_read_string(dev, "label", &pdata->name); device_for_each_child_node(dev, child) { if (is_of_node(child)) button->irq = irq_of_parse_and_map(to_of_node(child), 0); if (fwnode_property_read_u32(child, "linux,code", &button->code)) { dev_err(dev, "Button without keycode\n"); fwnode_handle_put(child); return ERR_PTR(-EINVAL); } fwnode_property_read_string(child, "label", &button->desc); if (fwnode_property_read_u32(child, "linux,input-type", &button->type)) button->type = EV_KEY; button->wakeup = fwnode_property_read_bool(child, "wakeup-source") || /* legacy name */ fwnode_property_read_bool(child, "gpio-key,wakeup"); button->can_disable = fwnode_property_read_bool(child, "linux,can-disable"); if (fwnode_property_read_u32(child, "debounce-interval", &button->debounce_interval)) button->debounce_interval = 5; button++; } return pdata; }
相关文章推荐
- Linux设备驱动模型之platform总线
- linux设备驱动模型 - platform总线
- Linux设备驱动模型之platform(平台)总线详解
- Linux设备驱动模型之platform总线
- Linux设备驱动模型之platform总线
- LINUX设备驱动模型之PLATFORM(平台)总线详解
- Linux设备驱动总线模型简介
- linux设备模型之platform总线
- linux设备总线驱动模型 之 platform总线驱动
- linux驱动模型开发——linux platform总线机制讲解与实例开发
- platform总线和普通总线,驱动模型间的一些关系和理解
- Linux设备模型之platform总线
- linux设备驱动模型之 bus(总线)原理与实例分析
- linux设备驱动之platform平台总线工作原理(二)
- linux设备总线驱动模型 之 platform总线驱动
- LINUX设备驱动之platform总线
- linux驱动模型开发——linux platform总线机制讲解与实例开发
- linux设备模型之platform总线(转)
- linux设备驱动模型之总线、设备、驱动三者的关系
- linux设备模型之platform总线