Linux MMC子系统6(基于Linux6.6)---MMC card添加流程介绍
对iic设备、spi设备、rtc设备等非热插拔设备而言,一般在板级文件或者设备树中定义外设信息,完成外设的注册;但针对mmc card而言,其属于热插拔设备,不需要在板级文件或设备树中进行外设的注册,而由mmc子系统通过mmc card的rescan机制,实现mmc card的自动检测及注册机制。
一、概述
MMC 卡的添加流程涉及硬件检测、驱动加载、设备注册、卡初始化以及最终通过用户空间访问设备。以下是概述的流程:
1. 硬件检测
当一个新的 MMC 卡(如 SD 卡、eMMC 等)被插入到系统中时,系统需要能够检测到这一事件。检测的方式通常包括:
- 硬件事件驱动:例如,通过 GPIO 引脚或总线事件(如 SDIO 或 SPI 总线上的信号变化)来触发插卡的事件。
- 轮询机制:在没有硬件事件支持的情况下,MMC 主机控制器的驱动可能会定期轮询卡插入状态。
2. 主机控制器驱动检测卡的插入
当 MMC 卡插入时,系统的主机控制器驱动会通过以下方式来探测和识别卡:
-
mmc_host 的探测:Linux 内核中的 MMC 子系统通过
mmc_host
来管理 MMC 主机控制器。主机控制器负责与卡之间的通信。- 主机控制器会发送识别命令(如 CMD0,CMD8 等)来初始化和识别卡的类型(如 SD 卡、eMMC 卡等)。
- 内核通过
mmc
子系统与主机控制器交互,获取卡的基本信息(如卡的容量、类型等)。
3. MMC 卡的初始化
卡被检测到后,MMC 子系统会开始卡的初始化过程。这一过程通常包括:
-
发送命令识别卡:内核向卡发送一系列的命令(如
CMD1
,CMD2
等),以确认卡的类型(如 SD、eMMC 等)并读取卡的相关信息。 -
分配
mmc_card
结构体:为每个卡创建一个mmc_card
结构体,保存卡的相关信息(如容量、特性等)。 -
初始化卡的硬件资源:包括时钟、总线宽度、电压等设置。
-
块设备初始化:对于支持块设备的 MMC 卡(如 SD 卡、eMMC 卡等),内核会将其注册为块设备,创建
block_device
结构体,并通过register_blkdev
或register_disk
将卡注册为块设备。
4. 驱动与设备绑定
当卡初始化成功后,内核会将 MMC 卡与相应的驱动进行绑定。
-
驱动探测:内核通过 MMC 子系统的驱动探测机制,根据
mmc_driver
的probe
函数来为卡加载相应的驱动。 -
驱动注册:例如,SD 卡可能会使用
sdhci
驱动,eMMC 卡可能会使用mmc
驱动。驱动负责管理卡的读取和写入操作、卡的状态检测等功能。
5. 设备节点创建
在卡初始化完成并与驱动绑定后,内核会在 /dev
目录下为该卡创建设备节点,通常是以 /dev/mmcblkX
的形式,其中 X
是数字索引。例如,第一张卡会被命名为 /dev/mmcblk0
,第二张卡会是 /dev/mmcblk1
。
- 设备节点的创建使得用户空间可以通过文件系统访问 MMC 卡。例如,用户可以使用
mount
命令挂载该设备、进行读写操作等。
6. 用户空间访问
一旦 MMC 卡被正确注册并创建了设备节点,用户空间的应用程序可以像访问普通块设备一样访问该设备。常见的操作包括:
- 挂载文件系统:如
mount /dev/mmcblk0 /mnt
。 - 使用
fdisk
、parted
等工具对卡进行分区。 - 读写文件系统:如通过
cp
、dd
等工具对卡进行数据读写操作。
二、Mmc card rescan机制
mmc card是依附于mmc controller的,因此mmc card rescan机制主要是基于mmc host进行设计的;该rescan机制主要 是通过工作队列实现的,如下图为该机制的实现流程,主要说明如下:
- Mmc host子系统提供了延迟队列机制,在执行mmc_alloc_host、mmc_add_host后,则完成了mmc card rescan延迟工作队列及其处理接口的创建等操作;
- 若要触发mmc card rescan(即调度工作队列),则调用mmc_detect_change接口,即可触发mmc card rescan(即完成mmc_host->detect队列的调度);
- 延迟队列的处理函数为mmc_rescan,该函数实现mmc card的添加与移除操作。
2.1、Mmc card rescan的几种方式
延迟工作队列的创建及处理函数的注册操作由mmc host子模块完成(mmc_alloc_host/mmc_add),无需驱动开发者关心;而延迟工作队列的调度则需要具体的mmc host驱动进行实现。而mmc card rescan的方式有如下几种:
- Mmc card是不可移除的(如emmc),则在mmc host初始化时设置mmc host为nonremovable(仅在mmc_add_host时,调用mmc_detect_change完成一次mmc rescan,此后不再执行mmc rescan操作);
- Mmc host支持mmc card detect功能(通过提供mmc detect中断,进行mmc card detect),此种情况在mmc card detect中断对应的中断处理接口中,调用mmc_detect_change接口,对延迟工作队列进行调度,从而调用接口mmc_rescan,完成一次mmc card的rescan;
- Mmc host不支持mmc card detect功能,针对此情形,可以设置mmc host为poll模式。针对此种模式,在mmc_add_host执行一次mmc rescan时,在mmc rescan的最后会执行延迟1s调度该延迟工作队列,从而完成每秒执行一次mmc rescan操作。
三、mmc card探测及移除
Mmc card的探测及移除由mmc_rescan完成(而mmc_rescan接口则由上述一中的mmc_detect_change调度),下面我们分析mmc_rescan,来说明mmc card的探测及移除
3.1、mmc_rescan接口分析
drivers/mmc/core/core.c
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;
if (host->rescan_disable)
return;
/* If there is a non-removable card registered, only scan once */
if (!mmc_card_is_removable(host) && host->rescan_entered)
return;
host->rescan_entered = 1;
if (host->trigger_card_event && host->ops->card_event) {
mmc_claim_host(host);
host->ops->card_event(host);
mmc_release_host(host);
host->trigger_card_event = false;
}
/* Verify a registered card to be functional, else remove it. */
if (host->bus_ops)
host->bus_ops->detect(host);
host->detect_change = 0;
/* if there still is a card present, stop here */
if (host->bus_ops != NULL)
goto out;
mmc_claim_host(host);
if (mmc_card_is_removable(host) && host->ops->get_cd &&
host->ops->get_cd(host) == 0) {
mmc_power_off(host);
mmc_release_host(host);
goto out;
}
/* If an SD express card is present, then leave it as is. */
if (mmc_card_sd_express(host)) {
mmc_release_host(host);
goto out;
}
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
unsigned int freq = freqs[i];
if (freq > host->f_max) {
if (i + 1 < ARRAY_SIZE(freqs))
continue;
freq = host->f_max;
}
if (!mmc_rescan_try_freq(host, max(freq, host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
/* A non-removable card should have been detected by now. */
if (!mmc_card_is_removable(host) && !host->bus_ops)
pr_info("%s: Failed to initialize a non-removable card",
mmc_hostname(host));
/*
* Ignore the command timeout errors observed during
* the card init as those are excepted.
*/
host->err_stats[MMC_ERR_CMD_TIMEOUT] = 0;
mmc_release_host(host);
out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
mmc_rescan接口实现mmc card rescan的操作,该接口的处理流程图如下:
- 若mmc host不支持mmc card的移除操作,则做相应的判断(非第一次执行,则直接退出);
- 针对已注册的mmc card,则执行一次detect(host->bus_ops->detect),移除已经离线的mmc card(这会触发mmc_card的release操作,即调用device_unregister将该mmc_card从mmc_bus上移除,这会触发mmc_card的release操作,即调用device_unregister将该mmc_card从mmc_bus上移除,而这即触发mmc_driver与mmc_card的解绑流程(详见上一篇分析文章),从而调用mmc_driver->remove接口,即将已注册的mmc block device进行注销。);
- 若mmc host支持get_cd接口,则调用其get_cd接口针对cd引脚值,判断mmc card是否在线;
- 针对每一个频率(400khz、300khz、200khz、100khz(若host->f_min大于400khz,则以host->f_min进行探测)),调用mmc_rescan_try_freq进行mmc card rescan操作。
3.2、mmc_rescan_try_freq接口
drivers/mmc/core/core.c
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
pr_debug("%s: %s: trying to init card at %u Hz\n",
mmc_hostname(host), __func__, host->f_init);
mmc_power_up(host, host->ocr_avail);
/*
* Some eMMCs (with VCCQ always on) may not be reset after power up, so
* do a hardware reset if possible.
*/
mmc_hw_reset_for_init(host);
/*
* sdio_reset sends CMD52 to reset card. Since we do not know
* if the card is being re-initialized, just send it. CMD52
* should be ignored by SD/eMMC cards.
* Skip it if we already know that we do not support SDIO commands
*/
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
sdio_reset(host);
mmc_go_idle(host);
if (!(host->caps2 & MMC_CAP2_NO_SD)) {
if (mmc_send_if_cond_pcie(host, host->ocr_avail))
goto out;
if (mmc_card_sd_express(host))
return 0;
}
#ifdef CONFIG_SCM_CM
if (host->ops->encrypt_config)
host->ops->encrypt_config(host, 0);
#endif
/* Order's important: probe SDIO, then SD, then MMC */
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;
#ifdef CONFIG_SCM_CM
if (host->ops->encrypt_config)
host->ops->encrypt_config(host, 1);
#endif
out:
mmc_power_off(host);
return -EIO;
}
该接口的处理流程如上图所示,该接口实现的功能如下:
- 设置mmc host的总线带宽、总线模式(pushpull),并设置mmc host(从powerup->poweron);
- 若mmc host支持hw_reset,执行hw_reset;
- 向mmc card 发送MMC_GO_IDLE_STATE命令(让mmc卡进入idle状态,该命令为broadcast commnad without response);
- 执行mmc 设备的探测:
- 针对sdio 设备,则调用mmc_attach_sdio执行sdio设备的探测;
- 针对sd设备,则调用mmc_attach_sd执行sd设备的探测;
- 针对mmc设备,则调用mmc_attach_mmc执行mmc设备的探测
mmc_attach_sdio、mmc_attach_sd、mmc_attach_mmc则为具体设备的探测及绑定流程。下面我们以sd card为主,进行sd card探测及绑定流程说明:
3.3、mmc_attach_sd接口分析
drivers/mmc/core/sd.c
/*
* Starting point for SD card init.
*/
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);
#ifdef CONFIG_SCM_CM
if (host->ops->encrypt_config)
host->ops->encrypt_config(host, 1);
#endif
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_attach_sd接口的主要处理流程如下图所示,其实现的主要功能如下:
- 发送send_op_cond(该指令为broadcast command with response);若没有响应,则说明card不在线,程序返回;
- 若card回复,则说明card在线,则调用mmc_sd_attach_bus_ops,设置mmc_host->bus_ops为mmc_sd_ops_unsafe/mmc_sd_ops;
- 调用mmc_sd_init_card,获取mmc card的csd、cid,并创建mmc_card,并对mmc card进行初始化(如是否只读等信息);
- 调用mmc_add_card,将该mmc_card注册至mmc_bus中,该接口会调用device_register将mmc_card注册至mmc_bus上,而这即触发mmc_driver与mmc_card的绑定流程(详见上一篇分析文章),从而调用mmc_driver->probe接口,即执行mmc block device的注册操作。
四、总结
在 Linux 系统中,MMC 卡的添加流程主要包括硬件识别、驱动加载、设备节点创建和文件系统挂载等步骤。以下是MMC卡添加的总体流程,从插入卡到成功使用的每个步骤:
1. 硬件插入
当你将一个 MMC 卡插入到设备(如 SD 卡读卡器或嵌入式系统)时,设备会物理连接到主机控制器(例如 SDHCI 或其他 MMC 控制器)。此时,内核检测到硬件的插入并开始硬件初始化过程。
2. 内核设备检测与驱动加载
内核通过检测设备总线来识别 MMC 卡。具体过程如下:
设备检测
-
内核通过 SDHCI (Secure Digital Host Controller Interface) 驱动、MMC 驱动或其他与 MMC 相关的驱动来识别设备。内核会扫描相关的总线(如 SDIO、SATA 或其他总线)来检查设备。
-
在 dmesg 日志中,系统会打印出相关信息,表明设备被检测到。例如,类似以下输出:
-
[ 123.456789] mmc0: new high speed SDHC card at address 0007 [ 123.457000] mmcblk0: mmc0:0007 SD32G 29.8 GiB
驱动加载
- 如果内核已经正确配置了 MMC 驱动(例如
mmc_block
,sdhci
,mmc_core
),驱动会被加载并开始初始化卡。驱动程序负责与硬件交互。 - 如果需要,内核会自动加载相关的模块(例如
mmc_block
、sdhci
)。
3. 设备节点创建
一旦硬件被正确识别,内核会为该设备创建一个相应的块设备节点。通常,该节点位于 /dev
目录下,格式为 /dev/mmcblkX
(X 是设备编号),例如:
/dev/mmcblk0
如果 MMC 卡支持分区,还会创建分区节点,例如:
/dev/mmcblk0p1 /dev/mmcblk0p2
4. 分区表识别
如果 MMC 卡上已经存在分区表(例如 GPT 或 MBR 分区表),内核会识别并读取该分区表。此时,可以通过以下命令查看分区信息:
fdisk -l /dev/mmcblk0
如果没有分区表,可以手动创建分区,或者使用工具如 parted
或 fdisk
来操作。
5. 文件系统检测
如果分区上有文件系统(如 ext4、vfat、ntfs 等),内核会自动识别并加载该文件系统。否则,你需要手动格式化分区为合适的文件系统。格式化命令如下:
mkfs.ext4 /dev/mmcblk0p1
6. 挂载文件系统
在文件系统创建后,可以将文件系统挂载到系统的某个目录,以便访问。使用 mount
命令来挂载 MMC 卡:
mount /dev/mmcblk0p1 /mnt
如果一切顺利,MMC 卡中的数据就可以通过 /mnt
目录访问。
7. 自动挂载(可选)
在许多 Linux 系统中,udev
或其他设备管理系统会自动处理挂载过程。例如,桌面环境(如 GNOME 或 KDE)会自动挂载插入的 MMC 卡并在文件管理器中显示它。
你也可以配置 /etc/fstab
文件,实现 MMC 卡的自动挂载:
/dev/mmcblk0p1 /mnt ext4 defaults 0 2
8. 设备使用
此时,MMC 卡已经成功插入、识别、分区、格式化、挂载,可以开始使用。
9. 设备卸载与移除
使用完毕后,可以通过 umount
命令卸载设备:
umount /mnt
然后可以安全地移除 MMC 卡。