[toc]
drivers/dma/stm32/stm32-dmamux.c STM32 DMAMUX (DMA多路复用器) 驱动 此代码片段是STM32 DMAMUX的完整平台驱动程序。它的核心作用是充当一个硬件”路由交换机”的软件抽象层 。在复杂的STM32微控制器(如STM32H7系列)中, 有大量的外设(如SPI, I2C, UART)可以发起DMA请求, 但实际的DMA控制器(DMA1, DMA2)通道数量是有限的。DMAMUX就是处在这两者之间的一个硬件单元, 它能将任意一个外设的请求信号连接到任意一个空闲的DMA通道上。
这个驱动的根本原理是实现内核DMA框架中的dma_router(DMA路由)模式 。当一个外设驱动(如SPI驱动)请求一个DMA通道时, 它并不知道DMAMUX的存在。它在设备树中的dmas属性指向的是DMAMUX设备, 而不是最终的DMA控制器。内核的DMA框架看到这个dmas属性后, 会识别出这是一个DMA路由, 于是它不会直接与DMA控制器驱动交互, 而是调用这个DMAMUX驱动注册的route_allocate回调函数 。
stm32_dmamux_route_allocate函数就是这个驱动的”大脑”, 它负责:
从大量可用的DMAMUX输出通道中, 找到一个空闲的通道 。
通过写硬件寄存器, 将发起请求的外设信号连接到这个空闲的DMAMUX通道上 。
巧妙地修改(rewrite)原始的DMA请求规格(dma_spec) , 将其目标从DMAMUX设备重定向 到实际的、承载这个通道的DMA控制器(如DMA1)以及该控制器上的具体流(stream)编号。
将修改后的请求规格返回给DMA框架, 框架再用这个新的规格去与最终的DMA控制器驱动交互, 完成后续的DMA传输设置。
通过这种方式, 该驱动完美地将DMAMUX的复杂性对上层驱动隐藏了起来, 实现了硬件的解耦和抽象。
数据结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #define STM32_DMAMUX_CCR(x) (0x4 * (x)) #define STM32_DMAMUX_MAX_DMA_REQUESTS 32 #define STM32_DMAMUX_MAX_REQUESTS 255 struct stm32_dmamux { u32 master; u32 request; u32 chan_id; }; struct stm32_dmamux_data { struct dma_router dmarouter ; struct clk *clk ; void __iomem *iomem; u32 dma_requests; u32 dmamux_requests; spinlock_t lock; DECLARE_BITMAP(dma_inuse, STM32_DMAMUX_MAX_DMA_REQUESTS); u32 ccr[STM32_DMAMUX_MAX_DMA_REQUESTS]; u32 dma_reqs[]; };
stm32_dmamux_probe: 驱动探测/初始化函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 static void stm32_dmamux_free (struct device *dev, void *route_data) { struct stm32_dmamux_data *dmamux = dev_get_drvdata(dev); struct stm32_dmamux *mux = route_data; unsigned long flags; spin_lock_irqsave(&dmamux->lock, flags); stm32_dmamux_write(dmamux->iomem, STM32_DMAMUX_CCR(mux->chan_id), 0 ); clear_bit(mux->chan_id, dmamux->dma_inuse); pm_runtime_put_sync(dev); spin_unlock_irqrestore(&dmamux->lock, flags); dev_dbg(dev, "Unmapping DMAMUX(%u) to DMA%u(%u)\n" , mux->request, mux->master, mux->chan_id); kfree(mux); } static int stm32_dmamux_probe (struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; const struct of_device_id *match ; struct device_node *dma_node ; struct stm32_dmamux_data *stm32_dmamux ; void __iomem *iomem; struct reset_control *rst ; int i, count, ret; u32 dma_req; if (!node) return -ENODEV; count = device_property_count_u32(&pdev->dev, "dma-masters" ); if (count < 0 ) { dev_err(&pdev->dev, "Can't get DMA master(s) node\n" ); return -ENODEV; } stm32_dmamux = devm_kzalloc(&pdev->dev, sizeof (*stm32_dmamux) + sizeof (u32) * (count + 1 ), GFP_KERNEL); if (!stm32_dmamux) return -ENOMEM; dma_req = 0 ; for (i = 1 ; i <= count; i++) { dma_node = of_parse_phandle(node, "dma-masters" , i - 1 ); match = of_match_node(stm32_stm32dma_master_match, dma_node); if (!match) { dev_err(&pdev->dev, "DMA master is not supported\n" ); of_node_put(dma_node); return -EINVAL; } if (of_property_read_u32(dma_node, "dma-requests" , &stm32_dmamux->dma_reqs[i])) { stm32_dmamux->dma_reqs[i] = STM32_DMAMUX_MAX_DMA_REQUESTS; } dma_req += stm32_dmamux->dma_reqs[i]; of_node_put(dma_node); } if (dma_req > STM32_DMAMUX_MAX_DMA_REQUESTS) { dev_err(&pdev->dev, "Too many DMA Master Requests to manage\n" ); return -ENODEV; } stm32_dmamux->dma_requests = dma_req; stm32_dmamux->dma_reqs[0 ] = count; if (device_property_read_u32(&pdev->dev, "dma-requests" , &stm32_dmamux->dmamux_requests)) { stm32_dmamux->dmamux_requests = STM32_DMAMUX_MAX_REQUESTS; } pm_runtime_get_noresume(&pdev->dev); iomem = devm_platform_ioremap_resource(pdev, 0 ); if (IS_ERR(iomem)) return PTR_ERR(iomem); spin_lock_init(&stm32_dmamux->lock); stm32_dmamux->clk = devm_clk_get(&pdev->dev, NULL ); if (IS_ERR(stm32_dmamux->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(stm32_dmamux->clk), "Missing clock controller\n" ); ret = clk_prepare_enable(stm32_dmamux->clk); if (ret < 0 ) { dev_err(&pdev->dev, "clk_prep_enable error: %d\n" , ret); return ret; } rst = devm_reset_control_get(&pdev->dev, NULL ); if (IS_ERR(rst)) { ret = PTR_ERR(rst); if (ret == -EPROBE_DEFER) goto err_clk; } else if (count > 1 ) { reset_control_assert(rst); udelay(2 ); reset_control_deassert(rst); } stm32_dmamux->iomem = iomem; stm32_dmamux->dmarouter.dev = &pdev->dev; stm32_dmamux->dmarouter.route_free = stm32_dmamux_free; platform_set_drvdata(pdev, stm32_dmamux); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); for (i = 0 ; i < stm32_dmamux->dma_requests; i++) stm32_dmamux_write(stm32_dmamux->iomem, STM32_DMAMUX_CCR(i), 0 ); pm_runtime_put(&pdev->dev); ret = of_dma_router_register(node, stm32_dmamux_route_allocate, &stm32_dmamux->dmarouter); if (ret) goto pm_disable; return 0 ; pm_disable: pm_runtime_disable(&pdev->dev); err_clk: clk_disable_unprepare(stm32_dmamux->clk); return ret; }
stm32_dmamux_route_allocate: 核心路由分配函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 static inline u32 stm32_dmamux_read (void __iomem *iomem, u32 reg) { return readl_relaxed(iomem + reg); } static inline void stm32_dmamux_write (void __iomem *iomem, u32 reg, u32 val) { writel_relaxed(val, iomem + reg); } static void *stm32_dmamux_route_allocate (struct of_phandle_args *dma_spec, struct of_dma *ofdma) { struct platform_device *pdev = of_find_device_by_node(ofdma->of_node); struct stm32_dmamux_data *dmamux = platform_get_drvdata(pdev); struct stm32_dmamux *mux ; u32 i, min, max; int ret; unsigned long flags; if (dma_spec->args_count != 3 ) { dev_err(&pdev->dev, "invalid number of dma mux args\n" ); return ERR_PTR(-EINVAL); } if (dma_spec->args[0 ] > dmamux->dmamux_requests) { dev_err(&pdev->dev, "invalid mux request number: %d\n" , dma_spec->args[0 ]); return ERR_PTR(-EINVAL); } mux = kzalloc(sizeof (*mux), GFP_KERNEL); if (!mux) return ERR_PTR(-ENOMEM); spin_lock_irqsave(&dmamux->lock, flags); mux->chan_id = find_first_zero_bit(dmamux->dma_inuse, dmamux->dma_requests); if (mux->chan_id == dmamux->dma_requests) { spin_unlock_irqrestore(&dmamux->lock, flags); dev_err(&pdev->dev, "Run out of free DMA requests\n" ); ret = -ENOMEM; goto error_chan_id; } set_bit(mux->chan_id, dmamux->dma_inuse); spin_unlock_irqrestore(&dmamux->lock, flags); for (i = 1 , min = 0 , max = dmamux->dma_reqs[i]; i <= dmamux->dma_reqs[0 ]; min += dmamux->dma_reqs[i], max += dmamux->dma_reqs[++i]) if (mux->chan_id < max) break ; mux->master = i - 1 ; dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters" , i - 1 ); if (!dma_spec->np) { dev_err(&pdev->dev, "can't get dma master\n" ); ret = -EINVAL; goto error; } spin_lock_irqsave(&dmamux->lock, flags); ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0 ) { spin_unlock_irqrestore(&dmamux->lock, flags); goto error; } spin_unlock_irqrestore(&dmamux->lock, flags); mux->request = dma_spec->args[0 ]; dma_spec->args[3 ] = dma_spec->args[2 ] | mux->chan_id << 16 ; dma_spec->args[2 ] = dma_spec->args[1 ]; dma_spec->args[1 ] = 0 ; dma_spec->args[0 ] = mux->chan_id - min; dma_spec->args_count = 4 ; stm32_dmamux_write(dmamux->iomem, STM32_DMAMUX_CCR(mux->chan_id), mux->request); dev_dbg(&pdev->dev, "Mapping DMAMUX(%u) to DMA%u(%u)\n" , mux->request, mux->master, mux->chan_id); return mux; error: clear_bit(mux->chan_id, dmamux->dma_inuse); error_chan_id: kfree(mux); return ERR_PTR(ret); }
STM32 DMAMUX 平台驱动程序注册 此代码片段的核心作用是定义并向Linux内核注册一个平台驱动程序(platform_driver) , 这个驱动专门用于管理ST微控制器(特别是STM32H7系列)上的DMAMUX 外设。DMAMUX是”DMA多路复用器”(DMA Multiplexer)的缩写, 它是一个硬件路由单元, 允许将数量众多的外设DMA请求灵活地连接到数量有限的实际DMA控制器通道上。
该驱动程序的结构是Linux内核中用于处理SoC片上外设的标准范例。它的原理如下:
定义匹配规则 : 通过of_device_id表声明一个compatible字符串 (“st,stm32h7-dmamux”)。当内核在解析设备树(Device Tree)时, 如果发现一个硬件节点的compatible属性与这个字符串完全匹配, 内核就确信stm32_dmamux_driver是管理该硬件的正确驱动。
定义核心操作 : 它将驱动的核心逻辑函数指针(如probe函数stm32_dmamux_probe)和电源管理回调函数集(stm32_dmamux_pm_ops)打包进一个platform_driver结构体中。probe函数会在匹配成功后被内核调用, 负责初始化DMAMUX硬件; 电源管理函数则负责在系统挂起/恢复或运行时空闲时关闭/打开DMAMUX的时钟以节省功耗。
注册与初始化 : 在内核启动的早期阶段(由arch_initcall指定), stm32_dmamux_init函数会被调用。它唯一的工作就是调用platform_driver_register, 将整个stm32_dmamux_driver结构体提交给内核的平台总线核心。从这一刻起, 该驱动就进入了”待命”状态, 等待内核为其派发匹配的设备。
对于STM32H750这样的单核系统, 这个驱动依然至关重要。它使得其他外设驱动(如SPI, I2C)无需关心DMAMUX的底层寄存器操作, 只需通过内核提供的标准DMA API来请求一个DMA通道, 而DMAMUX驱动和DMA引擎框架会自动处理复杂的请求路由和资源分配, 实现了硬件的抽象和驱动的解耦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 static const struct dev_pm_ops stm32_dmamux_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(stm32_dmamux_suspend, stm32_dmamux_resume) SET_RUNTIME_PM_OPS(stm32_dmamux_runtime_suspend, stm32_dmamux_runtime_resume, NULL ) }; static const struct of_device_id stm32_dmamux_match [] = { { .compatible = "st,stm32h7-dmamux" }, {}, }; static struct platform_driver stm32_dmamux_driver = { .probe = stm32_dmamux_probe, .driver = { .name = "stm32-dmamux" , .of_match_table = stm32_dmamux_match, .pm = &stm32_dmamux_pm_ops, }, }; static int __init stm32_dmamux_init (void ) { return platform_driver_register(&stm32_dmamux_driver); } arch_initcall(stm32_dmamux_init); MODULE_DESCRIPTION("DMA Router driver for STM32 DMA MUX" ); MODULE_AUTHOR("M'boumba Cedric Madianga <cedric.madianga@gmail.com>" ); MODULE_AUTHOR("Pierre-Yves Mordret <pierre-yves.mordret@st.com>" );
drivers/dma/stm32/stm32-dma.c STM32 DMA 驱动寄存器与常量定义 底层寄存器访问函数 这两个静态内联函数是所有硬件交互的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static u32 stm32_dma_read (struct stm32_dma_device *dmadev, u32 reg) { return readl_relaxed(dmadev->base + reg); } static void stm32_dma_write (struct stm32_dma_device *dmadev, u32 reg, u32 val) { writel_relaxed(val, dmadev->base + reg); }
中断状态与清除寄存器定义 这组宏用于定位与特定DMA流(Stream, 在驱动中称为chan或channel)关联的中断标志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #define STM32_DMA_LISR 0x0000 #define STM32_DMA_HISR 0x0004 #define STM32_DMA_ISR(n) (((n) & 4) ? STM32_DMA_HISR : STM32_DMA_LISR) #define STM32_DMA_LIFCR 0x0008 #define STM32_DMA_HIFCR 0x000c #define STM32_DMA_IFCR(n) (((n) & 4) ? STM32_DMA_HIFCR : STM32_DMA_LIFCR) #define STM32_DMA_TCI BIT(5) #define STM32_DMA_HTI BIT(4) #define STM32_DMA_TEI BIT(3) #define STM32_DMA_DMEI BIT(2) #define STM32_DMA_FEI BIT(0) #define STM32_DMA_MASKI (STM32_DMA_TCI \ | STM32_DMA_TEI \ | STM32_DMA_DMEI \ | STM32_DMA_FEI) #define STM32_DMA_FLAGS_SHIFT(n) ({ typeof(n) (_n) = (n); \ (((_n) & 2) << 3) | (((_n) & 1) * 6); })
DMA流 (Stream) 寄存器定义 这组宏定义了每个DMA流的独立寄存器组的地址和位域。STM32 DMA控制器有多个流(例如8个), 每个流都有一套独立的配置、地址和数据计数寄存器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #define STM32_DMA_SCR(x) (0x0010 + 0x18 * (x)) #define STM32_DMA_SCR_REQ_MASK GENMASK(27, 25) #define STM32_DMA_SCR_MBURST_MASK GENMASK(24, 23) #define STM32_DMA_SCR_PBURST_MASK GENMASK(22, 21) #define STM32_DMA_SCR_PL_MASK GENMASK(17, 16) #define STM32_DMA_SCR_MSIZE_MASK GENMASK(14, 13) #define STM32_DMA_SCR_PSIZE_MASK GENMASK(12, 11) #define STM32_DMA_SCR_DIR_MASK GENMASK(7, 6) #define STM32_DMA_SCR_MINC BIT(10) #define STM32_DMA_SCR_PINC BIT(9) #define STM32_DMA_SCR_CIRC BIT(8) #define STM32_DMA_SCR_PFCTRL BIT(5) #define STM32_DMA_SCR_TCIE BIT(4) #define STM32_DMA_SCR_TEIE BIT(2) #define STM32_DMA_SCR_DMEIE BIT(1) #define STM32_DMA_SCR_EN BIT(0) #define STM32_DMA_SCR_IRQ_MASK (STM32_DMA_SCR_TCIE \ | STM32_DMA_SCR_TEIE \ | STM32_DMA_SCR_DMEIE) #define STM32_DMA_SNDTR(x) (0x0014 + 0x18 * (x)) #define STM32_DMA_SPAR(x) (0x0018 + 0x18 * (x)) #define STM32_DMA_SM0AR(x) (0x001c + 0x18 * (x)) #define STM32_DMA_SM1AR(x) (0x0020 + 0x18 * (x)) #define STM32_DMA_SFCR(x) (0x0024 + 0x18 * (x)) #define STM32_DMA_SFCR_FTH_MASK GENMASK(1, 0) #define STM32_DMA_SFCR_FEIE BIT(7) #define STM32_DMA_SFCR_DMDIS BIT(2)
DMA 行为和硬件参数常量 这组宏和枚举定义了DMA的逻辑行为(如方向、优先级)和固有的硬件限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #define STM32_DMA_DEV_TO_MEM 0x00 #define STM32_DMA_MEM_TO_DEV 0x01 #define STM32_DMA_MEM_TO_MEM 0x02 #define STM32_DMA_PRIORITY_LOW 0x00 #define STM32_DMA_PRIORITY_MEDIUM 0x01 #define STM32_DMA_PRIORITY_HIGH 0x02 #define STM32_DMA_PRIORITY_VERY_HIGH 0x03 #define STM32_DMA_FIFO_THRESHOLD_1QUARTERFULL 0x00 #define STM32_DMA_FIFO_THRESHOLD_HALFFULL 0x01 #define STM32_DMA_FIFO_THRESHOLD_3QUARTERSFULL 0x02 #define STM32_DMA_FIFO_THRESHOLD_FULL 0x03 #define STM32_DMA_MAX_DATA_ITEMS 0xffff #define STM32_DMA_ALIGNED_MAX_DATA_ITEMS \ ALIGN_DOWN(STM32_DMA_MAX_DATA_ITEMS, 16) #define STM32_DMA_MAX_CHANNELS 0x08 #define STM32_DMA_FIFO_SIZE 16 #define STM32_DMA_MIN_BURST 4 #define STM32_DMA_MAX_BURST 16 enum stm32_dma_width { STM32_DMA_BYTE, STM32_DMA_HALF_WORD, STM32_DMA_WORD, }; enum stm32_dma_burst_size { STM32_DMA_BURST_SINGLE, STM32_DMA_BURST_INCR4, STM32_DMA_BURST_INCR8, STM32_DMA_BURST_INCR16, };
STM32 DMA通道硬件操作底层函数 这一组静态函数构成了STM32 DMA驱动程序中直接与硬件寄存器交互的最底层 。它们将对DMA通道的抽象操作(如”获取中断状态”、”停止传输”)转换为精确的寄存器读写序列。这些函数是驱动中所有更高级别逻辑(如中断处理、传输准备)的基础。
stm32_dma_irq_status: 读取通道的中断状态此函数的核心作用是查询一个DMA通道的硬件中断状态寄存器(DMA_ISR), 并返回与该通道相关的中断标志 。
原理 : STM32的DMA控制器通常将多个通道(流)的中断标志打包存放在同一个32位ISR(Interrupt Status Register)中。例如, Stream 0-3的标志在LISR, Stream 4-7的在HISR。此函数首先通过STM32_DMA_ISR(chan->id)宏计算出当前通道属于哪个ISR寄存器, 然后读取该寄存器的完整值。接着, 它使用STM32_DMA_FLAGS_SHIFT(chan->id)宏计算出该通道标志位在此寄存器内的偏移量, 并通过右移 操作将这些标志位对齐到寄存器的最低位。最后, 它与一个掩码(STM32_DMA_MASKI)进行按位与 , 以过滤掉不相关的位, 仅返回有效的中断标志(如传输完成、半传输、传输错误等)。
1 2 3 4 5 6 7 8 9 10 11 12 13 static u32 stm32_dma_irq_status (struct stm32_dma_chan *chan) { struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); u32 flags, dma_isr; dma_isr = stm32_dma_read(dmadev, STM32_DMA_ISR(chan->id)); flags = dma_isr >> STM32_DMA_FLAGS_SHIFT(chan->id); return flags & STM32_DMA_MASKI; }
stm32_dma_irq_clear: 清除通道的中断标志此函数的核心作用是向DMA通道的硬件中断标志清除寄存器(DMA_IFCR)写入相应的值, 以清除一个或多个已触发的中断标志 。
原理 : 在STM32中, 中断标志的清除是通过向IFCR(Interrupt Flag Clear Register)的特定位写入’1’来完成的。此函数的逻辑与irq_status相反。它首先获取要清除的标志flags, 通过STM32_DMA_FLAGS_SHIFT宏左移 到它们在IFCR寄存器中正确的位置, 然后通过stm32_dma_write将这个计算出的值写入由STM32_DMA_IFCR宏确定的正确寄存器地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 static void stm32_dma_irq_clear (struct stm32_dma_chan *chan, u32 flags) { struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); u32 dma_ifcr; flags &= STM32_DMA_MASKI; dma_ifcr = flags << STM32_DMA_FLAGS_SHIFT(chan->id); stm32_dma_write(dmadev, STM32_DMA_IFCR(chan->id), dma_ifcr); }
stm32_dma_disable_chan: 安全地禁用一个DMA通道此函数的核心作用是关闭一个正在运行的DMA通道(流), 并等待硬件确认该通道确实已经停止 。
原理 : 简单地向DMA流控制寄存器(DMA_SxCR)的EN(Enable)位写入’0’可能不会立即生效, 硬件可能需要几个时钟周期来完成当前的总线事务。直接返回可能会导致竞态条件。因此, 此函数实现了一个”写后轮询 “的安全序列:
读取DMA_SxCR寄存器。
如果EN位已经是’0’, 说明通道已禁用, 直接返回。
如果EN位是’1’, 则将该位置’0’后写回寄存器。
关键步骤 : 它调用readl_relaxed_poll_timeout_atomic函数, 持续轮询 DMA_SxCR寄存器, 直到EN位变为’0’, 或者超时发生。这确保了当函数返回时, 硬件传输流是确定性地停止 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static int stm32_dma_disable_chan (struct stm32_dma_chan *chan) { struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); u32 dma_scr, id, reg; id = chan->id; reg = STM32_DMA_SCR(id); dma_scr = stm32_dma_read(dmadev, reg); if (dma_scr & STM32_DMA_SCR_EN) { dma_scr &= ~STM32_DMA_SCR_EN; stm32_dma_write(dmadev, reg, dma_scr); return readl_relaxed_poll_timeout_atomic(dmadev->base + reg, dma_scr, !(dma_scr & STM32_DMA_SCR_EN), 10 , 1000000 ); } return 0 ; }
stm32_dma_stop: 完整地停止并清理一个通道这是一个更高级别的封装, 其核心作用是执行一个完整的、安全的通道停止序列 , 包括禁用中断、停止硬件传输、清除悬挂的中断标志以及更新软件状态。
原理 : 它按照一个严格的顺序执行清理操作:
禁用中断 : 首先, 它修改DMA_SxCR和DMA_SxFCR寄存器, 清除所有中断使能位(如TCIE, TEIE, FEIE等)。这是为了防止在停止过程中产生任何新的中断。
禁用DMA : 调用stm32_dma_disable_chan来安全地停止硬件传输流。
清除状态 : 调用stm32_dma_irq_status检查是否有在禁用中断之前就已经触发并悬挂的中断标志。如果有, 就调用stm32_dma_irq_clear来清除它们, 确保通道处于一个干净的状态。
更新软件状态 : 最后, 它更新驱动内部的软件标志, 将chan->busy置为false, chan->status置为DMA_COMPLETE, 以向上层表明该通道已空闲。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static void stm32_dma_stop (struct stm32_dma_chan *chan) { struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); u32 dma_scr, dma_sfcr, status; int ret; dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(chan->id)); dma_scr &= ~STM32_DMA_SCR_IRQ_MASK; stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), dma_scr); dma_sfcr = stm32_dma_read(dmadev, STM32_DMA_SFCR(chan->id)); dma_sfcr &= ~STM32_DMA_SFCR_FEIE; stm32_dma_write(dmadev, STM32_DMA_SFCR(chan->id), dma_sfcr); ret = stm32_dma_disable_chan(chan); if (ret < 0 ) return ; status = stm32_dma_irq_status(chan); if (status) { dev_dbg(chan2dev(chan), "%s(): clearing interrupt: 0x%08x\n" , __func__, status); stm32_dma_irq_clear(chan, status); } chan->busy = false ; chan->status = DMA_COMPLETE; }
stm32_dma_alloc_chan_resources 和 stm32_dma_free_chan_resources: DMA通道资源的分配与释放这两个函数是Linux DMA引擎框架中通道生命周期管理 的核心回调。它们分别在使用DMA通道之前 和之后 被调用, 负责准备和清理与一个特定DMA通道相关的所有软硬件资源。
stm32_dma_alloc_chan_resources: 分配并准备DMA通道资源此函数在DMA通道被一个”使用者”驱动(如SPI驱动)首次请求并获得时被调用。它的核心原理是将一个空闲的DMA通道转换到一个准备就绪、可以被安全编程的初始状态 。
这个准备过程包括两个关键的动作:
唤醒硬件 : 它首先通过pm_runtime_resume_and_get来确保DMA控制器本身是上电并工作的。如果DMA控制器因为长时间未使用而进入了低功耗的”运行时挂起”状态, 这个调用会唤醒它并增加其使用计数, 防止其再次休眠。
确保硬件空闲 : 接着, 它调用stm32_dma_disable_chan来强制禁用 该通道对应的硬件单元(在STM32中是”流”Stream)。这是一个至关重要的安全步骤, 它可以确保无论该通道之前处于何种状态(可能是上一个使用者异常退出留下的), 它现在都处于一个已知的、停止的、可以安全写入新配置的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static int stm32_dma_alloc_chan_resources (struct dma_chan *c) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); int ret; chan->config_init = false ; ret = pm_runtime_resume_and_get(dmadev->ddev.dev); if (ret < 0 ) return ret; ret = stm32_dma_disable_chan(chan); if (ret < 0 ) pm_runtime_put(dmadev->ddev.dev); return ret; }
stm32_dma_free_chan_resources: 释放DMA通道资源当使用者驱动不再需要DMA通道并调用dma_release_channel时, 此函数被调用。它的核心原理是安全地终止该通道上任何可能正在进行的活动, 清理所有相关的软硬件状态, 并通知电源管理框架该通道已变为空闲 。
其清理流程如下:
终止硬件传输 : 它首先检查通道是否处于busy状态。如果是, 它会进入一个由自旋锁保护的临界区, 调用stm32_dma_stop来强制停止硬件传输, 防止在清理过程中发生中断等竞态条件。
清理软件状态 : 它会清理与该通道相关的所有软件状态, 包括释放虚拟通道(vchan)的资源(如待处理的描述符列表)和重置驱动内部的缓存及标志位。
释放电源锁 : 最后, 它调用pm_runtime_put来减少DMA控制器的使用计数。如果这是最后一个被释放的活动通道, 那么DMA控制器的使用计数可能会降为0, 使得运行时电源管理框架可以在稍后将其置于低功耗状态, 以节省能源。
在STM32H750这样的单核抢占式系统上, spin_lock_irqsave至关重要, 它通过禁用中断来防止在清理通道时, 该通道的DMA完成中断突然触发, 从而避免了对通道状态和描述符链表的并发访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 static void stm32_dma_free_chan_resources (struct dma_chan *c) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); unsigned long flags; dev_dbg(chan2dev(chan), "Freeing channel %d\n" , chan->id); if (chan->busy) { spin_lock_irqsave(&chan->vchan.lock, flags); stm32_dma_stop(chan); chan->desc = NULL ; spin_unlock_irqrestore(&chan->vchan.lock, flags); } pm_runtime_put(dmadev->ddev.dev); vchan_free_chan_resources(to_virt_chan(c)); stm32_dma_clear_reg(&chan->chan_reg); chan->threshold = 0 ; }
stm32_dma_issue_pending 和 stm32_dma_tx_status: 传输的启动与状态查询这两个函数是DMA驱动程序中负责运行时传输管理 的核心回调。issue_pending是启动 操作的入口, 而tx_status是查询 操作的入口。它们都严重依赖于virt-dma(虚拟DMA通道)这个通用框架来管理传输描述符队列, 同时通过自旋锁来确保操作的原子性, 防止与中断处理程序发生竞态条件。
stm32_dma_issue_pending: 启动待处理的DMA传输当一个使用者驱动(如SPI)调用dmaengine_submit()提交一个或多个传输请求后, DMA引擎核心会调用此函数。它的核心原理是作为一个看门人, 检查DMA通道当前是否空闲, 以及是否有新的传输任务在排队。如果两个条件都满足, 它就从队列中取出下一个任务并启动硬件传输 。
这个过程是异步DMA操作的关键: submit只是把任务放入队列, 而issue_pending才是真正”按下启动按钮”的动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static void stm32_dma_issue_pending (struct dma_chan *c) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); unsigned long flags; spin_lock_irqsave(&chan->vchan.lock, flags); if (vchan_issue_pending(&chan->vchan) && !chan->desc && !chan->busy) { dev_dbg(chan2dev(chan), "vchan %p: issued\n" , &chan->vchan); stm32_dma_start_transfer(chan); } spin_unlock_irqrestore(&chan->vchan.lock, flags); }
stm32_dma_tx_status: 查询一个DMA传输的状态此函数用于查询一个特定DMA传输的当前状态。使用者驱动通过一个dma_cookie_t(唯一的传输ID)来指定要查询的传输。它的核心原理是首先利用通用DMA框架快速检查传输是否已经完成, 如果没有, 则通过查询硬件寄存器或软件队列来计算剩余未传输的数据量(residue) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 static enum dma_status stm32_dma_tx_status (struct dma_chan *c, dma_cookie_t cookie, struct dma_tx_state *state) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); struct virt_dma_desc *vdesc ; enum dma_status status ; unsigned long flags; u32 residue = 0 ; status = dma_cookie_status(c, cookie, state); if (status == DMA_COMPLETE) return status; status = chan->status; if (!state) return status; spin_lock_irqsave(&chan->vchan.lock, flags); vdesc = vchan_find_desc(&chan->vchan, cookie); if (chan->desc && cookie == chan->desc->vdesc.tx.cookie) residue = stm32_dma_desc_residue(chan, chan->desc, chan->next_sg); else if (vdesc) residue = stm32_dma_desc_residue(chan, to_stm32_dma_desc(vdesc), 0 ); dma_set_residue(state, residue); spin_unlock_irqrestore(&chan->vchan.lock, flags); return status; }
stm32_dma_set_xfer_param: DMA传输参数的智能优化与配置此函数是STM32 DMA驱动中一个至关重要的内部辅助函数 , 它的核心作用是在prep函数(如stm32_dma_prep_slave_sg)内部, 为一个具体的传输缓冲区(scatterlist中的一个sg条目)计算出最优的硬件传输参数, 并将这些参数更新到通道的配置缓存中 。
可以把它理解为一个智能配置引擎 。它接收高级的、与硬件无关的请求(如传输方向、缓冲区长度), 并结合之前由使用者驱动通过dma_slave_config设置的约束(如外设的数据宽度), 最终生成一组能够最高效地利用STM32 DMA硬件特性(如突发传输、FIFO)的底层寄存器值。
其工作原理是根据传输方向 (direction)执行两套不同的优化逻辑:
**2. 内存到外设 (`DMA_MEM_TO_DEV`) 逻辑:**
**3. 外设到内存 (`DMA_DEV_TO_MEM`) 逻辑:**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 static int stm32_dma_set_xfer_param (struct stm32_dma_chan *chan, enum dma_transfer_direction direction, enum dma_slave_buswidth *buswidth, u32 buf_len, dma_addr_t buf_addr) { src_addr_width = chan->dma_sconfig.src_addr_width; dst_addr_width = chan->dma_sconfig.dst_addr_width; src_maxburst = chan->dma_sconfig.src_maxburst; dst_maxburst = chan->dma_sconfig.dst_maxburst; fifoth = chan->threshold; switch (direction) { case DMA_MEM_TO_DEV: dst_bus_width = stm32_dma_get_width(chan, dst_addr_width); if (dst_bus_width < 0 ) return dst_bus_width; dst_best_burst = stm32_dma_get_best_burst(buf_len, dst_maxburst, fifoth, dst_addr_width); dst_burst_size = stm32_dma_get_burst(chan, dst_best_burst); if (dst_burst_size < 0 ) return dst_burst_size; src_addr_width = stm32_dma_get_max_width(buf_len, buf_addr, fifoth); chan->mem_width = src_addr_width; src_bus_width = stm32_dma_get_width(chan, src_addr_width); if (src_bus_width < 0 ) return src_bus_width; if (buf_addr & (buf_len - 1 )) src_maxburst = 1 ; else src_maxburst = STM32_DMA_MAX_BURST; src_best_burst = stm32_dma_get_best_burst(buf_len, src_maxburst, fifoth, src_addr_width); src_burst_size = stm32_dma_get_burst(chan, src_best_burst); if (src_burst_size < 0 ) return src_burst_size; dma_scr = FIELD_PREP(STM32_DMA_SCR_DIR_MASK, STM32_DMA_MEM_TO_DEV) | FIELD_PREP(STM32_DMA_SCR_PSIZE_MASK, dst_bus_width) | FIELD_PREP(STM32_DMA_SCR_MSIZE_MASK, src_bus_width) | FIELD_PREP(STM32_DMA_SCR_PBURST_MASK, dst_burst_size) | FIELD_PREP(STM32_DMA_SCR_MBURST_MASK, src_burst_size); chan->chan_reg.dma_sfcr &= ~STM32_DMA_SFCR_FTH_MASK; if (fifoth != STM32_DMA_FIFO_THRESHOLD_NONE) chan->chan_reg.dma_sfcr |= FIELD_PREP(STM32_DMA_SFCR_FTH_MASK, fifoth); chan->chan_reg.dma_spar = chan->dma_sconfig.dst_addr; *buswidth = dst_addr_width; break ; case DMA_DEV_TO_MEM: break ; default : dev_err(chan2dev(chan), "Dma direction is not supported\n" ); return -EINVAL; } stm32_dma_set_fifo_config(chan, src_best_burst, dst_best_burst); chan->chan_reg.dma_scr &= ~(STM32_DMA_SCR_DIR_MASK | STM32_DMA_SCR_PSIZE_MASK | STM32_DMA_SCR_MSIZE_MASK | STM32_DMA_SCR_PBURST_MASK | STM32_DMA_SCR_MBURST_MASK); chan->chan_reg.dma_scr |= dma_scr; return 0 ; }
stm32_dma_prep_slave_sg 和 stm32_dma_prep_dma_cyclic: DMA传输的”蓝图”构建器这两个函数是STM32 DMA驱动程序中负责将抽象的传输请求转化为具体的硬件编程指令集 的核心回调函数。当一个”使用者”驱动(如SPI)需要进行DMA传输时, 它会调用通用DMA引擎的API, 最终会路由到这两个函数之一。
它们的核心原理是充当一个”蓝图绘制师” : 接收使用者驱动提供的高层信息(如内存地址列表、长度、方向), 然后为硬件执行器(即DMA控制器)精心准备一个或多个详细的、可执行的描述符 (descriptor) 。这个描述符本质上是一个软件结构体, 它缓存了在启动传输时需要被写入到DMA硬件寄存器(如地址寄存器、数据计数器、控制寄存器)的所有数值。
关键点是, prep函数本身并不启动硬件传输 , 它只是创建和准备这些”蓝图”, 然后将它们提交到virt-dma框架的队列中。真正的硬件启动由stm32_dma_issue_pending在稍后完成。
stm32_dma_prep_slave_sg: 准备散列表(Scatter-Gather)传输此函数用于处理最常见的DMA传输类型: 在一个外设和多个不连续的内存块 之间传输数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 static struct dma_async_tx_descriptor *stm32_dma_prep_slave_sg ( struct dma_chan *c, struct scatterlist *sgl, u32 sg_len, enum dma_transfer_direction direction, unsigned long flags, void *context) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); struct stm32_dma_desc *desc ; struct scatterlist *sg ; enum dma_slave_buswidth buswidth ; u32 nb_data_items; int i, ret; if (!chan->config_init) { dev_err(chan2dev(chan), "dma channel is not configured\n" ); return NULL ; } if (sg_len < 1 ) { dev_err(chan2dev(chan), "Invalid segment length %d\n" , sg_len); return NULL ; } desc = kzalloc(struct_size(desc, sg_req, sg_len), GFP_NOWAIT); if (!desc) return NULL ; desc->num_sgs = sg_len; if (chan->dma_sconfig.device_fc) chan->chan_reg.dma_scr |= STM32_DMA_SCR_PFCTRL; else chan->chan_reg.dma_scr &= ~STM32_DMA_SCR_PFCTRL; if (chan->trig_mdma && sg_len > 1 ) { chan->chan_reg.dma_scr |= STM32_DMA_SCR_DBM; chan->chan_reg.dma_scr &= ~STM32_DMA_SCR_CT; } for_each_sg(sgl, sg, sg_len, i) { ret = stm32_dma_set_xfer_param(chan, direction, &buswidth, sg_dma_len(sg), sg_dma_address(sg)); if (ret < 0 ) goto err; desc->sg_req[i].len = sg_dma_len(sg); nb_data_items = desc->sg_req[i].len / buswidth; if (nb_data_items > STM32_DMA_ALIGNED_MAX_DATA_ITEMS) { goto err; } stm32_dma_clear_reg(&desc->sg_req[i].chan_reg); desc->sg_req[i].chan_reg.dma_scr = chan->chan_reg.dma_scr; desc->sg_req[i].chan_reg.dma_sfcr = chan->chan_reg.dma_sfcr; desc->sg_req[i].chan_reg.dma_spar = chan->chan_reg.dma_spar; desc->sg_req[i].chan_reg.dma_sm0ar = sg_dma_address(sg); desc->sg_req[i].chan_reg.dma_sm1ar = sg_dma_address(sg); if (chan->trig_mdma) desc->sg_req[i].chan_reg.dma_sm1ar += sg_dma_len(sg); desc->sg_req[i].chan_reg.dma_sndtr = nb_data_items; } desc->cyclic = false ; return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); err: kfree(desc); return NULL ; }
stm32_dma_prep_dma_cyclic: 准备循环(Cyclic)传输此函数用于准备一个特殊的DMA传输, 数据会从一个缓冲区中被周期性地、无限地传输。这对于音频(I2S)或连续ADC采样等应用是必不可少的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static struct dma_async_tx_descriptor *stm32_dma_prep_dma_cyclic ( struct dma_chan *c, dma_addr_t buf_addr, size_t buf_len, size_t period_len, enum dma_transfer_direction direction, unsigned long flags) { if (buf_len == period_len) { chan->chan_reg.dma_scr |= STM32_DMA_SCR_CIRC; } else { chan->chan_reg.dma_scr |= STM32_DMA_SCR_DBM; chan->chan_reg.dma_scr &= ~STM32_DMA_SCR_CT; } num_periods = buf_len / period_len; desc->num_sgs = num_periods; for (i = 0 ; i < num_periods; i++) { desc->sg_req[i].len = period_len; stm32_dma_clear_reg(&desc->sg_req[i].chan_reg); desc->sg_req[i].chan_reg.dma_scr = chan->chan_reg.dma_scr; desc->sg_req[i].chan_reg.dma_sfcr = chan->chan_reg.dma_sfcr; desc->sg_req[i].chan_reg.dma_spar = chan->chan_reg.dma_spar; desc->sg_req[i].chan_reg.dma_sm0ar = buf_addr; desc->sg_req[i].chan_reg.dma_sm1ar = buf_addr; if (chan->trig_mdma) desc->sg_req[i].chan_reg.dma_sm1ar += period_len; desc->sg_req[i].chan_reg.dma_sndtr = nb_data_items; if (!chan->trig_mdma) buf_addr += period_len; } desc->cyclic = true ; return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); }
stm32_dma_handle_chan_paused: 捕获DMA暂停状态并为恢复做准备此函数是一个内部状态处理函数 , 它在DMA硬件流被stm32_dma_disable_chan安全地停止后立即被调用。它的核心原理是为一个已暂停的DMA传输执行”状态快照”, 精确地捕获硬件在停止瞬间的状态, 并对硬件进行微调, 以确保后续的resume(恢复)操作能够无缝、正确地继续传输 。
可以把它看作是DMA暂停操作的”收尾工作”。stm32_dma_disable_chan负责”踩刹车”, 而stm32_dma_handle_chan_paused负责”记录停车位置和车辆状态”。
这个过程包含两个关键且精妙的步骤:
捕获剩余工作量 :
它首先读取硬件的DMA_SNDTR(Stream Number of Data to Transfer)寄存器。这个寄存器中的值是在硬件停止时精确剩余的、尚未传输的数据项数量 。这个值被保存在软件的通道配置缓存(chan->chan_reg.dma_sndtr)中, 这是resume函数能够计算出从哪里继续传输的最关键信息 。
处理循环/双缓冲模式的特殊逻辑 :
这是此函数最复杂也是最重要的部分。直接暂停一个处于循环(CIRC)或双缓冲(DBM)模式的传输, 然后再恢复, 会有一个问题: DMA硬件的自动重载机制可能会在恢复时使用一个不正确(部分)的数据计数值, 从而破坏后续的循环。
为了解决这个问题, 函数执行了一个巧妙的**”软件保存意图, 硬件简化状态”**的操作:
保存意图 : 它先读取SCR(Stream Configuration Register), 并在软件备份(chan->chan_reg.dma_scr)中重新确保 CIRC或DBM标志是设置的。这可以防止因某些临时状态(如上次恢复后)导致硬件SCR中的这些位被清除, 从而”忘记”了这次传输本应是循环的。
简化状态 : 然后, 它在硬件的SCR寄存器中, 故意清除CIRC和DBM位 。这暂时将当前被中断的传输片段变成了一个普通的”一次性”传输。这样, resume函数就可以简单地恢复这个一次性传输, 而不必担心硬件的自动重载机制会出错。当中断处理程序在这次恢复的传输完成后被触发时, 它会负责检查原始意图(从软件备份中读取), 并为下一个完整的周期重新启用CIRC或DBM模式。
最后, 它将通道的软件状态正式设置为DMA_PAUSED。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 static void stm32_dma_handle_chan_paused (struct stm32_dma_chan *chan) { struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); u32 dma_scr; dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(chan->id)); if (chan->desc && chan->desc->cyclic) { if (chan->desc->num_sgs == 1 ) dma_scr |= STM32_DMA_SCR_CIRC; else dma_scr |= STM32_DMA_SCR_DBM; } chan->chan_reg.dma_scr = dma_scr; if (chan->desc && chan->desc->cyclic) { dma_scr &= ~(STM32_DMA_SCR_DBM | STM32_DMA_SCR_CIRC); stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), dma_scr); } chan->chan_reg.dma_sndtr = stm32_dma_read(dmadev, STM32_DMA_SNDTR(chan->id)); chan->status = DMA_PAUSED; dev_dbg(chan2dev(chan), "vchan %p: paused\n" , &chan->vchan); }
stm32_dma_pause 和 stm32_dma_resume: DMA传输的暂停与恢复这两个函数实现了DMA引擎框架中对正在进行的传输进行临时暂停 和精确续传 的功能。这是一项高级功能, 要求驱动程序能够精确地记录硬件在被暂停时的状态, 并在恢复时正确地重构这个状态。
stm32_dma_pause: 暂停DMA传输此函数的核心原理是安全地停止硬件传输, 并记录下被暂停时的状态 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static int stm32_dma_pause (struct dma_chan *c) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); unsigned long flags; int ret; if (chan->status != DMA_IN_PROGRESS) return -EPERM; spin_lock_irqsave(&chan->vchan.lock, flags); ret = stm32_dma_disable_chan(chan); if (!ret) stm32_dma_handle_chan_paused(chan); spin_unlock_irqrestore(&chan->vchan.lock, flags); return ret; }
stm32_dma_resume: 从暂停点恢复DMA传输此函数是整个机制中最复杂的部分。它的核心原理是利用暂停时保存的状态, 精确地重新计算并配置硬件寄存器, 使得DMA传输能够从中断点无缝地继续, 而不是从头开始 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 static int stm32_dma_resume (struct dma_chan *c) { if (chan->status != DMA_PAUSED) return -EPERM; spin_lock_irqsave(&chan->vchan.lock, flags); if (!chan->next_sg) sg_req = &chan->desc->sg_req[chan->desc->num_sgs - 1 ]; else sg_req = &chan->desc->sg_req[chan->next_sg - 1 ]; ndtr = sg_req->chan_reg.dma_sndtr; offset = (ndtr - chan_reg.dma_sndtr); offset <<= FIELD_GET(STM32_DMA_SCR_PSIZE_MASK, chan_reg.dma_scr); spar = sg_req->chan_reg.dma_spar; sm0ar = sg_req->chan_reg.dma_sm0ar; sm1ar = sg_req->chan_reg.dma_sm1ar; if (chan_reg.dma_scr & STM32_DMA_SCR_PINC) stm32_dma_write(dmadev, STM32_DMA_SPAR(id), spar + offset); else stm32_dma_write(dmadev, STM32_DMA_SPAR(id), spar); if (!(chan_reg.dma_scr & STM32_DMA_SCR_MINC)) offset = 0 ; stm32_dma_write(dmadev, STM32_DMA_SNDTR(id), chan_reg.dma_sndtr); if (chan_reg.dma_scr & (STM32_DMA_SCR_CIRC | STM32_DMA_SCR_DBM)) chan_reg.dma_scr &= ~(STM32_DMA_SCR_CIRC | STM32_DMA_SCR_DBM); chan->status = DMA_IN_PROGRESS; chan_reg.dma_scr |= STM32_DMA_SCR_EN; stm32_dma_write(dmadev, STM32_DMA_SCR(id), chan_reg.dma_scr); spin_unlock_irqrestore(&chan->vchan.lock, flags); dev_dbg(chan2dev(chan), "vchan %p: resumed\n" , &chan->vchan); return 0 ; }
stm32_dma_terminate_all 和 stm32_dma_synchronize: 传输的终止与同步这两个函数是DMA引擎框架中负责流程控制 的两个关键回调。terminate_all提供了一种强制、立即中止 所有传输的机制, 主要用于错误恢复或驱动卸载。而synchronize则提供了一种阻塞式等待 机制, 确保在CPU继续执行之前, 所有已启动的DMA传输都已完成。
stm32_dma_terminate_all: 强制中止所有传输此函数的核心原理是执行一个**”焦土式”的清理操作**。它会立即停止硬件, 并清理掉该通道上所有正在进行和排队等待的传输任务, 包括软件描述符和硬件状态。
这个过程必须是原子的, 以防止在清理过程中有新的中断或任务提交发生, 因此它在自旋锁 的保护下执行关键步骤:
停止当前传输 : 如果有一个传输正在硬件上运行(chan->desc有效), 它会首先在软件层面将其标记为完成(即使是被中止的), 并调用vchan_terminate_vdesc来通知使用者驱动该任务已终止。然后, 它调用stm32_dma_stop来强制停止硬件。
清空队列 : 它调用vchan_get_all_descriptors将virt-dma框架中该通道的所有队列(submitted和issued)中的描述符全部 移动到一个临时的本地链表中。
释放资源 : 在释放自旋锁之后, 它调用vchan_dma_desc_free_list来安全地释放从队列中取出的所有描述符的内存。将内存释放操作放在锁之外是一个好的实践, 可以让临界区尽可能短。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 static int stm32_dma_terminate_all (struct dma_chan *c) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); unsigned long flags; LIST_HEAD(head); spin_lock_irqsave(&chan->vchan.lock, flags); if (chan->desc) { dma_cookie_complete(&chan->desc->vdesc.tx); vchan_terminate_vdesc(&chan->desc->vdesc); if (chan->busy) stm32_dma_stop(chan); chan->desc = NULL ; } vchan_get_all_descriptors(&chan->vchan, &head); spin_unlock_irqrestore(&chan->vchan.lock, flags); vchan_dma_desc_free_list(&chan->vchan, &head); return 0 ; }
stm32_dma_synchronize: 同步CPU执行与DMA完成此函数的核心原理是充当一个阻塞式的同步点或”栅栏” 。当一个使用者驱动调用dmaengine_synchronize()时, 调用线程会在此函数中暂停执行, 直到该DMA通道上所有先前已提交并启动的传输都全部被硬件完成 。
实现方式 : STM32 DMA驱动程序完全委托 virt-dma框架来实现这个功能。vchan_synchronize是一个通用的辅助函数, 它内部实现了一个等待循环, 检查虚拟通道的desc_issued队列是否为空。只有当中断处理程序将所有已发出的描述符都处理完毕(即从队列中移除)后, 这个等待才会结束, 函数才会返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static void stm32_dma_synchronize (struct dma_chan *c) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); vchan_synchronize(&chan->vchan); }
‘stm32_dma_prep_dma_memcpy’: 准备内存到内存的DMA传输 此函数是STM32 DMA驱动程序中负责内存到内存 (’memcpy’) 类型传输的回调函数。 它的核心原理是将一个用户请求的大块内存复制作, 分解(segmentation) 成一个或多个符合STM32 DMA硬件单次传输能力上限的小块传输任务 , 并为每一个小块任务精心准备一套完整的硬件寄存器配置, 最终将这些配置打包成一个描述符(descriptor) , 提交给’virt-dma’框架排队等待执行。
这个函数是实现高效内存复制的关键, 它通过以下步骤将一个抽象的’memcpy’请求转化为具体的硬件指令集:
分片计算 : 由于STM32 DMA的’NDTR’(数据传输数量)寄存器有其最大值(’STM32_DMA_ALIGNED_MAX_DATA_ITEMS’), 一个大的内存复制请求必须被拆分成多个DMA传输。 函数首先通过’DIV_ROUND_UP’计算出总共需要多少个这样的小块传输, 这决定了需要分配多大的描述符。
描述符分配 : 它动态地分配一个’stm32_dma_desc’结构体, 该结构体尾部包含一个足够容纳所有小块传输配置(’sg_req’)的弹性数组。
循环准备 : 函数进入一个循环, 为每一个小块传输(chunk)进行配置:
它计算出当前小块的长度 (’xfer_count’)。
它调用辅助函数 (’stm32_dma_get_best_burst’) 来为当前传输块智能地选择最优的突发传输尺寸(burst size) , 以最大限度地提高总线利用率。
绘制蓝图 : 最关键的一步, 它将启动这个小块传输所需的所有硬件寄存器值, 预先计算 并缓存到描述符对应的’sg_req[i].chan_reg’中。 这包括:
方向(’DIR’) : 明确设置为’STM32_DMA_MEM_TO_MEM’。
地址 : 将源地址(’src offset’)写入’SPAR’(外设地址寄存器), 将目标地址(’dest offset’)写入’SM0AR’(内存地址寄存器)。 在M2M模式下, ‘SPAR’被复用为源地址。
地址增量(’PINC’, ‘MINC’) : 同时使能 外设和内存地址的自增, 这是’memcpy’的标准行为。
数据计数(’SNDTR’) : 设置为当前小块的长度。
突发尺寸(’PBURST’, ‘MBURST’) : 设置为前面计算出的最优值。
中断使能(’TCIE’, ‘TEIE’) : 使能传输完成和传输错误中断。
提交给框架 : 当所有小块的“蓝图”都绘制完成后, 整个描述符通过’vchan_tx_prep’提交给’virt-dma’框架。 ‘vchan_tx_prep’会为其添加通用的接口, 并将其放入“已分配”队列, 等待用户驱动的最终提交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 static struct dma_async_tx_descriptor *stm32_dma_prep_dma_memcpy ( struct dma_chan *c, dma_addr_t dest, dma_addr_t src, size_t len, unsigned long flags) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); enum dma_slave_buswidth max_width ; struct stm32_dma_desc *desc ; size_t xfer_count, offset; u32 num_sgs, best_burst, threshold; int dma_burst, i; num_sgs = DIV_ROUND_UP(len, STM32_DMA_ALIGNED_MAX_DATA_ITEMS); desc = kzalloc(struct_size(desc, sg_req, num_sgs), GFP_NOWAIT); if (!desc) return NULL ; desc->num_sgs = num_sgs; threshold = chan->threshold; for (offset = 0 , i = 0 ; offset < len; offset = xfer_count, i ) { xfer_count = min_t (size_t , len - offset, STM32_DMA_ALIGNED_MAX_DATA_ITEMS); max_width = DMA_SLAVE_BUSWIDTH_1_BYTE; best_burst = stm32_dma_get_best_burst(len, STM32_DMA_MAX_BURST, threshold, max_width); dma_burst = stm32_dma_get_burst(chan, best_burst); if (dma_burst < 0 ) { kfree(desc); return NULL ; } stm32_dma_clear_reg(&desc->sg_req[i].chan_reg); desc->sg_req[i].chan_reg.dma_scr = FIELD_PREP(STM32_DMA_SCR_DIR_MASK, STM32_DMA_MEM_TO_MEM) | FIELD_PREP(STM32_DMA_SCR_PBURST_MASK, dma_burst) | FIELD_PREP(STM32_DMA_SCR_MBURST_MASK, dma_burst) | STM32_DMA_SCR_MINC | STM32_DMA_SCR_PINC | STM32_DMA_SCR_TCIE | STM32_DMA_SCR_TEIE; desc->sg_req[i].chan_reg.dma_sfcr |= STM32_DMA_SFCR_MASK; desc->sg_req[i].chan_reg.dma_sfcr |= FIELD_PREP(STM32_DMA_SFCR_FTH_MASK, threshold); desc->sg_req[i].chan_reg.dma_spar = src + offset; desc->sg_req[i].chan_reg.dma_sm0ar = dest + offset; desc->sg_req[i].chan_reg.dma_sndtr = xfer_count; desc->sg_req[i].len = xfer_count; } desc->cyclic = false ; return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags); }
stm32_dma_chan_irq: DMA通道硬件中断的主处理程序 (顶层)此函数是硬件中断的直接入口点 。当一个STM32 DMA通道完成传输、遇到错误或达到半程点时, 硬件会触发一个中断, 内核的中断子系统最终会调用这个函数来响应该事件。它是中断处理的”上半部”(Top Half), 运行在**硬中断上下文(hardirq context)**中。
其核心原理是快速响应、分类处理、最小化延迟 。它必须在尽可能短的时间内完成, 以便让CPU可以尽快响应其他可能更重要的中断。
获取状态快照 : 它首先获取保护通道状态的自旋锁, 然后立即读取所有相关的硬件状态寄存器(中断状态、流控制、FIFO控制)。这确保了它处理的是触发中断那一刻的精确硬件状态。
错误优先处理 : 它会优先检查并处理各种错误标志(如FIFO错误FEI、直接模式错误DMEI)。对于每个错误, 它会清除硬件中的中断标志位, 并检查该错误中断是否被使能。这是一种严谨的做法, 确保驱动只对它明确要求关注的事件做出反应, 并将错误信息记录到内核日志中。
成功路径处理 : 接下来, 它处理最重要的成功事件——传输完成(TCI)。如果传输完成中断发生并且被使能, 且通道不处于软件暂停状态, 它不会在此函数中执行所有后续逻辑, 而是将控制权转交给stm32_dma_handle_chan_done函数, 由该函数执行更复杂的”下半部”逻辑。
其他事件处理 : 它会检查并清除其他类型的中断标志(如半传输完成HTI), 即使当前驱动逻辑没有为它们附加特殊操作, 清除标志也是必需的, 以防止中断风暴。
兜底与清理 : 最后, 它会检查是否有任何未识别的中断标志被置位, 如果有, 则报告一个通用错误。完成所有操作后, 它释放自旋锁, 并返回IRQ_HANDLED告知内核该中断已成功处理。
在STM32H750这样的单核系统上, spin_lock_irq依然至关重要。它能防止在硬中断上下文中处理通道状态时, 被另一个中断(甚至是同一个中断的再次触发, 尽管不太可能)或被抢占的任务上下文中的代码(如dma_issue_pending)同时访问和修改chan结构体, 从而保证了数据的一致性和完整性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 static irqreturn_t stm32_dma_chan_irq (int irq, void *devid) { struct stm32_dma_chan *chan = devid; struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); u32 status, scr, sfcr; spin_lock(&chan->vchan.lock); status = stm32_dma_irq_status(chan); scr = stm32_dma_read(dmadev, STM32_DMA_SCR(chan->id)); sfcr = stm32_dma_read(dmadev, STM32_DMA_SFCR(chan->id)); if (status & STM32_DMA_FEI) { stm32_dma_irq_clear(chan, STM32_DMA_FEI); status &= ~STM32_DMA_FEI; if (sfcr & STM32_DMA_SFCR_FEIE) { if (!(scr & STM32_DMA_SCR_EN) && !(status & STM32_DMA_TCI)) dev_err(chan2dev(chan), "FIFO Error\n" ); else dev_dbg(chan2dev(chan), "FIFO over/underrun\n" ); } } if (status & STM32_DMA_DMEI) { stm32_dma_irq_clear(chan, STM32_DMA_DMEI); status &= ~STM32_DMA_DMEI; if (sfcr & STM32_DMA_SCR_DMEIE) dev_dbg(chan2dev(chan), "Direct mode overrun\n" ); } if (status & STM32_DMA_TCI) { stm32_dma_irq_clear(chan, STM32_DMA_TCI); if (scr & STM32_DMA_SCR_TCIE) { if (chan->status != DMA_PAUSED) stm32_dma_handle_chan_done(chan, scr); } status &= ~STM32_DMA_TCI; } if (status & STM32_DMA_HTI) { stm32_dma_irq_clear(chan, STM32_DMA_HTI); status &= ~STM32_DMA_HTI; } if (status) { stm32_dma_irq_clear(chan, status); dev_err(chan2dev(chan), "DMA error: status=0x%08x\n" , status); if (!(scr & STM32_DMA_SCR_EN)) dev_err(chan2dev(chan), "chan disabled by HW\n" ); } spin_unlock(&chan->vchan.lock); return IRQ_HANDLED; }
stm32_dma_handle_chan_done: DMA传输完成事件的逻辑处理器 (中层)此函数是中断处理的”下半部”逻辑的起点 。它由stm32_dma_chan_irq在确认一次成功的传输完成后调用, 负责根据DMA的**工作模式(循环模式或单次模式)**来执行不同的状态更新和后续操作。
其核心原理是区分不同的DMA工作流, 并精确地管理描述符和硬件状态 :
循环模式 (cyclic) :
通知客户端 : 它立即调用vchan_cyclic_callback。这个函数通常会调度一个tasklet, 由该tasklet在软中断上下文中去执行客户端驱动提供的回调函数。这遵循了将耗时工作移出硬中断上下文的最佳实践。
硬件管理 : 它接着检查硬件是否处于自动循环模式(CIRC或DBM)。
如果不是 , 意味着驱动正在”手动”模拟循环传输。在这种情况下, 硬件在完成一轮后已经停止, 必须调用stm32_dma_post_resume_reconfigure来完全重新编程 DMA寄存器并手动重启 下一次传输。
如果是 , 硬件会自动处理循环。驱动只需为硬件的双缓冲模式准备好下一个数据段(stm32_dma_configure_next_sg)即可。
单次/Scatter-Gather模式 :
状态更新 : 它将通道标记为空闲(busy = false), 状态为完成(DMA_COMPLETE)。
描述符处理 : 它检查当前完成的是否是整个传输请求的最后一个数据段 (next_sg == num_sgs)。
如果是, 意味着整个DMA任务(cookie)已完成。它调用vchan_cookie_complete来最终通知客户端, 并将chan->desc清空, 表示通道现在完全空闲。
如果不是, 意味着这只是Scatter-Gather列表中的一个中间段, 整个任务尚未完成。
启动下一次传输 : 无论当前任务是否完成, 它都会调用stm32_dma_start_transfer。这个函数会检查: 如果当前任务还有剩余的数据段, 它会立即启动下一个段的传输; 如果当前任务已完成, 它会检查是否有新的DMA任务在排队, 如果有则启动它。这实现了DMA传输的无缝衔接, 保持硬件的最高利用率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static void stm32_dma_handle_chan_done (struct stm32_dma_chan *chan, u32 scr) { if (!chan->desc) return ; if (chan->desc->cyclic) { vchan_cyclic_callback(&chan->desc->vdesc); if (chan->trig_mdma) return ; stm32_dma_sg_inc(chan); if (!(scr & (STM32_DMA_SCR_CIRC | STM32_DMA_SCR_DBM))) stm32_dma_post_resume_reconfigure(chan); else if (scr & STM32_DMA_SCR_DBM && chan->desc->num_sgs > 2 ) stm32_dma_configure_next_sg(chan); } else { chan->busy = false ; chan->status = DMA_COMPLETE; if (chan->next_sg == chan->desc->num_sgs) { vchan_cookie_complete(&chan->desc->vdesc); chan->desc = NULL ; } stm32_dma_start_transfer(chan); } }
stm32_dma_post_resume_reconfigure: 在暂停/恢复后重新配置并启动DMA (底层)此函数是一个非常具体的、用于恢复和重启DMA硬件状态的底层操作函数 。它主要被stm32_dma_handle_chan_done在”软件模拟循环传输”的特殊场景下调用。
其核心原理是将DMA通道的所有关键寄存器恢复到一次传输的初始状态, 然后重新使能通道 。这模拟了硬件循环模式的行为, 但完全由软件控制。
清理和定位 : 它首先清除任何可能残留的中断状态, 然后根据当前scatter-gather索引, 精确地定位到本轮传输应该使用的那个数据段描述符(sg_req)。
寄存器恢复 : 它从sg_req中读取预先保存好的初始值, 并依次写回到DMA硬件寄存器中, 包括:
SNDTR: 传输数据项的数量。
SPAR: 外设地址。
SM0AR/SM1AR: 内存地址(在双缓冲模式下有两个)。
模式恢复 : 它检查原始配置, 如果需要, 重新在流控制寄存器(SCR)中设置CIRC(循环)或DBM(双缓冲)标志。
重启 : 最后, 它在SCR中设置EN(使能)位, 重新启动DMA硬件 , 开始新一轮的传输。
这个函数体现了驱动程序如何通过精细的软件控制, 扩展硬件的功能, 实现硬件本身不支持或在特定条件下无法使用的操作模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 static void stm32_dma_post_resume_reconfigure (struct stm32_dma_chan *chan) { struct stm32_dma_device *dmadev = stm32_dma_get_dev(chan); struct stm32_dma_sg_req *sg_req ; u32 dma_scr, status, id; id = chan->id; dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(id)); status = stm32_dma_irq_status(chan); if (status) stm32_dma_irq_clear(chan, status); if (!chan->next_sg) sg_req = &chan->desc->sg_req[chan->desc->num_sgs - 1 ]; else sg_req = &chan->desc->sg_req[chan->next_sg - 1 ]; stm32_dma_write(dmadev, STM32_DMA_SNDTR(chan->id), sg_req->chan_reg.dma_sndtr); stm32_dma_write(dmadev, STM32_DMA_SPAR(id), sg_req->chan_reg.dma_spar); stm32_dma_write(dmadev, STM32_DMA_SM0AR(id), sg_req->chan_reg.dma_sm0ar); stm32_dma_write(dmadev, STM32_DMA_SM1AR(id), sg_req->chan_reg.dma_sm1ar); if (chan->chan_reg.dma_scr & STM32_DMA_SCR_DBM) { dma_scr |= STM32_DMA_SCR_DBM; if (chan->chan_reg.dma_scr & STM32_DMA_SCR_CT) dma_scr &= ~STM32_DMA_SCR_CT; else dma_scr |= STM32_DMA_SCR_CT; } else if (chan->chan_reg.dma_scr & STM32_DMA_SCR_CIRC) { dma_scr |= STM32_DMA_SCR_CIRC; } stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), dma_scr); stm32_dma_configure_next_sg(chan); stm32_dma_dump_reg(chan); dma_scr |= STM32_DMA_SCR_EN; stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), dma_scr); dev_dbg(chan2dev(chan), "vchan %p: reconfigured after pause/resume\n" , &chan->vchan); }
stm32_dma_of_xlate: 从设备树”翻译”DMA请求 此函数是STM32 DMA驱动程序中一个至关重要的回调函数 。它在DMA引擎框架中的角色是一个**”翻译器” (translator)**。当一个外设的客户端驱动(例如SPI, I2C, UART驱动)需要使用DMA时, 内核的DMA引擎框架会调用这个函数, 将客户端设备树中描述的、高度硬件相关的DMA请求信息, “翻译”成一个标准的、可供客户端驱动使用的struct dma_chan句柄。
此函数是连接客户端驱动 和DMA控制器驱动 之间的桥梁, 其核心原理如下:
解析硬件描述 : 函数的输入dma_spec包含了从客户端设备树的dmas属性中解析出的原始数据。例如, SPI驱动的设备树节点中可能会有一行dmas = <&dma1 7 0x420 0x800>;。dma_spec->args就包含了{7, 0x420, 0x800, ...}这些原始的整数。
翻译为有意义的配置 : 函数做的第一件事就是将这些匿名的数字翻译成一个有意义的、驱动内部使用的stm32_dma_cfg结构。这正是”xlate”的含义:
args[0] (7) -> cfg.channel_id: 硬件DMA流(Stream)的编号。
args[1] -> cfg.request_line: 外设的请求线。这是配置STM32特有的**DMAMUX(DMA多路复用器)**的关键, 它将特定的外设请求(如SPI1_TX)路由到指定的DMA流。
args[2] -> cfg.stream_config: DMA流的静态配置, 如传输方向、数据宽度、优先级等, 这些值可以直接写入硬件寄存器。
args[3] -> cfg.features: 特殊功能标志, 如是否使用FIFO。
验证与资源定位 : 函数会进行严格的有效性检查, 确保设备树中提供的值在硬件支持的范围内。然后, 它使用channel_id作为索引, 直接定位到DMA控制器驱动内部代表该硬件流的stm32_dma_chan结构。
通道申请与锁定 : 最关键的一步是调用dma_get_slave_channel。这是一个向通用DMA引擎框架发出的请求, 意图**”申请并独占”**这个物理DMA通道。如果该通道已经被其他驱动占用, 此调用将失败, 从而正确地管理了共享硬件资源的访问。
应用静态配置 : 一旦成功获得通道的独占使用权, 它就会调用stm32_dma_set_config, 将从设备树中解析出的静态配置(特别是DMAMUX的请求线)应用到硬件或软件状态中。这完成了通道的预配置, 使其准备好为该特定外设服务。
返回通用句柄 : 最后, 它返回一个标准的struct dma_chan句柄。客户端驱动接收到这个句柄后, 就可以使用硬件无关 的、标准的DMA引擎API(如dmaengine_prep_slave_sg, dmaengine_submit等)来发起传输, 而无需关心任何STM32特有的寄存器细节。
在STM32H750系统上, 这个函数是整个DMA子系统正常工作的基石。它完美地体现了设备树的设计哲学: 将板级的、不可变的硬件连接信息(哪个SPI连接到哪个DMA流的哪个请求线)保留在设备树中, 而驱动程序则负责在运行时解析这些信息, 从而实现了内核代码与具体硬件配置的解耦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 static struct dma_chan *stm32_dma_of_xlate (struct of_phandle_args *dma_spec, struct of_dma *ofdma) { struct stm32_dma_device *dmadev = ofdma->of_dma_data; struct device *dev = dmadev->ddev.dev; struct stm32_dma_cfg cfg ; struct stm32_dma_chan *chan ; struct dma_chan *c ; if (dma_spec->args_count < 4 ) { dev_err(dev, "Bad number of cells\n" ); return NULL ; } cfg.channel_id = dma_spec->args[0 ]; cfg.request_line = dma_spec->args[1 ]; cfg.stream_config = dma_spec->args[2 ]; cfg.features = dma_spec->args[3 ]; if (cfg.channel_id >= STM32_DMA_MAX_CHANNELS || cfg.request_line >= STM32_DMA_MAX_REQUEST_ID) { dev_err(dev, "Bad channel and/or request id\n" ); return NULL ; } chan = &dmadev->chan[cfg.channel_id]; c = dma_get_slave_channel(&chan->vchan.chan); if (!c) { dev_err(dev, "No more channels available\n" ); return NULL ; } stm32_dma_set_config(chan, &cfg); return c; }
stm32_dma_probe: STM32 DMA控制器探测与初始化 此函数是STM32 DMA驱动程序的核心, 是驱动与硬件交互的起点。当内核根据设备树匹配到DMA控制器设备时, 就会调用此函数。它的核心原理是执行一个全面的初始化序列, 将原始的DMA硬件资源(寄存器、时钟、中断)进行配置, 并将其封装成一个标准的Linux dma_device 对象, 然后将这个对象注册到内核的DMA引擎(DMA Engine)子系统中 。完成此过程后, 系统中其他需要DMA服务的设备驱动(如SPI, I2C, UART)就可以通过标准的DMA Engine API来请求和使用DMA通道了。
整个初始化过程可以分为以下几个关键阶段:
资源获取与硬件复位 :
函数首先为驱动的核心数据结构(stm32_dma_device)分配内存。
它从设备树中获取DMA控制器的寄存器基地址, 并通过ioremap将其映射到内核可访问的地址空间。
它获取并使能DMA控制器所需的时钟。
它获取复位控制器句柄, 并对DMA硬件执行一次”断言-延时-解除断言”的复位序列, 确保硬件处于一个已知的初始状态。
向DMA引擎描述硬件能力 :
函数会填充dma_device结构体。这部分至关重要, 它相当于驱动向通用的DMA引擎框架提交的一份”硬件能力说明书”。
通过dma_cap_set设置能力位掩码, 声明此DMA支持从设备模式(DMA_SLAVE)、循环模式(DMA_CYCLIC)以及可能的内存到内存模式(DMA_MEMCPY)。
它为dma_device结构体中的一系列函数指针(device_alloc_chan_resources, device_prep_slave_sg等)赋值。这些指针指向本驱动内部实现的、针对STM32 DMA硬件的特定操作函数。当上层驱动调用一个通用的DMA API时, DMA引擎核心会通过这些函数指针来调用STM32的特定实现, 这正是硬件抽象的核心。
它还定义了支持的数据宽度、传输方向、对齐要求等硬件特性。
初始化DMA通道 :
DMA控制器有多个通道, 函数会遍历所有通道, 为每一个通道初始化其软件表示(stm32_dma_chan)。
它会初始化vchan(虚拟通道)系统, 这是DMA引擎用于管理多个客户端对物理通道请求的机制。
为了提高效率, 它会预先计算好每个通道的中断清除和状态标志寄存器的地址和位掩码。
注册与中断设置 :
通过dma_async_device_register, 将完全配置好的dma_device对象正式注册到DMA引擎中。
它为每个DMA通道获取对应的中断号(IRQ), 并调用devm_request_irq为每个中断注册一个处理函数(stm32_dma_chan_irq)。
通过of_dma_controller_register, 将此DMA控制器注册为设备树的DMA提供者。这一步会注册一个xlate(翻译)函数, 该函数负责解析其他设备在设备树中定义的DMA请求(例如, dmas = <&dma1 5 ...>), 并将其转换为本驱动可以理解的通道和配置信息。
最后, 它会启用运行时电源管理(Runtime PM), 允许DMA控制器在不使用时自动进入低功耗状态。
对于STM32H750这样的单核系统, devm_*系列函数的使用确保了即使在初始化中途出错, 所有已分配的资源(内存、中断、时钟等)也能够被自动安全地释放, 极大地增强了驱动的健壮性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 static int stm32_dma_slave_config (struct dma_chan *c, struct dma_slave_config *config) { struct stm32_dma_chan *chan = to_stm32_dma_chan(c); memcpy (&chan->dma_sconfig, config, sizeof (*config)); if (config->peripheral_size) { config->peripheral_config = &chan->mdma_config; config->peripheral_size = sizeof (chan->mdma_config); chan->trig_mdma = true ; } chan->config_init = true ; return 0 ; } static int stm32_dma_probe (struct platform_device *pdev) { struct stm32_dma_chan *chan ; struct stm32_dma_device *dmadev ; struct dma_device *dd ; struct resource *res ; struct reset_control *rst ; int i, ret; dmadev = devm_kzalloc(&pdev->dev, sizeof (*dmadev), GFP_KERNEL); if (!dmadev) return -ENOMEM; dd = &dmadev->ddev; dmadev->base = devm_platform_get_and_ioremap_resource(pdev, 0 , &res); if (IS_ERR(dmadev->base)) return PTR_ERR(dmadev->base); dmadev->clk = devm_clk_get(&pdev->dev, NULL ); if (IS_ERR(dmadev->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(dmadev->clk), "Can't get clock\n" ); ret = clk_prepare_enable(dmadev->clk); if (ret < 0 ) { dev_err(&pdev->dev, "clk_prep_enable error: %d\n" , ret); return ret; } dmadev->mem2mem = of_property_read_bool(pdev->dev.of_node, "st,mem2mem" ); rst = devm_reset_control_get(&pdev->dev, NULL ); if (IS_ERR(rst)) { ret = PTR_ERR(rst); if (ret == -EPROBE_DEFER) goto clk_free; } else { reset_control_assert(rst); udelay(2 ); reset_control_deassert(rst); } dma_set_max_seg_size(&pdev->dev, STM32_DMA_ALIGNED_MAX_DATA_ITEMS); dma_cap_set(DMA_SLAVE, dd->cap_mask); dma_cap_set(DMA_PRIVATE, dd->cap_mask); dma_cap_set(DMA_CYCLIC, dd->cap_mask); dd->device_alloc_chan_resources = stm32_dma_alloc_chan_resources; dd->device_free_chan_resources = stm32_dma_free_chan_resources; dd->device_tx_status = stm32_dma_tx_status; dd->device_issue_pending = stm32_dma_issue_pending; dd->device_prep_slave_sg = stm32_dma_prep_slave_sg; dd->device_prep_dma_cyclic = stm32_dma_prep_dma_cyclic; dd->device_config = stm32_dma_slave_config; dd->device_pause = stm32_dma_pause; dd->device_resume = stm32_dma_resume; dd->device_terminate_all = stm32_dma_terminate_all; dd->device_synchronize = stm32_dma_synchronize; dd->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); dd->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); dd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); dd->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; dd->copy_align = DMAENGINE_ALIGN_32_BYTES; dd->max_burst = STM32_DMA_MAX_BURST; dd->max_sg_burst = STM32_DMA_ALIGNED_MAX_DATA_ITEMS; dd->descriptor_reuse = true ; dd->dev = &pdev->dev; INIT_LIST_HEAD(&dd->channels); if (dmadev->mem2mem) { dma_cap_set(DMA_MEMCPY, dd->cap_mask); dd->device_prep_dma_memcpy = stm32_dma_prep_dma_memcpy; dd->directions |= BIT(DMA_MEM_TO_MEM); } for (i = 0 ; i < STM32_DMA_MAX_CHANNELS; i++) { chan = &dmadev->chan[i]; chan->id = i; chan->vchan.desc_free = stm32_dma_desc_free; vchan_init(&chan->vchan, dd); chan->mdma_config.ifcr = res->start; chan->mdma_config.ifcr += STM32_DMA_IFCR(chan->id); chan->mdma_config.tcf = STM32_DMA_TCI; chan->mdma_config.tcf <<= STM32_DMA_FLAGS_SHIFT(chan->id); } ret = dma_async_device_register(dd); if (ret) goto clk_free; for (i = 0 ; i < STM32_DMA_MAX_CHANNELS; i++) { chan = &dmadev->chan[i]; ret = platform_get_irq(pdev, i); if (ret < 0 ) goto err_unregister; chan->irq = ret; ret = devm_request_irq(&pdev->dev, chan->irq, stm32_dma_chan_irq, 0 , dev_name(chan2dev(chan)), chan); if (ret) { dev_err(&pdev->dev, "request_irq failed with err %d channel %d\n" , ret, i); goto err_unregister; } } ret = of_dma_controller_register(pdev->dev.of_node, stm32_dma_of_xlate, dmadev); if (ret < 0 ) { dev_err(&pdev->dev, "STM32 DMA DMA OF registration failed %d\n" , ret); goto err_unregister; } platform_set_drvdata(pdev, dmadev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); pm_runtime_get_noresume(&pdev->dev); pm_runtime_put(&pdev->dev); dev_info(&pdev->dev, "STM32 DMA driver registered\n" ); return 0 ; err_unregister: dma_async_device_unregister(dd); clk_free: clk_disable_unprepare(dmadev->clk); return ret; }
STM32 DMA 驱动的注册与初始化 此代码片段是STM32 DMA驱动程序的入口点。它的核心作用不是执行DMA传输, 而是将整个DMA驱动程序作为一个platform_driver注册到Linux内核的设备模型中 。这使得内核能够识别并管理STM32的DMA控制器硬件。
其工作原理如下:
定义电源管理操作 : 首先, 它定义了一个dev_pm_ops结构体, stm32_dma_pm_ops。这个结构体包含了一系列函数指针, 用于响应内核的电源管理事件。通过使用SET_SYSTEM_SLEEP_PM_OPS和SET_RUNTIME_PM_OPS宏, 它将驱动内部的挂起/恢复函数(如stm32_dma_pm_suspend)与标准的系统睡眠和运行时电源管理(Runtime PM)钩子关联起来。这使得DMA控制器可以在系统进入低功耗状态或自身空闲时被安全地关闭, 并在需要时被唤醒。
定义平台驱动主体 : 接着, 它定义了platform_driver的核心结构体stm32_dma_driver。这个结构体是驱动的”身份证”, 它告诉内核:
名称(name) : 驱动的名字是 “stm32-dma”。
匹配方式(of_match_table) : 驱动通过一个名为stm32_dma_of_match的表来与设备树中的节点进行匹配。当内核在设备树中找到一个节点的compatible属性与此表中的条目匹配时, 就认为找到了一个该驱动可以管理的设备。
电源管理(pm) : 将上一步定义的电源管理操作挂接到驱动上。
探测函数(probe) : 当匹配成功时, 内核应该调用的核心初始化函数是stm32_dma_probe。这个probe函数(未在此代码段中显示)才是真正负责初始化DMA硬件、分配通道等工作的函数。
注册驱动 : 最后, stm32_dma_init函数作为驱动的初始化入口, 调用platform_driver_register将stm32_dma_driver结构体提交给内核。subsys_initcall宏则是一个链接器指令, 它告诉内核在系统启动过程中的一个特定阶段(在核心子系统初始化之后)来调用stm32_dma_init函数, 从而完成驱动的注册。
在STM32H750系统上, __init宏会将stm32_dma_init函数放入一个特殊的内存段, 这段内存在内核启动完成后可以被释放, 从而为内存资源有限的MCU节省宝贵的RAM。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 static const struct of_device_id stm32_dma_of_match [] = { { .compatible = "st,stm32-dma" , }, { }, }; MODULE_DEVICE_TABLE(of, stm32_dma_of_match); static const struct dev_pm_ops stm32_dma_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(stm32_dma_pm_suspend, stm32_dma_pm_resume) SET_RUNTIME_PM_OPS(stm32_dma_runtime_suspend, stm32_dma_runtime_resume, NULL ) }; static struct platform_driver stm32_dma_driver = { .driver = { .name = "stm32-dma" , .of_match_table = stm32_dma_of_match, .pm = &stm32_dma_pm_ops, }, .probe = stm32_dma_probe, }; static int __init stm32_dma_init (void ) { return platform_driver_register(&stm32_dma_driver); } subsys_initcall(stm32_dma_init);
好的,我将对您提供的这段关于Linux内核中STM32 DMA控制器驱动底层传输启动代码的进行分析。
stm32_dma_start_transfer: STM32 DMA硬件传输的启动引擎本代码片段展示了STM32 DMA控制器驱动的核心引擎 ——stm32_dma_start_transfer函数。这是在DMA传输被提交(submit)并 触发(issue)后,驱动内部真正与硬件交互、启动一次DMA数据搬运 的函数。其核心功能是:从virt_dma_chan的待处理队列中获取一个传输描述符,解析出其中预先计算好的寄存器值,然后将这些值 批量写入 到STM32 DMA控制器的物理寄存器 中,最后通过置位EN(Enable)位来点燃引擎,启动传输。
实现原理分析 此机制是通用dmaengine框架与具体STM32 DMA硬件之间的“最后一公里”。它将dmaengine抽象的、与硬件无关的传输请求(virt_dma_desc),转化为对硬件寄存器的精确、有序的写操作。
获取传输任务 :
vchan_next_desc(&chan->vchan): vchan(virtual channel)是dmaengine提供的一个通用队列。此函数从队列中取出下一个待处理的传输描述符(vdesc)。
chan->desc = to_stm32_dma_desc(vdesc);: 将通用的vdesc转换为STM32 DMA驱动特定的stm32_dma_desc。这个特定结构中包含了之前在prep阶段(如dmaengine_prep_dma_cyclic)就已经预先计算好 的所有寄存器值。
安全启动序列 :
stm32_dma_disable_chan(chan): 这是至关重要的第一步 。在向DMA寄存器写入新配置之前,必须确保该DMA流(stream/channel)处于禁用状态 。此函数会轮询等待SCR寄存器的EN位变为0。
寄存器批量写入 :
sg_req = &chan->desc->sg_req[chan->next_sg];: 获取当前散列表(scatter-gather list)段对应的寄存器配置。对于简单的非SG传输,只有一个段。
reg = &sg_req->chan_reg;: 获取预计算好的寄存器值结构体。
stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), reg->dma_scr);: 这是核心的硬件操作 。函数将预先计算好的值,逐一写入到DMA流的各个物理寄存器中:
SCR (Stream Configuration Register): 配置传输方向、数据宽度、优先级、中断使能等。
SPAR (Stream Peripheral Address Register): 设置外设端的地址(如USARTx_RDR)。
SM0AR (Stream Memory 0 Address Register): 设置内存端的地址(DMA缓冲区的地址)。
SFCR (Stream FIFO Control Register): 配置DMA内部的FIFO模式。
SM1AR (Stream Memory 1 Address Register): 用于双缓冲模式下的第二个内存地址。
SNDTR (Stream Number of Data to Transfer Register): 设置本次传输的数据量。
这种先在内存中准备好所有值,然后一次性批量写入硬件的设计,非常高效且清晰。
循环与双缓冲 (stm32_dma_configure_next_sg) :
STM32的DMA控制器支持双缓冲模式(Double Buffer Mode) ,这是实现无缝循环传输的基础。
DMA流有两个内存地址寄存器:SM0AR和SM1AR。SCR寄存器中有一个CT(Current Target)位,用于指示当前DMA正在使用哪个内存地址。
stm32_dma_configure_next_sg的作用是预先配置下一个缓冲区 。当DMA正在向SM0AR对应的缓冲区传输数据时(此时CT=0),这个函数会被调用,将下一个 SG段的内存地址写入到SM1AR中。当DMA完成当前传输后,硬件会自动将CT位翻转为1,并无缝地开始向SM1AR的地址传输,同时产生一次中断。中断服务程序会再次调用此函数,将再下一个段的地址写入SM0AR,如此循环往复。
if (chan->desc->cyclic): 只有在循环模式下,才会调用此函数来预配置下一个缓冲区。
启动传输 :
reg->dma_scr |= STM32_DMA_SCR_EN;: 在软件中将使能位置位。
stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), reg->dma_scr);: 最后一步 ,将包含EN=1的SCR值写入硬件,DMA传输立即开始 。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 static void stm32_dma_start_transfer (struct stm32_dma_chan *chan) { ret = stm32_dma_disable_chan(chan); if (ret < 0 ) return ; if (!chan->desc) { vdesc = vchan_next_desc(&chan->vchan); if (!vdesc) return ; list_del(&vdesc->node); chan->desc = to_stm32_dma_desc(vdesc); chan->next_sg = 0 ; } sg_req = &chan->desc->sg_req[chan->next_sg]; reg = &sg_req->chan_reg; if (chan->trig_mdma && chan->dma_sconfig.direction != DMA_MEM_TO_DEV) reg->dma_scr &= ~STM32_DMA_SCR_TCIE; reg->dma_scr &= ~STM32_DMA_SCR_EN; stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), reg->dma_scr); stm32_dma_write(dmadev, STM32_DMA_SPAR(chan->id), reg->dma_spar); stm32_dma_write(dmadev, STM32_DMA_SM0AR(chan->id), reg->dma_sm0ar); stm32_dma_write(dmadev, STM32_DMA_SFCR(chan->id), reg->dma_sfcr); stm32_dma_write(dmadev, STM32_DMA_SM1AR(chan->id), reg->dma_sm1ar); stm32_dma_write(dmadev, STM32_DMA_SNDTR(chan->id), reg->dma_sndtr); stm32_dma_sg_inc(chan); status = stm32_dma_irq_status(chan); if (status) stm32_dma_irq_clear(chan, status); if (chan->desc->cyclic) stm32_dma_configure_next_sg(chan); stm32_dma_dump_reg(chan); chan->busy = true ; chan->status = DMA_IN_PROGRESS; reg->dma_scr |= STM32_DMA_SCR_EN; stm32_dma_write(dmadev, STM32_DMA_SCR(chan->id), reg->dma_scr); dev_dbg(chan2dev(chan), "vchan %p: started\n" , &chan->vchan); } static void stm32_dma_configure_next_sg (struct stm32_dma_chan *chan) { dma_scr = stm32_dma_read(dmadev, STM32_DMA_SCR(id)); sg_req = &chan->desc->sg_req[chan->next_sg]; if (dma_scr & STM32_DMA_SCR_CT) { dma_sm0ar = sg_req->chan_reg.dma_sm0ar; stm32_dma_write(dmadev, STM32_DMA_SM0AR(id), dma_sm0ar); } else { dma_sm1ar = sg_req->chan_reg.dma_sm1ar; stm32_dma_write(dmadev, STM32_DMA_SM1AR(id), dma_sm1ar); } }
drivers/dma/stm32/stm32-mdma.c STM32高性能DMA架构解析:MDMA与DMA的核心差异及最佳实践 在STM32的高性能产品线中(如STM32H7和STM32U5系列), STMicroelectronics引入了一种强大的分层DMA架构, 同时包含了传统的DMA/BDMA控制器和一颗性能更强的MDMA(主DMA)控制器。对于开发者而言, 理解这两者的工作原理、设计哲学、优势和使用场景, 是充分发挥MCU性能、优化系统功耗的关键。
核心比喻:物流中心 vs. 专属快递员
DMA/BDMA (普通/基本DMA) : 可以看作是每个外设(如SPI、UART)的“专属快递员 ”。它的任务明确、路径固定, 主要负责将指定外设的数据传入或传出内存, 响应速度快, 功耗较低, 但灵活性和吞吐量有限。
MDMA (主DMA) : 则像是整个系统的“中央物流中心 ”。它是一个独立的、高性能的数据搬运处理器, 拥有访问所有系统总线和内存的最高权限(Master地位)。它可以处理来自任何地方、发往任何地方的大批量数据请求, 吞吐量巨大, 调度灵活, 但启动和运行的开销也相对更高。
一、 工作原理与设计哲学 DMA/BDMA 的原理与作用
原理 : DMA/BDMA是一个从设备 (Slave) , 紧密耦合于外设。它的工作通常由外设触发。例如, 当UART接收到一个字节后, 会向DMA控制器发出一个请求。DMA控制器在获得总线访问权后, 自动将UART数据寄存器的内容搬运到预先配置好的内存地址, 然后释放总线, 等待下一次请求。这个过程完全无需CPU干预。
设计 : 它的设计目标是解放CPU, 处理低速、持续、可预测的外设数据流 。其请求映射通常是固定的, 例如, SPI1的发送请求只能连接到DMA1的特定通道/流。它主要连接在性能较低的AHB/APB总线上。
MDMA 的原理与作用
原理 : MDMA是一个主设备 (Master) , 与CPU处于同等地位, 可以主动发起对总线矩阵的访问。它连接在MCU内部最高速的64位AXI总线 上, 能够以极高的速度在内存(内部SRAM、外部SDRAM)和外设之间传输数据。
设计 : 它的设计目标是处理高速、大批量的数据传输, 尤其是复杂的内存到内存操作 。其最关键的特性是拥有一个DMA请求路由器 (DMA Router) , 允许将来自任何外设的DMA请求灵活地路由到任意一个空闲的MDMA通道进行处理, 提供了极大的系统设计灵活性。
二、 核心异同点对比
特性
DMA / BDMA (专属快递员)
MDMA (物流中心)
硬件角色
从设备 (Slave), 由外设触发
主设备 (Master), 可主动发起传输
总线连接
32位 AHB / APB 总线
64位 AXI 总线
性能吞吐量
中低
非常高
请求映射
固定或有限映射 (外设 -> 特定通道)
灵活路由 (外设 -> 任意通道)
中断机制
每个通道/流通常有独立的中断
所有通道共享一个全局中断
数据宽度
最高支持32位 (4字节)
最高支持64位 (8字节)
主要任务
外设到内存 (P2M), 内存到外设 (M2P)
内存到内存 (M2M), 高速P2M/M2P
相同点 :
核心目标相同 : 两者都是为了在没有CPU干预的情况下传输数据, 降低CPU负载, 提升系统并行处理能力。
基本配置相似 : 都需要配置源地址、目标地址、传输数据量、传输方向、数据宽度等基本参数。
均可被内核框架管理 : 在Linux/RTOS中, 它们都可以被统一的DMA引擎框架管理, 为上层驱动提供标准化的API。
三、 各自优势与最佳使用场景 DMA/BDMA 的优势与场景
优势 :
低延迟 : 专为外设服务, 响应外设请求的延迟较低。
低功耗 : 处理简单任务时, 无需唤醒和驱动强大的AXI总线和MDMA控制器, 系统整体功耗更低。
配置简单 : 请求映射固定, 软件配置相对直接。
最佳使用场景 :
串行通信 : UART、SPI、I2C 的连续数据收发。
数据采集 : ADC 连续模式下, 将转换结果自动存入内存。
音频/信号生成 : I2S、SAI、DAC 的数据流传输。
定时器触发 : 由定时器更新事件触发, 向GPIO或内存块进行固定模式的数据传输。
MDMA 的优势与场景
优势 :
极高吞吐量 : 64位总线宽度使其在处理大块数据时效率无与伦比。
极高灵活性 : 请求路由器允许动态分配通道, 优化系统负载。
强大的内存操作 : 为内存到内存的复制、移动、格式转换等操作提供了硬件级加速。
支持高级特性 : 如链表模式, 可实现复杂的、无CPU干预的数据处理链。
最佳使用场景 :
图形图像处理 : 在内存中搬运LTDC的帧缓冲区, 或者配合DMA2D进行图层混合。
大数据块搬运 : 在内部RAM、外部SDRAM、QuadSPI Flash之间高速传输固件、资源文件等。
复杂数据处理 : 利用链表模式对数据包进行预处理, 如添加/剥离包头, 将分散的数据包拼接成连续的缓冲区。
CPU算法卸载 : 将CPU密集型的memcpy, memset等操作完全交给MDMA完成。
四、 补充要点:协同工作与高级特性 1. 协同工作:DMA链式操作 (DMA Chaining) MDMA和DMA并非孤立工作, 它们可以形成强大的DMA链 。这是一个非常高效的工作模式:
场景 : 一个传感器通过SPI接口将数据传输进来。
流程 :
DMA工作 : 普通DMA负责处理SPI的外设请求, 将一小块数据(例如1KB)从SPI数据寄存器搬运到内存中的一个Ping-Pong缓冲区A。
触发MDMA : 当DMA完成对缓冲区A的填充后, 它不产生CPU中断, 而是自动触发MDMA的一个通道 。
MDMA接力 : MDMA被触发后, 开始将缓冲区A中的1KB数据搬运到外部SDRAM的一个巨大缓冲区中进行存储, 或者对这1KB数据进行格式转换后存入另一个处理缓冲区。
并行处理 : 在MDMA处理缓冲区A数据的同时 , 普通DMA已经开始向另一个Ping-Pong缓冲区B中填充下一块SPI数据。
优势 : 整个数据流(从外设到最终内存)完全自动化, CPU只在需要时(例如整个大缓冲区都满了)才介入一次, 极大地提升了实时性能和系统效率。
2. MDMA的高级特性:链表模式 (Linked-List Mode) MDMA支持一种强大的“链表”模式, 这本质上是一种硬件级的分散-聚集(Scatter-Gather)DMA 。
原理 : 开发者可以在内存中构建一个描述符链表, 每个描述符包含了一次独立传输的所有信息(源地址、目标地址、长度等)以及指向下一个描述符的指针。
工作方式 : 启动MDMA时, 只需告诉它第一个描述符的地址。MDMA会自动完成第一个传输, 然后根据指针加载并执行第二个、第三个…直到遇到链表末尾。
优势 : 这允许MDMA在完全没有CPU干预的情况下, 执行一系列复杂且非连续的传输任务, 是实现高性能网络协议栈、文件系统数据读写等应用的神器。
3. 资源规划与功耗考量
“杀鸡焉用牛刀” : 永远不要用MDMA去处理一个低速的UART数据流。这样做不仅配置更复杂, 而且会不必要地激活AXI总线和MDMA的时钟, 增加系统功耗。
按需分配 : 在设计系统时, 应该将持续的、低带宽的外设任务分配给DMA/BDMA, 将突发的、高带宽的内存操作任务预留给MDMA。
功耗优化 : 充分利用普通DMA可以让MDMA和AXI总线矩阵长时间处于时钟门控关闭的低功耗状态, 这对于电池供电的设备至关重要。
结论 STM32的MDMA和DMA/BDMA共同构成了一个功能互补、性能分层的强大数据传输架构。它们不是竞争关系, 而是协同工作的伙伴 。掌握“简单持续用DMA, 复杂批量用MDMA, 高效联动靠链式触发 ”的核心思想, 并根据具体应用场景进行合理规划, 才能将STM32H7/U5系列MCU的潜力发挥到极致, 设计出真正高性能、低功耗的嵌入式系统。