API GUIDE / DMA / Middleware

G32S10 DMA Middleware 接口

Middleware 层接口:说明:

1. 资源模型与公开类型

1.1 模块能力摘要

项目IM DMAIM DMACPY
调用模型异步启动,通过回调或状态查询观察完成。阻塞调用线程直到完成、错误或等待超时。
传输模式普通单 descriptor、等长连续 linked、等长连续 cyclic。普通单 descriptor 内存拷贝。
OS 依赖不依赖 OS;需要 C heap allocator 和完整 IRQ 分发框架。需要 FreeRTOS OS backend。
逻辑请求存储调用方持有 im_dma_t;中间层分配私有上下文。服务内部持有固定请求池。
descriptor 存储第一次 start 时分配,需要更多 segment 时扩容,release 时释放。通过内部 IM DMA 请求按需分配和释放。
物理通道占用每次成功启动时申请,终态完成、错误或主动停止后归还。每笔正在执行的拷贝占用一个 PDMA0 通道。
数据 buffer cache调用方维护。服务维护;地址及长度必须满足运行时 cache-line 对齐。
完成通知PDMA ISR context 回调。内部 ISR 回调唤醒调用线程。

1.2 逻辑请求与物理通道

zero-initialized im_dma_t
          |
          | im_dma_request(): allocate private context
          v
        READY
          |
          | im_dma_start*(): prepare descriptors, acquire hardware channel
          v
         BUSY
          |
          +-- transfer complete / successful stop --> READY
          |
          +-- hardware error ----------------------> ERROR

READY / ERROR -- im_dma_release() --> RELEASED

说明:

  • request 成功表示获得逻辑服务,此时不占用物理 DMA 通道。

  • descriptor 和内部 HAL 句柄在 start 准备阶段按需建立。即使 request 成功,首次 start 仍可能因内存不足、请求源非法或无空闲物理通道而失败。

  • 普通和 linked 完成后可以复用同一请求再次 start,descriptor 容量足够时复用原有存储。

  • cyclic descriptor 完成事件保持 BUSY;需要调用 im_dma_stop() 停止,或由硬件错误结束。

  • ERROR 状态的空闲请求可以再次 start,也可以 release;start 成功后进入 BUSY。

1.3 公开宏

宏使用说明取值/约束
IM_DMA_MAX_SEGMENT_COUNTlinked/cyclic 支持的最大 segment 数量。128。
IM_DMA_UNIT_SIZE_AUTO选择 PDMA 自主传输大小模式。0;要求 IM_DMA_BUS_WIDTH_32BIT,每段长度至少 8 字节且为 8 的倍数。

IM_DMACPY_MAX_CHANNEL_NUM 是 .c 文件中的私有限制,不属于公开头文件接口;请求池大小通过 CONFIG_MIDDLEWARE_DMACPY_CHANNEL_NUM 配置。

1.4 返回状态约定

IM DMA 的普通可失败接口以及 IM DMACPY 的 init/deinit 使用 SDK 公共错误码类型 error_code_t。

返回值说明
IM_OK操作成功;异步 start 成功仅表示已经启动。
IM_ERR_INVALID_PARAM空指针、非法宽度/单元大小、非法 segment 数量或不满足 HAL 传输约束。
IM_ERR_NOT_READY请求尚未申请、已经释放,或查询所需的内部 HAL 句柄尚未建立。
IM_ERR_BUSY请求正在传输、内部资源正在准备/释放、无空闲物理通道,或共享初始化正在进行。
IM_ERR_NO_MEMORYIM DMA 私有上下文或 descriptor 分配失败。
IM_ERR_ALREADY_EXISTS同一个 im_dma_t 已经持有逻辑请求。
IM_ERR_HARDWAREDMA 硬件错误,或硬件当前地址持续不在记录范围内。
其他错误码im_dma_request() 或 start 准备阶段透传的 HAL/IRQ 错误码。

im_dmacpy() 和 im_dmacpy_timeout() 返回指针:成功返回 dest,失败返回 NULL,不通过返回值区分具体错误。IM DMA 本身不等待传输,也不主动产生传输等待超时;等待超时由调用方或 IM DMACPY 处理。

1.5 IM DMA 状态

