tg-rcore-tutorial-uart1 0.1.0-preview.1

NS16550A UART driver for S-Mode in rCore tutorial.
Documentation
# NS16550A UART 串口驱动库

`tg-rcore-tutorial-uart` 是一个用于 RISC-V S-Mode 的 NS16550A 兼容串口驱动库,提供内存映射 I/O(MMIO)方式的字符输入输出,适用于裸机环境和操作系统内核。

通过本驱动库的学习和使用,你将掌握:

- UART 串口硬件的工作原理和通信协议
- 内存映射 I/O(MMIO)的访问机制与 `volatile` 语义的重要性
- NS16550A 寄存器的定义与初始化流程
- 轮询式字符输入输出的实现方法
- 如何为裸机环境编写硬件设备驱动

## 项目结构

```
tg-rcore-tutorial-uart/
├── Cargo.toml          # 项目配置与依赖
├── README.md           # 本文档
└── src/
    ├── lib.rs          # 库入口,模块导出
    └── uart.rs         # UART 驱动实现:寄存器定义、初始化、字符 I/O
```

<a id="source-nav"></a>

## 源码阅读导航索引

[返回根文档导航总表](../../README.md#chapters-source-nav-map)

建议把源码阅读聚焦在一个文件:`src/uart.rs`。

| 阅读顺序 | 位置 | 重点问题 |
|---|---|---|
| 1 | `Uart::init()` | UART 初始化流程如何配置波特率、数据位和 FIFO? |
| 2 | `Uart::put_char()` | 轮询式输出如何通过 LSR 寄存器等待发送器空闲? |
| 3 | `Uart::try_get_char()` | 非阻塞输入如何检测接收器就绪标志? |
| 4 | `read_reg` / `write_reg` | MMIO 访问为何必须使用 `volatile` 操作? |

配套建议:结合 QEMU virt 机器的 UART 硬件文档,理解寄存器映射与功能。

## DoD 验收标准(驱动库完成判据)

- [ ] 能在依赖本驱动的程序(如 `tg-rcore-tutorial-ch1-uart`)中执行 `cargo run`,看到串口输出
- [ ] 能解释 MMIO 与端口 I/O 的区别,以及 `volatile` 在设备驱动中的作用
- [ ] 能说明 UART 初始化序列中每个寄存器写入的目的(IER、LCR、FCR)
- [ ] 能描述轮询输出与中断驱动输出的优缺点

## 概念-源码-测试三联表

| 核心概念 | 源码入口 | 自测方式(命令/现象) |
|---|---|---|
| MMIO 访问 | `src/uart.rs``read_reg` / `write_reg` | 输出稳定,无编译器优化问题 |
| 寄存器定义 | `src/uart.rs` 的常量定义(`UART0``RHR``THR` 等) | 可对照 NS16550A 数据手册验证偏移量 |
| 轮询输出 | `Uart::put_char` 等待 `LSR_TX_IDLE` | 每个字符可靠输出,无丢失 |
| 非阻塞输入 | `Uart::try_get_char` 检查 `LSR_RX_READY` | 有输入时返回 `Some(c)`,无输入时返回 `None` |

## 一、环境准备

### 1.1 安装 Rust 工具链

本项目使用 Rust 语言编写,需要通过 rustup 安装 Rust 工具链。

**Linux / macOS / WSL:**

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
```

**Windows:**

从 [https://rustup.rs](https://rustup.rs) 下载并运行 `rustup-init.exe`。

验证安装:

```bash
rustc --version    # 应显示 rustc 1.xx.x
cargo --version    # 应显示 cargo 1.xx.x
```

### 1.2 添加 RISC-V 64 编译目标

由于驱动可能用于 RISC-V 64 裸机平台,建议添加对应的编译目标:

```bash
rustup target add riscv64gc-unknown-none-elf
```

### 1.3 安装 QEMU 模拟器(可选)

如需在 QEMU 中测试驱动,需要安装 `qemu-system-riscv64`(建议版本 >= 7.0)。

**Ubuntu / Debian:**

```bash
sudo apt update
sudo apt install qemu-system-misc
```

**macOS(Homebrew):**

```bash
brew install qemu
```

**验证安装:**

```bash
qemu-system-riscv64 --version
```

## 二、使用方式

### 2.1 添加依赖

在 `Cargo.toml` 中添加:

```toml
[dependencies]
tg-rcore-tutorial-uart = { path = "../tg-rcore-tutorial-uart", version = "0.1.0-preview.1" }
```

### 2.2 基本使用

```rust
use tg_rcore_tutorial_uart::Uart;

// 初始化 UART 硬件
let _ = Uart::init();

// 输出字符串
Uart::put_str("Hello from UART!\n");

// 输出单个字符
Uart::put_char(b'A');

// 尝试读取字符(非阻塞)
if let Some(c) = Uart::try_get_char() {
    Uart::put_char(c); // 回显
}
```

### 2.3 在 panic 处理中使用同步输出

```rust
use tg_rcore_tutorial_uart::Uart;

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    // 使用同步输出确保 panic 信息能发出
    let _ = Uart::put_char_sync(b'P');
    let _ = Uart::put_char_sync(b'A');
    let _ = Uart::put_char_sync(b'N');
    let _ = Uart::put_char_sync(b'I');
    let _ = Uart::put_char_sync(b'C');
    loop {}
}
```

## 三、核心概念

### 3.1 内存映射 I/O(MMIO)

MMIO 是一种将设备寄存器映射到内存地址空间的硬件设计。CPU 通过普通的内存读写指令(如 `ld`、`sd`)访问这些地址,实际上是在与设备寄存器交互。

**与端口 I/O 的区别:**

- **端口 I/O**:使用专门的 `in`/`out` 指令,地址空间独立
- **MMIO**:使用内存访问指令,地址空间与物理内存统一

RISC-V 架构主要采用 MMIO 方式,设备寄存器被映射到特定的物理地址区间(如 `0x10000000`)。

### 3.2 volatile 语义

编译器在进行优化时,可能会认为对同一内存地址的多次读取是冗余的,从而合并或删除某些访问。对于设备寄存器,这种优化会导致错误,因为寄存器的值可能随时被硬件改变。

`volatile` 关键字(在 Rust 中为 `read_volatile` / `write_volatile`)告诉编译器:
- 每次访问都必须执行,不可优化掉
- 访问顺序必须严格按代码顺序进行
- 不假设该地址的内容在两次访问之间保持不变

### 3.3 UART 通信协议

UART 是一种异步串行通信协议,包含以下要素:

- **波特率**:每秒传输的符号数(如 38400 bps)
- **数据位**:每个字符的位数(通常为 8 位)
- **停止位**:字符结束的标志(通常为 1 位)
- **奇偶校验位**:错误检测位(可选)

NS16550A 是 PC 兼容的 UART 芯片,支持 FIFO 缓冲和中断,广泛应用于虚拟机和嵌入式系统。

## 四、代码解读

### 4.1 寄存器定义

```rust
/// QEMU virt 机器 UART0 基地址
const UART0: usize = 0x1000_0000;

/// 寄存器偏移量(某些寄存器读写时有不同含义)
const RHR: usize = 0; // 接收保持寄存器(只读)
const THR: usize = 0; // 发送保持寄存器(只写)
const IER: usize = 1; // 中断使能寄存器
const FCR: usize = 2; // FIFO 控制寄存器(只写)
const LCR: usize = 3; // 线路控制寄存器
const LSR: usize = 5; // 线路状态寄存器
```

### 4.2 初始化序列

```rust
pub fn init() -> Result<()> {
    unsafe { write_reg(IER, 0) };          // 禁用中断
    unsafe { write_reg(LCR, lcr::BAUD_LATCH) }; // 进入波特率设置模式
    unsafe { write_reg(0, 0x03) };         // 波特率除数低位(38400 bps)
    unsafe { write_reg(1, 0x00) };         // 波特率除数高位
    unsafe { write_reg(LCR, lcr::EIGHT_BITS) }; // 8 数据位,无奇偶校验
    unsafe { write_reg(FCR, fcr::FIFO_ENABLE | fcr::FIFO_CLEAR) }; // 启用并清空 FIFO
    Ok(())
}
```

### 4.3 字符输出

```rust
pub fn put_char(c: u8) {
    while unsafe { read_reg(LSR) } & lsr::TX_IDLE == 0 {} // 等待发送器空闲
    unsafe { write_reg(THR, c) }; // 写入字符
}
```

### 4.4 非阻塞字符输入

```rust
pub fn try_get_char() -> Option<u8> {
    if unsafe { read_reg(LSR) } & lsr::RX_READY != 0 { // 检查接收器就绪
        Some(unsafe { read_reg(RHR) }) // 读取字符
    } else {
        None
    }
}
```

### 4.5 volatile 访问函数

```rust
unsafe fn read_reg(offset: usize) -> u8 {
    let addr = UART0 + offset;
    unsafe { (addr as *const u8).read_volatile() }
}

unsafe fn write_reg(offset: usize, value: u8) {
    let addr = UART0 + offset;
    unsafe { (addr as *mut u8).write_volatile(value) }
}
```

## 五、驱动设计总结

通过本驱动库的学习和实践,你将掌握硬件设备驱动开发的核心技能:

1. **理解了 MMIO 机制**:设备寄存器通过内存地址映射,使用 `volatile` 访问确保可靠性
2. **熟悉了 UART 硬件**:NS16550A 的寄存器布局、初始化流程和通信协议
3. **实现了轮询式 I/O**:通过状态寄存器轮询实现可靠的字符输入输出
4. **掌握了驱动设计模式**:提供同步/异步接口,支持 panic 场景的可靠输出

这是操作系统设备驱动开发的第一步——在后续章节中,你将在此基础上学习中断驱动、DMA、设备树等更高级的设备管理技术。

## 六、思考题

1. **为什么 `put_char` 中需要轮询 `LSR_TX_IDLE` 标志?** 如果不检查直接写入 THR 寄存器,会发生什么问题?

2. **`volatile` 访问是否足以保证多核环境下的设备访问安全?** 还需要哪些同步机制?

3. **如何将本驱动改为中断驱动模式?** 需要修改哪些部分?中断处理函数如何与驱动交互?

4. **FIFO 的作用是什么?** 启用 FIFO 后,驱动应该如何优化?

## 参考资料

- [NS16550A Datasheet]https://www.nxp.com/docs/en/data-sheet/PC16550D.pdf
- [xv6-riscv uart.c]https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/uart.c
- [rCore-Tutorial 设备驱动章节]https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter9/2device-driver-1.html
- [Rustonomicon: Volatile]https://doc.rust-lang.org/nomicon/volatility.html
- [Memory-mapped I/O in RISC-V]https://five-embeddev.com/quickref/MMIO.html

## Dependencies

| 依赖 | 说明 |
|------|------|
|| 本驱动不依赖其他 crate,是纯硬件抽象层 |

## 附录:NS16550A 寄存器详解

| 寄存器 | 偏移 | 读写 | 说明 |
|--------|------|------|------|
| RHR   | 0    || 接收保持寄存器:存放接收到的字符 |
| THR   | 0    || 发送保持寄存器:写入待发送的字符 |
| IER   | 1    | 读写 | 中断使能寄存器:控制哪些事件产生中断 |
| FCR   | 2    || FIFO 控制寄存器:启用/清空 FIFO,设置触发阈值 |
| LCR   | 3    | 读写 | 线路控制寄存器:设置数据位、停止位、奇偶校验 |
| LSR   | 5    || 线路状态寄存器:反映发送/接收状态 |

**关键标志位:**

- `LSR_RX_READY` (bit 0):接收器有数据可读
- `LSR_TX_IDLE` (bit 5):发送器空闲,可接受新字符
- `LCR_BAUD_LATCH` (bit 7):置 1 时访问波特率除数寄存器
- `LCR_EIGHT_BITS` (bits 1:0 = 11):8 位数据位

---

## License

Licensed under GNU GENERAL PUBLIC LICENSE, Version 3.0.