标签:查看 date 清零 存储器 驱动 shell event 表示 字符设备
目录
title: DMA设计
tags: linux
date: 2019年1月5日 17:27:08
toc: true
---
一个简单的DMA框图如下DREQ→HOLD→HLDA→DACK
DMAC的一些必备特性:
信号线如下
流程顺序
DMA控制器的基本组成
软件触发
外设触发
外部引脚触发,这个是STM32所没有的,这个是有具体的时序的,STM32应该是可以用中断引脚触发
2440通道传输类型
外部引脚的DMA协议
这个貌似有点复杂,暂时也没用过,暂时不做深入分析了
2440里面的DMA传输分为两个层次,一个是REQ/ACK协议,还一个是单模式和全模式,所谓单模式个全模式是指的在一次DMA请求中的传输数量
时序参数
信号的有效性: 高电平无效,低电平有效,这里称为assert
REQ有效
REQ的只能在ACK释放(high)的时候才能被asserted(high),也就是说 请求信号只能在ACK为高的时候才能被MCU的DMA识别到
信号生效识别
nXDREQ请求生效并经过2CLK周期同步后,nXDACK响应并开始生效,但至少还要经过3CLK的周期延迟,DMA控制器才可获得总线的控制权,并开始数据传输。
Single service : 当没有原子传输(unit/burst)后,停止传输,等待下一次请求
Whole service : 重复原子传输,直到计数器到0.这个模式下,不需要另外的请求.这个是重点
在全模式下,DMA也会在每个原子传输后释放总线然后去尝试获得总线,以防止总线被占据
也就是说,全模式是我们一般使用的模式,使用计数器,一次请求会传输所有数据.单模式一次传输一个原子操作.
ACK清零:
中断发生:
这里的协议,指的是请求应答协议,分为两种.
Demand Mode 请求/查询模式
如果REQ信号有效,则一直保持传输,这个时候的ACK只是告诉你这一次传输完成
这个模式会霸占总线的,不像全服务中完成一个原子操作释放一下总线
Handshake Mode 握手模式
如果REQ信号释放,这个时候DMA控制器释放ACK两个周期,否则DMA会一直等到REQ的释放
也就是启动下一次传输前,需要请求端先释放,然后MCU完成后会无效ACK两个周期告诉请求端,请求端再来请求,否则一直等待
在Demond模式下,如果DMA完成一次请求后如果Request仍然有效,那么DMA就认为这是下一次DMA请求,并立即开始下一次的传输;
在Handshake模式下,DMA完成一次请求后等待Request信号无效,如果Request无效,DMA会无效ACK两个时钟周期,再等待下一次Request。
数据传输的大小=数据传输次数 * 每次传输的读写次数 * 一次读或者写的大小
单服务查询请求模式
单服务握手模式
全服务握手模式
在这里其实无所谓hand了,因为在全模式下只需要一次请求就能完成后续的所有操作
这里的代码就是驱动实现一个内存的拷贝,不涉及到上面长篇大论的时序分析,只是需要设置好相关的寄存器配置配置DMA的模式,然后启动DMA后进入休眠,完成后DMA中断唤醒后退出.
测试程序调用字符驱动程序的接口ioctl
来测试即可
写程序前需要查看用到的DMA
cat /proc/interrupts
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/dma-mapping.h>
#define S3C_DMA_SIZE 512*1024 //DMA传输长度 512KB
#define NORMAL_COPY 0 //两个地址之间的正常拷贝
#define DMA_COPY 1 //两个地址之间的DMA拷贝
/*函数声明*/
static DECLARE_WAIT_QUEUE_HEAD(s3c_dma_queue); //声明等待队列
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags);
/*
* 定义中断事件标志
* 0:进入等待队列 1:退出等待队列
*/
static int s3c_dma_even=0;
static unsigned char *source_virt; //源虚拟地址
static unsigned int source_phys; //源物理地址
static unsigned char *dest_virt; //目的虚拟地址
static unsigned int dest_phys; //目的虚拟地址
/*DMA3寄存器*/
struct S3c_dma3_regs{
unsigned int disrc3 ; //0x4b0000c0
unsigned int disrcc3 ;
unsigned int didst3 ;
unsigned int didstc3 ;
unsigned int dcon3 ;
unsigned int dstat3 ;
unsigned int dcsrc3 ;
unsigned int dcdst3 ;
unsigned int dmasktrig3; //0x4b0000e0
};
static volatile struct S3c_dma3_regs *s3c_dma3_regs;
/*字符设备操作*/
static struct file_operations s3c_dma_fops={
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
/*中断服务函数*/
static irqreturn_t s3c_dma_irq (int irq, void *dev_id)
{
s3c_dma_even=1; //退出等待队列
wake_up_interruptible(&s3c_dma_queue); //唤醒 中断
return IRQ_HANDLED;
}
/*ioctl函数*/
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags)
{
int i;
memset(source_virt, 0xAA, S3C_DMA_SIZE);
memset(dest_virt, 0x55, S3C_DMA_SIZE);
switch(cmd)
{
case NORMAL_COPY: //正常拷贝
for(i=0;i<S3C_DMA_SIZE;i++)
dest_virt[i] = source_virt[i];
if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)
{
printk("NORMAL_COPY OK\n");
return 0;
}
else
{
printk("NORMAL_COPY ERROR\n");
return -EAGAIN;
}
case DMA_COPY: //DMA拷贝
s3c_dma_even=0; //进入等待队列
/*设置DMA寄存器,启动一次DMA传输 */
/* 源的物理地址 */
s3c_dma3_regs->disrc3 = source_phys;
/* 源位于AHB总线, 源地址递增 */
s3c_dma3_regs->disrcc3 = (0<<1) | (0<<0);
/* 目的的物理地址 */
s3c_dma3_regs->didst3 = dest_phys;
/* 目的位于AHB总线, 目的地址递增 */
s3c_dma3_regs->didstc3 = (0<<2) | (0<<1) | (0<<0);
/* 使能中断,单个传输,软件触发, */
s3c_dma3_regs->dcon3=(1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(S3C_DMA_SIZE<<0);
//启动一次DMA传输
s3c_dma3_regs->dmasktrig3 = (1<<1) | (1<<0);
wait_event_interruptible(s3c_dma_queue, s3c_dma_even); //进入睡眠,等待DMA传输中断到来才退出
if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)
{
printk("DMA_COPY OK\n");
return 0;
}
else
{
printk("DMA_COPY ERROR\n");
return -EAGAIN;
}
break;
}
return 0;
}
static unsigned int major;
static struct class *cls;
static int s3c_dma_init(void)
{
/*1.1 注册DMA3 中断 */
if(request_irq(IRQ_DMA3, s3c_dma_irq,NULL, "s3c_dma",1))
{
printk("Can‘t request_irq \"IRQ_DMA3\"!!!\n ");
return -EBUSY;
}
/*1.2 分配两个DMA缓冲区(源、目的)*/
source_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &source_phys, GFP_KERNEL);
if(source_virt==NULL)
{
printk("Can‘t dma_alloc \n ");
return -ENOMEM;
}
dest_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &dest_phys, GFP_KERNEL);
if(dest_virt==NULL)
{
printk("Can‘t dma_alloc \n ");
return -ENOMEM;
}
/*2.注册字符设备,并提供文件操作集合fops*/
major=register_chrdev(0, "s3c_dma",&s3c_dma_fops);
cls= class_create(THIS_MODULE, "s3c_dma");
class_device_create(cls, NULL,MKDEV(major,0), NULL, "s3c_dma");
s3c_dma3_regs=ioremap(0x4b0000c0, sizeof(struct S3c_dma3_regs));
return 0;
}
static void s3c_dma_exit(void)
{
iounmap(s3c_dma3_regs);
class_device_destroy(cls, MKDEV(major,0));
class_destroy(cls);
dma_free_writecombine(NULL, S3C_DMA_SIZE, dest_virt, dest_phys);
dma_free_writecombine(NULL, S3C_DMA_SIZE, source_virt, source_phys);
free_irq(IRQ_DMA3, 1);
}
module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
/* ./dma_test NORMAL
* ./dma_test DMA
*/
#define NORMAL_COPY 0 //两个地址之间的正常拷贝
#define DMA_COPY 1 //两个地址之间的DMA拷贝
void print_usage(char *name)
{
printf("Usage:\n");
printf("%s <NORMAL | DMA>\n", name);
}
int main(int argc, char **argv)
{
int fd,i=30;
if (argc != 2)
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/s3c_dma", O_RDWR);
if (fd < 0)
{
printf("can‘t open /dev/s3c_dma\n");
return -1;
}
if (strcmp(argv[1], "NORMAL") == 0)
{
while (i--) //调用驱动的ioctl(),30次
{
ioctl(fd, NORMAL_COPY);
}
}
else if (strcmp(argv[1], "DMA") == 0)
{
while (i--) //调用驱动的ioctl(),30次
{
ioctl(fd, DMA_COPY);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
./dma_test NORMAL &
卡住./dma_test DMA &
,输入命令有反应标签:查看 date 清零 存储器 驱动 shell event 表示 字符设备
原文地址:https://www.cnblogs.com/zongzi10010/p/10225384.html