Linxu MMC 驱动子系统

Linux MMC 驱动子系统详解_linux mmc驱动-CSDN博客

SD/SDIO/MMC 驱动是一种基于 SDMMC 和 SD SPI 主机驱动的协议级驱动程序,目前已支持 SD 存储器、SDIO 卡和 eMMC 芯片。

因为linux内核mmc子系统里面已经实现了这些协议,我们以后并不需要重新实现这些,只需要对协议有个简单的了解。

mmc是比较老的存储卡了,sd是mmc的替代者,sdio是基于sd而额外开发出的一种io接口卡。


硬件关联

CPU、MMC controller、存储设备之间的关联如下图所示,主要包括了MMC controller、总线、存储卡等内容的连接,针对控制器与设备的总线连接,主要包括时钟、数据、命令三种类型的引脚,而这些引脚中的cd引脚主要用于卡的在位检测,当mmc controller检测到该位的变化后,则会进行mmc card的注册或注销操作。

目录说明

针对mmc子系统,在代码实现上主要包括mmc core、mmc block、 mmc host这三个模块。

mmc card:衔接最上层应用,主要用于实现mmc block驱动以及mmc driver即mmc层驱动(实际上我研究的源代码并没有这个目录(5.15.0-52-generic),猜测是合并到了core目录下);
而mmc core:实现mmc/sd/sdio协议,主要包括mmc 总线、sdio总线的实现、mmc device、mmc driver的注册接口、mmc host与mmc card的注册与注销接口等内容。
mmc host:存放各个mmc/sd/sdio控制器的驱动代码,最终操作mmc/sd/sdio卡的部分;


mmc子系统的逻辑架构

MMC子系统从上到下分为3层

块设备层(MMC card):与Linux的块设备子系统对接,实现块设备驱动以及完成请求,如sys_open调用;通过调用core接口函数(具体如host->ops->rquest),驱动MMC core抽象出来的虚拟的card设备,如mmc、sd、tf卡,实现读写数据。

核心层(MMC core):是不同协议和规范的实现,为MMC控制器层和块设备驱动层提供接口函数。

核心层封装了 MMC/SD 卡的命令(CMD),例如存储卡的识别、设置、读写、识别、设置等命令。

MMC核心层由三个部分组成:MMC,SD和SDIO,分别为三类设备驱动提供接口函数;

core.c 把 MMC 卡、 SD 卡的共性抽象出来,它们的差别由 sd.c 和 sd_ops.c 、 mmc.c 和 mmc_ops.c 来完成。

控制器层(MMC host):主机端MMC controller的驱动,依赖于平台,由struct mmc_host描述。

围绕此结构设计了struct mmc_host_ops(访问方法)、struct mmc_ios(相关参数)、struct mmc_bus_ops(电源管理和在位检测方法)
针对不同芯片,实现不同控制器对应的驱动代码。

 

块设备层与Linux的块设备子系统对接,实现块设备驱动以及完成请求,具体协议经过核心层的接口,最终通过控制器层完成传输,对MMC设备进行实际的操作。

更详细的结构图如下,指明了个部分的相关实现文件:

mmc core指的是mmc 子系统的核心,这里的mmc表示的是mmc总线、结构、设备相关的统称,而下方文件名的mmc单指mmc卡,区别于sd卡和sdio卡。

drivers/mmc/core/mmc.c(提供接口),
drivers/mmc/core/mmc-ops.c(提供和mmc type card协议相关的操作)

在mmc core层中的bus指的是由core抽象出来的虚拟总线,而与物理卡连接的MMC bus是物理的实际总线,是和host controller直接关联的。

设备-总线-驱动模型

针对MMC子系统而言,主要使用了系统中的两个模型:设备-总线-驱动模型块设备驱动模型。

在Linux驱动模型框架下,三者对应结构体以及MMC驱动子系统对应的实现关系如下:

总线 (struct bus_type) —— MMC总线( mmc_bus )
设备(struct device) —— 被封装在platform_device下的主设备 host
驱动 (struct device_driver) —— 依附于MMC总线的MMC驱动( mmc_driver )
三者之间的关联图如下,每一个具体的总线均包括设备和驱动两部分,而每一个具体总线的所有添加的设备均链接至device下,每一个总线的所有注册的驱动均链接至drivers,而bus接口所有实现的功能也可以大致分为总线的注册、设备的注册、驱动的注册这三个部分。

