2016-04-22 17:16:33 u010481276 阅读数 787

DMA工作原理

经过一致性映射后,将返回的数据 dma_addr_t给控制器读写,函数返回的给driver读写

之后将DMA工作模块打开,有数据到buf了,就产生中断通知driver

2015-12-09 11:20:20 linglongqiongge 阅读数 3996

转载了他人的一篇文章,已表明转载的地址,如有侵权,请告知。

dma传输原理


1.DMA请求 
CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。  
2.DMA响应 
  DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。  3.DMA传输 
  DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。  
  在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。  4.DMA结束 
  当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。  
  由此可见,DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,使CPU的效率大为提高。


Linux dma传输

下面转载了他人的一篇文章:

其中,也阐述了dma工作过程,看到有人说第一幅图错了,应该是首先dma请求总线。但是,我的理解是图没错。首先就是dma进行cpu请求,请求的目的是让cpu放弃总线控制。cpu只有放弃了总线控制权,给dma响应后,才能进行数据的传输。

---------------------------------------------------------------------------------------分--------------------割------------------------------线----------------------------------------------------------------

////转载的原文地址:http://blog.csdn.net/jk198310/article/details/8671866 

最早接触DMA的时候是大三的微机原理,当时不是很理解,什么DMA模式啊,只知道是传输速度快,不经过CPU,但是到底是怎么样的不经过CPU呢?还是不理解。这次I2C控制器里面带了DMA的模式,所以有机会去接触下了。

       而具体的DMA的意思是什么http://baike.baidu.com/view/32471.htm,百度百科里还是不错的。

 

DMA的工作过程:


 

       对于嵌入式中的DMA,其实是在写数据寄存器的时候用dma的传输来代替。就像i2c设备,在发送和接收数据的时候都是要往数据寄存器中写数据的。比如那个寄存器是I2C_DATA,如果用cpu来传输的话就是writel(data, I2C_DATA);而用dma传输就是配置好要传输的buf长度,然后源地址就是buf的地址,目标地址就是I2C_DATA。

       这里还要注意经过cpu的是虚拟地址,而dma传输的是物理地址。

       其实dma传输就是dma控制在两个物理地址之间传输数据。

 

Linux下用dma传输主要调用下面这些函数就可以实现外部的dma了。


 

具体的就可以看下面简单的解释,以下主要是dma发送的,其实接收也一样的。配置反一下就可以了。

1、初始化DMA

[html] view plaincopy
  1.  dma_cap_zero(mask);  
  2.        dma_cap_set(DMA_SLAVE,mask);  
  3.    
  4.        /*1. Init rx channel */  
  5.        dws->rxchandma_request_channel(mask, dma_chan_filter, params);  
  6.        主要就是申请DMA通道。  
  7.    
  8.        dma_chan_filter这个函数主要是查找你的dma传输的设备的请求信号线,其具体是在注册时填写的。  
  9. 这里会根据这个函数返回的真假来判断已经注册在总线上的dma slave的。  
  10.    
  11.    
  12. buf =kmalloc(DMA_BUFFER_SIZE, GFP_KERNEL);  
  13. //申请一块地址,用来DMA传输的数据就放在这里  
  14. sg_init_one(&dma_dev->dmatx.sg,  buf,  DMA_BUFFER_SIZE);  
  15.        //初始化,其主要为了发送时虚拟地址和物理地址的映射。  

 

2、启动DMA

[html] view plaincopy
  1. struct dma_async_tx_descriptor *txdesc = NULL;  
  2.        struct dma_chan *txchan,;  
  3.        struct dma_slave_config txconf;  
  4.    
  5.        txchandws->txchan;  
  6.         
  7.        /*2. Prepare the TX dma transfer */  
  8.        txconf.directionDMA_TO_DEVICE;              //表示dma传输方向为发送  
  9.        txconf.dst_addrdws->dma_addr;                   //目标地址,物理地址  
  10.        txconf.dst_maxburstLNW_DMA_MSIZE_16; //最大传输的字节数  (转者注:这个参数应该是单次传输的字节长度
  11.        txconf.dst_addr_widthDMA_SLAVE_BUSWIDTH_2_BYTES;  //数据的位宽  
  12.    
  13.        txchan->device->device_control(txchan,DMA_SLAVE_CONFIG,  
  14.                                    (unsigned long) &txconf);  
  15.         
  16.        dws->tx_sgl.lengthdws->len;    //要传输的数据的长度  
  17.    
  18. dma_map_sg(dma_dev->dev,&dmatx->sg, 1, DMA_TO_DEVICE);  
  19. //通过这个函数来实现虚拟地址和物理地址的映射。  
  20.    
  21.        txdesctxchan->device->device_prep_slave_sg(txchan,  
  22.                             &dws->tx_sgl,  
  23.                             1,  
  24.                             DMA_TO_DEVICE,  
  25.                             DMA_PREP_INTERRUPT| DMA_COMPL_SKIP_DEST_UNMAP);  
  26.        txdesc->callbackdw_spi_dma_done;   //传输完成后的回调函数  
  27.        txdesc->callback_paramparams;      //回调函数中的参数  
  28.    
  29.        dmaengine_submit(txdesc);  
  30. dma_dev->device_issue_pending(txchan); // 启动dma传输了  
  31.         

