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 __init s3c2440_dma_init(void)   

206 {    

207        return sysdev_driver_register(&s3c2440_sysclass, &s3c2440_dma_driver);

208 }    

DMA驱动作为系统驱动由sysdev_driver_register来向内核注册,这里只关注s3c2440_dma_driver相关的内容,即调用drive中的add方法,其他的kobject对象略过。

201 static struct sysdev_driver s3c2440_dma_driver = {            

202        .add       = s3c2440_dma_add,

203 };          

s3c2440_dma_add做了一系列的初始化工作,相应的代码如下:

194 static int __init s3c2440_dma_add(struct sys_device *sysdev)    

195 {    

196        s3c2410_dma_init();

197        s3c24xx_dma_order_set(&s3c2440_dma_order);

198        return s3c24xx_dma_init_map(&s3c2440_dma_sel);

199 }    

下面就其中出的三个函数一一进行分析。


一、     s3c2410_dma_init

首先s3c2410_dma_init()调用了plat-s3c24xx平台公共的函数s3c24xx_dma_init(4, IRQ_DMA0, 0x40);

1306      int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq,            

1307                               unsigned int stride)     

1308      {                         

1309             struct s3c2410_dma_chan *cp;              

1310             int channel;                 

1311             int ret;                  

1312                                 

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

1314                                 

1315             dma_channels = channels;                

1316                                 

1317             dma_base = ioremap(S3C24XX_PA_DMA, stride * channels);       

1318             if (dma_base == NULL) {               

1319                    printk(KERN_ERR "dma failed to remap register block/n");       

1320                    return -ENOMEM;           

1321             }                  

1322                                 

1323             dma_kmem = kmem_cache_create("dma_desc",                 

1324                                       sizeof(struct s3c2410_dma_buf), 0,

1325                                       SLAB_HWCACHE_ALIGN,

1326                                       s3c2410_dma_cache_ctor);

1327                                 

1328             if (dma_kmem == NULL) {                    

1329                    printk(KERN_ERR "dma failed to make kmem cache/n");          

1330                    ret = -ENOMEM;             

1331                    goto err;       

1332             }                  

1333                                 

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

1335                    cp = &s3c2410_chans[channel];             

1336                                 

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

1338                                 

1339                    /* dma channel irqs are in order.. */         

1340                    cp->number = channel;             

1341                    cp->irq    = channel + irq;            

1342                    cp->regs   = dma_base + (channel * stride);             

1343                                 

1344                    /* point current stats somewhere */         

1345                    cp->stats  = &cp->stats_store;             

1346                    cp->stats_store.timeout_shortest = LONG_MAX;             

1347                                 

1348                    /* basic channel configuration */       

1349                                 

1350                    cp->load_timeout = 1<<18;            

1351                                 

1352                    printk("DMA channel %d at %p, irq %d/n",          

1353                           cp->number, cp->regs, cp->irq);            

1354             }                  

1355                                 

1356             return 0;              

1357                                 

1358      err:                           

1359             kmem_cache_destroy(dma_kmem);               

1360             iounmap(dma_base);                

1361             dma_base = NULL;                 

1362             return ret;                   

1363      }                         

1364                                 

首先来关注一下函数传递的参数:

unsigned int channelss3c2440平台对应的DMA通道总数,为4

unsigned int irq:起始DMA中断的中断号

unsigned int stride:每通道DMA所占寄存器资源数

1309struct s3c2410_dma_chan记录dma通道信息,内容如下:

151 struct s3c2410_dma_chan {                                 

152        /* channel state flags and information */                        

153        unsigned char        number;      //dma通道号,   

154        unsigned char        in_use;      //当前通道是否已经使用   

155        unsigned char        irq_claimed; //     有无dma中断

156        unsigned char        irq_enabled; //是否使能了dma中断  

157        unsigned char        xfer_unit;   //传输块大小           

158                                   

159        /* channel state */                     

160                                   

161        enum s3c2410_dma_state  state;                 

162        enum s3c2410_dma_loadst       load_state;                

163        struct s3c2410_dma_client *client;                        

164                                   

165        /* channel configuration */                       

166        enum s3c2410_dmasrc       source;              

167        enum dma_ch              req_ch;       

168        unsigned long        dev_addr;          

169        unsigned long        load_timeout;            

170        unsigned int           flags;           /* channel flags */

171                                   

172        struct s3c24xx_dma_map   *map;            /* channel hw maps */

173                                   

174        /* channel's hardware position and configuration */                     

175        void __iomem              *regs;            /* channels registers */

176        void __iomem              *addr_reg;    /* data address register */  

177        unsigned int           irq;              中断号

178        unsigned long        dcon;           /默认控制寄存器的值

179                                   

180        /* driver handles */                          

181        s3c2410_dma_cbfn_t  callback_fn; 传输完成回调函数         

182        s3c2410_dma_opfn_t  op_fn;         操作完成回调函数*/      

183                                   

184        /* stats gathering */                          

185        struct s3c2410_dma_stats *stats;                          

186        struct s3c2410_dma_stats  stats_store;                      

187                                   

188        /* buffer list and information */                       

189        struct s3c2410_dma_buf    *curr;            /* current dma buffer */     

190        struct s3c2410_dma_buf    *next;            /* next buffer to load */      

191        struct s3c2410_dma_buf    *end;             /* end of queue */dma缓冲区链表

192                                   

193        /* system device */                          

194        struct sys_device  dev;                   

195 };                               

1315dma_channels是全局变量记录了当前系统dma通道总数

1317-1321行映射dma控制寄存器

1323-1332行为struct s3c2410_dma_buf分配cache,并利用函数s3c2410_dma_cache_ctor初始化为0

1334-1354行的循环就是初始化全局数组struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS];,其中包括通道编号、中断号以及寄存器基地址等信息。这个数组是针对实际的硬件信息建立的,每个硬件的dma通道唯一对应一个struct s3c2410_dma_chan的数据结构。与之对应的还有一个虚拟的dma通道,其实质是将不同dma请求源区分开来,然后用一个虚拟的通道号与之一一对应,然后与实际的dma通道通过一张map表关联起来。关于map的相关内容后面将会分析。


二、      s3c24xx_dma_order_set

首先这个函数的意义是预定一些目标板要用的dma通道,使用的是上文提到的虚拟的dma通道号。

1475      int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)        

1476      {           

1477             struct s3c24xx_dma_order *nord = dma_order;   

1478                   

1479             if (nord == NULL)     

1480                    nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);

1481                   

1482             if (nord == NULL) {  

1483                    printk(KERN_ERR "no memory to store dma channel order/n");

1484                    return -ENOMEM;

1485             }    

1486                   

1487             dma_order = nord;     

1488             memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));      

1489             return 0;

1490      }    

1477dma_order是个全局变量,其作用是记录下目标板的dma预定信息。这里使用的是s3c2440_dma_order为其赋值,数据如下:

51   static struct s3c24xx_dma_order __initdata s3c2440_dma_order = {              

52          .channels       = {               

53                 [DMACH_SDI]   = {        

54                        .list  = { 

55                               [0]   = 3 | DMA_CH_VALID,

56                               [1]   = 2 | DMA_CH_VALID,

57                               [2]   = 1 | DMA_CH_VALID,

58                               [3]   = 0 | DMA_CH_VALID,

59                        },          

60                 },                 

61                 [DMACH_I2S_IN]    = {        

62                        .list  = { 

63                               [0]   = 1 | DMA_CH_VALID,

64                               [1]   = 2 | DMA_CH_VALID,

65                        },          

66                 },                 

67                 [DMACH_I2S_OUT] = {        

68                        .list  = { 

69                               [0]   = 2 | DMA_CH_VALID,

70                               [1]   = 1 | DMA_CH_VALID,

71                        },          

72                 },                 

73                 [DMACH_PCM_IN] = {               

74                        .list  = { 

75                               [0]   = 2 | DMA_CH_VALID,

76                               [1]   = 1 | DMA_CH_VALID,

77                        },          

78                 },                 

79                 [DMACH_PCM_OUT] = {                   

80                        .list  = { 

81                               [0]   = 1 | DMA_CH_VALID,

82                               [1]   = 3 | DMA_CH_VALID,

83                        },          

84                 },                 

85                 [DMACH_MIC_IN] = {                

86                        .list  = { 

87                               [0]   = 3 | DMA_CH_VALID,

88                               [1]   = 2 | DMA_CH_VALID,

89                        },          

90                 },                 

91          },                        

92   };   

