[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驱动模型的两大核心思想:数据驱动 和抽象分层 。
数据驱动 :
结构体的大部分成员都是数据 ,而非代码。例如iobase, membase, irq, uartclk, fifosize等。驱动程序通过填充这些数据成员,来“告知”通用的串行核心层这个特定硬件的属性。
标志位 (flags, status) : 结构体包含了大量的标志位(upf_t, upstat_t)。这些标志位以一种紧凑、高效的方式记录了端口的配置和状态(例如,UPF_DEAD表示端口不可用,UPSTAT_CTS_ENABLE表示CTS流控已启用)。通用代码可以通过检查这些标志来快速做出决策。
抽象分层与回调机制 :
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结构体就是将这两者“粘合”在一起的实例对象。
资源与状态的集合 :
硬件资源 : 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 { spinlock_t lock; unsigned long iobase; unsigned char __iomem *membase; void (*set_termios)(struct uart_port *, struct ktermios *new, const struct ktermios *old); const struct uart_ops *ops ; unsigned int irq; unsigned long irqflags; unsigned int uartclk; unsigned int fifosize; unsigned char regshift; enum uart_iotype iotype ; unsigned int read_status_mask; unsigned int ignore_status_mask; struct uart_state *state ; struct uart_icount icount ; struct console *cons ; upf_t flags; #define UPF_DEAD ((__force upf_t) BIT_ULL(30)) upstat_t status; #define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) bool hw_stopped; unsigned int mctrl; unsigned int type; unsigned int line; unsigned int minor; resource_size_t mapbase; struct device *dev ; struct serial_port_device *port_dev ; unsigned char suspended; const char *name; struct attribute_group *attr_group ; const struct attribute_group **tty_groups ; struct serial_rs485 rs485 ; struct serial_rs485 rs485_supported ; struct gpio_desc *rs485_term_gpio ; void *private_data; };
uart_iotype, UPF_* & UPSTAT_*: UART端口的I/O类型、配置与状态标志本代码片段是struct uart_port定义的一部分,其核心功能是定义三个关键的组成部分:
enum uart_iotype : 一个枚举类型,用于描述访问UART硬件寄存器的不同方式 。
UPF_* 宏 : 一系列宏,定义了uart_port->flags成员的各个比特位。这些标志主要用于描述端口的静态配置、能力和驱动行为 。
UPSTAT_* 宏 : 一系列宏,定义了uart_port->status成员的各个比特位。这些标志主要用于描述端口的动态内部状态 ,特别是与流控相关的状态。
实现原理分析 这些定义是uart_port作为“中央档案室”的具体体现。它们通过枚举和位掩码,将硬件的多样性和驱动的复杂状态,以一种标准化的、节省空间的方式记录下来,供serial_core和驱动本身查询和使用。
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,并根据其值调用正确的底层访问函数,从而实现了硬件访问的抽象。
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。
UPSTAT_* 标志 (upstat_t status) :
职责 : 记录端口的动态内部状态 ,这些状态可能会在运行时根据termios的设置而改变。
流控核心 : 这些标志主要与流控逻辑相关。
UPSTAT_CTS_ENABLE, UPSTAT_DCD_ENABLE: 根据termios中的CRTSCTS和CLOCAL标志来设置,表示驱动是否应该关心 CTS和DCD信号线的变化。
UPSTAT_AUTOCTS, UPSTAT_AUTORTS: 表示驱动是否应该根据termios的CRTSCTS标志来自动处理 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 { UPIO_UNKNOWN = -1 , UPIO_PORT = SERIAL_IO_PORT, UPIO_HUB6 = SERIAL_IO_HUB6, UPIO_MEM = SERIAL_IO_MEM, UPIO_MEM32 = SERIAL_IO_MEM32, UPIO_AU = SERIAL_IO_AU, UPIO_TSI = SERIAL_IO_TSI, UPIO_MEM32BE = SERIAL_IO_MEM32BE, UPIO_MEM16 = SERIAL_IO_MEM16, }; #ifdef CONFIG_HAS_IOPORT #define UPF_FOURPORT ((__force upf_t) ASYNC_FOURPORT ) #else #define UPF_FOURPORT 0 #endif #define UPF_SAK ((__force upf_t) ASYNC_SAK ) #define UPF_SPD_HI ((__force upf_t) ASYNC_SPD_HI ) #define UPF_SPD_VHI ((__force upf_t) ASYNC_SPD_VHI ) #define UPF_SPD_CUST ((__force upf_t) ASYNC_SPD_CUST ) #define UPF_SPD_WARP ((__force upf_t) ASYNC_SPD_WARP ) #define UPF_SPD_MASK ((__force upf_t) ASYNC_SPD_MASK ) #define UPF_SKIP_TEST ((__force upf_t) ASYNC_SKIP_TEST ) #define UPF_AUTO_IRQ ((__force upf_t) ASYNC_AUTO_IRQ ) #define UPF_HARDPPS_CD ((__force upf_t) ASYNC_HARDPPS_CD ) #define UPF_SPD_SHI ((__force upf_t) ASYNC_SPD_SHI ) #define UPF_LOW_LATENCY ((__force upf_t) ASYNC_LOW_LATENCY ) #define UPF_BUGGY_UART ((__force upf_t) ASYNC_BUGGY_UART ) #define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER ) #define UPF_NO_THRE_TEST ((__force upf_t) BIT_ULL(19)) #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)) #define UPF_BUG_THRE ((__force upf_t) BIT_ULL(26)) #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)) #define UPF_FULL_PROBE ((__force upf_t) BIT_ULL(32)) upstat_t status; #define UPSTAT_CTS_ENABLE ((__force upstat_t) (1 << 0)) #define UPSTAT_DCD_ENABLE ((__force upstat_t) (1 << 1)) #define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2)) #define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3)) #define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4)) #define UPSTAT_SYNC_FIFO ((__force upstat_t) (1 << 5))
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),会被赋予不同的高级含义。
什么是Break信号?
在串行通信中,Break不是一个字符。它是一个特殊信号 ,表现为数据线(RX)被拉低(逻辑0)的时间,远超于一个正常数据帧的长度(例如,持续超过10个比特位的时间)。
它通常被用作一种“带外”的信令,用于复位、中断或触发特殊功能。
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_port的flags是否被用户空间配置为支持SAK。
do_SAK(state->port.tty): 如果支持,则调用do_SAK来处理。
延迟处理 (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 static inline int uart_handle_break (struct uart_port *port) { struct uart_state *state = port->state; if (port->handle_break) port->handle_break(port); #ifdef CONFIG_MAGIC_SYSRQ_SERIAL if (port->has_sysrq && uart_console(port)) { if (!port->sysrq) { port->sysrq = jiffies + SYSRQ_TIMEOUT; return 1 ; } } #endif if (port->flags & UPF_SAK) do_SAK(state->port.tty); return 0 ; } void do_SAK (struct tty_struct *tty) { if (!tty) return ; schedule_work(&tty->SAK_work); } EXPORT_SYMBOL(do_SAK);
drivers/tty/serial/serial_core.c 本代码片段展示了Linux内核中通用串口驱动层(UART core)对单个串口设备(uart_port)进行电源管理(挂起与恢复)和初始化配置的核心逻辑。它定义了uart_suspend_port和uart_resume_port两个关键函数,用于系统进入低功耗状态和从低功耗状态唤醒时,对串口硬件进行相应的状态转换。同时,uart_configure_port函数负责在驱动探测阶段对串口硬件进行初始化和资源配置。
实现原理分析 这段代码是设备驱动模型中电源管理(Power Management)和设备初始化流程的典型实现,它位于硬件无关的通用层,通过函数指针回调(struct uart_ops)来调用底层硬件相关的驱动代码。
挂起流程 (uart_suspend_port) :
唤醒源检查 : 首先,函数通过device_find_child和serial_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。
恢复流程 (uart_resume_port) :
该流程与挂起流程严格对应。首先同样检查唤醒源状态,如果设备之前被用于唤醒,则调用disable_irq_wake关闭中断的唤醒功能。
控制台恢复 : 如果是控制台,会根据console_suspend_enabled标志位来决定是完整恢复还是仅重新开启接收。它会重新应用终端设置(波特率、数据位等)。
标准恢复 : 对于完全挂起的端口,它会执行与挂起相反的开启序列。调用ops->startup()执行硬件相关的初始化,然后重新配置线路设置、调制解调器控制线(set_mctrl),并最终开启发送(start_tx),将TTY端口标记为已初始化。
配置流程 (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 { struct uart_port *port ; struct uart_driver *driver ; }; 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; dev_t devt = MKDEV(tty_drv->major, tty_drv->minor_start) + match->port->line; 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); } 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; } } 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}; guard(mutex)(&port->mutex); tty_dev = device_find_child(&uport->port_dev->dev, &match, serial_match_port); 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 ; if (tty_port_initialized(port)) { const struct uart_ops *ops = uport->ops; int tries; unsigned int mctrl; tty_port_set_suspended(port, true ); tty_port_set_initialized(port, false ); scoped_guard(uart_port_lock_irq, uport) { ops->stop_tx(uport); if (!(uport->rs485.flags & SER_RS485_ENABLED)) ops->set_mctrl(uport, 0 ); mctrl = uport->mctrl; uport->mctrl = 0 ; ops->stop_rx(uport); } for (tries = 3 ; !ops->tx_empty(uport) && tries; tries--) msleep(10 ); if (!tries) dev_err(uport->dev, "%s: 无法清空发送器\n" , uport->name); ops->shutdown(uport); uport->mctrl = mctrl; } if (uart_console(uport)) console_suspend(uport->cons); uart_change_pm(state, UART_PM_STATE_OFF); return 0 ; } EXPORT_SYMBOL(uart_suspend_port); 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 ; guard(mutex)(&port->mutex); 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)); termios.c_cflag = uport->cons->cflag; termios.c_ispeed = uport->cons->ispeed; termios.c_ospeed = uport->cons->ospeed; 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); } if (tty_port_suspended(port)) { const struct uart_ops *ops = uport->ops; int ret; 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; 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_port_set_initialized(port, true ); } else { uart_shutdown(tty, state); } } tty_port_set_suspended(port, false ); } return 0 ; } EXPORT_SYMBOL(uart_resume_port); static inline void uart_report_port (struct uart_driver *drv, struct uart_port *port) { char address[64 ]; 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 ); } static void uart_configure_port (struct uart_driver *drv, struct uart_state *state, struct uart_port *port) { unsigned int flags; if (!port->iobase && !port->mapbase && !port->membase) return ; flags = 0 ; 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(); 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(); uart_change_pm(state, UART_PM_STATE_ON); 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函数指针来操作具体硬件,实现了硬件无关性。
发送启停 (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来减少引用计数,允许设备在一段时间没有活动后自动进入低功耗状态。
调制解调器控制 (uart_update_mctrl) :
这是一个通用的、原子的调制解调器控制线(Modem Control Register, MCR)更新函数。它使用set和clear两个位掩码来精确地置位或清零mctrl状态变量中的特定位。
为了保证原子性,整个操作在uart_port_lock_irqsave锁的保护下进行,这在单核系统上会禁用本地中断。
它包含一个重要的优化:只有在mctrl的值确实发生改变时,才会调用ops->set_mctrl去操作硬件寄存器,避免了不必要的硬件访问。
uart_port_dtr_rts等函数是在此基础上为特定信号(DTR/RTS)提供的便捷封装。
应用终端设置 (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 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 ; } 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; err = pm_runtime_get(&port_dev->dev); if (err < 0 && err != -EINPROGRESS) { pm_runtime_put_noidle(&port_dev->dev); return ; } 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); } 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); } 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; port->mctrl = (old & ~clear) | set ; if (old != port->mctrl && !(port->rs485.flags & SER_RS485_ENABLED)) port->ops->set_mctrl(port, port->mctrl); } #define uart_set_mctrl(port, set) uart_update_mctrl(port, set, 0) #define uart_clear_mctrl(port, clear) uart_update_mctrl(port, 0, clear) 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); } 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; uport->ops->set_termios(uport, termios, old_termios); guard(uart_port_lock_irq)(uport); if (termios->c_cflag & CRTSCTS) uport->status |= UPSTAT_CTS_ENABLE; else uport->status &= ~UPSTAT_CTS_ENABLE; if (termios->c_cflag & CLOCAL) uport->status &= ~UPSTAT_DCD_ENABLE; else uport->status |= UPSTAT_DCD_ENABLE; 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控制)委托给底层驱动,实现了高度的抽象和代码复用。
用户接口 (uart_set_rs485_config) :
这是从TTY层进入RS-485配置的入口点,通常由TIOCSRS485 ioctl调用。
它首先通过copy_from_user从用户空间安全地拷贝配置数据,防止恶意指针。
在将配置应用到硬件之前,它执行了一个严格的“校验-修正”流程。
数据校验 (uart_check_rs485_flags) :
此函数负责检查用户提供的flags和address字段是否逻辑自洽且被驱动支持。
它首先忽略内核自己定义的、向后兼容的旧标志位(SER_RS485_LEGACY_FLAGS),然后检查是否有任何用户设置的、但底层驱动声称不支持(port->rs485_supported.flags)的标志位。
它还会检查逻辑错误,例如:请求地址过滤功能却没有设置地址模式,或者在未使能地址过滤的情况下提供了地址值。任何不匹配都会导致配置失败并返回-EINVAL。
数据修正/净化 (uart_sanitize_serial_rs485) :
这是增强驱动健壮性的关键。对于不合理但可修正的用户输入,它不会直接拒绝,而是将其修正为“理智的”默认值。
RTS逻辑修正 : RS-485的发送器使能(通常通过RTS信号)逻辑必须是明确的(发送前拉高/发送后拉低)。如果用户同时设置或同时未设置这两个互斥的标志,此函数会根据驱动的能力,强制选择一个默认的有效模式,并打印警告。
延迟值修正 (uart_sanitize_serial_rs485_delays) : 它检查驱动是否支持发送前/后RTS延迟,如果不支持,则强制将延迟设为0。如果支持但用户设置的值超出了最大范围(RS485_MAX_RTS_DELAY),则将其钳位到最大值。
填充区清理 : memset用于清零结构体中的padding字段,这是一种良好的安全实践,可以防止内核栈上的垃圾数据通过ioctl泄露到用户空间。
硬件应用 (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 #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) 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 ; } static void uart_sanitize_serial_rs485_delays (struct uart_port *port, struct serial_rs485 *rs485) { 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); } 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); } } static void uart_sanitize_serial_rs485 (struct uart_port *port, struct serial_rs485 *rs485) { u32 supported_flags = port->rs485_supported.flags; if (!(rs485->flags & SER_RS485_ENABLED)) { memset (rs485, 0 , sizeof (*rs485)); return ; } 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; 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)); } static void uart_set_rs485_termination (struct uart_port *port, const struct serial_rs485 *rs485) { if (!(rs485->flags & SER_RS485_ENABLED)) return ; gpiod_set_value_cansleep(port->rs485_term_gpio, !!(rs485->flags & SER_RS485_TERMINATE_BUS)); } static void uart_set_rs485_rx_during_tx (struct uart_port *port, const struct serial_rs485 *rs485) { if (!(rs485->flags & SER_RS485_ENABLED)) return ; gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio, !!(rs485->flags & SER_RS485_RX_DURING_TX)); } 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 ; 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) { 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; } static int uart_get_rs485_config (struct uart_port *port, struct serial_rs485 __user *rs485) { struct serial_rs485 aux ; scoped_guard(uart_port_lock_irqsave, port) aux = port->rs485; if (copy_to_user(rs485, &aux, sizeof (aux))) return -EFAULT; return 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; 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; if (!(rs485.flags & SER_RS485_ENABLED)) port->ops->set_mctrl(port, port->mctrl); } } if (ret) { 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=参数进行配置。这些函数是驱动与内核控制台框架之间的重要“粘合剂”。
实现原理分析 这些函数体现了内核驱动框架中“分离通用逻辑与特定实现”的设计哲学。
通用写入逻辑 (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),就可以使用这个通用的写入逻辑。
启动参数解析 (uart_parse_earlycon & uart_parse_options) :
职责 : 这两个函数是纯粹的字符串解析器,用于解码内核启动命令行。
uart_parse_earlycon: 用于解析earlycon参数,它负责识别IO类型(MMIO, IO等)和硬件的物理基地址。这对于在设备模型(device model)和驱动探测(probe)完成之前就建立一个“早期控制台”至关重要。
uart_parse_options: 负责解析更通用的串口参数字符串,如115200n8。它按照固定的位置和格式(波特率、校验位、数据位、流控)来提取信息。
这些函数将晦涩的命令行字符串,转换成驱动可以理解的、结构化的C语言变量。
应用配置 (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) 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++) { if (*s == '\n' ) putchar (port, '\r' ); putchar (port, *s); } } EXPORT_SYMBOL_GPL(uart_console_write); int uart_parse_earlycon (char *p, enum uart_iotype *iotype, resource_size_t *addr, char **options) { 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; } *addr = simple_strtoull(p, NULL , 0 ); p = strchr (p, ',' ); if (p) p++; *options = p; return 0 ; } EXPORT_SYMBOL_GPL(uart_parse_earlycon); 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); 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.c_cflag |= CREAD | HUPCL | CLOCAL; tty_termios_encode_baud_rate(&termios, baud, baud); if (bits == 7 ) termios.c_cflag |= CS7; else termios.c_cflag |= CS8; switch (parity) { case 'o' : case 'O' : termios.c_cflag |= PARODD; fallthrough; case 'e' : case 'E' : termios.c_cflag |= PARENB; break ; } if (flow == 'r' ) termios.c_cflag |= CRTSCTS; port->mctrl |= TIOCM_DTR; port->ops->set_termios(port, &termios, &dummy); 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
好的,我将对您提供的这段关于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_driver和uart_port之上,又增加了一层serial_ctrl_device和serial_port_device,以更好地管理多端口串口卡或集成了多个UART的SoC。
分层抽象 :
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。
注册流程 (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_device(port->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标志,正式宣告该端口已准备就绪,可以对外服务。
注销流程 (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 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); 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); 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); } 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); 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; 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; } 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; serial_core_remove_one_port(drv, 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)。
实现原理分析 这些函数共同构建了一个两级的设备模型,用于精确地表示物理硬件的拓扑结构,尤其适用于多端口串口设备。
设备树导航 (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类型。这体现了设备模型中清晰的父子层级关系。
控制器查找 (serial_core_ctrl_find) :
职责 : 这是实现控制器共享 的关键。在注册一个新的uart_port时,必须先检查是否已经有一个代表相同物理硬件的控制器设备存在。
实现 : 它遍历一个uart_driver下的所有已注册端口。
匹配逻辑 : 它使用两个条件来判断是否为同一个物理控制器:
state->uart_port->dev == phys_dev: port->dev通常指向一个platform_device或pci_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锁,这是安全遍历驱动端口列表的前提。
控制器创建 (serial_core_ctrl_device_add) :
职责 : 当serial_core_ctrl_find返回NULL时,调用此函数来创建一个新的控制器设备。
实现 : 它是一个对底层serial_base_ctrl_add函数的简单封装,传递了uart_port和它所依附的物理设备port->dev。
端口创建 (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) static struct serial_ctrl_device *serial_core_get_ctrl_dev (struct serial_port_device *port_dev) { struct device *dev = &port_dev->dev; return to_serial_base_ctrl_device(dev->parent); } 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 ; 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 ; } static struct serial_ctrl_device *serial_core_ctrl_device_add (struct uart_port *port) { return serial_base_ctrl_add(port, port->dev); } static int serial_core_port_device_add (struct serial_ctrl_device *ctrl_dev, struct uart_port *port) { struct serial_port_device *port_dev ; port_dev = serial_base_port_add(port, ctrl_dev); if (IS_ERR(port_dev)) return PTR_ERR(port_dev); 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_core和tty)向用户空间暴露接口的标准流程。
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: 这个结构体将所有定义好的属性文件组织成一个属性组 。
添加端口流程 (serial_core_add_one_port) :
上下文关联 :
它首先找到uart_driver中与uport->line对应的uart_state,这是一个长期存在的状态容器。
state->uart_port = uport; uport->state = state;: 建立uart_state和uart_port之间的双向链接。
初始化 :
uart_port_spin_lock_init(uport): 确保端口的自旋锁被初始化。
uart_configure_port(drv, state, uport): 调用此函数对端口进行最后的硬件配置,包括探测UART类型、申请资源、上电并禁用调制解demodulator控制线等。
TTY设备注册 :
tty_port_register_device_attr_serdev: 这是最核心的调用 。它请求TTY核心层执行以下操作:
分配并注册一个tty_device实例。
在/dev目录下创建一个对应的设备文件(例如,ttySTM0)。
在sysfs中为这个TTY设备创建一个目录。
将tty_dev_attr_group(以及驱动可能提供的自定义属性组)中的所有属性文件创建在这个sysfs目录下。
激活端口 : uport->flags &= ~UPF_DEAD; 清除UPF_DEAD标志,标志着端口现在是完全初始化并准备就绪的,可以安全地被其他子系统(如运行时PM)使用了。
移除端口流程 (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 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 }; static const struct attribute_group tty_dev_attr_group = { .attrs = tty_dev_attrs, }; 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 ; state = drv->state + uport->line; port = &state->port; guard(mutex)(&port->mutex); if (state->uart_port) return -EINVAL; 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); uport->tty_groups[0 ] = &tty_dev_attr_group; uport->flags &= ~UPF_DEAD; 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 { uport->flags |= UPF_DEAD; dev_err(uport->dev, "无法在line %u上注册tty设备\n" , uport->line); } return 0 ; } 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_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); } bool uart_match_port (const struct uart_port *port1, const struct uart_port *port2) { if (port1->iotype != port2->iotype) return false ; 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)**的处理。它确保无论用户提供多么不合理的配置,系统总能协商出一个可用的波特率。
历史兼容性:神奇的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。
健壮的多级回退逻辑 :
函数在一个for循环中最多尝试两次,以找到一个有效的波特率。
第1级:尝试新配置 : 第一次循环(try = 0),它使用用户传入的termios,并应用上述的38400“怪癖”逻辑。如果计算出的baud在硬件支持的[min, max]范围内,就直接返回这个值。
第2级:尝试旧配置 : 如果第一次尝试失败(baud超出范围),并且调用者提供了old(旧的)termios结构,函数会:
从old termios中提取出之前的波特率。
修改 当前正在处理的termios,将它的波特率设置回旧的值。
continue到下一次循环,用这个“恢复后”的termios再试一次。
第3级:裁剪到最近值 : 如果没有old termios可供回退,作为最后的手段,函数会将波特率裁剪(clip)到最接近的有效边界(min或max),并 修改 termios以反映这个最终的决定。
特殊B0处理 :
B0在termios中是一个特殊的速率,表示“挂断线路”。
函数在内部会临时将其当作一个普通速率(如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 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; 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 ; } for (try = 0 ; try < 2 ; try++) { baud = tty_termios_baud_rate(termios); if (try == 0 && baud == 38400 ) baud = altbaud; if (baud == 0 ) { hung_up = 1 ; baud = 9600 ; } if (baud >= min && baud <= max) return baud; termios->c_cflag &= ~CBAUD; if (old) { baud = tty_termios_baud_rate(old); if (!hung_up) tty_termios_encode_baud_rate(termios, baud, baud); old = NULL ; 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 ); } } 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 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则是第二步,它检查并“触发”这个状态。
调用时机 : 此函数在串口驱动的接收中断处理函数中被调用(例如,在stm32_usart_receive_chars_pio中),时机是在从硬件数据寄存器(RDR)读取一个字符ch之后,但在将其递交给uart_insert_char之前。
状态机检查 (if (!port->sysrq)) :
这是函数的快速路径 。port->sysrq是一个状态变量,它只有在uart_handle_break检测到Break信号后才会被设置为一个非零的时间戳。
如果port->sysrq为0,意味着当前不 处于一个SysRq序列中。函数会立即返回0,告诉ISR:“这个字符是普通数据,请继续处理。” 对于99.99%的接收字符,都会走这个路径。
合法性验证 (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_break将port->sysrq设置为jiffies + SYSRQ_TIMEOUT。这个检查确保了命令字符是在Break信号后的一个短暂时间窗口(SYSRQ_TIMEOUT)内到达的。如果用户发送Break后等待太久才发送命令字符,则序列无效。
分发与消耗 :
主路径 (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 static inline int uart_prepare_sysrq_char (struct uart_port *port, u8 ch) { if (!port->sysrq) return 0 ; if (ch && time_before(jiffies, port->sysrq)) { if (sysrq_mask()) { port->sysrq_ch = ch; port->sysrq = 0 ; return 1 ; } if (uart_try_toggle_sysrq(port, ch)) return 1 ; } port->sysrq = 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 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; 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);