API GUIDE / DMA / OVERVIEW

G32S10 DMA API 概述

模块概述:本文档定义 G32S10 SDK DMA 硬件抽象层和低层接口,覆盖以下两层:

1. 文档范围

本文档定义 G32S10 SDK DMA 硬件抽象层和低层接口,覆盖以下两层:

  • HAL DMA:供上层模块和高级用户使用的硬件操作层。

  • LL DMA:面向 DMA 控制器寄存器访问的低层接口。

对应文件:

类型路径
HAL 头文件code/include/drivers/g32s10/dma/hal_dma.h
HAL 实现code/drivers/g32s10/dma/hal_dma.c
LL 头文件code/include/drivers/g32s10/dma/ll_dma.h
寄存器定义code/include/drivers/g32s10/dma/dma_regs.h

补充说明:

  • LL 层公开接口均为 static inline,不维护软件状态。

  • LL 层不返回 SDK 错误码;需要读取寄存器值时返回 uint32_t。

  • HAL 层负责参数校验、通道分配、descriptor 编排、非 descriptor 直写寄存器启动、状态管理、pending 扫描和统一返回码。

  • HAL 是高级用户或上层模块的硬件接口;上层负责自己的 DMA 请求生命周期和 SoC IRQ owner。

  • 当前 DMA HAL 支持 8-word descriptor 模式和非 descriptor 单次传输模式。

2. 分层关系

Application / Advanced Example
              |
              v
           HAL DMA
              |
              v
           LL DMA
              |
              v
        PDMA0 / PDMA1 Registers

Advanced caller ----------------------> HAL DMA ----> LL DMA

分层职责如下:

  • HAL DMA

    • 封装 DMA 控制器初始化、通道打开/关闭、普通传输、链式传输、循环传输、停止、当前地址查询和诊断 dump。

    • 根据 hal_dma_request_t 选择 DMA 控制器和通道。

    • 维护 hal_dma_handle_t 中的请求源、总线宽度、单元大小、事件回调槽、通道号、descriptor 和传输地址范围。

    • 在调用方已接入 SoC IRQ 后,扫描 controller 的 DIRQP/DIP pending、分发 channel event 并调用用户回调。

    • 不注册、使能或释放 SoC IRQ。

  • LL DMA

    • 提供寄存器访问入口,例如全局使能、通道状态、descriptor 地址、doorbell、当前源/目的地址和诊断寄存器读取。

    • 不做请求源分类、descriptor 构造、通道分配或状态管理。

    • 公开接口首参数为 dma_regs_t *dma。

  • dma_regs.h

    • 定义 PDMA0/PDMA1 基地址实例、寄存器结构体布局、通道数量和寄存器位域 mask/shift。

3. 资源模型与公开类型

3.1 模块基地址与实例

实例基地址宏基地址说明
PDMA0PDMA0_IOBASE0x13420000HP-domain PDMA 控制器。
PDMA1PDMA1_IOBASE0x13610000LP-domain PDMA 控制器。

dma_regs.h 中提供如下实例宏:

#define PDMA0  ((dma_regs_t *)(uintptr_t)PDMA0_IOBASE)
#define PDMA1  ((dma_regs_t *)(uintptr_t)PDMA1_IOBASE)

说明:

  • 实例宏使用 (uintptr_t) 做整数地址到指针的中间转换,避免部分编译器对整数直接转指针产生告警。

  • HAL 内部通过请求源选择 PDMA0 或 PDMA1,上层通常不直接操作 dma_regs_t *。

3.2 DMA 硬件能力摘要

项目说明
控制器数量2 个,分别为 PDMA0 和 PDMA1。
每控制器通道数32 个通道。
传输模式支持 8-word descriptor 模式和非 descriptor 模式。
支持传输类型非 descriptor 单次传输、普通单 descriptor、linked descriptor、cyclic descriptor。
请求源覆盖 TPC、AES、HASH、MEM、UART、SSI、CAN、I2C、PWMA、ADC、AIC 等请求。
回调模型调用方将 normal/descriptor IRQ 转交 HAL;HAL 在 ISR context 调用 hal_dma_callback_t 并记录 event snapshot。
当前地址读取忙状态下会有限次重试读取硬件当前地址;持续异常返回 IM_ERR_HARDWARE,不会无限等待。
cache 一致性HAL 维护 descriptor cache;调用方负责自己的数据 buffer cache 一致性。

3.3 HAL 公开宏

宏使用说明取值/约束
HAL_DMA_CHANNEL_COUNTHAL 公开的单控制器通道数量。等于 DMA_CHANNEL_COUNT,当前为 32。
HAL_DMA_DESCRIPTOR_COUNT普通单次传输默认 descriptor 数量。1。
HAL_DMA_MAX_DESCRIPTOR_COUNTlinked/cyclic 当前支持的最大 descriptor 数量。128。
HAL_DMA_UNIT_SIZE_AUTO选择自主传输大小模式,使用 DCM.TSZ=7、DCM.RDIL=5。0;要求 32-bit 总线宽度,传输长度至少 8 字节且为 8 的倍数。
HAL_DMA_DESCRIPTOR_SIZE对外公开的单个 descriptor buffer 占用大小。32 字节。
HAL_DMA_DESCRIPTOR_ALIGNdescriptor buffer 地址最低对齐要求。IM_CACHE_MAX_LINE_SIZE_BYTES。
HAL_DMA_DESCRIPTOR_BUFFER_SIZE(count)计算可容纳 count 个 descriptor 的 buffer 字节数。结果按 HAL_DMA_DESCRIPTOR_ALIGN 对齐;不保证 linked/cyclic 的 4 KiB DOA 窗口约束。
CONFIG_DMA_ENABLE_DEBUG_HELPERSDMA 模块 debug helper 默认开关。未由 Kconfig 定义时默认 0U。

调试 dump 接口受双开关控制:

(CONFIG_SDK_ENABLE_DEBUG_HELPERS) && (CONFIG_DMA_ENABLE_DEBUG_HELPERS)

未同时启用时,dump API 仍然存在,但返回 IM_ERR_UNSUPPORTED。

3.4 HAL 返回状态约定

HAL 层普通可失败接口统一返回 SDK 公共错误码类型:

error_code_t

常用返回值如下:

返回值说明
IM_OK操作成功。
IM_ERR_INVALID_PARAM参数为空、请求源非法、总线宽度非法、长度不满足约束或地址不在当前范围内。
IM_ERR_BUSY无空闲通道、目标资源忙或关闭忙状态句柄。
IM_ERR_NOT_READY句柄/descriptor 状态不满足当前操作要求。
IM_ERR_HARDWARE当前硬件地址在有限次重试内始终未落入 HAL 记录的地址范围。
IM_ERR_TIMEOUT当前 HAL 公开接口未主动使用;示例代码等待完成时可能使用。
IM_ERR_UNSUPPORTED当前配置不支持该操作,例如未启用 debug helper 时调用 dump。

3.5 DMA 请求源类型

typedef enum {
    HAL_DMA_REQUEST_TPC_SHIFT_TX = 0x00U,
    ...
    HAL_DMA_REQUEST_AIC_RX_LOOPBACK = 0x47U,
} hal_dma_request_t;

请求源用于描述 DMA 硬件请求来源,并决定:

  • 使用 PDMA0 还是 PDMA1 控制器。

  • 源地址和目的地址是否按内存地址递增。

  • 是否需要 CAN RX 的 SACIDX 源地址 wrap/compare 配置。

当前 HAL 控制器选择规则:

控制器请求源范围
PDMA1AIC_TX/RX/RX_LOOPBACK、SSI0_TX/RX、I2C0_TX/RX、UART0_TX/RX、UART1_TX/RX、SSI_SLV0_TX/RX。
PDMA0其他合法请求源,包括 HAL_DMA_REQUEST_MEM。