[DMACH_SDI] [DMACH_I2S_IN]等是系统为dma所分配的虚拟dma通道号,犹如中断子系统为中断分配的中断号一样,与具体硬件的中断向量号是不一致的。后面我们在系统中使用的dma通道号,都将是内核虚拟出来的, s3c2410_dma_request函数将为用户找到硬件对应的dma通道号。提取上面[DMACH_SDI] 虚拟通道来分析一下:

[DMACH_SDI]   = {        

54                        .list  = { 

55                               [0]   = 3 | DMA_CH_VALID,

56                               [1]   = 2 | DMA_CH_VALID,

57                               [2]   = 1 | DMA_CH_VALID,

58                               [3]   = 0 | DMA_CH_VALID,

59                        },          

60                 },                 

List这个结构列出的是实际dma通道的可用信息,这里表面对于sdi所能够使用的dma通道包含通道3,2,1,0一共四个通道,为什么从大到小排列是因为某些dma请求只能使用dma0dma1等较小的通道号,比如外部总线dma只能使用dma0,为了避免sdi占用,这里就采用了这种排列。

 


三、     s3c24xx_dma_init_map    

上面提到过一个map,这里就是为这个map的初始化函数了。他实际是根据硬件情况为一个全局变量赋值。与前面的初始化一样,这里主要是为了统一管理plat24xx这个平台下的dma资源,所以不同的芯片必须将自己硬件有关的dma信息初始化到相应的全局变量中。再说函数之前先来关注一下struct s3c24xx_dma_map这个数据结构,他提供了dma虚拟通道与实际的dma通道直接的关联:

struct s3c24xx_dma_map {

       const char             *name;//虚拟dma通道名称

       struct s3c24xx_dma_addr  hw_addr;

 

       unsigned long        channels[S3C_DMA_CHANNELS];//实际dma通道信息

       unsigned long        channels_rx[S3C_DMA_CHANNELS];

};

上面的结构只提供了单个虚拟通道的dma视图,整个芯片的虚拟dma通道的分配情况是靠struct s3c24xx_dma_map数组完成的。在这里由struct s3c24XX_dma_selection来统一管理。

struct s3c24xx_dma_selection {

       struct s3c24xx_dma_map   *map;//记录了struct s3c24xx_dma_map数组的首地址

       unsigned long        map_size;// struct s3c24xx_dma_map数组的成员个数

       unsigned long        dcon_mask;//dma控制器掩码

 

       void (*select)(struct s3c2410_dma_chan *chan,

                       struct s3c24xx_dma_map *map);//虚拟通道选择函数

 

       void (*direction)(struct s3c2410_dma_chan *chan,

                          struct s3c24xx_dma_map *map,

                          enum s3c2410_dmasrc dir);//dma方向

};

有了上面的背景以后下面函数就是简单的数据拷贝了,函数比较简单不在展开说明。

1454      int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)      

1455      {           

1456             struct s3c24xx_dma_map *nmap;    

1457             size_t map_sz = sizeof(*nmap) * sel->map_size;   

1458             int ptr;   

1459                   

1460             nmap = kmalloc(map_sz, GFP_KERNEL);   

1461             if (nmap == NULL)    

1462                    return -ENOMEM;

1463                   

1464             memcpy(nmap, sel->map, map_sz); 

1465             memcpy(&dma_sel, sel, sizeof(*sel));     

1466                   

1467             dma_sel.map = nmap; 

1468                   

1469             for (ptr = 0; ptr < sel->map_size; ptr++) 

1470                    s3c24xx_dma_check_entry(nmap+ptr, ptr);

1471                   

1472             return 0;

1473      }           


初始化的任务比较简单,就是

1)建立硬件dma通道信息即:

struct s3c2410_dma_chan s3c2410_chans[S3C_DMA_CHANNELS];

2)建立目标板虚拟dma通道与硬件的dma通道的关联:

static struct s3c24xx_dma_order *dma_order;

3)建立芯片本身的虚拟dma通道与硬件dma通道的视图:

static struct s3c24xx_dma_selection dma_sel;

可见上面一段阐明了s3c2440内部的4通道DMA的硬件资源是如何在linux中表示,接下来就是如何使用内核提供的接口使用DMA资源了。DMA对外提供的接口函数定义在/linux/arch/arm/plat-s3c24xx/dma.c文件中。这里根据调用过程的先后次序对其一一说明,并简单介绍其实现原理。

 

 

 

int s3c2410_dma_request(unsigned int channel,

                     struct s3c2410_dma_client *client,

                     void *dev)

首先是分配dma通道,因为DMA是一个硬件资源,对2440来说就只有四个通道,如果你申请要用的通道已被别人占用,比如外部设备要想使用dma时只能使用dam0DMA1,一旦系统硬件通道被占用则分配将面临失败。

参数:Unsigned int channel:这个是系统分配的就如同中断向量号一样与硬件的通道没有什么联系,属于内核抽象的内容。需要使用那个就直接在map中查就行了。比如DMACH_XD0就是对应外设的dma

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",

              .channels[0]   = S3C2410_DCON_CH0_SDI | DMA_CH_VALID,

              .channels[1]   = S3C2440_DCON_CH1_SDI | DMA_CH_VALID,

              .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,

       },

};

struct s3c2410_dma_client *client:这个展开以后就是个void *name指针,算是个名字吧

void *dev:指向你自己的数据吧

返回:如果向硬件分配到了资源,那么就会返回分配成功的硬件通道号,否则是个负值表示出错。

这个函数主要就是将你申请的chanle和实际的硬件通道struct s3c2410_dma_chan *chan相关联,以后使用的时候会通过s3c_dma_lookup_channel找出chanle所对应的struct s3c2410_dma_chan。另外回为这个硬件通道申请其中断函数,如果传输完成DMA引擎将以中断方式通知CPU处理。中断处理的函数均为s3c2410_dma_irq,如何使用后文再说。

 

 

 

有了硬件DMA资源,同时还需要内存,这个就要借助内核的dma内存分配函数。比如dma_alloc_writecombine,dma_alloc等函数。Dma传输时使用的是物理地址,所以真正需要的是dma_addr_t类型的地址,而不是内核虚拟地址。

 

int s3c2410_dma_enqueue(unsigned int channel, void *id,

                     dma_addr_t data, int size)

这个函数是将要传输的数据放入到dma队列中,实际上在struct s3c2410_dma_chan中为dma的缓冲区维护了一个队列,结构是

struct s3c2410_dma_buf {

       struct s3c2410_dma_buf    *next;

       int                  magic;         /* magic */

       int                  size;             /* buffer size in bytes */

       dma_addr_t          data;            /* start of DMA data */

       dma_addr_t          ptr;              /* where the DMA got to [1] */

       void               *id;        /* client's id */

};

内容如下:

       /* buffer list and information */

       struct s3c2410_dma_buf    *curr;            /* current dma buffer */

       struct s3c2410_dma_buf    *next;            /* next buffer to load */

       struct s3c2410_dma_buf    *end;             /* end of queue */

所以这个函数的作用就是将新分配的内存缓冲区添加到队列中传输。

Unsigned int channel:是所使用的dma

Void *id:自由自配

dma_addr_t data:是内存物理地址

int size:传输的内存长度

 

 

 

 

 

static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)

函数是停止当前通道的所有DMA操作,并作相应的清理工作。

 

 

static inline void

s3c2410_dma_buffdone(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf, enum s3c2410_dma_buffresult result)

这个是函数功能很简单就是当完成某个dma_buffer的传输以后,会进入中断处理函数,中断处理函数会调用s3c2410_dma_buffdone来干活,而s3c2410_dma_buffdone又会去调用(chan->callback_fn)(chan, buf->id, buf->size, result);这个回调函数,这个回调函数则是使用int s3c2410_dma_set_buffdone_fn(unsigned int channel, s3c2410_dma_cbfn_t rtn)来安装的。所以如果想在传输完成时做点自己的事情的话,就调用int s3c2410_dma_set_buffdone_fn来安装回调函数。

 

 

int s3c2410_dma_config(unsigned int channel,int xferunit)

函数根据通道号的信息来完成DMA硬件的控制寄存器的设置,但是真正写入寄存器的函数则是int s3c2410_dma_devconfig(int channel,enum s3c2410_dmasrc source,unsigned long devaddr)

这两个函数联合起来共同完成DMA寄存器的硬件设置工作。

 

 

申请通道、申请内存、将缓冲区放入队列、安装回调函数以及初始化硬件寄存器以后,最后的工作就是使用static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)来开始DMA的传输了。

 

 

最后还有一组dma的控制操作3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op)

实际上就是根据op 的内容来决定开始或停止等一系列的DMA操作。

你可能感兴趣的:(Linux DMA驱动构架分析)