乐于分享
好东西不私藏

【从零开始撸内核驱动源码】:深度解析Linux内核8250串口核心驱动8250_core.c的设计框架与核心实现

【从零开始撸内核驱动源码】:深度解析Linux内核8250串口核心驱动8250_core.c的设计框架与核心实现

Hello,大家好,我是程序媛MM。

本文约2000字,继续走读基于 Linux 6.6.123 内核的ttyserial串口驱动之8250串口核心驱动8250_core.c及相关联的文件源码,拆解这部分驱动的设计框架、核心意图以及功能实现

关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。


8250/16550 系列串口是计算机系统中最经典的串行通信硬件架构,Linux 内核中的8250_core.c作为该类串口的核心驱动,承载了从 ISA 传统串口到平台化串口设备的全场景支持。本文将从设计框架、核心意图、功能实现三个维度,拆解这份经典驱动的底层逻辑。

一 驱动功能与设计核心意图


[1]. 核心功能

8250_core.c是 Linux 串口子系统的 “通用基座”,负责适配所有 8250/16550 兼容串口,包括:

ISA 总线兼容的传统 8250/16550 端口;

PnP(即插即用)串口、早期初始化的early_serial_setup()端口;

用户态可配置的 “虚拟(phantom)” 端口;

平台设备(platform_device)化的serial8250端口;

动态注册的端口(如 PCMCIA 调制解调器、PCI 多串口卡)。

[2]. 设计意图

兼容性优先:向下兼容 8250/16450/16550 等全系列硬件,向上适配不同总线(ISA/PCI/ 平台总线);

中断高效处理:针对 ISA 共享中断的 “边缘触发” 特性做特殊优化,避免中断卡死;

可扩展性:支持运行时动态注册 / 注销串口,适配热插拔设备(如 PCMCIA);

控制台适配:支持串口控制台(printk输出),满足系统启动 / 调试需求;

低功耗与 PM:集成运行时电源管理(PM),支持串口挂起 / 恢复。

二 整体设计框架


8250_core.c遵循 Linux 内核驱动的 “分层设计” 思想,整体框架可拆解为 5 层:

控制台层(console):串口控制台输出

核心管理层:端口注册/注销、资源管理

中断层:硬件中断/定时器模拟中断处理

硬件适配层:8250寄存器操作、特性适配

总线适配层:平台设备/ISA/PNP总线适配

[1]. 核心数据结构

驱动的核心数据结构是对内核通用串口结构的扩展,关键结构如下:

[2]. 核心宏与配置项

驱动通过宏定义实现编译期 / 运行期配置,关键宏:

UART_NR:支持的最大串口数(由CONFIG_SERIAL_8250_NR_UARTS控制);

PASS_LIMIT:中断处理循环最大次数(防止 ISA 共享中断死循环);

SHARE_IRQS:是否允许中断共享(边缘触发中断下不安全,默认可控);

SERIAL_PORT_DFNS:平台特有串口配置(如 SPARC/Alpha 架构)。

三 核心功能拆解


[1]. 中断处理:ISA 共享中断的特殊优化

串口中断是驱动性能的核心,8250_core.c针对 ISA 总线 “中断共享 + 边缘触发” 的特性做了极致优化:

>>中断处理入口:serial8250_interrupt

staticirqreturn_tserial8250_interrupt(int irq, void *dev_id){    struct irq_info *i = dev_id;    struct list_head *l, *end = NULL;    int pass_counter = 0, handled = 0;    spin_lock(&i->lock);    l = i->head;    do {        struct uart_8250_port *up = list_entry(l, struct uart_8250_port, list);        if (up->port.handle_irq(&up->port)) { // 处理单个串口中断            handled = 1;            end = NULL// 有中断处理,重置结束标记        } else if (end == NULL)            end = l;   // 无中断,标记当前节点为结束点        l = l->next;        if (l == i->head && pass_counter++ > PASS_LIMIT)            break// 防止死循环    } while (l != end);    spin_unlock(&i->lock);    return IRQ_RETVAL(handled);}

设计意图:

遍历共享当前中断的所有串口,确保每个串口的中断都被处理;

通过end标记避免重复遍历,通过PASS_LIMIT防止 ISA 中断线卡死;

只有当所有串口的中断都处理完毕(中断线解除断言),才退出循环。

>>中断链管理:serial_link_irq_chain/serial_unlink_irq_chain

基于中断号哈希(irq % NR_IRQ_HASH)管理串口列表;

首次注册中断时调用request_irq,最后一个串口注销时释放中断;

自旋锁保护中断链表,避免并发修改。

>>无硬件中断的降级方案:定时器模拟中断

对于无硬件中断的串口(如irq=0),驱动通过定时器模拟中断:

staticvoidserial8250_timeout(struct timer_list *t) {    struct uart_8250_port *up = from_timer(up, t, timer);    up->port.handle_irq(&up->port); // 轮询串口状态    mod_timer(&up->timer, jiffies + uart_poll_timeout(&up->port));}

代价:CPU 开销增加,但保证串口功能可用(适配老旧硬件)。

[2]. 端口管理:动态注册与注销

驱动支持运行时动态管理串口,核心接口为serial8250_register_8250_port/serial8250_unregister_port,满足热插拔设备需求。

>>端口注册流程

intserial8250_register_8250_port(conststruct uart_8250_port *up) {    mutex_lock(&serial_mutex); // 全局互斥锁,防止并发注册    // 1. 查找匹配/空闲端口    struct uart_8250_port *uart = serial8250_find_match_or_unused(&up->port);    if (!uart) {        uart = serial8250_setup_port(nr_uarts); // 初始化新端口        nr_uarts++;    }    // 2. 配置端口参数(IO基址、中断、时钟等)    uart->port.iobase = up->port.iobase;    uart->port.irq = up->port.irq;    // 3. 应用硬件 quirks(如跳过TXEN测试)    serial8250_apply_quirks(uart);    // 4. 注册到串口核心层    ret = uart_add_one_port(&serial8250_reg, &uart->port);    mutex_unlock(&serial_mutex);    return ret;}

关键设计:

优先复用已有端口,避免资源浪费;

全局互斥锁serial_mutex保护端口列表,防止并发修改;

支持 RS485、DMA 等扩展特性配置。

>>端口注销流程

voidserial8250_unregister_port(int line) {    mutex_lock(&serial_mutex);    struct uart_8250_port *uart = &serial8250_ports[line];    uart_remove_one_port(&serial8250_reg, &uart->port); // 从核心层注销    // 恢复端口为默认状态(供后续复用)    uart->port.type = PORT_UNKNOWN;    mutex_unlock(&serial_mutex);}

[3]. 串口控制台:系统启动与调试的关键

驱动集成串口控制台功能,支持console=ttyS0内核参数,核心实现:

>>控制台结构定义

static struct console univ8250_console = {    .name       = "ttyS",    .write      = univ8250_console_write, // 控制台输出函数    .setup      = univ8250_console_setup, // 控制台初始化    .match      = univ8250_console_match, // 匹配earlycon    .flags      = CON_PRINTBUFFER | CON_ANYTIME// 支持随时输出    .index      = -1,};

>>控制台输出:univ8250_console_write

直接操作 8250 寄存器发送字符,绕过通用串口层,确保系统启动早期(甚至驱动未完全初始化)也能输出printk信息。

[4]. 平台设备适配:总线无关性

驱动通过platform_driver适配不同总线的串口设备,核心接口:

serial8250_probe:解析平台设备的串口配置(IO 基址、中断等),注册端口;

serial8250_remove:注销平台设备关联的串口;

serial8250_suspend/resume:电源管理,挂起 / 恢复串口。

static struct platform_driver serial8250_isa_driver = {    .probe      = serial8250_probe,    .remove     = serial8250_remove,    .suspend    = serial8250_suspend,    .resume     = serial8250_resume,    .driver     = {        .name   = "serial8250",    },};

[5]. 硬件兼容性:Quirks 与特殊处理

驱动针对不同硬件的 “缺陷(bug)” 做了兼容处理:

UART_BUG_THRE:部分 UART 的 THRE(发送保持寄存器空)位不可靠,启用备份定时器;

skip_txen_test:运行期参数,跳过 TXEN(发送使能)测试,适配有硬件缺陷的串口;

serial8250_apply_quirks:应用硬件特有配置,如禁用 TXEN 测试。

四 驱动初始化与卸载


[1]. 初始化流程(serial8250_init)

初始化 ISA 串口配置(serial8250_isa_init_ports);

注册串口驱动(uart_register_driver);

初始化 PNP 串口(serial8250_pnp_init);

创建平台设备(platform_device_alloc);

注册平台驱动(platform_driver_register);

注册所有串口端口(serial8250_register_ports)。

[2]. 卸载流程(serial8250_exit)

注销平台驱动 / 设备;

注销 PNP 串口;

注销串口驱动;

清理资源。

五 关键亮点与设计思想


[1]. 中断处理的极致优化

针对 ISA 共享中断的 “边缘触发” 特性,通过中断链+循环遍历+次数限制解决了中断卡死问题,是内核中断处理的经典案例。

[2]. 分层与复用

基于 Linux 内核通用串口层(uart_driver/uart_port),复用核心逻辑;

8250 特有逻辑封装在8250_port.c中,与通用层解耦。

[3]. 兼容性与可扩展性

编译期宏(如CONFIG_SERIAL_8250_RSA)适配不同硬件特性;

运行期参数(share_irqs/nr_uarts)灵活调整驱动行为;

动态注册接口支持热插拔设备。

[4]. 控制台与系统调试

串口控制台是嵌入式 / 服务器系统调试的核心,驱动确保 “从开机到关机” 的串口输出可用,甚至支持earlycon(早期控制台)。

六 总结


8250_core.c是 Linux 内核中 “经典硬件驱动” 的典范:

[1].兼容性好,适配数十年的 8250 系列硬件;

[2].性能优化效果好,针对 ISA 中断做极致优化;

[3].可扩展性强,支持动态注册、多总线适配;

[4].系统可用性佳,集成串口控制台与电源管理。

这种驱动的设计思路,不仅适用于串口驱动开发,也为其他字符设备驱动(如键盘、鼠标)提供了参考:分层设计、中断优化、硬件兼容、资源管理四大核心,是内核驱动开发的通用准则。

以上为全文内容。

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

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

谢谢你看到这里

这里是女程序员的笔记本

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

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

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

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 【从零开始撸内核驱动源码】:深度解析Linux内核8250串口核心驱动8250_core.c的设计框架与核心实现

评论 抢沙发

9 + 5 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