塊設備是Linux系統中的基礎外設之一,而 MMC/SD 存儲設備是一種典型的塊設備。Linux內核設計了 MMC子系統,用於管理 MMC/SD 設備。html
MMC 子系統的框架結構以下圖所示,其中core layer根據MMC/SD設備協議標準實現了協議。card layer與Linux的塊設備子系統對接,實現塊設備驅動以及完成請求,具體協議通過core layer的接口,最終經過host layer完成傳輸,對 MMC設備進行實際的操做。和 MMC設備硬件相對應,host和card能夠分別理解爲 MMC device的兩個子設備:MMC主設備和MMC從設備,其中host爲集成於MMC設備內部的MMC controller,card爲MMC設備內部實際的存儲設備。node
Linux系統中,使用兩個結構體 struct mmc_host 和 struct mmc_card 分別描述host和card,其中host設備被封裝成 platform_device 註冊到Linux驅動模型中。總體而言,(Linux驅動模型框架下)MMC驅動子系統包括三個部分:linux
下文將經過內核源碼(Linux Kernel 5.2)對MMC驅動子系統進行簡述,並經過MMC驅動的實際案例說明MMC驅動編寫的通常步驟,同時分析驅動模型下完成驅動、設備綁定的過程。如對Linux設備驅動模型不熟悉,能夠參考另外一篇博文:Linux設備驅動模型簡述(源碼剖析)。ios
MMC總線的註冊和 platform 總線的註冊方法相同,均是調用 bus_register() 函數。函數的調用入口位於 mmc/core/core.c ,經過 mmc_init() 實現,此處主要關注MMC的部分。git
/* drivers/mmc/core/core.c */ subsys_initcall(mmc_init); static int __init mmc_init(void) { int ret; ret = mmc_register_bus(); if (ret) return ret; ret = mmc_register_host_class(); if (ret) goto unregister_bus; ret = sdio_register_bus(); if (ret) goto unregister_host_class; return 0; unregister_host_class: mmc_unregister_host_class(); unregister_bus: mmc_unregister_bus(); return ret; }
/*********************************************************** * mmc bus 總線註冊 ***********************************************************/ 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, }; int mmc_register_bus(void) { return bus_register(&mmc_bus_type); }
/*********************************************************** * mmc_host class 類註冊 ***********************************************************/ static struct class mmc_host_class = { .name = "mmc_host", .dev_release = mmc_host_classdev_release, }; int mmc_register_host_class(void) { return class_register(&mmc_host_class); }
主要包括兩個方面:github
drivers/mmc/core/block.c 中將 mmc_driver 註冊到 mmc_bus 對應的總線系統裏。主要步驟包括:cookie
mmc_driver 註冊完成以後,會在sysfs中創建目錄 /sys/bus/mmc/drivers/mmcblk 。框架
/* drivers/mmc/core/block.c */ module_init(mmc_blk_init); static struct mmc_driver mmc_driver = { .drv = { .name = "mmcblk", .pm = &mmc_blk_pm_ops, }, .probe = mmc_blk_probe, .remove = mmc_blk_remove, .shutdown = mmc_blk_shutdown, }; static int __init mmc_blk_init(void) { int res; ... ... res = register_blkdev(MMC_BLOCK_MAJOR, "mmc"); ... res = mmc_register_driver(&mmc_driver); ... return 0; ... ... } /** * mmc_register_driver - register a media driver * @drv: MMC media driver */ int mmc_register_driver(struct mmc_driver *drv) { drv->drv.bus = &mmc_bus_type; return driver_register(&drv->drv); }
前文已經簡單描述過,MMC設備主要包括主設備host和從設備card兩部分,而主設備host將被封裝在 platform_device 中註冊到驅動模型中。
爲了更加清晰地描述此部分的註冊過程,下文將以一個驅動爲例分析(此驅動源碼只包含關鍵步驟代碼,只爲描述MMC驅動的編寫基本框架,demo mmc driver)。async
module_init(xxx_mmc_init); #define DRIVER_NAME "xxx_mmc" /* platform driver definition */ static struct platform_driver xxx_mmc_driver = { .probe = xxx_mmc_probe, .remove = xxx_mmc_remove, .driver = { .name = DRIVER_NAME, }, }; static struct platform_device *pdev; static __init int xxx_mmc_init(void) { int err = 0; /* * Register platform driver into driver model */ // 將xxx_mmc_driver註冊到驅動模型中 err = platform_driver_register(&xxx_mmc_driver); /* * Allocate platform device and register into driver model * This will call driver->probe() */ // 動態分配platform_device,並將其註冊到驅動模型中 // 此過程會回調driver->probe()函數 pdev = platform_device_alloc(DRIVER_NAME, 0); err = platform_device_add(pdev); return err; }
從代碼中看到,驅動入口函數中將註冊 platform_driver 和 platform_device , name 均定義爲 xxx_mmc 。根據驅動模型,最終會回調 xxx_mmc_driver 中的 probe() 函數: xxx_mmc_probe() 。ide
// 自定義的mmc_host_ops,用於host作實際操做時回調 static const struct mmc_host_ops xxx_mmc_ops = { .request = xxx_mmc_request, .set_ios = xxx_mmc_set_ios, }; /* platform driver probe function */ static int xxx_mmc_probe(struct platform_device *pdev) { struct mmc_host *mmc; struct xxx_mmc_host *host = NULL; int ret = 0; /* Step 1: Allocate host structure */ // 第1步:動態分配mmc_host結構 mmc = mmc_alloc_host(sizeof(struct xxx_mmc_host), &pdev->dev); if (mmc == NULL) { pr_err("alloc host failed\n"); ret = -ENOMEM; goto err_alloc_host; } // pointer initialization host = mmc_priv(mmc); host->mmc = mmc; host->id = pdev->id; /* Step 2: Initialize struct mmc_host */ // 第2步:初始化mmc_host的結構成員 mmc->ops = &xxx_mmc_ops; mmc->f_min = 400000; mmc->f_max = 52000000; mmc->ocr_avail = MMC_VDD_32_33; mmc->caps = MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE | MMC_CAP_MMC_HIGHSPEED; mmc->caps2 = MMC_CAP2_BOOTPART_NOACC | MMC_CAP_PANIC_WRITE; mmc->max_segs = 1; mmc->max_blk_size = 512; mmc->max_req_size = 65536; // Maximum number of bytes in one req mmc->max_blk_count = mmc->max_req_size/mmc->max_blk_size; // Maximum number of blocks in one req mmc->max_seg_size = mmc->max_req_size; // Segment size in one req, in bytes host->dev = &pdev->dev; platform_set_drvdata(pdev, host); // pdev->dev->driver_data = host /* Step 3: Register the host with driver model */ // 第3步:將mmc_host註冊到驅動模型中 mmc_add_host(mmc); return 0; err_alloc_host: return ret; }
xxx_mmc_probe(pdev) 主要工做以下:
將 struct mmc_host
加入到驅動模型中。/* drivers/mmc/core/host.c */ static DEFINE_IDA(mmc_host_ida); /* mmc_alloc_host - initialise the per-host structure. */ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) { int err; struct mmc_host *host; host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); if (!host) return NULL; /* scanning will be enabled when we're ready */ host->rescan_disable = 1; // Allocate an unused ID err = ida_simple_get(&mmc_host_ida, 0, 0, GFP_KERNEL); if (err < 0) { kfree(host); return NULL; } host->index = err; dev_set_name(&host->class_dev, "mmc%d", host->index); // 此處能夠稍微注意一下,host->class_dev的類class設置爲mmc_host_class // 而且host->class_dev的parent指向了pdev->dev (platform_device) // 這些許的差別會改變device_add後在sysfs中表現出來的層次結構 host->parent = dev; // host->parent = &pdev->dev host->class_dev.parent = dev; // host->class_dev.parent = &pdev->dev host->class_dev.class = &mmc_host_class; // Initialize host->class_dev device_initialize(&host->class_dev); device_enable_async_suspend(&host->class_dev); if (mmc_gpio_alloc(host)) { put_device(&host->class_dev); return NULL; } // 初始化自旋鎖 spin_lock_init(&host->lock); // 初始化等待隊列頭 init_waitqueue_head(&host->wq); // 初始化延遲的工做隊列`host->detect`和`host->sdio_irq_work` INIT_DELAYED_WORK(&host->detect, mmc_rescan); INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work); timer_setup(&host->retune_timer, mmc_retune_timer, 0); host->max_segs = 1; host->max_seg_size = PAGE_SIZE; host->max_req_size = PAGE_SIZE; host->max_blk_size = 512; host->max_blk_count = PAGE_SIZE / 512; host->fixed_drv_type = -EINVAL; host->ios.power_delay_ms = 10; return host; }
主要工做以下:
在上述對host進行初始化後,調用 mmc_add_host() 將host註冊到驅動模型中。
/* drivers/mmc/core/host.c */ /* mmc_add_host - initialise host hardware */ int mmc_add_host(struct mmc_host *host) { int err; WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && !host->ops->enable_sdio_irq); err = device_add(&host->class_dev); if (err) return err; led_trigger_register_simple(dev_name(&host->class_dev), &host->led); #ifdef CONFIG_DEBUG_FS mmc_add_host_debugfs(host); #endif mmc_start_host(host); mmc_register_pm_notifier(host); return 0; }
主要的工做包括兩個部分:
在介紹 mmc_start_host() 以前,先簡單介紹下此處將 host->class_dev 加入到驅動模型後sysfs中表現出來的層次結構。 4.2一節中介紹 mmc_alloc_host() 時注意到 host->class_dev 的 class 被設置爲 mmc_host_class , parent 指向 pdev->dev 。 device_add()-->get_device_parent()/device_add_class_symlinks() 調用過程當中將在 platform_device 的目錄下多創建一個名稱和 class name 相同的子文件夾,同時在class類目錄下也會有指向實際設備的目錄項。sysfs此時的結構以下:
/sys/devices/platform/xxx_mmc.0/mmc_host/mmc0$ ll total 0 ... ... root root 0 Sep 17 11:21 ./ ... ... root root 0 Sep 17 11:21 ../ ... ... root root 0 Sep 17 11:23 device -> ../../../xxx_mmc.0/ ... ... root root 0 Sep 17 11:23 power/ ... ... root root 0 Sep 17 11:23 subsystem -> ../../../../../class/mmc_host/ ... ... root root 4096 Sep 17 11:23 uevent /sys/class/mmc_host$ ll /sys/class/mmc_host total 0 ... ... root root 0 Sep 17 11:21 ./ ... ... root root 0 Sep 17 11:09 ../ ... ... root root 0 Sep 17 11:26 mmc0 -> ../../devices/platform/xxx_mmc.0/mmc_host/mmc0/
此時 mmc_host 結構體成員初始化狀態簡要列舉以下(詳細能夠按照驅動模型中device
註冊步驟推導):
struct mmc_host mmc { .rescan_disable = 1 // set to 0 in mmc_start_host() .index = id (allocated) .class_dev (struct dev)= { .p = { .device = &mmc.class_dev .klist_children = .deferred_probe = } . kobj = { .name = (「mmc%d」, .index) // name = 「mmc0」 .kset = devices_kset .ktype = &device_ktype INIT_LIST_HEAD(.dma_pools); mutex_init(.mutex); spin_lock_init(.devres_lock); INIT_LIST_HEAD(.devres_head); .parent = dir->kobject; /* /sys/devices/platform/xxx_mmc.0/mmc_host/mmc0 */ //struct class_dir dir = { // .class = &mmc_host_class // .kobj = { // .ktype = &class_dir_ktype // .kset = & mmc_host_class->p->glue_dirs // .parent = &pdev->dev->kobj // /* /sys/devices/platform/xxx_mmc.0/ */ // .name = 「mmc_host」 // } //} } .parent = &pdev->dev .class = &mmc_host_class = { .name = "mmc_host", .dev_release = mmc_host_classdev_release, .dev_kobj = sysfs_dev_char_kobj .p (struct subsys_private) = { .class = &mmc_host_class . glue_dirs (kset) = { .kobj = .list = .list_lock = } . subsys (struct kset) = { .kobj = { .name = 「mmc_host」 .parent = & class_kset->kobj .kset = class_kset; .ktype = &class_ktype; // create_dir(kobj) } } } }; } .parent = &pdev->dev spin_lock_init(&.lock); init_waitqueue_head(&.wq); INIT_DELAYED_WORK(&.detect, mmc_rescan); INIT_DELAYED_WORK(&.sdio_irq_work, sdio_irq_work); timer_setup(&.retune_timer, mmc_retune_timer, 0); .max_segs = 1 .max_seg_size = PAGE_SIZE //max segment size 8K .max_req_size = PAGE_SIZE .max_blk_size = 512 .max_blk_count = PAGE_SIZE / 512 .fixed_drv_type = -EINVAL .ios = { .power_delay_ms = 10 .power_mode = MMC_POWER_UP // mmc_start_host() .vdd = fls(ocr) – 1 // mmc_power_up(host, host->ocr_avail); .clock = .f_init } . ops = { .request = xxx_mmc_request, .set_ios = xxx_mmc_set_ios, } .f_min = 400000 .f_max = 52000000 .ocr_avail = MMC_VDD_32_33 = 0x00100000 .caps = MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE | MMC_CAP_MMC_HIGHSPEED; .caps2 = MMC_CAP2_BOOTPART_NOACC | MMC_CAP_PANIC_WRITE; .pm_notify.notifier_call = mmc_pm_notify };
/* drivers/mmc/core/core.c */ void mmc_start_host(struct mmc_host *host) { host->f_init = max(freqs[0], host->f_min); host->rescan_disable = 0; host->ios.power_mode = MMC_POWER_UNDEFINED; if (!(host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP)) { mmc_claim_host(host); mmc_power_up(host, host->ocr_avail); mmc_release_host(host); } mmc_gpiod_request_cd_irq(host); _mmc_detect_change(host, 0, false); }
P
時,將完成 claim_host() 、 power_up() 、 release_host() 等一系列工做。mmc_claim_host(host) 函數用於申請得到host(主控制器)的使用權,進程將進入休眠等待狀態,直至能夠得到主控制器的使用權。該函數結合 mmc_release_host(host) ,利用等待隊列實現,原理細節能夠參考:Linux等待隊列(Wait Queue)。
static void _mmc_detect_change(struct mmc_host *host, unsigned long delay, bool cd_irq) { if (cd_irq && !(host->caps & MMC_CAP_NEEDS_POLL) && device_can_wakeup(mmc_dev(host))) pm_wakeup_event(mmc_dev(host), 5000); host->detect_change = 1; mmc_schedule_delayed_work(&host->detect, delay); } static int mmc_schedule_delayed_work(struct delayed_work *work, unsigned long delay) { return queue_delayed_work(system_freezable_wq, work, delay); }
_mmc_detect_change() 函數用來檢測MMC狀態的改變,具體是經過調度工做隊列實現,如4.2一節介紹, mmc_rescan() 做爲處理函數被綁定在延遲工做隊列 host->detect 上。所以,此處其實是啓動 mmc_rescan() 的執行過程。
void mmc_rescan(struct work_struct *work) { // 經過host->detect指針獲得mmc_host結構體指針 struct mmc_host *host = container_of(work, struct mmc_host, detect.work); // 若是rescan被禁止,函數提早返回 if (host->rescan_disable) return; // 對於不可移除(non-removable)的host,若是其正在作rescan工做時,函數提早返回(scan只作一次) if (!mmc_card_is_removable(host) && host->rescan_entered) return; // 基本檢查經過,進入rescan流程,標記rescan_entered host->rescan_entered = 1; ... ... // 檢查可移除(removable) host是否還存在 if (host->bus_ops && !host->bus_dead && mmc_card_is_removable(host)) host->bus_ops->detect(host); host->detect_change = 0; ... ... // 嘗試得到host的使用權,實現原理在4.4小節中有說起 mmc_claim_host(host); ... ... // rescan流程的關鍵步驟,依次嘗試四個給定頻率,直至檢測到mmc card的存在 // static const unsigned freqs[] = { 400000, 300000, 200000, 100000 } for (i = 0; i < ARRAY_SIZE(freqs); i++) { if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) break; if (freqs[i] <= host->f_min) break; } mmc_release_host(host); ... ... }
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; // 完成一系列初始化步驟,保證設備在合適的運行狀態,爲後面實際探測作準備 mmc_power_up(host, host->ocr_avail); mmc_hw_reset_for_init(host); ... ... mmc_go_idle(host); if (!(host->caps2 & MMC_CAP2_NO_SD)) mmc_send_if_cond(host, host->ocr_avail); /* Order's important: probe SDIO, then SD, then MMC */ // 依次探測設備:SDIO,SD,MMC // 對於MMC設備,嘗試調用mmc_attach_mmc(host) if (!(host->caps2 & MMC_CAP2_NO_SDIO)) if (!mmc_attach_sdio(host)) return 0; if (!(host->caps2 & MMC_CAP2_NO_SD)) if (!mmc_attach_sd(host)) return 0; if (!(host->caps2 & MMC_CAP2_NO_MMC)) if (!mmc_attach_mmc(host)) return 0; mmc_power_off(host); return -EIO; }
mmc_rescan(&host->detect) 會調用 mmc_rescan_try_freq(host, max(freqs[i], host->f_min)) ,進一步調用重要的 mmc_attach_mmc(host) 函數,將MMC設備加入到驅動模型中。
// mmc_bus相關的一系列operations函數 static const struct mmc_bus_ops mmc_ops = { .remove = mmc_remove, .detect = mmc_detect, .suspend = mmc_suspend, .resume = mmc_resume, .runtime_suspend = mmc_runtime_suspend, .runtime_resume = mmc_runtime_resume, .alive = mmc_alive, .shutdown = mmc_shutdown, .hw_reset = _mmc_hw_reset, }; // MMC card初始化的入口函數 int mmc_attach_mmc(struct mmc_host *host) { int err; u32 ocr, rocr; // 首先檢查host的使用權是否已經得到 WARN_ON(!host->claimed); /* Set correct bus mode for MMC before attempting attach */ if (!mmc_host_is_spi(host)) mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); // OCR register得到,可參考MMC設備操做規範 err = mmc_send_op_cond(host, 0, &ocr); // 將host結構成員bus_ops設置爲mmc_ops mmc_attach_bus(host, &mmc_ops); ... ... // 爲host選擇合適的工做電壓 rocr = mmc_select_voltage(host, ocr); ... ... // 關鍵步驟1:開始初始化MMC card的流程 err = mmc_init_card(host, rocr, NULL); // MMC card初始化後釋放host的使用權,起初在mmc_rescan()函數中得到 mmc_release_host(host); // 關鍵步驟2:將MMC card註冊進設備驅動模型中 err = mmc_add_card(host->card); mmc_claim_host(host); return 0; ... ... }
mmc_attach_mmc(&host) 做爲MMC card檢測和初始化的關鍵函數,執行的步驟可歸納爲:
static struct device_type mmc_type = { .groups = mmc_std_groups, }; static int mmc_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard) { struct mmc_card *card; int err; u32 cid[4]; u32 rocr; WARN_ON(!host->claimed); ... ... mmc_go_idle(host); /* The extra bit indicates that we support high capacity */ err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr); ... ... err = mmc_send_cid(host, cid); if (oldcard) { ... card = oldcard; } else { // 動態分配mmc_card結構 card = mmc_alloc_card(host, &mmc_type); card->ocr = ocr; card->type = MMC_TYPE_MMC; card->rca = 1; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); } ... ... ... ... }
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; }
mmc_init_mmc(&host, rocr, NULL) 會調用 mmc_alloc_card(host, &mmc_type) 動態分配一個 struct mmc_card 結構,並初始化其內部的 struct device 結構。此外, mmc_init_mmc() 中還完成了許多初始化工做,這些工做大可能是依據MMC操做規範定義的,在此不詳細介紹。
struct mmc_card 結構成員大體列舉以下,以方便分析。
struct mmc_card card = { .host = &mmc; .dev = { .parent = mmc_classdev(mmc) = &(mmc.class_dev); .bus = &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, }; .release = mmc_release_card; .type = &mmc_type = { .groups = mmc_std_groups, }; } .ocr = rocr; .type = MMC_TYPE_MMC; .rca = 1; // relative card address of device };
int mmc_add_card(struct mmc_card *card) { int ret; ... ... // #define mmc_hostname(x) (dev_name(&(x)->class_dev)) // 爲card->dev設置名稱「mmc0:0001」,實際上設置了card->dev->kobj.name = 「mmc0:0001」 dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca); ... ... card->dev.of_node = mmc_of_find_child_device(card->host, 0); device_enable_async_suspend(&card->dev); // 關鍵步驟:經過device_add() 將mmc card註冊到設備驅動模型中 ret = device_add(&card->dev); // 標記mmc card狀態爲PRESENT,card->state = MMC_STATE_PRESENT mmc_card_set_present(card); return 0; }
這裏最關鍵的一步是熟悉的 device_add(&card->dev) 函數,它將mmc card添加到驅動模型中。注意到 card.dev 的 parent 被設置爲 mmc.class_dev ,因此將在前述host的目錄層次下創建新的名爲 mmc0:0001 的 card 子目錄,而在調用 bus_add_device() 時,在mmc bus目錄下子目錄 devices 創建相應的連接,連接到上述 mmc0:0001 的設備目錄上。sysfs的總體目錄層次表現以下:
/sys/devices/platform/xxx_mmc.0/mmc_host/mmc0/mmc0:0001$ ll total 0 ... ... block/ ... ... ... ... driver -> ../../../../../../bus/mmc/drivers/mmcblk/ ... ... ... ... subsystem -> ../../../../../../bus/mmc/ ... ... ... ... uevent /sys/bus/mmc/devices$ ll ... ... mmc0:0001 -> ../../../devices/platform/xxx_mmc.0/mmc_host/mmc0/mmc0:0001/ /sys/bus/mmc/drivers/mmcblk$ ll ... ... mmc0:0001 -> ../../../../devices/platform/xxx_mmc.0/mmc_host/mmc0/mmc0:0001/ /sys/class/mmc_host$ ll ... ... mmc0 -> ../../devices/platform/klm_emmc.0/mmc_host/mmc0/
按照Linux驅動模型,接下來要完成的是設備和驅動在總線上的匹配工做,最終調用驅動的probe()
函數。調用的函數依次是:
mmc_bus_probe(&card.dev) --> mmc_blk_probe(&card)
Linux塊設備驅動初始化通常包括以下幾個方面:
第3節MMC驅動註冊中,函數 mmc_blk_init() 調用 register_blkdev(MMC_BLOCK_MAJOR, "mmc") 完成了設備號的申請,將塊設備註冊到內核中。下文將從 mmc_blk_probe(card)入手分析其餘幾個步驟的實現。
struct mmc_blk_data { struct device *parent; struct gendisk *disk; struct mmc_queue queue; struct list_head part; struct list_head rpmbs; unsigned int flags; unsigned int usage; unsigned int read_only; unsigned int part_type; unsigned int reset_done; ... ... };
static int mmc_blk_probe(struct mmc_card *card) { struct mmc_blk_data *md, *part_md; char cap_str[10]; ... ... card->complete_wq = alloc_workqueue("mmc_complete", WQ_MEM_RECLAIM | WQ_HIGHPRI, 0); // 分配和初始化gendisk結構,初始化請求隊列 md = mmc_blk_alloc(card); string_get_size((u64)get_capacity(md->disk), 512, STRING_UNITS_2, cap_str, sizeof(cap_str)); if (mmc_blk_alloc_parts(card, md)) goto out; dev_set_drvdata(&card->dev, md); // 添加gendisk if (mmc_add_disk(md)) goto out; list_for_each_entry(part_md, &md->part, part) { if (mmc_add_disk(part_md)) goto out; } ... return 0; ... }
mmc_blk_probe(card) 爲MMC card完成全部塊設備驅動有關的工做,主要調用了兩個重要的函數:
mmc_add_disk(md)
mmc_blk_alloc(card) 爲MMC card初始化請求隊列,函數調用過程爲:
mmc_blk_alloc(card) --> mmc_blk_alloc_req() --> mmc_init_queue(&md->queue, card) --> blk_mq_init_queue()
static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) { sector_t size; if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) { size = card->ext_csd.sectors; } else { size = (typeof(sector_t))card->csd.capacity << (card->csd.read_blkbits - 9); } return mmc_blk_alloc_req(card, &card->dev, size, false, NULL, MMC_BLK_DATA_AREA_MAIN); }
static const struct block_device_operations mmc_bdops = { .open = mmc_blk_open, .release = mmc_blk_release, .getgeo = mmc_blk_getgeo, .owner = THIS_MODULE, .ioctl = mmc_blk_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = mmc_blk_compat_ioctl, #endif }; static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, struct device *parent, sector_t size, bool default_ro, const char *subname, int area_type) { struct mmc_blk_data *md; int devidx, ret; devidx = ida_simple_get(&mmc_blk_ida, 0, max_devices, GFP_KERNEL); ... ... md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL); md->area_type = area_type; md->read_only = mmc_blk_readonly(card); // 分配gendisk結構 md->disk = alloc_disk(perdev_minors); INIT_LIST_HEAD(&md->part); INIT_LIST_HEAD(&md->rpmbs); md->usage = 1; // 初始化請求隊列 ret = mmc_init_queue(&md->queue, card); md->queue.blkdata = md; // 初始化gendisk結構成員 md->disk->major = MMC_BLOCK_MAJOR; md->disk->first_minor = devidx * perdev_minors; // 爲MMC card定義了block_device_operations結構體 md->disk->fops = &mmc_bdops; md->disk->private_data = md; md->disk->queue = md->queue.queue; md->parent = parent; set_disk_ro(md->disk, md->read_only || default_ro); md->disk->flags = GENHD_FL_EXT_DEVT; if (area_type & (MMC_BLK_DATA_AREA_RPMB | MMC_BLK_DATA_AREA_BOOT)) md->disk->flags |= GENHD_FL_NO_PART_SCAN | GENHD_FL_SUPPRESS_PARTITION_INFO; snprintf(md->disk->disk_name, sizeof(md->disk->disk_name), "mmcblk%u%s", card->host->index, subname ? subname : ""); set_capacity(md->disk, size); ... ... return md; ... ... }
值得注意的是, mmc_blk_alloc_req() 函數中爲將MMC card gendisk結構體的fops賦值爲 mmc_bdops ,即爲MMC card定義了 open , release , getgeo , ioctl 等操做的回調函數。
static const struct blk_mq_ops mmc_mq_ops = { .queue_rq = mmc_mq_queue_rq, .init_request = mmc_mq_init_request, .exit_request = mmc_mq_exit_request, .complete = mmc_blk_mq_complete, .timeout = mmc_mq_timed_out, }; int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card) { struct mmc_host *host = card->host; int ret; mq->card = card; mq->use_cqe = host->cqe_enabled; spin_lock_init(&mq->lock); memset(&mq->tag_set, 0, sizeof(mq->tag_set)); // mq->tag_set.ops設置爲mmc_mq_ops mq->tag_set.ops = &mmc_mq_ops; ... ... mq->tag_set.driver_data = mq; ret = blk_mq_alloc_tag_set(&mq->tag_set); if (ret) return ret; // 初始化請求隊列 mq->queue = blk_mq_init_queue(&mq->tag_set); ... ... mq->queue->queuedata = mq; blk_queue_rq_timeout(mq->queue, 60 * HZ); mmc_setup_queue(mq, card); return 0; ... ... }
struct request_queue *blk_mq_init_queue(struct blk_mq_tag_set *set) { struct request_queue *uninit_q, *q; uninit_q = blk_alloc_queue_node(GFP_KERNEL, set->numa_node); q = blk_mq_init_allocated_queue(set, uninit_q); return q; }
struct request_queue *blk_mq_init_allocated_queue(struct blk_mq_tag_set *set, struct request_queue *q) { // 用set->ops (mmc_mq_ops)來初始化request_queue的mq_ops成員 q->mq_ops = set->ops; ... ... blk_queue_make_request(q, blk_mq_make_request); ... ... }
mmc_init_queue(&md->queue, card) 最終調用 blk_queue_make_request(q, blk_mq_make_request) 初始化了請求隊列(製造請求函數)。
另外,也使用 mmc_mq_ops 初始化了 request_queue 的 mq_ops 成員,下文會提到。
至此,塊設備驅動註冊工做基本完成。
4.1節介紹MMX設備初始化時提到,驅動編寫過程當中特別地爲host編寫了 mmc_host_ops ,而至目前仍未介紹到其被使用的地方,本節將從請求隊列入手,經過一個簡單的情形,分析 mmc_host_ops 操做函數的回調過程。
當對mmc card發起塊設備I/O動做時,內核會首先調用到以前初始化的 blk_mq_make_request() 函數(定義在 block/blk-mq.c ),在特定狀況下調用 blk_mq_try_issue_directly() ,最終一步步調用到 __blk_mq_issue_directly() 。
static blk_qc_t blk_mq_make_request(struct request_queue *q, struct bio *bio) { ... blk_mq_try_issue_directly(data.hctx, rq, &cookie); .. }
static void blk_mq_try_issue_directly(struct blk_mq_hw_ctx *hctx, struct request *rq, blk_qc_t *cookie) { ... ret = __blk_mq_try_issue_directly(hctx, rq, cookie, false, true); if (ret == BLK_STS_RESOURCE || ret == BLK_STS_DEV_RESOURCE) blk_mq_request_bypass_insert(rq, true); else if (ret != BLK_STS_OK) blk_mq_end_request(rq, ret); ... }
static blk_status_t __blk_mq_try_issue_directly(struct blk_mq_hw_ctx *hctx, struct request *rq, blk_qc_t *cookie, bool bypass_insert, bool last) { ... return __blk_mq_issue_directly(hctx, rq, cookie, last); ... }
static blk_status_t __blk_mq_issue_directly(struct blk_mq_hw_ctx *hctx, struct request *rq, blk_qc_t *cookie, bool last) { struct request_queue *q = rq->q; ... ret = q->mq_ops->queue_rq(hctx, &bd); ... return ret; }
5.3一節中提到 q->mq_ops 指向 mmc_mq_ops ,所以此處最終會回調函數 mmc_mq_ops->queue_rq() ,即 mmc_mq_queue_rq() ,最終回調至 mmc_host_ops->request(host, mrq) 。
mmc_mq_queue_rq() --> mmc_blk_mq_issue_rq() --> mmc_blk_mq_issue_rw_rq() --> mmc_start_request() --> __mmc_start_request() --> host->ops->request(host, mrq)
static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd) { ... issued = mmc_blk_mq_issue_rq(mq, req); ... }
enum mmc_issued mmc_blk_mq_issue_rq(struct mmc_queue *mq, struct request *req) { ... ret = mmc_blk_mq_issue_rw_rq(mq, req); ... }
static int mmc_blk_mq_issue_rw_rq(struct mmc_queue *mq, struct request *req) { struct mmc_queue_req *mqrq = req_to_mmc_queue_req(req); struct mmc_host *host = mq->card->host; ... ... err = mmc_start_request(host, &mqrq->brq.mrq); ... ... return err; }
int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) { int err; init_completion(&mrq->cmd_completion); mmc_retune_hold(host); ... WARN_ON(!host->claimed); err = mmc_mrq_prep(host, mrq); ... __mmc_start_request(host, mrq); return 0; }
static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) { int err; ... err = mmc_retune(host); ... host->ops->request(host, mrq); }
綜合上述分析,能夠將MMC驅動子系統流程分析歸納爲下圖。從驅動編寫的角度,只需關注MMC card設備註冊相關代碼,主要包含以下幾個方面:
[1] Linux設備驅動開發詳解(基於最新的Linux4.0內核),宋寶華編著,2016年
[2] Linux SD/MMC/SDIO驅動分析:http://www.javashuo.com/article/p-rslduppo-mp.html