1. 程式人生 > >S3c6410linux下DMA驅動

S3c6410linux下DMA驅動

                 

DMA

謹以此文紀念過往的歲月。

DMA傳輸支援4種格式,記憶體到記憶體,裝置到記憶體,記憶體到裝置,裝置到裝置。
對於記憶體到記憶體比較好理解,就是不通過CPU的複製,直接使用進行資料傳輸。

1.dma的初始化
在cpu.c檔案中會對CPU的一些最最基本的資源初始化,如時鐘,中斷等等,在該檔案中會註冊一個s3c6410_sysclass類,
struct sysdev_class s3c6410_sysclass = {
 .name = "s3c6410-core",
};
對於搞驅動的人來說這個很不陌生,在建立一個裝置前一般會去註冊一個類的。
static int __init s3c6410_core_init(void)
{
 return sysdev_class_register(&s3c6410_sysclass);
}
這個會在核心啟動時初始化的,因為他被這樣聲明瞭core_initcall(s3c6410_core_init);在核心中像arch_initcall和core_initcall以及module_init,這些都會在
核心啟動的時候初始化。會有先後順序的,core_initcall在核心啟動後就會初始化,之後是arch_initcall再之後才是module_init。
在註冊該類後,dma的驅動以及dma的裝置都可以掛在該類下了。
在arch/arm/mach-s3c6410/dma.c中會將dma_driver掛在s3c6410_sysclass下
static int __init s3c6410_dma_init(void)
{
 return sysdev_driver_register(&s3c6410_sysclass, &s3c6410_dma_driver);
}
arch_initcall(s3c6410_dma_init);
對於s3c6410_dma_driver中,其實最主要的是其中兩個函式s3c_dma_init,和s3c_dma_init_map。
static struct sysdev_driver s3c6410_dma_driver = {
 .add = s3c6410_dma_add,
};
static int __init s3c6410_dma_add(struct sys_device *sysdev)
{
 s3c_dma_init(S3C_DMA_CHANNELS, IRQ_DMA0, 0x20); --S3C_DMA_CHANNELS = 4*8 應為pl080有四個dma控制器,每一個有8個通道。
 return s3c_dma_init_map(&s3c6410_dma_sel);
}

s3c_dma_init顧名思義是DMA的初始化,s3c_dma_init_map則是將dma通道與硬體匹配。
s3c_dma_init的原始碼在dma-pl080.c中,其具體的可以去參考原始碼,在其中主要是對dma的控制器以及每個通道的暫存器等等進行初始化。
其中有一個需要注意的是,為DMA開闢一個cache。dma_kmem是一個全域性變數。
dma_kmem = kmem_cache_create("dma_desc", sizeof(struct s3c_dma_buf), 0,SLAB_HWCACHE_ALIGN, (void *)s3c_dma_cache_ctor);
s3c_dma_init_map的原始碼也在dma-pl080.c中,其具體的可以去參考原始碼.


2.就從記憶體到記憶體開始,以從記憶體開闢開始。
在linux的記憶體開闢中有kmalloc,vmalloc,dma_alloc_coherent等辦法。kmalloc開闢連續的邏輯地址,vmalloc不連續,dma_alloc_coherent則開闢記憶體連續並且
一致的記憶體。
dma_alloc_coherent 原型:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
dma_alloc_coherent 應用如下:
dmabuf = (unsigned char *)dma_alloc_coherent(NULL,BUFF_SIZE,(dma_addr_t *)&dmaphys,GFP_KERNEL);
dmabuf返回的是核心虛擬地址,BUFF_SIZE為分配的地址空間大小,dmaphys為匯流排地址,在ARM框架下可以理解為實體地址。
在分配記憶體空間後,即是申請DMA通道。s3c2410_dma_request的原始碼在dma-pl080.c中,在此我們不去詳細的分析每一個程式碼,主要是分析程式碼的架構。以下有很多函式都是
在dma-pl080.c中,以後不會再說明函式的出處,除非特殊情況。
2.1 s3c2410_dma_request
int s3c2410_dma_request(unsigned int channel,struct s3c2410_dma_client *client,void *dev) 第二個引數是dma的名稱。區別於其他的。
在s3c2410_dma_request中有兩個比較重要的函式s3c_dma_map_channel 和s3c_enable_dmac。
s3c_dma_map_channel將虛擬的通道與真正的並且沒有使用的通道相匹配。
原型為struct s3c2410_dma_chan *s3c_dma_map_channel(int channel),該函式會返回真正dma通道的引數。
在上面初始化的時候,沒有將s3c_dma_init_map這個函式描述,在這兒描述一下。
s3c_dma_init_map在檢視原始碼的時候會發現,他僅僅是一個賦值函式,就是將s3c_dma_init_map傳下來的引數賦值給該檔案中的全域性變數dma_sel。其實這個函式作用就是
一個傳話筒的功能。只是將s3c6410_dma_sel這個引數的指標賦值給dma_sel.其實真正的資訊仍然儲存在s3c6410_dma_sel中。
簡要的將下面的函式講述一下,下面的原始碼做個刪減,一部分的檢測程式碼刪去。
struct s3c2410_dma_chan *s3c_dma_map_channel(int channel)
{
 struct s3c_dma_map *ch_map;
 struct s3c2410_dma_chan *dmach;
 int ch;
 
 ch_map = dma_sel.map + channel;    dma_sel.map就是s3c6410_dma_sel.map,也就是s3c6410_dma_mappings[]陣列的首指標。在這裡是為了查詢虛擬通道所對應的
                                    真正的通道。其中主要的是尋找一個沒有使用的通道。
 for (ch = 0; ch < dma_channels; ch++) {
  if (!is_channel_valid(ch_map->channels[ch]))   在這個是根據s3c6410_dma_sel中的ch_map的中channel是否可用來判定的。因為有些是被固定設定在某一個控制器的,
                                                 甚至某些功能被設定在固定的通道上。這個需要去仔細閱讀原始碼中s3c6410_dma_sel這個結構體陣列。
   continue;
  if (s3c_dma_chans[ch].in_use == 0) {           不僅是該dma的通道可以支援該功能,還要該通道未被使用
   pr_debug("mapped channel %d to %d\n", channel, ch);
   break;
  }
 }
 found:
 dmach = &s3c_dma_chans[ch];       這個是取出真正對用的dma通道的資訊的首指標返回。s3c_dma_chans是個全域性變數,在s3c_dma_init中被初始化了。
 dma_chan_map[channel] = dmach;    同時在匹配陣列中記錄。
 (dma_sel.select)(dmach, ch_map);  這個函式比較簡單直接就是dmach->map = ch_map.同樣是記錄資訊的。

 return dmach;
}
在linux中,往往真正的資訊只會被一個數據結構所記錄。而其他需要使用該資訊的均會使用指標的辦法來實現該資料結構的資訊儲存和呼叫。這也是linux核心比較難理解的地方。
在上述函式中真正儲存資訊的有s3c_dma_chans和s3c6410_dma_sel,而其他結構均使用指標來指向這兩個結構體。這樣的方法很常見。
OK,在上述函式中查詢到真正的dma通道。進行了虛擬通道與真實通道的匹配。
在 s3c2410_dma_request中還有一個比較重要的函式,s3c_enable_dmac,這個函式會在該控制器的中斷沒有被宣告使用的情況下用的,因為8個通道公用一箇中斷
如果該控制器第一次被使用的話,需要申請中斷和開啟控制器。

2.2 int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)
設定中斷完成後的回撥函式。這個很簡單。不敘述,會在s3c_dma_irq中講述。該回調函式是如何被呼叫的。

2.3 int s3c2410_dma_devconfig(int channel,enum s3c2410_dmasrc source,int hwcfg,unsigned long devaddr)
該函式配置DMA的源/目的硬體型別和地址。
引數 devaddr為源地址。source為dma傳輸的型別。在該函式的實現中根據不同的傳輸型別配置不同的暫存器。

2.4 int s3c2410_dma_config(dmach_t channel,int xferunit,int dcon)
xferunit 傳輸之間的位元組大小 dcon DCONx 暫存器的基值

2.5 int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)

2.6 int s3c2410_dma_enqueue(unsigned int channel, void *id,dma_addr_t data, int size)
這個函式非常重要,dma的操作幾乎都在這裡面完成。
在DMA中dma執行狀態有三態
enum s3c_dma_state {
 S3C_DMA_IDLE,       --DMA空閒
 S3C_DMA_RUNNING,    --DMA執行
 S3C_DMA_PAUSED      --DMA暫停
};
而buffer的載入則有五態
enum s3c_dma_loadst {
 S3C_DMALOAD_NONE,                 --沒有buffer載入,此時通道應無效
 S3C_DMALOAD_1LOADED,              --有一個buffer被載入,但是沒有被DMA確認載入,一是有可能通道沒有執行,二可能是dma認為此時載入太浪費,等一會兒
 S3C_DMALOAD_1RUNNING,             --buffer被確認運行了,但是還沒有結束。
 S3C_DMALOAD_1LOADED_1RUNNING,     --當前有一個緩衝區等待被dma載入,一個在執行
};
在dma執行過程中,理解這幾個狀態很重要。
struct s3c2410_dma_chan
{   ...
 /* buffer list and information */
 struct s3c_dma_buf *curr;  /* current dma buffer */   
 struct s3c_dma_buf *next;  /* next buffer to load */
 struct s3c_dma_buf *end;  /* end of queue */
  ...
}
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 s3c_dma_buf *buf;
 unsigned long flags;
 
 buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC); --在s3c_dma_init中dma_kmem = kmalloc_cache_create建立一個新的快取記憶體物件。在這裡呼叫kmem_cache_aloc
                                                 從中分配記憶體物件。關於這個以後在理解記憶體時候,好好的理解。
 if (buf == NULL) {
  return -ENOMEM;
 }
 buf->next = NULL;
 buf->data = buf->ptr = data;
 buf->size = size;
 buf->id = id;
 buf->magic = BUF_MAGIC;

 local_irq_save(flags);

 if (chan->curr == NULL) {   --如果當前的緩衝區為NULL
  chan->curr = buf;         當前DMA緩衝區設定為buf
  chan->end = buf;          同時將佇列未同樣設為buf
  chan->next = NULL;       
 }
 else {                     如果當前緩衝區不為NULL   
  if (chan->end == NULL)   /* In case of flushing */
     {}
  else {
   chan->end->next = buf; 將上一個buffer的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 == S3C_DMA_RUNNING) {          --如果當前DMA狀態為RUNNING,則等待載入
  if (chan->load_state == S3C_DMALOAD_1LOADED && 1) { --如果有一個buffer以被載入,但是沒有被確認。等待載入。
   if (s3c_dma_waitforload(chan, __LINE__) == 0) {
    local_irq_restore(flags);                 --如果載入超時返回錯誤值
    return -EINVAL;
   }
  }
 }
 else if (chan->state == S3C_DMA_IDLE) {      --如果通道空閒,並且被設定為自動啟動傳輸,則直接開始傳輸
  if (chan->flags & S3C2410_DMAF_AUTOSTART) {
   s3c2410_dma_ctrl(channel, S3C2410_DMAOP_START);  
 }
 else {
   pr_debug("loading onto stopped channel\n");  --否則錯誤,在該dma中並沒有實現暫停的功能
  }
 }
 local_irq_restore(flags);
 return 0;
}
如果將這幾個函式分開理解比較困難,如果將中斷處理函式加入一起比較好理解。
static int s3c_dma_waitforload(struct s3c2410_dma_chan *chan, int line)
{
 int timeout = chan->load_timeout;
 int took;
 if (chan->load_state != S3C_DMALOAD_1LOADED) {  --為什麼還會檢測load_state?因為中斷隨時都會發生,導致load_state的改變。
  return 0;
 }

 if (chan->stats != NULL)
  chan->stats->loads++;         --跟新channel的載入次數

 while (--timeout > 0) {
  if ((dma_rdreg(chan->dma_con, S3C_DMAC_ENBLD_CHANNELS)) & (0x1 << chan->number)) {   --檢測DMACEnbldChns 暫存器,如果對應的通道被啟動,則說明
                                                                                         前一個buffer被載入,並啟動dma          
    took = chan->load_timeout - timeout;                         --跟新等待時間。
   s3c_dma_stats_timeout(chan->stats, took);
   switch (chan->load_state) {
   case S3C_DMALOAD_1LOADED:                                   --同時更改load_state
    chan->load_state = S3C_DMALOAD_1RUNNING;                  --同時將狀態該為執行態
    break;
   default:
    dbg();
   }
   return 1;                                     --如果在限定時間內載入則返回1
  }
 }
 if (chan->stats != NULL) {
  chan->stats->timeout_failed++;                  --超時,則更新通道狀態。
 }
 return 0;                                         --超時返回0
}
下面來看一下中斷處理函式分析。
static irqreturn_t s3c_dma_irq(int irq, void *devpw)
{
 unsigned int channel = 0, dcon_num, i;
 unsigned long tmp;
 s3c_dma_controller_t *dma_controller = (s3c_dma_controller_t *) devpw;
 
 struct s3c2410_dma_chan *chan=NULL;
 struct s3c_dma_buf *buf;

 dcon_num = dma_controller->number;
 tmp = dma_rdreg(dma_controller, S3C_DMAC_INT_TCSTATUS);  --讀取當前控制器的中斷狀態,每一個控制器都有8個通道,每一個通道對應中斷狀態暫存器的每一位   
 if(tmp==0) {
  return IRQ_HANDLED;
 }
 for (i = 0; i < S3C_CHANNELS_PER_DMA; i++) {
  if (tmp & 0x01) {
   channel = i;
   chan = &s3c_dma_chans[channel + dcon_num * S3C_CHANNELS_PER_DMA];  --獲取產生中斷的通道資訊。
   buf = chan->curr;                                        --獲取當前buffer的資訊
   /* modify the channel state */
   switch (chan->load_state) {             load_state狀態的切換
    case S3C_DMALOAD_1RUNNING:               將執行態切換成,沒有載入沒有執行
     chan->load_state = S3C_DMALOAD_NONE;           
     break;
    case S3C_DMALOAD_1LOADED:                將執行態切換成,沒有載入沒有執行,拋棄更改狀態
     chan->load_state = S3C_DMALOAD_NONE;
     break;
    case S3C_DMALOAD_1LOADED_1RUNNING:       一個載入一個執行,改為一個載入
     chan->load_state = S3C_DMALOAD_1LOADED;
     break;
    case S3C_DMALOAD_NONE:
     break;
    default:
     break;
   }
   if (buf != NULL) {    --進行二次切換將下一個緩衝區前置
    chan->curr = buf->next;
    buf->next = NULL;

    if (buf->magic != BUF_MAGIC) {
     goto next_channel;
    }
    s3c_dma_buffdone(chan, buf, S3C2410_RES_OK);  --回撥函式,會回撥s3c2410_dma_set_buffdone_fn中設定的函式
    s3c_dma_freebuf(buf);
   }
   else {   
   }
   if (chan->next != NULL) { 
    unsigned long flags;
    switch (chan->load_state) {
    case S3C_DMALOAD_1RUNNING:
     break;
    case S3C_DMALOAD_NONE:
     break;
    case S3C_DMALOAD_1LOADED:
     if (s3c_dma_waitforload(chan, __LINE__) == 0) {
      goto next_channel;
     }
     break;
    case S3C_DMALOAD_1LOADED_1RUNNING:
     goto next_channel;

    default:
     goto next_channel;
    }

    local_irq_save(flags);
    s3c_dma_loadbuffer(chan, chan->next);  
    local_irq_restore(flags);
    
   } else {
    s3c_dma_lastxfer(chan);    
    if (chan->load_state == S3C_DMALOAD_NONE) {
     s3c2410_dma_ctrl(chan->index | DMACH_LOW_LEVEL, S3C2410_DMAOP_STOP);
    }
   }
  }
next_channel:
  tmp >>= 1;

 }
 s3c_clear_interrupts(chan->dma_con->number, chan->number); 
 return IRQ_HANDLED;
}
其中主要是dma啟動
static int s3c_dma_start(struct s3c2410_dma_chan *chan)
{
 unsigned long flags;
 local_irq_save(flags);

 if (chan->state == S3C_DMA_RUNNING) {
  local_irq_restore(flags);
  return 0;
 }

 chan->state = S3C_DMA_RUNNING;
 if (chan->load_state == S3C_DMALOAD_NONE) {
  if (chan->next == NULL) {
   chan->state = S3C_DMA_IDLE;
   local_irq_restore(flags);
   return -EINVAL;
  }
  s3c_dma_loadbuffer(chan, chan->next);
 }
 if (!chan->irq_enabled) {
  enable_irq(chan->irq);
  chan->irq_enabled = 1;
 }
 dma_wrreg(chan, S3C_DMAC_CxCONFIGURATION, chan->config_flags);
 s3c_dma_call_op(chan, S3C2410_DMAOP_START);
 local_irq_restore(flags);
 return 0;
}

static inline int s3c_dma_loadbuffer(struct s3c2410_dma_chan *chan,struct s3c_dma_buf *buf)
{
 writel(buf->data, chan->addr_reg);

 dma_wrreg(chan, S3C_DMAC_CxCONTROL0, chan->dcon);
 dma_wrreg(chan, S3C_DMAC_CxCONTROL1, (buf->size / chan->xfer_unit));
 
 chan->next = buf->next;
 switch (chan->load_state) {
 case S3C_DMALOAD_NONE:
  chan->load_state = S3C_DMALOAD_1LOADED;
  break;

 case S3C_DMALOAD_1RUNNING:
  chan->load_state = S3C_DMALOAD_1LOADED_1RUNNING;
  break;
 default:
  break;
 }
 return 0;
}