PCIE DOE(Data Object Exchange) 软件执行流程 - 教程

DOE(Data Object Exchange,数据对象交换)发现与数据发送逻辑总结(含完整缓存发送逻辑)

在PCIe 6.0规范中,DOE(Data Object Exchange)是实现设备与主机间安全数据交换的核心机制,其核心价值是通过标准化的“发现-配置-传输”流程,实现加密密钥、安全配置等敏感数据的可靠传输。以下从DOE发现逻辑数据发送完整逻辑(含缓存机制) 两部分展开,结合PCIe 6.0规范细节与技术原理全面梳理:

在这里插入图片描述

1、DOE发现逻辑:定位通信资源的标准化流程

DOE的发现本质是通过PCIe配置空间枚举,找到DOE扩展能力的寄存器区域,为后续数据交换确定“通信地址”与“能力范围”,具体步骤遵循PCIe规范的配置空间枚举机制:

1.1. 前提:PCIe设备的基础枚举

所有PCIe设备在系统初始化阶段,会被根复合体(Root Complex)分配唯一的BDF(Bus:Device.Function)地址,用于定位其4KB标准配置空间(物理地址由根复合体动态分配,如0x80000000)。操作系统或BIOS通过BDF地址,可访问设备的配置空间寄存器,这是DOE发现的基础。

1.2. 枚举DOE扩展能力:从配置空间定位DOE入口

DOE功能以“扩展能力”形式存在于设备的扩展配置空间(偏移0x100开始,非标准配置空间),需通过以下步骤定位:

  • 遍历扩展能力链表:扩展配置空间中,每个扩展能力以“扩展能力头(Extended Capability Header)”标识,包含两个核心字段:
    • Capability ID:DOE的标准ID为0x0025(PCI-SIG定义,区分于其他扩展能力如IDE、SR-IOV);
    • Next Capability Offset:指向下一个扩展能力的偏移地址(若为0x00h则表示链表结束)。
  • 匹配DOE能力ID:从扩展能力起始偏移(0x100)开始,依次读取每个扩展能力头的Capability ID,当匹配到0x0025时,当前偏移地址即为DOE扩展能力的基地址(如0x120)。

1.3. 读取DOE核心信息:确定通信范围与能力

找到DOE扩展能力基地址后,通过访问其内部寄存器,获取数据交换所需的关键信息:

  • DOE能力寄存器(DOE Capabilities Register):定义设备支持的DOE功能上限,如“最大数据对象大小”(通常为2~256K DW,1 DW=4字节)、“是否支持多DOE实例”“是否支持中断通知”等;
  • DOE MMIO基地址寄存器(DOE Base Address Register):存储DOE专用寄存器区域的物理基地址(如0xA0000000)与区域大小(通常为4KB~64KB,含读写邮箱、控制/状态寄存器),这是后续数据发送的“硬件通信地址”;
  • DOE控制/状态寄存器(DOE Control/Status Register):标识DOE的使能状态、中断使能位、数据交换状态(如“忙”“空闲”“错误”)。

1.4. 映射虚拟地址:主机侧访问准备

由于主机CPU无法直接访问物理地址,需通过操作系统的MMIO(内存映射I/O)机制,将DOE的物理地址区域映射为主机虚拟地址(如0xFFFF0000)。映射完成后,主机软件(如SPDM安全栈、TEE驱动)可通过内存读写指令(如MOV)访问DOE寄存器,完成数据发送与接收。

2、DOE数据发送完整逻辑:含缓存机制的可靠传输流程

DOE数据发送遵循“请求-响应”模型,核心是通过“邮箱寄存器+缓存队列”实现大尺寸数据对象的分块传输,同时保障数据完整性与传输可靠性。以下结合PCIe 6.0规范,梳理从“数据准备”到“发送完成”的全流程:

2.1. 数据发送前准备:对象封装与缓存初始化

(1)数据对象封装:标准化格式定义

DOE传输的基本单元是“数据对象(Data Object)”,需按PCIe 6.0规范封装,格式要求:

  • 大小范围:2~256K DW(即8字节~1MB),需为2 DW的整数倍;
  • 头部结构(固定2 DW):
    字段位宽说明
    Data Object Type16位数据对象类型(如密钥、配置参数,PCI-SIG定义标准类型,厂商可自定义扩展)
    Vendor ID16位厂商标识(如PCI-SIG为0001h
    Length16位数据对象总长度(单位:DW)
    Reserved16位保留字段,填0
  • 数据内容:紧跟头部,为实际传输的数据(如AES密钥、SPDM认证信息),需按小端字节序排列。
(2)发送缓存初始化:分块与队列管理

由于DOE的Write Data Mailbox Register(写数据邮箱寄存器) 位宽固定为32位(1 DW)或64位(2 DW,部分高性能设备),无法直接传输大尺寸数据对象,需通过“分块传输+缓存队列”实现:

  • 发送缓存队列:设备侧与主机侧均需维护“DOE发送缓存队列”,用于暂存待发送的分块数据(通常为FIFO结构,深度由设备硬件决定,如32级);
  • 分块规则:将封装后的完整数据对象,按“邮箱寄存器位宽”拆分为N个数据块(如32位邮箱则按1 DW/块拆分,64位则按2 DW/块拆分),并为每个块分配“块序号”(用于接收端重组);
  • 缓存状态标记:每个缓存块需标记“未发送”“发送中”“已确认”状态,避免重复发送或丢失。

2.2. 数据发送核心流程:分块传输与缓存控制

(1)步骤1:发送使能与状态检查

主机软件通过访问DOE控制寄存器(如DOE_CTRL):

  • 置位“发送使能位(Tx Enable)”,启动DOE发送流程;
  • 读取“发送状态位(Tx Status)”,确认DOE处于“空闲(Idle)”状态(无 ongoing 传输),避免冲突。
(2)步骤2:分块写入邮箱寄存器

主机软件按“块序号从小到大”的顺序,将缓存队列中的数据块逐块写入DOE Write Data Mailbox Register(偏移0x10h,32位/64位):

  • 每次写入前,需检查“邮箱空闲位(Mailbox Ready)”:仅当该位为1时,允许写入下一块数据(避免覆盖未发送的块);
  • 若设备支持“缓存预加载”,可一次性将多个块写入设备侧发送缓存队列(通过批量MMIO写指令),由设备硬件自动按序发送,减少主机CPU干预。
(3)步骤3:发送触发与硬件缓存调度
  • 主机触发:当一个数据对象的所有块写入完成后,主机置位“发送启动位(Tx Start)”,通知设备开始发送;
  • 设备侧缓存调度:设备硬件从“发送缓存队列”中读取数据块,按PCIe TLP格式封装为“Vendor-Defined Message”(协议ID01h,符合SPDM加密会话要求),并通过绑定的PCIe链路发送;
  • 流控制保障:设备发送前需检查PCIe链路的流控制信用(FC Credit),仅当有可用信用时才发送TLP,避免接收端缓存溢出(遵循PCIe 6.0的FC规则)。
(4)步骤4:发送状态反馈与缓存更新
  • 中断通知:若设备使能“发送完成中断”(通过DOE_CTRL的中断使能位),则发送完成后会触发中断,通知主机;
  • 状态寄存器查询:主机可轮询DOE_STATUS寄存器的“发送完成位(Tx Done)”,确认发送结果(成功/失败);
  • 缓存状态更新:发送成功后,主机与设备侧均将“发送缓存队列”中对应的块标记为“已发送”,并清空缓存(或移动队列指针),释放缓存空间。

2.3. 发送过程中的可靠性保障:重传与错误处理

(1)传输错误检测
  • 链路层错误:PCIe数据链路层通过LCRC(链路CRC)检测TLP传输错误,若发现错误,设备侧会触发“重传机制”(重传缓存中的未确认块);
  • 端到端错误:若启用ECRC(端到端CRC),接收端会检查数据对象的完整性,若错误则通过“DOE状态寄存器”反馈“数据错误位(Tx Error)”。
(2)重传机制:缓存块的重新发送
  • 重传触发条件:当主机检测到“发送超时”(超过规范定义的超时时间,如10ms)或“发送错误”时,触发重传;
  • 重传缓存依赖:由于发送缓存队列中暂存了未确认的分块数据,无需重新从主机内存读取,直接从缓存中取出对应块重新写入邮箱寄存器,触发发送;
  • 重传次数限制:规范建议重传次数不超过3次,若仍失败则上报“DOE传输错误”(通过PCIe AER机制)。

2.4. 发送完成:缓存清理与结果反馈

  • 缓存清理:发送成功后,主机侧清空“发送缓存队列”,设备侧释放硬件缓存资源(如FIFO复位);
  • 结果反馈:主机软件读取DOE_STATUS寄存器,确认“发送完成位(Tx Done)”置1,且“错误位(Tx Error)”为0,即表示整个数据对象发送完成;
  • 后续操作:若需传输下一个数据对象,重复“封装-分块-发送”流程;若传输结束,主机置位“DOE禁用位(Disable)”,关闭DOE功能以节省功耗。

3、关键补充:DOE发送缓存的核心设计要点

  1. 缓存一致性保障:主机侧缓存与设备侧缓存需通过“状态同步信号”(如Mailbox Ready位)确保一致性,避免“主机写入时设备正在读取”导致的数据冲突;
  2. 优先级调度:若设备支持多DOE实例或多数据流,发送缓存队列需支持“优先级调度”(如按数据对象类型分配优先级,密钥传输优先于配置参数);
  3. 安全隔离:发送缓存需与非DOE资源(如普通PCIe TLP缓存)物理隔离,避免敏感数据(如密钥)被非法访问,符合TEE(可信执行环境)的安全要求;
  4. 性能优化:部分高端设备支持“缓存预取”(Prefetch)功能,主机可提前将后续待发送块写入缓存,减少等待时间;同时支持“批量发送”(Burst Write),通过单次MMIO指令写入多个缓存块,提升传输效率。

4、总结:DOE发现与发送逻辑的核心特点

  1. 标准化发现:基于PCIe配置空间枚举,通过Capability ID=0x0025定位DOE资源,无需额外硬件信号,兼容性强;
  2. 分块缓存传输:通过“邮箱寄存器+缓存队列”解决大尺寸数据对象传输问题,平衡硬件复杂度与传输效率;
  3. 可靠性保障:结合PCIe链路层重传、ECRC校验、缓存状态标记,确保数据传输不丢失、不篡改;
  4. 安全适配:缓存隔离与标准化加密传输(如SPDM会话),满足PCIe 6.0的安全要求,适用于可信计算、密钥分发等敏感场景。

5. linux 内核6.6 关于doe的实现

你现在提供了Linux内核v6.6版本中doe.c的完整源码,核心需求是分析这段代码对PCIe DOE(Data Object Exchange,数据对象交换)机制的具体实现逻辑。我会从功能架构、核心流程、关键函数等维度,帮你拆解整个实现。

5.1、DOE核心定义与整体架构

PCIe设备与主机之间基于“协议+载荷”的标准化通信邮箱(Mailbox),用于设备固件升级、功能配置、协议发现等场景。

这段代码的核心架构分为三层:

  1. 基础数据结构:定义邮箱(pci_doe_mb)、任务(pci_doe_task)、协议(pci_doe_protocol)等核心对象;
  2. 硬件交互层:实现DOE寄存器读写、请求发送/响应接收、异常终止(Abort)等硬件操作;
  3. 任务调度层:通过工作队列(workqueue)异步处理DOE任务,提供同步/异步接口给上层调用。

5.2、核心数据结构解析

1. struct pci_doe_mb(DOE邮箱实例)
字段作用
pdev关联的PCI设备指针
cap_offsetDOE扩展能力在PCI配置空间的偏移地址
prots该邮箱支持的协议列表(XArray存储,key为索引,value为`(vid<<8)
wq等待队列,用于任务等待/唤醒
work_queue有序工作队列,确保DOE任务串行执行(避免硬件冲突)
flags状态标志(CANCEL:取消任务;DEAD:邮箱不可用)
2. struct pci_doe_task(DOE单次请求/响应任务)

封装单次DOE交互的所有信息:协议类型、请求/响应载荷、完成回调、工作项等,是任务调度的最小单元。

5.3、核心流程拆解

1. 邮箱初始化(pci_doe_init

这是DOE的入口函数,流程如下:

pci_doe_init(pdev)
遍历PCI设备的DOE扩展能力
pci_find_next_ext_capability找PCI_EXT_CAP_ID_DOE
pci_doe_create_mb创建邮箱实例
分配内存+初始化等待队列/XArray
创建有序工作队列
执行Abort重置邮箱
pci_doe_cache_protocols发现并缓存支持的协议
将邮箱实例存入pdev->doe_mbs(XArray)

关键:初始化时会通过“协议发现流程”(pci_doe_discovery)主动查询设备支持的所有DOE协议,并缓存到prots中,方便后续校验。

2. 协议发现流程(pci_doe_discovery

遵循PCIe规范的“协议发现协议”(VID=PCI_SIG,Type=0),流程:

  1. 构造“协议发现请求”(携带索引),调用pci_doe发送;
  2. 解析响应,获取该索引对应的协议(VID+Type);
  3. 循环直到索引为0(无更多协议),将所有协议存入prots
3. 单次DOE交互(pci_doe核心接口)

pci_doe是对外导出的核心同步接口,流程如下:

pci_doe(doe_mb, vid, type, req, req_sz, resp, resp_sz)
构造pci_doe_task任务对象
pci_doe_submit_task提交任务
校验协议是否支持(pci_doe_supports_prot)
初始化栈上工作项,绑定状态机函数
将任务加入工作队列
wait_for_completion等待任务完成
返回任务结果(响应长度/错误码)
4. 任务状态机(doe_statemachine_work

这是DOE交互的核心硬件逻辑,运行在工作队列中,流程:

  1. 发送请求(pci_doe_send_req
    • 检查邮箱是否忙/出错;
    • 计算载荷长度(2个DWORD头部 + 载荷DWORD数,最大2^18 DWORD);
    • 写入DOE头部(VID+Type + 长度);
    • 逐DWORD写入请求载荷(支持非DWORD对齐,不足补0);
    • 写入PCI_DOE_CTRL_GO触发硬件处理。
  2. 等待响应
    • 轮询PCI_DOE_STATUS寄存器,检查DATA_OBJECT_READY位;
    • 超时/出错时执行pci_doe_abort终止操作,标记邮箱不可用;
  3. 接收响应(pci_doe_recv_resp
    • 读取响应头部,校验VID+Type是否匹配;
    • 读取响应长度,处理“0表示最大长度”的规范要求;
    • 逐DWORD读取响应载荷(支持非DWORD对齐,截断超出用户缓冲区的部分);
    • 清空多余数据,最终校验错误位。
  4. 异常处理:任何步骤出错都会调用signal_task_abort,执行Abort操作并标记邮箱状态。
5. 资源销毁(pci_doe_destroy
  • 遍历设备的所有DOE邮箱,调用pci_doe_cancel_tasks取消所有待处理任务;
  • 销毁工作队列、XArray,释放内存。

5.4、关键细节与规范对齐

  1. 超时机制:严格遵循PCIe 6.0规范,超时时间1秒(PCI_DOE_TIMEOUT=HZ),轮询间隔1秒/128;

  2. 载荷处理:支持任意字节长度的载荷(无需用户对齐DWORD),底层自动处理部分DWORD的拷贝/补0;

  3. 字节序:载荷按原始字节流传输,用户需自行处理小端序转换(cpu_to_le32/le32_to_cpu);

  4. 并发安全:使用有序工作队列(alloc_ordered_workqueue)确保单个邮箱的任务串行执行,避免硬件冲突;

  5. 异常恢复:出错时执行Abort操作重置邮箱,Abort失败则标记邮箱“Dead”,拒绝后续请求。

  6. 核心功能:实现PCIe 6.0 DOE规范的标准化邮箱通信,提供同步的pci_doe接口,支持协议发现、请求发送/响应接收、异常处理;

  7. 设计亮点:通过工作队列异步处理硬件交互,有序队列保证并发安全,自动处理载荷对齐/长度转换,兼容规范的超时/Abort机制;

  8. 关键约束:单个DOE任务载荷最大2^18 DWORD,交互超时1秒,仅支持串行任务执行。

5.5 pci_doe_submit_task

pci_doe_submit_task函数本身并不直接负责把数据写入寄存器,它的核心作用是“任务提交与调度”——真正的寄存器读写逻辑在后续的工作队列处理函数中。我会帮你拆解清楚这个“任务提交→硬件交互”的完整链路,让你明白数据是如何最终写到寄存器的。

1. 先明确pci_doe_submit_task的定位

这个函数的核心工作只有3件事:

  • 校验协议是否支持、邮箱是否可用;
  • 把任务和邮箱绑定,初始化工作项;
  • 将任务加入工作队列(work_queue),等待异步执行。

它是“任务调度层”的入口,而寄存器读写属于“硬件交互层”,在工作项对应的处理函数doe_statemachine_work中完成。

2. 完整链路:从提交任务到写入寄存器
pci_doe_submit_task提交任务
将task加入work_queue有序工作队列
内核调度执行doe_statemachine_work
调用pci_doe_send_req发送请求
pci_doe_write_ctrl/直接pci_write_config_dword写寄存器
步骤1:任务提交(pci_doe_submit_task
// 关键代码拆解
task->doe_mb = doe_mb; // 绑定任务到对应的邮箱(包含PCI设备、寄存器偏移)
INIT_WORK_ONSTACK(&task->work, doe_statemachine_work); // 绑定工作项到处理函数
queue_work(doe_mb->work_queue, &task->work); // 任务入队,等待执行

这一步只是“把任务放进待执行队列”,没有任何硬件操作。

步骤2:工作队列执行处理函数(doe_statemachine_work

内核的工作队列机制会异步调用doe_statemachine_work,这个函数里会调用pci_doe_send_req——这才是真正写寄存器的入口

步骤3:核心寄存器写入(pci_doe_send_req

pci_doe_send_req函数中包含所有DOE寄存器的读写逻辑,核心代码如下(我标注关键寄存器操作):

static int pci_doe_send_req(struct pci_doe_mb *doe_mb, struct pci_doe_task *task)
{
struct pci_dev *pdev = doe_mb->pdev;
int offset = doe_mb->cap_offset; // DOE扩展能力在PCI配置空间的偏移
u32 val;
int i;
// 1. 先检查状态寄存器(读操作)
pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val); // 读状态寄存器
if (FIELD_GET(PCI_DOE_STATUS_BUSY, val)) // 检查忙位
return -EBUSY;
// 2. 计算载荷长度(符合PCIe 6.0规范)
size_t length = 2 + DIV_ROUND_UP(task->request_pl_sz, sizeof(__le32));
if (length > PCI_DOE_MAX_LENGTH)
return -EIO;
// 3. 写DOE头部(第一个DWORD:VID+协议类型)
val = FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_VID, task->prot.vid) |
FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, task->prot.type);
pci_write_config_dword(pdev, offset + PCI_DOE_WRITE, val); // 写WRITE寄存器
// 4. 写DOE头部(第二个DWORD:长度)
pci_write_config_dword(pdev, offset + PCI_DOE_WRITE,
FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH, length));
// 5. 写请求载荷(逐DWORD写入WRITE寄存器)
for (i = 0; i < task->request_pl_sz / sizeof(__le32); i++)
  pci_write_config_dword(pdev, offset + PCI_DOE_WRITE,
  le32_to_cpu(task->request_pl[i])); // 写载荷数据
  // 6. 触发硬件处理(写控制寄存器的GO位)
  pci_doe_write_ctrl(doe_mb, PCI_DOE_CTRL_GO); // 最终触发硬件执行
  return 0;
  }
3. 关键寄存器说明(PCIe DOE规范定义)
寄存器偏移(相对DOE cap)名称作用
PCI_DOE_CTRL控制寄存器GO位触发请求,写ABORT终止
PCI_DOE_STATUS状态寄存器指示忙、错误、响应就绪等状态
PCI_DOE_WRITE写数据寄存器写入请求头部和载荷
PCI_DOE_READ读数据寄存器读取响应头部和载荷

pci_write_config_dword(pdev, offset + 寄存器偏移, 数值) 是Linux内核操作PCI配置空间寄存器的标准接口,最终会通过PCI总线把数据写入硬件寄存器。

4. 总结:核心逻辑梳理
  1. pci_doe_submit_task是“调度员”:只负责把任务放进队列,不碰硬件;
  2. doe_statemachine_work是“执行者”:调度到后调用真正的硬件交互函数;
  3. pci_doe_send_req是“操作工”:通过pci_write_config_dword直接写DOE的WRITE/CTRL寄存器,完成数据发送。

你希望我标注pci_doe_send_req中每一行寄存器操作对应的PCIe 6.0规范条款,我会结合PCIe r6.0规范§6.30(Data Object Exchange)的具体条款,逐行标注核心寄存器操作的规范依据,同时补充条款核心含义,让你清晰理解代码与规范的对应关系。

5.6 pci_doe_send_req

先明确PCIe 6.0规范核心参考范围
所有DOE相关操作均对应 PCIe Base Specification Revision 6.0, Section 6.30 Data Object Exchange (DOE),以下标注的“§6.30.x”均为此规范章节。

pci_doe_send_req 完整代码 + 规范条款标注
static int pci_doe_send_req(struct pci_doe_mb *doe_mb,
struct pci_doe_task *task)
{
struct pci_dev *pdev = doe_mb->pdev;
int offset = doe_mb->cap_offset; // DOE扩展能力在PCI配置空间的偏移(§6.30.1 DOE Capability Structure)
size_t length, remainder;
u32 val;
int i;
/*
* Check the DOE busy bit is not set. If it is set, this could indicate
* someone other than Linux (e.g. firmware) is using the mailbox. Note
* it is expected that firmware and OS will negotiate access rights via
* an, as yet to be defined, method.
*/
// 1. 读DOE状态寄存器,检查Busy位(§6.30.2 Operation, §6.30.1.3 DOE Status Register)
// §6.30.1.3 定义:Status寄存器的Bit 0为Busy位,1表示邮箱忙,0表示空闲
// §6.30.2 要求:发送请求前必须确保Busy位为0,否则不能发起新请求
pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
if (FIELD_GET(PCI_DOE_STATUS_BUSY, val))
return -EBUSY;
// 2. 检查Status寄存器的Error位(§6.30.1.3 DOE Status Register)
// §6.30.1.3 定义:Status寄存器的Bit 1为Error位,1表示上一次操作出错
// §6.30.2 要求:发起新请求前需确保Error位为0,否则需先处理错误
if (FIELD_GET(PCI_DOE_STATUS_ERROR, val))
return -EIO;
/* Length is 2 DW of header + length of payload in DW */
// 3. 计算数据对象总长度(§6.30.1.1 Data Object Format)
// §6.30.1.1 定义:数据对象由2个DWORD头部 + N个DWORD载荷组成
// 长度单位为DWORD,需将字节数的载荷转换为DWORD数(向上取整)
length = 2 + DIV_ROUND_UP(task->request_pl_sz, sizeof(__le32));
// 4. 校验最大长度(§6.30.1.1 Data Object Format)
// §6.30.1.1 规定:数据对象最大长度为2^18 DWORD(即PCI_DOE_MAX_LENGTH)
if (length > PCI_DOE_MAX_LENGTH)
return -EIO;
// 5. 长度为0的特殊处理(§6.30.1.1 Data Object Format)
// §6.30.1.1 规定:若数据对象长度等于最大值(2^18),则Header 2的Length字段需填0
if (length == PCI_DOE_MAX_LENGTH)
length = 0;
/* Write DOE Header */
// 6. 写数据对象头部第1个DWORD(§6.30.1.1 Data Object Header 1)
// §6.30.1.1 定义:Header 1格式为:
// Bits 31:16 = Vendor ID(协议所属厂商ID)
// Bits 15:8  = 保留
// Bits 7:0   = Data Object Type(协议类型)
val = FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_VID, task->prot.vid) |
FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, task->prot.type);
// 写DOE Write寄存器(§6.30.1.2 DOE Write Register)
// §6.30.1.2 规定:所有请求数据(头部+载荷)需按顺序写入Write寄存器
pci_write_config_dword(pdev, offset + PCI_DOE_WRITE, val);
// 7. 写数据对象头部第2个DWORD(§6.30.1.1 Data Object Header 2)
// §6.30.1.1 定义:Header 2格式为:
// Bits 31:0  = Length(数据对象总DWORD数,0表示最大值)
pci_write_config_dword(pdev, offset + PCI_DOE_WRITE,
FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH,
length));
/* Write payload */
// 8. 写完整DWORD的载荷数据(§6.30.1.1 Data Object Payload)
// §6.30.1.1 规定:载荷为透明字节流,按DWORD为单位写入Write寄存器
for (i = 0; i < task->request_pl_sz / sizeof(__le32); i++)
  pci_write_config_dword(pdev, offset + PCI_DOE_WRITE,
  le32_to_cpu(task->request_pl[i]));
  // 9. 写最后一个不完整的DWORD载荷(规范未强制,但为兼容设计)
  // §6.30.1.1 虽要求载荷按DWORD对齐,但代码做了兼容:
  // 若载荷长度不是DWORD整数倍,剩余字节写入最后一个DWORD,其余位补0
  remainder = task->request_pl_sz % sizeof(__le32);
  if (remainder) {
  val = 0;
  memcpy(&val, &task->request_pl[i], remainder);
  le32_to_cpus(&val);
  pci_write_config_dword(pdev, offset + PCI_DOE_WRITE, val);
  }
  // 10. 触发请求执行(§6.30.2 Operation, §6.30.1.4 DOE Control Register)
  // §6.30.1.4 定义:Control寄存器的Bit 0为GO位,写1触发硬件处理请求
  // §6.30.2 规定:所有数据写入完成后,需写GO位启动DOE操作
  pci_doe_write_ctrl(doe_mb, PCI_DOE_CTRL_GO);
  return 0;
  }
补充规范条款核心含义(快速查阅)
规范条款核心含义
§6.30.1.1定义DOE数据对象格式:2个DWORD头部 + 载荷,Header 1存VID+协议类型,Header 2存长度
§6.30.1.2定义Write寄存器:用于主机向设备发送数据,按顺序写入头部和载荷
§6.30.1.3定义Status寄存器:Busy位表示邮箱忙,Error位表示操作出错
§6.30.1.4定义Control寄存器:GO位触发请求执行,ABORT位终止当前操作
§6.30.2定义DOE操作流程:检查状态→写数据→触发GO→等待响应→读数据
总结
  1. pci_doe_send_req的每一步寄存器操作都严格遵循PCIe 6.0 §6.30的规范,核心是“先校验状态→写头部→写载荷→触发执行”;
  2. 关键规范约束:Busy位检查、最大长度2^18 DWORD、长度0表示最大值、GO位触发执行;
  3. 代码额外做了兼容优化:支持非DWORD对齐的载荷,无需用户提前处理字节对齐。

5.7 pci_doe_recv_resp

pci_doe_recv_resp是DOE响应接收的核心函数,所有操作仍对应 PCIe Base Specification Revision 6.0, Section 6.30 Data Object Exchange (DOE),以下标注的“§6.30.x”均为此规范章节。

pci_doe_recv_resp 完整代码 + 规范条款标注
static int pci_doe_recv_resp(struct pci_doe_mb *doe_mb, struct pci_doe_task *task)
{
size_t length, payload_length, remainder, received;
struct pci_dev *pdev = doe_mb->pdev;
int offset = doe_mb->cap_offset; // DOE扩展能力偏移(§6.30.1 DOE Capability Structure)
int i = 0;
u32 val;
/* Read the first dword to get the protocol */
// 1. 读响应头部第1个DWORD(§6.30.1.1 Data Object Header 1、§6.30.1.5 DOE Read Register)
// §6.30.1.5 定义:Read寄存器用于主机从设备读取响应数据,按顺序读取头部+载荷
// §6.30.2 要求:响应读取需先读Header 1,校验协议匹配性
pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
// 2. 校验响应协议(§6.30.2 Operation)
// §6.30.2 规定:设备返回的响应必须与主机发送的请求协议(VID+Type)一致,否则为无效响应
if ((FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_VID, val) != task->prot.vid) ||
(FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, val) != task->prot.type)) {
dev_err_ratelimited(&pdev->dev, "[%x] expected [VID, Protocol] = [%04x, %02x], got [%04x, %02x]\n",
doe_mb->cap_offset, task->prot.vid, task->prot.type,
FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_VID, val),
FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, val));
return -EIO;
}
// 3. 读寄存器后写0确认(§6.30.1.5 DOE Read Register)
// §6.30.1.5 规定:每次读取Read寄存器后,需写0到该寄存器,告知设备已读取当前DWORD
pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0);
/* Read the second dword to get the length */
// 4. 读响应头部第2个DWORD(§6.30.1.1 Data Object Header 2)
// §6.30.1.1 定义:Header 2的Bits 31:0为响应数据对象总长度(DWORD数)
pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0); // 读后写0确认(§6.30.1.5)
// 5. 处理长度字段的特殊值(§6.30.1.1 Data Object Format)
// §6.30.1.1 规定:若Header 2的Length字段为0,表示响应长度为最大值(2^18 DWORD)
length = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH, val);
if (!length)
length = PCI_DOE_MAX_LENGTH;
// 6. 校验最小长度(§6.30.1.1 Data Object Format)
// §6.30.1.1 规定:数据对象至少包含2个DWORD头部,因此长度不能小于2
if (length < 2)
return -EIO;
/* First 2 dwords have already been read */
// 7. 计算载荷长度(§6.30.1.1 Data Object Format)
// 总长度减去2个DWORD头部,剩余为载荷的DWORD数
length -= 2;
received = task->response_pl_sz; // 用户提供的响应缓冲区长度(字节)
payload_length = DIV_ROUND_UP(task->response_pl_sz, sizeof(__le32)); // 缓冲区能容纳的DWORD数
remainder = task->response_pl_sz % sizeof(__le32); // 最后一个DWORD的有效字节数
/* remainder signifies number of data bytes in last payload dword */
// 8. 兼容非DWORD对齐的缓冲区(规范未强制,代码优化)
// 若缓冲区长度是DWORD整数倍,最后一个DWORD的有效字节数为4
if (!remainder)
remainder = sizeof(__le32);
// 9. 处理响应长度超过缓冲区的情况(§6.30.2 Operation)
// §6.30.2 允许主机截断超出缓冲区的响应数据,只需读取并丢弃多余部分
if (length < payload_length) {
received = length * sizeof(__le32); // 实际接收的字节数
payload_length = length; // 实际读取的DWORD数
remainder = sizeof(__le32); // 最后一个DWORD全部有效
}
if (payload_length) {
/* Read all payload dwords except the last */
// 10. 读取完整DWORD的载荷数据(§6.30.1.1 Data Object Payload、§6.30.1.5)
// §6.30.1.1 规定:载荷为透明字节流,按DWORD为单位从Read寄存器读取
for (; i < payload_length - 1; i++) {
pci_read_config_dword(pdev, offset + PCI_DOE_READ,
&val);
task->response_pl[i] = cpu_to_le32(val); // 保留小端序(规范无强制,代码兼容)
pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0); // 读后写0确认(§6.30.1.5)
}
/* Read last payload dword */
// 11. 读取最后一个(可能不完整的)DWORD载荷(代码兼容优化)
pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
cpu_to_le32s(&val);
memcpy(&task->response_pl[i], &val, remainder); // 仅拷贝有效字节
/* Prior to the last ack, ensure Data Object Ready */
// 12. 最后一次读取前校验Data Object Ready位(§6.30.1.3 DOE Status Register)
// §6.30.1.3 定义:Status寄存器Bit 2为Data Object Ready位,1表示设备有响应数据可读
// §6.30.2 要求:读取最后一个DWORD前需确认该位仍为1,避免数据未就绪
if (!pci_doe_data_obj_ready(doe_mb))
return -EIO;
pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0); // 读后写0确认(§6.30.1.5)
i++;
}
/* Flush excess length */
// 13. 读取并丢弃超出缓冲区的剩余数据(§6.30.2 Operation)
// §6.30.2 规定:主机必须读取设备返回的全部响应数据,即使超出缓冲区,否则邮箱会处于异常状态
for (; i < length; i++) {
pci_read_config_dword(pdev, offset + PCI_DOE_READ, &val);
pci_write_config_dword(pdev, offset + PCI_DOE_READ, 0);
}
/* Final error check to pick up on any since Data Object Ready */
// 14. 最终错误校验(§6.30.1.3 DOE Status Register、§6.30.2 Operation)
// §6.30.2 要求:响应读取完成后,需再次检查Error位,确认整个操作无硬件错误
pci_read_config_dword(pdev, offset + PCI_DOE_STATUS, &val);
if (FIELD_GET(PCI_DOE_STATUS_ERROR, val))
return -EIO;
return received;
}
响应接收核心规范条款补充(与发送端对应)
规范条款核心含义(响应接收侧)
§6.30.1.5定义Read寄存器:用于读取响应数据,读后必须写0确认,否则设备不会推送下一个DWORD
§6.30.1.3Status寄存器的Data Object Ready位(Bit 2):1表示设备有响应数据可读,读取过程中需保持有效
§6.30.2响应读取流程:读Header 1校验协议→读Header 2获取长度→读载荷→读取并丢弃多余数据→最终校验错误
§6.30.1.1响应数据对象格式与请求一致,长度字段0表示最大值,必须读取全部响应数据(即使截断)
总结
  1. pci_doe_recv_resp的核心规范约束:读Read寄存器后必须写0确认、必须读取全部响应数据(即使截断)、全程校验Data Object Ready和Error位;
  2. 代码兼容优化点:支持非DWORD对齐的响应缓冲区,仅拷贝有效字节,无需用户提前处理对齐;
  3. 关键容错逻辑:最后一次读取前校验Data Object Ready位、读取完成后再次校验Error位,确保响应完整且无硬件错误。
posted @ 2026-01-11 10:46  yangykaifa  阅读(6)  评论(0)    收藏  举报