3. UART 硬件基础

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是 xv6 内核与外部设备(如终端、串口线)进行字符交互的物理层基础。xv6 的 console 驱动直接操作 16550A UART 芯片的寄存器,因此理解其协议、寄存器映射、波特率、数据格式和中断机制是编写正确驱动的必要前提。

3.1 UART 协议简介

UART 是一种异步串行通信协议,它使用一对信号线(TX 发送、RX 接收)实现全双工通信。通信双方不需要共享时钟信号,而是通过预先约定的波特率(Baud Rate)和帧格式来同步数据。

核心特点:
  • 异步:无独立时钟线,依靠起始位和停止位同步。
  • 全双工:发送和接收可同时进行。
  • 帧结构:每个字符被封装在一个帧中,包含起始位、数据位、可选的校验位和停止位。

下图展示了一个典型的 UART 字符帧结构(8 数据位、无校验、1 停止位,即 8N1):

UART 字符帧结构(8N1) 起始位 0 D0 D1 D2 D3 D4 D5 D6 D7 停止位 1 数据位 LSB(D0)先发送,MSB(D7)最后发送 帧之间线路保持高电平(空闲状态)

3.2 16550A UART 芯片寄存器映射

16550A 是 PC 兼容系统中广泛使用的 UART 芯片。在 xv6 运行的 QEMU riscv64 环境中,UART 被映射到内存地址 0x10000000 开始的 I/O 空间。驱动通过读写这些内存地址来操作 UART 芯片。

下表列出了 xv6 驱动最常使用的寄存器及其偏移量(相对于基地址 0x10000000):

偏移量 寄存器名称 DLAB 位状态 读/写 功能描述
0x00 RBR / THR / DLL DLAB=0 读:接收缓冲寄存器
DLAB=0 写:发送保持寄存器
DLAB=1:波特率因子低字节
R/W 读:获取接收到的字符;写:发送字符;DLAB=1 时设置波特率低 8 位
0x01 IER / DLM DLAB=0:中断使能寄存器
DLAB=1:波特率因子高字节
R/W 使能/禁止各类中断;DLAB=1 时设置波特率高 8 位
0x02 IIR / FCR DLAB 无关 R/W 读:中断标识寄存器;写:FIFO 控制寄存器
0x03 LCR DLAB 无关 R/W 线路控制寄存器:设置数据格式、DLAB 位
0x05 LSR DLAB 无关 R 线路状态寄存器:指示发送/接收状态、错误标志
关键寄存器速记:
  • LSR (0x05):驱动通过读 LSR 判断是否可发送(THR 空)或是否有数据可读(DR 位)。
  • THR (0x00, 写):写入一个字节即启动发送。
  • RBR (0x00, 读):读取一个接收到的字节。
  • IER (0x01):使能接收/发送中断,是中断驱动 I/O 的核心。

下图展示了 xv6 驱动与 16550A 寄存器之间的交互关系:

xv6 驱动与 16550A 寄存器交互 xv6 Console 驱动 uartputc() / uartgetc() THR / RBR LSR IER 写/读数据 读状态 配置中断 驱动操作流程 1. 读 LSR 检查 THR 空 2. 写 THR 发送字符 3. 读 LSR 检查 DR 位 4. 读 RBR 接收字符 5. 写 IER 使能中断

3.3 波特率

波特率定义了每秒传输的符号数。对于 UART,一个符号即一个比特。16550A 使用一个可编程的波特率发生器,通过设置除数锁存器(DLL + DLM)来产生所需的波特率。

计算公式如下:

波特率 = 时钟频率 / (16 × 除数因子)

在 QEMU 模拟的 xv6 环境中,UART 时钟频率通常为 1.8432 MHz。下表列出了常用波特率对应的除数因子:

波特率 (bps) 除数因子 (十进制) DLL (低字节) DLM (高字节)
115200 1 0x01 0x00
57600 2 0x02 0x00
38400 3 0x03 0x00
19200 6 0x06 0x00
9600 12 0x0C 0x00
配置注意事项: 设置波特率前,必须先将 LCR 寄存器的 DLAB 位(bit 7)置 1,然后写入 DLL 和 DLM。配置完成后,必须将 DLAB 位清零,才能正常访问 RBR/THR 和 IER 寄存器。

3.4 数据格式

数据格式由 LCR(Line Control Register)配置。LCR 的位定义如下:

位 名称 功能
7 DLAB 除数锁存器访问位:1 允许访问 DLL/DLM,0 访问 RBR/THR/IER
6 Set Break 强制 TX 线路为低电平(发送 break 信号)
5 Stick Parity 固定校验位(与 bit 4 配合)
4 Even Parity Select 0=奇校验,1=偶校验(仅当 bit 3=1 时有效)
3 Parity Enable 1=使能校验位,0=无校验
2 Stop Bits 0=1 个停止位,1=2 个停止位(5 位数据时为 1.5 个)
1-0 Word Length 00=5 位,01=6 位,10=7 位,11=8 位

xv6 使用的典型配置为 8N1(8 数据位、无校验、1 停止位),对应的 LCR 值为 0x03(bit 1-0 = 11,bit 2 = 0,bit 3 = 0)。

// xv6 中初始化 UART 数据格式的代码片段
// 设置 LCR: 8 位数据, 无校验, 1 停止位
// LCR 地址: 基地址 + 0x03
*(volatile uint8 *)(UART_BASE + 0x03) = 0x03;

3.5 中断机制

16550A 支持多种中断源,通过 IER(Interrupt Enable Register)使能,通过 IIR(Interrupt Identification Register)识别中断类型。中断机制允许 xv6 驱动以事件驱动方式处理字符输入输出,避免轮询(polling)带来的 CPU 浪费。

IER 寄存器位定义:

位 中断类型 描述
3 Modem Status 调制解调器状态变化(通常不使用)
2 Receiver Line Status 接收线路错误(溢出、校验错、帧错等)
1 Transmitter Holding Register Empty THR 空,可以发送下一个字符
0 Received Data Available 接收数据就绪(RBR 中有数据)

中断优先级(IIR 标识):

优先级 IIR 值 (bit 3-0) 中断源
最高 0x06 接收线路状态错误
次高 0x04 接收数据就绪
次低 0x02 THR 空
最低 0x00 调制解调器状态变化
xv6 中的中断使用策略:
  • 发送中断:xv6 通常不使用发送中断,而是采用轮询方式检查 LSR 的 THR 空位。因为发送操作由内核主动发起,轮询效率足够。
  • 接收中断:xv6 使能接收中断(IER bit 0 = 1)。当键盘输入字符时,UART 产生中断,驱动在中断处理函数中读取 RBR 并将字符放入输入缓冲区。

下图展示了 xv6 中 UART 接收中断的处理流程:

xv6 UART 接收中断处理流程 外部设备发送字符 UART 接收完成,置位 DR UART 向 CPU 发送中断 驱动读 RBR,写入缓冲区 关键寄存器状态 LSR bit 0 (DR) = 1 IER bit 0 = 1 IIR = 0x04 读 RBR 自动清除 中断标志
中断 vs 轮询: 在 xv6 的 console 驱动中,接收使用中断(被动等待输入),发送使用轮询(主动检查 THR 空)。这种混合策略在保证输入实时性的同时,简化了发送路径的实现。