LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Linuxカーネルのファイルアクセスの処理を追いかける (20) PL181: mmci_probe

関連記事

概要

QEMUの vexpress-a9 (arm) で Linux 5.15を起動させながら、ファイル書き込みのカーネル処理を確認していく。

本章では、MMCサブシステムの初期化処理について確認した。

はじめに

ユーザプロセスはファイルシステムという機構によって記憶装置上のデータをファイルという形式で書き込み・読み込みすることができる。
本調査では、ユーザプロセスがファイルに書き込み要求を実行したときにLinuxカーネルではどのような処理が実行されるかを読み解いていく。

調査対象や環境などはPart 1: 環境セットアップを参照。

注意

一部の仕様書は非公開となっているため、公開情報からの推測が含まれています。そのため、内容に誤りが含まれている恐れがります。

Device Tree

Open Firmware Device Tree (DT) は、システムのハードウェア構成を記述するためのデータ構造である。 一般的に使われている PC (x86 および x86_64) では別の仕組み (ACPI) によって解決することが多いが、シングルボードコンピュータ (ARM) では DT によって情報を解決することが多い。 Linux では システムのハードウェア情報を DT として切り出すことによって kernel や ドライバ を共通化している。

DT では、各ノードにプロパティが定義できる木構造で表現される。 例えば、デュアルコアの cortex-a9 と I2Cデバイス (PCIe Switch)と MMCI が搭載しているシステムを考えてみる。

Device Tree 概念図

MMC に関連するノードに注目すると、ARM PrimeCell Multimedia Card Interface (PL180/1) ドライバ にプロパティregclocks を渡すことを表現している。 これらのプロパティに何が指定できるかについては、ドライバ毎によって異なる。 kernel に組み込まれているドライバについては、 Documentation/devicetree/bindings 以下にドキュメントが用意されている。

PL180/1 ドライバは Documentation/devicetree/bindings/mmc/arm,pl18x.yaml に記載されている。 例えば、reg については次のような記載がされている。

// 68:
  reg:
    description: the MMIO memory window must be exactly 4KB (0x1000) and the
      layout should provide the PrimeCell ID registers so that the device can
      be discovered. On ST Micro variants, a second register window may be
      defined if a delay block is present and used for tuning.

上記の場合では、PL180/1ドライバに対して次のデータを渡している。

  • 親ノード / からの相対アドレス 0x5000 から 0x1000 byte を MMIO memory window
  • Advanced Peripheral Bus(APB) に v2m_clk24mhz(24MHz)、MCI adapter Clock(MCLK) に smbclksmbclk(33Mhz ~ 100MHz)

QEMU Arm Versatile Express boards

起動しているLinuxマシンにロードされている DT は /proc/device-tree//sys/firmware/devicetree/base/ から確認することができる。
今回使用している vexpress-a9 (QEMU) で使用している DT は次のような木構造で表現できる。(橙色 は QEMU によって追加されたノードを表現している)

vexpress-a9(QEMU) の device tree structure

ここでは、MMCに関連するノードを抜粋して確認する。 QEMU Arm Versatile Express boards では vexpress-v2m.dtsi に MMC関連のノードが記載されている。

// 191:
                mmci@5000 {
                    compatible = "arm,pl180", "arm,primecell";
                    reg = <0x05000 0x1000>;
                    interrupts = <9>, <10>;
                    cd-gpios = <&v2m_mmc_gpios 0 0>;
                    wp-gpios = <&v2m_mmc_gpios 1 0>;
                    max-frequency = <12000000>;
                    vmmc-supply = <&v2m_fixed_3v3>;
                    clocks = <&v2m_clk24mhz>, <&smbclk>;
                    clock-names = "mclk", "apb_pclk";
                };

プロパティcompatible"arm,pl180", "arm,primecell"を指定しており、この値がデバイスドライバとの対応関係に用いられる。 QEMU Arm Versatile Express boards では、PL180/1 ドライバ で動作することになる。

MMCI の probe

PL180 は AMBA 規格に準拠した SoC のペリフェラルである。 AMBA 規格のバスに登録されているドライバは of_platform_default_populate_init()amba_deferred_retry() などによって probe 処理が呼ばれる。 この時、of_amba_device_create関数で amba_device構造体と amba_id構造体に最低限のデータを詰め込んでから渡される。 例えば、ノード名をnameに設定したり、regプロパティから先頭アドレスを設定をしたりする。

mmci の場合には次のようなデータを渡されることになる。

mmci_probe関数の引数の概要

mmci では、probe として mmci_probe関数が呼び出される。 この関数の定義は次のようになっている。

// 1990:
static int mmci_probe(struct amba_device *dev,
    const struct amba_id *id)
{
    struct mmci_platform_data *plat = dev->dev.platform_data;
    struct device_node *np = dev->dev.of_node;
    struct variant_data *variant = id->data;
    struct mmci_host *host;
    struct mmc_host *mmc;
    int ret;

    /* Must have platform data or Device Tree. */
    if (!plat && !np) {
        dev_err(&dev->dev, "No plat data or DT found\n");
        return -EINVAL;
    }

    if (!plat) {
        plat = devm_kzalloc(&dev->dev, sizeof(*plat), GFP_KERNEL);
        if (!plat)
            return -ENOMEM;
    }

    mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
    if (!mmc)
        return -ENOMEM;

    host = mmc_priv(mmc);
    host->mmc = mmc;
    host->mmc_ops = &mmci_ops;
    mmc->ops = &mmci_ops;

    ret = mmci_of_parse(np, mmc);
    if (ret)
        goto host_free;

    /*
    * Some variant (STM32) doesn't have opendrain bit, nevertheless
    * pins can be set accordingly using pinctrl
    */
    if (!variant->opendrain) {
        host->pinctrl = devm_pinctrl_get(&dev->dev);
        if (IS_ERR(host->pinctrl)) {
            dev_err(&dev->dev, "failed to get pinctrl");
            ret = PTR_ERR(host->pinctrl);
            goto host_free;
        }

        host->pins_opendrain = pinctrl_lookup_state(host->pinctrl,
                                MMCI_PINCTRL_STATE_OPENDRAIN);
        if (IS_ERR(host->pins_opendrain)) {
            dev_err(mmc_dev(mmc), "Can't select opendrain pins\n");
            ret = PTR_ERR(host->pins_opendrain);
            goto host_free;
        }
    }

    host->hw_designer = amba_manf(dev);
    host->hw_revision = amba_rev(dev);
    dev_dbg(mmc_dev(mmc), "designer ID = 0x%02x\n", host->hw_designer);
    dev_dbg(mmc_dev(mmc), "revision = 0x%01x\n", host->hw_revision);

    host->clk = devm_clk_get(&dev->dev, NULL);
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        goto host_free;
    }

    ret = clk_prepare_enable(host->clk);
    if (ret)
        goto host_free;

    if (variant->qcom_fifo)
        host->get_rx_fifocnt = mmci_qcom_get_rx_fifocnt;
    else
        host->get_rx_fifocnt = mmci_get_rx_fifocnt;

    host->plat = plat;
    host->variant = variant;
    host->mclk = clk_get_rate(host->clk);
    /*
    * According to the spec, mclk is max 100 MHz,
    * so we try to adjust the clock down to this,
    * (if possible).
    */
    if (host->mclk > variant->f_max) {
        ret = clk_set_rate(host->clk, variant->f_max);
        if (ret < 0)
            goto clk_disable;
        host->mclk = clk_get_rate(host->clk);
        dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n",
            host->mclk);
    }

    host->phybase = dev->res.start;
    host->base = devm_ioremap_resource(&dev->dev, &dev->res);
    if (IS_ERR(host->base)) {
        ret = PTR_ERR(host->base);
        goto clk_disable;
    }

    if (variant->init)
        variant->init(host);

    /*
    * The ARM and ST versions of the block have slightly different
    * clock divider equations which means that the minimum divider
    * differs too.
    * on Qualcomm like controllers get the nearest minimum clock to 100Khz
    */
    if (variant->st_clkdiv)
        mmc->f_min = DIV_ROUND_UP(host->mclk, 257);
    else if (variant->stm32_clkdiv)
        mmc->f_min = DIV_ROUND_UP(host->mclk, 2046);
    else if (variant->explicit_mclk_control)
        mmc->f_min = clk_round_rate(host->clk, 100000);
    else
        mmc->f_min = DIV_ROUND_UP(host->mclk, 512);
    /*
    * If no maximum operating frequency is supplied, fall back to use
    * the module parameter, which has a (low) default value in case it
    * is not specified. Either value must not exceed the clock rate into
    * the block, of course.
    */
    if (mmc->f_max)
        mmc->f_max = variant->explicit_mclk_control ?
                min(variant->f_max, mmc->f_max) :
                min(host->mclk, mmc->f_max);
    else
        mmc->f_max = variant->explicit_mclk_control ?
                fmax : min(host->mclk, fmax);


    dev_dbg(mmc_dev(mmc), "clocking block at %u Hz\n", mmc->f_max);

    host->rst = devm_reset_control_get_optional_exclusive(&dev->dev, NULL);
    if (IS_ERR(host->rst)) {
        ret = PTR_ERR(host->rst);
        goto clk_disable;
    }
    ret = reset_control_deassert(host->rst);
    if (ret)
        dev_err(mmc_dev(mmc), "failed to de-assert reset\n");

    /* Get regulators and the supported OCR mask */
    ret = mmc_regulator_get_supply(mmc);
    if (ret)
        goto clk_disable;

    if (!mmc->ocr_avail)
        mmc->ocr_avail = plat->ocr_mask;
    else if (plat->ocr_mask)
        dev_warn(mmc_dev(mmc), "Platform OCR mask is ignored\n");

    /* We support these capabilities. */
    mmc->caps |= MMC_CAP_CMD23;

    /*
    * Enable busy detection.
    */
    if (variant->busy_detect) {
        mmci_ops.card_busy = mmci_card_busy;
        /*
        * Not all variants have a flag to enable busy detection
        * in the DPSM, but if they do, set it here.
        */
        if (variant->busy_dpsm_flag)
            mmci_write_datactrlreg(host,
                           host->variant->busy_dpsm_flag);
        mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
    }

    /* Variants with mandatory busy timeout in HW needs R1B responses. */
    if (variant->busy_timeout)
        mmc->caps |= MMC_CAP_NEED_RSP_BUSY;

    /* Prepare a CMD12 - needed to clear the DPSM on some variants. */
    host->stop_abort.opcode = MMC_STOP_TRANSMISSION;
    host->stop_abort.arg = 0;
    host->stop_abort.flags = MMC_RSP_R1B | MMC_CMD_AC;

    /* We support these PM capabilities. */
    mmc->pm_caps |= MMC_PM_KEEP_POWER;

    /*
    * We can do SGIO
    */
    mmc->max_segs = NR_SG;

    /*
    * Since only a certain number of bits are valid in the data length
    * register, we must ensure that we don't exceed 2^num-1 bytes in a
    * single request.
    */
    mmc->max_req_size = (1 << variant->datalength_bits) - 1;

    /*
    * Set the maximum segment size.  Since we aren't doing DMA
    * (yet) we are only limited by the data length register.
    */
    mmc->max_seg_size = mmc->max_req_size;

    /*
    * Block size can be up to 2048 bytes, but must be a power of two.
    */
    mmc->max_blk_size = 1 << variant->datactrl_blocksz;

    /*
    * Limit the number of blocks transferred so that we don't overflow
    * the maximum request size.
    */
    mmc->max_blk_count = mmc->max_req_size >> variant->datactrl_blocksz;

    spin_lock_init(&host->lock);

    writel(0, host->base + MMCIMASK0);

    if (variant->mmcimask1)
        writel(0, host->base + MMCIMASK1);

    writel(0xfff, host->base + MMCICLEAR);

    /*
    * If:
    * - not using DT but using a descriptor table, or
    * - using a table of descriptors ALONGSIDE DT, or
    * look up these descriptors named "cd" and "wp" right here, fail
    * silently of these do not exist
    */
    if (!np) {
        ret = mmc_gpiod_request_cd(mmc, "cd", 0, false, 0);
        if (ret == -EPROBE_DEFER)
            goto clk_disable;

        ret = mmc_gpiod_request_ro(mmc, "wp", 0, 0);
        if (ret == -EPROBE_DEFER)
            goto clk_disable;
    }

    ret = devm_request_threaded_irq(&dev->dev, dev->irq[0], mmci_irq,
                    mmci_irq_thread, IRQF_SHARED,
                    DRIVER_NAME " (cmd)", host);
    if (ret)
        goto clk_disable;

    if (!dev->irq[1])
        host->singleirq = true;
    else {
        ret = devm_request_irq(&dev->dev, dev->irq[1], mmci_pio_irq,
                IRQF_SHARED, DRIVER_NAME " (pio)", host);
        if (ret)
            goto clk_disable;
    }

    writel(MCI_IRQENABLE | variant->start_err, host->base + MMCIMASK0);

    amba_set_drvdata(dev, mmc);

    dev_info(&dev->dev, "%s: PL%03x manf %x rev%u at 0x%08llx irq %d,%d (pio)\n",
         mmc_hostname(mmc), amba_part(dev), amba_manf(dev),
         amba_rev(dev), (unsigned long long)dev->res.start,
         dev->irq[0], dev->irq[1]);

    mmci_dma_setup(host);

    pm_runtime_set_autosuspend_delay(&dev->dev, 50);
    pm_runtime_use_autosuspend(&dev->dev);

    mmc_add_host(mmc);

    pm_runtime_put(&dev->dev);
    return 0;

 clk_disable:
    clk_disable_unprepare(host->clk);
 host_free:
    mmc_free_host(mmc);
    return ret;
}

