関連記事
- 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: 環境セットアップを参照。
一部の仕様書は非公開となっているため、公開情報からの推測が含まれています。そのため、内容に誤りが含まれている恐れがります。
SD判定
ここまで、MMCサブシステムから デバイス検出処理 (mmc_rescan
関数) が、非同期による遅延処理が実行される。
その処理内で、 mmc_attach_sd
関数によって、デバイスがSDであるかの検出とSDの初期化に入る。
mmc_attach_sd
関数の定義は次のようになっている。
// 1804: int mmc_attach_sd(struct mmc_host *host) { int err; u32 ocr, rocr; WARN_ON(!host->claimed); err = mmc_send_app_op_cond(host, 0, &ocr); if (err) return err; mmc_attach_bus(host, &mmc_sd_ops); if (host->ocr_avail_sd) host->ocr_avail = host->ocr_avail_sd; /* * We need to get OCR a different way for SPI. */ if (mmc_host_is_spi(host)) { mmc_go_idle(host); err = mmc_spi_read_ocr(host, 0, &ocr); if (err) goto err; } /* * Some SD cards claims an out of spec VDD voltage range. Let's treat * these bits as being in-valid and especially also bit7. */ ocr &= ~0x7FFF; rocr = mmc_select_voltage(host, ocr); /* * Can we support the voltage(s) of the card(s)? */ if (!rocr) { err = -EINVAL; goto err; } /* * Detect and init the card. */ err = mmc_sd_init_card(host, rocr, NULL); if (err) goto err; mmc_release_host(host); err = mmc_add_card(host->card); if (err) goto remove_card; mmc_claim_host(host); return 0; remove_card: mmc_remove_card(host->card); host->card = NULL; mmc_claim_host(host); err: mmc_detach_bus(host); pr_err("%s: error %d whilst initialising SD card\n", mmc_hostname(host), err); return err; }
初めに、 mmc_send_app_op_cond
関数によって SDかどうかを判定する。
mmc_send_app_op_cond
関数では、ACMD41(SD_SEND_OP_COND) のレスポンスで判断する。*1
SD仕様に準拠したメモリカードである場合には、このコマンドでレスポンスが返ってくる。
"inquiry CMD41"の場合には、レスポンスとしてOCR(Operation Conditions Register?)が取得できる。
Vddの選択
OCR は、メモリカードの動作電圧範囲が 100mV 単位で表現される。
SPIモードでは、OCR の取得方法が異なり、mmc_spi_read_ocr
関数によって取得する。 (しかし、今回はSPIモードではないため割愛する)
ここでパッチ概要によると、一部のメモリカードでは、この OCRの特定ビットを無効な電圧範囲とすることがある。
取得したOCRとホストコントローラの対応電圧を使い、mmc_select_voltage
関数は 供給電圧を設定する。
mmc_select_voltage
関数の定義は次のようになっている。
// 1109: u32 mmc_select_voltage(struct mmc_host *host, u32 ocr) { int bit; /* * Sanity check the voltages that the card claims to * support. */ if (ocr & 0x7F) { dev_warn(mmc_dev(host), "card claims to support voltages below defined range\n"); ocr &= ~0x7F; } ocr &= host->ocr_avail; if (!ocr) { dev_warn(mmc_dev(host), "no support for card's volts\n"); return 0; } if (host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) { bit = ffs(ocr) - 1; ocr &= 3 << bit; mmc_power_cycle(host, ocr); } else { bit = fls(ocr) - 1; ocr &= 3 << bit; if (bit != host->ios.vdd) dev_warn(mmc_dev(host), "exceeding card's volts\n"); } return ocr; }
mmc_select_voltage
関数では、OCRで取得された値とホストコントローラがサポートしている電圧から、最大VDDを設定する。
例えば、inquiry CMD41でocr
が0xff8000
、ホストコントローラの対応範囲host->ocr_avail
が0x300000
の場合には、3.3~3.4Vとなる。
カード初期化
OCRの値がホストコントローラの対応電圧の範囲に入っている場合、mmc_sd_init_card
関数によって、SDメモリカードの初期化の処理に入る。
mmc_sd_init_card
関数の定義は次のようになっている。
// 1389: static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard) { struct mmc_card *card; int err; u32 cid[4]; u32 rocr = 0; bool v18_fixup_failed = false; WARN_ON(!host->claimed); retry: err = mmc_sd_get_cid(host, ocr, cid, &rocr); if (err) return err; if (oldcard) { if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { pr_debug("%s: Perhaps the card was replaced\n", mmc_hostname(host)); return -ENOENT; } card = oldcard; } else { /* * Allocate card structure. */ card = mmc_alloc_card(host, &sd_type); if (IS_ERR(card)) return PTR_ERR(card); card->ocr = ocr; card->type = MMC_TYPE_SD; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); } /* * Call the optional HC's init_card function to handle quirks. */ if (host->ops->init_card) host->ops->init_card(host, card); /* * For native busses: get card RCA and quit open drain mode. */ if (!mmc_host_is_spi(host)) { err = mmc_send_relative_addr(host, &card->rca); if (err) goto free_card; } if (!oldcard) { err = mmc_sd_get_csd(card); if (err) goto free_card; mmc_decode_cid(card); } /* * handling only for cards supporting DSR and hosts requesting * DSR configuration */ if (card->csd.dsr_imp && host->dsr_req) mmc_set_dsr(host); /* * Select card, as all following commands rely on that. */ if (!mmc_host_is_spi(host)) { err = mmc_select_card(card); if (err) goto free_card; } err = mmc_sd_setup_card(host, card, oldcard != NULL); if (err) goto free_card; /* * If the card has not been power cycled, it may still be using 1.8V * signaling. Detect that situation and try to initialize a UHS-I (1.8V) * transfer mode. */ if (!v18_fixup_failed && !mmc_host_is_spi(host) && mmc_host_uhs(host) && mmc_sd_card_using_v18(card) && host->ios.signal_voltage != MMC_SIGNAL_VOLTAGE_180) { /* * Re-read switch information in case it has changed since * oldcard was initialized. */ if (oldcard) { err = mmc_read_switch(card); if (err) goto free_card; } if (mmc_sd_card_using_v18(card)) { if (mmc_host_set_uhs_voltage(host) || mmc_sd_init_uhs_card(card)) { v18_fixup_failed = true; mmc_power_cycle(host, ocr); if (!oldcard) mmc_remove_card(card); goto retry; } goto done; } } /* Initialization sequence for UHS-I cards */ if (rocr & SD_ROCR_S18A && mmc_host_uhs(host)) { err = mmc_sd_init_uhs_card(card); if (err) goto free_card; } else { /* * Attempt to change to high-speed (if supported) */ err = mmc_sd_switch_hs(card); if (err > 0) mmc_set_timing(card->host, MMC_TIMING_SD_HS); else if (err) goto free_card; /* * Set bus speed. */ mmc_set_clock(host, mmc_sd_get_max_clock(card)); /* * Switch to wider bus (if supported). */ if ((host->caps & MMC_CAP_4_BIT_DATA) && (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4); if (err) goto free_card; mmc_set_bus_width(host, MMC_BUS_WIDTH_4); } } if (!oldcard) { /* Read/parse the extension registers. */ err = sd_read_ext_regs(card); if (err) goto free_card; } /* Enable internal SD cache if supported. */ if (card->ext_perf.feature_support & SD_EXT_PERF_CACHE) { err = sd_enable_cache(card); if (err) goto free_card; } if (host->cqe_ops && !host->cqe_enabled) { err = host->cqe_ops->cqe_enable(host, card); if (!err) { host->cqe_enabled = true; host->hsq_enabled = true; pr_info("%s: Host Software Queue enabled\n", mmc_hostname(host)); } } if (host->caps2 & MMC_CAP2_AVOID_3_3V && host->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_330) { pr_err("%s: Host failed to negotiate down from 3.3V\n", mmc_hostname(host)); err = -EINVAL; goto free_card; } done: host->card = card; return 0; free_card: if (!oldcard) mmc_remove_card(card); return err; }
カード識別モード
SDメモリカードには、カードを識別する番号 Card IDentification(CID) を持つ。
mmc_sd_init_card
関数では、初めに CID を取得する。
mmc_sd_init_card
関数の定義は次のようになっている。
// 808: int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr) { int err; u32 max_current; int retries = 10; u32 pocr = ocr; try_again: if (!retries) { ocr &= ~SD_OCR_S18R; pr_warn("%s: Skipping voltage switch\n", mmc_hostname(host)); } /* * Since we're changing the OCR value, we seem to * need to tell some cards to go back to the idle * state. We wait 1ms to give cards time to * respond. */ mmc_go_idle(host); /* * If SD_SEND_IF_COND indicates an SD 2.0 * compliant card and we should set bit 30 * of the ocr to indicate that we can handle * block-addressed SDHC cards. */ err = mmc_send_if_cond(host, ocr); if (!err) ocr |= SD_OCR_CCS; /* * If the host supports one of UHS-I modes, request the card * to switch to 1.8V signaling level. If the card has failed * repeatedly to switch however, skip this. */ if (retries && mmc_host_uhs(host)) ocr |= SD_OCR_S18R; /* * If the host can supply more than 150mA at current voltage, * XPC should be set to 1. */ max_current = sd_get_host_max_current(host); if (max_current > 150) ocr |= SD_OCR_XPC; err = mmc_send_app_op_cond(host, ocr, rocr); if (err) return err; /* * In case the S18A bit is set in the response, let's start the signal * voltage switch procedure. SPI mode doesn't support CMD11. * Note that, according to the spec, the S18A bit is not valid unless * the CCS bit is set as well. We deliberately deviate from the spec in * regards to this, which allows UHS-I to be supported for SDSC cards. */ if (!mmc_host_is_spi(host) && rocr && (*rocr & 0x01000000)) { err = mmc_set_uhs_voltage(host, pocr); if (err == -EAGAIN) { retries--; goto try_again; } else if (err) { retries = 0; goto try_again; } } err = mmc_send_cid(host, cid); return err; }
カード認識するにあたってメモリカードを idle状態に設定する必要がある。
mmc_go_idle
関数によって、メモリカードを idle状態に設定することができる。
mmc_go_idle
関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。CMD0(FO_IDLE) はカードをidle状態に設定するコマンドである。
[ 1.173771][ T45] mmc0: starting CMD0 arg 00000000 flags 000000c0
SDメモリカードには、SDv1とSDv2の異なるバージョンが存在しており、それぞれで初期化のシーケンスが若干異なる。
SDv1かSDv2か判定するためには、SDv2で追加されたCMD8(SEND_IF_COND)のレスポンスによって判定する。
SDv2判定のためのCMD8は mmc_send_if_conf
関数によってコマンドを発行する発行することができる。
mmc_send_if_cond
関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.192937][ T45] mmc0: starting CMD8 arg 000001aa flags 000002f5
もし、レスポンスが返ってきた場合、SD High Capacity(SDHC) や SD eXtended Capacity(SDXC) を意味する Card Capacity Status(CCS) を設定する。(SDHC や SDXC は SDv2 で追加された仕様である)
一方で、UHS-I は 信号電圧を 1.8Vまで省電圧化されている。 もし、UHS-Iがサポートされている場合には、S18R (Switching to 1.8V Request) を設定する。
その後、sd_get_host_max_current
関数にてホストが供給できる最大電流を取得する。
もし、150mAまで供給できる場合には XPC (SDXC Power Control?) のビットを設定する。
ここまでで設定したocr
を引数として ACMD41(SD_SEND_OP_COND) を呼び出す。
ACMD41 は引数が設定されている場合には、inquiry ではなく first ACMD41 として扱われる。
mmc_send_app_op_cond
関数(first ACMD41)が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.193312][ T45] mmc0: starting CMD55 arg 00000000 flags 000000f5
[ 1.193603][ T45] mmc0: starting CMD41 arg 40200000 flags 000000e1
first AMCD41のレスポンス rocr
の特定ビット S18A(Switching to 1.8V Accepted) は、1.8Vへの切り替えが可能であることを意味する。
CIDの取得には、mmc_send_cid
関数を用いる。
SDモードでは、CMD2(ALL_SEND_CID)によってCIDを取得することができる。
mmc_send_cid
関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.193849][ T45] mmc0: starting CMD2 arg 00000000 flags 00000007
ここで取得した CID から既存のカードの置き換え処理でない場合には、mmc_alloc_card
関数によって mmc_card
構造体の変数card
の確保と初期化する。((もし、ホストコントローラ特有の初期化処理が必要な場合には、init_card
を呼び出すことができる))
SDモードでは、RCA(Relative Card Address) の取得が必要となる。 これは、CMD3(SEND_RELATIVE_ADDR)によって取得できる。
mmc_send_relative_addr
関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.194188][ T45] mmc0: starting CMD3 arg 00000000 flags 00000075
SDメモリカードでは記憶容量などといった情報を CSD (Card-Specific Data?) レジスタに保持している。
このレジスタの値の取得とcard
に情報を代入する処理を mmc_sd_get_csd
関数が担う。
データ転送モード
SDメモリカードは、CMD9(SEND_CSD)を受け取ると CSDレジスタの値を返す。
mmc_sd_get_csd
関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.448682][ T48] mmc0: starting CMD9 arg 45670000 flags 00000007
一部のSDメモリカードには DSR(Driver Stage Register) が実装されており、CMD/DATA出力の立ち上がり/立ち下がりの時間が強さを調整できる。
デバイスツリーに dsr
プロパティを設定している場合、mmc_set_dsr
関数によってCMD4 (SET_DSR)が発行される。
ただし、今回はこれが設定されていないため、CMD4は発行されない。
SDメモリカードでは、データ転送の前にCMD7(SELECT/DESELECT_CARD)でカードの選択をする。
mmc_select_card
関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.194743][ T45] mmc0: starting CMD7 arg 45670000 flags 00000015
そして、mmc_sd_setup_card
関数によってカードの取得・設定していく。
mmc_sd_setup_card
関数の定義は次のようになっている。
// 919: int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card, bool reinit) { int err; if (!reinit) { /* * Fetch SCR from card. */ err = mmc_app_send_scr(card); if (err) return err; err = mmc_decode_scr(card); if (err) return err; /* * Fetch and process SD Status register. */ err = mmc_read_ssr(card); if (err) return err; /* Erase init depends on CSD and SSR */ mmc_init_erase(card); /* * Fetch switch information from card. */ err = mmc_read_switch(card); if (err) return err; } /* * For SPI, enable CRC as appropriate. * This CRC enable is located AFTER the reading of the * card registers because some SDHC cards are not able * to provide valid CRCs for non-512-byte blocks. */ if (mmc_host_is_spi(host)) { err = mmc_spi_set_crc(host, use_spi_crc); if (err) return err; } /* * Check if read-only switch is active. */ if (!reinit) { int ro = mmc_sd_get_ro(host); if (ro < 0) { pr_warn("%s: host does not support reading read-only switch, assuming write-enable\n", mmc_hostname(host)); } else if (ro > 0) { mmc_card_set_readonly(card); } } return 0; }
SDメモリカードには、SCR(Sd Configuration Register) で SDメモリカードの特定の情報を持つ。
これには、mmc_app_send_scr
関数が ACMD51 を発行する必要がある。
この関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.194989][ T45] mmc0: starting CMD55 arg 45670000 flags 00000095
[ 1.195346][ T45] mmc0: starting CMD51 arg 00000000 flags 000000b5
その後、SSR(Sd Status Register) を取得する。
これには、mmc_read_ssr
関数が ACMD13 を発行する必要がある。
この関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.196964][ T45] mmc0: starting CMD55 arg 45670000 flags 00000095
[ 1.197161][ T45] mmc0: starting CMD13 arg 00000000 flags 000001b5
mmc_init_erase
関数では、SSRなどから erase_size(eraseの最小単位) や preferred_erase_size(Allocation Unit size) を card
に設定する。
mmc_read_switch
関数は、SDメモリカードが対応しているバススピードモードを確認する。
これには、mmc_read_switch
関数が CMD6 を発行する必要がある。
この関数が呼ばれたとき、次のようなデバッグメッセージが確認することができる。
[ 1.197741][ T45] mmc0: starting CMD6 arg 00fffff0 flags 000000b5
その後、バススピードやバス幅の設定をする。 ここでは、UHS-Iの場合とそうではない場合で分かれており、対応関係は次のようになっている。
バスインターフェース | バススピードモード | バススピード |
---|---|---|
default | 12.5MB/s | |
High speed | 25MB/s | |
UHS-I | SDR50/DDR50 | 50MB/s |
UHS-I | SDR104 | 104MB/s |
また、SD spec 6.x から 性能改善機能としてキャッシュなどが追加されており、これらをサポートしているカードに対しては CMD48(READ_EXTR_SINGLE)とCMD49(WRITE_EXTR_SINGLE)を発行することができる。
MMCバスに追加
mmc_add_card
関数によって初期化の処理が完了したMMCメモリカードをLinuxデバイスモデルに登録する。
mmc_add_card
関数の定義は次のようになっている。
// 308: int mmc_add_card(struct mmc_card *card) { int ret; const char *type; const char *uhs_bus_speed_mode = ""; static const char *const uhs_speeds[] = { [UHS_SDR12_BUS_SPEED] = "SDR12 ", [UHS_SDR25_BUS_SPEED] = "SDR25 ", [UHS_SDR50_BUS_SPEED] = "SDR50 ", [UHS_SDR104_BUS_SPEED] = "SDR104 ", [UHS_DDR50_BUS_SPEED] = "DDR50 ", }; dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca); switch (card->type) { case MMC_TYPE_MMC: type = "MMC"; break; case MMC_TYPE_SD: type = "SD"; if (mmc_card_blockaddr(card)) { if (mmc_card_ext_capacity(card)) type = "SDXC"; else type = "SDHC"; } break; case MMC_TYPE_SDIO: type = "SDIO"; break; case MMC_TYPE_SD_COMBO: type = "SD-combo"; if (mmc_card_blockaddr(card)) type = "SDHC-combo"; break; default: type = "?"; break; } if (mmc_card_uhs(card) && (card->sd_bus_speed < ARRAY_SIZE(uhs_speeds))) uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed]; if (mmc_host_is_spi(card->host)) { pr_info("%s: new %s%s%s card on SPI\n", mmc_hostname(card->host), mmc_card_hs(card) ? "high speed " : "", mmc_card_ddr52(card) ? "DDR " : "", type); } else { pr_info("%s: new %s%s%s%s%s%s card at address %04x\n", mmc_hostname(card->host), mmc_card_uhs(card) ? "ultra high speed " : (mmc_card_hs(card) ? "high speed " : ""), mmc_card_hs400(card) ? "HS400 " : (mmc_card_hs200(card) ? "HS200 " : ""), mmc_card_hs400es(card) ? "Enhanced strobe " : "", mmc_card_ddr52(card) ? "DDR " : "", uhs_bus_speed_mode, type, card->rca); } #ifdef CONFIG_DEBUG_FS mmc_add_card_debugfs(card); #endif card->dev.of_node = mmc_of_find_child_device(card->host, 0); device_enable_async_suspend(&card->dev); ret = device_add(&card->dev); if (ret) return ret; mmc_card_set_present(card); return 0; }
ここまでの処理によってカード種類などが判明しているため、カーネルメッセージに出力する。 例えば、今回の環境では次のようなデバッグメッセージが確認することができる。
[ 1.197018][ T45] mmc0: new SD card at address 4567
その後、mmc_add_card_debugfs
関数で debugfs にエントリを追加し、device_add
関数で sysfs にエントリを追加する。
mmc_add_card_debugfs
関数の定義は次のようになっている。
// 253: void mmc_add_card_debugfs(struct mmc_card *card) { struct mmc_host *host = card->host; struct dentry *root; if (!host->debugfs_root) return; root = debugfs_create_dir(mmc_card_id(card), host->debugfs_root); card->debugfs_root = root; debugfs_create_x32("state", S_IRUSR, root, &card->state); }
mmc_add_card_debugfs
関数によって メモリカードの状態を確認できるようなエントリが追加される。
// 1: /* Card states */ #define MMC_STATE_PRESENT (1<<0) /* present in sysfs */ #define MMC_STATE_READONLY (1<<1) /* card is read-only */ #define MMC_STATE_BLOCKADDR (1<<2) /* card uses block-addressing */ #define MMC_CARD_SDXC (1<<3) /* card is SDXC */ #define MMC_CARD_REMOVED (1<<4) /* card has been removed */ #define MMC_STATE_SUSPENDED (1<<5) /* card is suspended */
例えば、今回の環境で起動直後に確認した場合には次のような結果が得られる。
# cat /sys/kernel/debug/mmc0/mmc0\:4567/state
0x00000001
device_add
関数はLinuxデバイスモデルに"Device"を登録するAPIである。
この関数によって、関連するコンポーネントとして bus の probe処理が呼ばれる。
今回は mmc_bus_probe
関数 (と mmc_blk_probe
関数) を実行する。
おわりに
本記事では、カーネル起動時に呼び出される mmc_attach_sd
関数について確認した。
この関数では、SDメモリカードの初期化ロジックが組み込まれており、次のようなCMDが発行されている。
変更履歴
- 2024/05/10: 記事公開
参考
- SD規格の概要 | SD Association
- SD規格の概要
- MMC/SDCの使いかた
- MMC/SDC の仕組みの解説
- https://blog.csdn.net/lickylin/article/details/104718117
- 車載外部ストレージ バックナンバー | シミュレーションの世界に引きこもる部屋
- SD初期化ルーチンの解説
- SDIOについて #sdio - Qiita
- SDカードのCMDリクエストとレスポンス
- Device drivers infrastructure — The Linux Kernel documentation
- device driverのAPIドキュメント
*1:ACMDは、直前にCMD55である場合に、そのコマンドをACMDとして解釈される