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

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

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

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

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 Type 16位 数据对象类型(如密钥、配置参数,PCI-SIG定义标准类型,厂商可自定义扩展)
Vendor ID 16位 厂商标识(如PCI-SIG为
0001h
Length 16位 数据对象总长度(单位:DW)
Reserved 16位 保留字段,填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”(协议ID
01h
,符合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发送缓存的核心设计要点

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

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

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

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

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

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

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

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

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

5.2、核心数据结构解析

1.
struct pci_doe_mb
(DOE邮箱实例)
字段 作用

pdev
关联的PCI设备指针

cap_offset
DOE扩展能力在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_discovery
)主动查询设备支持的所有DOE协议,并缓存到
prots
中,方便后续校验。

2. 协议发现流程(
pci_doe_discovery

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

构造“协议发现请求”(携带索引),调用
pci_doe
发送;解析响应,获取该索引对应的协议(VID+Type);循环直到索引为0(无更多协议),将所有协议存入
prots

3. 单次DOE交互(
pci_doe
核心接口)


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

4. 任务状态机(
doe_statemachine_work

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

发送请求(
pci_doe_send_req

检查邮箱是否忙/出错;计算载荷长度(2个DWORD头部 + 载荷DWORD数,最大2^18 DWORD);写入DOE头部(VID+Type + 长度);逐DWORD写入请求载荷(支持非DWORD对齐,不足补0);写入
PCI_DOE_CTRL_GO
触发硬件处理。
等待响应
轮询
PCI_DOE_STATUS
寄存器,检查
DATA_OBJECT_READY
位;超时/出错时执行
pci_doe_abort
终止操作,标记邮箱不可用;
接收响应(
pci_doe_recv_resp

读取响应头部,校验VID+Type是否匹配;读取响应长度,处理“0表示最大长度”的规范要求;逐DWORD读取响应载荷(支持非DWORD对齐,截断超出用户缓冲区的部分);清空多余数据,最终校验错误位。
异常处理:任何步骤出错都会调用
signal_task_abort
,执行Abort操作并标记邮箱状态。

5. 资源销毁(
pci_doe_destroy

遍历设备的所有DOE邮箱,调用
pci_doe_cancel_tasks
取消所有待处理任务;销毁工作队列、XArray,释放内存。

5.4、关键细节与规范对齐

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

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

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

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

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

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

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

关键约束:单个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. 完整链路:从提交任务到写入寄存器
步骤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. 总结:核心逻辑梳理


pci_doe_submit_task
是“调度员”:只负责把任务放进队列,不碰硬件;
doe_statemachine_work
是“执行者”:调度到后调用真正的硬件交互函数;
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→等待响应→读数据
总结


pci_doe_send_req
的每一步寄存器操作都严格遵循PCIe 6.0 §6.30的规范,核心是“先校验状态→写头部→写载荷→触发执行”;关键规范约束:Busy位检查、最大长度2^18 DWORD、长度0表示最大值、GO位触发执行;代码额外做了兼容优化:支持非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]
",
				    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.3 Status寄存器的Data Object Ready位(Bit 2):1表示设备有响应数据可读,读取过程中需保持有效
§6.30.2 响应读取流程:读Header 1校验协议→读Header 2获取长度→读载荷→读取并丢弃多余数据→最终校验错误
§6.30.1.1 响应数据对象格式与请求一致,长度字段0表示最大值,必须读取全部响应数据(即使截断)
总结


pci_doe_recv_resp
的核心规范约束:读Read寄存器后必须写0确认、必须读取全部响应数据(即使截断)、全程校验Data Object Ready和Error位;代码兼容优化点:支持非DWORD对齐的响应缓冲区,仅拷贝有效字节,无需用户提前处理对齐;关键容错逻辑:最后一次读取前校验Data Object Ready位、读取完成后再次校验Error位,确保响应完整且无硬件错误。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...