mmci_probe関数は次のように様々な処理を行うため、それぞれ分割して確認していく。

  • mmc_host 初期化
  • DT 読み込み
  • Open Drain Bit 非サポートの対応
  • clock 設定
  • 仮想アドレスへマップ
  • variant specific 対応
  • 周波数設定
  • Regulators の取得
  • GPIO descriptor 設定
  • IRQ 設定
  • DMA Engine の設定
  • Runtime PM の autosuspend 設定
  • host 登録

mmc_host 初期化

// 2012:
    mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
    if (!mmc)
        return -ENOMEM;

    host = mmc_priv(mmc);
    host->mmc = mmc;
    host->mmc_ops = &mmci_ops;
    mmc->ops = &mmci_ops;

MMC host ドライバでは、mmc_host構造体に MMC host コントローラを管理するためのメタデータを詰め込む。 mmc_host構造体には、MMC host コントローラ毎に異なる固有のメタデータprivate に格納することができる。 PL181の場合では、pricatemmci_host構造体を固有のメタデータを詰め込む。

mmc_host構造体の初期化は mmc_alloc_host関数で実施する。

// 513:
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
    int index;
    struct mmc_host *host;
    int alias_id, min_idx, max_idx;

    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;

    alias_id = of_alias_get_id(dev->of_node, "mmc");
    if (alias_id >= 0) {
        index = alias_id;
    } else {
        min_idx = mmc_first_nonreserved_index();
        max_idx = 0;

        index = ida_simple_get(&mmc_host_ida, min_idx, max_idx, GFP_KERNEL);
        if (index < 0) {
            kfree(host);
            return NULL;
        }
    }

    host->index = index;

    dev_set_name(&host->class_dev, "mmc%d", host->index);
    host->ws = wakeup_source_register(NULL, dev_name(&host->class_dev));

    host->parent = dev;
    host->class_dev.parent = dev;
    host->class_dev.class = &mmc_host_class;
    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);
    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);

    /*
    * By default, hosts do not support SGIO or large requests.
    * They have to set these according to their abilities.
    */
    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;
    host->ios.power_mode = MMC_POWER_UNDEFINED;

    return host;
}

