mmc子系统框架

Linux kernel把mmc,sd以及sdio三者的驱动代码整合在一起,俗称mmc子系统。源码位于drivers/mmc下。其下有三个子目录,分别是:card、core、host,其中,card用于构建一个块设备作为上层与mmc子系统沟通的桥梁;core抽象了mmc,sd,sdio三者的通用操作;host则是各类平台上的host驱动代码,包括如TI Omap的omap_hsmmc,三星的s3cmci等。具体的SOC,会根据实际情况再次封装(比如rockchip采用新思designwave的IP),就重新封装为struct dw_mci。DesignWare是SoC/ASIC设计者最钟爱的设计IP库和验证IP库。它包括一个独立于工艺的、经验证的、可综合的虚拟微架构的元件集合,包括逻辑、算术、存储和专用元件系列,超过140个模块。

一、Mmc子系统涉及总线(SD为例)

1、Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。设备树指定compatible属性与源码匹配后调用对应的probe函数来初始化对应平台的host硬件。

2、Card驱动相应的driver和device挂载在mmc自己创建的虚拟总线mmc_bus_type下。该总线的结构体定义在driver/mmc/core/bus.c;因为是统一的协议,mmc_driver在内核已经写好并已注册driver/mmc/card/block.c-->mmc_blk_init()-->mmc_register_driver(&mmc_driver); SD卡中extended CSD寄存器中存储分区信息,上电检测到了SD卡,或者上电之后的热拔插操作,触发mmc_rescan查找检测到SD卡插入后,会通过读取该寄存器的值,获取mmc card的分区信息等,然后注册对应的mmc_card device,因mmc driver仅有一个,因此针对mmc bus而言其match函数直接返回1,表示匹配成功(因为是统一的协议),而不需要像spi/i2c总线的match接口那样对设备和驱动进行匹配检测;随后调用mmc_driver的probe函数(拔出卡则反之操作)。

3、mmc_driver的probe函数针对该mmc card的每一个分区,均执行如下操作:
①、为每一个分区,创建对应的通用磁盘设备gendisk;
②、设置gendisk的fops为mmc_bdops;
③、为每一个通用磁盘设备,均创建request_queue及其处理方法;
④、为该通用磁盘设备创建mmc block块i/o请求机制;
⑤、将该分区的通用磁盘设备注册至块设备i/o,以便应用层可通过块设备访问该mmc card 分区。

设备-总线-驱动模型:

在分析MMC子系统的设备-总线-驱动模型时,可以借助已经分析的i2c驱动模型、spi驱动模型的实现,来学习mmc子系统的驱动模型,通过与i2c驱动模型、spi驱动模型的对比,也可以加深我们对mmc子系统驱动模型的理解(从已知去学习未知,可提供我们的学习效率)。我们知道针对复杂的设备,则需要借助控制器进行通信,而针对i2c、spi、mmc而言,均需要对应的控制器实现与该类设备的通信。而针对这三类控制器,其模块都进行了抽象,其中i2c控制器抽象为i2c adapter、spi控制器抽象为spi master,而针对mmc控制器,则抽象为mmc host;而针对这三种类型设备的抽象,i2c设备抽象为i2c client、spi设备抽象为spi device、mmc device抽象为mmc card。 

下面分析下这三类驱动模型的异同:

1、均为控制器创建了对应的class,用于将对应的控制器设备链接至对应的class上

2、针对i2c adapter、spi master这两类而言,其驱动模型中均创建了对应的链表,将所有注册的控制器设备链接在一起,而mmc子系统并没有为mmc host创建类似的链表;

3、I2c/spi设备不属于热插拔设备,因此在具体系统的板级文件或者设备树中,需要针对系统中存在的i2c/spi设备定义注册信息,从而在LINUX系统初始化时完成i2c/spi设备的注册;而针对mmc子系统其属于热插拔的,因此在系统初始化时不需要单独进行设备的注册;

4、针对mmc子系统而言,针对mmc card的注册,mmc子系统提供了rescan接口,而该接口作为延迟工作队列的回调函数。针对mmc card而发起rescan扫描的时机如下:

①在mmc host添加的时候,执行一次mmc card的扫描操作

②热拔插过程执行扫描

这里大概分三个情况来描述热插拔识别流程:

①SOC带卡检测引脚,即mmc控制器部分带专门的中断寄存器位(MCI中断)

②SOC不带卡检测引脚,即用普通中断引脚做检测(即sdmmc_cd引脚)

③没有专门的寄存器,也没有用cd的gpio中断,采用轮询POLL

 二、分析mmc子系统框架函数调用流程(以SD卡为例)

关于SD卡的HOST最初始设备树节点在rk3399.dtsi 
sdmmc: dwmmc@fe320000 {
        compatible = "rockchip,rk3399-dw-mshc",
                     "rockchip,rk3288-dw-mshc";
					............................
					}

Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。我们设备树host device与driver匹配后调用对应的probe函数来初始化对应平台的host硬件。 

