平台总线 —— 平台总线驱动模型
目录
1、为什么会有平台总线?
2、平台总线三要素
3、平台总线编程接口
4、编写能在多平台下使用的led驱动
1、为什么会有平台总线?
1 用于平台升级:三星: 2410, 2440, 6410, s5pc100 s5pv210 4412 2 硬件平台升级的时候,部分的模块的控制方式,基本上是类似的 3 但是模块的地址是不一样 4 5 gpio控制逻辑: 1, 配置gpio的输入输出功能: gpxxconf 6 2, 给gpio的数据寄存器设置高低电平: gpxxdata 7 逻辑操作基本上是一样的 8 但是地址不一样 9 10 uart控制:1,设置8n1(数据位、奇偶校验位、停止位),115200, no AFC(流控) 11 UCON,ULCON, UMODOEN, UDIV 12 13 逻辑基本上是一样的 14 但是地址不一样 15 16 问题: 17 当soc升级的时候, 对于相似的设备驱动,需要编写很多次(如果不用平台总线) 18 但是会有大部分重复代码 19 20 解决:引入平台总线 21 device(中断/地址)和driver(操作逻辑) 分离 22 在升级的时候,只需要修改device中信息即可(中断/地址) 23 实现一个driver代码能够驱动多个平台相似的模块,并且修改的代码量很少
2、平台总线三要素 —— platform_bus、device、driver·
platform会存在/sys/bus/里面
如下图所示, platform目录下会有两个文件,分别就是platform设备和platform驱动
device设备
挂接在platform总线下的设备, 使用结构体platform_device描述
driver驱动
挂接在platform总线下,是个与某种设备相对于的驱动, 使用结构体platform_driver描述
platform总线
是个全局变量,为platform_bus_type,属于虚拟设备总线,通过这个总线将设备和驱动联系起来,属于Linux中bus的一种。
以下依次介绍其数据结构:
1)device
1 struct platform_device { 2 const char * name; //设备名称,要与platform_driver的name一样,这样总线才能匹配成功 3 u32 id; //插入总线下相同name的设备编号(一个驱动可以有多个设备),如果只有一个设备填-1 4 struct device dev; //具体的device结构体,继承了device父类 5 //成员platform_data可以给平台driver提供各种数据(比如:GPIO引脚等等) 6 u32 num_resources;//资源数目 7 struct resource * resource;//资源描述,用来描述io,内存等 8 }; 9 10 //资源文件 ,定义在include\linux\ioport.h 11 struct resource { 12 resource_size_t start; //起始资源,如果是地址的话,必须是物理地址 13 resource_size_t end; //结束资源,如果是地址的话,必须是物理地址 14 const char *name; //资源名 15 unsigned long flags; //资源类型,可以是io/irq/mem等 16 struct resource *parent, *sibling, *child; //链表结构,可以构成链表 17 }; 18 // type 19 #define IORESOURCE_IO 0x00000100 /* Resource type */ 20 #define IORESOURCE_MEM 0x00000200 21 #define IORESOURCE_IRQ 0x00000400 22 #define IORESOURCE_DMA 0x00000800
注册和注销
1 int platform_device_register(struct platform_device * pdev); 2 void platform_device_unregister(struct platform_device * pdev)
2)driver
1 struct platform_driver { 2 int (*probe)(struct platform_device *); //匹配成功之后被调用的函数 3 int (*remove)(struct platform_device *);//device移除的时候调用的函数 4 struct device_driver driver; //继承了driver父类 5 | 6 const char *name; 7 const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来 8 }
注册与注销
1 int platform_driver_register(struct platform_driver *drv); 2 void platform_driver_unregister(struct platform_driver *drv);
3)platform_bus
1 struct bus_type platform_bus_type = { 2 .name = "platform", //设备名称 3 .dev_attrs = platform_dev_attrs, //设备属性、含获取sys文件名,该总线会放在/sys/bus下 4 .match = platform_match, //匹配设备和驱动,匹配成功就调用driver的.probe函数 5 .uevent = platform_uevent, //消息传递,比如热插拔操作 6 .suspend = platform_suspend, //电源管理的低功耗挂起 7 .suspend_late = platform_suspend_late, 8 .resume_early = platform_resume_early, 9 .resume = platform_resume, //恢复 10 }; 11
匹配方法:match
1 static int platform_match(struct device *dev, struct device_driver *drv) 2 { 3 //1,优先匹配pdriver中的id_table,里面包含了支持不同的平台的名字 4 //2,直接匹配driver中名字和device中名字 5 6 struct platform_device *pdev = to_platform_device(dev); 7 struct platform_driver *pdrv = to_platform_driver(drv); 8 9 if (pdrv->id_table) // 如果pdrv中有idtable,平台列表名字和pdev中的名字 10 return platform_match_id(pdrv->id_table, pdev) != NULL; 11 12 /* fall-back to driver name match */ 13 return (strcmp(pdev->name, drv->name) == 0); 14 15 }
如何实现?
在pdev与pdrv指向的device、driver结构体中,各自都有一个成员 -- 父类结构体,实际上向总线注册的是这个父类结构体指针。通过container_of,可以通过成员找到包含该成员的整个结构体。
1 #define to_platform_device(x) container_of((x), struct platform_device, dev) 3 #define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))
3、平台总线编程接口
1) pdev 注册和注销
1 int platform_device_register(struct platform_device * pdev); 2 void platform_device_unregister(struct platform_device * pdev);
2)pdrv注册与注销
1 int platform_device_register(struct platform_device * pdev); 2 void platform_device_unregister(struct platform_device * pdev);
3)获取资源数据(对资源的定义可以参考内核/arch/arm/mach-xxx.c文件)
1 int platform_get_irq(struct platform_device * dev,unsigned int num); 2 struct resource * platform_get_resource_byname(struct platform_device * dev,unsigned int type,const char * name);
4、编写能在多平台下使用的LED驱动
1)注册一个platform_device,定义资源:地址和中断
1 struct resource { 2 resource_size_t start;// 开始 3 resource_size_t end; //结束 4 const char *name; //描述,自定义 5 unsigned long flags; //区分当前资源描述的是中断(IORESOURCE_IRQ)还是内存(IORESOURCE_MEM) 6 struct resource *parent, *sibling, *child; 7 };
2)注册一个platform_driver,实现操作失败的代码
1 注册完毕,同时如果和pdev匹配成功,自动调用probe方法: 2 probe方法: 对硬件进行操作 3 a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口 4 b,创建设备节点 5 c,初始化硬件 6 ioremap(地址); //地址从pdev需要获取 7 readl/writle(); 8 d,实现各种io接口: xxx_open, xxx_read, .. 9
1 获取资源的方式: 2 //获取资源 3 struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num) 4 { 5 int i; 6 7 for (i = 0; i < dev->num_resources; i++) { 8 struct resource *r = &dev->resource[i]; 9 10 if (type == resource_type(r) && num-- == 0) 11 return r; 12 } 13 return NULL; 14 } 15 // 参数1: 从哪个pdev中获取资源 16 // 参数2: 资源类型 17 // 参数3: 表示获取同种资源的第几个(0,1,2,3.....) 18 //返回值:返回一个指针,指向想获取的资源项
示例:编写平台驱动,实现最基本的匹配
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/ioport.h> 4 #include <plat/irqs.h> 5 #include <linux/platform_device.h> 6 7 8 static int led_pdrv_probe(struct platform_device * pdev) 9 { 10 printk("--------------%s-------------\n",__FUNCTION__); 11 return 0; 12 } 13 14 15 static int led_pdrv_remove(struct platform_device * pdev) 16 { 17 18 return 0; 19 } 20 21 22 //id_table:平台的id列表,包含支出不同平台的名字 23 const struct platform_device_id led_id_table[] = { 24 {"exynos_4412_led", 0x4444}, 25 {"s3c2410_led", 0x2244}, 26 {"s5pv210_led", 0x3344}, 27 }; 28 29 struct platform_driver led_pdrv = { 30 .probe = led_pdrv_probe, 31 .remove = led_pdrv_remove, 32 .driver = { 33 .name = "samsung_led_drv", 34 //可以用作匹配 35 // /sys/bus/platform/drivers/samsung_led_drv 36 }, 37 .id_table = led_id_table, 38 39 }; 40 41 42 static int __init plat_led_drv_init(void) 43 { 44 printk("---------------%s---------------\n",__FUNCTION__); 45 //注册一个平台驱动 46 return platform_driver_register(&led_pdrv); 47 48 } 49 50 static void __exit plat_led_drv_exit(void) 51 { 52 53 platform_driver_unregister(&led_pdrv); 54 } 55 56 57 58 module_init(plat_led_drv_init); 59 module_exit(plat_led_drv_exit); 60 61 MODULE_LICENSE("GPL");
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/ioport.h> 4 #include <plat/irqs.h> 5 #include <linux/platform_device.h> 6 7 #define GPIO_REG_BASE 0X11400000 8 9 #define GPF3_CON GPIO_REG_BASE + 0x01E0 10 #define GPF3_SIZE 24 //定义6个(一组)寄存器的连续空间 11 12 #define GPX1_CON GPIO_REG_BASE + 0x01E0 13 #define GPX1_SIZE 24 //定义6个(一组)寄存器的连续空间 14 15 16 //一个设备可能有多个资源 17 struct resource led_res[] = { 18 [0] = { 19 .start = GPF3_CON, 20 .end = GPF3_CON + GPF3_SIZE -1, 21 .flags = IORESOURCE_MEM, 22 }, 23 24 [1] = { 25 .start = GPX1_CON, 26 .end = GPX1_CON + GPX1_SIZE -1, 27 .flags = IORESOURCE_MEM, 28 }, 29 30 //有些设备也有中断资源,仅说明中断资源使用 31 [2] = { 32 .start = 4, //#define IRQ_EINT4 S3C2410_IRQ(36) /* 52 */ 33 .end = 4, //中断没有连续地址概念,开始与结束都是中断号 34 .flags = IORESOURCE_IRQ, 35 }, 36 37 }; 38 39 40 41 struct platform_device led_pdev = { 42 .name = "exynos_4412_led", //用于匹配的设备名称 /sys/bus/platform/devices 43 .id = -1, //表示只有一个设备 44 .num_resources = ARRAY_SIZE(led_res), //资源数量,ARRAY_SIZE()函数:获取数量 45 .resource = led_res, 46 47 48 }; 49 50 51 52 static int __init plat_led_dev_init(void) 53 { 54 //注册一个平台设备 55 return platform_device_register(&led_pdev); 56 57 } 58 59 static void __exit plat_led_dev_exit(void) 60 { 61 62 platform_device_unregister(&led_pdev); 63 } 64 65 module_init(plat_led_dev_init); 66 module_exit(plat_led_dev_exit); 67 68 MODULE_LICENSE("GPL");
测试:
进一步完善:在驱动程序中,实现了probe方法,获取硬件资源,完成led设备初始化,实现了上层调用的IO接口。实现了应用程序对硬件的控制。
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/ioport.h> 4 #include <plat/irqs.h> 5 #include <linux/platform_device.h> 6 #include <linux/of.h> 7 #include <linux/of_irq.h> 8 #include <linux/interrupt.h> 9 #include <linux/slab.h> 10 #include <linux/fs.h> 11 #include <linux/device.h> 12 #include <linux/kdev_t.h> 13 #include <linux/err.h> 14 #include <asm/io.h> 15 #include <asm/uaccess.h> 16 17 18 19 //设计一个全局变量 20 struct led_dev{ 21 int dev_major; 22 struct class *cls; 23 struct device *dev; //这个dev是用来创建设备文件的,不同与注册到bus的dev 24 struct resource *res; //获取到的某类资源 25 void* reg_base; //映射后的虚拟地址 26 }; 27 28 struct led_dev *samsung_led; 29 30 ssize_t led_pdrv_write(struct file *filp, const char __user * buf, size_t count, loff_t *fops) 31 { 32 int val; 33 int ret; 34 35 ret = copy_from_user(&val, buf, count); 36 if(ret > 0) 37 { 38 printk("copy_from_user failed\n"); 39 return -EFAULT; 40 } 41 42 if(val){ 43 44 writel(readl(samsung_led->reg_base + 4) | (0x3<<4) , samsung_led->reg_base+4); 45 46 }else{ 47 48 writel(readl(samsung_led->reg_base + 4) & ~(0x3<<4) , samsung_led->reg_base+4); 49 50 } 51 52 return 0; 53 54 } 55 56 int led_pdrv_open(struct inode *inode, struct file *filp) 57 { 58 printk("--------------%s-------------\n",__FUNCTION__); 59 //open相关的初始化也可以在open中完成,或在probe 60 return 0; 61 } 62 63 int led_pdrv_close(struct inode *inode, struct file *filp) 64 { 65 printk("--------------%s-------------\n",__FUNCTION__); 66 return 0; 67 } 68 69 70 71 //给上层应用提供文件IO接口 72 const struct file_operations led_fops = { 73 .open = led_pdrv_open, 74 .write = led_pdrv_write, 75 .release = led_pdrv_close, 76 77 }; 78 79 static int led_pdrv_probe(struct platform_device * pdev) 80 { 81 printk("--------------%s-------------\n",__FUNCTION__); 82 83 samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL); 84 if(samsung_led == NULL) 85 { 86 printk("kzalloc failed\n"); 87 return -ENOMEM; 88 } 89 90 91 /* 对硬件进行操作 92 * a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口 93 * b,创建设备节点 94 * c,初始化硬件 95 * ioremap(地址); //地址从pdev需要获取 96 * readl/writle(); 97 * d,实现各种io接口: xxx_open, xxx_read, .. 98 */ 99 100 //a. 注册设备号,动态 101 samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops); 102 103 //b. 创建设备节点 104 samsung_led->cls = class_create(THIS_MODULE, "led_new_cls"); 105 samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major,0), NULL, "led0"); 106 107 //c. 初始化硬件 108 109 //获取资源 110 //参数1:从哪个pdev中获取资源 111 //参数2:指定资源类型 112 //参数3:表示获取同种资源的第几个,(0,1,2,...) 113 samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 114 int irqno = platform_get_irq(pdev, 0); 115 //等同于platform_get_resource(pdev, IORESOURCE_IRQ, 0); 116 printk("-------get irqno = %d-------\n", irqno); 117 118 //大小 = end - start + 1; 加1是因为地址从0读起,eg: 7~0 = 7-0+1 119 //resource_size : samsung_led->res->end - samsung_led->res->start + 1 120 samsung_led->reg_base = ioremap(samsung_led->res->start,resource_size(samsung_led->res)); 121 122 //对寄存器进行配置 -- 输出功能 123 writel((readl(samsung_led->reg_base) & ~(0xff<<16)) | (0x11<<16), samsung_led->reg_base); 124 125 126 return 0; 127 } 128 129 //与probe是一对儿,做了与probe相反的事 130 static int led_pdrv_remove(struct platform_device * pdev) 131 { 132 iounmap(samsung_led->reg_base); 133 class_destroy(samsung_led->cls); 134 device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major,0)); 135 unregister_chrdev(samsung_led->dev_major, "led_drv"); 136 kfree(samsung_led); 137 return 0; 138 } 139 140 141 //id_table:平台的id列表,包含支出不同平台的名字 142 const struct platform_device_id led_id_table[] = { 143 {"exynos_4412_led", 0x4444}, 144 {"s3c2410_led", 0x2244}, 145 {"s5pv210_led", 0x3344}, 146 }; 147 148 struct platform_driver led_pdrv = { 149 .probe = led_pdrv_probe, 150 .remove = led_pdrv_remove, 151 .driver = { 152 .name = "samsung_led_drv", 153 //可以用作匹配 154 //创建/sys/bus/platform/drivers/samsung_led_drv 155 }, 156 .id_table = led_id_table, 157 158 }; 159 160 161 static int __init plat_led_drv_init(void) 162 { 163 printk("---------------%s---------------\n",__FUNCTION__); 164 //注册一个平台驱动 165 return platform_driver_register(&led_pdrv); 166 167 } 168 169 static void __exit plat_led_drv_exit(void) 170 { 171 172 platform_driver_unregister(&led_pdrv); 173 } 174 175 176 module_init(plat_led_drv_init); 177 module_exit(plat_led_drv_exit); 178 179 MODULE_LICENSE("GPL");
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/ioport.h> 4 #include <plat/irqs.h> 5 #include <linux/platform_device.h> 6 7 #define GPIO_REG_BASE 0X11400000 8 9 #define GPF3_CON GPIO_REG_BASE + 0x01E0 10 #define GPF3_SIZE 24 //定义6个(一组)寄存器的连续空间 11 12 #define GPX1_CON GPIO_REG_BASE + 0x01E0 13 #define GPX1_SIZE 24 //定义6个(一组)寄存器的连续空间 14 15 16 //一个设备可能有多个资源 17 struct resource led_res[] = { 18 [0] = { 19 .start = GPF3_CON, 20 .end = GPF3_CON + GPF3_SIZE -1, 21 .flags = IORESOURCE_MEM, 22 }, 23 24 [1] = { 25 .start = GPX1_CON, 26 .end = GPX1_CON + GPX1_SIZE -1, 27 .flags = IORESOURCE_MEM, 28 }, 29 30 //有些设备也有中断资源,仅说明中断资源使用 31 [2] = { 32 .start = 4, //#define IRQ_EINT4 S3C2410_IRQ(36) /* 52 */ 33 .end = 4, //中断没有连续地址概念,开始与结束都是中断号 34 .flags = IORESOURCE_IRQ, 35 }, 36 37 }; 38 39 40 41 struct platform_device led_pdev = { 42 .name = "exynos_4412_led", //用于匹配的设备名称 /sys/bus/platform/devices 43 .id = -1, //表示只有一个设备 44 .num_resources = ARRAY_SIZE(led_res), //资源数量,ARRAY_SIZE()函数:获取数量 45 .resource = led_res, 46 47 48 }; 49 50 51 52 static int __init plat_led_dev_init(void) 53 { 54 //注册一个平台设备 55 return platform_device_register(&led_pdev); 56 57 } 58 59 static void __exit plat_led_dev_exit(void) 60 { 61 62 platform_device_unregister(&led_pdev); 63 } 64 65 module_init(plat_led_dev_init); 66 module_exit(plat_led_dev_exit); 67 68 MODULE_LICENSE("GPL");
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 #include <unistd.h> 8 9 10 11 int main(int argc, char * argv[]) 12 { 13 int fd; 14 15 int on = 0; 16 17 fd = open("/dev/led0", O_RDWR); 18 if(fd < 0) 19 { 20 perror("open\n"); 21 exit(1); 22 } 23 24 while(1) 25 { 26 on = 0; 27 write(fd, &on, 4); 28 sleep(1); 29 30 on = 1; 31 write(fd, &on, 4); 32 sleep(1); 33 } 34 35 36 return 0; 37 }
1 ROOTFS_DIR = /home/linux/source/rootfs#根文件系统路径 2 3 APP_NAME = led_test 4 MODULE_NAME = plat_led_pdev 5 MODULE_NAME1 = plat_led_pdrv 6 7 CROSS_COMPILE = /home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi- 8 CC = $(CROSS_COMPILE)gcc 9 10 ifeq ($(KERNELRELEASE),) 11 12 KERNEL_DIR = /home/linux/kernel/linux-3.14-fs4412 #编译过的内核源码的路径 13 CUR_DIR = $(shell pwd) #当前路径 14 15 all: 16 make -C $(KERNEL_DIR) M=$(CUR_DIR) modules #把当前路径编成modules 17 $(CC) $(APP_NAME).c -o $(APP_NAME) 18 @#make -C 进入到内核路径 19 @#M 指定当前路径(模块位置) 20 21 clean: 22 make -C $(KERNEL_DIR) M=$(CUR_DIR) clean 23 24 install: 25 sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module #把当前的所有.ko文件考到根文件系统的drv_module目录 26 27 else 28 29 obj-m += $(MODULE_NAME).o #指定内核要把哪个文件编译成ko 30 obj-m += $(MODULE_NAME1).o 31 32 endif
测试结果:
在运行led_test后,可以看见led闪烁,查看platform总线下的devices,drivers目录,也分别注册
了设备与驱动
小结:
平台设备驱动模型,主要是用于平台升级的,可以在平台升级时,主要修改设备相关文件即可,对于设备驱动,尽可能重用。
平台设备驱动来源于设备驱动模型,只是平台总线由系统实现了而已。
平台设备代码里面主要实现:将设备注册到总线,填充设备相关的硬件资源,例如内存资源,中断资源;
在平台驱动里,最先做的也是将驱动注册到总线,然后对硬件进行一系列初始化,那么硬件信息从哪里来?在设备驱动注册到总线后,总线会将设备的platform_device中的成指针dev传给设备驱动,设备驱动通过成员就可获取到结构体的数据,访问到设备的资源,从而进行硬件相关操作,这些操作都可以在探测函数中完成。此外,设备驱动还要为应用层提供操作设备的接口fops。简言之:注册、硬件初始化、获取设备资源,实现上层接口,注销。