1. 程式人生 > >嵌入式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 < num; i++)  
            platform_device_register(devs[i]);
                device_initialize(&pdev->dev);
                platform_device_add(pdev);
/*
上面主要是遍歷裝置所佔用的資源,找到對應的父資源,如果沒有定義,那麼根據資源的型別,分別賦予iomem_resource和ioport_resource,然後呼叫insert_resource插入資源。                   
參考資源:http://blog.csdn.net/yaozhenguo2006/article/details/6784895
*/

從上面可知,通過呼叫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 <[email protected]> */
    .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
*/