当前位置:   article > 正文

第 27 课 DMA 驱动程序_dma驱动开发

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; 	
}
  • 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

这里有一个小技巧:如果在写程序的时候忘记了调用了某个函数需要包含的头文件,则可以在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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/976634
推荐阅读
相关标签
  

闽ICP备14008679号