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的devicestruct 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属性2mmc_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的标识,禁止搜索cardint         rescan_entered; /* used with nonremovable devices */   // 是否已经rescan过的标识,对应不可移除的设备只能rescan一次struct mmc_card     *card;      /* device attached to this host */   // 和该host绑定在一起的cardwait_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_REGULATORbool            regulator_enabled; /* regulator state */   // 代表regulator(LDO)的状态
#endifstruct 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 settingint (*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所属hoststruct device       dev;        /* the device */    // 对应的deviceunsigned 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总线上的cardint (*sleep)(struct mmc_host *);   // 休眠mmc总线上的cardvoid (*remove)(struct mmc_host *);   // 从软件上注销mmc总线上的cardvoid (*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 */   // 该命令关联到哪个requestunsigned 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_busreturn 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_cardif (!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:0001card->dev.bus = &mmc_bus_type;// 设置card的bus为mmc_bus_type,这样,mmc_card注册到设备驱动模型中之后就会挂在mmc_bus下。// 会在/sys/bus/mmc/devices/目录下生成相应card的节点,如mmc0:0001card->dev.release = mmc_release_card;card->dev.type = type;    // 设置device typespin_lock_init(&card->bkops_info.bkops_stats.lock);    // 初始化spin_lockspin_lock_init(&card->wr_pack_stats.lock);                   // 初始化spin_lockreturn 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_FSmmc_add_card_debugfs(card);// 创建card对应的debug节点,对应路径例如:/sys/kernel/debug/mmc0/mmc0:0001
#endifmmc_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节点转化出来的devicehost->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节点转化出来的devicehost->class_dev.class = &mmc_host_class;// 将mmc_device(class_dev)的类设置为mmc_host_class// 注册到sysfs之后,会相应生成/sys/class/mmc_host/mmc0device_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两个wakelockINIT_DELAYED_WORK(&host->detect, mmc_rescan);// !!!!这个很重要!!!!初始化detect工作为mmc_rescan,后续调度host->detect来检测是否有card插入时,就会调用到mmc_rescan。
#ifdef CONFIG_PMhost->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_FSmmc_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_DATAbreak;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,
#endifNULL,
};
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_cardif (err)goto err;/* 将分配到的mmc_card注册到mmc_bus中 */mmc_release_host(host);   // 先释放掉host,可能是在mmc_add_card中会获取这个hosterr = 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);   // 再次申请hostif (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);elseerr = 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_MMCcard->rca = 1;  // 设置card的RCA地址为1memcpy(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 stateerr = 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,扇区里面的擦除字节数,读出来是512Kif (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_CONFIGcard->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) {   // 判断版本是否大于3err = 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,也就是CMD13if (!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;} elsedata_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;} elsemmc_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;elsecmd.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也是将其设置为NULLDECLARE_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} elsewake_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_enablereturn 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_disablespin_lock_irqsave(&host->lock, flags);if (--host->claim_cnt) {/* Release for nested claim */spin_unlock_irqrestore(&host->lock, flags);    // 如果减一之后计数还不为0,说明当前进程需要继续占用该host,不做其他操作} else {    // 以下需要释放该hosthost->claimed = 0;    // 设置占用标志claimed为0host->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的工作电压elsebit = 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;    // 设置总线宽度为1host->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的使用和设置,需要先占用hosthost->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);elsemmc_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_DEBUGunsigned long flags;spin_lock_irqsave(&host->lock, flags);WARN_ON(host->removed);spin_unlock_irqrestore(&host->lock, flags);
#endifhost->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对应的busmmc_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_gethost->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总线的使用者计数+1spin_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总线的使用者计数-1if ((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 */   // 该命令关联到哪个requestunsigned 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_RESUMEif (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_PROFILINGif (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 coreif (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/52224160if (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的responsecmd->retries = retries;   // 失败时的重复尝试次数mrq.cmd = cmd;   // 封装到mmc_request中cmd->data = NULL;   // 不带数据包的命令,故清空datammc_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;elsehost->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_probedw_mci_init_slotmmc_alloc_hostINIT_DELAYED_WORK(&host->detect, mmc_rescan);//这里绑定mmc_rescanmmc_add_host //(rk将host导出了primary_sdio_host)mmc_start_host				mmc_detect_change_mmc_detect_changemmc_schedule_delayed_work(&host->detect, delay);(mmc_alloc_host中默认将host->detect工作设置为mmc_rescan)电源管理:
mmc_power_upmmc_pwrseq_pre_power_onpwrseq->ops->pre_power_on(host);.pre_power_on = mmc_pwrseq_simple_pre_power_onmmc_pwrseq_simple_pre_power_onmmc_pwrseq_post_power_onpwrseq->ops->post_power_on(host);.post_power_on = mmc_pwrseq_simple_post_power_onmmc_pwrseq_simple_post_power_onmmc_power_offmmc_pwrseq_power_offpwrseq->ops->power_off(host);.power_off = mmc_pwrseq_simple_power_offmmc_pwrseq_simple_power_off

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/716667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【微服务】微服务中常用认证加密方案总结

目录 一、前言 二、登录认证安全问题 3.1 认证方式选择 三、常用的加密方案 3.1 MD5加密算法 3.1.1 md5特点 3.1.2 md5原理 3.1.3 md5使用场景 3.2 AES加密算法 3.2.1 AES简介 3.2.2 AES加解原理 3.2.3 AES算法优缺点 3.2.4 AES算法使用场景 3.3 RSA加密算法 3.3…

Flutter Dio进阶:使用Flutter Dio拦截器实现高效的API请求管理和身份验证刷新

Flutter笔记 使用Flutter Dio拦截器实现高效的API请求管理和身份验证刷新 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article…

金融业被网络攻击了怎么办,如何治理和风险控制?

近年来&#xff0c;网络罪犯的人数和复杂程度都在增加&#xff0c;网络罪犯的目标锁定变得更具策略性&#xff0c;更加专注于最大效率和获利。随着有关全球网络犯罪的数据持续涌入&#xff0c;可以看出金融服务企业已然成为头号锁定目标。虽然金融服务企业在网络安全人员、工具…

图论例题解析

1.图论基础概念 概念 &#xff08;注意连通非连通情况&#xff0c;1节点&#xff09; 无向图&#xff1a; 度是边的两倍&#xff08;没有入度和出度的概念&#xff09; 1.完全图&#xff1a; 假设一个图有n个节点&#xff0c;那么任意两个节点都有边则为完全图 2.连通图&…

【MySQL】SQL 优化

MySQL - SQL 优化 1. 在 MySQL 中&#xff0c;如何定位慢查询&#xff1f; 1.1 发现慢查询 现象&#xff1a;页面加载过慢、接口压力测试响应时间过长&#xff08;超过 1s&#xff09; 可能出现慢查询的场景&#xff1a; 聚合查询多表查询表数据过大查询深度分页查询 1.2 通…

错误笔记:Anaconda 错误(闪退、无法安装等) + Pycharm 错误(无法启动)+ python 报错

Anaconda 错误 1、导航器启动中发生-- 闪退 方法一&#xff1a; Windows下&#xff1a; 1&#xff09;使用管理员运行&#xff1a;conda prompt 2&#xff09;执行命令 conda update anaconda-navigator 方法二&#xff1a; 重置Anaconda配置&#xff1a;anaconda-navigator…

C语言第三十四弹---动态内存管理(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 动态内存管理 1、动态内存经典笔试题分析 1.1、题目1 1.2、题目2 1.3、题目3 1.4、题目4 2、柔性数组 2.1、柔性数组的特点 2.2、柔性数组的使用 2.3、…

5.STL源码解析-算法、仿函数、适配器

算法 STL算法总览 仿函数与适配器 C标准模板库&#xff08;STL&#xff09;是C程序员的得力工具&#xff0c;提供了许多强大而高效的数据结构和算法。在STL中&#xff0c;仿函数&#xff08;Functor&#xff09;和适配器&#xff08;Adapter&#xff09;是两个重要的概念…

【C++精简版回顾】17.io流,流中提供的函数

1.流含义 2.流类 3.流对象 4.流对象的函数 举例&#xff1a; 要求&#xff1a;数据结构中经常需要对齐输出数据&#xff0c;应该怎么做&#xff1f; 1.头文件 #include<iomanip> 2.创建表格头 cout << setiosflags(ios::left) << setw(8) << "姓名…

BUGKU 网站被黑

打开环境&#xff0c;什么都没发现&#xff0c;使用蚁剑扫描一下&#xff0c;发现shell.php&#xff0c;打开 使用BP抓包&#xff0c;进行爆破 得到密码&#xff1a;hack 进去得到flag

每日一类:QLabel深入解析

QLabel是Qt中用于显示文本或图像的控件&#xff0c;属于Qt Widgets模块。它是展示静态内容的理想选择&#xff0c;支持富文本格式&#xff0c;使得文本可以包含不同的字体、颜色和链接。QLabel也可以用来显示图像&#xff0c;包括动态图像。此外&#xff0c;它还支持文本和图像…

【考研数学】汤家凤1800题什么水平?

我觉得汤家凤基础武忠祥强化这个组合非常的不错 汤家凤老师的讲课风格 汤家凤老师的基础课程是大家公认的讲的详细&#xff0c;并且非常照顾基础不好的学生&#xff0c;会把基础知识点掰开揉碎的讲给大家听&#xff0c;在上课过程中&#xff0c;还会把知识点写在A4纸上&#…

R750 install AMD MI210GPU

一、 查看服务器GPU卡信息 可以首先在服务器上check 当前GPU的详细信息是否匹配 二、安装 Ubuntu22.04操作系统 服务器CHECK 安装的AMD GPU 是否被系统识别 #lspci | grep AMD 查看GPU信息 可以看到已经识别成功 三、安装AMD GPU驱动 https://rocm.docs.amd.com/projec…

智能驾驶规划控制理论学习05-车辆运动学规划案例分析

目录 案例一——Hybrid A*&#xff08;基于正向运动学&#xff09; 1、基本思想 2、 实现流程 3、启发函数设计 4、分析扩张&#xff08;Analytic Expansions&#xff09; 5、分级规划&#xff08;Hierarchical planning&#xff09; 案例二——State Lattice Planning&…

子矩阵的和 刷题笔记 {二维前缀和}

首先我们的目标是让 s[i][j]表示为其左方和上方形成的矩阵所有元素的和 加上s[i-1][j]和s[i][j-1]后 s[i-1][j-1]部分重复了所以减去 最后加上a[i][j]即可完成目标 s[i][j]s[i-1][j]s[i][j-1]-s[i-1][j-1]a[i][j]; 然后看题目要求 要求x1,y1,x2,y2围成的小正方形内的元素和…

C/C++工程师面试题(数据库篇)

索引的优缺点 索引是一种支持快速查找特定行的数据结构&#xff0c;如果没有索引&#xff0c;就需要遍历整个表进行查找。用于提高数据检索的速度和效率。 好处&#xff1a; 提高检索速度&#xff1a; 索引可以加快数据的检索速度&#xff0c;因为它们允许数据库系统直接定位到…

Revit-二开之立面视图创建FilledRegion-(3)

在上一篇博客中介绍了FilledRegion的创建方法,这种方法通常只在平面视图中适用,在三维视图中也是无法创建的(目前研究的是这样的,如果有其他方法,请赐教)。 本片文章介绍一个下在立面视图中创建FilledRegion的方法,主要操作是在立面视图中拾取一个点,然后以该点为原点,…

YOLOv5 项目:推理代码和参数详细介绍(detect)

1、前言 本章将介绍yolov5项目的推理函数&#xff0c;关于yolov5的下载和配置环境&#xff0c;参考上一篇文章&#xff1a; YOLOv5 项目&#xff1a;环境配置-CSDN博客 pycharm 中打开的推理模块如红框中所示 pycharm将conda新建的虚拟环境导入&#xff0c;参考 &#xff1a;…

简单实现Transformer的自注意力

简单实现Transformer的自注意力 关注{晓理紫|小李子}&#xff0c;获取技术推送信息&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持&#xff01;&#xff01; 如果你感觉对你有所帮助&#xff0c;请关注我。 源码获取&#xff1a;VX关注并回复chatg…

二叉树的右视图,力扣

目录 题目&#xff1a; 我们直接看题解吧&#xff1a; 快速理解解题思路小建议&#xff1a; 审题目事例提示&#xff1a; 解题方法&#xff1a; 解题分析&#xff1a; 解题思路&#xff1a; 代码实现(DFS)&#xff1a; 代码1&#xff1a; 补充说明&#xff1a; 代码2&#xff1…