drivers\mmc\host\dw_mmc-rockchip.c
dw_mci_rockchip_probe(struct platform_device *pdev)
	--> dw_mci_pltfm_register(pdev, drv_data); //解析设备树信息,用来构造一个dw_mci *host;
		--> dw_mci_probe(dw_mci *host); //将上面构造好的dw_mci *host结构体传参进来;
			--> setup_timer(&host->cmd11_timer,dw_mci_cmd11_timer, (unsigned long)host); //设置一些定时器,定时发送一些命令
			--> host->dma_ops = host->pdata->dma_ops;
			--> ret = devm_request_irq(host->dev, host->irq, dw_mci_interrupt, host->irq_flags, "dw-mci", host);
			//这个dw_mci_interrupt中断函数会被注册两次,一次是emmc(irq=25),一次是sd卡(irq=26),所以源码虽然只注册了一次,但是实际上注册了两次,用的都是同一个中断处理函数
			//这个是mci寄存器的中断,我们的这款soc可以根据这个寄存器来判断发送,接收,SD卡热拔插的行为,都会触发这个中断,在这个中断处理函数里面再细分是什么操作触发的中断,进行处理,比如热拔插:
			--> dw_mci_init_slot(dw_mci *host, i); // 定义了mmc_host *mmc;,用我们封装了一层的dw_mci *host去填充注册他
				--> mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev);
					--> INIT_DELAYED_WORK(&host->detect, mmc_rescan); //初始化工作队列,里面有扫描SD卡的函数
				--> mmc->ops = &dw_mci_ops; //mmc_host_ops类型的变量,用于定义本mmc_host的操作接口(包括与mmc card通信的接口request、卡检测相关的接口等)
				--> mmc_add_host(mmc);
					--> mmc_start_host(mmc_host *host);
						--> mmc_schedule_delayed_work(&host->detect, delay); //上电添加mmc_host的时候,先主动执行一次mmc_rescan函数
			--> dw_mci_enable_cd(host); //cd = card detect ≠ sdmmc_cd 引脚中断,主要看函数内容,像这款soc就是在里面开启mci寄存器的SD卡热拔插中断
				--> temp = mci_readl(host, INTMASK); temp  |= SDMMC_INT_CD; mci_writel(host, INTMASK, temp); //开启mci寄存器的SD卡热拔插中断

假设热拔插SD卡引起了中断: 

dw_mci_interrupt()
	if (pending & SDMMC_INT_CD)
		dw_mci_handle_cd(dw_mci *host);
			--> mmc_detect_change(slot->mmc,msecs_to_jiffies(host->pdata->detect_delay_ms));
				--> _mmc_detect_change(host, delay, true);
					--> mmc_schedule_delayed_work(&host->detect, delay); //开启工作队列
						--> mmc_rescan() //最终会调用此函数

重点看看mmc_rescan检测SD卡热拔插的函数,我们在设备树中的SD卡定义了 supports-sd 所以:
drivers\mmc\core\host.c 
mmc_of_parse
	if (of_property_read_bool(np, "supports-sd"))
		host->restrict_caps |= RESTRICT_CARD_TYPE_SD; 

mmc_rescan()
	if (host->bus_ops && !host->bus_dead && !(host->caps & MMC_CAP_NONREMOVABLE)) //上电第一次调用这个函数时候,还没有设置host->bus_ops,所以会执行后面的语句来设置host->bus_ops
		host->bus_ops->detect(host);
	--> mmc_rescan_try_freq(host, max(freqs[i], host->f_min)) //以各种频率给host发,选择最合适的频率
			if ((host->restrict_caps & RESTRICT_CARD_TYPE_SD) && !mmc_attach_sd(host)) return 0; 所以就会调用mmc_attach_sd
		--> mmc_attach_sd(host)
			--> mmc_attach_bus(host, &mmc_sd_ops);
				--> host->bus_ops = ops; //这里的ops就是上面的mmc_ops参数
			--> mmc_sd_init_card(host, rocr, NULL);
				--> card = mmc_alloc_card(host, &sd_type);
			--> mmc_add_card(host->card);
				--> device_add(&card->dev);
					--> bus_add_device(dev); //Add the device to its bus's list of devices, 会给mmc_bus注册一个devices,会和系统自动注册的mmc_driver匹配,调用它的probe函数
                        ......
                        --> mmc_driver->probe
	                        --> mmc_blk_probe() //如上分析的,会读取SD卡的寄存器获取分区信息,根据分区信息注册block等。

放一张图中好看些:

三、mmc子系统的数据结构 (以emmc的为例)

mmc_host数据结构:

Mmc card: 

①host完成mmc_card与mmc_host的绑定;
②dev完成将该mmc_card与系统中的设备、总线、驱动的关联(即完成与系统设备驱动总线的关联与绑定操作);
③卡类型与卡状态记录
④卡相关的寄存器信息记录(主要从mmc card中读取),包括cid、csd、scr、ssr等内容
⑤Mmc card的分区信息,主要完成块设备的创建(由mmc driver实现)若为sdio设备,则有cccr、cis等(关于sdio的部分,请参考sdio协议相关的文档)

 mmc_ios:

该结构体主要定义mmc总线相关的参数,主要包括时钟频率、电源、总线模式、电源状态、总线带宽、支持的信号电压值等等这些参数的设置可通过mmc_host_ops->set_ios实现。

mmc_host_ops:

该数据结构定义了mmc_host的操作方法

mmc_bus_ops:

该接口主要是针对mmc card的sleep/awake的操作,因mmc card也需要进行sleep/awake等接口,这些接口类似于设备驱动的电源管理(suspend/resume)相关的接口。该结构体中的成员,不需要我们进行实现,mmc子系统已经完成,mmc子系统根据mmc各协议版本针对sleep/awake的支持情况,实现对应的操作。

 参考这位博主:LINUX MMC子系统分析之一 概述_jerry_chg的博客-CSDN博客_linux mmc子系统

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内核分析笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值