[sd card] mmc硬件总线扫描流程(以sd card为例)

一、扫描mmc硬件总线

扫描mmc硬件总线,也就是检测mmc硬件总线上是否有挂载card。更加通俗的,就是卡槽上是否有插入card。

1、扫描mmc硬件总线的时机

mmc core在如下情况下会去扫描mmc硬件总线:

  • 启动一个host的时候而调用mmc_detect_change
    当启动一个host的时候,并不知道当前是否有card插入,此时需要调用mmc_detect_change来假设card插入状态发生了变化,
    并且进行第一次扫描mmc硬件总线。
    代码如下,过滤掉无关代码:
void mmc_start_host(struct mmc_host *host)
{
        //...
    mmc_detect_change(host, 0);
}
  • 底层硬件发现card插入状态发生变化而调用mmc_detect_change的时候(sd card插入状态监控)
    当底层硬件发现card插入状态发生变化,例如sd card的插入或者拔出可以触发某个GPIO产生中断。
    此时,可以在中断处理中调用mmc_detect_change来进行扫描mmc硬件总线,并且根据总线上的card状态变化进行处理。
    这种情况的主要目的是为了实现sd card的热插拔。
    代码如下,过滤掉无关代码:
static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
{
    status = mmc_gpio_get_status(host);   // 先获取card的插入状态
    if (unlikely(status < 0))
        goto out;

    if (status ^ ctx->status) {   // 与之前的card的状态进行比较,不一样则说明mmc硬件总线上的card发生了插入或者拔出事件
        ctx->status = status;
        /* Schedule a card detection after a debounce timeout */
        mmc_detect_change(host, msecs_to_jiffies(200));
                // 调用mmc_detect_change对card的插入状态变化进行处理
                // 注意,这里延时了200ms是用来进行消抖
    }
out:
    return IRQ_HANDLED;
}
  • host要求轮询sd card插入状态的情况下,所进行的轮询操作(sd card插入状态监控)
    一般来说,在host无法根据硬件来及时获取card插入状态发生变化的情况下,会要求mmc_core每隔一段时间(一般是HZ,一秒)扫描一次mmc硬件总线。
    在这种情况下,mmc_host的MMC_CAP_NEEDS_POLL属性会被设置。
    这种情况的主要目的也是为了实现sd card的热插拔。
    代码如下,过滤掉无关代码:
void mmc_rescan(struct work_struct *work)
{
    if (host->caps & MMC_CAP_NEEDS_POLL)
        mmc_schedule_delayed_work(&host->detect, HZ);
        // 这里先说明下
        // 在《host模块说明》已经知道了在mmc_alloc_host中默认将host->detect工作设置为mmc_rescan(card扫描)函数, 
        // INIT_DELAYED_WORK(&host->detect, mmc_rescan)
        // 这样,通过mmc_schedule_delayed_work(&host->detect, HZ)就会每隔HZ时间就会执行一次mmc_rescan
}

2、如何扫描mmc硬件总线

从上述,我们知道了可以通过调用mmc_detect_change和执行host->detect工作来发起mmc硬件总线的扫描。
而mmc_detect_change最终也是执行mmc_host->detect工作来发起mmc硬件总线扫描的。
下面来看一下这两个函数

  • mmc_detect_change
    代码如下:
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
    host->detect_change = 1;    // 设置mmc_host->detect_change,表示检测到card状态发生了变化
    mmc_schedule_delayed_work(&host->detect, delay);
        // 间隔delay时间之后,执行工作host->detect
        // 这个delay时间用于消抖
}

可以发现,mmc_detect_change最终也是执行mmc_host->detect工作来发起mmc硬件总线扫描的。

  • mmc_host->detect
    通过前面的说明,可以知道mmc_host->detect才是发起mmc硬件总线扫描的工作的。
    那么,这个东西是在哪里被设置又是被设置成什么的呢?这个其实在《[mmc subsystem] mmc core(第四章)——host模块说明》中已经说明过了。
    代码如下,去掉无关代码:
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
        //...
    INIT_DELAYED_WORK(&host->detect, mmc_rescan);
        //...
}

可以发现mmc_host->detect是在mmc_alloc_host中被设置为mmc_rescan了。
所以mmc_rescan是扫描mmc硬件总线的核心内容。

  • mmc_rescan
    首先说明,当mmc core检测到一张card并且初始化该card完成时,会将该mmc_bus于该card绑定,相应的mmc_bus的操作mmc_bus_ops就会被设置成对应card类型的操作集。
    例如emmc的话就是mmc_ops或者mmc_ops_unsafe,而sd card的话就是mmc_sd_ops或者mmc_sd_ops_unsafe。
    具体可以参考《[mmc subsystem] mmc core(第五章)——card相关模块(mmc type card)
    所以mmc_rescan主要是进行以下两种操作:

    • 当mmc_host的mmc_bus_ops没有被设置的时候
      这种情况下,说明mmc_bus并没有和card绑定。也就是之前并没有card插入或者识别初始化card的过程中失败了。
      所以只需要去判断当前是否有card插入,如果有的话,进行card的识别和初始化操作(并且会设置mmc_bus_ops)。否则,说明都不做。

    • 当mmc_host的mmc_bus_ops被设置的时候
      这种情况下,说明mmc_bus已经和card绑定了。也就是之前已经有card插入并且初始化成功了。
      那么此时,需要调用mmc_bus_ops中的detect方法(对于sd card来说就是mmc_sd_detect)来判断card是否被移除,并且进行相应的动作。

代码如下(去掉一些和流程性无关的代码):

void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host == container_of(work, struct mmc_host, detect.work); // 根据mmc_host->decect.work来获取到mmc_host
    bool extend_wakelock = false;

    if (host->rescan_disable)
        return;    // 如果host还没有初始化完成的话,会设置rescan_disable,此时是不允许扫描硬件总线的

    /* If there is a non-removable card registered, only scan once */
    if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
        return;    // 对于MMC_CAP_NONREMOVABLE的host来说,其card是不可移除的,那么也就只需要扫描一次硬件总线
    host->rescan_entered = 1;

/** 以下先处理mmc硬件总线上原来已经存在card的情况 **/
    if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
        && !(host->caps & MMC_CAP_NONREMOVABLE))
        host->bus_ops->detect(host);    
       // 在mmc_bus_ops被设置被设置的情况下(也就是已经和card绑定了)
       // 调用mmc_bus_ops->detect来检测card是否被移除,如果是的话,进行相应的释放动作
       // 同时,会销毁mmc_bus_ops

    /* if there still is a card present, stop here */
    if (host->bus_ops != NULL) {    // 说明card并没有被移除,不需要进行什么动作了,直接退出
        goto out;
    }

/** 走到这里,说明之前并没有card插入,或者说card的拔出动作已经处理完成 **/
        // 根据host->ops->get_cd来获取当前card的插入状态,为0说明当前没有card插入
    if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
        goto out;    // 退出
    }


    if (!mmc_rescan_try_freq(host, host->f_min))
         // 说明当前有card插入,调用mmc_rescan_try_freq,以最小的工作频率来识别和初始化card
        extend_wakelock = true;

 out:
    if (host->caps & MMC_CAP_NEEDS_POLL)
        mmc_schedule_delayed_work(&host->detect, HZ);
        // 在需要轮询的情况下,间隔HZ工作之后,重新调度工作host->detect,也就是mmc_rescan
        // 就是通过这里实现轮询的机制的。
}

二、sd card插入状态的获取

通过第一节中,我们知道了mmc_rescan是通过调用mmc_host->mmc_host_ops->get_cd来获取当前card的插入状态的。
那么这个方法是怎么实现的呢?如何获取card当前的插入状态呢?以下来说明一下这两个问题。

1、获取sd card当前的插入状态

一般来说有两种方式来获取到sd card当前的插入状态
(1)GPIO获取的方法
可以通过sd card的card detect引脚来判断当前是否有sd card插入
(2)host寄存器获取的方法
某些host在硬件上有识别sd card是否插入的能力。这种情况下,可以通过读取host的寄存器来获取到当前是否有sd card插入。

1(1)、通过GPIO获取当前sd card的插入状态

  • DECECT引脚
    这里先
    以TF card为例,参考http://blog.csdn.net/xqhrs232/article/details/43339171
    首先以一个原理图来看一下TF card卡座的连接

    avatar
    (其中SD_CARD_DET_N是有外部上拉的)。
    以卡座的DET_SWITCH引脚作为card检测脚。
    其card和卡座引脚定义如下:

    avatar
    可以观察到,虽然TF CARD(micro)只有8个引脚,但是卡座另外定义了一个CD引脚(DET_SWITCH)。
    我的猜想是,对于某些tf card卡座来说,当tf card插入时,会把CD引脚(DET_SWITCH)直接定义到连接到groud上去。
    所以,这里可以梳理出,

    • 当card没有插入的情况下,由于外部上拉的关系,cpu连接到DET_SWITCH的gpio为高电平。
    • 当card插入时,tf card卡座会把DET_SWITCH拉低,相应的,cpu连接到DET_SWITCH的gpio为低电平。
      当然,上述只是一种情况,具体cd gpio的电平情况取决于使用的卡座的实现。
  • 注册card插入状态检测GPIO软件介绍
    mmc core提供了mmc_gpio_request_cd来为host定义自己的cd-detect引脚,也就是对应上面连接到卡座上的CD引脚的GPIO。
    mmc_gpio_request_cd实现如下(虽然也会注册GPIO中断,但是这里先不关心):

    int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio)
    {
    struct mmc_gpio *ctx;    // struct mmc_gpio用来表示连接卡槽的一些GPIO的状态
    int ret;
    
    ret = mmc_gpio_alloc(host);    // 调用mmc_gpio_alloc来为host分配一个struct mmc_gpio 
    ctx = host->slot.handler_priv;    // 获取struct mmc_gpio 
    
    ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN, ctx->cd_label);   // 申请这个GPIO
    ctx->cd_gpio = gpio;    // 将设置到ctx->cd_gpio中
    }

    所以后续就可以通过读取 mmc_host->slot.handler_priv->cd_gpio的电平状态来判断当前card的插入状态

  • 通过GPIO获取当前sd card的插入状态
    在上述通过mmc_gpio_request_cd注册完成cd gpio之后,就可以通过mmc_gpio_get_cd来获取card的插入状态。
    当其返回1时,表示当前有card插入,当其返回0是,表示当前没有card插入。
    其实现如下

    int mmc_gpio_get_cd(struct mmc_host *host)
    {
    struct mmc_gpio *ctx = host->slot.handler_priv;    // 提取出host对应的struct mmc_gpio
    
    if (!ctx || !gpio_is_valid(ctx->cd_gpio))    // 如果没有注册cd gpio的情况下,直接返回了
        return -ENOSYS;
    
    return !gpio_get_value_cansleep(ctx->cd_gpio) ^ !!(host->caps2 & MMC_CAP2_CD_ACTIVE_HIGH);
        // 因为mmc_gpio_get_cd返回1表示有card插入,返回0是表示没有card插入
        // MMC_CAP2_CD_ACTIVE_HIGH属性被设置的话,表示当有card插入时为cd gpio为高电平(主要取决于卡座)
        // 假设这里MMC_CAP2_CD_ACTIVE_HIGH没有被设置,也就是有card插入时cd gpio为低电平
        // 当cd gpio检测到低电平的时候,相当于是!0 ^ 0=1,因此会返回1,有card插入
        // 当cd gpio检测到高电平的时候,相当于是!1 ^ 0=0,因此会返回0,没有card插入
    }

1(2)、通过host寄存器获取当前card插入状态

前提是某些host在硬件上有识别sd card是否插入的能力。这种情况下,可以通过读取host的寄存器来获取到当前是否有sd card插入。

  • 寄存器介绍
    以sdhci类host为例,根据《SDHC_Ver3.00_Final_110225》 “2.2.9 Present State Register(Offset 024h)”

    avatar
    可以通过Present State Register(偏移地址为0x24)的bit16来判断card的插入状态。
    **注意:有可能存在实际使用sdhci的host不符合上述标准的情况,对于这类host,会定义sdhci_host中的quirks的SDHCI_QUIRK_BROKEN_CARD_DETECTION。
    SDHCI_QUIRK_BROKEN_CARD_DETECTION说明该sdhci host并没有通过Present State Register(偏移地址为0x24)的bit16来提供一个可靠的card插入状态判断方式。**

  • 软件介绍
    以sdhci类host为例

    static int sdhci_do_get_cd(struct sdhci_host *host)
    {
    if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
        return 1;
    
    /* Host native card detect */
    return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT);
    }

