当前位置:首页>Linux>深入解析 Linux 内核 8250 串口驱动(8250_port.c):功能实现与设计理念

深入解析 Linux 内核 8250 串口驱动(8250_port.c):功能实现与设计理念

  • 2026-09-04 22:19:53
深入解析 Linux 内核 8250 串口驱动(8250_port.c):功能实现与设计理念
Hello,大家好,我是程序媛MM。

本文约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 硬件具有重要的指导意义。

以上为全文内容。

往期文章(欢迎订阅技术分享栏目全部文章):

【从零开始撸内核驱动源码】:以ttyserial(串口驱动)为例,串联字符设备驱动基础知识点的学习计划
Linux内核源码顶层 Makefile分析并单独编译调试内核自带的驱动
【从零开始撸内核驱动源码】:ttynull驱动
Linux内核驱动安装失败问题调试及解决方法
Linux内核驱动源码走读之编译内核及外部驱动实操指南

“谢谢你看到这里”

这里是女程序员的笔记本

 15年+嵌入式软件工程师兼二胎宝妈

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助

最新文章

随机文章