Linux内核MMC框架

1.mmc的概念

1.MMC

MultiMedia Card,多媒体存储卡, 但后续泛指一个接口协定(一种卡式),能符合这接口的内存器都可称作mmc储存体,工作电压:高电压为2.7~3.6 V,低电压为1.65~1.95 V,可选.

2.MMC总线

mmc总线是和I2C总线、SPI总线类似的一种总线结构。

卡与主控制器间串行传送,工作时钟频率范围为0~200 MHz,mmc总线上最多可识别64 K个mmc设备,在总线上不超过10个卡时,可运行到最高频率。

3.mmc设备

使用mmc接口规范(MCI, Multimedia Card Interface)的设备都可以称之为mmc设备。分为以下三种:

1.mmc type card:

1.标准mmc卡:闪存卡的一种,使用mmc标准;

2.emmc:Embedded MultiMediaCard,是MMC协会所制定的内嵌式存储器标准规格,带有mmc接口,是具备mmc协议的芯片。

2.sd type card

sd卡:SD卡为Secure Digital Memory Card, 即安全数码卡。它在MMC的基础上发展而来,增加了两个主要特色:SD卡强调数据的安全安全,可以设定所储存的使用权限,防止数据被他人复制。兼容mmc接口规范。

3.sdio type card

sdio设备:SDIO是在SD标准上定义了一种外设接口,它和SD卡规范间的一个重要区别是增加了低速标准。在SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开销支持低速IO能力。常见的sdio设备有Wi-Fi card、Bluetooth card等等。

这几种类型的card统称为mmc card

4.mmc协议

类似i2c协议、spi协议,mmc总线上也有一套自己的通讯规范。通信规范后续在说明。而上述mmc设备基于上mmc总线通讯规范上由自身硬件特性设置了自己的一套协议。

1.标准mmc卡协议
2.emmc协议(主要区别在于读写速度上)
3.sd协议

5.mmc subsystem

kernel中的mmc subsystem用于管理所有mmc总线控制器以及mmc设备,包括mmc type card(标准mmc卡、emmc)、sd type card(sd卡)、sdio type card。
也就是说只要使用MCI的设备都交由mmc subsystem统一管理。

2.MMC framework的软件架构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

MMC framework分别有“从左到右”和“从下到上”两种层次结构:

1.从左到右

MMC协议是一个总线协议,因此包括Host controller、Bus、Card三类实体(从左到右)。相应的,MMC framework抽象出了host、bus、card三个软件实体,以便和硬件一一对应:

host:负责驱动Host controller,提供诸如访问card的寄存器、检测card的插拔、读写card等操作方法。从设备模型的角度看,host会检测卡的插入,并向bus注册MMC card设备;

bus:是MMC bus的虚拟抽象,以标准设备模型的方式,收纳MMC card(device)以及对应的MMC driver(driver);

card:抽象具体的MMC卡,由对应的MMC driver驱动(从这个角度看,可以忽略MMC的技术细节,只需关心一个个具有特定功能的卡设备,如存储卡、WIFI卡、GPS卡等等).

2.从左到右

MMC host controller driver位于底层,基于MMC core提供的框架,驱动具体的硬件(MMC controller);

MMC core位于中间,是MMC framework的核心实现,负责抽象host、bus、card等软件实体,负责向底层提供统一、便利的编写Host controller driver的API;

MMC card driver位于最上面,负责驱动MMC core抽象出来的虚拟的card设备,并对接内核其它的framework(例如块设备、TTY、wireless等),实现具体的功能。

3.工作流程

Linux MMC framework的工作流程如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.数据结构

1.host相关

1.struct mmc_host

struct mmc_host是mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器。

struct mmc_host {
    struct device       *parent;   // 对应的host controller的device
    struct device       class_dev;   // mmc_host的device结构体,会挂在class/mmc_host下
    int         index;   // 该host的索引号
    const struct mmc_host_ops *ops; // 该host的操作集,由host controller设置,后面说明
    unsigned int        f_min;   // 该host支持的最低频率
    unsigned int        f_max;   // 该host支持的最大频率
    unsigned int        f_init;   // 该host使用的初始化频率
    u32         ocr_avail;   // 该host可用的ocr值(电压相关)
    u32         ocr_avail_sdio; /* SDIO-specific OCR */
    u32         ocr_avail_sd;   /* SD-specific OCR */
    u32         ocr_avail_mmc;  /* MMC-specific OCR */
    struct notifier_block   pm_notify;
    u32         max_current_330;   // 3.3V时的最大电流
    u32         max_current_300;   // 3.0V时的最大电流
    u32         max_current_180;   // 1.8V时的最大电流
    u32         caps;       /* Host capabilities */   // host属性
    u32         caps2;      /* More host capabilities */   // host属性2
    mmc_pm_flag_t       pm_caps;    /* supported pm features */   // 电源管理属性

        /// 以下是和clock相关的成员
    int         clk_requests;   /* internal reference counter */
    unsigned int        clk_delay;  /* number of MCI clk hold cycles */
    bool            clk_gated;  /* clock gated */
    struct delayed_work clk_gate_work; /* delayed clock gate */
    unsigned int        clk_old;    /* old clock value cache */
    spinlock_t      clk_lock;   /* lock for clk fields */
    struct mutex        clk_gate_mutex; /* mutex for clock gating */
    struct device_attribute clkgate_delay_attr;
    unsigned long           clkgate_delay;

    /* host specific block data */   
        // 和块相关的成员
    unsigned int        max_seg_size;   /* see blk_queue_max_segment_size */
    unsigned short      max_segs;   /* see blk_queue_max_segments */
    unsigned short      unused;
    unsigned int        max_req_size;   /* maximum number of bytes in one req */
    unsigned int        max_blk_size;   /* maximum size of one mmc block */
    unsigned int        max_blk_count;  /* maximum number of blocks in one req */
    unsigned int        max_discard_to; /* max. discard timeout in ms */

    /* private data */
    spinlock_t      lock;       /* lock for claim and bus ops */   // host的bus使用的锁
    struct mmc_ios      ios;        /* current io bus settings */   // io setting,后续说明
    u32         ocr;        /* the current OCR setting */   // 当前使用的ocr的值

    /* group bitfields together to minimize padding */
    unsigned int        use_spi_crc:1;
    unsigned int        claimed:1;  /* host exclusively claimed */   // host是否已经被占用
    unsigned int        bus_dead:1; /* bus has been released */   // host的bus是否处于激活状态

    int         rescan_disable; /* disable card detection */   // 禁止rescan的标识,禁止搜索card
    int         rescan_entered; /* used with nonremovable devices */   // 是否已经rescan过的标识,对应不可移除的设备只能rescan一次

    struct mmc_card     *card;      /* device attached to this host */   // 和该host绑定在一起的card

    wait_queue_head_t   wq;
    struct task_struct  *claimer;   /* task that has host claimed */   // 该host的占有者进程
    struct task_struct  *suspend_task;
    int         claim_cnt;  /* "claim" nesting count */   // 占有者进程对该host的占用计数

    struct delayed_work detect;   // 检测卡槽变化的工作
    struct wake_lock    detect_wake_lock;   // 检测卡槽变化的工作使用的锁
    const char      *wlock_name;   // 锁名称
    int         detect_change;  /* card detect flag */ // 需要检测卡槽变化的标识
    struct mmc_slot     slot;   // 卡槽的结构体

    const struct mmc_bus_ops *bus_ops;  /* current bus driver */   // host的mmc总线的操作集,后面说明
    unsigned int        bus_refs;   /* reference counter */   // host的mmc总线的使用计数
    unsigned int        bus_resume_flags;   // host的mmc总线的resume标识

    mmc_pm_flag_t       pm_flags;   /* requested pm features */

#ifdef CONFIG_REGULATOR
    bool            regulator_enabled; /* regulator state */   // 代表regulator(LDO)的状态
#endif
    struct mmc_supply   supply;

    struct dentry       *debugfs_root;   // 对应的debug目录结构体

    struct mmc_async_req    *areq;      /* active async req */   // 当前正在处理的异步请求
    struct mmc_context_info context_info;   /* async synchronization info */ // 异步请求的信息

    unsigned int        actual_clock;   /* Actual HC clock rate */ // 实际的时钟频率
};

ocr值各个位代表的电压意义如下:

#define MMC_VDD_165_195     0x00000080  /* VDD voltage 1.65 - 1.95 */
#define MMC_VDD_20_21       0x00000100  /* VDD voltage 2.0 ~ 2.1 */
#define MMC_VDD_21_22       0x00000200  /* VDD voltage 2.1 ~ 2.2 */
#define MMC_VDD_22_23       0x00000400  /* VDD voltage 2.2 ~ 2.3 */
#define MMC_VDD_23_24       0x00000800  /* VDD voltage 2.3 ~ 2.4 */
#define MMC_VDD_24_25       0x00001000  /* VDD voltage 2.4 ~ 2.5 */
#define MMC_VDD_25_26       0x00002000  /* VDD voltage 2.5 ~ 2.6 */
#define MMC_VDD_26_27       0x00004000  /* VDD voltage 2.6 ~ 2.7 */
#define MMC_VDD_27_28       0x00008000  /* VDD voltage 2.7 ~ 2.8 */
#define MMC_VDD_28_29       0x00010000  /* VDD voltage 2.8 ~ 2.9 */
#define MMC_VDD_29_30       0x00020000  /* VDD voltage 2.9 ~ 3.0 */
#define MMC_VDD_30_31       0x00040000  /* VDD voltage 3.0 ~ 3.1 */
#define MMC_VDD_31_32       0x00080000  /* VDD voltage 3.1 ~ 3.2 */
#define MMC_VDD_32_33       0x00100000  /* VDD voltage 3.2 ~ 3.3 */
#define MMC_VDD_33_34       0x00200000  /* VDD voltage 3.3 ~ 3.4 */
#define MMC_VDD_34_35       0x00400000  /* VDD voltage 3.4 ~ 3.5 */
#define MMC_VDD_35_36       0x00800000  /* VDD voltage 3.5 ~ 3.6 */

host属性(mmc_host->caps)支持的属性如下

#define MMC_CAP_4_BIT_DATA  (1 << 0)    /* Can the host do 4 bit transfers */
#define MMC_CAP_MMC_HIGHSPEED   (1 << 1)    /* Can do MMC high-speed timing */
#define MMC_CAP_SD_HIGHSPEED    (1 << 2)    /* Can do SD high-speed timing */
#define MMC_CAP_SDIO_IRQ    (1 << 3)    /* Can signal pending SDIO IRQs */
#define MMC_CAP_SPI     (1 << 4)    /* Talks only SPI protocols */
#define MMC_CAP_NEEDS_POLL  (1 << 5)    /* Needs polling for card-detection */
#define MMC_CAP_8_BIT_DATA  (1 << 6)    /* Can the host do 8 bit transfers */
#define MMC_CAP_NONREMOVABLE    (1 << 8)    /* Nonremovable e.g. eMMC */
#define MMC_CAP_WAIT_WHILE_BUSY (1 << 9)    /* Waits while card is busy */
#define MMC_CAP_ERASE       (1 << 10)   /* Allow erase/trim commands */
#define MMC_CAP_1_8V_DDR    (1 << 11)   /* can support */
                        /* DDR mode at 1.8V */
#define MMC_CAP_1_2V_DDR    (1 << 12)   /* can support */
                        /* DDR mode at 1.2V */
#define MMC_CAP_HSDDR       (MMC_CAP_1_8V_DDR | MMC_CAP_1_2V_DDR)
#define MMC_CAP_POWER_OFF_CARD  (1 << 13)   /* Can power off after boot */
#define MMC_CAP_BUS_WIDTH_TEST  (1 << 14)   /* CMD14/CMD19 bus width ok */
#define MMC_CAP_UHS_SDR12   (1 << 15)   /* Host supports UHS SDR12 mode */
#define MMC_CAP_UHS_SDR25   (1 << 16)   /* Host supports UHS SDR25 mode */
#define MMC_CAP_UHS_SDR50   (1 << 17)   /* Host supports UHS SDR50 mode */
#define MMC_CAP_UHS_SDR104  (1 << 18)   /* Host supports UHS SDR104 mode */
#define MMC_CAP_UHS_DDR50   (1 << 19)   /* Host supports UHS DDR50 mode */
#define MMC_CAP_DRIVER_TYPE_A   (1 << 23)   /* Host supports Driver Type A */
#define MMC_CAP_DRIVER_TYPE_C   (1 << 24)   /* Host supports Driver Type C */
#define MMC_CAP_DRIVER_TYPE_D   (1 << 25)   /* Host supports Driver Type D */
#define MMC_CAP_CMD23       (1 << 30)   /* CMD23 supported. */
#define MMC_CAP_HW_RESET    (1 << 31)   /* Hardware reset */

host属性2(mmc_host->caps2)支持的属性如下

#define MMC_CAP2_BOOTPART_NOACC (1 << 0)    /* Boot partition no access */
#define MMC_CAP2_CACHE_CTRL (1 << 1)    /* Allow cache control */
#define MMC_CAP2_POWEROFF_NOTIFY (1 << 2)   /* Notify poweroff supported */
#define MMC_CAP2_NO_MULTI_READ  (1 << 3)    /* Multiblock reads don't work */
#define MMC_CAP2_NO_SLEEP_CMD   (1 << 4)    /* Don't allow sleep command */
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5)        /* can support */
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6)        /* can support */
#define MMC_CAP2_HS200      (MMC_CAP2_HS200_1_8V_SDR | \
                 MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP2_BROKEN_VOLTAGE (1 << 7)    /* Use the broken voltage */
