31、 2410下DMA驱动源码分析
2011-08-23 17:58
253 查看
首先我们由 kconfig 和 makefile 来获取 DMA 方面相关文件 ( 即源码 ): Arch/arm/plat-s3c24xx/Dma.c Arch/arm/mach-s3c2410/Dma.c 以上两个就是操作 DMA 的核心文件 . 我们会逐个的来分析 . 先看初始化函数 , 哪些是初始化函数呢 ? 就是哪些通过 module_init, core_initcall, arch_initcall 等声明的函数 . 首先在 arch/arm/mach-s3c2410/s3c2410.c 下有个初始化函数 . arch/arm/mach-s3c2410/s3c2410.c: static int __init s3c2410_core_init(void) { return sysdev_class_register(&s3c2410_sysclass); // 注册一个 class 类 } core_initcall(s3c2410_core_init); 我们以后会看到 , 后面的 DMA 设备及 DMA 驱动都会注册到该类下面 . arch/arm/mach-s3c2410/s3c2410.c: struct sysdev_class s3c2410_sysclass = { set_kset_name("s3c2410-core"), }; 很明显 , 实际上该类并没有其他什么操作 , 只是为了让 DMA 设备和驱动都注册到这个类下面 , 以使对方可以互相找的到 . 接着在 arch/arm/plat-s3c24xx/Dma.c 下也注册了一个类 arch/arm/plat-s3c24xx/Dma.c: static int __init s3c24xx_dma_sysclass_init(void) { int ret = sysdev_class_register(&dma_sysclass); // 注册的类 if (ret != 0) printk(KERN_ERR "dma sysclass registration failed/n"); return ret; } struct sysdev_class dma_sysclass = { set_kset_name("s3c24xx-dma"), .suspend = s3c2410_dma_suspend, .resume = s3c2410_dma_resume, }; 后面我们会看到这 2 个类是如何使用的 . 其中的 dma_sysclass 还有 suspend 和 resume 的操作 , 这些都是电源管理方面的东西 , 我们这里就不分析了 . 接着看在 arch/arm/mach-s3c2410/Dma.c 下注册了 DMA 的驱动程序 arch/arm/mach-s3c2410/Dma.c: #if defined(CONFIG_CPU_S3C2410) /* 我们以 2410 为例 */ static struct sysdev_driver s3c2410_dma_driver = { .add = s3c2410_dma_add, }; static int __init s3c2410_dma_drvinit(void) { // 注册驱动 , 把 s3c2410_dma_driver 注册到 s3c2410_sysclass 类下 return sysdev_driver_register(&s3c2410_sysclass, &s3c2410_dma_driver); } arch_initcall(s3c2410_dma_drvinit); #endif 可以看到这个函数就是把 DMA 的驱动程序注册到 s3c2410_sysclass 的类下面 , 后面我们会看到 DMA 设备是如何找到整个驱动并调用驱动的 add 函数的 . Drivers/base/sys.c: int sysdev_driver_register(struct sysdev_class * cls, struct sysdev_driver * drv) { down(&sysdev_drivers_lock); if (cls && kset_get(&cls->kset)) { list_add_tail(&drv->entry, &cls->drivers); // 把驱动注册到类下面的 drivers list 下 /* If devices of this class already exist, tell the driver */ if (drv->add) { // 如果驱动有 add 函数的话 struct sys_device *dev; list_for_each_entry(dev, &cls->kset.list, kobj.entry) drv->add(dev); // 为该类下的每个设备调用驱动的 add 函数 . } } else list_add_tail(&drv->entry, &sysdev_drivers); // 把驱动注册到类下面的 drivers list 下 up(&sysdev_drivers_lock); return 0; } 通过上面这个函数 , 我们就看到了 s3c2410_dma_driver 是如何注册进 s3c2410_sysclass 类的 , 即就是把 s3c2410_dma_driver 挂到 s3c2410_sysclass 下的 drivers 列表下 . 接着我们来看 DMA 设备的注册了 . Arch/arm/mach-s3c2410/s3c2410.c: int __init s3c2410_init(void) { printk("S3C2410: Initialising architecture/n"); return sysdev_register(&s3c2410_sysdev); // 注册设备了 } static struct sys_device s3c2410_sysdev = { .cls = &s3c2410_sysclass, }; 这个函数注册了一个系统设备 , 我们看到 , 其实这是个虚拟设备 ( 其实根本就不是个设备 ), 它仅仅是为了要触发 dma 驱动的那个 add 函数 , 所有的 DMA 设备会在那个时候才会真正的注册 . 至于这个函数是怎么调用的问题 , 就由读者自己去分析吧 J , 不过我记得我有文章分析过的哦 . Drivers/base/sys.c: int sysdev_register(struct sys_device * sysdev) { int error; struct sysdev_class * cls = sysdev->cls; if (!cls) return -EINVAL; /* Make sure the kset is set */ sysdev->kobj.kset = &cls->kset; /* But make sure we point to the right type for sysfs translation */ sysdev->kobj.ktype = &ktype_sysdev; error = kobject_set_name(&sysdev->kobj, "%s%d", kobject_name(&cls->kset.kobj), sysdev->id); if (error) return error; pr_debug("Registering sys device '%s'/n", kobject_name(&sysdev->kobj)); /* Register the object */ error = kobject_register(&sysdev->kobj); if (!error) { struct sysdev_driver * drv; down(&sysdev_drivers_lock); /* Generic notification is implicit, because it's that * code that should have called us. */ // 对于我们分析 DMA 来讲 , 更关心的是下面这段代码 /* Notify global drivers */ // 调用所有全局的 sysdev_drivers list_for_each_entry(drv, &sysdev_drivers, entry) { if (drv->add) drv->add(sysdev); } /* Notify class auxillary drivers */ // 接着调用具体 class 下面的驱动 list_for_each_entry(drv, &cls->drivers, entry) { if (drv->add) drv->add(sysdev); // 驱动的 add 函数 . } up(&sysdev_drivers_lock); } return error; } 我们可以看到 s3c2410_sysdev 的类就是 s3c2410_sysclass, 所以这里找到的驱动就是前面我们注册进 s3c2410_sysclass 的 dma 驱动 , 因此这里的 add 函数就是 s3c2410_dma_add 了 . Arch/arm/mach-s3c2410/dma.c: static int s3c2410_dma_add(struct sys_device *sysdev) { s3c2410_dma_init(); //DMA 初始化 s3c24xx_dma_order_set(&s3c2410_dma_order); return s3c24xx_dma_init_map(&s3c2410_dma_sel); } 真正的 DMA 方面的操作就从这个函数开始了 . 我们一个个函数来看 . Arch/arm/plat-s3c24xx/dma.c: int s3c2410_dma_init(void) { return s3c24xx_dma_init(4, IRQ_DMA0, 0x40); } 我们来看下参数 , 第一个参数代表 dma channel 数 ( 参考 2410 data sheet), 第二个参数是 dma 的中断号 , 第三个参数是每个 channel 对应的寄存器基地址与前一个 channel 的寄存器的基地址的偏移 , 即如果第一个 channel 的第一个寄存器的地址是 0x4b000000 则第二个 channel 的第一个寄存器的地址是 0x4b000040, 接着看 Arch/arm/plat-s3c24xx/dma.c: int __init s3c24xx_dma_init(unsigned int channels, unsigned int irq, unsigned int stride) { struct s3c2410_dma_chan *cp; // 每个 channel 都由个 s3c2410_dma_chan 表示 int channel; int ret; printk("S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics/n"); dma_channels = channels; // 保存 channel 的数量 // 把所有 channel 的所有寄存器地址由实地址转换成虚拟地址 . // 我们驱动中使用的都是虚拟地址 . dma_base = ioremap(S3C24XX_PA_DMA, stride * channels); if (dma_base == NULL) { printk(KERN_ERR "dma failed to remap register block/n"); return -ENOMEM; } // 创建一个高速缓冲对象 , 具体可参考 linux 设备驱动程序 III 的第 8 章 dma_kmem = kmem_cache_create("dma_desc", sizeof(struct s3c2410_dma_buf), 0, SLAB_HWCACHE_ALIGN, s3c2410_dma_cache_ctor, NULL); if (dma_kmem == NULL) { printk(KERN_ERR "dma failed to make kmem cache/n"); ret = -ENOMEM; goto err; } // 为每个 channel 初始化 . for (channel = 0; channel < channels; channel++) { cp = &s3c2410_chans[channel]; // 全局变量保存每个 channel 的信息 . memset(cp, 0, sizeof(struct s3c2410_dma_chan)); /* dma channel irqs are in order.. */ cp->number = channel; //channel 号 cp->irq = channel + irq; // 该 channel 的中断号 cp->regs = dma_base + (channel * stride); // 该 channel 的寄存器基地址 /* point current stats somewhere */ cp->stats = &cp->stats_store; //channel 状态 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); 1734d iounmap(dma_base); dma_base = NULL; return ret; } 这个函数就是对每个 channel 进行初始化 , 并把每个 channel 的相关信息保存起来供以后的操作使用 . 接着看下一个函数 : Arch/arm/plat-s3c24xx/dma.c: int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord) { struct s3c24xx_dma_order *nord = dma_order; //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; } // 保存 ord 信息 dma_order = nord; memcpy(nord, ord, sizeof(struct s3c24xx_dma_order)); return 0; } 这个函数主要是分配了一个内存用来保存 order 信息 , 我们来看传进来的参数 Arch/arm/mach-s3c2410/dma.c: 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, }, }, }, }; 注意这个变量用 __initdata 定义了 , 因此它只在初始化的时候存在 , 所以我们有必要分配一块内存来保存它的信息 . 这也是上面那个函数的作用 , 那这个 s3c2410_dma_order 到底有什么作用呢 , 我们看这个结构的解释 Include/asm-arm/plat-s3c24xx/dma.h:: /* struct s3c24xx_dma_order * * information provided by either the core or the board to give the * dma system a hint on how to allocate channels */ // 注释说的很明确了吧 , 就是用来指导系统如何分配 dma channel, 因为 2410 下的 4 个 channel 的源跟目的并不是所有的外设都可以使用的 . struct s3c24xx_dma_order { struct s3c24xx_dma_order_ch channels[DMACH_MAX]; }; 看完了 s3c24xx_dma_order_set, 我们接着看 s3c24xx_dma_init_map Arch/arm/plat-s3c24xx/dma.c: 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; } 这个函数和 s3c24xx_dma_order_set 的作用一样 , 也是先分配一块内存然后在保存信息 . 我们来看参数 : Arch/arm/mach-s3c2410/dma.c: static struct s3c24xx_dma_selection __initdata s3c2410_dma_sel = { .select = s3c2410_dma_select, .dcon_mask = 7 << 24, .map = s3c2410_dma_mappings, .map_size = ARRAY_SIZE(s3c2410_dma_mappings), }; 呵呵也是用 __initdata 定义的 , 难怪要重新分配内存并保存起来 , 那这些是什么信息呢 , 我们看到主要就是个 map, 我们接着来看这个 map 中到底存了些什么东西 . Arch/arm/mach-s3c2410/dma.c: 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, }, }; 一大堆东西 , 我们还是来看这个结构的注释吧 Include/asm-arm/plat-s3c24xx/dma.h: /* struct s3c24xx_dma_map * * this holds the mapping information for the channel selected * to be connected to the specified device */ // 保存了一些被选择使用的 channel 和规定的设备间的一些 map 信息 . 具体到了使用的时候就会明白了 struct s3c24xx_dma_map { const char *name; struct s3c24xx_dma_addr hw_addr; unsigned long channels[S3C2410_DMA_CHANNELS]; }; Ok, 这样就把 s3c2410_dma_add 函数分析完了 , 到这里把每个 channel 的各种信息包括各 channel 的寄存器地址 , 中断号 , 跟设备的关系等信息都保存好了 , 但是虽然每个 channel 都初始化好了 , 但是还记得吗 , 到目前为址 , 我们仅仅是向系统注册了一个虚拟的设备 , 真真的 DMA 设备还没注册进系统呢 , 因此接下来就是要注册 DMA 设备了 , 在哪呢 ? Arch/arm/plat-s3c24xx/dma.c: static int __init s3c24xx_dma_sysdev_register(void) { struct s3c2410_dma_chan *cp = s3c2410_chans; // 这个全局变量里已经保存了 channel 信息哦 int channel, ret; // 对每个 channel 操作 for (channel = 0; channel < dma_channels; cp++, channel++) { cp->dev.cls = &dma_sysclass; // 指定 class 为 dma_sysclass cp->dev.id = channel; //channel 号 ret = sysdev_register(&cp->dev); // 注册设备 if (ret) { printk(KERN_ERR "error registering dev for dma %d/n", channel); return ret; } } return 0; } late_initcall(s3c24xx_dma_sysdev_register); // 注意这行 , 它会在初始化完毕后被调用 , 这个函数把所有的 channel 注册到 dma_sysclass 类下 , 我们前面看到注册设备时会调用该类的 add 函数 , 还好这里的 dma_sysclass 类没有 add 函数 , 我们可以轻松下了 . Ok, 到这里 DMA 设备算是全部准备好了 , 可以随时被请求使用了 , 到这里我们总结一下 : Arch/arm/mach-s3c2410/dma.c 下的代码主要是跟具体板子相关的代码 , 而真正核心的代码都在 Arch/arm/plat-s3c24xx/dma.c 下 , 因此如果我们有块跟 2410 类似的板子的话 , 主要实现的就是 Arch/arm/mach-s3c2410/dma.c 这个文件了 , 同时我们也不难推测 , 使用 DMA 的函数应该都在 Arch/arm/plat-s3c24xx/dma.c 下 . 没错 , 说的更具体些就是这个文件下被 EXPORT_SYMBOL 出来的函数都是提供给外部使用的 , 也就是其他部分使用 DMA 的接口 . 知道了这些我们接着来分析这些被 EXPORT_SYMBOL 的函数吧 . Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_getposition * * returns the current transfer points for the dma source and destination */ int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst) { // 获取保存该 channel 信息的对象 , 初始化的时候讲过 struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); if (chan == NULL) return -EINVAL; if (src != NULL) // 获取源地址 *src = dma_rdreg(chan, S3C2410_DMA_DCSRC); if (dst != NULL) // 获取目的地址 *dst = dma_rdreg(chan, S3C2410_DMA_DCDST); return 0; } EXPORT_SYMBOL(s3c2410_dma_getposition); 这个函数获取某个 channel 当前正在传输的源地址和目的地址 . 主要就是通过读该 channel 的源和目的寄存器获得的 . S3C2410_DMA_DCSRC, S3C2410_DMA_DCDST 就是源和目的的偏移地址 . 可参考 2410 的 datasheet. dma_rdreg 就是读寄存器 . Arch/arm/plat-s3c24xx/dma.c: #define dma_rdreg(chan, reg) readl((chan)->regs + (reg)) 接着看下一个 export 的函数 /* s3c2410_dma_devconfig * * configure the dma source/destination hardware type and address * * source: S3C2410_DMASRC_HW: source is hardware * S3C2410_DMASRC_MEM: source is memory * * hwcfg: the value for xxxSTCn register, * bit 0: 0=increment pointer, 1=leave pointer * bit 1: 0=source is AHB, 1=source is APB * * devaddr: physical address of the source */ Arch/arm/plat-s3c24xx/dma.c: int s3c2410_dma_devconfig(int channel, enum s3c2410_dmasrc source, int hwcfg, unsigned long devaddr) { // 获取保存该 channel 信息的对象 , 初始化的时候讲过 struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); if (chan == NULL) return -EINVAL; pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx/n", __FUNCTION__, (int)source, hwcfg, devaddr); chan->source = source; // 保存 DMA 源 chan->dev_addr = devaddr; // 保存源地址 // 根据不同的 DMA 源来初始化 DMA channel switch (source) { case S3C2410_DMASRC_HW: /* source is hardware */ pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d/n", __FUNCTION__, devaddr, hwcfg); dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3); dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr); // 源地址 dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0)); chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST); return 0; case S3C2410_DMASRC_MEM: /* source is memory */ pr_debug( "%s: mem source, devaddr=%08lx, hwcfg=%d/n", __FUNCTION__, devaddr, hwcfg); dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0)); dma_wrreg(chan, S3C2410_DMA_DIDST, devaddr); dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3); chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC); return 0; } printk(KERN_ERR "dma%d: invalid source type (%d)/n", channel, source); return -EINVAL; } EXPORT_SYMBOL(s3c2410_dma_devconfig); 这个函数用来配置某个 channel 的源的类型及源地址 , 然后为某种源设置好地址增长方式 , 具体寄存器含义参考 2410 datasheet, 2410 下 DMA 的各种操作模式可参考我的另一篇文章 . Arch/arm/plat-s3c24xx/dma.c: int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); if (chan == NULL) return -EINVAL; pr_debug("%s: chan=%p, callback rtn=%p/n", __FUNCTION__, chan, rtn); chan->callback_fn = rtn; // 设置回调函数 return 0; } EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn); 该函数主要为某个 channel 设置一个 done 的回调函数 . 该回调函数会在传输完成后被调用 . Arch/arm/plat-s3c24xx/dma.c: /* do we need to protect the settings of the fields from * irq? */ int s3c2410_dma_set_opfn(dmach_t channel, s3c2410_dma_opfn_t rtn) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); if (chan == NULL) return -EINVAL; pr_debug("%s: chan=%p, op rtn=%p/n", __FUNCTION__, chan, rtn); chan->op_fn = rtn; return 0; } EXPORT_SYMBOL(s3c2410_dma_set_opfn); 该函数主要为某个 channel 设置一个操作的回调函数 . 该回调函数会在操作该 channel 时被调用 ( 有哪些操作会在 s3c2410_dma_ctrl 里看到 ) Arch/arm/plat-s3c24xx/dma.c: int s3c2410_dma_setflags(dmach_t channel, unsigned int flags) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); if (chan == NULL) return -EINVAL; pr_debug("%s: chan=%p, flags=%08x/n", __FUNCTION__, chan, flags); chan->flags = flags; // 设置标记 return 0; } EXPORT_SYMBOL(s3c2410_dma_setflags); 该函数主要为某个 channel 设置一个标记 , 标记有 : Include/asm-arm/arch-s3c2410/dma.h: /* flags */ #define S3C2410_DMAF_SLOW (1<<0) /* slow, so don't worry about * waiting for reloads */ #define S3C2410_DMAF_AUTOSTART (1<<1) /* auto-start if buffer queued */ 我们会在后面看到 flag 的使用 Arch/arm/plat-s3c24xx/dma.c: /* DMA configuration for each channel * * DISRCC -> source of the DMA (AHB,APB) * DISRC -> source address of the DMA * DIDSTC -> destination of the DMA (AHB,APD) * DIDST -> destination address of the DMA */ /* s3c2410_dma_config * * xfersize: size of unit in bytes (1,2,4) * dcon: base value of the DCONx register */ int s3c2410_dma_config(dmach_t channel, int xferunit, int dcon) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x/n", __FUNCTION__, channel, xferunit, dcon); if (chan == NULL) return -EINVAL; pr_debug("%s: Initial dcon is %08x/n", __FUNCTION__, dcon); dcon |= chan->dcon & dma_sel.dcon_mask; pr_debug("%s: New dcon is %08x/n", __FUNCTION__, dcon); // 设置每个传输单元的大小 switch (xferunit) { case 1: dcon |= S3C2410_DCON_BYTE; break; case 2: dcon |= S3C2410_DCON_HALFWORD; break; case 4: dcon |= S3C2410_DCON_WORD; break; default: pr_debug("%s: bad transfer size %d/n", __FUNCTION__, xferunit); return -EINVAL; } dcon |= S3C2410_DCON_HWTRIG; // 硬件请求模式 dcon |= S3C2410_DCON_INTREQ; // 打开中断 pr_debug("%s: dcon now %08x/n", __FUNCTION__, dcon); // 保存配置到全局变量中 chan->dcon = dcon; chan->xfer_unit = xferunit; return 0; } EXPORT_SYMBOL(s3c2410_dma_config); 该函数主要用来配置某个 channel 的请求模式 , 传输单元大小等 . 从中可以看出目前只支持硬件请求模式 Arch/arm/plat-s3c24xx/dma.c: int s3c2410_dma_ctrl(dmach_t channel, enum s3c2410_chan_op op) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); if (chan == NULL) return -EINVAL; switch (op) { case S3C2410_DMAOP_START: return s3c2410_dma_start(chan); // 开始一个 DMA 传输 case S3C2410_DMAOP_STOP: return s3c2410_dma_dostop(chan); // 停止一个 DMA 传输 case S3C2410_DMAOP_PAUSE: case S3C2410_DMAOP_RESUME: return -ENOENT; case S3C2410_DMAOP_FLUSH: return s3c2410_dma_flush(chan); // case S3C2410_DMAOP_STARTED: // 指示传输开始 return s3c2410_dma_started(chan); case S3C2410_DMAOP_TIMEOUT: return 0; } return -ENOENT; /* unknown, don't bother */ } EXPORT_SYMBOL(s3c2410_dma_ctrl); OK, 这个函数主要就是用来启用 , 停止 DMA 操作了 , 比较重要的一个函数 . 等分析完了 export 的接口后 , 我们在来逐个分析每个 DMA 操作 . Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_free * * release the given channel back to the system, will stop and flush * any outstanding transfers, and ensure the channel is ready for the * next claimant. * * Note, although a warning is currently printed if the freeing client * info is not the same as the registrant's client info, the free is still * allowed to go through. */ int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); unsigned long flags; if (chan == NULL) return -EINVAL; local_irq_save(flags); if (chan->client != client) { printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)/n", channel, chan->client, client); } /* sort out stopping and freeing the channel */ if (chan->state != S3C2410_DMA_IDLE) { // 该 channel 正在使用中 pr_debug("%s: need to stop dma channel %p/n", __FUNCTION__, chan); /* possibly flush the channel */ s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STOP); // 停止该 channel } //reset 该 channel 的相关信息 chan->client = NULL; chan->in_use = 0; if (chan->irq_claimed) free_irq(chan->irq, (void *)chan); // 释放该中断 chan->irq_claimed = 0; if (!(channel & DMACH_LOW_LEVEL)) dma_chan_map[channel] = NULL; local_irq_restore(flags); return 0; } EXPORT_SYMBOL(s3c2410_dma_free); 根据注释我们很清楚了 , 该函数主要就是释放一个 channel, 使其处于 ready 状态 , Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_request_dma * * get control of an dma channel */ int s3c2410_dma_request(unsigned int channel, struct s3c2410_dma_client *client, void *dev) { struct s3c2410_dma_chan *chan; unsigned long flags; int err; pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p/n", channel, client->name, dev); local_irq_save(flags); // 获取空闲的 channel chan = s3c2410_dma_map_channel(channel); if (chan == NULL) { // 无空闲 channel 则返回失败 local_irq_restore(flags); return -EBUSY; } dbg_showchan(chan); // 保存使用该 channel 的用户等信息 chan->client = client; chan->in_use = 1; if (!chan->irq_claimed) { // 该中断没注册 pr_debug("dma%d: %s : requesting irq %d/n", channel, __FUNCTION__, chan->irq); chan->irq_claimed = 1; // 标记注册 local_irq_restore(flags); err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED, client->name, (void *)chan); // 注册该中断 local_irq_save(flags); if (err) { // 失败则 reset 该 channel chan->in_use = 0; chan->irq_claimed = 0; local_irq_restore(flags); printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d/n", client->name, chan->irq, chan->number); return err; } chan->irq_enabled = 1; } local_irq_restore(flags); /* need to setup */ pr_debug("%s: channel initialised, %p/n", __FUNCTION__, chan); return 0; } EXPORT_SYMBOL(s3c2410_dma_request); 该函数主要就是为请求的用户找到一个空闲的 channel, 并把它分配给该用户 , 同时打开中断 , 保存相关信息 . Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_enqueue * * queue an given buffer for dma transfer. * * id the device driver's id information for this buffer * data the physical address of the buffer data * size the size of the buffer in bytes * * If the channel is not running, then the flag S3C2410_DMAF_AUTOSTART * is checked, and if set, the channel is started. If this flag isn't set, * then an error will be returned. * * It is possible to queue more than one DMA buffer onto a channel at * once, and the code will deal with the re-loading of the next buffer * when necessary. */ int s3c2410_dma_enqueue(unsigned int channel, void *id, dma_addr_t data, int size) { struct s3c2410_dma_chan *chan = lookup_dma_channel(channel); struct s3c2410_dma_buf *buf; unsigned long flags; if (chan == NULL) return -EINVAL; pr_debug("%s: id=%p, data=%08x, size=%d/n", __FUNCTION__, id, (unsigned int)data, size); // 从高速缓冲中分配一块 buffer 用于 DMA 传输 , dma_kmem 是我们在初始化的时候就创建好的 buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC); if (buf == NULL) { pr_debug("%s: out of memory (%ld alloc)/n", __FUNCTION__, (long)sizeof(*buf)); return -ENOMEM; } //pr_debug("%s: new buffer %p/n", __FUNCTION__, buf); //dbg_showchan(chan); // 初始化这块要被传输的 buf buf->next = NULL; buf->data = buf->ptr = data; // 指向要传输的 data buf->size = size; // 传输大小 buf->id = id; buf->magic = BUF_MAGIC; local_irq_save(flags); if (chan->curr == NULL) { // 当前 channel 没有在传输 /* we've got nothing loaded... */ pr_debug("%s: buffer %p queued onto empty channel/n", __FUNCTION__, buf); chan->curr = buf; // 直接挂在 curr 上 chan->end = buf; chan->next = NULL; } else { // 当前 channel 正在传输 pr_debug("dma%d: %s: buffer %p queued onto non-empty channel/n", chan->number, __FUNCTION__, buf); if (chan->end == NULL) pr_debug("dma%d: %s: %p not empty, and chan->end==NULL?/n", chan->number, __FUNCTION__, chan); // 把 buffer 挂到队列的最后面 , 并重设 end chan->end->next = buf; chan->end = buf; } /* if necessary, update the next buffer field */ if (chan->next == NULL) chan->next = buf; /* check to see if we can load a buffer */ if (chan->state == S3C2410_DMA_RUNNING) { // 该 channel 正在运行 if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) { // 已有 buf load 了 if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待 load printk(KERN_ERR "dma%d: loadbuffer:" "timeout loading buffer/n", chan->number); dbg_showchan(chan); local_irq_restore(flags); return -EINVAL; } } while (s3c2410_dma_canload(chan) && chan->next != NULL) { // 检查能否 load s3c2410_dma_loadbuffer(chan, chan->next); //load buffer } } else if (chan->state == S3C2410_DMA_IDLE) { // 该 channel 空闲着 if (chan->flags & S3C2410_DMAF_AUTOSTART) { // 如果设了自动启动标记 , 则直接启动该次传输 s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START); // 启动传输 } } local_irq_restore(flags); return 0; } EXPORT_SYMBOL(s3c2410_dma_enqueue); 该函数首先从先前创建的高速缓冲池中获取一个 buf, 并把要传输的 data 保存在该 buf 中 , 然后根据当前 channel 的运行状态来选择是 load 该 buf, 还是直接传输该 buf. Channel 在运行过程中会有很多的状态 , 所有状态如下 : Include/asm-arm/arch-s3c2410/dma.h: /* enum s3c2410_dma_loadst * * This represents the state of the DMA engine, wrt to the loaded / running * transfers. Since we don't have any way of knowing exactly the state of * the DMA transfers, we need to know the state to make decisions on wether * we can * * S3C2410_DMA_NONE * * There are no buffers loaded (the channel should be inactive) * * S3C2410_DMA_1LOADED * * There is one buffer loaded, however it has not been confirmed to be * loaded by the DMA engine. This may be because the channel is not * yet running, or the DMA driver decided that it was too costly to * sit and wait for it to happen. * * S3C2410_DMA_1RUNNING * * The buffer has been confirmed running, and not finisged * * S3C2410_DMA_1LOADED_1RUNNING * * There is a buffer waiting to be loaded by the DMA engine, and one * currently running. */ enum s3c2410_dma_loadst { S3C2410_DMALOAD_NONE, S3C2410_DMALOAD_1LOADED, S3C2410_DMALOAD_1RUNNING, S3C2410_DMALOAD_1LOADED_1RUNNING, }; 各种装态注释的很明显了 , 我就不再罗索了 . Channel 运行时会有一个正在传输的 buf, 一个已经加载的 buf, 还有很多等待加载的 buf. 我们来把这个函数中调用的函数也逐个分析下 : Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_waitforload * * wait for the DMA engine to load a buffer, and update the state accordingly */ static int s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line) { int timeout = chan->load_timeout; // 初始化时 load_timeout 被设成了 1 << 18 int took; // 该函数只在 S3C2410_DMALOAD_1LOADED 状态下被调用 if (chan->load_state != S3C2410_DMALOAD_1LOADED) { printk(KERN_ERR "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d/n", chan->number, chan->load_state, line); return 0; } if (chan->stats != NULL) chan->stats->loads++; // 更新统计信息 while (--timeout > 0) { // 获取还剩的传输量 , 左移 (32-20) 只是把 [21:20] 位移调 , 因为它仅和 0 比较 , 所以无需确切的数据 if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) { took = chan->load_timeout - timeout; // 等待了这么长时间 // 保存统计信息 , 该函数更新最长 , 最短超时时间 , 并更新总超时时间 s3c2410_dma_stats_timeout(chan->stats, took); switch (chan->load_state) { case S3C2410_DMALOAD_1LOADED: // 因为有数据在传输了 , 因此更新 channel 的状态 , 从这我们也能看到 , 一次只能有一个 //buf 被 load chan->load_state = S3C2410_DMALOAD_1RUNNING; break; default: printk(KERN_ERR "dma%d: unknown load_state in s3c2410_dma_waitforload() %d/n", chan->number, chan->load_state); } return 1; } } if (chan->stats != NULL) { chan->stats->timeout_failed++; } return 0; } 该函数很简单 , 它等待已经 load 的 buf 被 start 传输 , 然后更新相关统计信息 , 也正因为 load 的 buf 被开始传输了 , 因此该函数完后 , 应该会有一个新的 buf 被 load. 至于原先 load 的 buf 是如何被 start 的 , 我们以后在看 . 接下来我们看 s3c2410_dma_canload 函数 : Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_canload * * work out if we can queue another buffer into the DMA engine */ static int s3c2410_dma_canload(struct s3c2410_dma_chan *chan) { // 在这 2 个状态下是可以 load 的 if (chan->load_state == S3C2410_DMALOAD_NONE || chan->load_state == S3C2410_DMALOAD_1RUNNING) return 1; return 0; } 跑完 s3c2410_dma_waitforload 后如果正确 , 则状态应该是 S3C2410_DMALOAD_1RUNNING, 所以这里就是可以加载了 , 那当然要看加载函数了 Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_loadbuffer * * load a buffer, and update the channel state */ static inline int s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan, struct s3c2410_dma_buf *buf) { unsigned long reload; pr_debug("s3c2410_chan_loadbuffer: loading buff %p (0x%08lx,0x%06x)/n", buf, (unsigned long)buf->data, buf->size); if (buf == NULL) { dmawarn("buffer is NULL/n"); return -EINVAL; } /* check the state of the channel before we do anything */ // 状态错误 , 只能有 1 个 loaded 的 buf if (chan->load_state == S3C2410_DMALOAD_1LOADED) { dmawarn("load_state is S3C2410_DMALOAD_1LOADED/n"); } // 状态错误 , 只能有 1 个 loaded 的 buf if (chan->load_state == S3C2410_DMALOAD_1LOADED_1RUNNING) { dmawarn("state is S3C2410_DMALOAD_1LOADED_1RUNNING/n"); } /* it would seem sensible if we are the last buffer to not bother * with the auto-reload bit, so that the DMA engine will not try * and load another transfer after this one has finished... */ // 判断是否要自动加载后续的 buf, 如果有后续的 buf 则自动加载 if (chan->load_state == S3C2410_DMALOAD_NONE) { pr_debug("load_state is none, checking for noreload (next=%p)/n", buf->next); reload = (buf->next == NULL) ? S3C2410_DCON_NORELOAD : 0; } else { //pr_debug("load_state is %d => autoreload/n", chan->load_state); reload = S3C2410_DCON_AUTORELOAD; } if ((buf->data & 0xf0000000) != 0x30000000) { dmawarn("dmaload: buffer is %p/n", (void *)buf->data); } writel(buf->data, chan->addr_reg); // 写地址寄存器 // 不解释了 , 看寄存器说明吧 dma_wrreg(chan, S3C2410_DMA_DCON, chan->dcon | reload | (buf->size/chan->xfer_unit)); chan->next = buf->next; // 更新链表 /* update the state of the channel */ // 更新状态 switch (chan->load_state) { case S3C2410_DMALOAD_NONE: chan->load_state = S3C2410_DMALOAD_1LOADED; break; case S3C2410_DMALOAD_1RUNNING: chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING; break; default: dmawarn("dmaload: unknown state %d in loadbuffer/n", chan->load_state); break; } return 0; } 该函数主要是把要传输的数据的地址先存入寄存器中 , 等当前的传输完成后会根据时候 auto reload 的情况来确定是否开始这次的传输 . OK, 到目前为止讲完了所有的 export 的函数 , 现在还剩下 dma 的操作函数和中断函数没讲了 , let’s go! 我们先看中断函数 , 该函数在一次传输完成后被调用 Arch/arm/plat-s3c24xx/dma.c: static irqreturn_t s3c2410_dma_irq(int irq, void *devpw) { struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw; struct s3c2410_dma_buf *buf; buf = chan->curr; // 当前传输完毕的 buf dbg_showchan(chan); /* modify the channel state */ // 修改当前状态 switch (chan->load_state) { case S3C2410_DMALOAD_1RUNNING: /* TODO - if we are running only one buffer, we probably * want to reload here, and then worry about the buffer * callback */ chan->load_state = S3C2410_DMALOAD_NONE; break; case S3C2410_DMALOAD_1LOADED: /* iirc, we should go back to NONE loaded here, we * had a buffer, and it was never verified as being * loaded. */ chan->load_state = S3C2410_DMALOAD_NONE; break; case S3C2410_DMALOAD_1LOADED_1RUNNING: /* we'll worry about checking to see if another buffer is * ready after we've called back the owner. This should * ensure we do not wait around too long for the DMA * engine to start the next transfer */ chan->load_state = S3C2410_DMALOAD_1LOADED; break; case S3C2410_DMALOAD_NONE: printk(KERN_ERR "dma%d: IRQ with no loaded buffer?/n", chan->number); break; default: printk(KERN_ERR "dma%d: IRQ in invalid load_state %d/n", chan->number, chan->load_state); break; } if (buf != NULL) { /* update the chain to make sure that if we load any more * buffers when we call the callback function, things should * work properly */ chan->curr = buf->next; // 更新传输的 buf buf->next = NULL; if (buf->magic != BUF_MAGIC) { printk(KERN_ERR "dma%d: %s: buf %p incorrect magic/n", chan->number, __FUNCTION__, buf); return IRQ_HANDLED; } s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK); //buf 传输完成后的操作 /* free resouces */ s3c2410_dma_freebuf(buf); // 释放 buf, 我们看到传输前有申请 buf } else { } /* only reload if the channel is still running... our buffer done * routine may have altered the state by requesting the dma channel * to stop or shutdown... */ /* todo: check that when the channel is shut-down from inside this * function, we cope with unsetting reload, etc */ // 还有要传输的 buf, 则继续传输 if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) { unsigned long flags; switch (chan->load_state) { case S3C2410_DMALOAD_1RUNNING: /* don't need to do anything for this state */ break; case S3C2410_DMALOAD_NONE: /* can load buffer immediately */ break; case S3C2410_DMALOAD_1LOADED: if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待被传输 /* flag error? */ printk(KERN_ERR "dma%d: timeout waiting for load (%s)/n", chan->number, __FUNCTION__); return IRQ_HANDLED; } break; case S3C2410_DMALOAD_1LOADED_1RUNNING: goto no_load; default: printk(KERN_ERR "dma%d: unknown load_state in irq, %d/n", chan->number, chan->load_state); return IRQ_HANDLED; } local_irq_save(flags); s3c2410_dma_loadbuffer(chan, chan->next); // 加载下一个 buf local_irq_restore(flags); } else { // 所有的传输完成 s3c2410_dma_lastxfer(chan); // 完成处理工作 /* see if we can stop this channel.. */ if (chan->load_state == S3C2410_DMALOAD_NONE) { pr_debug("dma%d: end of transfer, stopping channel (%ld)/n", chan->number, jiffies); s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL, S3C2410_DMAOP_STOP); // 停止 dma 传输 } } no_load: return IRQ_HANDLED; } 我们看到当传输队列中还有 buf 要传输时 , 没有看到 start 的操作 , 这是为什么呢 ? 因为在 load 的时候我们分析过 , 如果后续还有 buf 要传输 , 则自动加载运行 , 所以这里没有必要手工 start. s3c2410_dma_buffdone() 函数仅仅是调用前面注册的回调函数 , 这里不列出来了 . s3c2410_dma_freebuf() 也很简单 , 就是把 buf 归还到缓冲池去 . 我们看下 s3c2410_dma_lastxfer Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_lastxfer * * called when the system is out of buffers, to ensure that the channel * is prepared for shutdown. */ static inline void s3c2410_dma_lastxfer(struct s3c2410_dma_chan *chan) { #if 0 pr_debug("dma%d: s3c2410_dma_lastxfer: load_state %d/n", chan->number, chan->load_state); #endif switch (chan->load_state) { case S3C2410_DMALOAD_NONE: break; case S3C2410_DMALOAD_1LOADED: if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待加载的 buf 被执行 /* flag error? */ printk(KERN_ERR "dma%d: timeout waiting for load (%s)/n", chan->number, __FUNCTION__); return; } break; case S3C2410_DMALOAD_1LOADED_1RUNNING: /* I belive in this case we do not have anything to do * until the next buffer comes along, and we turn off the * reload */ return; default: pr_debug("dma%d: lastxfer: unhandled load_state %d with no next/n", chan->number, chan->load_state); return; } /* hopefully this'll shut the damned thing up after the transfer... */ // 清楚自动加载标记 , 因为无后续要传输的 buf, 所以要清这个标记 dma_wrreg(chan, S3C2410_DMA_DCON, chan->dcon | S3C2410_DCON_NORELOAD); } 很简单的一个函数 , 这里不多说了 . 好了 , 到这个该着中分析操作函数了 . Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_start * * start a dma channel going */ static int s3c2410_dma_start(struct s3c2410_dma_chan *chan) { unsigned long tmp; unsigned long flags; pr_debug("s3c2410_start_dma: channel=%d/n", chan->number); local_irq_save(flags); if (chan->state == S3C2410_DMA_RUNNING) { // 已经有 run 的了 pr_debug("s3c2410_start_dma: already running (%d)/n", chan->state); local_irq_restore(flags); return 0; } chan->state = S3C2410_DMA_RUNNING; // 更新状态 /* check wether there is anything to load, and if not, see * if we can find anything to load */ if (chan->load_state == S3C2410_DMALOAD_NONE) { // 没有加载过 buf if (chan->next == NULL) { // 没有 buf 要传送的 printk(KERN_ERR "dma%d: channel has nothing loaded/n", chan->number); chan->state = S3C2410_DMA_IDLE; local_irq_restore(flags); return -EINVAL; } s3c2410_dma_loadbuffer(chan, chan->next); // 加载 buf, 加载状态也会相应更新 } dbg_showchan(chan); /* enable the channel */ if (!chan->irq_enabled) { enable_irq(chan->irq); // 使能中断 chan->irq_enabled = 1; } /* start the channel going */ // 启动 DMA 传输 , tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG); tmp &= ~S3C2410_DMASKTRIG_STOP; tmp |= S3C2410_DMASKTRIG_ON; dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp); pr_debug("dma%d: %08lx to DMASKTRIG/n", chan->number, tmp); #if 0 /* the dma buffer loads should take care of clearing the AUTO * reloading feature */ tmp = dma_rdreg(chan, S3C2410_DMA_DCON); tmp &= ~S3C2410_DCON_NORELOAD; dma_wrreg(chan, S3C2410_DMA_DCON, tmp); #endif s3c2410_dma_call_op(chan, S3C2410_DMAOP_START); // 调用注册的 op 回调函数 dbg_showchan(chan); /* if we've only loaded one buffer onto the channel, then chec * to see if we have another, and if so, try and load it so when * the first buffer is finished, the new one will be loaded onto * the channel */ // 由于当前 load 的已经在运行了 , 因此如果还有要传输的 buf 则 load 进来 if (chan->next != NULL) { if (chan->load_state == S3C2410_DMALOAD_1LOADED) { // 等待该 buf 被运行 , 别忘了我们设了自动加载运行 . if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { pr_debug("%s: buff not yet loaded, no more todo/n", __FUNCTION__); } else { chan->load_state = S3C2410_DMALOAD_1RUNNING; s3c2410_dma_loadbuffer(chan, chan->next); // 加载 buf } } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) { s3c2410_dma_loadbuffer(chan, chan->next); // 加载 buf } } local_irq_restore(flags); return 0; } 整个启动流程就这样完了 . Arch/arm/plat-s3c24xx/dma.c: static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan) { unsigned long flags; unsigned long tmp; pr_debug("%s:/n", __FUNCTION__); dbg_showchan(chan); local_irq_save(flags); s3c2410_dma_call_op(chan, S3C2410_DMAOP_STOP); // 通知用户该操作 // 停止 DMA 传输 tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG); tmp |= S3C2410_DMASKTRIG_STOP; //tmp &= ~S3C2410_DMASKTRIG_ON; dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp); #if 0 /* should also clear interrupts, according to WinCE BSP */ tmp = dma_rdreg(chan, S3C2410_DMA_DCON); tmp |= S3C2410_DCON_NORELOAD; dma_wrreg(chan, S3C2410_DMA_DCON, tmp); #endif // 更新状态 /* should stop do this, or should we wait for flush? */ chan->state = S3C2410_DMA_IDLE; chan->load_state = S3C2410_DMALOAD_NONE; local_irq_restore(flags); return 0; } 该函数比较简单 , 接着看 Arch/arm/plat-s3c24xx/dma.c: /* s3c2410_dma_flush * * stop the channel, and remove all current and pending transfers */ static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan) { struct s3c2410_dma_buf *buf, *next; unsigned long flags; pr_debug("%s: chan %p (%d)/n", __FUNCTION__, chan, chan->number); dbg_showchan(chan); local_irq_save(flags); if (chan->state != S3C2410_DMA_IDLE) { pr_debug("%s: stopping channel.../n", __FUNCTION__ ); s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP); // 停调传输 } buf = chan->curr; if (buf == NULL) buf = chan->next; chan->curr = chan->next = chan->end = NULL; if (buf != NULL) { for ( ; buf != NULL; buf = next) { next = buf->next; pr_debug("%s: free buffer %p, next %p/n", __FUNCTION__, buf, buf->next); s3c2410_dma_buffdone(chan, buf, S3C2410_RES_ABORT); // 通知用户中断了传输 s3c2410_dma_freebuf(buf); // 释放 buf } } dbg_showregs(chan); s3c2410_dma_waitforstop(chan); #if 0 /* should also clear interrupts, according to WinCE BSP */ { unsigned long tmp; tmp = dma_rdreg(chan, S3C2410_DMA_DCON); tmp |= S3C2410_DCON_NORELOAD; dma_wrreg(chan, S3C2410_DMA_DCON, tmp); } #endif dbg_showregs(chan); local_irq_restore(flags); return 0; } 该函数的作用就是中断所有的传输 , 并把所有队列中等待传输的 buf 都清掉 . Arch/arm/plat-s3c24xx/dma.c: static int s3c2410_dma_started(struct s3c2410_dma_chan *chan) { unsigned long flags; local_irq_save(flags); dbg_showchan(chan); /* if we've only loaded one buffer onto the channel, then chec * to see if we have another, and if so, try and load it so when * the first buffer is finished, the new one will be loaded onto * the channel */ // 看注释吧 , 不解释了 if (chan->next != NULL) { if (chan->load_state == S3C2410_DMALOAD_1LOADED) { if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { pr_debug("%s: buff not yet loaded, no more todo/n", __FUNCTION__); } else { chan->load_state = S3C2410_DMALOAD_1RUNNING; s3c2410_dma_loadbuffer(chan, chan->next); } } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) { s3c2410_dma_loadbuffer(chan, chan->next); } } local_irq_restore(flags); return 0; } 最后的这个函数就由大家自己分析吧 . 从 s3c2410_dma_config 函数可以看出 , 该驱动只支持硬件请求模式 , 而从 s3c2410_dma_devconfig 函数可以看出 , 该驱动只支持设备和 memory 之间的 DMA 传输 . 至于如何使用的问题 , 可以去代码里搜一下哪些地方调用了 export 出来的函数就懂了 , 2410 的板子上 PCM 会用到 DMA 传输 , 使用流程为 : s3c2410_dma_request -> s3c2410_dma_devconfig -> s3c2410_dma_config -> s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START); 当然一般还会注册回调函数的 . 到此为止整个 DMA 的操作流程都分析完了 , 希望对你有所帮助 , 以后会写些 cache, mmu, write buffer 等方面的驱动分析 .
相关文章推荐
- 2410下DMA驱动源码分析
- 2410下DMA驱动源码分析
- 2410下DMA驱动源码分析
- s3c 2410 DMA驱动源码分析1
- s3c2410 DMA驱动源码分析2
- Linux驱动修炼之道-DMA框架源码分析
- Linux驱动修炼之道-DMA框架源码分析
- Linux驱动修炼之道-SPI驱动框架源码分析(上)
- Linux USB 驱动开发实例(一) —— USB摄像头驱动实现源码分析
- OpenRisc-31-关于在设计具有DMA功能的ipcore时的虚实地址转换问题的分析与解决
- USB驱动及其源码分析
- RTC驱动源码分析
- Linux USB 驱动开发实例(一) —— USB摄像头驱动实现源码分析
- 我的内核学习笔记10:Intel GPIO驱动源码分析
- Nginx源码分析-事件驱动的初始化
- tiny4412 串口驱动分析五 --- LDD3上TTY驱动程序源码
- Redis ae事件驱动源码分析
- Android摄像头相关源码分析: 设备驱动, HAL, Framework
- NandFlash驱动源码详细分析
- linux设备驱动之misc驱动框架源码分析(二)