第 27 课 DMA 驱动程序

第1节 DMA的引入

直接存储器存取(DMADirect Memory Access)方式是用硬件实现存储器与存储器之间或存储器与I\O设备之间直接进行高速数据传送,不需要CPU的干预。这种方式通常用来传送数据块。可以认为DMA是一个半智能化的控制器,专门负责数据在存储设备间的传输工作。

在这里插入图片描述
在这里插入图片描述
注意:这里DMA的外部启动有一个例子:音频接口I2S的数据传输
当外部的MIC接收到数据后,会将数据首先存储在其内部的FIFO缓冲区,之后向DMA递交一个数据拷贝请求(源:就是I2S的FIFO,目的:就是目标存储器),最后由DMA控制器完成数据的拷贝工作,期间CPU不参与。

第2节 DMA驱动程序的编写

思路:一个简单的例子:在内存中完成数据从 “源” 到 “目的” 的拷贝。
用两种方式实现:使用DMA和不使用DMA。最后比较差别。
方式:以字符设备驱动程序为模板来实现,用字符设备驱动程序中的ioctl函数来实现内存数据的复制功能。

字符设备驱动程序的常规5步操作
按照字符设备驱动的常规5步来进行编写代码。

1. 确定主设备号:

在这里插入图片描述

2. 构造file_operations结构体,利用ioctl 函数来帮我们完成内存数据拷贝的任务

在这里插入图片描述
我们要做的事情是先分配一块可操作的缓冲区,然后进行数据拷贝操作;

这里需要注意的是:分配缓冲区的时候不能用kmalloc()函数!!
在这里插入图片描述
因为kmalloc()函数分配的内存虽然在虚拟地址上连续,但是其物理地址并不连续。

如果想用DMA操作内存,就必须得到连续的物理内存!!

分配方法:
在这里插入图片描述
用什么函数分配连续的物理内存?
在这里插入图片描述
函数的原型:在这里插入图片描述

3. 向内核注册。为了让内核帮我们自动创建设备节点,还需要创建设备类:

创建类:
在这里插入图片描述
之后在类下创建设备:
在这里插入图片描述

4. 入口、出口函数:

在这里插入图片描述
到这里整个框架就已经写好了。

重点在于如何实现ioctl()函数的内存拷贝功能。
1)首先使用常规操作(非DMA)
在这里插入图片描述
在这里插入图片描述
这里的ioctl()函数根据cmd命令参数来进行不同的操作。

注意:这里首先需要对之前分配好的内存进行设置:
在这里插入图片描述

2)使用DMA操作进行数据拷贝

使用DMA的方法就是看懂2440中的DMA控制器寄存器含义,然后通过寄存器的设置将“源 目的 和长度”告诉DMA控制器,然后启动即可。
在这里插入图片描述
2440DMA的控制寄存器一共有4个通道,每个通道都有一组寄存器进行控制:
在这里插入图片描述

在这里插入图片描述
DMA传输一启动,就会从 中得到字节数据,之后便立即将数据写入 目的 中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面分析DMA寄存器:重要的几个寄存器
1)DMA源相关的控制寄存器
在这里插入图片描述
在这里插入图片描述
2)DMA目的 相关的寄存器
在这里插入图片描述
在这里插入图片描述
由于DMA的寄存器数量太多(1个通道由9个寄存器控制),一个一个ioremap太麻烦。因此这里采用先定义一个结构体,然后直接映射的方法:
定义寄存器相关的结构体:
在这里插入图片描述
之后定义DMA各通道寄存器的基地址:
在这里插入图片描述
最后在入口函数中进行寄存器地址映射:
在这里插入图片描述
现在假设调用ioctl()函数后使用DMA操作:
把 源,目的,长度 告诉DMA控制器
前几个寄存器相对设置简单,DMA控制寄存器DCON参数较多,做以下解释:
在这里插入图片描述
选择Demand 模式,置位1;

在这里插入图片描述
由于实验在内存中完成,因此选择同步至HCLK时钟,设置为1;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
置为1;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里我们将DSZ设置为0(1字节)。
在这里插入图片描述
在这里插入图片描述
接下来设置DMASKTRIG寄存器启动DMA;
在这里插入图片描述
在这里插入图片描述

如何知道DMA什么时候完成??
DMA传输完成后会产生一个中断,因此需要注册一个中断来接收DMA发来的中断信号。
在这里插入图片描述
这里的中断号是在irqs.h中找到的:
在这里插入图片描述
但是需要注意的是:如果内核中已经使用了dma中断的话,我们是不能再次注册的,进入内核查看所有中断:
在这里插入图片描述
在这里插入图片描述
再次查看中断号后发现只能使用DMA3。
这里的请求中断和端口映射都需要改:
在这里插入图片描述
在这里插入图片描述
之后在入口函数中申请irq中断:
在这里插入图片描述
这里在中间如果没有申请成功目的内存,也要释放该irq中断:
在这里插入图片描述
最后在出口函数中释放之前申请的所有资源:
在这里插入图片描述
注意:这里可以引入“休眠-唤醒”机制:在入口函数中启动了DMA之后休眠;之后在DMA中断处理函数s3c_dma_irq()函数中唤醒:
①首先声明一个队列和事件标志:
在这里插入图片描述
②之后在启动DMA后休眠:
在这里插入图片描述
③在DMA的中断处理函数中唤醒
在这里插入图片描述
注意:“休眠-唤醒”的工作流程:
休眠后程序在休眠处停止执行,直到产生中断后在中断处理程序中将程序再次唤醒,唤醒后程序又会在之前休眠的地方继续往下执行。

编译后出现警告:
在这里插入图片描述
显示dma_free_writecombine函数未定义,参考LCD的程序发现是有关dma的一个头文件没有添加的缘故,
在这里插入图片描述
添加头文件后编译生成.ko文件—>/work/nfs_root/first_fs

5. 编写测试程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>

/* ./dma_test nodma
 * ./dma_test dma
 */
#define MEM_CPY_NO_DMA  0
#define MEM_CPY_DMA     1

void print_usage(char *name)
{
	printf("Usage:\n");
	printf("%s <nodma | dma>\n", name);
}
int main(int argc, char **argv)
{
	int fd;
	 	if (argc != 2)
	{
		print_usage(argv[0]);
		return -1;
	}
	fd = open("/dev/dma", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/dma\n");
		return -1;
	}

	if (strcmp(argv[1], "nodma") == 0)
	{
		while (1)
		{
			ioctl(fd, MEM_CPY_NO_DMA);
		}
	}
	else if (strcmp(argv[1], "dma") == 0)
	{
		while (1)
		{
			ioctl(fd, MEM_CPY_DMA);
		}
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	return 0; 	
}

这里有一个小技巧:如果在写程序的时候忘记了调用了某个函数需要包含的头文件,则可以在Linux系统中输入 “man + 函数名” 的方式查询:
在这里插入图片描述
这里的2指的是函数的参数个数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
之后上传至服务器中编译:
在这里插入图片描述

6. 测试

在这里插入图片描述
1)首先测试不使用DMA的情况:
在这里插入图片描述
应用程序在后台调用ioctl 进行数据的拷贝操作,在此过程中CPU全程都在参与数据的拷贝过程,一直消耗CPU资源,因此此时其他应用程序是不能够被有效执行的,或者说会出现很卡顿的情况。

2)测试使用DMA的情况:

传入相应的参数后,进入内核态执行ioctl函数中的dma分支,dma控制寄存器参数被设置好以后启动dma,启动之后CPU就不再介入数据的传输过程,期间CPU可以去执行其他程序,直到dma将数据拷贝完成产生中断信号才会通知CPU去处理。

在这里插入图片描述
输入了相应命令后并没有执行dma操作,没有打印出内存数据拷贝的结果,也就是说一直没有中断唤醒,应该是代码中有关dma寄存器设置的代码出现了问题:

7. 内核函数简单介绍

目前编写的操作DMA的程序是通过设置相应的DMA控制寄存器来进行数据的读写。但是实际上在内核中已经提供了完整的操作DMA的函数。
以声卡驱动的例子来看: s3c2410-uda1341.c

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AXI DMA是Xilinx公司提供的一种高性能DMA控制器IP,用于实现高速数据传输。在使用AXI DMA时,需要在Linux系统中加载相应的驱动程序,才能对其进行操作。以下是AXI DMA驱动程序的基本步骤: 1. 获取DMA的设备号:在Linux系统中,通过设备文件/dev下的名称来标识每一个硬件设备。在使用AXI DMA时,需要获取其设备号,可以通过以下命令获取: ``` cat /proc/devices | grep xdma ``` 其中xdma是AXI DMA的设备名,命令会返回类似于“246 xdma”这样的字符串,其中246就是该设备的主设备号。 2. 创建设备节点:在Linux系统中,每一个设备都需要创建一个对应的设备节点,以便用户空间程序可以通过该节点来访问设备。可以使用以下命令创建设备节点: ``` mknod /dev/xdma0 c 246 0 ``` 其中xdma0是设备节点的名称,246是设备的主设备号,0是设备的次设备号。 3. 打开设备:可以使用标准的文件操作函数open()来打开设备节点,并获取一个文件描述符。例如: ``` int fd = open("/dev/xdma0", O_RDWR); ``` 4. 配置AXI DMA:在使用AXI DMA进行数据传输之前,需要对其进行一些配置,例如设置传输方向、数据长度、中断使能等。可以通过ioctl()函数调用设备驱动程序提供的一些接口来进行配置。例如: ``` struct dma_config cfg; cfg.direction = DMA_MEM_TO_DEV; cfg.length = 1024; cfg.interrupt_enable = 1; ioctl(fd, XDMA_IOCTL_CONFIG, &cfg); ``` 其中XDMA_IOCTL_CONFIG是设备驱动程序提供的一个控制命令,用于配置AXI DMA的相关参数。 5. 启动传输:配置完成后,可以通过write()函数向设备节点发送数据,或者通过read()函数从设备节点读取数据。例如: ``` char buf[1024]; write(fd, buf, 1024); ``` 在传输完成后,可以通过设备驱动程序提供的一些接口来获取传输状态或者等待传输完成。例如: ``` int status; while (1) { ioctl(fd, XDMA_IOCTL_WAIT_TX_DONE, &status); if (status == XDMA_STAT_SUCCESS) break; } ``` 其中XDMA_IOCTL_WAIT_TX_DONE是设备驱动程序提供的一个控制命令,用于等待传输完成,并获取传输状态。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值