6. [mmc subsystem] mmc core(第六章)——mmc core主模块
一、说明
1、mmc core概述
mmc core主模块是mmc core的实现核心。也是本章的重点内容。
对应代码位置drivers/mmc/core/core.c
。
其主要负责如下功能:
- mmc core初始化,包括注册mmc bus、mm host class等等
- mmc host的管理和维护,包括为其他模块提供mmc_host的操作接口,如下
- host的启动和停止
- host的占用和释放
- host电源状态的保存和恢复
- host总线操作集的绑定和解绑
- host上卡状态检测
- 为其他模块提供mmc_card的操作接口,如下
- card的唤醒和休眠
- card擦除
- card属性的获取
- 为其他模块提供总线io setting的接口
- 为其他模块提供mmc请求接口
- card检测接口
- bkops操作接口
- regulator操作接口
- clock操作接口
- mmc core电源管理操作接口
2、操作集说明
在mmc_host中有两个操作集成员,需要理解一下,以免在代码中产生误会:
- mmc_host->struct mmc_host_ops *ops,这个是host的操作集,由host controller驱动决定。对于sdhci类host来说,就是sdhci_ops(sdhci.c中设置)。
- mmc_host->struct mmc_bus_ops *bus_ops,这个是mmc总线的操作集(也可以理解为host的mmc bus handler,host的总线处理方法),由总线上的card type决定。对于mmc card type来说,就是mmc_ops_unsafe或者mmc_ops(mmc_attach_bus_ops中设置)。
二、API总览
1、mmc core初始化相关
- mmc_init & mmc_exit (模块内使用)
2、mmc host的管理和维护相关
- mmc_claim_host & mmc_try_claim_host & mmc_release_host (模块内使用)
- mmc_power_up & mmc_power_off
- mmc_start_host & mmc_stop_host
- mmc_power_save_host & mmc_power_restore_host
- mmc_resume_host & mmc_suspend_host
- mmc_pm_notify
3、mmc card的操作相关(包括card状态的获取)
- mmc_hw_reset & mmc_hw_reset_check &
- mmc_card_awake & mmc_card_sleep
- mmc_card_is_prog_state
- mmc_can_erase
- mmc_can_trim
- mmc_can_discard
- mmc_can_sanitize
- mmc_can_secure_erase_trim
- mmc_erase_group_aligned
4、总线io setting相关
- mmc_set_ios
- mmc_set_chip_select
- mmc_set_clock
- mmc_set_bus_mode
- mmc_set_bus_width
- mmc_select_voltage
- mmc_set_signal_voltage(特殊)
- mmc_set_timing
- mmc_set_driver_type
- mmc_get_max_frequency & mmc_get_min_frequency
5、host的mmc总线相关
- mmc_resume_bus
- mmc_attach_bus & mmc_detach_bus
6、mmc请求相关
- mmc_request_done
- mmc_wait_for_req
- mmc_wait_for_cmd
- mmc_set_data_timeout
- mmc_align_data_size
7、card检测相关
mmc_detect_change
mmc_rescan
mmc_detect_card_removed
8、bkops操作相关
- mmc_blk_init_bkops_statistics
- mmc_start_delayed_bkops
- mmc_start_bkops & mmc_stop_bkops
- mmc_start_idle_time_bkops
- mmc_read_bkops_status
9、regulator操作相关
- mmc_regulator_get_ocrmask
- mmc_regulator_set_ocr
- mmc_regulator_get_supply
10、card擦除操作相关
- mmc_init_erase
- mmc_erase
11、clock操作接口
- mmc_init_clk_scaling & mmc_exit_clk_scaling
- mmc_can_scale_clk
- mmc_disable_clk_scaling
12、mmc core电源管理操作
- mmc_rpm_hold & mmc_rpm_release
二、接口代码说明——mmc core初始化相关
1、mmc_init实现
负责初始化整个mmc core。
主要工作:
- 分配一个workqueue,用于专门处理mmc core的执行的工作
- 注册mmc bus
- 注册mmc host class
代码如下:
static int __init mmc_init(void)
{
int ret;
/* 分配一个workqueue,用于专门处理mmc core的执行的工作 */
workqueue = alloc_ordered_workqueue("kmmcd", 0);
/* 注册mmc bus */
ret = mmc_register_bus(); // 调用mmc_register_bus注册mmc bus,具体参考《mmc core——bus模块说明》
// 会生成/sys/bus/mmc目录
/* 注册mmc host class */
ret = mmc_register_host_class(); // 调用mmc_register_host_class注册mmc host class,具体参考《mmc core——host模块说明》
// 会生成/sys/class/mmc_host目录
/* 注册sdio bus */
ret = sdio_register_bus();
return 0;
}
subsys_initcall(mmc_init);
三、接口代码说明——mmc host的管理和维护相关
1、mmc_claim_host & mmc_try_claim_host & mmc_release_host
host被使能之后就不能再次被使能,并且只能被某个进程独自占用。
可以简单地将host理解为一种资源,同时只能被一个进程获取,但是在占用进程里面可以重复占用。
在对host进行操作之前(包括发起mmc请求),必须使用mmc_claim_host和mmc_release_host来进行获取和释放。
变量说明
- mmc_host->claimed用来表示host是否被占用
- mmc_host->claimer用来表示host的占用者(进程)
- mmc_host->claim_cnt用来表示host的占用者的占用计数,为0时则会释放这个host
代码如下
static inline void mmc_claim_host(struct mmc_host *host)
{
__mmc_claim_host(host, NULL);·// 调用__mmc_claim_host来获取host
}
int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
{
/////只考虑abort为NULL的情况,在mmc core中的mmc_claim_host也是将其设置为NULL
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
int stop;
might_sleep(); // 说明这个函数可能导致进程休眠
add_wait_queue(&host->wq, &wait); // 把当前进程加入到等待队列中
spin_lock_irqsave(&host->lock, flags);
while (1) { // 以下尝试获取host,如果host正在被占用,会进入休眠
set_current_state(TASK_UNINTERRUPTIBLE); // 设置进程状态为TASK_UNINTERRUPTIBLE状态
stop = abort ? atomic_read(abort) : 0;
if (stop || !host->claimed || host->claimer == current) // 当host的占用标志claimed为0,或者占用者是当前进程的时候,说明可以占用了,退出
break;
spin_unlock_irqrestore(&host->lock, flags);
schedule(); // 否则,进行调度进入休眠
spin_lock_irqsave(&host->lock, flags);
}
set_current_state(TASK_RUNNING); // 设置进程为运行状态
if (!stop) {
host->claimed = 1; // 设置占用标志claimed
host->claimer = current; // 设置占用者为当前进程
host->claim_cnt += 1; // 占用计数加1
} else
wake_up(&host->wq);
spin_unlock_irqrestore(&host->lock, flags);
remove_wait_queue(&host->wq, &wait); // 将当前进程从等待队列中退出
if (host->ops->enable && !stop && host->claim_cnt == 1)
host->ops->enable(host); // 调用host操作集中的enable方法来占用该host,对应sdhci类host即为sdhci_enable
return stop;
}
void mmc_release_host(struct mmc_host *host)
{
unsigned long flags;
WARN_ON(!host->claimed);
if (host->ops->disable && host->claim_cnt == 1) // 当前claim_cnt为1(马上要变为0),调用释放host了
host->ops->disable(host); // 调用host操作集中的disable方法来释放该host,对应sdhci类host即为sdhci_disable
spin_lock_irqsave(&host->lock, flags);
if (--host->claim_cnt) {
/* Release for nested claim */
spin_unlock_irqrestore(&host->lock, flags); // 如果减一之后计数还不为0,说明当前进程需要继续占用该host,不做其他操作
} else { // 以下需要释放该host
host->claimed = 0; // 设置占用标志claimed为0
host->claimer = NULL; // 清空占用者(进程)
spin_unlock_irqrestore(&host->lock, flags);
wake_up(&host->wq); // 唤醒host的等待队列,让那些调用mmc_claim_host睡眠等待host资源的进程被唤醒
}
}
int mmc_try_claim_host(struct mmc_host *host)
{
// 和mmc_claim_host的主要区别在于进程不会休眠,获取失败直接返回
int claimed_host = 0;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
if (!host->claimed || host->claimer == current) {
host->claimed = 1;
host->claimer = current;
host->claim_cnt += 1;
claimed_host = 1;
}
spin_unlock_irqrestore(&host->lock, flags);
if (host->ops->enable && claimed_host && host->claim_cnt == 1)
host->ops->enable(host);
return claimed_host;
}
会调用mmc_host->struct mmc_host_ops->enable和mmc_host->struct mmc_host_ops->disable来使能和禁用host。
对于sdhci类的host,相应就是sdhci_enable和sdhci_disable。
2、mmc_power_up & mmc_power_off
mmc host的上电操作和关电操作。
mmc的power状态
- MMC_POWER_OFF:掉电状态
- MMC_POWER_UP:正在上电的状态
- MMC_POWER_ON:供电正常的状态
主要工作
主要工作就是初始化host的总线设置、总线时钟以及工作电压、信号电压。
代码如下
void mmc_power_up(struct mmc_host *host)
{
int bit;
/* 判断是否已经处于MMC_POWER_ON,是的话不进行后续操作 */
if (host->ios.power_mode == MMC_POWER_ON)
return;
/* 第一阶段,先设置对应的io setting使host处于MMC_POWER_UP的状态(总线工作频率没有设置) */
mmc_host_clk_hold(host); // 先获取host时钟
/* If ocr is set, we use it */
if (host->ocr)
bit = ffs(host->ocr) - 1; // 选择一个ocr配置设置为host的工作电压
else
bit = fls(host->ocr_avail) - 1;
host->ios.vdd = bit;
if (mmc_host_is_spi(host))
host->ios.chip_select = MMC_CS_HIGH;
else {
host->ios.chip_select = MMC_CS_DONTCARE;
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; // 设置总线模式
}
host->ios.power_mode = MMC_POWER_UP;
host->ios.bus_width = MMC_BUS_WIDTH_1; // 设置总线宽度为1
host->ios.timing = MMC_TIMING_LEGACY; // 串口时序
mmc_set_ios(host); // 调用mmc_set_ios设置总线的io setting,后面会说明
/*
* This delay should be sufficient to allow the power supply
* to reach the minimum voltage.
*/
mmc_delay(10);
/* 第二阶段,以host的初始化工作频率再次设置io setting,使host处于MMC_POWER_ON状态 */
host->ios.clock = host->f_init; // 设置总线的时钟频率
host->ios.power_mode = MMC_POWER_ON;
mmc_set_ios(host); // 调用mmc_set_ios设置总线的io setting,后面会说明
/*
* This delay must be at least 74 clock sizes, or 1 ms, or the
* time required to reach a stable voltage.
*/
mmc_delay(10);
/* 设置信号的电压 */
/* Set signal voltage to 3.3V */
__mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330);
mmc_host_clk_release(host); // 释放host时钟
}
3、mmc_start_host & mmc_stop_host
mmc_start_host 用来启动一个host,mmc_stop_host用来停止一个host。
当底层host controller调用mmc_add_host来注册host时,在mmc_add_host中就会调用mmc_start_host来启动一个host了。具体参考《mmc core——host模块说明》。
相对应的,会在mmc_remove_host中调用mmc_stop_host停止host。
void mmc_start_host(struct mmc_host *host)
{
mmc_claim_host(host); // 因为上电操作涉及到对host的使用和设置,需要先占用host
host->f_init = max(freqs[0], host->f_min); // 通过最小频率要设置初始化频率
host->rescan_disable = 0; // 设置rescan_disable标志为0,说明已经可以进行card检测了
if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP) // 如果mmc属性设置了MMC_CAP2_NO_PRESCAN_POWERUP,也就是在rescan前不需要进行power up操作时,则进行关电
mmc_power_off(host);
else
mmc_power_up(host); // 否则,调用mmc_power_up对host进行上电操作。这里也是mmc core中启动host的核心函数。
mmc_release_host(host); // 完成上电操作,释放host
/* 到这里host已经可以工作了,可以开始进行后续的card操作了 */
mmc_detect_change(host, 0); // 调用mmc_detect_change检测card变化,后续会继续说明
}
四、接口代码说明——card检测相关
1、mmc_detect_change
在上述中我们知道在启动host的函数mmc_start_host 中最后调用了mmc_detect_change来开始检测card(也就是检测mmc卡槽的状态变化情况)。
其实mmc_detect_change是在driver发现mmc卡槽状态发生变化时,调用mmc_detect_change来进行确认和处理。
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
host->detect_change = 1; // 检测到card状态发生变化的标识
mmc_schedule_delayed_work(&host->detect, delay); // 间隔delay jiffies之后调用host->detect的工作
}
在《host模块说明》已经知道了在mmc_alloc_host中默认将host->detect工作设置为mmc_rescan(card重新扫描)函数, INIT_DELAYED_WORK(&host->detect, mmc_rescan)。
当然,host也可以自己另外设置,但是一般都是使用mmc core提供的mmc_rescan作为detect工作来搜索card。下面说明。
2、mmc_rescan
用于检测host的卡槽状态,并对状态变化做相应的操作。
有card插入时,重新扫描mmc card。
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
bool extend_wakelock = false;
/* 如果rescan_disable被设置,说明host此时还禁止rescan */
if (host->rescan_disable)
return;
/* 对于设备不可移除的host来说,只能rescan一次 */
/* If there is a non-removable card registered, only scan once */
if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
return;
host->rescan_entered = 1;
mmc_bus_get(host); // 获取host对应的bus
mmc_rpm_hold(host, &host->class_dev); // 使host处于rpm resume的状态
/* 以下判断原来的card是否已经被移除,移除了则需要做相应的操作 */
/*
* if there is a _removable_ card registered, check whether it is
* still present
*/
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
&& !(host->caps & MMC_CAP_NONREMOVABLE))
host->bus_ops->detect(host);
// host->bus_ops存在的话说明之前是有card插入的状态
// 需要调用host->bus_ops->detect检测card是否被移除,是的话在host->bus_ops->detect中做相应的处理
// 对于mmc type card来说,对应就是mmc_detect,具体参考《card相关模块》
host->detect_change = 0;
/* If the card was removed the bus will be marked
* as dead - extend the wakelock so userspace
* can respond */
if (host->bus_dead)
extend_wakelock = 1; // 需要设置一个wakelock锁,使用户空间可以及时做出相应
/*
* Let mmc_bus_put() free the bus/bus_ops if we've found that
* the card is no longer present.
*/
mmc_bus_put(host);
// 因为在这个函数的前面已经获取了一次host,可能导致host->bus_ops->detect中检测到card拔出之后,没有真正释放到host的bus,所以这里先put一次
// host bus的计数(bus_refs)为0的时候,会调用__mmc_release_bus清空host bus的信息
mmc_bus_get(host);
// 再获取host bus
/* if there still is a card present, stop here */
if (host->bus_ops != NULL) { // 说明此时还有card插入,退出后续的操作
mmc_rpm_release(host, &host->class_dev);
mmc_bus_put(host);
goto out;
}
mmc_rpm_release(host, &host->class_dev);
/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);
/* 检测当前卡槽状态,根据卡槽状态做相应的操作 */
if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
// 调用host->ops->get_cd来判断host的卡槽的当前card插入状态
// 对应sdhci类型的host来说,就是sdhci_get_cd
// 为0的时候,表示没有card插入,对应host进行power off操作之后进行退出
// 为1的时候,表示当前有card插入,跳到后续的操作
mmc_claim_host(host);
mmc_power_off(host);
mmc_release_host(host);
goto out;
}
mmc_rpm_hold(host, &host->class_dev);
mmc_claim_host(host);
if (!mmc_rescan_try_freq(host, host->f_min)) // 调用mmc_rescan_try_freq,以支持的最低频率作为工作频率尝试搜索card,后续继续说明
extend_wakelock = true;
mmc_release_host(host);
mmc_rpm_release(host, &host->class_dev);
out:
/* only extend the wakelock, if suspend has not started yet */
if (extend_wakelock && !host->rescan_disable)
wake_lock_timeout(&host->detect_wake_lock, HZ / 2); // 占用wakelock,使系统在HZ/2的时间内不会休眠
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
// 当host设置了MMC_CAP_NEEDS_POLL属性时,需要每隔HZ的时间轮询检测host的卡槽状态,
// 调度了host->detect工作,对应就是mmc_rescan
}
会调用host->ops->get_cd来判断host的卡槽的当前card插入状态,对应sdhci类型的host就是sdhci_get_cd。
会调用host->bus_ops->detect检测card是否被移除,是的话在host->bus_ops->detect中做相应的处理。对于mmc type card,就是mmc_detect。
3、mmc_rescan_try_freq
以一定频率搜索host bus上的card。
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
mmc_power_up(host); // 给host做上电操作
mmc_hw_reset_for_init(host); // 硬件复位和初始化
mmc_go_idle(host);
mmc_send_if_cond(host, host->ocr_avail); // 获取card的可用频率,存储到host->ocr_avail中
/* Order's important: probe SDIO, then SD, then MMC */
/* 用于绑定card到host bus上(也就是card和host的绑定)。 */
if (!mmc_attach_sdio(host)) // 先假设card是sdio type card,尝试绑定到host bus上,失败则说明不是sdio type card,继续后面的操作,否则返回
return 0;
if (!mmc_attach_sd(host)) // 先假设card是sd type card,尝试绑定到host bus上,失败则说明不是sd type card,继续后面的操作,否则返回
return 0;
if (!mmc_attach_mmc(host)) // 先假设card是mmc type card,尝试绑定到host bus上,失败则说明不是mmc type card,继续后面的操作,否则返回
// mmc_attach_mmc通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。
// 具体参考《card相关模块说明》
return 0;
mmc_power_off(host);
return -EIO;
}
五、接口代码说明——总线io setting相关
0、mmc_ios说明
struct mmc_ios 由mmc core定义的规范的结构,用来维护mmc总线相关的一些io setting。如下:
struct mmc_ios {
unsigned int clock; /* clock rate */ // 当前工作频率
unsigned int old_rate; /* saved clock rate */ // 上一次的工作频率
unsigned long clk_ts; /* time stamp of last updated clock */ // 上一次更新工作频率的时间戳
unsigned short vdd;/* vdd stores the bit number of the selected voltage range from below. */ // 支持的电压表
unsigned char bus_mode; /* command output mode */ // 总线输出模式,包括开漏模式和上拉模式
unsigned char chip_select; /* SPI chip select */ // spi片选
unsigned char power_mode; /* power supply mode */ // 电源状态模式
unsigned char bus_width; /* data bus width */ // 总线宽度
unsigned char timing; /* timing specification used */ // 时序类型
unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */ // 信号的工作电压
unsigned char drv_type; /* driver type (A, B, C, D) */ // 驱动类型
};
在设置总线io setting的过程中,就是要设置mmc_host->mmc_ios中的这些成员。
然后通过调用mmc_set_ios进行统一设置。后续会继续说明。
1、mmc_set_ios
统一设置mmc总线的io设置(io setting)。
void mmc_set_ios(struct mmc_host *host)
{
struct mmc_ios *ios = &host->ios;
if (ios->clock > 0)
mmc_set_ungated(host); // 关闭clock的门控
host->ops->set_ios(host, ios); // 调用host->ops->set_ios来对mmc总线的io setting进行设置,核心函数
// 对于sdhci类型的host,对应就是sdhci_set_ios
}
会调用host->ops->set_ios来对mmc总线的io setting进行设置,核心函数。对于sdhci类型的host,对应就是sdhci_set_ios
2、mmc_set_bus_mode & mmc_set_bus_width
- mmc_set_bus_mode用于设置总线模式,有如下模式
- MMC_BUSMODE_OPENDRAIN(开漏模式)
- MMC_BUSMODE_PUSHPULL(上拉模式)
- mmc_set_bus_width用于设置总线宽度,有如下模式
- MMC_BUS_WIDTH_1
- MMC_BUS_WIDTH_4
- MMC_BUS_WIDTH_8
代码如下:
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
{
mmc_host_clk_hold(host);
host->ios.bus_mode = mode;
mmc_set_ios(host);
mmc_host_clk_release(host);
}
void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
{
mmc_host_clk_hold(host);
host->ios.bus_width = width;
mmc_set_ios(host);
mmc_host_clk_release(host);
}
其他设置io setting的函数类似,不多说明。
六、接口代码说明——host的mmc总线相关
1、mmc_attach_bus & mmc_detach_bus
- 主要功能
- mmc_attach_bus用于将分配一个mmc总线操作集给host。
- mmc_detach_bus用于释放和host相关联的mmc总线操作集。
- 一些变量
- mmc_host->bus_ops,表示host的mmc总线操作集
- mmc_host->bus_refs,表示host的mmc总线的使用者计数
- mmc_host->bus_dead,表示host的mmc总线是否被激活,如果设置了bus_ops,那么就会被激活了
- 代码如下
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
BUG_ON(host->bus_ops); // 不允许重复设置host的mmc总线操作集
BUG_ON(host->bus_refs); // 当mmc总线的使用者计数还存在时,不允许设置host的mmc总线操作集
host->bus_ops = ops; // 设置host的mmc总线操作集
host->bus_refs = 1; // host的mmc总线的使用者计数设置为1,相当于调用了mmc_bus_get
host->bus_dead = 0; // 总线被激活了
spin_unlock_irqrestore(&host->lock, flags);
}
void mmc_detach_bus(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->bus_dead = 1; // host的mmc总线设置为dead状态
spin_unlock_irqrestore(&host->lock, flags);
mmc_bus_put(host); // 调用mmc_bus_put释放host的mmc总线,也就是对host的mmc总线的使用者计数-1
}
在《card相关模块》中可以看到mmc_attach_mmc->mmc_attach_bus_ops调用mmc_attach_bus来绑定了host的mmc总线操作集为mmc_ops_unsafe或者mmc_ops
2、mmc_bus_get & mmc_bus_put
static inline void mmc_bus_get(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->bus_refs++; // 对host的mmc总线的使用者计数+1
spin_unlock_irqrestore(&host->lock, flags);
}
static inline void mmc_bus_put(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->bus_refs--; // 对host的mmc总线的使用者计数-1
if ((host->bus_refs == 0) && host->bus_ops) // 说明host的mmc总线当前并没有使用,调用__mmc_release_bus进行实际的释放操作
__mmc_release_bus(host);
spin_unlock_irqrestore(&host->lock, flags);
}
static void __mmc_release_bus(struct mmc_host *host)
{
host->bus_ops = NULL; // 清空host的mmc总线操作集
}
七、接口代码说明——mmc请求相关
分成同步的mmc请求和异步的mmc请求。差别如下:
1、流程上的差别:
(1)会阻塞的处理流程:
mmc_wait_for_req
——》__mmc_start_req // 发起请求
————》init_completion(&mrq->completion);
————》mrq->done = mmc_wait_done
————》mmc_start_request(host, mrq); // 实际发起请求的操作
——》mmc_wait_for_req_done // 阻塞等待请求处理完成
——》返回
(2)不阻塞等待该命令的处理流程:
(注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞)
mmc_start_req
——》mmc_wait_for_data_req_done // 阻塞等待上一次的请求处理
——》__mmc_start_data_req // 发起异步请求
————》mrq->done = mmc_wait_data_done
————》mmc_start_request // 实际发起请求的操作
——》返回
最后都是调用了mmc_start_request使host向MMC发起请求。
0、数据结构说明
一个mmc请求分成两部分内容,分别是命令部分和数据部分。
- mmc_command
struct mmc_command {
u32 opcode; // 命令的操作码,如MMC_GO_IDLE_STATE、MMC_SEND_OP_COND等等
u32 arg; // 命令的参数
u32 resp[4]; // response值
unsigned int flags; /* expected response type */ // 期待的response的类型
#define mmc_resp_type(cmd) ((cmd)->flags & (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC|MMC_RSP_BUSY|MMC_RSP_OPCODE))
/*
* These are the command types.
*/
#define mmc_cmd_type(cmd) ((cmd)->flags & MMC_CMD_MASK)
unsigned int retries; /* max number of retries */ // 失败时的重复尝试次数
unsigned int error; /* command error */ // 命令的错误码
/*
* Standard errno values are used for errors, but some have specific
* meaning in the MMC layer:
*
* ETIMEDOUT Card took too long to respond
* EILSEQ Basic format problem with the received or sent data
* (e.g. CRC check failed, incorrect opcode in response
* or bad end bit)
* EINVAL Request cannot be performed because of restrictions
* in hardware and/or the driver
* ENOMEDIUM Host can determine that the slot is empty and is
* actively failing requests
*/
unsigned int cmd_timeout_ms; /* in milliseconds */ // 命令执行的等待超时事件
struct mmc_data *data; /* data segment associated with cmd */ // 和该命令关联在一起的数据段
struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
};
- mmc_data
struct mmc_data {
unsigned int timeout_ns; /* data timeout (in ns, max 80ms) */ // 超时时间,以ns为单位
unsigned int timeout_clks; /* data timeout (in clocks) */ // 超时时间,以clock为单位
unsigned int blksz; /* data block size */ // 块大小
unsigned int blocks; /* number of blocks */ // 块数量
unsigned int error; /* data error */ // 传输的错误码
unsigned int flags; // 传输标识
#define MMC_DATA_WRITE (1 << 8)
#define MMC_DATA_READ (1 << 9)
#define MMC_DATA_STREAM (1 << 10)
unsigned int bytes_xfered;
struct mmc_command *stop; /* stop command */ // 结束传输的命令
struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
unsigned int sg_len; /* size of scatter list */
struct scatterlist *sg; /* I/O scatter list */
s32 host_cookie; /* host private data */
bool fault_injected; /* fault injected */
};
- mmc_request
struct mmc_request是mmc core向host controller发起命令请求的处理单位。
struct mmc_request {
struct mmc_command *sbc; /* SET_BLOCK_COUNT for multiblock */ // 设置块数量的命令,怎么用的后续再补充
struct mmc_command *cmd; // 要传输的命令
struct mmc_data *data; // 要传输的数据
struct mmc_command *stop; // 结束命令,怎么用的后续再补充
struct completion completion; // 完成量
void (*done)(struct mmc_request *);/* completion function */ // 传输结束后的回调函数
struct mmc_host *host; // 所属host
};
- mmc_async_req
struct mmc_async_req {
/* active mmc request */
struct mmc_request *mrq;
unsigned int cmd_flags; /* copied from struct request */
/*
* Check error status of completed mmc request.
* Returns 0 if success otherwise non zero.
*/
int (*err_check) (struct mmc_card *, struct mmc_async_req *);
/* Reinserts request back to the block layer */
void (*reinsert_req) (struct mmc_async_req *);
/* update what part of request is not done (packed_fail_idx) */
int (*update_interrupted_req) (struct mmc_card *,
struct mmc_async_req *);
};
1、mmc_wait_for_req
发起mmc_request请求并且等待其处理完成。由其他需要发起mmc请求的模块调用。
可以结合后面的mmc_request_done来看。
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
{
#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
if (mmc_bus_needs_resume(host))
mmc_resume_bus(host);
#endif
__mmc_start_req(host, mrq); // 开始发起mmc_request请求
mmc_wait_for_req_done(host, mrq); // 等待mmc_request处理完成
}
//-----------------------------------__mmc_start_req说明,开始发起mmc_request请求
static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
{
/* 发起mmc_request前的一些初始化工作,包括完成量和处理完成的回调函数的设置 */
init_completion(&mrq->completion); // 初始化完成量,在mmc_wait_for_req_done中会去等待这个完成量
mrq->done = mmc_wait_done;
// 设置mmc_request处理完成的回调函数,会调用complete(&mrq->completion);来设置完成量
// host controller会调用mmc_request_done来执行这个回调函数,具体在后面分析
if (mmc_card_removed(host->card)) { // 检测card是否存在
mrq->cmd->error = -ENOMEDIUM;
complete(&mrq->completion);
return -ENOMEDIUM;
}
/* 调用mmc_start_request发起mmc请求 */
mmc_start_request(host, mrq); // 开始处理mmc_request请求
return 0;
}
static void
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
WARN_ON(!host->claimed);
/* 以下对mmc_request的各个成员,包括cmd、data、stop做验证操作和关联操作 */
mrq->cmd->error = 0;
mrq->cmd->mrq = mrq;
if (mrq->data) {
BUG_ON(mrq->data->blksz > host->max_blk_size);
BUG_ON(mrq->data->blocks > host->max_blk_count);
BUG_ON(mrq->data->blocks * mrq->data->blksz >
host->max_req_size);
mrq->cmd->data = mrq->data; // 也就是说mmc_request的data和其cmd中的data是一一样的
mrq->data->error = 0;
mrq->data->mrq = mrq;
if (mrq->stop) {
mrq->data->stop = mrq->stop;
mrq->stop->error = 0;
mrq->stop->mrq = mrq;
}
#ifdef CONFIG_MMC_PERF_PROFILING
if (host->perf_enable)
host->perf.start = ktime_get();
#endif
}
/* 获取时钟 */
mmc_host_clk_hold(host);
/* 调用host controller的request方法来处理mmc_request请求 */
host->ops->request(host, mrq);
// host->ops->request也就是host controller的request方法,对于sdhci类型的host来说,就是sdhci_request
}
//-----------------------------------mmc_wait_for_req_done说明,等待mmc_request处理完成
static void mmc_wait_for_req_done(struct mmc_host *host,
struct mmc_request *mrq)
{
struct mmc_command *cmd;
while (1) {
wait_for_completion_io(&mrq->completion); // 在这里休眠,等待mrq->completion完成量,在__mmc_start_req中初始化的
cmd = mrq->cmd; // 获取对应的command
/*
* If host has timed out waiting for the commands which can be
* HPIed then let the caller handle the timeout error as it may
* want to send the HPI command to bring the card out of
* programming state.
*/
if (cmd->ignore_timeout && cmd->error == -ETIMEDOUT)
break;
if (!cmd->error || !cmd->retries || mmc_card_removed(host->card))
// 如果command正常处理完成,或者失败重复尝试次数为0,或者card被移除了,直接退出循环返回
break;
// 以下处理失败重复尝试的情况
pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
mmc_hostname(host), cmd->opcode, cmd->error);
cmd->retries--;
cmd->error = 0;
host->ops->request(host, mrq);
}
}
会调用host->ops->request来对mmc_request进行处理,对于sdhci类型的host,对应就是sdhci_request。
这个方法就是mmc_request实际被处理的核心。
2、mmc_request_done
通知mmc core某个mmc_request已经处理完成,由host controller调用。
以sdhci类型的host为例,处理完一个mmc_request之后,会执行sdhci_tasklet_finish,而在sdhci_tasklet_finish中会调用mmc_request_done来通知host某个mmc_request已经处理完成了。
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
{
struct mmc_command *cmd = mrq->cmd;
int err = cmd->error;
if (host->card)
mmc_update_clk_scaling(host);
if (err && cmd->retries && !mmc_card_removed(host->card)) {
// command执行出错,如果还需要重复尝试的话,这里不释放clock,只是通知mmc core
if (mrq->done)
mrq->done(mrq);
// 执行mmc_request的回调函数来通知mmc core,
// 对于__mmc_start_req发起的request来说,就是mmc_wait_done,上面已经说明过了
// 对于__mmc_start_data_req发起的request来说,就是mmc_wait_data_done,后面会说明
} else {
mmc_should_fail_request(host, mrq);
// 用于模拟data传输概率出错的情况
// 具体参考http://blog.csdn.net/luckywang1103/article/details/52224160
if (mrq->done)
mrq->done(mrq);
// 执行mmc_request的回调函数来通知mmc core,对于__mmc_start_req发起的request来说,就是mmc_wait_done,上面已经说明过了
mmc_host_clk_release(host);
}
}
通过上述,mrq->done被调度,mmc_wait_done被执行,mrq->completion被设置。
然后等待mrq->completion的mmc_wait_for_req_done就会继续往下执行。
3、mmc_wait_for_cmd
mmc_wait_for_cmd用于处理一个不带数据请求的命令。
会被封装到mmc_request中,通过调用mmc_wait_for_req来发起请求。
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
{
struct mmc_request mrq = {NULL};
WARN_ON(!host->claimed);
memset(cmd->resp, 0, sizeof(cmd->resp)); // 清空command的response
cmd->retries = retries; // 失败时的重复尝试次数
mrq.cmd = cmd; // 封装到mmc_request中
cmd->data = NULL; // 不带数据包的命令,故清空data
mmc_wait_for_req(host, &mrq); // 调用mmc_wait_for_req发起mmc请求并且等待其处理完成
return cmd->error; // 返回错误码
}
4、mmc_start_req(重要)
机制说明如下:mmc_start_req会先判断上一次的asycn_req是否处理完成,如果没有处理完成,则会等待其处理完成。
如果处理完成了,为当前要处理的asycn_req发起请求,但是并不会等待,而是直接返回。
注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞。这样,可以利用等待的一部分时间来做其他操作。
为了方便理解这个函数,需要看一下其函数注释。
- 要注意,在函数里面有两个异步请求:
- areq:表示新的异步请求
- host->areq:表示上一次发起的、正在处理、等待完成的异步请求
代码如下(为了方便理解,对代码进行了简化):
/**
* mmc_start_req - start a non-blocking request // 该函数用来发起一个不阻塞的请求
* @host: MMC host to start command // 要发起对应请求的host
* @areq: async request to start // 要发起的异步请求
* @error: out parameter returns 0 for success, otherwise non zero // 返回值,返回0表示成功,返回非零表示失败
*
* Start a new MMC custom command request for a host. // 为host发起的一个新的mmc命令请求
* If there is on ongoing async request wait for completion // 如果host已经有一个正在处理、等待完成的异步请求,那么会等待这个请求完成!!!
* of that request and start the new one and return. // 然后发起新的请求,然后返回!!!
* Does not wait for the new request to complete. // 并不会等待这个新的请求完成!!!
*
* Returns the completed request, NULL in case of none completed. // 会返回被完成的mmc请求(而不是新的mmc请求。)空表示没有mmc请求被完成。
* Wait for the an ongoing request (previoulsy started) to complete and
* return the completed request. If there is no ongoing request, NULL
* is returned without waiting. NULL is not an error condition.
// 等待上一次发起的mmc请求完成,然后把这个mmc请求返回。如果没有mmc请求正在处理,那么就直接返回而不会等待。空并不是错误条件。
*/
struct mmc_async_req *mmc_start_req(struct mmc_host *host,
struct mmc_async_req *areq, int *error)
{
int err = 0;
int start_err = 0;
struct mmc_async_req *data = host->areq;
unsigned long flags;
bool is_urgent;
/* Prepare a new request */
/* 为新的异步请求做准备处理 */
if (areq) {
/*
* start waiting here for possible interrupt
* because mmc_pre_req() taking long time
*/
mmc_pre_req(host, areq->mrq, !host->areq);
}
/* 对上一次发起的、正在处理、等待完成的异步请求进行处理、等待操作 */
if (host->areq) {
err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq); // 在这里等待正在处理的异步请求处理完成
//.......以下过滤了错误处理的部分
}
/* 对新的异步请求进行发起操作 */
if (!err && areq) {
/* urgent notification may come again */
spin_lock_irqsave(&host->context_info.lock, flags);
is_urgent = host->context_info.is_urgent;
host->context_info.is_urgent = false;
spin_unlock_irqrestore(&host->context_info.lock, flags);
if (!is_urgent || (areq->cmd_flags & REQ_URGENT)) {
start_err = __mmc_start_data_req(host, areq->mrq); // 调用__mmc_start_data_req发起新的异步请求
} else {
/* previous request was done */
err = MMC_BLK_URGENT_DONE;
if (host->areq) {
mmc_post_req(host, host->areq->mrq, 0);
host->areq = NULL;
}
areq->reinsert_req(areq);
mmc_post_req(host, areq->mrq, 0);
goto exit;
}
}
if (host->areq)
mmc_post_req(host, host->areq->mrq, 0);
/* Cancel a prepared request if it was not started. */
if ((err || start_err) && areq)
mmc_post_req(host, areq->mrq, -EINVAL);
if (err)
host->areq = NULL;
else
host->areq = areq;
exit:
if (error)
*error = err;
return data; // 反正上一次正常处理的异步请求
}
//-----------------------------------------------------------------------------------------------------------------------------
static int __mmc_start_data_req(struct mmc_host *host, struct mmc_request *mrq)
{
mrq->done = mmc_wait_data_done;
// 设置mmc_request处理完成的回调函数,会唤醒正在等待请求被完成的进程,后面说明
// host controller会调用mmc_request_done来执行这个回调函数,具体前面分析过了
mrq->host = host;
mmc_start_request(host, mrq); // 开始处理mmc_request请求,前面已经说明过了
return 0;
}
static void mmc_wait_data_done(struct mmc_request *mrq)
{
unsigned long flags;
struct mmc_context_info *context_info = &mrq->host->context_info;
spin_lock_irqsave(&context_info->lock, flags);
mrq->host->context_info.is_done_rcv = true; // 设置is_done_rcv标识
wake_up_interruptible(&mrq->host->context_info.wait); // 唤醒context_info上的等待进程
spin_unlock_irqrestore(&context_info->lock, flags);
}
//-----------------------------------------------------------------------------------------------------------------------------
static int mmc_wait_for_data_req_done(struct mmc_host *host,
struct mmc_request *mrq,
struct mmc_async_req *next_req)
{
// struct mmc_request *mrq:表示正在等待完成的请求
// struct mmc_async_req *next_req:表示下一次要执行的异步请求
struct mmc_command *cmd;
struct mmc_context_info *context_info = &host->context_info;
bool pending_is_urgent = false;
bool is_urgent = false;
bool is_done_rcv = false;
int err, ret;
unsigned long flags;
while (1) {
/* 在这里等待正在进行的请求完成,会在mmc_wait_data_done中被唤醒 */
/* 有几种情况会唤醒等待进程 */
ret = wait_io_event_interruptible(context_info->wait,(context_info->is_done_rcv || context_info->is_new_req || context_info->is_urgent));
spin_lock_irqsave(&context_info->lock, flags);
is_urgent = context_info->is_urgent;
is_done_rcv = context_info->is_done_rcv;
context_info->is_waiting_last_req = false;
spin_unlock_irqrestore(&context_info->lock, flags);
/* 对请求处理完成的处理 */
if (is_done_rcv) {
context_info->is_done_rcv = false;
context_info->is_new_req = false;
cmd = mrq->cmd;
if (!cmd->error || !cmd->retries || mmc_card_removed(host->card)) {
/* 请求正常处理完成,或者失败但是不需要重复尝试的情况的处理 */
err = host->areq->err_check(host->card, host->areq);
//.......
break; /* return err */
} else {
/* 对请求处理出错并且需要重复尝试的情况的处理 */
//.......
}
}
}
return err;
}
6. [mmc subsystem] mmc core(第六章)——mmc core主模块的更多相关文章
- 10. [mmc subsystem] host(第四章)——host实例(sdhci-msm说明)
一.说明 sdhci-msm是指高通的mmc host,其使用了标准SDHC标准.故可以使用前面说的<host(第二章)--sdhci>和<host(第三章)--sdhci-pltf ...
- 9. [mmc subsystem] host(第三章)——sdhci-pltfm说明
一.sdhci-pltfm说明 sdhci-pltfm并不是实际某个host的driver. sdhci-pltfm是指在sdhci core的基础上,提供了统一对sdhci_host的必要属性进行解 ...
- 5. [mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)
零.说明(重要,需要先搞清楚概念有助于后面的理解) 1.mmc core card相关模块为对应card实现相应的操作,包括初始化操作.以及对应的总线操作集合.负责和对应card协议层相关的东西. 这 ...
- 4. [mmc subsystem] mmc core(第四章)——host模块说明
零.说明 对应代码drivers/mmc/core/host.c,drivers/mmc/core/host.h. 为底层host controller driver实现mmc host的申请以及注册 ...
- 3. [mmc subsystem] mmc core(第三章)——bus模块说明
零.说明 对应代码drivers/mmc/core/bus.c. 抽象出虚拟mmc bus,实现mmc bus的操作. 一.API总览 1.mmc bus相关 mmc_register_bus &am ...
- 8. [mmc subsystem] host(第二章)——sdhci
一.sdhci core说明 1.sdhci说明 具体参考<host(第一章)--概述> SDHC:Secure Digital(SD) Host Controller,是指一套sd ho ...
- 7. [mmc subsystem] host(第一章)——概述
一.host简单说明 host,也可以理解为host controller,是指mmc总线上的主机端,mmc总线的控制器,每个host controller对应一条mmc总线. host contro ...
- 2. [mmc subsystem] mmc core数据结构和宏定义说明
一.host相关 1.struct mmc_host struct mmc_host是mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器. 数据结构如 ...
- 1. [mmc subsystem] 概念与框架
一.概念 1.mmc的概念 mmc有很多种意义,具体如下: mmc MultiMedia Card,多媒体存储卡, 但后续泛指一个接口协定(一种卡式),能符合这接口的内存器都可称作mmc储存体. 主要 ...
随机推荐
- [日常] 修复了grub引导问题
上周遇到的神奇引导问题竟然被鬼使神差的修复好了.因为我的电脑是64位的也就是x86_64架构,并且是UEFI模式下,但是之前装的grub一直是grub-传统,并且一直是i386-pc平台也就是32位的 ...
- 堆与栈(heap and stack)在c/c++的应用(概念)
在学习c/c++时,我们经常会遇到 堆与栈 的问题,今天就来讲一下各类情况下的heap,stack的应用. 程序内存布局场景下,堆与栈表示两种内存管理方式: 1.内部分配时,堆和栈表示两种不同的内存管 ...
- vue.nextTick()方法的使用详解
什么是Vue.nextTick()?? 定义:在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 所以就衍生出了这个获取更新后的DOM的Vue方法 ...
- 2、zabbix3.4的安装
系统版本:centos7 数据库版本:mysql二进制安装5.7 zabbix:阿里云安装3.4 一.Zabbix的安装 Zabbix3.4版本官方安装手册链接:https://www.zabbix. ...
- Selenium WebDriver 中鼠标事件(全)
Selenium WebDriver 中鼠标事件(全) 鼠标点击操作 鼠标点击事件有以下几种类型: 清单 1. 鼠标左键点击 Actions action = new Actions(driv ...
- go语言的redis客户端
redis3.0之后提供了新的HA的解决方案,即Cluster模式,由多个节点组成的集群模式.集群master之间基于crc16算法,对key进行校验,得到的值对16384取余,就是key的hash ...
- 机器学习之线性回归以及Logistic回归
1.线性回归 回归的目的是预测数值型数据的目标值.目标值的计算是通过一个线性方程得到的,这个方程称为回归方程,各未知量(特征)前的系数为回归系数,求这些系数的过程就是回归. 对于普通线性回归使用的损失 ...
- P2P中的NAT穿越(打洞)方案详解
一.P2P(点对点技术) 点对点技术(peer-to-peer,简称P2P)又称对等互联网络技术,是一种网络新技术,依赖网络中参与者的计算能力和带宽,而不是把依赖都聚集在较少的几台服务器上. 点对点技 ...
- -bash:vi:command not find 问题解决
Linux命令行输入命令执行后报“bash:vi:command not found”. 这是由于系统PATH设置问题,PATH没有设置对,系统就无法找到精确命令了. 1.在命令行中输入:export ...
- Paper | Densely Connected Convolutional Networks
目录 黄高老师190919在北航的报告听后感 故事背景 网络结构 Dense block DenseNet 过渡层 成长率 瓶颈层 细节 实验 发表在2017 CVPR. 摘要 Recent work ...