uart是Universal Asynchronous Receiver and Transmitter的缩写.翻译成中文即为”通用异步收发器”.它是串口设备驱动的封装层。它是linux在tty的基础上又做了一层封装,通过该封装层,可以比较容易的编写新的串口驱动程序。
一、uart数据结构
uart建立在tty之上,它是真实的驱动和tty之间的桥梁,其涉及到的关键数据结构及其相互关系如下图所示:
从上图可以看出,一个uart_driver就对应一个tty_driver,而一个uart_driver可以包括多个uart_state,每个uart_state对应一个uart_device,每个uart_device都有自己的tty_port、uart_port以及收发缓存。
circ_buf是一个发送缓存,在写数据时,当tty层调用驱动提供的写函数时,数据会首先进入circ_buf的环形缓存区,然后由uart_port从缓存区中取数据,将其写入到串口设备中。
当uart_port从串口设备接收到数据时,它会直接将数据放入tty的缓存中(tty缓存属于tty_port),进而放入对应的line discipline的缓存区中。
二、和其它模块的关系
2.1 和上层即tty的关系
从设计的角度上,uart依赖于tty,但是tty并不依赖于uart层存在,所以uart需要提供给tty的接口都以函数指针的形式保存在某种数据结构中,并且会通过tty提供的通用接口注册到tty中,以供tty随后调用,这也是一种很常见的设计方式。uart提供给tty的接口都保存在数据结构tty_operations中,并通过tty_set_operations注册到了tty中。
因此uart需要上层提供一些接口给它,比较重要的几个为
- alloc_tty_driver:申请一个tty驱动的数据结构
- tty_set_operations:注册uart的操作函数到tty中。
- tty_port_init、tty_register_driver:初始化tty_port数据结构、释放tty_port关联的缓存
- tty_register_driver:注册tty驱动到tty中
- tty_port_register_device_attr:将一个tty设备注册到系统中
- tty_unregister_device:将一个tty设备从系统中删除
- tty_insert_flip_char和tty_insert_flip_string:将字符插入到tty缓存中
2.2 和下层即真实驱动的关系
- uart_register_driver:将一个实际的串口驱动uart driver注册到uart框架中,这一步会
- 分配并初始化相关联的tty_driver
- 分配与每个uart_state关联的tty_port的数据结构并初始化
- 将tty驱动注册到tty框架
- uart_add_one_port:这一步在驱动检测到了实际的物理设备后执行(利用probe或者是手动),根据uart_port的信息配置串口,并将其注册到系统中,利用tty_port_register_device_attr完成。在完成者一步后(在这一步中会先将设备添加到sysfs中,然后发送udev消息),udev即可得到通知,udev机制会在dev下创建相应的设备节点,随后就可以使用该设备了。
- uart_insert_char:驱动通过该接口将数据送到uart框架,进而送到tty缓存。需要注意的是,利用该接口时往往还要使用tty框架使用的接口tty_flip_buffer_push进一步将数据刷新到tty对应的line discipline中去。当然也可以不做,而由line discipline的程序自己做,比如tty_ldisc_N_TTY就使用了tty_flush_to_ldisc来将数据刷到line discipline中。
- uart_handle_dcd_change:处理载波检测状态改变事件。
- uart_handle_cts_change:处理clear-to-send状态改变事件,这个接口和上一个接口是uart提供的不依赖于tty的事件处理函数,“可以认为”是串口所特有的事件处理。
总体上uart和上下层的关系如下图所示:
上层需要使用的下层的接口都是通过数据结构中的函数指针实现的,本图没有将它们画出。
三、读写操作
3.1 打开
- 由tty找到uart_driver,这个在注册驱动时会保存在tty中
- 由uart_driver以及tty的index找到对应的uart_state,tty的index在tty_open时由tty_init_dev调用initialize_tty_struct来设置,实际上即根据设备的设备号找到的
- 将uart_state保存在tty以及uart_port中
- 将tty和tty_port关联起来
- 调用uart_startup将继续初始化uart使得它可以开始工作
3.2 读
- 调用uart_insert_char将数据放入tty缓存或者调用tty_prepare_flip_string/tty_prepare_flip_string_flags来申请一片tty缓存,然后由自己把数据放到tty缓存
- 调用tty_flip_buffer_push和tty_schedule_flip或者通过其它机制将数据刷到line discipline中,然后由tty对应的line discipline读出数据并返回给用户
3.3 写
- 将数据放入uart设备对应的circ_buf中
- 调用uart_start,该函数会最终调用驱动提供的start_tx函数来启动发送