配置好后,这样DMA就会开始传输了,然后传输完了以后就会有调用callback函数。

linux DMA
2018-05-21 21:02:33 ivychend 阅读数 553

1 DMA工作原理


  DMA的原意为direct memory access,也就是直接内存访问(可以理解为读写)。DMA传输实际上是DMA控制器将数据从一个设备拷贝到另一个设备的过程,DMA控制器的初始化需要cpu参与,但是数据传输过程是不需要cpu参与的。
  实际上DMA不只适用于有内存参与下的数据传输,下表是DMA适用的数据传输场景

传输方式 目的
1 内存 内存
2 内存 外围设备
3 外围设备 内存
4 外围设备 外围设备

表 1-1 DMA传输场景

2 DMA控制器


  DMA控制器简称为DMAC,DMAC控制器是SOC的一部分,关于DMAC的配置、数据传输都是在DMAC完成的。DMAC一般是AMBA设备,通过AHB总线与cpu连接。

dmac-block-diagram 图 2-1 dmac block diagram

  图2-1是具体DMAC的模块图,AHB slave interface连接cpu,用于配置、初始化DMAC。两个ABH master interface用于两个设备间数据传输。Interrupt request是DMAC本身的中断。
  
  DMA request and response interface是DMAC的请求与响应接口。一般来DMAC会有多个channel(通道),channel可以进行双向数据传输。channel与request是相对应的,一个channel通过request line可以发出一个request。channel是有优先级的,通常channel序号越小优先级越高。当channel5与channel6同时产生DMA request是时,DMAC是优先响应、处理channel5的请求。只有与request line相连接的设备才能产生DMA request。

3 数据传输过程


  有两种方式可以引发DMA数据传输:软件对数据的请求、硬件异步地将数据传递给系统。参考LDD,也就是linux设备驱动程序

3.1 软件对数据请求传输过程

  1. 当进程调用read,驱动程序函数分配一个DMA缓冲区,并让硬件将数据传输到这个缓冲区中,进程进入睡眠状态
  2. 硬件将数据写入到DMA缓冲区,写入完成后产生一个中断(由DMAC产生)
  3. 中断处理程序获取到输入的数据,然后唤醒进程,进程可以读取数据

3.2 硬件异步数据传输过程

  1. 硬件产生中断,宣告数据的到来
  2. 中断处理程序分配一个缓冲区,并且告诉硬件向哪里传输数据
  3. 外围设备将数据写入缓冲区,完成后产生另外一个中断
  4. 处理程序分发新数据,唤醒相关进程,执行相应操作

   网卡数据传输就是使用硬件异步数据传输方式,在下载、访问的过程中,kernel并不确定数据到来的时间。网络数据最先到达的是硬件网卡,网卡接收到一定量的网络数据时,产生中断,通过kernel处理,进入硬件异步数据传输过程。

