1. 程式人生 > >Linux驅動中的platform匯流排分析

Linux驅動中的platform匯流排分析

最近在複習platform匯流排相關的知識,碰到一篇感覺不錯的文章。

概述

從Linux2.6核心起,引入一套新的驅動管理和註冊機制:platform_device 和 platform_driver 。Linux 中大部分的裝置驅動,都可以使用這套機制,裝置用 platform_device 表示;驅動用 platform_driver 進行註冊。

linux_platform_driver 機制和傳統的device_driver機制(即:通過 driver_register 函式進行註冊)相比,一個十分明顯的優勢在於platform機制將裝置本身的資源註冊進核心,由核心統一管理,在驅動程式中用使用這些資源時,通過platform device提供的標準介面進行申請並使用。

platform 是一個虛擬的地址匯流排,相比 PCI、USB,它主要用於描述SOC上的片上資源。platform 所描述的資源有一個共同點:在CPU 的總線上直接取址。平臺裝置會分到一個名稱(用在驅動繫結中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源。

platform 匯流排下驅動的開發步驟是:

1、 裝置

需要實現的結構體是:platform_device 。

1)初始化 resource 結構變數

2)初始化 platform_device 結構變數

3)向系統註冊裝置:platform_device_register。

以上三步,必須在裝置驅動載入前完成,即執行platform_driver_register()之前,原因是驅動註冊時需要匹配核心中所有已註冊的裝置名。platform_driver_register()中新增device到核心最終還是呼叫的device_add函式。Platform_device_add和device_add最主要的區別是多了一步insert_resource(p, r),即將platform資源(resource)新增進核心,由核心統一管理。

2、驅動

驅動註冊中,需要實現的結構體是:platform_driver 。

在驅動程式的初始化函式中,呼叫了platform_driver_register()註冊 platform_driver 。需要注意的是:platform_driver 和 platform_device 中的 name 變數的值必須是相同的 。這樣在 platform_driver_register() 註冊時,會將當前註冊的 platform_driver 中的 name 變數的值和已註冊的所有 platform_device 中的 name 變數的值進行比較,只有找到具有相同名稱的 platform_device 才能註冊成功。當註冊成功時,會呼叫 platform_driver 結構元素 probe 函式指標。

platform_driver_register()的註冊過程:


    1 platform_driver_register(&s3c2410fb_driver)

    2 driver_register(&drv->driver)

    3 bus_add_driver(drv)

    4 driver_attach(drv)

    5 bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)

    6 __driver_attach(struct device * dev, void * data7 driver_probe_device(drv, dev)

    8 really_probe(dev, drv)

    在really_probe()中:為裝置指派管理該裝置的驅動:dev->driver = drv, 呼叫probe()函式初始化裝置:drv->probe(dev)

platform_device_系列函式及其設備註冊的作用

http://blog.csdn.net/maopig/article/details/7410055
platform_device_系列函式,實際上是註冊了一個叫platform的虛擬匯流排。使用約定是如果一個不屬於任何匯流排的裝置,例如藍芽,串列埠等裝置,都需要掛在這個虛擬總線上。

driver/base/platform.c

//platform裝置宣告
struct device platform_bus = {
    .bus_id        = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

//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,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

int __init platform_bus_init(void)
{
    int error;

    error = device_register(&platform_bus);//註冊了"platform"的裝置
    if (error)
        return error;
    error = bus_register(&platform_bus_type);//註冊了叫"platform"的匯流排
    if (error)
        device_unregister(&platform_bus);
    return error;
}


//這裡在platform總線上掛裝置
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_bus

    pdev->dev.bus = &platform_bus_type;//設定掛在platform總線上

    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);
    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;
}
EXPORT_SYMBOL_GPL(platform_device_add);


//常用的platform_device_register,內部呼叫了platform_device_add,將裝置掛在了platform總線上
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

要用註冊一個platform驅動的步驟:

1,註冊裝置platform_device_register
2,註冊驅動platform_driver_register
註冊時候的兩個名字必須一樣,才能match上,才能work,例如:

struct platform_device pxa3xx_device_nand = {
    .name        = "pxa3xx-nand",
    .id        = -1,
    .dev        = {
        .dma_mask = &pxa3xx_nand_dma_mask,
        .coherent_dma_mask = DMA_BIT_MASK(32),
    },
    .resource    = pxa3xx_resource_nand,
    .num_resources    = ARRAY_SIZE(pxa3xx_resource_nand),
};

static struct platform_driver pxa3xx_nand_driver = {
    .driver = {
        .name    = "pxa3xx-nand",
    },
    .probe        = pxa3xx_nand_probe,
    .remove        = pxa3xx_nand_remove,
#ifdef CONFIG_PM
    .suspend    = pxa3xx_nand_suspend,
    .resume        = pxa3xx_nand_resume,
#endif
};

而且device註冊的時候,可以給driver傳引數

struct device {
    struct klist        klist_children;
    struct klist_node    knode_parent;    /* node in sibling list */
    struct klist_node    knode_driver;
    struct klist_node    knode_bus;
    struct device        *parent;

    struct kobject kobj;
    char    bus_id[BUS_ID_SIZE];    /* position on parent bus */
    struct device_type    *type;
    unsigned        is_registered:1;
    unsigned        uevent_suppress:1;

    struct semaphore    sem;    /* semaphore to synchronize calls to
                     * its driver.
                     */

    struct bus_type    *bus;        /* type of bus device is on */
    struct device_driver *driver;    /* which driver has allocated this
                       device */
    void        *driver_data;    /* data private to the driver */
    void        *platform_data;    /* Platform specific data, device
                       core doesn't touch it */
    struct dev_pm_info    power;

#ifdef CONFIG_NUMA
    int        numa_node;    /* NUMA node this device is close to */
#endif
    u64        *dma_mask;    /* dma mask (if dma'able device) */
    u64        coherent_dma_mask;/* Like dma_mask, but for
                         alloc_coherent mappings as
                         not all hardware supports
                         64 bit addresses for consistent
                         allocations such descriptors. */

    struct device_dma_parameters *dma_parms;

    struct list_head    dma_pools;    /* dma pools (if dma'ble) */

    struct dma_coherent_mem    *dma_mem; /* internal for coherent mem
                         override */
    /* arch specific additions */
    struct dev_archdata    archdata;

    spinlock_t        devres_lock;
    struct list_head    devres_head;

    /* class_device migration path */
    struct list_head    node;
    struct class        *class;
    dev_t            devt;    /* dev_t, creates the sysfs "dev" */
    struct attribute_group    **groups;    /* optional groups */

