linux-uart

来源:互联网 发布:美橙互联域名如何备案 编辑:程序博客网 时间:2024/06/05 17:58

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需要上层提供一些接口给它,比较重要的几个为

  1. alloc_tty_driver:申请一个tty驱动的数据结构
  2. tty_set_operations:注册uart的操作函数到tty中。
  3. tty_port_init、tty_register_driver:初始化tty_port数据结构、释放tty_port关联的缓存
  4. tty_register_driver:注册tty驱动到tty中
  5. tty_port_register_device_attr:将一个tty设备注册到系统中
  6. tty_unregister_device:将一个tty设备从系统中删除
  7. tty_insert_flip_char和tty_insert_flip_string:将字符插入到tty缓存中

标为黑体的为最关键的i几个,在uart的驱动中,所有的驱动都会被设置TTY_DRIVER_DYNAMIC_DEV标记,根据tty中的学习,我们知道这意味着在调用tty_register_driver时,设备不会被注册到系统中,也就是在uart注册驱动时不会将设备添加到系统中,将设备添加到系统中的工作是由tty_port_register_device_attr完成的。一个uart_driver支持的设备数目会被保存在uart_driver数据结构的nr中,并且会保存在相关联的tty_driver的num中,在调用tty_register_driver时,系统会为该设备分配相应数目的设备号(起始设备号取决于驱动是否指定了起始设备号,如果没有指定就由系统分配,否则取驱动设置的)。

2.2 和下层即真实驱动的关系

类似于uart层和tty层的关系,uart层被设计为不依赖于真实的驱动,因而它不会直接调用驱动的函数,所有它需要驱动提供的功能都保存在uart_port的ops字段所指向的数据结构中,驱动需要通过调用uart_register_driver将uart_driver注册到uart框架中,uart_port中的信息由驱动提供并负责填充。
uart框架向驱动提供的几个最主要的接口包括:
  1. uart_register_driver:将一个实际的串口驱动uart driver注册到uart框架中,这一步会
    • 分配并初始化相关联的tty_driver
    • 分配与每个uart_state关联的tty_port的数据结构并初始化
    • 将tty驱动注册到tty框架
  2. uart_add_one_port:这一步在驱动检测到了实际的物理设备后执行(利用probe或者是手动),根据uart_port的信息配置串口,并将其注册到系统中,利用tty_port_register_device_attr完成。在完成者一步后(在这一步中会先将设备添加到sysfs中,然后发送udev消息),udev即可得到通知,udev机制会在dev下创建相应的设备节点,随后就可以使用该设备了。
  3. 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中
  4. uart_handle_dcd_change:处理载波检测状态改变事件。
  5. uart_handle_cts_change:处理clear-to-send状态改变事件,这个接口和上一个接口是uart提供的不依赖于tty的事件处理函数,“可以认为”是串口所特有的事件处理。

总体上uart和上下层的关系如下图所示:


上层需要使用的下层的接口都是通过数据结构中的函数指针实现的,本图没有将它们画出。

三、读写操作

3.1 打开

类似于tty中的分析,open操作是最关键的操作,它由tty中的open调用,会:
  1. 由tty找到uart_driver,这个在注册驱动时会保存在tty中
  2. 由uart_driver以及tty的index找到对应的uart_state,tty的index在tty_open时由tty_init_dev调用initialize_tty_struct来设置,实际上即根据设备的设备号找到的
  3. uart_state保存在tty以及uart_port中
  4. 将tty和tty_port关联起来
  5. 调用uart_startup将继续初始化uart使得它可以开始工作

3.2 读

读操作实际上就是从设备获取信息,linux tty以及uart的设计中,每个uart接口都对应一个tty,当串口收到数据时,它应该
  1. 调用uart_insert_char将数据放入tty缓存或者调用tty_prepare_flip_string/tty_prepare_flip_string_flags来申请一片tty缓存,然后由自己把数据放到tty缓存
  2. 调用tty_flip_buffer_push和tty_schedule_flip或者通过其它机制将数据刷到line discipline中,然后由tty对应的line discipline读出数据并返回给用户

3.3 写

写就是将数据写入到设备中,linux tty以及uart的设计中,每个uart接口都对应一个tty,当串口收到数据时,当tty往uart写数据时,它调用了uart的写函数,uart的写函数很简单:
  1. 将数据放入uart设备对应的circ_buf中
  2. 调用uart_start,该函数会最终调用驱动提供的start_tx函数来启动发送

3.4 其它操作

uart支持的其它操作是通过ioctl来实现的。包括设置串口的波特率,流控方式等等。
原创粉丝点击