#define MMC_CAP2_DETECT_ON_ERR  (1 << 8)    /* On I/O err check card removal */
#define MMC_CAP2_HC_ERASE_SZ    (1 << 9)    /* High-capacity erase size */
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10)   /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11)   /* Write-protect signal active high */
#define MMC_CAP2_PACKED_RD  (1 << 12)   /* Allow packed read */
#define MMC_CAP2_PACKED_WR  (1 << 13)   /* Allow packed write */
#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \
                 MMC_CAP2_PACKED_WR)
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14)   /* Don't power up before scan */
#define MMC_CAP2_INIT_BKOPS     (1 << 15)   /* Need to set BKOPS_EN */
#define MMC_CAP2_PACKED_WR_CONTROL (1 << 16) /* Allow write packing control */
#define MMC_CAP2_CLK_SCALE  (1 << 17)   /* Allow dynamic clk scaling */
#define MMC_CAP2_STOP_REQUEST   (1 << 18)   /* Allow stop ongoing request */
/* Use runtime PM framework provided by MMC core */
#define MMC_CAP2_CORE_RUNTIME_PM (1 << 19)
#define MMC_CAP2_SANITIZE   (1 << 20)       /* Support Sanitize */
/* Allows Asynchronous SDIO irq while card is in 4-bit mode */
#define MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE (1 << 21)

#define MMC_CAP2_HS400_1_8V (1 << 22)        /* can support */
#define MMC_CAP2_HS400_1_2V (1 << 23)        /* can support */
#define MMC_CAP2_CORE_PM       (1 << 24)       /* use PM framework */
#define MMC_CAP2_HS400      (MMC_CAP2_HS400_1_8V | \
                 MMC_CAP2_HS400_1_2V)
#define MMC_CAP2_NONHOTPLUG (1 << 25)   /*Don't support hotplug*/
2.struct mmc_host_ops

mmc core将host需要提供的一些操作方法封装成struct mmc_host_ops。mmc core主模块的很多接口都是基于这里面的操作方法来实现的,通过这些方法来操作host硬件达到对应的目的。所以struct mmc_host_ops也是host controller driver需要实现的核心部分。

struct mmc_host_ops {
    /*
     * 'enable' is called when the host is claimed and 'disable' is called
     * when the host is released. 'enable' and 'disable' are deprecated.
     */
    int (*enable)(struct mmc_host *host);   // 使能host,当host被占用时(第一次调用mmc_claim_host)调用
    int (*disable)(struct mmc_host *host);   // 禁用host,当host被释放时(第一次调用mmc_release_host)调用
    /*
     * It is optional for the host to implement pre_req and post_req in
     * order to support double buffering of requests (prepare one
     * request while another request is active).
     * pre_req() must always be followed by a post_req().
     * To undo a call made to pre_req(), call post_req() with
     * a nonzero err condition.
     */
        // post_req和pre_req是为了实现异步请求处理而设置的
        // 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待
        // 具体参考《mmc core主模块》
    void    (*post_req)(struct mmc_host *host, struct mmc_request *req,
                int err);
    void    (*pre_req)(struct mmc_host *host, struct mmc_request *req,
               bool is_first_req);
    void    (*request)(struct mmc_host *host, struct mmc_request *req); // host处理mmc请求的方法,在mmc_start_request中会调用

    void    (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);   // 设置host的总线的io setting
    int (*get_ro)(struct mmc_host *host);   // 获取host上的card的读写属性
    int (*get_cd)(struct mmc_host *host);   // 检测host的卡槽中card的插入状态

    /* optional callback for HC quirks */
    void    (*init_card)(struct mmc_host *host, struct mmc_card *card);   // 初始化card的方法

    int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios);   // 切换信号电压的方法

    /* Check if the card is pulling dat[0:3] low */
    int (*card_busy)(struct mmc_host *host);   // 用于检测card是否处于busy状态

    /* The tuning command opcode value is different for SD and eMMC cards */
    int (*execute_tuning)(struct mmc_host *host, u32 opcode);   // 执行tuning操作,为card选择一个合适的采样点
    int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv);   // 选择信号的驱动强度
    void    (*hw_reset)(struct mmc_host *host);   // 硬件复位
    void    (*card_event)(struct mmc_host *host);   // 
    unsigned long (*get_max_frequency)(struct mmc_host *host); // 获取host支持的最大频率的方法
    unsigned long (*get_min_frequency)(struct mmc_host *host);   // 获取host支持的最小频率的方法
    int (*notify_load)(struct mmc_host *, enum mmc_load);
    int (*stop_request)(struct mmc_host *host);   // 停止请求处理的方法
    unsigned int    (*get_xfer_remain)(struct mmc_host *host);
};

2.core相关

1.struct mmc_card

struct mmc_card是mmc core由mmc设备抽象出来的card设备的结构体,用于代表一个mmc设备。

struct mmc_card {
    struct mmc_host     *host;      /* the host this device belongs to */    // 该mmc_card所属host
    struct device       dev;        /* the device */    // 对应的device
    unsigned int        rca;        /* relative card address of device */    // 该card的RCA地址
    unsigned int        type;       /* card type */    // card类型,后面说明

    unsigned int        state;      /* (our) card state */    // card的当前状态,后面说明

    unsigned int        quirks;     /* card quirks */    // 该card的一些特点
    unsigned int        erase_size; /* erase size in sectors */   
    unsigned int        erase_shift;    /* if erase unit is power 2 */
    unsigned int        pref_erase; /* in sectors */
    u8          erased_byte;    /* value of erased bytes */

    u32         raw_cid[4]; /* raw card CID */    // 原始的cid寄存器的值
    u32         raw_csd[4]; /* raw card CSD */    // 原始的csd寄存器的值
    u32         raw_scr[2]; /* raw card SCR */    // 原始的scr寄存器的值
    struct mmc_cid      cid;        /* card identification */    // 从cid寄存器的值解析出来的信息
    struct mmc_csd      csd;        /* card specific */    // 从csd寄存器的值解析出来的信息
    struct mmc_ext_csd  ext_csd;    /* mmc v4 extended card specific */    // 从ext_csd寄存器的值解析出来的信息
    struct sd_scr       scr;        /* extra SD information */    // 外部sdcard的信息
    struct sd_ssr       ssr;        /* yet more SD information */    // 更多关于sd card的信息
    struct sd_switch_caps   sw_caps;    /* switch (CMD6) caps */    // sd的切换属性
    unsigned int        sd_bus_speed;   /* Bus Speed Mode set for the card */

    struct dentry       *debugfs_root;    // 对应debug目录的结构体
    struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */    // 物理分区
    unsigned int    nr_parts;    // 分区数量
    unsigned int    part_curr;    // 当前分区

    struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/

    struct mmc_bkops_info   bkops_info;

    struct device_attribute rpm_attrib;    // rpm属性
    unsigned int        idle_timeout; 
    struct notifier_block        reboot_notify;
    bool issue_long_pon;
    u8 *cached_ext_csd;
};

mmc card类型(mmc_card->type)如下:

#define MMC_TYPE_MMC        0       /* MMC card */
#define MMC_TYPE_SD     1       /* SD card */
#define MMC_TYPE_SDIO       2       /* SDIO card */
#define MMC_TYPE_SD_COMBO   3       /* SD combo (IO+mem) card */

mmc card状态(mmc_card->state)如下:

#define MMC_STATE_PRESENT   (1<<0)      /* present in sysfs */
#define MMC_STATE_READONLY  (1<<1)      /* card is read-only */
#define MMC_STATE_HIGHSPEED (1<<2)      /* card is in high speed mode */
#define MMC_STATE_BLOCKADDR (1<<3)      /* card uses block-addressing */
#define MMC_STATE_HIGHSPEED_DDR (1<<4)      /* card is in high speed mode */
#define MMC_STATE_ULTRAHIGHSPEED (1<<5)     /* card is in ultra high speed mode */
#define MMC_CARD_SDXC       (1<<6)      /* card is SDXC */
#define MMC_CARD_REMOVED    (1<<7)      /* card has been removed */
#define MMC_STATE_HIGHSPEED_200 (1<<8)      /* card is in HS200 mode */
#define MMC_STATE_HIGHSPEED_400 (1<<9)      /* card is in HS400 mode */
#define MMC_STATE_DOING_BKOPS   (1<<10)     /* card is doing BKOPS */
#define MMC_STATE_NEED_BKOPS    (1<<11)     /* card needs to do BKOPS */

mmc card的一些特写标识(mmc_card->quirks)如下:

#define MMC_QUIRK_LENIENT_FN0   (1<<0)      /* allow SDIO FN0 writes outside of the VS CCCR range */
#define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1)    /* use func->cur_blksize */
                        /* for byte mode */
#define MMC_QUIRK_NONSTD_SDIO   (1<<2)      /* non-standard SDIO card attached */
                        /* (missing CIA registers) */
#define MMC_QUIRK_BROKEN_CLK_GATING (1<<3)  /* clock gating the sdio bus will make card fail */
#define MMC_QUIRK_NONSTD_FUNC_IF (1<<4)     /* SDIO card has nonstd function interfaces */
#define MMC_QUIRK_DISABLE_CD    (1<<5)      /* disconnect CD/DAT[3] resistor */
#define MMC_QUIRK_INAND_CMD38   (1<<6)      /* iNAND devices have broken CMD38 */
#define MMC_QUIRK_BLK_NO_CMD23  (1<<7)      /* Avoid CMD23 for regular multiblock */
#define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8)   /* Avoid sending 512 bytes in */
#define MMC_QUIRK_LONG_READ_TIME (1<<9)     /* Data read time > CSD says */
#define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim */
                        /* byte mode */
#define MMC_QUIRK_INAND_DATA_TIMEOUT  (1<<11)   /* For incorrect data timeout */
/* To avoid eMMC device getting broken permanently due to HPI feature */
#define MMC_QUIRK_BROKEN_HPI (1 << 12)
 /* Skip data-timeout advertised by card */
#define MMC_QUIRK_BROKEN_DATA_TIMEOUT   (1<<13)
#define MMC_QUIRK_CACHE_DISABLE (1 << 14)       /* prevent cache enable */

3.host的总线相关

1.struct mmc_bus_ops

host的mmc总线的操作集,由host插入的card决定,不同类型的card对mmc总线的操作有所不同。

struct mmc_bus_ops {
    int (*awake)(struct mmc_host *);   // 唤醒mmc总线上的card
    int (*sleep)(struct mmc_host *);   // 休眠mmc总线上的card
    void (*remove)(struct mmc_host *);   // 从软件上注销mmc总线上的card
    void (*detect)(struct mmc_host *);   // 检测mmc总线上的card是否被移除
    int (*suspend)(struct mmc_host *);   // 对应mmc总线的suspend操作
    int (*resume)(struct mmc_host *);   // 对应mmc总线的resume操作
    int (*power_save)(struct mmc_host *);   // 存储电源状态
    int (*power_restore)(struct mmc_host *);   // 恢复电源状态
    int (*alive)(struct mmc_host *);   // 检测mmc总线上的card的激活状态
    int (*change_bus_speed)(struct mmc_host *, unsigned long *);   // 修改mmc总线的工作时钟
};

2.struct 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) */    // 驱动类型
};

4.请求相关

1.struct mmc_command

mmc core用struct mmc_command来表示一个命令包,其中包括命令码、命令类型、response、response类型

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的类型,具体参考后面的命令类型和response类型
#define mmc_resp_type(cmd)    ((cmd)->flags & (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC|MMC_RSP_BUSY|MMC_RSP_OPCODE))
#define mmc_cmd_type(cmd)    ((cmd)->flags & MMC_CMD_MASK)

    unsigned int        retries;    /* max number of retries */    // 失败时的重复尝试次数
    unsigned int        error;        /* command error */    // 命令的错误码
    unsigned int        cmd_timeout_ms;    /* in milliseconds */    // 命令执行的等待超时事件

    struct mmc_data        *data;        /* data segment associated with cmd */    // 和该命令关联在一起的数据段
    struct mmc_request    *mrq;        /* associated request */    // 该命令关联到哪个request
};

命令类型如下(和协议相关):

#define MMC_CMD_MASK    (3 << 5)        /* non-SPI command type */
#define MMC_CMD_AC    (0 << 5)
#define MMC_CMD_ADTC    (1 << 5)
#define MMC_CMD_BC    (2 << 5)
#define MMC_CMD_BCR    (3 << 5)

response类型如下(和协议相关):

#define MMC_RSP_PRESENT (1 << 0)
#define MMC_RSP_136 (1 << 1)        /* 136 bit response */
#define MMC_RSP_CRC (1 << 2)        /* expect valid crc */
#define MMC_RSP_BUSY    (1 << 3)        /* card may send busy */
#define MMC_RSP_OPCODE  (1 << 4)        /* response contains opcode */

#define MMC_RSP_NONE    (0)
#define MMC_RSP_R1  (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
#define MMC_RSP_R2  (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC)
#define MMC_RSP_R3  (MMC_RSP_PRESENT)
#define MMC_RSP_R4  (MMC_RSP_PRESENT)
#define MMC_RSP_R5  (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R6  (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R7  (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)

2.struct mmc_data

mmc core用struct 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;   // 传输标识
    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 */
};

3.struct 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
};
4.struct mmc_async_req

异步请求的结构体。封装了struct mmc_request请求结构体。