4 burst transfer


  burst transfer也就是突发传输,是DMA传输模式的一种。DMA的传输模式的以下三种:突发(Burst mode)模式、循环挪用(Cycle stealing mode)模式、透明模式。参考1 参考2
  Burst mode[edit]
  An entire block of data is transferred in one contiguous sequence. Once the DMA controller is granted access to the system bus by the CPU, it transfers all bytes of data in the data block before releasing control of the system buses back to the CPU, but renders the CPU inactive for relatively long periods of time. The mode is also called “Block Transfer Mode”. It is also used to stop unnecessary data.
  翻译:突发模式就是在一个连续的时间序列内完成一块完整数据的传输。当cpu将系统总线(AMBA总线)控制权交给DMA控制器,cpu输入空闲状态,直到DMAC将数据块所有数据传输完成并将总线控制权交还给cpu。这个模式叫做块传输模式,也叫做突发模式。
  DMA是一次申请产生一次DMA传输,每次传输都要提供一个地址;而burst transfer是一个DMA request,多次传输,只需要提供一次地址,后面传输的地址是递增的。burst transfer的传输次数就是burst。
  比如要传64个字节,那么dma内部可能分为2次,一次传64/2=32个字节,这个2(a)次呢,就叫做burst。这个burst是可以设置的。这32个字节又可以分为32位 *8或者16位*16来传输。
  burst transfer中有几个重要的参数,DMA驱动上会用到

  • transfer size //就是数据宽度,一次dma传输的数据多大,比如8位、32位,一般跟外设的FIFO相同
  • burst size //一次dma请求,产生多少次传输

5 scatter gather


DMA原理
2013-12-19 20:32:57 shangyaowei 阅读数 407
下面是S3C2440A数据手册上的一段截图,展示了4个DMA通道和每个通道对应的DMA源:
DMA原理 - 尘 - 学习点滴
 

 对这些DMA通道和其对应DMA源的管理在文件linux/arch/arm/mach-s3c2440/dma.c中实现。

在文件dma.c中定义了一个结构体数组static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[],

这个结构体将所有DMA源和每个DMA源所能请求的DMA通道联系了起来。

这个管理结构体原型如下:

struct s3c24xx_dma_map {
 const char  *name; //DMA源的名
 struct s3c24xx_dma_addr  hw_addr; //源的物理地址。

 unsigned long   channels[S3C2410_DMA_CHANNELS]; //在S3C2440中S3C2410_DMA_CHANNELS为4,。

 unsigned long   channels_rx[S3C2410_DMA_CHANNELS];
};

 

