[toc]

在这里插入图片描述

include/linux/serial_core.h

struct uart_port: UART硬件端口的终极抽象

本代码片段展示了Linux串行核心层(Serial Core)中最核心、最重要的数据结构——struct uart_port。这个结构体是内核中对一个物理串口硬件(如STM32上的一个USART外设)的完整软件抽象。它像一个“中央档案室”,汇集了描述和操作一个物理串口所需的所有信息:硬件资源(IO地址、中断号)、配置参数(时钟频率、FIFO大小)、状态变量(调制解调器线状态)、操作函数集(.ops指针)以及与其他内核子系统的链接(console, TTY state等)。任何一个串口驱动(如stm32-usart)的核心工作,就是在驱动probe阶段,正确地填充这个结构体的一个实例。

实现原理分析

uart_port结构体的设计体现了Linux驱动模型的两大核心思想:数据驱动抽象分层

  1. 数据驱动:

    • 结构体的大部分成员都是数据,而非代码。例如iobase, membase, irq, uartclk, fifosize等。驱动程序通过填充这些数据成员,来“告知”通用的串行核心层这个特定硬件的属性。
    • 标志位 (flags, status): 结构体包含了大量的标志位(upf_t, upstat_t)。这些标志位以一种紧凑、高效的方式记录了端口的配置和状态(例如,UPF_DEAD表示端口不可用,UPSTAT_CTS_ENABLE表示CTS流控已启用)。通用代码可以通过检查这些标志来快速做出决策。
  2. 抽象分层与回调机制:

    • const struct uart_ops *ops;: 这是整个结构体中最重要的成员。它是一个指向uart_ops结构体的指针,而uart_ops本身又是一个包含了大量函数指针的结构体(如.startup, .shutdown, .set_termios等)。
    • 回调机制: 这个.ops指针就是回调机制的核心。通用的串行核心层代码(serial_core)不包含任何与特定硬件(如STM32, 8250, AMBA等)相关的代码。当它需要执行一个硬件操作时(例如,设置波特率),它会通过uart_port找到.ops指针,并调用其中对应的函数指针(.set_termios)。这个函数指针指向的,正是底层硬件驱动(如stm32-usart)提供的、用于操作具体硬件寄存器的函数。
    • 接口与实现分离: 这种设计完美地实现了“接口”(由uart_ops定义)与“实现”(由具体驱动提供)的分离。uart_port结构体就是将这两者“粘合”在一起的实例对象。
  3. 资源与状态的集合:

    • 硬件资源: iobase, membase, mapbase, irq。这些成员直接描述了驱动与硬件通信所需的基础资源。
    • 软件状态:
      • lock: 一个自旋锁,用于保护对该端口所有成员的并发访问,尤其是在进程上下文和中断上下文之间。
      • icount: struct uart_icount,用于统计各种事件的发生次数(如接收的字节数rx、溢出错误overrun等),是实现TIOCGICOUNT ioctl的基础。
      • mctrl: 缓存了当前调制解调器控制线(MCR)的状态。
    • 与其他子系统的链接:
      • state: 指向uart_state,是连接到TTY层的桥梁。
      • cons: 如果此端口被用作内核控制台,此指针会指向对应的console结构。
      • dev: 指向该端口所依附的物理设备(如platform_device)。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* @struct uart_port
* @brief 内核对一个物理串口硬件的完整软件抽象。
*/
struct uart_port {
spinlock_t lock; /*!< 保护此端口的自旋锁 */
unsigned long iobase; /*!< I/O端口基地址 (用于x86架构) */
unsigned char __iomem *membase; /*!< 内存映射I/O的虚拟基地址 */

/* 底层硬件操作的回调函数指针 */
void (*set_termios)(struct uart_port *, struct ktermios *new, const struct ktermios *old);
/* ... 其他回调函数,如set_mctrl, startup, shutdown等 ... */
const struct uart_ops *ops;

unsigned int irq; /*!< 中断号 */
unsigned long irqflags; /*!< 请求中断时使用的标志 */
unsigned int uartclk; /*!< UART的内核时钟频率 */
unsigned int fifosize; /*!< 发送FIFO的大小(字节) */
unsigned char regshift; /*!< 寄存器地址的位移量 */
enum uart_iotype iotype; /*!< I/O访问类型 (UPIO_MEM, UPIO_PORT等) */

unsigned int read_status_mask; /*!< 驱动需要处理的状态位掩码 */
unsigned int ignore_status_mask; /*!< 驱动应忽略的状态位掩码 */
struct uart_state *state; /*!< 指向包含此端口的uart_state */
struct uart_icount icount; /*!< 中断计数器/统计 */

struct console *cons; /*!< 如果是控制台,则指向console结构 */
upf_t flags; /*!< 端口行为标志 (UPF_*) */

/* UPF_* 标志位的宏定义,许多与用户空间的ASYNC_*标志对应 */
#define UPF_DEAD ((__force upf_t) BIT_ULL(30)) /*!< 端口已死/未初始化 */
/* ... 其他UPF_标志 ... */

upstat_t status; /*!< 端口内部状态标志 (UPSTAT_*) */

#define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) /*!< CTS流控已使能 */
/* ... 其他UPSTAT_标志 ... */

bool hw_stopped; /*!< 软件辅助的CTS流控状态 */
unsigned int mctrl; /*!< 当前的调制解调器控制线设置缓存 */
unsigned int type; /*!< UART芯片类型 (PORT_8250, PORT_STM32, etc.) */
unsigned int line; /*!< 端口的线路号/索引 */
unsigned int minor; /*!< 对应的tty次设备号 */
resource_size_t mapbase; /*!< 物理基地址 */
struct device *dev; /*!< 依附的物理父设备 (如platform_device) */
struct serial_port_device *port_dev; /*!< 在serial_core中创建的设备模型实体 */

/* ... Magic SysRq (系统请求) 相关的成员 ... */

unsigned char suspended; /*!< 端口是否处于挂起状态 */
const char *name; /*!< 端口名称 (如 "ttySTM") */
struct attribute_group *attr_group; /*!< 驱动提供的自定义sysfs属性 */
const struct attribute_group **tty_groups; /*!< (内部使用) 所有的sysfs属性组 */

/* RS485 和 ISO7816 智能卡模式相关的配置 */
struct serial_rs485 rs485;
struct serial_rs485 rs485_supported; /*!< 驱动支持的RS485功能掩码 */
struct gpio_desc *rs485_term_gpio; /*!< RS485终端电阻的GPIO描述符 */
/* ... */

void *private_data; /*!< 指向驱动私有数据 (如stm32_port) 的指针 */
};

uart_iotype, UPF_* & UPSTAT_*: UART端口的I/O类型、配置与状态标志

本代码片段是struct uart_port定义的一部分,其核心功能是定义三个关键的组成部分:

  1. enum uart_iotype: 一个枚举类型,用于描述访问UART硬件寄存器的不同方式
  2. UPF_*: 一系列宏,定义了uart_port->flags成员的各个比特位。这些标志主要用于描述端口的静态配置、能力和驱动行为
  3. UPSTAT_*: 一系列宏,定义了uart_port->status成员的各个比特位。这些标志主要用于描述端口的动态内部状态,特别是与流控相关的状态。

实现原理分析

这些定义是uart_port作为“中央档案室”的具体体现。它们通过枚举和位掩码,将硬件的多样性和驱动的复杂状态,以一种标准化的、节省空间的方式记录下来,供serial_core和驱动本身查询和使用。

  1. enum uart_iotype (I/O类型):

    • 职责: 解决不同CPU架构和总线访问硬件寄存器的差异。
    • UPIO_PORT: 用于x86架构上的传统I/O端口访问(inb/outb指令)。
    • UPIO_MEM: 这是嵌入式系统(如ARM)中最常见的类型。它表示UART寄存器是通过标准的内存映射(memory-mapped I/O)来访问的,即通过一个指针(port->membase)来读写。
    • UPIO_MEM32, UPIO_MEM32BE, UPIO_MEM16: UPIO_MEM的变体,用于指示访问内存时需要进行特定宽度(32位/16位)和字节序(大端/小端)的处理。这对于那些寄存器访问有特殊要求的平台非常重要。
    • UPIO_AU, UPIO_TSI: 用于特定SoC(Alchemy Au1x00, Tsi108)的、非标准的内存访问方式。
    • 作用: serial_core中的通用I/O函数(如serial_in/serial_out)会检查port->iotype,并根据其值调用正确的底层访问函数,从而实现了硬件访问的抽象。
  2. UPF_* 标志 (upf_t flags):

    • 职责: 记录端口的配置选项和核心行为标志。这些标志通常在驱动probe阶段被设置,并且在端口的生命周期内很少改变。
    • 用户空间映射: 注释中明确指出,许多低位的UPF_*标志(在UPF_CHANGE_MASK范围内)与用户空间通过ioctl(TIOCSSERIAL)设置的serial_struct中的ASYNC_*标志是一一对应的。例如,UPF_LOW_LATENCY直接对应ASYNC_LOW_LATENCY。这构成了内核态与用户态配置之间的桥梁。
    • 内核内部标志: 高位的标志(UPF_NO_THRE_TEST及以上)是serial_core和驱动内部使用的,用户空间无法修改。
      • UPF_BOOT_AUTOCONF: 指示驱动在启动时应自动探测和配置端口。
      • UPF_FIXED_TYPE: 告知驱动不要尝试探测UART类型,因为类型是已知的。
      • UPF_DEAD: 一个非常重要的状态标志,表示端口正在被注册或移除,尚不可用。
      • UPF_IOREMAP: 表示port->membase是由ioremap分配的,需要在release_port时被iounmap
  3. UPSTAT_* 标志 (upstat_t status):

    • 职责: 记录端口的动态内部状态,这些状态可能会在运行时根据termios的设置而改变。
    • 流控核心: 这些标志主要与流控逻辑相关。
      • UPSTAT_CTS_ENABLE, UPSTAT_DCD_ENABLE: 根据termios中的CRTSCTSCLOCAL标志来设置,表示驱动是否应该关心CTS和DCD信号线的变化。
      • UPSTAT_AUTOCTS, UPSTAT_AUTORTS: 表示驱动是否应该根据termiosCRTSCTS标志来自动处理CTS/RTS硬件流控。
    • 作用: 这些状态标志使得驱动内部的函数(如中断处理程序)可以快速地查询当前应该执行哪种流控逻辑,而无需每次都重新解析termios结构。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* @enum uart_iotype
* @brief 定义了访问UART硬件寄存器的不同I/O方式。
*/
enum uart_iotype {
UPIO_UNKNOWN = -1, /*!< 未知类型 */
UPIO_PORT = SERIAL_IO_PORT, /*!< 8位 I/O端口访问 (x86) */
UPIO_HUB6 = SERIAL_IO_HUB6, /*!< Hub6 ISA卡 */
UPIO_MEM = SERIAL_IO_MEM, /*!< 内存映射I/O (ARM等嵌入式系统常用) */
UPIO_MEM32 = SERIAL_IO_MEM32, /*!< 32位小端内存访问 */
UPIO_AU = SERIAL_IO_AU, /*!< Au1x00/RT288x SoC的特殊I/O */
UPIO_TSI = SERIAL_IO_TSI, /*!< Tsi108/109 SoC的特殊I/O */
UPIO_MEM32BE = SERIAL_IO_MEM32BE, /*!< 32位大端内存访问 */
UPIO_MEM16 = SERIAL_IO_MEM16, /*!< 16位小端内存访问 */
};

/*
* UPF_* 标志位: 描述端口的静态配置和能力。
* 低位部分与用户空间的 <linux/tty_flags.h> 中的 ASYNC_* 标志一一对应。
* 高位部分是内核内部使用的。
*/

#ifdef CONFIG_HAS_IOPORT
#define UPF_FOURPORT ((__force upf_t) ASYNC_FOURPORT /* 1 */ ) /*!< (PC-ism) 4-port card */
#else
#define UPF_FOURPORT 0
#endif
#define UPF_SAK ((__force upf_t) ASYNC_SAK /* 2 */ ) /*!< 启用安全注意键 (SAK) 处理 */
#define UPF_SPD_HI ((__force upf_t) ASYNC_SPD_HI /* 4 */ ) /*!< (legacy) 使用高速波特率 (57600) */
#define UPF_SPD_VHI ((__force upf_t) ASYNC_SPD_VHI /* 5 */ ) /*!< (legacy) 使用甚高速波特率 (115200) */
#define UPF_SPD_CUST ((__force upf_t) ASYNC_SPD_CUST /* 0x0030 */ ) /*!< (legacy) 使用自定义波特率分频数 */
#define UPF_SPD_WARP ((__force upf_t) ASYNC_SPD_WARP /* 0x1010 */ ) /*!< (legacy) 使用超高速波特率 (460800) */
#define UPF_SPD_MASK ((__force upf_t) ASYNC_SPD_MASK /* 0x1030 */ ) /*!< (legacy) 所有速度标志的掩码 */
#define UPF_SKIP_TEST ((__force upf_t) ASYNC_SKIP_TEST /* 6 */ ) /*!< 跳过启动时的UART自检 */
#define UPF_AUTO_IRQ ((__force upf_t) ASYNC_AUTO_IRQ /* 7 */ ) /*!< 自动探测IRQ */
#define UPF_HARDPPS_CD ((__force upf_t) ASYNC_HARDPPS_CD /* 11 */ ) /*!< 在CD线变化时触发硬件PPS事件 */
#define UPF_SPD_SHI ((__force upf_t) ASYNC_SPD_SHI /* 12 */ ) /*!< (legacy) 使用极高速率 (230400) */
#define UPF_LOW_LATENCY ((__force upf_t) ASYNC_LOW_LATENCY /* 13 */ ) /*!< 提示驱动优化以降低延迟 */
#define UPF_BUGGY_UART ((__force upf_t) ASYNC_BUGGY_UART /* 14 */ ) /*!< 标记为有bug的UART,需要变通 */
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ ) /*!< (16750) 使用特殊的波特率倍频器 */