mmc_alloc_host関数には、固有のメタデータのサイズ extraと デバイスメタデータ dev を受け取り、作成した mmc_host型のデータを返す。 この時、mmc_host構造体に最低限度の初期設定を施しているが、ここではすべてを確認することはしない。

mmc_alloc_host関数の成果物

DT 読み込み

// 2021:
    ret = mmci_of_parse(np, mmc);
    if (ret)
        goto host_free;

先述の通り、一部のパラメータ(コントローラ固有) はドライバから DT に切り出している。 MMCホストコントローラは、DT に記載されたパラメータを mmci_of_parse関数によって取得する。

mmci_of_parse関数の定義は次のようになっている。

// 1955:
static int mmci_of_parse(struct device_node *np, struct mmc_host *mmc)
{
    struct mmci_host *host = mmc_priv(mmc);
    int ret = mmc_of_parse(mmc);

    if (ret)
        return ret;

    if (of_get_property(np, "st,sig-dir-dat0", NULL))
        host->pwr_reg_add |= MCI_ST_DATA0DIREN;
    if (of_get_property(np, "st,sig-dir-dat2", NULL))
        host->pwr_reg_add |= MCI_ST_DATA2DIREN;
    if (of_get_property(np, "st,sig-dir-dat31", NULL))
        host->pwr_reg_add |= MCI_ST_DATA31DIREN;
    if (of_get_property(np, "st,sig-dir-dat74", NULL))
        host->pwr_reg_add |= MCI_ST_DATA74DIREN;
    if (of_get_property(np, "st,sig-dir-cmd", NULL))
        host->pwr_reg_add |= MCI_ST_CMDDIREN;
    if (of_get_property(np, "st,sig-pin-fbclk", NULL))
        host->pwr_reg_add |= MCI_ST_FBCLKEN;
    if (of_get_property(np, "st,sig-dir", NULL))
        host->pwr_reg_add |= MCI_STM32_DIRPOL;
    if (of_get_property(np, "st,neg-edge", NULL))
        host->clk_reg_add |= MCI_STM32_CLK_NEGEDGE;
    if (of_get_property(np, "st,use-ckin", NULL))
        mmci_probe_level_translator(mmc);

    if (of_get_property(np, "mmc-cap-mmc-highspeed", NULL))
        mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
    if (of_get_property(np, "mmc-cap-sd-highspeed", NULL))
        mmc->caps |= MMC_CAP_SD_HIGHSPEED;

    return 0;
}