typedef enum {
    IM_DMA_STATE_RELEASED,
    IM_DMA_STATE_READY,
    IM_DMA_STATE_BUSY,
    IM_DMA_STATE_ERROR,
} im_dma_state_t;
状态说明
IM_DMA_STATE_RELEASED对象从未 request,或已经 release。
IM_DMA_STATE_READY已申请的空闲请求;普通/linked 完成及活动传输成功 stop 后进入此状态。
IM_DMA_STATE_BUSYDMA 正在执行;cyclic descriptor 回调期间仍保持此状态。
IM_DMA_STATE_ERROR最近一次处理观察到硬件或事件读取错误;仍需在非 ISR context 处理重试或释放。

1.6 IM DMA 事件快照

typedef enum {
    IM_DMA_EVENT_NONE = 0,
    IM_DMA_EVENT_DESCRIPTOR_COMPLETE,
    IM_DMA_EVENT_TRANSFER_COMPLETE,
    IM_DMA_EVENT_ERROR,
} im_dma_event_t;

typedef struct {
    im_dma_event_t type;
    uint32_t descriptor_index;
    uint32_t descriptor_advance;
} im_dma_event_info_t;
字段说明
type尚无事件、descriptor 完成、整次传输完成或错误。
descriptor_index最近完成的 descriptor 下标;无法确定时为 UINT32_MAX。
descriptor_advance根据相邻观测计算的最小 descriptor 推进数量;不是实际完成数量的保证。

说明:

  • im_dma_get_last_event() 将 HAL 事件转换为上述中间层类型,适合在 DMA callback 中读取。

  • DIP 是每通道 pending bit,不是事件计数器;一个 callback 可能对应多次硬件推进。

  • 单 descriptor cyclic 固定报告 index 0、advance 1;多 descriptor 的 index 根据 HAL 对 CDOA 的解码和数组范围校验产生。

  • 中间层快照不包含 HAL 的 index-valid、coalesced 或 pending 标志。调用方应检查 descriptor_index != UINT32_MAX,并避免把 callback 次数或 advance 当作绝对传输计数。

1.7 IM DMA 句柄

struct im_dma_context;

typedef struct {
    struct im_dma_context *private_context;
} im_dma_t;

说明:

  • 第一次 request 前必须将对象清零,例如 im_dma_t dma = {0};。

  • 对象存储由调用方持有;私有上下文由中间层申请和释放。

  • request 到 release 期间,对象地址必须保持稳定,不能复制、移动或直接改写 private_context。

  • source/destination buffer 必须在传输完成或成功 stop 前保持有效。user_data 必须在每次可能触发 callback 期间有效,复用请求时也应继续保证其有效;对象本身必须保持有效到 release 完成。

  • im_dmacpy.h 不公开服务句柄,调用方使用共享服务接口。

1.8 IM DMA 回调类型

typedef void (*im_dma_callback_t)(im_dma_t *dma,
                                  error_code_t status,
                                  void *user_data);
参数说明
dma产生事件的原始 IM DMA 句柄。
status成功事件为 IM_OK;硬件错误通常为 IM_ERR_HARDWARE,读取 HAL 事件失败时透传该错误。
user_datarequest 时提供的上下文,允许为 NULL。

回调运行在 PDMA ISR context,不能阻塞。不能在回调中调用 im_dma_request()、任何 im_dma_start*() 或 im_dma_release(),因为它们可能分配或释放内存。回调中可以记录状态,并通过 im_dma_get_last_event() 读取当前事件;后续启动和释放应交给主循环或线程执行。

普通/linked 的成功终态回调前,请求状态已经变为 READY;硬件错误回调前为 ERROR。cyclic descriptor 回调保持 BUSY。回调参数 status == IM_OK 在 cyclic 场景仅表示本次观察到的 descriptor 事件成功。

2. IM DMA 接口规范

2.1 实现边界

  • 公开接口通过 im_dma_t * 或 const im_dma_t * 传递逻辑请求。

  • request/start/release 可能使用 heap,必须在允许分配或释放内存的非 ISR context 调用。

  • start 是异步操作,返回后 buffer 仍可能被 DMA 访问。

  • 同一个对象的 request/start/stop/release 生命周期应由调用方协调;局部 IRQ 临界区不提供跨核硬件仲裁。

  • 使用中间层时,descriptor buffer 和 SoC IRQ entry 由中间层内部管理。

2.2 请求生命周期

2.2.1 im_dma_request

error_code_t im_dma_request(im_dma_t *dma,
                            hal_dma_request_t request,
                            im_dma_bus_width_t bus_width,
                            uint32_t unit_size,
                            im_dma_callback_t callback,
                            void *user_data);

功能说明:

申请一个逻辑 DMA 服务,保存传输路由和回调配置,分配私有上下文,并在必要时初始化共享 DMA 子系统。成功后对象进入 READY,不立即分配 descriptor 或物理通道。

参数表:

参数 类型 方向 说明
dma im_dma_t * 输入/输出 第一次使用前已清零的对象,不能为 NULL。
request hal_dma_request_t 输入 HAL_DMA_REQUEST_* 请求源,首次 start 时由 HAL 校验。
bus_width im_dma_bus_width_t 输入 IM_DMA_BUS_WIDTH_8BIT/16BIT/32BIT。
unit_size uint32_t 输入 固定单元字节数,或 IM_DMA_UNIT_SIZE_AUTO;详细约束见 7.2。
callback im_dma_callback_t 输入 事件回调;允许为 NULL,此时仍会内部维护请求状态。
user_data void * 输入 传给 callback 的上下文,允许为 NULL。

返回值表:

返回值 说明
IM_OK 逻辑请求申请成功。
IM_ERR_INVALID_PARAM dma 为空、宽度/单元大小非法,或 AUTO 未配合 32-bit 总线宽度。
IM_ERR_ALREADY_EXISTS 对象已经持有逻辑请求。
IM_ERR_NO_MEMORY 私有上下文分配失败。
IM_ERR_BUSY 共享子系统正在初始化。
其他错误码 HAL 初始化、IRQ 注册或 IRQ 使能失败。

补充说明:

  • 一次 request 可复用多次 start,参数和回调在 request 生命周期内保持不变;需要更换时应先停止、release,再重新 request。
  • 全局初始化在私有上下文分配之前进行;即使后续上下文分配失败,已经成功建立的共享 IRQ 注册仍会保留。
  • 调用方不需要提供 descriptor buffer,也不能通过 request 固定指定某个物理通道号。

2.2.2 im_dma_release

error_code_t im_dma_release(im_dma_t *dma);

功能说明:

释放一个空闲逻辑请求,关闭其内部 HAL 句柄,释放 descriptor 和私有上下文,将 private_context 清为 NULL。

参数表:

参数 类型 方向 说明
dma im_dma_t * 输入/输出 已申请且没有活动传输的对象。

返回值表:

返回值 说明
IM_OK 请求已释放。
IM_ERR_INVALID_PARAM dma 为空。
IM_ERR_NOT_READY 对象未申请或已经释放。
IM_ERR_BUSY 传输正在运行,或内部资源正在准备/释放。
其他错误码 内部 hal_dma_close() 失败。

补充说明:

  • release 不隐式停止 BUSY 传输;必须先等待终态或成功调用 im_dma_stop()。
  • READY 和 ERROR 状态都允许释放;重复 release 返回 IM_ERR_NOT_READY。
  • 只能在非 ISR context 调用;释放请求不会撤销共享 IRQ 注册。

2.3 启动传输

2.3.1 im_dma_start

error_code_t im_dma_start(im_dma_t *dma,
                          const void *source,
                          void *destination,
                          uint32_t length);

功能说明:

使用一个 descriptor 启动异步 DMA 传输。中间层按需分配内部 descriptor,打开 HAL 句柄并由 HAL 申请一个物理通道。

参数表:

参数类型方向说明
dmaim_dma_t *输入/输出已 request 且空闲的对象。
sourceconst void *输入DMA 可访问的源地址,不能为 NULL。
destinationvoid *输入DMA 可访问的目的地址,不能为 NULL。
lengthuint32_t输入总字节数,必须大于 0,并满足 7.2 的长度约束。

返回值表:

返回值说明
IM_OK传输已启动,后续终态由回调或状态查询观察。
IM_ERR_INVALID_PARAM空指针、零长度、请求源非法或不满足 HAL 传输参数约束。
IM_ERR_NOT_READY对象尚未 request 或已经 release。
IM_ERR_BUSY对象正在运行/准备资源,或 HAL 没有空闲物理通道。
IM_ERR_NO_MEMORYdescriptor 分配失败。
其他错误码内部 HAL 打开、回调配置或启动失败。

补充说明:

  • 终态成功后状态为 READY;硬件错误后为 ERROR。普通传输不会把中间 descriptor 进度作为正常完成回调。

  • start 返回失败时,不会为这次失败同步调用用户 callback;调用方必须检查函数返回值。

  • 数据 buffer cache 不由本接口维护。返回 IM_OK 后,调用方应等待完成或成功 stop,再回收 buffer。

2.3.2 im_dma_start_linked

error_code_t im_dma_start_linked(im_dma_t *dma,
                                 const void *source,
                                 void *destination,
                                 uint32_t length,
                                 uint32_t segment_count);

功能说明:

将总长度拆分为 segment_count 个等长、连续的 descriptor,依次执行并在链尾结束。内存端按每段长度推进,外设端的地址行为由 request 决定。

参数表:

参数类型方向说明
dmaim_dma_t *输入/输出已 request 且空闲的对象。
sourceconst void *输入第一段源地址,不能为 NULL。
destinationvoid *输入第一段目的地址,不能为 NULL。
lengthuint32_t输入所有 segment 的总字节数。
segment_countuint32_t输入1 到 IM_DMA_MAX_SEGMENT_COUNT,包含两端。

返回值表:

返回值说明
IM_OKlinked 传输已启动。
IM_ERR_INVALID_PARAM空指针、零长度、segment 数量非法、总长度不能整除 segment 数量,或单段长度/请求源等 HAL 参数非法。
IM_ERR_NOT_READY对象尚未 request 或已经 release。
IM_ERR_BUSY对象正在运行/准备资源,或没有空闲物理通道。
IM_ERR_NO_MEMORY内部 descriptor 分配或扩容失败。
其他错误码内部 HAL 准备或启动失败。

补充说明:

  • 每段长度为 length / segment_count,每段必须满足 7.2 的约束。

  • 本接口使用连续等长 buffer 分段,不接收每段独立地址的 scatter/gather 列表。

  • 成功终态回调发生在整条链完成后,状态已经变为 READY;不保证每一段都有用户 callback。

  • 中间层自动满足 descriptor 容量、对齐和 4 KiB DOA 窗口要求。

2.3.3 im_dma_start_cyclic

error_code_t im_dma_start_cyclic(im_dma_t *dma,
                                 const void *source,
                                 void *destination,
                                 uint32_t length,
                                 uint32_t segment_count);

功能说明:

启动等长连续 segment 组成的循环 descriptor 链,最后一段链接回第一段。适用于需要持续运行的 DMA 环形缓冲区。

参数表:

参数类型方向说明
dmaim_dma_t *输入/输出已 request 且空闲的对象。
sourceconst void *输入环形区域的起始源地址,不能为 NULL。
destinationvoid *输入环形区域的起始目的地址,不能为 NULL。
lengthuint32_t输入一个环形区域的总字节数,不表示传输会在这些字节完成后自动停止。
segment_countuint32_t输入1 到 IM_DMA_MAX_SEGMENT_COUNT,包含两端。

返回值表:

返回值说明
IM_OKcyclic 传输已启动。
IM_ERR_INVALID_PARAM参数、等长分段关系或每段 HAL 传输约束不合法。
IM_ERR_NOT_READY对象尚未 request 或已经 release。
IM_ERR_BUSY对象正在运行/准备资源,或没有空闲物理通道。
IM_ERR_NO_MEMORYdescriptor 分配或扩容失败。
其他错误码内部 HAL 准备或启动失败。

补充说明:

  • 参数约束与 linked 相同;segment_count=1 表示单 descriptor 循环。

  • 每个 descriptor 都启用完成事件。每次观察到 descriptor pending 时可产生 callback,但请求保持 BUSY。

  • callback 中可用 im_dma_get_last_event() 获取进度;pending 合并可能导致中间事件不可见。

  • 正常循环不会自动进入 READY。调用方通过 im_dma_stop() 同步停止,再回收 buffer 或 release。

2.4 状态、事件与地址查询

2.4.1 im_dma_get_state

error_code_t im_dma_get_state(const im_dma_t *dma, im_dma_state_t *state);

功能说明:

查询逻辑请求状态。已清零、未 request 或已 release 的对象返回 IM_DMA_STATE_RELEASED。

参数表:

参数类型方向说明
dmaconst im_dma_t *输入合法对象存储,不能为 NULL。
stateim_dma_state_t *输出状态输出指针,不能为 NULL。

返回值表:

返回值说明
IM_OK已写入状态;RELEASED 也是成功查询结果。
IM_ERR_INVALID_PARAMdma/state 为空。

2.4.2 im_dma_get_last_event

error_code_t im_dma_get_last_event(const im_dma_t *dma,
                                   im_dma_event_info_t *event_out);

功能说明:

读取最近记录的 HAL 事件并转换为中间层事件快照。最推荐在 DMA callback 中调用。

参数表:

参数类型方向说明
dmaconst im_dma_t *输入已 request 且已建立内部 HAL 句柄的对象。
event_outim_dma_event_info_t *输出事件快照输出指针,不能为 NULL。

返回值表:

返回值说明
IM_OK事件快照已写入;尚无已记录事件时 type 为 IM_DMA_EVENT_NONE。
IM_ERR_INVALID_PARAMdma/event_out 为空。
IM_ERR_NOT_READY对象未申请、已释放或内部 HAL 句柄尚未建立。

补充说明:

  • 仅 request、尚未准备第一次 start 时,内部 HAL 句柄还没有建立,查询返回 IM_ERR_NOT_READY。

  • 每次 start 会重置这笔传输的事件状态。应在 callback 中读取对应事件,避免下一次 start 覆盖旧快照。

  • descriptor_index、descriptor_advance 的含义和限制见 3.9。

2.4.3 im_dma_read_src_addr

error_code_t im_dma_read_src_addr(const im_dma_t *dma, uintptr_t *address);

功能说明:

查询当前 DMA 可见源地址;非忙状态返回 HAL 记录的源结束地址。

参数表:

参数类型方向说明
dmaconst im_dma_t *输入已 request 且已建立内部 HAL 句柄的对象。
addressuintptr_t *输出DMA/物理地址输出指针,不能为 NULL。

返回值表:

返回值说明
IM_OK地址读取成功。
IM_ERR_INVALID_PARAM参数为空,或 HAL 忙状态句柄的控制器/通道信息非法。
IM_ERR_NOT_READY请求未申请、已释放或内部 HAL 句柄尚未建立。
IM_ERR_BUSY内部资源正在准备或释放。
IM_ERR_HARDWAREHAL 在最多 1024 次读取中,始终未得到记录源地址范围内的值。

补充说明:

  • 输出是 DMA/物理地址,不能直接当作 CPU 原始指针解引用;地址语义见 7.3。

  • 正常完成时,内存递增端的记录结束地址为起始物理地址加传输总长度;固定外设端保持其地址。主动 stop 会尝试采样当前地址更新记录。

2.4.4 im_dma_read_dst_addr

error_code_t im_dma_read_dst_addr(const im_dma_t *dma, uintptr_t *address);

功能说明:

查询当前 DMA 可见目的地址;非忙状态返回 HAL 记录的目的结束地址。

参数表:

参数类型方向说明
dmaconst im_dma_t *输入已 request 且已建立内部 HAL 句柄的对象。
addressuintptr_t *输出DMA/物理地址输出指针,不能为 NULL。

返回值表:

返回值说明
IM_OK地址读取成功。
IM_ERR_INVALID_PARAM参数为空,或 HAL 忙状态句柄的控制器/通道信息非法。
IM_ERR_NOT_READY请求未申请、已释放或内部 HAL 句柄尚未建立。
IM_ERR_BUSY内部资源正在准备或释放。
IM_ERR_HARDWAREHAL 在最多 1024 次读取中,始终未得到记录目的地址范围内的值。

地址语义、非忙状态行为和 stop 时的地址采样与 im_dma_read_src_addr() 对应。

2.5 停止传输

2.5.1 im_dma_stop

error_code_t im_dma_stop(im_dma_t *dma);

功能说明:

同步停止一个活动传输,归还物理通道并清除其 pending。成功停止后请求回到 READY,可以再次 start 或 release;该操作不合成完成 callback。

参数表:

参数 类型 方向 说明
dma im_dma_t * 输入/输出 已 request 的对象,不能为 NULL。

返回值表:

返回值 说明
IM_OK 活动传输已停止,或请求当前没有活动传输。
IM_ERR_INVALID_PARAM dma 为空。
IM_ERR_NOT_READY 对象未申请/已释放,或活动传输缺少内部 HAL 句柄。
IM_ERR_BUSY 内部资源正在准备或释放。
其他错误码 内部 hal_dma_stop() 失败。

