31、 2410下DMA驅動源碼分析

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

 


 

發佈了81 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章