首先我們由 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);
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 等方面的驅動分析 .