继续走读基于 Linux 6.6.123 内核的ttyserial串口驱动之serial_core抽象层。
学习驱动开发,走读驱动源码的一个核心内容是要理解这种驱动设计的意图,而不只是看代码怎么实现的。
serial_core.c 是 Linux内核 中串口驱动的核心框架代码,它抽象了串口(UART)驱动的通用逻辑,为具体硬件的 UART 驱动(如 8250、ttyAMA 等)提供统一的上层接口和通用功能实现。本文从核心功能、关键设计理念、核心数据结构、核心函数流程四个维度展开分析。
一 核心功能定位
serial_core.c 的核心目标是解耦串口通用逻辑与硬件专属逻辑:
- 向上:对接 Linux TTY 子系统,提供符合 TTY 框架的操作接口(如
tty_operations 实现);
- 向下:定义
uart_ops 硬件操作接口,让具体 UART 驱动只需实现硬件相关逻辑,无需关心 TTY 层的通用流程;
- 中间层:实现串口通用功能(波特率计算、流控、中断调度、PM 管理、RS485 配置、状态管理等)。
二 核心数据结构
[1]. 核心结构体关系

[2]. 关键结构体解析

三 核心功能模块解析
[1]. TTY 子系统对接
serial_core 实现了 TTY 子系统要求的 tty_operations 接口,核心函数包括:
uart_start / uart_stop:启动 / 停止串口发送(对接 TTY 层的发送控制);
uart_write:将用户数据写入串口发送缓冲区(环形缓冲区 circ_buf);
uart_write_room:返回发送缓冲区剩余空间;
uart_throttle / uart_unthrottle:流控(XON/XOFF/CTS/RTS)的节流 / 解除节流;
uart_flush_buffer:清空发送缓冲区;
uart_tiocmget / uart_tiocmset:获取/设置 Modem 控制线(DTR/RTS/CTS 等)。
关键逻辑:发送缓冲区管理
串口发送采用环形缓冲区(circ_buf) 设计:
- 用户数据先写入
uart_state->xmit 环形缓冲区;
- 硬件发送中断触发时,驱动从缓冲区取数据写入硬件 FIFO;
uart_write_wakeup:当缓冲区数据量低于阈值时,唤醒 TTY 层继续写数据;
__uart_start:核心发送启动逻辑,包含 PM 运行时管理(确保硬件处于激活状态)。
[2]. 波特率与参数配置
波特率计算
uart_get_baud_rate:解析 termios 中的波特率配置,处理特殊速率(如 38400 对应自定义速率),保底 9600;
uart_get_divisor:根据 UART 时钟(uartclk)和目标波特率,计算硬件分频系数(uartclk/(16*baud));
uart_update_timeout:根据帧大小(数据位 + 校验位 + 停止位)和波特率,计算帧超时时间(用于 FIFO 超时判断)。
串口参数设置
uart_change_line_settings:根据 termios 更新硬件参数(波特率、数据位、校验位、流控等),调用硬件 set_termios 接口;
uart_set_mctrl / uart_clear_mctrl:设置 / 清除 Modem 控制线(DTR/RTS),处理 RS485 模式下的控制线特殊逻辑。
[3]. 串口启停与生命周期管理
启动流程(uart_startup)

关闭流程(uart_shutdown)
- 清除 TTY 错误标记,停止发送;
- 关闭 DTR/RTS(若配置 HUPCL);
- 释放发送缓冲区(避免锁死,缓冲区释放不持有端口锁);
- 重置 PM 状态,清理运行时资源。
[4]. 流控(Flow Control)管理
支持软件流控(XON/XOFF) 和硬件流控(CTS/RTS):
uart_throttle:节流时,若开启硬件流控则清除 RTS,若开启软件流控则发送 XOFF;
uart_unthrottle:解除节流时,恢复 RTS 或发送 XON;
uart_send_xchar:发送高优先级的 XON/XOFF 字符(绕过普通发送缓冲区);
- 硬件流控额外逻辑:检测 CTS 状态变化,通过
hw_stopped 标记控制发送启停。
[5]. RS485 模式支持
RS485 是串口的半双工总线模式,serial_core 提供通用配置框架:
uart_check_rs485_flags:校验 RS485 配置标志的合法性(驱动是否支持);
uart_sanitize_serial_rs485:sanitize 配置(如 RTS 延时上限、默认 RTS 模式);
uart_set_rs485_termination:通过 GPIO 控制 RS485 总线终端电阻;
uart_set_rs485_rx_during_tx:控制是否允许发送时接收(半双工 / 全双工);
- 硬件驱动需实现
rs485_config 接口,完成具体的 RS485 硬件配置。
[6]. 中断与状态统计
uart_icount:统计串口各类中断事件(收 / 发字节、帧错误、溢出、Modem 状态变化等);
uart_get_icount:向用户态暴露中断统计数据;
uart_wait_modem_status:等待 Modem 状态(DCD/RI/DSR/CTS)变化,用于用户态同步等待;
uart_break_ctl:控制串口 Break 信号(硬件专属实现)。
[7]. 电源管理(PM)
集成 Linux 运行时 PM 框架,确保串口仅在活跃时耗电:
__uart_start:调用 pm_runtime_get 增加 PM 引用计数,激活硬件;
pm_runtime_put_autosuspend:使用自动挂起机制,闲置时自动进入低功耗;
uart_change_pm:切换串口 PM 状态(ON/SUSPEND)。
[8]. 并发与锁机制
串口是共享资源,serial_core 设计了多层锁保证并发安全:

核心宏封装:
uart_port_lock:加锁时自动增加引用计数,解锁时释放;
uart_port_unlock:解锁 + 引用计数减一,避免锁与引用计数不一致。
四 设计理念
[1]. 分层与抽象(Separation of Concerns)
- 通用逻辑与硬件解耦:serial_core 实现 TTY 对接、缓冲区管理、波特率计算、流控等通用逻辑,具体硬件驱动只需实现
uart_ops 接口(如 start_tx / set_termios);
- TTY子系统 适配:向上屏蔽串口特性,以标准 TTY 接口对接,符合 Linux 字符设备统一框架;
- 硬件无关性:通过
uart_port 抽象硬件属性,支持不同总线(IO/MMIO)、不同架构的 UART。
[2]. 模块化与可扩展
- 接口化设计:
uart_ops 定义硬件驱动的最小实现集,驱动可按需实现扩展接口(如 rs485_config、verify_port);
- RS485 扩展:通过
serial_rs485 结构体和 GPIO 控制,兼容不同硬件的 RS485 实现;
- PM 扩展:集成 runtime PM,驱动无需关心 PM 框架细节,只需适配核心的 PM 调用。
[3]. 健壮性与容错
- 边界检查:关键函数(如
uart_write_wakeup)通过 BUG_ON 检查非法状态(如端口已关闭);
- 资源清理:缓冲区释放、PM 引用计数、锁的释放都做了容错处理(如
pm_runtime_put_noidle 处理 PM 错误);
- 权限控制:修改串口硬件配置(如 IO 基址、中断)需
CAP_SYS_ADMIN 权限,避免普通用户篡改;
- 降级处理:波特率配置非法时,自动降级到旧配置或 9600 保底。
[4]. 性能优化
- 环形缓冲区:减少数据拷贝,提高发送效率;
- 自旋锁 + 引用计数:中断上下文使用自旋锁(低延迟),进程上下文使用互斥锁,兼顾性能与安全;
- runtime PM:仅在活跃时激活硬件,降低功耗;
- 锁粒度优化:
port->lock 为端口级自旋锁,而非全局锁,减少并发冲突。
[5]. 兼容性
- legacy 兼容:支持旧版 RS485 标志、自定义波特率(
UPF_SPD_MASK)等历史特性;
- TTY 标准兼容:严格遵循 TTY 子系统接口规范,保证用户态工具(如
stty)可正常使用;
- 架构兼容:通过
uart_port 的 iotype(IO/MMIO)、regshift 等字段,适配不同架构的硬件寻址方式。
五 总结
serial_core.c 是 Linux 串口驱动的“骨架”,它的设计核心是抽象通用逻辑、暴露硬件接口、对接上层框架:
- 对上层(TTY):提供标准的字符设备接口,隐藏串口硬件细节;
- 对下层(硬件驱动):封装通用流程(缓冲区、流控、PM、锁),只需实现硬件专属操作;
- 对用户:保证串口操作的一致性(如
setserial、stty 工具通用),同时支持 RS485 等扩展特性。
这种设计既符合 Linux 内核“复用、解耦、可扩展”的核心思想,也兼顾了串口驱动的高性能、高可靠性和硬件兼容性。
以上是关于 Linux 6.6.123 内核中 serial_core.c 的源码分析内容。
|