Linux MMC子系统5(基于Linux6.6)---MMC driver模块介绍
mmc 子系统为何实现了统一的mmc driver,主要有如下几点:
- Mmc host已提供了访问mmc card的方法(mmc_host->request);
- mmc/emmc/sd等协议规范均已定义mmc card(包括mmc/sd/tf等存储卡)支持的命令格式以及状态流转机制、寄存器定义等信息,因此可使用统一的驱动程序实现对所有厂家的card通信(这是由协议规范规定的,比如针对nandflash驱动模块,也不需要为nandflash设备实现特定驱动,因其协议规范也规定了nandflash需要支持的操作及通信格式等等)
基于以上几点,并不需要针对各厂家的mmc card,实现特殊的mmc driver,因此mmc 子系统实现了统一的mmc driver;
一、相关的数据结构及关联说明
针对mmc driver而言,相关的数据结构包括mmc_driver、device_driver、device、mmc_card、mmc_part、mmc_blk_data、mmc_queue、gendisk、request_queue等;
其中mmc_driver为一个mmc card驱动的抽象;device_driver、device为linux设备驱动模型相关的数据结构(该模块已在前面的文章中介绍,此处不再赘述);gendisk、request_queu、request_fn为块设备模型相关的数据结构(此处不展开);mmc_blk_data、mmc_queue主要用于实现mmc block device的通用块层与mmc host之间进行数据传输;而mmc_card则为mmc_driver所要驱动的外设。
这些数据结构之间的关联如下图所示,它们的关联关系说明如下:
- Mmc_driver与mmc_card,通过借助设备-总线-驱动模型的数据结构(device_driver、device)及接口,实现绑定与解绑操作;
- 针对mmc card,通过mmc_part可定义该mmc card的分区个数及分区大小等内容(mmc card中extended CSD寄存器中存储分区信息,当通过mmc rescan查找到mmc card时,会通过读取该寄存器的值,获取mmc card的分区信息);
- mmc_blk_data用于抽象mmc block device信息,这其中包括mmc_queue、request_queue、gendisk这些数据结构;
在完成mmc_driver与mmc_card的绑定过程中,在调用mmc_driver->probe进行设备探测时,则进行mmc block device的创建,针对每一个分区,均执行如下操作:
- 创建mmc_blk_data类型的变量,即为每一个分区均创建对应的通用块设备(gendisk类型);
- 创建mmc_queue,并为其创建对应的线程及处理接口(主要用于处理来自块I/O子系统的请求);
- 针对对mmc_queue,为其创建request_queue及其请求方法mmc_request_fn,并与gendisk进行关联;
- 将gendisk注册至块I/O子系统中,从而将本分区对应的通用块设备注册至块设备对应的bdev_map中,类似于字符设备的cdev_map;
1. MMC Driver 子系统
MMC 驱动子系统是 Linux 内核中专门负责与 MMC 存储设备进行交互的模块。它主要包括以下几个功能:
- 卡和主机控制器的抽象:处理不同类型的存储设备(如 SD 卡、eMMC 卡、UFS 等)。
- MMC 核心的命令调度:管理卡的初始化、读写请求和命令的调度。
- 设备支持:包括插拔检测、卡的电源管理等。
数据结构:
mmc_driver
:这是 MMC 驱动的核心结构体,定义了 MMC 设备与驱动程序的交互接口。例如,它包含了设备初始化、退出、驱动加载等函数。
struct mmc_driver {
const char *name; // 驱动名称
int (*probe)(struct mmc_card *card, struct mmc_host *host); // 驱动初始化
void (*remove)(struct mmc_card *card); // 驱动卸载
struct device_driver driver; // 与设备模型的集成
};
mmc_card
:表示一个插入的 MMC 卡,保存与卡相关的各种属性信息。
struct mmc_card {
struct mmc_host *host; // 所属的主机控制器
unsigned int capacity; // 存储卡的容量
struct mmc_device *dev; // 设备结构体
};
2. 设备-总线-驱动模型(Device-Bus-Driver Model)
Linux 采用设备-总线-驱动模型来实现硬件抽象,这使得设备驱动能够与硬件进行有效的交互,并且简化了设备管理。设备(Device)、总线(Bus)和驱动(Driver)之间通过统一的模型进行连接。
- 设备模型:设备是一个在 Linux 中表示硬件的抽象体,包含了设备的状态和属性。
- 总线模型:总线是设备和驱动程序之间的桥梁,负责设备的枚举和驱动的绑定。
- 驱动模型:驱动程序负责管理和控制设备的操作,确保设备可以正确地与系统交互。
在 MMC 驱动中,mmc_driver
是通过总线模型(如 Platform Bus 或 SDHCI 总线)与设备(如 mmc_host
)连接的。
数据结构:
struct device_driver
:驱动程序的基类,包含驱动的名称、初始化函数、匹配函数等。
struct device_driver {
const char *name; // 驱动名称
struct bus_type *bus; // 驱动所属的总线类型
int (*probe)(struct device *dev); // 设备匹配并初始化
int (*remove)(struct device *dev); // 设备移除
};
struct device
:表示一个设备,包含设备的基本信息和与驱动程序的连接。
3. 块设备驱动模型(Block Device Driver Model)
在 Linux 中,块设备是指按块访问的设备,如硬盘、SSD、SD 卡等。MMC 存储设备也属于块设备的一种。块设备驱动程序负责对这些设备进行读写操作,提供高效的磁盘访问方式。
- 块设备模型:块设备的驱动程序是通过 block subsystem 提供的接口进行管理和访问的。它管理磁盘分区、文件系统以及对设备的读写操作。
数据结构:
struct block_device
:表示块设备的结构体,它包含设备信息、指向块设备操作的指针等。
struct block_device {
struct device *bd_device; // 设备的基础信息
struct block_operations *bd_disk; // 设备操作接口
};
struct block_operations
:包含了对块设备的操作接口,如读、写、打开、关闭等。
struct block_operations {
int (*open)(struct block_device *bdev); // 打开设备
int (*read)(struct block_device *bdev, char *buffer); // 读取设备
int (*write)(struct block_device *bdev, const char *buffer); // 写入设备
};
4. MMC Host 子系统
MMC Host 子系统负责管理与物理硬件的通信,控制 MMC 卡与主机控制器之间的数据流。它提供了主机控制器与卡之间的抽象,并将高层的 MMC 请求转发到硬件层执行。
数据结构:
mmc_host
:表示一个 MMC 主机控制器,包含与硬件交互的必要信息,如支持的操作、最大块大小等。
struct mmc_host {
struct device *dev; // 设备的基础信息
struct mmc_ops *ops; // 主机控制器的操作函数集
struct mmc_card *card; // 当前插入的 MMC 卡
unsigned int max_blk_size; // 最大块大小
unsigned int caps; // 控制器的能力
};
mmc_ops
:这是一个包含操作函数的结构体,控制 MMC 主机控制器的行为。
struct mmc_ops {
int (*init)(struct mmc_host *host); // 初始化主机控制器
int (*request)(struct mmc_host *host, struct mmc_request *mrq); // 请求处理
int (*send_cmd)(struct mmc_host *host, struct mmc_command *cmd); // 发送命令
};
mmc_request
:表示一个 MMC 请求,包含一个命令和数据传输操作。
struct mmc_request {
struct mmc_command *cmd; // 发送的命令
struct mmc_data *data; // 数据操作
struct list_head list; // 队列链表
};
本数据结构体包括了mmc driver 子系统、设备-总线-驱动模型、块设备驱动模型、mmc host子系统这几个模块。针对mmc driver的probe而言,也就是完成如下图的数据结构间的关联。
1.1 mmc_driver 数据结构
/*
* MMC device driver (e.g., Flash card, I/O card...)
*/
struct mmc_driver {
struct device_driver drv;
int (*probe)(struct mmc_card *);
void (*remove)(struct mmc_card *);
void (*shutdown)(struct mmc_card *);
};
该数据结构主要包含了device_driver类型的成员,并定义了probe、remove、suspend、resume四个函数指针;这四个函数指针的功能与device_driver中对应的函数指针时对应的。
1.2 struct mmc_card数据结构
该数据结构之前文章已说明。此处不再赘述;
1.3 struct mmc_part数据结构
该数据结构用于描述一个mmc card 分区,主要包括分区大小、分区的类型、分区名称、是否为只读分区等信息。mmccard中extended CSD寄存器中存储分区信息,当通过mmc rescan查找到mmc card时,会通过读取该寄存器的值,获取mmc card的分区信息。
include/linux/mmc/card.h
/*
* MMC Physical partitions
*/
struct mmc_part {
u64 size; /* partition size (in bytes) */
unsigned int part_cfg; /* partition type */
char name[MAX_MMC_PART_NAME_LEN];
bool force_ro; /* to make boot parts RO by default */
unsigned int area_type;
#define MMC_BLK_DATA_AREA_MAIN (1<<0)
#define MMC_BLK_DATA_AREA_BOOT (1<<1)
#define MMC_BLK_DATA_AREA_GP (1<<2)
#define MMC_BLK_DATA_AREA_RPMB (1<<3)
};
1.4 struct mmc_blk_data数据结构
该数据结构用于抽象一个mmc card 分区对应的block device的数据信息,其中包括:
- 该分区对应的通用磁盘设备gendisk,用于将该分区注册至块设备层,以便应用程序可通过块设备I/O访问该分区;
- mmc_queue用于抽象mmc 块设备的请求队列,该数据结构中包括了块设备请求队列成员(request_queue及其访问方法,用于处理块I/O层的数据请求信息,并实现将请求分发给mmc host,进而完成与mmc card的数据通信);
- 链表成员part用于将该mmc card所有分区对应的mmc_blk_data变量链接至一起;
- force_ro、power_ro_lock主要用于描述该分区对应块设备的设备属性信息。
drivers/mmc/core/block.c
/*
* There is one mmc_blk_data per slot.
*/
struct mmc_blk_data {
struct device *parent;
struct gendisk *disk;
struct mmc_queue queue;
struct list_head part;
struct list_head rpmbs;
unsigned int flags;
#define MMC_BLK_CMD23 (1 << 0) /* Can do SET_BLOCK_COUNT for multiblock */
#define MMC_BLK_REL_WR (1 << 1) /* MMC Reliable write support */
struct kref kref;
unsigned int read_only;
unsigned int part_type;
unsigned int reset_done;
#define MMC_BLK_READ BIT(0)
#define MMC_BLK_WRITE BIT(1)
#define MMC_BLK_DISCARD BIT(2)
#define MMC_BLK_SECDISCARD BIT(3)
#define MMC_BLK_CQE_RECOVERY BIT(4)
#define MMC_BLK_TRIM BIT(5)
/*
* Only set in main mmc_blk_data associated
* with mmc_card with dev_set_drvdata, and keeps
* track of the current selected device partition.
*/
unsigned int part_curr;
#define MMC_BLK_PART_INVALID UINT_MAX /* Unknown partition active */
int area_type;
/* debugfs files (only in main mmc_blk_data) */
struct dentry *status_dentry;
struct dentry *ext_csd_dentry;
};
1.5、struct mmc_queue数据结构
该数据结构用于描述块设备I/O的request请求,该数据结构包含的内容如下:
- card用于指向该队列对应的mmc_card;
- thread用于描述该mmc_queue对应的线程,该线程主要用于处理所有从块I/O层下发的请求(request_queue);
- request_queue类型的变量用于向块I/O层注册request_queue及其请求方法,以便块I/O层可以将上层的数据请求下发至mmc block层;
- issue_fn用于mmc block层将数据请求下发给mmc host层,由mmc host层继续实现数据请求的处理。
drivers/mmc/core/queue.h
struct mmc_queue {
struct mmc_card *card;
struct mmc_ctx ctx;
struct blk_mq_tag_set tag_set;
struct mmc_blk_data *blkdata;
struct request_queue *queue;
spinlock_t lock;
int in_flight[MMC_ISSUE_MAX];
unsigned int cqe_busy;
#define MMC_CQE_DCMD_BUSY BIT(0)
bool busy;
bool recovery_needed;
bool in_recovery;
bool rw_wait;
bool waiting;
struct work_struct recovery_work;
wait_queue_head_t wait;
struct request *recovery_req;
struct request *complete_req;
struct mutex complete_lock;
struct work_struct complete_work;
};
1.6、Mmc block层的块I/O请求处理
Mmc block层主要借助mmc_queue类型的变量实现块i/o请求,mmc block层提供了块i/o请求处理的线程。块i/o层接口层通过调用mmc block层的request方法(即request_queue->request_fn,即mmc_request_fn接口),该方法通过调用wake_up_process接口,唤醒mmc block层的块i/o处理线程,该线程处理接口则调用mq->issue_fn进行i/o请求的分发,即调用mmc_blk_issue_rq接口,该接口通过一系统的接口调用,最终即调用mmc_host->ops->request,通过mmc_host提供的方法,实现与mmc_card的通信操作。Mmc block层的块i/o处理流程如下所示。
二、mmc driver的probe/remove
上面介绍了mmc driver层相关的数据结构,通过这些数据结构间的关联图,基本上对mmc driver 的probe/remove接口也有了一定的了解,而mmc driver的probe/remove接口,也就是完成上述数据结构间的关联的建立与消除。
2.1 mmc_driver的定义如注册
mmc_driver的定义如下,该driver的名称为“mmcblk”,主要实现了probe/remove接口以及suspend/resume接口(这两个接口主要用于电源管理)。
drivers/mmc/core/block.c
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
.pm = &mmc_blk_pm_ops,
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.shutdown = mmc_blk_shutdown,
};
2.2、mmc_driver与mmc_card的绑定流程
mmc_driver与mmc_card借助设备-总线-驱动模型提供的接口完成,mmc_driver与mmc_card绑定,并执行mmc_driver->probe接口。mmc_driver、mmc_card、mmc_bus之间的绑定流程如下所示:
- 通过driver_register与device_register,从而完成mmc_driver、mmc_card、mmc_bus之间的绑定。
(当mmc rescan一个mmc card后,则会创建mmc card,并同时调用device_register,将该mmc_card注册至mmc_bus中,并触发__device_attach,从而完成mmc_card与mmc_driver的绑定,并调用mmc_driver->probe,即调用mmc_blk_probe接口,实现mmc block device的创建及注册流程。)
2.2.1、mmc_blk_probe
mmc_blk_probe实现的功能如下:
针对该mmc card的每一个分区,均执行如下操作:
- 为每一个分区,创建对应的通用磁盘设备gendisk;
- 设置gendisk的fops为mmc_bdops;
- 为每一个通用磁盘设备,均创建request_queue及其处理方法;
- 为该通用磁盘设备创建mmc block块i/o请求机制;
- 将该分区对应的通用磁盘设备注册至块设备i/o中,以便应用层可通过块设备访问该mmc card 分区。
drivers/mmc/core/block.c
static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md;
int ret = 0;
/*
* Check that the card supports the command class(es) we need.
*/
if (!(card->csd.cmdclass & CCC_BLOCK_READ))
return -ENODEV;
mmc_fixup_device(card, mmc_blk_fixups);
card->complete_wq = alloc_workqueue("mmc_complete",
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
if (!card->complete_wq) {
pr_err("Failed to create mmc completion workqueue");
return -ENOMEM;
}
md = mmc_blk_alloc(card);
if (IS_ERR(md)) {
ret = PTR_ERR(md);
goto out_free;
}
ret = mmc_blk_alloc_parts(card, md);
if (ret)
goto out;
/* Add two debugfs entries */
mmc_blk_add_debugfs(card, md);
pm_runtime_set_autosuspend_delay(&card->dev, 3000);
pm_runtime_use_autosuspend(&card->dev);
/*
* Don't enable runtime PM for SD-combo cards here. Leave that
* decision to be taken during the SDIO init sequence instead.
*/
if (!mmc_card_sd_combo(card)) {
pm_runtime_set_active(&card->dev);
pm_runtime_enable(&card->dev);
}
return 0;
out:
mmc_blk_remove_parts(card, md);
mmc_blk_remove_req(md);
out_free:
destroy_workqueue(card->complete_wq);
return ret;
}
2.3、mmc_driver与mmc_card的解绑流程
当mmc card rescan机制,检测到mmc card移除后,在mmc card release的过程中,即调用device_unregiser接口,完成解绑流程,从而触发mmc_blk_remove接口的调用,完成解绑操作。而mmc_blk_remove接口的主要功能如下:
针对每一个mmc card分区,均执行如下操作:
- 将该分区对应的mmc 块请求处理机制删除;
- 将该分区对应的通用磁盘设备从块设备模块中移除;
- 解除mmc_card与mmc_host的关联;