linux platform 驅(qū)動(dòng)模型分析
一. 概述 platform設(shè)備和驅(qū)動(dòng)與linux設(shè)備模型密切相關(guān)。platform在linux設(shè)備模型中,其實(shí)就是一種虛擬總線沒有對(duì)應(yīng)的硬件結(jié)構(gòu)。它的主要作用就是管理系統(tǒng)的外設(shè)資源,比如io內(nèi)存,中斷信號(hào)線。現(xiàn)在大多數(shù)處理器芯片都是soc,如s3c2440,它包括處理器內(nèi)核(arm920t)和系統(tǒng)的外設(shè)(lcd接口,nandflash接口等)。linux在引入了platform機(jī)制之后,內(nèi)核假設(shè)所有的這些外設(shè)都掛載在platform虛擬總線上,以便進(jìn)行統(tǒng)一管理。
二. platform 總線 1. 在系統(tǒng)中platform對(duì)應(yīng)的文件drivers/base/platform.c,它不是作為一個(gè)模塊注冊(cè)到內(nèi)核的,關(guān)鍵的注冊(cè)總線的函數(shù)由系統(tǒng)初始化部分,對(duì)應(yīng)/init/main.c中的do_basic_setup函數(shù)間接調(diào)用。這里可以看出platform非常重要,要在系統(tǒng)其他驅(qū)動(dòng)加載之前注冊(cè)。下面分析platform總線注冊(cè)函數(shù)
- int __init platform_bus_init(void)
- {
- int error;
- early_platform_cleanup();
- error = device_register(&platform_bus);
-
- if (error)
- return error;
- error = bus_register(&platform_bus_type);
-
- if (error)
- device_unregister(&platform_bus);
- return error;
- }
這個(gè)函數(shù)向內(nèi)核注冊(cè)了一種總線。他首先由/drivers/base/init.c中的driver_init函數(shù)調(diào)用,driver_init函數(shù)由/init/main.c中的do_basic_setup函數(shù)調(diào)用,do_basic_setup這個(gè)函數(shù)由kernel_init調(diào)用,所以platform總線是在內(nèi)核初始化的時(shí)候就注冊(cè)進(jìn)了內(nèi)核。
2. platform_bus_type 總線結(jié)構(gòu)與設(shè)備結(jié)構(gòu)
(1) platform總線 設(shè)備結(jié)構(gòu)
- struct device platform_bus = {
- .init_name = "platform",
- };
platform總線也是一種設(shè)備,這里初始化一個(gè)device結(jié)構(gòu),設(shè)備名稱platform,因?yàn)闆]有指定父設(shè)備,所以注冊(cè)后將會(huì)在/sys/device/下出現(xiàn)platform目錄。
(2) 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_dev_attrs 設(shè)備屬性
platform_match match函數(shù),這個(gè)函數(shù)在當(dāng)屬于platform的設(shè)備或者驅(qū)動(dòng)注冊(cè)到內(nèi)核時(shí)就會(huì)調(diào)用,完成設(shè)備與驅(qū)動(dòng)的匹配工作。
platform_uevent 熱插拔操作函數(shù)
三. platform 設(shè)備 1. platform_device 結(jié)構(gòu)
- struct platform_device {
- const char * name;
- int id;
- struct device dev;
- u32 num_resources;
- struct resource * resource;
- struct platform_device_id *id_entry;
-
- struct pdev_archdata archdata;
- };
(1)platform_device結(jié)構(gòu)體中有一個(gè)struct resource結(jié)構(gòu),是設(shè)備占用系統(tǒng)的資源,定義在ioport.h中,如下
- struct resource {
- resource_size_t start;
- resource_size_t end;
- const char *name;
- unsigned long flags;
- struct resource *parent, *sibling, *child;
- };
(2) num_resources 占用系統(tǒng)資源的數(shù)目,一般設(shè)備都占用兩種資源,io內(nèi)存和中斷信號(hào)線。這個(gè)為兩種資源的總和。
2. 設(shè)備注冊(cè)函數(shù) platform_device_register
- int platform_device_register(struct platform_device *pdev)
- {
- device_initialize(&pdev->dev);
- return platform_device_add(pdev);
- }
這個(gè)函數(shù)首先初始化了platform_device的device結(jié)構(gòu),然后調(diào)用platform_device_add,這個(gè)是注冊(cè)函數(shù)的關(guān)鍵,下面分析platform_device_add:
- 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;
-
- if (pdev->id != -1)
- dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
- else
- dev_set_name(&pdev->dev, "%s", pdev->name);
-
- for (i = 0; i < pdev->num_resources; i++) {
- struct resource *p, *r = &pdev->resource[i];
-
- if (r->name == NULL)
- r->name = dev_name(&pdev->dev);
-
- p = r->parent;
- if (!p) {
- if (resource_type(r) == IORESOURCE_MEM)
- p = &iomem_resource;
- else if (resource_type(r) == IORESOURCE_IO)
- p = &ioport_resource;
- }
-
- if (p && insert_resource(p, r)) {
- printk(KERN_ERR
- "%s: failed to claim resource %d\n",
- dev_name(&pdev->dev), i);
- ret = -EBUSY;
- goto failed;
- }
- }
-
-
- pr_debug("Registering platform device '%s'. Parent at %s\n",
- dev_name(&pdev->dev), dev_name(pdev->dev.parent));
-
- ret = device_add(&pdev->dev);
-
- if (ret == 0)
- return ret;
- failed:
- while (--i >= 0) {
- struct resource *r = &pdev->resource[i];
- unsigned long type = resource_type(r);
- if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
- release_resource(r);
- }
- return ret;
- }
3. mini2440內(nèi)核注冊(cè)platform設(shè)備過程
因?yàn)橐环Nsoc確定之后,其外設(shè)模塊就已經(jīng)確定了,所以注冊(cè)platform設(shè)備就由板級(jí)初始化代碼來完成,在mini2440中是mach-mini2440.c的mini2440_machine_init函數(shù)中調(diào)用platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))來完成注冊(cè)。這個(gè)函數(shù)完成mini2440的所有platform設(shè)備的注冊(cè):
(1) platform_add_devices函數(shù)是platform_device_register的簡單封裝,它向內(nèi)核注冊(cè)一組platform設(shè)備
(2) mini2440_devices是一個(gè)platform_device指針數(shù)組,定義如下:
- static struct platform_device *mini2440_devices[] __initdata = {
- &s3c_device_usb,
- &s3c_device_rtc,
- &s3c_device_lcd,
- &s3c_device_wdt,
- &s3c_device_i2c0,
- &s3c_device_iis,
- &mini2440_device_eth,
- &s3c24xx_uda134x,
- &s3c_device_nand,
- &s3c_device_sdi,
- &s3c_device_usbgadget,
- };
這個(gè)就是mini2440的所有外設(shè)資源了,每個(gè)外設(shè)的具體定義在/arch/arm/plat-s3c24xx/devs.c,下面以s3c_device_lcd為例說明,其他的類似。s3c_device_lcd在devs.c中它定義為:
- struct platform_device s3c_device_lcd = {
- .name = "s3c2410-lcd",
- .id = -1,
- .num_resources = ARRAY_SIZE(s3c_lcd_resource),
- .resource = s3c_lcd_resource,
- .dev = {
- .dma_mask = &s3c_device_lcd_dmamask,
- .coherent_dma_mask = 0xffffffffUL
- }
- };
可以看出,它占用的資源s3c_lcd_resource,定義如下:
- static struct resource s3c_lcd_resource[] = {
- [0] = {
- .start = S3C24XX_PA_LCD,
- .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
- .flags = IORESOURCE_MEM,
- },
- [1] = {
- .start = IRQ_LCD,
- .end = IRQ_LCD,
- .flags = IORESOURCE_IRQ,
- }
- };
這是一個(gè)數(shù)組,有兩個(gè)元素,說明lcd占用了系統(tǒng)兩個(gè)資源,一個(gè)資源類型是IORESOURCE_MEM代表io內(nèi)存,起使地址S3C24XX_PA_LCD,這個(gè)是LCDCON1寄存器的地址。另外一個(gè)資源是中斷信號(hào)線。
四. platform設(shè)備驅(qū)動(dòng) 如果要將所寫的驅(qū)動(dòng)程序注冊(cè)成platform驅(qū)動(dòng),那么所做的工作就是初始化一個(gè)platform_driver,然后調(diào)用platform_driver_register進(jìn)行注冊(cè)。
1. 基本數(shù)據(jù)機(jī)構(gòu)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;
- struct platform_device_id *id_table;
- };
這是platform驅(qū)動(dòng)基本的數(shù)據(jù)結(jié)構(gòu),在驅(qū)動(dòng)程序中我們要做的就是聲明一個(gè)這樣的結(jié)構(gòu)并初始化。下面是lcd驅(qū)動(dòng)程序?qū)λ某跏蓟?br>
- static struct platform_driver s3c2412fb_driver = {
- .probe = s3c2412fb_probe,
- .remove = s3c2410fb_remove,
- .suspend = s3c2410fb_suspend,
- .resume = s3c2410fb_resume,
- .driver = {
- .name = "s3c2412-lcd",
- .owner = THIS_MODULE,
- },
- };
上面幾個(gè)函數(shù)是我們要實(shí)現(xiàn)的,它將賦值給device_driver中的相關(guān)成員,probe函數(shù)是用來查詢特定設(shè)備是夠真正存在的函數(shù)。當(dāng)設(shè)備從系統(tǒng)刪除的時(shí)候調(diào)用remove函數(shù)。
2. 注冊(cè)函數(shù)platform_driver_register
- 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;
- return driver_register(&drv->driver);
- }
這個(gè)函數(shù)首先使驅(qū)動(dòng)屬于platform_bus_type總線,將platform_driver結(jié)構(gòu)中的定義的probe,remove,shutdown賦值給device_driver結(jié)構(gòu)中的相應(yīng)成員,以供linux設(shè)備模型核心調(diào)用,然后調(diào)用driver_regster將設(shè)備驅(qū)動(dòng)注冊(cè)到linux設(shè)備模型核心中。
五. 各環(huán)節(jié)的整合 前面提到mini2440板級(jí)初始化程序?qū)⑺械膒latform設(shè)備注冊(cè)到了linux設(shè)備模型核心中,在/sys/devices/platform目錄中都有相應(yīng)的目錄表示。platform驅(qū)動(dòng)則是由各個(gè)驅(qū)動(dòng)程序模塊分別注冊(cè)到系統(tǒng)中的。但是他們是如何聯(lián)系起來的呢,這就跟linux設(shè)備模型核心有關(guān)系了。在ldd3中的linux設(shè)備模型的各環(huán)節(jié)的整合中有詳細(xì)的論述。這里簡要說明一下platform實(shí)現(xiàn)的方法。每當(dāng)注冊(cè)一個(gè)platform驅(qū)動(dòng)的時(shí)候就會(huì)調(diào)用driver_register,這個(gè)函數(shù)的調(diào)用會(huì)遍歷設(shè)備驅(qū)動(dòng)所屬總線上的所有設(shè)備,并對(duì)每個(gè)設(shè)備調(diào)用總線的match函數(shù)。platform驅(qū)動(dòng)是屬于platform_bus_type總線,所以調(diào)用platform_match函數(shù)。這個(gè)函數(shù)實(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);
-
-
- if (pdrv->id_table)
- return platform_match_id(pdrv->id_table, pdev) != NULL;
-
- return (strcmp(pdev->name, drv->name) == 0);
- }
這個(gè)函數(shù)將device結(jié)構(gòu)轉(zhuǎn)換為platform_devcie結(jié)構(gòu),將device_driver結(jié)構(gòu)轉(zhuǎn)換為platform_driver結(jié)構(gòu),并調(diào)用platform_match_id對(duì)設(shè)備與驅(qū)動(dòng)相關(guān)信息進(jìn)行比較。如果沒有比較成功會(huì)返回0,以便進(jìn)行下一個(gè)設(shè)備的比較,如果比較成功就會(huì)返回1,并且將device結(jié)構(gòu)中的driver指針指向這個(gè)驅(qū)動(dòng)。然后調(diào)用device_driver中的probe函數(shù),在lcd驅(qū)動(dòng)中就是s3c2412fb_probe。這個(gè)函數(shù)是我們要編寫的函數(shù)。這個(gè)函數(shù)檢測(cè)驅(qū)動(dòng)的狀態(tài),并且測(cè)試能否真正驅(qū)動(dòng)設(shè)備,并且做一些初始化工作。