3. UART 硬件基础
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是 xv6 内核与外部设备(如终端、串口线)进行字符交互的物理层基础。xv6 的 console 驱动直接操作 16550A UART 芯片的寄存器,因此理解其协议、寄存器映射、波特率、数据格式和中断机制是编写正确驱动的必要前提。
3.1 UART 协议简介
UART 是一种异步串行通信协议,它使用一对信号线(TX 发送、RX 接收)实现全双工通信。通信双方不需要共享时钟信号,而是通过预先约定的波特率(Baud Rate)和帧格式来同步数据。
- 异步:无独立时钟线,依靠起始位和停止位同步。
- 全双工:发送和接收可同时进行。
- 帧结构:每个字符被封装在一个帧中,包含起始位、数据位、可选的校验位和停止位。
下图展示了一个典型的 UART 字符帧结构(8 数据位、无校验、1 停止位,即 8N1):
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 寄存器之间的交互关系:
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 |
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 通常不使用发送中断,而是采用轮询方式检查 LSR 的 THR 空位。因为发送操作由内核主动发起,轮询效率足够。
- 接收中断:xv6 使能接收中断(IER bit 0 = 1)。当键盘输入字符时,UART 产生中断,驱动在中断处理函数中读取 RBR 并将字符放入输入缓冲区。
下图展示了 xv6 中 UART 接收中断的处理流程: