platform設備添加流程

今天我以fb設備的註冊過程來分析platform設備的添加流程


platform總線是kernel中最近加入的一種虛擬總線,它被用來連接處在僅有最少基本組件的總線上的那些設備.這樣的總線包括許多片上系統上的那些用來整合外設的總線, 也包括一些"古董" PC上的連接器; 但不包括像PCI或USB這樣的有龐大正規說明的總線.

平臺設備
~~~~~~
    平臺設備通常指的是系統中的自治體, 包括老式的基於端口的設備和連接外設總線的北橋(host bridges),以及集成在片上系統中的絕大多數控制器. 它們通常擁有的一個共同特徵是直接編址於CPU總線上. 即使在某些罕見的情況下, 平臺設備會通過某段其他類型的總線連入系統, 它們的寄存器也會被直接編址.平臺設備會分到一個名稱(用在驅動綁定中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源.

那什麼情況可以使用platform driver機制編寫驅動呢?

     我的理解是隻要和內核本身運行依賴性不大的外圍設備(換句話說只要不在內核運行所需的一個最小系統之內的設備),相對獨立的,擁有各自獨自的資源(addresses and IRQs),都可以用platform_driver實現。如:lcd,usb,uart等,都可以用platfrom_driver寫,而timer,irq等最小系統之內的設備則最好不用platfrom_driver機制,實際上內核實現也是這樣的。下面繼續我們的分析過程。

首先要定義一個platform_device,我們先來看一下platform_device結構的定義,如下所示:


// include/linux/platform_device.h:
  16struct platform_device {
  17        const char      * name;
  18        u32             id;
  19        struct device   dev;
  20        u32             num_resources;
  21        struct resource * resource;
  22};

下面是對應的FB設備的變量定義

// arch/arm/mach-pxa/generic.c
 229static struct platform_device pxafb_device = {
 230        .name           = "pxa2xx-fb",
 231        .id             = -1,
 232        .dev            = {
 233                .platform_data  = &pxa_fb_info,
 234                .dma_mask       = &fb_dma_mask,
 235                .coherent_dma_mask = 0xffffffff,
 236        },
 237        .num_resources  = ARRAY_SIZE(pxafb_resources),
 238        .resource       = pxafb_resources,
 239};


    由上可以看出,name成員表示設備名,系統正是通過這個名字來與驅動綁定的,所以驅動裏面相應的設備名必須與該項相符合;id表示設備編號,id的值爲-1表示只有一個這樣的設備。

    該結構中比較重要的一個成員就是resource, Linux設計了這個通用的數據結構來描述各種I/O資源(如:I/O端口、外設內存、DMA和IRQ等)。它的定義如下:

// include/linux/ioport.h:
  16struct resource {
  17        const char *name;
  18        unsigned long start, end;
  19        unsigned long flags;
  20        struct resource *parent, *sibling, *child;
  21};

下面關於這方面的內容,參考了http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html

struct resource 是linux對掛接在4G總線空間上的設備實體的管理方式。

    一個獨立的掛接在cpu總線上的設備單元,一般都需要一段線性的地址空間來描述設備自身,linux是怎麼管理所有的這些外部"物理地址範圍段",進而給用戶和linux自身一個比較好的觀察4G總線上掛接的一個個設備實體的簡潔、統一級聯視圖的呢?

    linux採用struct resource結構體來描述一個掛接在cpu總線上的設備實體(32位cpu的總線地址範圍是0~4G):

    resource->start           描述設備實體在cpu總線上的線性起始物理地址;
    resource->end          描述設備實體在cpu總線上的線性結尾物理地址;
    resource->name           描述這個設備實體的名稱,這個名字開發人員可以隨意起,但最好貼切;
    resource->flag        描述這個設備實體的一些共性和特性的標誌位;

    只需要瞭解一個設備實體的以上4項,linux就能夠知曉這個掛接在cpu總線的上的設備實體的基本使用情況,也就是 [resource->start, resource->end]這段物理地址現在是空閒着呢,還是被什麼設備佔用着呢?
    linux會堅決避免將一個已經被一個設備實體使用的總線物理地址區間段[resource->start, resource->end],再分配給另一個後來的也需要這個區間段或者區間段內部分地址的設備實體,進而避免設備之間出現對同一總線物理地址段的重複引用,而造成對唯一物理地址的設備實體二義性.
    以上的4個屬性僅僅用來描述一個設備實體自身,或者是設備實體可以用來自治的單元,但是這不是linux所想的,linux需要管理4G物理總線的所有空間,所以掛接到總線上的形形色色的各種設備實體,這就需要鏈在一起,因此resource結構體提供了另外3個成員:指針parent、sibling和 child:分別爲指向父親、兄弟和子資源的指針,它們的設置是爲了以一種樹的形式來管理各種I/O資源,以root source爲例,root->child(*pchild)指向root所有孩子中地址空間最小的一個;pchild->sibling是兄弟鏈表的開頭,指向比自己地址空間大的兄弟。

    屬性flags是一個unsigned long類型的32位標誌值,用以描述資源的屬性。比如:資源的類型、是否只讀、是否可緩存,以及是否已被佔用等。下面是一部分常用屬性標誌位的定義


// include/linux/ioport.h:
  29/*
  30 * IO resources have these defined flags.
  31 */
  32#define IORESOURCE_BITS         0x000000ff      /* Bus-specific bits */
  33
  34#define IORESOURCE_IO           0x00000100      /* Resource type */
  35#define IORESOURCE_MEM          0x00000200
  36#define IORESOURCE_IRQ          0x00000400
  37#define IORESOURCE_DMA          0x00000800
  38
  39#define IORESOURCE_PREFETCH     0x00001000      /* No side effects */
  40#define IORESOURCE_READONLY     0x00002000
  41#define IORESOURCE_CACHEABLE    0x00004000
  42#define IORESOURCE_RANGELENGTH  0x00008000
  43#define IORESOURCE_SHADOWABLE   0x00010000
  44#define IORESOURCE_BUS_HAS_VGA  0x00080000
  45
  46#define IORESOURCE_DISABLED     0x10000000
  47#define IORESOURCE_UNSET        0x20000000
  48#define IORESOURCE_AUTO         0x40000000
  49#define IORESOURCE_BUSY         0x80000000      /* Driver has marked this resource busy */


    下面來看我們所使用的LCD所佔用的資源,如下所示:

// arch/arm/mach-pxa/generic.c
static struct resource pxafb_resources[] = {
    [0] = {
        .start    = 0x44000000,
        .end    = 0x4400ffff,
        .flags    = IORESOURCE_MEM,
    },
    [1] = {
        .start    = IRQ_LCD,
        .end    = IRQ_LCD,
        .flags    = IORESOURCE_IRQ,
    },
};

    由上可知LCD佔用的資源包括兩類,一類是MEM類型,一類是IRQ類型。MEME類型資源對應的物理地址範圍是 0x44000000 - 0x4400ffff;IRQ類型資源對應的物理地址範圍是IRQ_LCD,查看相應的定義:

// include/asm-arm/arch-pxa/irqs.h:
  15#ifdef CONFIG_PXA27x
  16#define PXA_IRQ_SKIP    0
  17#else
  18#define PXA_IRQ_SKIP    7
  19#endif
  20
  21#define PXA_IRQ(x)      ((x) - PXA_IRQ_SKIP)

  43#define IRQ_LCD         PXA_IRQ(17)     /* LCD Controller Service Request */

    我們所使用的處理器爲PXA255,所以對應的PXA_IRQ_SKIP應該爲7,所以IRQ_LCD = 10,也就是它對應的中斷信號線爲10。


設置完了platform_device的相關成員後,下一步就是調用platform_add_devices()來向系統中添加該設備了,首先來看它的定義:

// drivers/base/platform.c:
/**
 *    platform_add_devices - add a numbers of platform devices
 *    @devs: array of platform devices to add
 *    @num: number of platform devices in array
 */
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]);
        if (ret) {
            while (--i >= 0)
                platform_device_unregister(devs[i]);
            break;
        }
    }

    return ret;
}


   我們目前只關注LCD設備,所以不管for循環,關鍵的一句就是platform_device_register(),該函數用來進行平臺設備的註冊,首先來看它的定義:

// drivers/base/platform.c:
/**
 *    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_initialize()來初始化該設備,然後調用platform_device_add()來添加該設備。關於device_initialize()我們暫且不分析,在這裏只關注platform_device_add()

// drivers/base/platform.c:
 229/**
 230 * platform_device_add - add a platform device to device hierarchy
 231 * @pdev: platform device we're adding
 232 *
 233 * This is part 2 of platform_device_register(), though may be called
 234 * separately _iff_ pdev was allocated by platform_device_alloc().
 235 */
 236int platform_device_add(struct platform_device *pdev)
 237{
 238        int i, ret = 0;
 239
 240        if (!pdev)
 241                return -EINVAL;
 242
 243        if (!pdev->dev.parent)
 244                pdev->dev.parent = &platform_bus;
 245
 246        pdev->dev.bus = &platform_bus_type;
 247
 248        if (pdev->id != -1)
 249                snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
 250                         pdev->id);
 251        else
 252                strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
 253
 254        for (i = 0; i < pdev->num_resources; i++) {
 255                struct resource *p, *r = &pdev->resource[i];
 256
 257                if (r->name == NULL)
 258                        r->name = pdev->dev.bus_id;
 259
 260                p = r->parent;
 261                if (!p) {
 262                        if (r->flags & IORESOURCE_MEM)
 263                                p = &iomem_resource;
 264                        else if (r->flags & IORESOURCE_IO)
 265                                p = &ioport_resource;
 266                }
 267
 268                if (p && insert_resource(p, r)) {
 269                        printk(KERN_ERR
 270                               "%s: failed to claim resource %d/n",
 271                               pdev->dev.bus_id, i);
 272                        ret = -EBUSY;
 273                        goto failed;
 274                }
 275        }
 276
 277        pr_debug("Registering platform device '%s'. Parent at %s/n",
 278                 pdev->dev.bus_id, pdev->dev.parent->bus_id);
 279
 280        ret = device_add(&pdev->dev);
 281        if (ret == 0)
 282                return ret;
 283
 284 failed:
 285        while (--i >= 0)
 286                if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
 287                        release_resource(&pdev->resource[i]);
 288        return ret;
 289}

    先看243 - 244兩行,如果該設備的父指針爲空,則將它的父指針指向platform_bus,這是一個device類型的變量,它的定義如下:

// drivers/base/platform.c:
  26struct device platform_bus = {
  27        .bus_id         = "platform",
  28};

   緊接着,246行設置設備的總線類型爲platform_bus_type

// drivers/base/platform.c:
 892struct bus_type platform_bus_type = {
 893        .name           = "platform",
 894        .dev_attrs      = platform_dev_attrs,
 895        .match          = platform_match,
 896        .uevent         = platform_uevent,
 897        .pm             = PLATFORM_PM_OPS_PTR,
 898};


    248 - 252行設置設備指向的dev結構的bus_id成員,由前面可知,我們只有一個LCD設備,所以 pdev->id = -1,因而對應的 bus_id = "pxa2xx-fb",關於這個bus_id,在定義的時候,內核開發者是後面加了一個註釋: /* position on parent bus */

    254 - 275行進行資源處理,首先設置資源的名稱,如果name成員爲空的話,就將該成員設置爲我們前面已經賦值的bus_id,也就是"pxa2xx-fb"
    260 - 266行先將 p 指向我們當前處理的資源的 parent 指針成員,如果 p 指向NULL,也就是我們當前處理的資源的 parent 指針成員指向NULL的話,再檢測當前處理的資源的類型,如果是MEM類型的,則設置 p 指向 iomem_resource ,如果是IO類型的,則使 p 指向 ioport_resource,這兩個均是 struct resource 類型的變量,它們的定義如下:

// kernel/resource.c
  23 struct resource ioport_resource = {
  24        .name       = "PCI IO",
  25        .start      = 0,
  26        .end        = IO_SPACE_LIMIT,
  27        .flags      = IORESOURCE_IO,
  28};
  29 EXPORT_SYMBOL(ioport_resource);
  30
  31 struct resource iomem_resource = {
  32        .name       = "PCI mem",
  33        .start      = 0,
  34        .end        = -1,
  35        .flags      = IORESOURCE_MEM,
  36};
  37 EXPORT_SYMBOL(iomem_resource);
   
// include/asm/io.h:
#define IO_SPACE_LIMIT 0xffffffff // 這並不是針對 ARM 平臺的定義,針對 ARM 平臺的定義我沒有找到,所以暫且列一個在這裏佔位

    關於這兩個struct resource類型的變量,在網絡上搜到了如下的信息:(http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html)

    物理內存頁面是重要的資源。從另一個角度看,地址空間本身,或者物理存儲器在地址空間中的位置,也是一種資源,也要加以管理 -- resource管理地址空間資源。

    內核中有兩棵resource樹,一棵是iomem_resource,另一棵是ioport_resource,分別代表着兩類不同性質的地址資源。兩棵樹的根也都是resource數據結構,不過這兩個數據結構描述的並不是用於具體操作對象的地址資源,而是概念上的整個地址空間。
    將主板上的ROM空間納入iomem_resource樹中;系統固有的I/O類資源則納入ioport_resource樹

// kernel/resource.c
----------------------------------------
struct resource ioport_resource = {
    .name       = "PCI IO",
    .start     = 0,
    .end        = IO_SPACE_LIMIT,
    .flags     = IORESOURCE_IO,
};

struct resource iomem_resource = {
    .name       = "PCI mem",
    .start     = 0,
    .end        = -1,
    .flags     = IORESOURCE_MEM,
};


/usr/src/linux/include/asm-i386/io.h
#define IO_SPACE_LIMIT 0xffff
0 ~ 0xffff   <===> 64K



   繼續我們的函數, 268 - 276行將我們當前處理的資源插入到 p 指針指向的resource樹裏面。這裏面只有一個關鍵的函數insert_resource()

// kernel/resource.c
 416/**
 417 * insert_resource - Inserts a resource in the resource tree
 418 * @parent: parent of the new resource
 419 * @new: new resource to insert
 420 *
 421 * Returns 0 on success, -EBUSY if the resource can't be inserted.
 422 *
 423 * This function is equivalent to request_resource when no conflict
 424 * happens. If a conflict happens, and the conflicting resources
 425 * entirely fit within the range of the new resource, then the new
 426 * resource is inserted and the conflicting resources become children of
 427 * the new resource.
 428 */
 429int insert_resource(struct resource *parent, struct resource *new)
 430{
 431        struct resource *conflict;
 432
 433        write_lock(&resource_lock);
 434        conflict = __insert_resource(parent, new);
 435        write_unlock(&resource_lock);
 436        return conflict ? -EBUSY : 0;
 437}

    資源鎖resource_lock對所有資源樹進行讀寫保護,任何代碼段在訪問某一顆資源樹之前都必須先持有該鎖,該鎖的定義也在 resource.c中。鎖機制我們暫且不管,該函數裏面關鍵的就是__insert_resource()函數:

// kernel/resource.c:
 365/*
 366 * Insert a resource into the resource tree. If successful, return NULL,
 367 * otherwise return the conflicting resource (compare to __request_resource())
 368 */
 369static struct resource * __insert_resource(struct resource *parent, struct resource *new)
 370{
 371        struct resource *first, *next;
 372
 373        for (;; parent = first) {
 374                first = __request_resource(parent, new);
 375                if (!first)
 376                        return first;
 377
 378                if (first == parent)
 379                        return first;
 380
 381                if ((first->start > new->start) || (first->end < new->end))
 382                        break;
 383                if ((first->start == new->start) && (first->end == new->end))
 384                        break;
 385        }
 386
 387        for (next = first; ; next = next->sibling) {
 388                /* Partial overlap? Bad, and unfixable */
 389                if (next->start < new->start || next->end > new->end)
 390                        return next;
 391                if (!next->sibling)
 392                        break;
 393                if (next->sibling->start > new->end)
 394                        break;
 395        }
 396
 397        new->parent = parent;
 398        new->sibling = next->sibling;
 399        new->child = first;
 400
 401        next->sibling = NULL;
 402        for (next = first; next; next = next->sibling)
 403                next->parent = new;
 404
 405        if (parent->child == first) {
 406                parent->child = new;
 407        } else {
 408                next = parent->child;
 409                while (next->sibling != first)
 410                        next = next->sibling;
 411                next->sibling = new;
 412        }
 413        return NULL;
 414}


    374行有個__request_resource(),它完成實際的資源分配工作。如果參數new所描述的資源中的一部分或全部已經被其它節點所佔用,則函數返回與new相沖突的resource結構的指針。否則就返回NULL。該函數的源代碼如下:

// kernel/resource.c:
 142/* Return the conflict entry if you can't request it */
 143static struct resource * __request_resource(struct resource *root, struct resource *new)
 144{
 145        resource_size_t start = new->start;
 146        resource_size_t end = new->end;
 147        struct resource *tmp, **p;
 148
 149        if (end < start)
 150                return root;
 151        if (start < root->start)
 152                return root;
 153        if (end > root->end)
 154                return root;
 155        p = &root->child;
 156        for (;;) {
 157                tmp = *p;
 158                if (!tmp || tmp->start > end) {
 159                        new->sibling = tmp;
 160                        *p = new;
 161                        new->parent = root;
 162                        return NULL;
 163                }
 164                p = &tmp->sibling;
 165                if (tmp->end < start)
 166                        continue;
 167                return tmp;
 168        }
 169}

    149 - 150行判斷是否是一段有效的資源,151 - 154行判斷資源是否在root的範圍之內,否則就返回root,表示與根結點衝突。
    156 - 168行遍歷根節點root的child鏈表,以便檢查是否有資源衝突,並將new插入到child鏈表中的合適位置(child鏈表是以I/O資源物理地址從低到高的順序排列的)。爲此,它用tmp指針指向當前正被掃描的resource結構,用指針p指向前一個resource結構的sibling指針成員變量,p的初始值爲指向root->sibling。For循環體的執行步驟如下:

  (1)讓tmp指向當前正被掃描的resource結構(tmp=*p)。

  (2)判斷tmp指針是否爲空(tmp指針爲空說明已經遍歷完整個child鏈表),或者當前被掃描節點的起始位置start是否比new的結束位置end還要大。只要這兩個條件之一成立的話,就說明沒有資源衝突,於是就可以把new鏈入child鏈表中:①設置new的sibling指針指向當前正被掃描的節點tmp(new->sibling=tmp);②當前節點tmp的前一個兄弟節點的sibling指針被修改爲指向new這個節點(*p=new);③將new的parent指針設置爲指向root。然後函數就可以返回了(返回值NULL表示沒有資源衝突)。

  (3)如果上述兩個條件都不成立,這說明當前被掃描節點的資源域有可能與new相沖突(實際上就是兩個閉區間有交集),因此需要進一步判斷。爲此它首先修改指針p,讓它指向tmp->sibling,以便於繼續掃描child鏈表。然後,判斷tmp->end是否小於new->start,如果小於,則說明當前節點tmp和new沒有資源衝突,因此執行continue語句,繼續向下掃描child鏈表。否則,如果tmp->end大於或等於new->start,則說明tmp->[start,end]和new->[start,end]之間有交集。所以返回當前節點的指針tmp,表示發生資源衝突。



    繼續回到platform_device_add()函數裏面,如果insert_resource()成功,下一步就會調用280行device_add()函數來將設備添加到設備樹裏面。這個函數暫且不做分析。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


     下面來看platform_driver驅動的註冊過程,一般分爲三個步驟:

     1、定義一個platform_driver結構
     2、初始化這個結構,指定其probe、remove等函數,並初始化其中的driver變量
     3、實現其probe、remove等函數 

    platform_device對應的驅動是struct platform_driver,它的定義如下

// include/linux/platform_device.h:
  48struct platform_driver {
  49        int (*probe)(struct platform_device *);
  50        int (*remove)(struct platform_device *);
  51        void (*shutdown)(struct platform_device *);
  52        int (*suspend)(struct platform_device *, pm_message_t state);
  53        int (*suspend_late)(struct platform_device *, pm_message_t state);
  54        int (*resume_early)(struct platform_device *);
  55        int (*resume)(struct platform_device *);
  56        struct device_driver driver;
  57};

    可見,它包含了設備操作的幾個功能函數,同樣重要的是,它還包含了一個device_driver結構。剛纔提到了驅動程序中需要初始化這個變量。下面看一下這個變量的定義,位於include/linux/device.h中:

// include/linux/device.h:
 120struct device_driver {
 121        const char              *name;
 122        struct bus_type         *bus;
 123
 124        struct module           *owner;
 125        const char              *mod_name;      /* used for built-in modules */
 126
 127        int (*probe) (struct device *dev);
 128        int (*remove) (struct device *dev);
 129        void (*shutdown) (struct device *dev);
 130        int (*suspend) (struct device *dev, pm_message_t state);
 131        int (*resume) (struct device *dev);
 132        struct attribute_group **groups;
 133
 134        struct driver_private *p;
 135};

    需要注意這兩個變量:name和owner。那麼的作用主要是爲了和相關的platform_device關聯起來,owner的作用是說明模塊的所有者,驅動程序中一般初始化爲THIS_MODULE。

   對於我們的LCD設備,它的platform_driver變量就是:

// drivers/video/pxafb.c:
1384static struct platform_driver pxafb_driver = {
1385        .probe          = pxafb_probe,
1386#ifdef CONFIG_PM
1387        .suspend        = pxafb_suspend,
1388        .resume         = pxafb_resume,
1389#endif
1390        .driver         = {
1391                .name   = "pxa2xx-fb",
1392        },
1393};


    由上可知pxafb_driver.driver.name的值與前面我們講過的platform_device裏面的name成員的值是一致的,內核正是通過這個一致性來爲驅動程序找到資源,即platform_device中的resource。

    上面把驅動程序中涉及到的主要結構都介紹了,下面主要說一下驅動程序中怎樣對這些結構進行處理,以使驅動程序能運行。相信大家都知道module_init()這個宏。驅動模塊加載的時候會調用這個宏。它接收一個函數爲參數,作爲它的參數的函數將會對上面提到的platform_driver進行處理。看我們的實例:這裏的module_init()要接收的參數爲pxafb_init這個函數,下面是這個函數的定義:

// drivers/video/pxafb.c:
1411 int __devinit pxafb_init(void)
1412{
1413#ifndef MODULE
1414        char *option = NULL;
1415
1416        if (fb_get_options("pxafb", &option))
1417                return -ENODEV;
1418        pxafb_setup(option);
1419#endif
1420        return platform_driver_register(&pxafb_driver);
1421}
1422
1423 module_init(pxafb_init);

    注意函數體的最後一行,它調用的是platform_driver_register這個函數。這個函數定義於driver/base/platform.c中,定義如下:

// drivers/base/platform.c
 439/**
 440 * platform_driver_register
 441 * @drv: platform driver structure
 442 */
 443int platform_driver_register(struct platform_driver *drv)
 444{
 445        drv->driver.bus = &platform_bus_type;
 446        if (drv->probe)
 447                drv->driver.probe = platform_drv_probe;
 448        if (drv->remove)
 449                drv->driver.remove = platform_drv_remove;
 450        if (drv->shutdown)
 451                drv->driver.shutdown = platform_drv_shutdown;
 452        if (drv->suspend)
 453                drv->driver.suspend = platform_drv_suspend;
 454        if (drv->resume)
 455                drv->driver.resume = platform_drv_resume;
 456        if (drv->pm)
 457                drv->driver.pm = &drv->pm->base;
 458        return driver_register(&drv->driver);
 459}
 460EXPORT_SYMBOL_GPL(platform_driver_register);

    由上可知,它的功能先是爲上面提到的plarform_driver中的driver這個結構中的probe、remove這些變量指定功能函數,最後調用driver_register()進行設備驅動的註冊。在註冊驅動的時候,這個函數會以上面提到的name成員的值爲搜索內容,搜索系統中註冊的device中有沒有與這個name值相一致的device,如果有的話,那麼接着就會執行platform_driver 裏probe函數。

    到目前爲止,內核就已經知道了有這麼一個驅動模塊。內核啓動的時候,就會調用與該驅動相關的probe函數。我們來看一下probe函數實現了什麼功能。
    probe函數的原型爲
    int xxx_probe(struct platform_device *pdev)
    即它的返回類型爲int,接收一個platform_device類型的指針作爲參數。返回類型就是我們熟悉的錯誤代碼了,而接收的這個參數呢,我們上面已經說過,驅動程序爲設備服務,就需要知道設備的信息。而這個參數,就包含了與設備相關的信息。
    probe函數接收到plarform_device這個參數後,就需要從中提取出需要的信息。它一般會通過調用內核提供的 platform_get_resource和platform_get_irq等函數來獲得相關信息。如通過 platform_get_resource獲得設備的起始地址後,可以對其進行request_mem_region和ioremap等操作,以便應用程序對其進行操作。通過platform_get_irq得到設備的中斷號以後,就可以調用request_irq函數來向系統申請中斷。這些操作在設備驅動程序中一般都要完成。
    在完成了上面這些工作和一些其他必須的初始化操作後,就可以向系統註冊我們在/dev目錄下能看在的設備文件了。舉一個例子,在音頻芯片的驅動中,就可以調用register_sound_dsp來註冊一個dsp設備文件,lcd的驅動中就可以調用register_framebuffer來註冊fb設備文件。這個工作完成以後,系統中就有我們需要的設備文件了。而和設備文件相關的操作都是通過一個file_operations 來實現的。在調用register_sound_dsp等函數的時候,就需要傳遞一個file_operations 類型的指針。這個指針就提供了可以供用戶空間調用的write、read等函數。file_operations結構的定義位於 include/linux/fs.h中,列出如下:
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*dir_notify)(struct file *filp, unsigned long arg);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};
    到目前爲止,probe函數的功能就完成了。
    當用戶打開一個設備,並調用其read、write等函數的時候,就可以通過上面的file_operations來找到相關的函數。所以,用戶驅動程序還需要實現這些函數,具體實現和相關的設備有密切的關係,這裏就不再介紹了。

    關於我們所使用的LCD設備的xxx_probe()函數的分析可查看文章 <<pxafb驅動程序分析>>
    下面看我們所使用的LCD設備的 probe() 函數:

// drivers/video/pxafb.c:
1271int __init pxafb_probe(struct platform_device *dev)
1272{
1273        struct pxafb_info *fbi;
1274        struct pxafb_mach_info *inf;
1275        int ret;
1276
1277        dev_dbg(dev, "pxafb_probe/n");
1278
1279        inf = dev->dev.platform_data;
1280        ret = -ENOMEM;
1281        fbi = NULL;
1282        if (!inf)
1283                goto failed;
1284
1285#ifdef CONFIG_FB_PXA_PARAMETERS
1286        ret = pxafb_parse_options(&dev->dev, g_options);
1287        if (ret < 0)
1288                goto failed;
1289#endif
1290
1291#ifdef DEBUG_VAR
1292        /* Check for various illegal bit-combinations. Currently only
1293         * a warning is given. */
1294
1295        if (inf->lccr0 & LCCR0_INVALID_CONFIG_MASK)
1296                dev_warn(&dev->dev, "machine LCCR0 setting contains illegal bits: %08x/n",
1297                        inf->lccr0 & LCCR0_INVALID_CONFIG_MASK);
1298        if (inf->lccr3 & LCCR3_INVALID_CONFIG_MASK)
1299                dev_warn(&dev->dev, "machine LCCR3 setting contains illegal bits: %08x/n",
1300                        inf->lccr3 & LCCR3_INVALID_CONFIG_MASK);
1301        if (inf->lccr0 & LCCR0_DPD &&
1302            ((inf->lccr0 & LCCR0_PAS) != LCCR0_Pas ||
1303             (inf->lccr0 & LCCR0_SDS) != LCCR0_Sngl ||
1304             (inf->lccr0 & LCCR0_CMS) != LCCR0_Mono))
1305                dev_warn(&dev->dev, "Double Pixel Data (DPD) mode is only valid in passive mono"
1306                         " single panel mode/n");
1307        if ((inf->lccr0 & LCCR0_PAS) == LCCR0_Act &&
1308            (inf->lccr0 & LCCR0_SDS) == LCCR0_Dual)
1309                dev_warn(&dev->dev, "Dual panel only valid in passive mode/n");
1310        if ((inf->lccr0 & LCCR0_PAS) == LCCR0_Pas &&
1311             (inf->upper_margin || inf->lower_margin))
1312                dev_warn(&dev->dev, "Upper and lower margins must be 0 in passive mode/n");
1313#endif
1314
1315        dev_dbg(&dev->dev, "got a %dx%dx%d LCD/n",inf->xres, inf->yres, inf->bpp);
1316        if (inf->xres == 0 || inf->yres == 0 || inf->bpp == 0) {
1317                dev_err(&dev->dev, "Invalid resolution or bit depth/n");
1318                ret = -EINVAL;
1319                goto failed;
1320        }
1321        pxafb_backlight_power = inf->pxafb_backlight_power;
1322        pxafb_lcd_power = inf->pxafb_lcd_power;
1323        fbi = pxafb_init_fbinfo(&dev->dev);
1324        if (!fbi) {
1325                dev_err(&dev->dev, "Failed to initialize framebuffer device/n");
1326                ret = -ENOMEM; // only reason for pxafb_init_fbinfo to fail is kmalloc
1327                goto failed;
1328        }
1329
1330        /* Initialize video memory */
1331        ret = pxafb_map_video_memory(fbi);
1332        if (ret) {
1333                dev_err(&dev->dev, "Failed to allocate video RAM: %d/n", ret);
1334                ret = -ENOMEM;
1335                goto failed;
1336        }
1337
1338        ret = request_irq(IRQ_LCD, pxafb_handle_irq, SA_INTERRUPT, "LCD", fbi);
1339        if (ret) {
1340                dev_err(&dev->dev, "request_irq failed: %d/n", ret);
1341                ret = -EBUSY;
1342                goto failed;
1343        }
1344
1345        /*
1346         * This makes sure that our colour bitfield
1347         * descriptors are correctly initialised.
1348         */
1349        pxafb_check_var(&fbi->fb.var, &fbi->fb);
1350        pxafb_set_par(&fbi->fb);
1351
1352        platform_set_drvdata(dev, fbi);
1353
1354        ret = register_framebuffer(&fbi->fb);
1355        if (ret < 0) {
1356                dev_err(&dev->dev, "Failed to register framebuffer device: %d/n", ret);
1357                goto failed;
1358        }
1359
1360#ifdef CONFIG_PM
1361        // TODO
1362#endif
1363
1364#ifdef CONFIG_CPU_FREQ
1365        fbi->freq_transition.notifier_call = pxafb_freq_transition;
1366        fbi->freq_policy.notifier_call = pxafb_freq_policy;
1367        cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER);
1368        cpufreq_register_notifier(&fbi->freq_policy, CPUFREQ_POLICY_NOTIFIER);
1369#endif
1370
1371        /*
1372         * Ok, now enable the LCD controller
1373         */
1374        set_ctrlr_state(fbi, C_ENABLE);
1375
1376        return 0;
1377
1378failed:
1379        platform_set_drvdata(dev, NULL);
1380        kfree(fbi);
1381        return ret;
1382}





參考文獻:
struct--resource                http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html
驅動程序模型-platform            http://www.ourkernel.com/bbs/archiver/?tid-67.html
Linux對I/O端口資源的管理            http://www.host01.com/article/server/00070002/0542417251875372.htm
Linux對I/O端口資源的管理(ZZ)         http://hi.baidu.com/zengzhaonong/blog/item/0d6f6909e2aa5dad2fddd444.html


platform_device和platform_driver        http://linux.chinaunix.net/techdoc/net/2008/09/10/1031351.shtml
linux resource, platform_device和驅動的關係    http://blog.csdn.net/wawuta/archive/2007/03/14/1529621.aspx

發佈了6 篇原創文章 · 獲贊 6 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章