/* --- 内核内部使用的 UPF_* 标志 --- */
#define UPF_NO_THRE_TEST ((__force upf_t) BIT_ULL(19)) /*!< 不测试THRE寄存器 */
/* 端口有硬件辅助的硬件流控 */
#define UPF_AUTO_CTS ((__force upf_t) BIT_ULL(20))
#define UPF_AUTO_RTS ((__force upf_t) BIT_ULL(21))
#define UPF_HARD_FLOW ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))
/* 端口有硬件辅助的软件流控 */
#define UPF_SOFT_FLOW ((__force upf_t) BIT_ULL(22))
#define UPF_CONS_FLOW ((__force upf_t) BIT_ULL(23)) /*!< 控制台使用流控 */
#define UPF_SHARE_IRQ ((__force upf_t) BIT_ULL(24)) /*!< 共享中断 */
#define UPF_EXAR_EFR ((__force upf_t) BIT_ULL(25)) /*!< Exar EFR寄存器 */
#define UPF_BUG_THRE ((__force upf_t) BIT_ULL(26)) /*!< THRE寄存器有bug */
/* UART类型是固定的,不应被探测。 */
#define UPF_FIXED_TYPE ((__force upf_t) BIT_ULL(27))
/* 驱动应在启动时自动配置端口。 */
#define UPF_BOOT_AUTOCONF ((__force upf_t) BIT_ULL(28))
#define UPF_FIXED_PORT ((__force upf_t) BIT_ULL(29)) /*!< 端口资源是固定的 */
#define UPF_DEAD ((__force upf_t) BIT_ULL(30)) /*!< 端口已死/不可用 */
#define UPF_IOREMAP ((__force upf_t) BIT_ULL(31)) /*!< membase是ioremap的结果 */
#define UPF_FULL_PROBE ((__force upf_t) BIT_ULL(32)) /*!< 执行完整的探测 */

/* ... (掩码定义,用于区分用户可配置和内核私有的标志) ... */

/**
* @typedef upstat_t
* @brief 定义端口内部动态状态的类型。
*/
upstat_t status;

/* UPSTAT_* 标志位: 描述端口的动态内部状态,主要与流控相关。 */
#define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) /*!< 驱动应检查CTS信号 */
#define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1)) /*!< 驱动应检查DCD信号 */
#define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2)) /*!< 驱动应自动处理RTS */
#define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3)) /*!< 驱动应自动处理CTS */
#define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4)) /*!< 驱动应自动处理XON/XOFF */
#define UPSTAT_SYNC_FIFO ((__force upstat_t) (1 << 5)) /*!< FIFO同步状态 */

uart_handle_break & do_SAK: Break信号的特殊含义分发器

本代码片段展示了当UART硬件检测到一个Break信号时,serial_core层所执行的高级语义分发逻辑。uart_handle_break函数是这个分发的中央枢纽。它的核心功能不是处理普通的字符,而是检查这个特殊的、带外的Break信号是否是两种特殊内核功能的前导信号Magic SysRq(魔术系统请求)或SAK(安全注意键)。do_SAK则是将SAK处理延迟到工作队列中执行的辅助函数。

实现原理分析

此机制的原理是一个基于配置的、有序的事件解释器。一个单一的硬件事件(Break信号),根据端口的配置(port->flags)和当前的状态(port->sysrq),会被赋予不同的高级含义。

  1. 什么是Break信号?

    • 在串行通信中,Break不是一个字符。它是一个特殊信号,表现为数据线(RX)被拉低(逻辑0)的时间,远超于一个正常数据帧的长度(例如,持续超过10个比特位的时间)。
    • 它通常被用作一种“带外”的信令,用于复位、中断或触发特殊功能。
  2. uart_handle_break 的分发逻辑:

    • 该函数在中断服务程序(ISR)检测到Break信号后被调用(例如,在STM32驱动中,当FE(帧错误)位置位且接收到的字符为0x00时)。
    • 它按以下顺序检查Break的可能含义:
      • a) 驱动特定处理 (port->handle_break): 这是一个可选的回调函数。它为底层驱动提供了一个最高优先级的钩子。如果一个特定的硬件(例如,一个外接的调制解调器)对Break信号有特殊的解释,驱动可以实现这个回调来处理它,并阻止内核进行后续的通用解释。
      • b) Magic SysRq (CONFIG_MAGIC_SYSRQ_SERIAL):
        • 目的: Magic SysRq是内核的一个强大的调试和恢复工具,允许管理员通过串口发送Break + <命令字符>的序列来执行内核命令(如重启、同步磁盘等),即使系统已经挂起。
        • 状态机: uart_handle_break实现了这个序列的起始部分。
          • if (!port->sysrq): 检查sysrq状态变量。如果它为0,表示当前不在一个SysRq序列中。
          • port->sysrq = jiffies + SYSRQ_TIMEOUT;: 将sysrq设置为一个未来的时间戳。这相当于“布防”了SysRq状态,表示内核现在正在等待一个SysRq命令字符,并且这个等待会在SYSRQ_TIMEOUT后超时。
          • return 1;: 这是关键。返回1会通知调用者(ISR),这个Break事件已经被SysRq系统**“消耗”**了,不应该再将其作为普通的TTY_BREAK传递给TTY层。
      • c) SAK (UPF_SAK):
        • 目的: SAK(Secure Attention Key)是一种用于高安全性环境的机制,它提供一个“可信路径”来启动登录或引起系统注意,通常通过Break或其他特殊键序触发。
        • if (port->flags & UPF_SAK): 检查uart_portflags是否被用户空间配置为支持SAK。
        • do_SAK(state->port.tty): 如果支持,则调用do_SAK来处理。
  3. 延迟处理 (do_SAK):

    • 职责: 安全地触发SAK的处理逻辑。
    • schedule_work(&tty->SAK_work): 这是核心。它不直接执行SAK处理,而是将一个预定义的SAK_work工作项提交到一个全局的工作队列(workqueue)
    • 为什么需要延迟?: do_SAK是从uart_handle_break调用的,而后者又是在原子上下文(中断处理程序)中执行的。在原子上下文中,代码绝对不能睡眠。而SAK的处理逻辑可能非常复杂,可能需要获取互斥锁、分配内存或与其他TTY层函数交互,这些都可能导致睡眠。通过schedule_work,实际的处理工作被延迟到一个允许睡眠的**内核线程(进程上下文)**中去执行,从而避免了在中断上下文中执行非法操作。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* @brief 处理串口上的Break信号。
* @param port 发生Break的UART端口。
* @return int 如果Break被SysRq消耗则返回1,否则返回0。
* @details 该函数是Break信号的高级语义分发器。
*/
static inline int uart_handle_break(struct uart_port *port)
{
struct uart_state *state = port->state;

/* 1. 驱动特定处理钩子 */
if (port->handle_break)
port->handle_break(port);

#ifdef CONFIG_MAGIC_SYSRQ_SERIAL
/* 2. Magic SysRq 处理 */
/* 检查端口是否被配置为SysRq控制台 */
if (port->has_sysrq && uart_console(port)) {
/* 如果当前不在SysRq序列中... */
if (!port->sysrq) {
/* ...则设置一个超时,"布防"SysRq状态,等待下一个字符。 */
port->sysrq = jiffies + SYSRQ_TIMEOUT;
/* 返回1,告诉调用者(ISR)此Break已被消耗。 */
return 1;
}
/*
* 如果在超时期间收到另一个Break,这里会使sysrq保持布防状态
* (在老代码中可能是取消布防,但当前代码会继续等待)。
* 然后会落到下面的SAK检查。
*/
}
#endif
/* 3. SAK 处理 */
/* 检查端口是否被配置为支持SAK。 */
if (port->flags & UPF_SAK)
do_SAK(state->port.tty);

/* 返回0,表示Break未被SysRq消耗,应作为普通TTY_BREAK事件处理。 */
return 0;
}

/**
* @brief 执行SAK(安全注意键)处理。
* @param tty 目标tty设备。
* @details 该函数将实际的SAK处理逻辑推迟到工作队列中执行。
*/
void do_SAK(struct tty_struct *tty)
{
if (!tty)
return;
/*
* 将tty->SAK_work工作项提交到系统工作队列。
* 这会将处理从当前(可能是原子的)中断上下文,
* 移动到一个可以安全睡眠的进程上下文中。
*/
schedule_work(&tty->SAK_work);
}
EXPORT_SYMBOL(do_SAK);

drivers/tty/serial/serial_core.c

uart_suspend_port, uart_resume_port & uart_configure_port:UART串口电源管理与配置

本代码片段展示了Linux内核中通用串口驱动层(UART core)对单个串口设备(uart_port)进行电源管理(挂起与恢复)和初始化配置的核心逻辑。它定义了uart_suspend_portuart_resume_port两个关键函数,用于系统进入低功耗状态和从低功耗状态唤醒时,对串口硬件进行相应的状态转换。同时,uart_configure_port函数负责在驱动探测阶段对串口硬件进行初始化和资源配置。

实现原理分析

这段代码是设备驱动模型中电源管理(Power Management)和设备初始化流程的典型实现,它位于硬件无关的通用层,通过函数指针回调(struct uart_ops)来调用底层硬件相关的驱动代码。

  1. 挂起流程 (uart_suspend_port):

    • 唤醒源检查: 首先,函数通过device_find_childserial_match_port找到与uart_port关联的TTY设备。如果该TTY设备被配置为系统的唤醒源(device_may_wakeup),则仅需调用enable_irq_wake使能串口中断的唤醒功能即可,无需关闭端口。serial_match_port通过比较设备号(dev_t)来精确匹配TTY设备。
    • 控制台特殊处理: 如果该串口是系统控制台(console),且系统并未完全进入挂起流程(!console_suspend_enabled),则仅停止数据接收(stop_rx)以防止数据丢失,但保持端口大部分功能开启。
    • 标准挂起: 在标准挂起流程中,代码执行一系列关闭操作:停止发送(stop_tx)、清空调制解调器控制线(set_mctrl)、停止接收(stop_rx)。在这些操作之前,会等待硬件的发送缓冲区为空(tx_empty),以确保所有数据都已发送出去。最后,调用ops->shutdown()执行硬件相关的关闭操作(如关闭时钟),并调用uart_change_pm将端口的电源状态置为UART_PM_STATE_OFF
  2. 恢复流程 (uart_resume_port):

    • 该流程与挂起流程严格对应。首先同样检查唤醒源状态,如果设备之前被用于唤醒,则调用disable_irq_wake关闭中断的唤醒功能。
    • 控制台恢复: 如果是控制台,会根据console_suspend_enabled标志位来决定是完整恢复还是仅重新开启接收。它会重新应用终端设置(波特率、数据位等)。
    • 标准恢复: 对于完全挂起的端口,它会执行与挂起相反的开启序列。调用ops->startup()执行硬件相关的初始化,然后重新配置线路设置、调制解调器控制线(set_mctrl),并最终开启发送(start_tx),将TTY端口标记为已初始化。
  3. 配置流程 (uart_configure_port):

    • 此函数在串口设备被内核发现时调用。
    • 它首先检查端口是否定义了有效的硬件资源(IO地址或内存地址)。
    • 通过调用port->ops->config_port(),它触发底层硬件驱动执行自动配置,例如探测UART芯片类型(如16550A)和自动检测中断号。
    • 配置成功后,调用uart_report_port打印端口的详细信息(地址、中断号、类型等)到内核日志中,便于调试。
    • 它会将端口的初始电源状态设为ON,以便进行后续的MCR设置等操作,然后将非控制台端口的电源状态设为OFF,以节约能源。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/**
* @struct uart_match
* @brief 用于在查找设备时传递 uart_port 和 uart_driver 的辅助结构体。
*/
struct uart_match {
struct uart_port *port; /*!< 指向要匹配的UART端口结构体 */
struct uart_driver *driver; /*!< 指向相关的UART驱动结构体 */
};

