努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处
http://blog.csdn.net/woshixingaaa/archive/2011/06/02/6462089.aspx
首先介绍一下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中,注册了系统设备的类:
struct sysdev_class s3c2440_sysclass = { .name = "s3c2440-core", .suspend = s3c244x_suspend, .resume = s3c244x_resume }; static int __init s3c2440_core_init(void) { return sysdev_class_register(&s3c2440_sysclass); }
在arch/arm/mach-s3c2410/dma.c中,注册了dma的驱动:
#if defined(CONFIG_CPU_S3C2410) static struct sysdev_driver s3c2410_dma_driver = { .add = s3c2410_dma_add, };
把dma驱动注册到设备类下:
static int __init s3c2410_dma_drvinit(void) { return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_dma_driver); }
先来看一下系统设备类:
struct sysdev_class { const char *name; struct list_head drivers; /* Default operations for these types of devices */ int (*shutdown)(struct sys_device *); int (*suspend)(struct sys_device *, pm_message_t state); int (*resume)(struct sys_device *); struct kset kset; };
这个结构体有一个drivers双向循环链表,注册到这个类的驱动都挂在这里。
下面分析一下dma驱动是怎样注册的:
int sysdev_driver_register(struct sysdev_class *cls, struct sysdev_driver *drv) { 。。。。。。。。。。。 if (cls && kset_get(&cls->kset)) { /*这里把这个驱动添加到了设备类的驱动链表上*/ list_add_tail(&drv->entry, &cls->drivers); /*如果驱动定义了add方法,则为类下的每个设备调用驱动的add方法*/ if (drv->add) { struct sys_device *dev; list_for_each_entry(dev, &cls->kset.list, kobj.entry) drv->add(dev); } } else { err = -EINVAL; WARN(1, KERN_ERR "%s: invalid device class/n", __func__); } mutex_unlock(&sysdev_drivers_lock); return err; }
在arch/arm/mach-s3c2440/s3c2440.c中,注册了一个系统设备s3c2440_sysdev,
static struct sys_device s3c2440_sysdev = { .cls = &s3c2440_sysclass, }; int __init s3c2440_init(void) { 。。。。。。。。 return sysdev_register(&s3c2440_sysdev); }
注意系统设备这个结构体,里边封装了一个系统设备类。
struct sys_device { u32 id; struct sysdev_class * cls; struct kobject kobj; };
下面来看一下系统设备的注册,系统设备是一个虚拟设备,这里的目的就是为了调用driver的add函数。
int sysdev_register(struct sys_device *sysdev){ 。。。。。。。。。。。。 /* Notify class auxillary drivers */ list_for_each_entry(drv, &cls->drivers, entry) { /*为这个设备调用了设备类下所有驱动的add函数*/ if (drv->add) drv->add(sysdev); } 。。。。。。。。。。。。。 }
下面来分析一下这个add函数。看上边的那个dma驱动的结构体,指明了add函数为s3c2410_dma_add:
static int __init s3c2410_dma_add(struct sys_device *sysdev) { s3c2410_dma_init(); (一) s3c24xx_dma_order_set(&s3c2410_dma_order); (二) return s3c24xx_dma_init_map(&s3c2410_dma_sel); (三) }
分别对s3c2410_dma_add中的3个函数进行分析:
(一)
int __init s3c2410_dma_init(void) { /*4个通道,中断号为IRQ_DMA0,每一个通道的寄存器覆盖的地址范围为0x40*/ return s3c24xx_dma_init(4, IRQ_DMA0, 0x40); }
int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq, unsigned int stride) { /*每一个通道用一个s3c2410_dma_chan结构体描述*/ struct s3c2410_dma_chan *cp; int channel; int ret; printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics/n"); /*dma_channels是一个全局变量,用来存放通道数量*/ dma_channels = channels; /*获得DMA寄存器的虚拟起始地址*/ 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_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++) { cp = &s3c2410_chans[channel]; memset(cp, 0, sizeof(struct s3c2410_dma_chan)); /*对通道的结构体进行初始化*/ cp->number = channel; //通道号 cp->irq = channel + irq; //通道中断号 cp->regs = dma_base + (channel * stride); //通道寄存器基址 /* 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; }
这里使用到了一个s3c2410_dma_chan结构体,struct 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 };
(二)
先看下边一个结构体,s3c2410_dma_order。这个是建立目标板dma源与硬件的dma通道的关联。
static struct s3c24xx_dma_order __initdata s3c2410_dma_order = { .channels = { [DMACH_SDI] = { .list = { [0] = 3 | DMA_CH_VALID, [1] = 2 | DMA_CH_VALID, [2] = 0 | DMA_CH_VALID, }, }, [DMACH_I2S_IN] = { .list = { [0] = 1 | DMA_CH_VALID, [1] = 2 | DMA_CH_VALID, }, }, }, };
分析这里SDI可以是使用通道3,2,0,为什么从大到小排列,是因为某些dma请求只能使用dma0,dma1等较小的通道号,比如外部总线dma只能只用dma0,为了避免sdi占用,这里就采用的这种排列。
[DMACH_SDI] = { .list = { [0] = 3 | DMA_CH_VALID, [1] = 2 | DMA_CH_VALID, [2] = 0 | DMA_CH_VALID, }, },
注意这个结构体是用__initdata修饰的,所以在初始化后会被释放掉。下边这个函数重新分配内存保存这个结构体就是这个原因。
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; }
(三)
struct s3c24xx_dma_map { const char *name; //DMA源的名 struct s3c24xx_dma_addr hw_addr; //源的物理地址 unsigned long channels[S3C2410_DMA_CHANNELS]; //DMA通道信息 unsigned long channels_rx[S3C2410_DMA_CHANNELS]; }; 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, //dma方向 struct s3c24xx_dma_map *map, enum s3c2410_dmasrc dir); };
建立芯片本身的dma源与硬件dma通道的视图。
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; } static struct s3c24xx_dma_selection __initdata s3c2410_dma_sel = { .select = s3c2410_dma_select, //通道选择函数 .dcon_mask = 7 << 24, //屏蔽DMA控制寄存器中用于选择请求源的位 .map = s3c2410_dma_mappings, //dma源与硬件dma通道的视图 .map_size = ARRAY_SIZE(s3c2410_dma_mappings), //虚拟通道的数目 }; static void s3c2410_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_map __initdata s3c2410_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[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[2] = S3C2410_DCON_CH2_I2SSDO | DMA_CH_VALID, .hw_addr.to = S3C2410_PA_IIS + S3C2410_IISFIFO, }, [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, }, };
更多内容请看: