本章所说的总线是虚拟的总线,只是为了让设备属性和驱动行为更好的分离所提出的概念

 

实际的Linux设备和驱动通常都会挂接在一种总线上,对于USB、I2C、SPI等总线设备而言,自然不是问题。但是挂接在SoC之外的外设却不依附于此类总线,因此Linux发明了虚拟的总线,称为platform总线,所有直接通过内存寻址的设备都映射到这条总线上。总线相应的结构体为struct bus_type,相应的设备为platform_device,相应的驱动为platform_drvier

 

在使用总线分层时,如果设备代码需要更改,而驱动代码不需要更改。那么我们只需要更改设备代码即可,而不需要大片地更改驱动代码

 

在以下章节中,我会依次介绍platform_device、platform_driver和总线结构体platform_bus_type

 

 

之前说过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源代码:

9、总线设备驱动模型
 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

相关文章: