9、總線設備驅動模型


 


由於TINY4412被學長借去做畢設了,因此從本章開始,以后章節的示例代碼均基於iTOP4412_SCP精英版

如讀者使用TINY4412開發板,可自行修改代碼


  

本章所說的總線是虛擬的總線,只是為了讓設備屬性和驅動行為更好的分離所提出的概念

 

實際的Linux設備和驅動通常都會掛接在一種總線上,對於USB、I2C、SPI等總線設備而言,自然不是問題。但是掛接在SoC之外的外設卻不依附於此類總線,因此Linux發明了虛擬的總線,稱為platform總線,所有直接通過內存尋址的設備都映射到這條總線上。總線相應的結構體為struct bus_type,相應的設備為platform_device,相應的驅動為platform_drvier

 

在使用總線分層時,如果設備代碼需要更改,而驅動代碼不需要更改。那么我們只需要更改設備代碼即可,而不需要大片地更改驅動代碼

 

在以下章節中,我會依次介紹platform_device、platform_driver和總線結構體platform_bus_type

 

 

一、platform_device

之前說過platform_device定義的是屬性,其結構體定義如下:

struct platform_device {
    const char    * name;        // 名字,用於與driver匹配
    int        id;
    struct device    dev;
    u32        num_resources;    // resource的個數
    struct resource    * resource;    // 存儲數據

    const struct platform_device_id    *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

 

我們需要重點關注的是struct resource * resource;,此變量存儲設備資源信息,其定義和示例如下:

struct resource {
    resource_size_t start;    // 起始地址
    resource_size_t end;    // 結束地址
    const char *name;
    unsigned long flags;    // 資源類型
    struct resource *parent, *sibling, *child;
};

static struct resource led_resource[] = { 
    /* 在iTOP4412中LED3對應GPK1_1 */
    [0] = { 
        .start = 0x11000060,            // GPK1CON地址
        .end   = 0x11000060 + 8 - 1,
        .flags = IORESOURCE_MEM,        // 內存
    },
    /* 要控制哪個I/O口(它屬於參數,這里為了方便,用resource傳參) */
    [1] = { 
        .start = 1,
        .end   = 1,
        .flags  = IORESOURCE_IRQ,        // 中斷屬性
    },
};

 

資源有以下幾種常用類型:

#define IORESOURCE_IO        0x00000100
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400
#define IORESOURCE_DMA        0x00000800
#define IORESOURCE_BUS        0x00001000

因為ARM中寄存器和內存是統一編址的,所以GPIO所使用的資源標志用IORESOURCE_MEM和IORESOURCE_REG都是可以的

需要注意的是,這里不能用I/O,這里的IORESOURCE_IO特指PCI/ISA總線的I/O

 

在定義完成resource之后,我們可以定義如下platform_device:

1 static struct platform_device led_platform_dev = {
2     .name    = "led",
3     .id        = 0,
4     .resource    = led_resource,
5     .num_resources = ARRAY_SIZE(led_resource),
6 };

 

在paltform_device定義完成后,我們需要使用如下函數向總線bus_type注冊/注銷:

/* 注冊platform_device */
platform_device_register(&led_platform_dev);

/* 注銷platform_driver */
platform_device_unregister(&led_platform_dev);

 

platform_device_register()函數調用過程如下:

platform_device_register()
  -> device_initialize(&pdev->dev);    // 初始化struct device
  -> platform_device_add(pdev);        // 添加設備到鏈表中
    -> pdev->dev.bus = &platform_bus_type;    // 指定總線
    -> device_add(&pdev->dev);
      -> bus_probe_device(struct device *dev)
        -> device_attach(struct device *dev)
          -> bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
            -> __device_attach()
              -> driver_match_device(drv, dev);
                // 調用總線成員函數match()
                -> return drv->bus->match ? drv->bus->match(dev, drv) : 1;
              -> driver_probe_device(drv, dev);
                -> really_probe(dev, drv);
                  -> drv->probe(dev);    // 調用probe()函數

 

 

二、platform_driver

platform_driver定義的是行為,其定義如下:

struct platform_driver {
    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;
};

 

platform_driver示例如下:

static struct platform_driver led_platform_drv = {
    .driver = {
        .name    = "led",
        .owner    = THIS_MODULE,
    },
    .probe        = led_probe,
    .remove        = __devexit_p(led_remove),  /* __devexit_p(x) x */
};

 

和platform_device一樣,在paltform_driver定義完成后,我們需要使用如下函數向總線bus_type注冊/注銷:

/* 注冊platform_driver */
platform_driver_register(&led_platform_drv);

/* 注銷platform_driver */
platform_driver_unregister(&led_platform_drv);

 

platform_driver_register()函數調用過程如下:

platform_driver_register()
  -> drv->driver.bus = &platform_bus_type;    // 指定總線
  -> driver_register(&drv->driver);
    -> bus_add_driver(drv);            // 添加驅動到鏈表中
      -> driver_attach(drv);
        -> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
          -> __driver_attach()
            -> driver_match_device(drv, dev);
                // 調用總線成員函數match()
                -> return drv->bus->match ? drv->bus->match(dev, drv) : 1;
          -> driver_probe_device(drv, dev);
            -> really_probe(dev, drv);
              -> drv->probe(dev);    // 調用probe()函數

和platform_device_register()一樣,platform_driver_register()也會調用總線的成員函數match();匹配成功則會調用paltform_driver的probe()函數

因此我們要在paltform_driver結構體中提供probe()函數

 

 

下面,我們來分析總線結構體platform_bus_type

三、platform_bus_type

platform_bus_type定義如下:

struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = &platform_dev_pm_ops,
};

 

我們需要分析其match()函數:

/* 1. 使用設備樹進行匹配,我們沒有用到 */
of_driver_match_device(dev, drv)

/* 2. 使用platform_driver的id_table進行匹配,我們也沒有用到 */
platform_match_id(pdrv->id_table, pdev)


/* 3. 匹配名字,我們使用這個 */
strcmp(pdev->name, drv->name)

 

 

四、總結

總線與輸入子系統不同,總線並沒有提供struct file_operations結構體,因此仍需要我們自己定義

 

1. 注冊platform_driver:platform_driver_register()

1.1 設置platform_driver的總線為platform_bus_type

1.2 添加platform_driver到總線的drv鏈表中

1.3 調用drv->bus->match(dev, drv)進行匹配

 

2. 注冊platform_device:platform_device_register()

2.1 設置platform_device的總線為platform_bus_type

2.2 添加platform_device到總線的dev鏈表中

2.3 調用drv->bus->match(dev, drv)進行匹配

 

3. 匹配:drv->bus->match(dev, drv)

3.1 匹配設備樹信息

3.2 匹配dev和drv->id_table

3.3 匹配dev->name和drv->name

3.4 成功,調用drv->probe(dev)

 

4. 驅動初始化:probe()

4.1 在probe()中做之前init()函數所做的事

 

5. 驅動注銷:remove()

5.1 在remove()中做之前exit()函數所做的事

 

 

五、更改led.c為總線設備驅動

此代碼我們需要完成platform_device和platform_driver兩個結構體,因此分為兩個文件

 

我們在probe()函數中需要獲取platform_device的數據,此時需要使用platform_get_resource()函數,示例代碼如下:

struct resource *led_resource;
led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0);
GPFCON = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1);
GPFDAT = GPFCON + 1;    // 映射

led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0);
pin = led_resource->start;

 

device源代碼:

 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/init.h>
 4 #include <linux/device.h>
 5 #include <linux/platform_device.h>
 6 #include <linux/ioport.h>
 7 
 8 #include <asm/uaccess.h>
 9 #include <asm/irq.h>
