DPA 开发
DOCA 库和驱动程序
NVIDIA DOCA 框架是释放 NVIDIA® BlueField®-3 平台潜力的关键。
DOCA 的软件环境允许开发人员对 DPA 进行编程以加速工作负载。具体来说,DOCA 包括
DOCA DPA SDK – 用于应用程序级协议加速的高级 SDK
DOCA Flex IO SDK – 用于将 DPA 程序加载到 DPA、管理 DPA 内存、创建执行处理程序以及所需的硬件环和上下文的低级 SDK
DPACC – 用于编译和 ELF 文件操作 DPA 代码的 DPA 工具链
编程模型
DPA 旨在加速 DPU 和主机 CPU 的数据路径操作。使用 DPA 加速的应用程序部分以库的形式呈现给主机应用程序。库中的代码在 DPA 上运行的进程上下文中以事件驱动的方式调用。一个或多个 DPA 执行单元可以协同工作以处理与网络事件相关的工作。程序员使用主机或 DPU 上的相应 SDK API 指定每个函数应在何时调用不同的条件。
DPA 不能用作独立的 CPU。
DPA 的管理(例如加载进程和分配内存)是从主机或 DPU 进程执行的。主机进程发现设备上的 DPA 功能并驱动控制平面来设置不同的 DPA 对象。DPA 对象只要主机进程存在就存在。当主机进程被销毁时,DPA 对象被释放。主机进程决定它想要使用 DPA 加速哪些功能:是其整个数据平面还是仅一部分。
下图说明了系统中存在的不同进程

编译器
DPACC 是 DPA 处理器的编译器。它将针对 DPA 处理器的代码编译为可执行文件并生成 DPA 程序。DPA 程序是主机库,其接口封装了 DPA 可执行文件。
此 DPA 程序与主机应用程序链接以生成主机可执行文件。主机可执行文件可以通过 DPA SDK 的运行时调用 DPA 代码。
编译器关键字
DPACC 实现了以下关键字
关键字 | 应用程序用法 | 注释 |
| 注释所有在 DPA 上执行的事件处理程序以及从主机作为参数传递给 DPA 的所有通用用户定义数据类型(包括用户定义的结构)。 | 编译器使用它在 DPA 可执行文件中生成入口点,并在主机和 DPA 之间自动复制用户定义的数据类型。 |
| 注释所有由主机调用并在 DPA 上执行的 RPC 调用。RPC 调用返回 | 编译器使用它生成 RPC 特定入口点。 |
有关更多详细信息,请参阅 DOCA DPACC 编译器。
Flex IO SDK
Flex IO SDK 是一个低级事件驱动库,用于在 DPA 上编程和加速函数。
Flex IO SDK 执行模型
要将应用程序加载到 DPA 上,用户必须在 DPA 上创建一个进程,称为 Flex IO SDK 进程。Flex IO SDK 进程彼此隔离,就像标准主机操作系统进程一样。
Flex IO SDK 支持以下在 DPA 上执行用户定义函数的选项
Flex IO SDK 事件处理程序 – 事件处理程序在每次事件发生时执行其函数。此上下文中的事件是在 CQ 处于 armed 状态时在 NIC 完成队列 (CQ) 上接收到的完成事件 (CQE)。该事件触发一个内部 DPA 中断,激活事件处理程序。当事件处理程序被激活时,它会获得一个用户定义的参数。在大多数情况下,该参数是指向事件处理程序的软件执行上下文的指针。
以下伪代码示例描述了如何创建事件处理程序并将其附加到 CQ
// Device code __dpa_global__ void myFunc(flexio_uintptr_t myArg){ struct my_db *db = (struct my_db *)myArg; get_completion(db->myCq) work(); arm_cq(myCq); // reschedule the thread flexio_dev_thread_reschedule(); } // Host code main() { /* Load the application code into the DPA */ flexio_process_create(device, application, &myProcess); /* Create event handler to run my_func with my_arg */ flexio_event_handler_create(myProcess, myFunc, myArg, &myEventHandler); /* Associate the event hanlder with a specific CQ */ create_cq(&myCQ,… , myEventHandler) /* Start the event handler */ flexio_event_handler_run(myEventHandler) … }
RPC – 远程、同步、一次性调用特定函数。RPC 主要用于控制路径,以更新进程的 DPA 内存上下文。RPC 的返回值报告回主机应用程序。
以下伪代码示例描述了如何使用 RPC
// Device code __dpa_rpc__ uint64_t myFunc(myArg) { struct my_db *db = (struct my_db *)myArg; if (db->flag) return 1; db->flag = 1; return 0; } // Host code main() { … /* Load the application code into the DPA */ flexio_process_create(device, application, &myProcess); /* run the function */ flexio_process_call(myProcess, myFunc, myArg, &returnValue); … }
Flex IO SDK 多硬件模型
Flex IO SDK 能够构建支持多种 DPA 硬件模型的单个 DPA 应用程序。DPA 硬件模型指的是针对特定 NVIDIA 设备(例如 NVIDIA® BlueField®-3 和 NVIDIA® ConnectX®-8)量身定制的不同 DPA 版本。
当为多种硬件模型构建应用程序时,它会为每个指定的硬件模型生成一个副本。然后,用户可以通过按名称和硬件模型搜索程序来选择要加载到 Flex IO 进程中的适当 Flex IO 程序(可执行文件的 Flex IO 表示形式)。
示例实现
Flex IO 程序选择
/* Initialize the Flex IO application selection attributes for the device. */
/* Set the application name to match. */
flexio_app_sel_attr.app_name = DEV_APP_NAME_XSTR(DEV_APP_NAME);
/* Set the hardware platform to default - this will automatically select the appropriate program. */
flexio_app_sel_attr.hw_model_id = FLEXIO_HW_MODEL_DEF;
/* Specify the IBV device context to use for the hardware model query. */
flexio_app_sel_attr.ibv_ctx = ctx.ibv_ctx;
/* Retrieve the Flex IO application.
* The Flex IO application is created per hardware model by DPACC.
* This function matches the selection attributes (application name and HW model)
* with the available applications and returns the best match. If no exact match is
* found, a program built for an older hardware model will be selected.
*/
flexio_app_get(&flexio_app_sel_attr, &ctx.flexio_app);
此示例演示了如何为设备的硬件模型或较旧的兼容版本搜索合适的 Flex IO 程序(因为新的硬件模型保持与旧代码的向后兼容性)。用户还可以请求特定硬件模型的程序,在这种情况下,只会选择专门为该模型构建的 Flex IO 程序。
Flex IO SDK 内存管理
DPA 进程可以访问多个内存位置
DPA 进程中定义的全局变量。
堆栈内存 – 本地于 DPA 执行单元。不保证堆栈内存在同一处理程序的不同执行之间保留。有关堆栈内存的限制,请参阅“内存模型”部分。
堆内存 – 这是进程的主内存。只要 DPA 进程处于活动状态,堆内存内容就会保留。
外部注册内存 – 远程于 DPA 但本地于服务器。DPA 可以访问可以使用提供的 API 注册到本地 NIC 的任何内存位置。这包括 BlueField DRAM、外部主机 DRAM、GPU 内存等。
堆和外部注册内存位置由主机进程管理。DPA 执行单元可以从堆栈/堆和外部内存位置加载/存储。请注意,对于外部内存位置,应使用 Flex IO 窗口 API 适当地配置窗口。
Flex IO SDK 允许用户在 DPA 上分配和填充堆内存。该内存稍后可以在 DPA 应用程序中用作执行上下文(RPC 和事件处理程序)的参数
/* Load the application code into the DPA */
flexio_process_create(device, application, &myProcess);
/* allocate some memory */
flexio_buf_dev_alloc(process, size, ptr)
/* populate it with user defined data */
flexio_host2dev_memcpy(process, src, size, ptr)
/* run the function */
flexio_process_call(myProcess, function, ptr, &return value);
Flex IO SDK 允许使用 Flex IO 窗口从 DPA 执行单元访问外部注册内存。Flex IO 窗口将内存区域从 DPA 进程地址空间映射到外部注册内存。外部内存区域的内存密钥需要与窗口关联。内存密钥用于地址转换和保护。Flex IO 窗口由主机进程创建,并在执行期间由 DPA 处理程序配置和使用。配置完成后,来自 DPA 执行单元的 LD/ST 直接访问外部内存。
外部内存的访问不是一致的。因此,需要显式的内存栅栏来刷新缓存的数据以保持一致性。有关更多信息,请参阅“内存栅栏”部分。
以下示例代码演示了窗口管理
// Device code
__dpa_rpc__ uint64_t myFunc(arg1, arg2, arg3)
{
struct flexio_dev_thread_ctx *dtctx;
flexio_dev_get_thread_ctx(&dtctx);
uint32_t windowId = arg1;
uint32_t mkey = arg2;
uint64_t *dev_ptr;
flexio_dev_window_config(dtctx, windowId, mkey );
/* get ptr to the external memory (arg3) from the DPA process address space */
flexio_dev_status status = flexio_dev_window_ptr_acquire (dtctx, arg3, dev_ptr);
/* will set the external memory */
*dev_ptr = 0xff;
/* flush the data out */
__dpa_thread_window_writeback();
return 0;
}
// Host code
main() {
/* Load the application code into the DPA */
flexio_process_create(device, application, &myProcess);
/* define an array on host */
uint64_t var= {0};
/* register host buffer */
mkey =ibv_reg_mr(&var, …)
/* create the window */
flexio_window_create(process, doca_device->pd, mkey, &window_ctx);
/* run the function */
flexio_process_call(myProcess, myFunc, flexio_window_get_id(window_ctx), mkey, &var, &returnValue);
}
发送和接收操作
DPA 进程可以使用 Flex IO 发件箱对象启动发送和接收操作。Flex IO 发件箱包含内存映射的 IO 寄存器,使 DPA 应用程序能够发出设备门铃以管理发送和接收平面。DPA 发件箱可以在运行时配置为从 DPU 公开的特定 NIC 功能执行发送和接收。此功能不适用于只能访问其分配的 NIC 功能的主机 CPU。
每个 DPA 执行引擎都有自己的发件箱。因此,每个处理程序都可以有效地使用发件箱,而无需锁定以防止来自其他处理程序的访问。为了强制执行所需的安全性和隔离,DPA 发件箱使 DPA 应用程序只能为 DPA 主机进程创建的队列以及该进程被允许访问的 NIC 功能发送和接收。
与 Flex IO 窗口一样,Flex IO 发件箱由主机进程创建,并在运行时由 DPA 进程配置和使用。
// Device code
__dpa_rpc__ uint64_t myFunc(arg1,arg2,arg3) {
struct flexio_dev_thread_ctx *dtctx;
flexio_dev_get_thread_ctx(&dtctx);
uint32_t outbox = arg1;
flexio_dev_outbox_config (dtctx, outbox);
/* Create some wqe and post it on sq */
/* Send DB on sq*/
flexio_dev_qp_sq_ring_db(dtctx, sq_pi,arg3);
/* Poll CQ (cq number is in arg2) */
return 0;
}
// Host code
main() {
/* Load the application code into the DPA */
flexio_process_create(device, application, &myProcess);
/* Allocate uar */
uar = ibv_alloc_uar(ibv_ctx);
/* Create queues*/
flexio_cq_create(myProcess, ibv_ctx, uar, cq_attr, &myCQ);
my_hwcq = flexio_cq_get_hw_cq (myCQ);
flexio_sq_create(myProcess, ibv_ctx, myCQ, uar, sq_attr, &mySQ);
my_hwsq = flexio_sq_get_hw_sq(mySQ);
/* Outbox will allow access only for queues created with the same UAR*/
flexio_outbox_create(process, ibv_ctx, uar, &myOutbox);
/* Run the function */
flexio_process_call(myProcess, myFunc, myOutbox, my_hwcq->cq_num, my_hwsq->sq_num, &return_value);
}
同步原语
DPA 执行单元支持原子指令,以防止并发访问 DPA 进程堆内存。使用这些指令,可以设计多种同步原语。
Flex IO 目前支持基本自旋锁原语。可以使用 DOCA DPA 事件实现更高级的线程流水线。
DOCA DPA
在 Beta 级别支持。
DOCA DPA SDK 通过为 DPA 工作负载卸载、同步和通信提供高级原语,简化了 DPA 代码管理。这使得代码更简单,但缺乏 Flex IO SDK 提供的低级控制。
希望利用 DPA 卸载其代码的用户级应用程序和库可以选择 DOCA DPA。更接近驱动程序级别并需要访问低级 NIC 功能的用例最好使用 Flex IO。
DOCA DPA 的实现基于 Flex IO API。更高级别的抽象使户能够专注于他们的程序逻辑,而不是低级机制。
有关更多详细信息,请参阅 DOCA DPA 文档。
内存模型
DPA 提供一致但弱序的内存模型。应用程序需要使用栅栏来施加所需的内存排序。此外,在适用的情况下,应用程序需要写回数据,以便数据对 NIC 引擎可见(请参阅 一致性 表)。
内存模型在线程内提供“相同地址排序”。这意味着,如果一个线程写入一个内存位置,然后读取该内存位置,则读取将返回先前写入的内容。
内存模型为对齐访问原子数据类型提供 8 字节原子性。这意味着读取和写入的所有八个字节都在一个不可分割的事务中执行。
DPA 不支持未对齐的访问,例如从地址不是 N
的倍数的地址访问 N
字节的数据。
DPA 进程内存可以分为以下内存空间
内存空间 | 定义 |
堆 | DPA 进程堆中的内存位置。 在代码中引用为 |
内存 | 属于 DPA 进程的内存位置(包括堆栈、堆、BSS 和数据段),但内存映射 IO 除外。 在代码中引用为 |
MMIO(内存映射 I/O) | 通过内存映射 IO 访问的 DPA 进程外部的外部内存。窗口和发件箱访问被视为 MMIO。 在代码中引用为 |
系统 | 线程在内存和 MMIO 空间中可访问的所有内存位置,如上所述。 在代码中引用为 |
与许多其他实时操作系统 (RTOS) 一样,用户空间堆栈的大小有限,用户有责任确保程序分配的内存不超过堆栈的限制。用户空间堆栈的总体内存范围略小于 8 KB(8184 字节),这意味着用户的程序和编译器用于函数调用的 ABI 本身不应超过此限制。DPA 程序消耗的内存超过为堆栈分配的量将导致异常行为。
一致性
下表描述了 DPA 线程和 NIC 引擎之间的一致性
生产者 | 观察者 | 一致性 | 注释 |
DPA 线程 | NIC 引擎 | 不一致 | 要由 NIC 读取的数据必须使用适当的内部函数写回(请参阅“内存栅栏和缓存控制用法示例”部分)。 |
NIC 引擎 | DPA 线程 | 一致 | NIC 写入的数据最终对 DPA 线程可见。 写入对 DPA 线程可见的顺序受内存区域的排序配置影响(请参阅 在一个典型的 NIC 写入数据并生成完成条目 (CQE) 的示例中,保证当对 CQE 的写入可见时,DPA 线程可以读取数据而无需额外的栅栏。 |
DPA 线程 | DPA 线程 | 一致 | DPA 线程写入的数据最终对其他 DPA 线程可见,而无需额外的栅栏。当不使用栅栏时,一个线程进行的写入对其他线程可见的顺序是未定义的。程序员可以使用栅栏强制更新排序(请参阅“内存栅栏”部分)。 |
内存栅栏
栅栏 API 旨在强制执行内存访问排序。栅栏操作在不同的内存空间上定义。有关内存空间的信息,请参阅“内存模型”部分。
栅栏 API 应用于调用线程发出的操作之间的排序。作为性能说明,栅栏 API 还具有将数据写回栅栏操作中使用的内存空间的副作用。但是,程序员不应依赖此副作用。有关显式缓存控制操作,请参阅“缓存控制”部分。栅栏 API 具有编译器屏障的效果,这意味着编译器不会在栅栏 API 调用周围重新排序内存访问。
栅栏应用于“前置”和“后继”操作之间。可以使用代码中的 __DPA_R
、__DPA_W
和 __DPA_RW
引用前置操作和后继操作。
通用内存栅栏操作可以在任何内存空间和任何前置和后继操作集上运行。其他栅栏操作作为特定于用例的便捷快捷方式提供。如果可能,程序员最好使用快捷方式。
可以使用 dpaintrin.h
头文件包含栅栏操作。
通用栅栏
void
__dpa_thread_fence(memory_space, pred_op, succ_op);
此栅栏可以应用于任何 DPA 线程内存空间。内存空间在“内存模型”部分下定义。栅栏确保在调用 __dpa_thread_fence()
之前,调用线程执行的所有操作 (pred_op
) 都已执行并对 DPA、主机、NIC 引擎和对等设备中的所有线程可见,并且发生在调用 __dpa_thread_fence()
之后对内存空间的所有操作 (succ_op
) 之前。
系统栅栏
void
__dpa_thread_system_fence();
这等效于调用 __dpa_thread_fence(__DPA_SYSTEM, __DPA_RW, __DPA_RW)
。
发件箱栅栏
void
__dpa_thread_outbox_fence(pred_op, succ_op);
这等效于调用 __dpa_thread_fence(__DPA_MMIO, pred_op, succ_op)
。
窗口栅栏
void
__dpa_thread_window_fence(pred_op, succ_op);
这等效于调用 __dpa_thread_fence(__DPA_MMIO, pred_op, succ_op)
。
内存栅栏
void
__dpa_thread_memory_fence(pred_op, succ_op);
这等效于调用 __dpa_thread_fence(__DPA_MEMORY, pred_op, succ_op)
。
缓存控制
缓存控制操作允许程序员对 DPA 缓存中驻留的数据进行细粒度控制。它们具有编译器屏障的效果。可以使用 dpaintrin.h
头文件包含这些操作。
窗口读取内容失效
void
__dpa_thread_window_read_inv();
DPA 可以缓存使用窗口从外部内存获取的数据。后续对窗口内存位置的内存访问可能会返回已缓存的数据。在某些情况下,程序员需要强制读取外部内存(请参阅“轮询外部设置标志”下的示例)。在这种情况下,必须删除窗口读取内容缓存。
此函数确保调用 __dpa_thread_window_read_inv()
之前线程的窗口内存空间中的内容在调用 __dpa_thread_window_read_inv()
之后调用线程进行的读取操作之前失效。
窗口写回
void
__dpa_thread_window_writeback();
对外部内存的写入必须显式写回才能对外实体可见。
此函数确保调用 __dpa_thread_window_writeback()
之前线程的窗口空间中的内容已执行并对 DPA、主机、NIC 引擎和对等设备中的所有线程可见,并且发生在调用 __dpa_thread_window_writeback()
之后的任何写入操作之前。
内存写回
void
__dpa_thread_memory_writeback();
可能需要写回对 DPA 内存空间的写入。例如,数据必须在 NIC 引擎可以读取之前写回。有关更多信息,请参阅一致性表。
此函数确保调用 __dpa_thread_writeback_memory()
之前线程的内存空间中的内容已执行并对 DPA、主机、NIC 引擎和对等设备中的所有线程可见,并且发生在调用 __dpa_thread_writeback_memory()
之后的任何写入操作之前。
内存栅栏和缓存控制用法示例
这些示例说明了程序员必须使用 栅栏 和 缓存控制操作 的情况。
在大多数情况下,使用 Flex IO 或 DOCA DPA SDK 的应用程序不需要直接使用栅栏,因为栅栏在 API 中使用。
发出发送操作
在此示例中,DPA 上的线程准备一个工作队列元素 (WQE),NIC 读取该元素以执行所需的操作。
排序要求是确保 WQE 数据内容对 NIC 引擎读取可见。NIC 仅在执行门铃(MMIO 操作)后才读取 WQE。请参阅 一致性表。
# | 用户代码 – WQE 存在于 DPA 内存中 | 注释 |
1 | 写入 WQE | 写入 DPA 中的内存位置(内存空间 = |
2 |
| 缓存控制操作 |
3 | 写入门铃 | 通过发件箱的 MMIO 操作 |
在某些情况下,WQE 可能存在于外部内存中。请参阅 flexio_qmem
的描述 下方。这种情况下的操作表如下所示。
# | 用户代码 – WQE 存在于外部内存中 | 注释 |
1 | 写入 WQE | 写入 DPA 中的内存位置(内存空间 = |
2 |
| 缓存控制操作 |
3 | 写入门铃 | 通过发件箱的 MMIO 操作 |
发布接收操作
在此示例中,DPA 上的线程正在为接收队列写入 WQE 并推进队列的生产者索引。DPA 线程将必须对其写入进行排序并写回门铃记录内容,以便 NIC 引擎可以读取内容。
# | 用户代码 – WQE 存在于 DPA 内存中 | 注释 |
1 | 写入 WQE | 写入 DPA 中的内存位置(内存空间 = |
2 |
| 对门铃记录的写入相对于 WQE 排序 |
3 | 写入门铃记录 | 写入 DPA 中的内存位置(内存空间 = |
4 |
| 确保门铃记录的内容对 NIC 引擎可见 |
轮询外部设置标志
在此示例中,DPA 上的线程正在轮询将由主机或其他对等设备更新的标志。DPA 线程通过窗口访问内存。DPA 线程必须使内容失效,以便底层硬件执行读取。
用户代码 – 标志存在于外部内存中 | 注释 |
|
|
线程到线程通信
在此示例中,DPA 上的线程正在写入数据值,并通过标志写入通知另一个线程数据已写入。数据和标志都在 DPA 内存中。
用户代码 – 线程 1 | 用户代码 – 线程 2 | 注释 |
初始条件, | ||
|
|
|
| 线程 1 – 对标志的写入不能绕过对 | |
| ||
|
|
|
设置要外部读取的标志
在此示例中,DPA 上的线程设置一个标志,该标志由对等设备观察。该标志是使用窗口写入的。
用户代码 – 标志存在于外部内存中 | 注释 |
|
|
| 来自窗口的 DPA 内存的内容被写入外部内存 |
轮询完成队列
在此示例中,DPA 上的线程读取 NIC 完成队列并更新其使用者索引。
首先,DPA 线程轮询内存位置以查找下一个预期的 CQE。当 CQE 可见时,DPA 线程处理它。处理完成后,DPA 线程更新 CQ 的使用者索引。NIC 读取使用者索引以确定 DPA 线程是否已读取完成队列条目。NIC 使用使用者索引来监视潜在的完成队列溢出情况。
用户代码 – DPA 内存中的 CQE | 注释 |
| 轮询 DPA 内存中的 CQ 所有者位,直到该值指示 CQE 处于软件所有权中。 一致性模型确保对 CQ 的更新对 DPA 执行单元可见,而无需额外的栅栏或缓存控制操作。 一致性模型确保当 CQE 将所有权更改为软件时,CQE 中或由其引用的数据可见。 |
| 用户根据应用程序的逻辑处理 CQE。 |
| 计算下一个 CQ 索引,同时考虑 CQ 深度的任何环绕。 |
| 内存操作以写入新的使用者索引。 |
| 确保对 CQ 的使用者索引的写入对 NIC 可见。根据应用程序的逻辑,如果 CQ 在过载忽略模式下配置,则可以合并或消除 |
| 如果此处理程序将调用 |
DPA 特定操作
DPA 支持一些平台特定的操作。可以使用以下小节中描述的函数访问这些操作。可以使用 dpaintrin.h
头文件包含这些操作。
时钟周期
uint64_t __dpa_thread_cycles();
返回一个计数器,其中包含线程当前在其上调度的执行单元上过去任意起点的周期数。
请注意,此函数在线程中返回的值仅在线程保持与此执行单元关联的持续时间内有意义。
此函数还充当编译器屏障,防止编译器移动使用它的位置周围的指令。
定时器滴答
uint64_t __dpa_thread_time();
返回线程当前在其上调度的执行单元上过去任意起点的定时器滴答数。
请注意,此函数在线程中返回的值仅在线程保持与此执行单元关联的持续时间内有意义。
此内部函数还充当编译器屏障,防止编译器移动使用内部函数的位置周围的指令。
已退役指令
uint64_t __dpa_thread_inst_ret();
返回一个计数器,其中包含线程当前在其上调度的执行单元从过去任意起点退役的指令数。
请注意,此函数在软件线程中返回的值仅在线程保持与此执行单元关联的持续时间内有意义。
此内部函数还充当编译器屏障,防止编译器移动使用内部函数的位置周围的指令。
定点 Log2
int
__dpa_fxp_log2(unsigned int
);
此函数评估定点 Q16.16 以 2 为底的对数。输入是无符号整数。
定点倒数
int
__dpa_fxp_rcp(int
);
此函数评估提供的值的定点 Q16.16 倒数 (1/x)。
定点 Pow2
int
__dpa_fxp_pow2(int
);
此函数评估提供值的定点 Q16.16 的 2 的幂。
本章概述了 DOCA Flex IO SDK API 的概述和配置说明。
DPA 处理器是一种辅助处理器,旨在加速数据包处理和其他数据路径操作。Flex IO SDK 公开了一个 API,用于管理 DPA 设备并在其上执行本机代码。
NVIDIA® BlueField®-3 DPU 和更高版本支持 DPA 处理器。
DOCA 安装后,可以在 /opt/mellanox/flexio/include
下找到 Flex IO SDK 头文件,可以在 /opt/mellanox/flexio/lib/
下找到库。
先决条件
DOCA Flex IO 应用程序可以在主机或目标 DPU 上运行。
在 Flex IO SDK 上开发程序需要了解 DPU 网络队列的使用和管理。
架构
Flex IO SDK 库公开了几个功能层
libflexio
– 用于主机端操作的库。它用于资源管理。libflexio_dev
– 用于 DPA 侧操作的库。它用于数据路径的实现。libflexio_libc
– 用于 DPA 设备代码的轻量级 C 库。libflexio_libc
与标准libc
相比,可能只公开非常有限的功能。
一个典型的应用程序由两部分组成:一部分在主机或 DPU 目标上运行,另一部分直接在 DPA 上运行。
API
请参考 DOCA Driver APIs。
资源管理
DPA 程序无法创建资源。创建资源(例如 Flex IO 进程、线程、输出邮箱和窗口)以及用于数据包处理的队列(完成队列、接收队列和发送队列)的责任在于 DPU 程序。相关信息应被传递(复制)到 DPA 侧,并且复制信息的地址应作为参数传递给正在运行的线程。
示例
主机侧
声明一个变量来保存 DPA 缓冲区地址。
flexio_uintptr_t app_data_dpa_daddr;
在 DPA 侧分配一个缓冲区。
flexio_buf_dev_alloc(flexio_process, sizeof(struct my_app_data), &app_data_dpa_daddr);
将应用程序数据复制到 DPA 缓冲区。
flexio_host2dev_memcpy(flexio_process, (uintptr_t)app_data, sizeof(struct my_app_data), app_data_dpa_daddr);
struct my_app_data
应该在 DPU 和 DPA 应用程序之间是通用的,以便 DPA 应用程序可以访问结构字段。事件处理程序应获取包含复制数据的 DPA 缓冲区的地址
flexio_event_handler_create(flexio_process, net_entry_point, app_data_dpa_daddr, NULL, flexio_outbox, &app_ctx.net_event_handler)
DPA 侧
__dpa_rpc__ uint64_t event_handler_init(uint64_t thread_arg)
{
struct my_app_data *app_data;
app_data = (my_app_data *)thread_arg;
...
}
DPA 内存管理
如前所述,DPU 程序负责在 DPA 侧分配缓冲区(与资源相同)。DPU 程序应提前为 DPA 程序的需求分配设备内存(例如,队列数据缓冲区和环形缓冲区、用于程序功能的缓冲区等)。
DPU 程序还负责释放已分配的内存。为此,Flex IO SDK API 公开了以下内存管理函数
flexio_status flexio_buf_dev_alloc(struct flexio_process *process, size_t buff_bsize, flexio_uintptr_t *dest_daddr_p);
flexio_status flexio_buf_dev_free(flexio_uintptr_t daddr_p);
flexio_status flexio_host2dev_memcpy(struct flexio_process *process, void
*src_haddr, size_t buff_bsize, flexio_uintptr_t dest_daddr);
flexio_status flexio_buf_dev_memset(struct flexio_process *process, int
value, size_t buff_bsize, flexio_uintptr_t dest_daddr);
为 DPA 分配 NIC 队列
Flex IO SDK 公开了一个 API,用于为 DPA 分配工作队列和完成队列。这意味着 DPA 可以直接访问和控制这些队列,从而允许它创建门铃并访问其内存。
在创建 Flex IO SDK 队列时,用户必须预先分配并为队列的工作队列元素 (WQE) 提供内存缓冲区。此缓冲区可以分配在 DPU 或 DPA 内存上。
为此,Flex IO SDK 公开了 flexio_qmem
结构,允许用户提供缓冲区地址和类型(DPA 或 DPU)。
内存分配最佳实践
为了优化进程设备内存分配,建议使用以下分配大小(或最接近的大小)
最多 1 页 (4KB)
26 页 (256KB)
211 页 (8MB)
216 页 (256MB)
使用这些大小可以最大限度地减少进程设备内存堆上的内存碎片。如果需要其他缓冲区大小,建议将分配向上舍入到列出的某个大小,并将其用于多个缓冲区。
DPA 窗口
DPA 窗口用于访问外部内存,例如 DPU 的 DDR 或主机的内存。DPA 窗口是使用“DPA Memory and Caches”部分中提到的内存光圈的软件机制。要使用窗口功能,必须使用 ibv_reg_mr()
调用为主机或 DPU 内存向设备注册。
为此调用提供的地址和大小都必须是 64 字节对齐的,窗口才能运行。可以使用 posix_memalign()
分配调用来获得此对齐方式。
DPA 事件处理程序
默认窗口/发件箱
DPA 事件处理程序在创建时需要 DPA 窗口和 DPA 输出邮箱结构。这些结构用作事件处理程序线程的默认值。用户可以选择将其中一个或两个都设置为 NULL,在这种情况下,其中一个或两个都没有有效的默认值。
在 DPA 侧调用线程时,将为提供的默认 ID 设置线程上下文。如果在任何时候输出邮箱/窗口 ID 发生更改,则下次调用时的线程上下文将恢复为默认 ID。这意味着每次调用线程时都必须配置 DPA Window MKey,因为它没有默认值。
执行单元管理
DPA 执行单元 (EU) 相当于逻辑核心。为了执行 DPA 程序,必须为其分配一个 EU。
可以在创建时为事件处理程序设置 EU 亲和性。这将导致事件处理程序在特定的 EU(或一组 EU)上执行其 DPA 程序。
DPA 支持三种类型的亲和性:none
、strict
、group
。
亲和性类型和 ID(如果适用)在使用 flexio_event_handler_attr
结构的 affinity
字段创建事件处理程序时传递给它。
有关更多信息,请参考 DOCA DPA Execution Unit Management Tool。
执行单元分区
为了在 DPA 上工作,必须为使用的设备创建 EU 分区。分区是标记为可用于设备的 EU 选择。对于 DPU ECPF,在启动时会创建一个默认分区,其中包含所有可用的 EU。对于任何其他设备(即功能),用户必须创建分区。这意味着在未创建分区的情况下在非 ECPF 功能上运行应用程序将导致失败。
Flex IO SDK 将 strict
和 none
亲和性用于内部线程,这需要至少有一个 EU 的分区用于参与设备。不遵守此假设可能会导致失败。
虚拟执行单元
用户应注意,除了暴露给真实 EU 编号的默认 EU 分区之外,所有其他创建的分区都使用虚拟 EU。
例如,如果用户创建了一个 EU 范围为 20-40 的分区,从其虚拟 HCA (vHCA) 之一查询分区信息,它将显示 EU 范围为 0-20。因此,本例中的真实 EU 编号 39 将对应于虚拟 EU 编号 19。
版本 API 和向后兼容性
Flex IO SDK 支持部分向后兼容性。这可能遵循以下选项之一
仅使用最新版本。用户必须根据每个版本随附文档中列出的 Flex IO SDK API 的更改来调整其整个代码。
确保工作代码的部分向后兼容性。用户必须告知 SDK 他们打算使用的版本。SDK 提供了一组工具来确保向后兼容性。该工具集由编译时和运行时工具组成。
版本 API 工具包
为了支持向后兼容性,Flex IO SDK 对主机使用宏 FLEXIO_VER
,对 DPA 设备使用宏 FLEXIO_DEV_VER
。宏有 3 个参数,第一个是主版本(年份),第二个是次版本(月份),第三个是子次版本(未使用,始终为 0)。
编译时
此工具包适用于主机和 DPA 设备。头文件 flexio_ver.h
和 flexio_dev_ver.h
包含用于主机的宏 FLEXIO_VER
和 FLEXIO_VER_LATEST
,以及用于 DPA 设备的宏 FLEXIO_DEV_VER
和 FLEXIO_DEV_VER_LATEST
。例如,要为 24.04 版本设置向后兼容性,用户必须为主机声明以下构造
#include <libflexio/flexio_ver.h>
#define FLEXIO_VER_USED FLEXIO_VER(24, 4, 0)
#include <libflexio/flexio.h>
用户必须为 DPA 设备声明以下构造
#include <libflexio-dev/flexio_dev_ver.h>
#define FLEXIO_DEV_VER_USED FLEXIO_DEV_VER(24, 4, 0)
#include <libflexio-dev/flexio_dev.h>
其中 24
是主版本,4
是次版本。
文件 flexio.h
和 flexio_dev.h
具有用于主机的宏 FLEXIO_CURRENT_VERSION
和 FLEXIO_LAST_SUPPORTED_VERSION
,以及用于 DPA 设备的宏 FLEXIO_DEV_CURRENT_VERSION
和 FLEXIO_DEV_LAST_SUPPORTED_VERSION
。这些版本供内部使用和用户参考。用户不应使用这些宏。
运行时
此工具包仅适用于主机。为了在运行时实现向后兼容性,用户可以在调用 API 中的任何其他函数之前,调用 flexio.h
中的函数 flexio_status flexio_version_set(uint64_t version);
一次,并使用他们希望使用的版本参数。在以下情况下,该函数返回错误
如果指定的版本小于
FLEXIO_LAST_SUPPORTED_VERSION
如果它超过
FLEXIO_CURRENT_VERSION
如果再次调用该函数,且版本值与前一个版本值不同
status = flexio_version_set(FLEXIO_VER(24, 4, 0));
if
(status == FLEXIO_STATUS_FAILED)
{
return
ERROR;
}
建议使用 FLEXIO_VER_USED
宏作为参数
flexio_version_set(FLEXIO_VER_USED);
向后兼容性结束
向后兼容性工具旨在具有一个终点。对于每个新版本,可以逐步提高主机端的 FLEXIO_LAST_SUPPORTED_VERSION
和 DPA 设备端的 FLEXIO_DEV_LAST_SUPPORTED_VERSION
的值。如果 FLEXIO_VER_USED
等于 FLEXIO_LAST_SUPPORTED_VERSION
,则编译器将发出警告。这是用户开始过渡到较新版本的信号。这样,用户至少有时间在下一个版本之前修改其代码以符合较旧版本。如果 FLEXIO_VER_USED
低于 FLEXIO_LAST_SUPPORTED_VERSION
,则编译器将发出错误。这是用户立即过渡到较新版本的信号。DPA 设备的行为相同。
应用程序调试
由于应用程序执行在主机侧和 DPA 处理器服务之间划分,因此调试可能会有些困难,尤其是在 DPA 侧没有允许使用 C stdio 库 printf 服务的终端的情况下。
使用 DPA GDB
有关信息,请参阅 DOCA DPA GDB Server Tool。
使用设备消息流 API
另一种日志记录(消息传递)选项是使用 Flex IO SDK 基础设施,将字符串或格式化文本从 DPA 侧发送到主机侧控制台或文件。主机侧的 flexio.h
文件提供了 flexio_msg_stream_create
API 函数,用于初始化支持此功能所需的基础设施。初始化后,DPA 侧必须具有线程上下文,可以通过调用 flexio_dev_get_thread_ctx
来获得线程上下文。flexio_dev_msg
然后可以被调用,以将 DPA 侧生成的字符串写入到主机侧创建的流(使用其 ID),根据创建阶段的用户配置,该流将定向到控制台或文件。
重要的是在退出 DPU 应用程序时调用 flexio_msg_stream_destroy
,以确保正确清理打印机制资源。
设备消息传递使用内部 QP 进行 DPA 和 DPU 之间的通信。当在 InfiniBand 结构上运行时,用户必须确保子网配置良好,并且相关设备的端口处于 active
状态。
消息流功能
用户可以根据需要创建任意数量的流,最多为 flexio.h
中定义的 FLEXIO_MSG_DEV_MAX_STREAMS_AMOUNT
。
每个流都有自己的消息级别,该级别充当过滤器,其中级别低于流级别的消息将被过滤掉。
创建的第一个流是 default_stream
,获取流 ID 0,并且默认情况下以消息级别 FLEXIO_MSG_DEV_INFO
创建。
由 FLEXIO_MSG_DEV_BROADCAST_STREAM
定义的流 ID 用作广播流,这意味着它将消息传递到所有打开的流(具有适当的消息级别)。
可以根据以下选项配置具有同步模式属性的流
sync
– 使用 verb SEND 在消息从设备发送到主机侧后立即显示消息。async
– 使用 verb RDMA write。当程序员调用流的 flush 功能时,缓冲区中的所有消息都会显示(除非由于消息大小大于为其分配的大小而发生环绕)。在这种同步模式下,应在运行结束时调用 flush。batch
– 使用 RDMA write 和 RDMA write with immediate。它的工作方式类似于异步模式,不同之处在于每批消息大小都会被刷新,因此每批都会自动显示。目的是允许主机使用更少的资源进行设备消息传递。
设备消息传递假设
设备消息传递使用 RPC 调用来创建、修改和销毁流。默认情况下,这些 RPC 调用以亲和性 none
运行,这需要默认组上至少有一个可用的 EU。如果用户希望将流的管理亲和性设置为不同的选项(支持任何亲和性选项,包括强制 none
,这是默认行为),他们应使用 mgmt_affinity
字段在流属性中指定这一点。
Printf 支持
Printf 仅实现了有限的功能。并非所有 libc printf 都受支持。
请查阅以下列表以了解支持的修饰符
格式 –
%c
、%s
、%d
、%ld
、%u
、%lu
、%i
、%li
、%x
、%hx
、%hxx
、%lx
、%X
、%lX
、%lo
、%p
、%%
标志 –
.
、*
、-
、+
、#
常规支持的修饰符
“0”填充
字符串中的最小/最大字符数
常规不支持的修饰符
浮点修饰符 –
%e
、%E
、%f
、%lf
、%LF
八进制修饰符
%o
部分受支持精度修饰符
核心转储
如果 DPA 进程遇到致命错误,用户可以创建一个核心转储文件,以使用 GDB 应用程序查看该点的应用程序状态。
可以在进程崩溃后(如 flexio_err_status
API 所指示)和进程被销毁之前,通过调用 flexio_coredump_create
API 来创建核心转储文件。
使用 GDB 打开 DPA 核心转储文件的建议
使用
gdb-multiarch
应用程序GDB 的
Program
参数应为设备侧 ELF 文件使用
dpacc-extract
工具(DPACC 包随附提供)从应用程序的 ELF 文件中提取设备侧 ELF 文件
实验性 Flex IO API
Flex IO API 现在标记为实验性 API,这需要调整用户的构建定义,如下所示
对于主机侧项目,用户必须将编译标志
-DFLEXIO_ALLOW_EXPERIMENTAL_API
添加到他们的项目中。例如如果使用 meson,则添加命令
add_project_arguments('-DFLEXIO_ALLOW_EXPERIMENTAL_API', language: 'c', native: true)
如果使用 makefile,则将
-DFLEXIO_ALLOW_EXPERIMENTAL_API
添加到编译器标志
对于设备侧项目,在启动 DPACC 应用程序时添加以下内容
将
-DFLEXIO_ALLOW_EXPERIMENTAL_API
添加到--hostcc_args
标志将
-DFLEXIO_DEV_ALLOW_EXPERIMENTAL_API
添加到--devicecc_options
标志
如果缺少这些宏,并且使用了 -Werror
标志,则会导致编译警告或错误。
如果主机侧项目使用 pkg-config 机制来处理 libflexio,例如 meson.build
的 dependency('libflexio', required: true)
或 makefile 的 CFLAGS := $(shell pkg-config --cflags 'libflexio')
,则无需手动添加编译标志,因为这是自动完成的。
在 flexio-samples
包的 doca_build_dpacc.sh
脚本中可以找到将这些标志添加到启动 DPACC 的示例。
Flex IO 示例
本节介绍基于 Flex IO SDK 的示例。这些示例说明了如何使用 Flex IO API 在 DPA 上配置和执行代码。
运行 Flex IO 示例
Flex IO SDK 示例可用作构建和运行基于 Flex IO 的 DPA 应用程序的参考。它们提供了一系列开箱即用的工作 DPA 应用程序,涵盖了 Flex IO SDK 的基本功能。
文档
有关如何安装 BlueField 相关软件的详细信息,请参阅 DOCA Installation Guide for Linux
对于您在 DOCA 示例的安装、编译或执行中可能遇到的任何问题,请参阅 DOCA Troubleshooting
最低要求
用户必须安装以下软件
DOCA DPACC 包
DOCA RDMA 包
pkg-config 包
Python3 包
Gcc 版本 7.0 或更高版本
Meson 包版本 0.53.0 或更高版本
Ninja 包
DOCA Flex IO SDK
示例结构
每个示例都位于其自己的目录中,并且在 README 文件中附带有相应的描述。每个示例都包含两个应用程序
第一个应用程序位于
device
目录中,专为 DPA 设计第二个应用程序位于
host
目录中,旨在在 Linux OS 环境中的 DPU 或主机上执行
此外,还有一个 common
目录,用于存放示例的库。这些库进一步分为 device
和 host
目录,以方便与类似的应用程序链接。除了包含函数和宏之外,这些库还充当如何使用它们的示例。
示例列表
flexio_rpc
– 演示如何从 DPA 运行 RPC 函数的示例packet_processor
– 演示如何处理数据包的示例
构建示例
cd /opt/mellanox/fleio/samples/
./build.sh --check-compatibility --rebuild
Flex IO 示例支持为 多种硬件型号 构建。用户可以使用 --cpu
标志指定要包含在构建中的一个或多个硬件型号。
要为特定硬件型号构建 Flex IO 示例,请使用以下示例命令
cd /opt/mellanox/fleio/samples/
./build.sh --check-compatibility --rebuild --cpu bf3,cx8
此过程生成针对请求的硬件型号优化的构建。
示例
flexio_rpc
此示例应用程序使用远程进程调用执行 Flex IO。
设备程序计算 2 个输入参数的总和,打印结果,并将结果复制回主机应用程序。
此示例演示了如何构建应用程序(DPA 和主机)、如何创建进程和消息流、如何打开 IBV 设备以及如何从主机到 DPA 函数使用 RPC。
编译
cd /opt/mellanox/flexio/samples/
./build.sh --check-compatibility --rebuild
输出路径
/opt/mellanox/flexio/samples/build/flexio_rpc/host/flexio_rpc
用法
<sample_root>/build/flexio_rpc/host/flexio_rpc <mlx5_device> <arg1> <arg2>
其中
mlx5_device
– 带有 DPA 的 IBV 设备arg1
– 第一个数字参数arg2
– 第二个数字参数
示例
$/opt/mellanox/flexio/samples/build/flexio_rpc/host/flexio_rpc mlx5_0 44
55
Welcome to 'Flex IO RPC'
sample
Registered on device mlx5_0
/ 2
/Calculate: 44
+ 55
= 99
Result: 99
Flex IO RPC sample is done
flexio_packet_process
此示例演示了数据包处理处理。
设备应用程序实现了 flexio_pp_dev
的处理程序,该处理程序从网络接收数据包,交换 MAC 地址,在数据包中插入一些文本,然后将其发回。
这允许用户发送 UDP 数据包(数据包长度为 65 字节)并检查返回数据包的内容。此外,控制台显示数据包处理的执行情况,打印每个新数据包索引。设备消息传递以同步模式运行(即,主机收到的来自设备的每条消息都会立即输出)。
此示例说明了应用程序如何与库(DPA 和主机)一起工作、如何创建 SQ、RQ、CQ、内存密钥和门铃环、如何创建和使用 DPA 内存缓冲区、如何使用 UAR 以及如何创建和运行事件处理程序。
编译
cd /opt/mellanox/flexio/samples/
./build.sh --check-compatibility --rebuild
输出路径
/opt/mellanox/flexio/samples/build/packet_processor/host/flexio_packet_processor
用法
<sample_root>/build/packet_processor/host/flexio_packet_processor <mlx5_device>
其中
mlx5_device
– 带有 DPA 的 IB 设备的名称--nic-mode
– 可选参数,指示应用程序从主机运行。如果应用程序从 DPU 运行,则不应使用该参数。
例如
$sudo /build/packet_processor/host/flexio_packet_processor mlx5_0
应用程序必须以 root 权限运行。
调试
运行应用程序时,输出将显示一个调试令牌。此令牌可用于连接到 DPA GDB 服务器以进行调试。
$sudo build/packet_processor/host/flexio_packet_processor mlx5_0
Welcome to 'Flex IO SDK packet processing'
sample app.
Use the token >>> 0x790478344927513b
<<< for
debugging
有关使用 DPA GDB 的说明,请参阅 DOCA DPA GDB Server Tool。
使用流量运行
运行主机侧示例
$ cd <sample_root>
$ sudo ./build/packet_processor/host/flexio_packet_processor mlx5_0
使用连接到运行应用程序的设置的另一台机器。启动用作数据包生成器的接口
$ sudo ifconfig my_interface up
使用 scapy
将流量运行到应用程序正在运行的设备
$ python
>>> from scapy.all import
*
>>> from scapy.layers.inet import
IP, UDP, Ether
>>> sendp(Ether(src="02:42:7e:7f:eb:02"
, dst='52:54:00:79:db:d3'
)/IP()/UDP()/Raw(load="===============12345678"
), iface="my_interface"
)
源 MAC 必须与上面相同,因为应用程序为其定义了转向规则。目标 MAC 可以是任何值。
负载应与上面保持相同,因为应用程序会查找此模式并在处理过程中对其进行更改。
接口名称应更改为用于流量生成的接口。
可以使用 tcpdump
查看数据包
$ sudo tcpdump -i my_interface -en host 127.0
.0.1
-X
示例输出
Example output:
11
:53
:51.422075
02
:42
:7e:7f:eb:02
> 52
:54
:00
:12
:34
:56
, ethertype IPv4 (0x0800
), length 65
: 127.0
.0.1
.domain > 127.0
.0.1
.domain: 15677
op7+% [b2&3
=0x3d3d
] [15677a] [15677q] [15677n] [15677au][|domain]
0x0000
: 4500
0033
0001
0000
4011
7cb7 7f00 0001
E..3
....@.|.....
0x0010
: 7f00 0001
0035
0035
001f 42c6 3d3d 3d3d .....5.5
..B.==== <-- Original data
0x0020
: 3d3d 3d3d 3d3d 3d3d 3d3d 3d31 3233
3435
===========12345
0x0030
: 3637
38
678
11
:53
:51.700038
52
:54
:00
:12
:34
:56
> 02
:42
:7e:7f:eb:02
, ethertype IPv4 (0x0800
), length 65
: 127.0
.0.1
.domain > 127.0
.0.1
.domain: 26144
op8+% [b2&3
=0x4576
] [29728a] [25966q] [25701n] [28015au][|domain]
0x0000
: 4500
0033
0001
0000
4011
7cb7 7f00 0001
E..3
....@.|.....
0x0010
: 7f00 0001
0035
0035
001f 42c6 6620
4576
.....5.5
..B.f.Ev <-- Modified data
0x0020
: 656e 7420
6465
6d6f 2a2a 2a2a 2a2a 2a2a ent.demo********
0x0030
: 2a2a 2a ***
Flex IO SDK 软件包
Flex IO SDK 包含以下软件包
flexio-sdk
– 此软件包包含主机侧和设备侧的 Flex IO SDK 头文件和库文件。设备侧的库文件按芯片类型划分(即,某些用于 NVIDIA® BlueField®-3,而另一些用于 NVIDIA® ConnectX®-8)。flexio-samples
– 此软件包包含主机侧和设备侧的 Flex IO 示例dpa-gdbserver
– 此软件包包含主机侧和设备侧的 Flex IO 二进制文件,并用作调试 DPA 应用程序的 gdbserver
DPA 应用程序身份验证在 BlueField-3 上以 Beta 级别受支持。
DPA 应用程序身份验证目前仅支持静态链接库。目前不支持动态链接库。
本节提供有关开发、签名和使用经过身份验证的 BlueField-3 数据路径加速器 (DPA) 应用程序的说明。它包括以下信息:
信任根的原理和支持它的结构
设备所有权转移/声明流程(即,用户应如何配置设备,使其验证来自用户的 DPA 应用程序)
加密签名流程以及 ELF 文件结构和支持它的工具
信任根原则
第三方 DPA 应用程序代码签名
NVIDIA® BlueField®-3 引入了客户/设备所有者可以使用其私钥对 DPA 上运行的应用程序进行签名,并由设备嵌入式证书链对其进行身份验证的功能。这提供了确保只有客户允许的代码才能在 DPA 上运行的好处。客户可以是任何编写旨在在 DPA 上运行的代码的一方(例如,云服务提供商、OEM 等)。
下图说明了客户代码的签名。此签名将允许 NVIDIA 固件验证应用程序代码的来源。
客户 DPA 代码由客户签名以进行身份验证的示例

高级方案如下(参见图“加载客户密钥和 CA 证书以及向 BlueField-3 设备提供 DPA 固件”):
这些步骤的编号对应于下图中标示的编号。
客户向 NVIDIA 企业支持部门提供设备所有权的公钥。
NVIDIA 对客户的公钥进行签名,然后将其发回给客户。
客户将 NVIDIA 签名的公钥上传到设备,从而实现从 NVIDIA 到客户的“所有权转移”。
使用与上传到设备的公钥对应的私钥,客户现在可以启用 DPA 身份验证并加载用于 DPA 应用程序代码身份验证的根证书。
由客户加密签名的 DPA 应用程序代码用于验证应用程序代码的来源。
用于验证 DPA 应用程序的公钥作为证书链(叶证书)的一部分与 DPA 固件映像一起提供。
应用程序代码和所有者签名用于授权 NVIDIA 固件执行应用程序(类似于 NVIDIA 自己的签名)。
加载客户密钥和 CA 证书以及向 BlueField-3 设备提供 DPA 固件

以下各节提供有关此高级过程的更多详细信息。
DPA 应用程序代码的真实性验证
应用程序固件代码在授权执行之前的身份验证应包括使用客户公钥验证客户证书链和客户签名。
公钥(基础设施、交付和验证)
为了应用程序固件的身份验证验证目的,必须安全地将公钥提供给硬件。为此,应使用安全的管理组件控制 (MCC) 流程。使用此流程,下载的证书内容被封装在 MCC 下载容器中,并由 NVIDIA 私钥签名。
以下是如何使用 MCC 流程的示例,详细描述了支持此流程的程序、工具和结构(“加载 CSP CA 证书和密钥以及向设备提供 DPA 固件”部分描述了此流程的高级流程)。
以下命令刻录证书容器
flint -d <mst device> -i <signed-certificate-container> burn
可能存在两种用例
DPA 应用程序在 NVIDIA 内部开发,身份验证基于 NVIDIA 内部密钥和签名基础设施
DPA 应用程序由客户开发,身份验证基于客户证书链
在任何一种情况下,客户都必须将 相关的 CA 证书 下载到设备。
ROT 证书链

下图说明了用于验证 DPA 应用程序映像的证书链的构建。这些链的叶证书用于验证客户提供的 DPA 应用程序(具有来自客户 CA 的 ROT)。用于验证 DPA 应用程序的 NVIDIA 证书链(在 NVIDIA 内部构建)以非常相似的方式构建。OEMDpaCert CA 是根 CA,客户可以使用它将其证书链扩展到客户叶证书,该证书用于验证应用程序映像的签名。同样,NVDADpaCert CA 是 NVIDIA 内部用于构建 DPA 证书链以验证 NVIDIA DPA 应用程序的根 CA。
客户私钥必须保持安全,并且完全由客户负责维护。建议准备一组密钥供客户使用,以实现冗余目的。整个客户证书链(包括根 CA 和叶证书)不得超过 4 个证书。
NVDA_CACert_DPA
和 OEM_CACert_DPA
证书是自签名的,并且是受信任的,因为它们由安全的 MCC 流程加载并由固件进行身份验证。
超出 OEM_CACert_DPA
的客户证书链与 DPA 映像一起交付,包括用于验证 DPA 固件的加密签名的叶证书(请参阅表“ELF Crypto Data Section Fields Description”)。
有关证书及其在闪存中位置的更多详细信息,请联系 NVIDIA 企业支持部门以获取闪存应用说明。用于 DPA 固件身份验证的其余证书链包括
对于 NVIDIA 签名的映像(例如,图“ROT 证书链”):NVDA DPA 根证书(
NVDA_CACert_DPA
可以从 此处 下载)对于客户签名的映像(例如,图“ROT 证书链”):客户 CA 证书、客户产品证书和客户叶证书
在两种情况下(NVIDIA 内部签名和客户签名),证书链的这些部分都附加到 DPA 固件映像。
加载 CSP CA 证书和密钥以及向设备提供 DPA 固件
图“加载客户密钥和 CA 证书以及向 BlueField-3 设备提供 DPA 固件”以概要方式展示了将用户公钥加载到设备、签名和加载客户证书 MCC 容器以及下载 DPA 固件映像的步骤。
为了清晰起见,ROT 验证的层级结构如下:
客户公钥,用于客户 TLV 和
CACert_DPA
证书验证,PK_TLV
(即NV_LC_NV_PUBLIC_KEY
)对于客户希望首次启用 DPA 身份验证功能的设备,客户必须联系 NVIDIA 企业支持部门,以获取 NVIDIA 密钥的签名和身份验证。完整的流程在“设备所有权声明流程”中描述。
加载
PK_TLV
后,可以使用相同的PK_TLV
对更新进行身份验证来更新它。完整的流程在“设备所有权声明流程”中描述。用于启用/禁用 DPA 身份验证的 TLV 的身份验证也由
PK_TLV
验证。完整的流程在“DPA 身份验证启用”部分中描述。
加载 CA 证书 (
CACert_DPA
) 用于 DPA 代码验证。它使用相同的PK_TLV
进行身份验证。完整的流程在“上传 DPA 根 CA 证书”中描述。
由
CACert_DPA
锚定的证书链的叶子节点中的公钥用于 DPA 固件映像的身份验证。包含 DPA 应用程序和证书链的 ELF 文件的结构在“ELF 文件结构”中描述。
需要可扩展且可靠的基础设施来支持众多用户。客户还必须拥有基础设施,以根据其组织的安全策略来支持其自身的代码签名流程。这两个问题均不在本文档的范围之内。
不支持在 DOCA 2.2.0 之前的固件版本中使用 DPA 签名流程。
设备所有权声明流程
NVIDIA 网络设备允许设备用户自定义配置,在某些情况下还可以更改设备的行为。这组可用的自定义设置由更高级别的 NVIDIA 配置控制,这些配置可以作为设备固件的一部分,也可以作为单独的更新文件提供。为了允许客户/设备所有者更改可用配置和允许行为的集合,每个设备都可以有一个设备所有者,该所有者被允许更改设备的默认行为和配置,并更改向用户公开的配置。
客户/设备所有者控制的项目包括:
设备配置:客户/设备所有者可以更改用户可用的任何配置的默认值。他们还可以阻止用户更改该值。
受信任的根证书:客户/设备所有者可以控制设备信任哪些根证书。这些证书控制各种行为(例如,BlueField DPA 接受哪些第三方代码)。
在设备拥有所有者的公钥后,每当使用此密钥对 NVconfig 文件进行签名时,以下两种情况之一必须为真:
NVconfig 文件中的
nv_file_id
字段必须将参数keep_same_priority
设置为True
;或者NVconfig 文件必须包含公钥本身(以便将公钥重新写入设备)
否则,公钥将从设备中删除,因此将不接受使用匹配的私钥签名的文件。
详细的所有权声明流程
客户生成私钥-公钥对,以及密钥对的 UUID。
为密钥对生成 UUID
uuidgen -t
示例输出
77dd4ef0-c633-11ed-9e20-001dd8b744ff
生成 RSA 密钥对
openssl genrsa -out OEM.77dd4ef0-c633-11ed-9e20-001dd8b744ff.pem 4096
示例输出
Generating RSA private key, 2048 bit long modulus ...........+++ ..............+++ e is 65537 (0x10001)
从 RSA 密钥对中提取公钥文件
openssl rsa -in OEM.77dd4ef0-c633-11ed-9e20-001dd8b744ff.pem -out OEM.77dd4ef0-c633-11ed-9e20-001dd8b744ff.public -pubout -outform PEM
输出
writing RSA key
公钥应类似于以下内容:
-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxfijde+27A3pQ7MoZnlm mtpyuHO1JY9AUeKaHUXkWRiopL9Puswx1KcGfWJSNzlEPZRevTHraYlLQCru4ofr W9NBE/qIwS2n7kiFwCCvZK6FKUUqZAuMJTpfuNtv9o4C4v0ZiX4TQqWDND8hy+1L hPf3QLRiJ/ux4G6uHIFwENSwagershuKD0RI6BaZ1g9S9IxdXcD0vTdEuDPqQ0m4 CwEs/3xnksNRLUM+TiPEZoc5MoEoKyJv4GFbGttabhDCt5sr9RqAqTNUSDI9B0jr XoQBQQpqRgYd3lQ31Fhh3G9GjtoAcUQ6l0Gct3DXKFTAADV3Lyo1vjFNrOKUhdhT pjDKzNmZAsxyIZI0buc24TCgj1yPyFboJtpnHmltyxfm9e+EJsdSIpRiX8YTWwkN aIzNj08VswULwbKow5Gu5FFpE/uXDE3cXjLOUNnKihszFv4qkqsQjKaK4GszXge+ jfiEwsDKwS+cuWd9ihnyLrIWF23+OX0S5xjFXDJE8UthOD+3j3gGmP3kze1Iz2YP Qvh3ITPRsqQltaiYh+CivqaCHC0voIMOP1ilAEZ/rW85pi6LA8EsudNMG2ELrUyl SznBzZI/OxMk4qKx9nGgjaP2YjmcPw2Ffc9zZcwl57ThEOhlyS6w3E9xwBvZINLe gMuOIWsu1FK3lIGxMSCUZQsCAwEAAQ== -----END PUBLIC KEY-----
客户向 NVIDIA 企业支持部门提供用于设备所有权的公钥及其 UUID。
NVIDIA 使用此公钥生成签名的 NVconfig 文件,并将其发送给客户。此密钥只能应用于尚未安装设备所有权密钥的设备。
客户使用
mlxconfig
在所需的设备上安装 OEM 密钥。mlxconfig -d /dev/mst/<dev> apply oem_public_key_nvconfig.bin
为了检查上传过程是否成功,客户可以使用 mlxconfig
查询设备,并检查是否已应用新的公钥。要查询的相关参数是 LC_NV_PUB_KEY_EXP
、LC_NV_PUB_KEY_UUID
和 LC_NV_PUB_KEY_0_255
。
查询命令和预期响应示例
mlxconfig -d <dev>-e q LC_NV_PUB_KEY_0_255
上传 DPA 根 CA 证书
在将设备所有权公钥上传到设备后,所有者可以将 DPA 根 CA 证书上传到设备。设备上可以同时存在多个 DPA 根 CA 证书。
如果所有者想要上传由 NVIDIA 开发的经过身份验证的 DPA 应用程序,他们必须上传在此处找到的 NVIDIA DPA 根 CA 证书:此处。
如果所有者想要对其自己的 DPA 应用程序进行签名,他们必须创建另一个公钥-私钥对(除了设备所有权密钥对之外),创建一个包含 DPA 根 CA 公钥的证书,并使用 mlxdpa
创建包含此证书的容器。
要将包含 DPA 根 CA 证书的签名容器上传到设备,必须使用 mlxdpa
。这可以针对 NVIDIA 或客户创建的证书完成。
生成 DPA 根 CA 证书
创建一个 DER 编码的证书,其中包含用于验证 DPA 应用程序的公钥。
生成证书和新的密钥对
openssl req -x509 -newkey rsa:4096 -keyout OEM-DPA-root-CA-key.pem -outform der -out OEM-DPA-root-CA-cert.crt -sha256 -nodes -subj "/C=XX/ST=OEMStateName/L=OEMCityName/O=OEMCompanyName/OU=OEMCompanySectionName/CN=OEMCommonName" -days 3650
注意证书中同时支持 SHA256 和 SHA512。仅支持 RSA 4096 密钥。DER 格式的每个证书的大小必须小于 1792 字节。
输出
Generating a 4096 bit RSA private key ......++ ......................++ writing new private key to 'OEM-DPA-root-CA-key.pem' -----
为证书创建一个容器,并使用设备所有权私钥对其进行签名。
创建和添加容器
mlxdpa --cert_container_type add -c <cert.der> -o <path to output> --life_cycle_priority <Nvidia/OEM/User> create_cert_container
输出示例
Certificate container created successfully!
签名容器
mlxdpa --cert_container <path to container> -p <key file> --keypair_uuid <uuid> --cert_uuid <uuid> --life_cycle_priority <Nvidia/OEM/User> -o <path-to-output> sign_cert_container Certificate container signed successfully!
手动签名容器
如果持有私钥的服务器无法运行 mlxdpa
,则可以手动签名证书容器并将签名添加到容器。在这种情况下,应遵循以下流程:
生成未签名的证书容器
mlxdpa --cert_container_type add -c <.DER-formatted-certificate> -o <unsigned-container-path> --keypair_uuid <uuid> --cert_uuid <uuid> --life_cycle_priority OEM create_cert_container
生成签名字段标头
echo "90 01 02 0C 10 00 00 00 00 00 00 00" | xxd -r -p - <signature-header-path>
生成容器的签名(以任何方式,这仅是一个示例)
openssl dgst -sha512 -sign <private-key-pem-file> -out <container-signature-path> <unsigned-container-path>
将未签名的容器、签名标头和签名连接到一个文件中
cat <unsigned-container-path> <signature-header-path> <container-signature-path> > <signed-container-path>
上传证书
上传每个包含设备所需证书的签名容器。
flint -d <dev> -i <signed-container> -y b
输出示例
-I- Downloading FW ...
FSMST_INITIALIZE - OK
Writing DIGITAL_CACERT_REMOVAL component - OK
-I- Component FW burn finished successfully.
删除证书
要从设备中删除根 CA 证书,用户必须应用由设备所有权私钥签名的证书删除容器。
有两种删除证书的方法,一种是删除所有证书,另一种是删除所有已安装的证书
从设备中删除所有根 CA 证书
生成一个签名容器以删除所有证书。
创建的证书容器
mlxdpa --cert_container_type remove --remove_all_certs -o <path-to-container> --life_cycle_priority <Nvidia/OEM/User> create_cert_container
输出示例
Certificate container created successfully!
签名证书容器
mlxdpa --cert_container <path-to-container> -p <key-file> --keypair_uuid <uuid> --life_cycle_priority <Nvidia/OEM/User> -o <path-to-signed-container> sign_cert_container
输出示例
Certificate container signed successfully!
将容器应用于设备。
flint -d <dev> -i <signed-container> -y b
输出示例
-I- Downloading FW ... FSMST_INITIALIZE - OK Writing DIGITAL_CACERT_REMOVAL component - OK -I- Component FW burn finished successfully.
根据 UUID 删除特定的根 CA 证书
生成一个签名容器以基于 UUID 删除证书。
创建容器。
mlxdpa --cert_container_type remove --cert_uuid <uuid> -o <path-to-container> --life_cycle_priority <Nvidia/OEM/User> create_cert_container
输出示例
Certificate container created successfully!
签名容器
mlxdpa --cert_container <path-to-container> -p <key-file> --keypair_uuid <uuid> --cert_uuid <uuid> --life_cycle_priority <Nvidia/OEM/User> -o <path to output> sign_cert_container
输出示例
Certificate container signed successfully!
将容器应用于设备
flint -d <dev> -i <signed container> -y b
输出
-I- Downloading FW ... FSMST_INITIALIZE - OK Writing DIGITAL_CACERT_REMOVAL component - OK -I- Component FW burn finished successfully.
DPA 身份验证启用
在设备安装了设备所有权密钥和 DPA 根 CA 证书后,设备所有者可以启用 DPA 身份验证。为此,他们必须创建一个 NVconfig 文件,使用设备所有权私钥对其进行签名,并将 NVconfig 上传到设备。
生成启用 DPA 身份验证的 NVconfig
创建包含 TLV 以启用 DPA 身份验证的 XML。
获取此设备的可用 TLV 列表
mlxconfig -d /dev/mst/<dev> gen_tlvs_file enable_dpa_auth.txt
输出
Saving output... Done!
生成的文本文件的一部分示例
file_applicable_to 0 file_comment 0 file_signature 0 file_dbg_fw_token_id 0 file_cs_token_id 0 file_btc_token_id 0 file_mac_addr_list 0 file_public_key 0 file_signature_4096_a 0 file_signature_4096_b 0 …
编辑文本文件以包含以下 TLV
file_applicable_to 1 nv_file_id_vendor 1 nv_dpa_auth 1
使用另一个 mlxconfig 命令将
.txt
文件转换为 XML 格式mlxconfig -a gen_xml_template enable_dpa_auth.txt enable_dpa_auth.xml
输出
Saving output... Done!
生成的
.xml
文件<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://www.mellanox.com/config"> <file_applicable_to ovr_en='1' rd_en='1' writer_id='0'> <psid></psid> <psid_branch></psid_branch> </file_applicable_to> <nv_file_id_vendor ovr_en='1' rd_en='1' writer_id='0'> <!-- Legal Values: False/True --> <disable_override></disable_override> <!-- Legal Values: False/True --> <keep_same_priority></keep_same_priority> <!-- Legal Values: False/True --> <per_tlv_priority></per_tlv_priority> <!-- Legal Values: False/True --> <erase_lower_priority></erase_lower_priority> <file_version></file_version> <day></day> <month></month> <year></year> <seconds></seconds> <minutes></minutes> <hour></hour> </nv_file_id_vendor> <nv_dpa_auth ovr_en='1' rd_en='1' writer_id='0'> <!-- Legal Values: False/True --> <dpa_auth_en></dpa_auth_en> </nv_dpa_auth> </config>
编辑 XML 文件,并为每个 TLV 添加信息,如以下 XML 文件示例所示
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://www.mellanox.com/config"> <file_applicable_to ovr_en='0' rd_en='1' writer_id='0'> <psid>TODO</psid> <psid_branch>TODO</psid_branch> </file_applicable_to> <nv_file_id_vendor ovr_en='0' rd_en='1' writer_id='0'> <disable_override>False</disable_override> <keep_same_priority>True</keep_same_priority> <per_tlv_priority>False</per_tlv_priority> <erase_lower_priority>False</erase_lower_priority> <file_version>TODO</file_version> <day>TODO</day> <month>TODO</month> <year>TODO</year> <seconds>TODO</seconds> <minutes>TODO</minutes> <hour>TODO</hour> </nv_file_id_vendor> <nv_dpa_auth ovr_en='0' rd_en='1' writer_id='0'> <dpa_auth_en>True</dpa_auth_en> </nv_dpa_auth> </config>
注意在
nv_file_id_vendor
中,keep_same_priority
必须为True
,以避免从设备中删除所有权公钥。更多信息可以在“设备所有权声明流程”部分中找到。注意ovr_en
应设置为 0。这可以忽略用户优先级更改nv_dpa_auth
。
将 XML 文件转换为二进制 NVconfig 文件并使用
mlxconfig
对其进行签名mlxconfig -p OEM.77dd4ef0-c633-11ed-9e20-001dd8b744ff.pem -u 77dd4ef0-c633-11ed-9e20-001dd8b744ff create_conf enable_dpa_auth.xml enable_dpa_auth.bin
create_conf
命令的输出Saving output... Done!
通过将文件写入设备,将 NVconfig 文件上传到设备
mlxconfig -d /dev/mst/<dev> apply enable_dpa_auth.bin
输出
Saving output... Done!
通过从设备读取 DPA 身份验证的状态,验证设备是否已启用 DPA 身份验证
mlxconfig -d /dev/mst/<dev> -e q DPA_AUTHENTICATION
输出
Device #1: ---------- Device type: BlueField3 … … Configurations: Default Current Next Boot RO DPA_AUTHENTICATION True(1) True(1) True(1)
DPU 的出厂默认设置配置为
dpa_auth_en=0
(即,DPA 应用程序可以在没有身份验证的情况下运行)。为了防止任何用户更改配置,强烈建议客户根据自己的偏好,使用ovr_en=0
生成并安装dpa_auth_en=0/1
的 NVconfig。
手动签名 NVconfig 文件
如果持有私钥的服务器无法运行 mlxconfig,则可以手动签名二进制 NVconfig 文件并将签名添加到文件中。在这种情况下,应遵循以下流程,而不是步骤 2
从 XML 文件生成未签名的 NVconfig bin 文件
mlxconfig create_conf <xml-nvconfig-path> <unsigned-nvconfig-path>
为签名生成随机 UUID
uuidgen -r | xxd -r -p - <signature-uuid-path>
生成 NVconfig bin 文件的签名(以任何方式,这仅是一个示例)
openssl dgst -sha512 -sign <private-key-pem-file> -out <nvconfig-signature-path> <unsigned-nvconfig-path>
将签名分成两部分
head -c 256 <nvconfig-signature-path> > <signature-part-1-path> && tail -c 256 <nvconfig-signature-path> > <signature-part-2-path>
添加签名密钥 UUID
echo "<signing-key-UUID>" | xxd -r -p - <signing-key-uuid-path>
使用签名密钥 UUID,其长度必须正好为 16 字节,格式如
aa9c8c2f-8b29-4e92-9b76-2429447620e0
。为签名结构生成标头
echo "03 00 01 20 06 00 00 0B 00 00 00 00" | xxd -r -p - <signature-1-header-path> echo "03 00 01 20 06 00 00 0C 00 00 00 00" | xxd -r -p - <signature-2-header-path>
将所有内容连接起来
cat <unsigned-nvconfig-path> <signature-1-header-path> <signature-uuid-path> <signing-key-uuid-path> <signature-part-1-path> <signature-2-header-path> <signature-uuid-path> <signing-key-uuid-path> <signature-part-2-path> > <signed-nvconfig-path>
设备所有权转移
设备所有者可以更改设备所有权密钥,以更改设备的所有者或完全删除所有者。
首次安装
要在设备上安装第一个 OEM_PUBLIC_KEY
,用户必须上传由 NVIDIA 签名的 NVCONFIG 文件。此文件将包含当前用户的 3 个 FILE
_OEM_PUBLIC_KEY
TLV。
删除设备所有权密钥
在完全删除设备所有权密钥之前,建议设备所有者恢复对设备所做的任何更改,因为密钥删除后无法撤消这些更改。主要是,应删除所有者安装的根 CA 证书。
要完全删除设备所有权密钥,请按照“生成启用 DPA 身份验证的 NVconfig”部分中的步骤创建包含 TLV 的 XML 文件。
编辑 XML 文件以包含以下 TLV
<?xml version="1.0" encoding="UTF-8"?> <config xmlns="http://www.mellanox.com/config"> <file_applicable_to ovr_en='0' rd_en='1' writer_id='0'> <psid> MT_0000000911</psid> <psid_branch> </psid_branch> </file_applicable_to> <nv_file_id_vendor ovr_en='0' rd_en='1' writer_id='0'> <disable_override>False</disable_override> <keep_same_priority>False</keep_same_priority> <per_tlv_priority>False</per_tlv_priority> <erase_lower_priority>False</erase_lower_priority> <file_version>0</file_version> <day>17</day> <month>7</month> <year>7e7</year> <seconds>1</seconds> <minutes>e</minutes> <hour>15</hour> </nv_file_id_vendor> </config>
此文件中的 TLV 是应用此文件后唯一具有 OEM 优先级的 TLV,并且由于设备所有权密钥将不再位于设备上,因此 OEM 将无法再更改 TLV。要在删除设备所有权密钥后在设备上保留 OEM 优先级 TLV,请将任何必须作为默认值保留在设备上的 TLV 添加到此 XML 中。
按照“生成启用 DPA 身份验证的 NVconfig”部分中的描述,将 XML 文件转换为由设备所有权密钥签名的二进制 NVconfig TLV 文件。
按照“生成启用 DPA 身份验证的 NVconfig”部分中的描述,将 NVconfig 文件应用于设备。
更改设备所有权密钥
要将设备的所有权转移给另一个实体,以前的所有者可以将设备所有权公钥更改为新所有者的公钥。
为此,他们可以使用 NVconfig 文件,并在其中包含以下 TLV
<nv_ls_nv_public_key_0 ovr_en='0' rd_en='1' writer_id='0'>
<public_key_exp>65537</public_key_exp>
<keypair_uuid>77dd4ef0-c633-11ed-9e20-001dd8b744ff</keypair_uuid>
</nv_ls_nv_public_key_0>
<nv_ls_nv_public_key_1 ovr_en='0' rd_en='1' writer_id='0'>
<key>
c5:f8:a3:75:ef:b6:ec:0d:e9:43:b3:28:66:79:
66:9a:da:72:b8:73:b5:25:8f:40:51:e2:9a:1d:45:
e4:59:18:a8:a4:bf:4f:ba:cc:31:d4:a7:06:7d:62:
52:37:39:44:3d:94:5e:bd:31:eb:69:89:4b:40:2a:
ee:e2:87:eb:5b:d3:41:13:fa:88:c1:2d:a7:ee:48:
85:c0:20:af:64:ae:85:29:45:2a:64:0b:8c:25:3a:
5f:b8:db:6f:f6:8e:02:e2:fd:19:89:7e:13:42:a5:
83:34:3f:21:cb:ed:4b:84:f7:f7:40:b4:62:27:fb:
b1:e0:6e:ae:1c:81:70:10:d4:b0:6a:07:ab:b2:1b:
8a:0f:44:48:e8:16:99:d6:0f:52:f4:8c:5d:5d:c0:
f4:bd:37:44:b8:33:ea:43:49:b8:0b:01:2c:ff:7c:
67:92:c3:51:2d:43:3e:4e:23:c4:66:87:39:32:81:
28:2b:22:6f:e0:61:5b:1a:db:5a:6e:10:c2:b7:9b:
2b:f5:1a:80:a9:33:54:48:32:3d:07:48:eb:5e:84:
01:41:0a:6a:46:06:1d:de:54:37:d4:58:61:dc:6f:
46:8e:da:00:71:44:3a:97:41:9c:b7:70:d7:28:54:
c0:00:35:77:2f:2a:35:be:31:4d:ac:e2:94:85:d8:
53:a6:
</key>
</nv_ls_nv_public_key_1>
<nv_ls_nv_public_key_2 ovr_en='0' rd_en='1' writer_id='0'>
<key>
30:ca:cc:d9:99:02:cc:72:21:92:34:6e:e7:
36:e1:30:a0:8f:5c:8f:c8:56:e8:26:da:67:1e:69:
6d:cb:17:e6:f5:ef:84:26:c7:52:22:94:62:5f:c6:
13:5b:09:0d:68:8c:cd:8f:4f:15:b3:05:0b:c1:b2:
a8:c3:91:ae:e4:51:69:13:fb:97:0c:4d:dc:5e:32:
ce:50:d9:ca:8a:1b:33:16:fe:2a:92:ab:10:8c:a6:
8a:e0:6b:33:5e:07:be:8d:f8:84:c2:c0:ca:c1:2f:
9c:b9:67:7d:8a:19:f2:2e:b2:16:17:6d:fe:39:7d:
12:e7:18:c5:5c:32:44:f1:4b:61:38:3f:b7:8f:78:
06:98:fd:e4:cd:ed:48:cf:66:0f:42:f8:77:21:33:
d1:b2:a4:25:b5:a8:98:87:e0:a2:be:a6:82:1c:2d:
2f:a0:83:0e:3f:58:a5:00:46:7f:ad:6f:39:a6:2e:
8b:03:c1:2c:b9:d3:4c:1b:61:0b:ad:4c:a5:4b:39:
c1:cd:92:3f:3b:13:24:e2:a2:b1:f6:71:a0:8d:a3:
f6:62:39:9c:3f:0d:85:7d:cf:73:65:cc:25:e7:b4:
e1:10:e8:65:c9:2e:b0:dc:4f:71:c0:1b:d9:20:d2:
de:80:cb:8e:21:6b:2e:d4:52:b7:94:81:b1:31:20:
94:65:0b
</key>
</nv_ls_nv_public_key_2>
如果转移是内部的,则所有者应在 nv_file_id_vendor
TLV 中设置 keep_same_priority=True
,并且仅在 NVconfig 文件中包含 3 个 nv_ls_nv_public_key_*
TLV、file_applicable_to
和 nv_file_id_vendor
TLV。
如果转移到另一个 OEM/CSP,则所有者应清理设备(类似于删除设备所有权密钥),并在 nv_file_id_vendor
TLV 中设置 keep_same_priority=False
。
ELF 文件结构
为了最大限度地重用固件代码,从驱动程序加载的 DPA 映像的格式应与从闪存加载的文件格式相同。对于从主机加载的文件,ELF 是默认文件格式。选择 ELF 作为 DPA 映像的格式,无论是用于闪存还是用于从主机加载的文件。
下图示意性地显示了通用 ELF 文件结构。
为了支持 DPA 代码身份验证,需要向固件提供其他信息。此信息必须包括
DPA 代码的加密签名
客户证书链,包括叶子证书,其中包含用于签名验证的公钥(如“公钥(基础设施、交付和验证)”部分中所述)
ELF 文件结构示意图

加密签名流程
主机 ELF 包括在主机上运行的部分和在 DPA 上运行的部分。DPA 代码文件作为二进制文件合并到“大”主机 ELF 中。每个主机文件可能包含多个 DPA 应用程序。
当需要对 DPA 应用程序进行签名时,MFT 签名工具 需要执行以下步骤(另请参阅图“加密签名流程”)
使用 DPACC 的 ELF 操作库 API,提取应用程序列表表
输入 – 主机 ELF
输出 – 要包含的应用程序列表数据表
DPA 应用程序索引
DPA 应用程序名称
主机 ELF 中的偏移量
应用程序大小
相应加密数据段的名称
对于每个 DPA 应用程序(从 i=1 到 i=N,N- 主机 ELF 中的 DPA 应用程序数量),运行步骤 2 和 3。
填充哈希列表表
输入:
Dpa_App_i
输出:哈希列表表
签名加密数据
输入:{元数据、哈希列表表}、密钥句柄(例如,来自证书链叶子节点的 UUID)
输出:
Crypto_Data
“Blob”,包括:元数据、哈希列表表、加密签名、证书链
将加密数据段添加到主机 ELF
输入:主机 ELF、要使用的加密数据段名称
输出:添加签名后的主机 ELF 的文件名
流程中使用的结构(哈希列表表、元数据等)在“ELF 加密数据段内容”和“哈希列表表布局”部分中描述。
可以使用签名服务器或本地存储的密钥完成加密数据的签名。
加密签名流程

ELF 加密数据段
下图示意性地显示了加密数据段的布局,以下小节提供了有关 ELF 段标头和其余结构的详细信息。
ELF 加密数据段布局

加密数据 ELF 段标头
根据 ELF 段标头格式 定义。
ELF 段标头
名称 | 偏移量 | 范围 | 描述 |
| 0x0 | 4B | &("Cryptographic Data Section DPA App X") 指向字符串的偏移量(在 ELF 的 |
| 0x4 | 4B | 0x70000666
|
| 0X8 | 8B | 0 – 无标志 |
| 0x10 | 8B | 加载段在内存中的虚拟地址 |
| 0x18 | 8B | 段在文件映像中的偏移量 |
| 0x20 | 8B | 段在文件映像中的大小(以字节为单位)。取决于内容(例如,公钥证书链和签名的存在和类型)。 |
| 0x28 | 4B | 0 – |
| 0x2C | 4B | 0 – 无关于段的额外信息 |
| 0x30 | 8B | 包含段的所需对齐方式。此字段必须是 2 的幂。 |
| 0x38 | 8B | 0 |
0x40 | 段标头结尾(大小) |
ELF 加密数据段内容
ELF 加密数据段字段描述
名称 | 偏移量 | 范围 | 描述 |
metadata_version | 0x0 | 15:0 | 版本元数据结构格式。初始版本为 0。 |
保留 ( | 0x4 | 15:8 | 保留 |
保留 | 0x8 | 31:0 | 保留 |
保留 | 0xC | 31:0 | 保留。应设置为全零。 |
保留 | 0x10 | 16B | 保留。应设置为全零。 |
保留 | 0x20 | 4 字节 | 保留。应设置为全零。 |
保留 | 0x24 | 24B | 保留。应设置为全零。 |
signature_type | 0x3c | 15:0 | 签名类型。仅与签名固件相关
|
哈希列表表 | 0x40 | HashTableLength | |
加密签名 | 0x40 + HashTableLength | Signature_Length | Signature_Length 取决于 signature_type。 |
Certificate_Chain | 0x40 + HashTableLength + Signature_Length | CrtChain_Length | 结构在“证书链布局”部分下的表格中给出。 |
填充 | FF 填充,以将数据的总大小对齐到 DWords (DW) 的倍数 |
ELF 加密数据段的总长度应为 DW 的倍数(由于固件的旧实现)。因此,MFT(作为图“加密签名流程”中描述的流程的一部分)应为此结构添加 FF 填充,以对齐到 DW 的倍数。
哈希列表表布局
此表指定哈希表布局(提议)。
该表包含两个部分
第一部分对应于 ELF 文件的段,由 EFL 文件的程序标头表引用
第二部分对应于 ELF 文件的节,由节标头表引用
要使用的哈希算法为 SHA-256。
哈希列表表布局(提议)
名称 | 偏移量 | 范围 | 描述 |
哈希表魔术模式 | 0x0 | 8 字节 | ASCII “HASHLIST” 字符串 0x0: 31:24 – “H”, 23:16 – “A”, 15:8 – “S”, 7:0 – “H” 0x4: 31:24 – “L”, 23:16 – “I”, 15:8 – “S”, 7:0 – “T” |
条目数 – 段 | 0x8 | 7:0 | 哈希段部分中的条目数,N_Segments。 |
保留 | 0x8 | 31:8 | 保留 |
条目数 – 节 | 0xc | 7:0 | 哈希节部分中的条目数,N_Sections。 最小值 – 0 |
保留 | 0xc | 31:8 | 保留 |
保留 | 0x10 | 16 字节 | 保留 |
DPA 应用程序 ELF 哈希 | 0x20 | 32 字节 | 完整 ELF 应用程序文件的哈希 |
ELF 标头哈希 | 0x40 | 32 字节 | ELF 标头的哈希 |
程序标头哈希 | 0x60 | 32 字节 | 程序标头的哈希 |
程序标头表中引用的第一个段的哈希 | 0x80 | 32 字节 | 程序标头表中引用的第一个段的哈希 |
程序标头表中引用的第二个段的哈希 | 0xA0 | 32 字节 | 程序标头表中引用的第二个段的哈希 |
…… | …… | ….. | …… |
程序标头表中引用的 N_Segments(最后一个)段的哈希 | 0x60 + N_Segments*0x20 | 32 字节 | 程序标头表中引用的第二个段的哈希 |
节标头表哈希 | 0x80 + N_Segments*0x20 | 32 字节 | 节标头表的哈希 |
节标头表中引用的第一个节的哈希 | + 0x20 | 32 字节 | 节标头表中引用的第一个节的哈希 |
节标头表中引用的第二个节的哈希 | + 0x20 | 32 字节 | 节标头表中引用的第二个节的哈希 |
…… | …… | ….. | …… |
节标头表中引用的 N_Sections(最后一个)节的哈希 | + 0x20 | 32 字节 | 节标头表中引用的 N_Sections(最后一个)节的哈希 |
前表不同节/段的 32 字节哈希字段应遵循大端约定,如此处所示
哈希字段(大端)字节对齐

证书链布局
下表指定证书链布局。链的叶子节点(最后一个证书)用作用于 DPA 代码身份验证的公钥。此结构与 Flash Application Note 中定义的证书链布局对齐。
证书链布局
名称 | 偏移量 | 范围 | 描述 |
类型 | 0x0 | 3:0 | 链类型。应设置为 1。第三方代码身份验证证书链。 |
计数 | 0x0 | 7:4 | 此链中证书的数量 |
长度 | 0x0 | 23:8 | 证书链的总长度,以字节为单位,包括此表中的所有字段 |
保留 | 0x4 | 31:0 | 31:0 – 保留 |
CRC | 0x8 | 15:0 | 标头的 CRC,用于标头完整性检查,覆盖 0x0、0x4 中的 DW |
证书 | 0xC-0x1000 | 一个或多个 ASN.1 DER 编码的 X509v3 证书。可以分析每个单独证书的 ASN.1 DER 编码以确定其长度。 证书应按层次顺序列出,叶子证书位于列表的最后。 |
支持的设备
基于 BlueField-3 的 DPU
基于 ConnectX-8 的 NIC
支持的主机操作系统
不支持 Windows
支持的 SDK
GA 级别的 DOCA Flex IO
Beta 级别的 DOCA DPA
工具链
当前不支持 DPA 映像签名和签名验证
Flex IO
当使用与 PF/ECPF ID 不同的
device_id
参数(即,移动到 SF/VF 输出邮箱)调用flexio_dev_outbox_config_uar_extension
API,并且调用 APIflexio_dev_yield()
、flexio_dev_print()
或flexio_dev_msg()
时,则当这 3 个 API 中的任何一个返回时,用户都无法使用 SF/VF 队列。Flex IO SDK 跟踪器在高负载(多线程/高速率)下工作时,可能会遇到数据丢失和排序问题。