static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[] = {
 [DMACH_XD0] = {
  .name  = "xdreq0",
  .channels[0] = S3C2410_DCON_CH0_XDREQ0 | DMA_CH_VALID,
 },
 [DMACH_XD1] = {
  .name  = "xdreq1",
  .channels[1] = S3C2410_DCON_CH1_XDREQ1 | DMA_CH_VALID,
 },
 [DMACH_SDI] = {                                                            
  .name  = "sdi",                         //DMA源为SD卡  ,该源可以对四个DMA通道发出DMA请求。                 
  .channels[0] = S3C2410_DCON_CH0_SDI | DMA_CH_VALID, // DMA_CH_VALID表明将该通道初始化为可用。       
  .channels[1] = S3C2440_DCON_CH1_SDI | DMA_CH_VALID, 

//S3C2440_DCON_CH2_SDI将被写入DMA控制寄存器,用于DMA通道请求源选择。

  .channels[2] = S3C2410_DCON_CH2_SDI | DMA_CH_VALID,
  .channels[3] = S3C2410_DCON_CH3_SDI | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
  .hw_addr.from = S3C2410_PA_IIS + S3C2410_IISFIFO,
 },
 [DMACH_SPI0] = {
  .name  = "spi0",
  .channels[1] = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_SPI + S3C2410_SPTDAT,
  .hw_addr.from = S3C2410_PA_SPI + S3C2410_SPRDAT,
 },
 [DMACH_SPI1] = {
  .name  = "spi1",
  .channels[3] = S3C2410_DCON_CH3_SPI | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_SPI + 0x20 + S3C2410_SPTDAT,
  .hw_addr.from = S3C2410_PA_SPI + 0x20 + S3C2410_SPRDAT,
 },
 [DMACH_UART0] = {
  .name  = "uart0",
  .channels[0] = S3C2410_DCON_CH0_UART0 | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_UART0 + S3C2410_UTXH,
  .hw_addr.from = S3C2410_PA_UART0 + S3C2410_URXH,
 },
 [DMACH_UART1] = {
  .name  = "uart1",
  .channels[1] = S3C2410_DCON_CH1_UART1 | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_UART1 + S3C2410_UTXH,
  .hw_addr.from = S3C2410_PA_UART1 + S3C2410_URXH,
 },
       [DMACH_UART2] = {
  .name  = "uart2",
  .channels[3] = S3C2410_DCON_CH3_UART2 | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_UART2 + S3C2410_UTXH,
  .hw_addr.from = S3C2410_PA_UART2 + S3C2410_URXH,
 },
 [DMACH_TIMER] = {
  .name  = "timer",
  .channels[0] = S3C2410_DCON_CH0_TIMER | DMA_CH_VALID,
  .channels[2] = S3C2410_DCON_CH2_TIMER | DMA_CH_VALID,
  .channels[3] = S3C2410_DCON_CH3_TIMER | DMA_CH_VALID,
 },
 [DMACH_I2S_IN] = {
  .name  = "i2s-sdi",
  .channels[1] = S3C2410_DCON_CH1_I2SSDI | DMA_CH_VALID,
  .channels[2] = S3C2410_DCON_CH2_I2SSDI | DMA_CH_VALID,
  .hw_addr.from = S3C2410_PA_IIS + S3C2410_IISFIFO,
 },
 [DMACH_I2S_OUT] = {
  .name  = "i2s-sdo",
  .channels[0] = S3C2440_DCON_CH0_I2SSDO | DMA_CH_VALID,
  .channels[2] = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,
  .hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,
 },
 [DMACH_PCM_IN] = {
  .name  = "pcm-in",
  .channels[0] = S3C2440_DCON_CH0_PCMIN | DMA_CH_VALID,
  .channels[2] = S3C2440_DCON_CH2_PCMIN | DMA_CH_VALID,
  .hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
 },
 [DMACH_PCM_OUT] = {
  .name  = "pcm-out",
  .channels[1] = S3C2440_DCON_CH1_PCMOUT | DMA_CH_VALID,
  .channels[3] = S3C2440_DCON_CH3_PCMOUT | DMA_CH_VALID,
  .hw_addr.to = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA,
 },
 [DMACH_MIC_IN] = {
  .name  = "mic-in",
  .channels[2] = S3C2440_DCON_CH2_MICIN | DMA_CH_VALID,
  .channels[3] = S3C2440_DCON_CH3_MICIN | DMA_CH_VALID,
  .hw_addr.from = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA,
 },
 [DMACH_USB_EP1] = {
  .name  = "usb-ep1",
  .channels[0] = S3C2410_DCON_CH0_USBEP1 | DMA_CH_VALID,
 },
 [DMACH_USB_EP2] = {
  .name  = "usb-ep2",
  .channels[1] = S3C2410_DCON_CH1_USBEP2 | DMA_CH_VALID,
 },
 [DMACH_USB_EP3] = {
  .name  = "usb-ep3",
  .channels[2] = S3C2410_DCON_CH2_USBEP3 | DMA_CH_VALID,
 },
 [DMACH_USB_EP4] = {
  .name  = "usb-ep4",
  .channels[3] = S3C2410_DCON_CH3_USBEP4 | DMA_CH_VALID,
 },
};

 

//当某DMA通道被使用时调用该函数,置该DMA通道被占用标识。

//获取该DMA源对应的DMA通道请求源选择,存于chan->dcon中最终将被写入DMA控制寄存器。

static void s3c2440_dma_select(struct s3c2410_dma_chan *chan,
          struct s3c24xx_dma_map *map)
{
 chan->dcon = map->channels[chan->number] & ~DMA_CH_VALID;
}

static struct s3c24xx_dma_selection __initdata s3c2440_dma_sel = {
 .select  = s3c2440_dma_select,   //通道选择函数
 .dcon_mask = 7 << 24,    //屏蔽DMA控制寄存器中用于设置DMA请求源的位。
 .map  = s3c2440_dma_mappings, //上面初始化的 源-----通道 管理结构体数组
 .map_size = ARRAY_SIZE(s3c2440_dma_mappings),  //源的数目。
};

//有一些DMA源不只对应一个DMA通道,将这些源提了出来单独用一个结构体来管理。

//DMA源DMACH_TIMER也对应多个DMA通道,但没有出现在下面结构体中,因为

//定时器本身并不进行数据传输,而其他DMA源都是在数据传输时请求一个DMA通道进行,

//源的寄存器与内存的数据传输。定时器是在DMA进行传输时让它来定时触发传输操作

static struct s3c24xx_dma_order __initdata s3c2440_dma_order = {
 .channels = {
  [DMACH_SDI] = {
   .list = {
    [0] = 3 | DMA_CH_VALID,
    [1] = 2 | DMA_CH_VALID,
    [2] = 1 | DMA_CH_VALID,
    [3] = 0 | DMA_CH_VALID,
   },
  },
  [DMACH_I2S_IN] = {
   .list = {
    [0] = 1 | DMA_CH_VALID,
    [1] = 2 | DMA_CH_VALID,
   },
  },
  [DMACH_I2S_OUT] = {
   .list = {
    [0] = 2 | DMA_CH_VALID,
    [1] = 1 | DMA_CH_VALID,
   },
  },
  [DMACH_PCM_IN] = {
   .list = {
    [0] = 2 | DMA_CH_VALID,
    [1] = 1 | DMA_CH_VALID,
   },
  },
  [DMACH_PCM_OUT] = {
   .list = {
    [0] = 1 | DMA_CH_VALID,
    [1] = 3 | DMA_CH_VALID,
   },
  },
  [DMACH_MIC_IN] = {
   .list = {
    [0] = 3 | DMA_CH_VALID,
    [1] = 2 | DMA_CH_VALID,
   },
  },
 },
};

//为把DMA注册到内核定义了一个s3c2440_dma_driver。

static struct sysdev_driver s3c2440_dma_driver = {
 .add = s3c2440_dma_add,
};

//函数s3c2440_dma_init()即是完成了这个注册的工作。

static int __init s3c2440_dma_init(void)
{
 return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);
}

//这个注册过程最重要的工作就是调用了函数s3c2440_dma_driver->add,即是函数s3c2440_dma_add()。

这个函数的实现仍然是在文件linux/arch/arm/mach-s3c2440/dma.c中。

static int __init s3c2440_dma_add(struct sys_device *sysdev)
{
 s3c2410_dma_init();                                                             (1)    
 s3c24xx_dma_order_set(&s3c2440_dma_order);                 (2)
 return s3c24xx_dma_init_map(&s3c2440_dma_sel);             (3)
}

 

(1)

int __init s3c2410_dma_init(void)
{//4个DMA通道,起始中断号IRQ_DMA0,每一个DMA通道的寄存器覆盖地址范围为0x40。
 return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);  
}

 

int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,
       unsigned int stride)
{
 struct s3c2410_dma_chan *cp;
 int channel;
 int ret;

 printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics\n");

//本函数的实现是在文件linux/arch/arm/plat-s3c24xx/dma.c中,这个文件中还实现了其他DMA操作函数。

//dma_channels是在该文件中的全局变量,在其他DMA操作函数中要用这个变量。

 dma_channels = channels;      //设置通道数目,

 dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);
 if (dma_base == NULL) {
  printk(KERN_ERR "dma failed to remap register block\n");
  return -ENOMEM;
 }

 //创建一个内存池,在创建内存管理结构体s3c2410_dma_buf时便是从该池中获取这些结构体内存。

//DMA的操作是外设与内存的数据传输,这些内存就是用结构体s3c2410_dma_buf来管理的。

 dma_kmem = kmem_cache_create("dma_desc",       
         sizeof(struct s3c2410_dma_buf), 0,
         SLAB_HWCACHE_ALIGN,
         s3c2410_dma_cache_ctor);

 if (dma_kmem == NULL) {
  printk(KERN_ERR "dma failed to make kmem cache\n");
  ret = -ENOMEM;
  goto err;
 }

 for (channel = 0; channel < channels;  channel++) {

//该结构体数组在文件linux/arch/arm/plat-s3c24xx/dma.c中定义,对于S3C2440这个结构体又四个元素,

//每个元素代表一个DMA通道。这个循环是要初始化每一个通道的中断号,通道寄存器基地址等字段。
  cp = &s3c2410_chans[channel] ;//

  memset(cp, 0, sizeof(struct s3c2410_dma_chan));

  
  cp->number = channel;    //通道号
  cp->irq    = channel + irq;     //通道中断号
  cp->regs   = dma_base + (channel * stride);  //通道寄存器基地址

  
  cp->stats  = &cp->stats_store;
  cp->stats_store.timeout_shortest = LONG_MAX;

  

  cp->load_timeout = 1<<18;

  printk("DMA channel %d at %p, irq %d\n",
         cp->number, cp->regs, cp->irq);
 }

 return 0;

 err:
 kmem_cache_destroy(dma_kmem);
 iounmap(dma_base);
 dma_base = NULL;
 return ret;
}

(2)

//该函数是将文件linux/arch/arm/mach-s3c2440/dma.c中初始化的结构体s3c2440_dma_order。

//拷到文件linux/arch/arm/plat-s3c24xx/dma.c中来,并用指针dma_order指向

//在文件linux/arch/arm/plat-s3c24xx/dma.c中实现了一些通用的DMA操作函数,这些函数

//将会有对s3c2440_dma_order的操作。

int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
 struct s3c24xx_dma_order *nord = dma_order;

 if (nord == NULL)
  nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);

 if (nord == NULL) {
  printk(KERN_ERR "no memory to store dma channel order\n");
  return -ENOMEM;
 }

 dma_order = nord;
 memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
 return 0;
}

(3)

//该函数是将文件linux/arch/arm/mach-s3c2440/dma.c中初始化的结构体s3c2440_dma_sel

//拷到文件linux/arch/arm/plat-s3c24xx/dma.c中来。

//在文件linux/arch/arm/plat-s3c24xx/dma.c中实现了一些通用的DMA操作函数,这些函数

//将会有对s3c2440_dma_sel的操作。

int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
{
 struct s3c24xx_dma_map *nmap;
 size_t map_sz = sizeof(*nmap) * sel->map_size;
 int ptr;

 nmap = kmalloc(map_sz, GFP_KERNEL);
 if (nmap == NULL)
  return -ENOMEM;

 memcpy(nmap, sel->map, map_sz);
 memcpy(&dma_sel, sel, sizeof(*sel));

 dma_sel.map = nmap;

 for (ptr = 0; ptr < sel->map_size; ptr++)
  s3c24xx_dma_check_entry(nmap+ptr, ptr);

 return 0;
}

2019-06-19 15:17:46 Newyan3651 阅读数 898

ZYNQ PCIe-DMA的实现过程

近期在网上淘来个源码,看了之后觉得还不错。完全刷新我对ZYNQ的认知啊,原来ZYNQ也可以这么玩的。PS-PL交互很方便。话不多说,分享一下他们的说明书(源码呢?源码呢?源码呢?大哥源码要几大洋咱不敢放上来啊)

一、概述

本系统在ZYNQ平台上实现PCIe的DMA收发(EP端),兼容ZYNQ系列所有带PCIe资源的芯片,如7015、7030、7045、ZCU102(MP9EG)等等。实现包括BAR0&BAR1空间访问,PS/PL/RC三方可同时访问BAR0空间,PL/RC双方同时访问BAR1空间(也可扩展到PS三方)。实现包括RC DDR数据DMA传输到EP DDR中,以及EP DDR数据DMA传输到RC DDR中。传输速度在实际项目中测试结果为理论值的80%。其中lane x1 x2略高于80%,lane x4略低于80%(DDR速率为1066时)

二、基础知识

此文源自于一个项目的PCIE模块的总结,暂且不对基础知识作过多的补充说明。
推荐阅读(感谢以下的作者们):

  1. Xilinx FPGA高速串行传输技术与应用 陈博,张建辉 等 (作者)
  2. https://blog.csdn.net/wenjia7803/article/details/80086284
  3. http://xilinx.wikidot.com/zynq-pcie-trd
  4. https://zhuanlan.zhihu.com/p/34096340
  5. https://blog.csdn.net/long_fly/article/details/79150820
  6. https://blog.csdn.net/Qiuzhongweiwei/article/details/78652951
    如果是零基础,想学得深一点的话,建议搞到第1本书阅读一下PCIE的章节。估计两三天可以读完。仅看上面的链接,再配合某T某B上买回来的源码以及注释,也可以理清整个过程。略微修改一下便能成为自己的代码,也算方便。
    对于基础知识,个人总结如下
    +1. PCIE配置空间以及配置寄存器之类的,暂时是不用管。
    +2. 最底层当然是调用xilinx的PCIe IP Core,IP core的配置也可以使用demo默认的。
    +3. 对于FPGA人来说,编程面向的是TLP层协议。无非就是完成不同的交易事务的处理。如发送数据就是从数据源读出数据,按照TLP格式打包成相应的报文,写入IP Core(AXI操作);而接收数据就是从IP Core得到TLP报文(AXI接口),按照相应TLP格式进行解析,然后将解析到的数据写入DDR。

三、系统总框架

