嵌入式Linux菜鸟学习之路
2016-12-09 00:40
246 查看
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 <ben@fluff.org> */ .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 */
相关文章推荐
- 嵌入式linux学习之路
- 嵌入式Linux驱动学习之路(一)嵌入式系统的软硬件架构
- 嵌入式Linux驱动学习之路(十六)输入子系统
- 嵌入式Linux驱动学习之路(二十四)Nor Flash驱动程序
- 嵌入式Linux驱动学习之路(二十)USB设备驱动
- 嵌入式linux的学习之路[转]
- 嵌入式Linux驱动学习之路(十)字符设备驱动-my_led
- 嵌入式Linux驱动学习之路(十七)驱动程序分层分离概念-平台设备驱动
- 嵌入式Linux驱动学习之路(十二)按键驱动-poll机制
- 嵌入式Linux驱动学习之路(二十三)NAND FLASH驱动程序
- 嵌入式Linux驱动学习之路(八)创建最小的根文件系统
- 嵌入式Linux驱动学习之路(三)u-boot配置分析
- 嵌入式Linux驱动学习之路(十八)LCD驱动
- 转帖(chinaunix 的creator):我的嵌入式学习之路(二) linux button 驱动
- 1.菜鸟的linux学习之路------linux初体验
- 嵌入式Linux驱动学习之路(六)u-boot启动内核
- 嵌入式Linux驱动学习之路(七)Linux内核启动流程
- 嵌入式Linux驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入
- 嵌入式Linux驱动学习之路(十九)触摸屏驱动、tslib测试