Chinaunix首页 | 论坛 | 博客
  • 博客访问: 351823
  • 博文数量: 41
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 784
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-21 20:58
文章分类

全部博文(41)

文章存档

2017年(1)

2010年(1)

2009年(18)

2008年(21)

分类: LINUX

2009-06-13 00:25:28

1 平台设备和驱动初识

platform是一个虚拟的地址总线,相比pci,usb,它主要用于描述SOC上的片上资源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的资源有一个共同点,就是在cpu的总线上直接取址。

平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源.
struct platform_device {
    const char    * name;
    int        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};

平台驱动遵循标准驱动模型的规范, 也就是说发现/列举(discovery/enumeration)在驱动之外处理, 而
由驱动提供probe()和remove方法. 平台驱动按标准规范对电源管理和关机通告提供支持
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 (*suspend_late)(struct platform_device *, pm_message_t state);
    int (*resume_early)(struct platform_device *);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
};
probe()总应该核实指定的设备硬件确实存在;平台设置代码有时不能确定这一点. 枚举(probing)可以使用的设备资源包括时钟及设备的platform_data.(译注: platform_data定义在device.txt中的"基本设备结构体"中.)

平台驱动通过普通的方法注册自身
int platform_driver_register(struct platform_driver *drv);

或者, 更常见的情况是已知设备不可热插拔, probe()过程便可以驻留在一个初始化区域(init section)
中,以便减少驱动的运行时内存占用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));


设备列举
按规定, 应由针对平台(也适用于针对板)的设置代码来注册平台设备
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)

一般的规则是只注册那些实际存在的设备, 但也有例外. 例如, 某外部网卡未必会装配在所有的板子上,
或者某集成控制器所在的板上可能没挂任何外设, 而内核却需要被配置来支持这些网卡和控制器


有些情况下, 启动固件(boot firmware)会导出一张装配到板上的设备的描述表. 如果没有这张表, 通常
就只能通过编译针对目标板的内核来让系统设置代码安装正确的设备了. 这种针对板的内核在嵌入式和自定
义的系统开发中是比较常见的.

多数情况下, 分给平台设备的内存和中断请求号资源是不足以让设备正常工作的. 板设置代码通常会用设备
的platform_data域来存放附加信息, 并向外提供它们.


嵌入式系统时常需要为平台设备提供一个或多个时钟信号. 除非被用到, 这些时钟一般处于静息状态以节电.
系统设置代码也负责为设备提供这些时钟, 以便设备能在它们需要是调用

clk_get(&pdev->dev, clock_name).

也可以用如下函数来一次性完成分配空间和注册设备的任务
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)

设备命名和驱动绑定
platform_device.dev.bus_id是设备的真名. 它由两部分组成:

        *platform_device.name ... 这也被用来匹配驱动

        *platform_device.id ...        设备实例号, 或者用"-1"表示只有一个设备.

连接这两项, 像"serial"/0就表示bus_id为"serial.0", "serial"/3表示bus_id为"serial.3";
上面二例都将使用名叫"serial"的平台驱动. 而"my_rtc"/-1的bus_id为"my_rtc"(无实例号), 它的
平台驱动为"my_rtc".



2 平台总线
下面我们看看与platform相关的操作
平台总线的初始化
int __init platform_bus_init(void)
{
    int error;

    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

这段初始化代码创建了一个platform设备,以后属于platform类型的设备就会以此为parent,增加的设备会出现在/sys/devices/platform目录下

[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth  floppy.0  i8042  pcspkr  power  serial8250  uevent  vesafb.0

紧接着注册名为platform的平台总线
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .suspend    = platform_suspend,
    .suspend_late    = platform_suspend_late,
    .resume_early    = platform_resume_early,
    .resume        = platform_resume,
};

int platform_device_add(struct platform_device *pdev)
{......
        pdev->dev.parent = &platform_bus;  //增加的platform设备以后都以platform_bus(platform设备)为父节点
        pdev->dev.bus = &platform_bus_type; //platform类型设备都挂接在platform总线上 /sys/bus/platform/
......
}

3 platform device的注册

struct platform_device {
    const char    * name;
    int        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};


1)动态分配一个名为name的platform设备

struct platform_object {
    struct platform_device pdev;
    char name[1];
};