    void    (*release)(struct device *dev);
};
傳引數都是通過platform_data傳,所以定義為void *
    void        *platform_data;    /* Platform specific data, device




static struct pxa3xx_nand_platform_data XXX_nand_info = {
    .parts            = android_256m_v75_partitions,
    .nr_parts        = ARRAY_SIZE(android_256m_v75_partitions),
};

static void __init XXX_init_nand(void)
{
    pxa3xx_device_nand.dev.platform_data = &XXX_nand_info;
    platform_device_register(&pxa3xx_device_nand);
}




static int __init pxa3xx_nand_probe(struct platform_device *pdev)
{
    struct pxa3xx_nand_platform_data *pdata;
    struct nand_chip *this;
    struct pxa3xx_nand_info *info;
    struct resource *res;
    struct clk *clk = NULL, *smc_clk = NULL;
    int status = -1;
    struct mtd_partition *parts;
    unsigned int data_buf_len;
#ifdef CONFIG_MTD_NAND_PXA3xx_DMA
    unsigned int buf_len;
#endif
    int i, ret = 0;
#ifdef CONFIG_MTD_PARTITIONS
    int err;
#endif

    pdata = pdev->dev.platform_data;
....
....
....
}

下面解釋一下pxa_register_device函式

    pxa_set_ohci_info(&XXX_ohci_info);

void __init pxa_set_ohci_info(struct pxaohci_platform_data *info)
{
    pxa_register_device(&pxa27x_device_ohci, info);
}

void __init pxa_register_device(struct platform_device *dev, void *data)
{
    int ret;

    dev->dev.platform_data = data;

    ret = platform_device_register(dev);
    if (ret)
        dev_err(&dev->dev, "unable to register device: %d\n", ret);
}

其實上,也就是給driver傳引數,通過dev.platform_data。

到這裡,platform_device系列函式,基本算通了,系列函式還有一堆設定的函式,和device_register同級別的那些功能函式,用法基本差不多,只不過都將裝置掛在了platform總線上。

platform_device_register向系統註冊裝置

platform_driver_register向系統註冊驅動,過程中在系統尋找註冊的裝置(根據.name),找到後執行.probe進行初始化。

**************************************************************

device_register()和platform_device_register()的區別(轉載)  


首先看device和platform_device區別
由struct platform_device {
const char * name; //NOTE:此處裝置的命名應和相應驅動程式命名一致
u32 id;            //以實現driver binding
struct device dev;
u32 num_resources;
struct resource * resource;
};
可知:platform_device由device派生而來

核心中關於Platform devices的註釋
Platform devices
~~~~~~~~~~~~~~~~
Platform devices are devices that typically appear as autonomous
entities in the system. This includes legacy port-based devices and
host bridges to peripheral buses, and most controllers integrated
into system-on-chip platforms. What they usually have in common
is direct addressing from a CPU bus. Rarely, a platform_device will
be connected through a segment of some other kind of bus; but its
registers will still be directly addressable.

Platform devices are given a name, used in driver binding, and a
list of resources such as addresses and IRQs.
個人理解:Platform devices是相對獨立的裝置,擁有各自獨自的資源(addresses and IRQs)

一個Platform devices例項:

static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb, //片上的各個裝置
&s3c_device_lcd, //下面以s3c_device_lcd為例
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
};

/* LCD Controller */
static struct resource s3c_lcd_resource[] = { //LCD的兩個資源
[0] = {
.start = S3C2410_PA_LCD,
.end = S3C2410_PA_LCD + S3C2410_SZ_LCD,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}

};

struct platform_device s3c_device_lcd = {//s3c_device_lcd裝置
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = { //device例項
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};

s3c_device_lcd的resource中硬體地址:

#define S3C2410_LCDREG(x) ((x) + S3C2410_VA_LCD)

/* LCD control registers */
#define S3C2410_LCDCON1 S3C2410_LCDREG(0x00)
#define S3C2410_LCDCON2 S3C2410_LCDREG(0x04)
#define S3C2410_LCDCON3 S3C2410_LCDREG(0x08)
#define S3C2410_LCDCON4 S3C2410_LCDREG(0x0C)
#define S3C2410_LCDCON5 S3C2410_LCDREG(0x10)

#define S3C2410_LCDCON1_CLKVAL(x) ((x) << 8)
#define S3C2410_LCDCON1_MMODE (1<<7)
#define S3C2410_LCDCON1_DSCAN4 (0<<5)
#define S3C2410_LCDCON1_STN4 (1<<5)
#define S3C2410_LCDCON1_STN8 (2<<5)
#define S3C2410_LCDCON1_TFT (3<<5)
--------------------------
#define S3C2410_ADDR(x) (0xF0000000 + (x))

/* LCD controller */
#define S3C2410_VA_LCD S3C2410_ADDR(0x00600000)
#define S3C2410_PA_LCD (0x4D000000)
#define S3C2410_SZ_LCD SZ_1M

再分析device_register()和platform_device_register()的實現程式碼:

device_register()------------------------

/**
* device_register - register a device with the system.
* @dev: pointer to the device structure
*
* This happens in two clean steps - initialize the device
* and add it to the system. The two steps can be called
* separately, but this is the easiest and most common.
* I.e. you should only call the two helpers separately if
* have a clearly defined need to use and refcount the device
* before it is added to the hierarchy.
*/

int device_register(struct device *dev)
{
device_initialize(dev); //初始化裝置結構
return device_add(dev); //新增裝置到裝置層
}

platform_device_register()--------------------
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*
*/
int platform_device_register(struct platform_device * pdev)
{
device_initialize(&pdev->dev); //初始化裝置結構
return platform_device_add(pdev); //新增一個片上的裝置到裝置層
}

由以上函式可知:device_register()和platform_device_register()都會首先初始化裝置,區別在於第二步:其實platform_device_add()包括device_add(),只不過要先註冊resources

platform_device_add()----------------------
/**
* platform_device_add - add a platform device to device hierarchy
* @pdev: platform device we're adding
*
* This is part 2 of platform_device_register(), though may be called
* separately _iff_ pdev was allocated by platform_device_alloc().
*/
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;
pdev->dev.bus = &platform_bus_type;

/*++++++++++++++
The platform_device.dev.bus_id is the canonical name for the devices.
It's built from two components:

* platform_device.name ... which is also used to for driver matching.
* platform_device.id ... the device instance number, or else "-1"
to indicate there's only one.

These are concatenated, so name/id "serial"/0 indicates bus_id "serial.0", and
"serial/3" indicates bus_id "serial.3"; both would use the platform_driver
named "serial". While "my_rtc"/-1 would be bus_id "my_rtc" (no instance id)
and use the platform_driver called "my_rtc".
++++++++++++++*/

if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id);
else //"-1" indicate there's only one
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
for (i = 0; i < pdev->num_resources; i++) { //遍歷裝置資源個數,如LCD的兩個資源:控制器和IRQ
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id;
p = r->parent;
if (!p) { //resources分為兩種IORESOURCE_MEM和IORESOURCE_IO
          //CPU對外設IO埠實體地址的編址方式有兩種:I/O對映方式和記憶體對映方式
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);
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;
}

