Linux驱动修炼之道-DMA框架源码分析

 

首先介绍一下DMA,S3C2440A支持位于系统总线和外围总线之间的4通道DMA控制器,每个通道都可以在系统总线或外围总线上的设备之间传输数据。每个通道可以对下面4种情况进行传输:
1.源和目的都在系统总线上
2.源在系统总线而目的在外围总线
3.源在外围总线而目的在系统总线
4.源和目的都在外围总线
下图是请求源为硬件模式时的每个通道的请求源:

DMA使用3个状态的有限状态机:
1.初始状态,DMA等待DMA请求,一旦请求到达DMA进入状态2,DMA ACK与INT REQ为0。
2.在这个状态,DMA ACK置为1并且计数器CURR_TC的值被从DCON[19:0]载入,注意DMA ACK保持为1直到它被清除。
3.在这个状态,处理DMA原子操作的子状态机被初始化。子状态机从源地址读取数据,然后写入目的地址。在这个操作中,要考虑数据的大小和传输的大小(单个/突发),这个操作重复执行直到计数器(CURR_TC)变成0在全服务模式,而只执行一次在单服务模式。当子状态机结束每一次原子操作的时候主状态机减少CURR_TC的值。另外,主状态机发出INT REQ信号当CURR_TC变成0并且DCON[29]位被置位1时。并且,清除DMA ACK,如果下面两个条件之一满足的话:
1)在全服务模式下CURR_TC变成0
2)在单服务模式下结束原子操作
注意在单服务模式下,主有限状态机的3个状态被执行然后停止,等待另一个DMA REQ。如果DMA REQ到来,所有的3个状态被重复执行。所以在每次原子操作中DMA ACK被置位,然后又被清除。与之对比,在全服务模式,主有限状态机等在状态3直到CURR_TC变成0。所以,DMA ACK在传输期间被置位,到TC为0时被清除。
然而,不管哪个服务模式,INT REQ被发出只有当CURR_TC变成0时。
如下图,是基本的DMA时序:

nXDREQ生效后等待至少2个时钟周期,nXDACK响应并开始生效,但要知道延时至少3个时钟周期,DMA控制器才可获得总线的控制权,进行读写操作一次。

下面来分析内核DMA驱动源码:
首先来看一下DMA驱动是怎样注册的:
这里使用了系统设备的概念,通过内核中源码的注释我们看看什么是系统设备。系统设备与驱动模型有一点不同,他们不需要动态的驱动绑定,也不能被探测,并且不属于任何类型的外围总线。对系统设备我们仍然有驱动的概念,因为我们仍想执行在这些设备上执行基本的操作。
在arch/arm/plat-s3c24xx/s3c244x.c中,注册了系统设备的类:

[c-sharp] view plain copy print ?
  1. struct sysdev_class s3c2440_sysclass = {  
  2.     .name       = "s3c2440-core",  
  3.     .suspend    = s3c244x_suspend,  
  4.     .resume     = s3c244x_resume  
  5. };  
  6. static int __init s3c2440_core_init(void)  
  7. {  
  8.     return sysdev_class_register(&s3c2440_sysclass);  
  9. }  

在arch/arm/mach-s3c2410/dma.c中,注册了dma的驱动:

[c-sharp] view plain copy print ?
  1. #if defined(CONFIG_CPU_S3C2410)   
  2. static struct sysdev_driver s3c2410_dma_driver = {  
  3.     .add    = s3c2410_dma_add,  
  4. };  

把dma驱动注册到设备类下:

[c-sharp] view plain copy print ?
  1. static int __init s3c2410_dma_drvinit(void)  
  2. {  
  3.     return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_dma_driver);  
  4. }  

先来看一下系统设备类:

[c-sharp] view plain copy print ?
  1. struct sysdev_class {  
  2.     const char *name;  
  3.     struct list_head    drivers;  
  4.   
  5.     /* Default operations for these types of devices */  
  6.     int (*shutdown)(struct sys_device *);  
  7.     int (*suspend)(struct sys_device *, pm_message_t state);  
  8.     int (*resume)(struct sys_device *);  
  9.     struct kset     kset;  
  10. };  

这个结构体有一个drivers双向循环链表,注册到这个类的驱动都挂在这里。
下面分析一下dma驱动是怎样注册的:

[c-sharp] view plain copy print ?
  1. int sysdev_driver_register(struct sysdev_class *cls, struct sysdev_driver *drv)  
  2. {  
  3.     。。。。。。。。。。。  
  4.     if (cls && kset_get(&cls->kset)) {  
  5.         /*这里把这个驱动添加到了设备类的驱动链表上*/  
  6.         list_add_tail(&drv->entry, &cls->drivers);  
  7.           
  8.         /*如果驱动定义了add方法,则为类下的每个设备调用驱动的add方法*/  
  9.         if (drv->add) {  
  10.             struct sys_device *dev;  
  11.             list_for_each_entry(dev, &cls->kset.list, kobj.entry)  
  12.                 drv->add(dev);  
  13.         }  
  14.     } else {  
  15.         err = -EINVAL;  
  16.         WARN(1, KERN_ERR "%s: invalid device class/n", __func__);  
  17.     }  
  18.     mutex_unlock(&sysdev_drivers_lock);  
  19.     return err;  
  20. }  

在arch/arm/mach-s3c2440/s3c2440.c中,注册了一个系统设备s3c2440_sysdev,

[c-sharp] view plain copy print ?
  1. static struct sys_device s3c2440_sysdev = {  
  2.     .cls        = &s3c2440_sysclass,  
  3. };  
  4. int __init s3c2440_init(void)  
  5. {  
  6.     。。。。。。。。  
  7.     return sysdev_register(&s3c2440_sysdev);  
  8. }  