/**
* @brief 设备匹配回调函数,用于 device_find_child。
* @param dev 正在被检查的子设备。
* @param data 指向 uart_match 结构的指针,包含要匹配的端口信息。
* @return int 如果设备与端口匹配则返回1,否则返回0。
*
* @details 该函数通过比较设备号(dev_t)来判断一个给定的设备是否是
* 目标 uart_port 对应的TTY设备。
*/
static int serial_match_port(struct device *dev, const void *data)
{
const struct uart_match *match = data;
struct tty_driver *tty_drv = match->driver->tty_driver;
/* 根据TTY驱动的主设备号、次设备号起始值和端口线路号计算出预期的设备号。 */
dev_t devt = MKDEV(tty_drv->major, tty_drv->minor_start) +
match->port->line;

/* 检查设备的设备号是否与计算出的设备号相等。每个端口只有一个TTY设备。 */
return dev->devt == devt;
}

#ifdef CONFIG_SERIAL_CORE_CONSOLE
#define uart_console(port) \
((port)->cons && (port)->cons->index == (port)->line)
#else
#define uart_console(port) ({ (void)port; 0; })
#endif

static inline void tty_port_set_suspended(struct tty_port *port, bool val)
{
assign_bit(TTY_PORT_SUSPENDED, &port->iflags, val);
}

static inline bool tty_port_initialized(const struct tty_port *port)
{
return test_bit(TTY_PORT_INITIALIZED, &port->iflags);
}


/**
* uart_change_pm - set power state of the port
*
* @state: port descriptor
* @pm_state: new state
*
* Locking: port->mutex has to be held
*/
static void uart_change_pm(struct uart_state *state,
enum uart_pm_state pm_state)
{
struct uart_port *port = uart_port_check(state);

if (state->pm_state != pm_state) {
if (port && port->ops->pm)
port->ops->pm(port, pm_state, state->pm_state);
state->pm_state = pm_state;
}
}

/**
* @brief 挂起一个UART端口。
* @param drv 指向UART驱动的指针。
* @param uport 指向要挂起的UART端口的指针。
* @return int 成功时返回0。
*/
int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport)
{
/* 获取端口对应的状态结构体 */
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
struct device *tty_dev;
struct uart_match match = {uport, drv};

/* 锁定TTY端口的互斥锁,保护端口状态。 */
guard(mutex)(&port->mutex);

/* 查找与此UART端口关联的TTY设备。 */
tty_dev = device_find_child(&uport->port_dev->dev, &match, serial_match_port);
/* 如果找到了TTY设备,并且该设备可以作为唤醒源。 */
if (tty_dev && device_may_wakeup(tty_dev)) {
/* 使能该端口的中断作为唤醒中断,然后直接返回。 */
enable_irq_wake(uport->irq);
put_device(tty_dev);
return 0;
}
put_device(tty_dev);

/*
* 如果控制台挂起功能未启用,且此端口是控制台,
* 则只需停止接收数据,以防止异步数据进入,
* 但要确保之后可以重新启动接收。
*/
if (!console_suspend_enabled && uart_console(uport)) {
if (uport->ops->start_rx) {
guard(uart_port_lock_irq)(uport);
uport->ops->stop_rx(uport);
}
/* 将设备路径标记为唤醒路径,防止系统深度休眠。 */
device_set_awake_path(uport->dev);
return 0;
}

/* 标记端口为已挂起状态。 */
uport->suspended = 1;

/* 如果TTY端口已初始化。 */
if (tty_port_initialized(port)) {
const struct uart_ops *ops = uport->ops;
int tries;
unsigned int mctrl;

/* 在TTY层标记端口为已挂起和未初始化。 */
tty_port_set_suspended(port, true);
tty_port_set_initialized(port, false);

/* 锁定端口的中断锁,保护硬件寄存器访问。 */
scoped_guard(uart_port_lock_irq, uport) {
ops->stop_tx(uport); /* 停止发送。 */
/* 如果未启用RS485,则清除调制解调器控制线。 */
if (!(uport->rs485.flags & SER_RS485_ENABLED))
ops->set_mctrl(uport, 0);
/* 保存当前的MCR状态,以便恢复时使用。 */
mctrl = uport->mctrl;
uport->mctrl = 0;
ops->stop_rx(uport); /* 停止接收。 */
}

/* 等待发送器清空,最多尝试3次。 */
for (tries = 3; !ops->tx_empty(uport) && tries; tries--)
msleep(10);
/* 如果发送器未能清空,则打印错误信息。 */
if (!tries)
dev_err(uport->dev, "%s: 无法清空发送器\n",
uport->name);

/* 调用底层驱动的shutdown函数,关闭硬件。 */
ops->shutdown(uport);
/* 恢复MCR状态变量。 */
uport->mctrl = mctrl;
}

/* 在挂起端口之前,先挂起控制台设备。 */
if (uart_console(uport))
console_suspend(uport->cons);

/* 改变端口的电源管理状态为OFF。 */
uart_change_pm(state, UART_PM_STATE_OFF);

return 0;
}
EXPORT_SYMBOL(uart_suspend_port);

/**
* @brief 恢复一个UART端口。
* @param drv 指向UART驱动的指针。
* @param uport 指向要恢复的UART端口的指针。
* @return int 成功时返回0。
*/
int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
struct device *tty_dev;
struct uart_match match = {uport, drv};
struct ktermios termios;

/* 锁定TTY端口的互斥锁。 */
guard(mutex)(&port->mutex);

/* 查找关联的TTY设备。 */
tty_dev = device_find_child(&uport->port_dev->dev, &match, serial_match_port);
/* 如果端口未被挂起且可作为唤醒源。 */
if (!uport->suspended && device_may_wakeup(tty_dev)) {
/* 如果中断的唤醒功能被设置,则禁用它。 */
if (irqd_is_wakeup_set(irq_get_irq_data((uport->irq))))
disable_irq_wake(uport->irq);
put_device(tty_dev);
return 0;
}
put_device(tty_dev);
/* 清除端口的挂起标志。 */
uport->suspended = 0;

/* 恢复之后,重新使能控制台设备。 */
if (uart_console(uport)) {
memset(&termios, 0, sizeof(struct ktermios));
/* 首先尝试使用控制台的cflag设置。 */
termios.c_cflag = uport->cons->cflag;
termios.c_ispeed = uport->cons->ispeed;
termios.c_ospeed = uport->cons->ospeed;

/* 如果上述设置为空,则使用TTY的termios设置。 */
if (port->tty && termios.c_cflag == 0)
termios = port->tty->termios;

if (console_suspend_enabled)
uart_change_pm(state, UART_PM_STATE_ON);
/* 应用终端设置。 */
uport->ops->set_termios(uport, &termios, NULL);
if (!console_suspend_enabled && uport->ops->start_rx) {
guard(uart_port_lock_irq)(uport);
uport->ops->start_rx(uport);
}
if (console_suspend_enabled)
console_resume(uport->cons);
}

/* 如果TTY端口之前被挂起。 */
if (tty_port_suspended(port)) {
const struct uart_ops *ops = uport->ops;
int ret;

/* 将电源状态切换为ON。 */
uart_change_pm(state, UART_PM_STATE_ON);
scoped_guard(uart_port_lock_irq, uport)
if (!(uport->rs485.flags & SER_RS485_ENABLED))
ops->set_mctrl(uport, 0);
if (console_suspend_enabled || !uart_console(uport)) {
struct tty_struct *tty = port->tty;

/* 调用底层驱动的startup函数,初始化硬件。 */
ret = ops->startup(uport);
if (ret == 0) {
if (tty)
/* 重新应用线路设置(波特率等)。 */
uart_change_line_settings(tty, state, NULL);
uart_rs485_config(uport);
scoped_guard(uart_port_lock_irq, uport) {
/* 恢复调制解调器控制线状态。 */
if (!(uport->rs485.flags & SER_RS485_ENABLED))
ops->set_mctrl(uport, uport->mctrl);
ops->start_tx(uport); /* 启动发送。 */
}
/* 标记TTY端口为已初始化。 */
tty_port_set_initialized(port, true);
} else {
/* 恢复失败 - 可能硬件已移除。 */
/* 清除“已初始化”标志,防止调用shutdown。 */
uart_shutdown(tty, state);
}
}

/* 在TTY层标记端口为已恢复。 */
tty_port_set_suspended(port, false);
}

return 0;
}
EXPORT_SYMBOL(uart_resume_port);

/**
* @brief 报告一个UART端口的信息。
* @param drv 指向UART驱动的指针。
* @param port 指向要报告的UART端口的指针。
*/
static inline void
uart_report_port(struct uart_driver *drv, struct uart_port *port)
{
char address[64];

/* 根据IO类型格式化地址字符串。 */
switch (port->iotype) {
case UPIO_PORT:
snprintf(address, sizeof(address), "I/O 0x%lx", port->iobase);
break;
case UPIO_HUB6:
snprintf(address, sizeof(address),
"I/O 0x%lx offset 0x%x", port->iobase, port->hub6);
break;
case UPIO_MEM:
case UPIO_MEM16:
case UPIO_MEM32:
case UPIO_MEM32BE:
case UPIO_AU:
case UPIO_TSI:
snprintf(address, sizeof(address),
"MMIO 0x%llx", (unsigned long long)port->mapbase);
break;
default:
strscpy(address, "*unknown*", sizeof(address));
break;
}

/* 打印端口的完整信息到内核日志。 */
pr_info("%s%s%s at %s (irq = %u, base_baud = %u) is a %s\n",
port->dev ? dev_name(port->dev) : "",
port->dev ? ": " : "",
port->name,
address, port->irq, port->uartclk / 16, uart_type(port));

/* 如果支持魔数波特率倍频,也进行报告。 */
if (port->flags & UPF_MAGIC_MULTIPLIER)
pr_info("%s%s%s extra baud rates supported: %u, %u",
port->dev ? dev_name(port->dev) : "",
port->dev ? ": " : "",
port->name,
port->uartclk / 8, port->uartclk / 4);
}

/**
* @brief 配置一个UART端口。
* @param drv 指向UART驱动的指针。
* @param state 指向端口状态结构的指针。
* @param port 指向要配置的UART端口的指针。
*/
static void
uart_configure_port(struct uart_driver *drv, struct uart_state *state,
struct uart_port *port)
{
unsigned int flags;

/* 如果端口没有定义IO基地址或内存基地址,则直接返回。 */
if (!port->iobase && !port->mapbase && !port->membase)
return;

/* 进行自动配置。config_port应该申请资源并映射端口。 */
flags = 0;
/* 如果需要自动配置IRQ。 */
if (port->flags & UPF_AUTO_IRQ)
flags |= UART_CONFIG_IRQ;
/* 如果需要在启动时自动配置。 */
if (port->flags & UPF_BOOT_AUTOCONF) {
if (!(port->flags & UPF_FIXED_TYPE)) {
port->type = PORT_UNKNOWN;
flags |= UART_CONFIG_TYPE; /* 需要探测端口类型。 */
}
/* 如果是启动控制台,则加锁以进行同步。 */
if (uart_console(port))
console_lock();
/* 调用底层驱动的config_port函数。 */
port->ops->config_port(port, flags);
if (uart_console(port))
console_unlock();
}

/* 如果端口类型被成功识别。 */
if (port->type != PORT_UNKNOWN) {
/* 报告端口信息。 */
uart_report_port(drv, port);

if (uart_console(port))
console_lock();

/* 打开端口电源以进行mctrl设置。 */
uart_change_pm(state, UART_PM_STATE_ON);

/* 确保调制解调器控制线被禁用,但保留DTR的设置。 */
scoped_guard(uart_port_lock_irqsave, port) {
port->mctrl &= TIOCM_DTR;
if (!(port->rs485.flags & SER_RS485_ENABLED))
port->ops->set_mctrl(port, port->mctrl);
}

uart_rs485_config(port);

if (uart_console(port))
console_unlock();

/* 如果此端口是控制台但尚未注册成功,则尝试重新注册。 */
if (port->cons && !console_is_registered(port->cons))
register_console(port->cons);

/* 默认关闭所有非控制台端口的电源。 */
if (!uart_console(port))
uart_change_pm(state, UART_PM_STATE_OFF);
}
}

uart_start, uart_stop & uart_change_line_settings: UART TX控制、调制解调器线与线路设置

本代码片段展示了Linux TTY(Teletype)子系统与底层UART(通用异步收发器)核心层交互的关键函数。其核心功能是实现对串口发送功能的启停、对调制解调器控制线(如DTR/RTS)的精确控制,以及将高层的termios终端设置(如波特率、数据位、流控)应用到底层硬件。这些函数是连接用户空间通过TTY设备文件进行的I/O操作与物理串口硬件行为的桥梁。

实现原理分析

