RC-EP 和 Switch-EP 的复位链路建立过程确实有重要区别。让我详细解释这个关键差异:
1. 根本区别:复位信号的来源
1.1 RC-EP 场景(传统方式)
RC (Root Complex) 控制复位:
┌─────────────┐ PERST# ┌─────────────┐
│ RC ├───────────────►│ EP │
│ │ │ │
│ 主动发起 │ │ 被动响应 │
└─────────────┘ └─────────────┘
│ │
▼ ▼
同时释放 PERST# 检测到 PERST# 释放
│ │
▼ ▼
开始发送 EIOS 开始发送 EIOS
│ │
▼ ▼
双方几乎同时开始链路训练
关键点:
- RC 主动控制 PERST# 信号
- RC 和 EP 同时看到 PERST# 释放
- 双方几乎同时开始链路训练
1.2 Switch-EP 场景(你的方案)
EP 控制复位(通过 GPIO):
┌─────────────┐ ┌─────────────┐
│ Switch │ │ EP │
│ │ │ │
│ 被动检测 │◄───────────────┤ 主动控制 │
└─────────────┘ └─────────────┘
│ │
▼ ▼
持续监测线路 GPIO 控制复位释放
│ │
▼ ▼
检测到 EIOS 才开始 主动发送 EIOS
│ │
▼ ▼
响应式开始链路训练 主动开始链路训练
关键点:
- EP 主动控制复位(通过 GPIO)
- Switch 不知道复位何时发生
- Switch 被动等待 EP 的信号
2. 时序差异对比
2.1 RC-EP 同步时序
c
// RC 和 EP 的同步复位时序
void rc_ep_synchronous_reset(void)
{
// RC 侧
rc_perform_reset() {
// 1. RC 拉低 PERST# (双方同时看到)
set_perst_low();
// 2. 等待 Tperst-min (100ms)
delay_ms(100);
// 3. RC 释放 PERST# (双方同时看到)
set_perst_high();
// 4. RC 立即开始发送 EIOS
send_eios_immediately(); // ← 关键!
}
// EP 侧
ep_handle_reset() {
// 1. 检测到 PERST# 变低
wait_for_perst_low();
// 2. 进入复位状态
enter_reset_state();
// 3. 检测到 PERST# 释放
wait_for_perst_high();
// 4. EP 立即开始发送 EIOS
send_eios_immediately(); // ← 关键!
}
// 结果:双方几乎同时开始训练
// 时间差主要来自信号传播延迟(纳秒级)
}
2.2 Switch-EP 异步时序
c
// Switch 和 EP 的异步复位时序
void switch_ep_asynchronous_reset(void)
{
// EP 侧(主动)
ep_gpio_controlled_reset() {
// 1. EP 通过 GPIO 拉低下游设备复位
gpio_set_value(RST_GPIO, 0);
// 2. EP 自身可能处于不同状态:
// a) 如果 EP 也被复位:需要先完成自身初始化
// b) 如果 EP 保持活动:可以直接控制
// 3. EP 释放 GPIO 复位
delay_ms(100); // 保持复位时间
gpio_set_value(RST_GPIO, 1);
// 4. EP 等待下游设备初始化
delay_ms(100); // Tperst-relax
// 5. EP 开始发送 EIOS
send_eios(); // ← EP 主动发起
}
// Switch 侧(被动)
switch_passive_detection() {
// 1. Switch 完全不知道复位发生
// 持续监测 RX 线路
// 2. 可能的状态:
// a) 链路原本是 DOWN:在 Detect.Quiet
// b) 链路原本是 UP:在 L0,需要检测到中断
// 3. 当检测到 EIOS 时:
if (detect_eios()) {
// 进入链路训练状态机
enter_ltssm_detect_active();
}
// 4. Switch 响应式开始训练
// 比 EP 晚一个 EIOS 检测时间
}
// 结果:EP 先开始,Switch 后响应
// 时间差:EIO检测时间 + 状态机转换时间
}
3. 状态机初始状态差异
3.1 RC-EP:双方从相同状态开始
上电/全局复位
拉低PERST#
释放PERST#
立即发送EIOS
上电/全局复位
检测到PERST#低
检测到PERST#高
立即发送EIOS
进入Detect.Active
进入Detect.Active
RC_Reset
RC_PerstLow
RC_PerstHigh
RC_SendEIOS
EP_Reset
EP_PerstLow
EP_PerstHigh
EP_SendEIOS
Both_DetectActive
3.2 Switch-EP:不同初始状态
Switch保持活动
检测到链路中断
超时或主动进入
EP控制复位
GPIO拉低复位
GPIO释放复位
等待Tperst-relax
发送EIOS
检测到EIOS
开始训练
开始训练
Switch_L0
Switch_Recovery
Switch_DetectQuiet
EP_Control
EP_GpioLow
EP_GpioHigh
EP_Wait
EP_SendEIOS
Switch_DetectActive
Both_Training
4. 实际实现的关键考虑
4.1 需要处理的状态不匹配
c
// Switch 可能处于的不同状态
enum switch_initial_state {
SWITCH_IN_L0, // 链路原本是通的
SWITCH_IN_RECOVERY, // 正在尝试恢复
SWITCH_IN_DETECT_QUIET, // 检测静默状态
SWITCH_IN_DISABLED, // 端口被禁用
SWITCH_IN_HOT_RESET, // 热复位状态
};
// EP 需要根据 Switch 状态调整策略
void ep_adjust_for_switch_state(struct pcie_ep *ep)
{
enum switch_initial_state sw_state = detect_switch_state(ep);
switch (sw_state) {
case SWITCH_IN_L0:
// Switch 认为链路是通的
// EP 需要先中断链路,再重新训练
ep_force_link_down(ep);
delay_ms(1); // 确保Switch检测到中断
break;
case SWITCH_IN_RECOVERY:
// Switch 已经在尝试恢复
// EP 可以立即发送EIOS加入恢复过程
break;
case SWITCH_IN_DETECT_QUIET:
// 理想状态:Switch在等待
// EP 可以直接开始训练
break;
case SWITCH_IN_DISABLED:
// Switch端口被禁用
// EP 需要等待或触发端口使能
ep_request_port_enable(ep);
break;
case SWITCH_IN_HOT_RESET:
// Switch正在热复位
// EP 需要等待复位完成
wait_for_hot_reset_complete(ep);
break;
}
// 然后开始复位下游设备
reset_downstream_via_gpio(ep);
}
4.2 确保 Switch 在正确状态
c
// 主动将 Switch 置于 Detect.Quiet 状态
void prepare_switch_for_reset(struct pcie_ep *ep)
{
// 方法1: 通过配置空间禁用/启用端口
pci_write_config_dword(ep->pdev,
PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_LD);
delay_ms(10);
pci_write_config_dword(ep->pdev,
PCI_EXP_LNKCTL,
0);
// 方法2: 发送 Electrical Idle 序列
// 强制链路进入电气空闲
send_electrical_idle_sequence(ep, 100); // 100us
// 方法3: 触发热复位
// 让 Switch 重新开始链路训练
trigger_hot_reset(ep);
// 等待 Switch 进入 Detect.Quiet
wait_for_switch_detect_quiet(ep, 100); // 100ms超时
}
5. 推荐的完整流程
5.1 优化的异步复位流程
c
// 推荐的 EP 控制复位完整流程
int ep_controlled_reset_sequence(struct pcie_ep *ep)
{
int ret;
// 阶段1: 准备阶段
dev_info(ep->dev, "Phase 1: Preparing for reset\n");
// 1.1 检查当前链路状态
u16 link_status = pcie_get_link_status(ep);
// 1.2 如果链路是通的,先优雅断开
if (link_status & PCI_EXP_LNKSTA_DLLLA) {
dev_dbg(ep->dev, "Link is up, gracefully bringing down\n");
// 发送 Electrical Idle
pcie_send_electrical_idle(ep);
delay_ms(2); // 确保Switch检测到
// 禁用链路
pcie_disable_link(ep);
delay_ms(10);
}
// 1.3 确保 Switch 在 Detect.Quiet
ret = ensure_switch_in_detect_quiet(ep);
if (ret) {
dev_warn(ep->dev, "Switch not in Detect.Quiet, forcing state\n");
force_switch_to_detect_quiet(ep);
}
// 阶段2: 复位下游设备
dev_info(ep->dev, "Phase 2: Resetting downstream device\n");
// 2.1 断言复位 (GPIO 低)
gpio_set_value(ep->rst_gpio, 0);
dev_dbg(ep->dev, "Reset asserted (GPIO low)\n");
// 2.2 保持复位时间 (符合 PCIe 规范)
delay_ms(100); // Tperst-min = 100ms
// 2.3 释放复位 (GPIO 高)
gpio_set_value(ep->rst_gpio, 1);
dev_dbg(ep->dev, "Reset deasserted (GPIO high)\n");
// 2.4 等待设备初始化 (Tperst-relax)
delay_ms(100); // 100ms 等待
// 阶段3: 启动链路训练
dev_info(ep->dev, "Phase 3: Starting link training\n");
// 3.1 EP 主动发送 EIOS
pcie_send_eios(ep);
dev_dbg(ep->dev, "EIOS sent\n");
// 3.2 进入 Detect.Active 状态
pcie_enter_detect_active(ep);
// 3.3 开始训练序列
ret = pcie_start_link_training(ep);
if (ret) {
dev_err(ep->dev, "Link training failed: %d\n", ret);
return ret;
}
// 阶段4: 验证和恢复
dev_info(ep->dev, "Phase 4: Verification\n");
// 4.1 等待链路建立
ret = wait_for_link_up(ep, 1000); // 1秒超时
if (ret) {
dev_err(ep->dev, "Link did not come up\n");
return ret;
}
// 4.2 验证链路参数
verify_link_parameters(ep);
dev_info(ep->dev, "Reset sequence completed successfully\n");
return 0;
}
5.2 时序图对比
RC-EP 同步时序:
时间 RC EP
-----------------------------------------
t0 拉低 PERST# 检测到 PERST# 低
t1 保持 100ms 保持复位状态
t2 释放 PERST# 检测到 PERST# 高
t2+ε 发送 EIOS 发送 EIOS
t3 开始训练 开始训练
Switch-EP 异步时序:
时间 EP Switch
-----------------------------------------
t0 GPIO拉低复位 未知(可能在线)
t1 保持 100ms 持续监测
t2 GPIO释放复位 未知
t3 等待 100ms 持续监测
t4 发送 EIOS 检测到 EIOS
t5 开始训练 响应并开始训练
t6 完成训练 完成训练
6. 关键差异总结
| 特性 | RC-EP (传统) | Switch-EP (你的方案) |
|---|---|---|
| 复位控制 | RC 主动控制 | EP 通过 GPIO 控制 |
| 同步性 | 同步复位 | 异步复位 |
| 初始状态 | 双方从复位开始 | EP 主动,Switch 被动 |
| 开始信号 | PERST# 释放 | EP 发送 EIOS |
| 时间协调 | 硬件同步 | 需要软件协调 |
| 复杂性 | 简单,标准化 | 复杂,需要定制 |
| 可靠性 | 高,经过验证 | 取决于实现质量 |
| 适用场景 | 标准 PCIe 系统 | 定制化/特殊需求 |
7. 你的方案需要特别注意的问题
7.1 竞争条件 (Race Conditions)
c
// 可能出现的竞争条件
void potential_race_condition(void)
{
// 场景:EP 发送 EIOS 时,Switch 正在其他状态
// 可能导致 EIOS 被忽略或误解
// 解决方案:状态同步
synchronize_with_switch() {
// 1. 先确认 Switch 状态
while (get_switch_state() != DETECT_QUIET) {
// 2. 如果不在正确状态,引导它
if (switch_in_l0()) {
send_electrical_idle(); // 强制进入空闲
}
delay_ms(1);
}
// 3. 确认 Switch 准备好后再发送 EIOS
send_eios();
}
}
7.2 超时处理
c
// 更严格的超时处理
struct reset_timeouts {
u32 switch_prepare_timeout = 500; // 500ms
u32 eios_detection_timeout = 100; // 100ms
u32 link_training_timeout = 1000; // 1000ms
u32 total_operation_timeout = 2000; // 2000ms
};
// 带超时的复位操作
int reset_with_timeouts(struct pcie_ep *ep)
{
u64 start_time = get_current_time_ms();
// 每个步骤检查总超时
if (check_total_timeout(start_time)) {
return -ETIMEDOUT;
}
// ... 执行复位步骤
}
结论
是的,RC-EP 和 Switch-EP 的复位链路建立过程确实不同:
-
RC-EP 是同步的:RC 控制 PERST#,双方同时看到信号变化,几乎同时开始训练。
-
你的 Switch-EP 方案是异步的:
- EP 通过 GPIO 控制下游设备复位
- Switch 完全不知道复位何时发生
- EP 需要主动发送 EIOS 来"唤醒" Switch
- Switch 被动检测到信号后才开始响应
这带来的挑战:
- 需要确保 Switch 在正确的状态(Detect.Quiet)
- 需要处理可能的竞争条件
- 需要更严格的超时和错误处理
- 链路建立时间可能更长
建议 :在你的 EP 驱动中实现状态检查 和同步机制,确保 Switch 准备好接收 EIOS,这样可以提高复位可靠性。