当前位置:首页>Linux>Linux 6.18 内核:一张 SD 卡的“诞生”全流程源码解读

Linux 6.18 内核:一张 SD 卡的“诞生”全流程源码解读

  • 2026-06-29 15:11:58
Linux 6.18 内核:一张 SD 卡的“诞生”全流程源码解读

当你插入一张 SD 卡,Linux 内核做了什么?

1 先备知识:SD卡协议与Linux MMC子系统地图

要想真正理解Linux内核对SD卡的初始化流程,而不是停留在“插卡就能用”的表面,需要先建立两张地图:一张是硬件协议的地图,另一张是内核软件架构的地图。它们叠加在一起,才能让你看到代码时知道“哦,这里是在给卡发地址,那里是在调电压”。
本章就从物理层出发,梳理SD卡状态机,再绘制Linux MMC子系统的分层结构,最后列一份6.18内核中的关键文件清单,为后面的源码导读铺路。

1.1 SD卡物理层:引脚、总线模式和初始态

SD 卡最初是从 MMC 卡演化而来的。你可能最熟悉的还是那个指甲盖大小的 micro SD 卡(也叫 TF 卡)。别被它小巧的外形迷惑,它的引脚和总线模式直接决定了初始化代码该怎么写。

标准 micro SD 卡(不含 UHS-II)有 8 个物理引脚,通过卡槽触点引出后,功能和标准大 SD 卡完全兼容。引脚排列如下:

注:卡检测DAT3(CD)通常由卡槽的独立机械开关或专用 GPIO 实现,这 8 个引脚本身不直接提供卡检测功能。

初始化时只用 1-bit 总线,这一点至关重要。无论你插的是标准大卡还是 micro SD 卡,所有命令和响应都走 CMD 线,数据交互最初只使用 DAT0 这一根数据线。而且,上电后 CMD 线的工作模式是开漏输出,多条设备可以共享,这也是为什么 SD 卡协议里有一大堆“广播命令”(Broadcast Commands),不需要地址就能发给所有卡。在完成卡的地址分配(CMD3/RCA)并选中某个卡(CMD7)之前,CMD 线无法进入推挽高速模式。

SD 卡的总线宽度是可以动态切换的:初始化完成后,可以通过 ACMD6 命令从 1-bit 切换到 4-bit 模式(DAT0~DAT3 全用上)。至于 UHS-II 这种更高速的模式,它使用了额外的差分信号线,为简化篇幅,本文仅聚焦传统 SD 以及 UHS-I 模式下的初始化,这已经覆盖了绝大多数嵌入式和消费级场景。

1.2 SD卡状态机:从Idle到Transfer的必经之路

SD卡内部有一个精确的状态机,主机每发一个命令,卡就可能跳转到新的状态。理解这个状态机,是阅读 mmc_sd_init_card 函数的前提。关键状态简化如下:
  1. Idle(空闲态):卡上电后自动进入,此时只能识别CMD0(GO_IDLE_STATE)和CMD8(SEND_IF_COND)等极少数命令。
  2. Ready(就绪态):当卡通过CMD8确认电压兼容,并通过ACMD41完成OCR(操作条件寄存器)的电压和容量协商后,卡进入Ready。此时卡已经“醒”了,但还没有获得地址。
  3. Ident(识别态):通过CMD2(ALL_SEND_CID)获取每个卡的唯一ID(CID),卡进入Ident态。因为CMD2是广播命令,总线上所有卡都会把自己的CID发出来,所以需要一个仲裁过程(不过现在总线上一般只有一张卡)。
  4. Stby(待机态):通过CMD3(SEND_RELATIVE_ADDR)给卡分配一个RCA(相对卡地址),卡进入Stby。此时卡已经有一个“短地址”,不会再响应广播的CMD2,但还没有被选中进行数据传输。
  5. Transfer(传输态):通过CMD7(SELECT/DESELECT_CARD)选中指定RCA的卡,卡进入Transfer态,此时可以进行数据读写、寄存器配置等所有带数据交互的操作。
初始化代码本质上就是驱动这个状态机从头走到Transfer,中间伴随着电压选择、容量判断、速度等级协商等旁路任务。这张图在后续章节会反复出现,建议你此刻就在脑中把它刻下来。

1.3 SD命令与响应:一瞥通信规范

每个SD命令都由主机发起,格式是一个48位的命令令牌(Command Token),包括6位的命令索引(如CMD0=0, CMD8=8, CMD17=17等)和32位参数,以及CRC7校验。响应也有多种类型:R1(普通状态响应)、R3(OCR寄存器)、R7(CMD8的响应)等,长度从48位到136位不等。
内核中使用 struct mmc_command 描述一个命令,它包括:
struct mmc_command {    u32         opcode;    u32         arg;    u32         resp[4];    unsigned int        flags;      /* expected response type */    unsigned int        retries;    /* max number of retries */    int         error;      /* command error */    unsigned int        busy_timeout;   /* busy detect timeout in ms */    struct mmc_data     *data;      /* data segment associated with cmd */    struct mmc_request  *mrq;       /* associated request */    struct uhs2_command *uhs2_cmd;  /* UHS2 command */    /* for SDUC */    bool has_ext_addr;    u8 ext_addr;};
一个 mmc_command 对象通常经由以下步骤完成使命:
  1. 栈上或 kzalloc 分配 —— 初始化函数局部变量或 request 内嵌。
  2. 填充 opcode, arg, flags —— 根据当前状态机需求设定命令。
  3. 设置 retries 和 busy_timeout —— 根据命令特性配置容错和等待时间。
  4. 关联 data(如有) —— 数据阶段命令才设置。
  5. 通过 mmc_wait_for_cmd / mmc_wait_for_req 下发 —— 核心函数会将 cmd 塞入 request,然后调用 host->ops->request 把整个请求推给硬件控制器。
  6. 硬件完成,resp[] 和 error 被填写 —— 中断上下文或轮询结束后,数据就绪。
  7. 上层检查 error 并解析 resp —— 提取 CID、CSD、RCA 等,决定下一步状态跳转。
而 struct mmc_request 则将一个命令和可选的数据传输包装成一个整体,交给Host控制器驱动执行。理解这两个结构体的关系,是理解mmc core如何与host驱动交互的关键,不过我们现在不需要钻进它们的实现,只要知道每一次“发命令”都对应一个 mmc_command 的填充和下发即可。