struct mmc_async_req {
    /* active mmc request */
    struct mmc_request  *mrq;    // mmc请求
    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 *);
};

5.bus模块

对应代码drivers/mmc/core/bus.c。
抽象出虚拟mmc bus,实现mmc bus的操作。

1.API总览

1.mmc bus相关

mmc_register_bus & mmc_unregister_bus

用于注册和卸载mmc bus(虚拟mmc总线)到设备驱动模型中。

int mmc_register_bus(void)
{
    return bus_register(&mmc_bus_type);    // 以mmc_bus_type为bus_type注册一条虚拟bus
}

相关节点:/sys/bus/mmc

2.mmc driver相关

mmc_register_driver & mmc_unregister_driver,用于注册和卸载struct mmc_driver *drv到mmc_bus上。mmc_driver就是mmc core抽象出来的card设备driver。

int mmc_register_driver(struct mmc_driver *drv)
{
    drv->drv.bus = &mmc_bus_type;    // 通过设置mmc_driver——》device_driver——》bus_type来设置mmc_driver所属bus为mmc_bus
    return driver_register(&drv->drv);    // 这样就将mmc_driver挂在了mmc_bus上了。
}

相关节点:/sys/bus/mmc/drivers.

3.mmc card相关

mmc_alloc_card & mmc_release_card,用于分配或者释放一个struct mmc_card结构体,创建其与mmc host以及mmc bus之间的关联。

struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
    struct mmc_card *card;

    card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);    // 分配一个mmc_card
    if (!card)
        return ERR_PTR(-ENOMEM);

    card->host = host;        // 关联mmc_card与mmc_host 

    device_initialize(&card->dev);

    card->dev.parent = mmc_classdev(host);    
        // 设置card的device的parent device为mmc_host的classdev,
        // 注册到设备驱动模型中之后,会在/sys/class/mmc_host/mmc0目录下生成相应card的节点,如mmc0:0001
    card->dev.bus = &mmc_bus_type;
        // 设置card的bus为mmc_bus_type,这样,mmc_card注册到设备驱动模型中之后就会挂在mmc_bus下。
        // 会在/sys/bus/mmc/devices/目录下生成相应card的节点,如mmc0:0001
    card->dev.release = mmc_release_card;
    card->dev.type = type;    // 设置device type

    spin_lock_init(&card->bkops_info.bkops_stats.lock);    // 初始化spin_lock
    spin_lock_init(&card->wr_pack_stats.lock);                   // 初始化spin_lock

    return card;
}

参数说明:host——》要分配的card所属的mmc_host,type——》对应的device type。

mmc_add_card & mmc_remove_card,用于注册或者卸载struct mmc_card到mmc_bus上。

/*
 * Register a new MMC card with the driver model.
 */
int mmc_add_card(struct mmc_card *card)
{
    int ret;

/* 以下用于打印card的注册信息 */
    const char *type;
    const char *uhs_bus_speed_mode = "";
        // 设置速度模式的字符串,为了后面打印出card信息
        //......

    if (mmc_host_is_spi(card->host)) {
        pr_info("%s: new %s%s%s card on SPI\n",
            mmc_hostname(card->host),
            mmc_card_highspeed(card) ? "high speed " : "",
            mmc_card_ddr_mode(card) ? "DDR " : "",
            type);
    } else {
        pr_info("%s: new %s%s%s%s%s%s card at address %04x\n",
            mmc_hostname(card->host),
            mmc_card_uhs(card) ? "ultra high speed " :
            (mmc_card_highspeed(card) ? "high speed " : ""),
            (mmc_card_hs400(card) ? "HS400 " : ""),
            (mmc_card_hs200(card) ? "HS200 " : ""),
            mmc_card_ddr_mode(card) ? "DDR " : "",
            uhs_bus_speed_mode, type, card->rca);
    }
        // 在这里会打印出card信息的字符串
        // eg:mmc0: new HS200 MMC card at address 0001

/* 设置card的debug节点 */
#ifdef CONFIG_DEBUG_FS
    mmc_add_card_debugfs(card);
        // 创建card对应的debug节点,对应路径例如:/sys/kernel/debug/mmc0/mmc0:0001
#endif
    mmc_init_context_info(card->host);   // 初始化同步的文本信息

/* 以下使能card device的pm runtime的功能 */
    ret = pm_runtime_set_active(&card->dev);   
    if (ret)
        pr_err("%s: %s: failed setting runtime active: ret: %d\n",
               mmc_hostname(card->host), __func__, ret);
    else if (!mmc_card_sdio(card) && mmc_use_core_runtime_pm(card->host))
        pm_runtime_enable(&card->dev);

/* 添加到设备驱动模型中 */
    ret = device_add(&card->dev);
        // 会创建/sys/bus/mmc/devices/mmc0:0001节点和/sys/class/mmc_host/mmc0/mmc0:0001节点

/* 使能异步device suspend,初始化runtime_pm_timeout属性 */
    device_enable_async_suspend(&card->dev);
    if (mmc_use_core_runtime_pm(card->host) && !mmc_card_sdio(card)) {
        card->rpm_attrib.show = show_rpm_delay;
        card->rpm_attrib.store = store_rpm_delay;
        sysfs_attr_init(&card->rpm_attrib.attr);
        card->rpm_attrib.attr.name = "runtime_pm_timeout";
        card->rpm_attrib.attr.mode = S_IRUGO | S_IWUSR;

        ret = device_create_file(&card->dev, &card->rpm_attrib);
        if (ret)
            pr_err("%s: %s: creating runtime pm sysfs entry: failed: %d\n",
                   mmc_hostname(card->host), __func__, ret);
        /* Default timeout is 10 seconds */
        card->idle_timeout = RUNTIME_SUSPEND_DELAY_MS;
    }

/* 设置mmc card的state标识 */
    mmc_card_set_present(card);
        // 设置card的MMC_STATE_PRESENT状态
        // #define MMC_STATE_PRESENT    (1<<0)      /* present in sysfs */
        // 表示card已经合入到sysfs中了

    return 0;
}

相关节点:
/sys/bus/mmc/devices/mmc0:0001
/sys/class/mmc_host/mmc0/mmc0:0001
/sys/kernel/debug/mmc0/mmc0:0001

2.数据结构

mmc_bus_type

mmc_bus_type代表了mmc虚拟总线。其内容如下:

static struct bus_type mmc_bus_type = {
    .name       = "mmc",                        // 相应会在/sys/bus下生成mmc目录
    .dev_attrs  = mmc_dev_attrs,                  // bus下的device下继承的属性,可以看到/sys/bus/mmc/devices/mmc0:0001/type属性就是这里来的
    .match      = mmc_bus_match,       // 用于mmc bus上device和driver的匹配
    .uevent     = mmc_bus_uevent,
    .probe      = mmc_bus_probe,       // 当match成功的时候,执行的probe操作
    .remove     = mmc_bus_remove,
    .shutdown        = mmc_bus_shutdown,
    .pm     = &mmc_bus_pm_ops,         // 挂在mmc bus上的device的电源管理操作集合
};

/***************************match方法***************************/
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
    return 1;      // 无条件返回1,说明挂载mmc bus上的device(mmc_card)和driver(mmc_driver)是无条件匹配的。
}

/****************************probe方法***************************/
static int mmc_bus_probe(struct device *dev)
{
    struct mmc_driver *drv = to_mmc_driver(dev->driver);
    struct mmc_card *card = mmc_dev_to_card(dev);
    return drv->probe(card);   // 直接调用mmc_driver中的probe操作,对于block.c来说就是mmc_blk_probe
}

通过上述mmc_bus的match方法实现,我们可以知道挂载mmc bus上的mmc_card和mmc_driver是无条件匹配的。

6.host模块

对应代码drivers/mmc/core/host.c,drivers/mmc/core/host.h。
为底层host controller driver实现mmc host的申请以及注册的API等等,以及host相关属性的实现。

1.API总览

1.mmc host分配、注册相关

mmc_alloc_host & mmc_free_host,底层host controller驱动调用,用来分配或者释放一个struct mmc_host结构体,将其于mmc_host_class关联,并且做部分初始化操作。具体为:分配内存空间,初始化其class device(对应/sys/class/mmc0节点),clock gate、锁、工作队列、wakelock、detect工作的初始化,初始化detect成员(也就是检测工作)为mmc_rescan.

参数说明:extra——》mmc_host的私有数据的长度,会和mmc_host结构体一起分配, dev——》底层host controller的device结构体,用于作为mmc_host的device的父设备

/**
 *  mmc_alloc_host - initialise the per-host structure.
 *  @extra: sizeof private data structure
 *  @dev: pointer to host device model structure
 *
 *  Initialise the per-host structure.
 */
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
//    参数说明:extra——》mmc_host的私有数据的长度,会和mmc_host结构体一起分配,
//                     dev——》底层host controller的device结构体,用于作为mmc_host的device的父设备
    int err;
    struct mmc_host *host;

/* 分配内存空间,其中多分配了extra字节作为私有数据 */
    host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
    if (!host)
        return NULL;

    /* scanning will be enabled when we're ready */
/* 因为只是分配了一个mmc_host,host还没有准备好,所以这里禁用rescan,也就是设置mmc_host->rescan_disable 
    host->rescan_disable = 1;   // 在在mmc_start_host中会去使能

/* 为该mmc_host分配一个唯一的id号,设置到host->index */
    idr_preload(GFP_KERNEL);
    spin_lock(&mmc_host_lock);
    err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
    if (err >= 0)
        host->index = err;
    spin_unlock(&mmc_host_lock);
    idr_preload_end();
    if (err < 0)
        goto free;

/* 设置mmc_host name */
    dev_set_name(&host->class_dev, "mmc%d", host->index);   // 以mmc_host的id号构成mmc_host的name,例如mmc0、mmc1

/* 关联mmc_host class_dev并进行初始化 */
/* class_dev就代表了mmc_host 的device结构体,是其在设备驱动模型中的体现 */
    host->parent = dev;                              // 将mmc_host的parent设置成对应host controller节点转化出来的device
    host->class_dev.parent = dev;             
        // 将mmc_host的device(class_dev)的parent设置成对应host controller节点转化出来的device
        // 注册到sysfs之后,会相应生成/sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
        // 其中7824900.sdhci表示qcom的host controller节点转化出来的device
    host->class_dev.class = &mmc_host_class;
         // 将mmc_device(class_dev)的类设置为mmc_host_class
        // 注册到sysfs之后,会相应生成/sys/class/mmc_host/mmc0

    device_initialize(&host->class_dev);   // 初始化mmc_host->class_dev

/* clock gate、锁、工作队列、wakelock、detect工作的初始化 */
    mmc_host_clk_init(host);

    mutex_init(&host->slot.lock);
    host->slot.cd_irq = -EINVAL;

    spin_lock_init(&host->lock);
    init_waitqueue_head(&host->wq);
    host->wlock_name = kasprintf(GFP_KERNEL, "%s_detect", mmc_hostname(host)); // 设置detect_wake_lock的名称为mmc0_detect,在card检测的时候会使用
    wake_lock_init(&host->detect_wake_lock, WAKE_LOCK_SUSPEND, host->wlock_name);   // // 初始化detect_wake_lock
        // 可以通过/sys/kernel/debug/wakeup_sources,相应生成了mmc0_detect和mmc1_detect两个wakelock

    INIT_DELAYED_WORK(&host->detect, mmc_rescan);
        // !!!!这个很重要!!!!初始化detect工作为mmc_rescan,后续调度host->detect来检测是否有card插入时,就会调用到mmc_rescan。
#ifdef CONFIG_PM
    host->pm_notify.notifier_call = mmc_pm_notify;
#endif

/* 一些size的初始化 */
    host->max_segs = 1;   // 初始化最大支持段(由host自己根据硬件进行修改),可以通过/sys/block/mmcblk0/queue/max_segments进行修改
    host->max_seg_size = PAGE_CACHE_SIZE;   // 初始化段大小,(由host自己根据硬件进行修改)

    host->max_req_size = PAGE_CACHE_SIZE;   // 一次MMC请求的最大字节数
    host->max_blk_size = 512;   // 一个块的最大字节数
    host->max_blk_count = PAGE_CACHE_SIZE / 512; // 一次MMC请求的最大块数量

    return host;

free:
    kfree(host);
    return NULL;
}

这边重点强调一个mmc_host->detect=mmc_rescan,当mmc_host的detect work被执行时,就会调用到mmc_rescan中。
相关节点,以mmc_host0为例:
/sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
/sys/class/mmc_host/mmc0

mmc_add_host & mmc_remove_host,底层host controller驱动调用,注册或者卸载mmc_host到设备驱动中,添加到sys类下面,并设置相应的debug目录。然后启动mmc_host。主要工作:使能pm runtime功能,将mmc_host的class_dev添加到设备驱动模型中,在sysfs中生成相应的节点,初始化mmc_host相关的debug目录,设置mmc_host的class_dev的属性,调用mmc_start_host启动host(进入mmc core主模块的部分).

/**
 *  mmc_add_host - initialise host hardware
 *  @host: mmc host
 *
 *  Register the host with the driver model. The host must be
 *  prepared to start servicing requests before this function
 *  completes.
 */
