dma.c源代码分析


由于在传输大块数据的过程中无须CPU干预(当然在开始、出错和结束时仍然需要),所以与轮询和中断相比,DMA传输效率要高得多。另外,Marvell平台上提供了所谓的memory switch,总线有更高的利用率,DMA就更能显出它的优势了。

下面我们看看mach-pxa/dma.c中的代码:

1    31   static   struct  dma_channel {
2    32           char   * name;
3    33           void  ( * irq_handler)( int void   * struct  pt_regs  * );
4    34           void   * data;
5    35  } dma_channels[PXA_DMA_CHANNELS];
6 

该结构用于保存已注册的DMA中断处理函数,成员name表示该通道的名称,它只是起说明的作用,没有什么实际用途。成员irq_handler是所注册的中断处理函数,当该通道发生中断时,该函数被调用。成员data是中断处理函数irq_handler的调用上下文,当中断处理函数被调用时,其作为第二个参数传入。

 1    38   int  pxa_request_dma ( char   * name, pxa_dma_prio prio,
 2    39                            void  ( * irq_handler)( int void   * struct  pt_regs  * ),
 3    40                            void   * data)
 4    41  {
 5    42          unsigned  long  flags;
 6    43           int  i, found  =   0 ;
 7    44  
 8    45           /*  basic sanity checks  */
 9    46           if  ( ! name  ||   ! irq_handler)
10    47                   return   - EINVAL;
11    48  
12    49          local_irq_save(flags);
13    50  
14    51           /*  try grabbing a DMA channel with the requested priority  */
15    52           for  (i  =  prio; i  <  prio  +  PXA_DMA_NBCH(prio); i ++ ) {
16    53                   if  ( ! dma_channels[i].name) {
17    54                          found  =   1 ;
18    55                           break ;
19    56                  }
20    57          }
21    58  
22    59           if  ( ! found) {
23    60                   /*  requested prio group is full, try hier priorities  */
24    61                   for  (i  =  prio - 1 ; i  >=   0 ; i -- ) {
25    62                           if  ( ! dma_channels[i].name) {
26    63                                  found  =   1 ;
27    64                                   break ;
28    65                          }
29    66                  }
30    67          }
31    68  
32    69           if  (found) {
33    70                  DCSR(i)  =  DCSR_STARTINTR | DCSR_ENDINTR | DCSR_BUSERR;
34    71                  dma_channels[i].name  =  name;
35    72                  dma_channels[i].irq_handler  =  irq_handler;
36    73                  dma_channels[i].data  =  data;
37    74          }  else  {
38    75                  printk (KERN_WARNING  " No more available DMA channels for %s\n " , name);
39    76                  i  =   - ENODEV;
40    77          }
41    78  
42    79          local_irq_restore(flags);
43    80           return  i;
44    81  }
45 

注册一个DMA通道,其参数有名称、优先级、中断处理函数和中断处理函数的调用上下文。

这里的优先级和开发手册中所说的略有差别,在开发手册中(11.3.1.1)里说,从硬件的角度,优先级分为四等,0等优先级最高,3等优先级最低。在代码中,优先级只分为高中低三等,高优先级和中优先级的通道数为8个,低优先级的通道数为16个。

dma_channels数组中,按优先级从高到低排列,在注册时,先看在所请求的优先级中是否有空位,如果有,就使用该空位,如果没有,就从更高优先级中去找,直到找一个空位,或者发现没有空位可用,则中断循环。

如果找到合适的空位,则重置该通道的状态。和我们前面几次分析中所提到的一样,70行中的代码并非是要设置DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR几个位域,而是在对应的位域上写1去清除它。

 1    83   void  pxa_free_dma ( int  dma_ch)
 2    84  {
 3    85          unsigned  long  flags;
 4    86  
 5    87           if  ( ! dma_channels[dma_ch].name) {
 6    88                  printk (KERN_CRIT
 7    89                           " %s: trying to free channel %d which is already freed\n " ,
 8    90                          __FUNCTION__, dma_ch);
 9    91                   return ;
10    92          }
11    93  
12    94          local_irq_save(flags);
13    95          DCSR(dma_ch)  =  DCSR_STARTINTR | DCSR_ENDINTR | DCSR_BUSERR;
14    96          dma_channels[dma_ch].name  =  NULL;
15    97          local_irq_restore(flags);
16    98  }
17 