相關參考+++++++++++++++++++++++
device_initialize()------------------
/** </drivers/base/core.c>
* device_initialize - init device structure.
* @dev: device.
*
* This prepares the device for use by other layers,
* including adding it to the device hierarchy.
* It is the first half of device_register(), if called by
* that, though it can also be called separately, so one
* may use @dev's fields (e.g. the refcount).
*/

void device_initialize(struct device *dev)
{
kobj_set_kset_s(dev, devices_subsys);
kobject_init(&dev->kobj);
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_init_wakeup(dev, 0);
set_dev_node(dev, -1);
}
device_add(struct device *dev)-------------
/**
* device_add - add device to device hierarchy.
* @dev: device.
*
* This is part 2 of device_register(), though may be called
* separately _iff_ device_initialize() has been called separately.
*
* This adds it to the kobject hierarchy via kobject_add(), adds it
* to the global and sibling lists for the device, then
* adds it to the other relevant subsystems of the driver model.
*/
結構體resource----------------------
/* < /include/linux/ioport.h>
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
---------------------------

在8250.c(driver/serial/8250.c)的初始化函式serial8250_init()中,給出了一個很簡單的例子

static struct platform_device *serial8250_isa_devs;

……

//create a platform_device

serial8250_isa_devs = platform_device_alloc(“serial8250”,PLAT8250_DEV_LEGACY);

platform_device_add(serial8250_isa_devs); //add the platform_device to system

platform_driver_register(&serial8250_isa_driver);//then register the platform_driver

還有另外一個比較類似的比較,就是driver_register和platform_driver_register的比較platform_driver_register(&xx_driver) 會向系統註冊xx_driver這個驅動程式,這個函式會根據 xx_driver中的.name內容,搜尋系統註冊的device中有沒有這個platform_device,如果有,就會執行 platform_driver(也就是xx_driver的型別)中的.probe函式。

對只需要初始化執行一次的函式都加上__init屬性,__init 巨集告訴編譯器如果這個模組被編譯到核心則把這個函式放到(.init.text)段,module_exit的引數解除安裝時同__init類似,如果驅動被編譯進核心,則__exit巨集會忽略清理函式,因為編譯進核心的模組不需要做清理工作,顯然__init和__exit對動態載入的模組是無效的,只支援完全編譯進核心。

probe()函式是什麼時候被呼叫,裝置和驅動是怎麼聯絡起來的

probe()函式是什麼時候被呼叫,裝置和驅動是怎麼聯絡起來的??
platform_add_devices(ldd6410_devices, ARRAY_SIZE(ldd6410_devices)); //這是bsp中新增所有的裝置–》 platform_device_register(devs[i]);//註冊平臺裝置—》platform_device_add(pdev);將平臺裝置加入到platform_bus中—》device_add(&pdev->dev);

下面是驅動:

static int __init gpio_led_init(void)
{
 return platform_driver_register(&gpio_led_driver);   //註冊平臺驅動
}
platform_driver_register(&gpio_led_driver) ----》driver_register(&drv->driver);----》bus_add_driver(drv); //新增驅動到匯流排 ---》driver_attach(drv);//為驅動尋找相應的裝置----》

int driver_attach(struct device_driver *drv)
{
 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);   //遍歷裝置匯流排尋找驅動
}



-----》__driver_attach()

static int __driver_attach(struct device *dev, void *data)
{
 struct device_driver *drv = data;

 /*
  * Lock device and try to bind to it. We drop the error
  * here and always return 0, because we need to keep trying
  * to bind to devices and some drivers will return an error
  * simply if it didn't support the device.
  *
  * driver_probe_device() will spit a warning if there 
  * is an error.
  */

 if (drv->bus->match && !drv->bus->match(dev, drv))   //通過match判斷驅動和裝置是否匹配,這裡通過比較dev和drv中的裝置名來判斷,所以裝置名需要唯一
  return 0;

 if (dev->parent) /* Needed for USB */
  down(&dev->parent->sem);
 down(&dev->sem);
 if (!dev->driver)
  driver_probe_device(drv, dev);  //   驅動和裝置繫結
 up(&dev->sem);
 if (dev->parent)
  up(&dev->parent->sem);

 return 0;
}


driver_probe_device(drv, dev);  ---》really_probe(dev, drv);




static int really_probe(struct device *dev, struct device_driver *drv)
{
 int ret = 0;

 atomic_inc(&probe_count);
 pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
   drv->bus->name, __func__, drv->name, dev->bus_id);
 WARN_ON(!list_empty(&dev->devres_head));

 dev->driver = drv;
 if (driver_sysfs_add(dev)) {
  printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
   __func__, dev->bus_id);
  goto probe_failed;
 }

 if (dev->bus->probe) {   
  ret = dev->bus->probe(dev);
  if (ret)
   goto probe_failed;
 } else if (drv->probe) {
  ret = drv->probe(dev);  //這裡才真正呼叫了驅動的probe
  if (ret)
   goto probe_failed;
 }

 driver_bound(dev);
 ret = 1;
 pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
   drv->bus->name, __func__, dev->bus_id, drv->name);
 goto done;

probe_failed:
 devres_release_all(dev);
 driver_sysfs_remove(dev);
 dev->driver = NULL;

 if (ret != -ENODEV && ret != -ENXIO) {
  /* driver matched but the probe failed */
  printk(KERN_WARNING
         "%s: probe of %s failed with error %d\n",
         drv->name, dev->bus_id, ret);
 }
 /*
  * Ignore errors returned by ->probe so that the next driver can try
  * its luck.
  */
 ret = 0;
done:
 atomic_dec(&probe_count);
 wake_up(&probe_waitqueue);
 return ret;
}

Platform Device and Drivers

從< linux/platform_device.h>我們可以瞭解Platform bus上面的驅動模型介面:platform_device,platform_driver。和PCI和USB這些大結構的匯流排不同,虛擬匯流排Platform bus使用最小結構來整合SOC processer上的各種外設,或者各種“legacy”之間的互聯。

Platform device
典型的Platform device是系統中的各種自主裝置,包括各種橋接在外圍總線上的port-based device和host,以及各種整合在SOC platform上的控制器。他們都有一個特點,那就是CPU BUS可以直接定址,或者特殊的情況platform_device連線在其他總線上,但是它的暫存器是可以被直接定址的。
Platform device有一個名字,用來進行driver的繫結;還有諸如中斷,地址之類的一些資源列表

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


Platform drivers
Platform driver滿足標準driver model,對driver的discovery/enumeration是在driver外部進行的,driver提供了probe()和 remove()方法.Platfomr dirvers通過標準模型提供power management和shutdown通知。

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()函式必須驗證指定裝置的硬體是否真的存在,probe()可以使用裝置的資源,包括時鐘,platform_data等,Platform driver可以通過下面的函式完成對驅動的註冊:
int platform_driver_register(struct platform_driver *drv);
一般來說裝置是不能被熱插拔的,所以可以將probe()函式放在init段裡面來節省driver執行時候的記憶體開銷:
int platform_driver_probe(struct platform_driver *drv,
int (probe)(struct platform_device ))