本系统是ZYNQ PCIE DMA,FPGA作为EP端。框架和文件结构如下图所示:
在这里插入图片描述
其中RC端可以是PC机,也可以是DSP/ARM等设备。
Pcie_support模块包含了IP Core的例化,clock时钟模块为IP Core提供时钟,以及IP Core的默认配置。一般PCIe support模块是不需要变动的涉及到的东西也是很底层的。
而PCIe app模块里则是用户对TLP事务的处理以及对IP Core一些信号的处理,比如复位、中断等信号。PCIe app模块包含EP模块和CTRL模块,CTRL模块与电源和复位有关。
EP模块则包含TX模块、RX模块、MEM模块,是TLP事务处理的三个主要模块。TX模块实现TLP的打包与发送,RX模块实现TLP的接收与解析,MEM模块实现BAR空间的存储操作。
REG_Table模块实现PS-PL交互,实现PS控制PL以及PL信号反馈到PS上。
DDR read&write模块实现DDR的读写操作,是标准模块且官方有相应的IP,可以是MIG操作DDR,也可以是AXI操作DDR,数据量小的话也可以直接采用片上BRAM。某T某B上买回来的皮卡丘之狂电码农的源码默认是PCIE核心功能,并没有提供DDR read&write模块。其他模块有提供,需要自己加上DDR两模块。

四、工作原理与工作模式

A. BARx空间读写

对于上层的linux软件编程人员来说,EP的BAR空间与RC的BAR空间是一一对应的。EP BAR0对应RC BAR0,EP BAR0对应RC BAR1.这种对应关系表现在读写同步上。RC/EP任意一方读写操作BAR空间,另一方都会同步改变。
底层原理上来说RC一但操作BAR空间,或读或写或刷新都会进行一次TLP事务交易。而发生的TLP事务交易正是同步了EP的BAR空间。细心的读者会发现,这不是会出现竞争关系?是的,所以用户需要约定互锁关系,以避免竞争出现。
源码分析:RX_ENGINE模块的wr_addr/wr_data/wr_xx,以及TX_ENGINE模块的rd_addr/rd_data/rd_xx连接到EP_MEM_ACCESS模块。在EP_MEM_ACCESS模块里分为BRA0与BAR1;而BAR0连接到外部的双口RAM上,通过AXI-GP与PS共享;BAR1则直接在MEM模块里例化一些寄存器,与RC进行交互,方便RC控制EP。RTL见下图:
在这里插入图片描述
B. EP DDR to RC DDR的数据传输
在这里插入图片描述
对于RC端应用开发人员来说,只需要操作BAR1将目标地址填写到wr32_RC_address上,将源地址填写到wr32_EP_address上,将数据长度填写到wr32_length上,再发起一个wr32_start脉冲信号。则可以发起一次EP DDR to RC DDR的DMA数据传输。直到收到wr32_finish信号,此次DMA传输完成。
原理上实际为将DDR读上来的数据,进行TLP格式打包,从AXI接口传入到IP Core,再从PCIE总线上发送到RC设备。
细心的人会发现,其实也可以将这些寄存器放在EP端操作。相当于EP写RC DDR,但这样会产生写DDR覆盖问题,需要严格的RC-EP DDR地址分配约定。否则EP操作RC DDR会另RC死机(相当于RC野指针)。此处RC主动控制传输,相当于RC读数据不会搞死EP,安全性有很大提高。
源码分析:较为复杂,详看源码注释。主要步骤集中在TX模块的几个状态机状态里。

C.RC DDR to EP DDR的数据传输
在这里插入图片描述
对于EP端开发人员来说,相似于以上情况。通过AXI-GP操作reg table即可。reg table是在PL端建立的一个寄存表,用于PS-PL交互便于PS控制PL。
原理上就是EP端主动去读RC的数据,宏观结果就是RC数据传输到EP上。EP将需要读取的RC地址和长度,进行TLP格式打包,发送到RC上。RC则会回应CPLD TLP包。EP再接收这些CPLD TLP包,解析得到数据,再将数据写入的指定的DDR地址上。
源码分析:较为复杂,详看源码注释。在TX及RX模块的多个状态机状态里。看的时候按照原理流程一步步看会清晰很多。

五、接口时序

在这里插入图片描述
在这里插入图片描述
关键点:1.在start之前,length/address(RC&EP)必需准备完成。2.只有ready和valid两信号同时有效,data才算是进行了有效的传输。3.只有ready和valid两信号同时有效的上升沿计数足够length个数(表明data有效传输了length个),finish才会有效。否则会一直等待状态,没有超时设计。
简化理解:寄存器配置完成后,start传输,输入或输出length个data后,finish置高结束传输。

六、资源使用情况

对于lane x1 x2 x4来说,资源变化不大。以下是7015的资源使用情况。
在这里插入图片描述
在这里插入图片描述

七、PS-PL交互以及测试程序