struct platform_device *platform_device_alloc(const char *name, int id)
{
    struct platform_object *pa;

    pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);//由于 platform_object内name只有一个字节,所以需要多分配strlen(name)长度
    if (pa) {
        strcpy(pa->name, name);
        pa->pdev.name = pa->name;
        pa->pdev.id = id;
        device_initialize(&pa->pdev.dev);
        pa->pdev.dev.release = platform_device_release;
    }

    return pa ? &pa->pdev : NULL;
}

实际上就是分配一个platform_object 结构体(包含了一个platform device结构体)并初始化内部成员platform driver,然后返回platform driver结构体以完成动态分配一个platform设备
然后调用platform_add_devices()以追加一个platform 设备到platform bus上
int platform_device_add(struct platform_device *pdev)
{
    int i, ret = 0;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus; //初始化设备的父节点所属类型为platform device(platform_bus)

    pdev->dev.bus = &platform_bus_type;  //初始化设备的总线为platform bus

    if (pdev->id != -1)
        snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
             pdev->id);
    else
        strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);

    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = pdev->dev.bus_id;

        p = r->parent;
        if (!p) {
            if (r->flags & IORESOURCE_MEM)
                p = &iomem_resource;
            else if (r->flags & IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {                //插入资源到资源树上
            printk(KERN_ERR
                   "%s: failed to claim resource %d\n",
                   pdev->dev.bus_id, i);
            ret = -EBUSY;
            goto failed;
        }
    }

    pr_debug("Registering platform device '%s'. Parent at %s\n",
         pdev->dev.bus_id, pdev->dev.parent->bus_id);

    ret = device_add(&pdev->dev);       //注册特定的设备到platform bus上
    if (ret == 0)
        return ret;

 failed:
    while (--i >= 0)
        if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
            release_resource(&pdev->resource[i]);
    return ret;
}

上面的操作我们看到另外一个陌生的结构 设备资源(struct resource)
关于资源的操作(从上面已经了解,平台设备会分到一系列诸如地址和中断请求号(IRQ)之类的资源.
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;// IORESOURCE_IO  IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA
    struct resource *parent, *sibling, *child;
};

基于资源的分类(flags)有I/O端口、IRQ、DMA等等,而I/O端口又分为2种类型, IORESOURCE_IO(I/O映射) IORESOURCE_MEM(内存映射)

这里说一下关于I/O端口:
CPU对外设IO端口物理地址的编址方式有2种:一种是IO映射方式(IO-mapped), 另一种是内存映射方式(Memory-mapped)。具体采用哪一种方式则取决于CPU的体系结构。 像X86体系对外设就专门实现了一个单独地址空间,并且有专门的I/O指令来访问I/O端口,像ARM体系结构通常只是实现一个物理地址空间,I/O端口就被映射到CPU的单一物理地址空间中,而成为内存的一部分,所以一般资源都采用(IORESOURCE_MEM)。

linux中对设备的资源按照资源树的结构来组织(其实就是一个链表结构的插入、删除、查找等操作),上面再添加设备(platform_device_add)的同时对相应的资源在资源树上进行插入操作int insert_resource(struct resource *parent, struct resource *new)


关于platform resource有相关的函数进行对资源的操作。

struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
int platform_get_irq(struct platform_device *, unsigned int);
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
int platform_get_irq_byname(struct platform_device *, char *);

例如s3c24210的watchdog资源分配实例:
watchdog寄存器的基地址为0x5300000
#define S3C2410_PA_WATCHDOG (0x53000000)
#define S3C24XX_SZ_WATCHDOG SZ_1M

static struct resource s3c_wdt_resource[] = {
    [0] = {    
        .start = S3C24XX_PA_WATCHDOG,
        .end   = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
        .flags = IORESOURCE_MEM,    //内存映射
    },
    [1] = {    
        .start = IRQ_WDT,
        .end   = IRQ_WDT,
        .flags = IORESOURCE_IRQ,    //IRQ
    }

};


动态注册platform device例:
/linux/drivers/serial/8250.c