int mmc_add_host(struct mmc_host *host)
{
    int err;
/* 使能mmc host的class_dev的pm runtime功能 */
    err = pm_runtime_set_active(&host->class_dev);
    if (err)
        pr_err("%s: %s: failed setting runtime active: err: %d\n",
               mmc_hostname(host), __func__, err);
    else if (mmc_use_core_runtime_pm(host))
        pm_runtime_enable(&host->class_dev);

/* 通过device_add将mmc_host->class_dev添加到设备驱动模型中,在sys下生成相应节点 */
    err = device_add(&host->class_dev);
        // 通过mmc_alloc_host中关于mmc_host的class_dev的关联,可以生成如下两个节点
        // /sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
        // /sys/class/mmc_host/mmc0

/* 使能mmc host的class_dev的异步suspend的功能 */
    device_enable_async_suspend(&host->class_dev);
    led_trigger_register_simple(dev_name(&host->class_dev), &host->led);

/* 设置mmc_host的debug节点 */
#ifdef CONFIG_DEBUG_FS
    mmc_add_host_debugfs(host);
#endif
        // 对应sys节点为/sys/kernel/debug/mmc0

/* 以下设置mmc host的class_dev的属性 */
    mmc_host_clk_sysfs_init(host);
        // 对应/sys/class/mmc_host/mmc0/clkgate_delay属性

    host->clk_scaling.up_threshold = 35;
    host->clk_scaling.down_threshold = 5;
    host->clk_scaling.polling_delay_ms = 100;
    err = sysfs_create_group(&host->class_dev.kobj, &clk_scaling_attr_grp);
        // 对应/sys/class/mmc_host/mmc0/clk_scaling目录下的四个属性,clk_scaling_attr_grp前面已经说明过了

    err = sysfs_create_group(&host->class_dev.kobj, &dev_attr_grp);
        // 对应/sys/class/mmc_host/mmc0/perf属性,dev_attr_grp前面已经说明过了

/* 调用mmc_start_host,也就调用到了mmc core主模块的启动host部分,在mmc core主模块的时候说明 */
    mmc_start_host(host);
    if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
        register_pm_notifier(&host->pm_notify);

    return 0;
}

注意,最后调用了mmc_start_host来启动host。关于mmc_start_host会在mmc core主模块的部分里面说明。也就是说,关于host的初始化工作,需要在调用mmc_add_host之前就要完成了。

相关节点:
/sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
/sys/class/mmc_host/mmc0
/sys/kernel/debug/mmc0

2.mmc host class相关

mmc_register_host_class & mmc_unregister_host_class,注册或者卸载mmc_host类。

int mmc_register_host_class(void)
{
    return class_register(&mmc_host_class);   // 以mmc_host_class为class创建一个class,关于mmc_host_class在上述数据结构已经说明过了
}

相关节点:/sys/class/mmc_host

3.mmc host属性解析相关

mmc_of_parse,底层host controller驱动调用,解析mmc_host的dtsi节点的部分属性。mmc_of_parse提供了通用的、解析host controller dtsi节点的属性的方法,这就要依赖于dtsi的属性是否符合规范。但是host controller driver并不一定要使用这个,也可以使用自己一套解析的方法。

void mmc_of_parse(struct mmc_host *host)
{
    struct device_node *np;
    u32 bus_width;
    bool explicit_inv_wp, gpio_inv_wp = false;
    enum of_gpio_flags flags;
    int len, ret, gpio;

    if (!host->parent || !host->parent->of_node)
        return;

/* 获取到mmc_host对应的host controller的dts节点 */
    np = host->parent->of_node;
        // host->parent指向了mmc_host的对应host controller的device,获取其of_node就获取到了对应的dtsi节点

/* 以下就是解析属性,并设置到mmc_host的属性标识caps和caps2 中 */
    /* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */
    if (of_property_read_u32(np, "bus-width", &bus_width) < 0) {
        dev_dbg(host->parent,
            "\"bus-width\" property is missing, assuming 1 bit.\n");
        bus_width = 1;
    }

    switch (bus_width) {
    case 8:
        host->caps |= MMC_CAP_8_BIT_DATA;   // "bus-width"——》MMC_CAP_8_BIT_DATA
        /* Hosts capable of 8-bit transfers can also do 4 bits */
    case 4:
        host->caps |= MMC_CAP_4_BIT_DATA;  // "bus-width"——》MMC_CAP_4_BIT_DATA
        break;
    case 1:
        break;
    default:
        dev_err(host->parent,
            "Invalid \"bus-width\" value %ud!\n", bus_width);
    }

    /* f_max is obtained from the optional "max-frequency" property */
    of_property_read_u32(np, "max-frequency", &host->f_max);
       //................后面的代码都类似,直接略过了
}

4.mmc host时钟相关

mmc_host_clk_hold & mmc_host_clk_release,mmc core主模块调用,用于获取host时钟和释放host时钟.

2.数据结构

1.mmc_host_class

mmc_host_class代表了mmc_host这个类。其内容如下:

static struct class mmc_host_class = {
    .name       = "mmc_host",        // 添加到sys文件系统之后,会生成/sys/class/mmc_host这个目录
    .dev_release    = mmc_host_classdev_release,    // 从mmc_host这个class下release掉某个设备之后要做的对应操作
    .pm     = &mmc_host_pm_ops,        // 该class下的host的pm电源管理操作
};

static const struct dev_pm_ops mmc_host_pm_ops = {
    SET_SYSTEM_SLEEP_PM_OPS(mmc_host_suspend, mmc_host_resume)
    SET_RUNTIME_PM_OPS(mmc_host_runtime_suspend, mmc_host_runtime_resume,
               pm_generic_runtime_idle)
};

2.clk_scaling_attr_grp

一些和时钟缩放(clk_scaling)相关的属性组

static struct attribute *clk_scaling_attrs[] = {
    &dev_attr_enable.attr,
    &dev_attr_up_threshold.attr,
    &dev_attr_down_threshold.attr,
    &dev_attr_polling_interval.attr,
    NULL,
};

static struct attribute_group clk_scaling_attr_grp = {
    .name = "clk_scaling",
    .attrs = clk_scaling_attrs,
};

对应/sys/class/mmc_host/mmc0/clk_scaling目录下的属性

3.dev_attr_grp

和设备相关的属性组,只定义了perf属性

static struct attribute *dev_attrs[] = {
#ifdef CONFIG_MMC_PERF_PROFILING
    &dev_attr_perf.attr,
#endif
    NULL,
};
static struct attribute_group dev_attr_grp = {
    .attrs = dev_attrs,
};

对应/sys/class/mmc_host/mmc0/perf属性

7.card模块

1.API总览

1.mmc type card匹配相关

mmc_attach_mmc提供给mmc core主模块使用,用于绑定card到host bus上(也就是card和host的绑定)。通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。主要工作:设置总线模式;选择一个card和host都支持的最低工作电压,对于不同type的card,相应mmc总线上的操作协议也可能有所不同,所以需要设置相应的总线操作集合(mmc_host->bus_ops;初始化card使其进入工作状态(mmc_init_card)为card构造对应的mmc_card并且注册到mmc_bus中(mmc_add_card,具体参考bus模块说明).

int mmc_attach_mmc(struct mmc_host *host)
{
    int err;
    u32 ocr;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    /* Set correct bus mode for MMC before attempting attach */
/* 在尝试匹配之前,先设置正确的总线模式 */
    if (!mmc_host_is_spi(host))
        mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);

/* 获取card的ocr寄存器 */
    err = mmc_send_op_cond(host, 0, &ocr);
        // 发送CMD1命令(MMC_SEND_OP_COND),并且参数为0
        // 这里获取OCR(Operation condition register)32位的OCR包含卡设备支持的工作电压表,存储到ocr变量中
        // 如果Host的IO电压可调整,那调整前需要读取OCR。为了不使卡误进入Inactive State,可以给MMC卡发送不带参数的CMD1,这样可以仅获取OCR寄存器,而不会改变卡的状态。

/* 对于不同type的card,相应mmc总线上的操作协议也可能有所不同 */
/* 所以这里设置mmc_host的总线操作集合,为mmc_ops_unsafe或者mmc_ops,上述已经说明 */
    mmc_attach_bus_ops(host);
        // 设置host->bus_ops,也就是会为host的bus选择一个操作集,对于non-removable的host来说,这里对应应该为mmc_ops_unsafe

/* 为card选择一个HOST和card都支持的最低电压 */
    if (host->ocr_avail_mmc)
        host->ocr_avail = host->ocr_avail_mmc; // 选择mmc的可用ocr值作为host的ocr_avail值

    if (ocr & 0x7F) {
        ocr &= ~0x7F; // 在标准MMC协议中,OCR寄存器的bit6-0位是属于保留位,并不会使用,所以这里对应将其清零
    }
    host->ocr = mmc_select_voltage(host, ocr); // 通过OCR寄存器选择一个HOST和card都支持的最低电压

/* 调用mmc_init_card初始化该mmc type card,这里是核心函数,后续会继续说明 */
    err = mmc_init_card(host, host->ocr, NULL);   // 初始化该mmc type card,并为其分配和初始化一个对应的mmc_card
    if (err)
        goto err;

/* 将分配到的mmc_card注册到mmc_bus中 */
    mmc_release_host(host);   // 先释放掉host,可能是在mmc_add_card中会获取这个host
    err = mmc_add_card(host->card);   
        // 调用到mmc_add_card,将card注册到设备驱动模型中。
        // 这时候该mmc_card就挂在了mmc_bus上,会和mmc_bus上的block这类mmc driver匹配起来。具体再学习mmc card driver的时候再说明。
    mmc_claim_host(host);   // 再次申请host
    if (err)
        goto remove_card;

/* clock scaling相关的东西,这里暂时先不关心 */
    mmc_init_clk_scaling(host);

    register_reboot_notifier(&host->card->reboot_notify);

    return 0;

remove_card:
    mmc_release_host(host);
    mmc_remove_card(host->card);
    mmc_claim_host(host);
    host->card = NULL;
err:
    mmc_detach_bus(host);

    pr_err("%s: error %d whilst initialising MMC card\n",
        mmc_hostname(host), err);

    return err;
}

重点说明
(1)在attach过程中,有一个很重要的函数mmc_init_card;
(2)调用了mmc_add_card之后mmc_card就挂在了mmc_bus上,会和mmc_bus上的block(mmc_driver)匹配起来。相应block(mmc_driver)就会进行probe,驱动card,实现card的实际功能(也就是存储设备的功能)。会对接到块设备子系统中。

2.mmc_init_card

mmc_attach_mmc中的一个核心函数就是mmc_init_card,用于对mmc type card进行实质性的初始化,并为其分配和初始化一个对应的mmc_card。这部分和协议相关,需要先学习一下mmc协议。主要工作:根据协议初始化mmc type card,使其进入相应状态(standby state);为mmc type card构造对应mmc_card并进行设置;从card的csd寄存器以及ext_csd寄存器获取card信息并设置到mmc_card的相应成员中;根据host属性以及一些需求修改ext_csd寄存器的值;设置mmc总线时钟频率以及位宽.

static int mmc_init_card(struct mmc_host *host, u32 ocr,
    struct mmc_card *oldcard)
{
// struct mmc_host *host:该mmc card使用的host
// ocr:表示了host要使用的电压,在mmc_attach_mmc中,已经得到了一个HOST和card都支持的最低电压  struct mmc_card *card;
    int err = 0;
    u32 cid[4];
    u32 rocr;
    u8 *ext_csd = NULL;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    /* Set correct bus mode for MMC before attempting init */
    if (!mmc_host_is_spi(host))
        mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);  // 设置总线模式为开漏模式

/* 根据mmc协议从mmc总线上选中一张card(协议的初始化流程) */
    mmc_go_idle(host);
        // 发送CMD0指令,GO_IDLE_STATE
        // 使mmc card进入idle state。
        // 虽然进入到了Idle State,但是上电复位过程并不一定完成了,这主要靠读取OCR的busy位来判断,而流程归结为下一步。

    /* The extra bit indicates that we support high capacity */
    err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
        // 发送CMD1指令,SEND_OP_COND
        // 这里会设置card的工作电压寄存器OCR,并且通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。
        // 完成之后,mmc card进入ready state。

    /*
     * Fetch CID from card.
     */
    if (mmc_host_is_spi(host))
        err = mmc_send_cid(host, cid);
    else
        err = mmc_all_send_cid(host, cid);
                // 这里会发送CMD2指令,ALL_SEND_CID
                // 广播指令,使card回复对应的CID寄存器的值。在这里就相应获得了CID寄存器的值了,存储在cid中。
                // 完成之后,MMC card会进入Identification State。

    if (oldcard) {
。。。
    } else {
/* 调用mmc_alloc_card分配一个mmc_card并进行部分设置 */
        card = mmc_alloc_card(host, &mmc_type); 
                // 为card配分一个struct mmc_card结构体并进行初始化,在mmc_type中为mmc定义了大量的属性。
                // 具体参考“《mmc core——bus模块说明》——》mmc_alloc_card”
        card->type = MMC_TYPE_MMC; // 设置card的type为MMC_TYPE_MMC
        card->rca = 1;  // 设置card的RCA地址为1
        memcpy(card->raw_cid, cid, sizeof(card->raw_cid));      // 将读到的CID存储到card->raw_cid,也就是原始CID值中
        card->reboot_notify.notifier_call = mmc_reboot_notify;
        host->card = card;      // 将mmc_card和mmc_host 进行关联
    }


/* 设置card RCA地址 */
    if (!mmc_host_is_spi(host)) {
        err = mmc_set_relative_addr(card);
                // 发送CMD3指令,SET_RELATIVE_ADDR
                // 设置该mmc card的关联地址为card->rca,也就是0x0001
                // 完成之后,该MMC card进入standby模式。

        mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
                // 设置总线模式为MMC_BUSMODE_PUSHPULL
    }

