Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Ben Skeggs 1313 99.39% 28 90.32%
Christian König 4 0.30% 1 3.23%
Karol Herbst 2 0.15% 1 3.23%
Konrad Rzeszutek Wilk 2 0.15% 1 3.23%
Total 1321 31


/* SPDX-License-Identifier: MIT
 *
 * Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
 */
#include "priv.h"

#include <nvhw/drf.h>
#include <nvhw/ref/gh100/dev_fsp_pri.h>
#include <nvhw/ref/gh100/dev_therm.h>

#include <nvrm/nvtypes.h>

#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);
}