设备和对应的驱动必须依附于同一种总线。

MMC驱动抽象模型

MMC驱动模型也是基于实际的硬件连接进行抽象的。

  • 针对通信总线,抽象出mmc_bus;
  • 针对mmc controller,该子系统抽象为mmc_host,用于描述一个进行设备通信的控制器,提供了相应的访问接口(记为mmc_host->request);
  • 针对 mmc,sd,tf,mmc子系统完成了统一的mmc driver,针对mmc总线规范以及SD规范,其已经详细的定义了一个存储卡的通信方式、通信命令,因此LINUX mmc系统定义了mmc driver,用于和Mmc,sd,tf等卡的通信,而不需要驱动开发人员来开发卡驱动。

特点:

1,MMC总线模型仅注册了一个驱动类型,即mmc driver

2,一个Mmc host与一个mmc card绑定

3,mmc card属于热插拔的设备,而mmc card的创建主要由mmc host负责探测与创建,mmc host根据卡在位检测引脚,当检测到mmc card的存在后,即创建mmc card,同时注册到Mmc bus上,并完成与Mmc driver的绑定操作

4,host和card可以分别理解为MMC device的两个子设备:mmc主设备和mmc从设备,其中host为集成于MMC设备内部的MMC controller,card为MMC设备内部实际的存储设备。

SDIO驱动抽象模型

sdio总线驱动模型和mmc类似,结构体上的区别为其driver类型为sdio_driver,并增加了sdio_func接头体变量(该结构体包含了该sdio设备相关的厂商id,设备id,同时包含了mmc_card)。

因sdio主要突出接口的概念,其设备端可以连接wifi,gps等设备,因此其外设驱动需要由驱动工程师自己实现,sdio驱动模块不提供对应的驱动。

MMC/SDIO总线

总线接口实现的功能可分为总线的注册、设备的注册、驱动的注册这三个部分。

总线结构体定义

结构体定义位于core\bus.c

static struct bus_type mmc_bus_type = {
  // 总线名称
    .name        = "mmc",
    .dev_groups    = mmc_dev_groups,
    // match接口用于实现mmc card与mmc driver的匹配检测,返回值均为1;
    .match        = mmc_bus_match,  
    // 应用层通知接口,用于添加该mmc bus的uevent参数(在调用device_add时,会调用kobject_uevent向应用层发送设备添加相关的事件,而kobject_uevent会调用该device所属bus和class的uevent接口,添加需要发送到应用的event参数
    .uevent        = mmc_bus_uevent,
    // probe接口主要用于mmc card与mmc driver匹配成功后,则会调用该mmc bus的probe接口实现探测操作;
    .probe        = mmc_bus_probe,
    // remove接口主要用于mmc card与mmc driver解绑时,调用该接口,进行remove操作(对于mmc drivemmc_ops
    .shutdown    = mmc_bus_shutdown,
    // pm是电源管理相关的接口。
    .pm        = &mmc_bus_pm_ops,
};

总线匹配接口 .mmc_bus_match


当向linux系统总线添加设备或驱动时,总是会调用各总线对应的match匹配函数来判断驱动和设备是否匹配。

此处的mmc_bus_match并没有进行匹配检测,直接返回1,表示mmc子系统实现的mmc driver可匹配所有注册至mmc bus上的mmc card

即总线上的match函数并不做过滤,而是由probe函数来进行过滤。

 sdio总线结构体


位于sdio_bus.c

static struct bus_type sdio_bus_type = {
    .name        = "sdio",
    .dev_groups    = sdio_dev_groups,mmc_ops
    .match        = sdio_bus_match,  // 根据id_table来匹配
    .uevent        = sdio_bus_uevent,
    .probe        = sdio_bus_probe,
    .remove        = sdio_bus_remove,
    .pm        = &sdio_bus_pm_ops,
};

 总线注册

调用入口位于core/core.c,通过mmc_init()实现。

subsys_initcall(mmc_init);

