Linux eMMC子系统深度解析:从硬件协议到内核实现

Linux eMMC子系统深度解析:从硬件协议到内核实现

1 eMMC基础概念与工作原理

1.1 eMMC物理结构与硬件接口

eMMC(Embedded Multi Media Card)是一种嵌入式存储解决方案,它将NAND Flash存储芯片、闪存控制器和标准MMC接口封装在同一块芯片中。这种高度集成的设计极大地简化了手机、平板电脑、嵌入式设备等产品的存储系统设计。从物理结构上看,eMMC可以划分为三个主要部分:NAND Flash存储阵列、闪存控制器和MMC接口控制器。

NAND Flash存储阵列 是实际数据存储的介质,由多个Block组成,每个Block又包含多个Page。这种结构特点使得NAND Flash适合大容量数据存储,但也带来了需要擦写均衡、坏块管理等挑战。闪存控制器 负责处理这些底层管理任务,包括ECC纠错、磨损均衡、坏块管理、垃圾回收等,从而让主机系统可以像访问普通存储设备一样访问eMMC,无需关心NAND Flash的复杂特性。MMC接口控制器则负责与主机通信,解析和执行主机发送的命令,并在内部闪存控制器和主机之间传输数据。

eMMC与主机的硬件连接主要通过以下信号线实现:

  • CLK:时钟信号线,由主机提供,用于同步数据传输。不同的总线模式下时钟频率差异很大,从基础模式的0-26MHz到HS400模式的0-200MHz不等。
  • CMD:双向命令/响应信号线,用于主机发送命令和设备返回响应。所有通信都由主机发起,设备通过CMD线回应。
  • DAT0-DAT7:双向数据总线,用于实际数据传输。eMMC支持1-bit、4-bit和8-bit数据总线宽度,上电默认使用1-bit模式,初始化后可以配置为更宽的总线以提高传输效率。
  • RST:复位信号线,用于硬件复位eMMC设备。

1.2 eMMC总线协议与通信原理

eMMC总线通信遵循严格的主从模式,主机始终发起所有通信,设备仅在响应主机命令时才会在CMD或DAT线上传输数据。每个通信事务都由命令(Command)响应(Response) 和可选的数据块(Data Block) 组成。

命令格式是所有eMMC通信的基础。每个命令都是固定的48位长度,包含以下字段:

  • 起始位(1位):固定为0
  • 传输位(1位):指示传输方向,主机到设备为1
  • 命令索引(6位):表示命令类型,范围0-63
  • 命令参数(32位):与具体命令相关的参数
  • CRC7校验(7位):对前面40位数据的校验码
  • 结束位(1位):固定为1

响应是设备对主机命令的回复,有6种不同的响应类型(R1、R1b、R2、R3、R4、R5),长度和内容各不相同。例如,R1响应为48位,包含命令索引和设备状态;R2响应为136位,用于传输CID或CSD寄存器的内容。

在实际通信中,eMMC支持单块读取多块读取。单块读取由CMD17命令发起,设备在接收到命令后,先发送一个响应,然后传输一个数据块,最后是CRC校验码。多块读取由CMD18命令发起,设备会连续传输多个数据块,直到主机发送CMD12停止命令。数据写入过程类似,只是数据传输方向相反。

表:eMMC主要命令类型及其功能

命令类型 命令索引 功能描述 响应类型
基本命令 CMD0 设备复位
基本命令 CMD1 发送操作条件 R3
设备识别 CMD2 获取CID号 R2
设备识别 CMD3 设置相对地址 R1
数据读 CMD17 读取单块数据 R1
数据读 CMD18 读取多块数据 R1
数据写 CMD24 写入单块数据 R1
数据写 CMD25 写入多块数据 R1
应用命令 CMD55 标识下一个为应用命令 R1
应用命令 ACMD41 发送操作条件 R3

1.3 eMMC总线模式与性能演进

eMMC协议经历了多个版本的演进,每个版本都引入了新的总线模式以提高性能。从最初的兼容MMC模式(最高26MHz时钟)到HS400模式(200MHz DDR,等效400MHz),eMMC的数据传输速率得到了显著提升。

HS400模式是eMMC 5.0协议中引入的最高性能模式,它结合了8位数据总线宽度、双倍数据率(DDR)技术和200MHz时钟频率,理论传输速率可达400MB/s。为了实现这一高性能,HS400模式要求使用1.8V I/O电压,并且需要精确的时序控制和信号完整性保证。

eMMC设备在启动后通常处于最低性能的兼容模式,主机需要通过一系列初始化步骤来配置设备,逐步切换到更高性能的总线模式。这个过程包括:识别设备、配置总线宽度、提高时钟频率、切换到高速时序模式等。主机通过读取设备的CSD(Card Specific Data)、EXT_CSD等寄存器来了解设备支持的能力,然后通过相应的切换命令来配置设备的工作参数。

2 Linux eMMC子系统架构分析

2.1 MMC子系统三层架构概述

Linux内核中的MMC子系统采用典型的三层架构设计,清晰地将功能划分为主机层(Host Layer)核心层(Core Layer)块设备层(Block Device Layer)。这种分层设计实现了硬件相关代码与硬件无关代码的分离,提高了代码的可维护性和可移植性。

主机层 是与具体硬件平台相关的驱动层,负责管理与eMMC控制器相关的硬件资源,如时钟、GPIO、DMA等。在Linux内核中,不同的SoC厂商需要实现自己的主机驱动,例如Rockchip的dw_mmc_rockchip、Samsung的sdhci-s3c等。主机层通过struct mmc_host_ops结构体向核心层提供一组回调函数,这些函数包括硬件初始化、请求处理、电源管理等操作。

核心层 是MMC子系统的核心部分,它实现了eMMC协议栈、设备检测初始化、电源管理、请求队列调度等通用功能。核心层通过struct mmc_host结构体管理主机控制器信息,通过struct mmc_card结构体管理eMMC设备信息。核心层作为主机层和块设备层之间的桥梁,负责将上层的块设备请求转换为eMMC命令序列,并通过主机层与硬件交互。

块设备层 将eMMC设备呈现为标准的Linux块设备(如/dev/mmcblk0),使得上层文件系统(如ext4、FAT32)和应用程序可以通过标准的块设备接口访问eMMC存储。这一层实现了Linux块设备驱动程序的框架,包括请求队列管理、I/O调度、缓冲区和直接I/O等功能。

2.2 各层组件功能与交互机制

主机层 负责最底层的硬件交互,其核心是实现struct mmc_host_ops中定义的回调函数。主要函数包括:

  • request:处理MMC请求的主要函数,将MMC命令和数据传输转换为硬件操作
  • set_ios:配置总线的时序参数,如时钟频率、总线宽度、电源模式等
  • get_cdget_ro:检测设备的插入状态和写保护状态
  • enable_sdio_irq:启用或禁用SDIO中断(对于SDIO设备)

当系统启动时,主机驱动通过mmc_alloc_hostmmc_add_host函数向内核注册主机控制器,并准备好处理来自核心层的请求。

核心层的功能最为复杂,它主要包括以下几个模块:

  • 设备生命周期管理 :负责eMMC设备的检测、初始化、识别和移除。当设备插入或系统启动时,核心层通过mmc_rescan函数扫描总线,发现设备后执行初始化序列,包括获取CID、CSD、EXT_CSD等寄存器信息,分配相对地址(RCA),配置设备参数等。

  • 协议命令处理 :将上层请求转换为符合eMMC协议的命令序列。核心层实现了各种eMMC标准命令的封装函数,如mmc_go_idle(发送CMD0)、mmc_send_op_cond(发送CMD1)、mmc_all_send_cid(发送CMD2)等。

  • 电源管理:管理eMMC设备和主机的电源状态,包括电源关闭、睡眠、唤醒等状态转换。eMMC协议定义了多种电源状态以降低功耗,核心层根据系统需求在适当的时候切换设备电源状态。

  • EXT_CSD寄存器管理 :EXT_CSD是eMMC设备的一个重要寄存器集合,包含设备特性、配置参数和性能相关的信息。核心层通过mmc_get_ext_csd函数读取这些信息,并通过mmc_switch函数修改设备配置。

块设备层 将每个eMMC设备的分区呈现为/dev/mmcblk0p1/dev/mmcblk0p2等块设备节点。它通过struct mmc_blk_data结构体管理块设备信息,并通过struct gendisk结构体与Linux块设备子系统集成。当文件系统发起I/O请求时,块设备层将请求封装为struct mmc_request,并通过核心层下发到主机层执行。

2.3 系统初始化与设备探测过程

eMMC子系统的初始化从主机驱动开始,当内核启动时,主机驱动通过模块初始化函数(如module_platform_driver)注册自己。在探测(probe)函数中,主机驱动执行以下操作:

  1. 分配struct mmc_host结构体,并设置主机能力(caps)和操作函数集(ops)
  2. 初始化硬件,包括时钟、中断、DMA、GPIO等资源
  3. 向核心层注册主机控制器(mmc_add_host
  4. 启动扫描工作队列,检测连接的设备

设备探测过程由mmc_rescan函数实现,该函数在独立的线程中运行,避免阻塞系统启动。主要步骤包括:

  1. 检测设备是否存在(通过CD引脚或发送CMD1)
  2. 使能设备并发送CMD0进行复位
  3. 发送CMD1查询设备操作条件,获取OCR寄存器
  4. 发送CMD2获取设备的唯一标识符(CID)
  5. 发送CMD3设置设备相对地址(RCA)
  6. 发送CMD9获取设备特定数据(CSD)
  7. 发送CMD7选择设备,使其进入传输状态
  8. 读取EXT_CSD寄存器,获取扩展设备信息
  9. 配置设备参数,如总线宽度、高速模式等
  10. 注册块设备,使设备对上层可用

表:eMMC子系统关键数据结构与功能

数据结构 所在层级 主要功能
struct mmc_host 核心层 描述MMC主机控制器,包含caps、ops、f_min、f_max等字段
struct mmc_card 核心层 描述MMC设备,包含cid、csd、ext_csd、rca等设备信息
struct mmc_driver 主机层 MMC主机驱动,包含probe、remove、shutdown等函数
struct mmc_host_ops 主机层 主机操作函数集,包含request、set_ios、get_cd等回调函数
struct mmc_request 核心层 封装MMC请求,包含cmd、data、stop等子请求
struct mmc_command 核心层 描述MMC命令,包含opcode、arg、resp等字段
struct mmc_data 核心层 描述数据传输,包含blksz、blocks、sg、flags等字段
struct mmc_blk_data 块设备层 描述MMC块设备,包含disk、read_only、part_curr等字段

下面的Mermaid图展示了Linux eMMC子系统的三层架构以及各层之间的交互关系:
设备树配置 emmc节点 clocks属性 bus-width属性 硬件层 eMMC设备 CLK/CMD/DATA信号 主机层 kdrv_emmc.ko 硬件抽象 寄存器操作 核心层 mmc_core.ko 协议处理 设备管理 电源管理 块设备层 mmc_block.ko 块设备请求队列 用户空间接口 /dev/mmcblk*

3 核心数据结构与代码分析

3.1 关键数据结构详解

struct mmc_host是描述MMC主机控制器的核心数据结构,每个eMMC控制器都对应一个该结构体的实例。其主要字段包括:

c 复制代码
struct mmc_host {
    const struct mmc_host_ops *ops;        // 主机操作函数集
    struct device *parent;                 // 父设备指针
    struct device class_dev;               // 类设备
    int index;                            // 主机索引号
    const struct mmc_bus_ops *bus_ops;    // 总线操作函数集
    unsigned int f_min, f_max;            // 最小和最大时钟频率
    u32 ocr_avail;                        // 可用电压范围
    u32 caps;                             // 主机能力标志
    u32 max_current_330, max_current_300, max_current_180; // 最大电流
    unsigned int max_seg_size;            // 最大段大小
    unsigned short max_segs;              // 最大段数
    unsigned short max_req_size;          // 最大请求大小
    unsigned short max_blk_size;          // 最大块大小
    unsigned short max_blk_count;         // 最大块计数
    unsigned short max_busy_timeout;      // 最大超时时间
    spinlock_t lock;                      // 自旋锁
    struct mmc_ios ios;                   // 当前IO设置
    struct mmc_card *card;                // 连接的设备
    struct mmc_bus_ops *bus_ops;          // 总线操作
    struct dentry *debugfs_root;          // debugfs根目录
    // ... 其他字段
};

其中,caps字段表示主机控制器的能力,常用的标志位包括:

  • MMC_CAP_4_BIT_DATA:支持4位数据总线
  • MMC_CAP_8_BIT_DATA:支持8位数据总线
  • MMC_CAP_MMC_HIGHSPEED:支持MMC高速模式
  • MMC_CAP_ERASE:支持擦除命令
  • MMC_CAP_CMD23:支持CMD23包装命令

struct mmc_card描述已识别的eMMC设备,主要字段包括:

c 复制代码
struct mmc_card {
    struct mmc_host *host;                // 所属主机
    struct device dev;                    // 设备结构
    unsigned int rca;                     // 相对设备地址
    unsigned int type;                    // 设备类型
    unsigned long state;                  // 设备状态
    u32 ocr;                             // 操作条件寄存器
    u32 cid[4];                          // 设备标识符
    u32 csd[4];                          // 设备特定数据
    u32 ext_csd[512];                    // 扩展设备特定数据
    u32 raw_cid[4], raw_csd[4], raw_scr[2], raw_ssr[16];
    struct mmc_csd csd;                  // 解析后的CSD
    struct mmc_ext_csd ext_csd;          // 解析后的EXT_CSD
    // ... 其他字段
};

struct mmc_request封装完整的MMC请求,可能包含命令、数据和停止命令:

c 复制代码
struct mmc_request {
    struct mmc_command *sbc;             // 前置命令(如CMD23)
    struct mmc_command *cmd;             // 主命令
    struct mmc_data *data;               // 数据传输
    struct mmc_command *stop;            // 停止命令
    struct completion completion;        // 完成量
    void *done_data;                     // 完成回调数据
    void (*done)(struct mmc_request *);  // 完成回调函数
    // ... 其他字段
};

3.2 核心函数与工作流程

设备探测流程mmc_rescan函数开始,这是eMMC子系统中最关键的函数之一:

c 复制代码
void mmc_rescan(struct work_struct *work)
{
    struct mmc_host *host = container_of(work, struct mmc_host, detect.work);
    int i;
    
    // 检查主机是否被移除
    if (mmc_host_is_removable(host)) {
        // 检测设备是否存在
        if (!mmc_try_claim_host(host))
            return;
        if (!mmc_get_cd(host)) {
            mmc_release_host(host);
            goto out;  // 设备不存在
        }
        mmc_release_host(host);
    }
    
    // 尝试获取主机控制权
    if (!mmc_try_claim_host(host))
        return;
    
    // 发送初始化序列
    mmc_power_up(host, host->ocr_avail);
    
    // 发送CMD0使设备进入空闲状态
    mmc_go_idle(host);
    
    // 发送CMD1查询设备操作条件
    if (mmc_send_op_cond(host, 0, &ocr))
        goto out;
    
    // 发送CMD2获取CID
    if (mmc_all_send_cid(host, cid))
        goto out;
    
    // 发送CMD3设置RCA
    if (mmc_set_relative_addr(host))
        goto out;
    
    // 发送CMD9获取CSD
    if (mmc_send_csd(host, card->raw_csd))
        goto out;
    
    // 发送CMD7选择设备
    if (mmc_select_card(host, card))
        goto out;
    
    // 读取EXT_CSD
    if (!mmc_card_sd(card) && host->ops->get_cd && mmc_card_highspeed(card)) {
        err = mmc_read_ext_csd(host, ext_csd);
        if (!err) {
            // 解析EXT_CSD并配置设备
            mmc_decode_ext_csd(host, card, ext_csd);
        }
    }
    
    // 配置设备为高速模式(如支持)
    if (card->ext_csd.hs_max_dtr && host->caps & MMC_CAP_MMC_HIGHSPEED) {
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, 
                        EXT_CSD_HS_TIMING, 1, 100000);
        if (!err) {
            // 切换主机时序
            mmc_set_timing(host, MMC_TIMING_MMC_HS);
        }
    }
    
    // 配置总线宽度
    if (host->caps & MMC_CAP_8_BIT_DATA) {
        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
                        EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_8, 100000);
        if (!err) {
            // 设置主机总线宽度
            host->ios.bus_width = MMC_BUS_WIDTH_8;
            host->ops->set_ios(host, &host->ios);
        }
    }
    
    // 注册块设备
    mmc_add_card(host, card);
    
    mmc_release_host(host);
    