RegisterMap_PL.h ,如下:
代码片.

/*
 * 	ZYNQ-PL register map
 *  register table
 * */
#pragma once

#include <stdint.h>

//base address
#define 	PCIE_AXI_CTRL_BASEADDR		0x40000000
#define     PCIE_BAR0_BASEADDR			0x80000000

//PCIE CONTROL table
#define		reg_link_up				0
#define		reg_rd32_ready			1
#define		reg_rd32_start			2
#define		reg_rd32_length			3			// in 64bit
#define		reg_rd32_rcaddr			4			// 32bit allign
#define		reg_rd32_epaddr			5			// 32bit allign
#define		reg_rd32_finish			6

//operation
#define		PL_REG(Base,reg) 	( *((volatile uint32_t *)(Base) +(reg)) )

main.c ,如下:
代码片.

/*
 *	main.cpp
 */
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <unistd.h>
#include <string.h>

#include "RegisterMap_PL.h"

using namespace std;

//虚拟地址映射
int Phy2Vir_MMap(uint32_t physAddr, uint32_t memSize , uint32_t *pMemVirtAddr);
int Phy2Vir_unmapMem(uint32_t physAddr, uint32_t memSize , uint32_t *pMemVirtAddr);

//全局变量
unsigned int PCIE_AXI_CTRL_VirtAddr  	= 0 ;
unsigned int PCIE_BAR0_VirtAddr 		= 0 ;

//主程序
int main()
{
	printf("hello world \r\n");

	//虚拟地址映射
	uint32_t PCIE_AXI_CTRL_Vritaddr = 0 ;
	Phy2Vir_MMap(PCIE_AXI_CTRL_BASEADDR, 16*4, &PCIE_AXI_CTRL_Vritaddr);

	//等待链路训练成功
	while(1){
		int link_up =PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_link_up) ;
		if( link_up ){
			printf("pcie link up ... \r\n");
			break;
		}
		printf("pcie linking  ... \r\n");
		usleep(100*10000);
	}

	//等待ZYNQ DDR/DDR控制模块准备完成
	while(1){
		int rd32_ready =PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_ready) ;
		if( rd32_ready ){
			printf("zynq ddr control ready done ... \r\n");
			break;
		}
		printf("waitting for zynq ddr control ready singal  ... \r\n");
		usleep(100*10000);
	}

	//填写读取长度、目标地址、源地址信息
	//从PCIE RC端DDR物理地址为0x20000000处,读取长度为1920*1080 byte的数据,保存到EP DDR物理地址0x3a800000处。
	PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_start ) = 0 ;
	PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_length) = 1920*1080/8 ;	//in 64bit(8byte)
	PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_rcaddr) = 0x20000000 ;	//DDR PHY address
	PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_epaddr) = 0x3a800000 ;	//DDR PHY address

	//开始传输
	PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_start ) = 1 ;

	//等待DMA传输完成
	while(1){
		int rd32_finish =PL_REG(PCIE_AXI_CTRL_Vritaddr,reg_rd32_finish) ;
		if( rd32_finish ){
			printf("pcie get data from RC done ... \r\n");
			break;
		}
		printf("pcie getting data ... \r\n");
		usleep(100*10000);
	}

	///////////////////////////////////////////////////////////
	// BAR 空间读写demo
	///////////////////////////////////////////////////////////

	//虚拟地址映射
	uint32_t PCIE_BAR0_Vritaddr     = 0 ;
	Phy2Vir_MMap(PCIE_BAR0_BASEADDR, 2*1024, &PCIE_BAR0_Vritaddr);

	//写入顺序数,RC的BAR空间同时会得到此处写入的数据
	//同理,在RC的BAR空间写入数据,此处也可以读到
	for(int i=0; i<128; i++){
		*((volatile uint32_t *)PCIE_BAR0_Vritaddr +i) = i ;
	}

	//整块操作,RC的BAR空间同时会得到此处写入的数据
	//同理,在RC的BAR空间写入数据,此处也可以读到
	uint32_t *a = (uint32_t *)PCIE_BAR0_Vritaddr      ;
	uint32_t *b = (uint32_t *)PCIE_BAR0_Vritaddr +256 ;
	memcpy(b,a,128*sizeof(uint32_t));

	printf("exit main \r\n");

	return 0 ;
}


本文完结

linux的dma驱动

阅读数 633

DMA原理和实验

阅读数 9963

没有更多推荐了,返回首页