该函数用于注销DMA通道,它重置对应的DCSR寄存器,并把name置空。

 1  100   static  irqreturn_t dma_irq_handler( int  irq,  void   * dev_id,  struct  pt_regs  * regs)
 2  101  {
 3  102           int  i, dint  =  DINT;
 4  103  
 5  104           for  (i  =   0 ; i  <  PXA_DMA_CHANNELS; i ++ ) {
 6  105                   if  (dint  &  ( 1   <<  i)) {
 7  106                           struct  dma_channel  * channel  =   & dma_channels[i];
 8  107                           if  (channel -> name  &&  channel -> irq_handler) {
 9  108                                  channel -> irq_handler(i, channel -> data, regs);
10  109                          }  else  {
11  110                                   /*
12  111                                  * IRQ for an unregistered DMA channel:
13  112                                  * let's clear the interrupts and disable it.
14  113                                   */
15  114                                  printk (KERN_WARNING  " spurious IRQ for DMA channel %d\n " , i);
16  115                                  DCSR(i)  =  DCSR_STARTINTR | DCSR_ENDINTR | DCSR_BUSERR;
17  116                          }
18  117                  }
19  118          }
20  119           return  IRQ_HANDLED;
21  120  }
22 
该函数是DMA中断处理函数的总入口,它在一个循环中检查所有DMA通道,如果对应通道发生中断,而且有人注册了该通道,它则调用注册的中断处理函数。如果没有人注册该通道,它就重置对应的DCSR寄存器。
 1  123   void  mhn_init_dmac( void )
 2  124  {
 3  125           int  i;
 4  126  
 5  127           for  (i  =   0 ; i  <  PXA_DMA_CHANNELS; i ++ ) {
 6  128                   /*  clear all write-1-to-clear bits  */
 7  129                  DCSR(i)  |=  (DCSR_BUSERR  |  DCSR_STARTINTR  |  DCSR_ENDINTR  |
 8  130                                  DCSR_RASINTR  |  DCSR_EORINTR);
 9  131                  DCSR(i)  =   0x0 ;
10  132          }
11  133  
12  134          DINT  =   0 ;
13  135  
14  136           /*  clear DRCMR0 ~ DRCMR63  */
15  137           for  (i  =   0 ; i  <   64 ; i ++ )
16  138                  DRCMR(i)  =   0x0 ;
17  139  
18  140           /*  clear DRCMR64 ~ DRCMR99  */
19  141           for  (i  =   0 ; i  <   36 ; i ++ )
20  142                   * (( volatile  uint32_t  * ) & DRCMR64  +  i)  =   0x0 ;
21  143  
22  144           /*  clear all the 32 DMA descriptors  */
23  145           for  (i  =   0 ; i  <   32   *   4 ; i ++ )
24  146                   * (( volatile  uint32_t  * ) & DDADR0  +  i)  =   0x0 ;
25  147  }
26 

该函数初始化所有通道的DMA寄存器,比如DCSRDINTDRCMRDDADR等。

 1  150   static   int  __init pxa_dma_init ( void )
 2  151  {
 3  152           int  ret;
 4  153  
 5  154          ret  =  request_irq (IRQ_DMA, dma_irq_handler,  0 " DMA " , NULL);
 6  155           if  (ret)
 7  156                  printk (KERN_CRIT  " Wow!  Can't register IRQ for DMA\n " );
 8  157           return  ret;
 9  158  }
10 

初始化DMA,向系统注册一个中断处理函数。

补充说明几点:

1.       ARM平台对DMA操作做了一次抽象,它让DMA操作可以独立于具体硬件平台,这样驱动程序具有更好的可移植性,但不清楚什么原因,marvellDMA实现并没有按照这个标准的方式去做。ARMDMA的抽象如下:

 

 1  struct  dma_ops {
 2           int     ( * request)(dmach_t, dma_t  * );            /*  optional  */
 3           void    ( * free)(dmach_t, dma_t  * );               /*  optional  */
 4           void    ( * enable)(dmach_t, dma_t  * );             /*  mandatory  */
 5           void    ( * disable)(dmach_t, dma_t  * );            /*  mandatory  */
 6           int     ( * residue)(dmach_t, dma_t  * );            /*  optional  */
 7           int     ( * setspeed)(dmach_t, dma_t  * int );         /*  optional  */
 8           char     * type;
 9  };
10 
11  struct  dma_struct {
12           struct  scatterlist buf;                 /*  single DMA                  */
13           int             sgcount;         /*  number of DMA SG               */
14           struct  scatterlist  * sg;                 /*  DMA Scatter-Gather List     */
15 
16          unsigned  int    active: 1 /*  Transfer active               */
17          unsigned  int    invalid: 1 ;         /*  Address/Count changed        */
18          unsigned  int    using_sg: 1 ;        /*  using scatter list?                 */
19          dmamode_t       dma_mode;        /*  DMA mode                         */
20           int             speed;           /*  DMA speed                       */
21 
22          unsigned  int     lock ;            /*  Device is allocated              */
23           const   char       * device_id;      /*  Device name                   */
24 
25          unsigned  int    dma_base;        /*  Controller base address       */
26           int             dma_irq;         /*  Controller IRQ                 */
27           struct  scatterlist cur_sg;      /*  Current controller buffer         */
28          unsigned  int    state;
29 
30           struct  dma_ops  * d_ops;
31  };
32 

2.       前面的代码没有涉及DMA的使用方法,这里我们看一段串口中代码,以补其不足。

 1    672   static   void  pxa_uart_receive_dma_start( struct  uart_pxa_port  * up)
 2    673  {
 3    674          dbg( " enter " );
 4    675          DCSR(up -> rxdma)   =  DCSR_NODESC; //  | DCSR_EORSTOPEN | DCSR_EORIRQEN;
 5    676          DSADR(up -> rxdma)  =  up -> port.mapbase;
 6    677          DTADR(up -> rxdma)  =  up -> rxdma_addr_phys;
 7    678          DCMD(up -> rxdma)  =  DCMD_INCTRGADDR  |  DCMD_FLOWSRC  |  DCMD_ENDIRQEN  |  DCMD_WIDTH1  |  DCMD_BURST16  |  DMA_BLOCK;
 8    679          DCSR(up -> rxdma)  |=  DCSR_RUN;
 9    680          dbg( " exit " );
10    681  }
11 

675marvell平台上,DMA有两种工作方式,一种可以传输多个不连续地址的buffer,称之为描述符方式传输。另外一种一次只能传输一个buffer,称为非描述符方式。这里设置为非描述符方式。

676 设置源地址,其为串口的FIFO

677 设置目标地址,其为物理内存地址。

678 设置命令寄存器。目标地址是内存,所以要加上DCMD_INCTRGADDR标志要求自动增加目标地址。而源地址是FIFO不需要显式的改变地址,所以不需要设置DCMD_INCSRCADDR标志。目标地址是内存,所以无需要流控。而源地址是FIFO,所以要设置源端流控DCMD_FLOWSRC标志。DCMD_ENDIRQEN标志允许传输完成时发现中断,DCMD_WIDTH1指明一个字节宽度,DCMD_BURST16指明一次传输16个字节,DMA_BLOCK指明传输数据的长度。

679 启动传输。

 

 1    951           if  ( 0   ==  up -> rxdma) {
 2    952                  up -> rxdma  =
 3    953                          pxa_request_dma(up -> name, DMA_PRIO_LOW, pxa_uart_receive_dma, up);
 4    954                   if  (up -> rxdma  <   0 )
 5    955                           goto   out ;
 6    956          }      
 7 
 8  971           if  (NULL  ==  up -> rxdma_addr) {
 9    972                  up -> rxdma_addr  =  dma_alloc_coherent(NULL, DMA_BLOCK,  & up -> rxdma_addr_phys, GFP_KERNEL);
10    973                   if  ( ! up -> rxdma_addr)
11    974                           goto  rxdma_err_alloc;
12    975          }    
13 

951-956 注册DMA通道,pxa_uart_receive_dma为中断处理函数。

971-975 分配用于DMA传输的内存。

你可能感兴趣的:(源代码)