out:
    // 重新调度检测工作(用于可移动设备)
    if (mmc_host_is_removable(host))
        mmc_schedule_delayed_work(&host->detect, HZ);
}

请求处理流程 是eMMC子系统的另一个核心部分。当块设备层有I/O请求时,会调用mmc_blk_issue_rq函数:

c 复制代码
static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
{
    struct mmc_blk_data *md = mq->blkdata;
    struct mmc_card *card = md->queue.card;
    struct mmc_blk_request *brq = &mq->brq;
    int ret = 0;
    
    // 准备MMC请求
    mmc_blk_rw_rq_prep(mq, req, brq, 1);
    
    // 设置完成回调
    brq->mrq.done = mmc_blk_req_done;
    
    // 下发请求到主机层
    mmc_wait_for_req(card->host, &brq->mrq);
    
    // 处理请求结果
    ret = mmc_blk_err_check(brq);
    if (ret)
        goto out;
    
    // 处理写入完成后的刷缓存操作(如需要)
    if (brq->cmd.flags & MMC_RSP_BUSY)
        mmc_blk_busy_wait(mq, req);
        
out:
    // 完成请求
    mmc_blk_rw_rq_post(mq, req, brq);
    return ret;
}

下面的Mermaid时序图展示了eMMC设备探测和初始化的完整流程:
用户空间 块设备层 核心层 主机层 eMMC设备 设备探测与初始化时序 mmc_add_host()注册主机 启动mmc_rescan工作队列 mmc_power_up()上电 硬件上电 CMD0(GO_IDLE_STATE) 无响应 CMD1(SEND_OP_COND) R3响应(OCR) CMD2(ALL_SEND_CID) R2响应(CID) CMD3(SET_RELATIVE_ADDR) R1响应(RCA) CMD9(SEND_CSD) R2响应(CSD) CMD7(SELECT_CARD) R1响应 CMD8(SEND_EXT_CSD) R1响应 + 数据块 解析EXT_CSD CMD6(SWITCH)切换高速模式 R1响应 set_ios()配置主机时序 CMD6(SWITCH)切换总线宽度 R1响应 set_ios()配置总线宽度 mmc_add_card()注册块设备 设备节点/dev/mmcblk0可用 用户空间 块设备层 核心层 主机层 eMMC设备

3.3 设备树配置与硬件抽象

在嵌入式Linux系统中,eMMC主机控制器通常通过设备树进行配置。以下是一个典型的eMMC设备树节点示例:

dts 复制代码
emmc {
    compatible = "sstar_mci";                    // 兼容性字符串,用于匹配驱动
    clocks = <&CLK_sd>;                          // 时钟源
    slot-num = <1>;                              // 使用的SD/SDIO IP个数
    adma-mode = <1>,<1>,<1>;                     // data传输模式:0-dma, 1-adma
    ip-select = <1>,<1>,<2>;                     // 对应卡槽的IP编号
    pad-select = <0>,<0>,<0>;                    // 对应卡槽的padmux mode编号
    bus-width = <8>,<4>,<4>;                     // 总线宽度:4-4bit, 8-8bit
    max-clks = <48000000>,<48000000>,<48000000>; // 支持的最大时钟频率
    status = "ok";                               // 设备状态
};

设备树中的每个属性都有特定的作用:

  • compatible属性是驱动匹配的关键,必须与驱动程序中of_device_id表的兼容字符串一致。
  • clocks属性指定主机控制器使用的时钟源。
  • bus-width属性配置数据总线的宽度,eMMC支持1-bit、4-bit和8-bit模式。
  • max-clks属性设置最大时钟频率,影响数据传输速率。
  • adma-mode选择数据传输使用的DMA模式,ADMA(Advanced DMA)通常比标准DMA效率更高。

主机驱动在探测阶段会解析这些设备树属性,并据此配置硬件寄存器。例如,当解析bus-width = <8>时,驱动会设置主机控制器使用8位数据总线,并通过mmc_switch命令配置eMMC设备也使用8位模式。

4 简单实例与代码展示

4.1 简化版eMMC主机驱动实例

以下是一个简化版的eMMC主机驱动示例,展示了驱动的基本框架和关键操作:

c 复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>

// 主机驱动私有数据结构
struct my_emmc_host {
    struct mmc_host *mmc;          // 标准MMC主机结构
    void __iomem *regbase;         // 寄存器基地址
    struct clk *clk;               // 时钟
    int irq;                       // 中断号
};

