什么是platform总线
一个现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。platform机制将设备本身的资源注册进内核,有内核统一管理,在驱动程序使用这些资源时使用统一的接口,这样提高了程序的可移植性。
1、platform总线:
这里用的是linux2.6.36 内核的源代码
linux在系统启动时就注册了platform总线,看内核代码:
/*drivers/base/platform.c*/ 626 static int platform_match(struct device *dev, struct device_driver *drv) 627 { 628 struct platform_device *pdev = to_platform_device(dev); 629 struct platform_driver *pdrv = to_platform_driver(drv); 630 631 /* Attempt an OF style match first */ 632 if (of_driver_match_device(dev, drv)) 633 return 1; 634 635 /* Then try to match against the id table */ 636 if (pdrv->id_table) 637 return platform_match_id(pdrv->id_table, pdev) != NULL; 638 639 /* fall-back to driver name match */ 640 return (strcmp(pdev->name, drv->name) == 0); //配对函数检验名字是否一致 641 } 。。。。。 970 struct bus_type platform_bus_type = { 971 .name = "platform", /*定义了总线名字为platform 总线注册后新建目录sys/bus/platform */ 972 .dev_attrs = platform_dev_attrs, 973 .match = platform_match, //指定配对函数 974 .uevent = platform_uevent, 975 .pm = &platform_dev_pm_ops, 976 }; 977 EXPORT_SYMBOL_GPL(platform_bus_type);
可以看到,总线中定义了成员名字和match函数,当有总线或者设备注册到platform总线时,内核自动调用match函数,
判断设备和驱动的name是否一致。
2、platform设备:
同样的,先看一下platform设备对应的结构体paltform_device:
/home/linux-2.6.36/include/linux 17 struct platform_device { 18 const char * name; 19 int id; //设备id 用于给插入总线并且具有相同name的设备编号 如果只有一个设备的话填 -1 20 struct device dev; // 结构体中内嵌的device结构体 21 u32 num_resources; // 资源数 22 struct resource * resource; // 用于存放资源的数据 23 24 const struct platform_device_id *id_entry; 25 26 /* arch specific additions */ 27 struct pdev_archdata archdata; 28 };
上面的结构体中先不介绍id、num_resources和resource。可以看到,platform_device的封装就是指定了一个目录的名字name,并且内嵌device。
platform_device的注册和注销使用以下函数:
/home/linux-2.6.36/drivers/base 325 int platform_device_register(struct platform_device *pdev) 326 { 327 device_initialize(&pdev->dev); 328 return platform_device_add(pdev); 329 } 330 EXPORT_SYMBOL_GPL(platform_device_register); 340 void platform_device_unregister(struct platform_device *pdev) 341 { 342 platform_device_del(pdev); 343 platform_device_put(pdev); 344 } 345 EXPORT_SYMBOL_GPL(platform_device_unregister);注册后,同样会在 /sys/device/platform 目录下创建一个以 name 命名的目录,并且创建软连接到 /sys/bus/platform/device 下。
3、platform驱动:
先看一下platform驱动对应的结构体paltform_driver:
114 struct platform_driver { 115 int (*probe)(struct platform_device *); 116 int (*remove)(struct platform_device *); 117 void (*shutdown)(struct platform_device *); 118 int (*suspend)(struct platform_device *, pm_message_t state); 119 int (*resume)(struct platform_device *); 120 struct device_driver driver; 121 const struct platform_device_id *id_table; 122 };可以看到, platform_driver结构体内嵌了 device_driver,并且实现了 prob、 remove等操作。其实,当内核需要调用 probe函数时,它会调用 driver->probe,在 driver->probe中再调用 platform_driver->probe
platform_driver的注册和注销使用以下函数:
432 int platform_driver_register(struct platform_driver *drv) 433 { 434 drv->driver.bus = &platform_bus_type; 435 if (drv->probe) 436 drv->driver.probe = platform_drv_probe; 437 if (drv->remove) 438 drv->driver.remove = platform_drv_remove; 439 if (drv->shutdown) 440 drv->driver.shutdown = platform_drv_shutdown; 441 442 return driver_register(&drv->driver); 443 } 444 EXPORT_SYMBOL_GPL(platform_driver_register); 。。。 450 void platform_driver_unregister(struct platform_driver *drv) 451 { 452 driver_unregister(&drv->driver); 453 } 454 EXPORT_SYMBOL_GPL(platform_driver_unregister);注册成功后内核会在 /sys/bus/platform/driver/ 目录下创建一个名字为 driver->name 的目录。
以下是一个led 的简单例子:
(用的是友善之臂的mini6410开发板)
图1 图2
只用了GPK8(图1)作为led的控制引脚 外接一个led(图2)
当引脚为电平时led灯亮 当引脚为高电平时灯灭
//device.c 1 #include <linux/module.h> 2 #include <linux/init.h> 3 4 #include <linux/platform_device.h> 5 6 void led_dev_release(struct device *dev) 7 { 8 printk("<kernel> release\n"); 9 } 10 struct resource s3c_led_res[1] = { 11 [0] = { 12 .start = 0x7F008800, 13 .end = 0x7F00880C, 14 .flags = IORESOURCE_MEM, 15 }, 16 }; 17 18 struct platform_device s3c_led_dev = { 19 .name = "plat_led", 20 .id = -1, 21 .dev = { 22 .release = led_dev_release, 23 }, 24 .num_resources = ARRAY_SIZE(s3c_led_res), //platform资源的数量,为1 25 .resource = s3c_led_res, 26 }; 27 28 static int __init led_device_init(void) 29 { 30 int ret; 31 ret = platform_device_register(&s3c_led_dev); 32 if(ret){ 33 printk("device register failed!\n"); 34 return ret; 35 } 36 37 printk("led device init\n"); 38 return 0; 39 } 40 41 static void __exit led_device_exit(void) 42 { 43 platform_device_unregister(&s3c_led_dev); 44 printk("led device bye!\n"); 45 } 46 47 module_init(led_device_init); 48 module_exit(led_device_exit); 49 50 MODULE_LICENSE("GPL"); 51 MODULE_AUTHOR("wenhui");
//driver.c 1 #include<linux/module.h> 2 #include<linux/init.h> 3 4 #include<linux/platform_device.h> 5 #include<asm/io.h> 6 #include<asm/sizes.h> 7 8 struct plat_led{ 9 unsigned long phys, virt; 10 unsigned long gpkcon1, gpkdat, gpkup; 11 unsigned long reg; 12 }; 13 14 struct plat_led pled; 15 16 int led_driver_probe(struct platform_device *pdev) 17 { 18 pled.phys = pdev->resource[0].start; /*0x7F008800*/ 19 20 /*不加强制性转换 会报 warning: assignment makes integer from pointer without a cast*/ 21 pled.virt = (unsigned long)ioremap(pled.phys, SZ_4K); 22 23 pled.gpkcon1 = pled.virt + 0x4; 24 pled.gpkdat = pled.virt + 0x8; 25 pled.gpkup = pled.virt + 0xc; 26 27 //config 28 pled.reg = ioread32(pled.gpkcon1); /*GPK4 LED1*/ 29 pled.reg &= ~(0xe<<0); /*0001 output*/ 30 pled.reg |= (0x1<<0); 31 iowrite32(pled.reg, pled.gpkcon1); 32 33 //up 34 pled.reg = ioread32(pled.gpkup); 35 pled.reg &= ~(0x3<<8); /*disable pull-up/down*/ 36 iowrite32(pled.reg, pled.gpkup); 37 38 //dat 39 pled.reg = ioread32(pled.gpkdat); 40 pled.reg &= ~(0x1<<8); /*low */ 41 iowrite32(pled.reg, pled.gpkdat); 42 43 printk("led on\n"); 44 return 0; 45 } 46 47 int led_driver_remove(struct platform_device *pdev) 48 { 49 pled.reg = ioread32(pled.gpkdat); 50 pled.reg |= (0x1<<8); 51 iowrite32(pled.reg, pled.gpkdat); 52 53 printk("led off\n"); 54 return 0; 55 } 56 57 struct platform_driver s3c_led_drv = { 58 .probe = led_driver_probe, 59 .remove = led_driver_remove, 60 .driver = { 61 .name = "plat_led", /*在/sys/ 中的驱动目录名字*/ 62 } 63 }; 64 65 static int __init plat_led_init(void) 66 { 67 int ret; 68 ret = platform_driver_register(&s3c_led_drv); 69 if(ret){ 70 printk("led register failed!\n"); 71 return ret; 72 } 73 printk("led driver init\n"); 74 75 return 0; 76 } 77 78 static void __exit plat_led_exit(void) 79 { 80 platform_driver_unregister(&s3c_led_drv); 81 printk("led driver exit"); 82 } 83 84 module_init(plat_led_init); 85 module_exit(plat_led_exit); 86 87 MODULE_LICENSE("GPL"); 88 MODULE_AUTHOR("wenhui");
Makefile:
1 KERDIR = /home/linux-2.6.36 2 obj-m += device.o driver.o 3 build: kernel_modules 4 5 kernel_modules: 6 make -C $(KERDIR) M=$(CURDIR) modules 7 clean: 8 make -C $(KERDIR) M=$(CURDIR) clean
在上面的程序中,设备和驱动都是通过手动加载的,但有些情况下,内核已经为设备注册到platform总线上,只需要我们注册驱动就可以了。
接下来 下一篇中 将介绍一下设备的静态注册。
参考:http://blog.chinaunix.net/space.php?do=blog&uid=25014876&id=111745