Qcom ARM平台Windows驱动(五):SD Host PoFx电源状态问题Debug实录

Qcom ARM平台Windows驱动(五):SD Host PoFx电源状态问题Debug实录

1. 问题概述

BH202 SD Host Controller 在高通 ARM Windows 平台上出现一系列电源管理问题,涉及 StorPort PoFx 框架的 Activate/Idle 引用计数机制。问题在 cold boot(含 Windows Fast Startup 的 Hybrid S4)、显式 S4 hibernate、带卡/不带卡等多种组合下表现不同。

1.1 问题矩阵

场景 现象 根因关键词
Cold boot 后 SD host 持续 D0 idle timeout 不触发 D3 PoFx 初始状态 mismatch
带卡 cold boot 拔卡 → BSOD 0xA0 INTERNAL_POWER_ERROR 双重 IdleComponent
S4 hibernate 期间拔卡 → resume 后 BSOD 10秒后系统重启 S4 恢复 per-caller 状态
带卡 cold boot 后插卡不识别 SD host 一直 D3 host_check_lost + StorPort 未 prime

1.2 环境信息

  • 平台:Qualcomm Snapdragon 8380 (ARM64)
  • OS:Windows 10/11 ARM64
  • 驱动模型:StorPort miniport + PoFx (Runtime D3)
  • 关键配置:STOR_POFX_DEVICE_FLAG_IDLE_TIMEOUT = 10ms

2. 背景知识:StorPort PoFx 模型

StorPort miniport 通过 StorPortInitializePoFxPower 注册 PoFx,之后 StorPort 框架管理 adapter 的 Active/Idle 状态转换。

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────┐
│ StorPort Framework │
│ │
│ PoFx Registration → Adapter Active (count=1) │
│ │
│ Miniport calls: │
│ ActivateComponent → count++ │
│ IdleComponent → count-- │
│ │
│ count == 0 → idle timer (10ms) → ScsiAdapterPower(D3) │
│ ActivateComponent → ScsiRestartAdapter (D0) │
└─────────────────────────────────────────────────────────┘

关键约束:

  • IdleComponent 必须有前置的 ActivateComponent(否则 BSOD 0xA0 arg1=0x608)
  • PoFx 注册后初始状态为 Active(count=1),但 miniport 的 pofx_active 计数器为 0
  • D3 callback(ScsiAdapterPower)运行在 DISPATCH_LEVEL,不能 busy-wait

2.1 驱动架构中的 PoFx 调用点

1
2
3
// 插卡:arm_vdd1_power_cycle → pofx_request_d0 → ActivateComponent
// 拔卡:thread_remove_card → pofx_request_d3 → IdleComponent
// D3 callback:card_power_off → pofx_request_d3(需要 guard 保护)

3. Debug 过程

3.1 WinDbg 分析 BSOD Dump

第一个 dump 揭示了 StorPort unit removal 路径的 BSOD:

1
2
3
4
5
6
7
8
9
10
11
INTERNAL_POWER_ERROR (a0)
Arg1: 0x608 — "A driver has attempted to transition a component to idle
without a preceding active request"

STACK_TEXT:
nt!KeBugCheckEx
qcpep8380+0x21721c ← Qualcomm PEP 检测到违规
storport!RaidAdapterPoFxIdleComponent
storport!RaidUnitFreeResources
storport!RaidDeleteUnit ← unit removal 触发 IdleComponent
bhtsdhubdr!BHTDispatchPnp

关键信息:StorPort 在 unit removal 时自行调用 IdleComponent。如果 miniport 在 thread_remove_card 中也调用了 IdleComponent,就形成双重 Idle → count 下溢 → BSOD。

3.2 DebugLogger 分析 D0 问题

带卡 cold boot 后 SD host 持续 D0 的 log:

