1 概述
本文描述Linux下的dma子系统,包括上层驱动调用方式(consumer),硬件驱动接入(provider)和dma子系统框架。
2 Linux下dma基本软件框架
如下框架大致描述了Linux下的dma子系统软件层次关系,Linux内核提供了dma子系统,向上驱动层提供DMA consumer,驱动开发者只需调用从summer的接口传入对应的dma通道需求信息;向下硬件层提供DMA Provider,该层主要由芯片厂商实现,向dma子系统注册一个chan_device;DMA核心层包括dmaengine、virt-dma和of-dma。
3 dma寄存器配置回顾
(1)配置前准备
1)时钟使能
启用DMA控制器的时钟(如STM32中通过RCC_AHBPeriphClockCmd开启DMA1/DMA2时钟)14。
2)参数确定
传输方向:外设↔存储器或存储器↔存储器18。
数据量:设置单次传输的数据单元数量(如DMA_CNDTRx寄存器)14。
数据宽度:选择字节(8位)、半字(16位)或字(32位)48。
(2)寄存器配置关键步骤
1)地址设置
外设地址:指向外设数据寄存器(如&USART1->DR)14。
存储器地址:指向内存缓冲区(如数组SendBuff[100]() )14。
地址递增模式:外设地址通常固定,存储器地址按数据宽度递增18。
2)传输模式与优先级
选择循环模式(自动重载数据量)或单次模式(需手动重启)18。
设置通道优先级(高/中/低),冲突时按通道编号仲裁14。
3)中断配置(可选)
使能传输完成/半传输/错误中断,并绑定中断服务函数14。
(3)启动与调试
1)初始化DMA通道
调用初始化函数(如DMA_Init()),加载配置到工作寄存器48。
2)启动传输
使能DMA通道(如DMA_Cmd(DMA1_Channel4, ENABLE))14。
3)数据监控
通过中断标志位或查询寄存器状态(如DMA_ISR)确认传输完成
4 驱动层调用DMA子系统
上层驱动使用DMA子系统,如下是deepseek生成的一个内存到内存的过程示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/platform_device.h>
#define DEVICE_NAME "dma_example"
#define BUFFER_SIZE 1024
static int major;
static struct dma_chan *dma_chan;
static struct completion dma_complete;
static dma_addr_t src_dma_addr, dst_dma_addr;
static void *src_buffer, *dst_buffer;
// DMA传输完成回调函数
static void dma_callback(void *data)
{
complete(&dma_complete);
}
// 字符设备的open方法
static int dma_example_open(struct inode *inode, struct file *filp)
{
return 0;
}
// 字符设备的release方法
static int dma_example_release(struct inode *inode, struct file *filp)
{
return 0;
}
// 字符设备的write方法,触发DMA传输
static ssize_t dma_example_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct dma_async_tx_descriptor *desc;
dma_cookie_t cookie;
// 初始化完成量
init_completion(&dma_complete);
// 准备DMA描述符
desc = dmaengine_prep_dma_memcpy(dma_chan, dst_dma_addr, src_dma_addr, BUFFER_SIZE, DMA_PREP_INTERRUPT);
if (!desc) {
printk(KERN_ERR "Failed to prepare DMA descriptor\n");
return -ENOMEM;
}
// 设置回调函数
desc->callback = dma_callback;
desc->callback_param = NULL;
// 提交DMA传输
cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie)) {
printk(KERN_ERR "Failed to submit DMA transfer\n");
return -EFAULT;
}
// 启动DMA传输
dma_async_issue_pending(dma_chan);
// 等待DMA传输完成
wait_for_completion(&dma_complete);
return count;
}
// 字符设备的文件操作结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = dma_example_open,
.release = dma_example_release,
.write = dma_example_write,
};
// 驱动模块初始化函数
static int __init dma_example_init(void)
{
// 注册字符设备
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) {
printk(KERN_ALERT "Failed to register character device\n");
return major;
}
printk(KERN_INFO "DMA example driver registered with major number %d\n", major);
// 分配DMA通道
dma_chan = dma_request_chan(NULL, "memcpy");
if (!dma_chan) {
printk(KERN_ERR "Failed to request DMA channel\n");
unregister_chrdev(major, DEVICE_NAME);
return -ENODEV;
}
// 分配源和目的缓冲区
src_buffer = dma_alloc_coherent(NULL, BUFFER_SIZE, &src_dma_addr, GFP_KERNEL);
dst_buffer = dma_alloc_coherent(NULL, BUFFER_SIZE, &dst_dma_addr, GFP_KERNEL);
if (!src_buffer || !dst_buffer) {
printk(KERN_ERR "Failed to allocate DMA buffers\n");
dma_release_channel(dma_chan);
unregister_chrdev(major, DEVICE_NAME);
return -ENOMEM;
}
// 初始化源缓冲区
memset(src_buffer, 0xAA, BUFFER_SIZE);
return 0;
}
// 驱动模块退出函数
static void __exit dma_example_exit(void)
{
// 释放DMA缓冲区
dma_free_coherent(NULL, BUFFER_SIZE, src_buffer, src_dma_addr);
dma_free_coherent(NULL, BUFFER_SIZE, dst_buffer, dst_dma_addr);
// 释放DMA通道
dma_release_channel(dma_chan);
// 注销字符设备
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_INFO "DMA example driver unregistered\n");
}
module_init(dma_example_init);
module_exit(dma_example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple DMA example driver");
先请求一个dma通道(示例请求了memcpy名的通道),使用