Linux mmc framework2:基本組件之block


1.前言

本文主要block組件的主要流程,在介紹的過程中,將詳細說明和block相關的流程,涉及到其它組件的詳細流程再在相關文章中說明。

2.主要數據結構和API

2.1 struct mmc_card

 

Elemete Name struct mmc_card
Path include/linux/mmc/card.h
Responsiblities

 是對mmc device的抽象,由於定義了mmc_bus_type類型的總線,此處mmc_card是與mmc_bus_type配套

Attributions
  • host:struct mmc_host *類型,這個mmc device屬於哪個host管理;
  • dev:struct device類型,代表設備驅動模型中的一個 device
  • ocr:當前的操作電壓設置
  • rca:device的relative card address
  • type:卡的類型,包括MMC/SD/SDIO/COMBO(SDIO+MEM)
  • state:卡的狀態,在線、只讀、是否使用block地址、是否是SDXC卡、卡被移除、卡在BKOPS、卡在suspend
  • quirks:卡的一些其它怪癖屬性
  • erase_size:單位sectors
  • erase_shift:可以擦除的 sectors是2的多少次方
  • pref_erase:單位sectors
  • eg_boundary
  • erased_byte:擦除的字節數
  • raw_cid:原始的CID值
  • raw_csd:原始的CSD值
  • raw_scr:原始的raw_scr值
  • cid:struct mmc_cid類型,卡identification
  • csd:struct mmc_csd類型,保存從卡的CSD寄存器讀取的內容
  • ext_csd:struct mmc_ext_csd類型,卡擴展信息
  • scr:其它的SD信息
  • ssr:更多的SD信息
  • sw_caps:swicth能力
  • sdio_funcs:SDIO功能的個數
  • cccr:struct sdio_cccr類型,卡的通常信息
  • cis:struct sdio_cis
  • sd_bus_speed:bus speed mode
  • mmc_avail_type:host和card都支持的設備類型
  • drive_strength:驅動能力,用於UHS-I, HS200 or HS400
  • debugfs_root:struct dentry *類型,用於debugfs顯示根目錄
  • part:struct mmc_part類型,物理分區
  • nr_parts:物理分區的個數
Operations

 

 

 

2.2 struct mmc_driver

 

Elemete Name struct mmc_driver
Path include/linux/mmc/card.h
Responsiblities

mmc driver,由於定義了mmc_bus_type類型的總線,此處mmc_driver是與mmc_bus_type配套

Attributions
  • drv:struct device_driver類型
  • probe,remove,shutdown:mmc driver相關函數
Operations
  •  int mmc_register_driver(struct mmc_driver *drv)

 設置總線類型,並將drv加入到設備驅動模型中

  • void mmc_unregister_driver(struct mmc_driver *drv)

將drv從設備驅動模型中移除

2.3 struct mmc_blk_data

 

Elemete Name struct mmc_blk_data
Path drivers/mmc/card/block.c
Responsiblities

mmc_blk_data為block的核心結構體,用於存放mmc block的一些數據,與mmc slot對應

Attributions
  • lock:spinlock_t類型
  • disk:struct gendisk *類型,代表一個磁盤設備
  • queue:struct mmc_queue類型,請求隊列
  • part:分區鏈表
  • flags:
  • usage:
  • read_only:
  • part_type:
  • name_idx:
  • reset_done:
  • part_curr:
  • force_ro:
  • power_ro_lock:
  • area_type:
Operations

 

 

 

3. 主要流程

3.1 mmc_blk_init

mmc_blk_init->

  初始化max_devices

  register_blkdev

  mmc_register_driver

 

module_init(mmc_blk_init)會執行到此函數

  • 初始化max_devices:設定最多支持多少個mmc塊設備給max_devices
每類塊設備支持256個次設備號,每個塊設備有16個次設備號(16個分區),由此得出支持的最大的mmc塊設備數max_devices為256/16=16,每個此設備號對應一個分區?
  • register_blkdev:向全局的struct blk_major_name類型的數組major_names注冊本塊設備的主設備號和設備名
mmc子系統對於上層block子系統來講是首先抽象為一個普通的塊設備。
通過register_blkdev向block子系統注冊一個block設備,主設備號為MMC_BLOCK_MAJOR,設備名為“mmc”。

通過分配一個blk_major_name結構體,來保存主設備號和設備名,blk_major_name被保存到全局的blk_major_name數組中。
如果不指定主設備號,將查詢全局的blk_major_name結構體找到一個未用的主設備號來使用,並將此主設備號作為返回值返回。

major_names中的信息會出現在/proc/devices中。
因此可以看出,注冊做的事情實際上非常少。注冊完成后,除了能夠在/proce/devices中看到設備之外,不能對設備做任何事情,設備還無法使用,只有當block_device與gendisk建立關聯用戶空間才可以訪問
  • mmc_register_driver(&mmc_driver)
設備驅動模型中通過driver_register將mmc_driver注冊到mmc_bus_type上

3.2 mmc_blk_exit

mmc_blk_exit->

  mmc_unregister_driver

  unregister_blkdev

在退出的時候會執行mmc_blk_exit,與mmc_blk_init相反的動作,主要包括:

  • mmc_unregister_driver(&mmc_driver)
從mmc_bus_type上將mmc_driver注銷
  •  unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
從全局的struct blk_major_name類型的數組major_names中注銷主設備號為MMC_BLOCK_MAJOR名為mmc的blk_major_name結構體
釋放對應的blk_major_name結構體

3.3 mmc_blk_probe

mmc_blk_probe->

       mmc_blk_alloc->

              mmc_blk_alloc_req->

        alloc_disk

                     mmc_init_queue->

          blk_queue_prep_rq

                            kthread_run(mmc_queue_thread, mq);

  mmc_blk_alloc_parts(card, md))

  mmc_add_disk->

      block_add_disk

初始化時mmc_blk_init中會執行mmc_register_driver,而前文所述執行mmc_attach_mmc時會通過mmc_add_card將mmc_card注冊到mmc bus,這樣就觸發了執行前文所述的mmc_blk_probe函數,后面有詳細解釋mmc_blk_probe的執行過程

mmc_blk_probe最主要的是初始化了request queue;初始化disk,同時通過mmc_add_disk將磁盤添加到系統中,使之可用

  • mmc_blk_alloc_req

創建並初始化請求隊列,啟動線程循環抓取請求隊列中的request,調用request處理函數進行處理

1分配mmc_blk_data結構體md並初始化,同時mmc_queue作為mmc_blk_data的成員也被創建
mmc_blk_data為block的核心結構體,與mmc_card關聯,用於存放mmc_card相關數據,每個mmc slot即每個mmc設備對應一個mmc_blk_data結構體。
此處會分配mmc_blk_data結構體md,同時mmc_queue作為mmc_blk_data的成員也被創建。並標識dev_use的bitmap來記錄已經分配的mmc device,

也就是說dev_use是與實際的物理設備相對應的,不是跟分區對應的,dev_use的index用dev_idx來記錄
注意到此處MMC_BLK_DATA_AREA_MAIN表示主分區的區域(mmc_blk_data與設備對應,此處看又像是與分區對應??)。
MMC分區類型包括如下幾種: #define MMC_BLK_DATA_AREA_MAIN  (1<<0)
#define MMC_BLK_DATA_AREA_BOOT  (1<<1)
#define MMC_BLK_DATA_AREA_GP    (1<<2)
#define MMC_BLK_DATA_AREA_RPMB  (1<<3)2) alloc_disk(perdev_minors):分配gendisk結構體保存到md中,gendisk與磁盤設備對應 (3)mmc_init_queue(queue_c):創建並初始化請求隊列
通過調用block子系統接口blk_init_queue來初始化請求隊列,其中mmc_request_fn為處理請求的回調函數
blk_queue_prep_rq(mq->queue, mmc_prep_request)設定requet_queue的prep回調函數;
mmc_alloc_sg(host->max_segs, &ret)分配max_segs個scatterlist用於request請求(只是分配scatterlist,並未分配存放數據的內存),
返回分配的scatterlist個數
kthread_run(mmc_queue_thread, mq) 起一個kennel thread運行
mmc_queue_thread來處理上層發送下來的request,對每個reqeust執行issue_fn回調

注:issue_fn回調在下面指定為mmc_blk_issue_rq
(4)指定issue_fn回調為mmc_blk_issue_rq,mmc_blk_issue_rq是具體的mmc request處理函數
  • mmc_blk_alloc_parts(card, md))

 

  • mmc_add_disk
為了將一個磁盤添加到系統中,對系統可用,必須初始化磁盤數據結構並調用add_disk方法。
需要特別注意的是一旦調用了add_disk,磁盤就被“激活”了,系統隨時都可能會調用該磁盤提供的各種方法,
甚至在該函數返回之前就會調用,因而在完成磁盤結構的初始化之前,不要調用add_disk。

 

3.4 mmc_add_disk

mmc_add_disk->

  device_add_disk

 

device_add_disk的原型為void device_add_disk(struct device *parent, struct gendisk *disk)

它完成的工作主要包括:
(1)根據磁盤的主次設備號信息為磁盤分配設備號;
(2)調用disk_alloc_events初始化磁盤的事件(alloc|add|del|release)處理機制。在最開始磁盤事件會被設置為被阻塞的。
(3)調用bdi_register_dev將磁盤注冊到bdi_list,注:bdi用於將page_cache或buffer_cache中的臟數據刷新到磁盤
(4)調用blk_register_region將磁盤添加到bdev_map中(通過設備號可以獲取kobject從而得到包含它的父對象進行操作)
(5)調用register_disk將磁盤添加到系統中。
(6)調用blk_register_queue注冊磁盤的請求隊列。主要是為隊列和隊列的調度器在設備的sys文件系統目錄中創建相應的sys目錄/文件,並且發出uevent事件。
(7)調用disk_add_events完成在/sys文件系統的設備目錄下創建磁盤的事件屬性文件,將磁盤事件添加到全局鏈表disk_events中,解除對磁盤事件的阻塞。

 

關於probe函數是如何被調用到的?

一般我們認為mmc_blk_probe的執行一定需要mmc_driver與mmc_device的匹配才可以,實際上沒有mmc_device,  而是有mmc_card,mmc_blk_probe的執行經歷如下歷程:

(1)先來看mmc_register_driver的流程

       mmc_register_driver->

              driver_register->

                     driver_find//bus查看driver是否已經注冊,如果已經注冊則退出,否則bus add driver

                     bus_add_driver->

                            driver_attach->

                                   bus_for_each_dev//此處由於還沒有device注冊,因此會退出

顯然mmc_blk_probe的執行不是在mmc_register_driver的時候,那么肯定是在device_register的時候,看看我們的假設是否正確,繼續往下看

 

(2)mmc_alloc_card, mmc_add_card

通過瀏覽代碼,我們發現在mmc/core/bus_c中有mmc_alloc_card和mmc_add_card

mmc_alloc_card:mmc_attach_mm->mmc_init_card初始化並分配一個新的mmc_card結構體,實際上是創建device設備;

mmc_add_card:mmc_attach_mmc->mmc_add_card時調用,通過調用device_add(&card->dev)來完成設備的注冊,過程如下:

              mmc_add_card->

                            device_add->

                                   bus_probe_device->

                                          device_attach->

                                                 __device_attach->

                                                               driver_match_device->

                                                               mmc_bus_match//此函數的特殊之處在於總是返回值為1

                                                        driver_probe_device->

                                                               really_probe->

                                                                      mmc_bus_probe->

                                                                             mmc_blk_probe

mmc_blk_probe的執行不是靠device和driver的匹配,而是將匹配函數mmc_bus_match總是返回1,如下:

static int mmc_bus_match(struct device *dev, struct device_driver *drv)

{

       return 1;

}

這樣就可以執行到mmc_bus_type的probe函數進而執行到mmc_blk_probe。

3.5 mmc_queue_thread

線程處理函數,用於循環抓取請求隊列中的request並交給請求處理函數進行處理

mmc_queue_thread->

       blk_fetch_request

         issue_fn(mmc_blk_issue_rq)->

             mmc_blk_issue_rw_rq->

                 mmc_blk_rw_rq_prep

                 mmc_start_req –>

                     mmc_wait_for_data_req_done->

                         mmc_blk_err_check

                         host->ops->request

 mmc_queue_thread是在mmc_init_quene中起的線程,主要作用是完成上層發送的請求進行處理

  •  blk_fetch_request
從請求隊列中取出一個request
  • issue_fn
由前面可知issue_fn在mmc_blk_probe->mmc_blk_alloc_req時將issue_rq初始化為mmc_blk_issue_rq,請求有幾種包括:discard, flush, 以及rw
  • mmc_blk_issue_rw_rq
首先通過mmc_blk_rw_rq_prep來做一些准備工作,獲取命令號、命令參數等,然后通過mmc_start_req發起請求
  • mmc_start_req
通過mmc_wait_for_data_req_done發起真正的請求,並等待請求結束。
mmc_wait_for_data_req_done會回調控制器的request函數發起請求,然后mmc_blk_err_check檢查是否有錯誤發生,
如果有錯誤發生將嘗試recovery進行修復開始新的傳輸

3.6 mmc_blk_issue_rq

mmc_blk_issue_rq->

   mmc_claim_host

  mmc_blk_part_switch

  mmc_blk_issue_rw_rq->

    mmc_blk_prep_packed_list

    mmc_blk_rw_rq_prep

    mmc_start_req->

      __mmc_start_data_req

    mmc_queue_bounce_post

    檢查mmc_start_req返回的狀態

mmc_blk_issue_rq對發送的mmc request進行具體的處理。

  • mmc_claim_host
實際上是聲明當前進程占有host controller,如果有其它進程占有則需要等待,詳細的可參考Linux mmc framework2:基本組件之core
  • mmc_blk_part_switch
通過MMC_SWITCH命令對EXT_CSD寄存器的PARTITION_CONFIG(bit[179])進行設置,主要包括boot是否使能、用哪個分區做boot分區、選擇要訪問的分區。
如果MMC_SWITCH命令出錯,將通過blk_end_request_all終止request
  • mmc_blk_issue_rw_rq
根據req->cmd_flags的命令做不同的事情。REQ_SANITIZE、REQ_DISCARD、REQ_FLUSH分別為
. mmc_blk_issue_secdiscard_rq 和mmc_blk_issue_discard_rq
. mmc_blk_issue_flush
. mmc_blk_issue_rw_rq(這個是我們要分析的讀寫數據流程
1. mmc_blk_prep_packed_list嘗試把當前request和隊列中的其他request合並,以增強性能。是否可以合並,要依賴於:
控制器支持packed功能;
device的MAX_PACKED_WRITES 大於0;
只對寫request進行packed

2. mmc_blk_rw_rq_prep:正常情況下執行mmc_blk_rw_rq_prep函數,從request構造mmc_request,畢竟下發給host請求,是mmc_request,而不是block層通用的request。
如果支持packed功能,那么就用pack_list來構造mmc_request

3. mmc_start_req:mmc_start_req 啟動一個非阻塞的request,這個函數會等待前一個request完成,然后啟動當前requeset,並立刻返回
如果mmc_start_req返回的areq不為空,說明完成了上一次的request
  • mmc_start_req
mmc_start_req 啟動一個非阻塞的request,這個函數會等待前一個request完成,然后啟動當前requeset,並立刻返回
如果mmc_start_req返回的areq不為空,說明完成了上一次的request

1. 首先它會執行到mmc_wait_for_data_req_done函數,等待上一次的命令的完成,如果上一次未完成就會將當前進程加入等待隊列休眠,等待被喚醒。

   當上一次完成后會立即返回,並將上一次命令執行的狀態返回給mmc_blk_issue_rw_rq。

2、if (host->areq) {

    err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq);

host->areq不為空,說明有正在處理的reuqest,函數mmc_wait_for_data_req_done用來等待這個host->areq,有兩個條件會喚醒該MMC上下文: is_done_rcv和is_new_req

3. if (!err && areq)      

   start_err = __mmc_start_data_req(host, areq->mrq);

進入__mmc_start_data_req(host, areq->mrq);

(1)首先會將函數指針mmc_wait_data_done賦給mrq->done.

mmc_wait_data_done會設置context_info->is_done_rcv=true,這正好是喚醒mmc_wait_for_data_req_done的條件之一,然后調wake_up_interruptible(&context_info->wait);喚醒之。

(2)然后會調用mmc_start_request(host, mrq);

 mmc_start_reuqest實際調用host->ops->request方法,進入了平台特定的request函數

進入特定的平台之后,會進入相應的中斷對硬件進行讀寫的命令的執行,當命令執行完畢后,會進行函數回調調到剛才的mmc_wait_data_done喚醒等待的進程進行下一次命令的執行。

  •  mmc_queue_bounce_post
如果使用了bounce buffer,那么需要把傳輸結果從bounce buffer復制會sg buffer。
所謂bounce buffer是因為某些DMA控制器只能處理連續物理內存,此時需要通過bounce buffer來達到物理內存連續性。
  • 檢查mmc_start_req返回的狀態
1. 如果是MMC_BLK_SUCCESS或者MMC_BLK_PARTIAL,需要調用blk_end_request通知block設備層,完成了本次讀寫request。
2. 如果是MMC_BLK_CMD_ERR,那么調用mmc_blk_reset復位host。調用mmc_blk_cmd_err嘗試blk_end_request,如果發現reuqest未完成,說明本次操作失敗,反之成功start_new_req

 

TODO


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM