本文约2500字,继续走读基于 Linux 6.6.123 内核的ttyserial串口驱动之8250串口核心驱动8250_port.c及相关联的文件源码,拆解这部分驱动的功能实现和设计理念。
关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。
Linux 内核中8250_port.c作为 8250 串口驱动的核心文件,承担了各类 8250 兼容 UART 的底层操作封装、硬件适配与功能抽象。
一 文件定位与核心功能
8250_port.c是 Linux 串口子系统中 8250 驱动的 “端口操作层”,专注于硬件无关的端口抽象和硬件相关的底层操作实现,核心功能可归纳为:
[1]. 多类型 UART 硬件适配
8250 系列 UART 衍生出众多变种(如 16450、16550A、16750、XR16850 等),不同型号的 FIFO 大小、中断触发机制、寄存器位定义存在差异。文件中通过serial8250_config数组定义了各型号 UART 的核心参数:
static const struct serial8250_config uart_config[] = {[PORT_8250] = {.name = "8250", .fifo_size = 1, ...},[PORT_16550A] = {.name = "16550A", .fifo_size = 16, .flags = UART_CAP_FIFO, ...},[PORT_16750] = {.name = "TI16750", .fifo_size = 64, ...},// 其他型号适配...};
该数组为不同 UART 提供 “配置模板”,包含 FIFO 大小、触发阈值、寄存器初始值、硬件能力标志(如是否支持 FIFO、睡眠模式)等,实现了 “一份代码适配多硬件” 的核心目标。
[2]. 寄存器操作抽象
不同 UART 的物理访问方式(IO 端口 / 内存映射、8/16/32 位宽、大端 / 小端)存在差异,文件封装了统一的寄存器读写接口:
io_serial_in/out:IO 端口方式的 8 位读写;
mem_serial_in/out:内存映射方式的 8 位读写;
mem16/mem32/mem32be_serial_in/out:16/32 位、大端内存映射读写;
hub6_serial_in/out:针对 HUB6 架构的特殊读写。
通过set_io_from_upio函数,根据uart_port的iotype字段自动绑定对应的读写函数,上层逻辑无需关心硬件访问细节:
static void set_io_from_upio(struct uart_port *p) {switch (p->iotype) {case UPIO_HUB6:p->serial_in = hub6_serial_in;p->serial_out = hub6_serial_out;break;case UPIO_MEM:p->serial_in = mem_serial_in;p->serial_out = mem_serial_out;break;// 其他类型绑定...}}
[3]. FIFO 管理
FIFO 是 16550A 及以上型号的核心特性,文件实现了 FIFO 的初始化、清空、触发阈值配置等关键操作:
serial8250_clear_fifos:清空收发 FIFO;
serial8250_clear_and_reinit_fifos:清空并重新初始化 FIFO(恢复配置的触发阈值);
size_fifo:自动检测 FIFO 大小(通过循环回送测试)。
FIFO 管理的核心设计是 “硬件能力驱动”:仅当 UART 标记UART_CAP_FIFO时,才执行 FIFO 相关操作,保证对 8250 等无 FIFO 型号的兼容。
[4]. 自动硬件探测(Autoconfig)
驱动无需手动配置 UART 型号,通过autoconfig函数实现硬件自动识别:
存在性检测:通过读写 IER 寄存器(中断使能寄存器)验证 UART 是否存在;
环路测试:设置 MCR 寄存器的 LOOP 位,验证 MSR 寄存器的反馈,确认硬件有效性;
型号识别:
读取 IIR 寄存器的 FIFO 使能位,区分 8250(无 FIFO)、16550(FIFO 未启用)、16550A(FIFO 启用);
对 16550A 及以上型号,进一步检测 EFR 寄存器、FIFO 大小、特殊位(如 XScale 的 UUE 位),识别具体变种(如 16750、XR16850)。
自动探测机制让驱动适配 “未知硬件环境”,是 Linux 驱动 “即插即用” 的核心体现。
[5]. RS485 软件仿真
针对无硬件 RS485 支持的 UART,文件实现了 RS485 软件仿真(serial8250_em485_*系列函数):
通过定时器控制 RTS 引脚的高低电平,模拟 “发送前置位 RTS、发送后释放 RTS”;
封装serial8250_em485_config接口,上层可通过标准serial_rs485结构体配置 RS485 参数;
支持 RTS 前置 / 后置延迟、总线终止、收发同时使能等特性。
[6]. 运行时电源管理(RPM)
为适配低功耗场景,文件实现了基于pm_runtime的电源管理:
serial8250_rpm_get/put:获取 / 释放电源引用,触发设备唤醒 / 休眠;
serial8250_rpm_get_tx/put_tx:针对发送流程的专用电源管理,避免发送过程中设备休眠。
[7]. 中断与睡眠模式支持
对支持睡眠模式的 UART(标记UART_CAP_SLEEP),通过serial8250_set_sleep函数配置 IER 寄存器的睡眠位;
封装默认中断处理函数serial8250_default_handle_irq,为上层中断处理提供基础;
针对 RSA 等特殊 UART,实现 FIFO 使能 / 禁用的专用逻辑。
二 核心设计理念
[1]. 分层抽象:隔离硬件差异与上层逻辑
驱动采用 “三层抽象” 设计:
硬件适配层:uart_config数组、寄存器读写函数(如mem_serial_in),隔离不同 UART 的硬件细节;
端口操作层:FIFO 管理、自动探测、RS485 仿真等,实现通用硬件操作;
接口层:导出serial8250_clear_and_reinit_fifos、serial8250_em485_config等符号,供上层(如 8250_core.c)调用。
分层设计让上层逻辑聚焦 “串口数据收发”,底层聚焦 “硬件适配”,符合 Linux 内核 “高内聚、低耦合” 的设计原则。
[2]. 能力驱动:基于硬件特性的条件执行
驱动通过capabilities字段(如UART_CAP_FIFO、UART_CAP_SLEEP、UART_CAP_RPM)标记硬件能力,所有操作均先检查能力标志再执行:
static void serial8250_clear_fifos(struct uart_8250_port *p) {if (p->capabilities & UART_CAP_FIFO) { // 仅FIFO设备执行清空serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO | ...);}}
这种设计避免了对无对应能力的硬件执行无效操作,同时简化了多硬件适配的逻辑分支。
[3]. 资源安全:自旋锁与中断保护
串口操作涉及硬件寄存器和共享资源(如uart_port),驱动通过自旋锁(port->lock)保护临界区:
spin_lock_irqsave(&port->lock, flags); // 加锁并保存中断状态// 临界区操作(如修改IER、LCR寄存器)spin_unlock_irqrestore(&port->lock, flags); // 解锁并恢复中断
同时,在寄存器读写、FIFO 操作等关键流程中禁用本地中断,避免并发访问导致的寄存器值错乱,保证操作的原子性。
[4]. 兼容性优先:向下兼容经典硬件
驱动始终兼容最基础的 8250 芯片,同时逐步扩展新特性:
无 FIFO 的 8250/16450 使用字节级收发,有 FIFO 的型号启用批量收发;
自动探测流程优先验证基础寄存器(IER、MCR、IIR),再检测扩展特性(EFR、64 字节 FIFO);
对有缺陷的硬件(如 Fintek F81216A、Oxford 952 rev B)提供专用兼容逻辑。
三 关键技术细节解析
[1]. 除数锁存器(DLM/DLL)操作
UART 的波特率由除数锁存器(DLM/DLL)控制,文件封装了default_serial_dl_read/write函数,统一读写除数寄存器:
static u32 default_serial_dl_read(struct uart_8250_port *up){unsigned char dll = serial_in(up, UART_DLL);unsigned char dlm = serial_in(up, UART_DLM);return dll | dlm << 8;}
读写前需设置 LCR 寄存器的 DLAB 位,驱动在自动探测、波特率配置时自动处理该逻辑,上层无需关注。
[2]. 中断处理基础
文件定义了默认中断处理函数serial8250_default_handle_irq,并通过set_io_from_upio绑定到uart_port的handle_irq字段。该函数是中断处理的入口,负责解析中断类型(接收、发送、线状态等)并调用对应处理逻辑,为上层中断处理提供基础框架。
[3]. 调试与日志控制
驱动通过DEBUG_AUTOCONF宏控制自动探测过程的调试日志:
#if 0#define DEBUG_AUTOCONF(fmt...) printk(fmt)#else#define DEBUG_AUTOCONF(fmt...) do { } while (0)#endif
调试模式下可输出硬件探测的关键参数(如 IIR 值、FIFO 大小、芯片 ID),便于定位硬件适配问题,发布模式下关闭日志以减少性能开销。
四 总结
8250_port.c作为 Linux 8250 串口驱动的核心,其设计与实现体现了 Linux 内核驱动的经典思想:
抽象化:通过配置数组、统一接口隔离硬件差异,实现 “一份代码适配多硬件”;
兼容性:向下兼容经典 8250 芯片,向上支持新型 UART 的扩展特性;
安全性:通过自旋锁、中断保护保证资源访问的原子性;
可扩展性:预留 RS485、RPM 等扩展接口,适配工业场景的复杂需求。
该文件的设计不仅支撑了各类 8250 兼容 UART 的稳定运行,也为其他字符设备驱动提供了参考范式 ——硬件无关逻辑与硬件相关逻辑分离,通用操作与专用操作分层。理解该文件的实现细节,对嵌入式开发者调试串口问题、适配自定义 UART 硬件具有重要的指导意义。
以上为全文内容。
这里是女程序员的笔记本
15年+嵌入式软件工程师兼二胎宝妈
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助