一位嵌入式Linux菜鳥設備驅動學習之路
1.設備驅動的作用
計算機系統由硬體、軟體組成,而對於實際開發來說,硬、軟體間耦合性應盡量低,即應用開發工程師不需關心 硬體,而硬體開發工程師無暇顧及軟體。為了降低硬、軟耦合性,產生了設備驅動工程師。
2.操作系統驅動設計架構
在無操作系統時,硬體工程師可以自定義API供應用開發工程師使用;而使用操作系統後,需按操作系統定義的架構設計驅動,如此才能良好的嵌入內核中。
對圖1.1而言:
優點:驅動編寫簡單
缺點:對於每一個硬體設備,應用工程師要花精力去了解硬體工程師提供的API
對圖1.4而言:
優點:對於應用工程師,任何硬體設備均體現為操作系統提供的API,無需知道硬體如何實現
缺點:驅動設計變得及其複雜
疑問:有了操作系統後,驅動變得十分複雜,那為什麼還要引入操作系統?
解答:
1.為上層應用開發提供便利,只需如操作文件似的使用open、write、read等函數即可實現對各種硬體設備的使用,不論設備的具體類型和工作方式。
2.沒有操作系統,難以實現多任務並發應用程序(對於這點,目前仍然體會不到2016.12.8)
3.LINUX操作系統驅動架構
縱觀Linux內核的源代碼,設備驅動並非各類教學視頻中所介紹的簡單形式。在實際Linux驅動中,Linux內核盡量做得更多,以便底層驅動做得更少,而且也特彆強調跨平台性。Linux勢必為不同驅動子系統設計不同的框架。
為了使Linux驅動盡量具有可重用性。如同一DM9000網卡驅動最好不改一行就可以在任何一個平台跑起來。Linux採用Linux匯流排、設備和驅動模型把開發板硬體信息從驅動剝離出來,驅動只管驅動、設備只管開發板具體硬體資源、匯流排負責匹配設備和驅動,如此驅動以標準方式拿到板極信息。
看到現在,不禁有個疑問,自己製作的全新硬體或者現成開發板如mini2440、JZ2440的硬體資源(即板極信息)是如何讓內核知曉的?(下面分析均基於s3c2440)
在linux/arch/arm/mach-s3c2440/mach-smdk2440.c中有板極信息初始化函數
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
//縮進表示函數間調用關係
smdk2440_machine_init
platform_add_devices(smdk2440_devices
for (i = 0; i
platform_device_register(devs[i]);
device_initialize(&pdev->dev);
platform_device_add(pdev);/*
上面主要是遍歷設備所佔用的資源,找到對應的父資源,如果沒有定義,那麼根據資源的類型,分別賦予iomem_resource和ioport_resource,然後調用insert_resource插入資源。*/
從上面可知,通過調用platform_add_devices將smdk2440_devices定義的s3c2440中所有的platform設備註冊到了linux設備模型核心中
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
};
前面提到Linux採用Linux匯流排、設備和驅動模型來使硬體信息從驅動中剝離出來,目前設備信息已經註冊進內核,而platform驅動則是由各個驅動程序模塊分別註冊到內核,那他們兩者該如何聯繫起來?這就跟linux設備模型核心有關係了。
為了解設備與驅動間的聯繫流程,以linux/input/keyboard/gpio-keys.c按鍵為例:
module_init(gpio_keys_init);
platform_driver_register(&gpio_keys_device_driver);
driver_register(&drv->driver);
bus_add_driver(drv);
driver_attach(drv);
bus_for_each_dev(drv->bus, NULL, drv,
driver_match_device(drv, dev)
/*匹配驅動與設備的name是否相同*/ platform_match(dev, drv)
driver_probe_device(drv, dev);
really_probe(dev, drv);
/*
gpio_keys_probe詳細分析參考:http://www.cnblogs.com/cyc2009/p/4127496.html*/gpio_keys_probe(struct
setup_timer(&bdata->timer
INIT_WORK
gpio_keys_report_event
input_event
input_handle_event
input_sync(input);
input_allocate_device();
input_register_device();
input_attach_handler();
handler->connect(handler, dev,id);
/*
input子系統介紹參考:
http://blog.csdn.net/sdvch/article/details/44619699
*/ gpio_keys_isr(int irq, void *dev_id)
schedule_work(&bdata->work);
具體的file_operations成員函數在Evdev.c中實現,通過核心層Input將事件傳送至事件驅動層Evdev,執行具體的open、read、write、ioctl等函數
module_init(evdev_init);
input_register_handler(&evdev_handler);
evdev_pass_event(struct evdev_client *client
Linux內核input子系統框架如下:
到目前,似乎摸著了驅動框架的邊緣,但是回過頭去看,內核支持許多硬體,內核如何知道應該執行smdk2440_machine_init,而不是其他板子的初始化函數,而且smdk2440_machine_init是何時何處調用的尚未可知?因此,回過頭去了解內核的啟動流程。
首先,關注MACHINE_START、MACHINE_END兩個宏中間的內容
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
將MACHINE_START、MACHINE_END展開可知,定義了一個machine_desc結構體類型的變數__mach_desc_S3C2440
static const struct machine_desc __mach_desc_S3C2440 __used
__attribute__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_S3C2440,
.name = "SMDK2440",
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
};
內核的第一個C函數為start_kernel,從這出發進行分析:
start_kernel(linux/init/main.c)
setup_arch(&command_line);
mdesc = setup_machine(machine_arch_type);
lookup_machine_type(nr);
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine; /*由此可知,setup_machine函數通過機器碼machine_arch_type,來通知內核要執行哪個開發板的初始化函數*/
雖然,上面講mdesc->init_machine賦值給了init_machine,但是並未執行,搜索代碼後發現,init_machine被customize_machine調用。
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)
init_machine();
return 0;
}
arch_initcall(customize_machine);
繼續跟隨start_kernel往下看
start_kernel(init/main.c)
setup_arch(&command_line);
rest_init
kernel_thread(kernel_init, NULL, CLONE_FS
kernel_init
do_basic_setup
do_initcalls
/*
在do_initcalls處將調用customize_machine,從而過渡至smdk2440_machine_init
*/
推薦閱讀:
1.簡單明了!高手總結嵌入式軟體方法步驟,你要知道的都在這裡了
2.大神講解!談談設備驅動的作用,有無操作系統Linux設備驅動有何區別?
3.你不曾來,怎能變NB
4.開源項目makefile晦澀難懂?能改就不寫?學會編寫自己的Makefile,讓別人對你投來羨慕的目光


※先了解嵌入式系統體系結構,再一步步下手,一位嵌入式er推薦的學習發展道路
TAG:嵌入式ARM |