mmci_of_parse関数では、MMC hostコントローラ共通のパラメータを読み込む mmc_of_parse関数と、MMC hostコントローラ固有のパラメータを読み込む mmci_of_parse関数を呼び出す。 mmci_of_parse関数内でパースされるプロパティはドキュメントにも記載されている。

elixir.bootlin.com

mmc_of_parse`関数内でパースされるプロパティもドキュメントに記載されている。

elixir.bootlin.com

今回の環境では mmci_of_parse関数でパースされるパラメータは次のとおりである。

プロパティ 概要
interrupts 9, 10 コマンド割り込み と ポーリング割り込み
cd-gpios v2m_mmc_gpios 0 0 Card Detection (CD) 用の GPIO
wp-gpios v2m_mmc_gpios 1 0 Write Protect (WP) 用の GPIO
max-frequency 12000000 バスの最大動作周波数
vmmc-supply v2m_fixed_3v3 電源供給
clocks v2m_clk24mhz, smbclk "apb_pclk" と "MCLK"
clock-names mclk, apb_pclk Clock名

Open Drain Bit 非サポートの対応

// 2029:
    if (!variant->opendrain) {
        host->pinctrl = devm_pinctrl_get(&dev->dev);
        if (IS_ERR(host->pinctrl)) {
            dev_err(&dev->dev, "failed to get pinctrl");
            ret = PTR_ERR(host->pinctrl);
            goto host_free;
        }

        host->pins_opendrain = pinctrl_lookup_state(host->pinctrl,
                                MMCI_PINCTRL_STATE_OPENDRAIN);
        if (IS_ERR(host->pins_opendrain)) {
            dev_err(mmc_dev(mmc), "Can't select opendrain pins\n");
            ret = PTR_ERR(host->pins_opendrain);
            goto host_free;
        }
    }

Open Drain bit がない機種向けに pinctrl を使用することで対応することになっている。 ただし、今回はここに該当しないため詳細は省く。

clock 設定

ここで、APB と MCLK の初期設定をする。

// 2051:
    host->clk = devm_clk_get(&dev->dev, NULL);
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        goto host_free;
    }

    ret = clk_prepare_enable(host->clk);
    if (ret)
        goto host_free;

APB は devm_clk_get関数によってhost->clk に設定される。

// 2068:
    host->mclk = clk_get_rate(host->clk);
    /*
    * According to the spec, mclk is max 100 MHz,
    * so we try to adjust the clock down to this,
    * (if possible).
    */
    if (host->mclk > variant->f_max) {
        ret = clk_set_rate(host->clk, variant->f_max);
        if (ret < 0)
            goto clk_disable;
        host->mclk = clk_get_rate(host->clk);
        dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n",
            host->mclk);
    }

MCLK は host->clkと同じ周波数で設定される。

仮想アドレスへマップ

// 2083:
    host->phybase = dev->res.start;
    host->base = devm_ioremap_resource(&dev->dev, &dev->res);
    if (IS_ERR(host->base)) {
        ret = PTR_ERR(host->base);
        goto clk_disable;
    }

Linuxカーネルでは、MMUを有効化しているため、Linuxカーネルから物理アドレスにアクセスするためにはioremap関数などでマッピングする必要がある。 そこで、devm_ioremap_resource関数ではDevres(Managed Device Resource) による管理をしながら host->baseマッピングする。

ioremap関数で仮想アドレスにマッピングする

variant specific 対応

// 2090:
    if (variant->init)
        variant->init(host);

今回の環境では、mmaci_variant_opsが設定される。

// 2124:
    host->rst = devm_reset_control_get_optional_exclusive(&dev->dev, NULL);
    if (IS_ERR(host->rst)) {
        ret = PTR_ERR(host->rst);
        goto clk_disable;
    }
    ret = reset_control_deassert(host->rst);
    if (ret)
        dev_err(mmc_dev(mmc), "failed to de-assert reset\n");

STM32 sdmmc では、電源再投入時にリセット処理が必要になる。ただし、今回の環境では必要ないため省略する。

// 2149:
    if (variant->busy_detect) {
        mmci_ops.card_busy = mmci_card_busy;
        /*
        * Not all variants have a flag to enable busy detection
        * in the DPSM, but if they do, set it here.
        */
        if (variant->busy_dpsm_flag)
            mmci_write_datactrlreg(host,
                           host->variant->busy_dpsm_flag);
        mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
    }

    /* Variants with mandatory busy timeout in HW needs R1B responses. */
    if (variant->busy_timeout)
        mmc->caps |= MMC_CAP_NEED_RSP_BUSY;

ux500, STM32 では、busy_detectフラグによって HWによるビジー検出をサポートしている。ただし、今回の環境では必要ないため省略する。

周波数設定

// 2093:
    /*
    * The ARM and ST versions of the block have slightly different
    * clock divider equations which means that the minimum divider
    * differs too.
    * on Qualcomm like controllers get the nearest minimum clock to 100Khz
    */
    if (variant->st_clkdiv)
        mmc->f_min = DIV_ROUND_UP(host->mclk, 257);
    else if (variant->stm32_clkdiv)
        mmc->f_min = DIV_ROUND_UP(host->mclk, 2046);
    else if (variant->explicit_mclk_control)
        mmc->f_min = clk_round_rate(host->clk, 100000);
    else
        mmc->f_min = DIV_ROUND_UP(host->mclk, 512);
    /*
    * If no maximum operating frequency is supplied, fall back to use
    * the module parameter, which has a (low) default value in case it
    * is not specified. Either value must not exceed the clock rate into
    * the block, of course.
    */
    if (mmc->f_max)
        mmc->f_max = variant->explicit_mclk_control ?
                min(variant->f_max, mmc->f_max) :
                min(host->mclk, mmc->f_max);
    else
        mmc->f_max = variant->explicit_mclk_control ?
                fmax : min(host->mclk, fmax);

ARM版とST版では最大/最小動作周波数が異なる。 そのため、variantに応じて値を調整する。

Regulators の取得

// 2133:
    /* Get regulators and the supported OCR mask */
    ret = mmc_regulator_get_supply(mmc);
    if (ret)
        goto clk_disable;

    if (!mmc->ocr_avail)
        mmc->ocr_avail = plat->ocr_mask;
    else if (plat->ocr_mask)
        dev_warn(mmc_dev(mmc), "Platform OCR mask is ignored\n");

Linuxカーネルでは、他の機器に電力を共有する電子機器 (レギュレータ) のAPI が多数用意されている。

SDカードは、3.3V (第二ロウの場合には1.8Vも必要) の電源が必要になる。 今回は、DT で vmmc-supplyv2m_fixed_3v3 ( 電圧固定のレギュレータ 3.3V) を指定しているため、mmc_regulator_get_supply関数で mmc->supply.vmmc に レギュレータが設定される。

MMC/SD/SDIO には operation conditions register (OCR) と呼ばれる32bit のレジスタを持つ。 このレジスタには MMC/SD/SDIOに供給可能な電圧のマスクが格納される。

GPIO descriptor 設定

// 2218:
    if (!np) {
        ret = mmc_gpiod_request_cd(mmc, "cd", 0, false, 0);
        if (ret == -EPROBE_DEFER)
            goto clk_disable;

        ret = mmc_gpiod_request_ro(mmc, "wp", 0, 0);
        if (ret == -EPROBE_DEFER)
            goto clk_disable;
    }

DT ではなく GPIO descriptor によって cdwp を設定することができる。 ただし、今回の環境では DT から設定するため省略する。

IRQ 設定

// 2204:
    writel(0, host->base + MMCIMASK0);

    if (variant->mmcimask1)
        writel(0, host->base + MMCIMASK1);

PL180/1 では、 MMCIMASK0MMCIMASK1 を割り込みマスクレジスタとなる。 割り込みハンドラの設定の前に各種割り込みの入力を禁止 (0x0000) しておく。

// 2228:
    ret = devm_request_threaded_irq(&dev->dev, dev->irq[0], mmci_irq,
                    mmci_irq_thread, IRQF_SHARED,
                    DRIVER_NAME " (cmd)", host);
    if (ret)
        goto clk_disable;

    if (!dev->irq[1])
        host->singleirq = true;
    else {
        ret = devm_request_irq(&dev->dev, dev->irq[1], mmci_pio_irq,
                IRQF_SHARED, DRIVER_NAME " (pio)", host);
        if (ret)
            goto clk_disable;
    }

    writel(MCI_IRQENABLE | variant->start_err, host->base + MMCIMASK0);

PL180/1 ドライバでは、最大で二つの割り込み信号を提供している。

  • コマンド(cmd): コマンド終了時のイベントに対応する
  • Polling I/O (pio): カードからの一括読み出しの一部として FIFO を空にする必要がある場合に発生する

devm_request_threaded_irq関数と devm_request_irq関数によって、指定したIRQに割り込みハンドラを登録する。

PL180/1 ドライバでは、前者の割り込み(cmd) の場合はmmci_irq関数を割り込みハンドラ、後者の割り込み(pio) の場合はmmci_pio_irq関数を割り込みハンドラとする。 また、cmdに対する割り込み IRQ_ENABLE | variant->start_err の一部 (0x002f) を有効化する。

コマンド終了時のイベントに対応するフラグ

DMA Engine の設定

// 2252:
    mmci_dma_setup(host);

今回の環境では DMA Engine を使用していないため省略する。

Runtime PM の autosuspend 設定

// 2254:
    pm_runtime_set_autosuspend_delay(&dev->dev, 50);
    pm_runtime_use_autosuspend(&dev->dev);

I/Oデバイス で autosuspend を有効にするためには、 pm_runtime_use_autosuspend関数を呼び出す必要がある。

git.kernel.org

また、すべてのリクエストにレイテンシ追加を防ぐために、autosuspend に50 ms の delay を設定する必要がある。

host 登録

// 2257:
    mmc_add_host(mmc);

これまでの処理でHOst コントローラの準備が完了したため、mmc_add_host関数によってドライバモデルに登録する。 mmc_add_host関数の定義は次のようになっている。

// 590:
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);
    return 0;
}

mmc_add_host関数では、Host コントローラの最終セットアップとしていくつかの処理を実施する。

  • /sys/devices 下に MMCI 用のサブディレクトリを作成
  • /sys/kernel/debug/mmc0/ 以下に MMC用のサブディレクトリを作成
  • LED Triggers の登録
  • Card Detect フラグを有効化

この中でも重要な処理となる mmc_start_host関数について、次回以降で確認する。

その他の設定

これ以外にも、PL180/1 を利用するにあたって、MMCIのメタデータ host のパラメータを設定する。

// 2046:
    host->hw_designer = amba_manf(dev);
    host->hw_revision = amba_rev(dev);
    dev_dbg(mmc_dev(mmc), "designer ID = 0x%02x\n", host->hw_designer);
    dev_dbg(mmc_dev(mmc), "revision = 0x%01x\n", host->hw_revision);

PL180/1 ドライバ で定義されている periphid から designer ID と revision を設定する。

// 2173:
    /*
    * We can do SGIO
    */
    mmc->max_segs = NR_SG;

    /*
    * Since only a certain number of bits are valid in the data length
    * register, we must ensure that we don't exceed 2^num-1 bytes in a
    * single request.
    */
    mmc->max_req_size = (1 << variant->datalength_bits) - 1;

    /*
    * Set the maximum segment size.  Since we aren't doing DMA
    * (yet) we are only limited by the data length register.
    */
    mmc->max_seg_size = mmc->max_req_size;

    /*
    * Block size can be up to 2048 bytes, but must be a power of two.
    */
    mmc->max_blk_size = 1 << variant->datactrl_blocksz;

    /*
    * Limit the number of blocks transferred so that we don't overflow
    * the maximum request size.
    */
    mmc->max_blk_count = mmc->max_req_size >> variant->datactrl_blocksz;

1回のリクエストで 2num-1 を超えないようにしなければならないので max_req_size を指定しなければならない。 max_seg_sizeはデータ長レジスタによって制限されるので、仮で max_req_size を設定する。 max_blk_size は 2n かつ 2048 以下で設定する。 また、最大リクエストサイズがオーバーフローしないように max_blk_count で転送ブロック数を制限する。

おわりに

本記事では、カーネル起動時に呼び出される mmci_probe関数について確認した。

変更履歴

  • 2024/01/04: 記事公開
  • 2024/02/18: タイトル修正

参考