说明:

  • HAL_DMA_REQUEST_RESERVE 不作为合法请求源开放。

  • 非法请求源会在 hal_dma_open() 中返回 IM_ERR_INVALID_PARAM。

  • hal_dma_controller_t 公开 HAL_DMA_CONTROLLER_PDMA0 和 HAL_DMA_CONTROLLER_PDMA1,与 PDMA0 / PDMA1 寄存器实例语义对应;它用于 hal_dma_irq_handler() 的 controller 参数。

  • 外设驱动必须根据自身实例和方向选择正确请求源,DMA HAL 不配置外设内部寄存器。

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

4.1 地址语义

  • hal_dma_virt_to_phys() 先调用 im_mmio_virt_to_phys(),按 IM_MMIO_PHYS_ADDR_MASK(0x1FFFFFFF)取得 DMA/物理地址,再转换为 uint32_t。

  • Descriptor 中的 dsa/dta 和通道 DDA 使用 DMA/物理地址语义。

  • handle->src_start/dst_start/src_end/dst_end 记录 DMA/物理地址范围。

  • 如果后续引入 MMU、uncached alias 或地址映射,HAL 内部地址比较语义仍应保持一致。

4.2 传输长度和单元大小

  • len 必须大于 0。

  • 固定单元大小模式下,len 必须不小于 unit_size 且能被其整除;unit_size 必须不小于总线宽度对应的字节数,且能被该字节数整除。

  • HAL_DMA_UNIT_SIZE_AUTO 要求 32-bit 总线宽度,len 必须至少为 8 字节且为 8 的倍数。

  • linked/cyclic 模式中,len 必须能被 count 整除。

  • linked/cyclic 单段长度在固定单元大小模式下必须不小于 unit_size 且能被其整除;AUTO 模式下必须至少为 8 字节且为 8 的倍数。

  • 当前实现要求每个 descriptor 或非 descriptor 单次传输的 segment_len / normalized_unit_size < (1U << 24),不满足时返回 IM_ERR_INVALID_PARAM。单 descriptor 和非 descriptor 的 segment_len 为 len,linked/cyclic 为 len / count;归一化单元大小在 AUTO 模式下为 1,固定大小 8 归一到 4、>=128 归一到 128,其余保持原值。因此 AUTO 模式的单段长度还必须小于 16 MiB。

  • AUTO 模式以及 HAL_DMA_REQUEST_AES0_TX、HAL_DMA_REQUEST_AES1_TX、HAL_DMA_REQUEST_HASH_TX 请求均使用 DCM.TSZ=7、DCM.RDIL=5,DTC 按字节数写入 segment_len;其他请求按 segment_len / normalized_unit_size 写入。AES/HASH 请求配置的固定 unit_size 仍参与前述长度和总线宽度校验。

4.3 中断和回调

  • HAL 不直接调用 im_irq_request()、im_irq_enable() 或 im_irq_release()。调用方必须为实际使用的 controller 注册并使能 normal 和 descriptor IRQ:PDMA0 使用 IRQ_PDMA0 与 IRQ_PDMA0_DIP,PDMA1 使用 IRQ_PDMA1 与 IRQ_PDMA1_DIP;每条 IRQ 都必须转交给对应的 hal_dma_irq_handler()。

  • 同一 firmware image 中,每条 PDMA IRQ 和每个 controller 的 channel 管理权都必须有唯一 owner;调用方不得重复注册同一 IRQ 或与其他 owner 共享 channel。

  • hal_dma_irq_handler() 同时处理 DIRQP 和 descriptor DIP。确认 channel 由 HAL 持有且 handle 有效后,先通过 LL 层同时清除两类 pending,再单次读取 DCS 分类;上层不得直接拼接 DIC 的反向 W0C 写值。

  • HAL 只处理当前已分配的 channel。terminal 和 active-stop 路径会同时清除 DIRQP/DIP;其后的 CPU sibling IRQ 若仍到达,读取不到 controller pending 时不会产生 callback。终止后是否会出现新的 controller-level DIP pending 仍需硅验证。

  • DIP && !DCS.TT 是 descriptor 完成事件:handle 保持 BUSY,descriptor callback 可通过 hal_dma_get_last_event() 读取最近完成的 descriptor index。

  • DCS.TT 是整次完成,优先于同一 IRQ 中的 DIP/DIRQP,只回调一次;在 status 分类前两类 pending 均已清除,完成后 HAL 将句柄状态切回 READY 并释放通道。

  • DCS.AR 或 DCS.HLT 归类为 HAL_DMA_EVENT_ERROR,完成 callback 前也会释放通道。

  • DIP 是每通道 pending bit 而非事件计数器。hal_dma_event_info_t.descriptor_events_coalesced 仅报告能由 CDOA index 跳跃检测到的合并,false 不保证没有合并。

4.4 Debug helper 开关

  • hal_dma_dump_regs() 和 hal_dma_dump_desc() 始终声明并实现。

  • 未同时启用 CONFIG_SDK_ENABLE_DEBUG_HELPERS 和 CONFIG_DMA_ENABLE_DEBUG_HELPERS 时返回 IM_ERR_UNSUPPORTED。

  • DMA 模块 Kconfig 中的 debug helper 选项为 DMA_ENABLE_DEBUG_HELPERS。

4.5 文件组织

DMA 模块采用当前 SDK 的平铺结构:

code/drivers/g32s10/dma/CMakeLists.txt
code/drivers/g32s10/dma/Kconfig
code/drivers/g32s10/dma/hal_dma.c
code/include/drivers/g32s10/dma/dma_regs.h
code/include/drivers/g32s10/dma/hal_dma.h
code/include/drivers/g32s10/dma/ll_dma.h

说明:

  • 不使用 hal/ 或 ll/ 子目录。

  • DMA 示例代码不放入 DMA driver CMake 中长期编译。

  • code/examples/hal/dma_memcpy_example.c 是高级 HAL bring-up/reference 示例。

  • 当前 memcpy 验证示例中 non-descriptor、single descriptor、linked 和 cyclic 模式均使用 4096 字节传输长度。

5. 典型用法

本节给出 HAL 的高级 bring-up/reference 用法。直接使用 HAL 的调用方必须确定实际使用 controller 的 normal/DIP IRQ 对的唯一 owner,并在自己的 SoC IRQ entry 中调用hal_dma_irq_handler();不得重复注册同一 IRQ。

5.1 内存到内存普通传输

static volatile bool dma_done;
static uint8_t dma_desc[HAL_DMA_DESCRIPTOR_BUFFER_SIZE(HAL_DMA_DESCRIPTOR_COUNT)]
    __attribute__((aligned(HAL_DMA_DESCRIPTOR_ALIGN)));

static void dma_done_callback(hal_dma_handle_t *handle, void *user_ctx)
{
    (void)handle;
    (void)user_ctx;
    dma_done = true;
}

error_code_t run_memcpy_dma(void *src, void *dst, uint32_t len)
{
    hal_dma_handle_t dma_handle;
    hal_dma_config_t dma_config = {
        .request = HAL_DMA_REQUEST_MEM,
        .bus_width = HAL_DMA_BUS_WIDTH_32BIT,
        .unit_size = 4U,
        .descriptor = dma_desc,
        .descriptor_capacity = HAL_DMA_DESCRIPTOR_COUNT,
    };
    error_code_t ret;

    /* The caller must route this controller's normal and DIP IRQs to HAL. */

    ret = hal_dma_open(&dma_handle, &dma_config);
    if (ret != IM_OK) {
        return ret;
    }
    ret = hal_dma_callback_register(&dma_handle,
                                    HAL_DMA_CALLBACK_TRANSFER_COMPLETE,
                                    dma_done_callback);
    if (ret != IM_OK) {
        (void)hal_dma_close(&dma_handle);
        return ret;
    }
    ret = hal_dma_callback_register(&dma_handle,
                                    HAL_DMA_CALLBACK_ERROR,
                                    dma_done_callback);
    if (ret != IM_OK) {
        (void)hal_dma_close(&dma_handle);
        return ret;
    }

    dma_done = false;
    ret = hal_dma_start(&dma_handle, src, dst, len);
    if (ret != IM_OK) {
        (void)hal_dma_close(&dma_handle);
        return ret;
    }

    while (!dma_done) {
    }

    return hal_dma_close(&dma_handle);
}

