linux设备驱动之platform总线驱动学习
2017-08-11 21:29
615 查看
一.Platform总线介绍
linux内核中常见的的总线有I2C总线,PCI总线,串口总线,SPI总线,PCI总线,CAN总线,单总线等,所以有些设备和驱动就可以挂在这些总线上,然后通过总线上的match进行设备和驱动的匹配。但是有的设备并不属于这些常见总线,所以我们引入了一种虚拟总线,也就是platform总线的概念,对应的设备叫做platform设备,对应的驱动叫做platform驱动。
1.platform总线
由于platform总线已经在内核中注册定义好了,首先系统启动的时候会先调用platform_bus_init来初始化这个虚拟总线注册设备
[weiming@Huangweiming linux-3.0]$ vim drivers/base/platform.c
platform总线 总线结构
需要重点关注的是总线的匹配函数match()
通过match这个函数,当属于platform的设备或者驱动注册到内核时就会调用,完成设备与驱动的匹配工作。
2.platform 设备
(1)platform_deviece结构
[weiming@Huangweiming linux-3.0]$ vim linux/platform_device.h
[weiming@Huangweiming linux-3.0]$ vim include/linux/ioport.h
(2)设备注册函数 platform_device_register
函数首先初始化了platform_device的device结构,然后调用platform_device_add函数
3. Platform设备驱动
(1)基本数据机构platform_driver
[weiming@Huangweiming linux-3.0]$ vim include/linux/platform_device.h
(2)注册函数platform_driver_register
[weiming@Huangweiming linux-3.0]$ vim drivers/base/platform.c
二.总线、设备、驱动的注册顺序
设备挂接到总线上时,与总线上的所有驱动进行匹配(用bus_type.match进行匹配),如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上;如果匹配失败,则只是将该设备挂接到总线上。
驱动挂接到总线上时,与总线上的所有设备进行匹配(用bus_type.match进行匹配),如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上;如果匹配失败,则只是将该驱动挂接到总线上。
1. platform bus先被kenrel注册。
do_basic_setup() -->-driver_init() -->-platform_bus_init()-->bus_register()
2. 系统初始化过程中调用platform_add_devices或者platform_device_register,将平台设备(platform devices)注册到平台总线中(platform_bus_type)
3. 平台驱动(platform driver)与平台设备(platform device)的关联是在platform_driver_register或者driver_register中实现,一般这个函数在驱动的初始化过程调用。
通过这三步,就将平台总线,设备,驱动关联起来。
参考博客:
platform_device_add()函数分析 点击打开链接
platform总线注册过程及platform_driver与platform_device的匹配 点击打开链接
linux内核中常见的的总线有I2C总线,PCI总线,串口总线,SPI总线,PCI总线,CAN总线,单总线等,所以有些设备和驱动就可以挂在这些总线上,然后通过总线上的match进行设备和驱动的匹配。但是有的设备并不属于这些常见总线,所以我们引入了一种虚拟总线,也就是platform总线的概念,对应的设备叫做platform设备,对应的驱动叫做platform驱动。
1.platform总线
由于platform总线已经在内核中注册定义好了,首先系统启动的时候会先调用platform_bus_init来初始化这个虚拟总线注册设备
[weiming@Huangweiming linux-3.0]$ vim drivers/base/platform.c
int __init platform_bus_init(void) { int error; early_platform_cleanup(); error = device_register(&platform_bus); //总线也是设备,所以也要进行设备的注册 if (error) return error; error = bus_register(&platform_bus_type); //注册platform_bus_type总线到内核 if (error) device_unregister(&platform_bus); return error; }
platform总线 总线结构
struct bus_type platform_bus_type = { .name = "platform", //名 .dev_attrs = platform_dev_attrs, //属性 .match = platform_match, //永远记住总线的match函数才是最终的设备与驱动的匹配函数,成功后会调用驱动的probe函数 .uevent = platform_uevent, //卸载处理 .pm = &platform_dev_pm_ops, //电源管理 };
需要重点关注的是总线的匹配函数match()
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); }这个函数将device结构转换为platform_devcie结构,将device_driver结构转换为platform_driver结构,并调用platform_match_id对设备与驱动相关信息进行比较。如果没有比较成功会返回0,以便进行下一个设备的比较,如果比较成功就会返回1,并且将device结构中的driver指针指向这个驱动。然后调用device_driver中的probe函数,在lcd驱动中就是s3c_led_probe。这个函数是我们要编写的函数。这个函数检测驱动的状态,并且测试能否真正驱动设备,并且做一些初始化工作。
通过match这个函数,当属于platform的设备或者驱动注册到内核时就会调用,完成设备与驱动的匹配工作。
2.platform 设备
(1)platform_deviece结构
[weiming@Huangweiming linux-3.0]$ vim linux/platform_device.h
struct platform_device { const char * name; //名 int id; struct device dev; //内嵌设备 u32 num_resources; //资源个数 struct resource * resource; //资源结构体 const struct platform_device_id *id_entry; /* MFD cell pointer */ struct mfd_cell *mfd_cell; /* arch specific additions */ struct pdev_archdata archdata; };重点看看platform_device中资源结构体的定义
[weiming@Huangweiming linux-3.0]$ vim include/linux/ioport.h
struct resource { resource_size_t start; //起始地址 resource_size_t end; //结束地址 const char *name; //名 unsigned long flags; //标号 struct resource *parent, *sibling, *child; };flags标号可以有IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA四种选择,重点是申请内存(IORESOURCE_MEM)和申请中断号(IORESOURCE_IRQ)用的比较多。
(2)设备注册函数 platform_device_register
/** * platform_device_register - add a platform-level device * @pdev: platform device we're adding */ int platform_device_register(struct platform_device *pdev) { device_initialize(&pdev->dev); return platform_device_add(pdev); }
函数首先初始化了platform_device的device结构,然后调用platform_device_add函数
/** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding * * This is part 2 of platform_device_register(), though may be called * separately _iff_ pdev was allocated by platform_device_alloc(). */ int platform_device_add(struct platform_device *pdev) { int i, ret = 0; if (!pdev) return -EINVAL; if (!pdev->dev.parent) //总线有两个链表,一个是设备链表(通过device 内嵌)一个是驱动链表(通过device_driver内嵌)这里如果pdev->dev.parent为0,说明设备链表还没有设备,因此处理办法是将platform_bus作为设备链表的开始 pdev->dev.parent = &platform_bus; //可以看出,platform设备的父设备一般都是platform_bus,所以注册后的platform设备都出现在/sys/devices/platform_bus下 pdev->dev.bus = &platform_bus_type;//device 要挂接在platform_bus_type这个总线上拉,看到了,设备和总线是这么勾搭上滴,很直接,很干脆 if (pdev->id != -1) dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); else dev_set_name(&pdev->dev, "%s", pdev->name); for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; if (r->name == NULL) r->name = dev_name(&pdev->dev); p = r->parent; if (!p) { if (resource_type(r) == IORESOURCE_MEM) p = &iomem_resource; else if (resource_type(r) == IORESOURCE_IO) p = &ioport_resource; } if (p && insert_resource(p, r)) { printk(KERN_ERR "%s: failed to claim resource %d\n", dev_name(&pdev->dev), i); ret = -EBUSY; goto failed; } } //上面主要是遍历设备所占用的资源,找到对应的父资源,如果没有定义,那么根据资源的类型,分别赋予iomem_resource和ioport_resource,然后调用insert_resource插入资源。 //这样系统的资源就形成了一个树形的数据结构,便于系统的管理 pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(&pdev->dev), dev_name(pdev->dev.parent)); ret = device_add(&pdev->dev); //资源也分配好了,准备工作也做足,终于可以把设备添加到设备链表里面了 if (ret == 0) return ret; failed: while (--i >= 0) { struct resource *r = &pdev->resource[i]; unsigned long type = resource_type(r); if (type == IORESOURCE_MEM || type == IORESOURCE_IO) release_resource(r); } return ret; } EXPORT_SYMBOL_GPL(platform_device_add);
3. Platform设备驱动
(1)基本数据机构platform_driver
[weiming@Huangweiming linux-3.0]$ vim 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 (*resume)(struct platform_device *); //恢复 struct device_driver driver; //内嵌设备驱动 const struct platform_device_id *id_table; //驱动支持项 };这是platform驱动基本的数据结构,在驱动程序中我们要做的就是声明一个这样的结构并初始化。下面是plat_led驱动程序对它的初始化
static struct platform_driver s3c_led_driver = { .probe = s3c_led_probe, .re 4000 move = s3c_led_remove, .driver = { .name = "s3c_led", .owner = THIS_MODULE, }, };上面是led驱动中定义了的一个platform_driver,然后我们只需要在驱动模块加载函数中执行platform_driver_register(&my_driver)就可以把platform驱动加入内核了。在我们进行insmod加载时就会调用这个模块加载函数,从而注册platform驱动。
(2)注册函数platform_driver_register
[weiming@Huangweiming linux-3.0]$ vim drivers/base/platform.c
int platform_driver_register(struct platform_driver *drv) { 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; return driver_register(&drv->driver); }该函数把驱动赋予platform_bus_type总线,然后把platform_driver结构中的定义的probe,remove,shutdown赋值给device_driver结构中的相应成员,以供linux设备模型核心调用,最后调用driver_regster将设备驱动注册到linux设备模型核心中。
二.总线、设备、驱动的注册顺序
设备挂接到总线上时,与总线上的所有驱动进行匹配(用bus_type.match进行匹配),如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上;如果匹配失败,则只是将该设备挂接到总线上。
驱动挂接到总线上时,与总线上的所有设备进行匹配(用bus_type.match进行匹配),如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上;如果匹配失败,则只是将该驱动挂接到总线上。
1. platform bus先被kenrel注册。
do_basic_setup() -->-driver_init() -->-platform_bus_init()-->bus_register()
2. 系统初始化过程中调用platform_add_devices或者platform_device_register,将平台设备(platform devices)注册到平台总线中(platform_bus_type)
3. 平台驱动(platform driver)与平台设备(platform device)的关联是在platform_driver_register或者driver_register中实现,一般这个函数在驱动的初始化过程调用。
通过这三步,就将平台总线,设备,驱动关联起来。
参考博客:
platform_device_add()函数分析 点击打开链接
platform总线注册过程及platform_driver与platform_device的匹配 点击打开链接
相关文章推荐
- Linux学习:platform平台总线、平台设备、平台驱动
- linux中platform总线解析(四)(platform设备注册后自动匹配驱动)
- 老查的ARM学习笔记:chapter-2(linux总线设备驱动详解)
- Linux虚拟总线platform设备和驱动
- Linux驱动学习——platform平台总线
- <2012 12 17> linux驱动中的platform总线架构(含具体IIC设备驱动)
- Linux驱动---------platform总线设备
- linux驱动中的platform总线架构(含具体IIC设备驱动)
- LINUX设备驱动之platform总线
- Linux 内核--总线设备驱动模型(字符/块/网络设备 && platform设备)
- Linux设备驱动程式学习(13)-Linux设备模型(总线、设备、驱动程式和类)
- linux设备总线驱动模型 之 platform总线驱动
- Linux下基于Platform总线led驱动学习(一)
- linux驱动platform平台设备总线
- Linux驱动编程——platform总线的设备和驱动
- ldd3学习之十一(2):Linux设备模型---总线、设备、驱动
- ldd3学习之十一(3):Linux设备模型---platform总线分析
- LINUX设备驱动之platform总线
- LINUX设备驱动之platform总线
- Linux驱动-platform总线设备驱动