関連記事
- Part 1: 環境セットアップ
- Part 2: System call Interface
- Part 3: VFS
- Part 4: ext2 (1) write_iter
- Part 5: ext2 (2) write_begin
- Part 6: ext2 (3) get_block
- Part 7: ext2 (4) write_end
- Part 8: writeback (1) work Queue
- Part 9: writeback (2) wb_writeback
- Part 10: writeback (3) writepages
- Part 11: writeback (4) write_inode
- Part 12: block (1) submit_bio
- Part 13: block (2) blk_mq
- Part 14: I/O scheduler (1) mq-deadline
- Part 15: I/O scheduler (2) insert_request
- Part 16: I/O scheduler (3) dispatch_request
- Part 17: block (3) blk_mq_run_work_fn
- Part 18: block (4) block: blk_mq_do_dispatch_sched
- Part 19: MMC (1) initialization
- Part 20: PL181 (1) mmci_probe
- Part 21: MMC (2) mmc_start_host
- Part 22: MMC (3) mmc_rescan
- Part 23: MMC (4) mmc_attach_sd
概要
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 が搭載しているシステムを考えてみる。
MMC に関連するノードに注目すると、ARM PrimeCell Multimedia Card Interface (PL180/1) ドライバ にプロパティreg
と clocks
を渡すことを表現している。
これらのプロパティに何が指定できるかについては、ドライバ毎によって異なる。
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) にsmbclk
にsmbclk
(33Mhz ~ 100MHz)
QEMU Arm Versatile Express boards
起動しているLinuxマシンにロードされている DT は /proc/device-tree/
や /sys/firmware/devicetree/base/
から確認することができる。
今回使用している vexpress-a9 (QEMU) で使用している DT は次のような木構造で表現できる。(橙色 は QEMU によって追加されたノードを表現している)
ここでは、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
関数が呼び出される。
この関数の定義は次のようになっている。
// 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の場合では、pricate
にmmci_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
構造体に最低限度の初期設定を施しているが、ここではすべてを確認することはしない。
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
関数内でパースされるプロパティはドキュメントにも記載されている。
mmc_of_parse`関数内でパースされるプロパティもドキュメントに記載されている。
今回の環境では 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
にマッピングする。
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-supply
に v2m_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 によって cd
と wp
を設定することができる。
ただし、今回の環境では DT から設定するため省略する。
IRQ 設定
// 2204: writel(0, host->base + MMCIMASK0); if (variant->mmcimask1) writel(0, host->base + MMCIMASK1);
PL180/1 では、 MMCIMASK0
と MMCIMASK1
を割り込みマスクレジスタとなる。
割り込みハンドラの設定の前に各種割り込みの入力を禁止 (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
関数を呼び出す必要がある。
また、すべてのリクエストにレイテンシ追加を防ぐために、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: タイトル修正
参考
- Linux and the Devicetree — The Linux Kernel documentation
- Linux Kernel の device tree ドキュメント
- [Linux][kernel] Device Tree についてのまとめ #Linux - Qiita
- Device Tree についてのまとめブログ
- Documentation – Arm Developer
- PL180 の仕様書
- SD規格の概要 | SD Association
- SD規格の概要
- MMC/SDCの使いかた
- MMC/SDC の仕組みの解説
- 【読解入門】Linuxカーネル (スケジューラと割込みハンドラの関係) #初心者 - Qiita
- スケジューラと割り込みハンドラの解説
- Runtime Power Management Framework for I/O Devices — The Linux Kernel documentation
- I/Oデバイスの Runtime PMのドキュメント