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/KconfigMID 和可选 PDMA 配置
code/middleware/aes/CMakeLists.txt在 CONFIG_MIDDLEWARE_AES=y 时编译 MID
code/middleware/aes/example/main/main.cAES-128 ECB known-answer 示例

应用包含接口时使用:

#include "im_aes.h"

2. 资源模型与公开宏

2.1 公开宏

宏当前值约束
IM_AES_BLOCK_SIZE_BYTES16所有输入长度必须是该值的整数倍。
IM_AES_IV_SIZE_BYTES16CBC IV 必须指向至少 16 byte。
IM_AES_MAX_KEY_SIZE_BYTES32AES-256 软件 key 的大小。
IM_AES_DMA_BUFFER_ALIGN_BYTES64DMA staging buffer 起始地址必须是该值的整数倍。
IM_AES_DEFAULT_DMA_BUFFER_SIZE1024im_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 必须:

  1. 对 CPU 可访问且对 AES DMA 可见。

  2. 起始地址按 IM_AES_DMA_BUFFER_ALIGN_BYTES 对齐,当前为 64 byte。

  3. 大小至少为一个 AES block,且为 16 byte 的整数倍。

  4. 从 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);

初始化顺序:

  1. 校验设备对象处于 RESET 且未被占用。

  2. 校验 staging buffer、对齐、大小和 IRQ_AES。

  3. 保存配置并初始化内部 waiter。

  4. 初始化 HAL,配置 AES 时钟和基础寄存器。

  5. 注册内部 DMA/EFUSE 完成 callback。

  6. 申请并使能 IRQ_AES。

  7. 若启用 MIDDLEWARE_AES_REG_WINDOW_PDMA,申请一个 PDMA 请求。

  8. 进入 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 拥有的资源:

  1. 中止 HAL 中活动的 AES/EFUSE 操作。

  2. 停止并释放可选的 PDMA 请求。

  3. 关闭并释放 IRQ_AES。

  4. 清除 HAL callback 和 user data。

  5. 反初始化 HAL。

  6. 清理内部 key、IV、请求游标和 waiter。

  7. 回到 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_HARDWAREHAL 报告 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 字节序和跨段生命周期管理。