补充说明:

  • 成功停止后,不会再为该笔已停止传输分发新的 callback,调用方可以回收这笔传输的 buffer 和 user_data。
  • 空闲 READY 请求调用 stop 返回 IM_OK;空闲 ERROR 请求也返回 IM_OK,但保留 ERROR 状态。
  • 外设传输应先关闭外设侧 DMA 请求,再 stop;中间层不操作外设内部寄存器。

3. IM DMACPY 接口规范

3.1 服务生命周期

3.1.1 im_dmacpy_init

error_code_t im_dmacpy_init(void);

功能说明:

建立共享同步对象和空闲请求池,尝试申请 CONFIG_MIDDLEWARE_DMACPY_CHANNEL_NUM 个逻辑内存拷贝请求。请求配置固定为 32-bit 总线宽度和 IM_DMA_UNIT_SIZE_AUTO。

参数表:

参数类型方向说明
无---

返回值表:

返回值说明
IM_OK至少一个逻辑请求可用,或服务已经初始化。
IM_ERR_BUSY没有成功申请到任何逻辑请求。

补充说明:

  • 只成功申请部分请求时仍返回 IM_OK,并打印 warning;实际池容量可能小于配置值。

  • init 只预申请逻辑请求,不预占物理 PDMA0 通道;descriptor 在每个请求第一次执行拷贝时才分配。

  • 请求申请失败的具体原因写入日志;init 的零可用请求结果统一为 IM_ERR_BUSY。

  • 同步对象在首次初始化时建立,后续 deinit/init 复用;多个模块应共享同一个受协调的服务生命周期。

3.1.2 im_dmacpy_deinit

error_code_t im_dmacpy_deinit(void);

功能说明:

在全部池内请求空闲时释放这些逻辑请求和各自的 waiter。存在活动拷贝时保留请求池,返回 IM_ERR_BUSY。

参数表:

参数类型方向说明
无---

返回值表:

返回值说明
IM_OK服务请求池已释放,或本来就没有已初始化的请求。
IM_ERR_BUSY100 ms 内未取得池互斥锁,或有请求尚未回到空闲池。

补充说明:

  • 活动拷贝包括已从池中取出请求、尚未归还的调用;deinit 不强制停止其他线程的拷贝。

  • 检查到忙时不会拆除部分请求池。

  • 共享 mutex 和 condition variable 保留,避免已进入等待流程的线程使用被销毁的同步对象;下次 init 复用它们。

  • deinit 不释放 IM DMA 的全局 IRQ owner,也不关闭共享 DMA 控制器时钟。

3.2 阻塞内存拷贝

3.2.1 im_dmacpy_timeout

void *im_dmacpy_timeout(void *dest,
                        const void *src,
                        size_t len,
                        uint32_t timeout_ms);

功能说明:

从共享空闲池取得一个逻辑请求,维护数据 cache,启动普通 DMA 内存拷贝,并在调用线程中等待完成。返回前将请求归还池;传输等待超时时会先调用 im_dma_stop()。

参数表:

参数类型方向说明
destvoid *输入/输出DMA 可访问的目的 buffer,地址按运行时数据 cache-line 大小对齐。
srcconst void *输入DMA 可访问的源 buffer,地址按运行时数据 cache-line 大小对齐。
lensize_t输入非零字节数,按运行时数据 cache-line 大小对齐,且满足 7.2 的 AUTO 单 descriptor 长度约束。
timeout_msuint32_t输入分别用于各等待阶段的毫秒超时;(uint32_t)OS_TIMEOUT_NOT_LIMIT_MS 表示无限等待。

返回值表:

返回值说明
destDMA 终态成功,数据拷贝已完成。
NULL未初始化、空指针、零长度、对齐/长度非法、资源等待失败、DMA 启动失败、硬件错误或传输等待超时。

补充说明:

  • timeout_ms 分别用于池互斥锁、空闲请求条件变量和传输完成等待,不是整次调用共用的绝对截止时间。一次三阶段等待可以接近其三倍;竞争引起的重复条件变量等待和归还池时的锁等待还可能增加总耗时。

  • 等待空闲池期间若服务已经 deinit,调用返回 NULL。

  • 当前实现先 clean 源 buffer,再 invalidate 目的 buffer,然后启动 DMA;调用方不得在传输过程中并发修改源 buffer 或访问目的 buffer。

  • 失败时目的区域可能已经写入部分数据;NULL 不表示目的内容保持原样。

  • 要使用无限等待常量时包含 os/os.h;也可以直接使用 im_dmacpy()。

