• 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...
    ",
                 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;
    }
    
    
    
  • 相关阅读:
    NPOI 操作 excel 帮助类
    文件帮助类
    浮点数精度问题
    多段文本显示省略号
    数字排序
    删除字符串首位空格
    生成一定范围的随机数
    锚链接动画
    原生js转json
    弹出遮罩和对话框
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/10811296.html
Copyright © 2020-2023  润新知