/* 从card的csd寄存器以及ext_csd寄存器获取信息并设置到mmc_card的相应成员中 */
    if (!oldcard) {
        /*
         * Fetch CSD from card.
         */
        err = mmc_send_csd(card, card->raw_csd);
                // 发送CMD9指令,MMC_SEND_CSD
                // 要求mmc card发送csd寄存器,存储到card->raw_csd中,也就是原始的csd寄存器的值。
                // 此时mmc card还是处于standby state

        err = mmc_decode_csd(card);
                // 解析raw_csd,获取到各个bit的值并设置到card->csd中的相应成员上

        err = mmc_decode_cid(card);
                // 解析raw_cid,获取到各个bit的值并设置到card->cid中的相应成员上
    }

    /*
     * Select card, as all following commands rely on that.
     */
    if (!mmc_host_is_spi(host)) {
        err = mmc_select_card(card);
                // 发送CMD7指令,SELECT/DESELECT CARD
                // 选择或者断开指定的card
                // 这时卡进入transfer state。后续可以通过各种指令进入到receive-data state或者sending-data state依次来进行数据的传输
    }

    if (!oldcard) {
        err = mmc_get_ext_csd(card, &ext_csd);
                // 发送CMD8指令,SEND_EXT_CSD
                // 这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中
                // 这里会使card进入sending-data state,完成之后又退出到transfer state。

        card->cached_ext_csd = ext_csd;    // 将ext_csd原始值存储到card->cached_ext_csd,表示用来保存ext_csd的一块缓存,可能还没有和card的ext_csd同步
        err = mmc_read_ext_csd(card, ext_csd);  // 解析ext_csd的值,获取到各个bit的值并设置到card->ext_csd中的相应成员上

        if (!(mmc_card_blockaddr(card)) && (rocr & (1<<30)))
            mmc_card_set_blockaddr(card);

        /* Erase size depends on CSD and Extended CSD */
        mmc_set_erase_size(card);  // 设置card的erase_size,扇区里面的擦除字节数,读出来是512K

        if (card->ext_csd.sectors && (rocr & MMC_CARD_SECTOR_ADDR))
            mmc_card_set_blockaddr(card);
    }

/* 根据host属性以及一些需求修改ext_csd寄存器的值 */
    /*
     * If enhanced_area_en is TRUE, host needs to enable ERASE_GRP_DEF
     * bit.  This bit will be lost every time after a reset or power off.
     */
    if (card->ext_csd.enhanced_area_en ||
        (card->ext_csd.rev >= 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) {
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                 EXT_CSD_ERASE_GROUP_DEF, 1,
                 card->ext_csd.generic_cmd6_time);
                // 发送CMD6命令,MMC_SWITCH
                // 用于设置ext_csd寄存器的某些bit
                // 当enhanced_area_en 被设置的时候,host需要去设置ext_csd寄存器中的EXT_CSD_ERASE_GROUP_DEF位为1
    }

    if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) {
        card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,
                 card->ext_csd.part_config,
                 card->ext_csd.part_time);
                // 发送CMD6命令,MMC_SWITCH
                // 用于设置ext_csd寄存器的某些bit
                // 设置ext_csd寄存器中的EXT_CSD_CMD_SET_NORMAL位为EXT_CSD_PART_CONFIG
        card->part_curr = card->ext_csd.part_config &
                  EXT_CSD_PART_CONFIG_ACC_MASK;
    }

    if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) &&
        (card->ext_csd.rev >= 6)) {
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                 EXT_CSD_POWER_OFF_NOTIFICATION,
                 EXT_CSD_POWER_ON,
                 card->ext_csd.generic_cmd6_time);
                 // 发送CMD6命令,MMC_SWITCH
                // 用于设置ext_csd寄存器的某些bit
                // 设置ext_csd寄存器中的EXT_CSD_POWER_OFF_NOTIFICATION位为EXT_CSD_POWER_ON
    }

/* 设置mmc总线时钟频率以及位宽 */
    err = mmc_select_bus_speed(card, ext_csd); // 激活host和card都支持的最大总线速度
        //.........这里过滤掉一些设置ext_csd的代码
    if (!oldcard) {

        if (card->ext_csd.bkops_en) {
            INIT_DELAYED_WORK(&card->bkops_info.dw,
                      mmc_start_idle_time_bkops);
                        // 如果emmc支持bkops的话,就初始化card->bkops_info.dw工作为mmc_start_idle_time_bkops
        }
    }

    return 0;
}

3.mmc_ops_unsafe相关函数
static const struct mmc_bus_ops mmc_ops_unsafe = {
    .awake = mmc_awake,    // 使mmc总线上的mmc type card退出sleep state
    .sleep = mmc_sleep,       // 使mmc总线的mmc type card进入sleep state
    .remove = mmc_remove,   // 释放mmc type card
    .detect = mmc_detect,   // 检测mmc总线的mmc type card是否拔出
    .suspend = mmc_suspend,   // suspend掉mmc总线上的mmc type card,注意不仅仅会使card进入sleep state,还会对clock以及mmc cache进行操作
    .resume = mmc_resume,   // resume上mmc总线上的mmc type card
    .power_restore = mmc_power_restore,   // 恢复mmc总线上的mmc type card的电源状态
    .alive = mmc_alive,   // 检测mmc总线上的mmc type card状态是否正常
    .change_bus_speed = mmc_change_bus_speed,   // 修改mmc总线时钟频率
};


/**********************使mmc总线上的mmc type card退出sleep state************************/
static int mmc_awake(struct mmc_host *host)
{
        //...
    if (card && card->ext_csd.rev >= 3) {   // 判断版本是否大于3
        err = mmc_card_sleepawake(host, 0);   
                // 发送CMD5指令,MMC_SLEEP_AWAKE,参数为0,表示退出sleep state.(如果参数为1就是进入sleep state)
                // 完成之后,该MMC card从sleep state进入standby模式。
    }
        //...
}

/**********************检测mmc总线的mmc type card是否拔出************************/
static void mmc_detect(struct mmc_host *host)
{
    int err;
    mmc_rpm_hold(host, &host->card->dev);
    mmc_claim_host(host);

/* 检测card是否被拔出 */
    err = _mmc_detect_card_removed(host);

    mmc_release_host(host);

    /*
     * if detect fails, the device would be removed anyway;
     * the rpm framework would mark the device state suspended.
     */
/* card并没有被拔出,说明出现异常了,标记card的rpm状态为suspend */
    if (!err)
        mmc_rpm_release(host, &host->card->dev);

/* card确实被拔出,正常释放card */
    if (err) {
        mmc_remove(host);

        mmc_claim_host(host);
        mmc_detach_bus(host);
        mmc_power_off(host);
        mmc_release_host(host);
    }
}

/********************** 修改mmc总线时钟频率************************/
/**
 * mmc_change_bus_speed() - Change MMC card bus frequency at runtime
 * @host: pointer to mmc host structure
 * @freq: pointer to desired frequency to be set
 *
 * Change the MMC card bus frequency at runtime after the card is
 * initialized. Callers are expected to make sure of the card's
 * state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime.
 */
static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq)
{
    int err = 0;
    struct mmc_card *card;

    mmc_claim_host(host);
    /*
     * Assign card pointer after claiming host to avoid race
     * conditions that may arise during removal of the card.
     */
    card = host->card;

    if (!card || !freq) {
        err = -EINVAL;
        goto out;
    }

/* 确定出一个可用频率 */
    if (mmc_card_highspeed(card) || mmc_card_hs200(card)
            || mmc_card_ddr_mode(card)
            || mmc_card_hs400(card)) {
        if (*freq > card->ext_csd.hs_max_dtr)
            *freq = card->ext_csd.hs_max_dtr;
    } else if (*freq > card->csd.max_dtr) {
        *freq = card->csd.max_dtr;
    }

    if (*freq < host->f_min)
        *freq = host->f_min;

/* 根据实际要设置的频率值来设置时钟 */
    if (mmc_card_hs400(card)) {
        err = mmc_set_clock_bus_speed(card, *freq);
        if (err)
            goto out;
    } else {
        mmc_set_clock(host, (unsigned int) (*freq));
    }

  /* 对于hs200来说,修改完频率之后需要执行execute_tuning来选择一个合适的采样点 */
    if (mmc_card_hs200(card) && card->host->ops->execute_tuning) {
        /*
         * We try to probe host driver for tuning for any
         * frequency, it is host driver responsibility to
         * perform actual tuning only when required.
         */
        mmc_host_clk_hold(card->host);
        err = card->host->ops->execute_tuning(card->host,
                MMC_SEND_TUNING_BLOCK_HS200);
        mmc_host_clk_release(card->host);

        if (err) {
            pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n",
                   mmc_hostname(card->host), __func__, err,
                   host->clk_scaling.curr_freq);
            mmc_set_clock(host, host->clk_scaling.curr_freq);   // 采样失败,设置回原来的时钟频率
        }
    }
out:
    mmc_release_host(host);
    return err;
}

4.mmc ops接口说明

mmc_ops提供了部分和mmc type card协议相关操作,这些操作会在mmc.c中mmc的初始化过程中被使用到,这些操作都会发起mmc请求,因此会调用mmc core主模块的mmc请求API.

1.mmc_send_status(典型)

发送CMD13命令,MMC_SEND_STATUS,要求card发送自己当前的状态寄存器.

int mmc_send_status(struct mmc_card *card, u32 *status)
{
    int err;
    struct mmc_command cmd = {0};

    BUG_ON(!card);
    BUG_ON(!card->host);

/* 主要是根据对应命令构造struct mmc_command */
    cmd.opcode = MMC_SEND_STATUS;   // 设置命令操作码opcode,这里设置为MMC_SEND_STATUS,也就是CMD13
    if (!mmc_host_is_spi(card->host))
        cmd.arg = card->rca << 16;   // 设置命令的对应参数,这里设置为card的RCA地址
    cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;   // 设置请求的一些标识,包括命令类型,response类型等等

/* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */
    err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
    if (err)
        return err;

    /* NOTE: callers are required to understand the difference
     * between "native" and SPI format status words!
     */
/* 对response的处理 */
    if (status)
        *status = cmd.resp[0];      // 依照协议,response[0]存储了status,因此这里提取cmd.resp[0]

    return 0;
}

mmc_go_idle、mmc_select_card、mmc_all_send_cid、mmc_set_relative_addr、mmc_send_cxd_native等等的实现方法和其类似。主要差异在于命令的构造区别以及对response的数据的处理。

2.mmc_send_op_cond(特殊)

发送CMD1指令,SEND_OP_COND.在idle状态时,向卡传送Host支持的电压范围,卡回复OCR的值以及上电复位的状态。如果发送的电压参数为0,则卡仅传回OCR的值,并不进行判断。如果发送的电压参数存在,则和卡本身的OCR对比,若不符合,则卡进入Inactive State,符合,则返回OCR寄存器的值。其实和典型的接口类似,但是其特殊之处在于需要通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。

int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
    struct mmc_command cmd = {0};
    int i, err = 0;

    BUG_ON(!host);

/* 主要是根据对应命令构造struct mmc_command */
    cmd.opcode = MMC_SEND_OP_COND;
    cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
    cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;

/* 需要判断status的busy(bit31)来判断上电复位是否完成,如果没有完成的话需要重复发送。 */
    for (i = 100; i; i--) {
        err = mmc_wait_for_cmd(host, &cmd, 0);
        if (err)
            break;

        /* if we're just probing, do a single pass */
        if (ocr == 0)   // ocr为0,说明只是读取ocr寄存器的值,不进行判断
            break;

        /* otherwise wait until reset completes */
        if (mmc_host_is_spi(host)) {
            if (!(cmd.resp[0] & R1_SPI_IDLE))
                break;
        } else {
            if (cmd.resp[0] & MMC_CARD_BUSY)
                break;
                        // 如果发送的电压参数存在,则和卡本身的OCR对比,若不符合,则卡进入Inactive State,符合,则返回OCR寄存器的值。
                        // 同时,需要判断OCR寄存器的busy位来判断上电复位是否完成。
        }

        err = -ETIMEDOUT;

        mmc_delay(10);
    }

    if (rocr && !mmc_host_is_spi(host))
        *rocr = cmd.resp[0];

    return err;
}

3.mmc_send_ext_csd

发送CMD8指令,SEND_EXT_CSD.这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中.这里会使card进入sending-data state,完成之后又退出到transfer state。特殊之处在于涉及到了DATA线上的数据传输,会调用到内部接口mmc_send_cxd_data。

int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd)
{
    return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
            ext_csd, 512);
}

static int
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
        u32 opcode, void *buf, unsigned len)
{
    struct mmc_request mrq = {NULL};
    struct mmc_command cmd = {0};
    struct mmc_data data = {0};
    struct scatterlist sg;
    void *data_buf;
    int is_on_stack;

    is_on_stack = object_is_on_stack(buf);
    if (is_on_stack) {
        /*
         * dma onto stack is unsafe/nonportable, but callers to this
         * routine normally provide temporary on-stack buffers ...
         */
        data_buf = kmalloc(len, GFP_KERNEL);
        if (!data_buf)
            return -ENOMEM;
    } else
        data_buf = buf;

/* 因为涉及到了data线上的数据传输,需要构造mmc_request请求 */
    mrq.cmd = &cmd;   // 设置mmc_request请求中的命令包
    mrq.data = &data;   // 设置mmc_request请求中的数据包

/* 主要是根据对应命令构造struct mmc_command */
    cmd.opcode = opcode;
    cmd.arg = 0;
    cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;

/* 主要是根据对应命令的数据包来构造struct mmc_data */
    data.blksz = len;
    data.blocks = 1;
    data.flags = MMC_DATA_READ;
    data.sg = &sg;
    data.sg_len = 1;
    sg_init_one(&sg, data_buf, len);
    if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
        data.timeout_ns = 0;
        data.timeout_clks = 64;
    } else
        mmc_set_data_timeout(&data, card);