// 主机操作函数集
static const struct mmc_host_ops my_emmc_ops = {
    .request = my_emmc_request,     // 请求处理函数
    .set_ios = my_emmc_set_ios,     // 配置总线参数
    .get_cd = my_emmc_get_cd,       // 检测设备插入
    .get_ro = my_emmc_get_ro,       // 检测写保护
    .enable_sdio_irq = my_emmc_enable_sdio_irq, // 使能SDIO中断
};

// 请求处理函数
static void my_emmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
    struct my_emmc_host *host = mmc_priv(mmc);
    struct mmc_command *cmd = mrq->cmd;
    struct mmc_data *data = mrq->data;
    
    // 处理命令阶段
    if (cmd) {
        // 将命令写入命令寄存器
        writel(cmd->opcode, host->regbase + COMMAND_REG);
        writel(cmd->arg, host->regbase + ARGUMENT_REG);
        
        // 等待命令完成中断或超时
        // ...
        
        // 读取响应
        if (cmd->flags & MMC_RSP_PRESENT) {
            if (cmd->flags & MMC_RSP_136) {
                // 136位响应
                cmd->resp[0] = readl(host->regbase + RESPONSE_0_REG);
                cmd->resp[1] = readl(host->regbase + RESPONSE_1_REG);
                cmd->resp[2] = readl(host->regbase + RESPONSE_2_REG);
                cmd->resp[3] = readl(host->regbase + RESPONSE_3_REG);
            } else {
                // 48位响应
                cmd->resp[0] = readl(host->regbase + RESPONSE_0_REG);
            }
        }
        
        // 检查命令错误
        if (cmd->error) {
            mmc_request_done(host->mmc, mrq);
            return;
        }
    }
    
    // 处理数据传输阶段
    if (data) {
        if (data->flags & MMC_DATA_READ) {
            // 读取数据
            sg_miter_start(&host->sg_miter, data->sg, data->sg_len, 
                          SG_MITER_ATOMIC | SG_MITER_TO_SG);
            while (sg_miter_next(&host->sg_miter)) {
                void *buf = host->sg_miter.addr;
                size_t len = host->sg_miter.length;
                
                // 从数据寄存器读取数据
                readsb(host->regbase + DATA_REG, buf, len);
            }
            sg_miter_stop(&host->sg_miter);
        } else {
            // 写入数据
            sg_miter_start(&host->sg_miter, data->sg, data->sg_len, 
                          SG_MITER_ATOMIC | SG_MITER_FROM_SG);
            while (sg_miter_next(&host->sg_miter)) {
                void *buf = host->sg_miter.addr;
                size_t len = host->sg_miter.length;
                
                // 向数据寄存器写入数据
                writesb(host->regbase + DATA_REG, buf, len);
            }
            sg_miter_stop(&host->sg_miter);
        }
        
        // 完成数据传输
        data->bytes_xfered = data->blocks * data->blksz;
    }
    
    // 完成请求
    mmc_request_done(host->mmc, mrq);
}

// 配置总线参数
static void my_emmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
    struct my_emmc_host *host = mmc_priv(mmc);
    u32 reg;
    
    // 配置时钟频率
    if (ios->clock) {
        reg = readl(host->regbase + CLOCK_REG);
        reg &= ~CLOCK_RATE_MASK;
        reg |= ios->clock & CLOCK_RATE_MASK;
        writel(reg, host->regbase + CLOCK_REG);
    }
    
    // 配置总线宽度
    reg = readl(host->regbase + CONTROL_REG);
    reg &= ~BUS_WIDTH_MASK;
    switch (ios->bus_width) {
    case MMC_BUS_WIDTH_1:
        reg |= BUS_WIDTH_1BIT;
        break;
    case MMC_BUS_WIDTH_4:
        reg |= BUS_WIDTH_4BIT;
        break;
    case MMC_BUS_WIDTH_8:
        reg |= BUS_WIDTH_8BIT;
        break;
    }
    writel(reg, host->regbase + CONTROL_REG);
    
    // 配置电源模式
    if (ios->power_mode == MMC_POWER_OFF) {
        // 关闭电源
        reg = readl(host->regbase + POWER_REG);
        reg &= ~POWER_ON;
        writel(reg, host->regbase + POWER_REG);
    } else if (ios->power_mode == MMC_POWER_UP) {
        // 开启电源
        reg = readl(host->regbase + POWER_REG);
        reg |= POWER_ON;
        writel(reg, host->regbase + POWER_REG);
    }
}

// 探测函数
static int my_emmc_probe(struct platform_device *pdev)
{
    struct mmc_host *mmc;
    struct my_emmc_host *host;
    struct resource *res;
    int ret;
    
    // 分配MMC主机结构
    mmc = mmc_alloc_host(sizeof(struct my_emmc_host), &pdev->dev);
    if (!mmc) {
        dev_err(&pdev->dev, "Failed to allocate MMC host\n");
        return -ENOMEM;
    }
    
    host = mmc_priv(mmc);
    host->mmc = mmc;
    
    // 获取寄存器资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    host->regbase = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(host->regbase)) {
        ret = PTR_ERR(host->regbase);
        goto host_free;
    }
    
    // 获取时钟资源
    host->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(host->clk)) {
        ret = PTR_ERR(host->clk);
        goto host_free;
    }
    
    // 配置MMC主机能力
    mmc->ops = &my_emmc_ops;
    mmc->f_min = 400000;           // 最小频率400KHz
    mmc->f_max = 52000000;         // 最大频率52MHz
    mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; // 可用电压
    mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA | 
                MMC_CAP_MMC_HIGHSPEED | MMC_CAP_ERASE;
    mmc->max_segs = 64;            // 最大段数
    mmc->max_seg_size = 65536;     // 最大段大小
    mmc->max_req_size = 512 * 1024; // 最大请求大小
    mmc->max_blk_size = 512;       // 最大块大小
    mmc->max_blk_count = 4096;     // 最大块数
    
    // 注册MMC主机
    ret = mmc_add_host(mmc);
    if (ret)
        goto host_free;
    
    platform_set_drvdata(pdev, host);
    dev_info(&pdev->dev, "My eMMC host driver initialized\n");
    return 0;
    
host_free:
    mmc_free_host(mmc);
    return ret;
}

4.2 用户空间eMMC设备使用示例

在用户空间,eMMC设备通常表现为块设备节点,可以通过标准文件操作或工具进行访问。以下示例展示了如何在用户空间与eMMC设备交互:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fs.h>

// 读取eMMC设备信息
int read_emmc_info(const char *device_path) {
    int fd;
    unsigned long long size;
    
    // 打开eMMC块设备
    fd = open(device_path, O_RDONLY);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }
    
    // 获取设备大小
    if (ioctl(fd, BLKGETSIZE64, &size) == 0) {
        printf("Device size: %llu bytes (%.2f GB)\n", 
               size, (double)size / (1024 * 1024 * 1024));
    }
    
    // 读取CSD寄存器信息(需要通过debugfs)
    system("cat /sys/kernel/debug/mmc0/ios");
    
    close(fd);
    return 0;
}

// 简单的eMMC性能测试
void emmc_benchmark(const char *device_path) {
    int fd;
    char buffer[512 * 1024]; // 512KB缓冲区
    struct timeval start, end;
    double elapsed;
    
    fd = open(device_path, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return;
    }
    
    // 写入性能测试
    gettimeofday(&start, NULL);
    for (int i = 0; i < 100; i++) {
        if (write(fd, buffer, sizeof(buffer)) != sizeof(buffer)) {
            perror("Write failed");
            break;
        }
    }
    gettimeofday(&end, NULL);
    
    elapsed = (end.tv_sec - start.tv_sec) + 
              (end.tv_usec - start.tv_usec) / 1000000.0;
    printf("Write performance: %.2f MB/s\n", 
           (100.0 * sizeof(buffer) / (1024 * 1024)) / elapsed);
    
    close(fd);
}

int main() {
    const char *emmc_device = "/dev/mmcblk0";
    
    printf("=== eMMC Device Information ===\n");
    read_emmc_info(emmc_device);
    
    printf("\n=== eMMC Performance Benchmark ===\n");
    emmc_benchmark(emmc_device);
    
    return 0;
}

4.3 通过sysfs进行eMMC调试

Linux内核提供了丰富的sysfs接口用于eMMC设备的调试和监控。以下是一些常用的调试命令:

bash 复制代码
# 进入eMMC设备sysfs目录
cd /sys/device/platform/soc/soc:emmc

# 开启读写监控
echo 1 > eMMC_monitor_count_enable

# 查看读写监控信息
cat eMMC_monitor_count_enable

# 开启写操作log打印
echo 1 > eMMC_write_log_enable

# 开启读操作log打印  
echo 1 > eMMC_read_log_enable

# 查看eMMC的bootbus值
cat eMMC_bootbus

# 查看eMMC的工作时钟频率
cat eMMC_get_clock

# 启动eMMC IP验证测试
echo 1 > eMMC_run_ipverify

5 核心模型与框架剖析

5.1 eMMC协议状态机解析

eMMC设备在运行过程中会处于不同的状态,这些状态构成了一个复杂的状态机。理解这个状态机对于深入掌握eMMC工作原理至关重要。主要状态包括:

  • 空闲状态(Idle State):设备上电或复位后的初始状态,在此状态下设备等待初始化命令。
  • 就绪状态(Ready State):设备已响应CMD1命令,准备好进行识别过程。
  • 识别状态(Identification State):主机正在通过CMD2和CMD3命令识别设备并分配地址。
  • 待命状态(Stand-by State):设备已被识别,但未被选中进行数据传输。
  • 传输状态(Transfer State):设备被选中(通过CMD7),可以进行数据传输。
  • 发送数据状态(Sending-data State):设备正在向主机发送数据。
  • 接收数据状态(Receive-data State):设备正在接收主机发送的数据。
  • 编程状态(Programming State):设备正在将接收到的数据编程到闪存中。
  • 断开连接状态(Disconnect State):设备处于高阻抗状态,用于SDIO操作。

下面的Mermaid状态图展示了eMMC设备的主要状态转换:
上电/复位 CMD1 CMD2 CMD3 CMD7 CMD7(其他RCA) 读命令 数据传输完成 写命令 CMD15 CMD7 数据传输完成 编程完成 IdleState ReadyState IdentificationState StandByState TransferState SendingDataState ReceiveDataState ProgrammingState 编程完成 Busy DisconnectState

状态转换由特定的命令触发,例如从Idle状态转换到Ready状态需要通过CMD1命令,而从Stand-by状态转换到Transfer状态需要通过CMD7命令选择设备。在Programming状态期间,设备会拉低DAT0线表示忙状态,直到内部编程操作完成。

5.2 数据传输的DMA描述符机制

为了提高数据传输效率,eMMC主机控制器通常使用DMA(直接内存访问)进行数据传输。ADMA(Advanced DMA)是eMMC规范中定义的一种更高效的DMA机制,它使用描述符表来管理传输过程。

ADMA描述符的数据结构如下:

c 复制代码
struct adma_descriptor {
    u16 attr;              // 属性字段
    u16 length;            // 数据长度
    u32 addr;              // 数据地址
    u32 next_desc;         // 下一个描述符地址
};

// 属性字段定义
#define ADMA_ATTR_VALID     (1 << 0)    // 描述符有效
#define ADMA_ATTR_END       (1 << 1)    // 最后一个描述符
#define ADMA_ATTR_INT       (1 << 2)    // 传输完成产生中断
#define ADMA_ATTR_ACT_NOP   (0 << 4)    // 无操作
#define ADMA_ATTR_ACT_TRAN  (2 << 4)    // 传输数据
#define ADMA_ATTR_ACT_LINK  (3 << 4)    // 链接到下一个描述符

ADMA的工作流程如下:

  1. 驱动程序准备ADMA描述符表,每个描述符指向一个内存中的数据缓冲区
  2. 将描述符表的基地址写入主机的ADMA系统地址寄存器
  3. 启动DMA传输,主机控制器按顺序处理描述符表中的每个描述符
  4. 当处理到带有END标志的描述符时,传输完成,产生中断

与简单的DMA相比,ADMA的优势在于它可以处理分散/聚集(scatter-gather)I/O,无需物理上连续的内存缓冲区。当处理文件系统的分散读写请求时,ADMA可以高效地将多个不连续的数据块通过一次eMMC请求完成传输。

5.3 错误处理与恢复机制

eMMC子系统实现了完善的错误处理与恢复机制,确保在传输失败时能够恢复正常操作。主要的错误类型包括:

  • 命令超时错误:设备在规定时间内没有响应命令
  • CRC错误:命令、响应或数据传输的CRC校验失败
  • 数据超时错误:数据传输未在规定时间内完成
  • 对齐错误:数据缓冲区地址或长度不符合要求

当检测到错误时,eMMC子系统会采取以下恢复措施:

c 复制代码
// 错误恢复函数示例
static int mmc_error_recovery(struct mmc_host *host, struct mmc_request *mrq)
{
    struct mmc_card *card = host->card;
    int err;
    
    // 重置主机控制器
    mmc_hw_reset(host);
    
    // 重新初始化设备
    err = mmc_init_card(host, card->ocr, card);
    if (err) {
        pr_err("Failed to reinit card after error: %d\n", err);
        return err;
    }
    
    // 恢复之前的I/O设置
    mmc_set_ios(host);
    
    // 重新下发失败的请求(如果可能)
    if (mrq && !mrq->error) {
        pr_info("Retrying failed request\n");
        mmc_wait_for_req(host, mrq);
    }
    
    return 0;
}

对于可恢复的错误,如偶尔的CRC错误或超时,子系统会自动重试操作。对于严重错误,如设备无响应或硬件故障,子系统会报告错误并向上层传递错误信息。

表:eMMC子系统错误类型与处理策略

错误类型 检测方式 恢复策略 严重程度
命令CRC错误 响应CRC校验 重试命令(最多3次)
数据CRC错误 数据CRC校验 重试数据传输
命令超时 计时器超时 重置主机控制器并重试
数据超时 数据线无活动 重置数据线并重试
电压不匹配 OCR寄存器检查 调整电压或放弃初始化
设备无响应 无CMD线响应 硬件复位或设备移除 严重

6 工具命令与调试手段

6.1 常用调试工具与命令

eMMC子系统的调试涉及多个层面,从硬件信号到上层文件系统。以下是一些常用的调试工具和命令:

基本状态检查命令:

bash 复制代码
# 查看eMMC设备信息
cat /sys/kernel/debug/mmc0/mmc0\:0001/ext_csd

# 查看eMMC设备寄存器信息
cat /sys/kernel/debug/mmc0/ios

# 查看eMMC设备时钟频率
cat /sys/kernel/debug/mmc0/clock

# 查看eMMC磨损程度估计(需要解析EXT_CSD)
cat /sys/kernel/debug/mmc0/mmc0\:0001/ext_csd | python -c 'import binascii, sys; print "~%d%% wear" % (ord(binascii.unhexlify(sys.stdin.read().strip())[0x5e])*10)'

# 查看块设备信息
lsblk /dev/mmcblk0
fdisk -l /dev/mmcblk0

性能测试工具fio的使用

bash 复制代码
# 安装fio工具
apt-get install fio

# 顺序写测试(1MB块大小,1GB数据量)
fio -filename=/dev/mmcblk0p1 -direct=1 -thread -rw=write -ioengine=psync -bs=1M -size=1G -numjobs=1 -group_reporting -name=emmc_test

# 顺序读测试  
fio -filename=/dev/mmcblk0p1 -direct=1 -thread -rw=read -ioengine=psync -bs=1M -size=1G -numjobs=1 -group_reporting -name=emmc_test

# 随机写测试(4KB块大小,4线程)
fio -filename=/dev/mmcblk0p1 -direct=1 -thread -rw=randwrite -ioengine=psync -bs=4k -size=1G -numjobs=4 -group_reporting -name=emmc_test

# 随机读测试
fio -filename=/dev/mmcblk0p1 -direct=1 -thread -rw=randread -ioengine=psync -bs=4k -size=1G -numjobs=4 -group_reporting -name=emmc_test

eMMC健康状态监测:

bash 复制代码
# 挂载debugfs
mount -t debugfs none /sys/kernel/debug/

# 读取eMMC健康状态(磨损程度)
cat /sys/kernel/debug/mmc0/mmc0\:0001/ext_csd | python -c 'import binascii, sys; print "~%d%% wear" % (ord(binascii.unhexlify(sys.stdin.read().strip())[0x5e])*10)'

# 查看eMMC错误统计
cat /sys/kernel/debug/mmc0/err_stats

# 查看eMMC重试计数
cat /sys/kernel/debug/mmc0/retune_count

6.2 故障排查与性能优化

常见故障排查方法

  1. 设备识别失败

    • 检查硬件连接和电源稳定性
    • 验证设备树配置是否正确
    • 检查时钟配置和信号完整性
    • 使用示波器测量CMD和DAT信号质量
  2. 数据传输错误

    • 降低总线频率测试稳定性
    • 检查DMA缓冲区对齐和大小
    • 验证CRC错误计数
    • 检查电源噪声和接地问题
  3. 性能优化建议

    • 使用8位总线宽度代替4位或1位
    • 启用HS400或HS200高速模式
    • 调整块大小和请求队列深度
    • 使用ADMA代替标准DMA
    • 启用命令队列(CQ)功能

设备树配置优化示例

dts 复制代码
&emmc {
    compatible = "sstar_mci";
    clocks = <&CLK_sd>;
    slot-num = <1>;
    
    /* 使用ADMA传输模式 */
    adma-mode = <1>,<1>,<1>;
    
    /* 配置为8位总线宽度 */
    bus-width = <8>,<4>,<4>;
    
    /* 提高最大时钟频率 */
    max-clks = <52000000>,<52000000>,<52000000>;
    
    /* 启用高速模式 */
    mmc-hs200-1_8v;
    
    /* 禁用1.8V电压(如不支持) */
    /* no-1-8-v; */
    
    status = "ok";
};

内核参数调整

bash 复制代码
# 提高读写超时时间(针对慢速设备)
echo 5000 > /sys/block/mmcblk0/device/timeout

# 调整调度器为deadline(针对实时性要求高的场景)
echo deadline > /sys/block/mmcblk0/queue/scheduler

# 增加队列深度
echo 128 > /sys/block/mmcblk0/queue/nr_requests

7 总结与梳理

7.1 eMMC子系统核心机制总结

通过对Linux eMMC子系统的深入分析,我们可以总结出以下几个核心机制:

  1. 分层架构设计:eMMC子系统采用清晰的三层架构 - 块设备层、核心层和主机层,这种设计实现了硬件无关性与硬件相关性的分离,提高了代码的可维护性和可移植性。

  2. 状态机管理:eMMC设备操作基于精细的状态机模型,从设备识别到数据传输,每个状态转换都由特定命令触发,确保了协议的正确实现。

  3. 请求处理流水线:I/O请求经过多级处理,从块设备层的请求队列到核心层的协议转换,再到主机层的硬件操作,形成了一个高效的处理流水线。

  4. 错误恢复机制:子系统实现了多级错误检测和恢复机制,包括命令重试、CRC错误处理、超时管理和硬件复位等,确保了系统的可靠性。

  5. 性能优化策略:通过总线宽度扩展、高速时序模式、DMA传输、命令队列等技术,充分发挥了eMMC设备的性能潜力。

7.2 关键知识点回顾

表:eMMC子系统关键知识点总结

知识点 核心内容 相关数据结构/函数
设备探测 上电、复位、识别、初始化流程 mmc_rescan, mmc_init_card
请求处理 命令构建、数据传输、完成回调 mmc_request, mmc_host_ops.request
电源管理 电压切换、时钟控制、电源状态 mmc_set_ios, mmc_power_up
错误处理 CRC校验、超时管理、重试机制 mmc_error_recovery, 错误统计
性能优化 总线宽度、时序模式、DMA mmc_switch, ADMA描述符
调试手段 sysfs接口、debugfs、性能测试 fio工具、ext_csd解析
相关推荐
亚林瓜子2 小时前
在amazon linux 2023上面通过Fedora 36软件仓库源安装tesseract5
linux·运维·服务器·ocr·tesseract·amazon·fedor
是专家不是砖家2 小时前
linux USB摄像头不停掉线问题
linux·运维·服务器
yuanManGan2 小时前
走进Linux的世界:初识进程(Task)
linux·运维·服务器
NiKo_W2 小时前
Linux UdpSocket的应用
linux·服务器·网络·内核·线程
AI柠檬2 小时前
C语言基于MPI并行计算矩阵的乘法
c语言·c++·算法
lin__ying2 小时前
机器学习-聚类
算法·机器学习
稚辉君.MCA_P8_Java2 小时前
深入理解 TCP;场景复现,掌握鲜为人知的细节
java·linux·网络·tcp/ip·kubernetes
小无名呀2 小时前
socket_udp
linux·网络·c++·网络协议·计算机网络·udp
wusam2 小时前
计算机网络实验04:IP与ICMP数据报分析实验
网络·计算机网络·icmp分片报文