SD Express驱动:SOC非hot-plug平台的驱动切换方案
概述
本文介绍一种适用于不支持PCIe热插拔的嵌入式平台的SD Express卡模式切换方案。
方案背景
传统PC平台的PCIe插槽支持硬件热插拔(Hot-Plug),但嵌入式相机平台通常采用贴片式SD卡槽,不具备热插拔能力。当SD Express卡需要从SD模式切换到PCIe/NVMe高速模式时,存在几秒的链路断开窗口期。本文介绍如何通过软件方式实现这一切换。
整体架构(硬件与驱动流水)


整体架构(软件模块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| ┌─────────────────────────────────────────────────────────────────────────┐ │ 嵌入式平台 SD Express 模式切换架构 │ └─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────┐ │ Linux Kernel Space │ ├─────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ NVMe Driver │ │ bht-sd │ │ │ │ (PCIe模式) │◄──►│ (SD模式) │ │ │ └─────────────┘ └──────┬──────┘ │ │ ▲ │ │ │ │ ▼ │ │ ┌──────┴──────────────────────────┐ │ │ │ SD Express Mode Switch Module │ │ │ │ • PCIe Bus Rescan │ │ │ │ • Runtime PM Control │ │ │ │ • Card Mode Detection │ │ │ └─────────────────────────────────┘ │ │ ▲ │ │ │ │ │ ┌───────────────┴─────────────────┐ │ │ │ SD Host Controller (PCIe EP) │ │ │ └─────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────┐ │ │ │ SD Express Card │ │ │ │ [SD Mode] ◄──► [PCIe Mode] │ │ │ └─────────────────────────────────┘ │ └─────────────────────────────────────────┘
工作模式: • 模式1: SD协议 → bht-sd SCSI驱动 • 模式2: PCIe/NVMe → 内核NVMe驱动
|
两种工作模式
SD Express卡支持两种工作模式:
| 模式 |
协议 |
驱动 |
典型速度 |
| SD模式 |
SD 4.0/UHS-II |
bht-sd (自定义) |
~200 MB/s |
| PCIe模式 |
NVMe |
内核nvme |
~1 GB/s |
核心问题:为什么需要软件介入切换?
PC平台 vs 嵌入式平台
| 特性 |
PC平台 |
嵌入式平台 |
| PCIe插槽 |
标准化热插拔 |
贴片式,无热插拔 |
| 链路断开 |
硬件中断通知 |
软件轮询检测 |
| Root Port |
标准ACPI电源管理 |
定制BSP |
| 模式切换 |
固件/ACPI处理 |
需软件模拟 |
问题分析
当SD Express卡执行SD 7.0协议切换到PCIe模式时:
- 链路断开阶段:卡停止响应SD命令,PCIe链路Down
- 模式切换阶段:卡内部完成协议切换(1-3秒)
- 链路恢复阶段:PCIe重新Link Up
在阶段2和阶段3之间,嵌入式平台的Root Port可能因Runtime PM进入D3状态,导致:
- 无法访问设备配置空间
- NVMe驱动无法枚举设备
- 模式切换失败
关键代码实现
1. 模式切换触发机制
1 2 3 4 5 6 7 8 9 10
| #if defined(SD_EXPRESS_NO_HOT_PLUG) if(card->pcie_init_flag == TRUE) { schedule_delayed_work(&host->detect, msecs_to_jiffies(1000)); return TRUE; } #endif
|
2. Runtime PM Forbid机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void mmc_rescan(struct work_struct *work) { sd_host_t *host = container_of(work, sd_host_t, detect.work); struct pci_dev *pdev = host->pci_dev.pci_dev; struct pci_bus *bus = pdev->bus; struct pci_dev *bridge = bus->self;
pm_runtime_forbid(&bridge->dev);
bht_pci_remove(pdev);
bht_pci_rescan(bus);
pm_runtime_allow(&bridge->dev); }
|
3. PCI总线重新扫描
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| static int bht_pci_rescan(struct pci_bus *bus) { struct pci_dev *bridge = bus->self; struct pci_bus *parent = bridge->subordinate; int rescan_cnt = 10;
pci_lock_rescan_remove();
dev = pci_get_slot(parent, PCI_DEVFN(0, 0)); if (dev) { pci_dev_put(dev); goto out; }
rescan: num = pci_scan_slot(parent, PCI_DEVFN(0, 0)); if (num == 0) { msleep(1000); if(!rescan_cnt--) goto out; else goto rescan; }
for_each_pci_bridge(dev, parent) pci_hp_add_bridge(dev); pci_assign_unassigned_bridge_resources(bridge); pcie_bus_configure_settings(parent); pci_bus_add_devices(parent);
out: pci_unlock_rescan_remove(); return ret; }
|
SD模式 → PCIe模式完整流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ┌─────────────────────────────────────────────────────────────────┐ │ SD Express 模式切换流程 │ └─────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 阶段1 │ │ 阶段2 │ │ 阶段3 │ │ 链路断开 │────►│ 模式切换 │────►│ 链路恢复 │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ bht-sd驱动 │ │ SD 7.0协议 │ │ Runtime PM │ │ 检测链路断开 │ │ 卡内部切换 │ │ forbid D3 │ └──────────────┘ └──────────────┘ └──────────────┘ │ ▼ ┌──────────────────────────────────────┐ │ PCIe Bus Rescan │ │ • pci_scan_slot() │ │ • 内核发现NVMe设备 │ │ • nvme驱动加载 │ │ • 设备挂载 /dev/nvmeXn1 │ └──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────┐ │ pm_runtime_allow() - 恢复电源管理 │ └──────────────────────────────────────┘
|
PCIe模式 → SD模式(拔卡场景)
当SD Express卡从PCIe模式拔出时,需要切换回SD模式供下次使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void bht_sd_remove(struct pci_dev *pdev) { bht_sd_slot_t *slot = pci_get_drvdata(pdev); bht_dev_ext_t *pdx = &slot->data;
bht_sd_runtime_pm_forbid(pdx, &pdev->dev);
bht_scsi_uinit(pdx);
if(os_stop_thread(&pdx->os, &pdx->os.thread) == FALSE) { os_kill_thread(&pdx->os, &pdx->os.thread); }
req_global_uninit(pdx); bht_sd_pci_release(pdx);
os_memset(slot, 0, sizeof(bht_sd_slot_t)); }
|
重新插入时的处理
当SD Express卡重新插入时,bht-sd驱动会自动检测并重新初始化:
- 卡插入检测(轮询GPIO)
- SD协议初始化
- 检测SD Express能力
- 重复上述模式切换流程
NVMe驱动侧修改
虽然模式切换主要由bht-sd驱动控制,但NVMe驱动侧需要适配嵌入式平台的特殊需求:
1. 禁用热插拔检测
嵌入式平台不需要NVMe驱动进行热插拔检测:
1 2
| nvme_core.no_dev_online = true;
|
2. 适配延迟枚举
在bht_pci_rescan中,NVMe驱动会接收到新设备,需要适配延迟场景:
3. 设备命名稳定性
确保设备路径稳定,避免/dev/nvme0n1变成/dev/nvme1n1:
关键数据结构
pcie_init_flag标志
1 2 3 4 5
| typedef struct { bool card_support_pcie; bool pcie_init_flag; } sd_card_t;
|
FALSE:卡不支持PCIe或初始化失败,维持SD模式
TRUE:PCIe初始化成功,触发模式切换流程
delayed_work结构
1 2 3 4
| typedef struct { struct delayed_work detect; } sd_host_t;
|
Windows驱动参考
Windows端存在类似问题:某些Intel平台的PCIe Root Port在SD Express切换的disconnect窗口期内进入Runtime D3。
问题表现
1
| DeviceIoControl读取寄存器返回0xFFFF/0x0000 → 检测到D3模式
|
解决方案
- INF配置:禁用Runtime D3Cold
1 2
| [DriverInstallx64.NT.HW] Needs = PciD3ColdNotSupported.HW
|
- 驱动代码:在切换前阻止D3
1 2 3 4 5
| StorPortAcquireMinidumpMutex(pdx);
StorPortReleaseMinidumpMutex(pdx);
|
总结
本方案通过软件方式在不支持硬件热插拔的嵌入式平台上实现了SD Express卡的模式切换:
- Runtime PM干预:确保切换过程中Root Port保持D0状态
- PCIe总线重新扫描:让NVMe驱动接管PCIe模式设备
- 延迟工作队列:避免在链路不稳定时进行操作
- 完整的模式回退:拔卡后可正常切换回SD模式
这种设计对于采用贴片式SD卡槽、不支持传统PCIe热插拔的嵌入式项目具有重要参考价值。