/* SPDX-License-Identifier: MIT * * Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. */ #include "priv.h" #include #include #include #include #define MCTP_HEADER_VERSION 3:0 #define MCTP_HEADER_RSVD 7:4 #define MCTP_HEADER_DEID 15:8 #define MCTP_HEADER_SEID 23:16 #define MCTP_HEADER_TAG 26:24 #define MCTP_HEADER_TO 27:27 #define MCTP_HEADER_SEQ 29:28 #define MCTP_HEADER_EOM 30:30 #define MCTP_HEADER_SOM 31:31 #define MCTP_MSG_HEADER_TYPE 6:0 #define MCTP_MSG_HEADER_IC 7:7 #define MCTP_MSG_HEADER_VENDOR_ID 23:8 #define MCTP_MSG_HEADER_NVDM_TYPE 31:24 #define MCTP_MSG_HEADER_TYPE_VENDOR_PCI 0x7e #define MCTP_MSG_HEADER_VENDOR_ID_NV 0x10de #define NVDM_TYPE_COT 0x14 #define NVDM_TYPE_FSP_RESPONSE 0x15 #pragma pack(1) typedef struct nvdm_payload_cot { NvU16 version; NvU16 size; NvU64 gspFmcSysmemOffset; NvU64 frtsSysmemOffset; NvU32 frtsSysmemSize; // Note this is an offset from the end of FB NvU64 frtsVidmemOffset; NvU32 frtsVidmemSize; // Authentication related fields NvU32 hash384[12]; NvU32 publicKey[96]; NvU32 signature[96]; NvU64 gspBootArgsSysmemOffset; } NVDM_PAYLOAD_COT; #pragma pack() #pragma pack(1) typedef struct { NvU32 taskId; NvU32 commandNvdmType; NvU32 errorCode; } NVDM_PAYLOAD_COMMAND_RESPONSE; #pragma pack() static u32 gh100_fsp_poll(struct nvkm_fsp *fsp) { struct nvkm_device *device = fsp->subdev.device; u32 head, tail; head = nvkm_rd32(device, NV_PFSP_MSGQ_HEAD(0)); tail = nvkm_rd32(device, NV_PFSP_MSGQ_TAIL(0)); if (head == tail) return 0; return (tail - head) + sizeof(u32); /* TAIL points at last DWORD written. */ } static int gh100_fsp_recv(struct nvkm_fsp *fsp, u8 *packet, u32 max_packet_size) { struct nvkm_device *device = fsp->subdev.device; u32 packet_size; int ret; packet_size = gh100_fsp_poll(fsp); if (!packet_size || WARN_ON(packet_size % 4 || packet_size > max_packet_size)) return -EINVAL; ret = nvkm_falcon_pio_rd(&fsp->falcon, 0, EMEM, 0, packet, 0, packet_size); if (ret) return ret; nvkm_wr32(device, NV_PFSP_MSGQ_TAIL(0), 0); nvkm_wr32(device, NV_PFSP_MSGQ_HEAD(0), 0); return packet_size; } static int gh100_fsp_wait(struct nvkm_fsp *fsp) { int time = 1000; do { if (gh100_fsp_poll(fsp)) return 0; usleep_range(1000, 2000); } while(time--); return -ETIMEDOUT; } static int gh100_fsp_send(struct nvkm_fsp *fsp, const u8 *packet, u32 packet_size) { struct nvkm_device *device = fsp->subdev.device; int time = 1000, ret; if (WARN_ON(packet_size % sizeof(u32))) return -EINVAL; /* Ensure any previously sent message has been consumed. */ do { u32 head = nvkm_rd32(device, NV_PFSP_QUEUE_HEAD(0)); u32 tail = nvkm_rd32(device, NV_PFSP_QUEUE_TAIL(0)); if (tail == head) break; usleep_range(1000, 2000); } while(time--); if (time < 0) return -ETIMEDOUT; /* Write message to EMEM. */ ret = nvkm_falcon_pio_wr(&fsp->falcon, packet, 0, 0, EMEM, 0, packet_size, 0, false); if (ret) return ret; /* Update queue pointers - TAIL points at last DWORD written. */ nvkm_wr32(device, NV_PFSP_QUEUE_TAIL(0), packet_size - sizeof(u32)); nvkm_wr32(device, NV_PFSP_QUEUE_HEAD(0), 0); return 0; } static int gh100_fsp_send_sync(struct nvkm_fsp *fsp, u8 nvdm_type, const u8 *packet, u32 packet_size) { struct nvkm_subdev *subdev = &fsp->subdev; struct { u32 mctp_header; u32 nvdm_header; NVDM_PAYLOAD_COMMAND_RESPONSE response; } reply; int ret; ret = gh100_fsp_send(fsp, packet, packet_size); if (ret) return ret; ret = gh100_fsp_wait(fsp); if (ret) return ret; ret = gh100_fsp_recv(fsp, (u8 *)&reply, sizeof(reply)); if (ret < 0) return ret; if (NVVAL_TEST(reply.mctp_header, MCTP, HEADER, SOM, !=, 1) || NVVAL_TEST(reply.mctp_header, MCTP, HEADER, EOM, !=, 1)) { nvkm_error(subdev, "unexpected MCTP header in reply: 0x%08x\n", reply.mctp_header); return -EIO; } if (NVDEF_TEST(reply.nvdm_header, MCTP, MSG_HEADER, TYPE, !=, VENDOR_PCI) || NVDEF_TEST(reply.nvdm_header, MCTP, MSG_HEADER, VENDOR_ID, !=, NV) || NVVAL_TEST(reply.nvdm_header, MCTP, MSG_HEADER, NVDM_TYPE, !=, NVDM_TYPE_FSP_RESPONSE)) { nvkm_error(subdev, "unexpected NVDM header in reply: 0x%08x\n", reply.nvdm_header); return -EIO; } if (reply.response.commandNvdmType != nvdm_type) { nvkm_error(subdev, "expected NVDM type 0x%02x in reply, got 0x%02x\n", nvdm_type, reply.response.commandNvdmType); return -EIO; } if (reply.response.errorCode) { nvkm_error(subdev, "NVDM command 0x%02x failed with error 0x%08x\n", nvdm_type, reply.response.errorCode); return -EIO; } return 0; } int gh100_fsp_boot_gsp_fmc(struct nvkm_fsp *fsp, u64 args_addr, u32 rsvd_size, bool resume, u64 img_addr, const u8 *hash, const u8 *pkey, const u8 *sig) { struct { u32 mctp_header; u32 nvdm_header; NVDM_PAYLOAD_COT cot; } msg = {}; msg.mctp_header = NVVAL(MCTP, HEADER, SOM, 1) | NVVAL(MCTP, HEADER, EOM, 1) | NVVAL(MCTP, HEADER, SEID, 0) | NVVAL(MCTP, HEADER, SEQ, 0); msg.nvdm_header = NVDEF(MCTP, MSG_HEADER, TYPE, VENDOR_PCI) | NVDEF(MCTP, MSG_HEADER, VENDOR_ID, NV) | NVVAL(MCTP, MSG_HEADER, NVDM_TYPE, NVDM_TYPE_COT); msg.cot.version = fsp->func->cot.version; msg.cot.size = sizeof(msg.cot); msg.cot.gspFmcSysmemOffset = img_addr; if (!resume) { msg.cot.frtsVidmemOffset = ALIGN(rsvd_size, 0x200000); msg.cot.frtsVidmemSize = 0x100000; } memcpy(msg.cot.hash384, hash, fsp->func->cot.size_hash); memcpy(msg.cot.publicKey, pkey, fsp->func->cot.size_pkey); memcpy(msg.cot.signature, sig, fsp->func->cot.size_sig); msg.cot.gspBootArgsSysmemOffset = args_addr; return gh100_fsp_send_sync(fsp, NVDM_TYPE_COT, (const u8 *)&msg, sizeof(msg)); } int gh100_fsp_wait_secure_boot(struct nvkm_fsp *fsp) { struct nvkm_device *device = fsp->subdev.device; unsigned timeout_ms = 4000; do { u32 status = NVKM_RD32(device, NV_THERM, I2CS_SCRATCH, FSP_BOOT_COMPLETE_STATUS); if (status == NV_THERM_I2CS_SCRATCH_FSP_BOOT_COMPLETE_STATUS_SUCCESS) return 0; usleep_range(1000, 2000); } while (timeout_ms--); return -ETIMEDOUT; } static const struct nvkm_fsp_func gh100_fsp = { .wait_secure_boot = gh100_fsp_wait_secure_boot, .cot = { .version = 1, .size_hash = 48, .size_pkey = 384, .size_sig = 384, .boot_gsp_fmc = gh100_fsp_boot_gsp_fmc, }, }; int gh100_fsp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_fsp **pfsp) { return nvkm_fsp_new_(&gh100_fsp, device, type, inst, pfsp); }