3.2.2 im_dmacpy

void *im_dmacpy(void *dest, const void *src, size_t len);

功能说明:

使用无限等待执行阻塞内存拷贝,相当于以 (uint32_t)OS_TIMEOUT_NOT_LIMIT_MS 调用 im_dmacpy_timeout()。

参数表:

参数类型方向说明
destvoid *输入/输出cache-line 对齐的 DMA 目的 buffer。
srcconst void *输入cache-line 对齐的 DMA 源 buffer。
lensize_t输入非零、cache-line 对齐且符合 AUTO 单 descriptor 限制的字节数。

返回值表:

返回值说明
dest拷贝成功完成。
NULL参数、初始化、资源或 DMA 执行失败。

初始化、cache、buffer 生命周期和调用 context 要求与 im_dmacpy_timeout() 相同;无限等待不会放宽参数和硬件长度限制。

4. 配置与集成约定

4.1 示例工程

示例目录内容
code/middleware/dma/example/V1 上的普通、linked、cyclic 内存到内存传输,调用方显式维护数据 cache。
code/middleware/dma/example_dmacpy/阻塞拷贝、多轮数据校验、未对齐输入拒绝和服务释放。

现有示例使用 4096 字节 buffer,并通过 IM_CACHE_MAX_LINE_SIZE_BYTES 对齐。构建入口及配套 V0/V1 所有权安排见各示例目录的 README。

5. 当前实现约束与行为说明

5.1 Descriptor 资源管理

  • 三个 im_dma_start*() 接口都使用 descriptor 路径。

  • 普通 start 需要一个 descriptor;linked/cyclic 需要 segment_count 个。

  • descriptor 按缓存行和数组大小选择对齐,以满足 HAL 的 4 KiB DOA 窗口限制,调用方不提供 descriptor 存储。

  • 容量只在需要更多 descriptor 时扩容。传输完成或 stop 不释放该存储,release 才释放。

  • 内存准备或 HAL 启动失败会由函数返回错误;只有实际观测到的 DMA 事件才走 callback。

5.2 传输长度和单元大小

  • length 必须大于 0;linked/cyclic 的 segment_count 为 1~128,且 length % segment_count == 0。

  • 固定 unit_size 支持 1/2/4/8/16/32/64 或 >=128。编码时 8 归一到 4,>=128 归一到 128,其他值保持原值。

  • 固定模式要求 unit_size 不小于总线宽度对应的字节数,且能被该字节数整除;总长度和每段长度必须不小于配置的 unit_size 且能被其整除。

  • IM_DMA_UNIT_SIZE_AUTO 要求 32-bit 总线宽度,总长度和每段长度至少为 8 字节且为 8 的倍数;此模式的归一化单元大小为 1。

  • 当前 HAL 对每个 descriptor 检查 segment_length / normalized_unit_size < (1U << 24)。普通传输的 segment_length 为 length,linked/cyclic 为 length / segment_count;不满足时 start 返回 IM_ERR_INVALID_PARAM。

  • 因此 AUTO 的单段长度必须小于 16 MiB。linked/cyclic 可通过分段满足单段限制,但仍受总长度 uint32_t 和最多 128 段限制。

  • AES0/AES1/HASH 请求由 HAL 使用自主传输大小和按字节计数,显式配置的固定 unit_size 仍参与参数校验;具体硬件编码规则见 HAL/LL 文档 7.3。

  • IM DMACPY 使用 AUTO 单 descriptor,不自动拆分大块拷贝。其 len 必须非零、满足运行时 cache-line 对齐及 AUTO 的 8 字节约束,并小于 16 MiB;超过 UINT32_MAX 的 size_t 长度也会被拒绝。

