3-Linux platform system

題圖:nipic

Linux platform system

platform是Linux內(nèi)的一種虛擬總線,稱為platform總線,相應(yīng)的設(shè)備稱為platform_device,而驅(qū)動(dòng)稱為platform_driver。platform總線、設(shè)備和驅(qū)動(dòng)這3個(gè)實(shí)體,總線將設(shè)備和驅(qū)動(dòng)綁定,在系統(tǒng)每注冊(cè)一個(gè)設(shè)備的時(shí)候,會(huì)尋找與之匹配的驅(qū)動(dòng);相反的,在系統(tǒng)每注冊(cè)一個(gè)驅(qū)動(dòng)的時(shí)候,會(huì)尋找與之匹配的設(shè)備,而匹配由總線完成。

先來(lái)看下platform的框架:
platform框架

platform框架其實(shí)比較直觀,就總線/設(shè)備/驅(qū)動(dòng)三部分,所以platform的實(shí)現(xiàn)一般就為三個(gè)步驟,先對(duì)platform設(shè)備進(jìn)行注冊(cè),再編寫(xiě)platform驅(qū)動(dòng),最后設(shè)備和驅(qū)動(dòng)進(jìn)行匹配,匹配成功則執(zhí)行probe探測(cè)函數(shù),probe函數(shù)里面對(duì)具體的功能實(shí)現(xiàn),下面對(duì)這三步的內(nèi)容進(jìn)行簡(jiǎn)單分析。

1.platform device


platform設(shè)備對(duì)應(yīng)的結(jié)構(gòu)體paltform_device,位于linux/platform_device.h中,如下:

struct platform_device {
    const char  * name;    //設(shè)備的名字,這將代替device->dev_id,用作sys/device下顯示的目錄名
    int     id;            //設(shè)備id,用于給插入給該總線并且具有相同name的設(shè)備編號(hào),如果只有一個(gè)設(shè)備的話填-1。
    struct device   dev;   //結(jié)構(gòu)體中內(nèi)嵌的device結(jié)構(gòu)體。
    u32     num_resources; //資源數(shù)。
    struct resource * resource;  //資源數(shù)。

    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

可以看到,platform_device封裝了指定的名字name、id、內(nèi)嵌device。

platform_device的注冊(cè)一般在平臺(tái)設(shè)備里面實(shí)現(xiàn),這邊舉例說(shuō)明下:

1.先要為設(shè)備提供platform_device結(jié)構(gòu)體,并賦值,這邊以i2c為例。

static struct resource s3c_i2c_resource[] = {
    [0] = {
        .start = S3C_PA_IIC,
        .end   = S3C_PA_IIC + SZ_4K - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_IIC,
        .end   = IRQ_IIC,
        .flags = IORESOURCE_IRQ,
    },
};

struct platform_device s3c_device_i2c0 = {
    .name         = "s3c-i2c",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(s3c_i2c_resource),
    .resource     = s3c_i2c_resource,
};

2.有了各設(shè)備的platform_device后,一般會(huì)統(tǒng)一放到用來(lái)注冊(cè)的platform_device,里面會(huì)包含各自platform設(shè)備,如下:

static struct platform_device *s3c_devices[] __initdata = 
{
    &s3c_device_i2c0,
    &s3c_device_spi,
    &s3c_device_hsmmc0,
    &s3c_device_hsmmc1,
}

3.最后使用platform_add_devices進(jìn)行將device添加到platform總線上。

platform_add_devices(s3c_devices, ARRAY_SIZE(s3c_devices));

該函數(shù)位于drivers/base/platform.c中,如下:

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

可以看到platform_device會(huì)掃描s3c_devices里面的每一個(gè)設(shè)備,并為它們進(jìn)行注冊(cè),這邊的注冊(cè)和注銷(xiāo)使用platform_device_register()platform_device_unregister()函數(shù)。

注冊(cè)后,同樣會(huì)在/sys/device/目錄下創(chuàng)建一個(gè)以name命名的目錄,并且創(chuàng)建軟連接到/sys/bus/platform/device下。

2.platform driver


platform驅(qū)動(dòng)對(duì)應(yīng)的結(jié)構(gòu)體paltform_driver,位于linux/platform_device.h中,如下:

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

與device類(lèi)似,driver里面嵌入的是device_driver,不過(guò)driver里面有實(shí)現(xiàn)函數(shù)probe、remove等。
platform_driver的實(shí)現(xiàn)也比較簡(jiǎn)單,這邊也以i2c為例,如下:

static struct platform_driver s3c_i2c_driver = {
    .probe      = s3c_i2c_probe,
    .remove     = s3c_i2c_remove,
    .id_table   = s3c_driver_ids,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "s3c-i2c",
        .pm = S3C_DEV_PM_OPS,
    },
};

static int __init i2c_adap_s3c_init(void)
{
    return platform_driver_register(&s3c24xx_i2c_driver);
}

static void __exit i2c_adap_s3c_exit(void)
{
    platform_driver_unregister(&s3c_i2c_driver);
}

module_init(i2c_adap_s3c_init);
module_exit(i2c_adap_s3c_exit);

platform驅(qū)動(dòng)的注冊(cè)和注銷(xiāo)分別用platform_driver_register()platform_driver_unregister()函數(shù)。

3.platform match


platform driver編寫(xiě)完成后,就是要執(zhí)行driver的probe函數(shù)。probe函數(shù)能否執(zhí)行的關(guān)鍵在于,device中的name與driver中的name是否相等,相等時(shí)說(shuō)明匹配成功,就可以執(zhí)行probe函數(shù)實(shí)現(xiàn)具體的功能了。

這邊也追蹤下如何執(zhí)行到match函數(shù),驅(qū)動(dòng)會(huì)調(diào)用platform_driver_register進(jìn)行注冊(cè),該函數(shù)會(huì)將總線指向platform總線,而platform總線的結(jié)構(gòu)體如下:

struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_attrs  = platform_dev_attrs,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};

可以看到platform_matchplatform_bus的一個(gè)成員,platform_match的實(shí)現(xiàn)如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

platform_match會(huì)調(diào)用platform_match_id()函數(shù),匹配函數(shù)platform_match_id()會(huì)去搜索所有已經(jīng)注冊(cè)到platform總線上的設(shè)備,如果匹配到則返回id,沒(méi)找到則返回NULL如下:

static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {
        printk("pdev->name:%s\n",pdev->name);
        printk("id->name:%s\n",id->name);
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}

這邊兩句printk調(diào)試信息是我自己加上去的,在調(diào)試的時(shí)候可以很直觀的觀察到兩個(gè)name是否相等,在Linux驅(qū)動(dòng)中很多設(shè)備都是通過(guò)這一原理來(lái)進(jìn)行device和driver的匹配,如下面幾個(gè)模塊:
1.drivers/i2c/i2c-core.c中的i2c_match_id

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}

2.drivers/spi/spi.c中的spi_match_id

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
                        const struct spi_device *sdev)
{
    while (id->name[0]) {
        if (!strcmp(sdev->modalias, id->name))
            return id;
        id++;
    }
    return NULL;
}

3.drivers/pci/pci-driver.c中的pci_match_id

const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
                     struct pci_dev *dev)
{
    if (ids) {
        while (ids->vendor || ids->subvendor || ids->class_mask) {
            if (pci_match_one_device(ids, dev))
                return ids;
            ids++;
        }
    }
    return NULL;
}

4.drivers/usb/core/driver.c中的usb_match_id

const struct usb_device_id *usb_match_id(struct usb_interface *interface,
                     const struct usb_device_id *id)
{
    if (id == NULL)
        return NULL;

    for (; id->idVendor || id->idProduct || id->bDeviceClass ||
           id->bInterfaceClass || id->driver_info; id++) {
        if (usb_match_one_id(interface, id))
            return id;
    }

    return NULL;
}

5.drivers/hid/hid-core.c中的hid_match_id

static const struct hid_device_id *hid_match_id(struct hid_device *hdev,
        const struct hid_device_id *id)
{
    for (; id->bus; id++)
        if (hid_match_one_id(hdev, id))
            return id;

    return NULL;
}

如果發(fā)現(xiàn)打印的name不一樣或有一個(gè)沒(méi)有打印出來(lái),這是候就要去檢測(cè)對(duì)應(yīng)的device和driver,可能就是總線沒(méi)注冊(cè)上或驅(qū)動(dòng)id、name不一致。

Linux platform system的分析就到這邊,有感悟時(shí)會(huì)持續(xù)會(huì)更新。

注:以上內(nèi)容都是本人在學(xué)習(xí)過(guò)程積累的一些心得,難免會(huì)有參考到其他文章的一些知識(shí),如有侵權(quán),請(qǐng)及時(shí)通知我,我將及時(shí)刪除或標(biāo)注內(nèi)容出處,如有錯(cuò)誤之處也請(qǐng)指出,進(jìn)行探討學(xué)習(xí)。文章只是起一個(gè)引導(dǎo)作用,詳細(xì)的數(shù)據(jù)解析內(nèi)容還請(qǐng)查看Linux相關(guān)教程,感謝您的查閱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容