首先我们由 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:
{
return sysdev_class_register( & s3c2410_sysclass); // 注册一个 class 类
}
core_initcall(s3c2410_core_init);
我们以后会看到 , 后面的 DMA 设备及 DMA 驱动都会注册到该类下面 .
arch\arm\mach-s3c2410\s3c2410.c:
set_kset_name( " s3c2410-core " ),
};
很明显 , 实际上该类并没有其他什么操作 , 只是为了让 DMA 设备和驱动都注册到这个类下面 , 以使对方可以互相找的到 .
接着在 arch\arm\plat-s3c24xx\Dma.c 下也注册了一个类
arch\arm\plat-s3c24xx\Dma.c:
{
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:
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:
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:
{
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 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:
{
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:
{
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:
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);
iounmap(dma_base);
dma_base = NULL;
return ret;
}
这个函数就是对每个 channel 进行初始化 , 并把每个 channel 的相关信息保存起来供以后的操作使用 .
接着看下一个函数 :
Arch\arm\plat-s3c24xx\dma.c:
{
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:
.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::
*
* 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:
{
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:
.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:
[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:
*
* 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:
{
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:
*
* 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 的函数
*
* 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:
{
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:
* 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:
{
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:
#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:
*
* 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:
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:
*
* 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:
*
* 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:
*
* 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:
*
* 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:
*
* 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:
*
* 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:
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:
*
* 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:
*
* 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:
{
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:
*
* 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:
{
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 的操作流程都分析完了 , 希望对你有所帮助 ,
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/leibniz_zsu/archive/2009/12/10/4979164.aspx