MMC/SD設備驅動在Linux中的結構層次
在Linux中MMC/SD卡的記憶體都當作塊設備。drivers\mmc 分別有card、core和host三個文件夾
- card層 要把操作的數據以塊設備的處理方式寫到記憶體上或從記憶體上讀取;
因爲這些記憶卡都是塊設備,當然需要提供塊設備的驅動程序,這部分就是實現了將你的SD 卡如何實現爲塊設備的。 - core層 則是將數據以何種格式,何種方式在 MMC/SD主機控制器與MMC/SD卡的記 憶體(即塊設備)之間進行傳遞,這種格式、方式被稱之爲規範或協議.
整個MMC 的核心存,這部分完成了不同協議和規範的實現,併爲HOST 層的驅動提供了接口函數。 - host層 下的代碼就是你要動手實現的具體MMC/SD設備驅動了,包括RAM芯片中的 SDI控制器(支持對MMC/SD卡的控制,俗稱MMC/SD主機控制器)和SDI控制器與MMC/SD卡的硬件接口電路。
針對不同主機的驅動程序,這一部是驅動程序工程師需要根據自己的特點平臺來完成的。其中,card(區塊層) 與core(核心層)是linux系統封裝好了部分,我們不需要修改,host(主控制器層)中提供與各芯片構架相關的文件,這纔是我們所要開發的部分.
hdj@M425:/workspace/kernel/drivers/mmc$ ls
built-in.o card core host Kconfig Makefile modules.builtin modules.order
核心層根據需要構造各種MMC/SD命令,這些命令怎麼發送給MMC/SD卡呢?這通過主機控制器層來實現。
設置MMC/SD/SDIO控制器使用到的GPIO引腳、使能控制器、註冊中斷處理函數等,然後向上面的核心層增加一個主機(Host),這樣核心層就能調用host/dw_mmc-rockchip.c(以rk平臺爲例子)提供的函數來識別、使用具體存儲卡了。
結構體
struct mmc_host 用來描述卡控制器
struct mmc_card 用來描述卡
struct mmc_driver 用來描述 mmc 卡驅動
struct sdio_func 用來描述 功能設備
struct mmc_host_ops 用來描述卡控制器操作接口函數功能,用於從 主機控制器層向 core 層註冊操作函數,從而將core 層與具體的主機控制器隔離。也就是說 core 要操作主機控制器,就用這個 ops 當中給的函數指針操作,不能直接調用具體主控制器的函數。
host代碼解析
根據rk平臺sdmmc 對應dw_mmc-rockchip.c
下面列出識別存儲卡、區塊層發起操作請求兩種情況下函數的主要調用關係:
添加識別卡
dw_mci_rockchip_probe //(host/dw_mmc-rockchip.c) 匹配節點
dw_mci_pltfm_register //(host/dw_mmc-pltfm.c ) 爲 struct dw_mci *host賦值
dw_mci_probe //(host/dw_mmc.c) 解析dts配置信息,解析在https://blog.csdn.net/h_8410435/article/details/105426863
dw_mci_init_slot //初始化配置的幾個slot.
mmc_alloc_host //(core/host.c) 創建mmc_host(對象),在mmc_alloc_host()中進行一些通用設置的初始化
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_rescan //(core/core.c) ******插入卡後執行此處,添加識別卡*****
mmc_rescan_try_freq
mmc_go_idle //發送CMD0
mmc_set_chip_select
mmc_set_ios
mmc_attach_sd //(mmc_attach_sd) 匹配sd卡 sd卡入口點
mmc_send_app_op_cond //發送SD_APP_OP_COND(ACMD41)命令用來判斷卡的工作電壓是否符合,如果不符合的話,卡應該放棄總線操作
mmc_select_voltage //電壓選擇和判斷,下面解釋
mmc_sd_init_card//初始化一個 card ,涉及內容挺多,包括get_cid,協議相關內容.參考:https://www.cnblogs.com/fengeryi/p/3469782.html sdi時鐘和波特率的有關設置
mmc_sd_get_cid //從卡取cid
mmc_alloc_card //申請卡
host->ops->init_card //init card
mmc_sd_switch_hs //設置模式
mmc_set_clock //設置clok
mmc_add_card
mmc_of_parse //解析dts配置信息,將配置信息賦值給host->caps
dw_mci_get_cd //得到卡
mmc_add_host //加到host
mmc_start_host
_mmc_detect_change
mc_schedule_delayed_work // 下面有解釋
dw_mci_enable_cd //使能card detect
總結:主要兩個mmc_alloc_host 申請一個mmc_host.
mmc_add_host 添加一個mmc_host.
- mc_schedule_delayed_work // workqueue 這個工作隊列當中添加一個延遲的工作任務,而這個工作任務就是由 host->detect 來描述的,在隨後的 delay 個 jiffies 後會有一個記錄在 host->detect 裏面的函數被執行,workqueue 這個工作隊列還在忙,不一會兒它就會調用 host->detect 裏面那個函數,這個函數到底是哪個函數,對應的爲INIT_DELAYED_WORK(&host->detect, mmc_rescan); 這個在檢測是否插入sd卡,我們平時用的 SD/MMC 卡就是一個卡,如果要操作它得用 SD/MMC 卡控制器纔行,所以可以看到有 struct mmc_card,struct mmc_host 的區分。
卡的檢測
參考:https://www.cnblogs.com/yanghong-hnu/p/4671352.html
dw_mci_probe
devm_request_irq(host->dev, host->irq, dw_mci_interrupt,host->irq_flags, "dw-mci", host);
dw_mci_interrupt
dw_mci_handle_cd(host);
mmc_detect_change
mmc_schedule_delayed_work(&host->detect, delay);
匹配執行INIT_DELAYED_WORK(&host->detect, mmc_rescan);中的mmc_rescan進行card的掃描
我們平時用的 SD/MMC 卡就是一個卡,如果要操作它得用 SD/MMC 卡控制器纔行,所以可以看到有 struct mmc_card,struct mmc_host 的區分。
到這裏了,來回憶一下 dw_mci_init_slot 這個函數做的事情,大概就是準備一個 mmc_host 結構,然後添加一個主控制器設備到內核,最後又調用了一下 mmc_rescan 來檢測是不是有卡插入了。
如果有卡插入了還好,可以去操作卡了,那如果沒有卡插入呢? mmc_rescan 不是白調用了一次嗎?是啊,的確是白調用了一次。可是卡插入時爲什麼 PC 還是能檢測到呢?看來卡檢測的動作不光是在 probe 的最後一步做了一次,其它地方也有做。卡插入一般都是人爲地隨時去插入的,像這種情況一般都是會用中斷機制去提供系統有外來侵入,然後再去採取行動。
中斷函數如上
掃描函數
mmc_rescan
mmc_rescan_try_freq
mmc_go_idle(host); //sd卡協議,發送CMD0
mmc_send_if_cond(host, host->ocr_avail); //發送CMD8,等待接收sd卡迴應,以此判斷是sd. sdio,mmc
mmc_attach_sdio(host) //以sdio爲例子
mmc_send_io_op_cond(host, 0, &ocr); //發送了CMD5,搞到了ocr的值.這個值不知道咋來的
mmc_sdio_init_card(host, rocr, NULL, 0); //初始化一個 card ,這個 card 就用 struct mmc_card 結構來描述,然後又調用 mmc_add_card 將卡設備添加到了內核
mmc_alloc_card
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
struct mmc_card *card;
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
if (!card)
return ERR_PTR(-ENOMEM);
card->host = host;
device_initialize(&card->dev);
card->dev.parent = mmc_classdev(host);
card->dev.bus = &mmc_bus_type;
card->dev.release = mmc_release_card;
card->dev.type = type;
return card;
}
struct mmc_card 結構裏面包含了一個 struct device 結構, mmc_alloc_card 不但申請了內存,而且還填充了 struct device 中的幾個成員,尤其 card->dev.bus = &mmc_bus_type; 這一句要重點對待。
static struct bus_type mmc_bus_type = {
.name = "mmc",
.dev_groups = mmc_dev_groups,
.match = mmc_bus_match,
.uevent = mmc_bus_uevent,
.probe = mmc_bus_probe,
.remove = mmc_bus_remove,
.shutdown = mmc_bus_shutdown,
.pm = &mmc_bus_pm_ops,
};
申請一個 mmc_card 結構,並簡單初始化後, mmc_init_card 的使命就完成了,然後再調用 mmc_add_card 將這個 card 設備添加到內核。 mmc_add_card 其實很簡單,就是調用 device_add 將 card->dev 添加到內核當中去。
device_add 裏面,設備對應的總線會拿着你這個設備和掛在這個總線上的所有驅動程序去匹配( match ),此時會調用 match 函數,如果匹配到了就會調用總線的 probe 函數或驅動的 probe 函數
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
return 1;
}
看來 match 永遠都能成功,那就去執行 probe 吧:
static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = dev_to_mmc_card(dev);
return drv->probe(card);
}
這裏就有點麻煩了,在這個函數裏面又調用了一下 drv->probe() ,那這個 drv 是什麼呢?上面
struct mmc_driver*drv=to_mmc_driver(dev->driver);
match 函數總是返回 1 ,那看來只要是掛在這條總線上的 driver 都有可能跑到這裏來了,事實的確也是這樣的,不過好在掛在這條總線上的 driver 只有一個,它是這樣定義的:
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
};
看到這裏時, card/core/host 幾個已經全部被扯進來了,邊看 mmc_driver 中的幾個函數,他們幾個如何聯繫起來也就慢慢明白了。
card代碼分析
static int mmc_blk_probe(struct mmc_card *card) // 來自 card/block.c
{
struct mmc_blk_data *md;
int err;
……
md = mmc_blk_alloc(card); //主要函數
if (IS_ERR(md))
return PTR_ERR(md);
……
add_disk(md->disk);
return 0;
out:
mmc_blk_put(md);
return err;
}
塊設備驅動的整個套路了:
- 分配、初始化請求隊列,並綁定請求隊列和請求函數。
- 分配,初始化 gendisk ,給 gendisk 的 major , fops , queue 等成員賦值,最後添加 gendisk 。
- 註冊塊設備驅動。
MMC 卡驅動程序套路:
- mmc_init_queue 初始了隊列,並將 mmc_blk_issue_rq; 函數綁定成請求函數;
- alloc_disk 分配了 gendisk 結構,並初始化了 major , fops ,和 queue ;
- 最後調用 add_disk 將塊設備加到 KERNEL 中去。
電壓選擇
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
{
int bit;
/*
* Sanity check the voltages that the card claims to
* support.
*/
if (ocr & 0x7F) { //根據協議保留前七位,查看下面ocr 電壓
dev_warn(mmc_dev(host),
"card claims to support voltages below defined range\n");
ocr &= ~0x7F;
}
ocr &= host->ocr_avail; //判斷host實際所支持的電壓與card所需要的電壓是否匹配,如果匹配,那麼ocr的值就非0
if (!ocr) {
dev_warn(mmc_dev(host), "no support for card's volts\n");
return 0;
}
if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) {
bit = ffs(ocr) - 1; // 它的作用就是返回參數中第一個爲1的bit的位置(ffs(0)=0,ffs(1)=1,ffs(8)=4),那麼該函數用在這裏的作用就是取出card需要的實際電壓是多少
ocr &= 3 << bit;
mmc_power_cycle(host, ocr); //供電
} else {
bit = fls(ocr) - 1;
ocr &= 3 << bit;
if (bit != host->ios.vdd)
dev_warn(mmc_dev(host), "exceeding card's volts\n");
}
return ocr;
}
void mmc_power_cycle(struct mmc_host *host, u32 ocr)
{
mmc_power_off(host);
/* Wait at least 1 ms according to SD spec */
mmc_delay(1);
mmc_power_up(host, ocr); //就是上電,不添加代碼了
}
##總結
分析到這裏, MMC/SD 卡的驅動整個構架基本也就很明析了,說簡單了就是做了兩件事:
卡的檢測;
卡數據的讀取。
1.卡的檢測
mmc_alloc_host(core/core.c)
mmc_rescan(core/core.c)
mmc_attach_mmc(core/mmc.c)
mmc_init_card(core/mmc.c)
mmc_add_card(core/bus.c)
device_add
mmc_bus_match(core/bus.c)
mmc_bus_probe(core/bus.c)
mmc_blk_probe(card/block.c)
alloc_disk/add_disk
2.讀寫數據
`mmc_blk_issue_rq ( card/block.c )
mmc_wait_for_req(core/core.c)
mmc_start_request(core/core.c)
host->ops->request(host, mrq)