注意系统设备这个结构体,里边封装了一个系统设备类。

[c-sharp] view plain copy print ?
  1. struct sys_device {  
  2.     u32     id;  
  3.     struct sysdev_class * cls;  
  4.     struct kobject      kobj;  
  5. };  

下面来看一下系统设备的注册,系统设备是一个虚拟设备,这里的目的就是为了调用driver的add函数。

[c-sharp] view plain copy print ?
  1. int sysdev_register(struct sys_device *sysdev){       
  2.     。。。。。。。。。。。。      
  3.     /* Notify class auxillary drivers */  
  4.     list_for_each_entry(drv, &cls->drivers, entry) {  
  5.         /*为这个设备调用了设备类下所有驱动的add函数*/  
  6.         if (drv->add)  
  7.             drv->add(sysdev);  
  8.     }  
  9.     。。。。。。。。。。。。。  
  10. }  

下面来分析一下这个add函数。看上边的那个dma驱动的结构体,指明了add函数为s3c2410_dma_add:

[c-sharp] view plain copy print ?
  1. static int __init s3c2410_dma_add(struct sys_device *sysdev)  
  2. {  
  3.     s3c2410_dma_init();                              (一)  
  4.     s3c24xx_dma_order_set(&s3c2410_dma_order);   (二)  
  5.     return s3c24xx_dma_init_map(&s3c2410_dma_sel);   (三)  
  6. }  
 

分别对s3c2410_dma_add中的3个函数进行分析:

(一)

[c-sharp] view plain copy print ?
  1. int __init s3c2410_dma_init(void)  
  2. {  
  3.     /*4个通道,中断号为IRQ_DMA0,每一个通道的寄存器覆盖的地址范围为0x40*/  
  4.     return s3c24xx_dma_init(4, IRQ_DMA0, 0x40);  
  5. }  
 

[c-sharp] view plain copy print ?
  1. int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,  
  2.                 unsigned int stride)  
  3. {  
  4.     /*每一个通道用一个s3c2410_dma_chan结构体描述*/  
  5.     struct s3c2410_dma_chan *cp;  
  6.     int channel;  
  7.     int ret;  
  8.   
  9.     printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics/n");  
  10.     /*dma_channels是一个全局变量,用来存放通道数量*/  
  11.     dma_channels = channels;  
  12.     /*获得DMA寄存器的虚拟起始地址*/  
  13.     dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);  
  14.     if (dma_base == NULL) {  
  15.         printk(KERN_ERR "dma failed to remap register block/n");  
  16.         return -ENOMEM;  
  17.     }  
  18.     /*分配一个高速缓冲区,以后用来分配s3c2410_dma_buf*/  
  19.     dma_kmem = kmem_cache_create("dma_desc",  
  20.                      sizeof(struct s3c2410_dma_buf), 0,  
  21.                      SLAB_HWCACHE_ALIGN,  
  22.                      s3c2410_dma_cache_ctor);  
  23.   
  24.     if (dma_kmem == NULL) {  
  25.         printk(KERN_ERR "dma failed to make kmem cache/n");  
  26.         ret = -ENOMEM;  
  27.         goto err;  
  28.     }  
  29.       
  30.     for (channel = 0; channel < channels;  channel++) {  
  31.         cp = &s3c2410_chans[channel];  
  32.         memset(cp, 0, sizeof(struct s3c2410_dma_chan));  
  33.         /*对通道的结构体进行初始化*/  
  34.         cp->number = channel;            //通道号   
  35.         cp->irq    = channel + irq;      //通道中断号   
  36.         cp->regs   = dma_base + (channel * stride);   //通道寄存器基址   
  37.   
  38.         /* point current stats somewhere */  
  39.         cp->stats  = &cp->stats_store;  
  40.         cp->stats_store.timeout_shortest = LONG_MAX;  
  41.   
  42.         /* basic channel configuration */  
  43.         /*设置加载的超时时间*/  
  44.         cp->load_timeout = 1<<18;  
  45.         printk("DMA channel %d at %p, irq %d/n",  
  46.                cp->number, cp->regs, cp->irq);  
  47.     }  
  48.     return 0;  
  49.   
  50.  err:  
  51.     kmem_cache_destroy(dma_kmem);  
  52.     iounmap(dma_base);  
  53.     dma_base = NULL;  
  54.     return ret;  
  55. }  
 

这里使用到了一个s3c2410_dma_chan结构体,struct s3c2410_dma_chan记录dma通道信息,内容如下:

[c-sharp] view plain copy print ?
  1. 151 struct s3c2410_dma_chan {                                   
  2. 152        /* channel state flags and information */                          
  3. 153        unsigned char        number;      //dma通道号,      
  4. 154        unsigned char        in_use;      //当前通道是否已经使用      
  5. 155        unsigned char        irq_claimed; //     有无dma中断   
  6. 156        unsigned char        irq_enabled; //是否使能了dma中断     
  7. 157        unsigned char        xfer_unit;   //传输块大小              
  8. 158                                     
  9. 159        /* channel state */                       
  10. 160                                     
  11. 161        enum s3c2410_dma_state  state;                   
  12. 162        enum s3c2410_dma_loadst       load_state;                  
  13. 163        struct s3c2410_dma_client *client;                          
  14. 164                                     
  15. 165        /* channel configuration */                         
  16. 166        enum s3c2410_dmasrc       source;                
  17. 167        enum dma_ch              req_ch;         
  18. 168        unsigned long        dev_addr;            
  19. 169        unsigned long        load_timeout;              
  20. 170        unsigned int           flags;           /* channel flags */  
  21. 171                                     
  22. 172        struct s3c24xx_dma_map   *map;            /* channel hw maps */  
  23. 173                                     
  24. 174        /* channel's hardware position and configuration */                       
  25. 175        void __iomem              *regs;            /* channels registers */  
  26. 176        void __iomem              *addr_reg;    /* data address register */    
  27. 177        unsigned int           irq;              中断号  
  28. 178        unsigned long        dcon;           /默认控制寄存器的值  
  29. 179                                     
  30. 180        /* driver handles */                            
  31. 181        s3c2410_dma_cbfn_t  callback_fn; 传输完成回调函数           
  32. 182        s3c2410_dma_opfn_t  op_fn;         操作完成回调函数*/        
  33. 183                                     
  34. 184        /* stats gathering */                            
  35. 185        struct s3c2410_dma_stats *stats;                            
  36. 186        struct s3c2410_dma_stats  stats_store;                        
  37. 187                                     
  38. 188        /* buffer list and information */                         
  39. 189        struct s3c2410_dma_buf    *curr;            /* current dma buffer */       
  40. 190        struct s3c2410_dma_buf    *next;            /* next buffer to load */        
  41. 191        struct s3c2410_dma_buf    *end;             /* end of queue */dma缓冲区链表  
  42. 192                                     
  43. 193        /* system device */                            
  44. 194        struct sys_device  dev;                     
  45. 195 };  
 

