Linuxのmmcドライバ概要


Linux Kernelのmmcドライバを調べる機会があったので、メモ代わりに概要をまとめてみたいと思います。
(Kernelバージョンは4.1をベースに記述しています)

はじめに

mmcドライバとは、Linuxのストレージデバイスドライバの一つです。
eMMCやSDカードといったデバイスをLinuxで扱うためのドライバとなっています。

以下のような処理を担っています。

  • ハードの初期化処理
  • 接続されているデバイスの認識やスピードモードの設定
    • SDカードか、eMMCか
    • SDホストコントローラの認識
  • ファイルシステムや上位層からのデータ転送要求の処理

mmcドライバの構造

mmcドライバのソースは、以下に格納されています。

(Kernelソースディレクトリ)
  └ drivers
   └ mmc
    ├ card
    ├ core
    └ host

mmcドライバは下図のように構成されています。

mmcブロック・キュードライバ

この部分では、上位のブロックI/O処理部から受けた要求を、キュードライバがキューで管理します。
そして、ブロックドライバはキューからピックアップされた要求のタイプ(read,write,eraseなど)を解析し、更に下のmmcコア部に渡します。

mmcコア、SDカード/eMMC処理部

mmcコア部は上位から受け取った要求の対象が、SDカードかeMMCかを判別し、SDカード/eMMC処理部に対してリクエストを渡します。

SDカードとeMMCではカードの認識やスピードモードの設定など、細かな部分で処理が異なっていますので、SDカード/eMMC処理部で、上位からの要求をそれぞれに対応した処理に換えて、下位のSDホストコントローラ共通処理部や機種依存部の実装を呼び出し、ホストコントローラを制御します。

SDホストコントローラ共通処理部/機種依存部

SDホストコントローラのレジスタを叩いて操作している箇所がここになります。
SDホストコントローラは規格化されており、レジスタ構造等は基本的に同一になっていますので、規格に沿った機種を使用している場合は、ほぼ共通処理部で処理できます。
機種依存部は、ハードの初期化や、その名の通り機種によって共通処理部では対応できない処理を担います。

mmcドライバでは、機種依存の処理を共通部に渡すために、以下の2つの構造体が用意されています。各メンバには共通処理部で実装されている関数を使用するか、機種依存部で実装している関数を使用するかを選択することができます。

include/linux/mmc/host.h
struct mmc_host_ops {
    /*
     * It is optional for the host to implement pre_req and post_req in
     * order to support double buffering of requests (prepare one
     * request while another request is active).
     * pre_req() must always be followed by a post_req().
     * To undo a call made to pre_req(), call post_req() with
     * a nonzero err condition.
     */
    void    (*post_req)(struct mmc_host *host, struct mmc_request *req,
                int err);
    void    (*pre_req)(struct mmc_host *host, struct mmc_request *req,
               bool is_first_req);
    void    (*request)(struct mmc_host *host, struct mmc_request *req);
    /*
     * Avoid calling these three functions too often or in a "fast path",
     * since underlaying controller might implement them in an expensive
     * and/or slow way.
     *
     * Also note that these functions might sleep, so don't call them
     * in the atomic contexts!
     *
     * Return values for the get_ro callback should be:
     *   0 for a read/write card
     *   1 for a read-only card
     *   -ENOSYS when not supported (equal to NULL callback)
     *   or a negative errno value when something bad happened
     *
     * Return values for the get_cd callback should be:
     *   0 for a absent card
     *   1 for a present card
     *   -ENOSYS when not supported (equal to NULL callback)
     *   or a negative errno value when something bad happened
     */
    void    (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
    int (*get_ro)(struct mmc_host *host);
    int (*get_cd)(struct mmc_host *host);

    void    (*enable_sdio_irq)(struct mmc_host *host, int enable);

    /* optional callback for HC quirks */
    void    (*init_card)(struct mmc_host *host, struct mmc_card *card);

    int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);

    /* Check if the card is pulling dat[0:3] low */
    int (*card_busy)(struct mmc_host *host);

    /* The tuning command opcode value is different for SD and eMMC cards */
    int (*execute_tuning)(struct mmc_host *host, u32 opcode);

    /* Prepare HS400 target operating frequency depending host driver */
    int (*prepare_hs400_tuning)(struct mmc_host *host, struct mmc_ios *ios);
    int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv);
    void    (*hw_reset)(struct mmc_host *host);
    void    (*card_event)(struct mmc_host *host);

    /*
     * Optional callback to support controllers with HW issues for multiple
     * I/O. Returns the number of supported blocks for the request.
     */
    int (*multi_io_quirk)(struct mmc_card *card,
                  unsigned int direction, int blk_size);
};
drivers/mmc/host/sdhci.h
struct sdhci_ops {
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
    u32     (*read_l)(struct sdhci_host *host, int reg);
    u16     (*read_w)(struct sdhci_host *host, int reg);
    u8      (*read_b)(struct sdhci_host *host, int reg);
    void        (*write_l)(struct sdhci_host *host, u32 val, int reg);
    void        (*write_w)(struct sdhci_host *host, u16 val, int reg);
    void        (*write_b)(struct sdhci_host *host, u8 val, int reg);
#endif

    void    (*set_clock)(struct sdhci_host *host, unsigned int clock);

    int     (*enable_dma)(struct sdhci_host *host);
    unsigned int    (*get_max_clock)(struct sdhci_host *host);
    unsigned int    (*get_min_clock)(struct sdhci_host *host);
    unsigned int    (*get_timeout_clock)(struct sdhci_host *host);
    unsigned int    (*get_max_timeout_count)(struct sdhci_host *host);
    void        (*set_timeout)(struct sdhci_host *host,
                       struct mmc_command *cmd);
    void        (*set_bus_width)(struct sdhci_host *host, int width);
    void (*platform_send_init_74_clocks)(struct sdhci_host *host,
                         u8 power_mode);
    unsigned int    (*get_ro)(struct sdhci_host *host);
    void        (*reset)(struct sdhci_host *host, u8 mask);
    int (*platform_execute_tuning)(struct sdhci_host *host, u32 opcode);
    void    (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);
    void    (*hw_reset)(struct sdhci_host *host);
    void    (*adma_workaround)(struct sdhci_host *host, u32 intmask);
    void    (*platform_init)(struct sdhci_host *host);
    void    (*card_event)(struct sdhci_host *host);
    void    (*voltage_switch)(struct sdhci_host *host);
};

このような構造になっているため、ドライバ開発者は共通部に手を入れずに、機種依存部のみを記述することでドライバの実装ができるようになっています。

参考資料

Linux Kernel MMC Storage driver Overview
https://www.slideshare.net/rampalliraj/linux-kernel-mmc-storage-driver-overview