linux驱动模型开发——linux platform总线机制讲解与实例开发
生活随笔
收集整理的這篇文章主要介紹了
linux驱动模型开发——linux platform总线机制讲解与实例开发
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1、概述:
通常在Linux中,把SoC系統中集成的獨立外設單元(如:I2C、IIS、RTC、看門狗等)都被當作平臺設備來處理。
從Linux2.6起,引入了一套新的驅動管理和注冊機制:Platform_device和Platform_driver,來管理相應設備。
Linux中大部分的設備驅動,都可以使用這套機制,設備用platform_device表示,驅動用platform_driver進行注冊。
Linux platform driver機制和傳統的device_driver機制相比,一個十分明顯的優勢在于platform機制將本身的資源注冊進內核,由內核統一管理,在驅動程序中使用這些資源時通過platform_device提供的標準接口進行申請并使用。
這樣提高了驅動和資源管理的獨立性,并且擁有較好的可移植性和安全性。 platform是一個虛擬的地址總線,相比pci,usb,它主要用于描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個共同點,就是可以在cpu的總線上直接取址。2、platform機制分為三個步驟 1)總線注冊階段: 內核啟動初始化時的main.c文件中的 kernel_init() ->do_basic_setup() -> driver_init() ->platform_bus_init() ->bus_register(&platform_bus_type),注冊了一條platform總線(虛擬總線) platform總線用于連接各類采用platform機制的設備,此階段無需我們修改,由linux內核維護。 2)添加設備階段: 設備注冊的時候 Platform_device_register() -> platform_device_add() -> pdev->dev.bus = &platform_bus_type -> device_add(),就這樣把設備給掛到虛擬的總線上。 本階段是增加設備到plartform總線上,我們增加硬件設備,需要修改此處信息。? 此部分操作一般arm/arm/mach-s3c2440/mach-smdk2440.c類似的文件中,需要我們根據硬件的實際需要修改相應代碼 3)驅動注冊階段: ?? ?Platform_driver_register() ->driver_register() -> bus_add_driver() -> driver_attach() ->bus_for_each_dev(),
對在每個掛在虛擬的platform bus的設備作__driver_attach() ->driver_probe_device() ?? ?判斷drv->bus->match()是否執行成功,此時通過指針執行platform_match-> strncmp(pdev->name , drv->name , BUS_ID_SIZE),如果相符就調用really_probe(實際就是執行相應設備的platform_driver->probe(platform_device)。)
開始真正的探測,如果probe成功,則綁定設備到該驅動。 本階段是在編寫具體的驅動程序時完成,在注冊驅動時獲取步驟2中已經申請的資料,一般由程序開發者完成。
3、platform設備開發過程 ?? ?platform機制開發的并不復雜,由兩部分組成:platform_device和platfrom_driver ?? ?通過Platform機制開發發底層驅動的大致流程為:?
?? ?定義 platform_device ?? ?注冊 platform_device ?? ?定義 platform_driver ?? ?注冊 platform_driver ???以linux2.6.34平臺下S3C2440為例:前兩項工作主要在arch/arm/mach-s3c2440/match-smdk2440.c與arch/arm/platform-s3c24xx/devs.c中完成,后兩項工作主要在編寫具體的驅動程序時完成
在2.6內核中platform設備用結構體platform_device來描述,該結構體定義在kernel\include\linux\platform_device.h中,
struct platform_device
?{
?? ? const char * name;
?? ? u32 ?id;
?? ? struct device dev;
?? ? u32 ?num_resources;
?? ? struct resource * resource;
};
每個具體的驅動都對應一個這樣的結構體。? Platform_device結構體描述了一個platform結構的設備,在其中包含了 一般設備的結構體:struct device ?dev; 設備的資源結構體:struct resource * resource; 還有設備的名字:const char * name。
(注意,這個名字一定要和后面platform_driver.driver->name相同,因為在注冊具體的設備驅動時會遍歷這個結構體查找相應的數據結構,后面會詳細講解)
該結構一個重要的元素是resource,該元素存入了最為重要的設備資源信息,定義在kernel\include\linux\ioport.h中,
struct resource
?{
?? ?resource_size_t start; ?//定義資源的起始地址
?? ?resource_size_t end; ? ?//定義資源的結束地址
?? ?const char *name; ? ? ? //定義資源的名稱
?? ?unsigned long flags; ? ?//定義資源的類型,比如MEM,IO,IRQ,DMA類型
?? ?struct resource *parent, *sibling, *child; ?//資源鏈表指針
};
主要用于定義具體設備占用的硬件資源(如:地址空間、中斷號等; 其中 flags位表示該資源的類型 start和end分別表示該資源的起始地址和結束地址; 要注意的是,這里的platform_device設備的注冊過程必須在相應設備驅動加載之前被調用; 即執行platform_driver_register()之前,原因是驅動注冊時需要匹配內核中所有已注冊的設備名。
我們以內核中對SMDK2440的支持為例觀察一下整個過程:
內核啟動過程中會調用用 arch/arm/mach-s3c2440/smdk2440_machine_init()函數進行板級硬件初始化
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
此函數中調用了platform_add_devices() -> platform_device_register()注冊platform設備
注冊順序根據同文件夾下的 static struct platform_device *smdk2440_devices[] __initdata =?
{
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_dm9k,
?&s3c24xx_uda134x,
&s3c_device_sdi,
};
結構體進行
這些設備的初始化一般都在arch/arm/plat-s3c24xx/devs.c下
我們以s3c_device_wdt為例進行觀察:
/* Watchdog */
//看門狗資源結構體
static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C24XX_PA_WATCHDOG,
.end ? = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
.flags = IORESOURCE_MEM, //看門狗所使用的IO口范圍
},
[1] = {
.start = IRQ_WDT,
.end ? = IRQ_WDT,
.flags = IORESOURCE_IRQ, ?//看門狗所使用的中斷資源
}
};
//定義了一個看門狗結構體
struct platform_device s3c_device_wdt = {
.name ?= "s3c2410-wdt",??//驅動名稱
.id ?= -1, ? ? ? ? ? ? ? ? ? ? ? ? ? //id號,-1代表自動分配
.num_resources?= ARRAY_SIZE(s3c_wdt_resource),
??//指定資源數量
.resource ??= s3c_wdt_resource, ? ? ? ? ? ? //指定資源結構體
};
platform_driver在具體的硬件設備驅動編寫中完成:
同plartform_device相似,需要定義并實現以下結構體 struct platform_driver ? ? ? ? ? ? ? ? ?(/include/linux/Platform_device.h)
{
?? ? ? 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;
}; Platform_driver結構體描述了一個platform結構的驅動。
其中除了一些函數指針外,還有一個一般驅動的device_driver結構。
/*Watchdog平臺驅動結構體,平臺驅動結構體定義在platform_device.h中,該結構體內的接口函數需要單獨實現*/
static struct platform_driver watchdog_driver =?
{
?? ?.probe ? ? ? = watchdog_probe, ? /*Watchdog探測函數*/
?? ?.remove ? ? ?= __devexit_p(watchdog_remove),/*Watchdog移除函數*/
?? ?.shutdown ? = watchdog_shutdown, /*Watchdog關閉函數*/
?? ?.suspend ? ?= watchdog_suspend, ?/*Watchdog掛起函數*/
?? ?.resume ? ? = watchdog_resume, ? /*Watchdog恢復函數*/
?? ?.driver ? ? =?
?? ?{
?? ? ? ?/*注意這里的名稱一定要和系統中定義平臺設備的地方一致,這樣才能把平臺設備與該平臺設備的驅動關聯起來*/
?? ? ???.name ? = "s3c2410-wdt",
?? ? ? ?.owner ?= THIS_MODULE,
?? ?},
};
static int __init watchdog_init(void)
{
?? ?/*將Watchdog注冊成平臺設備驅動*/
?? ?return platform_driver_register(&watchdog_driver);
}
static void __exit watchdog_exit(void)
{
?? ?/*注銷Watchdog平臺設備驅動*/
?? ?platform_driver_unregister(&watchdog_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");
MODULE_DESCRIPTION("S3C2440 Watchdog Driver");
static int __devinit watchdog_probe(struct platform_device *pdev)
{
?? ?int ret;
?? ?int started = 0;
?? ?struct resource *res;/*定義一個資源,用來保存獲取的watchdog的IO資源*/ ?/*在系統定義的watchdog平臺設備中獲取watchdog中斷號platform_get_irq定義在platform_device.h中*/
?? ?wdt_irqno = platform_get_irq(pdev, 0);
?/*申請Watchdog中斷服務,這里使用的是快速中斷:IRQF_DISABLED。中斷服務程序為:wdt_irq,將Watchdog平臺設備pdev做參數傳遞過去了*/
?? ?ret = request_irq(wdt_irqno, wdt_irq, IRQF_DISABLED, pdev->name, pdev);
/*獲取watchdog平臺設備所使用的IO端口資源,注意這個IORESOURCE_MEM標志和watchdog平臺設備定義中的一致*/
?? ?res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*申請watchdog的IO端口資源所占用的IO空間(要注意理解IO空間和內存空間的區別),request_mem_region定義在ioport.h中*/
?? ?wdt_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name);
/*將watchdog的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中。
?? ? 注意:IO空間要映射后才能使用,以后對虛擬地址的操作就是對IO空間的操作,*/
?? ?wdt_base = ioremap(res->start, res->end - res->start + 1);
?? ?return ret;
}
/*Watchdog平臺驅動的設備移除接口函數的實現*/
static int __devexit wdt_remove(struct platform_device *dev)
{
??/*釋放獲取的Watchdog平臺設備的IO資源*/
?? ?release_resource(wdt_mem);
?? ?kfree(wdt_mem);
?? ?wdt_mem = NULL;
?/*同watchdog_probe中中斷的申請相對應,在那里申請中斷,這里就釋放中斷*/
?? ?free_irq(wdt_irqno, dev);
?? ?wdt_irq = NULL;
?/*釋放獲取的Watchdog平臺設備的時鐘*/
?? ?clk_disable(wdt_clock);
?? ?clk_put(wdt_clock);
?? ?wdt_clock = NULL;
?/*釋放Watchdog設備虛擬地址映射空間*/
?? ?iounmap(wdt_base);
?? ?return 0;
}
#ifdef CONFIG_PM
/*定義兩個變量來分別保存掛起時的WTCON和WTDAT值,到恢復的時候使用*/
static unsigned long wtcon_save;
static unsigned long wtdat_save;
/*Watchdog平臺驅動的設備掛起接口函數的實現*/
static int wdt_suspend(struct platform_device *dev, pm_message_t state)
{
?? ?/*保存掛起時的WTCON和WTDAT值*/
?? ?wtcon_save = readl(wdt_base + S3C2410_WTCON);
?? ?wtdat_save = readl(wdt_base + S3C2410_WTDAT);
?? ?/*停止看門狗定時器*/
?? ?wdt_start_or_stop(0);
?? ?return 0;
}
/*Watchdog平臺驅動的設備恢復接口函數的實現*/
static int wdt_resume(struct platform_device *dev)
{
?? ?/*恢復掛起時的WTCON和WTDAT值,注意這個順序*/
?? ?writel(wtdat_save, wdt_base + S3C2410_WTDAT);
?? ?writel(wtdat_save, wdt_base + S3C2410_WTCNT);
?? ?writel(wtcon_save, wdt_base + S3C2410_WTCON);
?? ?return 0;
}
#else /*配置內核時沒選上電源管理,Watchdog平臺驅動的設備掛起和恢復功能均無效,這兩個函數也就無需實現了*/
?? ?#define wdt_suspend NULL
?? ?#define wdt_resume NULL
#endif
總結
以上是生活随笔為你收集整理的linux驱动模型开发——linux platform总线机制讲解与实例开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux设备驱动之pci设备的驱动架构
- 下一篇: 2440裸机编程之四 外部中断