static int __init serial8250_init(void)
{
    int ret, i;

    if (nr_uarts > UART_NR)
        nr_uarts = UART_NR;

    printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
        "%d ports, IRQ sharing %sabled\n", nr_uarts,
        share_irqs ? "en" : "dis");

    for (i = 0; i < NR_IRQS; i++)
        spin_lock_init(&irq_lists[i].lock);

    ret = uart_register_driver(&serial8250_reg);
    if (ret)
        goto out;

    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);
    if (ret == 0)
        goto out;

    platform_device_del(serial8250_isa_devs);
 put_dev:
    platform_device_put(serial8250_isa_devs);
 unreg_uart_drv:
    uart_unregister_driver(&serial8250_reg);
 out:
    return ret;
}


也可以在编译的时候就确定设备的相关信息,调用 int platform_device_register(struct platform_device *);

/linux/arch/arm/mach-smdk2410/mach-smdk2410.c

static struct platform_device *smdk2410_devices[] __initdata = {
    &s3c_device_usb,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c,
    &s3c_device_iis,
};

static void __init smdk2410_init(void)
{
    platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); //静态增加一组soc设备,以便在加载驱动的时候匹配相关驱动
    smdk_machine_init();
}

int platform_add_devices(struct platform_device **devs, int num)
{
    int i, ret = 0;

    for (i = 0; i < num; i++) {
        ret = platform_device_register(devs[i]);  //实际上是调用 platform_device_register追加platform device
        if (ret) {
            while (--i >= 0)
                platform_device_unregister(devs[i]);
            break;
        }
    }

    return ret;
}

int platform_device_register(struct platform_device * pdev)
{
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
}

从上面看出这和动态增加一个platform device所做的动作基本上是一样的(device_initialize,platform_device_add)

例 watchdog设备定义:
struct platform_device s3c_device_wdt = {
    .name          = "s3c2410-wdt",
    .id          = -1,
    .num_resources      = ARRAY_SIZE(s3c_wdt_resource),
    .resource      = s3c_wdt_resource,
};


4 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 (*suspend_late)(struct platform_device *, pm_message_t state);
    int (*resume_early)(struct platform_device *);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
};

int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
    if (drv->suspend)
        drv->driver.suspend = platform_drv_suspend;
    if (drv->resume)
        drv->driver.resume = platform_drv_resume;
    return driver_register(&drv->driver);
}

指定platform device所属总线,同时如果为platform_driver中各项指定了接口,则为struct device_driver中相应的接口赋值。
那么是如何赋值的呢?

#define to_platform_driver(drv)    (container_of((drv), struct platform_driver, driver))

static int platform_drv_probe(struct device *_dev)
{
    struct platform_driver *drv = to_platform_driver(_dev->driver);
    struct platform_device *dev = to_platform_device(_dev);

    return drv->probe(dev);
}


从上面可以看出,是将struct device转换为struct platform_device和struct platform_driver.然后调用platform_driver中的相应接口函数来实现,
最后调用 driver_register()将platform driver注册到总线上。


/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
    ......
    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);
    if (ret == 0)
        goto out;

    ......
}
在设备成功进行了注册后,调用platform_driver_register()进行驱动注册。

最后,总线上注册有设备和相应的驱动,就会进行设备和驱动的匹配。
在找到一个设备和驱动的配对后, 驱动绑定是通过调用probe()由驱动核心自动完成的. 如果probe()成功,
驱动和设备就正常绑定了. 有三种不同的方法来进行配对:

-设备一被注册, 就检查对应总线下的各驱动, 看是否匹配. 平台设备应在系统启动过程的早期被注册

-当驱动通过platform_driver_register()被注册时, 就检查对应总线上所有未绑定的设备.驱动通常在启动过程的后期被注册或通过装载模块来注册.

-用platform_driver_probe()来注册驱动的效果跟用platform_driver_register()几乎相同, 不同点仅在于,如果再有设备注册, 驱动就不会再被枚举了. (这无关紧要, 因为这种接口只用在不可热插拔的设备上.)
驱动和设备的匹配仅仅是通过名称来匹配的
static int platform_match(struct device * dev, struct device_driver * drv)
{
    struct platform_device *pdev = container_of(dev, struct platform_device, dev);

    return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

小结:本节总结平台设备和驱动的模型,这部份知识可以作为我们深入了解具体平台设备驱动的基础。

阅读(4858) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~