API GUIDE / AES / Middleware
G32S10 AES Middleware 接口
Middleware 层接口:应用包含接口时使用:
1. 构建配置与文件组织
1.1 文件职责
| 文件 | 说明 |
|---|---|
code/middleware/aes/include/im_aes.h | 公开宏、类型、设备对象和 4 个 API |
code/middleware/aes/src/im_aes.c | 初始化、请求校验、分段、IRQ、等待、EFUSE 和错误恢复 |
code/middleware/aes/Kconfig | MID 和可选 PDMA 配置 |
code/middleware/aes/CMakeLists.txt | 在 CONFIG_MIDDLEWARE_AES=y 时编译 MID |
code/middleware/aes/example/main/main.c | AES-128 ECB known-answer 示例 |
应用包含接口时使用:
#include "im_aes.h"
2. 资源模型与公开宏
2.1 公开宏
| 宏 | 当前值 | 约束 |
|---|---|---|
IM_AES_BLOCK_SIZE_BYTES | 16 | 所有输入长度必须是该值的整数倍。 |
IM_AES_IV_SIZE_BYTES | 16 | CBC IV 必须指向至少 16 byte。 |
IM_AES_MAX_KEY_SIZE_BYTES | 32 | AES-256 软件 key 的大小。 |
IM_AES_DMA_BUFFER_ALIGN_BYTES | 64 | DMA staging buffer 起始地址必须是该值的整数倍。 |
IM_AES_DEFAULT_DMA_BUFFER_SIZE | 1024 | im_aes_config_struct_init() 写入的默认分段大小。 |
2.2 缓冲区所有权
im_aes_t 和两个 DMA staging buffer 都由调用方分配并持有。MID 不分配、不释放、不
重新定位它们。
static im_aes_t s_aes; static uint8_t s_dma_input[IM_AES_DEFAULT_DMA_BUFFER_SIZE] __attribute__((aligned(IM_AES_DMA_BUFFER_ALIGN_BYTES))); static uint8_t s_dma_output[IM_AES_DEFAULT_DMA_BUFFER_SIZE] __attribute__((aligned(IM_AES_DMA_BUFFER_ALIGN_BYTES)));
两个 staging buffer 必须:
对 CPU 可访问且对 AES DMA 可见。
起始地址按
IM_AES_DMA_BUFFER_ALIGN_BYTES对齐,当前为 64 byte。大小至少为一个 AES block,且为 16 byte 的整数倍。
从
im_aes_init()成功返回后,一直保持有效到im_aes_deinit()返回。
2.3 中断与实例
G32S10 当前只开放一个 AES 中断入口 IRQ_AES。im_aes_config_t.irq_id 必须等于IRQ_AES,否则初始化返回 IM_ERR_INVALID_PARAM。同一时刻只有一个 MID 对象可以拥有
该 IRQ。
3. 公开类型
3.1 im_aes_t
typedef struct im_aes im_aes_t;
调用方持有的 MID 设备对象。第一次 im_aes_init() 前必须零初始化;初始化成功后保持
地址不变。应用不得直接读取或修改对象字段,也不得复制活动对象。
3.2 im_aes_direction_t
typedef enum {
IM_AES_DIRECTION_ENCRYPT = 0,
IM_AES_DIRECTION_DECRYPT,
} im_aes_direction_t;| 值 | 说明 |
|---|---|
IM_AES_DIRECTION_ENCRYPT | 加密。 |
IM_AES_DIRECTION_DECRYPT | 解密。 |
3.3 im_aes_mode_t
typedef enum {
IM_AES_MODE_ECB = 0,
IM_AES_MODE_CBC,
} im_aes_mode_t;MID 不支持 CTR、GCM、CFB、OFB 等模式,也不提供 padding、认证标签或协议封装。
3.4 im_aes_state_t
typedef enum {
IM_AES_STATE_RESET = 0,
IM_AES_STATE_READY,
IM_AES_STATE_WAITING,
IM_AES_STATE_TIMEOUT,
IM_AES_STATE_ERROR,
} im_aes_state_t;| 状态 | 说明 |
|---|---|
IM_AES_STATE_RESET | 未初始化或已 deinit,可以初始化。 |
IM_AES_STATE_READY | 可以接收一个 im_aes_crypt()。 |
IM_AES_STATE_WAITING | 正在等待内部 DMA/EFUSE 阶段完成。 |
IM_AES_STATE_TIMEOUT | 正在执行超时恢复。 |
IM_AES_STATE_ERROR | 恢复失败,只允许调用 im_aes_deinit()。 |
该枚举公开是因为 im_aes_t 使用调用方静态存储,但 MID 不提供状态查询 API。
3.5 im_aes_key_source_t
typedef enum {
IM_AES_KEY_SOURCE_SOFTWARE = 0,
IM_AES_KEY_SOURCE_EFUSE,
} im_aes_key_source_t;| 值 | 说明 |
|---|---|
IM_AES_KEY_SOURCE_SOFTWARE | 使用请求中的软件 key。 |
IM_AES_KEY_SOURCE_EFUSE | 使用 EFUSE 加载的 key,或 EFUSE + 128-bit register key。 |
4. 接口规范
4.1 im_aes_config_struct_init()
void im_aes_config_struct_init(im_aes_config_t *config);
把配置填充为第 5.5 节列出的默认值。
调用方仍必须设置:
config.dma_input_buffer = s_dma_input; config.dma_output_buffer = s_dma_output; config.dma_buffer_size = sizeof(s_dma_input);
传 NULL 时函数直接返回,无副作用。
4.2 im_aes_init()
error_code_t im_aes_init(im_aes_t *dev, const im_aes_config_t *config);
初始化顺序:
校验设备对象处于 RESET 且未被占用。
校验 staging buffer、对齐、大小和
IRQ_AES。保存配置并初始化内部 waiter。
初始化 HAL,配置 AES 时钟和基础寄存器。
注册内部 DMA/EFUSE 完成 callback。
申请并使能
IRQ_AES。若启用
MIDDLEWARE_AES_REG_WINDOW_PDMA,申请一个 PDMA 请求。进入 READY。
关键返回码:
| 返回值 | 条件 |
|---|---|
IM_OK | 初始化完成。 |
IM_ERR_INVALID_PARAM | 参数、缓冲、对齐、大小或 IRQ 非法。 |
IM_ERR_BUSY | 设备对象已初始化或正在使用。 |
| 其它 | HAL、IRQ 或可选 PDMA 初始化错误。 |
失败路径会释放本次初始化已经获取的 HAL、IRQ 和 PDMA 资源。调用方可在修正配置后 重试。
4.3 im_aes_crypt()
error_code_t im_aes_crypt(im_aes_t *dev, const im_aes_request_t *request, uint32_t timeout_ms);
同步执行一个完整的 AES 请求。
timeout_ms 是每个内部 DMA/EFUSE 阶段的最长等待时间,不是整包总超时。传 0 使用
MID 默认值 100 ms。处理大文件时,调用方应根据单个 staging segment 和硬件性能设置
合理值。
返回 IM_OK 时,连续请求的 request->output 或 scatter 请求的全部输出段包含完整
结果。返回错误时,输出可能已经被部分修改,调用方不得把该缓冲区当作完整结果使用。
im_aes_crypt() 不执行:
PKCS#7、zero padding 或 unpadding。
ECB 的块间链路。
CBC 的初始 IV 生成。
应用层协议 framing。
输入/输出所有权释放。
4.4 im_aes_deinit()
error_code_t im_aes_deinit(im_aes_t *dev);
中止活动请求并释放 MID 拥有的资源:
中止 HAL 中活动的 AES/EFUSE 操作。
停止并释放可选的 PDMA 请求。
关闭并释放
IRQ_AES。清除 HAL callback 和 user data。
反初始化 HAL。
清理内部 key、IV、请求游标和 waiter。
回到 RESET。
函数返回第一个清理错误。即使清理失败,调用方也不应继续发起新请求;需要检查硬件 状态并重新初始化。
5. 请求组织与数据格式
5.1 连续内存请求
当 request->segments == NULL 时:
request.input = input; request.output = output; request.length_bytes = length;
MID 按 dma_buffer_size 把请求切成多个 DMA segment。最后一段必须仍为 16 byte 的
整数倍。每个 segment 完成后,MID 更新 CBC working IV,再继续下一段。
输入和输出在 im_aes_crypt() 返回前必须保持有效。MID 会在 DMA 前后对 staging
buffer执行 cache clean/invalidate,但不会替应用同步其它别名缓存。
5.2 CBC IV
CBC 请求必须提供 iv:
request.mode = IM_AES_MODE_CBC; request.iv = iv;
MID 会把 IV 复制到内部 working_iv,调用方 IV 不会被修改。连续请求跨多个 DMA
segment 或 scatter 段时,MID 自动把上一块的最后一个密文块作为下一段 IV。
ECB 忽略 iv。MID 不生成随机 IV,也不保存 IV;需要持久化的 IV 由应用管理。
6. 错误处理与并发约束
6.1 错误恢复
AES 阶段返回非 IM_OK 或等待超时后,MID 会调用 HAL abort:
| 恢复结果 | MID 状态 | 后续操作 |
|---|---|---|
| abort 成功 | READY | 可以重新调用 im_aes_crypt() |
| abort 失败 | ERROR | 只允许调用 im_aes_deinit() |
超时返回 IM_ERR_TIMEOUT。HAL callback 返回错误时,MID 将对应错误码返回给im_aes_crypt()。
6.2 常见错误码
| 错误码 | 典型原因 |
|---|---|
IM_ERR_INVALID_PARAM | 空指针、长度未按 16 对齐、IV/key 非法、buffer 未对齐、IRQ ID 错误 |
IM_ERR_NOT_READY | 设备未初始化或不在 READY |
IM_ERR_BUSY | 重复初始化或设备已被占用 |
IM_ERR_TIMEOUT | 单个 DMA/EFUSE 阶段等待超时 |
IM_ERR_HARDWARE | HAL 报告 AES/EFUSE 操作失败 |
6.3 上下文和并发
im_aes_init()、im_aes_crypt()、im_aes_deinit()必须在任务上下文调用。不能从 ISR 调用公开 API。
同一个
im_aes_t不能并发执行多个im_aes_crypt()。im_aes_deinit()不能与同一设备的im_aes_crypt()并发。内部 HAL callback 在 AES IRQ 上下文运行,只更新完成状态并唤醒 waiter。
IRQ_AES只能有一个所有者。应用不得同时注册自己的 AES IRQ handler。
7. 典型用法
7.1 AES-128 ECB 加密
#include <string.h>
#include "im_aes.h"
static im_aes_t s_aes;
static uint8_t s_dma_in[IM_AES_DEFAULT_DMA_BUFFER_SIZE]
__attribute__((aligned(IM_AES_DMA_BUFFER_ALIGN_BYTES)));
static uint8_t s_dma_out[IM_AES_DEFAULT_DMA_BUFFER_SIZE]
__attribute__((aligned(IM_AES_DMA_BUFFER_ALIGN_BYTES)));
static const uint8_t s_key[16] = {
0x00U, 0x01U, 0x02U, 0x03U,
0x04U, 0x05U, 0x06U, 0x07U,
0x08U, 0x09U, 0x0aU, 0x0bU,
0x0cU, 0x0dU, 0x0eU, 0x0fU,
};
static const uint8_t s_plaintext[16] = {
0x6bU, 0xc1U, 0xbeU, 0xe2U, 0x2eU, 0x40U, 0x9fU, 0x96U,
0xe9U, 0x3dU, 0x7eU, 0x11U, 0x73U, 0x93U, 0x17U, 0x2aU,
};
static uint8_t s_ciphertext[16];
static const uint8_t s_expected_ciphertext[16] = {
0x3aU, 0xd7U, 0x7bU, 0xb4U, 0x0dU, 0x7aU, 0x36U, 0x60U,
0xa8U, 0x9eU, 0xcaU, 0xf3U, 0x24U, 0x66U, 0xefU, 0x97U,
};
static error_code_t aes_ecb_encrypt(const uint8_t *input,
uint8_t *output,
size_t length)
{
im_aes_request_t request;
memset(&request, 0, sizeof(request));
request.input = input;
request.output = output;
request.length_bytes = length;
request.direction = IM_AES_DIRECTION_ENCRYPT;
request.mode = IM_AES_MODE_ECB;
request.key_source = IM_AES_KEY_SOURCE_SOFTWARE;
request.key = s_key;
request.key_size_bytes = sizeof(s_key);
return im_aes_crypt(&s_aes, &request, 100U);
}
error_code_t aes_ecb_example(void)
{
im_aes_config_t config;
error_code_t ret;
im_aes_config_struct_init(&config);
config.dma_input_buffer = s_dma_in;
config.dma_output_buffer = s_dma_out;
config.dma_buffer_size = sizeof(s_dma_in);
ret = im_aes_init(&s_aes, &config);
if (ret != IM_OK) {
return ret;
}
ret = aes_ecb_encrypt(s_plaintext, s_ciphertext, sizeof(s_plaintext));
if ((ret == IM_OK) &&
(memcmp(s_ciphertext, s_expected_ciphertext,
sizeof(s_ciphertext)) != 0)) {
ret = IM_ERR_NOT_READY;
}
(void)im_aes_deinit(&s_aes);
return ret;
}该示例与 code/middleware/aes/example/main/main.c 中的 AES-128 ECB known-answer
测试一致。示例会先加密,再解密并与原 plaintext 比较。
7.2 AES-128 CBC
static error_code_t aes_cbc_encrypt(const uint8_t *input,
uint8_t *output,
size_t length,
const uint8_t iv[16])
{
im_aes_request_t request;
memset(&request, 0, sizeof(request));
request.input = input;
request.output = output;
request.length_bytes = length;
request.direction = IM_AES_DIRECTION_ENCRYPT;
request.mode = IM_AES_MODE_CBC;
request.key_source = IM_AES_KEY_SOURCE_SOFTWARE;
request.key = s_key;
request.key_size_bytes = sizeof(s_key);
request.iv = iv;
return im_aes_crypt(&s_aes, &request, 100U);
}length 必须是 16 的整数倍。CBC 没有自动 padding,因此应用必须先完成自己的填充。
7.3 scatter 请求
static im_aes_segment_t s_segments[2] = {
{
.input = pre_swapped_in_0,
.output = pre_swapped_out_0,
.length_bytes = 4096U,
},
{
.input = pre_swapped_in_1,
.output = pre_swapped_out_1,
.length_bytes = 1024U,
},
};
request.segments = s_segments;
request.segment_count = 2U;scatter 适合不连续的大块数据,但要求调用方自己完成 DMA buffer 对齐、可见性、 word 字节序和跨段生命周期管理。