2、mmc_host_ops->get_cd方法的实现

通过上述我们已经知道了两种获取当前card插入状态的方法。
接下来就是利用上述的两种方法来实现mmc_host_ops->get_cd。
对于这个方法来说,返回1表示当前有card插入,返回0表示当前没有card插入。
对于sdhci类host来说,这个方法对应实现为sdhci_do_get_cd,实现方法如下:

static int sdhci_get_cd(struct mmc_host *mmc)
{
    struct sdhci_host *host = mmc_priv(mmc);
    int ret;

    sdhci_runtime_pm_get(host);
    ret = sdhci_do_get_cd(host);   // 进一步调用sdhci_do_get_cd
    sdhci_runtime_pm_put(host);
    return ret;
}
static int sdhci_do_get_cd(struct sdhci_host *host)
{
    int gpio_cd = mmc_gpio_get_cd(host->mmc);   
         // 通过调用mmc_gpio_get_cd,通过cd gpio获取当前的card插入状态,对应上述“通过GPIO获取当前sd card的插入状态”

    /* If polling/nonremovable, assume that the card is always present. */
    if (host->mmc->caps & MMC_CAP_NONREMOVABLE)   // 对于不可移除的设备,总是返回1
        return 1;

    if (!IS_ERR_VALUE(gpio_cd))  
        return !!gpio_cd;   // mmc_gpio_get_cd返回值有效的话,返回插入状态

// 因为host并不一定有通过mmc_gpio_request_cd注册cd gpio,也就是没有实现“通过GPIO获取当前sd card的插入状态”
// 这种情况下就尝试通过用sdhci的寄存器来获取

        // 当SDHCI_QUIRK_BROKEN_CARD_DETECTION被设置的时候,说明该host并没有提供检测card插入状态检测的能力,
         // 直接返回1,假设插入,由后续协议的工作来尝试初始化
    if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
        return 1;

        // 读取sdhci的SDHCI_PRESENT_STATE的bit SDHCI_CARD_PRESENT来获取card的插入状态。
    /* Host native card detect */
    return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT);
}

三、sd card热插拔实现

sd card热插拔实现,就是对sd card的插入状态的监控。通常来说有两种方式中断监控和轮询监控。
mmc core会选择其中的一种方式来监控sd card的插入状态。

1、中断监控

通过sd card的cd引脚的电平变化来触发中断,从而告知cpu说sd card的插入状态已经发生了变化。
实现方式如下:

  • cd-gpio的解析
    在dtsi中可以定义如下:

    &sdhc_2 {
    cd-gpios = <&gpio_ctrl 99 0x1>;
    }
    // cd-gpios这个属性名的定义取决于host driver将cd gpio定义成了什么名字
    // gpio_ctrl,也就是要使用的GPIO所使用的gpio controller
    // 99:sd card的cd引脚连接到GPIO99上
    // 0x01:取决于host driver如何解释这个flag的,一般来说,0x01表示低电平有card插入,0x00则表示高电平有card插入

    在host driver的解析如下

    status_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &flags);
        // 解析cd-gpios属性,提取对应GPIO号到status_gpio中,提取flags
    if (gpio_is_valid(status_gpio) & !(flags & OF_GPIO_ACTIVE_LOW))
        pdata->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH;
        // 这里会解析flags,如前面说的
        // 0x01表示低电平有card插入,0x00则表示高电平有card插入
        // OF_GPIO_ACTIVE_LOW=1
        // 所以当flags=0的时候,会设置MMC_CAP2_CD_ACTIVE_HIGH,告诉mmc core,这个host现在是高电平表示有card插入
  • 注册cd-gpio中断引脚
    前面说过了可以通过mmc_gpio_request_cd来注册一个gpio检测卡插入状态的引脚。
    同样也是通过这个函数来注册这个GPIO对应的中断。

    mmc_gpio_request_cd(mmc_host, status_gpio);
  • mmc_gpio_request_cd实现

    int mmc_gpio_request_cd(struct mmc_host *host, unsigned int gpio)
    {
    struct mmc_gpio *ctx;
    int irq = gpio_to_irq(gpio);    // 获取gpio对应的中断号
    int ret;
    
    ret = mmc_gpio_alloc(host);    // 为mmc_host分配一个struct mmc_gpio
    ctx = host->slot.handler_priv;    // 提取struct mmc_gpio
    ret = devm_gpio_request_one(&host->class_dev, gpio, GPIOF_DIR_IN,  ctx->cd_label); // 申请这个GPIO
    
        // 判断host driver是否已经定义了轮询属性,如果定义了轮询属性,那么就不需要使用中断电控
    if (irq >= 0 && host->caps & MMC_CAP_NEEDS_POLL)
        irq = -EINVAL;
    
    ctx->cd_gpio = gpio;    // 设置ctx->cd_gpio,在GPIO检测sd card插入状态的情况下会使用到
    host->slot.cd_irq = irq;    // 设置cd中断,host->slot.cd_irq
    
    ret = mmc_gpio_get_status(host);    // 通过GPIO获取当前的card插入状态,主要是判断返回值是否正常,也就是改GPIO是否可用
    if (ret < 0)
        return ret;
    
    ctx->status = ret;
    
    if (irq >= 0) {
        ret = devm_request_threaded_irq(&host->class_dev, irq,
            NULL, mmc_gpio_cd_irqt,
            IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
            ctx->cd_label, host);    // 这里注册了cd-gpio的中断处理函数为mmc_gpio_cd_irqt,后面我们会看一下这个的实现
        if (ret < 0)
            irq = ret;
    }
    
        // 如果设置成中断监控的方式失败的情况下,设置轮询标识MMC_CAP_NEEDS_POLL,使能轮询监控
    if (irq < 0)
        host->caps |= MMC_CAP_NEEDS_POLL;
    
    return 0;
    }
  • 中断监控的中断处理函数mmc_gpio_cd_irqt
    代码如下:

    static irqreturn_t mmc_gpio_cd_irqt(int irq, void *dev_id)
    {
    /* Schedule a card detection after a debounce timeout */
    struct mmc_host *host = dev_id;
    struct mmc_gpio *ctx = host->slot.handler_priv;
    int status;
    
    status = mmc_gpio_get_status(host);    // 获取此时的card插入状态
    if (unlikely(status < 0))
        goto out;
    
    if (status ^ ctx->status) {    // 和上一次的card插入状态进行比较,如果是反的话,说明card发生了插入或者拔出事件
        ctx->status = status;    // 设置上一次的card插入状态为此时的card插入状态
    
        /* Schedule a card detection after a debounce timeout */
        mmc_detect_change(host, msecs_to_jiffies(200));    
               // 执行mmc_detect_change对card插入状态变化进行处理,200ms则是用来进行消抖
    }
    out:
    
    return IRQ_HANDLED;
    }

2、轮询监控

主要实现为每隔一段时间(一般是HZ,1s)扫描一下mmc硬件总线。

  • 对应需要设置的属性标识
    需要设置mmc_host的MMC_CAP_NEEDS_POLL属性标识

  • 实现方式
    每个1s中调用一次mmc_host->detect工作。

    void mmc_rescan(struct work_struct *work)
    {
    if (host->caps & MMC_CAP_NEEDS_POLL)
        mmc_schedule_delayed_work(&host->detect, HZ);
        // 这里先说明下
        // 在《host模块说明》已经知道了在mmc_alloc_host中默认将host->detect工作设置为mmc_rescan(card扫描)函数, 
        // INIT_DELAYED_WORK(&host->detect, mmc_rescan)
        // 这样,通过mmc_schedule_delayed_work(&host->detect, HZ)就会每隔HZ时间就会执行一次mmc_rescan
    }

    在host启动的时候,会执行一次mmc_host->detect工作,这时就开始进行轮询了。
    以上就实现了每隔1s执行一次mmc_rescan来扫描mmc硬件总线的变化情况。

  • 8
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值