Uart子系统
整体学习阶段
- 阶段 1:基础概念与架构
- 了解 TTY 子系统架构 :阅读相关文档和书籍,理解分层结构
- 学习核心结构体 :重点掌握 uart_driver 、 uart_port 、 uart_ops 之间的关系
- 分析注册流程 :理解 uart_add_one_port 如何将硬件驱动与系统集成
- 阶段 2:硬件驱动分析
- 阅读厂商驱动代码 :以 imx.c 为例,理解:
- 中断处理流程 ( imx_int 、 imx_rxint 、 imx_txint )
- 数据发送/接收机制 ( imx_transmit_buffer 、 dma_rx_callback )
- 串口参数配置 ( imx_set_termios )
- 研究 DMA 操作 :理解 imx_uart_dma_init 、 start_rx_dma 等函数
- 分析 Console 实现 :理解系统启动时的串口输出机制
- 阶段 3:行规层与 TTY 核心
- 学习 n_tty.c :理解行规层如何处理特殊字符
- 分析 tty_io.c :理解 TTY 核心如何管理设备和处理用户请求
- 追踪数据流程 :从用户空间写入,经过 TTY 核心、行规层,到硬件驱动的完整流程
附上我的脑图梳理链接:
imx.c驱动分析
1.整体初始化流程:
Linux 串口驱动属于 TTY 子系统的一种 driver,整体初始化流程:
imx_serial_init
├─ uart_register_driver() //注册 UART 驱动框架
│ ├─ 创建 tty_driver
│ ├─ 绑定 tty_operations
│ ├─ tty_register_driver() //注册字符设备
│ │ ├─ alloc_chrdev_region //分配主设备号
│ │ ├─ tty_cdev_add //注册 cdev
│ │ ├─ tty_register_device //创建 /dev/ttyX
│ │ └─ proc_tty_register_driver
│
└─ platform_driver_register()
└─ serial_imx_probe() //设备实例初始化
2.TTY Driver 注册流程
(1)tty_driver结构体代表着一类tty设备,比如/dev/ttyS0和/dev/ttyS1。
(2)绑定操作函数tty_set_operations(normal, &uart_ops)
struct tty_operations uart_ops
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.write_room = uart_write_room,
.chars_in_buffer = uart_chars_in_buffer,
.ioctl = uart_ioctl,
};
(3)初始化tty_port
tty_port 是 TTY设备实例状态管理结构:
负责
- open/close计数
- buffer
- hangup
- wakeup
(4)注册字符设备
3.Platform Driver 注册
IMX 串口属于 platform device。
struct platform_driver serial_imx_driver
{
.probe = serial_imx_probe,
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
.pm = &imx_serial_port_pm_ops,
}
}
主要是probe函数:
1 解析设备树
2 寄存器映射
3 初始化 uart_port, 填充uart_port的属性,比较重要是sport->port.ops = &imx_pops;
4 注册 uart port
5 申请中断
4.数据发送过程
用户空间:
write("/dev/ttymxc0")
调用路径:
write
↓
tty_write
↓
n_tty_write
↓
uart_write
↓
uart_start
↓
imx_start_tx
TX DMA模式:
imx_start_tx
↓
schedule_work(&sport->tsk_dma_tx)
dma_tx_work
↓
dmaengine_prep_slave_sg
DMA 完成
↓
dma_tx_callback
↓
uart_write_wakeup
5.接收流程
DMA初始化:
#imx_startup
imx_uart_dma_init
RX DMA:
imx_startup
↓
start_rx_dma
↓
dmaengine_prep_dma_cyclic
DMA接收完成:
dma_rx_callback
↓
dma_rx_work
↓
tty_insert_flip_string
↓
tty_flip_buffer_push
6.中断模式(非DMA)
非DMA模式下使用中断,DMA模式下dma_r/tx_callback处理
devm_request_irq(..., imx_int) //申请中断
imx_int //中断处理函数
├─ imx_rxint //接收中断处理函数
└─ imx_txint //发送中断处理函数
#流程
UART RX interrupt
↓
imx_rxint
↓
tty_insert_flip_string
↓
tty_flip_buffer_push
n_tty.c 驱动分析
1.概述
n_tty.c 是 Linux TTY 子系统中的 N_TTY 行规程(Line Discipline) 实现,它是终端设备最基本、最常用的行规程。行规程位于 TTY 核心和硬件驱动之间,负责处理字符的输入输出、特殊字符处理、换行转换等核心功能。
2.核心数据结构
struct n_tty_data
struct n_tty_data {
/* 生产者发布 */
size_t read_head; // 读缓冲区头指针
size_t commit_head; // 已提交数据的头指
针
size_t canon_head; // 规范模式下的行尾
指针
size_t echo_head; // 回显缓冲区头指针
size_t echo_commit; // 已提交的回显数据
size_t echo_mark; // 回显标记位置
DECLARE_BITMAP(char_map, 256); // 字符映射
表,用于快速判断特殊字符
/* 私有变量 */
unsigned long overrun_time; // 溢出时间
int num_overrun; // 溢出计数
/* 非原子操作 */
bool no_room; // 缓冲区空间不足标
记
/* 标志位 */
unsigned char lnext:1, erasing:1, raw:1,
real_raw:1, icanon:1;
unsigned char push:1;
/* 生产者和消费者共享 */
char read_buf
[N_TTY_BUF_SIZE]; // 读缓冲区
DECLARE_BITMAP(read_flags,
N_TTY_BUF_SIZE); // 读标志
unsigned char echo_buf
[N_TTY_BUF_SIZE]; // 回显缓冲区
/* 消费者发布 */
size_t read_tail; // 读缓冲区尾指针
size_t line_start; // 行开始位置
/* 受输出锁保护 */
unsigned int column; // 当前列位置
unsigned int canon_column; // 规范模式下的列
位置
size_t echo_tail; // 回显缓冲区尾指针
/* 互斥锁 */
struct mutex atomic_read_lock; // 原子读锁
struct mutex output_lock; // 输出锁
};
3.行规程注册
static struct tty_ldisc_ops n_tty_ops = {
.magic = TTY_LDISC_MAGIC,
.name = "n_tty",
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.read = n_tty_read,
.write = n_tty_write,
.ioctl = n_tty_ioctl,
.set_termios = n_tty_set_termios,
.poll = n_tty_poll,
.receive_buf = n_tty_receive_buf,
.write_wakeup = n_tty_write_wakeup,
.receive_buf2 = n_tty_receive_buf2,
};
void __init n_tty_init(void)
{
tty_register_ldisc(N_TTY, &n_tty_ops);
}
4.核心功能模块
- 写操作处理 n_tty_write 函数 (L2272):
- 处理作业控制检查
- 处理待回显字符
- 根据 O_POST 标志决定是否进行字符转换
- 支持阻塞和非阻塞模式
- 接收处理 n_tty_receive_buf 函数 (L1737):
- 处理接收到的字符
- 根据终端设置进行字符转换
- 处理特殊字符(中断、退出、挂起等)
- 管理回显
- 处理规范模式和非规范模式
- 终端参数设置 n_tty_set_termios 函数 (L1763):
- 响应终端参数变化
- 更新字符映射表
- 调整缓冲区状态
- 设置操作模式(规范/非规范/原始模式)
- 特殊字符处理
- 中断字符 (INTR, Ctrl+C):发送 SIGINT 信号
- 退出字符 (QUIT, Ctrl+):发送 SIGQUIT 信号
- 挂起字符 (SUSP, Ctrl+Z):发送 SIGTSTP 信号
- 删除字符 (ERASE, Ctrl+?):删除最后一个字符
- 删除行 (KILL, Ctrl+U):删除整行
- 文件结束 (EOF, Ctrl+D):结束输入
- 开始/停止字符 (START/STOP, Ctrl+Q/Ctrl+S):控制输出流
- 缓冲区管理
- 读缓冲区 :存储接收到的字符
- 回显缓冲区 :存储待回显的字符和操作
- 缓冲区水位 :控制接收和发送的流量
5.工作流程
输入处理流程
- 硬件驱动接收字符
- 调用 tty_insert_flip_char 将字符放入 flip 缓冲区
- 调用 tty_flip_buffer_push 通知 TTY 核心
- TTY 核心调用行规程的 receive_buf 方法
- n_tty_receive_buf 处理字符:
- 字符转换(CR/LF 等)
- 特殊字符处理
- 回显处理
- 放入读缓冲区
- 唤醒等待读取的进程
输出处理流程
- 用户调用 write 系统调用
- TTY 核心调用 n_tty_write
- n_tty_write 处理:
- 作业控制检查
- 处理待回显字符
- 字符转换(如果 O_POST 启用)
- 调用底层驱动的 write 方法
- 底层驱动发送数据到硬件
6.与其他层的关系
┌─────────────────┐
│ 用户空间 │ read()/write()
└────────┬────────┘
│
┌────────▼────────┐
│ tty_io.c │ tty_read()/tty_write()
└────────┬────────┘
│
┌────────▼────────┐
│ n_tty.c │ 行规程层
│ │ - 字符转换
│ │ - 特殊字符处理
│ │ - 回显处理
│ │ - 行编辑
└────────┬────────┘
│
┌────────▼────────┐
│ 硬件驱动 │ 如 imx.c
│ (uart_driver) │ - 硬件操作
└────────┬────────┘
│
┌────────▼────────┐
│ 硬件层 │ UART 控制器
└─────────────────┘
7.关键函数分析
1.n_tty_write
功能 :处理写操作,支持字符转换和回显。
核心流程 :
- 作业控制检查
- 处理待回显字符
- 如果 O_POST 启用:
- 调用 process_output_block 处理块输出
- 调用 process_output 处理单个字符
- 否则直接调用底层驱动的 write 方法
处理阻塞/非阻塞模式
2.n_tty_receive_buf
功能 :处理接收到的字符。
核心流程 :
- 调用 n_tty_receive_buf_common 处理接收数据
- 处理特殊字符(break、parity error、overrun)
- 根据终端设置进行字符转换
- 将处理后的数据放入读缓冲区
- 唤醒等待读取的进程
3.n_tty_set_termios
功能 :响应终端参数变化。
核心流程 :
- 更新字符映射表 char_map
- 根据 ICANON、ISTRIP、IXON 等标志位设置处理模式
- 重置规范模式的行缓冲
- 设置 raw/real_raw 标志
4.n_tty_open
功能 :打开行规程。
核心流程 :
- 分配并初始化 n_tty_data 结构
- 重置缓冲区状态
- 设置初始标志
5.n_tty_close
功能 :关闭行规程。
核心流程 :
- 释放 n_tty_data 结构
- 清理资源
8.缓冲区管理机制
读缓冲区
- 大小 : N_TTY_BUF_SIZE (通常 8192 字节)
- 操作 :
- read_head :生产者(接收)写入位置
- read_tail :消费者(读取)读取位置
- commit_head :已提交的读数据位置
- canon_head :规范模式下的行结束位置
回显缓冲区
- 用于存储待回显的字符
- 支持特殊的回显操作码(ERASE、KILL 等)
- 管理回显位置和状态
9.模式分类
- 规范模式 (Canonical Mode / ICANON)
- 每次读取一行(以换行符为结束标志)
- 支持行编辑(ERASE、KILL 等)
- 支持回显
- 支持信号生成(INTR、QUIT、SUSP)
- 非规范模式 (Non-Canonical Mode)
- 读取固定数量的字符或超时
- 不支持行编辑
- 常用于串口通信
- 可配置最小字符数和超时时间
- 原始模式 (Raw Mode)
- 禁用所有特殊处理
- 字符直接传递
- 无回显
- 无信号生成
- 常用于需要精确控制的应用
n_tty.c 是 Linux TTY 子系统的核心组件,它实现了 N_TTY 行规程,负责处理终端的输入输出、特殊字符处理、行编辑等功能。它是连接用户空间和硬件驱动的桥梁,为终端设备提供了标准的操作接口。
理解 n_tty.c 对于理解整个 TTY 子系统的工作原理至关重要,它展示了如何在操作系统中实现一个完整的行规程,包括字符处理、缓冲区管理、特殊字符处理等核心功能。
tty_io.c驱动分析
关键函数分析
1. tty_open
功能 :打开 TTY 设备。
核心流程 :
- 检查设备状态
- 分配和初始化 tty_struct
- 绑定到 tty_driver
- 初始化行规程
- 处理控制终端设置
- 调用驱动的 open 方法
2. tty_read
功能 :读取 TTY 设备数据。
核心流程 :
- 检查 TTY 状态
- 调用行规程的 read 方法
- 处理阻塞和非阻塞模式
- 管理信号和中断
3. tty_write
功能 :写入 TTY 设备数据。
核心流程 :
- 检查 TTY 状态
- 调用行规程的 write 方法
- 处理阻塞和非阻塞模式
- 管理信号和中断
4. tty_ioctl
功能 :处理 TTY 控制命令。
核心流程 :
- 解析命令类型
- 调用行规程或驱动的 IO 控制方法
- 处理终端参数设置和查询
5. tty_hangup
功能 :处理 TTY 设备的挂断事件。
核心流程 :
- 调度挂断工作
- 调用 __tty_hangup 处理实际挂断
- 通知相关进程
- 清理资源
缓冲区管理
Flip 缓冲区
- 功能 :临时存储硬件驱动接收到的数据
- 操作 :
- tty_insert_flip_char :插入字符到 flip 缓冲区
- tty_flip_buffer_push :通知 TTY 核心处理 flip 缓冲区
行规程缓冲区
- 功能 :存储行规程处理后的数据
- 操作 :由行规程管理(如 n_tty.c 中的 read_buf)
信号处理
- SIGHUP :当 TTY 设备挂断时发送
- SIGINT :当用户按下 Ctrl+C 时发送
- SIGQUIT :当用户按下 Ctrl+\ 时发送
- SIGTSTP :当用户按下 Ctrl+Z 时发送