参考内核源码:drivers\tty\serial\imx.c
drivers\tty\serial\serial_core.c
drivers\tty\tty_port.c
drivers\tty\tty_io.c
uart驱动由于历史原因,实现过程不像i2c那样简单——使用独立的模块实现, 而是基于tty框架来实现的。其实现过程相对比较复制,下面跟踪内核源码来解密uart是如何实现的,由于注册过程不涉及line discipline ,这里不详细解释。
很多中文资料习惯性的将uart驱动的实现过程分层处理,这符合unix驱动的分离分层思想,但我更喜欢将uart的实现过程看成 “uart是基于tty字符设备框架的一种封装”,注册uart驱动的过程实质上是注册tty设备驱动的过程,故很多资料上都有说明,串口就是tty设备的一种。让我们来解开uart的神秘面纱吧。
首先定位到drivers\tty\serial\imx.c的 init函数:
init函数主要做了两件事情:注册了一个uart_driver和一个平台设备驱动platform_driver。下面逐一分析两个函数。
首先看一下uart_register_driver:
它在构造tty_driver结构体,并且重要的成员变量的数据来源于uart_driver,其中最重要的是tty_set_operation(normal,&uart_ops)这句,它为tty_driver提供了tty驱动的操作函数,但uart_ops在哪里实现,这里先设置一个悬念。到此就注册好了uart_driver,从源码上可以看出,实质上注册的是tty_driver,只不过披了一层华丽的外衣。
接下来看看platform_driver_register(&serial_imx_driver):
当设备树的compatible属性和of_match_table匹配时会调用serial_imx_probe函数,接下来看看probe函数的实现过程:
probe函数先解析了设备树,然后获取设备树的硬件信息 ,然后构造了uart_port结构体(目前出现的第三个重要结构体,已红色标出),其中最重要的是imx_pops,它是uart_ops结构体类型,回到之前的tty_driver的注册过程可知,tty_driver所使用的设备操作函数就是uart驱动的设备操作函数,应用程序访问uart驱动实质上是访问tty驱动,然后操作了uart的操作函数。 之后又注册了uart_port。
看到这里基本可以想象出uart与tty的关系,但字符设备驱动是在哪里注册的呢,进入uart_add_one_port函数分析,下面是函数的调用关系:
uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
tty_register_device_attr(driver, index, device, drvdata, attr_grp);
tty_cdev_add(driver, devt, index, 1);
逐层调用后到tty_cdev_add,然后在这里实现字符设备驱动的注册。
由上面分析可以总结出,注册tty_driver是为了提供与硬件无关的信息,如设备号,设备名称;注册平台设备驱动是为了设置与串口相关的硬件操作函数。实现驱动与硬件的分离,符合unix编程思想。
本人在分析内核源码的过程中省略了大量细节,有些可以分析的不到位,甚至是错误的,欢迎嵌入式前辈批评指正。。。。。。