10 #include <asm/io.h>
11 
12 static struct resource led_resource[] = { 
13     /* 在iTOP4412中LED3對應GPK1_1 */
14     [0] = { 
15         .start = 0x11000060,            // GPK1CON地址
16         .end   = 0x11000060 + 8 - 1,
17         .flags = IORESOURCE_MEM,        // 內存
18     },
19     /* 要控制哪個I/O口(它屬於參數,這里為了方便,用resource傳參) */
20     [1] = { 
21         .start = 1,
22         .end   = 1,
23         .flags  = IORESOURCE_IRQ,        // 中斷屬性
24     },
25 };
26 
27 static void led_release(struct device *dev)
28 {
29     /* NULL */
30 }
31 
32 static struct platform_device led_platform_dev = {
33     .name    = "led",
34     .id        = 0,
35     .resource    = led_resource,
36     .num_resources = ARRAY_SIZE(led_resource),
37     .dev = {
38         .release = led_release,
39     },
40 };
41 
42 static int led_dev_init(void)
43 {
44     return platform_device_register(&led_platform_dev);
45 }
46 
47 static void led_dev_exit(void)
48 {
49     platform_device_unregister(&led_platform_dev);
50 }
51 
52 module_init(led_dev_init);
53 module_exit(led_dev_exit);
54 
55 MODULE_LICENSE("GPL");
View Code

driver源代碼:

  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/device.h>
  7 #include <linux/platform_device.h>
  8 #include <linux/ioport.h>
  9 #include <linux/kernel.h>
 10 
 11 
 12 #include <asm/uaccess.h>
 13 #include <asm/io.h>
 14 #include <asm/irq.h>
 15 
 16 /* 定義文件內私有結構體 */
 17 struct led_device {
 18     struct cdev cdev;
 19     int stat;            /* 用於保存LED狀態,0為滅,1為亮 */
 20 };
 21 
 22 static int g_major;
 23 module_param(g_major, int, S_IRUGO);
 24 
 25 static struct led_device*    dev;
 26 static struct class*        scls;
 27 static struct device*        sdev;
 28 
 29 static volatile unsigned long *con;
 30 static volatile unsigned long *dat;
 31 
 32 static int pin;
 33 
 34 /* LED write()函數 */
 35 static ssize_t led_write(struct file *filep, const char __user * buf, size_t len, loff_t *ppos)
 36 {
 37     struct led_device *dev = filep->private_data;
 38 
 39     if (copy_from_user(&(dev->stat), buf, 1))
 40         return -EFAULT;
 41 
 42     if (dev->stat == 1)
 43         *dat |= (1 << pin);
 44     else
 45         *dat &= ~(1 << pin);
 46 
 47     return 1;
 48 }
 49 
 50 /* LED open()函數 */
 51 static int led_open(struct inode *inodep, struct file *filep)
 52 {
 53     struct led_device *dev;
 54 
 55     dev = container_of(inodep->i_cdev, struct led_device, cdev);
 56     // 放入私有數據中
 57     filep->private_data = dev;
 58 
 59     // 設為輸出引腳,滅燈
 60     *con |= (1 << (4 * pin));
 61     *dat &= ~(1 << pin);
 62 
 63     return 0;
 64 }
 65 
 66 static int led_close(struct inode *inodep, struct file *filep)
 67 {
 68     return 0;
 69 }
 70 
 71 /* 把定義的函數接口集合起來,方便系統調用 */
 72 static const struct file_operations led_fops = {
 73     .write = led_write,
 74     .open  = led_open,
 75     .release = led_close,
 76 };
 77 
 78 static int led_probe(struct platform_device *platdev)
 79 {
 80     printk("%s\n", __FUNCTION__);
 81 
 82     int ret;
 83     dev_t devt;
 84 
 85     /* 1. 申請設備號 */
 86     if (g_major) {
 87         devt = MKDEV(g_major, 0);
 88         ret = register_chrdev_region(devt, 1, "led");
 89     }
 90     else {
 91         ret = alloc_chrdev_region(&devt, 0, 1, "led");
 92         g_major = MAJOR(devt);
 93     }
 94     if (ret)
 95         return ret;
 96 
 97     /* 2. 申請文件內私有結構體 */
 98     dev = kzalloc(sizeof(struct led_device), GFP_KERNEL);
 99     if (dev == NULL) {
100         ret = -ENOMEM;
101         goto fail_malloc;
102     }
103 
104     /* 3. 注冊字符設備驅動 */
105     cdev_init(&dev->cdev, &led_fops);    /* 初始化cdev並鏈接file_operations和cdev */
106     ret = cdev_add(&dev->cdev, devt, 1);    /* 注冊cdev */
107     if (ret)
108         return ret;
109 
110     /* 4. 創建類設備,insmod后會生成/dev/led設備文件 */
111     scls = class_create(THIS_MODULE, "led");
112     sdev = device_create(scls, NULL, devt, NULL, "led");
113     
114     struct resource *led_resource;
115     led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0);
116     con = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1);
117     dat = con + 1;    // 映射
118 
119     printk("con = %p, dat = %p\n", con, dat);
120 
121     led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0);
122     pin = led_resource->start;
123     
124     return 0;
125 
126 fail_malloc:
127     unregister_chrdev_region(devt, 1);
128 
129     return ret;
130 }
131 
132 static int led_remove(struct platform_device *platdev)
133 {
134     printk("%s\n", __FUNCTION__);
135 
136     iounmap(con);
137     
138     /* 鏡像注銷 */
139     dev_t devt = MKDEV(g_major, 0);
140 
141     device_destroy(scls, devt);
142     class_destroy(scls);
143 
144     cdev_del(&(dev->cdev));
145     kfree(dev);
146 
147     unregister_chrdev_region(devt, 1);
148 
149     return 0;
150 }
151 
152 static struct platform_driver led_platform_drv = {
153     .driver = {
154         .name    = "led",
155         .owner    = THIS_MODULE,
156     },
157     .probe        = led_probe,
158     .remove        = __devexit_p(led_remove),  /* __devexit_p(x) x */
159 };
160 
161 static int __init led_init(void)
162 {
163     return platform_driver_register(&led_platform_drv);
164 }
165  
166 static void __exit led_exit(void)
167 {    
168     platform_driver_unregister(&led_platform_drv);
169 }
170  
171 /* 聲明段屬性 */
172 module_init(led_init);
173 module_exit(led_exit);
174 
175 MODULE_LICENSE("GPL");
View Code

