1.与应用层的接口
这一层的操作是基于文件的。众所周知,UART属于TTY设备。因此实际执行的函数是tty_write@tty_io.c。
2.tty_ldisc.ops->write
3.n_tty_write@n_tty.c
4.tty_struct.ops->write => tty_operations->write@serial_core.c
5.uart_write@serial_core.c
6.__uart_start@serial_core.c
7.uart_port.ops->start_tx=>uart_ops->write@uartlite.c
这一层以下,就和具体的设备有关了。这里以Xlinux的uartlite为例。
8.ulite_start_tx
9.ulite_transmit
这里已经是具体的寄存器操作了。
Read的过程要复杂一些,可分为上层调用部分和底层驱动部分。
上层调用部分:
1.tty_read@tty_io.c
2.tty_ldisc.ops->read
3.n_tty_read@n_tty.c
上层调用,到这里为止。这个函数执行到add_wait_queue时,会等待底层驱动返回接收的数据。底层驱动可以是中断式的,也可以是轮询式的。函数会调用copy_from_read_buf,将内核态的数据搬到用户态。
底层驱动部分
1.ulite_startup=>ulite_isr@uartlite.c
这里仍以Xlinux的uartlite为例。初始化阶段注册ulite_isr中断服务程序。
2.ulite_receive@uartlite.c
具体的寄存器操作。
3.tty_flip_buffer_push@tty_buffer.c
4.tty_schedule_flip@tty_buffer.c
调用schedule_work唤醒上层应用。
1.select@select.c
2.core_sys_select@select.c
3.do_select@select.c
I2C的GPIO实现的代码在drivers/i2c/busses/i2c-gpio.c中。从本质来说这是一个i2c_adapter,它使用i2c_bit_add_numbered_bus函数将自己注册到I2S总线上。
由于i2c_adapter是直接寻址设备,因此I2C的GPIO实现是以platform driver的方式注册的,可以在/sys/devices/platform/i2c-gpio.0/i2c-0路径下查看总线上现有的设备(这里的0指的是0号i2c总线,某些系统中可能有不止一条i2c总线)。
drivers/i2c/i2c-dev.c: i2cdev_write
drivers/i2c/i2c-core.c: i2c_master_send
drivers/i2c/i2c-core.c: i2c_transfer
drivers/i2c/i2c-core.c: __i2c_transfer
这里会调用i2c_adapter结构的algo->master_xfer函数。具体到i2c-gpio就是:
drivers/i2c/algos/i2c-algo-bit.c: bit_xfer
drivers/i2c/algos/i2c-algo-bit.c: sendbytes -- 发送字节
drivers/i2c/algos/i2c-algo-bit.c: i2c_outb -- 发送bit
以下以SDA线的操作为例。这里调用i2c_algo_bit_data结构的setsda函数。具体到i2c-gpio就是:
drivers/i2c/busses/i2c-gpio.c: i2c_gpio_setsda_val
再以下就是具体的GPIO寄存器操作了。
静态:直接编译到内核中。
动态:编译成.ko文件,然后用insmod命令加载之。
驱动按设备总线类型分,可分为两类:
1.直接地址访问设备。这类设备的驱动被称为platform driver。
2.间接地址访问设备,也称作总线地址访问设备。这类设备的驱动按总线类型,可分为PCI驱动、USB驱动等等。
作为驱动的实现来说,首先就是要实现驱动的probe函数。probe函数起到了驱动的初始化功能。但之所以叫probe,而不是init,主要是由于probe函数,还具有设备检测的功能。
以下以s3c24xx_uda134x音频驱动为例,说一下设备检测的机制:
1.驱动模块主要处理两个对象:设备和驱动。s3c24xx_uda134x属于platform driver,因此它的设备对象的数据结构是platform_device类型的,而它的驱动对象的数据结构是platform_driver类型的。
2.驱动对象的注册。使用module_platform_driver宏即可。
3.生成设备对象。
4.设备检测时,首先根据总线类型,调用总线驱动的probe函数,再根据设备类型调用设备驱动的probe函数。最终完成设备对象和驱动对象的绑定。
module_platform_driver定义在include/linux/platform_device.h中。
它的主要内容是注册和反注册驱动,以及module_init/module_exit。
介绍module_init/module_exit的文章很多,这里仅将要点摘录如下:
1.__init宏修饰的函数会链接到.initcall.init段中,这个段只在内核初始化时被调用,然后就被释放出内存了。
2.定义别名。
int init_module(void) __attribute__((alias(#initfn)));
不管驱动模块的init函数名字叫什么,都定义别名为init_module。这样insmod在加载.ko文件时,就只找init_module函数的入口地址就可以了。
这里以mini2440为例,说明一下静态生成设备对象的过程:
在arch/arm/mach-s3c24xx/mach-mini2440.c中有一个mini2440_devices数组。这个数组定义了板子初始化阶段静态生成的设备对象。
在该文件的mini2440_init函数中,调用platform_add_devices函数,将mini2440_devices数组添加到内核中。
这个例子中和音频有关的设备为s3c_device_iis、uda1340_codec和mini2440_audio。这三个设备的name分别为s3c24xx-iis、uda134x-codec和s3c24xx_uda134x,与相应驱动名称一致,正好对应ASOC中的Platform、Codec和Machine。
PC上的情况比较特殊。由于设备数量种类繁多,因此并不采用将所有设备对象都放到一个数组中的方式。而是在驱动模块加载时,在模块的init函数中,生成设备对象。某些嵌入式驱动模块也采用了类似的方法。
drivers/base/driver.c: driver_register
drivers/base/bus.c: bus_add_driver
drivers/base/dd.c: driver_attach
drivers/base/dd.c: _driver_attach
drivers/base/dd.c: driver_probe_device
drivers/base/dd.c: really_probe
Linux既然由若干模块组成,那么这些模块在启动阶段,必然存在一个加载顺序的问题。这方面可以通过以下的宏来确定加载的优先级。
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
上面的宏中,越前面的优先级越高。同一优先级下,按照链接顺序确定加载顺序,因此可以通过修改链接文件来修改加载顺序,但一般来说,并没有这个必要。
运行阶段的加载,由于是动态加载,没有加载顺序的问题(程序员代码控制加载顺序),因此这些宏都被编译成module_init。
常见的Camera从软件角度,可分为三类:
1.Soc Camera。
2.USB Camera。
3.IP Camera。
其中,前两类设备需要相关的驱动程序。Linux内核配置方法如下:
Device Drivers --->
<*> Multimedia support --->
[*] Cameras/video grabbers support
[*] Media USB Adapters --->
<*> USB Video Class (UVC)
<*> GSPCA based webcams --->
[*] V4L platform devices --->
<*> SoC camera support
UVC是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,目前已成为USB org标准之一。
GSPCA同样是一种标准,早期的很多摄像头用的就是这一标准。
这两类设备的应用层接口为V4L2。教程可参见:
http://blog.csdn.net/mmz_xiaokong/article/details/5666993
对于UVC设备,可以用相关工具直接获取图像。常用的工具有:
GTK+ UVC Viewer
http://guvcview.sourceforge.net/
luvcview
https://github.com/ksv1986/luvcview
IP Camera无须驱动,只要提供网口就行了。有的设备支持wifi,连网口也省了。
IP Camera的应用层接口一般为网络协议接口,如TCP/IP、HTTP、RTP/RTSP等。
GPIO相对来说是最简单的一类驱动,代码在drivers/gpio文件夹下。
从硬件来说,GPIO有两种输出模式:
push-pull模式电平转换速度快,但是功耗相对会大些。
open-drain模式功耗低,且同时具有“线与”的功能。(同时注意GPIO硬件模块内部是否有上拉电阻,如果没有,需要硬件电路上添加额外的上拉电阻)
可在系统的/sys/class/gpio路径下查看当前的GPIO设备。
参考文献:
http://blog.csdn.net/mirkerson/article/details/8464290
http://www.cnblogs.com/lagujw/p/4226424.html
http://www.aichengxu.com/view/18529
http://wenku.baidu.com/view/a6c4b6bfc77da26925c5b001.html?re=view
include/linux/gpio.h: gpio_set_value
include/asm-generic/gpio.h: __gpio_set_value
drivers/gpio/gpiolib.c: gpiod_set_raw_value
这里会调用gpio_chip结构的set函数指针。我们只需要在定义gpio_chip结构的时候,将寄存器操作函数设置到set函数指针中即可。gpio_chip结构可在模块初始化阶段,使用gpiochip_add函数添加到系统中。
1.轮询方式
drivers/input/keyboard/gpio_keys_polled.c中提供了轮询方式的输入事件处理。该实现主要是注册了一个名为gpio-keys-polled的input_polled_dev。而input_polled_dev则利用queue_delayed_work实现任务的调度。
2.中断方式
drivers/input/keyboard/gpio_keys_polled.c中提供了中断方式的输入事件处理。该实现最终调用了request_irq注册中断处理函数。
无论是轮询还是中断,处理的结果最终都会调用input_event函数生成应用层可访问的事件。
代码的编译配置在:
Device Drivers --->
Input device support --->
Keyboards --->
<*> GPIO Buttons
<*> Polled GPIO buttons
除了gpio_direction_input和gpio_direction_output之外,devm_gpio_request_one和gpio_request_one也可用于设置使用方向,只要设置好flag参数即可。内核中的leds-gpio和gpio-keys模块都是使用后面的方法设置使用方向的。
此外,在board级的GPIO实现中,需要注意以下几点:
1.是否有单独的寄存器用于设置使用方向。这个问题与具体的硬件有关,有的硬件可根据赋值语句的方向,自动切换GPIO的使用方向。
2.如果有单独的设置使用方向的寄存器的话,需要在gpio_set_value和gpio_get_value函数的实现中,将使用方向的设置操作添加进去。I2C的algo代码并不会在set或着get操作时,修改GPIO的使用方向。
active_low的设置要根据硬件的连接,如果按下按键为高电平那么active_low=0,如果按下按键为低电平那么active_low=1.如果这个参数搞错了,按键松开后就不断发按键键码,表现为屏幕上乱动作。
也因为active_low的存在,input_event返回的value实际上并不是GPIO的值,1表示按键按下,0表示按键抬起。
您的打赏,是对我的鼓励