文章目录使用者视角的标准四步选择使用DMA的方法使用者视角的标准四步任何外设驱动使用 DMA 都是固定套路后面读驱动就知道每个回调是被谁、在什么时机调进来的/* ① 申请通道通常开机时做一次 */chandma_request_chan(dev,tx);/* 根据设备树 dmas/dma-names 找到通道 *//* ② 配置通道告诉 DMA 外设 FIFO 地址、位宽等 */structdma_slave_configcfg{.directionDMA_MEM_TO_DEV,.dst_addri2s_fifo_phys_addr,/* 外设 FIFO 物理地址 */.dst_addr_widthDMA_SLAVE_BUSWIDTH_4_BYTES,};dmaengine_slave_config(chan,cfg);/* ③ 准备描述符描述搬什么并提交 */descdmaengine_prep_slave_sg(chan,sgl,sg_len,DMA_MEM_TO_DEV,flags);desc-callbackmy_done_callback;/* 搬完后通知我 */cookiedmaengine_submit(desc);/* 进入已提交队列还没开始搬 *//* ④ 发令枪真正启动 */dma_async_issue_pending(chan);举个例子staticintguc_adc_dma_request(structdevice*dev,structiio_dev*indio_dev){structguc_adc*infoiio_priv(indio_dev);structdma_slave_configconfig;intret;info-dma_chandma_request_chan(dev,rx);if(IS_ERR(info-dma_chan)){retPTR_ERR(info-dma_chan);if(ret!-ENODEV)returndev_err_probe(dev,ret,DMA channel request failed with\n);/* DMA is optional: fall back to IRQ mode */info-dma_chanNULL;return0;}info-rx_bufdma_alloc_coherent(info-dma_chan-device-dev,GUC_DMA_BUFFER_SIZE,info-rx_dma_buf,GFP_KERNEL);if(!info-rx_buf){ret-ENOMEM;gotoerr_release;}/* Configure DMA channel to read data register */memset(config,0,sizeof(config));config.src_addr(dma_addr_t)info-phys_addr;config.src_addrGUC_CTRL_NOR_STS1;config.src_addr_widthDMA_SLAVE_BUSWIDTH_2_BYTES;config.dst_addr_widthDMA_SLAVE_BUSWIDTH_2_BYTES;config.src_msize64;config.dst_msize64;retdmaengine_slave_config(info-dma_chan,config);if(ret)gotoerr_free;return0;err_free:dma_free_coherent(info-dma_chan-device-dev,GUC_DMA_BUFFER_SIZE,info-rx_buf,info-rx_dma_buf);err_release:dma_release_channel(info-dma_chan);returnret;}选择使用DMA的方法① 申请通道dma_request_chan(dev,tx)← 走 DTS dmas/dma-namesdma_request_chan_by_mask(mask)← 按能力找空闲通道memcpy 用 │ ② 配置dmaengine_slave_config(chan,cfg)← 外设 FIFO 地址/位宽/msize │ ③ 描述dmaengine_prep_slave_single()┐dmaengine_prep_slave_sg()├─ 四选一 ★dmaengine_prep_dma_cyclic()│dmaengine_prep_dma_memcpy()┘ desc-callbackfn;← 可选完成通知 │ ④ 提交启动 cookiedmaengine_submit(desc)← 仅入队没开始搬dma_async_issue_pending(chan)← 发令枪 │ ⑤ 收尾dmaengine_tx_status()/回调/轮询 ← 等完成dmaengine_pause()/_resume()dmaengine_terminate_sync()← 中止dma_release_channel(chan)← 释放mem_to_mem 和 外设的使用是不一样的/* 路径 1外设专用通道走设备树 */chandma_request_chan(dev,rx);↑ 内核查 DTS 的 dmasaxi_dmac25;dma-namesrx;再调驱动的 of_xlate:1541chan-hw_handshake_numdma_spec-args[0] → 拿到的通道被绑定了特定握手号/* 路径 2任意空闲通道按能力匹配memcpy 专用 */dma_cap_zero(mask);dma_cap_set(DMA_MEMCPY,mask);chandma_request_chan_by_mask(mask);↑ 不需要 DTS因为 M2M 不用握手线 dma_request_slave_channel()已废弃新代码别用。四种 prep 的差异:客户端侧 4 个入口 驱动侧只实现 3 个回调 ───────────────── ──────────────────── prep_slave_single ─┐ ├────── device_prep_slave_sg :892 prep_slave_sg ─────┘ prep_dma_cyclic ───────── device_prep_dma_cyclic :811 prep_dma_memcpy ───────── device_prep_dma_memcpy :976完成通知的三种方式/* A. 回调最常用——在 tasklet 上下文不能睡眠 */desc-callbackmy_done;desc-callback_paramctx;/* B. 轮询 cookie —— 不要中断时用 */statusdmaengine_tx_status(chan,cookie,state);/* DMA_COMPLETE / DMA_IN_PROGRESS / DMA_PAUSEDstate.residue 剩余字节 *//* C. completion 同步等待 —— 进程上下文 */desc-callbackcomplete_fn;/* 回调里 complete(done) */dma_async_issue_pending(chan);wait_for_completion_timeout(done,HZ);