(二)
先看下边一个结构体,s3c2410_dma_order。这个是建立目标板dma源与硬件的dma通道的关联。

[c-sharp] view plain copy print ?
  1. static struct s3c24xx_dma_order __initdata s3c2410_dma_order = {  
  2.     .channels   = {  
  3.         [DMACH_SDI] = {  
  4.             .list   = {  
  5.                 [0] = 3 | DMA_CH_VALID,  
  6.                 [1] = 2 | DMA_CH_VALID,  
  7.                 [2] = 0 | DMA_CH_VALID,  
  8.             },  
  9.         },  
  10.         [DMACH_I2S_IN]  = {  
  11.             .list   = {  
  12.                 [0] = 1 | DMA_CH_VALID,  
  13.                 [1] = 2 | DMA_CH_VALID,  
  14.             },  
  15.         },  
  16.     },  
  17. };  
 

分析这里SDI可以是使用通道3,2,0,为什么从大到小排列,是因为某些dma请求只能使用dma0,dma1等较小的通道号,比如外部总线dma只能只用dma0,为了避免sdi占用,这里就采用的这种排列。

[c-sharp] view plain copy print ?
  1. [DMACH_SDI] = {  
  2.             .list   = {  
  3.                 [0] = 3 | DMA_CH_VALID,  
  4.                 [1] = 2 | DMA_CH_VALID,  
  5.                 [2] = 0 | DMA_CH_VALID,  
  6.             },  
  7.         },  
 

注意这个结构体是用__initdata修饰的,所以在初始化后会被释放掉。下边这个函数重新分配内存保存这个结构体就是这个原因。

[c-sharp] view plain copy print ?
  1. int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)  
  2. {  
  3.     struct s3c24xx_dma_order *nord = dma_order;  
  4.   
  5.     if (nord == NULL)  
  6.         nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);  
  7.   
  8.     if (nord == NULL) {  
  9.         printk(KERN_ERR "no memory to store dma channel order/n");  
  10.         return -ENOMEM;  
  11.     }  
  12.   
  13.     dma_order = nord;  
  14.     memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));  
  15.     return 0;  
  16. }  
 

(三)

[c-sharp] view plain copy print ?
  1. struct s3c24xx_dma_map {  
  2.     const char      *name;      //DMA源的名   
  3.     struct s3c24xx_dma_addr  hw_addr;   //源的物理地址   
  4.   
  5.     unsigned long        channels[S3C2410_DMA_CHANNELS];  //DMA通道信息   
  6.     unsigned long        channels_rx[S3C2410_DMA_CHANNELS];  
  7. };  
  8.   
  9. struct s3c24xx_dma_selection {  
  10.     struct s3c24xx_dma_map  *map;       //记录了struct s3c24xx_dma_map数组的首地址   
  11.     unsigned long        map_size;  //struct s3c24xx_dma_map数组的成员个数   
  12.     unsigned long        dcon_mask; //dma控制器掩码   
  13.   
  14.     void    (*select)(struct s3c2410_dma_chan *chan,  
  15.               struct s3c24xx_dma_map *map);    //源选择函数   
  16.   
  17.     void    (*direction)(struct s3c2410_dma_chan *chan,  //dma方向   
  18.                  struct s3c24xx_dma_map *map,  
  19.                  enum s3c2410_dmasrc dir);  
  20. };  
 

建立芯片本身的dma源与硬件dma通道的视图。

