mmc驅動的讀寫過程解析

mmc io的讀寫從mmc_queue_thread()的獲取queue裏面的request開始。框架

 

先列出調用棧,看下大概的調用順序, 下面的內容主要闡述這些函數如何工做。異步

host->ops->request() // sdhci_request()async

mmc_start_request()函數

mmc_start_req()post

mmc_blk_issue_rw_rq()fetch

mmc_blk_issue_rq()優化

Mmc_queue_thread()線程

 

mmc_queue_thread()  struct request *req = NULL; 用來提取req設計

req = blk_fetch_request(q); 從塊設備隊列提取存儲的req。存儲到此次處理mqrq_cur裏面mq->mqrq_cur->req = req; blk_fetch_request()能夠屢次調用,若是queue裏面沒有內容,req將返回NULL。指針

接下來調用mq->issue_fn()對req進行處理

處理完畢後把mq->mqrq_prev = mq->mqrq_cur, 而後清空mq->mqrq_cur。

假若req || mq->mqrq_prev->req 此次獲取的req和上次的req都爲NULL的話,線程進入睡眠狀態kthread_should_stop() –> schedule();

 

mmc_blk_issue_rq()

  1. if (req && !mq->mqrq_prev->req) 若是是第一次命令mmc_claim_host(card->host); 須要佔住host,激活時鐘。
  2. ret = mmc_blk_part_switch(card, md); 選擇對應的分區。
  3. 根據req->cmd_flags的命令作不一樣的事情。REQ_SANITIZE、REQ_DISCARD、REQ_FLUSH
  4. 關注結構體host->context_info

 

mmc_blk_issue_rw_rq() 開始讀寫

  1. Req參數變換名稱struct request *rqc
  2. 若是req有值,則進入一個關鍵的函數mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);作一些準備工做。而後areq = &mq->mqrq_cur->mmc_active; 取到異步request結構體 areq (struct mmc_async_req)。
  3. 正式啓動areq = mmc_start_req(card->host, areq, (int *) &status);
  4. 命令完成以後,對命令的完成狀態作各類判斷,是否正確完成,是否出錯,是否須要retry。
  5. 有幾個部分用於獲取執行狀態。mq_rq從areq反向抽取獲得, brq = &mq_rq->brq;  req  = mq_rq->req; status變量。 經過switch case來判斷status返回的是什麼狀態,決定接下來如何作。能夠mmc_blk_reinsert_req()從新把req放回queue裏面。能夠  blk_end_request (req, 0, brq->data.bytes_xfered); 完成本次傳輸,說明數據已正確讀寫。該函數本質是req->end_io(req, error); 有上層request queue的時候註冊的回調。通常多是作unlock buffer或page的動做。 若是是MMC_BLK_CMD_ERR,則mmc_blk_reset()把控制器都reset一遍,要作從新上下電的動做。若是是retry MMC_BLK_RETRY,則循環體重試五次。若是是MMC_BLK_DATA_ERR 也要reset控制器。若是是MMC_BLK_ECC_ERR, 而且發現是多塊讀,則切換到單塊讀,若是仍是失敗,沒辦法blk_end_request(req, -EIO, 給上層直接EIO的錯誤。若是是MMC_BLK_NOMEDIUM沒設備了,直接退出。
  6. 剩餘的都是爲了命令出錯處理,或者重試,start_new_req()。

 

mmc_blk_rw_rq_prep()

  1. 光從函數名就能夠看出這是一個prepare的函數。注意這裏面的幾個結構體struct mmc_blk_request  struct mmc_request。 brq = &mqrq->brq; brq->mrq.cmd = &brq->cmd; brq->mrq.data = &brq->data; 未來這個mrq將是承載命令發送的結構。
  2. 整個函數的宗旨就是填充各類結構體,用正確的值,譬如cmd號,是讀仍是寫,是單塊仍是多塊。MMC_READ_MULTIPLE_BLOCK MMC_READ_SINGLE_BLOCK MMC_WRITE_BLOCK MMC_WRITE_MULTIPLE_BLOCK。也包含一些特殊狀況須要作的事情,譬如特殊命令。
  3. 另外一個該函數重要的工做,是mmc_queue_map_sg。要把request裏面包含的數據buffer指針,給map到data.sg結構裏面。struct scatterlist       *sg; 結構是分散聚攏DMA的描述,從這點能夠看出mmc host的處理是經過dma來完成,sgdma的好處是,它能夠處理非連續的多個命令,而不須要cpu干擾。Cpu只須要填充好命令,剩下的事情交個dma處理便可。簡單說就是,普通dma能夠處理單個命令,sgdma能夠在一次dma裏面處理一組命令。
  4. mqrq->mmc_active.mrq = &brq->mrq; mqrq->mmc_active.cmd_flags = req->cmd_flags; 以後完成退出。

 

Mmc_start_req()

  1. 該函數的寫法有點饒,首先是看得出來,mmc_start_req()的目的實際上是要處理本次areq。 因此一開始對areq作mmc_pre_req(host, areq->mrq, !host->areq); 預準備。
  2. 但接下來是一個if (host->areq) {    err = mmc_wait_for_data_req_done (host,  host->areq->mrq, 一個很明顯的等待操做,從函數名就能夠得知這是一個同步等待,會放棄處理器等待命令完成。但從上文能夠看到,一直還未正在處理命令,也沒往host發送命令,此時就開始等待命令完成顯然是毫無道理。但有時候代碼容易看漏,該等待是針對的host->areq,並非參數傳遞的areq。本次等待的是上一次傳遞未完成的動做。若是上次的傳輸以及完成,則該等待函數會很快返回。並把成功仍是錯誤的狀況反饋給外面的mmc_blk_issue_rw_rq()
  3. 接下來的if (!err && areq) { 纔是真正本次的處理若是不是urgent事件的話,start_err = __mmc_start_data_req(host, areq->mrq); 開始。
  4. 完成以後對應的作一個mmc_post_req(host, areq->mrq, -EINVAL); 和以前的mmc_pre_req(host, areq->mrq, !host->areq); 對應起來。試想一下,若是想在命令前或命令後作自定義的事情,則能夠考慮在這裏添加。
  5. 若是這其中沒發生錯誤,host->areq = areq; 就保存起來了,即current操做變成pre操做。而且這裏面不須要在作等待,由於等待的操做將在下次函數在進來時的第2步進行。能夠看出設計者爲了最大化數據吞吐量,把函數設計成最大限度的流水線處理,壓縮全部可能的耗時操做。試想若是不這麼作,則每次操做都須要完成準備,等待,準備,等待的循環。函數設計成這樣,則把同步等待的時間利用起來,作另外一次傳輸的準備,減小無謂的帶寬損失。
  6. 把參數state賦值爲函數返回值err,返回上一次的傳輸結果,host->areq ,爲何?由於本次的傳輸確定還未完成,須要等待硬件處理,但上次的host->areq已經完成,能夠處理後續事情。這就是爲何mmc_blk_issue_rw_rq()在發起命令後返回須要mq_rq = container_of(areq, struct mmc_queue_req, mmc_active);用這樣的方式得到 mmc_queue_req。

 

mmc_start_request ()

  1. 第一件事情,咱們觀察傳遞的參數,是areq->mrq。能夠知道mmc最終命令的承載都是用struct mmc_request *mrq 這樣的結構完成。
  2. 在調用mmc_start_request()前,mrq->done=mmc_wait_data_done就肯定了,是request完成以後的回調函數。
  3. 函數開始就不斷的對mrq->cmd和mrq->data結構作判斷,mmc_start_request實際上是個通用函數,咱們知道mmc命令有些是單命令,有些是命令數據合併型,對於有數據傳輸要求的命令,要對mmc->data結構錯誤判斷。
  4. 如無心外的話,mmc_start_request要交給各個host完成處理了。Mmc驅動是一個通用框架驅動,不一樣的host對應的命令處理一定有所差異。針對sdhci標準的host mmc驅動。host->ops->request(host, mrq);的執行將交給,sdhci_request()完成。

 

sdhci_request

  1. 注意函數一進來,host結構體發生變化,已經再也不是mmc_host結構,而是各具體的廠商的host,如這裏的struct sdhci_host *host; 實際上是host = mmc_priv(mmc);這麼的得來的。
  2. host->mrq = mrq; 保存起mrq結構。函數有很多對sdhci host寄存器的讀寫,此時開始真正與硬件設備打交道,即準備把控制信息交託給咱們的mmc host控制器。
  3. 以後的sdhci_send_command(host, mrq->cmd); 控制host啓動命令。
  4. 最後的mmiowb();是爲了保證編譯器順序編譯,防止編譯器優化打亂執行順序。

 

sdhci_send_command()

  1. 該函數還值得推敲,從上文看出,request裏面的buffer數據被放在mrq->data->sg裏面存好了,僅僅是存在代碼結構體裏面,和真正的DMA還沒創建聯繫,此時說命令發送出去,一定不夠合理。因此DMA的初始化必不可少。
  2. 前面的也主要作出錯檢查工做,把host->cmd = cmd;命令保存起來。
  3. sdhci_prepare_data(host, cmd); 看這個函數名,準備數據,就知道個大概了。裏面的關鍵函數sdhci_pre_dma_transfer()就是準備DMA,dma_map_sg(),從data->sg裏面獲取到信息,填充到DMA控制器裏面。
  4. 數據都準備好以後,sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); 來個終極的,數據發送,這纔是真正的控制host發送命令的操做,到這類,mmc控制器纔開始跟sd卡作交互。

 

命令等待

  1. 前文說道,命令發送以後是在Mmc_start_req()的第二步mmc_wait_for_data_req_done()裏面作等待。mmc_wait_for_data_req_done函數大量用到了host->context_info的結構體。context_info->wait是等待queue的標誌,__add_wait_queue(q, wait); 再io_schedule()出去。今後mmcqd線程將交換出去,知道有人喚醒wait queue。
  2. 如何能激活等待隊列呢?還記得mmc_start_request()的第2步,mmc_wait_data_done回調,wake_up_interruptible(&mrq->host->context_info.wait);wakeup這個 context_info.wait wait queue。說明命令結束以後,會有人調用該回調來喚醒mmcqd線程。
  3. 在哪裏調用回調?既然mmc命令是有sdhci host啓動發送,一定mrq->done這個回調也要在sdhci host階段完成。而這個正是由sdhci host的irq中斷來完成的。想一想也合理,線程啓動命令以後,由host控制器完成命令,而後觸發中斷通知cpu事情完成,中斷處理裏面啓動回調函數,喚醒mmcqd線程。
  4. sdhci_irq就是上步咱們說的中斷處理函數。根據中斷類型的不一樣,分爲sdhci_cmd_irq()處理和sdhci_data_irq()處理。因此能夠看出,命令處理中斷和數據處理中斷是不一樣的,一條既有命令又有數據的mmc cmd,會至少激活2次中斷,1次給命令,1次給數據。
  5. done回調的地方在tasklet_schedule(&host->finish_tasklet);的finish_tasklet裏面,中斷完成上部處理以後,啓動finish_tasklet完成後面的事情。finish_tasklet的定義是sdhci_tasklet_finish。 mmc_request_done() -> mrq->done(mrq);

 

並非全部的mmc命令都是讀寫命令,那其餘的命令該如何完成呢,他們與mmc的讀寫命令有什麼差異。咱們用mmc的CMD8 SEND_IF_COND做爲例子,mmc_send_if_cond()是發送CMD8的函數。

  1. 函數很簡單,進來就初始化一個局部變量struct mmc_command cmd。填好命令CMD8,給定返回的RSP參數值,無需初始化cmd->data,由於CMD8沒有數據階段。直接經過mmc_wait_for_cmd() 發送出去。
  2. mmc_wait_for_cmd()裏面建立mrq結構變量,以前說過mrq變量的意義, mrq.cmd = cmd; cmd->data = NULL; mmc_wait_for_req(host, &mrq);
  3. __mmc_start_req() 啓動 mmc_start_request() 這基本跟讀寫命令的流程就一致了。
  4. mmc_wait_for_req_done() 等待wait_for_completion_io(&mrq->completion); 看得出來這裏面和讀寫流程的不一樣,在本次傳輸啓動後,馬上同步等待中斷到來。由於單次的CMD8命令並無其餘的循環處理,所以若是再也不本次處理等待,未來也沒有機會再進入同步等待階段。
  5. 本次的wait等待是mrq->completion,和讀寫命令的也有所不一樣。仔細看__mmc_start_req() mrq->done = mmc_wait_done; 而讀寫的是mrq->done=mmc_wait_data_done。剩下的事情就是返回處理結果。
  6. 對於又有命令又有數據的單次命令,譬如mmc_send_cxd_data(). mrq.data也須要賦值,咱們知道讀寫命令裏面,須要初始化data->sg變量。這裏也不例外,data->sg的初始化由sg_init_one(&sg, data_buf, len);完成,看函數名就知道,這是一個初始化單一數據處理的dma。只須要傳輸一次,大部分是作讀取用。
相關文章
相關標籤/搜索