static int __init mmc_init(void)
{
    int ret;
    // 将mmc总线注册到linux的总线系统中,管理块设备
    ret = mmc_register_bus();

    // 注册mmc_host_class
    ret = mmc_register_host_class();

    // 注册sido总线到linux的总线系统中,管理sdio接口类型的设备
    ret = sdio_register_bus();

    return 0;
}

主要工作是:

a: mmc_register_bus注册mmc总线,这个总线主要是为card目录里实现的mmc设备驱动层和mmc控制器十里湖按一个Mmc(包括sd/sdio) 设备对象建立的。

b:sdio_register_bus这时sdio的部分,它比较特殊,需要额外的一条总线。

具体包括两个方便:

  • 利用bus_register()注册mmc_bus,包括Mmc总线和sdio总线。对应sysfs下的/sys/bus/mmc目录。
  • 利用class_register()注册Mmc_host_class,对应sysfs下的/sys/class/mmc_host目录。

core/bus.c

int mmc_register_bus(void)
{
    // 实际调用内核接口,注册总线
    return bus_register(&mmc_bus_type);
}

core/sdio_bus.c

int sdio_register_bus(void)
{
    return bus_register(&sdio_bus_type);
}

 

驱动注册

mmc_driver的注册、注销接口是对内核函数的封装。实现将mmc_driver注册到mmc_bus总线中。

调用入口位于core/block.c,通过mmc_blk_init()实现,先给出Mmc设备结构体的定义。

static struct mmc_driver mmc_driver = {
    .drv        = {device_register
        .name    = "mmcblk",
        .pm    = &mmc_blk_pm_ops,
    },
    .probe        = mmc_blk_probe,  // probe回调函数
    .remove        = mmc_blk_remove,
    .shutdown    = mmc_blk_shutdown,
};

入口函数:

core/block.c

module_init(mmc_blk_init);

static int __init mmc_blk_init(void)
{
    int res;

    // 注册mmc_rpmb_bus总线
    res  = bus_register(&mmc_rpmb_bus_type);

    res = alloc_chrdev_region(&mmc_rpmb_devt, 0, MAX_DEVICES, "rpmb");

    // 注册块设备,申请块设备号
    res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");

    // 将mmc_driver注册到mmc_bus总线系统中
    res = mmc_register_driver(&mmc_driver);

    return 0;
}

关于RPMB总线:RPMB(Replay Protected Memory Block重放保护内存块)Partition 是 eMMC 中的一个具有安全特性的分区。eMMC 在写入数据到 RPMB 时,会校验数据的合法性,只有指定的 Host 才能够写入,同时在读数据时,也提供了签名机制,保证 Host 读取到的数据是 RPMB 内部数据,而不是攻击者伪造的数据。

 core/bus.c

int mmc_register_driver(struct mmc_driver *drv)
{
    drv->drv.bus = &mmc_bus_type;
    // 实际调用内核接口,注册设备到总线系统
    return driver_register(&drv->drv);
}

// 使用EXPORT_SYMBOL将函数以符号的方式导出给其他模块使用。
EXPORT_SYMBOL(mmc_register_driver);

主要步骤包括:

a:通过register_blkdev()向内核注册块设备。(仅注册,初始化的其他操作在mmc_driver结构体的probe接口中完成) ,借助该块设备驱动模型,将Mmc card与vfs(虚拟文件系统)完成了关联,即可通过系统调用借助VFS模型实现对块设备的读写访问操作。

b:调用Mmc_register_driver()将Mmc_driver注册到mmc_bus总线系统。简单封装,和大部分驱动注册方式一致。

SDIO驱动注册

这两个接口的实现与mmc_driver的实现类似,均是简单的对driver_register/driver_unregister的封装(还有设置driver需要绑定的bus_type)

sdio_uart.c

module_init(sdio_uart_init);

static int __init sdio_uart_init(void)
{
    // ……
    ret = tty_register_driver(tty_drv);

    ret = sdio_register_driver(&sdio_uart_driver);
    // ……
}

sdio_bus.c

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);

设备注册

主要包括mmc card内存的申请、mmc card的注册、mmc card的注销等接口。

调用入口位于实际host设备的驱动文件中,通过xxx_driver实现。下面以mvsdio驱动为例分析。

host/mvsdio.c

module_platform_driver(mvsd_driver);