[c-sharp] view plain copy print ?
  1. int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)  
  2. {  
  3.     struct s3c24xx_dma_map *nmap;  
  4.     size_t map_sz = sizeof(*nmap) * sel->map_size;  
  5.     int ptr;  
  6.   
  7.     nmap = kmalloc(map_sz, GFP_KERNEL);  
  8.     if (nmap == NULL)  
  9.         return -ENOMEM;  
  10.   
  11.     memcpy(nmap, sel->map, map_sz);  
  12.     memcpy(&dma_sel, sel, sizeof(*sel));  
  13.   
  14.     dma_sel.map = nmap;  
  15.   
  16.     for (ptr = 0; ptr < sel->map_size; ptr++)  
  17.         s3c24xx_dma_check_entry(nmap+ptr, ptr);  
  18.   
  19.     return 0;  
  20. }  
  21. static struct s3c24xx_dma_selection __initdata s3c2410_dma_sel = {  
  22.     .select     = s3c2410_dma_select,      //通道选择函数   
  23.     .dcon_mask  = 7 << 24,                 //屏蔽DMA控制寄存器中用于选择请求源的位   
  24.     .map        = s3c2410_dma_mappings,    //dma源与硬件dma通道的视图   
  25.     .map_size   = ARRAY_SIZE(s3c2410_dma_mappings),  //虚拟通道的数目   
  26. };  
  27. static void s3c2410_dma_select(struct s3c2410_dma_chan *chan,  
  28.                    struct s3c24xx_dma_map *map)  
  29. {  
  30.     chan->dcon = map->channels[chan->number] & ~DMA_CH_VALID;       //选择通道,并设置成请求模式   
  31. }  
  32. static struct s3c24xx_dma_map __initdata s3c2410_dma_mappings[] = {  
  33.     [DMACH_XD0] = {  
  34.         .name       = "xdreq0",  
  35.         .channels[0]    = S3C2410_DCON_CH0_XDREQ0 | DMA_CH_VALID,  
  36.     },  
  37.     [DMACH_XD1] = {  
  38.         .name       = "xdreq1",  
  39.         .channels[1]    = S3C2410_DCON_CH1_XDREQ1 | DMA_CH_VALID,  
  40.     },  
  41.     [DMACH_SDI] = {  
  42.         .name       = "sdi",  
  43.         .channels[0]    = S3C2410_DCON_CH0_SDI | DMA_CH_VALID,  
  44.         .channels[2]    = S3C2410_DCON_CH2_SDI | DMA_CH_VALID,  
  45.         .channels[3]    = S3C2410_DCON_CH3_SDI | DMA_CH_VALID,  
  46.         .hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,  
  47.         .hw_addr.from   = S3C2410_PA_IIS + S3C2410_IISFIFO,  
  48.     },  
  49.     [DMACH_SPI0] = {  
  50.         .name       = "spi0",  
  51.         .channels[1]    = S3C2410_DCON_CH1_SPI | DMA_CH_VALID,  
  52.         .hw_addr.to = S3C2410_PA_SPI + S3C2410_SPTDAT,  
  53.         .hw_addr.from   = S3C2410_PA_SPI + S3C2410_SPRDAT,  
  54.     },  
  55.     [DMACH_SPI1] = {  
  56.         .name       = "spi1",  
  57.         .channels[3]    = S3C2410_DCON_CH3_SPI | DMA_CH_VALID,  
  58.         .hw_addr.to = S3C2410_PA_SPI + 0x20 + S3C2410_SPTDAT,  
  59.         .hw_addr.from   = S3C2410_PA_SPI + 0x20 + S3C2410_SPRDAT,  
  60.     },  
  61.     [DMACH_UART0] = {  
  62.         .name       = "uart0",  
  63.         .channels[0]    = S3C2410_DCON_CH0_UART0 | DMA_CH_VALID,  
  64.         .hw_addr.to = S3C2410_PA_UART0 + S3C2410_UTXH,  
  65.         .hw_addr.from   = S3C2410_PA_UART0 + S3C2410_URXH,  
  66.     },  
  67.     [DMACH_UART1] = {  
  68.         .name       = "uart1",  
  69.         .channels[1]    = S3C2410_DCON_CH1_UART1 | DMA_CH_VALID,  
  70.         .hw_addr.to = S3C2410_PA_UART1 + S3C2410_UTXH,  
  71.         .hw_addr.from   = S3C2410_PA_UART1 + S3C2410_URXH,  
  72.     },  
  73.         [DMACH_UART2] = {  
  74.         .name       = "uart2",  
  75.         .channels[3]    = S3C2410_DCON_CH3_UART2 | DMA_CH_VALID,  
  76.         .hw_addr.to = S3C2410_PA_UART2 + S3C2410_UTXH,  
  77.         .hw_addr.from   = S3C2410_PA_UART2 + S3C2410_URXH,  
  78.     },  
  79.     [DMACH_TIMER] = {  
  80.         .name       = "timer",  
  81.         .channels[0]    = S3C2410_DCON_CH0_TIMER | DMA_CH_VALID,  
  82.         .channels[2]    = S3C2410_DCON_CH2_TIMER | DMA_CH_VALID,  
  83.         .channels[3]    = S3C2410_DCON_CH3_TIMER | DMA_CH_VALID,  
  84.     },  
  85.     [DMACH_I2S_IN] = {  
  86.         .name       = "i2s-sdi",  
  87.         .channels[1]    = S3C2410_DCON_CH1_I2SSDI | DMA_CH_VALID,  
  88.         .channels[2]    = S3C2410_DCON_CH2_I2SSDI | DMA_CH_VALID,  
  89.         .hw_addr.from   = S3C2410_PA_IIS + S3C2410_IISFIFO,  
  90.     },  
  91.     [DMACH_I2S_OUT] = {  
  92.         .name       = "i2s-sdo",  
  93.         .channels[2]    = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID,  
  94.         .hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO,  
  95.     },  
  96.     [DMACH_USB_EP1] = {  
  97.         .name       = "usb-ep1",  
  98.         .channels[0]    = S3C2410_DCON_CH0_USBEP1 | DMA_CH_VALID,  
  99.     },  
  100.     [DMACH_USB_EP2] = {  
  101.         .name       = "usb-ep2",  
  102.         .channels[1]    = S3C2410_DCON_CH1_USBEP2 | DMA_CH_VALID,  
  103.     },  
  104.     [DMACH_USB_EP3] = {  
  105.         .name       = "usb-ep3",  
  106.         .channels[2]    = S3C2410_DCON_CH2_USBEP3 | DMA_CH_VALID,  
  107.     },  
  108.     [DMACH_USB_EP4] = {  
  109.         .name       = "usb-ep4",  
  110.         .channels[3]    =S3C2410_DCON_CH3_USBEP4 | DMA_CH_VALID,  
  111.     },  
  112. };  

