平台總線 —— 平台總線驅動模型


目錄

  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。簡言之:注冊、硬件初始化、獲取設備資源,實現上層接口,注銷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM