弄了两天,把platform模型看了。
linux设备驱动模型中,三个重要的实体,总线、设备、驱动。
其中的概念和意义不再强调,网上还有书上都有很多。
这里只强调两点:
1)引入platform使得设备被挂在在总线上,符合了linux2.6内核的设备模型。
2)隔离BSP和驱动。在BSP中定义的platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
几个重要的结构体:
platform_bus_type是platform总线定义了一个bus_type的实例。
这部分有系统内部为我们完成,我们不需要去做特殊的定义或者初始化。
- struct bus_type platform_bus_type = {
- .name = "platform", //总线名称
- .dev_attrs = platform_dev_attrs,
- .match = platform_match, //匹配函数,该函数确定了platform_device和platform_driver之间如何匹配
- .uevent = platform_uevent, //用于添加环境变量
- .pm = PLATFORM_PM_OPS_PTR,
- };
- EXPORT_SYMBOL_GPL(platform_bus_type);
-
- 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);
- if (error)
- device_unregister(&platform_bus);
- return error;
- }
总线也是一种设备,要先注册总线设备,在使用总线设备完成总线注册。
- static struct platform_device *smdk6410_devices[] __initdata = {
- //#ifdef CONFIG_SMDK6410_SD_CH0
- &s3c_device_hsmmc0,
- //#endif
- //#ifdef CONFIG_SMDK6410_SD_CH1
- &s3c_device_hsmmc1,
- //#endif
- &s3c_device_i2c0,
- // &s3c_device_i2c1,
- &s3c_device_fb,
- &s3c_device_ohci,
- &s3c_device_usb_hsotg,
- ......
描述了总线,就要描述设备和驱动了。下面是两个重要的数据结构体,定义在
- struct platform_device { //platform设备 在设备源文件中实现
- const char * name;
- int id;
- struct device dev;
- u32 num_resources;
- struct resource * resource;
- const struct platform_device_id *id_entry;
- /* arch specific additions */
- struct pdev_archdata archdata;
- };
- struct platform_driver { //platform驱动 在驱动源文件中实现
- 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;
- };
resource结构,dm9
000_resources是他的一个实例(OK6410)对resource的定义通常在BSP板文件中进行,具体设备驱动通过platform_get_resource()这个API来获取。
- struct resource {
- resource_size_t start; /*资源的起始*/ //起始和结束都依赖于设备类型
- resource_size_t end; /*资源的结束*/
- const char *name; /*资源的名称*/
- unsigned long flags; /*资源的类型*/
- struct resource *parent, *sibling, *child; /*资源的链表指针*/
- };
- static struct resource dm9000_resources[] = {
- [0] = {
- .start = S3C64XX_PA_DM9000,
- .end = S3C64XX_PA_DM9000 + 3,
- .flags = IORESOURCE_MEM,
- },
- [1] = {
- .start = S3C64XX_PA_DM9000 + 4,
- .end = S3C64XX_PA_DM9000 + S3C64XX_SZ_DM9000 - 1,
- .flags = IORESOURCE_MEM,
- },
- [2] = {
- .start = IRQ_EINT(7),//DM9000AE中断信号使用S3C6410处理器中断EINT7信号
- .end = IRQ_EINT(7),
- .flags = IORESOURCE_IRQ | IRQF_TRIGGER_HIGH,
- },
- };
除了这些信息,硬件设备可能还会有一些配置信息,也是依赖于板的,不适合放在驱动程序中。
因此,platform提供platform_date支持
- static struct dm9000_plat_data dm9000_setup = {
- .flags = DM9000_PLATF_16BITONLY,
- .dev_addr = { 0x08, 0x90, 0x00, 0xa0, 0x90, 0x90 },
- };
- static struct platform_device s3c_device_dm9000 = {
- .name = "dm9000",
- .id = 0,
- .num_resources = ARRAY_SIZE(dm9000_resources),
- .resource = dm9000_resources,
- .dev = {
- .platform_data = &dm9000_setup,
- }
- };
- #endif //#ifdef CONFIG_DM9000
而在网卡驱动中,通过
struct dm9000_plat_data *pdate=pdev->dev.platform_data
获得platform_data 即硬件的配置信息
最重要的在于platform_device与platform_driver之间是如何通过platform总线建立联系的。
通过对函数platform_driver_register的追踪,可以看到,最终两者通过platform_mach建立联系的。
- 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);
-
- /* match against the id table first */
- 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与platform_driver是通过name来匹配的。
匹配成功后,调用probe函数。
- int driver_probe_device(struct device_driver *drv, struct device *dev)
- {
- 。。。。。。。。。
- ret = really_probe(dev, drv);
- 。。。。。。。。
- }
- static int really_probe(struct device *dev, struct device_driver *drv)
- {
- 。。。。。。。。
- if (dev->bus->probe) {
- ret = dev->bus->probe(dev);
- if (ret)
- goto probe_failed;
- } else if (drv->probe) {
- ret = drv->probe(dev);
- if (ret)
- goto probe_failed;
- }
- 。。。。。。。。
- }
可以看出,如果bus定义了probe函数,则调用bus的probe函数;如果bus,没有定义而driver定义了probe函数,则调用driver的probe函数。由上边的platform_bus_type可以看出bus并没有定义probe函数,所以调用driver的probe函数。
我的测试程序用的是linux设备驱动开发详解这本书,但是这里出现一个问题。
书上的例程是将device和driver放在同一个文件中,我将源代码完全拷贝后编译可以通过,但是insmod并没有把生成的模块加载进来。
用lsmod可以查看到加载的模块,但是cat /proc/devices找不到设备号,无法创建设备节点,也无法测试应用程序。
然后按照网友的说明,将两者分开,然后分别编译分别加载,就可以通过了。
部分代码如下,没列出的和原来的部分是一样的。
在globalfifo-device.c中添加
- static struct platform_device *globalfifo_device;
- /* module_init */
- static int __init globalfifo_dev_init(void)
- {
- int ret;
- globalfifo_device=platform_device_alloc("globalfifo",-1);//分配设备
- ret=platform_device_add(globalfifo_device); //注册设备 挂载设备到总线
- if(ret)
- {
- printk("platform_device_add failed\n");
- }
- else
- printk("platform_device_add success!\n");
- }
- /* module_exit */
- void __exit globalfifo_dev_exit(void)
- {
- platform_device_unregister(globalfifo_device); //设备注销
- }
在platform-driver.c中添加
- static int __devexit globalfifo_remove(struct platform_device *pdev)
- {
- cdev_del(&globalfifo_devp->cdev);
- kfree(globalfifo_devp);
- unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
- return 0;
- }
- static struct platform_driver globalfifo_driver=
- {
- .probe=globalfifo_probe,
- .remove=__devexit_p(globalfifo_remove),
- .driver=
- {
- .name="globalfifo",
- .owner=THIS_MODULE,
- }
- };
- /* module_init */
- static int __init globalfifo_drv_init(void)
- {
- return platform_driver_register(&globalfifo_driver);
- }
- /* module_exit */
- void __exit globalfifo_drv_exit(void)
- {
- platform_driver_unregister(&globalfifo_driver);
- }
同时还需要在板文件中mach-smdk6410.c中添加如下信息
- static struct platform_device globalfifo_device={
- .name="globalfifo",
- .id=-1,
- };
为了完成globalfifo_device这个设备的注册,修改如下
- static struct platform_device *smdk6410_devices[] __initdata = {
- +&globalfifo_device
- //#ifdef CONFIG_SMDK6410_SD_CH0
- &s3c_device_hsmmc0,
- //#endif
- //#ifdef CONFIG_SMDK6410_SD_CH1
- &s3c_device_hsmmc1,
- //#endif
- &s3c_device_i2c0,
- // &s3c_device_i2c1,
- &s3c_device_fb,
- &s3c_device_ohci,
下面编译,加载就可以了。
加载后可以发现如下结点
/sys/bus/platform/devices/globalfifo
/sys/devices/platform/globalfifo
cat /proc/devices可以查看到设备号。
mknod创建设备节点后就可以测试应用程序了。
阅读(1297) | 评论(0) | 转发(0) |