DMA通道的使用:申请通道,申请中断,设置寄存器,安装回调函数,设置标志,将数据放入队列,最后就是调用static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)来开始DMA的传输了。
首先看通道的申请:

[c-sharp] view plain copy print ?
  1. int s3c2410_dma_request(unsigned int channel,  
  2.             struct s3c2410_dma_client *client,  
  3.             void *dev)  
  4. {  
  5.     struct s3c2410_dma_chan *chan;  
  6.     unsigned long flags;  
  7.     int err;  
  8.   
  9.     pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p/n",  
  10.          channel, client->name, dev);  
  11.   
  12.     local_irq_save(flags);                  //关中断   
  13.     /*找到一个有效的物理通道*/  
  14.     chan = s3c2410_dma_map_channel(channel);  
  15.     if (chan == NULL) {  
  16.         local_irq_restore(flags);  
  17.         return -EBUSY;  
  18.     }  
  19.   
  20.     dbg_showchan(chan);  
  21.     /*设置通道的名字*/  
  22.     chan->client = client;  
  23.     /*设置通道的使用标志*/  
  24.     chan->in_use = 1;  
  25.   
  26.     if (!chan->irq_claimed) {       //该中断没有被注册   
  27.         pr_debug("dma%d: %s : requesting irq %d/n",  
  28.              channel, __func__, chan->irq);  
  29.   
  30.         chan->irq_claimed = 1;   //标记该中断被注册   
  31.         local_irq_restore(flags); //开中断   
  32.   
  33.         err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,  //注册中断处理程序   
  34.                   client->name, (void *)chan);  
  35.   
  36.         local_irq_save(flags);  
  37.   
  38.         if (err) {  
  39.             chan->in_use = 0;  
  40.             chan->irq_claimed = 0;  
  41.             local_irq_restore(flags);  
  42.   
  43.             printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d/n",  
  44.                    client->name, chan->irq, chan->number);  
  45.             return err;  
  46.         }  
  47.   
  48.         chan->irq_enabled = 1;  
  49.     }  
  50.   
  51.     local_irq_restore(flags);  
  52.   
  53.     /* need to setup */  
  54.   
  55.     pr_debug("%s: channel initialised, %p/n", __func__, chan);  
  56.   
  57.     return chan->number | DMACH_LOW_LEVEL;  
  58. }  

下面的函数是找通道好,先在板子通道映射中找,再在芯片通道映射中找。

[c-sharp] view plain copy print ?
  1. static struct s3c2410_dma_chan *s3c2410_dma_map_channel(int channel)  
  2. {  
  3.     struct s3c24xx_dma_order_ch *ord = NULL;  
  4.     struct s3c24xx_dma_map *ch_map;  
  5.     struct s3c2410_dma_chan *dmach;  
  6.     int ch;  
  7.   
  8.     if (dma_sel.map == NULL || channel > dma_sel.map_size)  
  9.         return NULL;  
  10.     /*获得芯片的虚拟通道与真实通道映射的结构*/  
  11.     ch_map = dma_sel.map + channel;  
  12.   
  13.     /* first, try the board mapping */  
  14.     /*如果有板子通道映射*/  
  15.     if (dma_order) {  
  16.         /*得到对应虚拟通道的所有真实通道的结构*/  
  17.         ord = &dma_order->channels[channel];  
  18.         /*找这个虚拟通道对应的每一个真实通道,看有没有有效并且未被使用的*/  
  19.         for (ch = 0; ch < dma_channels; ch++) {  
  20.             if (!is_channel_valid(ord->list[ch]))  
  21.                 continue;  
  22.   
  23.             if (s3c2410_chans[ord->list[ch]].in_use == 0) {  
  24.                 ch = ord->list[ch] & ~DMA_CH_VALID;  
  25.                 goto found;  
  26.             }  
  27.         }  
  28.   
  29.         if (ord->flags & DMA_CH_NEVER)  
  30.             return NULL;  
  31.     }  
  32.     /*检查芯片虚拟通道与真实通道的映射,看有没有有效且未被使用的真实通道*/  
  33.     for (ch = 0; ch < dma_channels; ch++) {  
  34.         if (!is_channel_valid(ch_map->channels[ch]))  
  35.             continue;  
  36.   
  37.         if (s3c2410_chans[ch].in_use == 0) {  
  38.             printk("mapped channel %d to %d/n", channel, ch);  
  39.             break;  
  40.         }  
  41.     }  
  42.     if (ch >= dma_channels)  
  43.         return NULL;  
  44.     /* update our channel mapping */  
  45.   
  46.  found:  
  47.     /*将找到的通道保存在dmach中,并返回*/  
  48.     dmach = &s3c2410_chans[ch];  
  49.     dmach->map = ch_map;  
  50.     dma_chan_map[channel] = dmach;  
  51.   
  52.     /* select the channel */  
  53.     /*调用选择通道的函数*/  
  54.     (dma_sel.select)(dmach, ch_map);  
  55.   
  56.     return dmach;  
  57. }  

设置寄存器,设置寄存器的工作由s3c2410_dma_devconfig和s3c2410_dma_config完成:

[c-sharp] view plain copy print ?
  1. int s3c2410_dma_devconfig(int channel,  
  2.               enum s3c2410_dmasrc source,  
  3.               int hwcfg,  
  4.               unsigned long devaddr)  
  5. {  
  6.     /*根据虚拟通道号找到真实通道*/  
  7.     struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);  
  8.   
  9.     if (chan == NULL)  
  10.         return -EINVAL;  
  11.   
  12.     pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx/n",  
  13.          __func__, (int)source, hwcfg, devaddr);  
  14.       
  15.     chan->source = source;     //保存dma源   
  16.     chan->dev_addr = devaddr;  //保存dma源地址   
  17.     chan->hw_cfg = hwcfg;      //保存dma源的控制信息   
  18.   
  19.     switch (source) {  
  20.     case S3C2410_DMASRC_HW:                       //源是外设,从外设读数据到内存,源的地址是固定的   
  21.         /* source is hardware */  
  22.         pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d/n",  
  23.              __func__, devaddr, hwcfg);  
  24.         dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);         //初始化源控制寄存器   
  25.         dma_wrreg(chan, S3C2410_DMA_DISRC,  devaddr);           //将源地址写入初始源寄存器   
  26.         dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));   //目的地在AHB总线上   
  27.   
  28.         chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);    
  29.         break;  
  30.   
  31.     case S3C2410_DMASRC_MEM:                    //源是内存,从内存读数据到外设上,目的地址是固定的   
  32.         /* source is memory */  
  33.         pr_debug("%s: mem source, devaddr=%08lx, hwcfg=%d/n",  
  34.              __func__, devaddr, hwcfg);  
  35.         dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));   //目的地址在AHB总线上   
  36.         dma_wrreg(chan, S3C2410_DMA_DIDST,  devaddr);           //把目的地址写到初始目的寄存器   
  37.         dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);         //初始化目的控制寄存器   
  38.   
  39.         chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);    
  40.         break;  
  41.         /*无论内存是源还是目的,这个地址始终是保存在chan->addr_reg*/  
  42.     default:  
  43.         printk(KERN_ERR "dma%d: invalid source type (%d)/n",  
  44.                channel, source);  
  45.   
  46.         return -EINVAL;  
  47.     }  
  48.   
  49.     if (dma_sel.direction != NULL)  
  50.         (dma_sel.direction)(chan, chan->map, source);  
  51.   
  52.     return 0;  
  53. }  

[c-sharp] view plain copy print ?
  1. int s3c2410_dma_config(unsigned int channel,  
  2.                int xferunit,  
  3.                int dcon)  
  4. {  
  5.     /*找到虚拟通道对应的实际通道*/  
  6.     struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);  
  7.   
  8.     pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x/n",  
  9.          __func__, channel, xferunit, dcon);  
  10.   
  11.     if (chan == NULL)  
  12.         return -EINVAL;  
  13.       
  14.     pr_debug("%s: Initial dcon is %08x/n", __func__, dcon);  
  15.     /*清除DMA源的选择位*/  
  16.     dcon |= chan->dcon & dma_sel.dcon_mask;            
  17.   
  18.     pr_debug("%s: New dcon is %08x/n", __func__, dcon);  
  19.     /*传输数据的大小*/  
  20.     switch (xferunit) {  
  21.     case 1:  
  22.         dcon |= S3C2410_DCON_BYTE;  
  23.         break;  
  24.   
  25.     case 2:  
  26.         dcon |= S3C2410_DCON_HALFWORD;  
  27.         break;  
  28.   
  29.     case 4:  
  30.         dcon |= S3C2410_DCON_WORD;  
  31.         break;  
  32.   
  33.     default:  
  34.         pr_debug("%s: bad transfer size %d/n", __func__, xferunit);  
  35.         return -EINVAL;  
  36.     }  
  37.   
  38.     dcon |= S3C2410_DCON_HWTRIG;     //DMA源是硬件   
  39.     dcon |= S3C2410_DCON_INTREQ;     //中断使能   
  40.   
  41.     pr_debug("%s: dcon now %08x/n", __func__, dcon);  
  42.     /*将通道控制寄存器和传输大小存于chan中*/  
  43.     chan->dcon = dcon;               
  44.     chan->xfer_unit = xferunit;  
  45.   
  46.     return 0;  
  47. }  

设置回调函数:

[c-sharp] view plain copy print ?
  1. int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn)  
  2. {  
  3.     。。。。。。。  
  4.     chan->callback_fn = rtn;  
  5.   
  6.     return 0;  
  7. }  

设置标志:

[c-sharp] view plain copy print ?
  1. int s3c2410_dma_setflags(unsigned int channel, unsigned int flags)  
  2. {  
  3.     。。。。。。。。。。。。。。  
  4.     chan->flags = flags;  
  5.   
  6.     return 0;  
  7. }  

将数据放入队列,先看一下一个结构:

[c-sharp] view plain copy print ?
  1. struct s3c2410_dma_buf {  
  2.        struct s3c2410_dma_buf    *next;  
  3.        int                  magic;         /* magic */  
  4.        int                  size;             /* buffer size in bytes */  
  5.        dma_addr_t          data;            /* start of DMA data */  
  6.        dma_addr_t          ptr;              /* where the DMA got to [1] */  
  7.        void               *id;        /* client's id */  
  8. };  

每个struct s3c2410_dma_chan维护了一个缓冲区队列,每个缓冲区用上边的结构表示。在struct s3c2410_dma_chan中的结构是:

[c-sharp] view plain copy print ?
  1. /* buffer list and information */  
  2.  struct s3c2410_dma_buf    *curr;            /* current dma buffer */  
  3.  struct s3c2410_dma_buf    *next;            /* next buffer to load */  
  4.  struct s3c2410_dma_buf    *end;             /* end of queue */  

下边这个函数就是完成将s3c2410_dma_buf放入这个队列中排队:

[c-sharp] view plain copy print ?
  1. int s3c2410_dma_enqueue(unsigned int channel, void *id,  
  2.             dma_addr_t data, int size)  
  3. {  
  4.     /*找到虚拟通道对应的实际通道*/  
  5.     struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);  
  6.     struct s3c2410_dma_buf *buf;  
  7.     unsigned long flags;  
  8.   
  9.     if (chan == NULL)  
  10.         return -EINVAL;  
  11.   
  12.     pr_debug("%s: id=%p, data=%08x, size=%d/n",  
  13.          __func__, id, (unsigned int)data, size);  
  14.     /*分配s3c2410_dma_chan结构的buffer*/  
  15.     buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);  
  16.     if (buf == NULL) {  
  17.         pr_debug("%s: out of memory (%ld alloc)/n",  
  18.              __func__, (long)sizeof(*buf));  
  19.         return -ENOMEM;  
  20.     }  
  21.   
  22.     //pr_debug("%s: new buffer %p/n", __func__, buf);   
  23.     //dbg_showchan(chan);   
  24.     /*设置这个buffer*/  
  25.     buf->next  = NULL;  
  26.     buf->data  = buf->ptr = data;  //指向要传输数据的地址   
  27.     buf->size  = size;             //该段buffer的大小   
  28.     buf->id    = id;  
  29.     buf->magic = BUF_MAGIC;  
  30.   
  31.     local_irq_save(flags);  
  32.     /*加载的是该通道的第一段buf*/  
  33.     if (chan->curr == NULL) {  
  34.         /* we've got nothing loaded... */  
  35.         pr_debug("%s: buffer %p queued onto empty channel/n",  
  36.              __func__, buf);  
  37.               
  38.         chan->curr = buf;   //curr指向现在生成的buf   
  39.         chan->end  = buf;  
  40.         chan->next = NULL;  
  41.     } else {  
  42.         pr_debug("dma%d: %s: buffer %p queued onto non-empty channel/n",  
  43.              chan->number, __func__, buf);  
  44.   
  45.         if (chan->end == NULL)  
  46.             pr_debug("dma%d: %s: %p not empty, and chan->end==NULL?/n",  
  47.                  chan->number, __func__, chan);  
  48.         /*从链表尾加入链表*/  
  49.         chan->end->next = buf;    
  50.         chan->end = buf;  
  51.     }  
  52.   
  53.     /* if necessary, update the next buffer field */  
  54.     if (chan->next == NULL)  
  55.         chan->next = buf;  
  56.       
  57.     if (chan->state == S3C2410_DMA_RUNNING) {                               //该channel正在运行   
  58.         if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {         //已有buf load了   
  59.             if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {     //等待load   
  60.                 printk(KERN_ERR "dma%d: loadbuffer:"  
  61.                        "timeout loading buffer/n",  
  62.                        chan->number);  
  63.                 dbg_showchan(chan);  
  64.                 local_irq_restore(flags);  
  65.                 return -EINVAL;  
  66.             }  
  67.         }  
  68.   
  69.         while (s3c2410_dma_canload(chan) && chan->next != NULL) {        //检查能否load   
  70.             s3c2410_dma_loadbuffer(chan, chan->next);                //load buffer   
  71.         }  
  72.     } else if (chan->state == S3C2410_DMA_IDLE) {                            //该channel空闲着   
  73.         if (chan->flags & S3C2410_DMAF_AUTOSTART) {                      //如果设了自动启动标记,则直接启动该次传输   
  74.             s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,         //启动传输   
  75.                      S3C2410_DMAOP_START);  
  76.         }  
  77.     }  
  78.   
  79.     local_irq_restore(flags);  
  80.     return 0;  
  81. }  

channel在运行的时候会有很多状态,在arch/arm/mach-s3c2410/include/mach/dma.h,注意已经很清楚了,我就不多解释了。

[c-sharp] view plain copy print ?
  1. /* enum s3c2410_dma_loadst 
  2.  * 
  3.  * This represents the state of the DMA engine, wrt to the loaded / running 
  4.  * transfers. Since we don't have any way of knowing exactly the state of 
  5.  * the DMA transfers, we need to know the state to make decisions on wether 
  6.  * we can 
  7.  * 
  8.  * S3C2410_DMA_NONE 
  9.  * 
  10.  * There are no buffers loaded (the channel should be inactive) 
  11.  * 
  12.  * S3C2410_DMA_1LOADED 
  13.  * 
  14.  * There is one buffer loaded, however it has not been confirmed to be 
  15.  * loaded by the DMA engine. This may be because the channel is not 
  16.  * yet running, or the DMA driver decided that it was too costly to 
  17.  * sit and wait for it to happen. 
  18.  * 
  19.  * S3C2410_DMA_1RUNNING 
  20.  * 
  21.  * The buffer has been confirmed running, and not finisged 
  22.  * 
  23.  * S3C2410_DMA_1LOADED_1RUNNING 
  24.  * 
  25.  * There is a buffer waiting to be loaded by the DMA engine, and one 
  26.  * currently running. 
  27. */  
  28.   
  29. enum s3c2410_dma_loadst {  
  30.     S3C2410_DMALOAD_NONE,  
  31.     S3C2410_DMALOAD_1LOADED,  
  32.     S3C2410_DMALOAD_1RUNNING,  
  33.     S3C2410_DMALOAD_1LOADED_1RUNNING,  
  34. };  

中断处理函数:

[c-sharp] view plain copy print ?
  1. static irqreturn_t  
  2. s3c2410_dma_irq(int irq, void *devpw)  
  3. {  
  4.     struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw;  
  5.     struct s3c2410_dma_buf  *buf;  
  6.   
  7.     buf = chan->curr;                                   //当前传输完毕的buf   
  8.   
  9.     dbg_showchan(chan);  
  10.   
  11.     /* modify the channel state */  
  12.   
  13.     switch (chan->load_state) {                         //改变状态,如果对上边那4个状态理解了很容易看懂的   
  14.     case S3C2410_DMALOAD_1RUNNING:  
  15.         /* TODO - if we are running only one buffer, we probably 
  16.          * want to reload here, and then worry about the buffer 
  17.          * callback */  
  18.   
  19.         chan->load_state = S3C2410_DMALOAD_NONE;  
  20.         break;  
  21.   
  22.     case S3C2410_DMALOAD_1LOADED:  
  23.         /* iirc, we should go back to NONE loaded here, we 
  24.          * had a buffer, and it was never verified as being 
  25.          * loaded. 
  26.          */  
  27.   
  28.         chan->load_state = S3C2410_DMALOAD_NONE;  
  29.         break;  
  30.   
  31.     case S3C2410_DMALOAD_1LOADED_1RUNNING:  
  32.         /* we'll worry about checking to see if another buffer is 
  33.          * ready after we've called back the owner. This should 
  34.          * ensure we do not wait around too long for the DMA 
  35.          * engine to start the next transfer 
  36.          */  
  37.   
  38.         chan->load_state = S3C2410_DMALOAD_1LOADED;  
  39.         break;  
  40.   
  41.     case S3C2410_DMALOAD_NONE:  
  42.         printk(KERN_ERR "dma%d: IRQ with no loaded buffer?/n",  
  43.                chan->number);  
  44.         break;  
  45.   
  46.     default:  
  47.         printk(KERN_ERR "dma%d: IRQ in invalid load_state %d/n",  
  48.                chan->number, chan->load_state);  
  49.         break;  
  50.     }  
  51.   
  52.     if (buf != NULL) {                                                       //如果不为空   
  53.         /* update the chain to make sure that if we load any more 
  54.          * buffers when we call the callback function, things should 
  55.          * work properly */  
  56.   
  57.         chan->curr = buf->next;                                         //指向下一个buf   
  58.         buf->next  = NULL;  
  59.   
  60.         if (buf->magic != BUF_MAGIC) {  
  61.             printk(KERN_ERR "dma%d: %s: buf %p incorrect magic/n",  
  62.                    chan->number, __func__, buf);  
  63.             return IRQ_HANDLED;  
  64.         }  
  65.   
  66.         s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);                 //buf传输完成后的操作   
  67.   
  68.         /* free resouces */  
  69.         s3c2410_dma_freebuf(buf);                                        //释放buf   
  70.     } else {  
  71.     }  
  72.   
  73.     /* only reload if the channel is still running... our buffer done 
  74.      * routine may have altered the state by requesting the dma channel 
  75.      * to stop or shutdown... */  
  76.   
  77.     /* todo: check that when the channel is shut-down from inside this 
  78.      * function, we cope with unsetting reload, etc */  
  79.   
  80.     if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {            //还有要传输的buf,则继续传输   
  81.         unsigned long flags;  
  82.   
  83.         switch (chan->load_state) {  
  84.         case S3C2410_DMALOAD_1RUNNING:  
  85.             /* don't need to do anything for this state */  
  86.             break;  
  87.   
  88.         case S3C2410_DMALOAD_NONE:  
  89.             /* can load buffer immediately */  
  90.             break;  
  91.   
  92.         case S3C2410_DMALOAD_1LOADED:  
  93.             if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {     //如果已经有载入的,则等待被载入   
  94.                 /* flag error? */  
  95.                 printk(KERN_ERR "dma%d: timeout waiting for load (%s)/n",  
  96.                        chan->number, __func__);  
  97.                 return IRQ_HANDLED;  
  98.             }  
  99.   
  100.             break;  
  101.   
  102.         case S3C2410_DMALOAD_1LOADED_1RUNNING:  
  103.             goto no_load;  
  104.   
  105.         default:  
  106.             printk(KERN_ERR "dma%d: unknown load_state in irq, %d/n",  
  107.                    chan->number, chan->load_state);  
  108.             return IRQ_HANDLED;  
  109.         }  
  110.   
  111.         local_irq_save(flags);  
  112.         s3c2410_dma_loadbuffer(chan, chan->next);                       //载入buf   
  113.         local_irq_restore(flags);  
  114.     } else {                                                                    //所有传输完成   
  115.         s3c2410_dma_lastxfer(chan);                                         //完成处理工作   
  116.   
  117.         /* see if we can stop this channel.. */  
  118.         if (chan->load_state == S3C2410_DMALOAD_NONE) {  
  119.             pr_debug("dma%d: end of transfer, stopping channel (%ld)/n",  
  120.                  chan->number, jiffies);  
  121.             s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,            //停止DMA传输   
  122.                      S3C2410_DMAOP_STOP);  
  123.         }  
  124.     }  
  125.   
  126.  no_load:  
  127.     return IRQ_HANDLED;  
  128. }  

可以选择不同的dma操作:

[c-sharp] view plain copy print ?
  1. int  
  2. s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op)  
  3. {  
  4.     struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);  
  5.   
  6.     if (chan == NULL)  
  7.         return -EINVAL;  
  8.   
  9.     switch (op) {  
  10.     case S3C2410_DMAOP_START:  
  11.         return s3c2410_dma_start(chan);  
  12.   
  13.     case S3C2410_DMAOP_STOP:  
  14.         return s3c2410_dma_dostop(chan);  
  15.   
  16.     case S3C2410_DMAOP_PAUSE:  
  17.     case S3C2410_DMAOP_RESUME:  
  18.         return -ENOENT;  
  19.   
  20.     case S3C2410_DMAOP_FLUSH:  
  21.         return s3c2410_dma_flush(chan);  
  22.   
  23.     case S3C2410_DMAOP_STARTED:  
  24.         return s3c2410_dma_started(chan);  
  25.   
  26.     case S3C2410_DMAOP_TIMEOUT:  
  27.         return 0;  
  28.     }  
  29.     return -ENOENT;      /* unknown, don't bother */  
  30. }  

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值