diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-01 10:13:04 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-01 10:13:04 -0400 |
| commit | 582696da869374455c3bcdaf63cbcd28352da14e (patch) | |
| tree | ce0832a6c1caf050d3fdb4706d13a738e3fbbdcc | |
| parent | b589a88ff559833a80ff60c86d1e2c823bd66839 (diff) | |
| download | open-iov-582696da869374455c3bcdaf63cbcd28352da14e.tar.gz open-iov-582696da869374455c3bcdaf63cbcd28352da14e.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.
| -rw-r--r-- | docs/03-architecture/08-physical-gpu-integration.md | 38 | ||||
| -rw-r--r-- | kernel/gsp_phys_booter.c | 433 | ||||
| -rw-r--r-- | kernel/gsp_phys_booter.h | 40 | ||||
| -rw-r--r-- | kernel/gsp_phys_gpu.c | 9 | ||||
| -rw-r--r-- | kernel/main.c | 8 |
5 files changed, 519 insertions, 9 deletions
diff --git a/docs/03-architecture/08-physical-gpu-integration.md b/docs/03-architecture/08-physical-gpu-integration.md index 0c18e84..f4dfa68 100644 --- a/docs/03-architecture/08-physical-gpu-integration.md +++ b/docs/03-architecture/08-physical-gpu-integration.md @@ -202,12 +202,14 @@ event before it raises `GSP_INIT_DONE` (0x1001). **A plain release is not yet possible:** the Booter Unload gate requires all four secure scratch14 handoff bits (24–27); a host-driven boot sets only bit 24, and bit 27 is set by the server's shutdown -path (host RPC `UNLOADING_GUEST_DRIVER`), which is not implemented, while the +path (host RPC `UNLOADING_GUEST_DRIVER`; implemented in the module as the +rung-4b command-queue TX + shutdown path and pending its first live run), +while the host-side seed/unlock alternative is fuse-locked on this part. Until that RPC -lands, a green session is left claimed with the harness `--keep` flag and -returning the GPU to the distribution driver takes a power cycle. The -supervised harness modes are `--boot` (the only write mode: full boot -lifecycle), `--boot-dryrun` (zero-write staging rehearsal) and `--keep` +has run in a live session, a green session is left claimed with the harness +`--keep` flag and returning the GPU to the distribution driver takes a power +cycle. The supervised harness modes are `--boot` (the only write mode: full +boot lifecycle), `--boot-dryrun` (zero-write staging rehearsal) and `--keep` (leave a verified claimed session running); `--fwsec` is retired. ### 4.4 Host-side resource-manager client @@ -321,10 +323,28 @@ per-context scheduling. Tenant policy and fairness are a later layer on top. executing the sequencer buffer carried in that event. Clean release is not yet possible: the Booter Unload gate requires all four secure scratch14 handoff bits (24–27), a host-driven boot sets only bit 24, bit 27 is set by - the server's shutdown path (host RPC `UNLOADING_GUEST_DRIVER`, not - implemented), and host-side seed/unlock is fuse-locked on this part. The - live session is therefore left claimed (`--keep`) and a fresh cycle needs a - power cycle. See §4.3. + the server's shutdown path (host RPC `UNLOADING_GUEST_DRIVER`), and + host-side seed/unlock is fuse-locked on this part. The live session is + therefore left claimed (`--keep`) and a fresh cycle needs a power cycle. + See §4.3. +* **Update (2026-10-01) — rung 4b implemented (build-only):** the module now + produces real command-queue records to GSP-RM and attempts a clean shutdown + on release. The producer writes the 48-byte transport element header plus + the 32-byte rpc header (signature `0x43505256`, header version `0x03000000`, + `rpc.length = 32 + payload`), an XOR-fold 64→32 checksum over the + zero-padded record that makes a consumer's fold read zero, an incrementing + sequence number, and publishes the wrap-around `writePtr` under a DMA write + barrier before ringing the queue-head doorbell at `0x110c00`. The shutdown + RPC is `UNLOADING_GUEST_DRIVER` (function 47) with `bInPMTransition=0`, + `bGc6Entering=0`, `newLevel=0`; the wait criterion is the GSP falcon + mailbox0 reading `0x80000000` (processor-suspended), bounded at 10 s. On a + release the RPC runs once (only when the full boot stage ran) before the + existing quiesce/FWSEC teardown; without a full boot the fail-safe + behaviour is unchanged. A write-only debugfs trigger + (`open-iov-vfio/gsp-phys-unload-rpc`, write "1") lets a live claimed + session test the shutdown without touching WPR2. All of it is inert unless + the full boot stage ran; the code has not yet run live, and this bullet + must be updated with the live outcome. ## 9. Interaction sequence (end state) 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(¶ms, 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, + ¶ms, 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 == diff --git a/kernel/gsp_phys_booter.h b/kernel/gsp_phys_booter.h index d76439d..a669bf3 100644 --- a/kernel/gsp_phys_booter.h +++ b/kernel/gsp_phys_booter.h @@ -71,6 +71,11 @@ void gsp_phys_boot_rmwait(struct gsp_phys_boot *boot, void __iomem *bar0, /* * Release sequence, in this order (gsp_phys_gpu_release): + * 0. gsp_phys_boot_rm_unload(): if stage 3d ran and the + * UNLOADING_GUEST_DRIVER RPC has not been submitted yet, submit it once + * (bounded) and wait for the GSP-RM processor suspend so the scratch14 + * handoff bit the Booter Unload gate needs can be set; a no-op when + * stage 3d did not run or the RPC was already sent in this boot; * 1. gsp_phys_boot_quiesce(): if a Booter Load kick was issued, reset the * GSP falcon (stops RISC-V/GSP-RM) BEFORE the FWSEC teardown runs; no * device access when bar0 is NULL (unresponsive-board abort); @@ -82,4 +87,39 @@ void gsp_phys_boot_rmwait(struct gsp_phys_boot *boot, void __iomem *bar0, void gsp_phys_boot_quiesce(void __iomem *bar0); void gsp_phys_boot_staging_free(struct pci_dev *pdev); +/* + * Command-queue TX: build a single-element msgq record with the 570.144 + * element/rpc header layout, compute the XOR-fold checksum, copy it into the + * next command-queue slot, publish the new writePtr with a DMA barrier and + * ring the GSP queue-head doorbell. Requires the stage-3d command-queue + * staging; bounded and refuses (with a log) when the TX header does not match + * what was staged. Does not touch WPR2 or the falcons. + */ +int gsp_phys_queue_tx_submit(void __iomem *bar0, u32 function, + const void *payload, u32 payload_len); + +/* + * Send NV_VGPU_MSG_FUNCTION_UNLOADING_GUEST_DRIVER (function 47) with a normal + * unload payload (bInPMTransition=0, bGc6Entering=0, newLevel=0), then wait + * (bounded, 10 s) for the GSP-RM processor to suspend the way + * kgspWaitForProcessorSuspend_TU102 does: GSP falcon MAILBOX0 == 0x80000000. + * Sends at most once per boot (tracked across release and the debugfs + * trigger). Returns 0 only when the suspend was observed and all four + * scratch14 handoff bits (24..27) read DONE, -EALREADY if already sent, + * -EINVAL when stage 3d did not run, -ETIMEDOUT on the suspend bound and + * -EIO when the handoff bits are not all DONE after the suspend. Never writes + * WPR2. + */ +int gsp_phys_boot_rm_unload(void __iomem *bar0); + +/* + * Module-owned debugfs trigger (dir "open-iov-vfio", file + * "gsp-phys-unload-rpc", write-only). Writing "1" runs + * gsp_phys_boot_rm_unload() on a live claim; every other value or state + * (parameter off, GPU unclaimed, stage 3d not run, already submitted) is a + * logged no-op. Registered by nv_vgpu_init() and removed by nv_vgpu_exit(). + */ +int gsp_phys_boot_debugfs_init(void); +void gsp_phys_boot_debugfs_fini(void); + #endif /* GSP_PHYS_BOOTER_H */ diff --git a/kernel/gsp_phys_gpu.c b/kernel/gsp_phys_gpu.c index 79c79bf..88d13f2 100644 --- a/kernel/gsp_phys_gpu.c +++ b/kernel/gsp_phys_gpu.c @@ -898,6 +898,15 @@ void gsp_phys_gpu_release(void) wpr2_ret = -EIO; gsp_phys_boot_staging_free(phys.pdev); } else { + /* + * Clean GSP-RM shutdown: after a stage-3d boot, send + * UNLOADING_GUEST_DRIVER once (no-op otherwise) so + * GSP-RM suspends and sets the scratch14 handoff bits + * the Booter Unload gate needs; then quiesce the GSP + * falcon and run the existing FWSEC teardown. A failed + * RPC leaves the existing fail-safe in charge. + */ + gsp_phys_boot_rm_unload(phys.bar0); gsp_phys_boot_quiesce(phys.bar0); wpr2_ret = gsp_phys_fwsec_release(&phys.boot, phys.pdev, diff --git a/kernel/main.c b/kernel/main.c index f86dc98..30156ab 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -32,6 +32,7 @@ #include "nv-vgpu-vfio-interface.h" #include "gsp_phys_gpu.h" +#include "gsp_phys_booter.h" #define NV_VERSION_STRING "550.163.01" @@ -1453,6 +1454,12 @@ static int __init nv_vgpu_init(void) ret); } + /* Test-enabling trigger for the GSP-RM unload RPC; inert unless a + * stage-3d boot ran on a live claim (gsp_phys_boot=1). Removed on + * module exit. Creation failure is non-fatal. */ + if (gsp_phys_boot_debugfs_init()) + pr_warn("gsp-resman-vfio: gsp-phys-unload-rpc debugfs trigger unavailable\n"); + pr_info("NVIDIA vGPU VFIO driver loaded (version %s)\n", NVIDIA_VGPU_VFIO_VERSION); /* Start the emulated-GPU progress service: it discovers the guest's * GPU-VA -> guest-PA map and applies copy-engine semaphore releases so @@ -1488,6 +1495,7 @@ static void __exit nv_vgpu_exit(void) pr_info("gsp-resman-vfio: nv_vgpu_exit: clearing VFIO ops to break circular dependency\n"); g_vgpu_vfio_ops = NULL; + gsp_phys_boot_debugfs_fini(); gsp_phys_gpu_release(); if (nv_vgpu_driver.device_count > 0) { |