Makefile:

 1 KERN_DIR = /work/itop4412/tools/linux-3.5
 2 
 3 all:
 4     make -C $(KERN_DIR) M=`pwd` modules 
 5 
 6 clean:
 7     make -C $(KERN_DIR) M=`pwd` modules clean
 8     rm -rf modules.order
 9 
10 obj-m    += device.o driver.o
View Code

測試文件:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 6 #include <string.h>
 7 
 8 int main(int argc, char** argv)
 9 {
10     if (argc != 2) {
11         printf("Usage: \n");
12         printf("%s <on|off>\n", argv[0]);
13         return -1;
14     }
15 
16     int fd;
17     fd = open("/dev/led", O_RDWR);
18     if (fd < 0) {
19         printf("can't open /dev/led\n");
20         return -1;
21     }
22 
23     char stat;
24     if (0 == strcmp(argv[1], "off")) {
25         stat = 0;
26         write(fd, &stat, 1);
27     } else {
28         stat = 1;
29         write(fd, &stat, 1);
30     }
31     close(fd);
32 
33     return 0;
34 }
View Code

 

在rmmod device時,產生如下圖錯誤:

也就是說我們需要提供release()函數,更改platform_device如下:

 1 static void led_release(struct device *dev)
 2 {
 3     /* NULL */
 4 }
 5 
 6 static struct platform_device led_platform_dev = {
 7     .name    = "led",
 8     .id        = 0,
 9     .resource    = led_resource,
10     .num_resources = ARRAY_SIZE(led_resource),
11     .dev = {
12         .release = led_release,
13     },
14 };

 

 

下一章  10、LCD的framebuffer設備驅動

 

 


免責聲明!

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



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