硬件接口与寄存器:TRNG 模块的寄存器映射详解
搞驱动开发,说白了就是跟寄存器打交道。TRNG 模块也不例外。
你拿到一份芯片手册,第一件事就是找到 TRNG 的寄存器基地址,然后搞清楚每个偏移量对应什么功能。我刚开始做 TRNG 驱动时,就吃过一次亏——手册上写的是 32 位寄存器,结果我按 16 位去读,数据死活不对。嗯,从那以后我养成了一个习惯:先确认总线宽度,再动手写代码。
寄存器映射概览
TRNG 模块通常挂载在 APB 或 AHB 总线上。基地址由芯片设计决定,比如 0x4000_1000。偏移量从 0x00 开始,依次排列。下面这张表是我从多个项目里总结出来的典型寄存器布局,不同厂商会有些差异,但核心寄存器基本一致。
| 偏移地址 | 寄存器名称 | 读写属性 | 功能描述 |
|---|---|---|---|
| 0x00 | TRNG_CR | R/W | 控制寄存器:使能、复位、模式选择 |
| 0x04 | TRNG_SR | R | 状态寄存器:数据就绪、错误标志、忙标志 |
| 0x08 | TRNG_DR | R | 数据寄存器:读取随机数 |
| 0x0C | TRNG_IMR | R/W | 中断屏蔽寄存器:控制中断触发条件 |
| 0x10 | TRNG_ISR | R/W1C | 中断状态寄存器:读清零或写1清零 |
| 0x14 | TRNG_CFGR | R/W | 配置寄存器:时钟分频、采样周期 |
#define TRNG_CR 0x00。这样代码可读性高,后期移植也方便。
控制寄存器(TRNG_CR)
控制寄存器是 TRNG 模块的「总开关」。你写驱动时,第一步就是操作它。
典型的位域定义如下:
- Bit 0 — RNGEN:随机数发生器使能。写 1 启动,写 0 关闭。我建议在初始化时先写 0 复位,再写 1 使能,避免残留状态影响。
- Bit 1 — RST:软件复位。写 1 复位整个 TRNG 模块,复位后自动清零。我在调试时经常用这个位来恢复模块到已知状态。
- Bit 2 — MODE:模式选择。0 表示连续模式,1 表示单次模式。连续模式下,TRNG 会持续产生随机数;单次模式需要每次手动触发。
- Bit 3 — IE:中断使能。写 1 允许 TRNG 产生中断,写 0 禁用。
避坑指南:我曾经在一个项目里,使能 TRNG 后立即去读数据寄存器,结果读回来全是 0。后来才发现,TRNG 需要一段启动时间才能稳定输出。正确的做法是:使能后等待状态寄存器的「数据就绪」位被置位,再去读数据。
代码示例——初始化控制寄存器:
void trng_init(void)
{
uint32_t base = TRNG_BASE_ADDR;
// 先复位模块
writel(base + TRNG_CR, 0x02);
// 等待复位完成
while (readl(base + TRNG_CR) & 0x02);
// 使能 TRNG,连续模式,开启中断
writel(base + TRNG_CR, 0x09); // RNGEN=1, MODE=0, IE=1
}
状态寄存器(TRNG_SR)
状态寄存器告诉你 TRNG 模块「现在怎么样了」。驱动里大部分时间都在轮询它。
关键位域:
- Bit 0 — DRDY:数据就绪位。1 表示数据寄存器中有有效随机数。这是你最常用的位。
- Bit 1 — ERR:错误标志位。1 表示检测到错误,比如时钟异常或熵源故障。我建议在驱动里加一个错误计数器,连续出错就上报。
- Bit 2 — BUSY:忙标志位。1 表示 TRNG 正在生成随机数。此时不能写入控制寄存器。
你想想看,如果 DRDY 一直为 0,说明 TRNG 可能没工作。这时候就要检查时钟和使能位了。
代码示例——轮询等待数据就绪:
int trng_wait_for_data(uint32_t timeout_us)
{
uint32_t base = TRNG_BASE_ADDR;
uint32_t timeout = timeout_us;
while (timeout--) {
if (readl(base + TRNG_SR) & 0x01) {
return 0; // 数据就绪
}
udelay(1);
}
return -1; // 超时
}
数据寄存器(TRNG_DR)
数据寄存器是最终产出随机数的地方。每次读取,TRNG 都会返回一个新的 32 位随机数。
读取时要注意几点:
- 必须先确认 DRDY 为 1,再读 DR。否则读到的可能是旧数据或无效数据。
- 读 DR 后,DRDY 会自动清零。硬件就是这么设计的。
- 有些芯片的 DR 是 32 位,有些是 64 位。我遇到过一款芯片,手册写 32 位,实际读出来只有低 16 位有变化。后来确认是勘误——嗯,芯片手册也有坑。
代码示例——读取随机数:
uint32_t trng_get_random(void)
{
uint32_t base = TRNG_BASE_ADDR;
if (trng_wait_for_data(1000) != 0) {
// 超时处理
return 0xFFFFFFFF;
}
return readl(base + TRNG_DR);
}
中断相关寄存器
轮询方式简单,但浪费 CPU。实际产品里,我更喜欢用中断方式。
中断屏蔽寄存器(TRNG_IMR)控制哪些事件能触发中断:
- Bit 0 — DRDY_IE:数据就绪中断使能
- Bit 1 — ERR_IE:错误中断使能
中断状态寄存器(TRNG_ISR)记录中断事件。读它就能知道是数据就绪还是错误触发了中断。注意,这个寄存器通常是 W1C(写 1 清零),处理完中断后要手动清除标志位。
配置寄存器(TRNG_CFGR)用来调整采样参数。比如时钟分频系数、采样窗口长度。这些参数直接影响随机数的质量和生成速度。我一般会留一个调试接口,方便在测试阶段调整。
寄存器操作流程图
下面这张图是我画的一个典型 TRNG 驱动流程,从初始化到读取随机数,再到中断处理。你写驱动时可以照着这个思路来。
寄存器操作的注意事项
最后,我总结几个实际项目中踩过的坑:
- 字节序问题:有些芯片的 TRNG 寄存器是小端,有些是大端。如果你的驱动跑在不同平台上,记得用
readl/writel这类接口,它们会帮你处理字节序。 - 时钟门控:TRNG 模块的时钟可能被门控了。我遇到过驱动写寄存器没反应,查了半天发现是时钟没开。初始化前先确认时钟树配置。
- 多核访问:如果 TRNG 被多个核共享,需要加锁保护。否则两个核同时读 DR,数据可能乱掉。
- 电源管理:系统休眠时,TRNG 寄存器内容可能丢失。唤醒后要重新初始化。
嗯,寄存器这部分就讲到这里。你把这些寄存器操作搞清楚了,TRNG 驱动的基本骨架就有了。下一层就是怎么把这些寄存器操作封装成可靠的 API,以及怎么处理各种边界情况。