4. AES-128硬件加速引擎驱动开发
硬件加速引擎的寄存器模型与数据通路
带 AES 加速器的 MCU 通常把加解密单元做成一个挂在总线上的外设,驱动开发的第一步是弄清它的寄存器接口和内部数据通路。以常见的“控制/状态寄存器 + 密钥寄存器组 + 数据输入/输出寄存器 + 中断标志”结构为例,典型寄存器布局如下(地址为示例,实际以芯片手册为准):
| 寄存器 | 偏移 | 位域含义 |
|---|---|---|
| CR | 0x00 | bit0 EN 使能,bit1 START 启动,bit2 DEC 解密,bit3 IE 中断使能 |
| SR | 0x04 | bit0 BUSY,bit1 DONE,bit2 KEYRDY |
| KEY0..KEY3 | 0x10~0x1C | 128 位密钥,按 32 位小端分 4 次写入 |
| DIN0..DIN3 | 0x20~0x2C | 128 位明文/密文输入 |
| DOUT0..DOUT3 | 0x30~0x3C | 128 位结果输出 |
| ICR | 0x08 | 写 1 清 DONE 中断标志 |
数据通路是:CPU 把密钥写入 KEY 组 → 写 DIN 组 → 置 START → 引擎内部完成 10 轮运算 → 置 DONE 并触发中断 → CPU 读 DOUT 组。密钥只需在会话开始时加载一次,之后每次加解密只搬 16 字节数据,这正是硬件加速相对查表法的核心优势。
驱动实现:寄存器操作与中断处理
下面给出一个可直接移植的驱动骨架。关键点:密钥加载后置 KEYRDY,每次加解密前检查 BUSY,启动后可用轮询或中断方式等待 DONE。示例使用轮询 + 中断双模式,中断服务程序只做最小工作(读结果、清标志),把后续处理留给主循环。
#include <stdint.h>
#include <string.h>
/* 寄存器基址与偏移(示例,按芯片手册修改) */
#define AES_BASE 0x40021000UL
#define AES_CR (*(volatile uint32_t *)(AES_BASE + 0x00))
#define AES_SR (*(volatile uint32_t *)(AES_BASE + 0x04))
#define AES_ICR (*(volatile uint32_t *)(AES_BASE + 0x08))
#define AES_KEY(i) (*(volatile uint32_t *)(AES_BASE + 0x10 + 4*(i)))
#define AES_DIN(i) (*(volatile uint32_t *)(AES_BASE + 0x20 + 4*(i)))
#define AES_DOUT(i) (*(volatile uint32_t *)(AES_BASE + 0x30 + 4*(i)))
#define CR_EN (1u << 0)
#define CR_START (1u << 1)
#define CR_DEC (1u << 2)
#define CR_IE (1u << 3)
#define SR_BUSY (1u << 0)
#define SR_DONE (1u << 1)
#define SR_KEYRDY (1u << 2)
static volatile uint8_t g_done_flag = 0;
static uint8_t g_out[16];
/* 小端写入 128 位数据:buf[0] 对应 DIN0 的最低字节 */
static void aes_write_block(volatile uint32_t *reg, const uint8_t *buf)
{
for (int i = 0; i < 4; i++) {
reg[i] = (uint32_t)buf[4*i]
| ((uint32_t)buf[4*i+1] << 8)
| ((uint32_t)buf[4*i+2] << 16)
| ((uint32_t)buf[4*i+3] << 24);
}
}
static void aes_read_block(volatile uint32_t *reg, uint8_t *buf)
{
for (int i = 0; i < 4; i++) {
uint32_t v = reg[i];
buf[4*i] = (uint8_t)(v);
buf[4*i+1] = (uint8_t)(v >> 8);
buf[4*i+2] = (uint8_t)(v >> 16);
buf[4*i+3] = (uint8_t)(v >> 24);
}
}
/* 初始化:使能引擎、加载密钥、开中断 */
int aes_init(const uint8_t key[16], int use_irq)
{
AES_CR = 0; /* 先关闭引擎 */
aes_write_block(AES_KEY(0), key);
AES_CR = CR_EN | (use_irq ? CR_IE : 0);
/* 等待密钥就绪,超时保护 */
for (volatile int t = 0; t < 100000; t++) {
if (AES_SR & SR_KEYRDY) return 0;
}
return -1; /* 密钥加载失败 */
}
/* 单块加解密:in/out 各 16 字节,dec=1 解密 */
int aes_crypt_block(const uint8_t in[16], uint8_t out[16], int dec)
{
if (AES_SR & SR_BUSY) return -1; /* 引擎忙 */
aes_write_block(AES_DIN(0), in);
AES_CR = CR_EN | CR_START | (dec ? CR_DEC : 0)
| ((AES_CR & CR_IE) ? CR_IE : 0);
/* 轮询等待 DONE */
for (volatile int t = 0; t < 100000; t++) {
if (AES_SR & SR_DONE) {
aes_read_block(AES_DOUT(0), out);
AES_ICR = 1; /* 清 DONE 标志 */
return 0;
}
}
return -2; /* 超时 */
}
/* 中断服务程序:只读结果、清标志 */
void AES_IRQHandler(void)
{
if (AES_SR & SR_DONE) {
aes_read_block(AES_DOUT(0), g_out);
AES_ICR = 1;
g_done_flag = 1;
}
}
结果验证:与软件参考实现比对
硬件加速最容易出错的地方是字节序和密钥加载顺序。验证时不要只看“能不能跑”,而要用已知测试向量逐字节比对。下面用 FIPS-197 标准向量做自检:
| 项目 | 值(十六进制) |
|---|---|
| 密钥 | 000102030405060708090a0b0c0d0e0f |
| 明文 | 00112233445566778899aabbccddeeff |
| 期望密文 | 69c4e0d86a7b0430d8cdb78070b4c55a |
static const uint8_t kKey[16] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f };
static const uint8_t kPlain[16] = {
0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,
0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff };
static const uint8_t kCipher[16] = {
0x69,0xc4,0xe0,0xd8,0x6a,0x7b,0x04,0x30,
0xd8,0xcd,0xb7,0x80,0x70,0xb4,0xc5,0x5a };
int aes_selftest(void)
{
uint8_t enc[16], dec[16];
if (aes_init(kKey, 0) != 0) return -1;
if (aes_crypt_block(kPlain, enc, 0) != 0) return -2;
if (memcmp(enc, kCipher, 16) != 0) return -3; /* 加密不匹配 */
if (aes_crypt_block(kCipher, dec, 1) != 0) return -4;
if (memcmp(dec, kPlain, 16) != 0) return -5; /* 解密不匹配 */
return 0; /* 全部通过 */
}
若加密结果与期望值呈“每 4 字节内部逆序”的规律,说明寄存器字节序理解反了,把
aes_write_block 中的移位方向对调即可;若只有第一轮正确、后续全错,通常是密钥加载顺序或 KEYRDY 未等待。常见故障与排查路径
- 启动后 BUSY 一直为 1:检查时钟是否使能、CR.EN 是否置位、密钥是否已加载(KEYRDY)。
- DONE 不置位:确认 START 是“写 1 触发”还是“电平保持”,部分芯片要求 START 自清。
- 中断反复进入:ISR 中必须写 ICR 清标志,否则会重复触发。
- 结果全 0:DOUT 读取过早,需等 DONE 后再读;或总线位宽/对齐不匹配。
密钥寄存器通常只允许在引擎关闭(CR.EN=0)时写入。若在运行中改密钥,必须先关引擎、写 KEY、等 KEYRDY,再重新使能,否则新密钥可能不生效。