1.mmc的概念
1.MMC
MultiMedia Card,多媒体存储卡, 但后续泛指一个接口协定(一种卡式),能符合这接口的内存器都可称作mmc储存体,工作电压:高电压为2.7~3.6 V,低电压为1.65~1.95 V,可选.
2.MMC总线
mmc总线是和I2C总线、SPI总线类似的一种总线结构。
卡与主控制器间串行传送,工作时钟频率范围为0~200 MHz,mmc总线上最多可识别64 K个mmc设备,在总线上不超过10个卡时,可运行到最高频率。
3.mmc设备
使用mmc接口规范(MCI, Multimedia Card Interface)的设备都可以称之为mmc设备。分为以下三种:
1.mmc type card:
1.标准mmc卡:闪存卡的一种,使用mmc标准;
2.emmc:Embedded MultiMediaCard,是MMC协会所制定的内嵌式存储器标准规格,带有mmc接口,是具备mmc协议的芯片。
2.sd type card
sd卡:SD卡为Secure Digital Memory Card, 即安全数码卡。它在MMC的基础上发展而来,增加了两个主要特色:SD卡强调数据的安全安全,可以设定所储存的使用权限,防止数据被他人复制。兼容mmc接口规范。
3.sdio type card
sdio设备:SDIO是在SD标准上定义了一种外设接口,它和SD卡规范间的一个重要区别是增加了低速标准。在SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开销支持低速IO能力。常见的sdio设备有Wi-Fi card、Bluetooth card等等。
这几种类型的card统称为mmc card
4.mmc协议
类似i2c协议、spi协议,mmc总线上也有一套自己的通讯规范。通信规范后续在说明。而上述mmc设备基于上mmc总线通讯规范上由自身硬件特性设置了自己的一套协议。
1.标准mmc卡协议
2.emmc协议(主要区别在于读写速度上)
3.sd协议
5.mmc subsystem
kernel中的mmc subsystem用于管理所有mmc总线控制器以及mmc设备,包括mmc type card(标准mmc卡、emmc)、sd type card(sd卡)、sdio type card。
也就是说只要使用MCI的设备都交由mmc subsystem统一管理。
2.MMC framework的软件架构
MMC framework分别有“从左到右”和“从下到上”两种层次结构:
1.从左到右
MMC协议是一个总线协议,因此包括Host controller、Bus、Card三类实体(从左到右)。相应的,MMC framework抽象出了host、bus、card三个软件实体,以便和硬件一一对应:
host:负责驱动Host controller,提供诸如访问card的寄存器、检测card的插拔、读写card等操作方法。从设备模型的角度看,host会检测卡的插入,并向bus注册MMC card设备;
bus:是MMC bus的虚拟抽象,以标准设备模型的方式,收纳MMC card(device)以及对应的MMC driver(driver);
card:抽象具体的MMC卡,由对应的MMC driver驱动(从这个角度看,可以忽略MMC的技术细节,只需关心一个个具有特定功能的卡设备,如存储卡、WIFI卡、GPS卡等等).
2.从左到右
MMC host controller driver位于底层,基于MMC core提供的框架,驱动具体的硬件(MMC controller);
MMC core位于中间,是MMC framework的核心实现,负责抽象host、bus、card等软件实体,负责向底层提供统一、便利的编写Host controller driver的API;
MMC card driver位于最上面,负责驱动MMC core抽象出来的虚拟的card设备,并对接内核其它的framework(例如块设备、TTY、wireless等),实现具体的功能。
3.工作流程
Linux MMC framework的工作流程如下
4.数据结构
1.host相关
1.struct mmc_host
struct mmc_host是mmc core由host controller抽象出来的结构体,用于代表一个mmc host控制器。
struct mmc_host {
struct device *parent; // 对应的host controller的device
struct device class_dev; // mmc_host的device结构体,会挂在class/mmc_host下
int index; // 该host的索引号
const struct mmc_host_ops *ops; // 该host的操作集,由host controller设置,后面说明
unsigned int f_min; // 该host支持的最低频率
unsigned int f_max; // 该host支持的最大频率
unsigned int f_init; // 该host使用的初始化频率
u32 ocr_avail; // 该host可用的ocr值(电压相关)
u32 ocr_avail_sdio; /* SDIO-specific OCR */
u32 ocr_avail_sd; /* SD-specific OCR */
u32 ocr_avail_mmc; /* MMC-specific OCR */
struct notifier_block pm_notify;
u32 max_current_330; // 3.3V时的最大电流
u32 max_current_300; // 3.0V时的最大电流
u32 max_current_180; // 1.8V时的最大电流
u32 caps; /* Host capabilities */ // host属性
u32 caps2; /* More host capabilities */ // host属性2
mmc_pm_flag_t pm_caps; /* supported pm features */ // 电源管理属性
/// 以下是和clock相关的成员
int clk_requests; /* internal reference counter */
unsigned int clk_delay; /* number of MCI clk hold cycles */
bool clk_gated; /* clock gated */
struct delayed_work clk_gate_work; /* delayed clock gate */
unsigned int clk_old; /* old clock value cache */
spinlock_t clk_lock; /* lock for clk fields */
struct mutex clk_gate_mutex; /* mutex for clock gating */
struct device_attribute clkgate_delay_attr;
unsigned long clkgate_delay;
/* host specific block data */
// 和块相关的成员
unsigned int max_seg_size; /* see blk_queue_max_segment_size */
unsigned short max_segs; /* see blk_queue_max_segments */
unsigned short unused;
unsigned int max_req_size; /* maximum number of bytes in one req */
unsigned int max_blk_size; /* maximum size of one mmc block */
unsigned int max_blk_count; /* maximum number of blocks in one req */
unsigned int max_discard_to; /* max. discard timeout in ms */
/* private data */
spinlock_t lock; /* lock for claim and bus ops */ // host的bus使用的锁
struct mmc_ios ios; /* current io bus settings */ // io setting,后续说明
u32 ocr; /* the current OCR setting */ // 当前使用的ocr的值
/* group bitfields together to minimize padding */
unsigned int use_spi_crc:1;
unsigned int claimed:1; /* host exclusively claimed */ // host是否已经被占用
unsigned int bus_dead:1; /* bus has been released */ // host的bus是否处于激活状态
int rescan_disable; /* disable card detection */ // 禁止rescan的标识,禁止搜索card
int rescan_entered; /* used with nonremovable devices */ // 是否已经rescan过的标识,对应不可移除的设备只能rescan一次
struct mmc_card *card; /* device attached to this host */ // 和该host绑定在一起的card
wait_queue_head_t wq;
struct task_struct *claimer; /* task that has host claimed */ // 该host的占有者进程
struct task_struct *suspend_task;
int claim_cnt; /* "claim" nesting count */ // 占有者进程对该host的占用计数
struct delayed_work detect; // 检测卡槽变化的工作
struct wake_lock detect_wake_lock; // 检测卡槽变化的工作使用的锁
const char *wlock_name; // 锁名称
int detect_change; /* card detect flag */ // 需要检测卡槽变化的标识
struct mmc_slot slot; // 卡槽的结构体
const struct mmc_bus_ops *bus_ops; /* current bus driver */ // host的mmc总线的操作集,后面说明
unsigned int bus_refs; /* reference counter */ // host的mmc总线的使用计数
unsigned int bus_resume_flags; // host的mmc总线的resume标识
mmc_pm_flag_t pm_flags; /* requested pm features */
#ifdef CONFIG_REGULATOR
bool regulator_enabled; /* regulator state */ // 代表regulator(LDO)的状态
#endif
struct mmc_supply supply;
struct dentry *debugfs_root; // 对应的debug目录结构体
struct mmc_async_req *areq; /* active async req */ // 当前正在处理的异步请求
struct mmc_context_info context_info; /* async synchronization info */ // 异步请求的信息
unsigned int actual_clock; /* Actual HC clock rate */ // 实际的时钟频率
};
ocr值各个位代表的电压意义如下:
#define MMC_VDD_165_195 0x00000080 /* VDD voltage 1.65 - 1.95 */
#define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */
#define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */
#define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */
#define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */
#define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */
#define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */
#define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */
#define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */
#define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */
#define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */
#define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */
#define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */
#define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */
#define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */
#define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */
#define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */
host属性(mmc_host->caps)支持的属性如下
#define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */
#define MMC_CAP_MMC_HIGHSPEED (1 << 1) /* Can do MMC high-speed timing */
#define MMC_CAP_SD_HIGHSPEED (1 << 2) /* Can do SD high-speed timing */
#define MMC_CAP_SDIO_IRQ (1 << 3) /* Can signal pending SDIO IRQs */
#define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */
#define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */
#define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */
#define MMC_CAP_NONREMOVABLE (1 << 8) /* Nonremovable e.g. eMMC */
#define MMC_CAP_WAIT_WHILE_BUSY (1 << 9) /* Waits while card is busy */
#define MMC_CAP_ERASE (1 << 10) /* Allow erase/trim commands */
#define MMC_CAP_1_8V_DDR (1 << 11) /* can support */
/* DDR mode at 1.8V */
#define MMC_CAP_1_2V_DDR (1 << 12) /* can support */
/* DDR mode at 1.2V */
#define MMC_CAP_HSDDR (MMC_CAP_1_8V_DDR | MMC_CAP_1_2V_DDR)
#define MMC_CAP_POWER_OFF_CARD (1 << 13) /* Can power off after boot */
#define MMC_CAP_BUS_WIDTH_TEST (1 << 14) /* CMD14/CMD19 bus width ok */
#define MMC_CAP_UHS_SDR12 (1 << 15) /* Host supports UHS SDR12 mode */
#define MMC_CAP_UHS_SDR25 (1 << 16) /* Host supports UHS SDR25 mode */
#define MMC_CAP_UHS_SDR50 (1 << 17) /* Host supports UHS SDR50 mode */
#define MMC_CAP_UHS_SDR104 (1 << 18) /* Host supports UHS SDR104 mode */
#define MMC_CAP_UHS_DDR50 (1 << 19) /* Host supports UHS DDR50 mode */
#define MMC_CAP_DRIVER_TYPE_A (1 << 23) /* Host supports Driver Type A */
#define MMC_CAP_DRIVER_TYPE_C (1 << 24) /* Host supports Driver Type C */
#define MMC_CAP_DRIVER_TYPE_D (1 << 25) /* Host supports Driver Type D */
#define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */
#define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */
host属性2(mmc_host->caps2)支持的属性如下
#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */
#define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */
#define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */
#define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */
#define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */
#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */
#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */
#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \
MMC_CAP2_HS200_1_2V_SDR)
#define MMC_CAP2_BROKEN_VOLTAGE (1 << 7) /* Use the broken voltage */
#define MMC_CAP2_DETECT_ON_ERR (1 << 8) /* On I/O err check card removal */
#define MMC_CAP2_HC_ERASE_SZ (1 << 9) /* High-capacity erase size */
#define MMC_CAP2_CD_ACTIVE_HIGH (1 << 10) /* Card-detect signal active high */
#define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */
#define MMC_CAP2_PACKED_RD (1 << 12) /* Allow packed read */
#define MMC_CAP2_PACKED_WR (1 << 13) /* Allow packed write */
#define MMC_CAP2_PACKED_CMD (MMC_CAP2_PACKED_RD | \
MMC_CAP2_PACKED_WR)
#define MMC_CAP2_NO_PRESCAN_POWERUP (1 << 14) /* Don't power up before scan */
#define MMC_CAP2_INIT_BKOPS (1 << 15) /* Need to set BKOPS_EN */
#define MMC_CAP2_PACKED_WR_CONTROL (1 << 16) /* Allow write packing control */
#define MMC_CAP2_CLK_SCALE (1 << 17) /* Allow dynamic clk scaling */
#define MMC_CAP2_STOP_REQUEST (1 << 18) /* Allow stop ongoing request */
/* Use runtime PM framework provided by MMC core */
#define MMC_CAP2_CORE_RUNTIME_PM (1 << 19)
#define MMC_CAP2_SANITIZE (1 << 20) /* Support Sanitize */
/* Allows Asynchronous SDIO irq while card is in 4-bit mode */
#define MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE (1 << 21)
#define MMC_CAP2_HS400_1_8V (1 << 22) /* can support */
#define MMC_CAP2_HS400_1_2V (1 << 23) /* can support */
#define MMC_CAP2_CORE_PM (1 << 24) /* use PM framework */
#define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | \
MMC_CAP2_HS400_1_2V)
#define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/
2.struct mmc_host_ops
mmc core将host需要提供的一些操作方法封装成struct mmc_host_ops。mmc core主模块的很多接口都是基于这里面的操作方法来实现的,通过这些方法来操作host硬件达到对应的目的。所以struct mmc_host_ops也是host controller driver需要实现的核心部分。
struct mmc_host_ops {
/*
* 'enable' is called when the host is claimed and 'disable' is called
* when the host is released. 'enable' and 'disable' are deprecated.
*/
int (*enable)(struct mmc_host *host); // 使能host,当host被占用时(第一次调用mmc_claim_host)调用
int (*disable)(struct mmc_host *host); // 禁用host,当host被释放时(第一次调用mmc_release_host)调用
/*
* It is optional for the host to implement pre_req and post_req in
* order to support double buffering of requests (prepare one
* request while another request is active).
* pre_req() must always be followed by a post_req().
* To undo a call made to pre_req(), call post_req() with
* a nonzero err condition.
*/
// post_req和pre_req是为了实现异步请求处理而设置的
// 异步请求处理就是指,当另外一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待
// 具体参考《mmc core主模块》
void (*post_req)(struct mmc_host *host, struct mmc_request *req,
int err);
void (*pre_req)(struct mmc_host *host, struct mmc_request *req,
bool is_first_req);
void (*request)(struct mmc_host *host, struct mmc_request *req); // host处理mmc请求的方法,在mmc_start_request中会调用
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); // 设置host的总线的io setting
int (*get_ro)(struct mmc_host *host); // 获取host上的card的读写属性
int (*get_cd)(struct mmc_host *host); // 检测host的卡槽中card的插入状态
/* optional callback for HC quirks */
void (*init_card)(struct mmc_host *host, struct mmc_card *card); // 初始化card的方法
int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios); // 切换信号电压的方法
/* Check if the card is pulling dat[0:3] low */
int (*card_busy)(struct mmc_host *host); // 用于检测card是否处于busy状态
/* The tuning command opcode value is different for SD and eMMC cards */
int (*execute_tuning)(struct mmc_host *host, u32 opcode); // 执行tuning操作,为card选择一个合适的采样点
int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); // 选择信号的驱动强度
void (*hw_reset)(struct mmc_host *host); // 硬件复位
void (*card_event)(struct mmc_host *host); //
unsigned long (*get_max_frequency)(struct mmc_host *host); // 获取host支持的最大频率的方法
unsigned long (*get_min_frequency)(struct mmc_host *host); // 获取host支持的最小频率的方法
int (*notify_load)(struct mmc_host *, enum mmc_load);
int (*stop_request)(struct mmc_host *host); // 停止请求处理的方法
unsigned int (*get_xfer_remain)(struct mmc_host *host);
};
2.core相关
1.struct mmc_card
struct mmc_card是mmc core由mmc设备抽象出来的card设备的结构体,用于代表一个mmc设备。
struct mmc_card {
struct mmc_host *host; /* the host this device belongs to */ // 该mmc_card所属host
struct device dev; /* the device */ // 对应的device
unsigned int rca; /* relative card address of device */ // 该card的RCA地址
unsigned int type; /* card type */ // card类型,后面说明
unsigned int state; /* (our) card state */ // card的当前状态,后面说明
unsigned int quirks; /* card quirks */ // 该card的一些特点
unsigned int erase_size; /* erase size in sectors */
unsigned int erase_shift; /* if erase unit is power 2 */
unsigned int pref_erase; /* in sectors */
u8 erased_byte; /* value of erased bytes */
u32 raw_cid[4]; /* raw card CID */ // 原始的cid寄存器的值
u32 raw_csd[4]; /* raw card CSD */ // 原始的csd寄存器的值
u32 raw_scr[2]; /* raw card SCR */ // 原始的scr寄存器的值
struct mmc_cid cid; /* card identification */ // 从cid寄存器的值解析出来的信息
struct mmc_csd csd; /* card specific */ // 从csd寄存器的值解析出来的信息
struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */ // 从ext_csd寄存器的值解析出来的信息
struct sd_scr scr; /* extra SD information */ // 外部sdcard的信息
struct sd_ssr ssr; /* yet more SD information */ // 更多关于sd card的信息
struct sd_switch_caps sw_caps; /* switch (CMD6) caps */ // sd的切换属性
unsigned int sd_bus_speed; /* Bus Speed Mode set for the card */
struct dentry *debugfs_root; // 对应debug目录的结构体
struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ // 物理分区
unsigned int nr_parts; // 分区数量
unsigned int part_curr; // 当前分区
struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/
struct mmc_bkops_info bkops_info;
struct device_attribute rpm_attrib; // rpm属性
unsigned int idle_timeout;
struct notifier_block reboot_notify;
bool issue_long_pon;
u8 *cached_ext_csd;
};
mmc card类型(mmc_card->type)如下:
#define MMC_TYPE_MMC 0 /* MMC card */
#define MMC_TYPE_SD 1 /* SD card */
#define MMC_TYPE_SDIO 2 /* SDIO card */
#define MMC_TYPE_SD_COMBO 3 /* SD combo (IO+mem) card */
mmc card状态(mmc_card->state)如下:
#define MMC_STATE_PRESENT (1<<0) /* present in sysfs */
#define MMC_STATE_READONLY (1<<1) /* card is read-only */
#define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */
#define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */
#define MMC_STATE_HIGHSPEED_DDR (1<<4) /* card is in high speed mode */
#define MMC_STATE_ULTRAHIGHSPEED (1<<5) /* card is in ultra high speed mode */
#define MMC_CARD_SDXC (1<<6) /* card is SDXC */
#define MMC_CARD_REMOVED (1<<7) /* card has been removed */
#define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */
#define MMC_STATE_HIGHSPEED_400 (1<<9) /* card is in HS400 mode */
#define MMC_STATE_DOING_BKOPS (1<<10) /* card is doing BKOPS */
#define MMC_STATE_NEED_BKOPS (1<<11) /* card needs to do BKOPS */
mmc card的一些特写标识(mmc_card->quirks)如下:
#define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */
#define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize */
/* for byte mode */
#define MMC_QUIRK_NONSTD_SDIO (1<<2) /* non-standard SDIO card attached */
/* (missing CIA registers) */
#define MMC_QUIRK_BROKEN_CLK_GATING (1<<3) /* clock gating the sdio bus will make card fail */
#define MMC_QUIRK_NONSTD_FUNC_IF (1<<4) /* SDIO card has nonstd function interfaces */
#define MMC_QUIRK_DISABLE_CD (1<<5) /* disconnect CD/DAT[3] resistor */
#define MMC_QUIRK_INAND_CMD38 (1<<6) /* iNAND devices have broken CMD38 */
#define MMC_QUIRK_BLK_NO_CMD23 (1<<7) /* Avoid CMD23 for regular multiblock */
#define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in */
#define MMC_QUIRK_LONG_READ_TIME (1<<9) /* Data read time > CSD says */
#define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim */
/* byte mode */
#define MMC_QUIRK_INAND_DATA_TIMEOUT (1<<11) /* For incorrect data timeout */
/* To avoid eMMC device getting broken permanently due to HPI feature */
#define MMC_QUIRK_BROKEN_HPI (1 << 12)
/* Skip data-timeout advertised by card */
#define MMC_QUIRK_BROKEN_DATA_TIMEOUT (1<<13)
#define MMC_QUIRK_CACHE_DISABLE (1 << 14) /* prevent cache enable */
3.host的总线相关
1.struct mmc_bus_ops
host的mmc总线的操作集,由host插入的card决定,不同类型的card对mmc总线的操作有所不同。
struct mmc_bus_ops {
int (*awake)(struct mmc_host *); // 唤醒mmc总线上的card
int (*sleep)(struct mmc_host *); // 休眠mmc总线上的card
void (*remove)(struct mmc_host *); // 从软件上注销mmc总线上的card
void (*detect)(struct mmc_host *); // 检测mmc总线上的card是否被移除
int (*suspend)(struct mmc_host *); // 对应mmc总线的suspend操作
int (*resume)(struct mmc_host *); // 对应mmc总线的resume操作
int (*power_save)(struct mmc_host *); // 存储电源状态
int (*power_restore)(struct mmc_host *); // 恢复电源状态
int (*alive)(struct mmc_host *); // 检测mmc总线上的card的激活状态
int (*change_bus_speed)(struct mmc_host *, unsigned long *); // 修改mmc总线的工作时钟
};
2.struct mmc_ios
struct mmc_ios 由mmc core定义的规范的结构,用来维护mmc总线相关的一些io setting。如下:
struct mmc_ios {
unsigned int clock; /* clock rate */ // 当前工作频率
unsigned int old_rate; /* saved clock rate */ // 上一次的工作频率
unsigned long clk_ts; /* time stamp of last updated clock */ // 上一次更新工作频率的时间戳
unsigned short vdd;/* vdd stores the bit number of the selected voltage range from below. */ // 支持的电压表
unsigned char bus_mode; /* command output mode */ // 总线输出模式,包括开漏模式和上拉模式
unsigned char chip_select; /* SPI chip select */ // spi片选
unsigned char power_mode; /* power supply mode */ // 电源状态模式
unsigned char bus_width; /* data bus width */ // 总线宽度
unsigned char timing; /* timing specification used */ // 时序类型
unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */ // 信号的工作电压
unsigned char drv_type; /* driver type (A, B, C, D) */ // 驱动类型
};
4.请求相关
1.struct mmc_command
mmc core用struct mmc_command来表示一个命令包,其中包括命令码、命令类型、response、response类型
struct mmc_command {
u32 opcode; // 命令的操作码,如MMC_GO_IDLE_STATE、MMC_SEND_OP_COND等等
u32 arg; // 命令的参数
u32 resp[4]; // response值
unsigned int flags; /* expected response type */ // 期待的response的类型,具体参考后面的命令类型和response类型
#define mmc_resp_type(cmd) ((cmd)->flags & (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC|MMC_RSP_BUSY|MMC_RSP_OPCODE))
#define mmc_cmd_type(cmd) ((cmd)->flags & MMC_CMD_MASK)
unsigned int retries; /* max number of retries */ // 失败时的重复尝试次数
unsigned int error; /* command error */ // 命令的错误码
unsigned int cmd_timeout_ms; /* in milliseconds */ // 命令执行的等待超时事件
struct mmc_data *data; /* data segment associated with cmd */ // 和该命令关联在一起的数据段
struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
};
命令类型如下(和协议相关):
#define MMC_CMD_MASK (3 << 5) /* non-SPI command type */
#define MMC_CMD_AC (0 << 5)
#define MMC_CMD_ADTC (1 << 5)
#define MMC_CMD_BC (2 << 5)
#define MMC_CMD_BCR (3 << 5)
response类型如下(和协议相关):
#define MMC_RSP_PRESENT (1 << 0)
#define MMC_RSP_136 (1 << 1) /* 136 bit response */
#define MMC_RSP_CRC (1 << 2) /* expect valid crc */
#define MMC_RSP_BUSY (1 << 3) /* card may send busy */
#define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */
#define MMC_RSP_NONE (0)
#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
#define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC)
#define MMC_RSP_R3 (MMC_RSP_PRESENT)
#define MMC_RSP_R4 (MMC_RSP_PRESENT)
#define MMC_RSP_R5 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
2.struct mmc_data
mmc core用struct mmc_data来表示一个命令包
struct mmc_data {
unsigned int timeout_ns; /* data timeout (in ns, max 80ms) */ // 超时时间,以ns为单位
unsigned int timeout_clks; /* data timeout (in clocks) */ // 超时时间,以clock为单位
unsigned int blksz; /* data block size */ // 块大小
unsigned int blocks; /* number of blocks */ // 块数量
unsigned int error; /* data error */ // 传输的错误码
unsigned int flags; // 传输标识
unsigned int bytes_xfered;
struct mmc_command *stop; /* stop command */ // 结束传输的命令
struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
unsigned int sg_len; /* size of scatter list */
struct scatterlist *sg; /* I/O scatter list */
s32 host_cookie; /* host private data */
bool fault_injected; /* fault injected */
};
3.struct mmc_request
struct mmc_request是mmc core向host controller发起命令请求的处理单位,其包含了要传输的命令和数据。
struct mmc_request {
struct mmc_command *sbc; /* SET_BLOCK_COUNT for multiblock */ // 设置块数量的命令,怎么用的后续再补充
struct mmc_command *cmd; // 要传输的命令
struct mmc_data *data; // 要传输的数据
struct mmc_command *stop; // 结束命令,怎么用的后续再补充
struct completion completion; // 完成量
void (*done)(struct mmc_request *);/* completion function */ // 传输结束后的回调函数
struct mmc_host *host; // 所属host
};
4.struct mmc_async_req
异步请求的结构体。封装了struct mmc_request请求结构体。
struct mmc_async_req {
/* active mmc request */
struct mmc_request *mrq; // mmc请求
unsigned int cmd_flags; /* copied from struct request */ // 命令标识
/*
* Check error status of completed mmc request.
* Returns 0 if success otherwise non zero.
*/
int (*err_check) (struct mmc_card *, struct mmc_async_req *);
/* Reinserts request back to the block layer */
void (*reinsert_req) (struct mmc_async_req *);
/* update what part of request is not done (packed_fail_idx) */
int (*update_interrupted_req) (struct mmc_card *,
struct mmc_async_req *);
};
5.bus模块
对应代码drivers/mmc/core/bus.c。
抽象出虚拟mmc bus,实现mmc bus的操作。
1.API总览
1.mmc bus相关
mmc_register_bus & mmc_unregister_bus
用于注册和卸载mmc bus(虚拟mmc总线)到设备驱动模型中。
int mmc_register_bus(void)
{
return bus_register(&mmc_bus_type); // 以mmc_bus_type为bus_type注册一条虚拟bus
}
相关节点:/sys/bus/mmc
2.mmc driver相关
mmc_register_driver & mmc_unregister_driver,用于注册和卸载struct mmc_driver *drv到mmc_bus上。mmc_driver就是mmc core抽象出来的card设备driver。
int mmc_register_driver(struct mmc_driver *drv)
{
drv->drv.bus = &mmc_bus_type; // 通过设置mmc_driver——》device_driver——》bus_type来设置mmc_driver所属bus为mmc_bus
return driver_register(&drv->drv); // 这样就将mmc_driver挂在了mmc_bus上了。
}
相关节点:/sys/bus/mmc/drivers.
3.mmc card相关
mmc_alloc_card & mmc_release_card,用于分配或者释放一个struct mmc_card结构体,创建其与mmc host以及mmc bus之间的关联。
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
struct mmc_card *card;
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL); // 分配一个mmc_card
if (!card)
return ERR_PTR(-ENOMEM);
card->host = host; // 关联mmc_card与mmc_host
device_initialize(&card->dev);
card->dev.parent = mmc_classdev(host);
// 设置card的device的parent device为mmc_host的classdev,
// 注册到设备驱动模型中之后,会在/sys/class/mmc_host/mmc0目录下生成相应card的节点,如mmc0:0001
card->dev.bus = &mmc_bus_type;
// 设置card的bus为mmc_bus_type,这样,mmc_card注册到设备驱动模型中之后就会挂在mmc_bus下。
// 会在/sys/bus/mmc/devices/目录下生成相应card的节点,如mmc0:0001
card->dev.release = mmc_release_card;
card->dev.type = type; // 设置device type
spin_lock_init(&card->bkops_info.bkops_stats.lock); // 初始化spin_lock
spin_lock_init(&card->wr_pack_stats.lock); // 初始化spin_lock
return card;
}
参数说明:host——》要分配的card所属的mmc_host,type——》对应的device type。
mmc_add_card & mmc_remove_card,用于注册或者卸载struct mmc_card到mmc_bus上。
/*
* Register a new MMC card with the driver model.
*/
int mmc_add_card(struct mmc_card *card)
{
int ret;
/* 以下用于打印card的注册信息 */
const char *type;
const char *uhs_bus_speed_mode = "";
// 设置速度模式的字符串,为了后面打印出card信息
//......
if (mmc_host_is_spi(card->host)) {
pr_info("%s: new %s%s%s card on SPI\n",
mmc_hostname(card->host),
mmc_card_highspeed(card) ? "high speed " : "",
mmc_card_ddr_mode(card) ? "DDR " : "",
type);
} else {
pr_info("%s: new %s%s%s%s%s%s card at address %04x\n",
mmc_hostname(card->host),
mmc_card_uhs(card) ? "ultra high speed " :
(mmc_card_highspeed(card) ? "high speed " : ""),
(mmc_card_hs400(card) ? "HS400 " : ""),
(mmc_card_hs200(card) ? "HS200 " : ""),
mmc_card_ddr_mode(card) ? "DDR " : "",
uhs_bus_speed_mode, type, card->rca);
}
// 在这里会打印出card信息的字符串
// eg:mmc0: new HS200 MMC card at address 0001
/* 设置card的debug节点 */
#ifdef CONFIG_DEBUG_FS
mmc_add_card_debugfs(card);
// 创建card对应的debug节点,对应路径例如:/sys/kernel/debug/mmc0/mmc0:0001
#endif
mmc_init_context_info(card->host); // 初始化同步的文本信息
/* 以下使能card device的pm runtime的功能 */
ret = pm_runtime_set_active(&card->dev);
if (ret)
pr_err("%s: %s: failed setting runtime active: ret: %d\n",
mmc_hostname(card->host), __func__, ret);
else if (!mmc_card_sdio(card) && mmc_use_core_runtime_pm(card->host))
pm_runtime_enable(&card->dev);
/* 添加到设备驱动模型中 */
ret = device_add(&card->dev);
// 会创建/sys/bus/mmc/devices/mmc0:0001节点和/sys/class/mmc_host/mmc0/mmc0:0001节点
/* 使能异步device suspend,初始化runtime_pm_timeout属性 */
device_enable_async_suspend(&card->dev);
if (mmc_use_core_runtime_pm(card->host) && !mmc_card_sdio(card)) {
card->rpm_attrib.show = show_rpm_delay;
card->rpm_attrib.store = store_rpm_delay;
sysfs_attr_init(&card->rpm_attrib.attr);
card->rpm_attrib.attr.name = "runtime_pm_timeout";
card->rpm_attrib.attr.mode = S_IRUGO | S_IWUSR;
ret = device_create_file(&card->dev, &card->rpm_attrib);
if (ret)
pr_err("%s: %s: creating runtime pm sysfs entry: failed: %d\n",
mmc_hostname(card->host), __func__, ret);
/* Default timeout is 10 seconds */
card->idle_timeout = RUNTIME_SUSPEND_DELAY_MS;
}
/* 设置mmc card的state标识 */
mmc_card_set_present(card);
// 设置card的MMC_STATE_PRESENT状态
// #define MMC_STATE_PRESENT (1<<0) /* present in sysfs */
// 表示card已经合入到sysfs中了
return 0;
}
相关节点:
/sys/bus/mmc/devices/mmc0:0001
/sys/class/mmc_host/mmc0/mmc0:0001
/sys/kernel/debug/mmc0/mmc0:0001
2.数据结构
mmc_bus_type
mmc_bus_type代表了mmc虚拟总线。其内容如下:
static struct bus_type mmc_bus_type = {
.name = "mmc", // 相应会在/sys/bus下生成mmc目录
.dev_attrs = mmc_dev_attrs, // bus下的device下继承的属性,可以看到/sys/bus/mmc/devices/mmc0:0001/type属性就是这里来的
.match = mmc_bus_match, // 用于mmc bus上device和driver的匹配
.uevent = mmc_bus_uevent,
.probe = mmc_bus_probe, // 当match成功的时候,执行的probe操作
.remove = mmc_bus_remove,
.shutdown = mmc_bus_shutdown,
.pm = &mmc_bus_pm_ops, // 挂在mmc bus上的device的电源管理操作集合
};
/***************************match方法***************************/
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
return 1; // 无条件返回1,说明挂载mmc bus上的device(mmc_card)和driver(mmc_driver)是无条件匹配的。
}
/****************************probe方法***************************/
static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
return drv->probe(card); // 直接调用mmc_driver中的probe操作,对于block.c来说就是mmc_blk_probe
}
通过上述mmc_bus的match方法实现,我们可以知道挂载mmc bus上的mmc_card和mmc_driver是无条件匹配的。
6.host模块
对应代码drivers/mmc/core/host.c,drivers/mmc/core/host.h。
为底层host controller driver实现mmc host的申请以及注册的API等等,以及host相关属性的实现。
1.API总览
1.mmc host分配、注册相关
mmc_alloc_host & mmc_free_host,底层host controller驱动调用,用来分配或者释放一个struct mmc_host结构体,将其于mmc_host_class关联,并且做部分初始化操作。具体为:分配内存空间,初始化其class device(对应/sys/class/mmc0节点),clock gate、锁、工作队列、wakelock、detect工作的初始化,初始化detect成员(也就是检测工作)为mmc_rescan.
参数说明:extra——》mmc_host的私有数据的长度,会和mmc_host结构体一起分配, dev——》底层host controller的device结构体,用于作为mmc_host的device的父设备
/**
* mmc_alloc_host - initialise the per-host structure.
* @extra: sizeof private data structure
* @dev: pointer to host device model structure
*
* Initialise the per-host structure.
*/
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
// 参数说明:extra——》mmc_host的私有数据的长度,会和mmc_host结构体一起分配,
// dev——》底层host controller的device结构体,用于作为mmc_host的device的父设备
int err;
struct mmc_host *host;
/* 分配内存空间,其中多分配了extra字节作为私有数据 */
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
/* scanning will be enabled when we're ready */
/* 因为只是分配了一个mmc_host,host还没有准备好,所以这里禁用rescan,也就是设置mmc_host->rescan_disable
host->rescan_disable = 1; // 在在mmc_start_host中会去使能
/* 为该mmc_host分配一个唯一的id号,设置到host->index */
idr_preload(GFP_KERNEL);
spin_lock(&mmc_host_lock);
err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
if (err >= 0)
host->index = err;
spin_unlock(&mmc_host_lock);
idr_preload_end();
if (err < 0)
goto free;
/* 设置mmc_host name */
dev_set_name(&host->class_dev, "mmc%d", host->index); // 以mmc_host的id号构成mmc_host的name,例如mmc0、mmc1
/* 关联mmc_host class_dev并进行初始化 */
/* class_dev就代表了mmc_host 的device结构体,是其在设备驱动模型中的体现 */
host->parent = dev; // 将mmc_host的parent设置成对应host controller节点转化出来的device
host->class_dev.parent = dev;
// 将mmc_host的device(class_dev)的parent设置成对应host controller节点转化出来的device
// 注册到sysfs之后,会相应生成/sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
// 其中7824900.sdhci表示qcom的host controller节点转化出来的device
host->class_dev.class = &mmc_host_class;
// 将mmc_device(class_dev)的类设置为mmc_host_class
// 注册到sysfs之后,会相应生成/sys/class/mmc_host/mmc0
device_initialize(&host->class_dev); // 初始化mmc_host->class_dev
/* clock gate、锁、工作队列、wakelock、detect工作的初始化 */
mmc_host_clk_init(host);
mutex_init(&host->slot.lock);
host->slot.cd_irq = -EINVAL;
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
host->wlock_name = kasprintf(GFP_KERNEL, "%s_detect", mmc_hostname(host)); // 设置detect_wake_lock的名称为mmc0_detect,在card检测的时候会使用
wake_lock_init(&host->detect_wake_lock, WAKE_LOCK_SUSPEND, host->wlock_name); // // 初始化detect_wake_lock
// 可以通过/sys/kernel/debug/wakeup_sources,相应生成了mmc0_detect和mmc1_detect两个wakelock
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
// !!!!这个很重要!!!!初始化detect工作为mmc_rescan,后续调度host->detect来检测是否有card插入时,就会调用到mmc_rescan。
#ifdef CONFIG_PM
host->pm_notify.notifier_call = mmc_pm_notify;
#endif
/* 一些size的初始化 */
host->max_segs = 1; // 初始化最大支持段(由host自己根据硬件进行修改),可以通过/sys/block/mmcblk0/queue/max_segments进行修改
host->max_seg_size = PAGE_CACHE_SIZE; // 初始化段大小,(由host自己根据硬件进行修改)
host->max_req_size = PAGE_CACHE_SIZE; // 一次MMC请求的最大字节数
host->max_blk_size = 512; // 一个块的最大字节数
host->max_blk_count = PAGE_CACHE_SIZE / 512; // 一次MMC请求的最大块数量
return host;
free:
kfree(host);
return NULL;
}
这边重点强调一个mmc_host->detect=mmc_rescan,当mmc_host的detect work被执行时,就会调用到mmc_rescan中。
相关节点,以mmc_host0为例:
/sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
/sys/class/mmc_host/mmc0
mmc_add_host & mmc_remove_host,底层host controller驱动调用,注册或者卸载mmc_host到设备驱动中,添加到sys类下面,并设置相应的debug目录。然后启动mmc_host。主要工作:使能pm runtime功能,将mmc_host的class_dev添加到设备驱动模型中,在sysfs中生成相应的节点,初始化mmc_host相关的debug目录,设置mmc_host的class_dev的属性,调用mmc_start_host启动host(进入mmc core主模块的部分).
/**
* mmc_add_host - initialise host hardware
* @host: mmc host
*
* Register the host with the driver model. The host must be
* prepared to start servicing requests before this function
* completes.
*/
int mmc_add_host(struct mmc_host *host)
{
int err;
/* 使能mmc host的class_dev的pm runtime功能 */
err = pm_runtime_set_active(&host->class_dev);
if (err)
pr_err("%s: %s: failed setting runtime active: err: %d\n",
mmc_hostname(host), __func__, err);
else if (mmc_use_core_runtime_pm(host))
pm_runtime_enable(&host->class_dev);
/* 通过device_add将mmc_host->class_dev添加到设备驱动模型中,在sys下生成相应节点 */
err = device_add(&host->class_dev);
// 通过mmc_alloc_host中关于mmc_host的class_dev的关联,可以生成如下两个节点
// /sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
// /sys/class/mmc_host/mmc0
/* 使能mmc host的class_dev的异步suspend的功能 */
device_enable_async_suspend(&host->class_dev);
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
/* 设置mmc_host的debug节点 */
#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
// 对应sys节点为/sys/kernel/debug/mmc0
/* 以下设置mmc host的class_dev的属性 */
mmc_host_clk_sysfs_init(host);
// 对应/sys/class/mmc_host/mmc0/clkgate_delay属性
host->clk_scaling.up_threshold = 35;
host->clk_scaling.down_threshold = 5;
host->clk_scaling.polling_delay_ms = 100;
err = sysfs_create_group(&host->class_dev.kobj, &clk_scaling_attr_grp);
// 对应/sys/class/mmc_host/mmc0/clk_scaling目录下的四个属性,clk_scaling_attr_grp前面已经说明过了
err = sysfs_create_group(&host->class_dev.kobj, &dev_attr_grp);
// 对应/sys/class/mmc_host/mmc0/perf属性,dev_attr_grp前面已经说明过了
/* 调用mmc_start_host,也就调用到了mmc core主模块的启动host部分,在mmc core主模块的时候说明 */
mmc_start_host(host);
if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
register_pm_notifier(&host->pm_notify);
return 0;
}
注意,最后调用了mmc_start_host来启动host。关于mmc_start_host会在mmc core主模块的部分里面说明。也就是说,关于host的初始化工作,需要在调用mmc_add_host之前就要完成了。
相关节点:
/sys/bus/platform/devices/7824900.sdhci/mmc_host/mmc0
/sys/class/mmc_host/mmc0
/sys/kernel/debug/mmc0
2.mmc host class相关
mmc_register_host_class & mmc_unregister_host_class,注册或者卸载mmc_host类。
int mmc_register_host_class(void)
{
return class_register(&mmc_host_class); // 以mmc_host_class为class创建一个class,关于mmc_host_class在上述数据结构已经说明过了
}
相关节点:/sys/class/mmc_host
3.mmc host属性解析相关
mmc_of_parse,底层host controller驱动调用,解析mmc_host的dtsi节点的部分属性。mmc_of_parse提供了通用的、解析host controller dtsi节点的属性的方法,这就要依赖于dtsi的属性是否符合规范。但是host controller driver并不一定要使用这个,也可以使用自己一套解析的方法。
void mmc_of_parse(struct mmc_host *host)
{
struct device_node *np;
u32 bus_width;
bool explicit_inv_wp, gpio_inv_wp = false;
enum of_gpio_flags flags;
int len, ret, gpio;
if (!host->parent || !host->parent->of_node)
return;
/* 获取到mmc_host对应的host controller的dts节点 */
np = host->parent->of_node;
// host->parent指向了mmc_host的对应host controller的device,获取其of_node就获取到了对应的dtsi节点
/* 以下就是解析属性,并设置到mmc_host的属性标识caps和caps2 中 */
/* "bus-width" is translated to MMC_CAP_*_BIT_DATA flags */
if (of_property_read_u32(np, "bus-width", &bus_width) < 0) {
dev_dbg(host->parent,
"\"bus-width\" property is missing, assuming 1 bit.\n");
bus_width = 1;
}
switch (bus_width) {
case 8:
host->caps |= MMC_CAP_8_BIT_DATA; // "bus-width"——》MMC_CAP_8_BIT_DATA
/* Hosts capable of 8-bit transfers can also do 4 bits */
case 4:
host->caps |= MMC_CAP_4_BIT_DATA; // "bus-width"——》MMC_CAP_4_BIT_DATA
break;
case 1:
break;
default:
dev_err(host->parent,
"Invalid \"bus-width\" value %ud!\n", bus_width);
}
/* f_max is obtained from the optional "max-frequency" property */
of_property_read_u32(np, "max-frequency", &host->f_max);
//................后面的代码都类似,直接略过了
}
4.mmc host时钟相关
mmc_host_clk_hold & mmc_host_clk_release,mmc core主模块调用,用于获取host时钟和释放host时钟.
2.数据结构
1.mmc_host_class
mmc_host_class代表了mmc_host这个类。其内容如下:
static struct class mmc_host_class = {
.name = "mmc_host", // 添加到sys文件系统之后,会生成/sys/class/mmc_host这个目录
.dev_release = mmc_host_classdev_release, // 从mmc_host这个class下release掉某个设备之后要做的对应操作
.pm = &mmc_host_pm_ops, // 该class下的host的pm电源管理操作
};
static const struct dev_pm_ops mmc_host_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mmc_host_suspend, mmc_host_resume)
SET_RUNTIME_PM_OPS(mmc_host_runtime_suspend, mmc_host_runtime_resume,
pm_generic_runtime_idle)
};
2.clk_scaling_attr_grp
一些和时钟缩放(clk_scaling)相关的属性组
static struct attribute *clk_scaling_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_up_threshold.attr,
&dev_attr_down_threshold.attr,
&dev_attr_polling_interval.attr,
NULL,
};
static struct attribute_group clk_scaling_attr_grp = {
.name = "clk_scaling",
.attrs = clk_scaling_attrs,
};
对应/sys/class/mmc_host/mmc0/clk_scaling目录下的属性
3.dev_attr_grp
和设备相关的属性组,只定义了perf属性
static struct attribute *dev_attrs[] = {
#ifdef CONFIG_MMC_PERF_PROFILING
&dev_attr_perf.attr,
#endif
NULL,
};
static struct attribute_group dev_attr_grp = {
.attrs = dev_attrs,
};
对应/sys/class/mmc_host/mmc0/perf属性
7.card模块
1.API总览
1.mmc type card匹配相关
mmc_attach_mmc提供给mmc core主模块使用,用于绑定card到host bus上(也就是card和host的绑定)。通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。主要工作:设置总线模式;选择一个card和host都支持的最低工作电压,对于不同type的card,相应mmc总线上的操作协议也可能有所不同,所以需要设置相应的总线操作集合(mmc_host->bus_ops;初始化card使其进入工作状态(mmc_init_card)为card构造对应的mmc_card并且注册到mmc_bus中(mmc_add_card,具体参考bus模块说明).
int mmc_attach_mmc(struct mmc_host *host)
{
int err;
u32 ocr;
BUG_ON(!host);
WARN_ON(!host->claimed);
/* Set correct bus mode for MMC before attempting attach */
/* 在尝试匹配之前,先设置正确的总线模式 */
if (!mmc_host_is_spi(host))
mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);
/* 获取card的ocr寄存器 */
err = mmc_send_op_cond(host, 0, &ocr);
// 发送CMD1命令(MMC_SEND_OP_COND),并且参数为0
// 这里获取OCR(Operation condition register)32位的OCR包含卡设备支持的工作电压表,存储到ocr变量中
// 如果Host的IO电压可调整,那调整前需要读取OCR。为了不使卡误进入Inactive State,可以给MMC卡发送不带参数的CMD1,这样可以仅获取OCR寄存器,而不会改变卡的状态。
/* 对于不同type的card,相应mmc总线上的操作协议也可能有所不同 */
/* 所以这里设置mmc_host的总线操作集合,为mmc_ops_unsafe或者mmc_ops,上述已经说明 */
mmc_attach_bus_ops(host);
// 设置host->bus_ops,也就是会为host的bus选择一个操作集,对于non-removable的host来说,这里对应应该为mmc_ops_unsafe
/* 为card选择一个HOST和card都支持的最低电压 */
if (host->ocr_avail_mmc)
host->ocr_avail = host->ocr_avail_mmc; // 选择mmc的可用ocr值作为host的ocr_avail值
if (ocr & 0x7F) {
ocr &= ~0x7F; // 在标准MMC协议中,OCR寄存器的bit6-0位是属于保留位,并不会使用,所以这里对应将其清零
}
host->ocr = mmc_select_voltage(host, ocr); // 通过OCR寄存器选择一个HOST和card都支持的最低电压
/* 调用mmc_init_card初始化该mmc type card,这里是核心函数,后续会继续说明 */
err = mmc_init_card(host, host->ocr, NULL); // 初始化该mmc type card,并为其分配和初始化一个对应的mmc_card
if (err)
goto err;
/* 将分配到的mmc_card注册到mmc_bus中 */
mmc_release_host(host); // 先释放掉host,可能是在mmc_add_card中会获取这个host
err = mmc_add_card(host->card);
// 调用到mmc_add_card,将card注册到设备驱动模型中。
// 这时候该mmc_card就挂在了mmc_bus上,会和mmc_bus上的block这类mmc driver匹配起来。具体再学习mmc card driver的时候再说明。
mmc_claim_host(host); // 再次申请host
if (err)
goto remove_card;
/* clock scaling相关的东西,这里暂时先不关心 */
mmc_init_clk_scaling(host);
register_reboot_notifier(&host->card->reboot_notify);
return 0;
remove_card:
mmc_release_host(host);
mmc_remove_card(host->card);
mmc_claim_host(host);
host->card = NULL;
err:
mmc_detach_bus(host);
pr_err("%s: error %d whilst initialising MMC card\n",
mmc_hostname(host), err);
return err;
}
重点说明
(1)在attach过程中,有一个很重要的函数mmc_init_card;
(2)调用了mmc_add_card之后mmc_card就挂在了mmc_bus上,会和mmc_bus上的block(mmc_driver)匹配起来。相应block(mmc_driver)就会进行probe,驱动card,实现card的实际功能(也就是存储设备的功能)。会对接到块设备子系统中。
2.mmc_init_card
mmc_attach_mmc中的一个核心函数就是mmc_init_card,用于对mmc type card进行实质性的初始化,并为其分配和初始化一个对应的mmc_card。这部分和协议相关,需要先学习一下mmc协议。主要工作:根据协议初始化mmc type card,使其进入相应状态(standby state);为mmc type card构造对应mmc_card并进行设置;从card的csd寄存器以及ext_csd寄存器获取card信息并设置到mmc_card的相应成员中;根据host属性以及一些需求修改ext_csd寄存器的值;设置mmc总线时钟频率以及位宽.
static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
// struct mmc_host *host:该mmc card使用的host
// ocr:表示了host要使用的电压,在mmc_attach_mmc中,已经得到了一个HOST和card都支持的最低电压 struct mmc_card *card;
int err = 0;
u32 cid[4];
u32 rocr;
u8 *ext_csd = NULL;
BUG_ON(!host);
WARN_ON(!host->claimed);
/* Set correct bus mode for MMC before attempting init */
if (!mmc_host_is_spi(host))
mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); // 设置总线模式为开漏模式
/* 根据mmc协议从mmc总线上选中一张card(协议的初始化流程) */
mmc_go_idle(host);
// 发送CMD0指令,GO_IDLE_STATE
// 使mmc card进入idle state。
// 虽然进入到了Idle State,但是上电复位过程并不一定完成了,这主要靠读取OCR的busy位来判断,而流程归结为下一步。
/* The extra bit indicates that we support high capacity */
err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
// 发送CMD1指令,SEND_OP_COND
// 这里会设置card的工作电压寄存器OCR,并且通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。
// 完成之后,mmc card进入ready state。
/*
* Fetch CID from card.
*/
if (mmc_host_is_spi(host))
err = mmc_send_cid(host, cid);
else
err = mmc_all_send_cid(host, cid);
// 这里会发送CMD2指令,ALL_SEND_CID
// 广播指令,使card回复对应的CID寄存器的值。在这里就相应获得了CID寄存器的值了,存储在cid中。
// 完成之后,MMC card会进入Identification State。
if (oldcard) {
。。。
} else {
/* 调用mmc_alloc_card分配一个mmc_card并进行部分设置 */
card = mmc_alloc_card(host, &mmc_type);
// 为card配分一个struct mmc_card结构体并进行初始化,在mmc_type中为mmc定义了大量的属性。
// 具体参考“《mmc core——bus模块说明》——》mmc_alloc_card”
card->type = MMC_TYPE_MMC; // 设置card的type为MMC_TYPE_MMC
card->rca = 1; // 设置card的RCA地址为1
memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); // 将读到的CID存储到card->raw_cid,也就是原始CID值中
card->reboot_notify.notifier_call = mmc_reboot_notify;
host->card = card; // 将mmc_card和mmc_host 进行关联
}
/* 设置card RCA地址 */
if (!mmc_host_is_spi(host)) {
err = mmc_set_relative_addr(card);
// 发送CMD3指令,SET_RELATIVE_ADDR
// 设置该mmc card的关联地址为card->rca,也就是0x0001
// 完成之后,该MMC card进入standby模式。
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
// 设置总线模式为MMC_BUSMODE_PUSHPULL
}
/* 从card的csd寄存器以及ext_csd寄存器获取信息并设置到mmc_card的相应成员中 */
if (!oldcard) {
/*
* Fetch CSD from card.
*/
err = mmc_send_csd(card, card->raw_csd);
// 发送CMD9指令,MMC_SEND_CSD
// 要求mmc card发送csd寄存器,存储到card->raw_csd中,也就是原始的csd寄存器的值。
// 此时mmc card还是处于standby state
err = mmc_decode_csd(card);
// 解析raw_csd,获取到各个bit的值并设置到card->csd中的相应成员上
err = mmc_decode_cid(card);
// 解析raw_cid,获取到各个bit的值并设置到card->cid中的相应成员上
}
/*
* Select card, as all following commands rely on that.
*/
if (!mmc_host_is_spi(host)) {
err = mmc_select_card(card);
// 发送CMD7指令,SELECT/DESELECT CARD
// 选择或者断开指定的card
// 这时卡进入transfer state。后续可以通过各种指令进入到receive-data state或者sending-data state依次来进行数据的传输
}
if (!oldcard) {
err = mmc_get_ext_csd(card, &ext_csd);
// 发送CMD8指令,SEND_EXT_CSD
// 这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中
// 这里会使card进入sending-data state,完成之后又退出到transfer state。
card->cached_ext_csd = ext_csd; // 将ext_csd原始值存储到card->cached_ext_csd,表示用来保存ext_csd的一块缓存,可能还没有和card的ext_csd同步
err = mmc_read_ext_csd(card, ext_csd); // 解析ext_csd的值,获取到各个bit的值并设置到card->ext_csd中的相应成员上
if (!(mmc_card_blockaddr(card)) && (rocr & (1<<30)))
mmc_card_set_blockaddr(card);
/* Erase size depends on CSD and Extended CSD */
mmc_set_erase_size(card); // 设置card的erase_size,扇区里面的擦除字节数,读出来是512K
if (card->ext_csd.sectors && (rocr & MMC_CARD_SECTOR_ADDR))
mmc_card_set_blockaddr(card);
}
/* 根据host属性以及一些需求修改ext_csd寄存器的值 */
/*
* If enhanced_area_en is TRUE, host needs to enable ERASE_GRP_DEF
* bit. This bit will be lost every time after a reset or power off.
*/
if (card->ext_csd.enhanced_area_en ||
(card->ext_csd.rev >= 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) {
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_ERASE_GROUP_DEF, 1,
card->ext_csd.generic_cmd6_time);
// 发送CMD6命令,MMC_SWITCH
// 用于设置ext_csd寄存器的某些bit
// 当enhanced_area_en 被设置的时候,host需要去设置ext_csd寄存器中的EXT_CSD_ERASE_GROUP_DEF位为1
}
if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) {
card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,
card->ext_csd.part_config,
card->ext_csd.part_time);
// 发送CMD6命令,MMC_SWITCH
// 用于设置ext_csd寄存器的某些bit
// 设置ext_csd寄存器中的EXT_CSD_CMD_SET_NORMAL位为EXT_CSD_PART_CONFIG
card->part_curr = card->ext_csd.part_config &
EXT_CSD_PART_CONFIG_ACC_MASK;
}
if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) &&
(card->ext_csd.rev >= 6)) {
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_POWER_OFF_NOTIFICATION,
EXT_CSD_POWER_ON,
card->ext_csd.generic_cmd6_time);
// 发送CMD6命令,MMC_SWITCH
// 用于设置ext_csd寄存器的某些bit
// 设置ext_csd寄存器中的EXT_CSD_POWER_OFF_NOTIFICATION位为EXT_CSD_POWER_ON
}
/* 设置mmc总线时钟频率以及位宽 */
err = mmc_select_bus_speed(card, ext_csd); // 激活host和card都支持的最大总线速度
//.........这里过滤掉一些设置ext_csd的代码
if (!oldcard) {
if (card->ext_csd.bkops_en) {
INIT_DELAYED_WORK(&card->bkops_info.dw,
mmc_start_idle_time_bkops);
// 如果emmc支持bkops的话,就初始化card->bkops_info.dw工作为mmc_start_idle_time_bkops
}
}
return 0;
}
3.mmc_ops_unsafe相关函数
static const struct mmc_bus_ops mmc_ops_unsafe = {
.awake = mmc_awake, // 使mmc总线上的mmc type card退出sleep state
.sleep = mmc_sleep, // 使mmc总线的mmc type card进入sleep state
.remove = mmc_remove, // 释放mmc type card
.detect = mmc_detect, // 检测mmc总线的mmc type card是否拔出
.suspend = mmc_suspend, // suspend掉mmc总线上的mmc type card,注意不仅仅会使card进入sleep state,还会对clock以及mmc cache进行操作
.resume = mmc_resume, // resume上mmc总线上的mmc type card
.power_restore = mmc_power_restore, // 恢复mmc总线上的mmc type card的电源状态
.alive = mmc_alive, // 检测mmc总线上的mmc type card状态是否正常
.change_bus_speed = mmc_change_bus_speed, // 修改mmc总线时钟频率
};
/**********************使mmc总线上的mmc type card退出sleep state************************/
static int mmc_awake(struct mmc_host *host)
{
//...
if (card && card->ext_csd.rev >= 3) { // 判断版本是否大于3
err = mmc_card_sleepawake(host, 0);
// 发送CMD5指令,MMC_SLEEP_AWAKE,参数为0,表示退出sleep state.(如果参数为1就是进入sleep state)
// 完成之后,该MMC card从sleep state进入standby模式。
}
//...
}
/**********************检测mmc总线的mmc type card是否拔出************************/
static void mmc_detect(struct mmc_host *host)
{
int err;
mmc_rpm_hold(host, &host->card->dev);
mmc_claim_host(host);
/* 检测card是否被拔出 */
err = _mmc_detect_card_removed(host);
mmc_release_host(host);
/*
* if detect fails, the device would be removed anyway;
* the rpm framework would mark the device state suspended.
*/
/* card并没有被拔出,说明出现异常了,标记card的rpm状态为suspend */
if (!err)
mmc_rpm_release(host, &host->card->dev);
/* card确实被拔出,正常释放card */
if (err) {
mmc_remove(host);
mmc_claim_host(host);
mmc_detach_bus(host);
mmc_power_off(host);
mmc_release_host(host);
}
}
/********************** 修改mmc总线时钟频率************************/
/**
* mmc_change_bus_speed() - Change MMC card bus frequency at runtime
* @host: pointer to mmc host structure
* @freq: pointer to desired frequency to be set
*
* Change the MMC card bus frequency at runtime after the card is
* initialized. Callers are expected to make sure of the card's
* state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime.
*/
static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq)
{
int err = 0;
struct mmc_card *card;
mmc_claim_host(host);
/*
* Assign card pointer after claiming host to avoid race
* conditions that may arise during removal of the card.
*/
card = host->card;
if (!card || !freq) {
err = -EINVAL;
goto out;
}
/* 确定出一个可用频率 */
if (mmc_card_highspeed(card) || mmc_card_hs200(card)
|| mmc_card_ddr_mode(card)
|| mmc_card_hs400(card)) {
if (*freq > card->ext_csd.hs_max_dtr)
*freq = card->ext_csd.hs_max_dtr;
} else if (*freq > card->csd.max_dtr) {
*freq = card->csd.max_dtr;
}
if (*freq < host->f_min)
*freq = host->f_min;
/* 根据实际要设置的频率值来设置时钟 */
if (mmc_card_hs400(card)) {
err = mmc_set_clock_bus_speed(card, *freq);
if (err)
goto out;
} else {
mmc_set_clock(host, (unsigned int) (*freq));
}
/* 对于hs200来说,修改完频率之后需要执行execute_tuning来选择一个合适的采样点 */
if (mmc_card_hs200(card) && card->host->ops->execute_tuning) {
/*
* We try to probe host driver for tuning for any
* frequency, it is host driver responsibility to
* perform actual tuning only when required.
*/
mmc_host_clk_hold(card->host);
err = card->host->ops->execute_tuning(card->host,
MMC_SEND_TUNING_BLOCK_HS200);
mmc_host_clk_release(card->host);
if (err) {
pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n",
mmc_hostname(card->host), __func__, err,
host->clk_scaling.curr_freq);
mmc_set_clock(host, host->clk_scaling.curr_freq); // 采样失败,设置回原来的时钟频率
}
}
out:
mmc_release_host(host);
return err;
}
4.mmc ops接口说明
mmc_ops提供了部分和mmc type card协议相关操作,这些操作会在mmc.c中mmc的初始化过程中被使用到,这些操作都会发起mmc请求,因此会调用mmc core主模块的mmc请求API.
1.mmc_send_status(典型)
发送CMD13命令,MMC_SEND_STATUS,要求card发送自己当前的状态寄存器.
int mmc_send_status(struct mmc_card *card, u32 *status)
{
int err;
struct mmc_command cmd = {0};
BUG_ON(!card);
BUG_ON(!card->host);
/* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = MMC_SEND_STATUS; // 设置命令操作码opcode,这里设置为MMC_SEND_STATUS,也就是CMD13
if (!mmc_host_is_spi(card->host))
cmd.arg = card->rca << 16; // 设置命令的对应参数,这里设置为card的RCA地址
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; // 设置请求的一些标识,包括命令类型,response类型等等
/* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
/* NOTE: callers are required to understand the difference
* between "native" and SPI format status words!
*/
/* 对response的处理 */
if (status)
*status = cmd.resp[0]; // 依照协议,response[0]存储了status,因此这里提取cmd.resp[0]
return 0;
}
mmc_go_idle、mmc_select_card、mmc_all_send_cid、mmc_set_relative_addr、mmc_send_cxd_native等等的实现方法和其类似。主要差异在于命令的构造区别以及对response的数据的处理。
2.mmc_send_op_cond(特殊)
发送CMD1指令,SEND_OP_COND.在idle状态时,向卡传送Host支持的电压范围,卡回复OCR的值以及上电复位的状态。如果发送的电压参数为0,则卡仅传回OCR的值,并不进行判断。如果发送的电压参数存在,则和卡本身的OCR对比,若不符合,则卡进入Inactive State,符合,则返回OCR寄存器的值。其实和典型的接口类似,但是其特殊之处在于需要通过busy位(bit31)来判断card的上电复位过程是否完成,如果没有完成的话需要重复发送。
int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd = {0};
int i, err = 0;
BUG_ON(!host);
/* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = MMC_SEND_OP_COND;
cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
/* 需要判断status的busy(bit31)来判断上电复位是否完成,如果没有完成的话需要重复发送。 */
for (i = 100; i; i--) {
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
break;
/* if we're just probing, do a single pass */
if (ocr == 0) // ocr为0,说明只是读取ocr寄存器的值,不进行判断
break;
/* otherwise wait until reset completes */
if (mmc_host_is_spi(host)) {
if (!(cmd.resp[0] & R1_SPI_IDLE))
break;
} else {
if (cmd.resp[0] & MMC_CARD_BUSY)
break;
// 如果发送的电压参数存在,则和卡本身的OCR对比,若不符合,则卡进入Inactive State,符合,则返回OCR寄存器的值。
// 同时,需要判断OCR寄存器的busy位来判断上电复位是否完成。
}
err = -ETIMEDOUT;
mmc_delay(10);
}
if (rocr && !mmc_host_is_spi(host))
*rocr = cmd.resp[0];
return err;
}
3.mmc_send_ext_csd
发送CMD8指令,SEND_EXT_CSD.这里要求处于transfer state的card发送ext_csd寄存器,这里获取之后存放在ext_csd寄存器中.这里会使card进入sending-data state,完成之后又退出到transfer state。特殊之处在于涉及到了DATA线上的数据传输,会调用到内部接口mmc_send_cxd_data。
int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd)
{
return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
ext_csd, 512);
}
static int
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
u32 opcode, void *buf, unsigned len)
{
struct mmc_request mrq = {NULL};
struct mmc_command cmd = {0};
struct mmc_data data = {0};
struct scatterlist sg;
void *data_buf;
int is_on_stack;
is_on_stack = object_is_on_stack(buf);
if (is_on_stack) {
/*
* dma onto stack is unsafe/nonportable, but callers to this
* routine normally provide temporary on-stack buffers ...
*/
data_buf = kmalloc(len, GFP_KERNEL);
if (!data_buf)
return -ENOMEM;
} else
data_buf = buf;
/* 因为涉及到了data线上的数据传输,需要构造mmc_request请求 */
mrq.cmd = &cmd; // 设置mmc_request请求中的命令包
mrq.data = &data; // 设置mmc_request请求中的数据包
/* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = opcode;
cmd.arg = 0;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
/* 主要是根据对应命令的数据包来构造struct mmc_data */
data.blksz = len;
data.blocks = 1;
data.flags = MMC_DATA_READ;
data.sg = &sg;
data.sg_len = 1;
sg_init_one(&sg, data_buf, len);
if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
data.timeout_ns = 0;
data.timeout_clks = 64;
} else
mmc_set_data_timeout(&data, card);
/* 发起mmc请求并且等待mmc_request处理完成 */
mmc_wait_for_req(host, &mrq);
if (is_on_stack) {
memcpy(buf, data_buf, len);
kfree(data_buf);
}
if (cmd.error)
return cmd.error;
if (data.error)
return data.error;
return 0;
}
4.mmc_switch
发送CMD6命令,MMC_SWITCH.用于设置ext_csd寄存器的某些bit。特殊之处在于:在__mmc_switch中会发起CMD6命令,会导致card进入programming state,因此,在__mmc_switch中必须去获取card的status,直到card退出programming state。这部分就是通过CMD13来实现的。
int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
unsigned int timeout_ms)
{
return __mmc_switch(card, set, index, value, timeout_ms, true, false);
}
int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
unsigned int timeout_ms, bool use_busy_signal,
bool ignore_timeout)
{
int err;
struct mmc_command cmd = {0};
unsigned long timeout;
u32 status;
BUG_ON(!card);
BUG_ON(!card->host);
/* 主要是根据对应命令构造struct mmc_command */
cmd.opcode = MMC_SWITCH;
cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
(index << 16) |
(value << 8) |
set;
cmd.flags = MMC_CMD_AC;
if (use_busy_signal)
cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B;
else
cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1;
cmd.cmd_timeout_ms = timeout_ms;
cmd.ignore_timeout = ignore_timeout;
/* 调用mmc_wait_for_cmd发送命令请求并且等待命令处理完成。 */
err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
if (err)
return err;
/* No need to check card status in case of unblocking command */
if (!use_busy_signal)
return 0;
/* 调用mmc_send_status发送CMD13获取card status,等待card退出programming state。 */
timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
do {
err = mmc_send_status(card, &status);
if (err)
return err;
if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
break;
if (mmc_host_is_spi(card->host))
break;
/* Timeout if the device never leaves the program state. */
if (time_after(jiffies, timeout)) {
pr_err("%s: Card stuck in programming state! %s\n",
mmc_hostname(card->host), __func__);
return -ETIMEDOUT;
}
} while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
if (mmc_host_is_spi(card->host)) {
if (status & R1_SPI_ILLEGAL_COMMAND)
return -EBADMSG;
} else {
if (status & 0xFDFFA000)
pr_warning("%s: unexpected status %#x after "
"switch", mmc_hostname(card->host), status);
if (status & R1_SWITCH_ERROR)
return -EBADMSG;
}
return 0;
}
2.数据结构
1.mmc_ops & mmc_ops_unsafe
struct mmc_bus_ops表示mmc host在总线上的操作集合,由host的card 设备来决定,mmc type card、sd type card相应的操作集合是不一样的。mmc_ops和mmc_ops_unsafe则表示mmc type card所属的host对于总线的操作集合。
static const struct mmc_bus_ops mmc_ops = {
.awake = mmc_awake,
.sleep = mmc_sleep,
.remove = mmc_remove,
.detect = mmc_detect,
.suspend = NULL,
.resume = NULL,
.power_restore = mmc_power_restore,
.alive = mmc_alive,
.change_bus_speed = mmc_change_bus_speed,
};
static const struct mmc_bus_ops mmc_ops_unsafe = {
.awake = mmc_awake, // 使mmc总线上的mmc type card退出sleep state
.sleep = mmc_sleep, // 使mmc总线的mmc type card进入sleep state
.remove = mmc_remove, // 释放mmc type card
.detect = mmc_detect, // 检测mmc总线的mmc type card是否拔出
.suspend = mmc_suspend, // suspend掉mmc总线上的mmc type card,注意不仅仅会使card进入sleep state,还会对clock以及mmc cache进行操作
.resume = mmc_resume, // resume上mmc总线上的mmc type card
.power_restore = mmc_power_restore, // 恢复mmc总线上的mmc type card的电源状态
.alive = mmc_alive, // 检测mmc总线上的mmc type card状态是否正常
.change_bus_speed = mmc_change_bus_speed, // 修改mmc总线时钟频率
};
mmc_ops_unsafe和mmc_ops的区别在于是否实现suspend和resume方法。对于card不可移除的host来说,需要使用mmc_ops_unsafe这个mmc_bus_ops来支持suspend和resume。之所以在上述注释中不断说明mmc总线,是为了强调应该和mmc_bus虚拟总线区分开来,这里的mmc总线是物理概念、是和host controller直接相关联的。
2.mmc_type
struct device_type mmc_type中为mmc_card定义了很多属性,可以在sysfs中进行查看
/sys/class/mmc_host/mmc0/mmc0:0001
或者/sys/bus/mmc/devices/mmc0:0001下可以查看到如下属性
block cid csd date driver enhanced_area_offset enhanced_area_size erase_size fwrev hwrev
manfid name oemid power preferred_erase_size prv raw_rpmb_size_mult rel_sectors
runtime_pm_timeout serial subsystem type uevent
mmc_type对应实现如下:
static struct device_type mmc_type = {
.groups = mmc_attr_groups,
};
static const struct attribute_group *mmc_attr_groups[] = {
&mmc_std_attr_group,
NULL,
};
static struct attribute_group mmc_std_attr_group = {
.attrs = mmc_std_attrs,
};
MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
card->raw_cid[2], card->raw_cid[3]);
MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
card->raw_csd[2], card->raw_csd[3]);
//...................略过一些
MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult);
MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors);
static struct attribute *mmc_std_attrs[] = {
&dev_attr_cid.attr,
&dev_attr_csd.attr,
&dev_attr_date.attr,
&dev_attr_erase_size.attr,
&dev_attr_preferred_erase_size.attr,
&dev_attr_fwrev.attr,
&dev_attr_hwrev.attr,
//.....................略过一些
&dev_attr_rel_sectors.attr,
NULL,
};
8.core模块
mmc core主模块是mmc core的实现核心,对应代码位置drivers/mmc/core/core.c
其主要负责如下功能:
-
mmc core初始化,包括注册mmc bus、mm host class等等
-
mmc host的管理和维护,包括为其他模块提供mmc_host的操作接口,如下
- host的启动和停止
- host的占用和释放
- host电源状态的保存和恢复
- host总线操作集的绑定和解绑
- host上卡状态检测
-
为其他模块提供mmc_card的操作接口,如下
- card的唤醒和休眠
- card擦除
- card属性的获取
-
为其他模块提供总线io setting的接口
-
为其他模块提供mmc请求接口
-
card检测接口
-
bkops操作接口
-
regulator操作接口
-
clock操作接口
-
mmc core电源管理操作接口
1.API总览
1.mmc core初始化相关
- mmc_init & mmc_exit (模块内使用)
2.mmc host的管理和维护相关
mmc_claim_host & mmc_try_claim_host & mmc_release_host (模块内使用)
mmc_power_up & mmc_power_off
mmc_start_host & mmc_stop_host
mmc_power_save_host & mmc_power_restore_host
mmc_resume_host & mmc_suspend_host
mmc_pm_notify
3.mmc card的操作相关(包括card状态的获取)
mmc_hw_reset & mmc_hw_reset_check &
mmc_card_awake & mmc_card_sleep
mmc_card_is_prog_state
mmc_can_erase
mmc_can_trim
mmc_can_discard
mmc_can_sanitize
mmc_can_secure_erase_trim
mmc_erase_group_aligned
4.总线io setting相关
mmc_set_ios
mmc_set_chip_select
mmc_set_clock
mmc_set_bus_mode
mmc_set_bus_width
mmc_select_voltage
mmc_set_signal_voltage(特殊)
mmc_set_timing
mmc_set_driver_typemmc_get_max_frequency & mmc_get_min_frequency
5.host的mmc总线相关
- mmc_resume_bus
- mmc_attach_bus & mmc_detach_bus
6.mmc请求相关
- mmc_request_done
- mmc_wait_for_req
- mmc_wait_for_cmd
- mmc_set_data_timeout
- mmc_align_data_size
7.card检测相关
- mmc_detect_change
- mmc_rescan
- mmc_detect_card_removed
8.bkops操作相关
- mmc_blk_init_bkops_statistics
- mmc_start_delayed_bkops
- mmc_start_bkops & mmc_stop_bkops
- mmc_start_idle_time_bkops
- mmc_read_bkops_status
9.regulator操作相关
- mmc_regulator_get_ocrmask
- mmc_regulator_set_ocr
- mmc_regulator_get_supply
10.card擦除操作相关
- mmc_init_erase
- mmc_erase
11.clock操作接口
- mmc_init_clk_scaling & mmc_exit_clk_scaling
- mmc_can_scale_clk
- mmc_disable_clk_scaling
12.mmc core电源管理操作
- mmc_rpm_hold & mmc_rpm_release
2.函数详解
1.mmc core初始化相关
1.mmc_init实现
负责初始化整个mmc core。
- 主要工作:
- 分配一个workqueue,用于专门处理mmc core的执行的工作
- 注册mmc bus
- 注册mmc host class
static int __init mmc_init(void)
{
int ret;
/* 分配一个workqueue,用于专门处理mmc core的执行的工作 */
workqueue = alloc_ordered_workqueue("kmmcd", 0);
/* 注册mmc bus */
ret = mmc_register_bus(); // 调用mmc_register_bus注册mmc bus,具体参考《mmc core——bus模块说明》
// 会生成/sys/bus/mmc目录
/* 注册mmc host class */
ret = mmc_register_host_class(); // 调用mmc_register_host_class注册mmc host class,具体参考《mmc core——host模块说明》
// 会生成/sys/class/mmc_host目录
/* 注册sdio bus */
ret = sdio_register_bus();
return 0;
}
subsys_initcall(mmc_init);
2.mmc host的管理和维护相关
1.mmc_claim_host & mmc_try_claim_host & mmc_release_host
host被使能之后就不能再次被使能,并且只能被某个进程独自占用。可以简单地将host理解为一种资源,同时只能被一个进程获取,但是在占用进程里面可以重复占用。在对host进行操作之前(包括发起mmc请求),必须使用mmc_claim_host和mmc_release_host来进行获取和释放。
- 变量说明
- mmc_host->claimed用来表示host是否被占用
- mmc_host->claimer用来表示host的占用者(进程)
- mmc_host->claim_cnt用来表示host的占用者的占用计数,为0时则会释放这个host
static inline void mmc_claim_host(struct mmc_host *host)
{
__mmc_claim_host(host, NULL);·// 调用__mmc_claim_host来获取host
}
int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
{
/只考虑abort为NULL的情况,在mmc core中的mmc_claim_host也是将其设置为NULL
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
int stop;
might_sleep(); // 说明这个函数可能导致进程休眠
add_wait_queue(&host->wq, &wait); // 把当前进程加入到等待队列中
spin_lock_irqsave(&host->lock, flags);
while (1) { // 以下尝试获取host,如果host正在被占用,会进入休眠
set_current_state(TASK_UNINTERRUPTIBLE); // 设置进程状态为TASK_UNINTERRUPTIBLE状态
stop = abort ? atomic_read(abort) : 0;
if (stop || !host->claimed || host->claimer == current) // 当host的占用标志claimed为0,或者占用者是当前进程的时候,说明可以占用了,退出
break;
spin_unlock_irqrestore(&host->lock, flags);
schedule(); // 否则,进行调度进入休眠
spin_lock_irqsave(&host->lock, flags);
}
set_current_state(TASK_RUNNING); // 设置进程为运行状态
if (!stop) {
host->claimed = 1; // 设置占用标志claimed
host->claimer = current; // 设置占用者为当前进程
host->claim_cnt += 1; // 占用计数加1
} else
wake_up(&host->wq);
spin_unlock_irqrestore(&host->lock, flags);
remove_wait_queue(&host->wq, &wait); // 将当前进程从等待队列中退出
if (host->ops->enable && !stop && host->claim_cnt == 1)
host->ops->enable(host); // 调用host操作集中的enable方法来占用该host,对应sdhci类host即为sdhci_enable
return stop;
}
void mmc_release_host(struct mmc_host *host)
{
unsigned long flags;
WARN_ON(!host->claimed);
if (host->ops->disable && host->claim_cnt == 1) // 当前claim_cnt为1(马上要变为0),调用释放host了
host->ops->disable(host); // 调用host操作集中的disable方法来释放该host,对应sdhci类host即为sdhci_disable
spin_lock_irqsave(&host->lock, flags);
if (--host->claim_cnt) {
/* Release for nested claim */
spin_unlock_irqrestore(&host->lock, flags); // 如果减一之后计数还不为0,说明当前进程需要继续占用该host,不做其他操作
} else { // 以下需要释放该host
host->claimed = 0; // 设置占用标志claimed为0
host->claimer = NULL; // 清空占用者(进程)
spin_unlock_irqrestore(&host->lock, flags);
wake_up(&host->wq); // 唤醒host的等待队列,让那些调用mmc_claim_host睡眠等待host资源的进程被唤醒
}
}
int mmc_try_claim_host(struct mmc_host *host)
{
// 和mmc_claim_host的主要区别在于进程不会休眠,获取失败直接返回
int claimed_host = 0;
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
if (!host->claimed || host->claimer == current) {
host->claimed = 1;
host->claimer = current;
host->claim_cnt += 1;
claimed_host = 1;
}
spin_unlock_irqrestore(&host->lock, flags);
if (host->ops->enable && claimed_host && host->claim_cnt == 1)
host->ops->enable(host);
return claimed_host;
}
会调用mmc_host->struct mmc_host_ops->enable和mmc_host->struct mmc_host_ops->disable来使能和禁用host。对于sdhci类的host,相应就是sdhci_enable和sdhci_disable。
2.mmc_power_up & mmc_power_off
mmc host的上电操作和关电操作。
-
mmc的power状态
- MMC_POWER_OFF:掉电状态
- MMC_POWER_UP:正在上电的状态
- MMC_POWER_ON:供电正常的状态
-
主要工作
主要工作就是初始化host的总线设置、总线时钟以及工作电压、信号电压。
void mmc_power_up(struct mmc_host *host)
{
int bit;
/* 判断是否已经处于MMC_POWER_ON,是的话不进行后续操作 */
if (host->ios.power_mode == MMC_POWER_ON)
return;
/* 第一阶段,先设置对应的io setting使host处于MMC_POWER_UP的状态(总线工作频率没有设置) */
mmc_host_clk_hold(host); // 先获取host时钟
/* If ocr is set, we use it */
if (host->ocr)
bit = ffs(host->ocr) - 1; // 选择一个ocr配置设置为host的工作电压
else
bit = fls(host->ocr_avail) - 1;
host->ios.vdd = bit;
if (mmc_host_is_spi(host))
host->ios.chip_select = MMC_CS_HIGH;
else {
host->ios.chip_select = MMC_CS_DONTCARE;
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; // 设置总线模式
}
host->ios.power_mode = MMC_POWER_UP;
host->ios.bus_width = MMC_BUS_WIDTH_1; // 设置总线宽度为1
host->ios.timing = MMC_TIMING_LEGACY; // 串口时序
mmc_set_ios(host); // 调用mmc_set_ios设置总线的io setting,后面会说明
/*
* This delay should be sufficient to allow the power supply
* to reach the minimum voltage.
*/
mmc_delay(10);
/* 第二阶段,以host的初始化工作频率再次设置io setting,使host处于MMC_POWER_ON状态 */
host->ios.clock = host->f_init; // 设置总线的时钟频率
host->ios.power_mode = MMC_POWER_ON;
mmc_set_ios(host); // 调用mmc_set_ios设置总线的io setting,后面会说明
/*
* This delay must be at least 74 clock sizes, or 1 ms, or the
* time required to reach a stable voltage.
*/
mmc_delay(10);
/* 设置信号的电压 */
/* Set signal voltage to 3.3V */
__mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330);
mmc_host_clk_release(host); // 释放host时钟
}
3.mmc_start_host & mmc_stop_host
mmc_start_host 用来启动一个host,mmc_stop_host用来停止一个host。当底层host controller调用mmc_add_host来注册host时,在mmc_add_host中就会调用mmc_start_host来启动一个host了.相对应的,会在mmc_remove_host中调用mmc_stop_host停止host。
void mmc_start_host(struct mmc_host *host)
{
mmc_claim_host(host); // 因为上电操作涉及到对host的使用和设置,需要先占用host
host->f_init = max(freqs[0], host->f_min); // 通过最小频率要设置初始化频率
host->rescan_disable = 0; // 设置rescan_disable标志为0,说明已经可以进行card检测了
if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP) // 如果mmc属性设置了MMC_CAP2_NO_PRESCAN_POWERUP,也就是在rescan前不需要进行power up操作时,则进行关电
mmc_power_off(host);
else
mmc_power_up(host); // 否则,调用mmc_power_up对host进行上电操作。这里也是mmc core中启动host的核心函数。
mmc_release_host(host); // 完成上电操作,释放host
/* 到这里host已经可以工作了,可以开始进行后续的card操作了 */
mmc_detect_change(host, 0); // 调用mmc_detect_change检测card变化,后续会继续说明
}
3.card检测相关
1.mmc_detect_change
在上述中我们知道在启动host的函数mmc_start_host 中最后调用了mmc_detect_change来开始检测card(也就是检测mmc卡槽的状态变化情况)。其实mmc_detect_change是在driver发现mmc卡槽状态发生变化时,调用mmc_detect_change来进行确认和处理。
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
host->detect_change = 1; // 检测到card状态发生变化的标识
mmc_schedule_delayed_work(&host->detect, delay); // 间隔delay jiffies之后调用host->detect的工作
}
在《host模块说明》已经知道了在mmc_alloc_host中默认将host->detect工作设置为mmc_rescan(card重新扫描)函数,INIT_DELAYED_WORK(&host->detect, mmc_rescan)。当然,host也可以自己另外设置,但是一般都是使用mmc core提供的mmc_rescan作为detect工作来搜索card。下面说明。
2.mmc_rescan
用于检测host的卡槽状态,并对状态变化做相应的操作。有card插入时,重新扫描mmc card。
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
bool extend_wakelock = false;
/* 如果rescan_disable被设置,说明host此时还禁止rescan */
if (host->rescan_disable)
return;
/* 对于设备不可移除的host来说,只能rescan一次 */
/* If there is a non-removable card registered, only scan once */
if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)
return;
host->rescan_entered = 1;
mmc_bus_get(host); // 获取host对应的bus
mmc_rpm_hold(host, &host->class_dev); // 使host处于rpm resume的状态
/* 以下判断原来的card是否已经被移除,移除了则需要做相应的操作 */
/*
* if there is a _removable_ card registered, check whether it is
* still present
*/
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
&& !(host->caps & MMC_CAP_NONREMOVABLE))
host->bus_ops->detect(host);
// host->bus_ops存在的话说明之前是有card插入的状态
// 需要调用host->bus_ops->detect检测card是否被移除,是的话在host->bus_ops->detect中做相应的处理
// 对于mmc type card来说,对应就是mmc_detect,具体参考《card相关模块》
host->detect_change = 0;
/* If the card was removed the bus will be marked
* as dead - extend the wakelock so userspace
* can respond */
if (host->bus_dead)
extend_wakelock = 1; // 需要设置一个wakelock锁,使用户空间可以及时做出相应
/*
* Let mmc_bus_put() free the bus/bus_ops if we've found that
* the card is no longer present.
*/
mmc_bus_put(host);
// 因为在这个函数的前面已经获取了一次host,可能导致host->bus_ops->detect中检测到card拔出之后,没有真正释放到host的bus,所以这里先put一次
// host bus的计数(bus_refs)为0的时候,会调用__mmc_release_bus清空host bus的信息
mmc_bus_get(host);
// 再获取host bus
/* if there still is a card present, stop here */
if (host->bus_ops != NULL) { // 说明此时还有card插入,退出后续的操作
mmc_rpm_release(host, &host->class_dev);
mmc_bus_put(host);
goto out;
}
mmc_rpm_release(host, &host->class_dev);
/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);
/* 检测当前卡槽状态,根据卡槽状态做相应的操作 */
if (host->ops->get_cd && host->ops->get_cd(host) == 0) {
// 调用host->ops->get_cd来判断host的卡槽的当前card插入状态
// 对应sdhci类型的host来说,就是sdhci_get_cd
// 为0的时候,表示没有card插入,对应host进行power off操作之后进行退出
// 为1的时候,表示当前有card插入,跳到后续的操作
mmc_claim_host(host);
mmc_power_off(host);
mmc_release_host(host);
goto out;
}
mmc_rpm_hold(host, &host->class_dev);
mmc_claim_host(host);
if (!mmc_rescan_try_freq(host, host->f_min)) // 调用mmc_rescan_try_freq,以支持的最低频率作为工作频率尝试搜索card,后续继续说明
extend_wakelock = true;
mmc_release_host(host);
mmc_rpm_release(host, &host->class_dev);
out:
/* only extend the wakelock, if suspend has not started yet */
if (extend_wakelock && !host->rescan_disable)
wake_lock_timeout(&host->detect_wake_lock, HZ / 2); // 占用wakelock,使系统在HZ/2的时间内不会休眠
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
// 当host设置了MMC_CAP_NEEDS_POLL属性时,需要每隔HZ的时间轮询检测host的卡槽状态,
// 调度了host->detect工作,对应就是mmc_rescan
}
会调用host->ops->get_cd来判断host的卡槽的当前card插入状态,对应sdhci类型的host就是sdhci_get_cd。会调用host->bus_ops->detect检测card是否被移除,是的话在host->bus_ops->detect中做相应的处理。对于mmc type card,就是mmc_detect。
3.mmc_rescan_try_freq
以一定频率搜索host bus上的card
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
mmc_power_up(host); // 给host做上电操作
mmc_hw_reset_for_init(host); // 硬件复位和初始化
mmc_go_idle(host);
mmc_send_if_cond(host, host->ocr_avail); // 获取card的可用频率,存储到host->ocr_avail中
/* Order's important: probe SDIO, then SD, then MMC */
/* 用于绑定card到host bus上(也就是card和host的绑定)。 */
if (!mmc_attach_sdio(host)) // 先假设card是sdio type card,尝试绑定到host bus上,失败则说明不是sdio type card,继续后面的操作,否则返回
return 0;
if (!mmc_attach_sd(host)) // 先假设card是sd type card,尝试绑定到host bus上,失败则说明不是sd type card,继续后面的操作,否则返回
return 0;
if (!mmc_attach_mmc(host)) // 先假设card是mmc type card,尝试绑定到host bus上,失败则说明不是mmc type card,继续后面的操作,否则返回
// mmc_attach_mmc通过mmc_host获取mmc type card信息,初始化mmc_card,并进行部分驱动,最后将其注册到mmc_bus上。
// 具体参考《card相关模块说明》
return 0;
mmc_power_off(host);
return -EIO;
}
4.总线io setting相关
0.mmc_ios说明
struct mmc_ios 由mmc core定义的规范的结构,用来维护mmc总线相关的一些io setting。如下:
struct mmc_ios {
unsigned int clock; /* clock rate */ // 当前工作频率
unsigned int old_rate; /* saved clock rate */ // 上一次的工作频率
unsigned long clk_ts; /* time stamp of last updated clock */ // 上一次更新工作频率的时间戳
unsigned short vdd;/* vdd stores the bit number of the selected voltage range from below. */ // 支持的电压表
unsigned char bus_mode; /* command output mode */ // 总线输出模式,包括开漏模式和上拉模式
unsigned char chip_select; /* SPI chip select */ // spi片选
unsigned char power_mode; /* power supply mode */ // 电源状态模式
unsigned char bus_width; /* data bus width */ // 总线宽度
unsigned char timing; /* timing specification used */ // 时序类型
unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */ // 信号的工作电压
unsigned char drv_type; /* driver type (A, B, C, D) */ // 驱动类型
};
在设置总线io setting的过程中,就是要设置mmc_host->mmc_ios中的这些成员。然后通过调用mmc_set_ios进行统一设置.
1.mmc_set_ios
统一设置mmc总线的io设置(io setting)。
void mmc_set_ios(struct mmc_host *host)
{
struct mmc_ios *ios = &host->ios;
if (ios->clock > 0)
mmc_set_ungated(host); // 关闭clock的门控
host->ops->set_ios(host, ios); // 调用host->ops->set_ios来对mmc总线的io setting进行设置,核心函数
// 对于sdhci类型的host,对应就是sdhci_set_ios
}
会调用host->ops->set_ios来对mmc总线的io setting进行设置,核心函数。对于sdhci类型的host,对应就是sdhci_set_ios.
2.mmc_set_bus_mode & mmc_set_bus_width
mmc_set_bus_mode:
用于设置总线模式,有如下模式:MMC_BUSMODE_OPENDRAIN(开漏模式),MMC_BUSMODE_PUSHPULL(上拉模式)
mmc_set_bus_width:
用于设置总线宽度,有如下模式:MMC_BUS_WIDTH_1,MMC_BUS_WIDTH_4,MMC_BUS_WIDTH_8
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
{
mmc_host_clk_hold(host);
host->ios.bus_mode = mode;
mmc_set_ios(host);
mmc_host_clk_release(host);
}
void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
{
mmc_host_clk_hold(host);
host->ios.bus_width = width;
mmc_set_ios(host);
mmc_host_clk_release(host);
5.host的mmc总线相关
1.mmc_attach_bus & mmc_detach_bus
-
主要功能
- mmc_attach_bus用于将分配一个mmc总线操作集给host。
- mmc_detach_bus用于释放和host相关联的mmc总线操作集。
-
一些变量
- mmc_host->bus_ops,表示host的mmc总线操作集
- mmc_host->bus_refs,表示host的mmc总线的使用者计数
- mmc_host->bus_dead,表示host的mmc总线是否被激活,如果设置了bus_ops,那么就会被激活了
oid mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
BUG_ON(host->bus_ops); // 不允许重复设置host的mmc总线操作集
BUG_ON(host->bus_refs); // 当mmc总线的使用者计数还存在时,不允许设置host的mmc总线操作集
host->bus_ops = ops; // 设置host的mmc总线操作集
host->bus_refs = 1; // host的mmc总线的使用者计数设置为1,相当于调用了mmc_bus_get
host->bus_dead = 0; // 总线被激活了
spin_unlock_irqrestore(&host->lock, flags);
}
void mmc_detach_bus(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->bus_dead = 1; // host的mmc总线设置为dead状态
spin_unlock_irqrestore(&host->lock, flags);
mmc_bus_put(host); // 调用mmc_bus_put释放host的mmc总线,也就是对host的mmc总线的使用者计数-1
}
在card模块中可以看到mmc_attach_mmc->mmc_attach_bus_ops调用mmc_attach_bus来绑定了host的mmc总线操作集为mmc_ops_unsafe或者mmc_ops
2.mmc_bus_get & mmc_bus_put
static inline void mmc_bus_get(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->bus_refs++; // 对host的mmc总线的使用者计数+1
spin_unlock_irqrestore(&host->lock, flags);
}
static inline void mmc_bus_put(struct mmc_host *host)
{
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
host->bus_refs--; // 对host的mmc总线的使用者计数-1
if ((host->bus_refs == 0) && host->bus_ops) // 说明host的mmc总线当前并没有使用,调用__mmc_release_bus进行实际的释放操作
__mmc_release_bus(host);
spin_unlock_irqrestore(&host->lock, flags);
}
static void __mmc_release_bus(struct mmc_host *host)
{
host->bus_ops = NULL; // 清空host的mmc总线操作集
}
6.mmc请求相关
分成同步的mmc请求和异步的mmc请求。差别如下:
1、流程上的差别:
(1)会阻塞的处理流程:
mmc_wait_for_req
——》__mmc_start_req // 发起请求
————》init_completion(&mrq->completion);
————》mrq->done = mmc_wait_done
————》mmc_start_request(host, mrq); // 实际发起请求的操作
——》mmc_wait_for_req_done // 阻塞等待请求处理完成
——》返回
(2)不阻塞等待该命令的处理流程:
(注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞)
mmc_start_req
——》mmc_wait_for_data_req_done // 阻塞等待上一次的请求处理
——》__mmc_start_data_req // 发起异步请求
————》mrq->done = mmc_wait_data_done
————》mmc_start_request // 实际发起请求的操作
——》返回
最后都是调用了mmc_start_request使host向MMC发起请求。
0.数据结构说明
一个mmc请求分成两部分内容,分别是命令部分和数据部分。
- mmc_command
struct mmc_command {
u32 opcode; // 命令的操作码,如MMC_GO_IDLE_STATE、MMC_SEND_OP_COND等等
u32 arg; // 命令的参数
u32 resp[4]; // response值
unsigned int flags; /* expected response type */ // 期待的response的类型
#define mmc_resp_type(cmd) ((cmd)->flags & (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC|MMC_RSP_BUSY|MMC_RSP_OPCODE))
/*
* These are the command types.
*/
#define mmc_cmd_type(cmd) ((cmd)->flags & MMC_CMD_MASK)
unsigned int retries; /* max number of retries */ // 失败时的重复尝试次数
unsigned int error; /* command error */ // 命令的错误码
/*
* Standard errno values are used for errors, but some have specific
* meaning in the MMC layer:
*
* ETIMEDOUT Card took too long to respond
* EILSEQ Basic format problem with the received or sent data
* (e.g. CRC check failed, incorrect opcode in response
* or bad end bit)
* EINVAL Request cannot be performed because of restrictions
* in hardware and/or the driver
* ENOMEDIUM Host can determine that the slot is empty and is
* actively failing requests
*/
unsigned int cmd_timeout_ms; /* in milliseconds */ // 命令执行的等待超时事件
struct mmc_data *data; /* data segment associated with cmd */ // 和该命令关联在一起的数据段
struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
};
- mmc_data
struct mmc_data {
unsigned int timeout_ns; /* data timeout (in ns, max 80ms) */ // 超时时间,以ns为单位
unsigned int timeout_clks; /* data timeout (in clocks) */ // 超时时间,以clock为单位
unsigned int blksz; /* data block size */ // 块大小
unsigned int blocks; /* number of blocks */ // 块数量
unsigned int error; /* data error */ // 传输的错误码
unsigned int flags; // 传输标识
#define MMC_DATA_WRITE (1 << 8)
#define MMC_DATA_READ (1 << 9)
#define MMC_DATA_STREAM (1 << 10)
unsigned int bytes_xfered;
struct mmc_command *stop; /* stop command */ // 结束传输的命令
struct mmc_request *mrq; /* associated request */ // 该命令关联到哪个request
unsigned int sg_len; /* size of scatter list */
struct scatterlist *sg; /* I/O scatter list */
s32 host_cookie; /* host private data */
bool fault_injected; /* fault injected */
};
- mmc_request
struct mmc_request是mmc core向host controller发起命令请求的处理单位。
struct mmc_request {
struct mmc_command *sbc; /* SET_BLOCK_COUNT for multiblock */ // 设置块数量的命令,怎么用的后续再补充
struct mmc_command *cmd; // 要传输的命令
struct mmc_data *data; // 要传输的数据
struct mmc_command *stop; // 结束命令,怎么用的后续再补充
struct completion completion; // 完成量
void (*done)(struct mmc_request *);/* completion function */ // 传输结束后的回调函数
struct mmc_host *host; // 所属host
};
- mmc_async_req
struct mmc_async_req {
/* active mmc request */
struct mmc_request *mrq;
unsigned int cmd_flags; /* copied from struct request */
/*
* Check error status of completed mmc request.
* Returns 0 if success otherwise non zero.
*/
int (*err_check) (struct mmc_card *, struct mmc_async_req *);
/* Reinserts request back to the block layer */
void (*reinsert_req) (struct mmc_async_req *);
/* update what part of request is not done (packed_fail_idx) */
int (*update_interrupted_req) (struct mmc_card *,
struct mmc_async_req *);
};
1.mmc_wait_for_req
发起mmc_request请求并且等待其处理完成。由其他需要发起mmc请求的模块调用。可以结合后面的mmc_request_done来看。
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
{
#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
if (mmc_bus_needs_resume(host))
mmc_resume_bus(host);
#endif
__mmc_start_req(host, mrq); // 开始发起mmc_request请求
mmc_wait_for_req_done(host, mrq); // 等待mmc_request处理完成
}
//-----------------------------------__mmc_start_req说明,开始发起mmc_request请求
static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
{
/* 发起mmc_request前的一些初始化工作,包括完成量和处理完成的回调函数的设置 */
init_completion(&mrq->completion); // 初始化完成量,在mmc_wait_for_req_done中会去等待这个完成量
mrq->done = mmc_wait_done;
// 设置mmc_request处理完成的回调函数,会调用complete(&mrq->completion);来设置完成量
// host controller会调用mmc_request_done来执行这个回调函数,具体在后面分析
if (mmc_card_removed(host->card)) { // 检测card是否存在
mrq->cmd->error = -ENOMEDIUM;
complete(&mrq->completion);
return -ENOMEDIUM;
}
/* 调用mmc_start_request发起mmc请求 */
mmc_start_request(host, mrq); // 开始处理mmc_request请求
return 0;
}
static void
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
WARN_ON(!host->claimed);
/* 以下对mmc_request的各个成员,包括cmd、data、stop做验证操作和关联操作 */
mrq->cmd->error = 0;
mrq->cmd->mrq = mrq;
if (mrq->data) {
BUG_ON(mrq->data->blksz > host->max_blk_size);
BUG_ON(mrq->data->blocks > host->max_blk_count);
BUG_ON(mrq->data->blocks * mrq->data->blksz >
host->max_req_size);
mrq->cmd->data = mrq->data; // 也就是说mmc_request的data和其cmd中的data是一一样的
mrq->data->error = 0;
mrq->data->mrq = mrq;
if (mrq->stop) {
mrq->data->stop = mrq->stop;
mrq->stop->error = 0;
mrq->stop->mrq = mrq;
}
#ifdef CONFIG_MMC_PERF_PROFILING
if (host->perf_enable)
host->perf.start = ktime_get();
#endif
}
/* 获取时钟 */
mmc_host_clk_hold(host);
/* 调用host controller的request方法来处理mmc_request请求 */
host->ops->request(host, mrq);
// host->ops->request也就是host controller的request方法,对于sdhci类型的host来说,就是sdhci_request
}
//-----------------------------------mmc_wait_for_req_done说明,等待mmc_request处理完成
static void mmc_wait_for_req_done(struct mmc_host *host,
struct mmc_request *mrq)
{
struct mmc_command *cmd;
while (1) {
wait_for_completion_io(&mrq->completion); // 在这里休眠,等待mrq->completion完成量,在__mmc_start_req中初始化的
cmd = mrq->cmd; // 获取对应的command
/*
* If host has timed out waiting for the commands which can be
* HPIed then let the caller handle the timeout error as it may
* want to send the HPI command to bring the card out of
* programming state.
*/
if (cmd->ignore_timeout && cmd->error == -ETIMEDOUT)
break;
if (!cmd->error || !cmd->retries || mmc_card_removed(host->card))
// 如果command正常处理完成,或者失败重复尝试次数为0,或者card被移除了,直接退出循环返回
break;
// 以下处理失败重复尝试的情况
pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
mmc_hostname(host), cmd->opcode, cmd->error);
cmd->retries--;
cmd->error = 0;
host->ops->request(host, mrq);
}
}
会调用host->ops->request来对mmc_request进行处理,对于sdhci类型的host,对应就是sdhci_request。这个方法就是mmc_request实际被处理的核心。
2.mmc_request_done
通知mmc core某个mmc_request已经处理完成,由host controller调用。以sdhci类型的host为例,处理完一个mmc_request之后,会执行sdhci_tasklet_finish,而在sdhci_tasklet_finish中会调用mmc_request_done来通知host某个mmc_request已经处理完成了。
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
{
struct mmc_command *cmd = mrq->cmd;
int err = cmd->error;
if (host->card)
mmc_update_clk_scaling(host);
if (err && cmd->retries && !mmc_card_removed(host->card)) {
// command执行出错,如果还需要重复尝试的话,这里不释放clock,只是通知mmc core
if (mrq->done)
mrq->done(mrq);
// 执行mmc_request的回调函数来通知mmc core,
// 对于__mmc_start_req发起的request来说,就是mmc_wait_done,上面已经说明过了
// 对于__mmc_start_data_req发起的request来说,就是mmc_wait_data_done,后面会说明
} else {
mmc_should_fail_request(host, mrq);
// 用于模拟data传输概率出错的情况
// 具体参考http://blog.csdn.net/luckywang1103/article/details/52224160
if (mrq->done)
mrq->done(mrq);
// 执行mmc_request的回调函数来通知mmc core,对于__mmc_start_req发起的request来说,就是mmc_wait_done,上面已经说明过了
mmc_host_clk_release(host);
}
}
通过上述,mrq->done被调度,mmc_wait_done被执行,mrq->completion被设置。然后等待mrq->completion的mmc_wait_for_req_done就会继续往下执行。
3.mmc_wait_for_cmd
mmc_wait_for_cmd用于处理一个不带数据请求的命令。会被封装到mmc_request中,通过调用mmc_wait_for_req来发起请求。
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
{
struct mmc_request mrq = {NULL};
WARN_ON(!host->claimed);
memset(cmd->resp, 0, sizeof(cmd->resp)); // 清空command的response
cmd->retries = retries; // 失败时的重复尝试次数
mrq.cmd = cmd; // 封装到mmc_request中
cmd->data = NULL; // 不带数据包的命令,故清空data
mmc_wait_for_req(host, &mrq); // 调用mmc_wait_for_req发起mmc请求并且等待其处理完成
return cmd->error; // 返回错误码
}
4.mmc_start_req(重要)
机制说明如下:mmc_start_req会先判断上一次的asycn_req是否处理完成,如果没有处理完成,则会等待其处理完成。如果处理完成了,为当前要处理的asycn_req发起请求,但是并不会等待,而是直接返回。
注意:并不是说调用这个接口并不会阻塞,而是不会为了等待当前请求处理完成而阻塞,但是可能会等待上一次请求处理完成而阻塞。这样,可以利用等待的一部分时间来做其他操作。
/**
* mmc_start_req - start a non-blocking request // 该函数用来发起一个不阻塞的请求
* @host: MMC host to start command // 要发起对应请求的host
* @areq: async request to start // 要发起的异步请求
* @error: out parameter returns 0 for success, otherwise non zero // 返回值,返回0表示成功,返回非零表示失败
*
* Start a new MMC custom command request for a host. // 为host发起的一个新的mmc命令请求
* If there is on ongoing async request wait for completion // 如果host已经有一个正在处理、等待完成的异步请求,那么会等待这个请求完成!!!
* of that request and start the new one and return. // 然后发起新的请求,然后返回!!!
* Does not wait for the new request to complete. // 并不会等待这个新的请求完成!!!
*
* Returns the completed request, NULL in case of none completed. // 会返回被完成的mmc请求(而不是新的mmc请求。)空表示没有mmc请求被完成。
* Wait for the an ongoing request (previoulsy started) to complete and
* return the completed request. If there is no ongoing request, NULL
* is returned without waiting. NULL is not an error condition.
// 等待上一次发起的mmc请求完成,然后把这个mmc请求返回。如果没有mmc请求正在处理,那么就直接返回而不会等待。空并不是错误条件。
*/
struct mmc_async_req *mmc_start_req(struct mmc_host *host,
struct mmc_async_req *areq, int *error)
{
int err = 0;
int start_err = 0;
struct mmc_async_req *data = host->areq;
unsigned long flags;
bool is_urgent;
/* Prepare a new request */
/* 为新的异步请求做准备处理 */
if (areq) {
/*
* start waiting here for possible interrupt
* because mmc_pre_req() taking long time
*/
mmc_pre_req(host, areq->mrq, !host->areq);
}
/* 对上一次发起的、正在处理、等待完成的异步请求进行处理、等待操作 */
if (host->areq) {
err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq); // 在这里等待正在处理的异步请求处理完成
//.......以下过滤了错误处理的部分
}
/* 对新的异步请求进行发起操作 */
if (!err && areq) {
/* urgent notification may come again */
spin_lock_irqsave(&host->context_info.lock, flags);
is_urgent = host->context_info.is_urgent;
host->context_info.is_urgent = false;
spin_unlock_irqrestore(&host->context_info.lock, flags);
if (!is_urgent || (areq->cmd_flags & REQ_URGENT)) {
start_err = __mmc_start_data_req(host, areq->mrq); // 调用__mmc_start_data_req发起新的异步请求
} else {
/* previous request was done */
err = MMC_BLK_URGENT_DONE;
if (host->areq) {
mmc_post_req(host, host->areq->mrq, 0);
host->areq = NULL;
}
areq->reinsert_req(areq);
mmc_post_req(host, areq->mrq, 0);
goto exit;
}
}
if (host->areq)
mmc_post_req(host, host->areq->mrq, 0);
/* Cancel a prepared request if it was not started. */
if ((err || start_err) && areq)
mmc_post_req(host, areq->mrq, -EINVAL);
if (err)
host->areq = NULL;
else
host->areq = areq;
exit:
if (error)
*error = err;
return data; // 反正上一次正常处理的异步请求
}
//-----------------------------------------------------------------------------------------------------------------------------
static int __mmc_start_data_req(struct mmc_host *host, struct mmc_request *mrq)
{
mrq->done = mmc_wait_data_done;
// 设置mmc_request处理完成的回调函数,会唤醒正在等待请求被完成的进程,后面说明
// host controller会调用mmc_request_done来执行这个回调函数,具体前面分析过了
mrq->host = host;
mmc_start_request(host, mrq); // 开始处理mmc_request请求,前面已经说明过了
return 0;
}
static void mmc_wait_data_done(struct mmc_request *mrq)
{
unsigned long flags;
struct mmc_context_info *context_info = &mrq->host->context_info;
spin_lock_irqsave(&context_info->lock, flags);
mrq->host->context_info.is_done_rcv = true; // 设置is_done_rcv标识
wake_up_interruptible(&mrq->host->context_info.wait); // 唤醒context_info上的等待进程
spin_unlock_irqrestore(&context_info->lock, flags);
}
//-----------------------------------------------------------------------------------------------------------------------------
static int mmc_wait_for_data_req_done(struct mmc_host *host,
struct mmc_request *mrq,
struct mmc_async_req *next_req)
{
// struct mmc_request *mrq:表示正在等待完成的请求
// struct mmc_async_req *next_req:表示下一次要执行的异步请求
struct mmc_command *cmd;
struct mmc_context_info *context_info = &host->context_info;
bool pending_is_urgent = false;
bool is_urgent = false;
bool is_done_rcv = false;
int err, ret;
unsigned long flags;
while (1) {
/* 在这里等待正在进行的请求完成,会在mmc_wait_data_done中被唤醒 */
/* 有几种情况会唤醒等待进程 */
ret = wait_io_event_interruptible(context_info->wait,(context_info->is_done_rcv || context_info->is_new_req || context_info->is_urgent));
spin_lock_irqsave(&context_info->lock, flags);
is_urgent = context_info->is_urgent;
is_done_rcv = context_info->is_done_rcv;
context_info->is_waiting_last_req = false;
spin_unlock_irqrestore(&context_info->lock, flags);
/* 对请求处理完成的处理 */
if (is_done_rcv) {
context_info->is_done_rcv = false;
context_info->is_new_req = false;
cmd = mrq->cmd;
if (!cmd->error || !cmd->retries || mmc_card_removed(host->card)) {
/* 请求正常处理完成,或者失败但是不需要重复尝试的情况的处理 */
err = host->areq->err_check(host->card, host->areq);
//.......
break; /* return err */
} else {
/* 对请求处理出错并且需要重复尝试的情况的处理 */
//.......
}
}
}
return err;
}
参考:
https://blog.csdn.net/ooonebook/article/details/55001201
9.wowotech.net
Linux MMC framework(2)_host controller driver
1. 前言
本文是Linux MMC framework的第二篇,将从驱动工程师的角度,介绍MMC host controller driver有关的知识,学习并掌握如何在MMC framework的框架下,编写MMC控制器的驱动程序。同时,通过本篇文章,我们会进一步的理解MMC、SD、SDIO等有关的基础知识。
2. MMC host驱动介绍
MMC的host driver,是用于驱动MMC host控制器的程序,位于“drivers/mmc/host”目录。从大的流程上看,编写一个这样的驱动非常简单,只需要三步:
1)调用mmc_alloc_host,分配一个struct mmc_host类型的变量,用于描述某一个具体的MMC host控制器。
2)根据MMC host控制器的硬件特性,填充struct mmc_host变量的各个字段,例如MMC类型、电压范围、操作函数集等等。
3)调用mmc_add_host接口,将正确填充的MMC host注册到MMC core中。
当然,看着简单,一牵涉到实现细节,还是很麻烦的,后面我们会慢慢分析。
注1:分析MMC host driver的时候,Linux kernel中有大把大把的例子(例如drivers/mmc/host/pxamci.c),大家可尽情参考、学习,不必谦虚(这是学习Linux的最佳方法)。
注2:由于MMC host driver牵涉到具体的硬件controller,分析的过程中需要一些具体的硬件辅助理解,本文将以“X Project”所使用Bubblegum-96平台为例,具体的硬件spec可参考[1]。
3. 主要数据结构
3.1 struct mmc_host
MMC core使用struct mmc_host结构抽象具体的MMC host controller,该结构的定义位于“include/linux/mmc/host.h”中,它既可以用来描述MMC控制器所具有的特性、能力(host driver关心的内容),也保存了host driver运行过程中的一些状态、参数(MMC core关心的内容)。需要host driver关心的部分的具体的介绍如下:
parent,一个struct device类型的指针,指向该MMC host的父设备,一般是注册该host的那个platform设备;
class_dev,一个struct device类型的变量,是该MMC host在设备模型中作为一个“设备”的体现。当然,人如其名,该设备从属于某一个class(mmc_host_class);
ops,一个struct mmc_host_ops类型的指针,保存了该MMC host有关的操作函数集,具体可参考3.2小节的介绍;
pwrseq,一个struct mmc_pwrseq类型的指针,保存了该MMC host电源管理有关的操作函数集,具体可参考3.2小节的介绍;
f_min、f_max、f_init,该MMC host支持的时钟频率范围,最小频率、最大频率以及初始频率;
ocr_avail,该MMC host可支持的操作电压范围(具体可参考include/linux/mmc/host.h中MMC_VDD_开头的定义);
注3:OCR(Operating Conditions Register)是MMC/SD/SDIO卡的一个32-bit的寄存器,其中有些bit指明了该卡的操作电压。MMC host在驱动这些卡的时候,需要和Host自身所支持的电压范围匹配之后,才能正常操作,这就是ocr_avail的存在意义。ocr_avail_sdio、ocr_avail_sd、ocr_avail_mmc,如果MMC host针对SDIO、SD、MMC等不同类型的卡,所支持的电压范围不同的话,需要通过这几个字段特别指定。否则,不需要赋值(初始化为0);
pm_notify,一个struct notifier_block类型的变量,用于支持power management有关的notify实现;
max_current_330、max_current_300、max_current_180,当工作电压分别是3.3v、3v以及1.8v的时候,所支持的最大操作电流(如果MMC host没有特别的限制,可以不赋值);
caps、caps2,指示该MMC host所支持的功能特性,具体可参考3.4小节的介绍;
pm_caps,mmc_pm_flag_t类型的变量,指示该MMC host所支持的电源管理特性;
max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count、max_busy_timeout,和块设备(如MMC、SD、eMMC等)有关的参数,在古老的磁盘时代,这些参数比较重要。对基于MMC技术的块设备来说,硬件的性能大大提升,这些参数就没有太大的意义了。具体可参考5.2章节有关MMC数据传输的介绍;
lock,一个spin lock,是MMC host driver的私有变量,可用于保护host driver的临界资源;
ios,一个struct mmc_ios类型的变量,用于保存MMC bus的当前配置,具体可参考3.5小节的介绍;
supply,一个struct mmc_supply类型的变量,用于描述MMC系统中的供电信息,具体可参考3.6小节的介绍;
……
private,一个0长度的数组,可以在mmc_alloc_host时指定长度,由host controller driver自行支配。
3.2 struct mmc_host_ops
struct mmc_host_ops抽象并集合了MMC host controller所有的操作函数集,包括:
1)数据传输有关的函数
/*
* It is optional for the host to implement pre_req and post_req in
* order to support double buffering of requests (prepare one
* request while another request is active).
* pre_req() must always be followed by a post_req().
* To undo a call made to pre_req(), call post_req() with
* a nonzero err condition.
*/
void (*post_req)(struct mmc_host *host, struct mmc_request *req,
int err);
void (*pre_req)(struct mmc_host *host, struct mmc_request *req,
bool is_first_req);
void (*request)(struct mmc_host *host, struct mmc_request *req);
pre_req和post_req是非必需的,host driver可以利用它们实现诸如双buffer之类的高级功能。
数据传输的主题是struct mmc_request类型的指针,具体可参考3.7小节的介绍。
2)总线参数的配置以及卡状态的获取函数
/*
* Avoid calling these three functions too often or in a “fast path”,
* since underlaying controller might implement them in an expensive
* and/or slow way.
*
* Also note that these functions might sleep, so don’t call them
* in the atomic contexts!
*
* Return values for the get_ro callback should be:
* 0 for a read/write card
* 1 for a read-only card
* -ENOSYS when not supported (equal to NULL callback)
* or a negative errno value when something bad happened
*
* Return values for the get_cd callback should be:
* 0 for a absent card
* 1 for a present card
* -ENOSYS when not supported (equal to NULL callback)
* or a negative errno value when something bad happened
*/
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int (*get_ro)(struct mmc_host *host);
int (*get_cd)(struct mmc_host *host);
set_ios用于设置bus的参数(ios,可参考3.5小节的介绍);get_ro可获取card的读写状态(具体可参考上面的注释);get_cd用于检测卡的存在状态。
注4:注释中特别说明了,这几个函数可以sleep,耗时较长,没事别乱用。
3)其它一些非主流函数,都是optional的,用到的时候再去细看即可。
3.3 struct mmc_pwrseq
MMC framework的power sequence是一个比较有意思的功能,它提供一个名称为struct mmc_pwrseq_ops的操作函数集,集合了power on、power off等操作函数,用于控制MMC系统的供电,如下:
struct mmc_pwrseq_ops {
void (*pre_power_on)(struct mmc_host *host);
void (*post_power_on)(struct mmc_host *host);
void (*power_off)(struct mmc_host *host);
void (*free)(struct mmc_host *host);
};struct mmc_pwrseq {
const struct mmc_pwrseq_ops *ops;
};
与此同时,MMC core提供了一个通用的pwrseq的管理模块(drivers/mmc/core/pwrseq.c),以及一些简单的pwrseq策略(如drivers/mmc/core/pwrseq_simple.c、drivers/mmc/core/pwrseq_emmc.c),最终的目的是,通过一些简单的dts配置,即可正确配置MMC的供电,例如:
/* arch/arm/boot/dts/omap3-igep0020.dts /
mmc2_pwrseq: mmc2_pwrseq {
compatible = “mmc-pwrseq-simple”;
reset-gpios = <&gpio5 11 GPIO_ACTIVE_LOW>, / gpio_139 - RESET_N_W /
<&gpio5 10 GPIO_ACTIVE_LOW>; / gpio_138 - WIFI_PDN */
};/* arch/arm/boot/dts/rk3288-veyron.dtsi */
emmc_pwrseq: emmc-pwrseq {
compatible = “mmc-pwrseq-emmc”;
pinctrl-0 = <&emmc_reset>;
pinctrl-names = “default”;
reset-gpios = <&gpio2 9 GPIO_ACTIVE_HIGH>;
};
具体的细节,在需要的时候,阅读代码即可,这里不再赘述。
3.4 Host capabilities
通过的caps和caps2两个字段,MMC host driver可以告诉MMC core控制器的一些特性、能力,包括(具体可参考include/linux/mmc/host.h中相关的宏定义,更为细致的使用场景和指南,需要结合实际的硬件,具体分析):
MMC_CAP_4_BIT_DATA,支持4-bit的总线传输;
MMC_CAP_MMC_HIGHSPEED,支持“高速MMC时序”;
MMC_CAP_SD_HIGHSPEED,支持“高速SD时序”;
MMC_CAP_SDIO_IRQ,可以产生SDIO有关的中断;
MMC_CAP_SPI,仅仅支持SPI协议(可参考“drivers/mmc/host/mmc_spi.c”中有关的实现);
MMC_CAP_NEEDS_POLL,表明需要不停的查询卡的插入状态(如果需要支持热拔插的卡,则需要设置该feature);
MMC_CAP_8_BIT_DATA,支持8-bit的总线传输;
MMC_CAP_AGGRESSIVE_PM,支持比较积极的电源管理策略(kernel的注释为“Suspend (e)MMC/SD at idle”);
MMC_CAP_NONREMOVABLE,表明该MMC控制器所连接的卡是不可拆卸的,例如eMMC;
MMC_CAP_WAIT_WHILE_BUSY,表面Host controller在向卡发送命令时,如果卡处于busy状态,是否等待。如果不等待,MMC core在一些流程中(如查询busy状态),需要额外做一些处理;
MMC_CAP_ERASE,表明该MMC控制器允许执行擦除命令;
MMC_CAP_1_8V_DDR,支持工作电压为1.8v的DDR(Double Data Rate)mode[3];
MMC_CAP_1_2V_DDR,支持工作电压为1.2v的DDR(Double Data Rate)mode[3];
MMC_CAP_POWER_OFF_CARD,可以在启动之后,关闭卡的供电(一般只有在SDIO的应用场景中才可能用到,因为SDIO所连接的设备可能是一个独立的设备);
MMC_CAP_BUS_WIDTH_TEST,支持通过CMD14和CMD19进行总线宽度的测试,以便选择一个合适的总线宽度进行通信;
MMC_CAP_UHS_SDR12、MMC_CAP_UHS_SDR25、MMC_CAP_UHS_SDR50、MMC_CAP_UHS_SDR104,它们是SD3.0的速率模式,分别表示支持25MHz、50MHz、100MHz和208MHz的SDR(Single Data Rate,相对[3]中的DDR)模式;
MMC_CAP_UHS_DDR50,它也是SD3.0的速率模式,表示支持50MHz的DDR(Double Data Rate[3])模式;
MMC_CAP_DRIVER_TYPE_A、MMC_CAP_DRIVER_TYPE_C、MMC_CAP_DRIVER_TYPE_D,分别表示支持A/C/D类型的driver strength(驱动能力,具体可参考相应的spec);
MMC_CAP_CMD23,表示该controller支持multiblock transfers(通过CMD23);
MMC_CAP_HW_RESET,支持硬件reset;MMC_CAP2_FULL_PWR_CYCLE,表示该controller支持从power off到power on的完整的power cycle;
MMC_CAP2_HS200_1_8V_SDR、MMC_CAP2_HS200_1_2V_SDR,HS200是eMMC5.0支持的一种速率模式,200是200MHz的缩写,分别表示支持1.8v和1.2v的SDR模式;
MMC_CAP2_HS200,表示同时支持MMC_CAP2_HS200_1_8V_SDR和MMC_CAP2_HS200_1_2V_SDR;
MMC_CAP2_HC_ERASE_SZ,支持High-capacity erase size;
MMC_CAP2_CD_ACTIVE_HIGH,CD(Card-detect)信号高有效;
MMC_CAP2_RO_ACTIVE_HIGH,RO(Write-protect)信号高有效;
MMC_CAP2_PACKED_RD、MMC_CAP2_PACKED_WR,允许packed read、packed write;
MMC_CAP2_PACKED_CMD,同时支持DMMC_CAP2_PACKED_RD和MMC_CAP2_PACKED_WR;
MMC_CAP2_NO_PRESCAN_POWERUP,在scan之前不要上电;
MMC_CAP2_HS400_1_8V、MMC_CAP2_HS400_1_2V,HS400是eMMC5.0支持的一种速率模式,400是400MHz的缩写,分别表示支持1.8v和1.2v的HS400模式;
MMC_CAP2_HS400,同时支持MMC_CAP2_HS400_1_8V和MMC_CAP2_HS400_1_2V;
MMC_CAP2_HSX00_1_2V,同时支持MMC_CAP2_HS200_1_2V_SDR和MMC_CAP2_HS400_1_2V;
MMC_CAP2_SDIO_IRQ_NOTHREAD,SDIO的IRQ的处理函数,不能在线程里面执行;
MMC_CAP2_NO_WRITE_PROTECT,没有物理的RO管脚,意味着任何时候都是可以读写的;
MMC_CAP2_NO_SDIO,在初始化的时候,不会发送SDIO相关的命令(也就是说不支持SDIO模式)。
3.5 struct mmc_ios
struct mmc_ios中保存了MMC总线当前的配置情况,包括如下信息:
1)clock,时钟频率。
2)vdd,卡的供电电压,通过“1 << vdd”可以得到MMC_VDD_x_x(具体可参考include/linux/mmc/host.h中MMC_VDD_开头的定义),进而得到电压信息。
3)bus_mode,两种信号模式,open-drain(MMC_BUSMODE_OPENDRAIN)和push-pull(MMC_BUSMODE_PUSHPULL),对应不同的高低电平(可参考相应的spec,例如[2])。
4)chip_select,只针对SPI模式,指定片选信号的有效模式,包括没有片选信号(MMC_CS_DONTCARE)、高电平有效(MMC_CS_HIGH)、低电平有效(MMC_CS_LOW)。
5)power_mode,当前的电源状态,包括MMC_POWER_OFF、MMC_POWER_UP、MMC_POWER_ON和MMC_POWER_UNDEFINED。
6)bus_width,总线的宽度,包括1-bit(MMC_BUS_WIDTH_1)、4-bit(MMC_BUS_WIDTH_4)和8-bit(MMC_BUS_WIDTH_8)。
7)timing,符合哪一种总线时序(大多对应某一类MMC规范),包括:
MMC_TIMING_LEGACY,旧的、不再使用的规范;
MMC_TIMING_MMC_HS,High speed MMC规范(具体可参考相应的spec,这里不再详细介绍,下同);
MMC_TIMING_SD_HS,High speed SD;
MMC_TIMING_UHS_SDR12;
MMC_TIMING_UHS_SDR25
MMC_TIMING_UHS_SDR50
MMC_TIMING_UHS_SDR104
MMC_TIMING_UHS_DDR50
MMC_TIMING_MMC_DDR52
MMC_TIMING_MMC_HS200
MMC_TIMING_MMC_HS400
8)signal_voltage,总线信号使用哪一种电压,3.3v(MMC_SIGNAL_VOLTAGE_330)、1.8v(MMC_SIGNAL_VOLTAGE_180)或者1.2v(MMC_SIGNAL_VOLTAGE_120)。
9)drv_type,驱动能力,包括:
MMC_SET_DRIVER_TYPE_B
MMC_SET_DRIVER_TYPE_A
MMC_SET_DRIVER_TYPE_C
MMC_SET_DRIVER_TYPE_D
3.6 struct mmc_supply
struct mmc_supply中保存了两个struct regulator指针(如下),用于控制MMC子系统有关的供电(vmmc和vqmmc)。
struct mmc_supply {
struct regulator vmmc; / Card power supply */
struct regulator vqmmc; / Optional Vccq supply */
};
关于vmmc和vqmmc,说明如下:
vmmc是卡的供电电压,一般连接到卡的VDD管脚上。而vqmmc则用于上拉信号线(CMD、CLK和DATA[6])。
通常情况下vqmmc使用和vmmc相同的regulator,同时供电即可。
后来,一些高速卡(例如UHS SD)要求在高速模式下,vmmc为3.3v,vqmmc为1.8v,这就需要两个不同的regulator独立控制。
3.7 struct mmc_request
struct mmc_request封装了一次传输请求,定义如下:
/* include/linux/mmc/core.h */
struct mmc_request {
struct mmc_command sbc; / SET_BLOCK_COUNT for multiblock */
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_command *stop; struct completion completion;
void (*done)(struct mmc_request );/ completion function */
struct mmc_host *host;
};
要理解这个数据结构,需要先了解MMC的总线协议(bus protocol),这里以eMMC[2]为例进行简单的介绍(更为详细的解释,可参考相应的spec以及本站的文章–“eMMC 原理 4 :总线协议[7]”)。
3.7.1 MMC bus protocol
在eMMC的spec中,称总线协议为“message-based MultiMediaCard bus protocol”,这里的message由三种信标(token)组成:
Command,用于启动(或者停止)一次传输,由Host通过CMD line向Card发送;
Response,用于应答上一次的Command,由Card通过CMD line想Host发送;
Data,传输数据,由Host(或者Card)通过DATA lines向Card(或者Host发送)。
以上token除了Command之外,剩余的两个(Response和Data)都是非必需的,也就是说,一次传输可以是:不需要应答、不需要数据传输的Command;需要应答、不需要数据传输的Command;不需要应答、需要数据传输的Command;不需要应答、不需要数据传输的Command。
Command token的格式只有一种(具体可参考[2]中“Command token format”有关的表述),长度为48bits,包括Start bit(0)、Transmitter bit(1, host command)、Content(38bits)、CRC checksum(7bits)、Stop bit(1)。
根据内容的不同,Response token的格式有5中,分别简称为R1/R3/R4/R5/R2,其中R1/R3/R4/R5的长度为48bits,R2为136bits(具体可参考[2]中“Response token format”有关的表述)。
对于包含了Data token的Command,有两种类型:
Sequential commands,发送Start command之后,数据以stream的形式传输,直到Stop command为止。这种方式只支持1-bit总线模式,主要为了兼容旧的技术,一般不使用;
Block-oriented commands,发送Start command之后,数据以block的形式传输(每个block的大小是固定的,且都由CRC保护)。
最后,以block为单位的传输,大体上也分为两类:
在传输开始的时候(Start command),没有指定需要传输的block数目,直到发送Stop command为止。这种方法在spec中称作“Open-ended”;
在传输开始的时候(Start command),指定需要传输的block数据,当达到数据之后,Card会自动停止传输,这样可以省略Stop command。这种方法在spec中称作pre-defined block count。
3.7.2 struct mmc_request
了解MMC bus protocol之后,再来看一次MMC传输请求(struct mmc_request )所包含的内容:
cmd,Start command,为struct mmc_command类型(具体请参考3.7.3中的介绍)的指针,在一次传输的过程中是必须的;
data,传输的数据,为struct mmc_data类型(具体请参考3.7.4中的介绍)的指针,不是必须要的;
stop、sbc,如果需要进行数据传输,根据数据传输的方式(参考3.7.1中的介绍):如果是“Open-ended”,则需要stop命令(stop指针,或者data->stop指针);如果是pre-defined block count,则需要sbc指针(用于发送SET_BLOCK_COUNT–CMD23命令);
completion,一个struct completion变量,用于等待此次传输完成,host controller driver可以根据需要使用;
done,传输完成时的回调,用于通知传输请求的发起者;
host,对应的mmc host controller指针。
3.7.3 struct mmc_command
struct mmc_command结构抽象了一个MMC command,包括如下内容:
/* include/linux/mmc/core.h */
opcode,Command的操作码,用于标识该命令是哪一个命令,具体可参考相应的spec(例如[2]);
arg,一个Command可能会携带参数,具体可参考相应的spec(例如[2]);
resp[4],Command发出后,如果需要应答,结果保存在resp数组中,该数组是32-bit的,因此最多可以保存128bits的应答;
flags,是一个bitmap,保存该命令所期望的应答类型,例如:
MMC_RSP_PRESENT(1 << 0),是否需要应答,如果该bit为0,则表示该命令不需要应答,否则,需要应答;
MMC_RSP_136(1 << 1),如果为1,表示需要136bits的应答;
MMC_RSP_CRC(1 << 2),如果为1,表示需要对该命令进行CRC校验;
等等,具体可参考include/linux/mmc/core.h中“MMC_RSP_”开头的定义;retries,如果命令发送出错,可以重新发送,该字段向host driver指明最多可重发的次数;
error,如果最终还是出错,host driver需要通过该字段返回错误的原因,kernel定义了一些标准错误,例如ETIMEDOUT、EILSEQ、EINVAL、ENOMEDIUM等,具体含义可参考include/linux/mmc/core.h中注释;
busy_timeout,如果card具有busy检测的功能,该字段指定等待card返回busy状态的超时时间,单位为ms;
data,和该命令对应的struct mmc_data指针;
mrq,和该命令对应的struct mmc_request指针。
3.7.4 struct mmc_data
struct mmc_data结构包含了数据传输有关的内容:
/* include/linux/mmc/core.h */
timeout_ns、timeout_clks,这一笔数据传输的超时时间(单位分别为ns和clks),如果超过这个时间host driver还无法成功发送,则要将状态返回给mmc core;
blksz、blocks,该笔数据包含多少block(blocks),每个block的size多大(blksz),这两个值不会大于struct mmc_host中上报的max_blk_size和max_blk_count;
error,如果数据传输出错,错误值保存在该字段,具体意义和struct mmc_command中的一致;
flags,一个bitmap,指明该笔传说的方向(MMC_DATA_WRITE或者MMC_DATA_READ);
sg,一个struct scatterlist类型的数组,保存了需要传输的数据(可以通过dma_相关的接口,获得相应的物理地址);
sg_len,sg数组的size;
sg_count,通过sg map出来的实际的entry的个数(可能由于物理地址的连续、IOMMU的干涉等,map出来的entry的个数,可能会小于sg的size);
注5:有关scatterlist的介绍,可参考本站另外的文章(TODO)。有关struct mmc_data的使用场景,可参考5.2小节的介绍;host_cookie,host driver的私有数据,怎么用由host driver自行决定。
4. 主要API
第3章花了很大的篇幅介绍了用于抽象MMC host的数据结构----struct mmc_host,并详细说明了和mmc_host相关的mmc request、mmc command、mmc data等结构。基于这些知识,本章将介绍MMC core提供的和struct mmc_host有关的操作函数,主要包括如下几类。
4.1 向MMC host controller driver提供的用于操作struct mmc_host的API
包括:
struct mmc_host *mmc_alloc_host(int extra, struct device *); int mmc_add_host(struct mmc_host *); void mmc_remove_host(struct mmc_host *); void mmc_free_host(struct mmc_host *); int mmc_of_parse(struct mmc_host *host); static inline void *mmc_priv(struct mmc_host *host) { return (void *)host->private; }
mmc_alloc_host,动态分配一个struct mmc_host变量。extra是私有数据的大小,可通过host->private指针访问(也可通过mmc_priv接口直接获取)。mmc_free_host执行相反动作。
mmc_add_host,将已初始化好的host变量注册到kernel中。mmc_remove_host执行相反动作。
为了方便,host controller driver可以在dts中定义host的各种特性,然后在代码中调用mmc_of_parse解析并填充到struct mmc_host变量中。dts属性关键字可参考mmc_of_parse的source code(drivers/mmc/core/host.c),并结合第三章的内容自行理解。
int mmc_power_save_host(struct mmc_host *host); int mmc_power_restore_host(struct mmc_host *host);
从mmc host的角度进行电源管理,进入/退出power save状态。
void mmc_detect_change(struct mmc_host *, unsigned long delay);
当host driver检测到总线上的设备有变动的话(例如卡的插入和拔出等),需要调用这个接口,让MMC core帮忙做后续的工作,例如检测新插入的卡到底是个什么东东……
另外,可以通过delay参数告诉MMC core延时多久(单位为jiffies)开始处理,通常可以用来对卡的拔插进行去抖动。
void mmc_request_done(struct mmc_host *, struct mmc_request *);
当host driver处理完成一个mmc request之后,需要调用该函数通知MMC core,MMC core会进行一些善后的操作,例如校验结果、调用mmc request的.done回调等等。
static inline void mmc_signal_sdio_irq(struct mmc_host *host) void sdio_run_irqs(struct mmc_host *host);
对于SDIO类型的总线,这两个函数用于操作SDIO irqs,后面用到的时候再分析。
int mmc_regulator_get_ocrmask(struct regulator *supply); int mmc_regulator_set_ocr(struct mmc_host *mmc, struct regulator *supply, unsigned short vdd_bit); int mmc_regulator_set_vqmmc(struct mmc_host *mmc, struct mmc_ios *ios); int mmc_regulator_get_supply(struct mmc_host *mmc);
regulator有关的辅助函数:
mmc_regulator_get_ocrmask可根据传入的regulator指针,获取该regulator支持的所有电压值,并以此推导出对应的ocr mask(可参考3.1中的介绍)。
mmc_regulator_set_ocr用于设置host controller为某一个操作电压(vdd_bit),该接口会调用regulator framework的API,进行具体的电压切换。
mmc_regulator_set_vqmmc可根据struct mmc_ios信息,自行调用regulator framework的接口,设置vqmmc的电压。
最后,mmc_regulator_get_supply可以帮忙从dts的vmmc、vqmmc属性值中,解析出对应的regulator指针,以便后面使用。
4.2 用于判断MMC host controller所具备的能力的API
比较简单,可结合第3章的介绍理解:
#define mmc_host_is_spi(host) ((host)->caps & MMC_CAP_SPI) static inline int mmc_card_is_removable(struct mmc_host *host) static inline int mmc_card_keep_power(struct mmc_host *host) static inline int mmc_card_wake_sdio_irq(struct mmc_host *host) static inline int mmc_host_cmd23(struct mmc_host *host) static inline int mmc_boot_partition_access(struct mmc_host *host) static inline int mmc_host_uhs(struct mmc_host *host) static inline int mmc_host_packed_wr(struct mmc_host *host) static inline int mmc_card_hs(struct mmc_card *card) static inline int mmc_card_uhs(struct mmc_card *card) static inline bool mmc_card_hs200(struct mmc_card *card) static inline bool mmc_card_ddr52(struct mmc_card *card) static inline bool mmc_card_hs400(struct mmc_card *card) static inline void mmc_retune_needed(struct mmc_host *host) static inline void mmc_retune_recheck(struct mmc_host *host)
5. MMC host驱动的编写步骤
经过上面章节的描述,相信大家对MMC controller driver有了比较深的理解,接下来驱动的编写就是一件水到渠成的事情了。这里简要描述一下驱动编写步骤,也顺便为本文做一个总结。
5.1 struct mmc_host的填充和注册
编写MMC host驱动的所有工作,都是围绕struct mmc_host结构展开的。在对应的platform driver的probe函数中,通过mmc_alloc_host分配一个mmc host后,我们需要根据controller的实际情况,填充对应的字段。
mmc host中大部分和controller能力/特性有关的字段,可以通过dts配置(然后在代码中调用mmc_of_parse自动解析并填充),举例如下(注意其中红色的部分,都是MMC framework的标准字段):
/* arch/arm/boot/dts/exynos5420-peach-pit.dts */
&mmc_1 {
status = “okay”;
num-slots = <1>;
non-removable;
cap-sdio-irq;
keep-power-in-suspend;
clock-frequency = <400000000>;
samsung,dw-mshc-ciu-div = <1>;
samsung,dw-mshc-sdr-timing = <0 1>;
samsung,dw-mshc-ddr-timing = <0 2>;
pinctrl-names = “default”;
pinctrl-0 = <&sd1_clk>, <&sd1_cmd>, <&sd1_int>, <&sd1_bus1>,
<&sd1_bus4>, <&sd1_bus8>, <&wifi_en>;
bus-width = <4>;
cap-sd-highspeed;
mmc-pwrseq = <&mmc1_pwrseq>;
vqmmc-supply = <&buck10_reg>;
};
5.2 数据传输的实现
填充struct mmc_host变量的过程中,工作量最大的,就是对struct mmc_host_ops的实现(毫无疑问!所有MMC host的操作逻辑都封在这里呢!!)。这里简单介绍一下相关的概念,具体的驱动编写步骤,后面文章会结合“X Project”详细描述。
5.2.1 Sectors(扇区)、Blocks(块)以及Segments(段)的理解
我们在3.1小节介绍struct mmc_host的时候,提到了max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count等参数。这和磁盘设备(块设备)中Sectors、Blocks、Segments等概念有关,下面简单介绍一下:
1)Sectors
Sectors是存储设备访问的基本单位。
对磁盘、NAND等块设备来说,Sector的size是固定的,例如512、2048等。
对存储类的MMC设备来说,按理说也应有固定size的sector。但因为有MMC协议的封装,host驱动以及上面的块设备驱动,不需要关注物理的size。它们需要关注的就是bus上的数据传输单位(具体可参考MMC protocol的介绍[7])。
最后,对那些非存储类的MMC设备来说,完全没有sector的概念了。
2) Blocks
Blocks是数据传输的基本单位,是VFS(虚拟文件系统)抽象出来的概念,是纯软件的概念,和硬件无关。它必须是2的整数倍、不能大于Sectors的单位、不能大于page的长度,一般为512B、2048B或者4096B。
对MMC设备来说,由于协议的封装,淡化了Sector的概念,或者说,MMC设备可以支持一定范围内的任意的Block size。Block size的范围,由两个因素决定:
a)host controller的能力,这反映在struct mmc_host结构的max_blk_size字段上。
b)卡的能力,这可以通过MMC command从卡的CSD(Card-Specific Data)寄存器中读出。
3)Segments[8]
块设备的数据传输,本质上是设备上相邻扇区与内存之间的数据传输。通常情况下,为了提升性能,数据传输通过DMA方式。
在磁盘控制器的旧时代,DMA操作都比较简单,每次传输,数据在内存中必须是连续的。现在则不同,很多SOC都支持“分散/聚合”(scatter-gather)DMA操作,这种操作模式下,数据传输可以在多个非连续的内存区域中进行。
对于每个“分散/聚合”DMA操作,块设备驱动需要向控制器发送:
a)初始扇区号和传输的总共扇区数
b)内存区域的描述链表,每个描述都包含一个地址和长度。不同的描述之间,可以在物理上连续,也可以不连续。控制器来管理整个数据传输,例如:在读操作中,控制器从块设备相邻的扇区上读取数据,然后将数据分散存储在内存的不同区域。
这里的每个内存区域描述(物理连续的一段内存,可以是一个page,也可以是page的一部分),就称作Segment。一个Segment包含多个相邻扇区。
最后,利用“分散/聚合”的DMA操作,一次数据传输可以会涉及多个segments。
理解了Segment的概念之后,max_seg_size和max_segs两个字段就好理解了:
虽然控制器支持“分散/聚合”的DMA操作,但物理硬件总有限制,例如最大的Segment size(也即一个内存描述的最大长度),最多支持的segment个数(max_segs)等。
5.2.2 struct mmc_data中的sg
我们在3.7.4小节介绍struct mmc_data时,提到了scatterlist的概念。结合上面Segment的解释,就很好理解了:
MMC core提交给MMC host driver的数据传输请求,是一个struct scatterlist链表(也即内存区域的描述链表),也可以理解为是一个个的Segment(Segment的个数保存在sg_len变量中了)。
每个Segment是一段物理地址连续的内存区域,所有的Segments对应了MMC设备中连续的Sector(或者说Block,初始扇区号和传输的总共扇区数已经在之前的MMC command中指定了。
host driver在接收到这样的数据传输请求之后,需要调用dma_map_sg将这些Segment映射出来(获得相应的物理地址),以便交给MMC controller传输。
当然,相邻两个Segment的物理地址可能是连续(或者其它原因),map的时候可能会将两个Segment合成一个。因此可供MMC controller传输的内存片可能少于sg_len(具体要看dma_map_sg的返回值,可将结果保存在sg_count中)。
最后,如何实施传输,则要看具体的MMC controller的硬件实现(可能涉及DMA操作),后面文章再详细介绍。
扫卡流程(或许错误):
sdhci_s3c_probe->sdhci_add_host->mmc_start->_mmc_detect_change
10.函数调用过程
1.rk3308b函数调用过程
dw_mci_init
dw_mci_probe
dw_mci_init_slot
mmc_alloc_host
INIT_DELAYED_WORK(&host->detect, mmc_rescan);//这里绑定mmc_rescan
mmc_add_host //(rk将host导出了primary_sdio_host)
mmc_start_host
mmc_detect_change
_mmc_detect_change
mmc_schedule_delayed_work(&host->detect, delay);(mmc_alloc_host中默认将host->detect工作设置为mmc_rescan)
电源管理:
mmc_power_up
mmc_pwrseq_pre_power_on
pwrseq->ops->pre_power_on(host);
.pre_power_on = mmc_pwrseq_simple_pre_power_on
mmc_pwrseq_simple_pre_power_on
mmc_pwrseq_post_power_on
pwrseq->ops->post_power_on(host);
.post_power_on = mmc_pwrseq_simple_post_power_on
mmc_pwrseq_simple_post_power_on
mmc_power_off
mmc_pwrseq_power_off
pwrseq->ops->power_off(host);
.power_off = mmc_pwrseq_simple_power_off
mmc_pwrseq_simple_power_off