/* 发起mmc请求并且等待mmc_request处理完成 */
    mmc_wait_for_req(host, &mrq);

    if (is_on_stack) {
        memcpy(buf, data_buf, len);
        kfree(data_buf);
    }

    if (cmd.error)
        return cmd.error;
    if (data.error)
        return data.error;

    return 0;
}

4.mmc_switch

发送CMD6命令,MMC_SWITCH.用于设置ext_csd寄存器的某些bit。特殊之处在于:在__mmc_switch中会发起CMD6命令,会导致card进入programming state,因此,在__mmc_switch中必须去获取card的status,直到card退出programming state。这部分就是通过CMD13来实现的。

int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
        unsigned int timeout_ms)
{
    return __mmc_switch(card, set, index, value, timeout_ms, true, false);
}

int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
         unsigned int timeout_ms, bool use_busy_signal,
         bool ignore_timeout)
{
    int err;
    struct mmc_command cmd = {0};
    unsigned long timeout;
    u32 status;

    BUG_ON(!card);
    BUG_ON(!card->host);

/* 主要是根据对应命令构造struct mmc_command */
    cmd.opcode = MMC_SWITCH;
    cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
          (index << 16) |
          (value << 8) |
          set;
    cmd.flags = MMC_CMD_AC;
    if (use_busy_signal)
        cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
    else
        cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
    cmd.cmd_timeout_ms = timeout_ms;
    cmd.ignore_timeout = ignore_timeout;


/* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */
    err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
    if (err)
        return err;

    /* No need to check card status in case of unblocking command */
    if (!use_busy_signal)
        return 0;

/* 调用mmc_send_status发送CMD13获取card status,等待card退出programming state。 */
    timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
    do {
        err = mmc_send_status(card, &status);
        if (err)
            return err;
        if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
            break;
        if (mmc_host_is_spi(card->host))
            break;

        /* Timeout if the device never leaves the program state. */
        if (time_after(jiffies, timeout)) {
            pr_err("%s: Card stuck in programming state! %s\n",
                mmc_hostname(card->host), __func__);
            return -ETIMEDOUT;
        }
    } while (R1_CURRENT_STATE(status) == R1_STATE_PRG);

    if (mmc_host_is_spi(card->host)) {
        if (status & R1_SPI_ILLEGAL_COMMAND)
            return -EBADMSG;
    } else {
        if (status & 0xFDFFA000)
            pr_warning("%s: unexpected status %#x after "
                   "switch", mmc_hostname(card->host), status);
        if (status & R1_SWITCH_ERROR)
            return -EBADMSG;
    }

    return 0;
}

2.数据结构

1.mmc_ops & mmc_ops_unsafe

struct mmc_bus_ops表示mmc host在总线上的操作集合,由host的card 设备来决定,mmc type card、sd type card相应的操作集合是不一样的。mmc_ops和mmc_ops_unsafe则表示mmc type card所属的host对于总线的操作集合。

static const struct mmc_bus_ops mmc_ops = {
    .awake = mmc_awake,  
    .sleep = mmc_sleep,     
    .remove = mmc_remove,   
    .detect = mmc_detect,
    .suspend = NULL,
    .resume = NULL,
    .power_restore = mmc_power_restore,
    .alive = mmc_alive,
    .change_bus_speed = mmc_change_bus_speed,
};

static const struct mmc_bus_ops mmc_ops_unsafe = {
    .awake = mmc_awake,    // 使mmc总线上的mmc type card退出sleep state
    .sleep = mmc_sleep,       // 使mmc总线的mmc type card进入sleep state
    .remove = mmc_remove,   // 释放mmc type card
    .detect = mmc_detect,   // 检测mmc总线的mmc type card是否拔出
    .suspend = mmc_suspend,   // suspend掉mmc总线上的mmc type card,注意不仅仅会使card进入sleep state,还会对clock以及mmc cache进行操作
    .resume = mmc_resume,   // resume上mmc总线上的mmc type card
    .power_restore = mmc_power_restore,   // 恢复mmc总线上的mmc type card的电源状态
    .alive = mmc_alive,   // 检测mmc总线上的mmc type card状态是否正常
    .change_bus_speed = mmc_change_bus_speed,   // 修改mmc总线时钟频率
};

mmc_ops_unsafe和mmc_ops的区别在于是否实现suspend和resume方法。对于card不可移除的host来说,需要使用mmc_ops_unsafe这个mmc_bus_ops来支持suspend和resume。之所以在上述注释中不断说明mmc总线,是为了强调应该和mmc_bus虚拟总线区分开来,这里的mmc总线是物理概念、是和host controller直接相关联的。

2.mmc_type

struct device_type mmc_type中为mmc_card定义了很多属性,可以在sysfs中进行查看

/sys/class/mmc_host/mmc0/mmc0:0001
或者/sys/bus/mmc/devices/mmc0:0001下可以查看到如下属性
block    cid   csd   date   driver   enhanced_area_offset   enhanced_area_size   erase_size   fwrev   hwrev
manfid    name   oemid   power   preferred_erase_size   prv   raw_rpmb_size_mult   rel_sectors
runtime_pm_timeout    serial   subsystem   type   uevent

mmc_type对应实现如下:

static struct device_type mmc_type = {
    .groups = mmc_attr_groups,
};

static const struct attribute_group *mmc_attr_groups[] = {
    &mmc_std_attr_group,
    NULL,
};

static struct attribute_group mmc_std_attr_group = {
    .attrs = mmc_std_attrs,
};

MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
    card->raw_cid[2], card->raw_cid[3]);
MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
    card->raw_csd[2], card->raw_csd[3]);
//...................略过一些
MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult);
MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors);

static struct attribute *mmc_std_attrs[] = {
    &dev_attr_cid.attr,
    &dev_attr_csd.attr,
    &dev_attr_date.attr,
    &dev_attr_erase_size.attr,
    &dev_attr_preferred_erase_size.attr,
    &dev_attr_fwrev.attr,
    &dev_attr_hwrev.attr,
//.....................略过一些
    &dev_attr_rel_sectors.attr,
    NULL,
};

8.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电源管理操作接口

1.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_typemmc_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

2.函数详解

1.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);
2.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_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变化,后续会继续说明
}

3.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;
}

4.总线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);

5.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,那么就会被激活了
oid 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总线操作集
}

6.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发起请求,但是并不会等待,而是直接返回。
注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞。这样,可以利用等待的一部分时间来做其他操作。

/**
 *  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;
}

参考:

https://blog.csdn.net/ooonebook/article/details/55001201

9.wowotech.net

Linux MMC framework(2)_host controller driver

1. 前言

本文是Linux MMC framework的第二篇,将从驱动工程师的角度,介绍MMC host controller driver有关的知识,学习并掌握如何在MMC framework的框架下,编写MMC控制器的驱动程序。同时,通过本篇文章,我们会进一步的理解MMC、SD、SDIO等有关的基础知识。

2. MMC host驱动介绍

MMC的host driver,是用于驱动MMC host控制器的程序,位于“drivers/mmc/host”目录。从大的流程上看,编写一个这样的驱动非常简单,只需要三步:

1)调用mmc_alloc_host,分配一个struct mmc_host类型的变量,用于描述某一个具体的MMC host控制器。

2)根据MMC host控制器的硬件特性,填充struct mmc_host变量的各个字段,例如MMC类型、电压范围、操作函数集等等。

3)调用mmc_add_host接口,将正确填充的MMC host注册到MMC core中。

当然,看着简单,一牵涉到实现细节,还是很麻烦的,后面我们会慢慢分析。

注1:分析MMC host driver的时候,Linux kernel中有大把大把的例子(例如drivers/mmc/host/pxamci.c),大家可尽情参考、学习,不必谦虚(这是学习Linux的最佳方法)。

注2:由于MMC host driver牵涉到具体的硬件controller,分析的过程中需要一些具体的硬件辅助理解,本文将以“X Project”所使用Bubblegum-96平台为例,具体的硬件spec可参考[1]。

3. 主要数据结构

3.1 struct mmc_host

MMC core使用struct mmc_host结构抽象具体的MMC host controller,该结构的定义位于“include/linux/mmc/host.h”中,它既可以用来描述MMC控制器所具有的特性、能力(host driver关心的内容),也保存了host driver运行过程中的一些状态、参数(MMC core关心的内容)。需要host driver关心的部分的具体的介绍如下:

parent,一个struct device类型的指针,指向该MMC host的父设备,一般是注册该host的那个platform设备;

class_dev,一个struct device类型的变量,是该MMC host在设备模型中作为一个“设备”的体现。当然,人如其名,该设备从属于某一个class(mmc_host_class);

ops,一个struct mmc_host_ops类型的指针,保存了该MMC host有关的操作函数集,具体可参考3.2小节的介绍;

pwrseq,一个struct mmc_pwrseq类型的指针,保存了该MMC host电源管理有关的操作函数集,具体可参考3.2小节的介绍;

f_min、f_max、f_init,该MMC host支持的时钟频率范围,最小频率、最大频率以及初始频率;

ocr_avail,该MMC host可支持的操作电压范围(具体可参考include/linux/mmc/host.h中MMC_VDD_开头的定义);
注3:OCR(Operating Conditions Register)是MMC/SD/SDIO卡的一个32-bit的寄存器,其中有些bit指明了该卡的操作电压。MMC host在驱动这些卡的时候,需要和Host自身所支持的电压范围匹配之后,才能正常操作,这就是ocr_avail的存在意义。

ocr_avail_sdio、ocr_avail_sd、ocr_avail_mmc,如果MMC host针对SDIO、SD、MMC等不同类型的卡,所支持的电压范围不同的话,需要通过这几个字段特别指定。否则,不需要赋值(初始化为0);

pm_notify,一个struct notifier_block类型的变量,用于支持power management有关的notify实现;

max_current_330、max_current_300、max_current_180,当工作电压分别是3.3v、3v以及1.8v的时候,所支持的最大操作电流(如果MMC host没有特别的限制,可以不赋值);

caps、caps2,指示该MMC host所支持的功能特性,具体可参考3.4小节的介绍;

pm_caps,mmc_pm_flag_t类型的变量,指示该MMC host所支持的电源管理特性;

max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count、max_busy_timeout,和块设备(如MMC、SD、eMMC等)有关的参数,在古老的磁盘时代,这些参数比较重要。对基于MMC技术的块设备来说,硬件的性能大大提升,这些参数就没有太大的意义了。具体可参考5.2章节有关MMC数据传输的介绍;

lock,一个spin lock,是MMC host driver的私有变量,可用于保护host driver的临界资源;

ios,一个struct mmc_ios类型的变量,用于保存MMC bus的当前配置,具体可参考3.5小节的介绍;

supply,一个struct mmc_supply类型的变量,用于描述MMC系统中的供电信息,具体可参考3.6小节的介绍;

……

private,一个0长度的数组,可以在mmc_alloc_host时指定长度,由host controller driver自行支配。

3.2 struct mmc_host_ops

struct mmc_host_ops抽象并集合了MMC host controller所有的操作函数集,包括:

1)数据传输有关的函数

/*
* It is optional for the host to implement pre_req and post_req in
* order to support double buffering of requests (prepare one
* request while another request is active).
* pre_req() must always be followed by a post_req().
* To undo a call made to pre_req(), call post_req() with
* a nonzero err condition.
*/
void (*post_req)(struct mmc_host *host, struct mmc_request *req,
int err);
void (*pre_req)(struct mmc_host *host, struct mmc_request *req,
bool is_first_req);
void (*request)(struct mmc_host *host, struct mmc_request *req);

pre_req和post_req是非必需的,host driver可以利用它们实现诸如双buffer之类的高级功能。

数据传输的主题是struct mmc_request类型的指针,具体可参考3.7小节的介绍。

2)总线参数的配置以及卡状态的获取函数

/*
* Avoid calling these three functions too often or in a “fast path”,
* since underlaying controller might implement them in an expensive
* and/or slow way.
*
* Also note that these functions might sleep, so don’t call them
* in the atomic contexts!
*
* Return values for the get_ro callback should be:
* 0 for a read/write card
* 1 for a read-only card
* -ENOSYS when not supported (equal to NULL callback)
* or a negative errno value when something bad happened
*
* Return values for the get_cd callback should be:
* 0 for a absent card
* 1 for a present card
* -ENOSYS when not supported (equal to NULL callback)
* or a negative errno value when something bad happened
*/
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int (*get_ro)(struct mmc_host *host);
int (*get_cd)(struct mmc_host *host);

set_ios用于设置bus的参数(ios,可参考3.5小节的介绍);get_ro可获取card的读写状态(具体可参考上面的注释);get_cd用于检测卡的存在状态。

注4:注释中特别说明了,这几个函数可以sleep,耗时较长,没事别乱用。

3)其它一些非主流函数,都是optional的,用到的时候再去细看即可。

3.3 struct mmc_pwrseq