1
2
3
4
5
6
7
8
9
06-01 04:45:21.408 --> arm_vdd1_power_cycle:
06-01 04:45:21.408 --> pofx_request_d3:
06-01 04:45:21.408 pofx_request_d3: pofx already idle, exit ← pofx_active=0
06-01 04:45:21.408 --> pofx_request_d0: ← ActivateComponent
06-01 04:45:21.456 <-- pofx_request_d0:
06-01 04:45:21.456 <-- arm_vdd1_power_cycle:
...
06-01 04:45:25.406 thread_remove_card: pofx_request_d3
06-01 04:45:26.406 pofx_request_d3: timeout! card_request_d3 not response

关键发现:

  1. arm_vdd1_power_cyclepofx_request_d3pofx_active=0 直接返回(不调 IdleComponent)
  2. pofx_request_d0 成功调用 ActivateComponent(pofx_active → 1)
  3. 拔卡时 pofx_request_d3 调了 IdleComponent 但 StorPort 不响应 D3 callback(1秒 timeout)

这说明 S4 resume 后 StorPort 的 PoFx 状态机与 miniport 不同步。

3.3 Windows Fast Startup 的影响

Windows 10/11 默认”关机”实际是 Hybrid S4(内核会话 hibernate,用户会话关闭)。这意味着:

1
2
用户操作 "Shutdown"  →  驱动收到 StorPowerActionHibernate (S4)
用户操作 "Boot" → 实际是 S4 resume(内核恢复)

驱动中的 s4_happened_flag 在 S4 D3 callback 中设为 TRUE,永不清除。因此”cold boot”后 s4_happened_flag=TRUE

4. 根因分析

4.1 问题一:Cold Boot D0

1
2
3
4
5
PoFx 注册后:StorPort 认为 adapter active (count=1)
miniport 的 pofx_active = 0

→ 所有 IdleComponent 路径检查 pofx_active==0 → 跳过
→ StorPort 永远不收到 IdleComponent → idle timer 不启动 → 永远 D0

修复:在 pofx_request_d3 中实现 force-idle(检测 mismatch 并强制设 pofx_active=1 后调 IdleComponent)。

4.2 问题二:带卡 Cold Boot 拔卡 BSOD

1
2
3
4
5
6
带卡启动 → card_init → pofx_request_d0 → pofx_active=1
拔卡 → thread_remove_card:
1. os_bus_change → StorPort 开始 unit removal
2. pofx_request_d3 → 我们的 IdleComponent (count -1)
3. StorPort unit removal → StorPort 的 IdleComponent (count -1)
→ 双重 Idle → count 下溢 → BSOD 0xA0 (0x608)

修复:S4 resume 后(s4_happened_flag=TRUE)跳过 pofx_request_d3,让 StorPort unit removal 独占 IdleComponent。

4.3 问题三:S4 Hibernate 期间拔卡 BSOD

1
2
3
4
5
6
S4 前:卡工作中,曾调过 IdleComponent(进入 D3 前)
S4 hibernate → 状态保存
休眠期间拔卡
S4 resume → PoFx 恢复 per-caller 状态(已 idle)
→ TASK_CARD_CHG 检测无卡 → force-idle 触发 → 再次调 IdleComponent
→ "idle without preceding active" → BSOD 0xA0 (0x608)

修复:force-idle 增加 !s4_happened_flag 条件。S4 resume 后不触发 force-idle(PoFx 恢复状态可能已 idle)。

5. 修复方案总结

5.1 force-idle 条件(cardinterface.c pofx_request_d3 内)

1
2
3
4
5
6
7
8
9
10
11
12
13
if (pdx->pm_state.rtd3_en &&
!pdx->card.card_present &&
!pdx->inited_card.card_present &&
(pdx->scsi.last_present == 0) &&
!pdx->pm_state.rtd3_entered &&
!pdx->pm_state.s3s4_entered &&
!pdx->pm_state.warm_boot_entered &&
!pdx->pm_state.s5_entered &&
(gHwResTbl.adapter_powerstate == StorPowerDeviceD0) &&
pdx->inited_card.s3s4_resume_for_card_init &&
!pdx->pm_state.s4_happened_flag) {
// Force idle: set pofx_active=1 then IdleComponent
}

每个条件的作用:

条件 防止的问题
!card_present 卡在位时不 idle
!inited_card.card_present unit 存在时不 idle(避免双重 Idle)
last_present == 0 bus change 已完成
!rtd3_entered 不在 RTD3 D3 callback 内
!s3s4_entered 不在 S3/S4 D3 callback 内
adapter_powerstate == D0 设备确实在 D0
s3s4_resume_for_card_init 只在 init/resume 路径触发(非正常拔卡)
!s4_happened_flag S4 resume 后不触发(PoFx 状态已恢复)

5.2 card_power_off guard(cardinterface.c)

1
2
3
4
5
6
if (!pdx->pm_state.s3s4_entered
&& !pdx->pm_state.s5_entered
&& !pdx->pm_state.warm_boot_entered
&& !pdx->pm_state.rtd3_entered) {
pofx_request_d3(pdx, timeout_ms);
}

所有 D3 callback 内都不调用 pofx_request_d3,防止 busy-wait at DISPATCH_LEVEL。

5.3 arm_vdd1_power_cycle(cardinterface.c)

1
2
3
4
5
6
7
8
9
void arm_vdd1_power_cycle(bht_dev_ext_t *pdx){
if (os_atomic_read(&pdx->pofx_active)) {
return; // 已 active,跳过
}
if (!pdx->pm_state.s4_happened_flag) {
pofx_request_d3(pdx, 1000); // 非S4:先关电
}
pofx_request_d0(pdx, 1000); // 始终上电 + pofx_active=1
}

S4 resume 后跳过 D3(设备已在 D0),但保留 D0(PMIC 上电 + 确保 pofx_active=1)。

5.4 thread_remove_card 的 pofx_request_d3

S4 resume 后由 StorPort unit removal 独占 IdleComponent,miniport 不调用。

6. 关键设计约束

  1. PoFx Activate/Idle 必须配对:每次 ActivateComponent 必须有对应的 IdleComponent,反之亦然
  2. D3 callback 不能 busy-wait:运行在 DISPATCH_LEVEL,busy-wait 超过几秒触发 DPC_WATCHDOG
  3. StorPort unit removal 会调 IdleComponent:miniport 不能再调,否则双重 Idle
  4. S4 resume 恢复 PoFx per-caller 状态:不能假设 S4 resume 后状态是 fresh
  5. Windows Fast Startup = Hybrid S4s4_happened_flag 在”关机”后为 TRUE
  6. s4_happened_flag 永不清除:S4 后 GPIO 中断失效,依赖 BusRelations 作为卡检测替代

7. 验证矩阵

测试场景 预期结果 验证点
不带卡 cold boot D3 force-idle 触发
带卡 cold boot → 拔卡 无 BSOD StorPort unit removal 独占 Idle
S4 hibernate → 休眠拔卡 → resume 无 BSOD force-idle 不触发
不带卡 S4 resume D3 StorPort idle timer
Warm boot → 插卡拔卡 D3 正常 Activate/Idle 流程
带卡 cold boot → 拔卡 → 再插卡 识别 host_check_lost goto retry

8. 教训与总结

8.1 StorPort PoFx 的隐含行为

StorPort 文档未明确说明 unit removal 时会调用 IdleComponent。这个行为通过 WinDbg dump 的 call stack 才发现。StorPort miniport 开发时需要假设:StorPort 会在 unit 生命周期管理中自行管理 PoFx 引用

8.2 Hybrid S4 的陷阱

Windows Fast Startup 使得”关机→开机”在驱动层面等价于 S4 hibernate→resume。驱动开发时不能假设 cold boot 是 fresh start——必须处理 S4 恢复的 stale 状态。

8.3 Debug 方法论

  1. WinDbg dump 分析:确定 BSOD 的 call stack 和触发条件
  2. DebugLogger 实时日志:追踪 PoFx 状态转换时序
  3. 对比分析:Good case(不带卡/warm boot)vs Bad case(带卡/S4),定位差异点
  4. Git bisect:缩小引入问题的 commit 范围
  5. 逐条件分析:列出 force-idle 的每个条件,分析每个场景下哪个条件阻止/允许触发