1.4 Linux MMC子系统分层架构

Linux把SD/MMC卡的支持拆成了一个清晰的三层架构,加上顶层的块设备层,就是四层。每一层在内核中都有明确的目录和职责:
用户态                   /dev/mmcblk0───────────────────────────────────────块设备层 (block layer)   drivers/mmc/core/block.c                         mmc_blk_probe → 创建 gendisk───────────────────────────────────────MMC核心层 (mmc core)     drivers/mmc/core/  │  ├─ core.c          (host注册、rescan)  │  ├─ sd.c            (SD卡协议实现)  │  ├─ mmc.c           (MMC通用命令封装)  │  ├─ sd_ops.c        (SD专用命令)  │  └─ queue.c         (请求队列)───────────────────────────────────────HOST控制器驱动 (host)    drivers/mmc/host/  │  ├─ sdhci.c         (标准SD Host Controller Interface)  │  └─ 具体SoC驱动     (如 sdhci-msm.c, dw_mmc.c)───────────────────────────────────────物理层                   SD卡硬件、总线
  • 块设备层:它与普通的磁盘驱动别无二致,负责生成 /dev/mmcblkX 设备节点,把上层的bio请求转换为mmc请求,管理分区和请求队列。
  • MMC核心层:这是整个子系统的“大脑”,它负责协议状态的推进、卡识别、总线切换、错误处理,以及与host控制器的交互。无论是SD卡、SDIO卡还是eMMC,都共用这一套框架,只是通过不同的 mmc_bus_ops 来区分协议。
  • Host控制器驱动:它面向具体的硬件寄存器,实现 struct mmc_host_ops 中的方法,例如 request(发送命令)、set_ios(设置时钟、总线宽度、电压)等。sdhci.c 提供了SD Host Controller标准接口的通用实现,大部分SoC的host驱动都基于它扩展。
这种分层设计使得卡协议的变动(如UHS-II)只需修改core/sd.c,而硬件的差异被隔离在host驱动中,互相不会直接干扰。

1.5 关键文件索引(基于内核6.18)

阅读SD卡初始化代码,你需要重点关注以下几个文件,这里列出了它们的主要职责,方便你日后直接跳转:
6.18版本注意:虽然主线内核始终在演进,但这些核心文件的路径和主要函数名称保持了高度稳定。本文基于6.18代码,若你使用其他版本,差异通常只体现在UHS-II支持、SD Express等新特性上,基本初始化流程依然适用。

1.6 SD卡关键寄存器:OCR、CID、CSD、RCA

初始化过程中会频繁读取卡上的几个寄存器,它们的含义最好提前有个印象:
  1. OCR(Operation Conditions Register):32位,在ACMD41的响应R3中返回。其中bit31(busy)表示卡是否完成了上电初始化;bit30(CCS)指示容量类型(0=SDSC,1=SDHC/SDXC);其他位则表示支持的电压范围(如3.2-3.4V对应bit20)。
  2. CID(Card Identification Register):128位,独一无二的卡ID,包含制造商ID、产品名、序列号等,通过CMD2读取。
  3. CSD(Card Specific Data Register):128位,包含容量、最大速率、块长度、写保护等卡特性,通过CMD9读取。CSD有两个版本(v1.0/v2.0),容量计算方式不同,内核用 unstuff_bits 等函数解析。
  4. RCA(Relative Card Address):16位,由主机通过CMD3分配给卡的“短地址”,后续选卡、命令中都要用到它。
这些寄存器在内核中分别对应 mmc_card 结构体里的 raw_cid、raw_csd、ocr 等成员,解析后的参数则存储在 cid、csd、ocr 等子结构体中。

1.7 本章小结

你已经在脑中植入了四样东西:
  1. 初始化之前,SD卡只用CMD和一根数据线,以开漏模式通信;
  2. 卡需要经过“Idle→Ready→Ident→Stby→Transfer”这样一个固定状态机;
  3. Linux用分层架构隔离了协议细节和硬件差异,核心逻辑在mmc core的sd.c中;
  4. 关键文件、命令结构和寄存器名称已提前备好,后续代码不会陌生。
有了这些先备知识,下一章我们就会看到,一次简单的插卡动作,是如何在内核中激起一串长达十几步的连锁反应的。

2 初始化流程总览:一条中断引发的连锁反应

  1. 卡插入 → GPIO/卡检测中断
  2. 调度 mmc_rescan 工作
  3. mmc_rescan 遍历host,执行 mmc_rescan_try_freq
  4. 调用 mmc_attach_sd 进入SD专用初始化
  5. mmc_sd_init_card 完成状态机跳转(IDLE→READY→IDENT→STBY)
  6. mmc_sd_setup_card 进行高速/UHS模式协商
  7. mmc_add_card 注册卡设备到内核
  8. 块设备驱动绑定,生成 /dev/mmcblkX
整个过程可以浓缩成八个阶段,发生在四个实体之间:Card(物理卡)、Host Controller(主机控制器硬件及其驱动)、mmc core(内核核心协议层)和 Block Layer(块设备层)。它们之间的交互就像一场精心编排的四手联弹。

2.1 核心步骤清单

这八个步骤从硬件电平变化一路走到用户空间可用的块设备文件,构成了一条完美的流水线。任何一个环节出错,都可能导致“插了卡但没反应”这种令人抓狂的情况。

2.2 宏观时序图

下面这张Mermaid时序图把这八个阶段压缩到一条时间轴上,每个箭头的动作都能在后续的章节找到对应的代码:

注:图中的 autonumber 编号对应清单中的阶段①~⑧。注意第⑤步是一个“浓缩包”,内部包含了从 Idle 到 Transfer 的全部状态跳转命令,这是第3章的重头戏。

2.3 每个阶段的一句话速写

(请脑补以下文字飘在时序图旁边,帮助建立直觉)
  1. 卡插入:机械动作变成电信号——卡槽的机械开关或者专用检测引脚给出一个边沿触发。
  2. 调度 rescan:内核很聪明,不在中断里死磕,而是优雅地丢给工作队列:“有空了去查一下卡”。
  3. 尝试频率:host 还不知道卡是什么协议,先用安全的低频(400kHz)发个 CMD0 试试水。
  4. 进入 SD 大门:确认是 SD 卡后,开始漫长的电压和容量握手,ACMD41 循环像两个人在对暗号。
  5. 状态机舞蹈:这是协议的核心,卡从“睡眼惺忪”的 Idle 被一步步带到“整装待发”的 Transfer,每一步都由一个命令推动。
  6. 性能提升:基础通信建立后,马上开始“飙车”——加宽车道(4-bit)、提速(50MHz/208MHz)、校准(tuning)。
  7. 注册设备:卡的信息被登记到设备模型中,就像一个新生儿报上了户口。
  8. 诞生块设备:最终,块设备子系统和驱动接管,为上层提供标准的磁盘接口,你钟爱的 lsblk 此刻终于能看见它了。

2.4 带着这张图,走向代码

在接下来的第3章,我们会把这张时序图“拆箱”,逐帧透视每个阶段背后的内核函数。你会看到 mmc_rescan 里的 for 循环,mmc_attach_sd 里的电压判断,以及 mmc_sd_init_card 里那个精准的状态跳转序列。每当你迷失在函数调用链中时,不妨翻回这一页,看一眼这张图——它就像地铁线路图,告诉你正处在哪一站,下一站又是哪里。

3 代码导读:从卡检测到设备模型注册的核心路径

有了前两章的地图和协议铺垫,现在终于可以打开内核源码,按调用链逐帧分析初始化过程中每一个“原子动作”。本章会沿着第2章给出的八阶段时序,从插卡中断一路追踪到块设备节点诞生,重点剖析 mmc_rescan、mmc_attach_sd、mmc_sd_init_card、mmc_sd_setup_card 和 mmc_add_card 这五个核心函数。

3.1 插卡检测与rescan机制

当一张SD卡插入卡槽时,硬件检测引脚(通常是GPIO)会产生一个电平变化。内核必须迅速做出反应,但不能直接在中断上下文里做数百毫秒的初始化——那样会长时间关中断、阻塞其他任务。因此,中断处理仅触发一个轻量级的“扫描请求”,将真正的初始化工作推迟到内核线程(workqueue)中去执行。
上下文与调用链
卡插入 → GPIO中断 → mmc_gpio_cd_irqt() → mmc_detect_change(host, msecs_to_jiffies(200))→ _mmc_detect_change(host, delay) → 调度 host->detect 工作 → mmc_rescan(host)
这里的 mmc_detect_change 不是立即执行,而是通过 mmc_schedule_delayed_work 延迟约200ms后在工作队列中执行 mmc_rescan。这200ms的延迟有两个作用:一是消除卡插入时的机械抖动,二是等卡内部上电复位完成。
核心函数:mmc_rescan 简化代码
static const unsigned freqs[] = { 400000300000200000100000 };void mmc_rescan(struct work_struct *work){    struct mmc_host *host =        container_of(work, struct mmc_host, detect.work);    /* 若 host 正在被移除,则直接返回 */    if (host->rescan_disable)        return;    /* 如果当前有卡存在,先尝试发送CMD0复位,再重新识别 */    if (host->bus_ops && host->bus_ops->detect && !host->bus_ops->detect(host))        goto out;  // 卡仍在,不重新初始化    mmc_bus_get(host);    /* 关键循环:尝试不同的频率和协议 */        for (i = 0; i < ARRAY_SIZE(freqs); i++) {        unsigned int freq = freqs[i];        if (freq > host->f_max) {            if (i + 1 < ARRAY_SIZE(freqs))                continue;            freq = host->f_max;        }        if (!mmc_rescan_try_freq(host, max(freq, host->f_min)))// f_init 通常为400kHz            break;        if (freqs[i] <= host->f_min)            break;    }    mmc_bus_put(host);out:    if (host->caps & MMC_CAP_NEEDS_POLL)        mmc_schedule_delayed_work(&host->detect, HZ);}
要点解析
  1. 为什么用workqueue? 因为初始化过程涉及电压切换、命令发送、等待响应和忙信号,耗时可能达到数百毫秒甚至秒级,且会调用 mmc_wait_for_req 这类可能睡眠的函数。在中断上下文中睡眠会导致内核崩溃。workqueue运行在进程上下文中,允许睡眠。
  2. 重试与容错:mmc_rescan 内部调用的 mmc_rescan_try_freq 会遍历host支持的频率列表,如果卡未就绪或响应超时,还会进行有限次数的重试(参见后文)。
  3. bus_ops 切换:如果之前是SDIO卡,现在插入的是SD卡,mmc_rescan 会检测到 bus_ops->detect 返回false,从而清除旧的bus_ops,进入重新探测流程。

3.2 通向SD协议的入口:mmc_attach_sd

函数作用mmc_attach_sd 是SD卡专用的“入口函数”,在 mmc_rescan_try_freq 确认总线可能存在SD协议设备后被调用。它负责完成SD协议的电压协商、OCR获取,然后驱动整个状态机初始化,最后将卡注册进系统。如果初始化失败,它还负责回退尝试MMC/eMMC协议。
上下文 该函数定义于 drivers/mmc/core/sd.c,由 mmc_rescan_try_freq 发起调用:
// 在 mmc_rescan_try_freq 内部:if (!mmc_attach_sd(host))    return;  // SD卡初始化成功// 否则继续尝试 mmc_attach_mmc...
简化代码与四个关键动作
intmmc_attach_sd(struct mmc_host *host){    int err;    u32 ocr, rocr;    struct mmc_card *card;    // 1) 发送ACMD41循环,获取OCR    err = mmc_send_app_op_cond(host, 0, &ocr);    if (err)        return err;/*  设置总线操作为 SD 协议族 */    mmc_attach_bus(host, &mmc_sd_ops);        if (host->ocr_avail_sd)            host->ocr_avail = host->ocr_avail_sd;   /* 屏蔽不规范的电压位(bit7 及保留位) */        ocr &= ~0x7FFF;    // 如果host强制1.8V,但卡不支持,则失败    if (host->ocr_avail_sd)        rocr = mmc_select_voltage(host, ocr); // 从host支持中选最佳电压    // 3) 核心状态机初始化    card = mmc_sd_init_card(host, rocr, NULL);    if (IS_ERR(card)) {        err = PTR_ERR(card);        goto err;    }    // 4) 性能提升(总线宽度、UHS等)    err = mmc_sd_setup_card(host, card, false);    if (err)        goto remove_card;    // 注册卡    err = mmc_add_card(host, card);    if (err)        goto remove_card;    return 0;remove_card:    mmc_remove_card(card);err:    // 清理,可能继续尝试MMC    return err;}
错误处理与回退 如果 mmc_attach_sd 返回错误,mmc_rescan_try_freq 会接着调用 mmc_attach_mmc 和 mmc_attach_sdio,尝试其他协议族。这就是为什么有些卡槽既能读SD也能读eMMC或SDIO。

3.3 状态机核心:mmc_sd_init_card 深度剖析

这是整个初始化最长的函数,它严格遵循SD卡状态机:Idle → Ready → Ident → Stby → Transfer。下面按协议顺序分步解读,代码片段均取自 mmc_sd_init_card 内部。

3.3.1 从IDLE到READY:CMD0 + CMD8

发送CMD0 (GO_IDLE_STATE)
// 让所有卡进入Idle状态err = mmc_go_idle(host);if (err)    return ERR_PTR(err);
mmc_go_idle 内部封装了一个 opcode = MMC_GO_IDLE_STATE(即0)的命令,参数为0。这是一个广播命令,无需响应。
发送CMD8 (SEND_IF_COND) 检测电压和版本
/* 发送CMD8,参数:VHS=0x1 (2.7-3.6V),check pattern=0xAA */err = mmc_send_if_cond(host, host->ocr_avail);if (!err) {    /* 卡支持SD 2.0及以上,且电压兼容 */    ocr |= SD_OCR_CCS// 预设CCS,后面ACMD41会确认}/* 若CMD8无响应且不是超时,可能是SD 1.x卡,走降级路径 */
mmc_send_if_cond 发送CMD8,参数中电压范围(VHS)通常设为1,即2.7-3.6V。若卡返回响应R7,且回显的check pattern正确,则卡支持SD 2.0。若超时且后续ACMD41无CCS位,则按SD 1.x处理(容错逻辑省略)。

3.3.2 OCR电压协商:ACMD41循环

/* * 循环发送ACMD41,直到卡就绪(OCR busy位为1) * arg: 包含HCS(Host Capacity Support)位,告知卡主机支持高容量 */err = mmc_send_app_op_cond(host, ocr | SD_OCR_HCS, rocr);if (err)    return ERR_PTR(err);/* ocr 现在包含了卡真实支持的电压、CCS等信息 */card->ocr = ocr;

mmc_send_app_op_cond  内部实现精要

/** * mmc_send_app_op_cond - 发送 ACMD41 循环,等待卡上电完成 * @host: 主机控制器 * @ocr:  期望的 OCR 值(含 HCS 等标志) * @rocr: 输出参数,存放卡返回的 OCR 寄存器值(非 SPI 模式下有效) * * 功能:循环发送 CMD55+ACMD41,直到卡拉高 busy 信号(OCR bit31), *      表示卡已完成上电初始化,准备好进入 Ready 状态。 * 超时:单次轮询间隔 SD_APP_OP_COND_PERIOD_US,总超时 SD_APP_OP_COND_TIMEOUT_MS。 */int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr){    struct mmc_command cmd = {};    struct sd_app_op_cond_busy_data cb_data = {        .host = host,        .ocr  = ocr,          // 传入的期望 OCR(含 HCS 位)        .cmd  = &cmd          // 指向本地的命令结构,供回调使用    };    int err;    /* 命令号:41 (ACMD41) */    cmd.opcode = SD_APP_OP_COND;    /* SPI 模式下仅识别 bit30(CCS),普通模式传递完整 OCR */    if (mmc_host_is_spi(host))        cmd.arg = ocr & (1 << 30);    else        cmd.arg = ocr;    /* 响应类型:R1(SPI 模式) / R3(SD 模式),广播命令无数据 */    cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;    /*     * 核心:轮询直到卡就绪或超时。     * 内部会不断发送 CMD55 + CMD41,每次检查响应 R3 的 bit31(busy)。     * 若 busy 未置位,等待 SD_APP_OP_COND_PERIOD_US 后重试,     * 总超时 SD_APP_OP_COND_TIMEOUT_MS 后会返回 -ETIMEDOUT。     */    err = __mmc_poll_for_busy(host, SD_APP_OP_COND_PERIOD_US,                              SD_APP_OP_COND_TIMEOUT_MS,                              &sd_app_op_cond_cb, &cb_data);    if (err)        return err;    /* 非 SPI 模式下,把卡返回的 OCR 存入 *rocr */    if (rocr && !mmc_host_is_spi(host))        *rocr = cmd.resp[0];    return 0;}

3.3.3 获取卡身份:CMD2 (CID) 与 CMD3 (RCA)

CMD2 (ALL_SEND_CID) 获取CID
err = mmc_all_send_cid(host, card->raw_cid);if (err)    return ERR_PTR(err);/* 解析CID,填充 card->cid 结构 */memcpy(card->raw_cid, resp, sizeof(card->raw_cid));mmc_decode_cid(card);
mmc_all_send_cid 发送CMD2,卡回复R2(长响应136位),包含制造商标识、产品名称、序列号等。mmc_decode_cid 将这些原始位域解析为字符串和整数,供sysfs使用。
CMD3 (SEND_RELATIVE_ADDR) 分配RCA
err = mmc_send_relative_addr(host, &card->rca);if (err)    return ERR_PTR(err);
卡返回一个16位的RCA,主机将此地址存入 card->rca。此时状态从Ident变为Standby。RCA将在之后所有点对点命令中使用(CMD7、ACMD6等)。

3.3.4 读取卡参数:CMD9 (CSD)

err = mmc_sd_get_csd(card, is_sduc);  if (err)    goto free_card;/* 解析CSD,得到容量、速度信息 */mmc_decode_csd(card);
CSD有v1.0、v2.0、v3.0等版本,解析函数根据 CSD_STRUCTURE 字段区分。例如:
/** * mmc_decode_csd - 解析 CSD 寄存器(128位),填充 mmc_csd 结构 * @card:   所属的卡对象(其 raw_csd 已存有原始数据) * @is_sduc: 是否为 SDUC 卡(容量超过 2TB) * * CSD 有两个主流版本:v1.0(csd_struct=0)和 v2.0(csd_struct=1/2)。 * v1.0 用于标准容量 SDSC 卡,v2.0 用于高容量 SDHC/SDXC/SDUC 卡。 */staticintmmc_decode_csd(struct mmc_card *card, bool is_sduc){    struct mmc_csd *csd = &card->csd;    unsigned int e, m, csd_struct;    u32 *resp = card->raw_csd;    /* 提取 CSD 版本(最高两位) */    csd_struct = unstuff_bits(resp, 1262);    switch (csd_struct) {    case 2:   /* CSD 版本 2.0 —— 高容量 / 扩展容量卡 */        mmc_card_set_blockaddr(card);   // 标记为块寻址(每块 512 字节)        csd->taac_ns   = 0;   /* 未使用 */        csd->taac_clks = 0;   /* 未使用 */        m = unstuff_bits(resp, 994);        e = unstuff_bits(resp, 963);        csd->max_dtr    = tran_exp[e] * tran_mant[m];        csd->cmdclass   = unstuff_bits(resp, 8412);        /* 容量字段:v2.0 为 22 位,SDUC 为 28 位 */        if (csd_struct == 1)            m = unstuff_bits(resp, 4822);        else            m = unstuff_bits(resp, 4828);        csd->c_size = m;        /* 根据容量标志设置卡类型 (SDUC/Ultra) */        if (csd->c_size >= 0x400000 && is_sduc)            mmc_card_set_ult_capacity(card);        else if (csd->c_size >= 0xFFFF)            mmc_card_set_ext_capacity(card);        /* 容量 = (1 + c_size) * 512 KB = (1+m) << 10 个扇区 (每个扇区512B) */        csd->capacity   = (1 + (typeof(sector_t))m) << 10;        csd->read_blkbits = 9;    /* 固定 512 字节块 */        csd->erase_size = 1;      /* 单位 1 个扇区 */        if (unstuff_bits(resp, 131))            mmc_card_set_readonly(card);        break;    card->erase_size = csd->erase_size;    return 0;}
此外,TRAN_SPEED、READ_BL_LEN 等字段决定了卡默认能支持的最高速度和块大小。

3.3.5 选择卡并进入传输状态:CMD7

err = mmc_select_card(card);if (err)    return ERR_PTR(err);
mmc_select_card 发送CMD7并带上RCA,卡从Standby进入Transfer状态,并拉低DAT0线表示忙。该函数内部会等待busy清除:
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card){    struct mmc_command cmd = {};    cmd.opcode = MMC_SELECT_CARD;    if (card) {        cmd.arg = card->rca << 16;        cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;    } else {        cmd.arg = 0;        cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;    }    return mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);}// 等待卡释放DAT0
至此,卡已完全进入可传输数据的状态,初始化最核心的部分宣告完成。

3.4 高速模式与总线宽度协商:mmc_sd_setup_card 的魔法

在基础通信建立后,mmc_sd_setup_card 负责将总线性能从“安全但低效”提升至“高速且稳定”。它按顺序执行:切换总线宽度 → 协商高速/UHS模式 → 提升时钟 → 调谐(如有必要)。 在 mmc_attach_sd 中,mmc_sd_init_card 返回后立即调用,且 card 已处于Transfer状态。
mmc_sd_init_card 完成(卡处于 Transfer 状态)        ↓mmc_sd_setup_card 入口        ↓  ┌── reinit=false? ──→ YES ──→ ① 读取 SCR  (ACMD51)  │                             ② 解析 SCR  (版本、1.8V支持、总线宽度)  │                             ③ 读取 SSR  (ACMD13)  │                             ④ 初始化擦除参数  │                             ⑤ 检查写保护开关  └─────────────────────────────        ↓     ⑥ 读取 CMD6 切换能力表(速度模式、驱动强度、电流限制)        ↓     ⑦ SPI 模式下使能 CRC(如需要)        ↓    返回 0
/** * mmc_sd_setup_card - SD 卡性能提升与特性发现 * @host:   主机控制器 * @card:   已完成基础状态机初始化的卡 * @reinit: 是否为重新初始化(系统恢复时跳过部分硬件读取) * * 此函数在 mmc_sd_init_card 成功后调用。 * 它不负责切换总线宽度/UHS 模式(那些在 sd_init_card 末尾完成), * 而是负责 **读取卡的能力寄存器** 和 **执行特性发现**: *   - 读取 SCR 寄存器(SD 配置寄存器) *   - 读取 SSR 寄存器(SD 状态寄存器) *   - 初始化擦除参数 *   - 读取 CMD6 切换能力表(速度模式、驱动强度等) *   - 检查物理写保护开关 * * 这些信息为后续的总线宽度切换、UHS 模式协商、时钟提升 * 以及块设备层的擦除优化提供决策依据。 */int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,                      bool reinit){    int err;    /* ---------- 仅全新初始化时执行 ---------- */    if (!reinit) {        /* ① 发送 ACMD51,获取 SCR 寄存器(64 位) */        err = mmc_app_send_scr(card);        if (err)            return err;        /* ② 解析 SCR:SD 版本、1.8V 支持、总线宽度能力等 */        err = mmc_decode_scr(card);        if (err)            return err;        /* ③ 发送 ACMD13,获取 SSR 寄存器(512 位) */        err = mmc_read_ssr(card);        if (err)            return err;        /* ④ 根据 CSD 和 SSR 初始化擦除参数 */        mmc_init_erase(card);    }    /*     * ⑤ 发送 CMD6,查询卡的功能切换能力。     *     返回的 switch_caps 结构包含:     *       - 功能组 1:支持的速度模式(普通高速 / SDR25 / SDR50 / SDR104 / DDR50)     *       - 功能组 2:驱动强度(A 类 / B 类等)     *       - 功能组 3:电流限制     *     注意:sd3_bus_mode 可能因电压切换结果而改变,因此总是读取。     */    err = mmc_read_switch(card);    if (err)        return err;    /*     * ⑥ SPI 模式:根据卡的能力使能 CRC 校验。     *     必须在读取完寄存器之后执行,因为部分 SDHC 卡     *     在非 512 字节块传输时 CRC 不合法。     */    if (mmc_host_is_spi(host)) {        err = mmc_spi_set_crc(host, use_spi_crc);        if (err)            return err;    }    /* ---------- 仅全新初始化时执行 ---------- */    if (!reinit) {        /* ⑦ 检查物理写保护开关(卡侧面的 Lock 滑块) */        int ro = mmc_sd_get_ro(host);        if (ro < 0) {            pr_warn("%s: host does not support reading read-only switch, "                    "assuming write-enable\n", mmc_hostname(host));        } else if (ro > 0) {            mmc_card_set_readonly(card);        }    }    return 0;}

3.5 UHS-I初始化mmc_sd_init_uhs_card

/** * mmc_sd_init_uhs_card - UHS-I 超高速模式初始化流程 * @card: 已完成基础识别和 SCR/CID/CSD 读取的 SD 卡 * * 此函数在 mmc_sd_init_card 识别到卡支持 UHS-I 后调用, * 执行 SD 物理层规范定义的 UHS-I 初始化序列: *   1. 切换到 4-bit 总线宽度 *   2. 选择卡与主机都支持的最高速度模式(SDR50/104/DDR50 等) *   3. 设置驱动强度(A 类 / B 类) *   4. 设置电流限制 *   5. 执行模式切换(发送 CMD6) *   6. 若模式需要调谐(SDR104 / SDR50),则执行 tuning * * 若卡不支持 CMD6 切换命令(命令类 CCC_SWITCH 未置位),则直接返回。 */staticintmmc_sd_init_uhs_card(struct mmc_card *card){    int err;    u8 *status;    /* 若卡不支持切换命令(CMD6),直接退出,不进入 UHS 模式 */    if (!(card->csd.cmdclass & CCC_SWITCH))        return 0;    status = kmalloc(64, GFP_KERNEL);    if (!status)        return -ENOMEM;    /* ① 切换到 4-bit 总线宽度 (ACMD6) */    err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);    if (err)        goto out;    mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);    /* ② 根据主机能力和卡能力选择最佳速度模式 */    sd_update_bus_speed_mode(card);    /* ③ 设置驱动强度(功能组 2) */    err = sd_select_driver_type(card, status);    if (err)        goto out;    /* ④ 设置电流限制(功能组 3) */    err = sd_set_current_limit(card, status);    if (err)        goto out;    /* ⑤ 执行速度模式切换(CMD6 功能组 1) */    err = sd_set_bus_speed_mode(card, status);    if (err)        goto out;    /* ⑥ 若所选模式需要 tuning,则执行 CMD19 tuning */    if (mmc_sd_use_tuning(card)) {        err = mmc_execute_tuning(card);        /*         * SD 物理层规范 v3.01 允许 DDR50 模式也可执行 tuning,         * 但部分卡不支持,若 tuning 失败仅警告,不视为致命错误。         */        if (err && card->host->ios.timing == MMC_TIMING_UHS_DDR50) {            pr_warn("%s: ddr50 tuning failed\n",                    mmc_hostname(card->host));            err = 0;        }    }out:    kfree(status);    return err;}

3.6 调谐(Tuning)过程

对于UHS-I SDR104和SDR50模式,数据采样窗口很窄,必须执行调谐以找到最佳采样点:
/** * mmc_sd_use_tuning - 判断当前卡是否需要执行 CMD19 调谐 * @card: 已完成模式切换的 SD 卡 * * 返回值: true 表示需要 tuning,false 表示不需要。 * * 调谐仅适用于 UHS-I 总线速度模式下的 SDR50、SDR104 和 DDR50。 *  - SDR50 / SDR104:必须 tuning 以保证高速数据采样窗口正确。 *  - DDR50:tuning 是可选的,部分卡可能不支持或不需 tuning(通过 quirk 标记跳过)。 *  - SPI 模式不支持 CMD19,因此永远不需要 tuning。 */staticboolmmc_sd_use_tuning(struct mmc_card *card){    /* SPI 模式不定义 CMD19,直接返回 false */    if (mmc_host_is_spi(card->host))        return false;    switch (card->host->ios.timing) {    case MMC_TIMING_UHS_SDR50:    case MMC_TIMING_UHS_SDR104:        /* SDR50 和 SDR104 模式强制需要 tuning */        return true;    case MMC_TIMING_UHS_DDR50:        /* DDR50 模式下,若卡有跳过 tuning 的 quirk 则不需要,否则建议 tuning */        return !mmc_card_no_uhs_ddr50_tuning(card);    }    /* 其他模式(如普通高速、SDR25 等)不需要 tuning */    return false;}
调谐算法由host驱动实现(通过 host->ops->execute_tuning),通常发送CMD19(Tuning Block)并调整延迟链,直到找到正确的数据窗口。

电压/频率/模式选择决策树

下面用文字结合Mermaid图展示host能力与卡能力如何交叉匹配,得出最终配置:
这一决策树确保了始终选出host与卡都能支持的最高性能配置。

3.7 注册到块设备层:mmc_add_card 之后的旅程

函数作用mmc_add_card 将初始化完成的 struct mmc_card 注册到Linux设备模型中。之后,mmc总线上的块设备驱动(mmc_blk)会立刻匹配并运行probe,创建请求队列和 gendisk,最终生成用户可见的 /dev/mmcblkX。
mmc_add_card 内部
/** * mmc_add_card - 将已初始化的 MMC/SD 卡注册到 Linux 设备模型 * @card: 已完成初始化、填充了所有关键信息的卡对象 * * 这是初始化流程的倒数第二步(阶段⑦)。 * 功能:把抽象的 struct mmc_card 变成内核设备模型中的一个 device, *      触发总线匹配和驱动 probe,最终生成用户可用的块设备节点。 * * 内部会打印一条著名的内核日志,类似: *   "mmc0: new ultra high speed SDR104 SDHC card at address 0001" */intmmc_add_card(struct mmc_card *card){    int ret;    const char *type;    const char *speed_mode = "";    const char *uhs_bus_speed_mode = "";    static const char *const uhs_speeds[] = {        [UHS_SDR12_BUS_SPEED]  = "SDR12 ",        [UHS_SDR25_BUS_SPEED]  = "SDR25 ",        [UHS_SDR50_BUS_SPEED]  = "SDR50 ",        [UHS_SDR104_BUS_SPEED] = "SDR104 ",        [UHS_DDR50_BUS_SPEED]  = "DDR50 ",    };    /* ① 设置设备名称:格式为 "mmc0:0001"(主机名:RCA) */    dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);    /* ② 标记设备是否可移除(SD 卡为 removable,eMMC 为 fixed) */    dev_set_removable(&card->dev,                      mmc_card_is_removable(card->host) ?                      DEVICE_REMOVABLE : DEVICE_FIXED);    /* ③ 根据卡类型生成可读字符串 */    switch (card->type) {    case MMC_TYPE_MMC:        type = "MMC";        break;    case MMC_TYPE_SD:        type = "SD";                           // 默认 SDSC        if (mmc_card_blockaddr(card)) {        // 高容量卡            if (mmc_card_ult_capacity(card))                type = "SDUC";                 // 超大容量(>2TB)            else if (mmc_card_ext_capacity(card))                type = "SDXC";                 // 扩展容量(32GB~2TB)            else                type = "SDHC";                 // 高容量(2GB~32GB)        }        break;    case MMC_TYPE_SDIO:        type = "SDIO";        break;    case MMC_TYPE_SD_COMBO:        type = "SD-combo";        if (mmc_card_blockaddr(card))            type = "SDHC-combo";        break;    default:        type = "?";        break;    }    /* ④ 确定速度等级字符串 */    if (mmc_card_hs(card))        speed_mode = "high speed ";    else if (mmc_card_uhs(card))        speed_mode = "ultra high speed ";      // UHS-I    else if (mmc_card_uhs2(card->host))        speed_mode = "UHS-II speed ";    else if (mmc_card_ddr52(card))        speed_mode = "high speed DDR ";    else if (mmc_card_hs200(card))        speed_mode = "HS200 ";    else if (mmc_card_hs400es(card))        speed_mode = "HS400 Enhanced strobe ";    else if (mmc_card_hs400(card))        speed_mode = "HS400 ";    /* ⑤ 确定 UHS 具体模式字符串(SDR50/SDR104 等) */    if (mmc_card_uhs(card) &&        (card->sd_bus_speed < ARRAY_SIZE(uhs_speeds)))        uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed];    /* ⑥ 打印那条著名的内核日志 */    if (mmc_host_is_spi(card->host))        pr_info("%s: new %s%s card on SPI\n",                mmc_hostname(card->host), speed_mode, type);    else        pr_info("%s: new %s%s%s card at address %04x\n",                mmc_hostname(card->host), speed_mode,                uhs_bus_speed_mode, type, card->rca);    /* ⑦ 创建 debugfs 调试目录 */    mmc_add_card_debugfs(card);    /* ⑧ 关联设备树节点 */    card->dev.of_node = mmc_of_find_child_device(card->host, 0);    /* ⑨ 允许异步挂起(优化系统休眠速度) */    device_enable_async_suspend(&card->dev);    /* ⑩ 注册设备到内核设备模型——触发总线 match 和驱动 probe */    ret = device_add(&card->dev);    if (ret)        return ret;    /* ⑪ 标记卡为 present(已就位) */    mmc_card_set_present(card);    /*     * ⑫ 若为 MMC 卡且支持断电通知,则注册电压不足回调,     *     以便在电源异常时能紧急关闭卡。     */    if (mmc_card_mmc(card) &&        card->ext_csd.power_off_notification == EXT_CSD_POWER_ON)        mmc_regulator_register_undervoltage_notifier(card->host);    return 0;}
device_add 触发总线扫描,由于 card->dev.bus 被设为 &mmc_bus_type,内核将调用 mmc_bus_match 寻找合适的驱动。对于SD卡,驱动是 mmc_blk_driver。
mmc_blk_probe 核心逻辑
staticintmmc_blk_probe(struct mmc_card *card){    struct mmc_blk_data *md;    int ret = 0;    /* 检查卡是否支持块读取命令类 */    if (!(card->csd.cmdclass & CCC_BLOCK_READ))        return -ENODEV;    /* 应用设备特殊修正(quirks) */    mmc_fixup_device(card, mmc_blk_fixups);    /* 创建高优先级、可内存回收的完成工作队列 */    card->complete_wq = alloc_workqueue("mmc_complete",                    WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);    if (!card->complete_wq) {        pr_err("Failed to create mmc completion workqueue");        return -ENOMEM;    }    /* 分配主块设备数据结构(gendisk、请求队列) */    md = mmc_blk_alloc(card);    if (IS_ERR(md)) {        ret = PTR_ERR(md);        goto out_free;    }    /* 为硬件分区(如RPMB、boot分区)创建对应设备 */    ret = mmc_blk_alloc_parts(card, md);    if (ret)        goto out;    /* 添加 debugfs 调试接口 */    mmc_blk_add_debugfs(card, md);    /* 配置运行时电源管理:空闲 3 秒后自动挂起 */    pm_runtime_set_autosuspend_delay(&card->dev, 3000);    pm_runtime_use_autosuspend(&card->dev);    /* SD-combo卡由SDIO侧管理电源,此处仅对纯存储卡启用runtime PM */    if (!mmc_card_sd_combo(card)) {        pm_runtime_set_active(&card->dev);        pm_runtime_enable(&card->dev);    }    /* 注册RPMB(重放保护内存块)设备 */    mmc_blk_rpmb_add(card);    return 0;out:    mmc_blk_remove_parts(card, md);    mmc_blk_remove_req(md);out_free:    destroy_workqueue(card->complete_wq);    return ret;}
其中 mmc_blk_alloc 调用 alloc_disk 分配 gendisk,并创建 mmc_request 专用的请求队列。device_add_disk 调用后,设备节点 /dev/mmcblk0 诞生,uevent 触发用户空间 mdev/udev 创建设备文件。
I/O路径一瞥 当文件系统通过 submit_bio 下发请求时,会经过 mmc_blk_issue_rq 函数,该函数将 bio 转换为一个或多个 mmc_request,再通过 mmc_start_request 调用 host->ops->request 交由硬件执行。至此,SD卡从插卡到能读写数据的完整链条全部打通。

4 融会贯通:关键数据结构速查

在阅读第3章的代码时,你一定反复看到了几个核心结构体:mmc_host、mmc_card、mmc_command 和 mmc_bus_ops。它们像乐高积木一样,嵌在初始化流程的每个关节。本节将这些结构体汇集成速查手册,标明其主要成员、作用,以及最关键的一点——在初始化哪一步被填充或使用。有了这张表,下次再看代码时,你就能“一眼看穿”每个字段的来历。

4.1 struct mmc_host —— 主机控制器抽象

mmc_host 描述一个 SD/MMC 主机控制器,由 host 驱动在 mmc_alloc_host 时分配,并逐渐填充。初始化过程中,它通过 host->ops 调用硬件操作,通过 host->caps 决定能力上限。

4.2 struct mmc_card —— 卡的抽象

mmc_card 在 mmc_sd_init_card 中动态分配,用来承载一张物理卡的所有属性。它也是块设备驱动的 parent 设备。

4.3 struct mmc_command / struct mmc_request —— 命令与请求

一对搭档:mmc_request 封装一次完整的硬件交互(包含一个主命令、可选的停止命令、可选的数据传输);mmc_command 描述单条命令的细节。它们贯穿整个初始化流程,几乎每个 mmc_wait_for_cmd 或 mmc_wait_for_req 都会临时组装一对实例。

4.3.1 struct mmc_command(已在第1.3节详述,这里表格化)

4.3.2 struct mmc_request

struct mmc_request {    struct mmc_command  *sbc;       /* 多块读写前的 SET_BLOCK_COUNT 命令 */    struct mmc_command  *cmd;       /* 主命令(必须) */    struct mmc_data     *data;      /* 关联的数据传输(可选) */    struct mmc_command  *stop;      /* 停止传输命令(如 CMD12,可选) */    struct completion   completion;      /* 整体请求完成信号量 */    struct completion   cmd_completion;  /* 仅命令阶段完成信号量 */    void (*done)(struct mmc_request *);  /* 完成回调函数 */    void (*recovery_notifier)(struct mmc_request *); /* 错误恢复通知(CQE使用) */    struct mmc_host     *host;       /* 所属host控制器 */    bool                cap_cmd_during_tfr; /* 允许传输期间发送其他命令 */    int                 tag;         /* 请求标签(用于blk-mq队列) */#ifdef CONFIG_MMC_CRYPTO    const struct bio_crypt_ctx *crypto_ctx; /* 内联加密上下文 */    int                   crypto_key_slot; /* 密钥槽位 */#endif    struct uhs2_command uhs2_cmd;     /* UHS-II 扩展命令描述 */};
在初始化中,上层常用 mmc_wait_for_cmd 宏,它内部构造一个 mmc_request,将其 cmd 指向我们的 mmc_command,然后调用 mmc_wait_for_req 阻塞等待完成。

4.4 struct mmc_bus_ops —— 协议操作集

mmc_bus_ops 让 mmc core 可以面向协议编程,无需关心下面是 SD、MMC 还是 SDIO。在初始化阶段,当 mmc_attach_sd 确定卡是 SD 卡后,就会将 host->bus_ops 设为 &mmc_sd_ops。SD 卡的操作集实例如下:
static const struct mmc_bus_ops mmc_sd_ops = {    .remove = mmc_sd_remove,               /* 卡移除时调用,注销设备、释放资源 */    .detect = mmc_sd_detect,               /* 热插拔检测:返回true表示卡仍在槽 */    .runtime_suspend = mmc_sd_runtime_suspend, /* 运行时挂起:空闲时关时钟省电 */    .runtime_resume = mmc_sd_runtime_resume,   /* 运行时恢复:重新使能时钟与总线 */    .suspend = mmc_sd_suspend,             /* 系统挂起:通知卡进入休眠状态 */    .resume = mmc_sd_resume,               /* 系统唤醒:重新初始化卡回到传输态 */    .alive = mmc_sd_alive,                 /* 心跳检测:发送CMD13检查卡是否响应 */    .shutdown = mmc_sd_suspend,            /* 系统关机:复用挂起逻辑,优雅关闭卡 */    .hw_reset = mmc_sd_hw_reset,           /* 硬件复位:拉低RST引脚或发送CMD0复位 */    .cache_enabled = sd_cache_enabled,     /* 查询卡是否开启了内部写缓存 */    .flush_cache = sd_flush_cache,         /* 刷写卡内部缓存(CMD32/CMD33序列) */};
其中最重要的是 detect 回调。mmc_rescan 在决定是否重新扫描卡时,会调用 host->bus_ops->detect,若返回 false 则表示卡已移除,需重新初始化。

4.5 速查脉络图:数据结构之间的引用关系

在初始化流程中,mmc core 临时构造 mmc_command 和 mmc_request,通过 host->ops->request 丢给硬件;硬件返回响应后,解析结果填入 mmc_card,最终通过 mmc_add_card 将 card 挂到 host->card 上。

写在最后

一张小小的 SD 卡,从插入卡槽到 mount 可见,内核背后执行了十余步精密的协商与配置。这些步骤看似繁琐,实则每一环都是在为“跑得更快、更稳”铺路——用低频 1-bit 起步保安全,用电压协商和 tuning 冲刺极限性能。

希望这篇文章能帮你建立起 MMC 子系统的全景视图。下次再看到 mmc0: new ultra high speed SDR104 SDHC card at address 0001 这行日志时,你可以会心一笑:“这背后的一切,我都知道。”

目前文章知识介绍了底层驱动的初始化与识别,你在系统重要看到还需要上层的挂载,在Android中等底层初始化完成之后会触发事件通知到上层的vold,最后的挂载操作由vold完成!这部分后续会继续补充!

代码永远是最好的老师。去读源码吧。

感兴趣的小伙伴可以评论区共同讨论那些SD卡驱动调试遇到的疑难杂症问题根因是啥!
SD卡硬件引脚详细解读
SD卡的基本知识(如何选SD卡)
Linux驱动学习资料
文中如果整理有误的地方还请各位大佬指正!

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 08:07:10 HTTP/2.0 GET : https://f.mffb.com.cn/a/499466.html
  2. 运行时间 : 0.107813s [ 吞吐率:9.28req/s ] 内存消耗:4,919.58kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=cd8cb6e8069bb776a85dbcf33500c281
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000873s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000986s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000400s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000344s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000642s ]
  6. SELECT * FROM `set` [ RunTime:0.000332s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000822s ]
  8. SELECT * FROM `article` WHERE `id` = 499466 LIMIT 1 [ RunTime:0.000844s ]
  9. UPDATE `article` SET `lasttime` = 1783037230 WHERE `id` = 499466 [ RunTime:0.017472s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000479s ]
  11. SELECT * FROM `article` WHERE `id` < 499466 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000696s ]
  12. SELECT * FROM `article` WHERE `id` > 499466 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.003059s ]
  13. SELECT * FROM `article` WHERE `id` < 499466 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001484s ]
  14. SELECT * FROM `article` WHERE `id` < 499466 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.002398s ]
  15. SELECT * FROM `article` WHERE `id` < 499466 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.005307s ]
0.110788s