九、总线设备驱动模型


由于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源代码driver源代码

在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 };

猜你喜欢

转载自www.cnblogs.com/Lioker/p/10893768.html