5.3 地址与数据 cache

  • source/destination 使用调用方提供的 CPU 可见地址;HAL 通过 im_mmio_virt_to_phys() 转换为 DMA/物理地址。

  • 地址查询返回 DMA/物理地址,当前转换使用 0x1FFFFFFF 掩码;应在相同地址域下比较进度。

  • request 决定地址递增属性:内存到内存两端递增,外设传输的 FIFO/数据寄存器端按该 request 的路由规则访问。

  • IM DMA 只管理 descriptor cache,调用方维护数据 buffer cache:DMA 读取源 buffer 前 clean,目的区域按缓存行布局在启动前准备,并在完成后读取数据前 invalidate。

  • cache 维护可能覆盖完整缓存行。buffer 布局和并发访问应保证不会丢失同一缓存行内其他 CPU 数据;8.1 示例使用独立对齐数组。

  • IM DMACPY 从 im_cache_data_line_size_get() 获取运行时维护粒度并严格校验 dest/src/len;编译期分配 buffer 可使用 IM_CACHE_MAX_LINE_SIZE_BYTES(当前为 64)对齐。

  • IM DMACPY 的数据 cache 操作在内部完成;调用方无需为同一笔拷贝重复执行示例中的 IM DMA cache 流程。

5.4 回调、停止与错误处理

  • im_dma_start*() 的 IM_OK 只表示启动成功。普通/linked 等待整次完成,cyclic 根据事件快照处理 descriptor 进度。

  • 用户 callback 为空时,内部 HAL callback 仍更新状态,调用方可用 im_dma_get_state() 查询终态。

  • 硬件 AR/HLT 等错误由 HAL 分类,IM DMA 把状态设为 ERROR 并通知 callback;读取事件失败也会进入 ERROR。

  • 进入资源准备/启动流程后,返回 IM_ERR_HARDWARE 的失败会记录 ERROR,其他失败记录 READY;参数预检查失败或请求已经忙时直接返回,保留已有状态。

  • im_dma_stop() 是同步停止,不产生用于通知完成的 callback,调用方不应在 stop 后继续等待本笔完成回调。

  • 业务超时处理应停止活动传输后再释放资源,避免仍运行的 DMA 访问失效 buffer。

5.5 文件组织

code/middleware/dma/CMakeLists.txt
code/middleware/dma/Kconfig
code/middleware/dma/include/im_dma.h
code/middleware/dma/include/im_dmacpy.h
code/middleware/dma/src/im_dma.c
code/middleware/dma/src/im_dmacpy.c
code/middleware/dma/example/
code/middleware/dma/example_dmacpy/

6. 典型用法

6.1 Linked 等长分段传输

复用 8.1 中的 request、buffer cache、等待和清理流程,将普通 start 替换为下面的 linked 调用:

ret = im_dma_start_linked(&s_demo_dma,
                          s_demo_src,
                          s_demo_dst,
                          DEMO_DMA_SIZE,
                          16U);

4096 字节被拆分为 16 个 256 字节 segment,每段满足 AUTO 的 8 字节约束。成功 callback 表示整条链的终态完成;中间层分配和复用所需的 16 个 descriptor。

6.2 阻塞内存拷贝

下面的示例由服务所有者在线程 context 中执行,负责本次服务生命周期。服务内部维护 buffer cache;示例将拷贝或数据比对失败转换为业务错误 IM_ERR_IO。

#include <string.h>
#include "im_cache.h"
#include "im_dmacpy.h"

#define DEMO_COPY_SIZE 4096U

static uint8_t s_copy_src[DEMO_COPY_SIZE]
    __attribute__((aligned(IM_CACHE_MAX_LINE_SIZE_BYTES)));
static uint8_t s_copy_dst[DEMO_COPY_SIZE]
    __attribute__((aligned(IM_CACHE_MAX_LINE_SIZE_BYTES)));

error_code_t run_blocking_copy(void)
{
    error_code_t ret = im_dmacpy_init();
    error_code_t deinit_ret;

    if (ret != IM_OK) {
        return ret;
    }
    memset(s_copy_src, 0x5A, sizeof(s_copy_src));
    memset(s_copy_dst, 0, sizeof(s_copy_dst));

    if (im_dmacpy_timeout(s_copy_dst, s_copy_src,
                          sizeof(s_copy_src), 1000U) != s_copy_dst) {
        ret = IM_ERR_IO;
    } else if (memcmp(s_copy_dst, s_copy_src, sizeof(s_copy_src)) != 0) {
        ret = IM_ERR_IO;
    }

    deinit_ret = im_dmacpy_deinit();
    return (ret == IM_OK) ? deinit_ret : ret;
}

需要无限等待时,将拷贝调用替换为 im_dmacpy(s_copy_dst, s_copy_src, sizeof(s_copy_src))。产品中可以在服务启动时 init 一次,在全部使用者停止拷贝后 deinit,避免每笔拷贝重复申请和释放请求池。