这段代码是典型的驱动分层模型实现,TTY层作为通用接口,通过uart_state结构体找到对应的uart_port,并调用其ops函数指针来操作具体硬件,实现了硬件无关性。

  1. 发送启停 (uart_start/uart_stop):

    • uart_stop是一个简单的封装,它通过uart_port_ref_lock安全地获取uart_port的引用并加锁,然后调用硬件操作集中的stop_tx回调函数来停止硬件发送器。
    • uart_start则更为复杂,它调用了核心函数__uart_start__uart_start不仅调用ops->start_tx来启动硬件发送,更重要的是,它与内核的**运行时电源管理(Runtime PM)**框架深度集成。
    • 在启动发送前,它会调用pm_runtime_get来增加硬件的引用计数,这会确保UART外设的电源(如时钟)是开启的。发送操作完成后,它通过pm_runtime_mark_last_busy更新活动时间戳,并调用pm_runtime_put_autosuspend来减少引用计数,允许设备在一段时间没有活动后自动进入低功耗状态。
  2. 调制解调器控制 (uart_update_mctrl):

    • 这是一个通用的、原子的调制解调器控制线(Modem Control Register, MCR)更新函数。它使用setclear两个位掩码来精确地置位或清零mctrl状态变量中的特定位。
    • 为了保证原子性,整个操作在uart_port_lock_irqsave锁的保护下进行,这在单核系统上会禁用本地中断。
    • 它包含一个重要的优化:只有在mctrl的值确实发生改变时,才会调用ops->set_mctrl去操作硬件寄存器,避免了不必要的硬件访问。
    • uart_port_dtr_rts等函数是在此基础上为特定信号(DTR/RTS)提供的便捷封装。
  3. 应用终端设置 (uart_change_line_settings):

    • 当用户空间通过ioctl等方式更改终端设置时,此函数被调用。
    • 核心操作: uport->ops->set_termios(uport, termios, old_termios)。这是最关键的一步,它将termios结构体中的信息(波特率、数据位、停止位、校验位等)传递给底层驱动,由底层驱动负责将其转化为对硬件寄存器的具体配置。
    • 流控处理: 根据termios中的CRTSCTS(硬件流控)和CLOCAL(忽略调制解调器状态)标志,更新uart_port内部的软件状态标志(UPSTAT_CTS_ENABLE, UPSTAT_DCD_ENABLE)。
    • 软/硬协同: 它还处理了软件辅助的硬件流控。如果系统使用软件来响应CTS线的变化,此函数会检查CTS线的当前状态,并根据需要决定是停止还是启动硬件发送器,以确保流控逻辑的正确实现。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
* @brief 停止TTY设备的发送。
* @param tty 指向目标TTY结构的指针。
*/
static void uart_stop(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
unsigned long flags;

/* 安全地获取端口引用并加锁(禁用中断)。 */
port = uart_port_ref_lock(state, &flags);
if (port)
/* 调用底层驱动的操作函数来停止硬件发送器。 */
port->ops->stop_tx(port);
/* 解锁并释放端口引用。 */
uart_port_unlock_deref(port, flags);
}

static inline int uart_tx_stopped(struct uart_port *port)
{
struct tty_struct *tty = port->state->port.tty;
if ((tty && tty->flow.stopped) || port->hw_stopped)
return 1;
return 0;
}

/**
* @brief 启动UART端口的发送(核心实现)。
* @param state 指向UART状态结构的指针。
*/
static void __uart_start(struct uart_state *state)
{
struct uart_port *port = state->uart_port;
struct serial_port_device *port_dev;
int err;

/* 如果端口不存在、已死或已被停止,则不执行任何操作。 */
if (!port || port->flags & UPF_DEAD || uart_tx_stopped(port))
return;

port_dev = port->port_dev;

/* 增加设备的运行时PM使用计数,以唤醒设备。 */
err = pm_runtime_get(&port_dev->dev);
if (err < 0 && err != -EINPROGRESS) {
/* 如果唤醒失败,则撤销计数增加。 */
pm_runtime_put_noidle(&port_dev->dev);
return;
}

/*
* 如果运行时PM未启用,或者设备已处于活动状态,则启动TX。
* 如果设备正在恢复,serial_port_runtime_resume() 会再次调用 start_tx()。
*/
if (!pm_runtime_enabled(port->dev) || pm_runtime_active(&port_dev->dev))
port->ops->start_tx(port);
/* 标记设备为最近使用过,以推迟自动挂起。 */
pm_runtime_mark_last_busy(&port_dev->dev);
/* 减少使用计数,允许设备在未来自动挂起。 */
pm_runtime_put_autosuspend(&port_dev->dev);
}

/**
* @brief 启动TTY设备的发送。
* @param tty 指向目标TTY结构的指针。
*/
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
unsigned long flags;

/* 安全地获取端口引用并加锁。 */
port = uart_port_ref_lock(state, &flags);
/* 调用核心启动函数。 */
__uart_start(state);
/* 解锁并释放引用。 */
uart_port_unlock_deref(port, flags);
}

/**
* @brief 更新调制解调器控制线状态。
* @param port 指向要操作的UART端口。
* @param set 要置位的比特掩码。
* @param clear 要清零的比特掩码。
*/
static void
uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear)
{
unsigned int old;

/* 锁住端口并禁用中断,以保证原子性。 */
guard(uart_port_lock_irqsave)(port);
old = port->mctrl;
/* 计算新的mctrl值。 */
port->mctrl = (old & ~clear) | set;
/* 如果值有变化,并且未启用RS485模式,则调用硬件操作函数。 */
if (old != port->mctrl && !(port->rs485.flags & SER_RS485_ENABLED))
port->ops->set_mctrl(port, port->mctrl);
}

/* 设置mctrl位的便捷宏 */
#define uart_set_mctrl(port, set) uart_update_mctrl(port, set, 0)
/* 清除mctrl位的便捷宏 */
#define uart_clear_mctrl(port, clear) uart_update_mctrl(port, 0, clear)

/**
* @brief 控制DTR和RTS信号线。
* @param uport UART端口。
* @param active true表示激活(拉高),false表示取消激活(拉低)。
*/
static void uart_port_dtr_rts(struct uart_port *uport, bool active)
{
if (active)
uart_set_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
else
uart_clear_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
}

/**
* @brief 更改线路设置(波特率、数据位等)。
* @param tty TTY结构体。
* @param state UART状态结构体。
* @param old_termios 旧的termios设置,可为NULL。
* @pre 调用者必须持有端口互斥锁。
*/
static void uart_change_line_settings(struct tty_struct *tty, struct uart_state *state,
const struct ktermios *old_termios)
{
struct uart_port *uport = uart_port_check(state);
struct ktermios *termios;
bool old_hw_stopped;

/* 如果必要结构不存在,则无法设置参数。 */
if (!tty || uport->type == PORT_UNKNOWN)
return;

termios = &tty->termios;
/* 调用底层驱动函数,将termios设置应用到硬件。 */
uport->ops->set_termios(uport, termios, old_termios);

/* 根据termios的c_cflag更新调制解调器状态的使能位。 */
guard(uart_port_lock_irq)(uport);
/* 如果设置了CRTSCTS,则使能CTS流控。 */
if (termios->c_cflag & CRTSCTS)
uport->status |= UPSTAT_CTS_ENABLE;
else
uport->status &= ~UPSTAT_CTS_ENABLE;

/* 如果未设置CLOCAL,则使能DCD状态检测。 */
if (termios->c_cflag & CLOCAL)
uport->status &= ~UPSTAT_DCD_ENABLE;
else
uport->status |= UPSTAT_DCD_ENABLE;

/* 根据新的模式,重置软件辅助的CTS流控状态。 */
old_hw_stopped = uport->hw_stopped;
uport->hw_stopped = uart_softcts_mode(uport) &&
!(uport->ops->get_mctrl(uport) & TIOCM_CTS);
/* 如果硬件停止状态发生变化。 */
if (uport->hw_stopped != old_hw_stopped) {
if (!old_hw_stopped) /* 如果之前未停止,现在停止。 */
uport->ops->stop_tx(uport);

else /* 如果之前停止了,现在启动。 */
__uart_start(state);
}
}

uart_rs485_config & uart_set_rs485_config: UART RS-485模式配置与管理

本代码片段完整地展示了Linux内核UART核心层(UART core)对RS-485/RS-422半双工通信模式的配置与管理框架。其核心功能是通过ioctl系统调用,接收来自用户空间的serial_rs485结构体配置,对配置参数进行严格的合法性检查(uart_check_rs485_flags)和健壮性修正(uart_sanitize_serial_rs485),并最终通过底层驱动的回调函数(port->rs485_config)和通用的GPIO接口,将配置应用到具体的硬件上。

实现原理分析

该机制是一个优秀的分层驱动设计典范,它将RS-485的通用逻辑(如参数校验、数据结构管理)置于通用核心层,而将与具体硬件相关的实现(如寄存器配置、GPIO控制)委托给底层驱动,实现了高度的抽象和代码复用。

  1. 用户接口 (uart_set_rs485_config):

    • 这是从TTY层进入RS-485配置的入口点,通常由TIOCSRS485 ioctl调用。
    • 它首先通过copy_from_user从用户空间安全地拷贝配置数据,防止恶意指针。
    • 在将配置应用到硬件之前,它执行了一个严格的“校验-修正”流程。
  2. 数据校验 (uart_check_rs485_flags):

    • 此函数负责检查用户提供的flagsaddress字段是否逻辑自洽且被驱动支持。
    • 它首先忽略内核自己定义的、向后兼容的旧标志位(SER_RS485_LEGACY_FLAGS),然后检查是否有任何用户设置的、但底层驱动声称不支持(port->rs485_supported.flags)的标志位。
    • 它还会检查逻辑错误,例如:请求地址过滤功能却没有设置地址模式,或者在未使能地址过滤的情况下提供了地址值。任何不匹配都会导致配置失败并返回-EINVAL
  3. 数据修正/净化 (uart_sanitize_serial_rs485):

    • 这是增强驱动健壮性的关键。对于不合理但可修正的用户输入,它不会直接拒绝,而是将其修正为“理智的”默认值。
    • RTS逻辑修正: RS-485的发送器使能(通常通过RTS信号)逻辑必须是明确的(发送前拉高/发送后拉低)。如果用户同时设置或同时未设置这两个互斥的标志,此函数会根据驱动的能力,强制选择一个默认的有效模式,并打印警告。
    • 延迟值修正 (uart_sanitize_serial_rs485_delays): 它检查驱动是否支持发送前/后RTS延迟,如果不支持,则强制将延迟设为0。如果支持但用户设置的值超出了最大范围(RS485_MAX_RTS_DELAY),则将其钳位到最大值。
    • 填充区清理: memset用于清零结构体中的padding字段,这是一种良好的安全实践,可以防止内核栈上的垃圾数据通过ioctl泄露到用户空间。
  4. 硬件应用 (uart_rs485_config):

    • 这是将净化后的配置最终应用到硬件的函数。
    • 通用GPIO控制: 它首先处理两个通用的、通常由GPIO控制的功能:总线终端电阻(rs485_term_gpio)和发送期间是否接收(rs485_rx_during_tx_gpio)。这使得即使UART硬件本身不支持这些功能,也能通过外部电路和GPIO来实现。
    • 驱动特定回调: 核心的RS-485时序和逻辑控制通过调用port->rs485_config(port, NULL, rs485)来完成。这个函数指针指向的是底层具体UART驱动(如STM32的UART驱动)提供的实现。
    • 原子性与回滚: 整个配置过程被scoped_guard(uart_port_lock_irqsave, port)保护,确保是原子的。如果rs485_config回调失败,函数会清空port->rs485配置,并回滚GPIO的设置,使系统保持在一个一致的、已知的状态。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
/* 定义一个掩码,包含了所有向后兼容的旧版RS485标志位。 */
#define SER_RS485_LEGACY_FLAGS (SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | \
SER_RS485_RTS_AFTER_SEND | SER_RS485_RX_DURING_TX | \
SER_RS485_TERMINATE_BUS)

/**
* @brief 检查用户提供的RS485配置标志是否有效和被支持。
* @param port UART端口。
* @param rs485 用户提供的RS485配置。
* @return int 成功返回0,无效则返回-EINVAL。
*/
static int uart_check_rs485_flags(struct uart_port *port, struct serial_rs485 *rs485)
{
u32 flags = rs485->flags;

/* 首先忽略所有旧版标志,不因它们不被支持而报错。 */
flags &= ~SER_RS485_LEGACY_FLAGS;

/* 检查是否有任何驱动不支持的、非旧版的标志被设置。 */
if (flags & ~port->rs485_supported.flags)
return -EINVAL;

/* 检查逻辑错误:请求地址过滤,但未设置地址模式? */
if (!(rs485->flags & SER_RS485_ADDRB) &&
(rs485->flags & (SER_RS485_ADDR_RECV|SER_RS485_ADDR_DEST)))
return -EINVAL;

/* 检查逻辑错误:提供了地址值,但未使能相应的地址过滤标志? */
if (!(rs485->flags & SER_RS485_ADDR_RECV) && rs485->addr_recv)
return -EINVAL;
if (!(rs485->flags & SER_RS485_ADDR_DEST) && rs485->addr_dest)
return -EINVAL;

return 0;
}