static struct platform_driver mvsd_driver = device_register{
    .probe        = mvsd_probe,
    .remove        = mvsd_remove,
    .driver        = {
        .name    = DRIVER_NAME,
        .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        .of_match_table = mvsdio_dt_ids,
    },
};

// 在probe回调中调用
static int mvsd_probe(struct platform_device *pdev)
{
    // ……
    // 实例化一个控制器对象
    mmc = mmc_alloc_host(sizeof(struct mvsd_host), &pdev->dev);
    // ……
    mmc->ops = &mvsd_ops;  // 控制器操作集
    // ……(一系列对控制器对象的初始化工作)
    ret = mmc_add_host(mmc);
    // ……
}

// 控制器操作集,编写控制器驱动的一个主要任务就是实现这个操作集
static const struct mmc_host_ops mvsd_ops = {
    .request        = mvsd_request,  // 最终执行硬件操作的函数,参数由核心层提供,由核心层更上一层的card设备驱动层向下调用
    .get_ro            = mmc_gpio_get_ro,  // 判断是否写保护
    .set_ios        = mvsd_set_ios,  // 配置控制器的函数
    .enable_sdio_irq    = mvsd_enable_sdio_irq,  // 与sdio相关
};

host.c

mmc host子系统提供了延迟队列机制,在执行mmc_alloc_host、mmc_add_host后,则完成了mmc card rescan延迟工作队列及其处理接口的创建INIT_DELAYED_WORK。

若要触发mmc card rescan(即调用工作队列),则调用mmc_detect_change接口,即可触发mmc card rescan(即完成mmc_host->detect队列的调度)

struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
    // ……
    // 将mmc_rescan指定为延时工作队列的工作函数
    INIT_DELAYED_WORK(&host->detect, mmc_rescan);
    // ……
}

EXPORT_SYMBOL(mmc_alloc_host);

mmc_rescan函数的大致调用流程如下,由mmc子系统通过mmc card的rescan机制,实现Mmc card的自动检测及注册机制,依次完成了对sdio,sd和mmc设备的添加与移除操作。

换句话说,是使用事件的触发监控机制完成了卡(sd mmc sdio)的热插拔处理。

mmc_rescan[core.c]-->
    mmc_rescan_try_freq[core.c]-->
        mmc_attach_sdio[sdio.c]-->
            mmc_attach_bus[core.c]
            mmc_sdio_init_card[sdio.c]-->
                mmc_alloc_card[bus.c]
            sdio_init_func[sdio.c]-->
                sdio_alloc_func[sdio_bus.c]
            mmc_add_card[bus.c]
            sdio_add_func[sdio_bus.c]
        mmc_attach_sd[sd.c]-->
            mmc_attach_bus[core.c]
            mmc_sd_init_card[sd.c]-->
                mmc_alloc_card[bus.c]
            mmc_add_card[bus.c]
        mmc_attach_mmc[mmc.c]-->
            mmc_attach_bus[core.c]
            mmc_init_card[mmc.c]-->
                mmc_alloc_card[bus.c]
            mmc_add_card[bus.c]

从mmc_rescan调用关系中可以看出,mmc设备注册的过程依次完成了sdio设备、sd卡和mmc卡设备的初始化。

mmc_attach_sdio()

SDIO卡初始化的入口。

a:向卡发送CMD5命令,该命令有两个作用:

第一:通过判断卡是否有反馈信息来判断是否为SDIO设备mmc_send_io_ip_cond():

1)如果有响应,并且响应中的MP位为0,说明对应卡槽中的卡位SDIO卡,进而开始SDIO卡的初始化流程;

2)如果命令没有响应,则说明对应卡槽的卡为SD或MMC卡,进而开始SD/MMC卡的初始化流程(SDIO卡使用SDIO协议,SD卡使用SD协议)

3)如果有响应,且响应中的MP位为1,说明这个卡Mmc_alloc_card不但是SDIO卡,同时也是SD卡,也就是所谓的combo卡,则进行combo卡的初始化流程mmc_sdio_ops

第二,如果是SDIO设备,就会给Host反馈电压信息,就是说告诉Host,本卡锁能支持的电压是多少。