MMC framework的power sequence是一个比较有意思的功能,它提供一个名称为struct mmc_pwrseq_ops的操作函数集,集合了power on、power off等操作函数,用于控制MMC系统的供电,如下:

struct mmc_pwrseq_ops {
void (*pre_power_on)(struct mmc_host *host);
void (*post_power_on)(struct mmc_host *host);
void (*power_off)(struct mmc_host *host);
void (*free)(struct mmc_host *host);
};

struct mmc_pwrseq {
const struct mmc_pwrseq_ops *ops;
};

与此同时,MMC core提供了一个通用的pwrseq的管理模块(drivers/mmc/core/pwrseq.c),以及一些简单的pwrseq策略(如drivers/mmc/core/pwrseq_simple.c、drivers/mmc/core/pwrseq_emmc.c),最终的目的是,通过一些简单的dts配置,即可正确配置MMC的供电,例如:

/* arch/arm/boot/dts/omap3-igep0020.dts /
mmc2_pwrseq: mmc2_pwrseq {
compatible = “mmc-pwrseq-simple”;
reset-gpios = <&gpio5 11 GPIO_ACTIVE_LOW>, /
gpio_139 - RESET_N_W /
<&gpio5 10 GPIO_ACTIVE_LOW>; /
gpio_138 - WIFI_PDN */
};

/* arch/arm/boot/dts/rk3288-veyron.dtsi */
emmc_pwrseq: emmc-pwrseq {
compatible = “mmc-pwrseq-emmc”;
pinctrl-0 = <&emmc_reset>;
pinctrl-names = “default”;
reset-gpios = <&gpio2 9 GPIO_ACTIVE_HIGH>;
};

具体的细节,在需要的时候,阅读代码即可,这里不再赘述。

3.4 Host capabilities

通过的caps和caps2两个字段,MMC host driver可以告诉MMC core控制器的一些特性、能力,包括(具体可参考include/linux/mmc/host.h中相关的宏定义,更为细致的使用场景和指南,需要结合实际的硬件,具体分析):

MMC_CAP_4_BIT_DATA,支持4-bit的总线传输;
MMC_CAP_MMC_HIGHSPEED,支持“高速MMC时序”;
MMC_CAP_SD_HIGHSPEED,支持“高速SD时序”;
MMC_CAP_SDIO_IRQ,可以产生SDIO有关的中断;
MMC_CAP_SPI,仅仅支持SPI协议(可参考“drivers/mmc/host/mmc_spi.c”中有关的实现);
MMC_CAP_NEEDS_POLL,表明需要不停的查询卡的插入状态(如果需要支持热拔插的卡,则需要设置该feature);
MMC_CAP_8_BIT_DATA,支持8-bit的总线传输;
MMC_CAP_AGGRESSIVE_PM,支持比较积极的电源管理策略(kernel的注释为“Suspend (e)MMC/SD at idle”);
MMC_CAP_NONREMOVABLE,表明该MMC控制器所连接的卡是不可拆卸的,例如eMMC;
MMC_CAP_WAIT_WHILE_BUSY,表面Host controller在向卡发送命令时,如果卡处于busy状态,是否等待。如果不等待,MMC core在一些流程中(如查询busy状态),需要额外做一些处理;
MMC_CAP_ERASE,表明该MMC控制器允许执行擦除命令;
MMC_CAP_1_8V_DDR,支持工作电压为1.8v的DDR(Double Data Rate)mode[3];
MMC_CAP_1_2V_DDR,支持工作电压为1.2v的DDR(Double Data Rate)mode[3];
MMC_CAP_POWER_OFF_CARD,可以在启动之后,关闭卡的供电(一般只有在SDIO的应用场景中才可能用到,因为SDIO所连接的设备可能是一个独立的设备);
MMC_CAP_BUS_WIDTH_TEST,支持通过CMD14和CMD19进行总线宽度的测试,以便选择一个合适的总线宽度进行通信;
MMC_CAP_UHS_SDR12、MMC_CAP_UHS_SDR25、MMC_CAP_UHS_SDR50、MMC_CAP_UHS_SDR104,它们是SD3.0的速率模式,分别表示支持25MHz、50MHz、100MHz和208MHz的SDR(Single Data Rate,相对[3]中的DDR)模式;
MMC_CAP_UHS_DDR50,它也是SD3.0的速率模式,表示支持50MHz的DDR(Double Data Rate[3])模式;
MMC_CAP_DRIVER_TYPE_A、MMC_CAP_DRIVER_TYPE_C、MMC_CAP_DRIVER_TYPE_D,分别表示支持A/C/D类型的driver strength(驱动能力,具体可参考相应的spec);
MMC_CAP_CMD23,表示该controller支持multiblock transfers(通过CMD23);
MMC_CAP_HW_RESET,支持硬件reset;

MMC_CAP2_FULL_PWR_CYCLE,表示该controller支持从power off到power on的完整的power cycle;
MMC_CAP2_HS200_1_8V_SDR、MMC_CAP2_HS200_1_2V_SDR,HS200是eMMC5.0支持的一种速率模式,200是200MHz的缩写,分别表示支持1.8v和1.2v的SDR模式;
MMC_CAP2_HS200,表示同时支持MMC_CAP2_HS200_1_8V_SDR和MMC_CAP2_HS200_1_2V_SDR;
MMC_CAP2_HC_ERASE_SZ,支持High-capacity erase size;
MMC_CAP2_CD_ACTIVE_HIGH,CD(Card-detect)信号高有效;
MMC_CAP2_RO_ACTIVE_HIGH,RO(Write-protect)信号高有效;
MMC_CAP2_PACKED_RD、MMC_CAP2_PACKED_WR,允许packed read、packed write;
MMC_CAP2_PACKED_CMD,同时支持DMMC_CAP2_PACKED_RD和MMC_CAP2_PACKED_WR;
MMC_CAP2_NO_PRESCAN_POWERUP,在scan之前不要上电;
MMC_CAP2_HS400_1_8V、MMC_CAP2_HS400_1_2V,HS400是eMMC5.0支持的一种速率模式,400是400MHz的缩写,分别表示支持1.8v和1.2v的HS400模式;
MMC_CAP2_HS400,同时支持MMC_CAP2_HS400_1_8V和MMC_CAP2_HS400_1_2V;
MMC_CAP2_HSX00_1_2V,同时支持MMC_CAP2_HS200_1_2V_SDR和MMC_CAP2_HS400_1_2V;
MMC_CAP2_SDIO_IRQ_NOTHREAD,SDIO的IRQ的处理函数,不能在线程里面执行;
MMC_CAP2_NO_WRITE_PROTECT,没有物理的RO管脚,意味着任何时候都是可以读写的;
MMC_CAP2_NO_SDIO,在初始化的时候,不会发送SDIO相关的命令(也就是说不支持SDIO模式)。

3.5 struct mmc_ios

struct mmc_ios中保存了MMC总线当前的配置情况,包括如下信息:

1)clock,时钟频率。

2)vdd,卡的供电电压,通过“1 << vdd”可以得到MMC_VDD_x_x(具体可参考include/linux/mmc/host.h中MMC_VDD_开头的定义),进而得到电压信息。

3)bus_mode,两种信号模式,open-drain(MMC_BUSMODE_OPENDRAIN)和push-pull(MMC_BUSMODE_PUSHPULL),对应不同的高低电平(可参考相应的spec,例如[2])。

4)chip_select,只针对SPI模式,指定片选信号的有效模式,包括没有片选信号(MMC_CS_DONTCARE)、高电平有效(MMC_CS_HIGH)、低电平有效(MMC_CS_LOW)。

5)power_mode,当前的电源状态,包括MMC_POWER_OFF、MMC_POWER_UP、MMC_POWER_ON和MMC_POWER_UNDEFINED。

6)bus_width,总线的宽度,包括1-bit(MMC_BUS_WIDTH_1)、4-bit(MMC_BUS_WIDTH_4)和8-bit(MMC_BUS_WIDTH_8)。

7)timing,符合哪一种总线时序(大多对应某一类MMC规范),包括:

MMC_TIMING_LEGACY,旧的、不再使用的规范;
MMC_TIMING_MMC_HS,High speed MMC规范(具体可参考相应的spec,这里不再详细介绍,下同);
MMC_TIMING_SD_HS,High speed SD;
MMC_TIMING_UHS_SDR12;
MMC_TIMING_UHS_SDR25
MMC_TIMING_UHS_SDR50
MMC_TIMING_UHS_SDR104
MMC_TIMING_UHS_DDR50
MMC_TIMING_MMC_DDR52
MMC_TIMING_MMC_HS200
MMC_TIMING_MMC_HS400

8)signal_voltage,总线信号使用哪一种电压,3.3v(MMC_SIGNAL_VOLTAGE_330)、1.8v(MMC_SIGNAL_VOLTAGE_180)或者1.2v(MMC_SIGNAL_VOLTAGE_120)。

9)drv_type,驱动能力,包括:

MMC_SET_DRIVER_TYPE_B
MMC_SET_DRIVER_TYPE_A
MMC_SET_DRIVER_TYPE_C
MMC_SET_DRIVER_TYPE_D

3.6 struct mmc_supply

struct mmc_supply中保存了两个struct regulator指针(如下),用于控制MMC子系统有关的供电(vmmc和vqmmc)。

struct mmc_supply {
struct regulator vmmc; / Card power supply */
struct regulator vqmmc; / Optional Vccq supply */
};

关于vmmc和vqmmc,说明如下:

vmmc是卡的供电电压,一般连接到卡的VDD管脚上。而vqmmc则用于上拉信号线(CMD、CLK和DATA[6])。

通常情况下vqmmc使用和vmmc相同的regulator,同时供电即可。

后来,一些高速卡(例如UHS SD)要求在高速模式下,vmmc为3.3v,vqmmc为1.8v,这就需要两个不同的regulator独立控制。

3.7 struct mmc_request

struct mmc_request封装了一次传输请求,定义如下:

/* include/linux/mmc/core.h */

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

要理解这个数据结构,需要先了解MMC的总线协议(bus protocol),这里以eMMC[2]为例进行简单的介绍(更为详细的解释,可参考相应的spec以及本站的文章–“eMMC 原理 4 :总线协议[7]”)。

3.7.1 MMC bus protocol

在eMMC的spec中,称总线协议为“message-based MultiMediaCard bus protocol”,这里的message由三种信标(token)组成:

Command,用于启动(或者停止)一次传输,由Host通过CMD line向Card发送;

Response,用于应答上一次的Command,由Card通过CMD line想Host发送;

Data,传输数据,由Host(或者Card)通过DATA lines向Card(或者Host发送)。

以上token除了Command之外,剩余的两个(Response和Data)都是非必需的,也就是说,一次传输可以是:不需要应答、不需要数据传输的Command;需要应答、不需要数据传输的Command;不需要应答、需要数据传输的Command;不需要应答、不需要数据传输的Command。

Command token的格式只有一种(具体可参考[2]中“Command token format”有关的表述),长度为48bits,包括Start bit(0)、Transmitter bit(1, host command)、Content(38bits)、CRC checksum(7bits)、Stop bit(1)。

根据内容的不同,Response token的格式有5中,分别简称为R1/R3/R4/R5/R2,其中R1/R3/R4/R5的长度为48bits,R2为136bits(具体可参考[2]中“Response token format”有关的表述)。

对于包含了Data token的Command,有两种类型:

Sequential commands,发送Start command之后,数据以stream的形式传输,直到Stop command为止。这种方式只支持1-bit总线模式,主要为了兼容旧的技术,一般不使用;

Block-oriented commands,发送Start command之后,数据以block的形式传输(每个block的大小是固定的,且都由CRC保护)。

最后,以block为单位的传输,大体上也分为两类:

在传输开始的时候(Start command),没有指定需要传输的block数目,直到发送Stop command为止。这种方法在spec中称作“Open-ended”;

在传输开始的时候(Start command),指定需要传输的block数据,当达到数据之后,Card会自动停止传输,这样可以省略Stop command。这种方法在spec中称作pre-defined block count。

3.7.2 struct mmc_request

了解MMC bus protocol之后,再来看一次MMC传输请求(struct mmc_request )所包含的内容:

cmd,Start command,为struct mmc_command类型(具体请参考3.7.3中的介绍)的指针,在一次传输的过程中是必须的;

data,传输的数据,为struct mmc_data类型(具体请参考3.7.4中的介绍)的指针,不是必须要的;

stop、sbc,如果需要进行数据传输,根据数据传输的方式(参考3.7.1中的介绍):如果是“Open-ended”,则需要stop命令(stop指针,或者data->stop指针);如果是pre-defined block count,则需要sbc指针(用于发送SET_BLOCK_COUNT–CMD23命令);

completion,一个struct completion变量,用于等待此次传输完成,host controller driver可以根据需要使用;

done,传输完成时的回调,用于通知传输请求的发起者;

host,对应的mmc host controller指针。

3.7.3 struct mmc_command

struct mmc_command结构抽象了一个MMC command,包括如下内容:

/* include/linux/mmc/core.h */

opcode,Command的操作码,用于标识该命令是哪一个命令,具体可参考相应的spec(例如[2]);

arg,一个Command可能会携带参数,具体可参考相应的spec(例如[2]);

resp[4],Command发出后,如果需要应答,结果保存在resp数组中,该数组是32-bit的,因此最多可以保存128bits的应答;