/**
* @brief 净化(修正)RS485的延迟时间参数。
* @param port UART端口。
* @param rs485 要净化的RS485配置。
*/
static void uart_sanitize_serial_rs485_delays(struct uart_port *port,
struct serial_rs485 *rs485)
{
/* 检查并修正发送前RTS延迟。 */
if (!port->rs485_supported.delay_rts_before_send) {
if (rs485->delay_rts_before_send)
dev_warn_ratelimited(port->dev,
"%s (%u): 不支持发送前RTS延迟\n",
port->name, port->line);
rs485->delay_rts_before_send = 0;
} else if (rs485->delay_rts_before_send > RS485_MAX_RTS_DELAY) {
rs485->delay_rts_before_send = RS485_MAX_RTS_DELAY;
dev_warn_ratelimited(port->dev,
"%s (%u): 发送前RTS延迟被限制到 %u ms\n",
port->name, port->line, rs485->delay_rts_before_send);
}

/* 检查并修正发送后RTS延迟。 */
if (!port->rs485_supported.delay_rts_after_send) {
if (rs485->delay_rts_after_send)
dev_warn_ratelimited(port->dev,
"%s (%u): 不支持发送后RTS延迟\n",
port->name, port->line);
rs485->delay_rts_after_send = 0;
} else if (rs485->delay_rts_after_send > RS485_MAX_RTS_DELAY) {
rs485->delay_rts_after_send = RS485_MAX_RTS_DELAY;
dev_warn_ratelimited(port->dev,
"%s (%u): 发送后RTS延迟被限制到 %u ms\n",
port->name, port->line, rs485->delay_rts_after_send);
}
}

/**
* @brief 净化(修正)整个RS485配置,使其合法且合理。
* @param port UART端口。
* @param rs485 要净化的RS485配置。
*/
static void uart_sanitize_serial_rs485(struct uart_port *port, struct serial_rs485 *rs485)
{
u32 supported_flags = port->rs485_supported.flags;

/* 如果未使能RS485,则清空整个结构体并返回。 */
if (!(rs485->flags & SER_RS485_ENABLED)) {
memset(rs485, 0, sizeof(*rs485));
return;
}

/* 如果是RS422模式,只保留使能、模式和终端电阻标志。 */
if (rs485->flags & SER_RS485_MODE_RS422) {
rs485->flags &= (SER_RS485_ENABLED | SER_RS485_MODE_RS422 | SER_RS485_TERMINATE_BUS);
return;
}

/* 清除所有驱动不支持的标志位。 */
rs485->flags &= supported_flags;

/* 如果用户没有提供合理的RTS极性设置(两者都置位或都未置位),则强制修正。 */
if (!(rs485->flags & SER_RS485_RTS_ON_SEND) ==
!(rs485->flags & SER_RS485_RTS_AFTER_SEND)) {
if (supported_flags & SER_RS485_RTS_ON_SEND) {
rs485->flags |= SER_RS485_RTS_ON_SEND;
rs485->flags &= ~SER_RS485_RTS_AFTER_SEND;
dev_warn_ratelimited(port->dev,
"%s (%u): 无效的RTS设置,使用RTS_ON_SEND替代\n",
port->name, port->line);
} else {
rs485->flags |= SER_RS485_RTS_AFTER_SEND;
rs485->flags &= ~SER_RS485_RTS_ON_SEND;
dev_warn_ratelimited(port->dev,
"%s (%u): 无效的RTS设置,使用RTS_AFTER_SEND替代\n",
port->name, port->line);
}
}

uart_sanitize_serial_rs485_delays(port, rs485);

/* 将结构体中的填充区域清零,防止内核信息泄露。 */
memset(rs485->padding0, 0, sizeof(rs485->padding0));
memset(rs485->padding1, 0, sizeof(rs485->padding1));
}

/**
* @brief 通过GPIO设置RS485总线终端电阻。
* @param port UART端口。
* @param rs485 RS485配置。
*/
static void uart_set_rs485_termination(struct uart_port *port,
const struct serial_rs485 *rs485)
{
if (!(rs485->flags & SER_RS485_ENABLED))
return;
/* 根据标志位设置GPIO的值,可以睡眠。 */
gpiod_set_value_cansleep(port->rs485_term_gpio,
!!(rs485->flags & SER_RS485_TERMINATE_BUS));
}

/**
* @brief 通过GPIO设置RS485发送期间是否接收。
* @param port UART端口。
* @param rs485 RS485配置。
*/
static void uart_set_rs485_rx_during_tx(struct uart_port *port,
const struct serial_rs485 *rs485)
{
if (!(rs485->flags & SER_RS485_ENABLED))
return;
/* 根据标志位设置GPIO的值,可以睡眠。 */
gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
!!(rs485->flags & SER_RS485_RX_DURING_TX));
}

/**
* @brief 将端口的RS485配置应用到硬件。
* @param port 要配置的UART端口。
* @return int 成功返回0,失败返回错误码。
*/
static int uart_rs485_config(struct uart_port *port)
{
struct serial_rs485 *rs485 = &port->rs485;
int ret;

if (!(rs485->flags & SER_RS485_ENABLED))
return 0;

/* 先对当前端口的rs485配置进行净化和GPIO设置。 */
uart_sanitize_serial_rs485(port, rs485);
uart_set_rs485_termination(port, rs485);
uart_set_rs485_rx_during_tx(port, rs485);

/* 在锁保护下,调用底层驱动的回调函数。 */
scoped_guard(uart_port_lock_irqsave, port)
ret = port->rs485_config(port, NULL, rs485);
if (ret) {
/* 如果失败,清空配置并回滚GPIO设置。 */
memset(rs485, 0, sizeof(*rs485));
gpiod_set_value_cansleep(port->rs485_term_gpio, 0);
gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio, 0);
}

return ret;
}

/**
* @brief 从端口获取当前的RS485配置。
* @param port UART端口。
* @param rs485 指向用户空间缓冲区的指针。
* @return int 成功返回0,失败返回错误码。
*/
static int uart_get_rs485_config(struct uart_port *port,
struct serial_rs485 __user *rs485)
{
struct serial_rs485 aux;

/* 在锁保护下,拷贝端口当前的RS485配置。 */
scoped_guard(uart_port_lock_irqsave, port)
aux = port->rs485;

/* 将配置拷贝到用户空间。 */
if (copy_to_user(rs485, &aux, sizeof(aux)))
return -EFAULT;

return 0;
}

/**
* @brief 为端口设置新的RS485配置。
* @param tty TTY结构体。
* @param port UART端口。
* @param rs485_user 指向用户空间配置的指针。
* @return int 成功返回0,失败返回错误码。
*/
static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
struct serial_rs485 __user *rs485_user)
{
struct serial_rs485 rs485;
int ret;

/* 如果驱动根本不支持RS485,返回错误。 */
if (!(port->rs485_supported.flags & SER_RS485_ENABLED))
return -ENOTTY;

/* 从用户空间拷贝配置数据。 */
if (copy_from_user(&rs485, rs485_user, sizeof(*rs485_user)))
return -EFAULT;

/* 校验和净化从用户空间接收的数据。 */
ret = uart_check_rs485_flags(port, &rs485);
if (ret)
return ret;
uart_sanitize_serial_rs485(port, &rs485);
uart_set_rs485_termination(port, &rs485);
uart_set_rs485_rx_during_tx(port, &rs485);

/* 在锁保护下,调用底层驱动回调函数应用配置。 */
scoped_guard(uart_port_lock_irqsave, port) {
ret = port->rs485_config(port, &tty->termios, &rs485);
if (!ret) {
/* 成功后,更新端口的当前配置。 */
port->rs485 = rs485;

/* 如果是禁用RS485,则重置RTS和其他mctrl线。 */
if (!(rs485.flags & SER_RS485_ENABLED))
port->ops->set_mctrl(port, port->mctrl);
}
}
if (ret) {
/* 如果失败,恢复旧的GPIO设置。 */
gpiod_set_value_cansleep(port->rs485_term_gpio,
!!(port->rs485.flags & SER_RS485_TERMINATE_BUS));
gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
!!(port->rs485.flags & SER_RS485_RX_DURING_TX));
return ret;
}

/* 成功后,将最终(可能被净化过的)配置拷贝回用户空间。 */
if (copy_to_user(rs485_user, &port->rs485, sizeof(port->rs485)))
return -EFAULT;

return 0;
}

uart_console_write & uart_parse/set_options: UART控制台通用辅助函数

本代码片段展示了Linux内核UART核心层(UART core)为所有串口控制台驱动提供的一套标准、可复用的辅助函数。其核心功能是:1) 提供一个通用的字符串写入例程 (uart_console_write),它能处理换行符转换;2) 提供一套完整的内核启动命令行参数解析和应用逻辑 (uart_parse_earlycon, uart_parse_options, uart_set_options),使得任何串口驱动都能轻松地支持通过console=earlycon=参数进行配置。这些函数是驱动与内核控制台框架之间的重要“粘合剂”。

实现原理分析

这些函数体现了内核驱动框架中“分离通用逻辑与特定实现”的设计哲学。

  1. 通用写入逻辑 (uart_console_write):

    • 职责: 此函数只负责两件事:循环遍历字符串,以及处理换行符。
    • 换行符转换: if (*s == '\n') putchar(port, '\r'); 这是它的关键特性。在传统的串行终端中,换行符\n (LF, Line Feed) 只会将光标下移一行,而回车符\r (CR, Carriage Return) 才会将光标移至行首。为了保证在所有终端上都能正确地换行并回到行首,printk输出的单个\n必须被转换为\r\n序列。此函数就负责了这个转换。
    • 抽象: 它通过一个putchar函数指针来执行实际的单字符硬件写入。这意味着uart_console_write本身完全与硬件无关。任何串口驱动,只要能提供一个符合签名的putchar函数(如上一段分析中的stm32_usart_console_putchar),就可以使用这个通用的写入逻辑。
  2. 启动参数解析 (uart_parse_earlycon & uart_parse_options):

    • 职责: 这两个函数是纯粹的字符串解析器,用于解码内核启动命令行。
    • uart_parse_earlycon: 用于解析earlycon参数,它负责识别IO类型(MMIO, IO等)和硬件的物理基地址。这对于在设备模型(device model)和驱动探测(probe)完成之前就建立一个“早期控制台”至关重要。
    • uart_parse_options: 负责解析更通用的串口参数字符串,如115200n8。它按照固定的位置和格式(波特率、校验位、数据位、流控)来提取信息。
    • 这些函数将晦涩的命令行字符串,转换成驱动可以理解的、结构化的C语言变量。
  3. 应用配置 (uart_set_options):

    • 职责: 这是将解析出的参数应用到uart_port的“最后一公里”。
    • termios转换: 它是整个TTY子系统的核心。该函数创建一个临时的ktermios结构体,然后根据传入的baud, parity, bits, flow参数,设置termios结构体中对应的标准标志位(如CS8, PARENB, CRTSCTS等)。
    • 硬件调用: 最关键的一步是port->ops->set_termios(port, &termios, &dummy);。它将这个填满了配置信息的termios结构体,通过函数指针传递给底层的、与具体硬件相关的驱动。底层驱动(如STM32驱动)的set_termios函数会负责读取这个结构体,并将其中的信息翻译成对硬件寄存器(如波特率寄存器、控制寄存器)的写操作。
    • 锁初始化: 它包含一个重要的早期初始化逻辑,确保在第一次设置控制台选项时,该端口的自旋锁已经被初始化,为后续安全的控制台写入做好准备。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)
/**
* @brief 将控制台消息写入一个串口。
* @param port 要写入的串口端口。
* @param s 字符数组。
* @param count 要写入的字符数。
* @param putchar 用于向端口写入单个字符的函数。
*/
void uart_console_write(struct uart_port *port, const char *s,
unsigned int count,
void (*putchar)(struct uart_port *, unsigned char))
{
unsigned int i;

/* 循环遍历字符串中的每个字符。 */
for (i = 0; i < count; i++, s++) {
/* 如果遇到换行符 '\n',则先发送一个回车符 '\r'。 */
if (*s == '\n')
putchar(port, '\r');
/* 发送当前字符。 */
putchar(port, *s);
}
}
EXPORT_SYMBOL_GPL(uart_console_write);

/**
* @brief 解析 earlycon 选项。
* @param p 指向第二个字段的指针(即'<name>,'之后)。
* @param iotype 指向解码出的iotype的指针(输出)。
* @param addr 指向解码出的mapbase/iobase的指针(输出)。
* @param options 指向<options>字段的指针;如果不存在则为%NULL(输出)。
* @return int 成功返回0,失败返回-%EINVAL。
*/
int uart_parse_earlycon(char *p, enum uart_iotype *iotype,
resource_size_t *addr, char **options)
{
/* 根据关键字判断IO类型。 */
if (strncmp(p, "mmio,", 5) == 0) {
*iotype = UPIO_MEM;
p += 5;
} else if (strncmp(p, "mmio16,", 7) == 0) {
*iotype = UPIO_MEM16;
p += 7;
} else if (strncmp(p, "mmio32,", 7) == 0) {
*iotype = UPIO_MEM32;
p += 7;
} else if (strncmp(p, "mmio32be,", 9) == 0) {
*iotype = UPIO_MEM32BE;
p += 9;
} else if (strncmp(p, "mmio32native,", 13) == 0) {
*iotype = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) ?
UPIO_MEM32BE : UPIO_MEM32;
p += 13;
} else if (strncmp(p, "io,", 3) == 0) {
*iotype = UPIO_PORT;
p += 3;
} else if (strncmp(p, "0x", 2) == 0) {
*iotype = UPIO_MEM;
} else {
return -EINVAL;
}

