平台总线 —— 平台总线驱动模型

目录

  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");
plat_led_pdrv.c
 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");
plat_led_pdev.c

   测试:

 

 

 进一步完善:在驱动程序中,实现了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");
plat_led_pdrv.c
 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");
plat_led_pdev.c
 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 }
led_test.c
 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
Makefile

测试结果:

 

 

 在运行led_test后,可以看见led闪烁,查看platform总线下的devices,drivers目录,也分别注册

了设备与驱动

 

 

 小结:

  平台设备驱动模型,主要是用于平台升级的,可以在平台升级时,主要修改设备相关文件即可,对于设备驱动,尽可能重用。

  平台设备驱动来源于设备驱动模型,只是平台总线由系统实现了而已。

  平台设备代码里面主要实现:将设备注册到总线,填充设备相关的硬件资源,例如内存资源,中断资源;

  在平台驱动里,最先做的也是将驱动注册到总线,然后对硬件进行一系列初始化,那么硬件信息从哪里来?在设备驱动注册到总线后,总线会将设备的platform_device中的成指针dev传给设备驱动,设备驱动通过成员就可获取到结构体的数据,访问到设备的资源,从而进行硬件相关操作,这些操作都可以在探测函数中完成。此外,设备驱动还要为应用层提供操作设备的接口fops。简言之:注册、硬件初始化、获取设备资源,实现上层接口,注销。

posted @ 2020-03-04 01:59  朱果果  阅读(1133)  评论(0编辑  收藏  举报