b: 设置sdio卡的总线操作集mmc_attach_bus(),传入struct mmc_bus_ops类型的实现mmc_sdio_ops。

void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{
    host->bus_ops = ops;
}

c:host根据SDIO卡反馈回来的电压要求,给其提供合适的电压mmc_select_voltage()

d:对sdio卡进行探测和初始化mmc_sdio_init_card()

e:注册SDIO的各个功能模块sdio_init_func()

g. 将所有SDIO功能添加到device架构中sdio_add_func()

mmc_alloc_card():
调用device模型对应的接口完成device类型变量的初始化,并完成mmc_card与mmc_host的绑定。

mmc_add_card():

调用device_add,完成将该mmc_card注册至mmc bus上;
设置mmc_card的状态为在位状态。
sdio func
sdio_func的注册与注销接口对应于mmc_card的注册与注销接口。主要函数有sdio_alloc_func、sdio_add_func、sdio_remove_func、sdio_release_func(相比mmc card,多了针对acpi的配置调用)


mmc_attach_sd()

SD卡初始化的入口

a. 发送CMD41指令,(sd卡支持该指令,但mmc卡不支持,所以可以以此区分)mmc_send_app_op_cond()

b. 设置sdio卡的总线操作集mmc_attach_bus(),传入struct mmc_bus_ops类型的实现mmc_sd_ops。

c. 设置合适的电压mmc_select_voltage()

d. 调用mmc_sd_init_card()(探测和初始化),获取mmc card的csd、cid,并创建mmc_card,并对mmc card进行初始化(如是否只读等信息)

e.调用mmc_add_card(),将该mmc_card注册至mmc_bus中,该接口会调用device_register将mmc_card注册至mmc_bus上,而这即触发mmc_driver与mmc_card的绑定流程,从而调用mmc_driver->probe接口,即执行mmc block device的注册操作(待解决,没有找到device_register相关代码)。


mmc_attach_mmc()

mmc卡初始化入口

a. 发送CMD1指令mmc_send_op_cond()

b. 设置mmc卡的总线操作集mmc_attach_bus(),传入struct mmc_bus_ops类型的实现mmc_ops。

c. 选择一个card和host都支持的最低工作电压mmc_select_voltage()

d. 初始化card使其进入工作状态mmc_init_card()

e. 为card构造对应的mmc_card并且注册到mmc_bus中mmc_add_card(),之后mmc_card就挂在了mmc_bus上,会和mmc_bus上的block(mmc_driver)匹配起来。相应block(mmc_driver)就会进行probe,驱动card,实现card的实际功能(也就是存储设备的功能)。会对接到块设备子系统中。

上面多次提到了mmc_bus_ops结构体,这是一个定义在core/core.h中的,用于表示总线操作的结构体。

代码调用顺序

 mmc驱动的总入口为 host/dw_mmc-rockchip.c文件

sdio通过host/sdhci.c文件中的sdhci_alloc_host()函数调用mmc_alloc_host

SD卡通过host/dw_mmc.c文件中的dw_mci_probe函数调用mmc_alloc_host

mmc_alloc_host()函数中指定mmc_rescan()为延时队列工作函数

mmc_rescan()函数中对所有mmc总线上的设备进行探测:

freqs数组的定义在core/core.h中

在mmc_rescan_try_freq()函数中依次探测该总线接了什么设备

在core/core.c中的mmc_rescan_try_freq()函数中存在如下代码:

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2023-12-12 13:32:07       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-12 13:32:07       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-12 13:32:07       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-12 13:32:07       20 阅读

热门阅读

  1. gdb使用

    gdb使用

    2023-12-12 13:32:07      40 阅读
  2. NB-IoT BC260Y Open CPU SDK⑫多任务调度应用

    2023-12-12 13:32:07       33 阅读
  3. C++大型项目经验

    2023-12-12 13:32:07       39 阅读
  4. Windows使用virtualenv创建python环境

    2023-12-12 13:32:07       42 阅读
  5. 使用CloudCompare计算点云曲率 - 编程指南

    2023-12-12 13:32:07       44 阅读
  6. C++相关闲碎记录(7)

    2023-12-12 13:32:07       29 阅读
  7. UGUI - 动态赋值后刷新不及时问题

    2023-12-12 13:32:07       44 阅读