/* 解析地址,使用simple_strtoull以兼容逗号分隔符。 */
*addr = simple_strtoull(p, NULL, 0);
p = strchr(p, ',');
if (p)
p++;

*options = p;
return 0;
}
EXPORT_SYMBOL_GPL(uart_parse_earlycon);

/**
* @brief 解析串口的波特率/校验/数据位/流控选项。
* @param options 指向选项字符串的指针。
* @param baud 指向波特率变量的指针。
* @param parity 指向校验位变量的指针。
* @param bits 指向数据位变量的指针。
* @param flow 指向流控字符变量的指针。
*/
void
uart_parse_options(const char *options, int *baud, int *parity,
int *bits, int *flow)
{
const char *s = options;

/* 解析波特率(纯数字部分)。 */
*baud = simple_strtoul(s, NULL, 10);
while (*s >= '0' && *s <= '9')
s++;
/* 第一个非数字字符是校验位。 */
if (*s)
*parity = *s++;
/* 下一个字符是数据位。 */
if (*s)
*bits = *s++ - '0';
/* 再下一个是流控。 */
if (*s)
*flow = *s;
}
EXPORT_SYMBOL_GPL(uart_parse_options);

/**
* @brief 设置串口控制台的参数。
* @param port 串口的uart_port结构指针。
* @param co 控制台指针。
* @param baud 波特率。
* @param parity 校验字符 - 'n', 'o', 'e'。
* @param bits 数据位数。
* @param flow 流控字符 - 'r' (rts)。
* @return int 总是返回0。
*/
int
uart_set_options(struct uart_port *port, struct console *co,
int baud, int parity, int bits, int flow)
{
struct ktermios termios;
static struct ktermios dummy;

/* 确保串口控制台的自旋锁在早期被初始化。 */
if (!uart_console_registered_locked(port) && !port->console_reinit)
uart_port_spin_lock_init(port);

memset(&termios, 0, sizeof(struct ktermios));

/* 设置通用的、合理的termios默认标志。 */
termios.c_cflag |= CREAD | HUPCL | CLOCAL;
/* 将整数波特率编码为termios的c_cflag格式。 */
tty_termios_encode_baud_rate(&termios, baud, baud);

/* 根据位数设置CS7或CS8。 */
if (bits == 7)
termios.c_cflag |= CS7;
else
termios.c_cflag |= CS8;

/* 根据校验字符设置PARENB和PARODD。 */
switch (parity) {
case 'o': case 'O':
termios.c_cflag |= PARODD;
fallthrough;
case 'e': case 'E':
termios.c_cflag |= PARENB;
break;
}

/* 根据流控字符设置CRTSCTS。 */
if (flow == 'r')
termios.c_cflag |= CRTSCTS;

/*
* 某些对端设备不支持无流控,所以我们在主机端设置DTR
* 以便让它们正常工作。
*/
port->mctrl |= TIOCM_DTR;

/* 调用底层驱动的操作函数来实际设置硬件。 */
port->ops->set_termios(port, &termios, &dummy);
/*
* 如果提供了console结构,则将termios的标志缓存到
* console结构中。
*/
if (co) {
co->cflag = termios.c_cflag;
co->ispeed = termios.c_ispeed;
co->ospeed = termios.c_ospeed;
}

return 0;
}
EXPORT_SYMBOL_GPL(uart_set_options);
#endif /* CONFIG_SERIAL_CORE_CONSOLE */

好的,我将对您提供的这段关于Linux内核serial_core子系统中端口注册与注销的代码进行分析。

uart_add_one_port & serial_core_register_port: UART端口向内核设备模型的注册

本代码片段展示了Linux内核中一个独立的UART端口(uart_port)如何被注册到内核的设备模型中,并最终呈现给用户空间的整个过程。其核心功能由serial_core_register_port实现,它引入了一个“控制器设备” (serial_ctrl_device) 的抽象层级,允许多个逻辑上的串口(port)依附于一个物理上的控制器(controller)。这个过程是动态的,并且通过UPF_DEAD标志确保了在注册完成之前,设备不会被意外地访问。

实现原理分析

这段代码是Linux设备模型思想的体现,即通过分层的struct device来构建硬件拓扑。它在传统的uart_driveruart_port之上,又增加了一层serial_ctrl_deviceserial_port_device,以更好地管理多端口串口卡或集成了多个UART的SoC。

  1. 分层抽象:

    • uart_port: 代表一个独立的、可操作的物理串口,包含寄存器地址、中断号、操作函数集(ops)等。这是硬件功能的抽象。
    • serial_port_device: 这是uart_port在Linux设备模型中的表现形式。它是一个struct device,拥有自己的sysfs条目,并作为下面要说的serial_ctrl_device的子设备。
    • serial_ctrl_device: 代表一个物理上的“串口控制器”。对于一个PCIe八口串口卡,它就代表这张卡;对于一个SoC上的4个UART,它可能就代表这个SoC本身。它作为serial_port_device的父设备。
    • uart_add_one_port只是serial_core_register_port的一个简单封装,提供了向后兼容的API。
  2. 注册流程 (serial_core_register_port):

    • 加锁: 整个注册过程由一个全局互斥锁port_mutex保护,确保了对uart_driver状态列表等共享资源的串行访问。
    • 标记为死亡 (port->flags |= UPF_DEAD): 这是关键的第一步。在端口的设备结构完全建立并注册到TTY核心之前,将端口标记为UPF_DEAD。这可以防止任何其他内核子系统(特别是运行时PM)在此时尝试访问一个尚未完全初始化的端口,避免了竞态条件。
    • 查找或创建控制器:
      • serial_core_ctrl_find: 函数首先会检查是否已经有一个匹配的控制器设备被注册。它通过比较物理设备指针(port->dev)和控制器ID(port->ctrl_id)来判断。例如,一个SoC上的4个UART端口共享同一个platform_deviceport->dev),但可能有不同的ctrl_id
      • serial_core_ctrl_device_add: 如果没有找到匹配的控制器,就为这个端口创建一个新的控制器设备。
    • 创建端口设备 (serial_core_port_device_add): 在控制器设备准备好之后,调用此函数来创建serial_port_device,并将其注册为控制器设备的子设备。此时,port->port_dev被赋值,uart_port与它的设备模型实体正式关联。
    • 控制台匹配: serial_base_match_and_update_preferred_console会检查这个新注册的端口是否匹配内核启动命令行中指定的控制台,如果是,则进行相应的设置。
    • 添加到TTY核心 (serial_core_add_one_port): 这是最后一步,它会将端口注册到TTY子系统,创建/dev/ttySx设备节点,并清除UPF_DEAD标志,正式宣告该端口已准备就绪,可以对外服务。
  3. 注销流程 (serial_core_unregister_port):

    • 这是一个与注册严格相反的“撤销”过程。
    • 它再次将端口标记为UPF_DEAD,以阻止新的访问。
    • serial_core_remove_one_port: 从TTY核心移除端口,删除设备节点。注意注释:在此调用之后,struct uart_port指针本身可能不再有效,因为它可能已经被释放。
    • serial_base_port_device_remove: 从设备模型中移除serial_port_device
    • serial_base_ctrl_device_remove: 最后,它会再次检查是否还有其他端口在使用这个控制器。如果没有,它就会移除这个控制器设备。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
* @brief 添加一个UART端口到指定的UART驱动。
* @param drv UART驱动。
* @param port 要添加的UART端口。
* @return int 成功返回0,失败返回错误码。
*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
return serial_ctrl_register_port(drv, port);
}
EXPORT_SYMBOL(uart_add_one_port);

/**
* @brief 从指定的UART驱动中移除一个UART端口。
* @param drv UART驱动。
* @param port 要移除的UART端口。
*/
void uart_remove_one_port(struct uart_driver *drv, struct uart_port *port)
{
serial_ctrl_unregister_port(drv, port);
}
EXPORT_SYMBOL(uart_remove_one_port);

/*
* 以下两个函数是serial_ctrl层对serial_core层的简单封装。
*/
int serial_ctrl_register_port(struct uart_driver *drv, struct uart_port *port)
{
return serial_core_register_port(drv, port);
}
void serial_ctrl_unregister_port(struct uart_driver *drv, struct uart_port *port)
{
serial_core_unregister_port(drv, port);
}

/* ... 辅助函数 ... */

/**
* @brief 注册一个串口核心端口设备,并在需要时创建一个控制器设备。
* @param drv UART驱动。
* @param port 要注册的UART端口。
* @return int 成功返回0,失败返回错误码。
*/
int serial_core_register_port(struct uart_driver *drv, struct uart_port *port)
{
struct serial_ctrl_device *ctrl_dev, *new_ctrl_dev = NULL;
int ret;

/* 锁定全局互斥锁,保护对驱动列表的访问。 */
guard(mutex)(&port_mutex);

/*
* 将端口标记为“死亡”,以防止在注册完成前被意外使用
* (例如被运行时PM框架)。
*/
port->flags |= UPF_DEAD;

/* 如果需要,初始化一个串口核心控制器设备。 */
ctrl_dev = serial_core_ctrl_find(drv, port->dev, port->ctrl_id);
if (!ctrl_dev) {
/* 如果不存在匹配的控制器,则创建一个新的。 */
new_ctrl_dev = serial_core_ctrl_device_add(port);
if (IS_ERR(new_ctrl_dev))
return PTR_ERR(new_ctrl_dev);
ctrl_dev = new_ctrl_dev;
}

/* 初始化串口核心端口设备。 */
ret = serial_core_port_device_add(ctrl_dev, port);
if (ret)
goto err_unregister_ctrl_dev;

/* 检查此端口是否是内核命令行指定的首选控制台。 */
ret = serial_base_match_and_update_preferred_console(drv, port);
if (ret)
goto err_unregister_port_dev;

/* 将端口添加到TTY核心,并清除UPF_DEAD标志。 */
ret = serial_core_add_one_port(drv, port);
if (ret)
goto err_unregister_port_dev;

return 0;

/* 错误处理回滚路径 */
err_unregister_port_dev:
serial_base_port_device_remove(port->port_dev);
err_unregister_ctrl_dev:
/* 只有在本次调用中新创建了控制器时,才移除它。 */
serial_base_ctrl_device_remove(new_ctrl_dev);
return ret;
}

/**
* @brief 移除一个串口核心端口设备,并在需要时移除相关的控制器设备。
* @param drv UART驱动。
* @param port 要移除的UART端口。
*/
void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port)
{
struct device *phys_dev = port->dev;
struct serial_port_device *port_dev = port->port_dev;
struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);
int ctrl_id = port->ctrl_id;

mutex_lock(&port_mutex);

/* 标记为死亡以阻止新的访问。 */
port->flags |= UPF_DEAD;

/* 从TTY核心中移除端口。 */
serial_core_remove_one_port(drv, port);

/* 注意:此时 uart_port 指针可能已失效! */
serial_base_port_device_remove(port_dev);

/* 如果没有其他端口在使用此控制器,则移除控制器设备。 */
if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id))
serial_base_ctrl_device_remove(ctrl_dev);

mutex_unlock(&port_mutex);
}

serial_core_ctrl_find & _add: 串口控制器与端口设备的创建与关联

本代码片段展示了serial_core子系统内部用于管理“控制器-端口”设备层级关系的四个核心辅助函数。它们是serial_core_register_port实现其功能的基石。其核心功能是:1) 查找一个现有的控制器设备以实现共享(serial_core_ctrl_find);2) 创建一个新的控制器设备(serial_core_ctrl_device_add);3) 创建一个新的端口设备并将其关联到控制器下(serial_core_port_device_add);4) 在设备树中向上导航以找到控制器(serial_core_get_ctrl_dev)。

实现原理分析

