Linux设备驱动模型之platform总线
生活随笔
收集整理的這篇文章主要介紹了
Linux设备驱动模型之platform总线
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
1 平臺(tái)設(shè)備和驅(qū)動(dòng)初識(shí)
platform是一個(gè)虛擬的地址總線,相比pci,usb,它主要用于描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個(gè)共同點(diǎn),就是在cpu的總線上直接取址。
平臺(tái)設(shè)備會(huì)分到一個(gè)名稱(用在驅(qū)動(dòng)綁定中)以及一系列諸如地址和中斷請(qǐng)求號(hào)(IRQ)之類的資源.
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
平臺(tái)驅(qū)動(dòng)遵循標(biāo)準(zhǔn)驅(qū)動(dòng)模型的規(guī)范, 也就是說發(fā)現(xiàn)/列舉(discovery/enumeration)在驅(qū)動(dòng)之外處理, 而
由驅(qū)動(dòng)提供probe()和remove方法. 平臺(tái)驅(qū)動(dòng)按標(biāo)準(zhǔn)規(guī)范對(duì)電源管理和關(guān)機(jī)通告提供支持
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()總應(yīng)該核實(shí)指定的設(shè)備硬件確實(shí)存在;平臺(tái)設(shè)置代碼有時(shí)不能確定這一點(diǎn). 枚舉(probing)可以使用的設(shè)備資源包括時(shí)鐘及設(shè)備的platform_data.(譯注: platform_data定義在device.txt中的"基本設(shè)備結(jié)構(gòu)體"中.)
平臺(tái)驅(qū)動(dòng)通過普通的方法注冊(cè)自身
int platform_driver_register(struct platform_driver *drv);
或者, 更常見的情況是已知設(shè)備不可熱插拔, probe()過程便可以駐留在一個(gè)初始化區(qū)域(init section)
中,以便減少驅(qū)動(dòng)的運(yùn)行時(shí)內(nèi)存占用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));
設(shè)備列舉
按規(guī)定, 應(yīng)由針對(duì)平臺(tái)(也適用于針對(duì)板)的設(shè)置代碼來注冊(cè)平臺(tái)設(shè)備
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)
一般的規(guī)則是只注冊(cè)那些實(shí)際存在的設(shè)備, 但也有例外. 例如, 某外部網(wǎng)卡未必會(huì)裝配在所有的板子上,
或者某集成控制器所在的板上可能沒掛任何外設(shè), 而內(nèi)核卻需要被配置來支持這些網(wǎng)卡和控制器
有些情況下, 啟動(dòng)固件(boot firmware)會(huì)導(dǎo)出一張裝配到板上的設(shè)備的描述表. 如果沒有這張表, 通常
就只能通過編譯針對(duì)目標(biāo)板的內(nèi)核來讓系統(tǒng)設(shè)置代碼安裝正確的設(shè)備了. 這種針對(duì)板的內(nèi)核在嵌入式和自定
義的系統(tǒng)開發(fā)中是比較常見的.
多數(shù)情況下, 分給平臺(tái)設(shè)備的內(nèi)存和中斷請(qǐng)求號(hào)資源是不足以讓設(shè)備正常工作的. 板設(shè)置代碼通常會(huì)用設(shè)備
的platform_data域來存放附加信息, 并向外提供它們.
嵌入式系統(tǒng)時(shí)常需要為平臺(tái)設(shè)備提供一個(gè)或多個(gè)時(shí)鐘信號(hào). 除非被用到, 這些時(shí)鐘一般處于靜息狀態(tài)以節(jié)電.
系統(tǒng)設(shè)置代碼也負(fù)責(zé)為設(shè)備提供這些時(shí)鐘, 以便設(shè)備能在它們需要是調(diào)用
clk_get(&pdev->dev, clock_name).
也可以用如下函數(shù)來一次性完成分配空間和注冊(cè)設(shè)備的任務(wù)
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)
設(shè)備命名和驅(qū)動(dòng)綁定
platform_device.dev.bus_id是設(shè)備的真名. 它由兩部分組成:
? ?? ???*platform_device.name ... 這也被用來匹配驅(qū)動(dòng)
? ?? ???*platform_device.id ...? ?? ???設(shè)備實(shí)例號(hào), 或者用"-1"表示只有一個(gè)設(shè)備.
連接這兩項(xiàng), 像"serial"/0就表示bus_id為"serial.0", "serial"/3表示bus_id為"serial.3";
上面二例都將使用名叫"serial"的平臺(tái)驅(qū)動(dòng). 而"my_rtc"/-1的bus_id為"my_rtc"(無實(shí)例號(hào)), 它的
平臺(tái)驅(qū)動(dòng)為"my_rtc".
2 平臺(tái)總線
下面我們看看與platform相關(guān)的操作
平臺(tái)總線的初始化
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;
}
這段初始化代碼創(chuàng)建了一個(gè)platform設(shè)備,以后屬于platform類型的設(shè)備就會(huì)以此為parent,增加的設(shè)備會(huì)出現(xiàn)在/sys/devices/platform目錄下
[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth??floppy.0??i8042??pcspkr??power??serial8250??uevent??vesafb.0
緊接著注冊(cè)名為platform的平臺(tái)總線
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設(shè)備以后都以platform_bus(platform設(shè)備)為父節(jié)點(diǎn)
? ?? ???pdev->dev.bus = &platform_bus_type; //platform類型設(shè)備都掛接在platform總線上 /sys/bus/platform/
......
}
3 platform device的注冊(cè)
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
1)動(dòng)態(tài)分配一個(gè)名為name的platform設(shè)備
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內(nèi)name只有一個(gè)字節(jié),所以需要多分配strlen(name)長(zhǎng)度
? ? 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;
}
實(shí)際上就是分配一個(gè)platform_object 結(jié)構(gòu)體(包含了一個(gè)platform device結(jié)構(gòu)體)并初始化內(nèi)部成員platform driver,然后返回platform driver結(jié)構(gòu)體以完成動(dòng)態(tài)分配一個(gè)platform設(shè)備
然后調(diào)用platform_add_devices()以追加一個(gè)platform 設(shè)備到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; //初始化設(shè)備的父節(jié)點(diǎn)所屬類型為platform device(platform_bus)
? ? pdev->dev.bus = &platform_bus_type;??//初始化設(shè)備的總線為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);? ?? ? //注冊(cè)特定的設(shè)備到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;
}
上面的操作我們看到另外一個(gè)陌生的結(jié)構(gòu) 設(shè)備資源(struct resource)
關(guān)于資源的操作(從上面已經(jīng)了解,平臺(tái)設(shè)備會(huì)分到一系列諸如地址和中斷請(qǐng)求號(hào)(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(內(nèi)存映射)
這里說一下關(guān)于I/O端口:
CPU對(duì)外設(shè)IO端口物理地址的編址方式有2種:一種是IO映射方式(IO-mapped), 另一種是內(nèi)存映射方式(Memory-mapped)。具體采用哪一種方式則取決于CPU的體系結(jié)構(gòu)。 像X86體系對(duì)外設(shè)就專門實(shí)現(xiàn)了一個(gè)單獨(dú)地址空間,并且有專門的I/O指令來訪問I/O端口,像ARM體系結(jié)構(gòu)通常只是實(shí)現(xiàn)一個(gè)物理地址空間,I/O端口就被映射到CPU的單一物理地址空間中,而成為內(nèi)存的一部分,所以一般資源都采用(IORESOURCE_MEM)。
linux中對(duì)設(shè)備的資源按照資源樹的結(jié)構(gòu)來組織(其實(shí)就是一個(gè)鏈表結(jié)構(gòu)的插入、刪除、查找等操作),上面再添加設(shè)備(platform_device_add)的同時(shí)對(duì)相應(yīng)的資源在資源樹上進(jìn)行插入操作int insert_resource(struct resource *parent, struct resource *new)
關(guān)于platform resource有相關(guān)的函數(shù)進(jìn)行對(duì)資源的操作。
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資源分配實(shí)例:
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,? ? //內(nèi)存映射
? ? },
? ? [1] = {? ??
? ?? ???.start = IRQ_WDT,
? ?? ???.end? ?= IRQ_WDT,
? ?? ???.flags = IORESOURCE_IRQ,? ? //IRQ
? ? }
};
動(dòng)態(tài)注冊(cè)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;
}
也可以在編譯的時(shí)候就確定設(shè)備的相關(guān)信息,調(diào)用 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)); //靜態(tài)增加一組soc設(shè)備,以便在加載驅(qū)動(dòng)的時(shí)候匹配相關(guān)驅(qū)動(dòng)
? ? 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);??//實(shí)際上是調(diào)用 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);
}
從上面看出這和動(dòng)態(tài)增加一個(gè)platform device所做的動(dòng)作基本上是一樣的(device_initialize,platform_device_add)
例 watchdog設(shè)備定義:
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的注冊(cè)
先看結(jié)構(gòu)體,里面內(nèi)嵌了一個(gè)
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所屬總線,同時(shí)如果為platform_driver中各項(xiàng)指定了接口,則為struct device_driver中相應(yīng)的接口賦值。
那么是如何賦值的呢?
#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轉(zhuǎn)換為struct platform_device和struct platform_driver.然后調(diào)用platform_driver中的相應(yīng)接口函數(shù)來實(shí)現(xiàn),
最后調(diào)用 driver_register()將platform driver注冊(cè)到總線上。
/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;
? ? ......
}
在設(shè)備成功進(jìn)行了注冊(cè)后,調(diào)用platform_driver_register()進(jìn)行驅(qū)動(dòng)注冊(cè)。
最后,總線上注冊(cè)有設(shè)備和相應(yīng)的驅(qū)動(dòng),就會(huì)進(jìn)行設(shè)備和驅(qū)動(dòng)的匹配。
在找到一個(gè)設(shè)備和驅(qū)動(dòng)的配對(duì)后, 驅(qū)動(dòng)綁定是通過調(diào)用probe()由驅(qū)動(dòng)核心自動(dòng)完成的. 如果probe()成功,
驅(qū)動(dòng)和設(shè)備就正常綁定了. 有三種不同的方法來進(jìn)行配對(duì):
-設(shè)備一被注冊(cè), 就檢查對(duì)應(yīng)總線下的各驅(qū)動(dòng), 看是否匹配. 平臺(tái)設(shè)備應(yīng)在系統(tǒng)啟動(dòng)過程的早期被注冊(cè)
-當(dāng)驅(qū)動(dòng)通過platform_driver_register()被注冊(cè)時(shí), 就檢查對(duì)應(yīng)總線上所有未綁定的設(shè)備.驅(qū)動(dòng)通常在啟動(dòng)過程的后期被注冊(cè)或通過裝載模塊來注冊(cè).
-用platform_driver_probe()來注冊(cè)驅(qū)動(dòng)的效果跟用platform_driver_register()幾乎相同, 不同點(diǎn)僅在于,如果再有設(shè)備注冊(cè), 驅(qū)動(dòng)就不會(huì)再被枚舉了. (這無關(guān)緊要, 因?yàn)檫@種接口只用在不可熱插拔的設(shè)備上.)
驅(qū)動(dòng)和設(shè)備的匹配僅僅是通過名稱來匹配的
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);
}
小結(jié):本節(jié)總結(jié)平臺(tái)設(shè)備和驅(qū)動(dòng)的模型,這部份知識(shí)可以作為我們深入了解具體平臺(tái)設(shè)備驅(qū)動(dòng)的基礎(chǔ)。
本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u2/72003/showart_1963302.html
platform是一個(gè)虛擬的地址總線,相比pci,usb,它主要用于描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個(gè)共同點(diǎn),就是在cpu的總線上直接取址。
平臺(tái)設(shè)備會(huì)分到一個(gè)名稱(用在驅(qū)動(dòng)綁定中)以及一系列諸如地址和中斷請(qǐng)求號(hào)(IRQ)之類的資源.
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
平臺(tái)驅(qū)動(dòng)遵循標(biāo)準(zhǔn)驅(qū)動(dòng)模型的規(guī)范, 也就是說發(fā)現(xiàn)/列舉(discovery/enumeration)在驅(qū)動(dòng)之外處理, 而
由驅(qū)動(dòng)提供probe()和remove方法. 平臺(tái)驅(qū)動(dòng)按標(biāo)準(zhǔn)規(guī)范對(duì)電源管理和關(guān)機(jī)通告提供支持
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()總應(yīng)該核實(shí)指定的設(shè)備硬件確實(shí)存在;平臺(tái)設(shè)置代碼有時(shí)不能確定這一點(diǎn). 枚舉(probing)可以使用的設(shè)備資源包括時(shí)鐘及設(shè)備的platform_data.(譯注: platform_data定義在device.txt中的"基本設(shè)備結(jié)構(gòu)體"中.)
平臺(tái)驅(qū)動(dòng)通過普通的方法注冊(cè)自身
int platform_driver_register(struct platform_driver *drv);
或者, 更常見的情況是已知設(shè)備不可熱插拔, probe()過程便可以駐留在一個(gè)初始化區(qū)域(init section)
中,以便減少驅(qū)動(dòng)的運(yùn)行時(shí)內(nèi)存占用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));
設(shè)備列舉
按規(guī)定, 應(yīng)由針對(duì)平臺(tái)(也適用于針對(duì)板)的設(shè)置代碼來注冊(cè)平臺(tái)設(shè)備
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)
一般的規(guī)則是只注冊(cè)那些實(shí)際存在的設(shè)備, 但也有例外. 例如, 某外部網(wǎng)卡未必會(huì)裝配在所有的板子上,
或者某集成控制器所在的板上可能沒掛任何外設(shè), 而內(nèi)核卻需要被配置來支持這些網(wǎng)卡和控制器
有些情況下, 啟動(dòng)固件(boot firmware)會(huì)導(dǎo)出一張裝配到板上的設(shè)備的描述表. 如果沒有這張表, 通常
就只能通過編譯針對(duì)目標(biāo)板的內(nèi)核來讓系統(tǒng)設(shè)置代碼安裝正確的設(shè)備了. 這種針對(duì)板的內(nèi)核在嵌入式和自定
義的系統(tǒng)開發(fā)中是比較常見的.
多數(shù)情況下, 分給平臺(tái)設(shè)備的內(nèi)存和中斷請(qǐng)求號(hào)資源是不足以讓設(shè)備正常工作的. 板設(shè)置代碼通常會(huì)用設(shè)備
的platform_data域來存放附加信息, 并向外提供它們.
嵌入式系統(tǒng)時(shí)常需要為平臺(tái)設(shè)備提供一個(gè)或多個(gè)時(shí)鐘信號(hào). 除非被用到, 這些時(shí)鐘一般處于靜息狀態(tài)以節(jié)電.
系統(tǒng)設(shè)置代碼也負(fù)責(zé)為設(shè)備提供這些時(shí)鐘, 以便設(shè)備能在它們需要是調(diào)用
clk_get(&pdev->dev, clock_name).
也可以用如下函數(shù)來一次性完成分配空間和注冊(cè)設(shè)備的任務(wù)
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)
設(shè)備命名和驅(qū)動(dòng)綁定
platform_device.dev.bus_id是設(shè)備的真名. 它由兩部分組成:
? ?? ???*platform_device.name ... 這也被用來匹配驅(qū)動(dòng)
? ?? ???*platform_device.id ...? ?? ???設(shè)備實(shí)例號(hào), 或者用"-1"表示只有一個(gè)設(shè)備.
連接這兩項(xiàng), 像"serial"/0就表示bus_id為"serial.0", "serial"/3表示bus_id為"serial.3";
上面二例都將使用名叫"serial"的平臺(tái)驅(qū)動(dòng). 而"my_rtc"/-1的bus_id為"my_rtc"(無實(shí)例號(hào)), 它的
平臺(tái)驅(qū)動(dòng)為"my_rtc".
2 平臺(tái)總線
下面我們看看與platform相關(guān)的操作
平臺(tái)總線的初始化
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;
}
這段初始化代碼創(chuàng)建了一個(gè)platform設(shè)備,以后屬于platform類型的設(shè)備就會(huì)以此為parent,增加的設(shè)備會(huì)出現(xiàn)在/sys/devices/platform目錄下
[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth??floppy.0??i8042??pcspkr??power??serial8250??uevent??vesafb.0
緊接著注冊(cè)名為platform的平臺(tái)總線
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設(shè)備以后都以platform_bus(platform設(shè)備)為父節(jié)點(diǎn)
? ?? ???pdev->dev.bus = &platform_bus_type; //platform類型設(shè)備都掛接在platform總線上 /sys/bus/platform/
......
}
3 platform device的注冊(cè)
struct platform_device {
? ? const char? ? * name;
? ? int? ?? ???id;
? ? struct device? ? dev;
? ? u32? ?? ???num_resources;
? ? struct resource? ? * resource;
};
1)動(dòng)態(tài)分配一個(gè)名為name的platform設(shè)備
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內(nèi)name只有一個(gè)字節(jié),所以需要多分配strlen(name)長(zhǎng)度
? ? 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;
}
實(shí)際上就是分配一個(gè)platform_object 結(jié)構(gòu)體(包含了一個(gè)platform device結(jié)構(gòu)體)并初始化內(nèi)部成員platform driver,然后返回platform driver結(jié)構(gòu)體以完成動(dòng)態(tài)分配一個(gè)platform設(shè)備
然后調(diào)用platform_add_devices()以追加一個(gè)platform 設(shè)備到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; //初始化設(shè)備的父節(jié)點(diǎn)所屬類型為platform device(platform_bus)
? ? pdev->dev.bus = &platform_bus_type;??//初始化設(shè)備的總線為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);? ?? ? //注冊(cè)特定的設(shè)備到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;
}
上面的操作我們看到另外一個(gè)陌生的結(jié)構(gòu) 設(shè)備資源(struct resource)
關(guān)于資源的操作(從上面已經(jīng)了解,平臺(tái)設(shè)備會(huì)分到一系列諸如地址和中斷請(qǐng)求號(hào)(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(內(nèi)存映射)
這里說一下關(guān)于I/O端口:
CPU對(duì)外設(shè)IO端口物理地址的編址方式有2種:一種是IO映射方式(IO-mapped), 另一種是內(nèi)存映射方式(Memory-mapped)。具體采用哪一種方式則取決于CPU的體系結(jié)構(gòu)。 像X86體系對(duì)外設(shè)就專門實(shí)現(xiàn)了一個(gè)單獨(dú)地址空間,并且有專門的I/O指令來訪問I/O端口,像ARM體系結(jié)構(gòu)通常只是實(shí)現(xiàn)一個(gè)物理地址空間,I/O端口就被映射到CPU的單一物理地址空間中,而成為內(nèi)存的一部分,所以一般資源都采用(IORESOURCE_MEM)。
linux中對(duì)設(shè)備的資源按照資源樹的結(jié)構(gòu)來組織(其實(shí)就是一個(gè)鏈表結(jié)構(gòu)的插入、刪除、查找等操作),上面再添加設(shè)備(platform_device_add)的同時(shí)對(duì)相應(yīng)的資源在資源樹上進(jìn)行插入操作int insert_resource(struct resource *parent, struct resource *new)
關(guān)于platform resource有相關(guān)的函數(shù)進(jìn)行對(duì)資源的操作。
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資源分配實(shí)例:
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,? ? //內(nèi)存映射
? ? },
? ? [1] = {? ??
? ?? ???.start = IRQ_WDT,
? ?? ???.end? ?= IRQ_WDT,
? ?? ???.flags = IORESOURCE_IRQ,? ? //IRQ
? ? }
};
動(dòng)態(tài)注冊(cè)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;
}
也可以在編譯的時(shí)候就確定設(shè)備的相關(guān)信息,調(diào)用 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)); //靜態(tài)增加一組soc設(shè)備,以便在加載驅(qū)動(dòng)的時(shí)候匹配相關(guān)驅(qū)動(dòng)
? ? 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);??//實(shí)際上是調(diào)用 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);
}
從上面看出這和動(dòng)態(tài)增加一個(gè)platform device所做的動(dòng)作基本上是一樣的(device_initialize,platform_device_add)
例 watchdog設(shè)備定義:
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的注冊(cè)
先看結(jié)構(gòu)體,里面內(nèi)嵌了一個(gè)
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所屬總線,同時(shí)如果為platform_driver中各項(xiàng)指定了接口,則為struct device_driver中相應(yīng)的接口賦值。
那么是如何賦值的呢?
#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轉(zhuǎn)換為struct platform_device和struct platform_driver.然后調(diào)用platform_driver中的相應(yīng)接口函數(shù)來實(shí)現(xiàn),
最后調(diào)用 driver_register()將platform driver注冊(cè)到總線上。
/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;
? ? ......
}
在設(shè)備成功進(jìn)行了注冊(cè)后,調(diào)用platform_driver_register()進(jìn)行驅(qū)動(dòng)注冊(cè)。
最后,總線上注冊(cè)有設(shè)備和相應(yīng)的驅(qū)動(dòng),就會(huì)進(jìn)行設(shè)備和驅(qū)動(dòng)的匹配。
在找到一個(gè)設(shè)備和驅(qū)動(dòng)的配對(duì)后, 驅(qū)動(dòng)綁定是通過調(diào)用probe()由驅(qū)動(dòng)核心自動(dòng)完成的. 如果probe()成功,
驅(qū)動(dòng)和設(shè)備就正常綁定了. 有三種不同的方法來進(jìn)行配對(duì):
-設(shè)備一被注冊(cè), 就檢查對(duì)應(yīng)總線下的各驅(qū)動(dòng), 看是否匹配. 平臺(tái)設(shè)備應(yīng)在系統(tǒng)啟動(dòng)過程的早期被注冊(cè)
-當(dāng)驅(qū)動(dòng)通過platform_driver_register()被注冊(cè)時(shí), 就檢查對(duì)應(yīng)總線上所有未綁定的設(shè)備.驅(qū)動(dòng)通常在啟動(dòng)過程的后期被注冊(cè)或通過裝載模塊來注冊(cè).
-用platform_driver_probe()來注冊(cè)驅(qū)動(dòng)的效果跟用platform_driver_register()幾乎相同, 不同點(diǎn)僅在于,如果再有設(shè)備注冊(cè), 驅(qū)動(dòng)就不會(huì)再被枚舉了. (這無關(guān)緊要, 因?yàn)檫@種接口只用在不可熱插拔的設(shè)備上.)
驅(qū)動(dòng)和設(shè)備的匹配僅僅是通過名稱來匹配的
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);
}
小結(jié):本節(jié)總結(jié)平臺(tái)設(shè)備和驅(qū)動(dòng)的模型,這部份知識(shí)可以作為我們深入了解具體平臺(tái)設(shè)備驅(qū)動(dòng)的基礎(chǔ)。
本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u2/72003/showart_1963302.html
總結(jié)
以上是生活随笔為你收集整理的Linux设备驱动模型之platform总线的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设备IO
- 下一篇: 详解Linux2.6内核中基于platf