补充说明:

  • 如果源/目的 buffer 经过 D-cache,调用方需要在 DMA 读源 buffer 前 flush,在 DMA 写目的 buffer 后 invalidate。

  • descriptor buffer 由调用方提供,HAL 负责填写 descriptor 并 flush descriptor cache。

  • HAL_DMA_DESCRIPTOR_ALIGN 是 descriptor buffer 的最低对齐要求;linked/cyclic 还要求本次使用的 descriptor 数组不跨 4 KiB DOA 编码窗口。

  • linked/cyclic 场景建议为 descriptor buffer 使用 4 KiB 对齐,避免跨 4 KiB DOA 窗口。

  • 示例省略了超时处理,正式代码应结合业务场景增加等待超时。

5.2 内存到内存非 descriptor 传输

static volatile bool dma_done;

static void dma_done_callback(hal_dma_handle_t *handle, void *user_ctx)
{
    (void)handle;
    (void)user_ctx;
    dma_done = true;
}

error_code_t run_memcpy_dma_nondescriptor(void *src, void *dst, uint32_t len)
{
    hal_dma_handle_t dma_handle;
    hal_dma_config_t dma_config = {
        .request = HAL_DMA_REQUEST_MEM,
        .bus_width = HAL_DMA_BUS_WIDTH_32BIT,
        .unit_size = 4U,
        .descriptor = NULL,
        .descriptor_capacity = 0U,
    };
    error_code_t ret;

    /* The caller must route this controller's normal and DIP IRQs to HAL. */

    ret = hal_dma_open(&dma_handle, &dma_config);
    if (ret != IM_OK) {
        return ret;
    }
    ret = hal_dma_callback_register(&dma_handle,
                                    HAL_DMA_CALLBACK_TRANSFER_COMPLETE,
                                    dma_done_callback);
    if (ret != IM_OK) {
        (void)hal_dma_close(&dma_handle);
        return ret;
    }
    ret = hal_dma_callback_register(&dma_handle,
                                    HAL_DMA_CALLBACK_ERROR,
                                    dma_done_callback);
    if (ret != IM_OK) {
        (void)hal_dma_close(&dma_handle);
        return ret;
    }

    dma_done = false;
    ret = hal_dma_start_nondescriptor(&dma_handle, src, dst, len);
    if (ret != IM_OK) {
        (void)hal_dma_close(&dma_handle);
        return ret;
    }

    while (!dma_done) {
    }

    return hal_dma_close(&dma_handle);
}

补充说明:

  • 非 descriptor 模式不需要 descriptor buffer,descriptor 应为 NULL,descriptor_capacity 应为 0。

  • 如果源/目的 buffer 经过 D-cache,调用方仍需要在 DMA 读源 buffer 前 flush,在 DMA 写目的 buffer 后 invalidate。

  • 当前非 descriptor 模式暂不支持 CAN RX 请求源。

5.3 Linked 内存压力传输

#define LINKED_DMA_DESC_COUNT 16U
#define LINKED_DMA_STRESS_SIZE 4096U

static uint8_t linked_dma_desc[HAL_DMA_DESCRIPTOR_BUFFER_SIZE(LINKED_DMA_DESC_COUNT)]
    __attribute__((aligned(4096)));

hal_dma_config_t dma_config = {
    .request = HAL_DMA_REQUEST_MEM,
    .bus_width = HAL_DMA_BUS_WIDTH_32BIT,
    .unit_size = 4U,
    .descriptor = linked_dma_desc,
    .descriptor_capacity = LINKED_DMA_DESC_COUNT,
};

ret = hal_dma_open(&dma_handle, &dma_config);
if (ret != IM_OK) {
    return ret;
}
ret = hal_dma_callback_register(&dma_handle,
                                HAL_DMA_CALLBACK_TRANSFER_COMPLETE,
                                dma_done_callback);
if (ret != IM_OK) {
    (void)hal_dma_close(&dma_handle);
    return ret;
}

ret = hal_dma_start_linked(&dma_handle,
                           src_buffer,
                           dst_buffer,
                           LINKED_DMA_STRESS_SIZE,
                           LINKED_DMA_DESC_COUNT);

适用场景:

  • 需要使用多个 descriptor 压力验证分段搬运。

  • 需要检查 descriptor 链配置或验证 DESC_DTC_DOA 链接行为。

约束:

  • LINKED_DMA_STRESS_SIZE 必须能被 LINKED_DMA_DESC_COUNT 整除。

  • LINKED_DMA_DESC_COUNT 当前最大为 128。

5.4 Cyclic 压力传输并在 descriptor 边界停止

#define CYCLIC_DMA_DESC_COUNT 16U
#define CYCLIC_DMA_STRESS_SIZE 4096U

ret = hal_dma_start_cyclic(&dma_handle,
                           src_buffer,
                           dst_buffer,
                           CYCLIC_DMA_STRESS_SIZE,
                           CYCLIC_DMA_DESC_COUNT);
if (ret != IM_OK) {
    return ret;
}

ret = hal_dma_stop_after_current_transfer(&dma_handle,
                                          &src_buffer[CYCLIC_DMA_STRESS_SIZE -
                                                      (CYCLIC_DMA_STRESS_SIZE / CYCLIC_DMA_DESC_COUNT)]);

适用场景:

  • 使用多个 descriptor 压力验证环形缓冲区。

  • 源地址递增的持续传输,希望在 descriptor 边界停止。

说明:

  • stop_src_addr 必须是当前源侧地址范围内的地址。

  • 固定源地址的外设 RX 不支持通过本接口按 descriptor 边界停止;应使用 hal_dma_stop()。

6. 推荐关注的错误点

场景建议检查项
hal_dma_open() 返回 IM_ERR_INVALID_PARAM检查 request 是否为合法请求源,bus_width、unit_size、descriptor buffer 指针、数量和对齐是否满足约束。
hal_dma_start*() 返回 IM_ERR_BUSY检查是否无空闲通道,或同一个句柄仍处于 BUSY。
hal_dma_start_nondescriptor() 返回 IM_ERR_UNSUPPORTED检查请求源是否为当前暂不支持的 CAN RX。
descriptor 模式启动失败检查 descriptor buffer 指针、数量和对齐是否满足约束。
linked/cyclic 启动失败检查 count、len % count、单段长度、descriptor buffer 容量和 4 KiB DOA 窗口。
cyclic 无法停止检查传入 hal_dma_stop_after_current_transfer() 的地址是否为源侧地址,且落在当前源地址范围内;固定源地址的外设 RX 应使用 hal_dma_stop()。
数据不一致检查源 buffer flush、目的 buffer invalidate、地址方向、外设 FIFO 地址和 bus_width/unit_size。
dump 返回 IM_ERR_UNSUPPORTED检查是否同时启用 CONFIG_SDK_ENABLE_DEBUG_HELPERS 和 CONFIG_DMA_ENABLE_DEBUG_HELPERS。
当前地址读取返回 IM_ERR_HARDWARE硬件 DSA/DTA 在有限次重试内未落入 HAL 记录的地址范围;检查地址方向、DMA 状态和寄存器值。