这些函数共同构建了一个两级的设备模型,用于精确地表示物理硬件的拓扑结构,尤其适用于多端口串口设备。

  1. 设备树导航 (serial_core_get_ctrl_dev):

    • 职责: 从一个已知的serial_port_device(端口设备)找到其父设备,即serial_ctrl_device(控制器设备)。
    • 实现: 它利用了Linux设备模型的核心特性:dev->parent指针。它首先获取serial_port_device内嵌的struct device,然后访问其parent指针,最后使用to_serial_base_ctrl_device宏将通用的struct device指针安全地转换为serial_ctrl_device类型。这体现了设备模型中清晰的父子层级关系。
  2. 控制器查找 (serial_core_ctrl_find):

    • 职责: 这是实现控制器共享的关键。在注册一个新的uart_port时,必须先检查是否已经有一个代表相同物理硬件的控制器设备存在。
    • 实现: 它遍历一个uart_driver下的所有已注册端口。
    • 匹配逻辑: 它使用两个条件来判断是否为同一个物理控制器:
      • state->uart_port->dev == phys_dev: port->dev通常指向一个platform_devicepci_dev,代表了物理芯片或设备。如果两个uart_port的这个指针相同,说明它们属于同一个物理设备(例如,同一个PCIe串口卡或同一个SoC上的UARTs)。
      • state->uart_port->ctrl_id == ctrl_id: 这是一个可选的ID,用于区分同一个物理设备上的多个独立控制器(虽然不常见)。
    • 如果找到一个已注册的端口满足这两个条件,它就通过serial_core_get_ctrl_dev获取其控制器并返回。这确保了一个物理控制器在内核中只会被实例化为一个serial_ctrl_device
    • lockdep_assert_held(&port_mutex): 这是一个调试断言,它确保调用此函数的代码路径已经持有了port_mutex锁,这是安全遍历驱动端口列表的前提。
  3. 控制器创建 (serial_core_ctrl_device_add):

    • 职责: 当serial_core_ctrl_find返回NULL时,调用此函数来创建一个新的控制器设备。
    • 实现: 它是一个对底层serial_base_ctrl_add函数的简单封装,传递了uart_port和它所依附的物理设备port->dev
  4. 端口创建 (serial_core_port_device_add):

    • 职责: 创建一个serial_port_device,并将其正式注册为ctrl_dev的子设备。
    • 实现: 它调用底层的serial_base_port_add函数来完成设备模型的注册。成功后,最重要的一步是port->port_dev = port_dev;,它将uart_port这个逻辑结构与刚刚创建的、代表其在设备模型中实体serial_port_device关联起来。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#define to_serial_base_ctrl_device(d) container_of((d), struct serial_ctrl_device, dev)
#define to_serial_base_port_device(d) container_of((d), struct serial_port_device, dev)

/**
* @brief 从一个端口设备获取其父控制器设备。
* @param port_dev 串口端口设备。
* @return struct serial_ctrl_device* 指向控制器设备的指针。
*/
static struct serial_ctrl_device *
serial_core_get_ctrl_dev(struct serial_port_device *port_dev)
{
struct device *dev = &port_dev->dev;

/*
* 通过dev->parent指针向上导航到父设备,
* 然后将其安全地转换为serial_ctrl_device类型。
*/
return to_serial_base_ctrl_device(dev->parent);
}

/**
* @brief 查找一个已注册的、匹配指定条件的串口控制器设备。
* @param drv UART驱动。
* @param phys_dev 物理设备指针(如 platform_device)。
* @param ctrl_id 控制器ID。
* @return struct serial_ctrl_device* 找到则返回指针,否则返回NULL。
* @pre 调用者必须持有 port_mutex 锁。
*/
static struct serial_ctrl_device *serial_core_ctrl_find(struct uart_driver *drv,
struct device *phys_dev,
int ctrl_id)
{
struct uart_state *state;
int i;

/* 调试断言:确保调用者已加锁。 */
lockdep_assert_held(&port_mutex);

/* 遍历驱动下的所有端口槽位。 */
for (i = 0; i < drv->nr; i++) {
state = drv->state + i;
/* 跳过未初始化或未关联设备模型的端口。 */
if (!state->uart_port || !state->uart_port->port_dev)
continue;

/* 如果物理设备指针和控制器ID都匹配,则说明找到了同一个控制器。 */
if (state->uart_port->dev == phys_dev &&
state->uart_port->ctrl_id == ctrl_id)
/* 获取并返回该端口的控制器。 */
return serial_core_get_ctrl_dev(state->uart_port->port_dev);
}

return NULL;
}

/**
* @brief 添加一个新的串口控制器设备。
* @param port 用于创建控制器的UART端口信息。
* @return struct serial_ctrl_device* 成功则返回新创建的控制器指针,
* 失败则返回错误指针。
*/
static struct serial_ctrl_device *serial_core_ctrl_device_add(struct uart_port *port)
{
/* 这是一个对底层实现的简单封装。 */
return serial_base_ctrl_add(port, port->dev);
}

/**
* @brief 添加一个新的串口端口设备,并将其关联到控制器。
* @param ctrl_dev 父控制器设备。
* @param port 要为其创建设备的UART端口。
* @return int 成功返回0,失败返回错误码。
*/
static int serial_core_port_device_add(struct serial_ctrl_device *ctrl_dev,
struct uart_port *port)
{
struct serial_port_device *port_dev;

/* 调用底层函数创建端口设备,并将其注册为ctrl_dev的子设备。 */
port_dev = serial_base_port_add(port, ctrl_dev);
if (IS_ERR(port_dev))
return PTR_ERR(port_dev);

/* 将uart_port与新创建的设备模型实体关联起来。 */
port->port_dev = port_dev;

return 0;
}

serial_core_add_one_port: 将UART端口注册为TTY设备

本代码片段展示了serial_core子系统中的一个关键函数serial_core_add_one_port,以及与之配对的serial_core_remove_one_port。这个函数是连接UART硬件抽象(uart_port)与Linux TTY子系统的最后一道桥梁。其核心功能是接收一个已经初始化好的uart_port,为其在TTY层创建一个对应的tty_device,从而生成用户空间可见的设备文件(如/dev/ttySTM0),并创建一系列sysfs属性文件,允许用户查看和配置端口参数。

实现原理分析

这是Linux驱动模型中,一个功能驱动(如STM32 UART驱动)通过一个通用子系统(serial_coretty)向用户空间暴露接口的标准流程。

  1. Sysfs属性定义:

    • DEVICE_ATTR_RO(name)DEVICE_ATTR_RW(name): 这些是内核提供的宏,用于快速定义sysfs属性。每个宏都会自动创建一个struct device_attribute实例,并预设其show(读)和/或store(写)回调函数。例如,DEVICE_ATTR_RO(uartclk)会创建一个名为dev_attr_uartclk的结构体,它对应sysfs中的一个名为uartclk的只读文件。
    • tty_dev_attr_group: 这个结构体将所有定义好的属性文件组织成一个属性组
  2. 添加端口流程 (serial_core_add_one_port):

    • 上下文关联:
      • 它首先找到uart_driver中与uport->line对应的uart_state,这是一个长期存在的状态容器。
      • state->uart_port = uport; uport->state = state;: 建立uart_stateuart_port之间的双向链接。
    • 初始化:
      • uart_port_spin_lock_init(uport): 确保端口的自旋锁被初始化。
      • uart_configure_port(drv, state, uport): 调用此函数对端口进行最后的硬件配置,包括探测UART类型、申请资源、上电并禁用调制解demodulator控制线等。
    • TTY设备注册:
      • tty_port_register_device_attr_serdev: 这是最核心的调用。它请求TTY核心层执行以下操作:
        1. 分配并注册一个tty_device实例。
        2. /dev目录下创建一个对应的设备文件(例如,ttySTM0)。
        3. 在sysfs中为这个TTY设备创建一个目录。
        4. tty_dev_attr_group(以及驱动可能提供的自定义属性组)中的所有属性文件创建在这个sysfs目录下。
    • 激活端口: uport->flags &= ~UPF_DEAD; 清除UPF_DEAD标志,标志着端口现在是完全初始化并准备就绪的,可以安全地被其他子系统(如运行时PM)使用了。
  3. 移除端口流程 (serial_core_remove_one_port):

    • 这是一个与添加过程严格相反的清理序列。
    • tty_port_unregister_device: 从TTY核心注销设备,删除/dev下的设备文件和sysfs目录。
    • tty_port_tty_vhangup: 强制挂断(hang up)所有当前打开了这个TTY设备的进程,通知它们此端口已不再可用。
    • unregister_console: 如果此端口被用作内核控制台,则从printk系统中注销它。
    • uport->ops->release_port(uport): 调用底层驱动提供的回调函数,释放硬件资源(如IO内存映射)。
    • 引用计数与同步:
      • atomic_dec_return(&state->refcount): 递减uart_state的引用计数。
      • wait_event(state->remove_wait, !atomic_read(&state->refcount)): 如果还有其他地方(例如一个正在进行的TTY操作)持有对uart_state的引用,这里会阻塞,直到所有引用都被释放为止。这是一个优雅的同步机制,确保在state->uart_port = NULL之前,没有任何代码正在使用这个即将被移除的端口。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
* 使用DEVICE_ATTR_RO/RW宏定义一系列对应uart_port成员的sysfs属性文件。
* 例如,DEVICE_ATTR_RO(uartclk)会创建一个只读的sysfs文件"uartclk"。
*/
static DEVICE_ATTR_RO(uartclk);
static DEVICE_ATTR_RO(type);
static DEVICE_ATTR_RO(line);
static DEVICE_ATTR_RO(port);
static DEVICE_ATTR_RO(irq);
static DEVICE_ATTR_RO(flags);
static DEVICE_ATTR_RO(xmit_fifo_size);
static DEVICE_ATTR_RO(close_delay);
static DEVICE_ATTR_RO(closing_wait);
static DEVICE_ATTR_RO(custom_divisor);
static DEVICE_ATTR_RO(io_type);
static DEVICE_ATTR_RO(iomem_base);
static DEVICE_ATTR_RO(iomem_reg_shift);
static DEVICE_ATTR_RW(console);

static struct attribute *tty_dev_attrs[] = {
&dev_attr_uartclk.attr,
&dev_attr_type.attr,
&dev_attr_line.attr,
&dev_attr_port.attr,
&dev_attr_irq.attr,
&dev_attr_flags.attr,
&dev_attr_xmit_fifo_size.attr,
&dev_attr_close_delay.attr,
&dev_attr_closing_wait.attr,
&dev_attr_custom_divisor.attr,
&dev_attr_io_type.attr,
&dev_attr_iomem_base.attr,
&dev_attr_iomem_reg_shift.attr,
&dev_attr_console.attr,
NULL
};


/* 将所有sysfs属性组织成一个属性组。 */
static const struct attribute_group tty_dev_attr_group = {
.attrs = tty_dev_attrs,
};

/**
* @brief 将一个驱动定义的端口结构附加到serial_core。
* @param drv UART驱动。
* @param uport 要附加的UART端口。
* @return int 成功返回0,失败返回错误码。
* @pre 调用者必须持有 port_mutex 锁。
*/
static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
struct device *tty_dev;
/* ... */

/* ... 检查line号是否有效 ... */

state = drv->state + uport->line;
port = &state->port;

guard(mutex)(&port->mutex); /* 锁定单个TTY端口的互斥锁。 */
if (state->uart_port)
return -EINVAL; /* 防止重复添加。 */

/* 建立uart_state和uart_port之间的双向链接。 */
atomic_set(&state->refcount, 1);
init_waitqueue_head(&state->remove_wait);
state->uart_port = uport;
uport->state = state;

/* 如果不是控制台,则初始化端口的自旋锁。 */
if (!uart_console_registered(uport))
uart_port_spin_lock_init(uport);
/* ... */
/* 对端口进行硬件相关的配置。 */
uart_configure_port(drv, state, uport);
/* ... */
/* 为TTY设备准备sysfs属性组。 */
uport->tty_groups[0] = &tty_dev_attr_group;
/* ... */
/* 清除DEAD标志,正式激活端口。 */
uport->flags &= ~UPF_DEAD;

/*
* 将端口注册为TTY设备,并创建sysfs属性。
* 这是将端口暴露给用户空间的关键一步。
*/
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
uport->line, uport->dev, &uport->port_dev->dev, port,
uport->tty_groups);
if (!IS_ERR(tty_dev)) {
device_set_wakeup_capable(tty_dev, 1);
} else {
/* 注册失败,重新标记为DEAD。 */
uport->flags |= UPF_DEAD;
dev_err(uport->dev, "无法在line %u上注册tty设备\n",
uport->line);
}

return 0;
}

/**
* @brief 分离一个驱动定义的端口结构。
* @param drv UART驱动。
* @param uport 要分离的UART端口。
* @pre 调用者必须持有 port_mutex 锁。
*/
static void serial_core_remove_one_port(struct uart_driver *drv,
struct uart_port *uport)
{
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
/* ... */

/* 从TTY层注销设备,这将移除/dev下的设备文件。 */
tty_port_unregister_device(port, drv->tty_driver, uport->line);

/* 强制挂断所有打开此端口的用户。 */
tty_port_tty_vhangup(port);

/* 如果是控制台,则注销。 */
if (uart_console(uport))
unregister_console(uport->cons);

/* 调用底层驱动回调,释放硬件资源。 */
if (uport->type != PORT_UNKNOWN && uport->ops->release_port)
uport->ops->release_port(uport);
/* ... 释放内存 ... */

uport->type = PORT_UNKNOWN;
/* ... */

mutex_lock(&port->mutex);
/* 递减引用计数。 */
WARN_ON(atomic_dec_return(&state->refcount) < 0);
/* 等待,直到所有对该端口的引用都消失。 */
wait_event(state->remove_wait, !atomic_read(&state->refcount));
/* 清除链接。 */
state->uart_port = NULL;
mutex_unlock(&port->mutex);
}

/**
* @brief 比较两个uart_port是否代表同一个物理端口。
* @param port1 第一个端口。
* @param port2 第二个端口。
* @return bool 如果等效,返回true。
*/
bool uart_match_port(const struct uart_port *port1,
const struct uart_port *port2)
{
if (port1->iotype != port2->iotype)
return false;

/* 根据IO类型,比较IO基地址或内存映射基地址。 */
switch (port1->iotype) {
case UPIO_PORT:
return port1->iobase == port2->iobase;
/* ... */
case UPIO_MEM:
/* ... */
return port1->mapbase == port2->mapbase;
default:
return false;
}
}
EXPORT_SYMBOL(uart_match_port);

uart_get_baud_rate: 串口波特率的决策引擎

本代码片段是Linux串行核心层(Serial Core)中一个至关重要的辅助函数——uart_get_baud_rate。它不是一个简单的查询函数,而是一个复杂的决策引擎。其核心功能是接收一个termios结构体,并根据其中的设置、uart_port遗留(legacy)速度标志UPF_SPD_*),以及硬件驱动提供的能力范围(min, max),计算出一个最终的、有效的、可被硬件实现的数值波特率。这个函数是处理历史兼容性问题和保证配置健壮性的典范。

实现原理分析

此函数的核心原理是**多级回退(multi-stage fallback)和对历史兼容性“怪癖”(kludge)**的处理。它确保无论用户提供多么不合理的配置,系统总能协商出一个可用的波特率。

  1. 历史兼容性:神奇的38400波特率:

    • termios接口还没有标准化支持超高波特率(如115200 bps)的早期,用户空间程序(如setserial)采用了一种“技巧”:它们将termios中的波特率设置为B38400,同时通过ioctl(TIOCSSERIAL)设置一个特殊的ASYNC_SPD_*标志(如ASYNC_SPD_VHI)。
    • uart_get_baud_rate函数就是这个技巧的内核端实现。
    • switch (flags): 函数首先检查port->flags中的UPF_SPD_*位。这些位是用户空间ASYNC_SPD_*标志的直接映射。根据这些标志,它会设定一个“备用波特率”altbaud(例如,UPF_SPD_VHI对应115200)。
    • if (try == 0 && baud == 38400) baud = altbaud;: 这是整个兼容逻辑的核心。在第一次尝试解析时,如果函数发现termios中设置的波特率恰好是38400,它就会用之前计算出的altbaud来替换它。这样,B38400 + UPF_SPD_VHI的组合就被正确地“翻译”成了115200。
  2. 健壮的多级回退逻辑:

    • 函数在一个for循环中最多尝试两次,以找到一个有效的波特率。
    • 第1级:尝试新配置: 第一次循环(try = 0),它使用用户传入的termios,并应用上述的38400“怪癖”逻辑。如果计算出的baud在硬件支持的[min, max]范围内,就直接返回这个值。
    • 第2级:尝试旧配置: 如果第一次尝试失败(baud超出范围),并且调用者提供了old(旧的)termios结构,函数会:
      • old termios中提取出之前的波特率。
      • 修改当前正在处理的termios,将它的波特率设置回旧的值。
      • continue到下一次循环,用这个“恢复后”的termios再试一次。
    • 第3级:裁剪到最近值: 如果没有old termios可供回退,作为最后的手段,函数会将波特率裁剪(clip)到最接近的有效边界(minmax),并修改termios以反映这个最终的决定。
  3. 特殊B0处理:

    • B0termios中是一个特殊的速率,表示“挂断线路”。
    • 函数在内部会临时将其当作一个普通速率(如9600)来进行有效性检查,但它会设置hung_up = 1标志。这个标志会阻止函数在回退或裁剪时永久性地修改termios中的B0设置,从而保留了“挂断”的原始意图。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* @brief 获取一个特定端口的波特率。
* @param port 相关的uart_port结构。
* @param termios 期望的termios设置(会被修改)。
* @param old 旧的termios设置(可为NULL),用于回退。
* @param min 硬件支持的最小波特率。
* @param max 硬件支持的最大波特率。
* @return unsigned int 计算出的有效波特率,失败则返回0。
*
* @details 该函数将termios结构解码为一个数值波特率,同时处理了
* 历史遗留的“神奇38400”波特率(通过spd_*标志)问题,
* 并将B0速率映射为9600。如果新波特率无效,它会尝试使用
* 旧的设置。如果仍然无效,则尝试9600。
*/
unsigned int
uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
const struct ktermios *old, unsigned int min, unsigned int max)
{
unsigned int try;
unsigned int baud;
unsigned int altbaud;
int hung_up = 0;
/* 获取端口的遗留速度标志。 */
upf_t flags = port->flags & UPF_SPD_MASK;

/* 根据遗留速度标志,确定备用波特率altbaud。 */
switch (flags) {
case UPF_SPD_HI:
altbaud = 57600;
break;
case UPF_SPD_VHI:
altbaud = 115200;
break;
case UPF_SPD_SHI:
altbaud = 230400;
break;
case UPF_SPD_WARP:
altbaud = 460800;
break;
default:
altbaud = 38400;
break;
}

/* 最多尝试两次(第一次用新termios,第二次用旧termios)。 */
for (try = 0; try < 2; try++) {
/* 从termios中解码出数值波特率。 */
baud = tty_termios_baud_rate(termios);

/* 核心兼容逻辑:在第一次尝试时,如果波特率是38400,
则用flags计算出的高速波特率替代它。 */
if (try == 0 && baud == 38400)
baud = altbaud;

/* 特殊处理B0(挂断)速率,临时用9600代替进行有效性检查。 */
if (baud == 0) {
hung_up = 1;
baud = 9600;
}

/* 如果计算出的波特率在硬件支持范围内,则成功返回。 */
if (baud >= min && baud <= max)
return baud;

/*
* 波特率无效,准备下一次尝试。
* 首先清除当前termios中的波特率设置。
*/
termios->c_cflag &= ~CBAUD;
/* 如果有旧的termios可以回退... */
if (old) {
/* 从旧的termios中获取波特率。 */
baud = tty_termios_baud_rate(old);
/* 如果不是挂断请求,则将当前termios更新为旧的波特率。 */
if (!hung_up)
tty_termios_encode_baud_rate(termios,
baud, baud);
old = NULL; /* 确保下次循环不再使用old。 */
continue; /* 进入下一次循环。 */
}

/*
* 作为最后手段,如果无法满足范围,则裁剪到最接近的
* 硬件支持速率。
*/
if (!hung_up) {
if (baud <= min)
tty_termios_encode_baud_rate(termios,
min + 1, min + 1);
else
tty_termios_encode_baud_rate(termios,
max - 1, max - 1);
}
}
/* 两次尝试后仍然失败,返回0。 */
return 0;
}
EXPORT_SYMBOL(uart_get_baud_rate);

uart_update_timeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* uart_update_timeout - update per-port frame timing information
* @port: uart_port structure describing the port
* @cflag: termios cflag value
* @baud: speed of the port
*
* Set the @port frame timing information from which the FIFO timeout value is
* derived. The @cflag value should reflect the actual hardware settings as
* number of bits, parity, stop bits and baud rate is taken into account here.
*
* Locking: caller is expected to take @port->lock
*/
void
uart_update_timeout(struct uart_port *port, unsigned int cflag,
unsigned int baud)
{
u64 temp = tty_get_frame_size(cflag);

temp *= NSEC_PER_SEC;
port->frame_time = (unsigned int)DIV64_U64_ROUND_UP(temp, baud);
}
EXPORT_SYMBOL(uart_update_timeout);

uart_prepare_sysrq_char: Magic SysRq 命令字符的拦截器

本代码片段展示了Magic SysRq over serial机制的第二部分——uart_prepare_sysrq_char函数。这是一个在中断服务程序(ISR)中调用的拦截器(interceptor)。它的核心功能是检查一个刚刚从串口硬件接收到的字符ch,判断它是否是一个紧跟在Break信号之后的、合法的SysRq命令字符。如果是,该函数会**“消耗”**这个字符,将其保存以备后续处理,并阻止它被当作普通数据传递给TTY层;否则,它会通知调用者此字符与SysRq无关。

实现原理分析

此函数是Break + <命令字符>这个两步状态机的后半部分。uart_handle_break是第一步,它“布防”了SysRq状态;uart_prepare_sysrq_char则是第二步,它检查并“触发”这个状态。

  1. 调用时机: 此函数在串口驱动的接收中断处理函数中被调用(例如,在stm32_usart_receive_chars_pio中),时机是在从硬件数据寄存器(RDR)读取一个字符ch之后,但在将其递交给uart_insert_char之前。

  2. 状态机检查 (if (!port->sysrq)):

    • 这是函数的快速路径port->sysrq是一个状态变量,它只有在uart_handle_break检测到Break信号后才会被设置为一个非零的时间戳。
    • 如果port->sysrq为0,意味着当前处于一个SysRq序列中。函数会立即返回0,告诉ISR:“这个字符是普通数据,请继续处理。” 对于99.99%的接收字符,都会走这个路径。
  3. 合法性验证 (if (ch && time_before(jiffies, port->sysrq))):

    • 如果port->sysrq非0(即SysRq已“布防”),函数会进行两项检查:
      • ch: 命令字符不能是NULL(0x00)。一个Break后紧跟一个NULL字符通常被解释为Break信号本身,而不是SysRq命令。
      • time_before(jiffies, port->sysrq): 这是一个超时检查uart_handle_breakport->sysrq设置为jiffies + SYSRQ_TIMEOUT。这个检查确保了命令字符是在Break信号后的一个短暂时间窗口(SYSRQ_TIMEOUT)内到达的。如果用户发送Break后等待太久才发送命令字符,则序列无效。
  4. 分发与消耗:

    • 主路径 (if (sysrq_mask())):
      • sysrq_mask()检查/proc/sys/kernel/sysrq配置,看SysRq功能是否被全局使能
      • 如果使能,port->sysrq_ch = ch;将这个命令字符保存到uart_port中。内核的SysRq核心后续会处理这个字符。
      • port->sysrq = 0; 重置状态机,表示SysRq序列已成功完成,防止下一个字符被错误地解释。
      • return 1;: 这是关键的返回值。返回1(true)会通知调用者(ISR):“这个字符已经被SysRq系统消耗了,你不应该再对它做任何事(比如把它传给TTY层)。
    • 备用路径 (if (uart_try_toggle_sysrq(port, ch))): 这是一个特殊功能,允许即使在SysRq被全局禁用时,也能通过发送一个特殊的切换字符来重新启用它。如果ch是那个特殊字符,此函数会处理它并返回1
    • 失败路径: 如果上述所有条件都不满足(例如超时、字符为NULL、SysRq被禁用且不是切换字符),代码会执行到函数末尾。port->sysrq = 0;重置状态机,return 0;通知ISR:“SysRq序列已中止,这个字符是普通数据,请继续处理。”

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* @brief 准备(检查并消耗)一个SysRq字符。
* @param port UART端口。
* @param ch 刚刚从硬件接收到的字符。
* @return int 如果该字符被SysRq系统消耗,则返回1;否则返回0。
*/
static inline int uart_prepare_sysrq_char(struct uart_port *port, u8 ch)
{
/* 快速路径:如果SysRq状态未被“布防”,则此字符是普通字符。*/
if (!port->sysrq)
return 0;

/*
* 检查SysRq序列是否有效:
* 1. 命令字符不能是NULL。
* 2. 必须在uart_handle_break设定的超时期限内到达。
*/
if (ch && time_before(jiffies, port->sysrq)) {
/* 检查SysRq功能是否被全局使能。 */
if (sysrq_mask()) {
/* SysRq已使能:保存命令字符。 */
port->sysrq_ch = ch;
/* 重置SysRq状态机。 */
port->sysrq = 0;
/* 返回1,通知调用者(ISR)“消耗”此字符。 */
return 1;
}
/* 检查是否是用于启用/禁用SysRq的特殊切换字符。 */
if (uart_try_toggle_sysrq(port, ch))
return 1; /* 如果是,同样消耗此字符。 */
}
/*
* SysRq序列无效(超时、NULL字符、或未使能且非切换字符):
* 重置SysRq状态机。
*/
port->sysrq = 0;

/* 返回0,通知调用者此字符是普通字符,应继续处理。 */
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* uart_insert_char - push a char to the uart layer
*
* User is responsible to call tty_flip_buffer_push when they are done with
* insertion.
*
* @port: corresponding port
* @status: state of the serial port RX buffer (LSR for 8250)
* @overrun: mask of overrun bits in @status
* @ch: character to push
* @flag: flag for the character (see TTY_NORMAL and friends)
*/
void uart_insert_char(struct uart_port *port, unsigned int status,
unsigned int overrun, u8 ch, u8 flag)
{
struct tty_port *tport = &port->state->port;

if ((status & port->ignore_status_mask & ~overrun) == 0)
if (tty_insert_flip_char(tport, ch, flag) == 0)
++port->icount.buf_overrun;

/*
* Overrun is special. Since it's reported immediately,
* it doesn't affect the current character.
*/
if (status & ~port->ignore_status_mask & overrun)
if (tty_insert_flip_char(tport, 0, TTY_OVERRUN) == 0)
++port->icount.buf_overrun;
}
EXPORT_SYMBOL_GPL(uart_insert_char);