summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_phys_booter.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 10:13:04 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 10:13:04 -0400
commitc21f7099a8544b35628a7e104f02c5335a08d79d (patch)
treec840bbb360f2c09b1d58609b9b327bd37cf36d89 /kernel/gsp_phys_booter.c
parentd018cc38014f96dfe947d9064010fd43977b10f7 (diff)
downloadopen-iov-c21f7099a8544b35628a7e104f02c5335a08d79d.tar.gz
open-iov-c21f7099a8544b35628a7e104f02c5335a08d79d.zip
gsp-phys: command-queue TX and the GSP-RM unload RPC for clean teardown
Adds a minimal msgq producer on the command queue (element layout, checksum, sequence, dma_wmb before writePtr, doorbell) and submits the UNLOADING_GUEST_DRIVER RPC so GSP-RM shuts down and sets the scratch14 handoff bit the Booter Unload gate needs. Release attempts the RPC once before quiescing when a boot ran; on-demand testing is available through the debugfs file gsp-phys-unload-rpc while the module owns the GPU. GSP-RM processor suspend is polled on the GSP mailbox.
Diffstat (limited to 'kernel/gsp_phys_booter.c')
-rw-r--r--kernel/gsp_phys_booter.c433
1 files changed, 433 insertions, 0 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index d50a4ad..fe52a96 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -34,6 +34,9 @@
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
#include <linux/jiffies.h>
#include <linux/byteorder/generic.h>
#include <asm/barrier.h>
@@ -42,6 +45,7 @@
#include "gsp_phys_booter.h"
#include "gsp_phys_falcon.h"
#include "gsp_phys_fwsec.h"
+#include "gsp_phys_gpu.h"
#include "gsp_phys_booter_load_img.h"
#include "gsp_phys_gsp_rm_boot_img.h"
@@ -138,6 +142,43 @@ bool gsp_phys_boot_requested(void)
#define GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF \
(GSP_PHYS_MSG_RPC_HEADER_OFF + GSP_PHYS_MSG_RPC_HEADER_SIZE)
#define GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF_LEGACY 68
+#define GSP_PHYS_MSG_PAYLOAD_MAX \
+ (GSP_PHYS_MSG_SIZE - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF)
+
+/*
+ * Command-queue TX wire constants (rpc_headers.h, rpc_global_enums.h,
+ * rpc_common.c, message_queue_priv.h):
+ * - rpc.header_version = MAJOR_TOT(3) << 24 | MINOR_TOT(0) << 16;
+ * - rpc.signature = NV_VGPU_MSG_SIGNATURE_VALID;
+ * - rpc.length = sizeof(rpc_message_header_v=32) + payload (so the element
+ * record is 48 + rpc.length bytes and the checksum covers exactly that);
+ * - rpc_result/rpc_result_private = NV_VGPU_MSG_RESULT_RPC_PENDING while in
+ * flight, rpc.sequence = 0 on the GSP client path (rpcWriteCommonHeader
+ * zeroes the buffer and does not set it).
+ */
+#define GSP_PHYS_RPC_HEADER_VERSION 0x03000000u
+#define GSP_PHYS_RPC_SIGNATURE 0x43505256u
+#define GSP_PHYS_RPC_RESULT_PENDING 0xffffffffu
+#define GSP_PHYS_RPC_FUNC_UNLOADING_GUEST_DRIVER 47u
+
+/*
+ * GSP command-queue doorbell. After publishing writePtr the reference
+ * producer rings NV_PGSP_QUEUE_HEAD(RPC_TASK_RM_QUEUE_IDX=0) = 0x110c00
+ * (kgspSetCmdQueueHead_TU102); the only caller passes value 0.
+ */
+#define GSP_PHYS_QUEUE_HEAD_REG 0x00110c00
+#define GSP_PHYS_QUEUE_HEAD_IDX 0
+
+/*
+ * GSP-RM suspend criterion after UNLOADING_GUEST_DRIVER
+ * (kgspWaitForProcessorSuspend_TU102 -> _kgspIsProcessorSuspended): the GSP
+ * falcon MAILBOX0 reads LIBOS_INTERRUPT_PROCESSOR_SUSPENDED. Bounded here to
+ * 10 s (the reference uses the platform thread-state timeout via
+ * gpuTimeoutCondWait).
+ */
+#define GSP_PHYS_LIBOS_PROCESSOR_SUSPENDED 0x80000000u
+#define GSP_PHYS_RM_UNLOAD_TIMEOUT_MS 10000
+#define GSP_PHYS_RM_UNLOAD_POLL_MS 10
/*
* CPU-sequencer execution (rmgspseq.h and kernel_gsp.c in the reference).
@@ -269,6 +310,21 @@ struct gsp_phys_seq_request {
__le32 reg_save_area[GSP_PHYS_SEQ_REG_SAVE_SIZE];
};
+/*
+ * rpc_unloading_guest_driver_v1F_07 (g_rpc-structures.h): two NvBool (NvU8)
+ * flags then the level, 8 bytes with the natural 2-byte tail padding. A
+ * normal unload sends all zeroes (kgspUnloadRm_IMPL with
+ * KGSP_UNLOAD_MODE_NORMAL); newLevel 0 / NV_FALSE / NV_FALSE is the same
+ * payload the distribution driver's gpu.c unload path sends for
+ * GPU_STATE_FLAGS_FAST_UNLOAD.
+ */
+struct gsp_phys_rpc_unloading_guest_driver {
+ u8 b_in_pm_transition;
+ u8 b_gc6_entering;
+ u8 pad[2];
+ __le32 new_level;
+};
+
/* GSP_SEQ_BUF_OPCODE (rmgspseq.h): opcode dword then its payload dwords. */
enum gsp_phys_seq_opcode {
GSP_PHYS_SEQ_OP_REG_WRITE = 0,
@@ -309,6 +365,8 @@ static struct gsp_phys_boot_stage {
bool ran; /* stage 3d reached the device sequence */
bool riscv_active;
bool dryrun;
+ bool rm_unload_submitted; /* UNLOADING_GUEST_DRIVER was published */
+ u32 tx_seq_num; /* next command-queue element seqNum */
struct gsp_phys_boot_chunk fmc;
struct gsp_phys_radix3 radix3;
struct gsp_phys_boot_chunk signature;
@@ -1645,6 +1703,369 @@ void gsp_phys_boot_rmwait(struct gsp_phys_boot *boot, void __iomem *bar0,
}
}
+/* ============================================================
+ * Command-queue TX: host -> GSP-RM msgq producer
+ * ============================================================ */
+
+/*
+ * XOR-fold checksum (message_queue_priv.h _checkSum32): XOR every 8-byte
+ * little-endian word of the record, fold the 64-bit result to 32 bits. The
+ * producer zeroes the checkSum field first and stores this value, so a
+ * consumer XORing the whole record (checkSum included) gets 0; the caller
+ * guarantees len is 8-byte aligned. GSP-RM's receive path rejects a record
+ * whose folded value is nonzero.
+ */
+static u32 gsp_phys_msgq_checksum(const u8 *data, u32 len)
+{
+ u64 sum = 0;
+ u32 off;
+
+ for (off = 0; off < len; off += 8)
+ sum ^= get_unaligned_le64(data + off);
+ return upper_32_bits(sum) ^ lower_32_bits(sum);
+}
+
+/*
+ * Submit one command-queue record (single element). Validates the staged
+ * command-queue TX header (the one GSP-RM linked against), logs it together
+ * with the GSP-RM command readPtr and the status TX header, builds the
+ * element in a zeroed staging buffer, copies it into the next slot and
+ * publishes writePtr under dma_wmb() before ringing the queue-head doorbell.
+ * Bounded: one slot, payload <= one element.
+ *
+ * Pointer placement note (msgq.c MSGQ_FLAGS_SWAP_RX, both sides flags=1):
+ * the GSP-RM read pointer for the command queue lives at
+ * status + (status tx header rxHdrOff), exactly where the emulated GSP-RM
+ * publishes its cmd_read (gsp_rm_poll_commands), not at cmd + rxHdrOff.
+ */
+int gsp_phys_queue_tx_submit(void __iomem *bar0, u32 function,
+ const void *payload, u32 payload_len)
+{
+ const struct gsp_phys_boot_chunk *q = &boot_stage.queue;
+ const struct gsp_phys_msgq_tx_header *cmd_hdr;
+ const struct gsp_phys_msgq_tx_header *stat_hdr;
+ struct gsp_phys_msgq_element *e;
+ __le32 *write_ptr;
+ u8 *elem, *slot_va;
+ u32 version, size, msg_size, msg_count, wp, entry_off, rx_hdr_off;
+ u32 stat_version, stat_msg_size, stat_msg_count, stat_entry_off;
+ u32 stat_rx_hdr_off, stat_wp, cmd_read = 0;
+ u32 msg_len, aligned_len, elem_count, checksum, new_wp, slot;
+ bool stat_linked;
+
+ if (!bar0 || !boot_stage.primed || !q->pages) {
+ pr_err("gsp-phys-boot: TX refused: command-queue staging is not primed\n");
+ return -EINVAL;
+ }
+ if (payload_len > GSP_PHYS_MSG_PAYLOAD_MAX) {
+ pr_err("gsp-phys-boot: TX refused: payload %u exceeds %u bytes\n",
+ payload_len, GSP_PHYS_MSG_PAYLOAD_MAX);
+ return -EINVAL;
+ }
+
+ msg_len = GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF + payload_len;
+ aligned_len = (msg_len + 7u) & ~7u;
+ elem_count = DIV_ROUND_UP(msg_len, GSP_PHYS_MSG_SIZE);
+ if (elem_count != 1) {
+ pr_err("gsp-phys-boot: TX refused: message spans %u elements\n",
+ elem_count);
+ return -EINVAL;
+ }
+
+ cmd_hdr = gsp_phys_chunk_page_va(q,
+ GSP_PHYS_QUEUE_CMD_OFF / GSP_PHYS_PAGE_SIZE);
+ stat_hdr = gsp_phys_chunk_page_va(q,
+ GSP_PHYS_QUEUE_STAT_OFF / GSP_PHYS_PAGE_SIZE);
+ version = le32_to_cpu(READ_ONCE(cmd_hdr->version));
+ size = le32_to_cpu(READ_ONCE(cmd_hdr->size));
+ msg_size = le32_to_cpu(READ_ONCE(cmd_hdr->msg_size));
+ msg_count = le32_to_cpu(READ_ONCE(cmd_hdr->msg_count));
+ wp = le32_to_cpu(READ_ONCE(cmd_hdr->write_ptr));
+ rx_hdr_off = le32_to_cpu(READ_ONCE(cmd_hdr->rx_hdr_off));
+ entry_off = le32_to_cpu(READ_ONCE(cmd_hdr->entry_off));
+
+ if (version != 0 || size != GSP_PHYS_QUEUE_CMD_SIZE ||
+ msg_size != GSP_PHYS_MSG_SIZE ||
+ msg_count != (GSP_PHYS_QUEUE_CMD_SIZE - GSP_PHYS_PAGE_SIZE) /
+ GSP_PHYS_MSG_SIZE ||
+ entry_off != GSP_PHYS_PAGE_SIZE ||
+ rx_hdr_off < GSP_PHYS_MSGQ_TX_WRITEPTR_OFF + 4 ||
+ rx_hdr_off + 4 > entry_off || wp >= msg_count) {
+ pr_err("gsp-phys-boot: TX refused: command-queue TX header mismatch (version=%u size=0x%x msgSize=%u msgCount=%u entryOff=0x%x rxHdrOff=0x%x writePtr=%u); GSP-RM did not link the staged header\n",
+ version, size, msg_size, msg_count, entry_off, rx_hdr_off,
+ wp);
+ return -EIO;
+ }
+
+ /*
+ * Status TX header (GSP-RM's producer header) and the GSP-RM command
+ * readPtr at status+rxHdrOff (MSGQ_FLAGS_SWAP_RX). Best-effort: the
+ * reference allows TX before its RX link, so a not-yet-linked status
+ * header is logged as a warning, not a refusal.
+ */
+ stat_version = le32_to_cpu(READ_ONCE(stat_hdr->version));
+ stat_msg_size = le32_to_cpu(READ_ONCE(stat_hdr->msg_size));
+ stat_msg_count = le32_to_cpu(READ_ONCE(stat_hdr->msg_count));
+ stat_entry_off = le32_to_cpu(READ_ONCE(stat_hdr->entry_off));
+ stat_rx_hdr_off = le32_to_cpu(READ_ONCE(stat_hdr->rx_hdr_off));
+ stat_wp = le32_to_cpu(READ_ONCE(stat_hdr->write_ptr));
+ stat_linked = stat_version == 0 &&
+ stat_msg_size == GSP_PHYS_MSG_SIZE &&
+ stat_msg_count != 0 &&
+ stat_entry_off + (u64)stat_msg_count * stat_msg_size <=
+ GSP_PHYS_QUEUE_STAT_SIZE &&
+ stat_rx_hdr_off + 4 <= stat_entry_off;
+ if (stat_linked)
+ cmd_read = le32_to_cpu(READ_ONCE(*(const __le32 *)
+ ((const u8 *)stat_hdr + stat_rx_hdr_off)));
+
+ pr_info("gsp-phys-boot: TX preflight: cmd hdr v%u size=0x%x msgSize=%u msgCount=%u entryOff=0x%x rxHdrOff=0x%x writePtr(slot)=%u; GSP-RM cmd readPtr=%u (status+0x%x); status hdr v%u writePtr=%u msgCount=%u %s\n",
+ version, size, msg_size, msg_count, entry_off, rx_hdr_off, wp,
+ cmd_read, stat_rx_hdr_off, stat_version, stat_wp,
+ stat_msg_count,
+ stat_linked ? "(status TX linked)" :
+ "(WARNING: status TX header not linked)");
+
+ elem = kmalloc(GSP_PHYS_MSG_SIZE, GFP_KERNEL);
+ if (!elem)
+ return -ENOMEM;
+ memset(elem, 0, GSP_PHYS_MSG_SIZE);
+
+ e = (struct gsp_phys_msgq_element *)elem;
+ e->seq_num = cpu_to_le32(boot_stage.tx_seq_num);
+ e->elem_count = cpu_to_le32(elem_count);
+ e->rpc_header_version = cpu_to_le32(GSP_PHYS_RPC_HEADER_VERSION);
+ e->rpc_signature = cpu_to_le32(GSP_PHYS_RPC_SIGNATURE);
+ e->rpc_length = cpu_to_le32(GSP_PHYS_MSG_RPC_HEADER_SIZE + payload_len);
+ e->rpc_function = cpu_to_le32(function);
+ e->rpc_result = cpu_to_le32(GSP_PHYS_RPC_RESULT_PENDING);
+ e->rpc_result_private = cpu_to_le32(GSP_PHYS_RPC_RESULT_PENDING);
+ e->rpc_sequence = cpu_to_le32(0);
+ e->rpc_union = cpu_to_le32(0);
+ if (payload_len)
+ memcpy(elem + GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF, payload,
+ payload_len);
+ checksum = gsp_phys_msgq_checksum(elem, aligned_len);
+ e->check_sum = cpu_to_le32(checksum);
+
+ slot = wp;
+ /*
+ * entry_off and msg_size are validated page-sized, so the element is
+ * exactly one chunk page: index it through the chunk page helper (the
+ * per-page fallback layout is not virtually contiguous).
+ */
+ slot_va = (u8 *)gsp_phys_chunk_page_va(q,
+ (GSP_PHYS_QUEUE_CMD_OFF + entry_off) /
+ GSP_PHYS_PAGE_SIZE + slot);
+ memcpy(slot_va, elem, msg_size);
+ kfree(elem);
+
+ /* Entries first, then writePtr; dma_wmb() orders both. */
+ dma_wmb();
+ new_wp = slot + 1;
+ if (new_wp >= msg_count)
+ new_wp -= msg_count;
+ write_ptr = (__le32 *)cmd_hdr + GSP_PHYS_MSGQ_TX_WRITEPTR_OFF / 4;
+ WRITE_ONCE(*write_ptr, cpu_to_le32(new_wp));
+ dma_wmb();
+
+ /*
+ * Ring the queue-head doorbell the way the reference producer does
+ * (kgspSetCmdQueueHead_TU102 writes 0 to NV_PGSP_QUEUE_HEAD(qIdx)).
+ */
+ writel(0, bar0 + GSP_PHYS_QUEUE_HEAD_REG);
+
+ pr_info("gsp-phys-boot: TX submitted: function=%u (0x%x) slot=%u length=%u (rpc.length=%u elemCount=%u seqNum=%u checksum=0x%08x) writePtr slot %u -> %u; doorbell QUEUE_HEAD(%u)@0x%x<-0\n",
+ function, function, slot, msg_len,
+ GSP_PHYS_MSG_RPC_HEADER_SIZE + payload_len, elem_count,
+ boot_stage.tx_seq_num, checksum, wp, new_wp,
+ GSP_PHYS_QUEUE_HEAD_IDX, GSP_PHYS_QUEUE_HEAD_REG);
+ boot_stage.tx_seq_num++;
+ return 0;
+}
+
+static DEFINE_MUTEX(gsp_phys_rm_unload_lock);
+
+int gsp_phys_boot_rm_unload(void __iomem *bar0)
+{
+ struct gsp_phys_rpc_unloading_guest_driver params;
+ struct gsp_phys_scratch14 s14;
+ unsigned long start, deadline;
+ u32 mailbox;
+ bool all_done;
+ int ret;
+
+ if (!bar0)
+ return -EINVAL;
+
+ mutex_lock(&gsp_phys_rm_unload_lock);
+ /*
+ * Quiet no-op when the boot stages did not run: gsp_phys_gpu_release()
+ * calls this unconditionally on a healthy board.
+ */
+ if (!boot_stage.primed || !boot_stage.ran) {
+ pr_debug("gsp-phys-boot: UNLOADING_GUEST_DRIVER skipped: stage 3d did not run (primed=%u ran=%u)\n",
+ boot_stage.primed, boot_stage.ran);
+ ret = -EINVAL;
+ goto out;
+ }
+ if (boot_stage.rm_unload_submitted) {
+ pr_info("gsp-phys-boot: UNLOADING_GUEST_DRIVER already submitted for this boot; not sending it twice\n");
+ ret = -EALREADY;
+ goto out;
+ }
+
+ /* Normal unload: bInPMTransition=0, bGc6Entering=0, newLevel=0. */
+ memset(&params, 0, sizeof(params));
+ gsp_phys_scratch14_log(bar0, "pre-unload-rpc");
+ pr_info("gsp-phys-boot: sending UNLOADING_GUEST_DRIVER (function %u): bInPMTransition=0 bGc6Entering=0 newLevel=0\n",
+ GSP_PHYS_RPC_FUNC_UNLOADING_GUEST_DRIVER);
+ ret = gsp_phys_queue_tx_submit(bar0,
+ GSP_PHYS_RPC_FUNC_UNLOADING_GUEST_DRIVER,
+ &params, sizeof(params));
+ if (ret) {
+ pr_err("gsp-phys-boot: UNLOADING_GUEST_DRIVER submit failed: %d; GSP-RM stays up, WPR2 teardown keeps its existing fail-safe\n",
+ ret);
+ goto out;
+ }
+ boot_stage.rm_unload_submitted = true;
+
+ /*
+ * kgspWaitForProcessorSuspend_TU102: the GSP falcon MAILBOX0 becomes
+ * LIBOS_INTERRUPT_PROCESSOR_SUSPENDED (0x80000000) once GSP-RM has
+ * shut down. Bounded to 10 s here.
+ */
+ start = jiffies;
+ deadline = start + msecs_to_jiffies(GSP_PHYS_RM_UNLOAD_TIMEOUT_MS);
+ mailbox = readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX0);
+ while (mailbox != GSP_PHYS_LIBOS_PROCESSOR_SUSPENDED) {
+ if (time_after_eq(jiffies, deadline)) {
+ gsp_phys_scratch14_log(bar0,
+ "unload-rpc-suspend-timeout");
+ pr_err("gsp-phys-boot: GSP-RM processor suspend not observed within %u ms (GSP MAILBOX0=0x%08x, want 0x%08x); the unload RPC may have been dropped (checksum/seqNum/link) or GSP-RM is stuck\n",
+ GSP_PHYS_RM_UNLOAD_TIMEOUT_MS, mailbox,
+ GSP_PHYS_LIBOS_PROCESSOR_SUSPENDED);
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ msleep(GSP_PHYS_RM_UNLOAD_POLL_MS);
+ mailbox = readl(bar0 + GSP_PHYS_GSP_BASE +
+ GSP_PHYS_FALCON_MAILBOX0);
+ }
+
+ gsp_phys_fwsec_scratch14_read(bar0, &s14);
+ all_done = s14.booter_load && s14.ahesasc_booter_reload &&
+ s14.booter_reload_acr_unload && s14.acr_unload_booter_unload;
+ gsp_phys_scratch14_log(bar0, "post-unload-rpc");
+ pr_info("gsp-phys-boot: UNLOADING_GUEST_DRIVER done: GSP-RM processor suspended (GSP MAILBOX0=0x%08x) after %u ms; scratch14 handoffs 24|25|26|27 all DONE=%u\n",
+ mailbox, jiffies_to_msecs(jiffies - start), all_done);
+ if (!all_done) {
+ pr_err("gsp-phys-boot: GSP-RM suspended but scratch14 handoff bits are not all DONE; the booter-unload gate will refuse (WPR2 stays up, fail-safe keeps the hand-back)\n");
+ ret = -EIO;
+ goto out;
+ }
+ ret = 0;
+
+out:
+ mutex_unlock(&gsp_phys_rm_unload_lock);
+ return ret;
+}
+
+/* ============================================================
+ * Debugfs trigger (test-enabling; inert unless a live boot ran)
+ * ============================================================ */
+
+static struct dentry *gsp_phys_debugfs_root;
+
+static ssize_t gsp_phys_unload_rpc_write(struct file *file,
+ const char __user *ubuf, size_t len,
+ loff_t *ppos)
+{
+ char buf[8];
+ unsigned long value;
+ void __iomem *bar0;
+ int ret;
+
+ if (len == 0 || len >= sizeof(buf))
+ return -EINVAL;
+ if (copy_from_user(buf, ubuf, len))
+ return -EFAULT;
+ buf[len] = '\0';
+
+ if (kstrtoul(strim(buf), 0, &value))
+ return -EINVAL;
+ if (value != 1) {
+ pr_info("gsp-phys-boot: gsp-phys-unload-rpc: write ignored (write \"1\" to trigger)\n");
+ return len;
+ }
+ if (!gsp_phys_boot_enabled()) {
+ pr_info("gsp-phys-boot: gsp-phys-unload-rpc: no-op: gsp_phys_boot is not enabled\n");
+ return len;
+ }
+ if (!gsp_phys_gpu_claimed()) {
+ pr_info("gsp-phys-boot: gsp-phys-unload-rpc: no-op: the physical GPU is not claimed\n");
+ return len;
+ }
+ if (!boot_stage.ran) {
+ pr_info("gsp-phys-boot: gsp-phys-unload-rpc: no-op: stage 3d did not run (primed=%u kicked=%u dryrun=%u)\n",
+ boot_stage.primed, boot_stage.kicked,
+ boot_stage.dryrun);
+ return len;
+ }
+ bar0 = gsp_phys_gpu_bar0();
+ if (!bar0) {
+ pr_warn("gsp-phys-boot: gsp-phys-unload-rpc: no-op: BAR0 is not mapped\n");
+ return len;
+ }
+
+ ret = gsp_phys_boot_rm_unload(bar0);
+ pr_info("gsp-phys-boot: gsp-phys-unload-rpc: UNLOADING_GUEST_DRIVER attempt returned %d (WPR2 untouched; run release/rmmod to tear down WPR2)\n",
+ ret);
+ return len;
+}
+
+static const struct file_operations gsp_phys_unload_rpc_fops = {
+ .open = simple_open,
+ .write = gsp_phys_unload_rpc_write,
+ .llseek = no_llseek,
+};
+
+int gsp_phys_boot_debugfs_init(void)
+{
+ struct dentry *file;
+
+ gsp_phys_debugfs_root = debugfs_create_dir("open-iov-vfio", NULL);
+ if (IS_ERR_OR_NULL(gsp_phys_debugfs_root)) {
+ long err = IS_ERR(gsp_phys_debugfs_root) ?
+ PTR_ERR(gsp_phys_debugfs_root) : -ENODEV;
+
+ gsp_phys_debugfs_root = NULL;
+ pr_warn("gsp-phys-boot: debugfs dir open-iov-vfio creation failed: %ld\n",
+ err);
+ return (int)err;
+ }
+ file = debugfs_create_file("gsp-phys-unload-rpc", 0200,
+ gsp_phys_debugfs_root, NULL,
+ &gsp_phys_unload_rpc_fops);
+ if (IS_ERR_OR_NULL(file)) {
+ long err = IS_ERR(file) ? PTR_ERR(file) : -ENODEV;
+
+ debugfs_remove_recursive(gsp_phys_debugfs_root);
+ gsp_phys_debugfs_root = NULL;
+ pr_warn("gsp-phys-boot: debugfs file gsp-phys-unload-rpc creation failed: %ld\n",
+ err);
+ return (int)err;
+ }
+ pr_info("gsp-phys-boot: debugfs trigger /sys/kernel/debug/open-iov-vfio/gsp-phys-unload-rpc ready (write \"1\" on a booted claim; inert unless gsp_phys_boot=1)\n");
+ return 0;
+}
+
+void gsp_phys_boot_debugfs_fini(void)
+{
+ debugfs_remove_recursive(gsp_phys_debugfs_root);
+ gsp_phys_debugfs_root = NULL;
+}
+
void gsp_phys_boot_quiesce(void __iomem *bar0)
{
if (!bar0)
@@ -1738,6 +2159,18 @@ static_assert(offsetof(struct gsp_phys_msgq_element, rpc_sequence) == 72,
"rpc.sequence must live at element offset 72 (reference ABI)");
static_assert(offsetof(struct gsp_phys_msgq_element, rpc_union) == 76,
"the rpc union word must live at element offset 76 (reference ABI)");
+static_assert(sizeof(struct gsp_phys_rpc_unloading_guest_driver) == 8,
+ "rpc_unloading_guest_driver_v1F_07 must be 8 bytes (two NvBool + u32 with 2-byte tail padding)");
+static_assert(offsetof(struct gsp_phys_rpc_unloading_guest_driver,
+ b_gc6_entering) == 1,
+ "bGc6Entering must follow bInPMTransition (reference NvBool=NvU8)");
+static_assert(offsetof(struct gsp_phys_rpc_unloading_guest_driver,
+ new_level) == 4,
+ "newLevel must live at payload offset 4 (reference ABI)");
+static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF +
+ sizeof(struct gsp_phys_rpc_unloading_guest_driver) <=
+ GSP_PHYS_MSG_SIZE,
+ "the unload RPC payload must fit one queue element");
static_assert(sizeof(struct gsp_phys_seq_request) == GSP_PHYS_SEQ_REQUEST_SIZE,
"rpc_run_cpu_sequencer_v17_00 fixed header must be 40 bytes (reference ABI)");
static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF ==