4. AES-128硬件加速引擎驱动开发

AES-128硬件加速引擎驱动开发 学习卡片

硬件加速引擎的寄存器模型与数据通路

带 AES 加速器的 MCU 通常把加解密单元做成一个挂在总线上的外设,驱动开发的第一步是弄清它的寄存器接口和内部数据通路。以常见的“控制/状态寄存器 + 密钥寄存器组 + 数据输入/输出寄存器 + 中断标志”结构为例,典型寄存器布局如下(地址为示例,实际以芯片手册为准):

寄存器偏移位域含义
CR0x00bit0 EN 使能,bit1 START 启动,bit2 DEC 解密,bit3 IE 中断使能
SR0x04bit0 BUSY,bit1 DONE,bit2 KEYRDY
KEY0..KEY30x10~0x1C128 位密钥,按 32 位小端分 4 次写入
DIN0..DIN30x20~0x2C128 位明文/密文输入
DOUT0..DOUT30x30~0x3C128 位结果输出
ICR0x08写 1 清 DONE 中断标志

数据通路是:CPU 把密钥写入 KEY 组 → 写 DIN 组 → 置 START → 引擎内部完成 10 轮运算 → 置 DONE 并触发中断 → CPU 读 DOUT 组。密钥只需在会话开始时加载一次,之后每次加解密只搬 16 字节数据,这正是硬件加速相对查表法的核心优势。

AES-128 硬件加速引擎驱动数据通路 CPU / 驱动 写密钥 写数据 读结果 总线 AES-128 硬件引擎 KEY0..KEY3 (128bit) DIN0..DIN3 (128bit) 10 轮 AES 运算 DOUT0..DOUT3 (128bit) CR / SR / ICR START · BUSY · DONE · IE 写1清中断标志 中断服务程序 读 DOUT → 清 ICR APB/AHB 状态 IRQ

驱动实现:寄存器操作与中断处理

下面给出一个可直接移植的驱动骨架。关键点:密钥加载后置 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,再重新使能,否则新密钥可能不生效。