Linux设备驱动模型之platform总线
生活随笔
收集整理的這篇文章主要介紹了
Linux设备驱动模型之platform总线
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1 平臺設備和驅動初識
platform是一個虛擬的地址總線,相比pci,usb,它主要用于描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個共同點,就是在cpu的總線上直接取址。
平臺設備會分到一個名稱(用在驅動綁定中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
平臺驅動遵循標準驅動模型的規范, 也就是說發現/列舉(discovery/enumeration)在驅動之外處理, 而
由驅動提供probe()和remove方法. 平臺驅動按標準規范對電源管理和關機通告提供支持
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()總應該核實指定的設備硬件確實存在;平臺設置代碼有時不能確定這一點. 枚舉(probing)可以使用的設備資源包括時鐘及設備的platform_data.(譯注: platform_data定義在device.txt中的"基本設備結構體"中.)
平臺驅動通過普通的方法注冊自身
int platform_driver_register(struct platform_driver *drv);
或者, 更常見的情況是已知設備不可熱插拔, probe()過程便可以駐留在一個初始化區域(init section)
中,以便減少驅動的運行時內存占用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));
設備列舉
按規定, 應由針對平臺(也適用于針對板)的設置代碼來注冊平臺設備
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)
一般的規則是只注冊那些實際存在的設備, 但也有例外. 例如, 某外部網卡未必會裝配在所有的板子上,
或者某集成控制器所在的板上可能沒掛任何外設, 而內核卻需要被配置來支持這些網卡和控制器
有些情況下, 啟動固件(boot firmware)會導出一張裝配到板上的設備的描述表. 如果沒有這張表, 通常
就只能通過編譯針對目標板的內核來讓系統設置代碼安裝正確的設備了. 這種針對板的內核在嵌入式和自定
義的系統開發中是比較常見的.
多數情況下, 分給平臺設備的內存和中斷請求號資源是不足以讓設備正常工作的. 板設置代碼通常會用設備
的platform_data域來存放附加信息, 并向外提供它們.
嵌入式系統時常需要為平臺設備提供一個或多個時鐘信號. 除非被用到, 這些時鐘一般處于靜息狀態以節電.
系統設置代碼也負責為設備提供這些時鐘, 以便設備能在它們需要是調用
clk_get(&pdev->dev, clock_name).
也可以用如下函數來一次性完成分配空間和注冊設備的任務
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)
設備命名和驅動綁定
platform_device.dev.bus_id是設備的真名. 它由兩部分組成:
? ?? ???*platform_device.name ... 這也被用來匹配驅動
? ?? ???*platform_device.id ...? ?? ???設備實例號, 或者用"-1"表示只有一個設備.
連接這兩項, 像"serial"/0就表示bus_id為"serial.0", "serial"/3表示bus_id為"serial.3";
上面二例都將使用名叫"serial"的平臺驅動. 而"my_rtc"/-1的bus_id為"my_rtc"(無實例號), 它的
平臺驅動為"my_rtc".
2 平臺總線
下面我們看看與platform相關的操作
平臺總線的初始化
int __init platform_bus_init(void)
{
? ? int error;
? ? error = device_register(&platform_bus);
? ? if (error)
? ?? ???return error;
? ? error =??bus_register(&platform_bus_type);
? ? if (error)
? ?? ???device_unregister(&platform_bus);
? ? return error;
}
這段初始化代碼創建了一個platform設備,以后屬于platform類型的設備就會以此為parent,增加的設備會出現在/sys/devices/platform目錄下
[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth??floppy.0??i8042??pcspkr??power??serial8250??uevent??vesafb.0
緊接著注冊名為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,
};
int platform_device_add(struct platform_device *pdev)
{......
? ?? ???pdev->dev.parent = &platform_bus;??//增加的platform設備以后都以platform_bus(platform設備)為父節點
? ?? ???pdev->dev.bus = &platform_bus_type; //platform類型設備都掛接在platform總線上 /sys/bus/platform/
......
}
3 platform device的注冊
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
1)動態分配一個名為name的platform設備
struct platform_object {
? ? struct platform_device pdev;
? ? char name[1];
};
struct platform_device *platform_device_alloc(const char *name, int id)
{
? ? struct platform_object *pa;
? ? pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);//由于 platform_object內name只有一個字節,所以需要多分配strlen(name)長度
? ? if (pa) {
? ?? ???strcpy(pa->name, name);
? ?? ???pa->pdev.name = pa->name;
? ?? ???pa->pdev.id = id;
? ?? ???device_initialize(&pa->pdev.dev);
? ?? ???pa->pdev.dev.release = platform_device_release;
? ? }
? ? return pa ? &pa->pdev : NULL;
}
實際上就是分配一個platform_object 結構體(包含了一個platform device結構體)并初始化內部成員platform driver,然后返回platform driver結構體以完成動態分配一個platform設備
然后調用platform_add_devices()以追加一個platform 設備到platform bus上
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 device(platform_bus)
? ? pdev->dev.bus = &platform_bus_type;??//初始化設備的總線為platform bus
? ? 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 num_resources; i++) {
? ?? ???struct resource *p, *r = &pdev->resource ;
? ?? ???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);? ?? ? //注冊特定的設備到platform bus上
? ? if (ret == 0)
? ?? ???return ret;
failed:
? ? while (--i >= 0)
? ?? ???if (pdev->resource.flags & (IORESOURCE_MEM|IORESOURCE_IO))
? ?? ?? ?? ?release_resource(&pdev->resource);
? ? return ret;
}
上面的操作我們看到另外一個陌生的結構 設備資源(struct resource)
關于資源的操作(從上面已經了解,平臺設備會分到一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct resource {
? ? resource_size_t start;
? ? resource_size_t end;
? ? const char *name;
? ? unsigned long flags;// IORESOURCE_IO??IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA
? ? struct resource *parent, *sibling, *child;
};
基于資源的分類(flags)有I/O端口、IRQ、DMA等等,而I/O端口又分為2種類型, IORESOURCE_IO(I/O映射) IORESOURCE_MEM(內存映射)
這里說一下關于I/O端口:
CPU對外設IO端口物理地址的編址方式有2種:一種是IO映射方式(IO-mapped), 另一種是內存映射方式(Memory-mapped)。具體采用哪一種方式則取決于CPU的體系結構。 像X86體系對外設就專門實現了一個單獨地址空間,并且有專門的I/O指令來訪問I/O端口,像ARM體系結構通常只是實現一個物理地址空間,I/O端口就被映射到CPU的單一物理地址空間中,而成為內存的一部分,所以一般資源都采用(IORESOURCE_MEM)。
linux中對設備的資源按照資源樹的結構來組織(其實就是一個鏈表結構的插入、刪除、查找等操作),上面再添加設備(platform_device_add)的同時對相應的資源在資源樹上進行插入操作int insert_resource(struct resource *parent, struct resource *new)
關于platform resource有相關的函數進行對資源的操作。
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
int platform_get_irq(struct platform_device *, unsigned int);
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
int platform_get_irq_byname(struct platform_device *, char *);
例如s3c24210的watchdog資源分配實例:
watchdog寄存器的基地址為0x5300000
#define S3C2410_PA_WATCHDOG (0x53000000)
#define S3C24XX_SZ_WATCHDOG SZ_1M
static struct resource s3c_wdt_resource[] = {
? ? [0] = {? ??
? ?? ???.start = S3C24XX_PA_WATCHDOG,
? ?? ???.end? ?= S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
? ?? ???.flags = IORESOURCE_MEM,? ? //內存映射
? ? },
? ? [1] = {? ??
? ?? ???.start = IRQ_WDT,
? ?? ???.end? ?= IRQ_WDT,
? ?? ???.flags = IORESOURCE_IRQ,? ? //IRQ
? ? }
};
動態注冊platform device例:
/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
? ? int ret, i;
? ? if (nr_uarts > UART_NR)
? ?? ???nr_uarts = UART_NR;
? ? printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
? ?? ???"%d ports, IRQ sharing %sabled\n", nr_uarts,
? ?? ???share_irqs ? "en" : "dis");
? ? for (i = 0; i platform_device_alloc("serial8250",
? ?? ?? ?? ?? ?? ?? ?? ?? ? PLAT8250_DEV_LEGACY);
? ? if (!serial8250_isa_devs) {
? ?? ???ret = -ENOMEM;
? ?? ???goto unreg_uart_drv;
? ? }
? ? ret = platform_device_add(serial8250_isa_devs);
? ? if (ret)
? ?? ???goto put_dev;
? ? serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
? ? ret = platform_driver_register(&serial8250_isa_driver);
? ? if (ret == 0)
? ?? ???goto out;
? ? platform_device_del(serial8250_isa_devs);
put_dev:
? ? platform_device_put(serial8250_isa_devs);
unreg_uart_drv:
? ? uart_unregister_driver(&serial8250_reg);
out:
? ? return ret;
}
也可以在編譯的時候就確定設備的相關信息,調用 int platform_device_register(struct platform_device *);
/linux/arch/arm/mach-smdk2410/mach-smdk2410.c
static struct platform_device *smdk2410_devices[] __initdata = {
? ? &s3c_device_usb,
? ? &s3c_device_lcd,
? ? &s3c_device_wdt,
? ? &s3c_device_i2c,
? ? &s3c_device_iis,
};
static void __init smdk2410_init(void)
{
? ? platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); //靜態增加一組soc設備,以便在加載驅動的時候匹配相關驅動
? ? smdk_machine_init();
}
int platform_add_devices(struct platform_device **devs, int num)
{
? ? int i, ret = 0;
? ? for (i = 0; i platform_device_register(devs);??//實際上是調用 platform_device_register追加platform device
? ?? ???if (ret) {
? ?? ?? ?? ?while (--i >= 0)
? ?? ?? ?? ?? ? platform_device_unregister(devs);
? ?? ?? ?? ?break;
? ?? ???}
? ? }
? ? return ret;
}
int platform_device_register(struct platform_device * pdev)
{
? ? device_initialize(&pdev->dev);
? ? return platform_device_add(pdev);
}
從上面看出這和動態增加一個platform device所做的動作基本上是一樣的(device_initialize,platform_device_add)
例 watchdog設備定義:
struct platform_device s3c_device_wdt = {
? ? .name? ?? ?? ? = "s3c2410-wdt",
? ? .id? ?? ?? ? = -1,
? ? .num_resources? ?? ?= ARRAY_SIZE(s3c_wdt_resource),
? ? .resource? ?? ?= s3c_wdt_resource,
};
4 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 (*suspend_late)(struct platform_device *, pm_message_t state);
? ? int (*resume_early)(struct platform_device *);
? ? int (*resume)(struct platform_device *);
? ? struct device_driver driver;
};
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;
? ? if (drv->suspend)
? ?? ???drv->driver.suspend = platform_drv_suspend;
? ? if (drv->resume)
? ?? ???drv->driver.resume = platform_drv_resume;
? ? return driver_register(&drv->driver);
}
指定platform device所屬總線,同時如果為platform_driver中各項指定了接口,則為struct device_driver中相應的接口賦值。
那么是如何賦值的呢?
#define to_platform_driver(drv)? ? (container_of((drv), struct platform_driver, driver))
static int platform_drv_probe(struct device *_dev)
{
? ? struct platform_driver *drv = to_platform_driver(_dev->driver);
? ? struct platform_device *dev = to_platform_device(_dev);
? ? return drv->probe(dev);
}
從上面可以看出,是將struct device轉換為struct platform_device和struct platform_driver.然后調用platform_driver中的相應接口函數來實現,
最后調用 driver_register()將platform driver注冊到總線上。
/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
? ? ......
? ? serial8250_isa_devs = platform_device_alloc("serial8250",
? ?? ?? ?? ?? ?? ?? ?? ?? ? PLAT8250_DEV_LEGACY);
? ? if (!serial8250_isa_devs) {
? ?? ???ret = -ENOMEM;
? ?? ???goto unreg_uart_drv;
? ? }
? ? ret = platform_device_add(serial8250_isa_devs);
? ? if (ret)
? ?? ???goto put_dev;
? ? serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
? ? ret = platform_driver_register(&serial8250_isa_driver);
? ? if (ret == 0)
? ?? ???goto out;
? ? ......
}
在設備成功進行了注冊后,調用platform_driver_register()進行驅動注冊。
最后,總線上注冊有設備和相應的驅動,就會進行設備和驅動的匹配。
在找到一個設備和驅動的配對后, 驅動綁定是通過調用probe()由驅動核心自動完成的. 如果probe()成功,
驅動和設備就正常綁定了. 有三種不同的方法來進行配對:
-設備一被注冊, 就檢查對應總線下的各驅動, 看是否匹配. 平臺設備應在系統啟動過程的早期被注冊
-當驅動通過platform_driver_register()被注冊時, 就檢查對應總線上所有未綁定的設備.驅動通常在啟動過程的后期被注冊或通過裝載模塊來注冊.
-用platform_driver_probe()來注冊驅動的效果跟用platform_driver_register()幾乎相同, 不同點僅在于,如果再有設備注冊, 驅動就不會再被枚舉了. (這無關緊要, 因為這種接口只用在不可熱插拔的設備上.)
驅動和設備的匹配僅僅是通過名稱來匹配的
static int platform_match(struct device * dev, struct device_driver * drv)
{
? ? struct platform_device *pdev = container_of(dev, struct platform_device, dev);
? ? return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
小結:本節總結平臺設備和驅動的模型,這部份知識可以作為我們深入了解具體平臺設備驅動的基礎。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u2/72003/showart_1963302.html
platform是一個虛擬的地址總線,相比pci,usb,它主要用于描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個共同點,就是在cpu的總線上直接取址。
平臺設備會分到一個名稱(用在驅動綁定中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
平臺驅動遵循標準驅動模型的規范, 也就是說發現/列舉(discovery/enumeration)在驅動之外處理, 而
由驅動提供probe()和remove方法. 平臺驅動按標準規范對電源管理和關機通告提供支持
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()總應該核實指定的設備硬件確實存在;平臺設置代碼有時不能確定這一點. 枚舉(probing)可以使用的設備資源包括時鐘及設備的platform_data.(譯注: platform_data定義在device.txt中的"基本設備結構體"中.)
平臺驅動通過普通的方法注冊自身
int platform_driver_register(struct platform_driver *drv);
或者, 更常見的情況是已知設備不可熱插拔, probe()過程便可以駐留在一個初始化區域(init section)
中,以便減少驅動的運行時內存占用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));
設備列舉
按規定, 應由針對平臺(也適用于針對板)的設置代碼來注冊平臺設備
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)
一般的規則是只注冊那些實際存在的設備, 但也有例外. 例如, 某外部網卡未必會裝配在所有的板子上,
或者某集成控制器所在的板上可能沒掛任何外設, 而內核卻需要被配置來支持這些網卡和控制器
有些情況下, 啟動固件(boot firmware)會導出一張裝配到板上的設備的描述表. 如果沒有這張表, 通常
就只能通過編譯針對目標板的內核來讓系統設置代碼安裝正確的設備了. 這種針對板的內核在嵌入式和自定
義的系統開發中是比較常見的.
多數情況下, 分給平臺設備的內存和中斷請求號資源是不足以讓設備正常工作的. 板設置代碼通常會用設備
的platform_data域來存放附加信息, 并向外提供它們.
嵌入式系統時常需要為平臺設備提供一個或多個時鐘信號. 除非被用到, 這些時鐘一般處于靜息狀態以節電.
系統設置代碼也負責為設備提供這些時鐘, 以便設備能在它們需要是調用
clk_get(&pdev->dev, clock_name).
也可以用如下函數來一次性完成分配空間和注冊設備的任務
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)
設備命名和驅動綁定
platform_device.dev.bus_id是設備的真名. 它由兩部分組成:
? ?? ???*platform_device.name ... 這也被用來匹配驅動
? ?? ???*platform_device.id ...? ?? ???設備實例號, 或者用"-1"表示只有一個設備.
連接這兩項, 像"serial"/0就表示bus_id為"serial.0", "serial"/3表示bus_id為"serial.3";
上面二例都將使用名叫"serial"的平臺驅動. 而"my_rtc"/-1的bus_id為"my_rtc"(無實例號), 它的
平臺驅動為"my_rtc".
2 平臺總線
下面我們看看與platform相關的操作
平臺總線的初始化
int __init platform_bus_init(void)
{
? ? int error;
? ? error = device_register(&platform_bus);
? ? if (error)
? ?? ???return error;
? ? error =??bus_register(&platform_bus_type);
? ? if (error)
? ?? ???device_unregister(&platform_bus);
? ? return error;
}
這段初始化代碼創建了一個platform設備,以后屬于platform類型的設備就會以此為parent,增加的設備會出現在/sys/devices/platform目錄下
[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth??floppy.0??i8042??pcspkr??power??serial8250??uevent??vesafb.0
緊接著注冊名為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,
};
int platform_device_add(struct platform_device *pdev)
{......
? ?? ???pdev->dev.parent = &platform_bus;??//增加的platform設備以后都以platform_bus(platform設備)為父節點
? ?? ???pdev->dev.bus = &platform_bus_type; //platform類型設備都掛接在platform總線上 /sys/bus/platform/
......
}
3 platform device的注冊
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
1)動態分配一個名為name的platform設備
struct platform_object {
? ? struct platform_device pdev;
? ? char name[1];
};
struct platform_device *platform_device_alloc(const char *name, int id)
{
? ? struct platform_object *pa;
? ? pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);//由于 platform_object內name只有一個字節,所以需要多分配strlen(name)長度
? ? if (pa) {
? ?? ???strcpy(pa->name, name);
? ?? ???pa->pdev.name = pa->name;
? ?? ???pa->pdev.id = id;
? ?? ???device_initialize(&pa->pdev.dev);
? ?? ???pa->pdev.dev.release = platform_device_release;
? ? }
? ? return pa ? &pa->pdev : NULL;
}
實際上就是分配一個platform_object 結構體(包含了一個platform device結構體)并初始化內部成員platform driver,然后返回platform driver結構體以完成動態分配一個platform設備
然后調用platform_add_devices()以追加一個platform 設備到platform bus上
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 device(platform_bus)
? ? pdev->dev.bus = &platform_bus_type;??//初始化設備的總線為platform bus
? ? 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 num_resources; i++) {
? ?? ???struct resource *p, *r = &pdev->resource ;
? ?? ???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);? ?? ? //注冊特定的設備到platform bus上
? ? if (ret == 0)
? ?? ???return ret;
failed:
? ? while (--i >= 0)
? ?? ???if (pdev->resource.flags & (IORESOURCE_MEM|IORESOURCE_IO))
? ?? ?? ?? ?release_resource(&pdev->resource);
? ? return ret;
}
上面的操作我們看到另外一個陌生的結構 設備資源(struct resource)
關于資源的操作(從上面已經了解,平臺設備會分到一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct resource {
? ? resource_size_t start;
? ? resource_size_t end;
? ? const char *name;
? ? unsigned long flags;// IORESOURCE_IO??IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA
? ? struct resource *parent, *sibling, *child;
};
基于資源的分類(flags)有I/O端口、IRQ、DMA等等,而I/O端口又分為2種類型, IORESOURCE_IO(I/O映射) IORESOURCE_MEM(內存映射)
這里說一下關于I/O端口:
CPU對外設IO端口物理地址的編址方式有2種:一種是IO映射方式(IO-mapped), 另一種是內存映射方式(Memory-mapped)。具體采用哪一種方式則取決于CPU的體系結構。 像X86體系對外設就專門實現了一個單獨地址空間,并且有專門的I/O指令來訪問I/O端口,像ARM體系結構通常只是實現一個物理地址空間,I/O端口就被映射到CPU的單一物理地址空間中,而成為內存的一部分,所以一般資源都采用(IORESOURCE_MEM)。
linux中對設備的資源按照資源樹的結構來組織(其實就是一個鏈表結構的插入、刪除、查找等操作),上面再添加設備(platform_device_add)的同時對相應的資源在資源樹上進行插入操作int insert_resource(struct resource *parent, struct resource *new)
關于platform resource有相關的函數進行對資源的操作。
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
int platform_get_irq(struct platform_device *, unsigned int);
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
int platform_get_irq_byname(struct platform_device *, char *);
例如s3c24210的watchdog資源分配實例:
watchdog寄存器的基地址為0x5300000
#define S3C2410_PA_WATCHDOG (0x53000000)
#define S3C24XX_SZ_WATCHDOG SZ_1M
static struct resource s3c_wdt_resource[] = {
? ? [0] = {? ??
? ?? ???.start = S3C24XX_PA_WATCHDOG,
? ?? ???.end? ?= S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
? ?? ???.flags = IORESOURCE_MEM,? ? //內存映射
? ? },
? ? [1] = {? ??
? ?? ???.start = IRQ_WDT,
? ?? ???.end? ?= IRQ_WDT,
? ?? ???.flags = IORESOURCE_IRQ,? ? //IRQ
? ? }
};
動態注冊platform device例:
/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
? ? int ret, i;
? ? if (nr_uarts > UART_NR)
? ?? ???nr_uarts = UART_NR;
? ? printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
? ?? ???"%d ports, IRQ sharing %sabled\n", nr_uarts,
? ?? ???share_irqs ? "en" : "dis");
? ? for (i = 0; i platform_device_alloc("serial8250",
? ?? ?? ?? ?? ?? ?? ?? ?? ? PLAT8250_DEV_LEGACY);
? ? if (!serial8250_isa_devs) {
? ?? ???ret = -ENOMEM;
? ?? ???goto unreg_uart_drv;
? ? }
? ? ret = platform_device_add(serial8250_isa_devs);
? ? if (ret)
? ?? ???goto put_dev;
? ? serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
? ? ret = platform_driver_register(&serial8250_isa_driver);
? ? if (ret == 0)
? ?? ???goto out;
? ? platform_device_del(serial8250_isa_devs);
put_dev:
? ? platform_device_put(serial8250_isa_devs);
unreg_uart_drv:
? ? uart_unregister_driver(&serial8250_reg);
out:
? ? return ret;
}
也可以在編譯的時候就確定設備的相關信息,調用 int platform_device_register(struct platform_device *);
/linux/arch/arm/mach-smdk2410/mach-smdk2410.c
static struct platform_device *smdk2410_devices[] __initdata = {
? ? &s3c_device_usb,
? ? &s3c_device_lcd,
? ? &s3c_device_wdt,
? ? &s3c_device_i2c,
? ? &s3c_device_iis,
};
static void __init smdk2410_init(void)
{
? ? platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); //靜態增加一組soc設備,以便在加載驅動的時候匹配相關驅動
? ? smdk_machine_init();
}
int platform_add_devices(struct platform_device **devs, int num)
{
? ? int i, ret = 0;
? ? for (i = 0; i platform_device_register(devs);??//實際上是調用 platform_device_register追加platform device
? ?? ???if (ret) {
? ?? ?? ?? ?while (--i >= 0)
? ?? ?? ?? ?? ? platform_device_unregister(devs);
? ?? ?? ?? ?break;
? ?? ???}
? ? }
? ? return ret;
}
int platform_device_register(struct platform_device * pdev)
{
? ? device_initialize(&pdev->dev);
? ? return platform_device_add(pdev);
}
從上面看出這和動態增加一個platform device所做的動作基本上是一樣的(device_initialize,platform_device_add)
例 watchdog設備定義:
struct platform_device s3c_device_wdt = {
? ? .name? ?? ?? ? = "s3c2410-wdt",
? ? .id? ?? ?? ? = -1,
? ? .num_resources? ?? ?= ARRAY_SIZE(s3c_wdt_resource),
? ? .resource? ?? ?= s3c_wdt_resource,
};
4 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 (*suspend_late)(struct platform_device *, pm_message_t state);
? ? int (*resume_early)(struct platform_device *);
? ? int (*resume)(struct platform_device *);
? ? struct device_driver driver;
};
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;
? ? if (drv->suspend)
? ?? ???drv->driver.suspend = platform_drv_suspend;
? ? if (drv->resume)
? ?? ???drv->driver.resume = platform_drv_resume;
? ? return driver_register(&drv->driver);
}
指定platform device所屬總線,同時如果為platform_driver中各項指定了接口,則為struct device_driver中相應的接口賦值。
那么是如何賦值的呢?
#define to_platform_driver(drv)? ? (container_of((drv), struct platform_driver, driver))
static int platform_drv_probe(struct device *_dev)
{
? ? struct platform_driver *drv = to_platform_driver(_dev->driver);
? ? struct platform_device *dev = to_platform_device(_dev);
? ? return drv->probe(dev);
}
從上面可以看出,是將struct device轉換為struct platform_device和struct platform_driver.然后調用platform_driver中的相應接口函數來實現,
最后調用 driver_register()將platform driver注冊到總線上。
/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
? ? ......
? ? serial8250_isa_devs = platform_device_alloc("serial8250",
? ?? ?? ?? ?? ?? ?? ?? ?? ? PLAT8250_DEV_LEGACY);
? ? if (!serial8250_isa_devs) {
? ?? ???ret = -ENOMEM;
? ?? ???goto unreg_uart_drv;
? ? }
? ? ret = platform_device_add(serial8250_isa_devs);
? ? if (ret)
? ?? ???goto put_dev;
? ? serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
? ? ret = platform_driver_register(&serial8250_isa_driver);
? ? if (ret == 0)
? ?? ???goto out;
? ? ......
}
在設備成功進行了注冊后,調用platform_driver_register()進行驅動注冊。
最后,總線上注冊有設備和相應的驅動,就會進行設備和驅動的匹配。
在找到一個設備和驅動的配對后, 驅動綁定是通過調用probe()由驅動核心自動完成的. 如果probe()成功,
驅動和設備就正常綁定了. 有三種不同的方法來進行配對:
-設備一被注冊, 就檢查對應總線下的各驅動, 看是否匹配. 平臺設備應在系統啟動過程的早期被注冊
-當驅動通過platform_driver_register()被注冊時, 就檢查對應總線上所有未綁定的設備.驅動通常在啟動過程的后期被注冊或通過裝載模塊來注冊.
-用platform_driver_probe()來注冊驅動的效果跟用platform_driver_register()幾乎相同, 不同點僅在于,如果再有設備注冊, 驅動就不會再被枚舉了. (這無關緊要, 因為這種接口只用在不可熱插拔的設備上.)
驅動和設備的匹配僅僅是通過名稱來匹配的
static int platform_match(struct device * dev, struct device_driver * drv)
{
? ? struct platform_device *pdev = container_of(dev, struct platform_device, dev);
? ? return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
小結:本節總結平臺設備和驅動的模型,這部份知識可以作為我們深入了解具體平臺設備驅動的基礎。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u2/72003/showart_1963302.html
總結
以上是生活随笔為你收集整理的Linux设备驱动模型之platform总线的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设备IO
- 下一篇: 详解Linux2.6内核中基于platf