Linux MMC子系统3

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;
}
  1. 调用device_add,完成将该mmc_card注册至mmc bus上;
  2. 设置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_typesdio_bus_type:定义了 MMC 和 SDIO 总线类型,用于注册和管理对应类型的设备。
  • mmc_cardmmc_drivermmc_card 表示一个 MMC 存储卡,mmc_driver 用于注册和管理 MMC 驱动,支持卡的探测和移除。
  • sdio_funcsdio_driversdio_func 表示 SDIO 功能(外设接口),sdio_driver 用于注册和管理 SDIO 驱动,支持功能的探测和移除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值