當前位置:
首頁 > 最新 > 一位嵌入式Linux菜鳥設備驅動學習之路

一位嵌入式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,讓別人對你投來羨慕的目光


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嵌入式ARM 的精彩文章:

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

TAG:嵌入式ARM |