dma原理 linux - CSDN
精华内容
参与话题
  • DMA原理

    2014-04-09 14:12:47
    下面是S3C2440A数据手册上... 对这些DMA通道和其对应DMA源的管理在文件linux/arch/arm/mach-s3c2440/dma.c中实现。 在文件dma.c中定义了一个结构体数组static struct s3c24xx_dma_map __initdata s3c2440_dma_mapp
    下面是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)
    }

    /*

    函数s3c2440_dma_add()的工作就是将文件linux/arch/arm/mach-s3c2440/dma.c中初始化的

    一些量拷到文件linux/arch/arm/plat-s3c24xx/dma.c中,或初始化文件linux/arch/arm/plat-s3c24xx/dma.c中

    的结构体。文件linux/arch/arm/plat-s3c24xx/dma.c中定义了一些通用的DMA操作函数,这些函数最终是对

    一个具体平台的硬件的操作。

    */

    (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));

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

      /* point current stats somewhere */
      cp->stats  = &cp->stats_store;
      cp->stats_store.timeout_shortest = LONG_MAX;

      /* basic channel configuration */

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

    展开全文
  • dma传输原理 1.DMA请求 CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。 2.DMA响应 DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放...

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

    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
     dma_cap_zero(mask);  
           dma_cap_set(DMA_SLAVE,mask);  
       
           /*1. Init rx channel */  
           dws->rxchan= dma_request_channel(mask, dma_chan_filter, params);  
           主要就是申请DMA通道。  
       
           dma_chan_filter这个函数主要是查找你的dma传输的设备的请求信号线,其具体是在注册时填写的。  
    这里会根据这个函数返回的真假来判断已经注册在总线上的dma slave的。  
       
       
    buf =kmalloc(DMA_BUFFER_SIZE, GFP_KERNEL);  
    //申请一块地址,用来DMA传输的数据就放在这里  
    sg_init_one(&dma_dev->dmatx.sg,  buf,  DMA_BUFFER_SIZE);  
           //初始化,其主要为了发送时虚拟地址和物理地址的映射。  

     

    2、启动DMA


    [html] view plaincopy
    struct dma_async_tx_descriptor *txdesc = NULL;  
           struct dma_chan *txchan,;  
           struct dma_slave_config txconf;  
       
           txchan= dws->txchan;  
            
           /*2. Prepare the TX dma transfer */  
           txconf.direction= DMA_TO_DEVICE;              //表示dma传输方向为发送  
           txconf.dst_addr= dws->dma_addr;                   //目标地址,物理地址  
           txconf.dst_maxburst= LNW_DMA_MSIZE_16; //最大传输的字节数  (转者注:这个参数应该是单次传输的字节长度)
           txconf.dst_addr_width= DMA_SLAVE_BUSWIDTH_2_BYTES;  //数据的位宽  
       
           txchan->device->device_control(txchan,DMA_SLAVE_CONFIG,  
                                       (unsigned long) &txconf);  
            
           dws->tx_sgl.length= dws->len;    //要传输的数据的长度  
       
    dma_map_sg(dma_dev->dev,&dmatx->sg, 1, DMA_TO_DEVICE);  
    //通过这个函数来实现虚拟地址和物理地址的映射。  
       
           txdesc= txchan->device->device_prep_slave_sg(txchan,  
                                &dws->tx_sgl,  
                                1,  
                                DMA_TO_DEVICE,  
                                DMA_PREP_INTERRUPT| DMA_COMPL_SKIP_DEST_UNMAP);  
           txdesc->callback= dw_spi_dma_done;   //传输完成后的回调函数  
           txdesc->callback_param= params;      //回调函数中的参数  
       
           dmaengine_submit(txdesc);  
    dma_dev->device_issue_pending(txchan); // 启动dma传输了  
            

    配置好后,这样DMA就会开始传输了,然后传输完了以后就会有调用callback函数。
    --------------------- 
    作者:sung_june 
    来源:CSDN 
    原文:https://blog.csdn.net/linglongqiongge/article/details/50233663 
    版权声明:本文为博主原创文章,转载请附上博文链接!

    展开全文
  • Linux内核中DMA分析

    千次阅读 2017-09-04 20:39:46
     DMA---直接内存访问 用来在设备内存与主存RAM之间直接进行数据交换,这个过程无需CPU干预, 对于系统中有大量数据交换的设备而言,如果能够...--一致性映射类型的dma_alloc_coherent/流式映射类型的dma_map_si
    

    DMA---直接内存访问
    用来在设备内存与主存RAM之间直接进行数据交换,这个过程无需CPU干预,
    对于系统中有大量数据交换的设备而言,如果能够充分利用DMA特性,可以大大提高系统性能。

    1.内核中DMA层

    --内核为设备驱动程序提供了统一的DMA接口,这些接口屏蔽了不同平台之间的差异。

    --一致性映射类型的dma_alloc_coherent/流式映射类型的dma_map_single

    不同的平台(X86/ARM)提供各自的struct dma_map_ops对象来实现对应的DMA映射。

    2.物理地址和总线地址

    物理地址是指cpu地址信号线上产生的地址。

    总线地址可以认为从设备的角度看到的地址,不同类型的总线具有不同类型的总线地址。

    DMA地址--用来在设备和主存之间寻址,虽然是总线地址,但是从内核代码的角度来看,被称为DMA地址,

    与之相对应的数据结构是dma_addr_t(-->typedef u32 dma_addr_t;)。

    3.DMA映射
    3.1 基本原理
     DMA映射主要为在设备与主存之间建立DMA数据传输通道时,在主存中为该DMA通道分配内存空间的行为,该内存空间
    也称为DMA缓冲区。这个任务原本可以很简单,但是由于现代处理器cache的存在,使得事情变得复杂。

    3.2 RAM与cache内容的一致性问题

    1.出现问题原因
    现代处理器为了提升系统性能,在CPU与RAM之间加入了高速缓存cache,
    所以当在RAM中为一个DMA通道建立一段缓冲区时,
    必须仔细考虑RAM与cache内容的一致性问题。

    /*具体的分析*/
    如果RAM与Device之间的一次数据交换改变了RAM中DMA缓冲区的内容,
    而cache中缓存了DMA缓冲区对应的RAM中一段内存块。
    如果没有机制保护cache中的内容被新的DMA缓冲区数据更新(或者无效),
    那么cache和他对应的RAM中的一段内存块在内容上出现了不一致,
    此时如果CPU去读取device传到RAM的DMA缓冲区中的数据,
    它将直接从cache获得数据,这些数据显然不是它所期望的,
    因为cache对应的RAM中的数据已经更新了。


    2.解决问题--
    就cache一致性问题,不同的体系架构有不同的策略,有些是在硬件层面予以保证(x86平台)
    有些没有硬件支持而需要软件的参与(ARM品台)。
    --Linux内核中的通用DMA尽力为设备驱动程序提供统一的接口来处理cache缓存一致性的问题,
    而将大量品台相关的代码对设备驱动程序隐藏起来。

    3.3 DMA映射三种情况

    1. 一致性DMA映射

    linux内核DMA层为一致性DMA映射提供的接口函数为dma_alloc_coherent()
    -->
    /*
     * Allocate DMA-coherent memory space and return both the kernel remapped
     * virtual and bus address for that space.
     */
    void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)

    函数分配的一致性DMA缓冲区的总线地址(也是DMA地址)由参数handle带回,
    函数返回的则是映射到DMA缓冲区的虚拟地址的起始地址


    接着调用->__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp,pgprot_t prot)
    {
     struct page *page;
     void *addr;

     *handle = ~0;
     size = PAGE_ALIGN(size);

     page = __dma_alloc_buffer(dev, size, gfp); /*分配大小为size的一段连续的物理内存页,并且对应得虚拟地址范围已经使cache失效*/
     if (!page)
      return NULL;

     if (!arch_is_coherent())/*arch_is_coherent确定体系结构是否通过硬件来保证cache一致性(arm不是,所以函数返回0)*/
      addr = __dma_alloc_remap(page, size, gfp, prot);/*在(?--0xffe0 0000)之间寻找一段虚拟地址段,将其重建新映射到page,
    --------------------------------------------------------->由于关闭了cache功能所以保证了DMA操作时不会出现cache一致性问题*/-
     else
      addr = page_address(page);

     if (addr)
      *handle = pfn_to_dma(dev, page_to_pfn(page));

     return addr;
    }

    一致性所获得的DMA缓冲区的大小都是页面的整数倍,如果驱动程序需要更小的DMA一致性的DMA缓冲区,则应该使用内核提供的DMA池(pool)机制

    /*释放一致性DMA缓冲区*/
    void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle)
    /*cpu_addr表示要释放的DMA缓冲区的起始虚拟地址,参数bus表示DMA缓冲区的总线地址*/

    对于一致性DMA映射,在分配DMA缓冲区时各平台相关代码已经从根本上解决了cache一致性问题.

    但是,一致性映射也会遇到无法克服的困难,主要是指驱动程序中使用的DMA缓冲区并非由驱动程序分配,

    而是来自其他模块(如网络设备驱动程序中用于数据包传输的skb->data所指向的缓冲区),此时需要流式DMA映射。

    2. 流式DMA映射

    流式DMA映射的特点是DMA传输通道使用的缓冲区不是由当前驱动程序自身分配的,
    而且往往对每次DMA传输都会重新建立一个流式映射的缓冲区,所以使用流式DMA映射时,
    设备驱动程序必须小心负责处理可能出现的cache一致性。

    linux内核DMA层为设备驱动提供的建立流式DMA映射的函数---dma_map_single

    //dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, NULL)

    static inline dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
      size_t size, enum dma_data_direction dir)
              
    /*dev-->设备对象指针,cpu_addr-->cpu的虚拟地址,
    size-->流式空间的范围,dir-->表明当前流式映射中DMA传输通道中的数据方向*/
    函数返回数据类型-->dma_addr_t即表示DMA操作中的源地址和目的地址。

    /*下面分析ARM的平台*/
    /*将cpu_addr表示的段虚拟地址映射到DMA缓冲区中,返回该缓冲区的起始地址*/
    2.1
    static inline dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
      size_t size, enum dma_data_direction dir)
    {
     dma_addr_t addr;

     addr = __dma_map_single(dev, cpu_addr, size, dir);
     
     return addr;
    }

    2.2
    static inline dma_addr_t __dma_map_single(struct device *dev, void *cpu_addr,
      size_t size, enum dma_data_direction dir)
    {
     __dma_single_cpu_to_dev(cpu_addr, size, dir);
     return virt_to_dma(dev, cpu_addr);
    }

    2.3
    void ___dma_page_cpu_to_dev(struct page *page, unsigned long off,
     size_t size, enum dma_data_direction dir)
    {
     unsigned long paddr;

     dma_cache_maint_page(page, off, size, dir, dmac_map_area);

     paddr = page_to_phys(page) + off;
     
     if (dir == DMA_FROM_DEVICE) {
      outer_inv_range(paddr, paddr + size); /*保证读数据使得时候,使得(paddr, paddr + size)对应的cache失效*/
     } else {
      outer_clean_range(paddr, paddr + size); 
     }
    }

    /*
    sync_single_for_cpu方法用于数据从设备传到主存的情况:
    为了避免cache的介入导致CPU读到的只是cache中旧的数据,
    驱动程序需要在CPU读取之前调用该函数---->使得cache无效,这样处理器将直接从主存中获得数据。

    sync_single_for_device方法用于数据从主存传到设备,
    在启动DMA操作之前,CPU需要将数据放在位于主存的DMA缓冲区中,
    为了防止write buffer的介入,导致数据只是临时写到write buffer中,
    驱动程序需要在CPU往主存写数据之后启动DMA操作之前调用该函数。
    */


    3. 分散/聚集DMA映射

    分散/聚集DMA映射通过将虚拟地址上分散的DMA缓冲区通过一个struct scatterlist的数组或链表组织起来,
    然后通过一次的DMA传输操作在主存RAM与设备之间传输数据。

    分散/聚集DMA映射本质上是通过一次DMA操作把内存中分散的数据块在主存与设备之间进行传输,对于其中的每个数据块
    内核都会建立对应的一个流式DMA映射。---》需要设备的支持。


    3.4 回弹缓冲区

    如果CPU侧虚拟地址对应的物理地址不适合设备的DMA操作,那么需要建立回弹缓冲区,相当于一个 中转站,把数据往设备传输时,
    驱动程序需要把CPU给的数据拷贝到回弹缓冲区,然后再启动DMA操作。


    3.5 DMA池

    由于DMA映射所建立的缓冲区是单个页面的整数倍,
    如果驱动程序需要更小的一致性映射的DMA缓冲区,
    可以使用内核提供的DMA池机制(非常类似于Linux内存管理中的slab机制).

    struct dma_pool就是内核用来完成该任务的数据结构
    struct dma_pool {  /* the pool */
     struct list_head page_list; /*用来将一致性DMA映射建立的页面组织成链表*/
     spinlock_t lock;   /*自旋锁*/
     size_t size;    /*该DMA池用来分配一致性DMA映射的缓冲区的大小,也称为块大小*/
     struct device *dev;   /*进行DMA操作的 设备对象指针*/
     size_t allocation; 
     size_t boundary;
     char name[32];    /*dma池的名称*/
     wait_queue_head_t waitq; /*等待队列*/
     struct list_head pools;  /*用来将当前DMA池对象加入到dev->dma_pools链表中*/
    };

    /*相关操作*/
    1--创建dma_pool,并初始化
     /* dma_pool_create - Creates a pool of consistent memory blocks, for dma.
     * @name: name of pool, for diagnostics
     * @dev: device that will be doing the DMA
     * @size: size of the blocks in this pool.
     * @align: alignment requirement for blocks; must be a power of two
     * @boundary: returned blocks won't cross this power of two boundary
     * Context: !in_interrupt()
     */
    struct dma_pool *dma_pool_create(const char *name, struct device *dev,
         size_t size, size_t align, size_t boundary)

    2--释放DMA池中的DMA缓冲块
    /**
     * dma_pool_free - put block back into dma pool
     * @pool: the dma pool holding the block
     * @vaddr: virtual address of block
     * @dma: dma address of block
    **/
    void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t dma)

    3--销毁dma_pool
    /* dma_pool_destroy - destroys a pool of dma memory blocks.
     * @pool: dma pool that will be destroyed
     */
    void dma_pool_destroy(struct dma_pool *pool)


    //参考文献:陈雪松-深入Linux设备驱动程序内核机制

    展开全文
  • linux DMA原理

    千次阅读 2016-04-24 00:24:40
    DMA

    DMA工作原理

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

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

    展开全文
  • 2 DMA原理  DMA传输需要由DMA控制器DMAC进行,当需要进行DMA传输的时候,DMA控制器会发出占用总线的请求,当CPU响应DMA的请求时,暂时放弃对总线的控制权,当DMA传输结束的时候,DMAC会向I/O接口发出结束命令,并...
  • 简介:  本文主要介绍在内核中简单使用DMA实现内存数据传递。由于本篇文章中没有介绍与框架相关的程序,只是使用字符设备来操作DMA,同时也没有抽象的层次... Linux内核:linux-2.6.22.6  所用开发板:JZ2440 V...
  • linuxdma驱动

    2016-08-29 21:19:24
    硬件平台:TI的3530 ...工作原理:通过软件配置dma控制器的寄存器,让dma自己讲数据从外部读入到内存中,使得linux系统可以直接访问,而在这数据传送的期间,cpu不参与数据的传输,这也大大提高了
  • linux DMA

    2018-05-22 20:54:36
    1 DMA工作原理 2 DMA控制器 3 数据传输过程 3.1 软件对数据请求传输过程 3.2 硬件异步数据传输过程 4 burst transfer 1 DMA工作原理  DMA的原意为direct memory access,也就是直接内存访问(可以理解...
  • Linux DMA 驱动学习总结

    千次阅读 2016-04-15 09:05:53
    Linux DMA驱动构架分析  以linux2.6.32中的S3C2440驱动为例进行分析,DMA驱动所对应的源码为linux-2.6.32.2\arch \arm\mach-s3c2440\dma.c,代码入口为: arch_initcall(s3c2440_dma_init); 205 static int...
  • linux内核驱动 DMA Engine使用

    千次阅读 2019-08-16 21:00:54
    DMA control 驱动 1. 前言 前面文章介绍“ Linux MMC framework ”的时候,涉及到了MMC数据传输,进而不可避免地遭遇了DMA(Direct Memory Access)。因而,择日不如撞日,就开几篇文章介绍LinuxDMA Engine ...
  • ZYNQ PCIe-DMA源码 例程 PS-PL交互 linux/裸机 verilog C/C++ZYNQ PCIe-DMA的实现过程一、概述二、基础知识三、系统总框架四、工作原理与工作模式五、接口时序六、资源使用情况七、PS-PL交互以及测试程序 ...
  • (优化了上一版代码)STM32F407的6路串口均已配置为DMA方式的发送和接收,发送函数也已封装为类似printf的形式,方便使用。 最新优化版请移步:https://download.csdn.net/download/jack__linux/10941198
  • Linux DMA驱动构架分析

    千次阅读 2011-05-24 18:55:00
     以linux2.6.32中的S3C2440驱动为例进行分析,DMA驱动所对应的源码为linux-2.6.32.2/arch/arm/mach-s3c2440/dma.c,代码入口为: arch_initcall(s3c2440_dma_init); 205static int __init s3c2440_...
  • 内存映射和DMA 内存的概念: 内存是与CPU进行沟通的桥梁,所有程序的运行都是在内存中进行的。 内存的作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要...
  • ...
  • arm linux dma_map_single原理

    千次阅读 2018-08-16 01:03:14
    因为需要验证spi dma(IP内部)驱动的原因,使用了linux 3.0的spi子系统,并且实操了dma驱动,详情如下: spi_master-&gt;transfer 的实现中foreach spi_message中每个spi_transfer,判断transfer 的长度超过spi ...
  • uart dma实现方式分析

    千次阅读 2015-12-07 15:19:24
    在做uart DMA驱动期间,前后加起来也有1个月左右的时间,总的来说比较全面的了解了uart,DMA的工作原理。 在调试中,遇到了最大问题就是关于DMA操作这快的不熟悉,导致浪费了很多的时间和精力。对UART,DMA的工作...
  • 介绍了DMA和cache的关系和内在原理,内核中流式DMA结构的介绍和使用
  • 和菜鸟一起学linux总线驱动之DMA传输

    万次阅读 热门讨论 2013-01-14 18:22:11
    最早接触DMA的时候是大三的微机原理,当时不是很理解,什么DMA模式啊,只知道是传输速度快,不经过CPU,但是到底是怎么样的不经过CPU呢?还是不理解。这次I2C控制器里面带了DMA的模式,所以有机会去接触下了。  而...
1 2 3 4 5 ... 20
收藏数 9,905
精华内容 3,962
热门标签
关键字:

dma原理 linux