1. 程式人生 > >Linux MMC 驅動子系統簡述(原始碼剖析)

Linux MMC 驅動子系統簡述(原始碼剖析)

1. Linux MMC 驅動子系統

塊裝置是Linux系統中的基礎外設之一,而 MMC/SD 儲存裝置是一種典型的塊裝置。Linux核心設計了 MMC子系統,用於管理 MMC/SD 裝置。

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裝置內部實際的儲存裝置。

Linux系統中,使用兩個結構體 struct mmc_host 和 struct mmc_card 分別描述host和card,其中host裝置被封裝成 platform_device 註冊到Linux驅動模型中。整體而言,(Linux驅動模型框架下)MMC驅動子系統包括三個部分:

  • MMC匯流排( mmc_bus )
  • 封裝在 platform_device下的host裝置
  • 依附於MMC匯流排的MMC驅動( mmc_driver )

下文將通過核心原始碼(Linux Kernel 5.2)對MMC驅動子系統進行簡述,並通過MMC驅動的實際案例說明MMC驅動編寫的一般步驟,同時分析驅動模型下完成驅動、裝置繫結的過程。如對Linux裝置驅動模型不熟悉,可以參考另一篇博文:Linux裝置驅動模型簡述(原始碼剖析)。

 

2. MMC 匯流排的註冊

MMC匯流排的註冊和 platform 匯流排的註冊方法相同,均是呼叫 bus_register() 函式。函式的呼叫入口位於 mmc/core/core.c ,通過 mmc_init() 實現,此處主要關注MMC的部分。

/*  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);
}

主要包括兩個方面:

  • 利用 bus_register() 註冊 mmc_bus 。對應sysfs下的 /sys/bus/mmc/ 目錄。
  • 利用 class_register() 註冊 mmc_host_class 。對應sysfs下的 /sys/class/mmc_host 目錄。

 

3. MMC 驅動(mmc_driver)的註冊

 drivers/mmc/core/block.c 中將 mmc_driver 註冊到 mmc_bus 對應的匯流排系統裡。主要步驟包括:

  • 通過 register_blkdev() 向核心註冊塊裝置。
  • 呼叫 driver_register() 將 mmc_driver 註冊到 mmc_bus 匯流排系統。和其他驅動註冊方式一致。

 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);
}

 

4. MMC 裝置的註冊

前文已經簡單描述過,MMC裝置主要包括主裝置host和從裝置card兩部分,而主裝置host將被封裝在 platform_device 中註冊到驅動模型中。
為了更加清晰地描述此部分的註冊過程,下文將以一個驅動為例分析(此驅動原始碼只包含關鍵步驟程式碼,只為描述MMC驅動的編寫基本框架,demo mmc driver)。

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() 。

4.1 xxx_mmc_probe(pdev)

// 自定義的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) 主要工作如下:

  • 呼叫 mmc_alloc_host() 分配一個 struct mmc_host 結構。
  • 對 struct mmc_host 結構體成員初始化。
  • 呼叫 mmc_add_host() 將 struct mmc_host 加入到驅動模型中。

4.2 mmc_alloc_host(sizeof(struct xxx_mmc_host), &pdev->dev)

/*  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;
}

主要工作如下:

  • 動態分配記憶體給 struct mmc_host 結構體,並對結構體成員初始化。
  • 呼叫 device_initialize() 對 host->class_dev 進行初始化,包括 kobject 、 mutex 等。
  • 初始化自旋鎖、等待佇列 (waitqueue)和延遲的工作佇列 (Delayed Work),其中,用處理函式 mmc_rescan() 來初始化延遲的工作佇列 host->detect ,後文會再次提到。
  • 初始化定時器 host->retune_timer ,處理函式為 mmc_retune_timer() 。

4.3 mmc_add_host(mmc)

在上述對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;
}

主要的工作包括兩個部分:

  •  device_add() 將 host->class_dev 加入到sysfs中device層次結構中。
  • 呼叫 mmc_start_host() 啟動主裝置,也即MMC裝置開始正常工作。

在介紹 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
};
View Code

4.4 mmc_add_host(mmc)

/*  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);
}
  •  host->rescan_disable = 0 使能主裝置的重新檢測。
  • 未使能 MMC_CAP2_NO_PRESCAN_POWERU P時,將完成 claim_host() 、 power_up() 、 release_host() 等一系列工作。
  •  mmc_gpiod_request_cd_irq() 用於為host申請中斷號(和GPIO口對應),並繫結中斷服務函式。
  •  _mmc_detect_change(host, 0, false) 用於檢測MMC槽位上的變動。

 mmc_claim_host(host) 函式用於申請獲得host(主控制器)的使用權,程序將進入休眠等待狀態,直至可以獲得主控制器的使用權。該函式結合 mmc_release_host(host) ,利用等待佇列實現,原理細節可以參考:Linux等待佇列(Wait Queue)。

4.5 _mmc_detect_change(host, 0, false)

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() 的執行過程。

4.6 mmc_rescan(&host->detect)

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裝置加入到驅動模型中。

4.7 mmc_attach_mmc(&host)

// 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檢測和初始化的關鍵函式,執行的步驟可概括為:

  • 獲取MMC基本硬體初始化資訊,例如OCR register (工作電壓相關)
  • 初始化host->bus_ops成員, host->bus_ops = &mmc_ops 
  • mmc_init_card(host, rocr, NULL) :MMC card初始化,下文詳細介紹
  • mmc_add_card(host->card) :將MMC card加入到裝置驅動模型中,下文將詳細介紹

4.8 mmc_init_mmc(&host, rocr, NULL)

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
};

 

4.9 mmc_add_card(host->card)

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)

 

5. 塊裝置裝置驅動

Linux塊裝置驅動初始化一般包括如下幾個方面:

  • 申請裝置號,並將塊裝置驅動註冊到核心。 register_blkdev() 
  • 初始化請求佇列。 blk_init_queue() / blk_alloc_queue() 
  • 分配gendisk結構,並進行初始化。 alloc_disk() 
  • 新增gendisk。 add_disk() 

第3節MMC驅動註冊中,函式 mmc_blk_init() 呼叫 register_blkdev(MMC_BLOCK_MAJOR, "mmc") 完成了裝置號的申請,將塊設備註冊到核心中。下文將從 mmc_blk_probe(card)入手分析其他幾個步驟的實現。

5.1 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_blk_alloc(card) 
  •  mmc_add_disk(md) 

5.2 mmc_blk_alloc(card)

 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 等操作的回撥函式。

5.3 mmc_init_queue(&md->queue, card)

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 成員,下文會提到。
至此,塊裝置驅動註冊工作基本完成。

 

6. 請求佇列的工作流程梳理

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);
}

 

7. 總結

綜合上述分析,可以將MMC驅動子系統流程分析概括為下圖。從驅動編寫的角度,只需關注MMC card設備註冊相關程式碼,主要包含如下幾個方面:

  • 將mmc host封裝進一個 platform device ,同時定義一 platform driver ,並將它們註冊到驅動模型中
  •  platform driver 的 probe() 函式中呼叫 mmc_alloc_host() 和 mmc_add_host() ,將mmc card註冊到驅動模型中

 

 

參考資料

[1] Linux裝置驅動開發詳解(基於最新的Linux4.0核心),宋寶華編著,2016年
[2] Linux SD/MMC/SDIO驅動分析:https://www.cnblogs.com/cslunatic/p/3678045.