platform設備和驅動與linux設備模型密切相關。platform在linux設備模型中,其實就是一種虛擬總線沒有對應的硬件結構。它的主要作用就是管理系統的外設資源,比如io內存,中斷信號線。現在大多數處理器芯片都是soc,如s3c2440,它包括處理器內核(arm920t)和系統的外設(lcd接口,nandflash接口等)。linux在引入了platform機制之後,內核假設所有的這些外設都掛載在platform虛擬總線上,以便進行統一管理。
二. platform 總線
1. 在系統中platform對應的文件drivers/base/platform.c,它不是作爲一個模塊註冊到內核的,關鍵的註冊總線的函數由系統初始化部分,對應/init/main.c中的do_basic_setup函數間接調用。這裏可以看出platform非常重要,要在系統其他驅動加載之前註冊。下面分析platform總線註冊函數
- 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);
- //註冊platform_bus_type總線到內核
- if (error)
- device_unregister(&platform_bus);
- return error;
- }
2. platform_bus_type 總線結構與設備結構
(1) platform總線 設備結構
- struct device platform_bus = {
- .init_name = "platform",
- };
(2) platform總線 總線結構
- 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_match match函數,這個函數在當屬於platform的設備或者驅動註冊到內核時就會調用,完成設備與驅動的匹配工作。
platform_uevent 熱插拔操作函數
三. platform 設備
1. platform_device 結構
- struct platform_device {
- const char * name;
- int id;
- struct device dev;
- u32 num_resources;
- struct resource * resource;
- struct platform_device_id *id_entry;
- /* arch specific additions */
- struct pdev_archdata archdata;
- };
- struct resource {
- resource_size_t start;
- resource_size_t end;
- const char *name;
- unsigned long flags;
- struct resource *parent, *sibling, *child;
- };
2. 設備註冊函數 platform_device_register
- int platform_device_register(struct platform_device *pdev)
- {
- device_initialize(&pdev->dev);
- return platform_device_add(pdev);
- }
- 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設備的父設備一般都是platform_bus,所以註冊後的platform設備都出現在/sys/devices/platform_bus下
- pdev->dev.bus = &platform_bus_type;
- //掛到platform總線上
- if (pdev->id != -1)
- dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
- else
- dev_set_name(&pdev->dev, "%s", pdev->name);
- //設置設備名字,這個名字與/sys/devices/platform_bus下的名字對應
- 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;
- }
- }
- //上面主要是遍歷設備所佔用的資源,找到對應的父資源,如果沒有定義,那麼根據資源的類型,分別賦予iomem_resource和ioport_resource,然後調用insert_resource插入資源。
- //這樣系統的資源就形成了一個樹形的數據結構,便於系統的管理
- 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;
- }
因爲一種soc確定之後,其外設模塊就已經確定了,所以註冊platform設備就由板級初始化代碼來完成,在mini2440中是mach-mini2440.c的mini2440_machine_init函數中調用platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))來完成註冊。這個函數完成mini2440的所有platform設備的註冊:
(1) platform_add_devices函數是platform_device_register的簡單封裝,它向內核註冊一組platform設備
(2) mini2440_devices是一個platform_device指針數組,定義如下:
- 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,
- };
- 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
- }
- };
- 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,
- }
- };
四. platform設備驅動
如果要將所寫的驅動程序註冊成platform驅動,那麼所做的工作就是初始化一個platform_driver,然後調用platform_driver_register進行註冊。
1. 基本數據機構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;
- };
- 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,
- },
- };
2. 註冊函數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);
- }
五. 各環節的整合
前面提到mini2440板級初始化程序將它所有的platform設備註冊到了linux設備模型核心中,在/sys/devices/platform目錄中都有相應的目錄表示。platform驅動則是由各個驅動程序模塊分別註冊到系統中的。但是他們是如何聯繫起來的呢,這就跟linux設備模型核心有關係了。在ldd3中的linux設備模型的各環節的整合中有詳細的論述。這裏簡要說明一下platform實現的方法。每當註冊一個platform驅動的時候就會調用driver_register,這個函數的調用會遍歷設備驅動所屬總線上的所有設備,並對每個設備調用總線的match函數。platform驅動是屬於platform_bus_type總線,所以調用platform_match函數。這個函數實現如下:
- 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);
- /* match against the id table first */
- 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);
- }