Device Enumeration
作為一個規則,平臺(一般來說是板級)啟動程式碼會註冊所有的Platform device:
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev);
一般來說只會註冊那些實際存在的裝置,不過也存在特殊的情況,比如kernel可能需要與一個不在板子上的外部網路介面卡工作,或者那些不掛在任何總線上的控制器。
一般情況下,韌體啟動的過程會輸出一個描述板子上所有存在裝置的表。如果沒有這個表,系統啟動程式碼建立正確的裝置的唯一方法就是為一個特定的板子編譯一個kernel。這種board-specific kernel廣泛用在嵌入式和一般系統的開發上。
在大部分情況下,裝置的memory和IRQ資源不足夠讓驅動正常工作。board setup code會用device的platform_data域來為裝置提供一些額外的資源。
嵌入式系統上的裝置會頻繁地使用一個或者多個時鐘,這些時鐘因為節電的原因只有在真正使用的時候才會被開啟,系統在啟動過程中會為裝置分配時鐘,可以通過clk_get(&pdev->dev, clock_name)來獲得需要的時鐘。

Legacy Drivers : Device Probing
一些driver並不會完全遵守標準driver model,這些driver會去註冊自己的platform device,而不是讓系統來完成註冊。這是不值得推
薦的,主要用來相容以前的一些舊裝置。可以通過下面的API來支援這些legacy driver,一般這些API使用在不支援熱插拔的driver上面:
struct platform_device *platform_device_alloc(
const char *name, int id);
可以使用platform_device_alloc動態地建立一個裝置,一個更好的方法是,通過下面的函式動態建立一個裝置,並把這個設備註冊到系統中:
struct platform_device *platform_device_register_simple(
const char *name, int id,
struct resource *res, unsigned int nres);

Device Naming and Driver Binding
platform_device.dev.bus_id是一個裝置在總線上的名字,它包含兩部分:
* platform_device.name 裝置名字,用來進行driver的匹配
* platform_device.id 裝置例項的標號,如果是-1,表示同樣名字的裝置只有一個
舉個簡單的例子,name/id是“serial/1”則它的bus_id就是serial.1 如果name/id是“serial/0”則它的bus_id就是serial.0 ,如果它的name/id是“serial/-1”則它的bus_id就是serial。
driver的繫結是通過driver core自動完成的,完成driver和device的匹配後以後會自動執行probe()函式,如果函式執行成功,則driver和device就繫結在一起了,drvier和device匹配的方法有3種:
* 當一個設備註冊的時候,他會在總線上尋找匹配的driver,platform device一般在系統啟動很早的時候就註冊了
* 當一個驅動註冊[platform_driver_register()]的時候,他會遍歷所有總線上的裝置來尋找匹配,在啟動的過程驅動的註冊一般比較晚,或者在模組載入的時候
* 當一個驅動註冊[platform_driver_probe()]的時候, 功能上和使用platform_driver_register()是一樣的,唯一的區別是它不能被以後其他的device probe了,也就是說這個driver只能和一個device繫結。

  Platform device 和 Platform driver實際上是cpu匯流排可以直接定址的裝置和驅動,他們掛載在一個虛擬的匯流排platform_bus_type上,是一種bus-specific裝置和驅動。與其他bus-specific驅動比如pci是一樣的。他們都是將device和device_driver加了一個warpper產生,仔細看看platform_device就可以看到它必然包含一個device dev,而platform_driver也一樣,它必然包含一個device_driver driver。
  所有的裝置通過bus_id掛在總線上,多個device可以共用一個driver,但是一個device不可以對應多個driver。驅動去註冊時候會根據裝置名尋找裝置,沒有裝置會註冊失敗,註冊的過程會通過probe來進行相應資源的申請,以及硬體的初始化,如果probe執行成功,則device和driver的繫結就成功了。設備註冊的時候同樣會在總線上尋找相應的驅動,如果找到他也會試圖繫結,繫結的過程同樣是執行probe。

platform_driver_probe與platform_driver_register的區別