Linux MMC子系统3(基于Linux6.6)---MMC/SDIO总线接口介绍
针对mmc子系统包括mmc bus、mmc driver、 mmc host以及mmc通用命令接口层、mmc card rescan机制、mmc block driver层等等内容。针对mmc 子系统包括mmc bus、sdio bus两个bus总线。其中mmc bus主要针对mmc/sd/tf/emmc等存储卡设备。
一、Mmc bus分析
针对mmc bus相关的操作,主要分析如下三个部分:mmc bus type的定义及其接口实现说明、mmc driver的注册与注销接口、mmc card的注册与注销接口。
1.1、Mmc bus的定义及其接口分析
drivers/mmc/core/bus.c
static struct bus_type mmc_bus_type = {
.name = "mmc",
.dev_groups = mmc_dev_groups,
.match = mmc_bus_match,
.uevent = mmc_bus_uevent,
.probe = mmc_bus_probe,
.remove = mmc_bus_remove,
.shutdown = mmc_bus_shutdown,
.pm = &mmc_bus_pm_ops,
};
mmc_bus_type
提供了一些关键功能,主要是用于管理 MMC 总线上的设备:
1. 设备注册与注销
mmc_bus_type
提供了注册和注销设备的接口。设备通过bus_register()
和bus_unregister()
函数与总线类型进行关联。- 例如,MMC 设备(如 SD 卡、eMMC 等)会通过
mmc_register_host()
注册到mmc_bus_type
,在初始化或插拔时,内核能够识别和管理它们。
2. 设备匹配
mmc_bus_type
定义了设备与驱动匹配的机制。通过设备匹配函数(如mmc_bus_match
),内核可以找到与特定 MMC 设备匹配的驱动程序。- 当设备插入或拔出时,内核会调用此匹配函数来检查设备是否符合条件,并根据匹配的结果加载相应的驱动。
3. 设备事件通知
mmc_bus_type
还提供了设备事件的通知机制,例如卡的插入、拔出等。这些事件通过mmc_uevent
函数来处理,并通知用户空间的相关程序(例如udevadm
)。- 这对于实现动态设备管理(例如,热插拔支持)是非常重要的。
4. 设备和驱动的管理
- 通过
mmc_bus_type
,MMC 子系统可以将 MMC 控制器(Host)和 MMC 卡(设备)与相应的驱动程序关联。驱动通过与mmc_bus_type
进行交互,能够接管设备的操作。
5. 设备的生命周期管理
- MMC 设备的生命周期(如初始化、配置、数据传输等)是通过
mmc_bus_type
进行协调的。它负责在卡的插入、拔出时执行必要的资源管理和硬件操作。
6. 统一的总线接口
mmc_bus_type
提供了一个统一的接口,用于管理不同类型的 MMC 设备(SD 卡、eMMC、SDIO 设备等)。它将各类设备抽象成一个通用的接口,使得上层系统无需关心具体的硬件细节。
1.1.1、mmc_dev_attrs
该属性为所有注册至该总线上的设备所默认创建的,其定义如下,主要提供只读的设备属性,该属性用于提供mmc card的类型,在具体的mmc card目录下(sysfs),会创建type的文件,该文件只读,读取该文件可以获取mmc card的类型,目前支持的类型包括mmc、sd、sdio、sd combo。
drivers/mmc/core/bus.c
static ssize_t type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mmc_card *card = mmc_dev_to_card(dev);
switch (card->type) {
case MMC_TYPE_MMC:
return sprintf(buf, "MMC\n");
case MMC_TYPE_SD:
return sprintf(buf, "SD\n");
case MMC_TYPE_SDIO:
return sprintf(buf, "SDIO\n");
case MMC_TYPE_SD_COMBO:
return sprintf(buf, "SDcombo\n");
default:
return -EFAULT;
}
}
static DEVICE_ATTR_RO(type);
static struct attribute *mmc_dev_attrs[] = {
&dev_attr_type.attr,
NULL,
};
ATTRIBUTE_GROUPS(mmc_dev);
1.1.2、mmc_bus_match
该接口主要用于mmc card、mmc driver的匹配检测,mmc bus的mmc driver由mmc 子系统实现,且针对mmc/sd/emmc等存储卡设备,其均与mmc子系统实现的mmc driver匹配,因此此处的mmc_bus_match并没有进行匹配检测,直接返回1,表示mmc 子系统实现的mmc driver可匹配所以注册至mmc bus上的mmc card。
1.1.3、mmc_bus_probe、mmc_bus_remove
这两个接口均直接调用mmc 子系统注册的mmc driver的probe/remove接口,实现简单。
drivers/mmc/core/bus.c
static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
return drv->probe(card);
}
static void mmc_bus_remove(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
drv->remove(card);
}
1.1.4、mmc_bus_uevent
该接口主要是提供mmc bus添加的event env,该接口主要提供“MMC_TYPE”、“MMC_NAME”、“MODALIAS”这三个env。若应用层的udev、mdev需要关注mmc card的这三个env,则可以添加对应这三个env的规则即可。
drivers/mmc/core/bus.c
static int
mmc_bus_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct mmc_card *card = mmc_dev_to_card(dev);
const char *type;
unsigned int i;
int retval = 0;
switch (card->type) {
case MMC_TYPE_MMC:
type = "MMC";
break;
case MMC_TYPE_SD:
type = "SD";
break;
case MMC_TYPE_SDIO:
type = "SDIO";
break;
case MMC_TYPE_SD_COMBO:
type = "SDcombo";
break;
default:
type = NULL;
}
if (type) {
retval = add_uevent_var(env, "MMC_TYPE=%s", type);
if (retval)
return retval;
}
if (mmc_card_sdio(card) || mmc_card_sd_combo(card)) {
retval = add_uevent_var(env, "SDIO_ID=%04X:%04X",
card->cis.vendor, card->cis.device);
if (retval)
return retval;
retval = add_uevent_var(env, "SDIO_REVISION=%u.%u",
card->major_rev, card->minor_rev);
if (retval)
return retval;
for (i = 0; i < card->num_info; i++) {
retval = add_uevent_var(env, "SDIO_INFO%u=%s", i+1, card->info[i]);
if (retval)
return retval;
}
}
/*
* SDIO (non-combo) cards are not handled by mmc_block driver and do not
* have accessible CID register which used by mmc_card_name() function.
*/
if (mmc_card_sdio(card))
return 0;
retval = add_uevent_var(env, "MMC_NAME=%s", mmc_card_name(card));
if (retval)
return retval;
/*
* Request the mmc_block device. Note: that this is a direct request
* for the module it carries no information as to what is inserted.
*/
retval = add_uevent_var(env, "MODALIAS=mmc:block");
return retval;
}
1.2、mmc driver的注册与注销接口
针对mmc_register_driver、mmc_unregister_driver接口,就是对driver_register、driver_unregister的封装。
drivers/mmc/core/bus.c
/**
* mmc_register_driver - register a media driver
* @drv: MMC media driver
*/
int mmc_register_driver(struct mmc_driver *drv)
{
drv->drv.bus = &mmc_bus_type;
return driver_register(&drv->drv);
}
EXPORT_SYMBOL(mmc_register_driver);
/**
* mmc_unregister_driver - unregister a media driver
* @drv: MMC media driver
*/
void mmc_unregister_driver(struct mmc_driver *drv)
{
drv->drv.bus = &mmc_bus_type;
driver_unregister(&drv->drv);
}
1.3、mmc card的注册与注销接口
主要包括mmc card内存的申请、mmc card的注册、mmc card的注销等接口。
1.3.1、mmc_alloc_card
该接口主要实现的流程图如下:
1.申请一个mmc_card类型的内存空间;
2.对mmc_card->dev成员进行初始化,并设置该device类型变量所依附的总线为mmc_bus;
3.设置mmc_card->dev的release接口为mmc_release_card,该接口实现mmc_card类型内存空间的释放操作;
4.设置mmc_card->dev的父设备为mmc_host->class_dev。
其实就是调用device模型对应的接口完成device类型变量的初始化,并完成mmc_card与mmc_host的绑定。
drivers/mmc/core/bus.c
/*
* Allocate and initialise a new MMC card structure.
*/
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
struct mmc_card *card;
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
if (!card)
return ERR_PTR(-ENOMEM);
card->host = host;
device_initialize(&card->dev);
card->dev.parent = mmc_classdev(host);
card->dev.bus = &mmc_bus_type;
card->dev.release = mmc_release_card;
card->dev.type = type;
return card;
}
1.3.2 mmc_add_card
该接口也包括两部分,主要内容如下。
drivers/mmc/core/bus.c
/*
* Register a new MMC card with the driver model.
*/
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);
dev_set_removable(&card->dev,
mmc_card_is_removable(card->host) ?
DEVICE_REMOVABLE : DEVICE_FIXED);
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);
}
mmc_add_card_debugfs(card);
card->dev.of_node = mmc_of_find_child_device(card->host, 0);
device_enable_async_suspend(&card->dev);
#ifdef CONFIG_SCM_CM
if (card->host->ops->encrypt_config)
card->host->ops->encrypt_config(card->host, 1);
#endif
ret = device_add(&card->dev);
if (ret)
return ret;
mmc_card_set_present(card);
return 0;
}
- 调用device_add,完成将该mmc_card注册至mmc bus上;
- 设置mmc_card的状态为在位状态。
1.3.3、mmc_remove_card
该接口主要将mmc_card的device类型的成员与mmc bus、mmc driver之间的绑定解除,并去除对device类型成员的引用计数,当mmc_card->dev的引用计数为0时,则触发mmc_card->dev->kobject->kobj_type->release的执行,即device_release接口的执行,该接口最终调用会调用接口mmc_card->dev->release接口(mmc_release_card),从而完成mmc_card对应内存的释放操作。
drivers/mmc/core/bus.c
/*
* Unregister a new MMC card with the driver model, and
* (eventually) free it.
*/
void mmc_remove_card(struct mmc_card *card)
{
struct mmc_host *host = card->host;
mmc_remove_card_debugfs(card);
if (mmc_card_present(card)) {
if (mmc_host_is_spi(card->host)) {
pr_info("%s: SPI card removed\n",
mmc_hostname(card->host));
} else {
pr_info("%s: card %04x removed\n",
mmc_hostname(card->host), card->rca);
}
device_del(&card->dev);
of_node_put(card->dev.of_node);
}
if (host->cqe_enabled) {
host->cqe_ops->cqe_disable(host);
host->cqe_enabled = false;
}
put_device(&card->dev);
}
二、Sdio bus分析
Sdio bus type的定义如下:
drivers/mmc/core/sdio_bus.c
static struct bus_type sdio_bus_type = {
.name = "sdio",
.dev_groups = sdio_dev_groups,
.match = sdio_bus_match,
.uevent = sdio_bus_uevent,
.probe = sdio_bus_probe,
.remove = sdio_bus_remove,
.pm = &sdio_bus_pm_ops,
};
通过与mmc bus type对比,来分析sdio bus。
以下基于:drivers/mmc/core/sdio_bus.c
2.1、sdio_dev_attrs
与mmc bus的dev_attr相比,sdio_dev_attrs则主要创建了class、vendor、device、modalias这四个只读的sysfs 属性文件,用于获取sdio device的class、vendor、device这三个属性。
static struct attribute *sdio_dev_attrs[] = {
&dev_attr_class.attr,
&dev_attr_vendor.attr,
&dev_attr_device.attr,
&dev_attr_revision.attr,
&dev_attr_info1.attr,
&dev_attr_info2.attr,
&dev_attr_info3.attr,
&dev_attr_info4.attr,
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(sdio_dev);
2.2、sdio_bus_match
与mmc bus的match接口相比,该匹配检测,则对sdio_driver、sdio_func进行绑定检测,主要包括对class、vendor、device这三个参数的匹配检测,只要匹配成功,则会进行sdio_driver、sdio_func绑定。
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct sdio_driver *sdrv = to_sdio_driver(drv);
if (sdio_match_device(func, sdrv))
return 1;
return 0;
}
2.3 sdio_bus_uevent
针对sdio bus而言,当执行kobject_uevent接口时,则针对sdio_bus而言会增加“SDIO_CLASS”、“SDIO_ID”、“MODALIAS”这三个env添加至uevent数组中,发送给应用层程序。
static int
sdio_bus_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct sdio_func *func = dev_to_sdio_func(dev);
unsigned int i;
if (add_uevent_var(env,
"SDIO_CLASS=%02X", func->class))
return -ENOMEM;
if (add_uevent_var(env,
"SDIO_ID=%04X:%04X", func->vendor, func->device))
return -ENOMEM;
if (add_uevent_var(env,
"SDIO_REVISION=%u.%u", func->major_rev, func->minor_rev))
return -ENOMEM;
for (i = 0; i < func->num_info; i++) {
if (add_uevent_var(env, "SDIO_INFO%u=%s", i+1, func->info[i]))
return -ENOMEM;
}
if (add_uevent_var(env,
"MODALIAS=sdio:c%02Xv%04Xd%04X",
func->class, func->vendor, func->device))
return -ENOMEM;
return 0;
}
2.4 sdio_bus_probe、sdio_bus_remove
这两个接口相比于mmc bus的probe/remove接口,增加了sdio_func、sdio_driver的匹配检测、电源管理以及设置block size的接口等等。
static int sdio_bus_probe(struct device *dev)
{
struct sdio_driver *drv = to_sdio_driver(dev->driver);
struct sdio_func *func = dev_to_sdio_func(dev);
const struct sdio_device_id *id;
int ret;
id = sdio_match_device(func, drv);
if (!id)
return -ENODEV;
ret = dev_pm_domain_attach(dev, false);
if (ret)
return ret;
atomic_inc(&func->card->sdio_funcs_probed);
/* Unbound SDIO functions are always suspended.
* During probe, the function is set active and the usage count
* is incremented. If the driver supports runtime PM,
* it should call pm_runtime_put_noidle() in its probe routine and
* pm_runtime_get_noresume() in its remove routine.
*/
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {
ret = pm_runtime_get_sync(dev);
if (ret < 0)
goto disable_runtimepm;
}
/* Set the default block size so the driver is sure it's something
* sensible. */
sdio_claim_host(func);
if (mmc_card_removed(func->card))
ret = -ENOMEDIUM;
else
ret = sdio_set_block_size(func, 0);
sdio_release_host(func);
if (ret)
goto disable_runtimepm;
ret = drv->probe(func, id);
if (ret)
goto disable_runtimepm;
return 0;
disable_runtimepm:
atomic_dec(&func->card->sdio_funcs_probed);
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
dev_pm_domain_detach(dev, false);
return ret;
}
static void sdio_bus_remove(struct device *dev)
{
struct sdio_driver *drv = to_sdio_driver(dev->driver);
struct sdio_func *func = dev_to_sdio_func(dev);
/* Make sure card is powered before invoking ->remove() */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_get_sync(dev);
drv->remove(func);
atomic_dec(&func->card->sdio_funcs_probed);
if (func->irq_handler) {
pr_warn("WARNING: driver %s did not remove its interrupt handler!\n",
drv->name);
sdio_claim_host(func);
sdio_release_irq(func);
sdio_release_host(func);
}
/* First, undo the increment made directly above */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
/* Then undo the runtime PM settings in sdio_bus_probe() */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_sync(dev);
dev_pm_domain_detach(dev, false);
}
2.5 sdio_driver的注册与注销接口
这两个接口的实现与mmc_driver的实现类似,均是简单的对driver_register/driver_unregister的封装(还有设置driver需要绑定的bus_type)。
/**
* sdio_register_driver - register a function driver
* @drv: SDIO function driver
*/
int sdio_register_driver(struct sdio_driver *drv)
{
drv->drv.name = drv->name;
drv->drv.bus = &sdio_bus_type;
return driver_register(&drv->drv);
}
EXPORT_SYMBOL_GPL(sdio_register_driver);
/**
* sdio_unregister_driver - unregister a function driver
* @drv: SDIO function driver
*/
void sdio_unregister_driver(struct sdio_driver *drv)
{
drv->drv.bus = &sdio_bus_type;
driver_unregister(&drv->drv);
}
EXPORT_SYMBOL_GPL(sdio_unregister_driver);
2.6 Sdio func的注册与注销接口
sdio_func的注册与注销接口对应于mmc_card的注册与注销接口。主要函数有sdio_alloc_func、sdio_add_func、sdio_remove_func、sdio_release_func(相比mmc card,多了针对acpi的配置调用)。
static void sdio_release_func(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
if (!(func->card->quirks & MMC_QUIRK_NONSTD_SDIO))
sdio_free_func_cis(func);
/*
* We have now removed the link to the tuples in the
* card structure, so remove the reference.
*/
put_device(&func->card->dev);
kfree(func->info);
kfree(func->tmpbuf);
kfree(func);
}
/*
* Allocate and initialise a new SDIO function structure.
*/
struct sdio_func *sdio_alloc_func(struct mmc_card *card)
{
struct sdio_func *func;
func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);
if (!func)
return ERR_PTR(-ENOMEM);
/*
* allocate buffer separately to make sure it's properly aligned for
* DMA usage (incl. 64 bit DMA)
*/
func->tmpbuf = kmalloc(4, GFP_KERNEL);
if (!func->tmpbuf) {
kfree(func);
return ERR_PTR(-ENOMEM);
}
func->card = card;
device_initialize(&func->dev);
/*
* We may link to tuples in the card structure,
* we need make sure we have a reference to it.
*/
get_device(&func->card->dev);
func->dev.parent = &card->dev;
func->dev.bus = &sdio_bus_type;
func->dev.release = sdio_release_func;
return func;
}
/*
* Register a new SDIO function with the driver model.
*/
int sdio_add_func(struct sdio_func *func)
{
int ret;
dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);
sdio_set_of_node(func);
sdio_acpi_set_handle(func);
device_enable_async_suspend(&func->dev);
ret = device_add(&func->dev);
if (ret == 0)
sdio_func_set_present(func);
return ret;
}
/*
* Unregister a SDIO function with the driver model, and
* (eventually) free it.
* This function can be called through error paths where sdio_add_func() was
* never executed (because a failure occurred at an earlier point).
*/
void sdio_remove_func(struct sdio_func *func)
{
if (sdio_func_present(func))
device_del(&func->dev);
of_node_put(func->dev.of_node);
put_device(&func->dev);
}
三、小结
mmc_bus_type
和sdio_bus_type
:定义了 MMC 和 SDIO 总线类型,用于注册和管理对应类型的设备。mmc_card
和mmc_driver
:mmc_card
表示一个 MMC 存储卡,mmc_driver
用于注册和管理 MMC 驱动,支持卡的探测和移除。sdio_func
和sdio_driver
:sdio_func
表示 SDIO 功能(外设接口),sdio_driver
用于注册和管理 SDIO 驱动,支持功能的探测和移除。