flags,是一个bitmap,保存该命令所期望的应答类型,例如:
MMC_RSP_PRESENT(1 << 0),是否需要应答,如果该bit为0,则表示该命令不需要应答,否则,需要应答;
MMC_RSP_136(1 << 1),如果为1,表示需要136bits的应答;
MMC_RSP_CRC(1 << 2),如果为1,表示需要对该命令进行CRC校验;
等等,具体可参考include/linux/mmc/core.h中“MMC_RSP_”开头的定义;

retries,如果命令发送出错,可以重新发送,该字段向host driver指明最多可重发的次数;

error,如果最终还是出错,host driver需要通过该字段返回错误的原因,kernel定义了一些标准错误,例如ETIMEDOUT、EILSEQ、EINVAL、ENOMEDIUM等,具体含义可参考include/linux/mmc/core.h中注释;

busy_timeout,如果card具有busy检测的功能,该字段指定等待card返回busy状态的超时时间,单位为ms;

data,和该命令对应的struct mmc_data指针;

mrq,和该命令对应的struct mmc_request指针。

3.7.4 struct mmc_data

struct mmc_data结构包含了数据传输有关的内容:

/* include/linux/mmc/core.h */

timeout_ns、timeout_clks,这一笔数据传输的超时时间(单位分别为ns和clks),如果超过这个时间host driver还无法成功发送,则要将状态返回给mmc core;

blksz、blocks,该笔数据包含多少block(blocks),每个block的size多大(blksz),这两个值不会大于struct mmc_host中上报的max_blk_size和max_blk_count;

error,如果数据传输出错,错误值保存在该字段,具体意义和struct mmc_command中的一致;

flags,一个bitmap,指明该笔传说的方向(MMC_DATA_WRITE或者MMC_DATA_READ);

sg,一个struct scatterlist类型的数组,保存了需要传输的数据(可以通过dma_相关的接口,获得相应的物理地址);
sg_len,sg数组的size;
sg_count,通过sg map出来的实际的entry的个数(可能由于物理地址的连续、IOMMU的干涉等,map出来的entry的个数,可能会小于sg的size);
注5:有关scatterlist的介绍,可参考本站另外的文章(TODO)。有关struct mmc_data的使用场景,可参考5.2小节的介绍;

host_cookie,host driver的私有数据,怎么用由host driver自行决定。

4. 主要API

第3章花了很大的篇幅介绍了用于抽象MMC host的数据结构----struct mmc_host,并详细说明了和mmc_host相关的mmc request、mmc command、mmc data等结构。基于这些知识,本章将介绍MMC core提供的和struct mmc_host有关的操作函数,主要包括如下几类。

4.1 向MMC host controller driver提供的用于操作struct mmc_host的API

包括:

struct mmc_host *mmc_alloc_host(int extra, struct device *); int mmc_add_host(struct mmc_host *); void mmc_remove_host(struct mmc_host *); void mmc_free_host(struct mmc_host *); int mmc_of_parse(struct mmc_host *host); static inline void *mmc_priv(struct mmc_host *host) { return (void *)host->private; }

mmc_alloc_host,动态分配一个struct mmc_host变量。extra是私有数据的大小,可通过host->private指针访问(也可通过mmc_priv接口直接获取)。mmc_free_host执行相反动作。

mmc_add_host,将已初始化好的host变量注册到kernel中。mmc_remove_host执行相反动作。

为了方便,host controller driver可以在dts中定义host的各种特性,然后在代码中调用mmc_of_parse解析并填充到struct mmc_host变量中。dts属性关键字可参考mmc_of_parse的source code(drivers/mmc/core/host.c),并结合第三章的内容自行理解。

int mmc_power_save_host(struct mmc_host *host); int mmc_power_restore_host(struct mmc_host *host);

从mmc host的角度进行电源管理,进入/退出power save状态。

void mmc_detect_change(struct mmc_host *, unsigned long delay);

当host driver检测到总线上的设备有变动的话(例如卡的插入和拔出等),需要调用这个接口,让MMC core帮忙做后续的工作,例如检测新插入的卡到底是个什么东东……

另外,可以通过delay参数告诉MMC core延时多久(单位为jiffies)开始处理,通常可以用来对卡的拔插进行去抖动。

void mmc_request_done(struct mmc_host *, struct mmc_request *);

当host driver处理完成一个mmc request之后,需要调用该函数通知MMC core,MMC core会进行一些善后的操作,例如校验结果、调用mmc request的.done回调等等。

static inline void mmc_signal_sdio_irq(struct mmc_host *host) void sdio_run_irqs(struct mmc_host *host);

对于SDIO类型的总线,这两个函数用于操作SDIO irqs,后面用到的时候再分析。

int mmc_regulator_get_ocrmask(struct regulator *supply); int mmc_regulator_set_ocr(struct mmc_host *mmc, struct regulator *supply, unsigned short vdd_bit); int mmc_regulator_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios); int mmc_regulator_get_supply(struct mmc_host *mmc);

regulator有关的辅助函数:

mmc_regulator_get_ocrmask可根据传入的regulator指针,获取该regulator支持的所有电压值,并以此推导出对应的ocr mask(可参考3.1中的介绍)。

mmc_regulator_set_ocr用于设置host controller为某一个操作电压(vdd_bit),该接口会调用regulator framework的API,进行具体的电压切换。

mmc_regulator_set_vqmmc可根据struct mmc_ios信息,自行调用regulator framework的接口,设置vqmmc的电压。

最后,mmc_regulator_get_supply可以帮忙从dts的vmmc、vqmmc属性值中,解析出对应的regulator指针,以便后面使用。

4.2 用于判断MMC host controller所具备的能力的API

比较简单,可结合第3章的介绍理解:

#define mmc_host_is_spi(host) ((host)->caps & MMC_CAP_SPI) static inline int mmc_card_is_removable(struct mmc_host *host) static inline int mmc_card_keep_power(struct mmc_host *host) static inline int mmc_card_wake_sdio_irq(struct mmc_host *host) static inline int mmc_host_cmd23(struct mmc_host *host) static inline int mmc_boot_partition_access(struct mmc_host *host) static inline int mmc_host_uhs(struct mmc_host *host) static inline int mmc_host_packed_wr(struct mmc_host *host) static inline int mmc_card_hs(struct mmc_card *card) static inline int mmc_card_uhs(struct mmc_card *card) static inline bool mmc_card_hs200(struct mmc_card *card) static inline bool mmc_card_ddr52(struct mmc_card *card) static inline bool mmc_card_hs400(struct mmc_card *card) static inline void mmc_retune_needed(struct mmc_host *host) static inline void mmc_retune_recheck(struct mmc_host *host)

5. MMC host驱动的编写步骤

经过上面章节的描述,相信大家对MMC controller driver有了比较深的理解,接下来驱动的编写就是一件水到渠成的事情了。这里简要描述一下驱动编写步骤,也顺便为本文做一个总结。

5.1 struct mmc_host的填充和注册

编写MMC host驱动的所有工作,都是围绕struct mmc_host结构展开的。在对应的platform driver的probe函数中,通过mmc_alloc_host分配一个mmc host后,我们需要根据controller的实际情况,填充对应的字段。

mmc host中大部分和controller能力/特性有关的字段,可以通过dts配置(然后在代码中调用mmc_of_parse自动解析并填充),举例如下(注意其中红色的部分,都是MMC framework的标准字段):

/* arch/arm/boot/dts/exynos5420-peach-pit.dts */

&mmc_1 {
status = “okay”;
num-slots = <1>;
non-removable;
cap-sdio-irq;
keep-power-in-suspend;
clock-frequency = <400000000>;
samsung,dw-mshc-ciu-div = <1>;
samsung,dw-mshc-sdr-timing = <0 1>;
samsung,dw-mshc-ddr-timing = <0 2>;
pinctrl-names = “default”;
pinctrl-0 = <&sd1_clk>, <&sd1_cmd>, <&sd1_int>, <&sd1_bus1>,
<&sd1_bus4>, <&sd1_bus8>, <&wifi_en>;
bus-width = <4>;
cap-sd-highspeed;
mmc-pwrseq = <&mmc1_pwrseq>;
vqmmc-supply = <&buck10_reg>;
};

5.2 数据传输的实现

填充struct mmc_host变量的过程中,工作量最大的,就是对struct mmc_host_ops的实现(毫无疑问!所有MMC host的操作逻辑都封在这里呢!!)。这里简单介绍一下相关的概念,具体的驱动编写步骤,后面文章会结合“X Project”详细描述。

5.2.1 Sectors(扇区)、Blocks(块)以及Segments(段)的理解

我们在3.1小节介绍struct mmc_host的时候,提到了max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count等参数。这和磁盘设备(块设备)中Sectors、Blocks、Segments等概念有关,下面简单介绍一下:

1)Sectors

Sectors是存储设备访问的基本单位。

对磁盘、NAND等块设备来说,Sector的size是固定的,例如512、2048等。

对存储类的MMC设备来说,按理说也应有固定size的sector。但因为有MMC协议的封装,host驱动以及上面的块设备驱动,不需要关注物理的size。它们需要关注的就是bus上的数据传输单位(具体可参考MMC protocol的介绍[7])。

最后,对那些非存储类的MMC设备来说,完全没有sector的概念了。

2) Blocks

Blocks是数据传输的基本单位,是VFS(虚拟文件系统)抽象出来的概念,是纯软件的概念,和硬件无关。它必须是2的整数倍、不能大于Sectors的单位、不能大于page的长度,一般为512B、2048B或者4096B。

对MMC设备来说,由于协议的封装,淡化了Sector的概念,或者说,MMC设备可以支持一定范围内的任意的Block size。Block size的范围,由两个因素决定:
a)host controller的能力,这反映在struct mmc_host结构的max_blk_size字段上。
b)卡的能力,这可以通过MMC command从卡的CSD(Card-Specific Data)寄存器中读出。

3)Segments[8]

块设备的数据传输,本质上是设备上相邻扇区与内存之间的数据传输。通常情况下,为了提升性能,数据传输通过DMA方式。

在磁盘控制器的旧时代,DMA操作都比较简单,每次传输,数据在内存中必须是连续的。现在则不同,很多SOC都支持“分散/聚合”(scatter-gather)DMA操作,这种操作模式下,数据传输可以在多个非连续的内存区域中进行。

对于每个“分散/聚合”DMA操作,块设备驱动需要向控制器发送:
a)初始扇区号和传输的总共扇区数
b)内存区域的描述链表,每个描述都包含一个地址和长度。不同的描述之间,可以在物理上连续,也可以不连续。

控制器来管理整个数据传输,例如:在读操作中,控制器从块设备相邻的扇区上读取数据,然后将数据分散存储在内存的不同区域。

这里的每个内存区域描述(物理连续的一段内存,可以是一个page,也可以是page的一部分),就称作Segment。一个Segment包含多个相邻扇区。

最后,利用“分散/聚合”的DMA操作,一次数据传输可以会涉及多个segments。

理解了Segment的概念之后,max_seg_size和max_segs两个字段就好理解了:

虽然控制器支持“分散/聚合”的DMA操作,但物理硬件总有限制,例如最大的Segment size(也即一个内存描述的最大长度),最多支持的segment个数(max_segs)等。

5.2.2 struct mmc_data中的sg

我们在3.7.4小节介绍struct mmc_data时,提到了scatterlist的概念。结合上面Segment的解释,就很好理解了:

MMC core提交给MMC host driver的数据传输请求,是一个struct scatterlist链表(也即内存区域的描述链表),也可以理解为是一个个的Segment(Segment的个数保存在sg_len变量中了)。

每个Segment是一段物理地址连续的内存区域,所有的Segments对应了MMC设备中连续的Sector(或者说Block,初始扇区号和传输的总共扇区数已经在之前的MMC command中指定了。

host driver在接收到这样的数据传输请求之后,需要调用dma_map_sg将这些Segment映射出来(获得相应的物理地址),以便交给MMC controller传输。

当然,相邻两个Segment的物理地址可能是连续(或者其它原因),map的时候可能会将两个Segment合成一个。因此可供MMC controller传输的内存片可能少于sg_len(具体要看dma_map_sg的返回值,可将结果保存在sg_count中)。

最后,如何实施传输,则要看具体的MMC controller的硬件实现(可能涉及DMA操作),后面文章再详细介绍。

扫卡流程(或许错误):

sdhci_s3c_probe->sdhci_add_host->mmc_start->_mmc_detect_change

10.函数调用过程

1.rk3308b函数调用过程

dw_mci_init
dw_mci_probe
	dw_mci_init_slot
		mmc_alloc_host
			INIT_DELAYED_WORK(&host->detect, mmc_rescan);//这里绑定mmc_rescan
		mmc_add_host //(rk将host导出了primary_sdio_host)
			mmc_start_host				
				mmc_detect_change
					_mmc_detect_change
						mmc_schedule_delayed_work(&host->detect, delay);(mmc_alloc_host中默认将host->detect工作设置为mmc_rescan)
						
						
电源管理:
mmc_power_up
	mmc_pwrseq_pre_power_on
		pwrseq->ops->pre_power_on(host);
		.pre_power_on = mmc_pwrseq_simple_pre_power_on
			mmc_pwrseq_simple_pre_power_on
	mmc_pwrseq_post_power_on
		pwrseq->ops->post_power_on(host);
		.post_power_on = mmc_pwrseq_simple_post_power_on
			mmc_pwrseq_simple_post_power_on
			
mmc_power_off
	mmc_pwrseq_power_off
		pwrseq->ops->power_off(host);
		.power_off = mmc_pwrseq_simple_power_off
			mmc_pwrseq_simple_power_off
	
  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值