// SPDX-License-Identifier: GPL-2.0 /* * Real-GSP boot stages 3d/3e: Booter Load on SEC2 and the GSP-RM init poll. * * After FWSEC has carved WPR2 (stage 3c), the driver loads and starts the * GSP-RM from host sysmem in three steps, mirrored here: * 1. the GSP falcon is reset into RISC-V and the libos init-argument page * address is programmed into its mailboxes; * 2. the Booter Load ucode runs on SEC2 with the WPR metadata page in its * mailboxes; the ucode, not the host, reads and verifies the FMC image, * the GSP-RM ELF (through the radix3 page tables) and the signature from * host DMA memory, then starts the RISC-V core; * 3. FALCON_OS is programmed with the FMC descriptor appVersion and the * RISC-V active bit is checked. * Stage 3e then observes the status message queue for the GSP-RM * "init done" event (function 0x1001) as the first RM liveness signal. Before * that event, GSP-RM asks the host to execute an inline CPU-sequencer command * buffer (function 0x1002, NV_VGPU_MSG_EVENT_GSP_RUN_CPU_SEQUENCER); stage 3e * executes those commands against BAR0 (register writes/modifies/polls, delays, * register stores and the GSP falcon CORE_* operations) and then keeps * polling. * * All host buffers are DMA-coherent on the claimed device and are freed on * release or on the first staging error. Disabled by default: the parameters * gsp_phys_boot / gsp_phys_boot_dryrun are the only entry points, and the dry * run performs no BAR0 writes and no falcon resets. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" #include "gsp_phys_registry_img.h" static bool gsp_phys_boot_param; module_param_named(gsp_phys_boot, gsp_phys_boot_param, bool, 0644); MODULE_PARM_DESC(gsp_phys_boot, "Run the full real-GSP boot on the claimed GPU: FWSEC then Booter Load, RISC-V start, FALCON_OS and the GSP-RM init poll (default 0; device writes; implies gsp_phys_layout)"); static bool gsp_phys_boot_dryrun_param; module_param_named(gsp_phys_boot_dryrun, gsp_phys_boot_dryrun_param, bool, 0644); MODULE_PARM_DESC(gsp_phys_boot_dryrun, "Prepare the stage-3d host-memory staging (radix3, queues, args) on the claimed GPU without any BAR0 write or falcon reset (default 0; read-only)"); static bool gsp_phys_nocat_param; module_param_named(gsp_phys_nocat, gsp_phys_nocat_param, bool, 0644); MODULE_PARM_DESC(gsp_phys_nocat, "Log every GSP-RM NOCAT journal record (status-queue function 0x1020) individually during the stage-3e RM-init poll (default 0; the unique-record summary is always accumulated and dumped when the poll ends)"); bool gsp_phys_boot_enabled(void) { return gsp_phys_boot_param; } bool gsp_phys_boot_dryrun_enabled(void) { return gsp_phys_boot_dryrun_param; } bool gsp_phys_boot_requested(void) { return gsp_phys_boot_param || gsp_phys_boot_dryrun_param; } /* Host sysmem staging sizes (reference kgspAllocBootArgs/signature/queue). */ #define GSP_PHYS_FMC_SIZE 0x1000 #define GSP_PHYS_SIGNATURE_SIZE 0x1000 #define GSP_PHYS_PAGE_SIZE 0x1000 /* Radix3 for the GSP-RM ELF: 4 KB pages, 512 entries per node. */ #define GSP_PHYS_RADIX3_PAGE_SIZE 0x1000 #define GSP_PHYS_RADIX3_PAGE_LOG2 12 #define GSP_PHYS_RADIX3_ENTRIES_LOG2 9 #define GSP_PHYS_RADIX3_ENTRIES_PER_NODE (1u << GSP_PHYS_RADIX3_ENTRIES_LOG2) #define GSP_PHYS_RADIX3_L2_PAGES 14 /* ceil(7057 / 512) */ #define GSP_PHYS_RADIX3_TABLE_PAGES 16 /* L0 + L1 + 14 L2 */ #define GSP_PHYS_RADIX3_DATA_OFFSET 0x10000 /* tables first, then data */ #define GSP_PHYS_RADIX3_CHUNK_PAGES 256 /* 1 MB chunks, per-page fallback */ #define GSP_PHYS_RADIX3_MAX_CHUNKS 32 /* Message queue shared buffer (GspMsgQueuesInit layout). */ #define GSP_PHYS_QUEUE_PT_SIZE 0x1000 #define GSP_PHYS_QUEUE_CMD_SIZE 0x40000 #define GSP_PHYS_QUEUE_STAT_SIZE 0x40000 #define GSP_PHYS_QUEUE_TOTAL_SIZE 0x81000 #define GSP_PHYS_QUEUE_PTE_COUNT 129 #define GSP_PHYS_QUEUE_CMD_OFF 0x1000 #define GSP_PHYS_QUEUE_STAT_OFF 0x41000 #define GSP_PHYS_MSG_SIZE 4096 #define GSP_PHYS_MSG_HDR_ALIGN 4 /* 2^4 = 16 bytes */ #define GSP_PHYS_MSG_ENTRY_ALIGN 12 /* 2^12 = 4096 bytes */ #define GSP_PHYS_MSG_FLAGS_SWAP_RX 1 /* * libos task log buffers (LOGINIT and LOGRM). The reference allocates one * page-aligned contiguous sysmem region per log task, 64 KB in release builds * for LOGINIT/LOGRM on Turing (`_kgspInitLibosLoggingStructures` release * sizes). The libos ABI uses word 0 of the region as the producer put pointer * and stores the per-page GPU DMA addresses from byte 8; the libos init * argument publishes the page-0 DMA address (the first PA-table entry), not * the region base. */ #define GSP_PHYS_LOG_BUFFER_SIZE 0x10000 #define GSP_PHYS_LOG_BUFFER_PAGES (GSP_PHYS_LOG_BUFFER_SIZE / GSP_PHYS_PAGE_SIZE) #define GSP_PHYS_LOG_PA_TABLE_OFF 8 /* after the u64 put pointer */ /* Status queue header/element offsets (msgq_priv.h, message_queue_priv.h). */ #define GSP_PHYS_MSGQ_TX_WRITEPTR_OFF 0x10 #define GSP_PHYS_MSGQ_TX_RXHDROFF_OFF 0x18 #define GSP_PHYS_MSGQ_TX_ENTRYOFF_OFF 0x1c #define GSP_PHYS_MSG_ELEMENT_FUNCTION_OFF 60 #define GSP_PHYS_MSG_ELEMENT_RPC_RESULT_OFF 64 #define GSP_PHYS_MSG_FUNC_GSP_INIT_DONE 0x1001 #define GSP_PHYS_MSG_FUNC_RUN_CPU_SEQUENCER 0x1002 #define GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD 0x1020 /* * Status-queue element layout. The 48-byte element header * (authTag/aad/checkSum/seqNum/elemCount + 4 pad) is followed by the * 32-byte rpc_message_header_v03_00 (header_version/signature/length/function/ * rpc_result/rpc_result_private/sequence/union) and then the RPC payload. * The reference sets message length = sizeof(rpc_message_header_v) + params * (rpc_common.c), and the open r535 GSP port programs/parses the same layout, * so the payload starts at element+80 and rpc.length covers the 32-byte * header. The older stage-3e note assumed the header stopped at rpc_result * (element+68); the executor keeps that as a guarded fallback only if the * reference offset fails validation. */ #define GSP_PHYS_MSG_RPC_HEADER_OFF 48 #define GSP_PHYS_MSG_RPC_HEADER_SIZE 32 #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 /* * One message queue record may span 1..16 4 KB elements (reference * GSP_MSG_QUEUE_ELEMENT_SIZE_MAX = 16 * RM_PAGE_SIZE), so the payload bound * for a single logical rpc.length is 16 * 4096 - 80 = 65456 bytes. * Multi-element records are split on 4 KB boundaries (message_queue_cpu.c * copies one full element per msgqTxGetWriteBuffer slot), only the first * element carries the 48-byte transport header with elemCount, and the * checksum covers the whole record up to the 8-byte-padded * 48 + rpc.length, exactly as a contiguous record (the tail beyond * 48 + rpc.length is zero and folds away). */ #define GSP_PHYS_MSG_ELEMENTS_MAX 16 #define GSP_PHYS_MSG_PAYLOAD_MAX \ (GSP_PHYS_MSG_ELEMENTS_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 #define GSP_PHYS_RPC_FUNC_SET_GUEST_SYSTEM_INFO 1u #define GSP_PHYS_RPC_FUNC_GET_GSP_STATIC_INFO 65u #define GSP_PHYS_RPC_FUNC_GSP_SET_SYSTEM_INFO 72u #define GSP_PHYS_RPC_FUNC_SET_REGISTRY 73u /* * PCIe config-space mirror window, as carried in GspSystemInfo. The field is * a BAR0 register offset, not a host buffer: the GSP-RM server feeds it to * osPciInitHandle()/osPciReadDword(), which do sysBar0Read(base + config * offset), i.e. they read the GPU's hardware config mirror in its own BAR0. * On Turing that is DEVICE_BASE(NV_PCFG) = 0x88000 with a 4 KB extent * (`NV_PCFG 0x00088FFF:0x00088000` in the published turing/tu102 swref), and * the CPU-RM BIF HAL for this family returns exactly those two values * (kbifGetPciConfigSpacePriMirror_GM107). Do not allocate a DMA buffer here: * GSP-RM would interpret its physical address as a BAR0 offset and read an * unrelated register window. */ #define GSP_PHYS_PCI_CONFIG_MIRROR_BASE 0x00088000u #define GSP_PHYS_PCI_CONFIG_MIRROR_SIZE 0x00001000u /* * 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 /* * Host RM probe (debugfs gsp-phys-rm-probe): SET_GUEST_SYSTEM_INFO (1) and * GET_GSP_STATIC_INFO (65) reply bounds. GSP-RM answers once init-done was * observed; ~10 s mirrors the RPC timeout, the fn-1 attempt is shorter. */ #define GSP_PHYS_RM_PROBE_TIMEOUT_MS 10000 #define GSP_PHYS_RM_PROBE_FN1_TIMEOUT_MS 5000 #define GSP_PHYS_RM_PROBE_POLL_MS 20 /* * CPU-sequencer execution (rmgspseq.h and kernel_gsp.c in the reference). * GSP-RM asks the host to run a stream of register commands against BAR0; the * stream is inline in the element payload as a 40-byte fixed header followed * by the command buffer (opcode dword + opcode-specific payload dwords). All * bounds and delays are hard-capped so a malformed element can never drive an * unbounded access. * * bufferSizeDWord is the size of GSP-RM's own sequencer buffer, not the * message: the reference only requires it to be nonzero and greater than * cmdIndex, and live GSP-RM reports 0x3FE2 (16354) on this TU104. The cap is * therefore a sanity ceiling, not a protocol limit; the real execution bound * is cmdIndex against the element payload length. */ #define GSP_PHYS_SEQ_MAX_BUFFER_DWORDS 0x40000 #define GSP_PHYS_SEQ_REG_SAVE_SIZE 8 #define GSP_PHYS_SEQ_HALT_TIMEOUT_MS 5000 #define GSP_PHYS_SEQ_RESUME_TIMEOUT_MS 10000 /* * REG_POLL timeout dword is microseconds in the reference: * kgspExecuteSequencerBuffer_IMPL passes regPoll.timeout straight to * gpuSetTimeout() (kernel_gsp.c:4844), and message_queue_cpu.c:518-519 * documents that unit ("Set a timeout of 1 sec" -> 1000000). A zero dword is * GPU_TIMEOUT_DEFAULT (gpu_timeout.h:40); timeoutSet() replaces it with the * platform default (gpu_timeout.c:235-237), which osGetTimeoutParams() sets * to 4 s in graphics mode (the switch default, os.c:1952) and 30 s in * compute mode (os.c:1956). Use the 4 s graphics-mode default for zero, then * clamp to 1 us .. 10 s here. */ #define GSP_PHYS_SEQ_POLL_TIMEOUT_DEFAULT_US 4000000u #define GSP_PHYS_SEQ_POLL_TIMEOUT_MIN_US 1u #define GSP_PHYS_SEQ_POLL_TIMEOUT_MAX_US 10000000u #define GSP_PHYS_SEQ_DELAY_MAX_US 1000000 #define GSP_PHYS_SEQ_UDELAY_MAX_US 1000 #define GSP_PHYS_SEQ_REQUEST_SIZE 40 /* Bounded stage-3e 0x1002 decode: hex-dump and opcode-list caps. */ #define GSP_PHYS_SEQ_DUMP_DWORDS 64 #define GSP_PHYS_SEQ_DUMP_OPS 64 /* Stage 3e poll bounds. */ #define GSP_PHYS_RM_INIT_TIMEOUT_MS 60000 #define GSP_PHYS_RM_INIT_POLL_MS 20 /* Booter Load image section split (decoded from the bindata header). */ #define GSP_PHYS_BOOTER_LOAD_IMAGE_SIZE 59136 #define GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF 0x0000 #define GSP_PHYS_BOOTER_LOAD_NS_CODE_SIZE 0x0100 #define GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF 0x0100 #define GSP_PHYS_BOOTER_LOAD_SEC_CODE_SIZE 0x8400 #define GSP_PHYS_BOOTER_LOAD_DATA_OFF 0x8500 #define GSP_PHYS_BOOTER_LOAD_DATA_SIZE 0x6200 #define GSP_PHYS_BOOTER_LOAD_SIG_OFF 0x8700 /* FMC descriptor values (RM_RISCV_UCODE_DESC v4). */ #define GSP_PHYS_FMC_APP_VERSION 0 /* Boot device registers not part of the shared falcon map. */ #define GSP_PHYS_WPR2_ADDR_LO 0x1fa824 #define GSP_PHYS_WPR2_ADDR_HI 0x1fa828 #define GSP_PHYS_RISCV_STATUS 0x111240 #define GSP_PHYS_RISCV_ACTIVE BIT(0) #define GSP_PHYS_VBIOS_SCRATCH_0E 0x001438 #define GSP_PHYS_VBIOS_SCRATCH_15 0x001454 #define GSP_PHYS_GFW_BOOT 0x118234 #define GSP_PHYS_BOOTER_HALT_TIMEOUT_MS 5000 /* * PTIMER system timer (BAR0 0x9000..0x9fff). TU10x bare metal programs the * timer from the host wall clock before starting GSP-RM (GV100 HAL, * timer/arch/volta/timer_gv100.c): TIME_1 gets the high and TIME_0 the low * 32 bits, and the TIME_0 write is the load trigger. The write is guarded by * TIME_PRIV_LEVEL_MASK.WRITE_PROTECTION_LEVEL0 (bit 4). Published register * offsets: published/volta/gv100/dev_timer.h. */ #define GSP_PHYS_PTIMER_TIME_0 0x00009400 #define GSP_PHYS_PTIMER_TIME_1 0x00009410 #define GSP_PHYS_PTIMER_TIME_PRIV_LEVEL_MASK 0x00009430 #define GSP_PHYS_PTIMER_WRITE_PROT_LEVEL0 BIT(4) /* libos init argument ids: the name bytes packed big-endian into the u64. */ #define GSP_PHYS_LIBOS_ID_LOGINIT 0x4c4f47494e4954ULL /* "LOGINIT" */ #define GSP_PHYS_LIBOS_ID_LOGRM 0x4c4f474dULL /* "LOGRM" */ #define GSP_PHYS_LIBOS_ID_RMARGS 0x524d41524753ULL /* "RMARGS" */ #define GSP_PHYS_LIBOS_KIND_CONTIGUOUS 1 #define GSP_PHYS_LIBOS_LOC_SYSMEM 1 /* Message queue and libos/GSP argument ABI structures. */ struct gsp_phys_libos_arg { __le64 id8; __le64 pa; __le64 size; u8 kind; u8 loc; u8 pad[6]; }; struct gsp_phys_msgq_init_args { __le64 shared_mem_phys_addr; __le32 page_table_entry_count; __le32 reserved; __le64 cmd_queue_offset; __le64 stat_queue_offset; }; struct gsp_phys_sr_init_args { __le32 old_level; __le32 flags; u8 b_in_pm_transition; u8 pad[3]; }; struct gsp_phys_gsp_args_page { struct gsp_phys_msgq_init_args mq; struct gsp_phys_sr_init_args sr; __le32 gpu_instance; u8 b_dmem_stack; u8 pad0[7]; struct { __le64 pa; __le64 size; } profiler; }; /* msgqTxHeader (msgq_priv.h): 8 u32 written by the queue producer. */ struct gsp_phys_msgq_tx_header { __le32 version; __le32 size; __le32 msg_size; __le32 msg_count; __le32 write_ptr; __le32 flags; __le32 rx_hdr_off; __le32 entry_off; }; /* GSP_MSG_QUEUE_ELEMENT through the full rpc_message_header_v (80 bytes). */ struct gsp_phys_msgq_element { u8 auth_tag[16]; u8 aad[16]; __le32 check_sum; __le32 seq_num; __le32 elem_count; __le32 reserved; __le32 rpc_header_version; __le32 rpc_signature; __le32 rpc_length; __le32 rpc_function; __le32 rpc_result; __le32 rpc_result_private; __le32 rpc_sequence; __le32 rpc_union; /* cpuRmGfid / spare */ }; /* * rpc_run_cpu_sequencer_v17_00 fixed header (40 bytes); the flexible command * buffer follows inline at GSP_PHYS_SEQ_REQUEST_SIZE. The payload begins at * GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF inside the queue element. */ struct gsp_phys_seq_request { __le32 buffer_size_dwords; __le32 cmd_index; __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 is called with * KGSP_UNLOAD_MODE_NORMAL and newLevel = NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_0 * (== 0) on the NORMAL teardown path (gpu.c:3272-3273), not a fast-unload * level. */ struct gsp_phys_rpc_unloading_guest_driver { u8 b_in_pm_transition; u8 b_gc6_entering; u8 pad[2]; __le32 new_level; }; /* * rpc_get_gsp_static_info_v14_00 (g_rpc-structures.h:408): the request-side * struct is one NvU32 `data`. The server answers with a zeroed-prefixed * GspStaticConfigInfo payload (rpcGetGspStaticInfo_v14_00, * rpc.c:8875-8913: rpc.length = 32 + sizeof(GspStaticConfigInfo), reply * copied into the `data` union member). */ struct gsp_phys_rpc_get_gsp_static_info { __le32 data; }; /* * rpc_set_guest_system_info_v03_00 (g_rpc-structures.h:36-47): three * 0x100-byte version strings and six u32 counters, 792 bytes. The reference * sender (RmRpcSetGuestSystemInfo, rpc.c:7950-8129) fills * guestDriverVersion=NvVersion, guestVersion=branch, guestTitle=display * title with bufferLength = roundup(256, 4) = 256, guestClNum=changelist and * vgxVersion = VGX_MAJOR/MINOR_VERSION_NUMBER (0x29/0x0C for 570.144). */ struct gsp_phys_rpc_set_guest_system_info { __le32 vgx_version_major_num; __le32 vgx_version_minor_num; __le32 guest_driver_version_buffer_length; __le32 guest_version_buffer_length; __le32 guest_title_buffer_length; __le32 guest_cl_num; char guest_driver_version[0x100]; char guest_version[0x100]; char guest_title[0x100]; }; /* * GspSystemInfo, the GSP_SET_SYSTEM_INFO (function 72, v17_00) payload. The * reference queues this RPC into the command queue before the GSP is booted * (kgspQueueAsyncInitRpcs, kernel_gsp.c:3358-3381, called from kgspInitRm * before _kgspBootGspRm) so GSP-RM consumes it before OBJGPU is created. * * Field offsets/widths below were derived by compiling the reference header * (kernel/gpu/gsp/gsp_static_config.h) with offsetof/sizeof against the * 570.144 tree and are also pinned in the tracked ABI check file * (gsp_abi_check.c:324-375, ABI_CHECK_SIZE_GE 920). The reference sender * transmits sizeof(GspSystemInfo) = 928 bytes (hostPageSize was appended at * offset 920); the whole struct is sent with rpc.length = 32 + 928 = 960. * Static asserts at the end of this file re-check every offset here without * including any reference header in the kernel module. */ struct gsp_phys_gsp_system_info { __le64 gpu_phys_addr; /* 0 */ __le64 gpu_phys_fb_addr; /* 8 */ __le64 gpu_phys_inst_addr; /* 16 */ __le64 gpu_phys_io_addr; /* 24 */ __le64 nv_domain_bus_device_func; /* 32 */ __le64 sim_access_buf_phys_addr; /* 40 */ __le64 notify_op_shared_surface_phys_addr; /* 48 */ __le64 pcie_atomics_op_mask; /* 56 */ __le64 console_mem_size; /* 64 */ __le64 max_user_va; /* 72 */ __le32 pci_config_mirror_base; /* 80 */ __le32 pci_config_mirror_size; /* 84 */ __le32 pci_device_id; /* 88 */ __le32 pci_sub_device_id; /* 92 */ __le32 pci_revision_id; /* 96 */ __le32 pcie_atomics_cpl_device_cap_mask; /* 100 */ u8 oor_arch; /* 104 */ u8 pad0[7]; /* 105..111 */ __le64 cl_pdb_properties; /* 112 */ __le32 chipset; /* 120 */ u8 b_gpu_behind_bridge; /* 124 */ u8 b_flr_supported; /* 125 */ u8 b64b_bar0_supported; /* 126 */ u8 b_mnoc_available; /* 127 */ __le32 chipset_l1ss_enable; /* 128 */ u8 b_upstream_l0s_unsupported; /* 132 */ u8 b_upstream_l1_unsupported; /* 133 */ u8 b_upstream_l1_por_supported; /* 134 */ u8 b_upstream_l1_por_mobile_only; /* 135 */ u8 b_system_has_mux; /* 136 */ u8 upstream_address_valid; /* 137 */ u8 fhb_bus_info[10]; /* 138 */ u8 chipset_id_info[10]; /* 148 */ u8 pad1[2]; /* 158..159 */ u8 acpi_method_data[676]; /* 160 */ __le32 hypervisor_type; /* 836 */ u8 b_is_passthru; /* 840 */ u8 pad2[7]; /* 841..847 */ __le64 sys_timer_offset_ns; /* 848 */ u8 gsp_vf_info[40]; /* 856 */ u8 b_is_primary; /* 896 */ u8 is_grid_build; /* 897 */ u8 pad3[2]; /* 898..899 */ __le32 pcie_config_reg_link_cap; /* 900 */ __le32 grid_build_csp; /* 904 */ u8 b_preserve_video_memory_allocations; /* 908 */ u8 b_tdr_event_supported; /* 909 */ u8 b_feature_stretch_vblank_capable; /* 910 */ u8 b_enable_dynamic_granularity_page_arrays; /* 911 */ u8 b_clock_boost_supported; /* 912 */ u8 b_route_disp_intrs_to_cpu; /* 913 */ u8 pad4[6]; /* 914..919 */ __le64 host_page_size; /* 920 */ }; /* * PACKED_REGISTRY_TABLE, the SET_REGISTRY (function 73, v17_00) payload * (g_os_nvoc.h:217-230; ABI checks gsp_abi_check.c:2126-2136). The header is * 8 bytes: `size` is the total table size (header + entry array + name/value * blob, entries[] = 16 bytes each) and `numEntries` the number of entries. * The submitted payload is gsp_phys_registry_image[] (generated): 37 dword * entries (the host driver's default profile plus RmDisableDisplay=1 and * RMDevidCheckIgnore=1) with the NUL-terminated names and inline dword * values after the entry array. */ struct gsp_phys_packed_registry_entry { __le32 name_offset; /* offset from table base to the NUL-terminated name */ u8 type; /* REGISTRY_TABLE_ENTRY_TYPE_* */ u8 pad[3]; __le32 data; /* dword value or offset to value bytes */ __le32 length; /* value length in bytes */ }; struct gsp_phys_packed_registry_table { __le32 size; /* total table size including this header */ __le32 num_entries; /* struct gsp_phys_packed_registry_entry entries[] follows */ }; /* * GspStaticConfigInfo reply field offsets, pinned by the reference ABI checks * (gsp_abi_check.c:266-320, ABI_CHECK_SIZE_GE 1656). Only the fields this * module decodes are named here; the struct itself never lands on the stack. */ #define GSP_PHYS_SCI_SRIOV_MAX_GFID_OFF 1200 #define GSP_PHYS_SCI_ENGINE_CAPS_OFF 1204 #define GSP_PHYS_SCI_POISON_FUSE_OFF 1216 #define GSP_PHYS_SCI_FB_LENGTH_OFF 1224 #define GSP_PHYS_SCI_FBIO_MASK_OFF 1232 #define GSP_PHYS_SCI_FB_BUS_WIDTH_OFF 1240 #define GSP_PHYS_SCI_FB_RAM_TYPE_OFF 1244 #define GSP_PHYS_SCI_FBP_MASK_OFF 1248 #define GSP_PHYS_SCI_L2_CACHE_SIZE_OFF 1256 #define GSP_PHYS_SCI_GPU_NAME_OFF 1260 #define GSP_PHYS_SCI_GPU_SHORT_NAME_OFF 1324 #define GSP_PHYS_SCI_RTD3_TOTAL_BOARD_POWER_OFF 1530 #define GSP_PHYS_SCI_RTD3_PERST_DELAY_OFF 1532 #define GSP_PHYS_SCI_BAR1_PDE_BASE_OFF 1536 #define GSP_PHYS_SCI_BAR2_PDE_BASE_OFF 1544 #define GSP_PHYS_SCI_VBIOS_VALID_OFF 1552 #define GSP_PHYS_SCI_VBIOS_SUB_VENDOR_OFF 1556 #define GSP_PHYS_SCI_VBIOS_SUB_DEVICE_OFF 1560 #define GSP_PHYS_SCI_PAGE_RETIREMENT_OFF 1564 #define GSP_PHYS_SCI_SPLIT_VAS_OFF 1565 #define GSP_PHYS_SCI_CL_NOSNOOP_WAR_OFF 1566 #define GSP_PHYS_SCI_H_INTERNAL_CLIENT_OFF 1600 #define GSP_PHYS_SCI_H_INTERNAL_DEVICE_OFF 1604 #define GSP_PHYS_SCI_H_INTERNAL_SUBDEVICE_OFF 1608 #define GSP_PHYS_SCI_SELF_HOSTED_OFF 1612 #define GSP_PHYS_SCI_ATS_SUPPORTED_OFF 1613 #define GSP_PHYS_SCI_GPU_UEFI_OFF 1614 #define GSP_PHYS_SCI_EFI_INIT_OFF 1615 #define GSP_PHYS_SCI_FW_WPR_NONWPR_OFF 1640 #define GSP_PHYS_SCI_FW_WPR_FRTS_OFF 1648 #define GSP_PHYS_SCI_MIN_SIZE 1656 #define GSP_PHYS_SCI_GPU_NAME_LEN 64 /* GSP_SEQ_BUF_OPCODE (rmgspseq.h): opcode dword then its payload dwords. */ enum gsp_phys_seq_opcode { GSP_PHYS_SEQ_OP_REG_WRITE = 0, GSP_PHYS_SEQ_OP_REG_MODIFY = 1, GSP_PHYS_SEQ_OP_REG_POLL = 2, GSP_PHYS_SEQ_OP_DELAY_US = 3, GSP_PHYS_SEQ_OP_REG_STORE = 4, GSP_PHYS_SEQ_OP_CORE_RESET = 5, GSP_PHYS_SEQ_OP_CORE_START = 6, GSP_PHYS_SEQ_OP_CORE_WAIT_FOR_HALT = 7, GSP_PHYS_SEQ_OP_CORE_RESUME = 8, }; /* * A DMA-coherent staging buffer. The whole buffer is preferred contiguous; * when the contiguous allocation fails every 4 KB page is allocated * separately and the pages list their own DMA addresses (the radix3 and the * queue page table can express either layout). */ struct gsp_phys_boot_chunk { void *va; dma_addr_t dma; void **page_va; dma_addr_t *page_dma; u32 pages; }; struct gsp_phys_radix3 { struct gsp_phys_boot_chunk table; struct gsp_phys_boot_chunk chunks[GSP_PHYS_RADIX3_MAX_CHUNKS]; u32 nr_chunks; u32 data_pages; }; static struct gsp_phys_boot_stage { bool primed; bool kicked; /* a Booter Load STARTCPU was issued (may have run) */ bool ran; /* stage 3d reached the device sequence */ bool riscv_active; bool dryrun; bool rm_init_done; /* stage 3e saw 0x1001 with rpc_result 0 */ bool rm_unload_submitted; /* UNLOADING_GUEST_DRIVER was published */ bool rm_probe_done; /* debugfs rm-probe ran at least once */ int rm_probe_last_ret; /* last probe status (0 = got fn 65) */ u32 rm_probe_last_function; /* last probe reply function (0 = none) */ u32 rm_probe_last_payload_len; /* last probe reply payload bytes */ u32 rm_probe_attempts; /* rm-probe triggers this boot */ bool seq_dump_logged; /* 0x1002 hex/opcode dump done this boot */ bool preinit_submitted; /* fn 72/73 queued before the booter kick */ u32 tx_seq_num; /* next command-queue element seqNum */ u32 status_read_ptr; /* next status-queue slot to consume (persists * across stage 3e and debugfs probe walks) */ struct gsp_phys_boot_chunk fmc; struct gsp_phys_radix3 radix3; struct gsp_phys_boot_chunk signature; struct gsp_phys_boot_chunk queue; struct gsp_phys_boot_chunk login; /* LOGINIT task log */ struct gsp_phys_boot_chunk logrm; /* LOGRM task log */ } boot_stage; static void *gsp_phys_chunk_page_va(const struct gsp_phys_boot_chunk *c, u32 page) { if (c->page_va) return c->page_va[page]; return (u8 *)c->va + (size_t)page * GSP_PHYS_PAGE_SIZE; } static dma_addr_t gsp_phys_chunk_page_dma(const struct gsp_phys_boot_chunk *c, u32 page) { if (c->page_dma) return c->page_dma[page]; return c->dma + (dma_addr_t)page * GSP_PHYS_PAGE_SIZE; } static void gsp_phys_chunk_free(struct pci_dev *pdev, struct gsp_phys_boot_chunk *c) { u32 i; if (pdev && c->page_dma) { for (i = 0; i < c->pages; i++) { if (c->page_va && c->page_va[i]) dma_free_coherent(&pdev->dev, GSP_PHYS_PAGE_SIZE, c->page_va[i], c->page_dma[i]); } } else if (pdev && c->va) { dma_free_coherent(&pdev->dev, (size_t)c->pages * GSP_PHYS_PAGE_SIZE, c->va, c->dma); } kfree(c->page_va); kfree(c->page_dma); memset(c, 0, sizeof(*c)); } static int gsp_phys_chunk_alloc(struct pci_dev *pdev, struct gsp_phys_boot_chunk *c, u32 pages) { size_t size = (size_t)pages * GSP_PHYS_PAGE_SIZE; u32 i; memset(c, 0, sizeof(*c)); c->va = dma_alloc_coherent(&pdev->dev, size, &c->dma, GFP_KERNEL); if (c->va) { c->pages = pages; return 0; } c->page_va = kcalloc(pages, sizeof(*c->page_va), GFP_KERNEL); c->page_dma = kcalloc(pages, sizeof(*c->page_dma), GFP_KERNEL); if (!c->page_va || !c->page_dma) goto err; c->pages = pages; for (i = 0; i < pages; i++) { c->page_va[i] = dma_alloc_coherent(&pdev->dev, GSP_PHYS_PAGE_SIZE, &c->page_dma[i], GFP_KERNEL); if (!c->page_va[i]) goto err; } pr_warn("gsp-phys-boot: contiguous %u-page coherent allocation failed; using %u per-page allocations\n", pages, pages); return 0; err: gsp_phys_chunk_free(pdev, c); return -ENOMEM; } static void gsp_phys_chunk_zero(struct gsp_phys_boot_chunk *c) { u32 i; if (!c->page_dma) { memset(c->va, 0, (size_t)c->pages * GSP_PHYS_PAGE_SIZE); return; } for (i = 0; i < c->pages; i++) memset(c->page_va[i], 0, GSP_PHYS_PAGE_SIZE); } /* * Fill the libos log buffer PA table: word 0 is the put pointer (left zeroed * by gsp_phys_chunk_zero) and the region's per-page GPU DMA addresses follow * as u64 little-endian from byte 8. With the per-page fallback each entry is * that page's own DMA address; with a contiguous allocation the entries are * base + page * 4 KB. The page-0 entry (word 1) is what the libos init * argument publishes. */ static void gsp_phys_log_chunk_fill(struct gsp_phys_boot_chunk *c) { __le64 *pa_table = gsp_phys_chunk_page_va(c, 0); u32 page; for (page = 0; page < c->pages; page++) pa_table[page + 1] = cpu_to_le64(gsp_phys_chunk_page_dma(c, page)); } static dma_addr_t gsp_phys_radix3_page_dma(const struct gsp_phys_radix3 *r3, u32 page) { u32 i, base = 0; for (i = 0; i < r3->nr_chunks; i++) { if (page < base + r3->chunks[i].pages) return gsp_phys_chunk_page_dma(&r3->chunks[i], page - base); base += r3->chunks[i].pages; } return 0; } static void *gsp_phys_radix3_page_va(const struct gsp_phys_radix3 *r3, u32 page) { u32 i, base = 0; for (i = 0; i < r3->nr_chunks; i++) { if (page < base + r3->chunks[i].pages) return gsp_phys_chunk_page_va(&r3->chunks[i], page - base); base += r3->chunks[i].pages; } return NULL; } static void gsp_phys_radix3_free(struct pci_dev *pdev, struct gsp_phys_radix3 *r3) { u32 i; gsp_phys_chunk_free(pdev, &r3->table); for (i = 0; i < r3->nr_chunks; i++) gsp_phys_chunk_free(pdev, &r3->chunks[i]); memset(r3, 0, sizeof(*r3)); } /* * Build the radix3 region for the GSP-RM ELF exactly as kgspCreateRadix3: * page tables first (L0 root, L1, 14 L2 nodes), data at offset 0x10000; the * root's first entry points at the L1 page, the L1 entries at the L2 nodes * and every L2 entry at one 4 KB data page. Entries are GPU DMA addresses. */ static int gsp_phys_radix3_build(struct pci_dev *pdev, struct gsp_phys_radix3 *r3, const u8 *data, u64 size) { u32 data_pages = (u32)((size + GSP_PHYS_RADIX3_PAGE_SIZE - 1) >> GSP_PHYS_RADIX3_PAGE_LOG2); u32 remaining = data_pages; u32 i, page, slot; int ret; memset(r3, 0, sizeof(*r3)); if (!size) { pr_err("gsp-phys-boot: radix3 image size is zero; refusing\n"); return -EINVAL; } if (data_pages > GSP_PHYS_RADIX3_L2_PAGES * GSP_PHYS_RADIX3_ENTRIES_PER_NODE) { pr_err("gsp-phys-boot: radix3 data (%u pages) exceeds the fixed %u-node table (max %u pages)\n", data_pages, GSP_PHYS_RADIX3_L2_PAGES, GSP_PHYS_RADIX3_L2_PAGES * GSP_PHYS_RADIX3_ENTRIES_PER_NODE); return -ERANGE; } r3->data_pages = data_pages; ret = gsp_phys_chunk_alloc(pdev, &r3->table, GSP_PHYS_RADIX3_TABLE_PAGES); if (ret) return ret; gsp_phys_chunk_zero(&r3->table); while (remaining) { u32 pages = min_t(u32, remaining, GSP_PHYS_RADIX3_CHUNK_PAGES); if (r3->nr_chunks >= GSP_PHYS_RADIX3_MAX_CHUNKS) { pr_err("gsp-phys-boot: radix3 needs more than %u chunks\n", GSP_PHYS_RADIX3_MAX_CHUNKS); ret = -ENOSPC; goto err; } ret = gsp_phys_chunk_alloc(pdev, &r3->chunks[r3->nr_chunks], pages); if (ret) goto err; gsp_phys_chunk_zero(&r3->chunks[r3->nr_chunks]); r3->nr_chunks++; remaining -= pages; } /* L0 root: entry 0 -> L1 page. */ *(__le64 *)gsp_phys_chunk_page_va(&r3->table, 0) = cpu_to_le64(gsp_phys_chunk_page_dma(&r3->table, 1)); /* L1: entry i -> L2 node i. */ for (i = 0; i < GSP_PHYS_RADIX3_L2_PAGES; i++) *(__le64 *)((u8 *)gsp_phys_chunk_page_va(&r3->table, 1) + (size_t)i * 8) = cpu_to_le64(gsp_phys_chunk_page_dma(&r3->table, 2 + i)); /* L2 leaves -> data pages, and copy the image into the data pages. */ for (page = 0; page < data_pages; page++) { u64 offset = (u64)page * GSP_PHYS_RADIX3_PAGE_SIZE; u32 bytes = (u32)min_t(u64, GSP_PHYS_RADIX3_PAGE_SIZE, size - offset); u8 *dst = gsp_phys_radix3_page_va(r3, page); if (!dst) { ret = -EINVAL; goto err; } slot = page / GSP_PHYS_RADIX3_ENTRIES_PER_NODE; *(__le64 *)((u8 *)gsp_phys_chunk_page_va(&r3->table, 2 + slot) + (size_t)(page % GSP_PHYS_RADIX3_ENTRIES_PER_NODE) * 8) = cpu_to_le64(gsp_phys_radix3_page_dma(r3, page)); memcpy(dst, data + offset, bytes); } return 0; err: gsp_phys_radix3_free(pdev, r3); return ret; } /* * Message queue shared buffer: 4 KB page table (129 PTEs, entry 0 is the * table page itself) + 256 KB command queue + 256 KB status queue. Only the * command queue TX header is written by the host; the status queue is left * zeroed for GSP-RM to publish its TX header (writePtr at +0x10), which * stage 3e polls. */ static int gsp_phys_queue_init(struct pci_dev *pdev, struct gsp_phys_boot_chunk *queue) { struct gsp_phys_msgq_tx_header *tx; __le64 *ptes; u32 i; int ret; ret = gsp_phys_chunk_alloc(pdev, queue, GSP_PHYS_QUEUE_PTE_COUNT); if (ret) return ret; gsp_phys_chunk_zero(queue); ptes = gsp_phys_chunk_page_va(queue, 0); for (i = 0; i < GSP_PHYS_QUEUE_PTE_COUNT; i++) ptes[i] = cpu_to_le64(gsp_phys_chunk_page_dma(queue, i)); tx = gsp_phys_chunk_page_va(queue, GSP_PHYS_QUEUE_CMD_OFF / GSP_PHYS_PAGE_SIZE); memset(tx, 0, sizeof(*tx)); tx->version = cpu_to_le32(0); tx->size = cpu_to_le32(GSP_PHYS_QUEUE_CMD_SIZE); tx->msg_size = cpu_to_le32(GSP_PHYS_MSG_SIZE); tx->msg_count = cpu_to_le32((GSP_PHYS_QUEUE_CMD_SIZE - 0x1000) / GSP_PHYS_MSG_SIZE); tx->write_ptr = cpu_to_le32(0); tx->flags = cpu_to_le32(GSP_PHYS_MSG_FLAGS_SWAP_RX); tx->rx_hdr_off = cpu_to_le32(0x20); tx->entry_off = cpu_to_le32(0x1000); return 0; } static void gsp_phys_libos_args_fill(struct gsp_phys_boot *boot) { struct gsp_phys_libos_arg *args = boot->libos_args_va; u64 login_pa = (u64)gsp_phys_chunk_page_dma(&boot_stage.login, 0); u64 logrm_pa = (u64)gsp_phys_chunk_page_dma(&boot_stage.logrm, 0); memset(boot->libos_args_va, 0, boot->libos_args_size); /* * LOGINIT must be first for early init logging; LOGRM follows and * RMARGS closes the list (the GSP args page from stage 3b). */ args[0].id8 = cpu_to_le64(GSP_PHYS_LIBOS_ID_LOGINIT); args[0].pa = cpu_to_le64(login_pa); args[0].size = cpu_to_le64(GSP_PHYS_LOG_BUFFER_SIZE); args[0].kind = GSP_PHYS_LIBOS_KIND_CONTIGUOUS; args[0].loc = GSP_PHYS_LIBOS_LOC_SYSMEM; args[1].id8 = cpu_to_le64(GSP_PHYS_LIBOS_ID_LOGRM); args[1].pa = cpu_to_le64(logrm_pa); args[1].size = cpu_to_le64(GSP_PHYS_LOG_BUFFER_SIZE); args[1].kind = GSP_PHYS_LIBOS_KIND_CONTIGUOUS; args[1].loc = GSP_PHYS_LIBOS_LOC_SYSMEM; args[2].id8 = cpu_to_le64(GSP_PHYS_LIBOS_ID_RMARGS); args[2].pa = cpu_to_le64((u64)boot->gsp_args_dma); args[2].size = cpu_to_le64((u64)boot->gsp_args_size); args[2].kind = GSP_PHYS_LIBOS_KIND_CONTIGUOUS; args[2].loc = GSP_PHYS_LIBOS_LOC_SYSMEM; pr_info("gsp-phys-boot: libos args[0]: LOGINIT id8=0x%llx pa=0x%llx size=0x%x kind=%u loc=%u\n", GSP_PHYS_LIBOS_ID_LOGINIT, login_pa, GSP_PHYS_LOG_BUFFER_SIZE, args[0].kind, args[0].loc); pr_info("gsp-phys-boot: libos args[1]: LOGRM id8=0x%llx pa=0x%llx size=0x%x kind=%u loc=%u\n", GSP_PHYS_LIBOS_ID_LOGRM, logrm_pa, GSP_PHYS_LOG_BUFFER_SIZE, args[1].kind, args[1].loc); pr_info("gsp-phys-boot: libos args[2]: RMARGS id8=0x%llx pa=0x%llx size=0x%llx kind=%u loc=%u\n", GSP_PHYS_LIBOS_ID_RMARGS, (u64)boot->gsp_args_dma, (u64)boot->gsp_args_size, args[2].kind, args[2].loc); pr_info("gsp-phys-boot: libos args list: LOGINIT, LOGRM, RMARGS + zero terminator (LOGINIT first for early init logging; pa = page-0 DMA, not the region base)\n"); } static void gsp_phys_gsp_args_fill(struct gsp_phys_boot *boot, const struct gsp_phys_boot_chunk *queue) { struct gsp_phys_gsp_args_page *args = boot->gsp_args_va; memset(boot->gsp_args_va, 0, boot->gsp_args_size); args->mq.shared_mem_phys_addr = cpu_to_le64((u64)gsp_phys_chunk_page_dma(queue, 0)); args->mq.page_table_entry_count = cpu_to_le32(GSP_PHYS_QUEUE_PTE_COUNT); args->mq.cmd_queue_offset = cpu_to_le64(GSP_PHYS_QUEUE_CMD_OFF); args->mq.stat_queue_offset = cpu_to_le64(GSP_PHYS_QUEUE_STAT_OFF); args->sr.old_level = cpu_to_le32(0); args->sr.flags = cpu_to_le32(0); args->sr.b_in_pm_transition = 0; args->gpu_instance = cpu_to_le32(0); args->b_dmem_stack = 1; args->profiler.pa = cpu_to_le64(0); args->profiler.size = cpu_to_le64(0); pr_info("gsp-phys-boot: GSP args: mq{shared=0x%llx ptes=%u cmd=0x%x stat=0x%x} sr{0,0,0} gpuInstance=0 bDmemStack=1 profiler{0,0}\n", (u64)gsp_phys_chunk_page_dma(queue, 0), GSP_PHYS_QUEUE_PTE_COUNT, GSP_PHYS_QUEUE_CMD_OFF, GSP_PHYS_QUEUE_STAT_OFF); } static int gsp_phys_staging_self_check(struct gsp_phys_boot *boot) { const struct gsp_phys_radix3 *r3 = &boot_stage.radix3; const struct gsp_phys_boot_chunk *q = &boot_stage.queue; const struct gsp_phys_msgq_tx_header *tx; const struct gsp_phys_gsp_args_page *gsp_args = boot->gsp_args_va; const struct gsp_phys_libos_arg *libos = boot->libos_args_va; u32 expected_pages = (u32)((boot->size_of_radix3_elf + GSP_PHYS_RADIX3_PAGE_SIZE - 1) >> GSP_PHYS_RADIX3_PAGE_LOG2); u32 last_used = r3->data_pages - 1; u32 first_unused = r3->data_pages; u32 i; __le64 *ptes = gsp_phys_chunk_page_va(q, 0); __le64 *login_pa = gsp_phys_chunk_page_va(&boot_stage.login, 0); __le64 *logrm_pa = gsp_phys_chunk_page_va(&boot_stage.logrm, 0); __le64 *l0 = gsp_phys_chunk_page_va(&r3->table, 0); __le64 *l1 = gsp_phys_chunk_page_va(&r3->table, 1); __le64 *l2_used = gsp_phys_chunk_page_va(&r3->table, 2 + last_used / GSP_PHYS_RADIX3_ENTRIES_PER_NODE); __le64 *l2_unused = first_unused < GSP_PHYS_RADIX3_L2_PAGES * GSP_PHYS_RADIX3_ENTRIES_PER_NODE ? gsp_phys_chunk_page_va(&r3->table, 2 + first_unused / GSP_PHYS_RADIX3_ENTRIES_PER_NODE) : NULL; if (r3->data_pages != expected_pages || r3->nr_chunks == 0) { pr_err("gsp-phys-boot: self-check radix3 counts FAIL (dataPages=%u want %u, chunks=%u)\n", r3->data_pages, expected_pages, r3->nr_chunks); return -EINVAL; } if (le64_to_cpu(l0[0]) != gsp_phys_chunk_page_dma(&r3->table, 1) || le64_to_cpu(l1[0]) != gsp_phys_chunk_page_dma(&r3->table, 2) || le64_to_cpu(l1[GSP_PHYS_RADIX3_L2_PAGES - 1]) != gsp_phys_chunk_page_dma(&r3->table, 2 + GSP_PHYS_RADIX3_L2_PAGES - 1) || le64_to_cpu(l2_used[last_used % GSP_PHYS_RADIX3_ENTRIES_PER_NODE]) != gsp_phys_radix3_page_dma(r3, last_used) || (l2_unused && l2_unused[first_unused % GSP_PHYS_RADIX3_ENTRIES_PER_NODE] != 0)) { pr_err("gsp-phys-boot: self-check radix3 entry encoding FAIL (L0/L1/L2 leaf mismatch)\n"); return -EINVAL; } tx = gsp_phys_chunk_page_va(q, GSP_PHYS_QUEUE_CMD_OFF / GSP_PHYS_PAGE_SIZE); if (le32_to_cpu(tx->version) != 0 || le32_to_cpu(tx->size) != GSP_PHYS_QUEUE_CMD_SIZE || le32_to_cpu(tx->msg_size) != GSP_PHYS_MSG_SIZE || le32_to_cpu(tx->msg_count) != (GSP_PHYS_QUEUE_CMD_SIZE - 0x1000) / GSP_PHYS_MSG_SIZE || le32_to_cpu(tx->flags) != GSP_PHYS_MSG_FLAGS_SWAP_RX || le32_to_cpu(tx->rx_hdr_off) != 0x20 || le32_to_cpu(tx->entry_off) != 0x1000 || le64_to_cpu(ptes[0]) != gsp_phys_chunk_page_dma(q, 0) || le64_to_cpu(ptes[GSP_PHYS_QUEUE_PTE_COUNT - 1]) != gsp_phys_chunk_page_dma(q, GSP_PHYS_QUEUE_PTE_COUNT - 1)) { pr_err("gsp-phys-boot: self-check queue PTEs/header FAIL\n"); return -EINVAL; } if (le64_to_cpu(gsp_args->mq.shared_mem_phys_addr) != gsp_phys_chunk_page_dma(q, 0) || le32_to_cpu(gsp_args->mq.page_table_entry_count) != GSP_PHYS_QUEUE_PTE_COUNT || le64_to_cpu(gsp_args->mq.cmd_queue_offset) != GSP_PHYS_QUEUE_CMD_OFF || le64_to_cpu(gsp_args->mq.stat_queue_offset) != GSP_PHYS_QUEUE_STAT_OFF || gsp_args->b_dmem_stack != 1 || le64_to_cpu(libos[0].id8) != GSP_PHYS_LIBOS_ID_LOGINIT || le64_to_cpu(libos[0].pa) != (u64)gsp_phys_chunk_page_dma(&boot_stage.login, 0) || le64_to_cpu(libos[0].size) != GSP_PHYS_LOG_BUFFER_SIZE || le64_to_cpu(libos[1].id8) != GSP_PHYS_LIBOS_ID_LOGRM || le64_to_cpu(libos[1].pa) != (u64)gsp_phys_chunk_page_dma(&boot_stage.logrm, 0) || le64_to_cpu(libos[1].size) != GSP_PHYS_LOG_BUFFER_SIZE || le64_to_cpu(libos[2].id8) != GSP_PHYS_LIBOS_ID_RMARGS || le64_to_cpu(libos[2].pa) != (u64)boot->gsp_args_dma || le64_to_cpu(libos[2].size) != (u64)boot->gsp_args_size || le64_to_cpu(libos[3].id8) != 0 || libos[3].kind != 0) { pr_err("gsp-phys-boot: self-check GSP/libos args FAIL (LOGINIT/LOGRM/RMARGS ids, addresses, sizes or the zero terminator mismatch)\n"); return -EINVAL; } for (i = 0; i < 3; i++) { if (libos[i].kind != GSP_PHYS_LIBOS_KIND_CONTIGUOUS || libos[i].loc != GSP_PHYS_LIBOS_LOC_SYSMEM) { pr_err("gsp-phys-boot: self-check libos args[%u] kind/loc FAIL\n", i); return -EINVAL; } } if (le64_to_cpu(login_pa[1]) != gsp_phys_chunk_page_dma(&boot_stage.login, 0) || le64_to_cpu(login_pa[GSP_PHYS_LOG_BUFFER_PAGES]) != gsp_phys_chunk_page_dma(&boot_stage.login, GSP_PHYS_LOG_BUFFER_PAGES - 1) || le64_to_cpu(logrm_pa[1]) != gsp_phys_chunk_page_dma(&boot_stage.logrm, 0) || le64_to_cpu(logrm_pa[GSP_PHYS_LOG_BUFFER_PAGES]) != gsp_phys_chunk_page_dma(&boot_stage.logrm, GSP_PHYS_LOG_BUFFER_PAGES - 1)) { pr_err("gsp-phys-boot: self-check log PA tables FAIL (first/last page DMA mismatch)\n"); return -EINVAL; } if (!gsp_phys_chunk_page_dma(&boot_stage.fmc, 0) || !gsp_phys_chunk_page_dma(&boot_stage.signature, 0) || !gsp_phys_chunk_page_dma(&r3->table, 0) || !gsp_phys_chunk_page_dma(q, 0) || !gsp_phys_chunk_page_dma(&boot_stage.login, 0) || !gsp_phys_chunk_page_dma(&boot_stage.logrm, 0)) { pr_err("gsp-phys-boot: self-check sysmem addresses FAIL (a page-0 DMA address is zero)\n"); return -EINVAL; } pr_info("gsp-phys-boot: self-check PASS: radix3 %u data pages, %u L2 + 1 L1 + 1 L0, data at 0x%x; queue %u PTEs, cmd header v0 size=0x%x msgSize=%u msgCount=%u flags=%u rxHdrOff=0x20 entryOff=0x1000; libos LOGINIT/LOGRM/RMARGS + zero terminator, 64 KB log PA tables; all sysmem addrs nonzero\n", r3->data_pages, GSP_PHYS_RADIX3_L2_PAGES, GSP_PHYS_RADIX3_DATA_OFFSET, GSP_PHYS_QUEUE_PTE_COUNT, GSP_PHYS_QUEUE_CMD_SIZE, GSP_PHYS_MSG_SIZE, (GSP_PHYS_QUEUE_CMD_SIZE - 0x1000) / GSP_PHYS_MSG_SIZE, GSP_PHYS_MSG_FLAGS_SWAP_RX); return 0; } /* * One-line read-only view of the VPR secure scratch 14 boot-sequence state * (the booter/ACR handoff gate). Every stage boundary logs it so a failed * boot can be read back from dmesg alone. */ static void gsp_phys_scratch14_log(void __iomem *bar0, const char *where) { struct gsp_phys_scratch14 s; gsp_phys_fwsec_scratch14_read(bar0, &s); pr_info("gsp-phys-boot: scratch14 handoffs: raw=0x%08x version=%u booterLoad=%u ahesasc=%u booterReload=%u acrUnload=%u booterBinaryVersion=%u [%s]\n", s.raw, s.acr_version, s.booter_load, s.ahesasc_booter_reload, s.booter_reload_acr_unload, s.acr_unload_booter_unload, s.booter_binary_version, where); } int gsp_phys_boot_prime(struct gsp_phys_boot *boot, struct pci_dev *pdev, void __iomem *bar0, const struct gsp_phys_boot_sources *src, bool dryrun) { u64 data_pages; int ret; if (!boot || !pdev || !bar0 || !src || !src->fw_image || !src->fw_signature || !src->fw_image_size) return -EINVAL; if (!boot->initialized || !boot->wpr_meta_va) { pr_err("gsp-phys-boot: boot layout not initialised; refusing stage 3d staging\n"); return -EINVAL; } if (boot_stage.primed) { pr_err("gsp-phys-boot: staging already primed; refusing a second stage 3d staging\n"); return -EBUSY; } if (src->fw_image_size != boot->size_of_radix3_elf || src->fw_signature_size != GSP_PHYS_SIGNATURE_SIZE) { pr_err("gsp-phys-boot: staged firmware views mismatch (image=0x%llx layout=0x%llx signature=0x%llx want 0x%x); refusing stage 3d\n", src->fw_image_size, boot->size_of_radix3_elf, src->fw_signature_size, GSP_PHYS_SIGNATURE_SIZE); return -EINVAL; } data_pages = (src->fw_image_size + GSP_PHYS_PAGE_SIZE - 1) / GSP_PHYS_PAGE_SIZE; boot_stage.dryrun = dryrun; pr_info("gsp-phys-boot: stage 3d %s on %s: staging 0x%llx (.fwimage) + FMC 0x%x + signature 0x%x + queue 0x%x + LOGINIT 0x%x + LOGRM 0x%x (total 0x%llx; host memory only, %s)\n", dryrun ? "prime" : "staging", pci_name(pdev), src->fw_image_size, GSP_PHYS_FMC_SIZE, GSP_PHYS_SIGNATURE_SIZE, GSP_PHYS_QUEUE_TOTAL_SIZE, GSP_PHYS_LOG_BUFFER_SIZE, GSP_PHYS_LOG_BUFFER_SIZE, src->fw_image_size + GSP_PHYS_FMC_SIZE + GSP_PHYS_SIGNATURE_SIZE + GSP_PHYS_QUEUE_TOTAL_SIZE + 2 * (u64)GSP_PHYS_LOG_BUFFER_SIZE, dryrun ? "no BAR0 writes, no falcon resets" : "device writes follow"); /* The staging reads are the only device access here. */ pr_info("gsp-phys-boot: state before staging (read-only): WPR2_LO=0x%08x WPR2_HI=0x%08x GFW_BOOT=0x%08x GSP_CPUCTL=0x%08x SEC2_CPUCTL=0x%08x\n", readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), readl(bar0 + GSP_PHYS_WPR2_ADDR_HI), readl(bar0 + GSP_PHYS_GFW_BOOT), readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL), readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL)); gsp_phys_scratch14_log(bar0, "prime-pre-staging"); ret = gsp_phys_chunk_alloc(pdev, &boot_stage.fmc, 1); if (ret) goto err; memcpy(gsp_phys_chunk_page_va(&boot_stage.fmc, 0), gsp_phys_gsp_rm_boot_image, sizeof(gsp_phys_gsp_rm_boot_image)); pr_info("gsp-phys-boot: FMC copy 0x%zx bytes at dma=0x%llx (appVersion=%u from the v4 descriptor)\n", sizeof(gsp_phys_gsp_rm_boot_image), (u64)gsp_phys_chunk_page_dma(&boot_stage.fmc, 0), GSP_PHYS_FMC_APP_VERSION); ret = gsp_phys_radix3_build(pdev, &boot_stage.radix3, src->fw_image, src->fw_image_size); if (ret) goto err; pr_info("gsp-phys-boot: radix3 for 0x%llx: dataPages=%llu L2=%u L1=1 L0=1 tables=%u pages (data at 0x%x), rootDma=0x%llx, %u data chunks\n", src->fw_image_size, data_pages, GSP_PHYS_RADIX3_L2_PAGES, GSP_PHYS_RADIX3_TABLE_PAGES, GSP_PHYS_RADIX3_DATA_OFFSET, (u64)gsp_phys_chunk_page_dma(&boot_stage.radix3.table, 0), boot_stage.radix3.nr_chunks); ret = gsp_phys_chunk_alloc(pdev, &boot_stage.signature, 1); if (ret) goto err; memcpy(gsp_phys_chunk_page_va(&boot_stage.signature, 0), src->fw_signature, GSP_PHYS_SIGNATURE_SIZE); pr_info("gsp-phys-boot: signature copy 0x%x bytes at dma=0x%llx (256-aligned)\n", GSP_PHYS_SIGNATURE_SIZE, (u64)gsp_phys_chunk_page_dma(&boot_stage.signature, 0)); ret = gsp_phys_chunk_alloc(pdev, &boot_stage.login, GSP_PHYS_LOG_BUFFER_PAGES); if (ret) goto err; gsp_phys_chunk_zero(&boot_stage.login); gsp_phys_log_chunk_fill(&boot_stage.login); ret = gsp_phys_chunk_alloc(pdev, &boot_stage.logrm, GSP_PHYS_LOG_BUFFER_PAGES); if (ret) goto err; gsp_phys_chunk_zero(&boot_stage.logrm); gsp_phys_log_chunk_fill(&boot_stage.logrm); pr_info("gsp-phys-boot: libos log regions 0x%x each: LOGINIT pa=0x%llx LOGRM pa=0x%llx (put pointer 0, %u-page PA table at +0x%x)\n", GSP_PHYS_LOG_BUFFER_SIZE, (u64)gsp_phys_chunk_page_dma(&boot_stage.login, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.logrm, 0), GSP_PHYS_LOG_BUFFER_PAGES, GSP_PHYS_LOG_PA_TABLE_OFF); ret = gsp_phys_queue_init(pdev, &boot_stage.queue); if (ret) goto err; pr_info("gsp-phys-boot: queue 0x%x at dma=0x%llx: pt@+0x0 (129 PTEs) cmd@+0x%x stat@+0x%x (GSP-RM publishes the status TX header)\n", GSP_PHYS_QUEUE_TOTAL_SIZE, (u64)gsp_phys_chunk_page_dma(&boot_stage.queue, 0), GSP_PHYS_QUEUE_CMD_OFF, GSP_PHYS_QUEUE_STAT_OFF); gsp_phys_libos_args_fill(boot); gsp_phys_gsp_args_fill(boot, &boot_stage.queue); ret = gsp_phys_boot_meta_set_sysmem(boot, (u64)gsp_phys_chunk_page_dma( &boot_stage.radix3.table, 0), src->fw_image_size, (u64)gsp_phys_chunk_page_dma( &boot_stage.fmc, 0), GSP_PHYS_FMC_SIZE, (u64)gsp_phys_chunk_page_dma( &boot_stage.signature, 0), GSP_PHYS_SIGNATURE_SIZE); if (ret) goto err; ret = gsp_phys_staging_self_check(boot); if (ret) goto err; boot_stage.primed = true; pr_info("gsp-phys-boot: stage 3d prime complete: host staging ready (radix3 root=0x%llx FMC=0x%llx signature=0x%llx queue=0x%llx LOGINIT=0x%llx LOGRM=0x%llx); %s\n", (u64)gsp_phys_chunk_page_dma(&boot_stage.radix3.table, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.fmc, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.signature, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.queue, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.login, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.logrm, 0), dryrun ? "no device writes (dry-run)" : "device sequence follows"); return 0; err: gsp_phys_radix3_free(pdev, &boot_stage.radix3); gsp_phys_chunk_free(pdev, &boot_stage.fmc); gsp_phys_chunk_free(pdev, &boot_stage.signature); gsp_phys_chunk_free(pdev, &boot_stage.queue); gsp_phys_chunk_free(pdev, &boot_stage.login); gsp_phys_chunk_free(pdev, &boot_stage.logrm); memset(&boot_stage, 0, sizeof(boot_stage)); return ret; } static void gsp_phys_boot_telemetry(void __iomem *bar0) { pr_info("gsp-phys-boot: telemetry: GSP_CPUCTL=0x%08x SEC2_CPUCTL=0x%08x SEC2_M0=0x%08x SEC2_M1=0x%08x RISCV_STATUS=0x%08x FALCON_OS=0x%08x WPR2_LO=0x%08x WPR2_HI=0x%08x scratch0E=0x%08x scratch15=0x%08x GFW_BOOT=0x%08x\n", readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL), readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL), readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0), readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1), readl(bar0 + GSP_PHYS_RISCV_STATUS), readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_OS), readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), readl(bar0 + GSP_PHYS_WPR2_ADDR_HI), readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_0E), readl(bar0 + GSP_PHYS_VBIOS_SCRATCH_15), readl(bar0 + GSP_PHYS_GFW_BOOT)); } static int gsp_phys_preinit_rpcs(void __iomem *bar0, struct pci_dev *pdev); int gsp_phys_boot_run(struct gsp_phys_boot *boot, struct pci_dev *pdev, void __iomem *bar0) { const u8 *img = gsp_phys_booter_load_image; u32 m0, m1, cpuctl, riscv; int ret; if (!boot || !pdev || !bar0) return -EINVAL; if (!boot_stage.primed || !boot->initialized) { pr_err("gsp-phys-boot: stage 3d FAILED: host staging is not primed\n"); return -EINVAL; } if (!gsp_phys_fwsec_wpr2_up(bar0)) { pr_err("gsp-phys-boot: stage 3d FAILED: WPR2 is not up (FWSEC did not complete); refusing to start the booter\n"); return -EBUSY; } if (boot_stage.dryrun) { pr_err("gsp-phys-boot: stage 3d FAILED: staging was primed in dry-run mode; refusing device writes\n"); return -EINVAL; } if (gsp_phys_fwsec_board_debug(bar0)) { pr_err("gsp-phys-boot: stage 3d FAILED: board is in debug mode (SECURE_GSP_DEBUG_DIS bit0==0) but the staged Booter Load image is the production build; refusing the device sequence\n"); return -EPERM; } /* * Queue the pre-boot async init RPCs (GSP_SET_SYSTEM_INFO 72 then * SET_REGISTRY 73) into the already-staged command queue, exactly * where the reference does it relative to the boot: kgspQueueAsyncInitRpcs * runs before _kgspBootGspRm starts the booter, so GSP-RM consumes * both messages at early init before OBJGPU is created. A failed * publish aborts the boot; GSP-RM would otherwise start with zero * system info (gpuPhys* and the config mirror all zero). */ if (!boot_stage.preinit_submitted) { ret = gsp_phys_preinit_rpcs(bar0, pdev); if (ret) { pr_err("gsp-phys-boot: stage 3d FAILED: pre-boot async init RPCs (fn %u/%u) were not queued (%d); refusing to start GSP-RM without system info\n", GSP_PHYS_RPC_FUNC_GSP_SET_SYSTEM_INFO, GSP_PHYS_RPC_FUNC_SET_REGISTRY, ret); return ret; } boot_stage.preinit_submitted = true; } pr_info("gsp-phys-boot: stage 3d: resetting GSP falcon into RISC-V, libos args 0x%llx\n", (u64)boot->libos_args_dma); gsp_phys_scratch14_log(bar0, "pre-gsp-reset"); ret = gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE); if (ret) { pr_err("gsp-phys-boot: stage 3d FAILED: GSP falcon reset (RISC-V) error %d\n", ret); return ret; } writel(lower_32_bits((u64)boot->libos_args_dma), bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX0); writel(upper_32_bits((u64)boot->libos_args_dma), bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX1); pr_info("gsp-phys-boot: GSP MAILBOX0/1 = libos args DMA 0x%llx (LO=0x%08x HI=0x%08x)\n", (u64)boot->libos_args_dma, readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX0), readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX1)); pr_info("gsp-phys-boot: Booter Load on SEC2: meta 0x%llx, image 0x%x (ns 0x%x@0x%x sec 0x%x@0x%x dmem 0x%x@0x%x), BOOTVEC=0\n", (u64)boot->wpr_meta_dma, GSP_PHYS_BOOTER_LOAD_IMAGE_SIZE, GSP_PHYS_BOOTER_LOAD_NS_CODE_SIZE, GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF, GSP_PHYS_BOOTER_LOAD_SEC_CODE_SIZE, GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF, GSP_PHYS_BOOTER_LOAD_DATA_SIZE, GSP_PHYS_BOOTER_LOAD_DATA_OFF); ret = gsp_phys_falcon_reset(bar0, GSP_PHYS_SEC2_BASE); if (ret) { pr_err("gsp-phys-boot: stage 3d FAILED: SEC2 falcon reset error %d\n", ret); return ret; } gsp_phys_falcon_disable_ctx_req(bar0, GSP_PHYS_SEC2_BASE, GSP_PHYS_SEC2_FBIF_BASE); gsp_phys_falcon_imem_copy(bar0, GSP_PHYS_SEC2_BASE, GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF, img + GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF, GSP_PHYS_BOOTER_LOAD_NS_CODE_SIZE, false, GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF); gsp_phys_falcon_imem_copy(bar0, GSP_PHYS_SEC2_BASE, GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF, img + GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF, GSP_PHYS_BOOTER_LOAD_SEC_CODE_SIZE, true, GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF); gsp_phys_falcon_dmem_copy(bar0, GSP_PHYS_SEC2_BASE, 0, img + GSP_PHYS_BOOTER_LOAD_DATA_OFF, GSP_PHYS_BOOTER_LOAD_DATA_SIZE); writel(0, bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_BOOTVEC); writel(lower_32_bits((u64)boot->wpr_meta_dma), bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0); writel(upper_32_bits((u64)boot->wpr_meta_dma), bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1); cpuctl = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL); pr_info("gsp-phys-boot: SEC2 MAILBOX0/1 = WPR meta DMA 0x%llx (LO=0x%08x HI=0x%08x), BOOTVEC=0x%08x CPUCTL before start=0x%08x\n", (u64)boot->wpr_meta_dma, readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0), readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1), readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_BOOTVEC), cpuctl); boot_stage.kicked = true; gsp_phys_falcon_start_cpu(bar0, GSP_PHYS_SEC2_BASE); ret = gsp_phys_falcon_wait_halt(bar0, GSP_PHYS_SEC2_BASE, GSP_PHYS_BOOTER_HALT_TIMEOUT_MS); m0 = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0); m1 = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1); pr_info("gsp-phys-boot: Booter Load done: SEC2 M0=0x%08x M1=0x%08x (success M0==0), CPUCTL=0x%08x\n", m0, m1, readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL)); gsp_phys_scratch14_log(bar0, "post-booter-halt"); if (ret) { gsp_phys_boot_telemetry(bar0); pr_err("gsp-phys-boot: stage 3d FAILED: SEC2 did not halt within %u ms (M0=0x%08x M1=0x%08x)\n", GSP_PHYS_BOOTER_HALT_TIMEOUT_MS, m0, m1); return ret; } if (m0 != 0) { gsp_phys_boot_telemetry(bar0); pr_err("gsp-phys-boot: stage 3d FAILED: Booter Load refused the metadata (M0=0x%08x M1=0x%08x)\n", m0, m1); return -EIO; } writel(GSP_PHYS_FMC_APP_VERSION, bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_OS); riscv = readl(bar0 + GSP_PHYS_RISCV_STATUS); if (!(riscv & GSP_PHYS_RISCV_ACTIVE)) { gsp_phys_boot_telemetry(bar0); pr_err("gsp-phys-boot: stage 3d FAILED: RISC-V core not active after Booter Load (RISCV_STATUS=0x%08x)\n", riscv); return -ENODEV; } gsp_phys_scratch14_log(bar0, "post-riscv-active"); boot_stage.ran = true; boot_stage.riscv_active = true; gsp_phys_boot_telemetry(bar0); pr_info("gsp-phys-boot: stage 3d complete: Booter Load M0=0, RISC-V active (RISCV_STATUS=0x%08x), FALCON_OS=0x%08x (appVersion=%u), WPR2 up (LO=0x%08x HI=0x%08x), SEC2 M1=0x%08x\n", riscv, readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_OS), GSP_PHYS_FMC_APP_VERSION, readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), readl(bar0 + GSP_PHYS_WPR2_ADDR_HI), m1); return 0; } /* Opcode payload dword counts (GSP_SEQUENCER_PAYLOAD_SIZE_DWORDS). */ static u32 gsp_phys_seq_payload_dwords(u32 opcode) { switch (opcode) { case GSP_PHYS_SEQ_OP_REG_WRITE: case GSP_PHYS_SEQ_OP_REG_STORE: return 2; case GSP_PHYS_SEQ_OP_REG_MODIFY: return 3; case GSP_PHYS_SEQ_OP_REG_POLL: return 5; case GSP_PHYS_SEQ_OP_DELAY_US: return 1; case GSP_PHYS_SEQ_OP_CORE_RESET: case GSP_PHYS_SEQ_OP_CORE_START: case GSP_PHYS_SEQ_OP_CORE_WAIT_FOR_HALT: case GSP_PHYS_SEQ_OP_CORE_RESUME: return 0; default: return U32_MAX; } } static u32 gsp_phys_seq_dw(const u8 *cmd, u32 index) { return get_unaligned_le32(cmd + (size_t)index * 4u); } static const char *gsp_phys_seq_op_name(u32 opcode) { switch (opcode) { case GSP_PHYS_SEQ_OP_REG_WRITE: return "REG_WRITE"; case GSP_PHYS_SEQ_OP_REG_MODIFY: return "REG_MODIFY"; case GSP_PHYS_SEQ_OP_REG_POLL: return "REG_POLL"; case GSP_PHYS_SEQ_OP_DELAY_US: return "DELAY_US"; case GSP_PHYS_SEQ_OP_REG_STORE: return "REG_STORE"; case GSP_PHYS_SEQ_OP_CORE_RESET: return "CORE_RESET"; case GSP_PHYS_SEQ_OP_CORE_START: return "CORE_START"; case GSP_PHYS_SEQ_OP_CORE_WAIT_FOR_HALT: return "CORE_WAIT_FOR_HALT"; case GSP_PHYS_SEQ_OP_CORE_RESUME: return "CORE_RESUME"; default: return "UNKNOWN"; } } /* * Bounded, read-only view of the validated 0x1002 command buffer for the * stage-3e log, emitted once per boot: bufferSizeDWord/cmdIndex, the first * GSP_PHYS_SEQ_DUMP_DWORDS command dwords as a hex dump and the decoded * opcode list (opcode + operands, rmgspseq.h layout). cmd_index dwords were * validated against the element payload by gsp_phys_seq_parse_req(), so no * read here can pass the element bound. */ static void gsp_phys_seq_log_buffer(const u8 *payload, u32 payload_off, u32 buffer_size, u32 cmd_index) { const u8 *cmd = payload + GSP_PHYS_SEQ_REQUEST_SIZE; u32 dump_dwords = min_t(u32, cmd_index, GSP_PHYS_SEQ_DUMP_DWORDS); u32 index = 0, ops = 0; pr_info("gsp-phys-boot: stage 3e sequencer buffer (once per boot): payloadOff=%u bufferSizeDWord=%u cmdIndex=%u hexDump=%u dwords\n", payload_off, buffer_size, cmd_index, dump_dwords); if (dump_dwords) print_hex_dump(KERN_INFO, "gsp-phys-boot: seq cmd: ", DUMP_PREFIX_OFFSET, 16, 4, cmd, dump_dwords * 4u, true); while (index < cmd_index && ops < GSP_PHYS_SEQ_DUMP_OPS) { u32 opcode = gsp_phys_seq_dw(cmd, index); u32 args = gsp_phys_seq_payload_dwords(opcode); u32 at = index; u32 a0, a1, a2, a3, a4; index++; if (args == U32_MAX || (u64)index + (u64)args > (u64)cmd_index) { pr_info("gsp-phys-boot: stage 3e sequencer decode stopped at dword %u: opcode=%u args=%u remaining=%u\n", at, opcode, args, cmd_index - at); break; } a0 = args > 0 ? gsp_phys_seq_dw(cmd, index) : 0; a1 = args > 1 ? gsp_phys_seq_dw(cmd, index + 1) : 0; a2 = args > 2 ? gsp_phys_seq_dw(cmd, index + 2) : 0; a3 = args > 3 ? gsp_phys_seq_dw(cmd, index + 3) : 0; a4 = args > 4 ? gsp_phys_seq_dw(cmd, index + 4) : 0; index += args; ops++; switch (opcode) { case GSP_PHYS_SEQ_OP_REG_WRITE: pr_info("gsp-phys-boot: stage 3e seq op[%u] @dw%u: REG_WRITE(addr=0x%08x value=0x%08x)\n", ops - 1, at, a0, a1); break; case GSP_PHYS_SEQ_OP_REG_MODIFY: pr_info("gsp-phys-boot: stage 3e seq op[%u] @dw%u: REG_MODIFY(addr=0x%08x mask=0x%08x value=0x%08x)\n", ops - 1, at, a0, a1, a2); break; case GSP_PHYS_SEQ_OP_REG_POLL: pr_info("gsp-phys-boot: stage 3e seq op[%u] @dw%u: REG_POLL(addr=0x%08x mask=0x%08x value=0x%08x timeoutUs=%u error=0x%x)\n", ops - 1, at, a0, a1, a2, a3, a4); break; case GSP_PHYS_SEQ_OP_DELAY_US: pr_info("gsp-phys-boot: stage 3e seq op[%u] @dw%u: DELAY_US(%u us)\n", ops - 1, at, a0); break; case GSP_PHYS_SEQ_OP_REG_STORE: pr_info("gsp-phys-boot: stage 3e seq op[%u] @dw%u: REG_STORE(addr=0x%08x saveIndex=%u)\n", ops - 1, at, a0, a1); break; default: pr_info("gsp-phys-boot: stage 3e seq op[%u] @dw%u: %s operands=[0x%08x,0x%08x,0x%08x,0x%08x,0x%08x]\n", ops - 1, at, gsp_phys_seq_op_name(opcode), a0, a1, a2, a3, a4); break; } } if (ops == GSP_PHYS_SEQ_DUMP_OPS && index < cmd_index) pr_info("gsp-phys-boot: stage 3e sequencer opcode list truncated at %u ops (%u of %u command dwords decoded)\n", ops, index, cmd_index); } /* * The sequencer's register operands are BAR0 offsets. Validate alignment and * that the access lies inside the mapped BAR0 window before touching it. */ static bool gsp_phys_seq_addr_valid(void __iomem *bar0, resource_size_t bar0_len, u32 addr) { if (!bar0 || !bar0_len) { pr_err("gsp-phys-boot: sequencer register access without a BAR0 window\n"); return false; } if (addr & 0x3u) { pr_err("gsp-phys-boot: sequencer register address 0x%08x is not 4-byte aligned\n", addr); return false; } if ((u64)addr + 4u > (u64)bar0_len) { pr_err("gsp-phys-boot: sequencer register address 0x%08x is outside BAR0 (len 0x%llx)\n", addr, (u64)bar0_len); return false; } return true; } /* * REG_POLL: wait for (reg & mask) == value with a bounded timeout, then log * the observed value when it expires (reference kgspExecuteSequencerBuffer). * The dword is microseconds (gpuSetTimeout); a zero dword selects the * reference's platform default (GPU_TIMEOUT_DEFAULT). The reference spins in * osSpinLoop, ours sleeps coarsely, so the resolution is coarser while the * bound stays exact. */ static int gsp_phys_seq_reg_poll(void __iomem *bar0, u32 addr, u32 mask, u32 val, u32 timeout_us, u32 error) { unsigned long deadline; u32 sleep_lo, sleep_hi, observed; if (timeout_us == 0) timeout_us = GSP_PHYS_SEQ_POLL_TIMEOUT_DEFAULT_US; timeout_us = clamp_t(u32, timeout_us, GSP_PHYS_SEQ_POLL_TIMEOUT_MIN_US, GSP_PHYS_SEQ_POLL_TIMEOUT_MAX_US); deadline = jiffies + usecs_to_jiffies(timeout_us); sleep_lo = timeout_us > 100000u ? 1000u : 10u; sleep_hi = sleep_lo * 2u; observed = readl(bar0 + addr); while ((observed & mask) != val) { if (time_after_eq(jiffies, deadline)) { pr_err("gsp-phys-boot: stage 3e sequencer REG_POLL timeout after %u us: addr=0x%08x mask=0x%08x want=0x%08x observed=0x%08x err=0x%x\n", timeout_us, addr, mask, val, observed, error); return -ETIMEDOUT; } usleep_range(sleep_lo, sleep_hi); observed = readl(bar0 + addr); } return 0; } /* * CORE_RESUME (kgspExecuteSequencerCommand_TU102): reset the GSP falcon into * RISC-V, publish the libos init-arguments page in the GSP mailboxes, start * SEC2 to reload GSP-RM, wait for scratch14 BOOT_STAGE_3_HANDOFF (bit 26, the * reference _kgspIsReloadCompleted), then require SEC2 mailbox0 == 0 and the * RISC-V core active. Bounded throughout; reuses the stage-3d falcon reset and * the shared scratch14 reader. */ static int gsp_phys_seq_core_resume(struct gsp_phys_boot *boot, void __iomem *bar0) { unsigned long deadline; u32 m0, riscv; int ret; pr_info("gsp-phys-boot: stage 3e sequencer CORE_RESUME: reset GSP into RISC-V, libos args 0x%llx, start SEC2 (GSP-RM reload)\n", (u64)boot->libos_args_dma); ret = gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE); if (ret) { pr_err("gsp-phys-boot: stage 3e sequencer CORE_RESUME: GSP falcon reset failed (%d)\n", ret); return ret; } writel(lower_32_bits((u64)boot->libos_args_dma), bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX0); writel(upper_32_bits((u64)boot->libos_args_dma), bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX1); gsp_phys_falcon_start_cpu(bar0, GSP_PHYS_SEC2_BASE); deadline = jiffies + msecs_to_jiffies(GSP_PHYS_SEQ_RESUME_TIMEOUT_MS); for (;;) { struct gsp_phys_scratch14 s; gsp_phys_fwsec_scratch14_read(bar0, &s); if (s.booter_reload_acr_unload) /* BOOT_STAGE_3_HANDOFF */ break; if (time_after_eq(jiffies, deadline)) { gsp_phys_scratch14_log(bar0, "sequencer-resume-timeout"); pr_err("gsp-phys-boot: stage 3e sequencer CORE_RESUME: SEC2 reload not complete within %u ms\n", GSP_PHYS_SEQ_RESUME_TIMEOUT_MS); return -ETIMEDOUT; } usleep_range(1000, 2000); } m0 = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX0); if (m0 != 0) { pr_err("gsp-phys-boot: stage 3e sequencer CORE_RESUME: SEC2 M0=0x%08x (want 0)\n", m0); return -EIO; } riscv = readl(bar0 + GSP_PHYS_RISCV_STATUS); if (!(riscv & GSP_PHYS_RISCV_ACTIVE)) { pr_err("gsp-phys-boot: stage 3e sequencer CORE_RESUME: RISC-V core not active (RISCV_STATUS=0x%08x)\n", riscv); return -ENODEV; } gsp_phys_scratch14_log(bar0, "sequencer-resume-done"); pr_info("gsp-phys-boot: stage 3e sequencer CORE_RESUME complete: SEC2 M0=0, SEC2 M1=0x%08x, RISCV_STATUS=0x%08x\n", readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_MAILBOX1), riscv); return 0; } /* * Validate the fixed sequencer request header: a single-element message whose * bufferSizeDWord/cmdIndex are in range and whose command stream fits in the * bytes available. Parse-only, so a failed layout probe never executes. */ static int gsp_phys_seq_parse_req(const u8 *payload, u32 payload_len, u32 elem_count, u32 *buffer_size, u32 *cmd_index) { u32 bs, ci; if (elem_count != 1) { pr_err("gsp-phys-boot: sequencer message spans %u elements; only single-element buffers are supported\n", elem_count); return -EOPNOTSUPP; } if (payload_len < GSP_PHYS_SEQ_REQUEST_SIZE) { pr_err("gsp-phys-boot: sequencer payload too small (%u bytes, need %u)\n", payload_len, GSP_PHYS_SEQ_REQUEST_SIZE); return -EINVAL; } bs = gsp_phys_seq_dw(payload, 0); ci = gsp_phys_seq_dw(payload, 1); if (bs == 0 || bs > GSP_PHYS_SEQ_MAX_BUFFER_DWORDS) { pr_err("gsp-phys-boot: sequencer bufferSizeDWord=%u out of range (1..%u)\n", bs, GSP_PHYS_SEQ_MAX_BUFFER_DWORDS); return -EINVAL; } if (ci >= bs) { pr_err("gsp-phys-boot: sequencer cmdIndex=%u is not below bufferSizeDWord=%u\n", ci, bs); return -EINVAL; } if ((u64)GSP_PHYS_SEQ_REQUEST_SIZE + (u64)ci * 4u > (u64)payload_len) { pr_err("gsp-phys-boot: sequencer cmdIndex=%u needs %llu payload bytes but the element holds %u\n", ci, (u64)GSP_PHYS_SEQ_REQUEST_SIZE + (u64)ci * 4u, payload_len); return -EINVAL; } *buffer_size = bs; *cmd_index = ci; return 0; } /* * Run a validated 0x1002 command stream against BAR0. Returns nonzero on any * execution failure (the caller leaves the 3d boot and the WPR2 teardown * untouched). */ static int gsp_phys_seq_execute_req(struct gsp_phys_boot *boot, void __iomem *bar0, resource_size_t bar0_len, u8 *payload, u32 cmd_index, u32 *cmd_count_out) { const u8 *cmd; u32 index = 0, cmds = 0; int ret; cmd = payload + GSP_PHYS_SEQ_REQUEST_SIZE; while (index < cmd_index) { u32 opcode = gsp_phys_seq_dw(cmd, index); u32 args = gsp_phys_seq_payload_dwords(opcode); u32 a0, a1, a2; index++; if (args == U32_MAX) { pr_err("gsp-phys-boot: sequencer unknown opcode %u at dword %u\n", opcode, index - 1); return -EINVAL; } if ((u64)index + (u64)args > (u64)cmd_index) { pr_err("gsp-phys-boot: sequencer opcode %u truncated at dword %u (cmdIndex=%u)\n", opcode, index, cmd_index); return -EINVAL; } a0 = args > 0 ? gsp_phys_seq_dw(cmd, index) : 0; a1 = args > 1 ? gsp_phys_seq_dw(cmd, index + 1) : 0; a2 = args > 2 ? gsp_phys_seq_dw(cmd, index + 2) : 0; switch (opcode) { case GSP_PHYS_SEQ_OP_REG_WRITE: if (!gsp_phys_seq_addr_valid(bar0, bar0_len, a0)) return -EINVAL; writel(a1, bar0 + a0); break; case GSP_PHYS_SEQ_OP_REG_MODIFY: if (!gsp_phys_seq_addr_valid(bar0, bar0_len, a0)) return -EINVAL; { u32 value = readl(bar0 + a0); value &= ~a1; value |= a2; writel(value, bar0 + a0); } break; case GSP_PHYS_SEQ_OP_REG_POLL: if (!gsp_phys_seq_addr_valid(bar0, bar0_len, a0)) return -EINVAL; ret = gsp_phys_seq_reg_poll(bar0, a0, a1, a2, gsp_phys_seq_dw(cmd, index + 3), gsp_phys_seq_dw(cmd, index + 4)); if (ret) return ret; break; case GSP_PHYS_SEQ_OP_DELAY_US: if (a0 > GSP_PHYS_SEQ_DELAY_MAX_US) { pr_info("gsp-phys-boot: sequencer DELAY_US %u clamped to %u us\n", a0, GSP_PHYS_SEQ_DELAY_MAX_US); a0 = GSP_PHYS_SEQ_DELAY_MAX_US; } if (a0 <= GSP_PHYS_SEQ_UDELAY_MAX_US) udelay(a0); else msleep(DIV_ROUND_UP(a0, 1000)); break; case GSP_PHYS_SEQ_OP_REG_STORE: if (!gsp_phys_seq_addr_valid(bar0, bar0_len, a0)) return -EINVAL; if (a1 >= GSP_PHYS_SEQ_REG_SAVE_SIZE) { pr_err("gsp-phys-boot: sequencer REG_STORE index %u out of range (max %u)\n", a1, GSP_PHYS_SEQ_REG_SAVE_SIZE - 1); return -EINVAL; } put_unaligned_le32(readl(bar0 + a0), payload + offsetof(struct gsp_phys_seq_request, reg_save_area) + (size_t)a1 * 4u); break; case GSP_PHYS_SEQ_OP_CORE_RESET: ret = gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE); if (ret) return ret; gsp_phys_falcon_disable_ctx_req(bar0, GSP_PHYS_GSP_BASE, GSP_PHYS_GSP_FBIF_BASE); break; case GSP_PHYS_SEQ_OP_CORE_START: gsp_phys_falcon_start_cpu(bar0, GSP_PHYS_GSP_BASE); break; case GSP_PHYS_SEQ_OP_CORE_WAIT_FOR_HALT: ret = gsp_phys_falcon_wait_halt(bar0, GSP_PHYS_GSP_BASE, GSP_PHYS_SEQ_HALT_TIMEOUT_MS); if (ret) return ret; break; case GSP_PHYS_SEQ_OP_CORE_RESUME: ret = gsp_phys_seq_core_resume(boot, bar0); if (ret) return ret; break; } index += args; cmds++; } *cmd_count_out = cmds; return 0; } /* * Execute a 0x1002 inline command buffer from a status-queue element. The * reference layout (payload at element+80, rpc.length covering the 32-byte rpc * header) is validated first. The legacy stage-3e layout (payload at * element+68, rpc.length payload-only) is only considered when the reference * interpretation is implausible (its bufferSizeDWord is zero or out of range) * AND the legacy candidate parses in range itself; a plausible reference * layout is never reinterpreted. Commands are never executed twice. Returns * nonzero when no layout validates or execution fails. */ static int gsp_phys_seq_run_buffer(struct gsp_phys_boot *boot, void __iomem *bar0, resource_size_t bar0_len, const u8 *elem, u32 rpc_len, u32 elem_count, u32 *buffer_size_dwords, u32 *cmd_index_out, u32 *cmd_count_out, u32 *payload_off_out) { u8 *payload; u32 payload_len, buffer_size = 0, cmd_index = 0, cmds = 0; u32 offset = GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF; u32 ref_buffer_size = 0; bool legacy_layout = false; int ret; if (rpc_len < GSP_PHYS_MSG_RPC_HEADER_SIZE) { pr_err("gsp-phys-boot: sequencer rpc.length=%u is smaller than the %u-byte rpc header\n", rpc_len, GSP_PHYS_MSG_RPC_HEADER_SIZE); return -EINVAL; } /* Reference semantics: rpc.length includes the 32-byte rpc header. */ payload = (u8 *)elem + offset; payload_len = min_t(u32, rpc_len - GSP_PHYS_MSG_RPC_HEADER_SIZE, GSP_PHYS_MSG_SIZE - offset); if (payload_len >= 4u) ref_buffer_size = gsp_phys_seq_dw(payload, 0); ret = gsp_phys_seq_parse_req(payload, payload_len, elem_count, &buffer_size, &cmd_index); if (ret) { /* * Opt-in fallback: only a zero/out-of-range reference * bufferSizeDWord marks the reference interpretation as * implausible enough to try the legacy layout, and the legacy * candidate must parse in range on its own. */ if (elem_count != 1 || ref_buffer_size == 0 || ref_buffer_size > GSP_PHYS_SEQ_MAX_BUFFER_DWORDS) { /* Legacy fallback: payload at element+68, length * payload-only. */ offset = GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF_LEGACY; payload = (u8 *)elem + offset; payload_len = min_t(u32, rpc_len, GSP_PHYS_MSG_SIZE - offset); ret = gsp_phys_seq_parse_req(payload, payload_len, elem_count, &buffer_size, &cmd_index); if (ret) { pr_err("gsp-phys-boot: stage 3e sequencer: neither layout parses (element+%u failed, legacy element+%u failed %d, reference bufferSizeDWord=%u); refusing to execute\n", GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF, GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF_LEGACY, ret, ref_buffer_size); return ret; } legacy_layout = true; pr_warn("gsp-phys-boot: stage 3e sequencer: reference element+%u implausible (bufferSizeDWord=%u); using legacy element+%u (rpc.length=%u)\n", GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF, ref_buffer_size, GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF_LEGACY, rpc_len); } else { pr_err("gsp-phys-boot: stage 3e sequencer: element+%u layout failed (%d) with a plausible bufferSizeDWord=%u; not trying the legacy layout (would execute a reinterpretation)\n", GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF, ret, ref_buffer_size); return ret; } } ret = gsp_phys_seq_execute_req(boot, bar0, bar0_len, payload, cmd_index, &cmds); if (ret) return ret; if (!boot_stage.seq_dump_logged) { boot_stage.seq_dump_logged = true; gsp_phys_seq_log_buffer(payload, offset, buffer_size, cmd_index); } pr_info("gsp-phys-boot: stage 3e sequencer: layout=%s element+%u (bufferSizeDWord=%u cmdIndex=%u)\n", legacy_layout ? "legacy" : "reference", offset, buffer_size, cmd_index); *buffer_size_dwords = buffer_size; *cmd_index_out = cmd_index; *cmd_count_out = cmds; *payload_off_out = offset; return 0; } /* ============================================================ * Status-queue RX: ring walk, multi-element coalescing, reply wait * ============================================================ */ static void gsp_phys_rx_readptr_publish(u32 read_ptr); /* * 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. The reference computes this over the whole * logical record (all elements contiguous, message_queue_cpu.c:508), which is * the same as folding a coalesced multi-element buffer. */ 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); } /* * Bounded walk state for the status queue. read_ptr is the next ring slot the * host has not consumed (starts at 0, as the reference rxReadPtr does); * last_wp is the last observed GSP-RM writePtr for transition logging. */ struct gsp_phys_status_queue { bool linked; u32 entry_off; u32 msg_count; u32 read_ptr; u32 last_wp; u32 last_wp_logged; }; /* * One coalesced status-queue record. buf holds elem_count * 4096 bytes copied * from the ring (the reference receiver copies every element whole, * message_queue_cpu.c:617-649); record_len is the sender's logical length * (48 + rpc.length), payload starts at element+80. checksum is the XOR fold * of the first 8-byte-aligned record_len bytes: 0 means the transport * checksum validates. */ struct gsp_phys_rx_record { u8 *buf; u32 buf_len; u32 record_len; u32 payload_len; u32 elem_count; u32 first_slot; u32 function; u32 rpc_result; u32 rpc_result_private; u32 rpc_length; u32 seq_num; u32 rpc_sequence; u32 checksum; bool checksum_ok; }; static void gsp_phys_rx_record_free(struct gsp_phys_rx_record *rec) { kfree(rec->buf); memset(rec, 0, sizeof(*rec)); } /* * GSP-RM NOCAT journal records (status-queue function 0x1020, * NV_VGPU_MSG_EVENT_GSP_POST_NOCAT_RECORD). The RPC payload is the reference * NV2080CtrlNocatJournalInsertRecord (ctrl2080nvd.h): a u64 timestamp, then * two 65-byte strings around a packed u32 subsystem and an 8-byte-aligned u64 * errorCode, then a diagnostic buffer of up to 1 KB. Offsets below are * relative to the RPC payload (element offset 80) and were verified by * compiling the reference struct with NV_DECLARE_ALIGNED semantics and * offsetof; the static asserts at the end of this file re-check them against * the local re-declaration. In particular the 65-byte source ends at 89 and * subsystem is padded to 92; errorCode is 8-byte aligned at 96. * * A producer may truncate a record (diagBufferLen, or a short element), so * every field read is bounded by rec->payload_len: no byte is ever read past * the received record, and the strings are copied into terminated buffers * before they are printed. */ #define GSP_PHYS_NOCAT_OFF_RECTYPE 16 #define GSP_PHYS_NOCAT_OFF_BUGCHECK 20 #define GSP_PHYS_NOCAT_OFF_SOURCE 24 #define GSP_PHYS_NOCAT_LEN_SOURCE 65 #define GSP_PHYS_NOCAT_OFF_SUBSYSTEM 92 #define GSP_PHYS_NOCAT_OFF_ERRORCODE 96 #define GSP_PHYS_NOCAT_OFF_FAULTENGINE 104 #define GSP_PHYS_NOCAT_OFF_TDRREASON 172 #define GSP_PHYS_NOCAT_OFF_DIAGBUFLEN 176 #define GSP_PHYS_NOCAT_OFF_DIAGBUF 180 #define GSP_PHYS_NOCAT_MIN_PAYLOAD GSP_PHYS_NOCAT_OFF_DIAGBUF #define GSP_PHYS_NOCAT_FULL_PAYLOAD 1208 /* Local re-declaration of the reference record, only for the offset asserts. */ struct gsp_phys_nocat_journal_record { u32 flags; u64 timestamp __attribute__((aligned(8))); u8 rec_type; u32 bugcheck; char source[GSP_PHYS_NOCAT_LEN_SOURCE]; u32 subsystem; u64 error_code __attribute__((aligned(8))); char faulting_engine[GSP_PHYS_NOCAT_LEN_SOURCE]; u32 tdr_reason; u32 diag_buffer_len; u8 diag_buffer[1024]; }; /* * Unique-record summary, keyed on recType/errorCode/subsystem/bugcheck plus * the truncated source/faultingEngine strings. Static storage so the capture * never adds a large frame; 48 slots then count overflow separately. */ #define GSP_PHYS_NOCAT_SUMMARY_SLOTS 48 #define GSP_PHYS_NOCAT_SUMMARY_STR 48 struct gsp_phys_nocat_slot { u8 rec_type; u64 error_code; u32 subsystem; u32 bugcheck; u32 count; u32 first_seq; u32 last_seq; char source[GSP_PHYS_NOCAT_SUMMARY_STR]; char faulting_engine[GSP_PHYS_NOCAT_SUMMARY_STR]; }; static struct gsp_phys_nocat_slot gsp_phys_nocat_slots[GSP_PHYS_NOCAT_SUMMARY_SLOTS]; static u32 gsp_phys_nocat_slot_count; static u32 gsp_phys_nocat_record_count; static u32 gsp_phys_nocat_overflow; static void gsp_phys_nocat_reset(void) { memset(gsp_phys_nocat_slots, 0, sizeof(gsp_phys_nocat_slots)); gsp_phys_nocat_slot_count = 0; gsp_phys_nocat_record_count = 0; gsp_phys_nocat_overflow = 0; } /* * Copy at most max_chars bytes of payload[off .. payload_len) into dst and * NUL-terminate. A field outside the received payload yields an empty * string; no read ever crosses payload_len. */ static void gsp_phys_nocat_copy_str(char *dst, u32 dst_size, const u8 *payload, u32 payload_len, u32 off, u32 max_chars) { u32 n = 0; if (off < payload_len) { n = payload_len - off; if (n > max_chars) n = max_chars; } if (n >= dst_size) n = dst_size - 1; if (n) memcpy(dst, payload + off, n); dst[n] = '\0'; } static void gsp_phys_nocat_accumulate(u8 rec_type, u64 error_code, u32 subsystem, u32 bugcheck, const char *source, const char *faulting_engine, u32 seq) { struct gsp_phys_nocat_slot *slot; u32 i; gsp_phys_nocat_record_count++; for (i = 0; i < gsp_phys_nocat_slot_count; i++) { slot = &gsp_phys_nocat_slots[i]; if (slot->rec_type == rec_type && slot->error_code == error_code && slot->subsystem == subsystem && slot->bugcheck == bugcheck && strncmp(slot->source, source, GSP_PHYS_NOCAT_SUMMARY_STR - 1) == 0 && strncmp(slot->faulting_engine, faulting_engine, GSP_PHYS_NOCAT_SUMMARY_STR - 1) == 0) { slot->count++; slot->last_seq = seq; return; } } if (gsp_phys_nocat_slot_count >= GSP_PHYS_NOCAT_SUMMARY_SLOTS) { gsp_phys_nocat_overflow++; return; } slot = &gsp_phys_nocat_slots[gsp_phys_nocat_slot_count++]; slot->rec_type = rec_type; slot->error_code = error_code; slot->subsystem = subsystem; slot->bugcheck = bugcheck; slot->count = 1; slot->first_seq = seq; slot->last_seq = seq; strscpy(slot->source, source, sizeof(slot->source)); strscpy(slot->faulting_engine, faulting_engine, sizeof(slot->faulting_engine)); } static void gsp_phys_nocat_record(const struct gsp_phys_rx_record *rec) { const u8 *p = rec->buf + GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF; u32 len = rec->payload_len; char source[GSP_PHYS_NOCAT_LEN_SOURCE]; char engine[GSP_PHYS_NOCAT_LEN_SOURCE]; u8 rec_type = 0; u32 subsystem = 0; u32 bugcheck = 0; u64 error_code = 0; if (len > rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF) len = rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF; if (len > GSP_PHYS_NOCAT_OFF_RECTYPE) rec_type = p[GSP_PHYS_NOCAT_OFF_RECTYPE]; if (len >= GSP_PHYS_NOCAT_OFF_BUGCHECK + 4) bugcheck = get_unaligned_le32(p + GSP_PHYS_NOCAT_OFF_BUGCHECK); if (len >= GSP_PHYS_NOCAT_OFF_SUBSYSTEM + 4) subsystem = get_unaligned_le32(p + GSP_PHYS_NOCAT_OFF_SUBSYSTEM); if (len >= GSP_PHYS_NOCAT_OFF_ERRORCODE + 8) error_code = get_unaligned_le64(p + GSP_PHYS_NOCAT_OFF_ERRORCODE); gsp_phys_nocat_copy_str(source, sizeof(source), p, len, GSP_PHYS_NOCAT_OFF_SOURCE, GSP_PHYS_NOCAT_LEN_SOURCE); gsp_phys_nocat_copy_str(engine, sizeof(engine), p, len, GSP_PHYS_NOCAT_OFF_FAULTENGINE, GSP_PHYS_NOCAT_LEN_SOURCE); gsp_phys_nocat_accumulate(rec_type, error_code, subsystem, bugcheck, source, engine, rec->seq_num); if (gsp_phys_nocat_param) pr_info("gsp-nocat: rec seq=%u recType=%u errorCode=0x%llx subsystem=0x%x bugcheck=0x%x payload=%u source=\"%s\" faultingEngine=\"%s\"\n", rec->seq_num, rec_type, error_code, subsystem, bugcheck, len, source, engine); } static void gsp_phys_nocat_summary_dump(void) { u32 i; pr_info("gsp-nocat: summary: %u unique, %u total, %u overflow\n", gsp_phys_nocat_slot_count, gsp_phys_nocat_record_count, gsp_phys_nocat_overflow); for (i = 0; i < gsp_phys_nocat_slot_count; i++) { const struct gsp_phys_nocat_slot *slot = &gsp_phys_nocat_slots[i]; pr_info("gsp-nocat: entry[%u]: count=%u recType=%u errorCode=0x%llx subsystem=0x%x bugcheck=0x%x firstSeq=%u lastSeq=%u source=\"%s\" faultingEngine=\"%s\"\n", i, slot->count, slot->rec_type, slot->error_code, slot->subsystem, slot->bugcheck, slot->first_seq, slot->last_seq, slot->source, slot->faulting_engine); } } static void gsp_phys_status_queue_init(struct gsp_phys_status_queue *sq) { memset(sq, 0, sizeof(*sq)); } /* Ring distance from `from` to `to` in a `count`-slot ring. */ static u32 gsp_phys_ring_distance(u32 from, u32 to, u32 count) { u32 d = to + count - from; if (d >= count) d -= count; return d; } /* * Validate the GSP-RM-published status TX header (msgqTxCreate layout) and * switch the walk state to linked. The walk resumes from the persisted host * read pointer (0 on the first link, the consumed cursor afterwards) so a * later probe never rewinds the ring. A not-yet-linked header is -EAGAIN * with a rate-limited log; a malformed header stays unlinked (the caller * keeps polling until its own timeout). Read-only. */ static int gsp_phys_status_queue_link(struct gsp_phys_status_queue *sq, const struct gsp_phys_boot_chunk *q) { const struct gsp_phys_msgq_tx_header *tx; u32 version, size, msg_size, msg_count, wp, rx_hdr_off, entry_off; if (!q->pages || sq->linked) return -EINVAL; tx = gsp_phys_chunk_page_va(q, GSP_PHYS_QUEUE_STAT_OFF / GSP_PHYS_PAGE_SIZE); wp = le32_to_cpu(READ_ONCE(tx->write_ptr)); version = le32_to_cpu(READ_ONCE(tx->version)); size = le32_to_cpu(READ_ONCE(tx->size)); msg_size = le32_to_cpu(READ_ONCE(tx->msg_size)); msg_count = le32_to_cpu(READ_ONCE(tx->msg_count)); rx_hdr_off = le32_to_cpu(READ_ONCE(tx->rx_hdr_off)); entry_off = le32_to_cpu(READ_ONCE(tx->entry_off)); if (version == 0 && size == GSP_PHYS_QUEUE_STAT_SIZE && msg_size == GSP_PHYS_MSG_SIZE && msg_count != 0 && (entry_off % msg_size) == 0 && rx_hdr_off >= sizeof(*tx) && entry_off >= rx_hdr_off + 4 && entry_off + (u64)msg_count * msg_size <= size) { /* * Resume from the persisted host read pointer: stage 3e and a * later debugfs probe must never rewind the cursor and re-read * consumed records (GSP-RM derives its free space from it). */ if (boot_stage.status_read_ptr >= msg_count) { pr_warn("gsp-phys-boot: status queue persisted readPtr=%u outside the ring (msgCount=%u); restarting at slot 0\n", boot_stage.status_read_ptr, msg_count); boot_stage.status_read_ptr = 0; } sq->linked = true; sq->entry_off = entry_off; sq->msg_count = msg_count; sq->read_ptr = boot_stage.status_read_ptr; sq->last_wp = wp; sq->last_wp_logged = wp; pr_info("gsp-phys-boot: status queue TX header linked (host read-only): size=0x%x msgSize=%u msgCount=%u flags=0x%x rxHdrOff=0x%x entryOff=0x%x writePtr slot=%u (%s from slot %u)\n", size, msg_size, msg_count, le32_to_cpu(READ_ONCE(tx->flags)), rx_hdr_off, entry_off, wp, sq->read_ptr == 0 ? "initial, scanning" : "resuming", sq->read_ptr); return 0; } if (wp != sq->last_wp_logged) { pr_info("gsp-phys-boot: status queue writePtr=%u but TX header not linked yet (version=%u size=0x%x msgSize=%u msgCount=%u)\n", wp, version, size, msg_size, msg_count); sq->last_wp_logged = wp; } return -EAGAIN; } /* * Consume one fully-published status-queue record: read writePtr (published * after the payload, so a dma_rmb() orders the element reads after it), take * elemCount from the first element, require that many slots to be published, * copy every element page into a kmalloc'd contiguous buffer (walking the * ring), fold the checksum, then publish the new host readPtr so GSP-RM's * status TX ring keeps free space. Returns 0 with rec filled, -EAGAIN when * nothing is available, -EIO on a malformed element (nothing consumed) and * -ENOMEM. The caller owns rec->buf. */ static int gsp_phys_status_queue_next(struct gsp_phys_status_queue *sq, struct gsp_phys_rx_record *rec) { const struct gsp_phys_boot_chunk *q = &boot_stage.queue; const struct gsp_phys_msgq_element *e; u8 *stat, *buf; u32 wp, available, elem_count, i, slot, src; u32 rpc_length, record_len, aligned_len, new_read; if (!sq->linked) return -EAGAIN; stat = gsp_phys_chunk_page_va(q, GSP_PHYS_QUEUE_STAT_OFF / GSP_PHYS_PAGE_SIZE); wp = le32_to_cpu(READ_ONCE(*(const __le32 *) (stat + GSP_PHYS_MSGQ_TX_WRITEPTR_OFF))); dma_rmb(); if (wp >= sq->msg_count) { pr_err("gsp-phys-boot: status queue writePtr=%u outside the ring (msgCount=%u); refusing to walk\n", wp, sq->msg_count); return -EIO; } if (wp != sq->last_wp) { pr_info("gsp-phys-boot: status queue writePtr (ring slot) %u -> %u\n", sq->last_wp, wp); sq->last_wp = wp; } available = gsp_phys_ring_distance(sq->read_ptr, wp, sq->msg_count); if (available == 0) return -EAGAIN; slot = sq->read_ptr; /* * entry_off and msg_size are validated page-aligned/page-sized, so * each element slot is exactly one chunk page: index it through the * chunk page helper (the per-page allocation fallback is not * virtually contiguous). */ e = (const struct gsp_phys_msgq_element *) gsp_phys_chunk_page_va(q, (GSP_PHYS_QUEUE_STAT_OFF + sq->entry_off) / GSP_PHYS_PAGE_SIZE + slot); elem_count = le32_to_cpu(READ_ONCE(e->elem_count)); dma_rmb(); rpc_length = le32_to_cpu(READ_ONCE(e->rpc_length)); if (elem_count == 0 || elem_count > GSP_PHYS_MSG_ELEMENTS_MAX) { pr_err("gsp-phys-boot: status queue slot %u elemCount=%u is out of range (1..%u); refusing to walk\n", slot, elem_count, GSP_PHYS_MSG_ELEMENTS_MAX); return -EIO; } if (elem_count > available) { pr_err("gsp-phys-boot: status queue slot %u elemCount=%u exceeds the %u published slot(s); refusing to walk\n", slot, elem_count, available); return -EIO; } if (rpc_length < GSP_PHYS_MSG_RPC_HEADER_SIZE || (u64)GSP_PHYS_MSG_RPC_HEADER_OFF + rpc_length > (u64)elem_count * GSP_PHYS_MSG_SIZE) { pr_err("gsp-phys-boot: status queue slot %u rpc.length=%u does not fit %u element(s); refusing to walk\n", slot, rpc_length, elem_count); return -EIO; } buf = kmalloc((size_t)elem_count * GSP_PHYS_MSG_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; for (i = 0; i < elem_count; i++) { u8 *src_va; src = sq->read_ptr + i; if (src >= sq->msg_count) src -= sq->msg_count; src_va = gsp_phys_chunk_page_va(q, (GSP_PHYS_QUEUE_STAT_OFF + sq->entry_off) / GSP_PHYS_PAGE_SIZE + src); memcpy(buf + (size_t)i * GSP_PHYS_MSG_SIZE, src_va, GSP_PHYS_MSG_SIZE); } record_len = GSP_PHYS_MSG_RPC_HEADER_OFF + rpc_length; aligned_len = (record_len + 7u) & ~7u; rec->buf = buf; rec->buf_len = elem_count * GSP_PHYS_MSG_SIZE; rec->record_len = record_len; rec->payload_len = rpc_length - GSP_PHYS_MSG_RPC_HEADER_SIZE; rec->elem_count = elem_count; rec->first_slot = slot; rec->function = le32_to_cpu(READ_ONCE(e->rpc_function)); rec->rpc_result = le32_to_cpu(READ_ONCE(e->rpc_result)); rec->rpc_result_private = le32_to_cpu(READ_ONCE(e->rpc_result_private)); rec->rpc_length = rpc_length; rec->seq_num = le32_to_cpu(READ_ONCE(e->seq_num)); rec->rpc_sequence = le32_to_cpu(READ_ONCE(e->rpc_sequence)); rec->checksum = gsp_phys_msgq_checksum(buf, aligned_len); rec->checksum_ok = rec->checksum == 0; new_read = sq->read_ptr + elem_count; if (new_read >= sq->msg_count) new_read -= sq->msg_count; sq->read_ptr = new_read; boot_stage.status_read_ptr = new_read; gsp_phys_rx_readptr_publish(new_read); return 0; } /* * Bounded wait for a status-queue record whose rpc.function equals * `function`, optionally also matching rpc.sequence. Every other record is * logged and consumed (its readPtr advances) so it cannot block the matching * reply. GSP-RM's own client receive matches the function only * (kernel_gsp.c:1735 `pMsgHdr->function == expectedFunc`), so the debugfs * probe uses match_sequence=false; the parameter covers variants that carry * a meaningful rpc.sequence. Returns 0 with rec filled on a match, * -ETIMEDOUT on the bound, or a negative transport error. The caller owns * rec->buf. */ static int gsp_phys_status_wait(struct gsp_phys_status_queue *sq, u32 function, bool match_sequence, u32 sequence, u32 timeout_ms, struct gsp_phys_rx_record *rec) { unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms); int ret; for (;;) { struct gsp_phys_rx_record one; if (!sq->linked) { ret = gsp_phys_status_queue_link(sq, &boot_stage.queue); if (ret == -EAGAIN) { if (time_after_eq(jiffies, deadline)) return -ETIMEDOUT; msleep(GSP_PHYS_RM_PROBE_POLL_MS); continue; } if (ret) return ret; } ret = gsp_phys_status_queue_next(sq, &one); if (ret == -EAGAIN) { if (time_after_eq(jiffies, deadline)) return -ETIMEDOUT; msleep(GSP_PHYS_RM_PROBE_POLL_MS); continue; } if (ret) return ret; pr_info("gsp-phys-boot: rm-probe: status record slot %u (seqNum=%u elemCount=%u rpc.len=%u rpc.seq=%u checksum=0x%08x): function=0x%x rpc_result=%u\n", one.first_slot, one.seq_num, one.elem_count, one.rpc_length, one.rpc_sequence, one.checksum, one.function, one.rpc_result); if (one.function == function && (!match_sequence || one.rpc_sequence == sequence)) { *rec = one; return 0; } gsp_phys_rx_record_free(&one); } } /* * Publish the host's consumed status-queue read pointer (a ring slot index) * into the command-queue RX header. With MSGQ_FLAGS_SWAP_RX on both queues the * consumer-side read pointer lives at commandQueue + tx.rxHdrOff: msgqTxCreate * sets pOurRxHdr there and msgqRxMarkConsumed writes pReadOutgoing * (= pOurRxHdr->readPtr) when the queues are swapped (msgq.c:254-270, * 659-700). GSP-RM derives its status-queue TX free space from this pointer, * so without it the 63-slot status ring could fill after 63 events. Only real * consumption may call this: the read-only debugfs snapshot must never * advance the cursor. */ static void gsp_phys_rx_readptr_publish(u32 read_ptr) { const struct gsp_phys_boot_chunk *q = &boot_stage.queue; const struct gsp_phys_msgq_tx_header *cmd_hdr; __le32 *rx_ptr; u32 flags, rx_hdr_off, entry_off; if (!q->pages) return; cmd_hdr = gsp_phys_chunk_page_va(q, GSP_PHYS_QUEUE_CMD_OFF / GSP_PHYS_PAGE_SIZE); flags = le32_to_cpu(READ_ONCE(cmd_hdr->flags)); 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 (!(flags & GSP_PHYS_MSG_FLAGS_SWAP_RX) || rx_hdr_off < GSP_PHYS_MSGQ_TX_WRITEPTR_OFF + 4 || rx_hdr_off + 4 > entry_off) { pr_warn("gsp-phys-boot: cannot publish the status-queue readPtr (%u): cmd TX flags=0x%x rxHdrOff=0x%x entryOff=0x%x\n", read_ptr, flags, rx_hdr_off, entry_off); return; } rx_ptr = (__le32 *)((u8 *)cmd_hdr + rx_hdr_off); pr_debug("gsp-phys-boot: status-queue readPtr -> %u (cmd+0x%x)\n", read_ptr, rx_hdr_off); WRITE_ONCE(*rx_ptr, cpu_to_le32(read_ptr)); dma_wmb(); } void gsp_phys_boot_rmwait(struct gsp_phys_boot *boot, void __iomem *bar0, resource_size_t bar0_len) { struct gsp_phys_status_queue sq; unsigned long start_jiffies, deadline; /* * The summary is per stage-3e poll: reset it on every entry and dump it * on every exit (success, transport error, GSP-RM failure or timeout) * so a failed boot always leaves the NOCAT record summary in dmesg. */ if (!boot || !boot_stage.ran) { pr_warn("gsp-phys-boot: stage 3e skipped: stage 3d did not complete\n"); gsp_phys_nocat_reset(); goto out; } gsp_phys_nocat_reset(); gsp_phys_status_queue_init(&sq); start_jiffies = jiffies; deadline = start_jiffies + msecs_to_jiffies(GSP_PHYS_RM_INIT_TIMEOUT_MS); pr_info("gsp-phys-boot: stage 3e: polling status queue at shared+0x%x (writePtr @ +0x%x), timeout %u ms\n", GSP_PHYS_QUEUE_STAT_OFF, GSP_PHYS_MSGQ_TX_WRITEPTR_OFF, GSP_PHYS_RM_INIT_TIMEOUT_MS); for (;;) { if (!sq.linked) gsp_phys_status_queue_link(&sq, &boot_stage.queue); if (sq.linked) { for (;;) { struct gsp_phys_rx_record rec; int rret = gsp_phys_status_queue_next(&sq, &rec); if (rret == -EAGAIN) break; if (rret) { pr_err("gsp-phys-boot: stage 3e incomplete: status-queue record walk failed (%d); stage 3d done, WPR2 teardown remains valid\n", rret); goto out; } pr_info("gsp-phys-boot: status queue element slot %u (seqNum=%u elemCount=%u): function=0x%x rpc_result=%u\n", rec.first_slot, rec.seq_num, rec.elem_count, rec.function, rec.rpc_result); if (rec.function == GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD) gsp_phys_nocat_record(&rec); if (rec.function == GSP_PHYS_MSG_FUNC_RUN_CPU_SEQUENCER) { u32 seq_buffer = 0, seq_index = 0; u32 seq_cmds = 0, seq_off = 0; const u8 *seq_payload; struct gsp_phys_scratch14 s14; int sret; if (!bar0 || !bar0_len) { pr_err("gsp-phys-boot: stage 3e incomplete: sequencer 0x%x arrived without a BAR0 window; stage 3d done, WPR2 teardown remains valid\n", rec.function); gsp_phys_rx_record_free(&rec); goto out; } if (rec.rpc_length > GSP_PHYS_MSG_SIZE - GSP_PHYS_MSG_RPC_HEADER_OFF) { pr_err("gsp-phys-boot: stage 3e incomplete: sequencer 0x%x rpc.length=%u exceeds the %u-byte element (slot %u); stage 3d done, WPR2 teardown remains valid\n", rec.function, rec.rpc_length, GSP_PHYS_MSG_SIZE, rec.first_slot); gsp_phys_rx_record_free(&rec); goto out; } sret = gsp_phys_seq_run_buffer(boot, bar0, bar0_len, rec.buf, rec.rpc_length, rec.elem_count, &seq_buffer, &seq_index, &seq_cmds, &seq_off); if (sret) { pr_err("gsp-phys-boot: stage 3e incomplete: sequencer 0x%x execution failed (%d) at slot %u seqNum=%u; stage 3d done, WPR2 teardown remains valid\n", rec.function, sret, rec.first_slot, rec.seq_num); gsp_phys_rx_record_free(&rec); goto out; } seq_payload = rec.buf + seq_off; gsp_phys_fwsec_scratch14_read(bar0, &s14); pr_info("gsp-phys-boot: stage 3e: ran GSP-RM sequencer (cmdIndex=%u, %u cmds): bufferSizeDWord=0x%x rpcLength=0x%x payloadOff=%u regSaveArea={0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x} RISCV_STATUS=0x%x scratch14=0x%08x\n", seq_index, seq_cmds, seq_buffer, rec.rpc_length, seq_off, gsp_phys_seq_dw(seq_payload, 2), gsp_phys_seq_dw(seq_payload, 3), gsp_phys_seq_dw(seq_payload, 4), gsp_phys_seq_dw(seq_payload, 5), gsp_phys_seq_dw(seq_payload, 6), gsp_phys_seq_dw(seq_payload, 7), gsp_phys_seq_dw(seq_payload, 8), gsp_phys_seq_dw(seq_payload, 9), readl(bar0 + GSP_PHYS_RISCV_STATUS), s14.raw); } if (rec.function == GSP_PHYS_MSG_FUNC_GSP_INIT_DONE) { if (rec.rpc_result == 0) { boot_stage.rm_init_done = true; pr_info("gsp-phys-boot: stage 3e complete: GSP-RM init event 0x%x (rpc_result=0, seqNum=%u) after %u ms (writePtr slot=%u)\n", rec.function, rec.seq_num, jiffies_to_msecs(jiffies - start_jiffies), sq.last_wp); gsp_phys_rx_record_free(&rec); goto out; } pr_info("gsp-phys-boot: stage 3e incomplete: GSP-RM reported init failure (function=0x%x rpc_result=0x%x, seqNum=%u); stage 3d done, WPR2 teardown remains valid\n", rec.function, rec.rpc_result, rec.seq_num); gsp_phys_rx_record_free(&rec); goto out; } gsp_phys_rx_record_free(&rec); } } if (time_after_eq(jiffies, deadline)) { pr_info("gsp-phys-boot: stage 3e incomplete: no GSP-RM init event 0x%x in %u ms (writePtr=%u; stage 3d done, WPR2 teardown remains valid)\n", GSP_PHYS_MSG_FUNC_GSP_INIT_DONE, GSP_PHYS_RM_INIT_TIMEOUT_MS, sq.linked ? sq.last_wp : 0); goto out; } msleep(GSP_PHYS_RM_INIT_POLL_MS); } out: gsp_phys_nocat_summary_dump(); } /* ============================================================ * Command-queue TX: host -> GSP-RM msgq producer * ============================================================ */ /* * Submit one command-queue record. Validates the staged command-queue TX * header (the one GSP-RM linked against), logs it together with the peer * command read-pointer candidates and the status TX header, checks the * peer-derived free space, builds the record in a zeroed staging buffer, * copies it into the next element slot(s) and publishes writePtr under wmb() * before ringing the queue-head doorbell. * * Multi-element records follow message_queue_cpu.c:470-555 / msgq.c:472-559: * msgLen = 48 + rpc.length, elemCount = ceil(msgLen / 4096) <= 16, the * record is split on fixed 4 KB element boundaries (only the first element * carries the transport header; later elements are raw continuation bytes), * each element is copied to slot (writePtr + i) wrap-around, the checksum is * the XOR fold over the whole record up to the 8-byte-padded msgLen with the * checkSum field zeroed, and writePtr advances by elemCount once. The * reference's msgqTxGetWriteBuffer requires elemCount free slots * (msgqTxGetFreeSpace semantics: txFree = peerReadPtr + msgCount - writePtr * - 1). Bounded by the 16-element cap. * * Pointer placement (msgq.c MSGQ_FLAGS_SWAP_RX): with both sides flagged the * incoming read pointer is status + (status tx header rxHdrOff), exactly where * the emulated GSP-RM publishes its cmd_read (gsp_rm_poll_commands); without * the flag on the status header it is cmd + rxHdrOff (pReadIncoming = * pOurRxHdr). Both candidates are read and logged; the status flag selects. */ 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_flags, stat_wp, cmd_read = 0; u32 cmd_read_cmd = 0, cmd_read_status = 0, tx_free; u32 msg_len, aligned_len, elem_count, checksum, new_wp, slot; u32 i, slot_i; bool stat_linked, stat_rx_swapped, peer_from_status; 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 == 0 || elem_count > GSP_PHYS_MSG_ELEMENTS_MAX) { pr_err("gsp-phys-boot: TX refused: message spans %u elements (max %u)\n", elem_count, GSP_PHYS_MSG_ELEMENTS_MAX); 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 peer read * pointer candidates for our command queue. MSGQ_FLAGS_SWAP_RX on the * status header selects status+rxHdrOff; otherwise the incoming read * pointer is the command queue's own RX header at cmd+rxHdrOff. * Best-effort: the reference allows TX before its RX link, so a * not-yet-linked status header is 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_flags = le32_to_cpu(READ_ONCE(stat_hdr->flags)); 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 >= sizeof(*stat_hdr) && stat_rx_hdr_off + 4 <= stat_entry_off; stat_rx_swapped = (stat_flags & GSP_PHYS_MSG_FLAGS_SWAP_RX) != 0; cmd_read_cmd = le32_to_cpu(READ_ONCE(*(const __le32 *) ((const u8 *)cmd_hdr + rx_hdr_off))); if (stat_linked) cmd_read_status = le32_to_cpu(READ_ONCE(*(const __le32 *) ((const u8 *)stat_hdr + stat_rx_hdr_off))); peer_from_status = stat_linked && stat_rx_swapped; cmd_read = peer_from_status ? cmd_read_status : cmd_read_cmd; /* * F7 sanity check: free space from the peer readPtr, exactly as * msgqTxGetFreeSpace (msgq.c:445-470). Before the RX link the * reference uses its cached txFree = msgCount - 1. */ if (stat_linked) { if (cmd_read >= msg_count) { pr_err("gsp-phys-boot: TX refused: peer readPtr=%u is outside the command ring (msgCount=%u)\n", cmd_read, msg_count); return -EIO; } tx_free = cmd_read + msg_count - wp - 1; if (tx_free >= msg_count) tx_free -= msg_count; if (tx_free < elem_count) { pr_err("gsp-phys-boot: TX refused: command queue full (txFree=%u < elemCount=%u; peer readPtr=%u writePtr=%u msgCount=%u)\n", tx_free, elem_count, cmd_read, wp, msg_count); return -EBUSY; } } else { tx_free = msg_count - 1; } 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 txFree=%u; peer readPtr candidates: cmd+0x%x=%u, status+0x%x=%u (status flags=0x%x swapRx=%u) -> %s; status hdr v%u writePtr=%u msgCount=%u %s\n", version, size, msg_size, msg_count, entry_off, rx_hdr_off, wp, tx_free, rx_hdr_off, cmd_read_cmd, stat_rx_hdr_off, cmd_read_status, stat_flags, stat_rx_swapped, stat_linked ? (peer_from_status ? "status+rxHdrOff" : "cmd+rxHdrOff") : "unlinked, cached txFree", stat_version, stat_wp, stat_msg_count, stat_linked ? "(status TX linked)" : "(WARNING: status TX header not linked)"); /* * One zeroed staging buffer holds the whole record; only the first * element carries the transport header (message_queue_cpu.c:466-472 * pads the working copy to 8 bytes and the checksum covers msgLen). * kvzalloc because a 16-element record is 64 KB. */ elem = kvzalloc((size_t)elem_count * GSP_PHYS_MSG_SIZE, GFP_KERNEL); if (!elem) return -ENOMEM; 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 each element is * exactly one chunk page: index it through the chunk page helper (the * per-page fallback layout is not virtually contiguous). Slots wrap * per element, as msgqTxGetWriteBuffer(handle, i) does. */ for (i = 0; i < elem_count; i++) { slot_i = wp + i; if (slot_i >= msg_count) slot_i -= msg_count; slot_va = (u8 *)gsp_phys_chunk_page_va(q, (GSP_PHYS_QUEUE_CMD_OFF + entry_off) / GSP_PHYS_PAGE_SIZE + slot_i); memcpy(slot_va, elem + (size_t)i * GSP_PHYS_MSG_SIZE, msg_size); } kvfree(elem); /* Entries first, then writePtr. */ dma_wmb(); new_wp = wp + elem_count; 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)); /* * The doorbell is an MMIO write: wmb() orders the coherent-memory * writePtr store before it (dma_wmb() alone only orders DMA-visible * writes against each other and does not order the MMIO store). */ 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; } /* * Fill the GspSystemInfo payload from the claimed pci_dev, mirroring the * reference rpcGspSetSystemInfo_v17_00 field by field (rpc.c:9446-9604): * * - gpuPhys*: the Linux OS layer attaches the GPU with * nv->regs->cpu_address / nv->fb->cpu_address / * nv->bars[NV_GPU_BAR_INDEX_IMEM].cpu_address (osinit.c:703-708), all of * which are pci_resource_start() values of the memory BARs in order * (nv-pci.c:816-823: regs = first memory BAR, fb = second, imem = third). * gpuPhysIoAddr is never set on Linux and stays 0. * - nvDomainBusDeviceFunc = gpuEncodeDomainBusDevice(domain, bus, slot) with * domain bits 63:32, bus 15:8, slot (device) 7:0; nv-pci.c passes * NV_PCI_SLOT_NUMBER (PCI_SLOT(devfn), function excluded). * - PCI IDs/revision from the pci_dev. * - pciConfigMirror*: the BIF HAL's BAR0 offset/size (see the constant * comment above). * - FLR comes from the PCIe device capability (kbifCacheFlrSupport_TU102 * reads the same bit through the XVE mirror); the link capability is the * PCIe Link Capabilities register (kbifGetGpuLinkCapabilities_IMPL). * b64bBar0Supported/bMnocAvailable/pcieAtomics* are only ever set on * GA100+/GB100 BIFs, so the reference sends 0 on TU10x. * - ASPM/L1SS/bBehindBridge/upstreamAddressValid mirror chipset_pcie.c's * upstream-port walk (upstream port = pci_upstream_bridge()). * - maxUserVa = TASK_SIZE (osGetMaxUserVa -> os_get_max_user_va), host page * size = PAGE_SIZE, bIsPrimary from the VGA arbiter default device * (osinit.c:955: primary_vga || client_managed_console; the dGPU on this * host is a secondary device). * - Everything else is a documented conservative zero (hypervisorType, * SR-IOV VF info, grid, ACPI method data, system mux, POR flags, * passthru, console memory, dynamic-granularity arrays, TDR, * stretch-vblank, route display interrupts; Windows/display/optional * paths that are inactive in a bare-metal Linux headless boot). * bClockBoostSupported is 1 (the task-specified value for this board). * - sysTimerOffsetNs comes from the timer step that runs just before this * fill (gsp_phys_timer_set_current_time); TU10x/GV100 programs PTIMER * with wall time itself and leaves the offset 0. */ static void gsp_phys_preinit_fill_system_info( struct gsp_phys_gsp_system_info *info, struct pci_dev *pdev, u64 sys_timer_offset_ns) { struct pci_dev *bridge; u64 bars[3] = { 0, 0, 0 }; u32 cap = 0; unsigned int i, nbars = 0; memset(info, 0, sizeof(*info)); /* * The reference BAR walk: memory BARs with a nonzero start and size * in PCI config order (nv-pci.c:816-823). */ for (i = 0; i < 6 && nbars < ARRAY_SIZE(bars); i++) { if (!pci_resource_start(pdev, i) || !pci_resource_len(pdev, i)) continue; if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) continue; bars[nbars++] = pci_resource_start(pdev, i); } info->gpu_phys_addr = cpu_to_le64(bars[0]); info->gpu_phys_fb_addr = cpu_to_le64(bars[1]); info->gpu_phys_inst_addr = cpu_to_le64(bars[2]); info->gpu_phys_io_addr = cpu_to_le64(0); info->nv_domain_bus_device_func = cpu_to_le64( ((u64)pci_domain_nr(pdev->bus) << 32) | ((u64)pdev->bus->number << 8) | (u64)PCI_SLOT(pdev->devfn)); info->pci_config_mirror_base = cpu_to_le32(GSP_PHYS_PCI_CONFIG_MIRROR_BASE); info->pci_config_mirror_size = cpu_to_le32(GSP_PHYS_PCI_CONFIG_MIRROR_SIZE); info->pci_device_id = cpu_to_le32(pdev->device); info->pci_sub_device_id = cpu_to_le32(pdev->subsystem_device); info->pci_revision_id = cpu_to_le32(pdev->revision); info->oor_arch = 0; /* OOR_ARCH_X86_64 */ if (!pcie_capability_read_dword(pdev, PCI_EXP_DEVCAP, &cap) && (cap & PCI_EXP_DEVCAP_FLR)) info->b_flr_supported = 1; cap = 0; if (!pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &cap)) info->pcie_config_reg_link_cap = cpu_to_le32(cap); bridge = pci_upstream_bridge(pdev); if (bridge) { info->b_gpu_behind_bridge = 1; info->upstream_address_valid = 1; cap = 0; if (!pcie_capability_read_dword(bridge, PCI_EXP_LNKCAP, &cap)) { if (!(cap & PCI_EXP_LNKCAP_ASPM_L0S)) info->b_upstream_l0s_unsupported = 1; if (!(cap & PCI_EXP_LNKCAP_ASPM_L1)) info->b_upstream_l1_unsupported = 1; } else { info->b_upstream_l0s_unsupported = 1; info->b_upstream_l1_unsupported = 1; } cap = 0; i = pci_find_ext_capability(bridge, PCI_EXT_CAP_ID_L1SS); if (i && !pci_read_config_dword(bridge, i + PCI_L1SS_CTL1, &cap)) info->chipset_l1ss_enable = cpu_to_le32(cap); } info->max_user_va = cpu_to_le64((u64)TASK_SIZE); info->host_page_size = cpu_to_le64((u64)PAGE_SIZE); info->b_is_primary = (vga_default_device() == pdev) ? 1 : 0; info->b_clock_boost_supported = 1; info->b_route_disp_intrs_to_cpu = 0; /* Linux: NV_FALSE */ info->sys_timer_offset_ns = cpu_to_le64(sys_timer_offset_ns); } /* * Program the GPU system timer exactly where the reference does: after GFW * boot is complete (kgspWaitForGfwBootOk_HAL) and before the GSP-RM image is * started, "to avoid PLM collisions" and because the firmware consumes the * updated GPU time (kernel_gsp.c:3676-3683). Bare-metal TU10x dispatches to * the GV100 HAL (generated/g_objtmr_nvoc.c -> tmrSetCurrentTime_GV100, * timer/arch/volta/timer_gv100.c): * * 1. the write is only allowed while PTIMER_TIME_PRIV_LEVEL_MASK. * WRITE_PROTECTION_LEVEL0 (bit 4) is enabled; otherwise the reference * asserts and returns NV_ERR_PRIV_SEC_VIOLATION, and its caller ignores * the status - so a failed check is logged and the boot continues; * 2. the host wall clock is read with microsecond resolution * (osGetCurrentTime -> ktime_get_real_ts64; ns = (seconds * 1e6 + * useconds) * 1000); * 3. TIME_1 = high 32 bits, then the TIME_0 write triggers the load. * * The GV100/TU10x HAL computes no pTmr->sysTimerOffsetNs: that secure-timer * offset is GH100-only (timer_gh100.c), where PTIMER stays a counter and * GSP-RM adds the offset. Here PTIMER itself holds wall time, so the value * delivered in GspSystemInfo is 0. Returns that value. */ static u64 gsp_phys_timer_set_current_time(void __iomem *bar0) { struct timespec64 ts; u32 plm, seconds, useconds; u64 ns; plm = readl(bar0 + GSP_PHYS_PTIMER_TIME_PRIV_LEVEL_MASK); if (!(plm & GSP_PHYS_PTIMER_WRITE_PROT_LEVEL0)) { pr_warn("gsp-phys-boot: tmrSetCurrentTime skipped: PTIMER level-0 write protection is not enabled (PLM=0x%08x)\n", plm); return 0; } ktime_get_real_ts64(&ts); seconds = (u32)ts.tv_sec; useconds = (u32)(ts.tv_nsec / NSEC_PER_USEC); ns = ((u64)seconds * 1000000 + useconds) * 1000; writel(upper_32_bits(ns), bar0 + GSP_PHYS_PTIMER_TIME_1); writel(lower_32_bits(ns), bar0 + GSP_PHYS_PTIMER_TIME_0); pr_info("gsp-phys-boot: tmrSetCurrentTime: PTIMER_TIME_1/0 = 0x%08x%08x (%llu ns wall clock, %u s + %u us); sysTimerOffsetNs=0 (GV100/TU10x HAL programs PTIMER directly)\n", upper_32_bits(ns), lower_32_bits(ns), ns, seconds, useconds); return 0; } /* * Queue the two pre-boot async init RPCs the reference calls * kgspQueueAsyncInitRpcs (kernel_gsp.c:3358-3381): GSP_SET_SYSTEM_INFO (72, * rpcGspSetSystemInfo_v17_00, 928-byte payload) then SET_REGISTRY (73, * rpcSetRegistry_v17_00, the host driver's default registry profile plus * RmDisableDisplay=1). Both are fire-and-forget (no rpc_result wait): the * GSP-RM server's function table entries return "nothing" and no reply is * expected. The existing TX producer already has submit-only semantics, so * both records go through gsp_phys_queue_tx_submit with increasing seqNum. * The timer step runs first, mirroring the reference order in kgspInitRm * (timer, then init args, then kgspQueueAsyncInitRpcs, then the boot). */ static int gsp_phys_preinit_rpcs(void __iomem *bar0, struct pci_dev *pdev) { struct gsp_phys_gsp_system_info *info; u64 sys_timer_offset_ns; int ret; sys_timer_offset_ns = gsp_phys_timer_set_current_time(bar0); info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; gsp_phys_preinit_fill_system_info(info, pdev, sys_timer_offset_ns); pr_info("gsp-phys-boot: pre-boot GSP_SET_SYSTEM_INFO (fn%u) prepared: gpuPhys=0x%llx fb=0x%llx inst=0x%llx io=0x%llx dbf=0x%llx mirror=0x%x/0x%x ids=%04x:%04x:%02x maxUserVa=0x%llx pageSize=0x%llx flr=%u behindBridge=%u l1ss=0x%x lnkCap=0x%x primary=%u sysTimerOffsetNs=0x%llx\n", GSP_PHYS_RPC_FUNC_GSP_SET_SYSTEM_INFO, le64_to_cpu(info->gpu_phys_addr), le64_to_cpu(info->gpu_phys_fb_addr), le64_to_cpu(info->gpu_phys_inst_addr), le64_to_cpu(info->gpu_phys_io_addr), le64_to_cpu(info->nv_domain_bus_device_func), le32_to_cpu(info->pci_config_mirror_base), le32_to_cpu(info->pci_config_mirror_size), le32_to_cpu(info->pci_device_id), le32_to_cpu(info->pci_sub_device_id), info->pci_revision_id, le64_to_cpu(info->max_user_va), le64_to_cpu(info->host_page_size), info->b_flr_supported, info->b_gpu_behind_bridge, le32_to_cpu(info->chipset_l1ss_enable), le32_to_cpu(info->pcie_config_reg_link_cap), info->b_is_primary, le64_to_cpu(info->sys_timer_offset_ns)); ret = gsp_phys_queue_tx_submit(bar0, GSP_PHYS_RPC_FUNC_GSP_SET_SYSTEM_INFO, info, sizeof(*info)); kfree(info); if (ret) { pr_err("gsp-phys-boot: pre-boot GSP_SET_SYSTEM_INFO (fn%u) submit failed: %d\n", GSP_PHYS_RPC_FUNC_GSP_SET_SYSTEM_INFO, ret); return ret; } /* * Host default registry profile (37 dword entries, 1377 bytes): the * host driver's default parameter table plus RmDisableDisplay=1 (the * GSP-RM display-presence check treats display as absent so the display * engine is removed during pre-init) and RMDevidCheckIgnore=1 (escape * for the static-config devid/board check); the rest reproduces the * stock host environment. The payload is the generated * PACKED_REGISTRY_TABLE blob (header + entries + names), not aligned * to 8 bytes; the TX producer zero-pads the staging copy and folds the * checksum over the padded length, matching the reference checksum. */ pr_info("gsp-phys-boot: pre-boot SET_REGISTRY (fn%u) prepared: size=%u numEntries=%u (host default profile + RmDisableDisplay=1)\n", GSP_PHYS_RPC_FUNC_SET_REGISTRY, (unsigned int)GSP_PHYS_REGISTRY_IMAGE_SIZE, get_unaligned_le32(gsp_phys_registry_image + 4)); ret = gsp_phys_queue_tx_submit(bar0, GSP_PHYS_RPC_FUNC_SET_REGISTRY, gsp_phys_registry_image, GSP_PHYS_REGISTRY_IMAGE_SIZE); if (ret) { pr_err("gsp-phys-boot: pre-boot SET_REGISTRY (fn%u) submit failed: %d\n", GSP_PHYS_RPC_FUNC_SET_REGISTRY, ret); return ret; } pr_info("gsp-phys-boot: pre-boot async init RPCs queued (fn%u then fn%u); GSP-RM will consume them at early init before OBJGPU creation\n", GSP_PHYS_RPC_FUNC_GSP_SET_SYSTEM_INFO, GSP_PHYS_RPC_FUNC_SET_REGISTRY); return 0; } static DEFINE_MUTEX(gsp_phys_rm_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_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; } /* * GSP-RM must have signalled init-done (function 0x1001 with * rpc_result 0) before its RPC task accepts UNLOADING_GUEST_DRIVER; an * RPC sent before init-done can be dropped, wasting the one-shot * session. A boot without init-done is a quiet no-op here and release * falls through to quiesce + the existing fail-safe. */ if (!boot_stage.rm_init_done) { pr_debug("gsp-phys-boot: UNLOADING_GUEST_DRIVER skipped: GSP-RM init-done (function 0x%x) was not observed (primed=%u ran=%u)\n", GSP_PHYS_MSG_FUNC_GSP_INIT_DONE, 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_lock); return ret; } /* ============================================================ * Host RM probe: SET_GUEST_SYSTEM_INFO / GET_GSP_STATIC_INFO * ============================================================ */ static u32 gsp_phys_sci_u32(const u8 *p, u32 len, u32 off) { if (len < off + 4) return 0; return get_unaligned_le32(p + off); } static u64 gsp_phys_sci_u64(const u8 *p, u32 len, u32 off) { if (len < off + 8) return 0; return get_unaligned_le64(p + off); } static u8 gsp_phys_sci_u8(const u8 *p, u32 len, u32 off) { return len > off ? p[off] : 0; } static u16 gsp_phys_sci_u16(const u8 *p, u32 len, u32 off) { if (len < off + 2) return 0; return get_unaligned_le16(p + off); } static void gsp_phys_sci_string(char *dst, size_t dst_len, const u8 *p, u32 len, u32 off) { u32 n = 0; if (len > off) { n = min_t(u32, GSP_PHYS_SCI_GPU_NAME_LEN, len - off); n = min_t(u32, n, dst_len - 1); memcpy(dst, p + off, n); } dst[n] = '\0'; } /* * Decode the GspStaticConfigInfo fields this module needs. Every field is * bounds-checked against the reply payload length, so a short or older reply * logs only what it carries. The struct itself never lands on the stack. */ static void gsp_phys_sci_decode(const u8 *p, u32 len) { char name[GSP_PHYS_SCI_GPU_NAME_LEN + 1]; char short_name[GSP_PHYS_SCI_GPU_NAME_LEN + 1]; gsp_phys_sci_string(name, sizeof(name), p, len, GSP_PHYS_SCI_GPU_NAME_OFF); gsp_phys_sci_string(short_name, sizeof(short_name), p, len, GSP_PHYS_SCI_GPU_SHORT_NAME_OFF); pr_info("gsp-phys-boot: rm-probe: static-info raw payload=%u bytes (struct min %u)%s\n", len, GSP_PHYS_SCI_MIN_SIZE, len < GSP_PHYS_SCI_MIN_SIZE ? " SHORT: only present fields are decoded" : ""); pr_info("gsp-phys-boot: rm-probe: static-info internal: hClient=0x%08x hDevice=0x%08x hSubdevice=0x%08x bSplitVasBetweenServerClientRm=%u bSelfHostedMode=%u bAtsSupported=%u\n", gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_H_INTERNAL_CLIENT_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_H_INTERNAL_DEVICE_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_H_INTERNAL_SUBDEVICE_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_SPLIT_VAS_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_SELF_HOSTED_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_ATS_SUPPORTED_OFF)); pr_info("gsp-phys-boot: rm-probe: static-info fb: bar1PdeBase=0x%016llx bar2PdeBase=0x%016llx fb_length=0x%llx fbio_mask=0x%llx fbp_mask=0x%llx busWidth=%u ramType=%u l2CacheSize=%u\n", gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_BAR1_PDE_BASE_OFF), gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_BAR2_PDE_BASE_OFF), gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_FB_LENGTH_OFF), gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_FBIO_MASK_OFF), gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_FBP_MASK_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_FB_BUS_WIDTH_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_FB_RAM_TYPE_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_L2_CACHE_SIZE_OFF)); pr_info("gsp-phys-boot: rm-probe: static-info gpu: name='%s' short='%s' sriovMaxGfid=%u poisonFuse=%u bPageRetirement=%u bVbiosValid=%u vbiosSubVendor=0x%04x vbiosSubDevice=0x%04x\n", name, short_name, gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_SRIOV_MAX_GFID_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_POISON_FUSE_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_PAGE_RETIREMENT_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_VBIOS_VALID_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_VBIOS_SUB_VENDOR_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_VBIOS_SUB_DEVICE_OFF)); pr_info("gsp-phys-boot: rm-probe: static-info engineCaps=[0x%08x,0x%08x,0x%08x] rtd3BoardPower=%u rtd3PerstDelay=%u fwWpr{nonWpr=0x%llx frts=0x%llx} bIsGpuUefi=%u bIsEfiInit=%u bClRootportNeedsNosnoopWAR=%u\n", gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_ENGINE_CAPS_OFF), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_ENGINE_CAPS_OFF + 4), gsp_phys_sci_u32(p, len, GSP_PHYS_SCI_ENGINE_CAPS_OFF + 8), gsp_phys_sci_u16(p, len, GSP_PHYS_SCI_RTD3_TOTAL_BOARD_POWER_OFF), gsp_phys_sci_u16(p, len, GSP_PHYS_SCI_RTD3_PERST_DELAY_OFF), gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_FW_WPR_NONWPR_OFF), gsp_phys_sci_u64(p, len, GSP_PHYS_SCI_FW_WPR_FRTS_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_GPU_UEFI_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_EFI_INIT_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_CL_NOSNOOP_WAR_OFF)); } /* * One GET_GSP_STATIC_INFO attempt: submit the 4-byte request, wait up to * ~10 s for the matching reply and decode it. Matching is by rpc.function * only, as the reference client does (kernel_gsp.c:1735); the reply's * rpc.sequence and transport seqNum are logged. */ static int gsp_phys_rm_probe_fn65(void __iomem *bar0, bool after_fn1) { struct gsp_phys_rpc_get_gsp_static_info req; struct gsp_phys_status_queue sq; struct gsp_phys_rx_record rec; u32 tx_seq; int ret; memset(&req, 0, sizeof(req)); tx_seq = boot_stage.tx_seq_num; ret = gsp_phys_queue_tx_submit(bar0, GSP_PHYS_RPC_FUNC_GET_GSP_STATIC_INFO, &req, sizeof(req)); if (ret) { pr_err("gsp-phys-boot: rm-probe: GET_GSP_STATIC_INFO (65) submit failed: %d\n", ret); return ret; } pr_info("gsp-phys-boot: rm-probe: GET_GSP_STATIC_INFO (65) request submitted (payload %zu B, rpc.sequence=0, txSeqNum=%u%s); waiting up to %u ms for the reply\n", sizeof(req), tx_seq, after_fn1 ? ", after SET_GUEST_SYSTEM_INFO" : "", GSP_PHYS_RM_PROBE_TIMEOUT_MS); gsp_phys_status_queue_init(&sq); memset(&rec, 0, sizeof(rec)); ret = gsp_phys_status_wait(&sq, GSP_PHYS_RPC_FUNC_GET_GSP_STATIC_INFO, false, 0, GSP_PHYS_RM_PROBE_TIMEOUT_MS, &rec); if (ret) { pr_err("gsp-phys-boot: rm-probe: GET_GSP_STATIC_INFO (65) no reply within %u ms (%d)\n", GSP_PHYS_RM_PROBE_TIMEOUT_MS, ret); return ret; } pr_info("gsp-phys-boot: rm-probe: GET_GSP_STATIC_INFO (65) reply: rpc.length=%u payload=%u B elemCount=%u seqNum=%u rpc.seq=%u rpc_result=%u rpc_result_private=0x%x checksum=0x%08x%s\n", rec.rpc_length, rec.payload_len, rec.elem_count, rec.seq_num, rec.rpc_sequence, rec.rpc_result, rec.rpc_result_private, rec.checksum, rec.checksum_ok ? "" : " (WARNING: transport checksum does not fold to 0)"); boot_stage.rm_probe_last_function = GSP_PHYS_RPC_FUNC_GET_GSP_STATIC_INFO; boot_stage.rm_probe_last_payload_len = rec.payload_len; if (rec.rpc_result != 0) { pr_err("gsp-phys-boot: rm-probe: GET_GSP_STATIC_INFO (65) server rpc_result=0x%x (not success); no decode\n", rec.rpc_result); gsp_phys_rx_record_free(&rec); return -EIO; } gsp_phys_sci_decode(rec.buf + GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF, rec.payload_len); gsp_phys_rx_record_free(&rec); return 0; } /* * One SET_GUEST_SYSTEM_INFO attempt, filling the version fields the * reference CPU-RM sends (RmRpcSetGuestSystemInfo, rpc.c:7950-8129): * guestDriverVersion = 570.144, guestVersion = the r570_00 branch string, * guestTitle = the display-driver title, all buffer lengths 256, guestClNum * the official changelist, vgxVersion 0x29/0x0C. Waits for the fn-1 reply. */ static int gsp_phys_rm_probe_fn1(void __iomem *bar0) { struct gsp_phys_rpc_set_guest_system_info *req; struct gsp_phys_status_queue sq; struct gsp_phys_rx_record rec; int ret; req = kvzalloc(sizeof(*req), GFP_KERNEL); if (!req) return -ENOMEM; req->vgx_version_major_num = cpu_to_le32(0x29); req->vgx_version_minor_num = cpu_to_le32(0x0c); req->guest_driver_version_buffer_length = cpu_to_le32(256); req->guest_version_buffer_length = cpu_to_le32(256); req->guest_title_buffer_length = cpu_to_le32(256); req->guest_cl_num = cpu_to_le32(35817632); strscpy(req->guest_driver_version, "570.144", sizeof(req->guest_driver_version)); strscpy(req->guest_version, "rel/gpu_drv/r570/r570_00-407", sizeof(req->guest_version)); strscpy(req->guest_title, "Official r570_00 rel/gpu_drv/r570/r570_00-407 unknown", sizeof(req->guest_title)); ret = gsp_phys_queue_tx_submit(bar0, GSP_PHYS_RPC_FUNC_SET_GUEST_SYSTEM_INFO, req, sizeof(*req)); kvfree(req); if (ret) { pr_err("gsp-phys-boot: rm-probe: SET_GUEST_SYSTEM_INFO (1) submit failed: %d\n", ret); return ret; } pr_info("gsp-phys-boot: rm-probe: SET_GUEST_SYSTEM_INFO (1) request submitted (payload %zu B: vgx 41.12, cl %u); waiting up to %u ms for the reply\n", sizeof(*req), 35817632, GSP_PHYS_RM_PROBE_FN1_TIMEOUT_MS); gsp_phys_status_queue_init(&sq); memset(&rec, 0, sizeof(rec)); ret = gsp_phys_status_wait(&sq, GSP_PHYS_RPC_FUNC_SET_GUEST_SYSTEM_INFO, false, 0, GSP_PHYS_RM_PROBE_FN1_TIMEOUT_MS, &rec); if (ret) { pr_err("gsp-phys-boot: rm-probe: SET_GUEST_SYSTEM_INFO (1) no reply within %u ms (%d)\n", GSP_PHYS_RM_PROBE_FN1_TIMEOUT_MS, ret); return ret; } pr_info("gsp-phys-boot: rm-probe: SET_GUEST_SYSTEM_INFO (1) reply: rpc.length=%u payload=%u B elemCount=%u seqNum=%u rpc.seq=%u rpc_result=%u rpc_result_private=0x%x checksum=0x%08x\n", rec.rpc_length, rec.payload_len, rec.elem_count, rec.seq_num, rec.rpc_sequence, rec.rpc_result, rec.rpc_result_private, rec.checksum); boot_stage.rm_probe_last_function = GSP_PHYS_RPC_FUNC_SET_GUEST_SYSTEM_INFO; boot_stage.rm_probe_last_payload_len = rec.payload_len; ret = rec.rpc_result == 0 ? 0 : -EIO; gsp_phys_rx_record_free(&rec); return ret; } /* * One debugfs write: GET_GSP_STATIC_INFO; when it gets no reply, send * SET_GUEST_SYSTEM_INFO once and retry GET_GSP_STATIC_INFO once. Serialised * with the unload trigger by gsp_phys_rm_lock, and bounded by the per-wait * timeouts. Never touches WPR2 and never sends before GSP-RM init-done (the * caller gates on boot_stage.rm_init_done). At most one probe cycle per * write. */ static int gsp_phys_rm_probe(void __iomem *bar0) { int ret, fn1_ret; mutex_lock(&gsp_phys_rm_lock); /* * Defensive re-check: GSP-RM does not service RM RPCs before * init-done, so sending here could be dropped. */ if (!boot_stage.rm_init_done) { pr_info("gsp-phys-boot: rm-probe: no-op: GSP-RM init-done (function 0x%x, rpc_result 0) was not observed\n", GSP_PHYS_MSG_FUNC_GSP_INIT_DONE); mutex_unlock(&gsp_phys_rm_lock); return -EINVAL; } boot_stage.rm_probe_attempts++; ret = gsp_phys_rm_probe_fn65(bar0, false); if (ret) { pr_info("gsp-phys-boot: rm-probe: no static-info reply; sending SET_GUEST_SYSTEM_INFO (1) once, then retrying GET_GSP_STATIC_INFO (65) once\n"); fn1_ret = gsp_phys_rm_probe_fn1(bar0); if (fn1_ret) pr_err("gsp-phys-boot: rm-probe: SET_GUEST_SYSTEM_INFO (1) attempt returned %d; retrying GET_GSP_STATIC_INFO (65) anyway\n", fn1_ret); ret = gsp_phys_rm_probe_fn65(bar0, true); } boot_stage.rm_probe_done = true; boot_stage.rm_probe_last_ret = ret; mutex_unlock(&gsp_phys_rm_lock); return ret; } /* ============================================================ * Debugfs read-only state snapshot (no device writes) * ============================================================ */ /* * Decode and print one queue TX header. Returns true when the header is * self-consistent with its queue size (version 0, the staged sizes and a * ring that fits inside the queue), i.e. when a ring walk may trust it. */ static bool gsp_phys_state_tx_header(struct seq_file *m, const char *tag, const struct gsp_phys_msgq_tx_header *tx, u32 size_expected) { u32 version = le32_to_cpu(READ_ONCE(tx->version)); u32 size = le32_to_cpu(READ_ONCE(tx->size)); u32 msg_size = le32_to_cpu(READ_ONCE(tx->msg_size)); u32 msg_count = le32_to_cpu(READ_ONCE(tx->msg_count)); u32 write_ptr = le32_to_cpu(READ_ONCE(tx->write_ptr)); u32 flags = le32_to_cpu(READ_ONCE(tx->flags)); u32 rx_hdr_off = le32_to_cpu(READ_ONCE(tx->rx_hdr_off)); u32 entry_off = le32_to_cpu(READ_ONCE(tx->entry_off)); bool valid = version == 0 && size == size_expected && msg_size == GSP_PHYS_MSG_SIZE && msg_count != 0 && rx_hdr_off >= sizeof(*tx) && entry_off >= rx_hdr_off + 4 && (u64)entry_off + (u64)msg_count * msg_size <= size; seq_printf(m, "%s: version=%u size=0x%x msgSize=%u msgCount=%u writePtr=%u flags=0x%x rxHdrOff=0x%x entryOff=0x%x linked=%u\n", tag, version, size, msg_size, msg_count, write_ptr, flags, rx_hdr_off, entry_off, valid); return valid; } /* * Read-only snapshot for /sys/kernel/debug/open-iov-vfio/gsp-phys-state. * Touches the device only when the boot/claim is live AND the stage-3d * staging is primed; otherwise it returns a one-line status and no hardware * access. No register or queue state is written and nothing is consumed: the * host status-queue readPtr at cmd+rxHdrOff is displayed but never advanced * (only gsp_phys_rx_readptr_publish() from real stage-3e consumption does). */ static int gsp_phys_state_show(struct seq_file *m, void *v) { const struct gsp_phys_boot_chunk *q = &boot_stage.queue; const struct gsp_phys_msgq_tx_header *cmd_tx; const struct gsp_phys_msgq_tx_header *stat_tx; struct gsp_phys_scratch14 s14; void __iomem *bar0 = gsp_phys_gpu_bar0(); u32 wpr2_lo, wpr2_hi, riscv, gsp_cpuctl, sec2_cpuctl; bool stat_valid, cmd_valid; if (!gsp_phys_gpu_claimed() || !bar0) { seq_puts(m, "gsp-phys-state: inactive: physical GPU not claimed or BAR0 not owned (gsp_phys_gpu=0 or claim inactive); no device state\n"); return 0; } if (!boot_stage.primed) { seq_printf(m, "gsp-phys-state: inactive: stage-3d staging not primed (kicked=%u ran=%u rm_init_done=%u dryrun=%u rm_unload_submitted=%u); no device access\n", boot_stage.kicked, boot_stage.ran, boot_stage.rm_init_done, boot_stage.dryrun, boot_stage.rm_unload_submitted); return 0; } wpr2_lo = readl(bar0 + GSP_PHYS_WPR2_ADDR_LO); wpr2_hi = readl(bar0 + GSP_PHYS_WPR2_ADDR_HI); riscv = readl(bar0 + GSP_PHYS_RISCV_STATUS); gsp_cpuctl = readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL); sec2_cpuctl = readl(bar0 + GSP_PHYS_SEC2_BASE + GSP_PHYS_FALCON_CPUCTL); gsp_phys_fwsec_scratch14_read(bar0, &s14); seq_printf(m, "gsp-phys-state: claimed=1 boot_param=%u dryrun_param=%u\n", gsp_phys_boot_enabled(), gsp_phys_boot_dryrun_enabled()); seq_printf(m, "stage: primed=%u kicked=%u ran=%u riscv_active=%u rm_init_done=%u rm_unload_submitted=%u dryrun=%u tx_seq_num=%u\n", boot_stage.primed, boot_stage.kicked, boot_stage.ran, boot_stage.riscv_active, boot_stage.rm_init_done, boot_stage.rm_unload_submitted, boot_stage.dryrun, boot_stage.tx_seq_num); seq_printf(m, "rm_probe: attempted=%u last_ret=%d last_function=%u last_payload_len=%u (0 function = no reply; 1 = SET_GUEST_SYSTEM_INFO, 65 = GET_GSP_STATIC_INFO)\n", boot_stage.rm_probe_attempts, boot_stage.rm_probe_last_ret, boot_stage.rm_probe_last_function, boot_stage.rm_probe_last_payload_len); seq_printf(m, "wpr2: LO=0x%08x (VAL=0x%x) HI=0x%08x (VAL=0x%x) state=%s\n", wpr2_lo, gsp_phys_fwsec_wpr2_lo_val(bar0), wpr2_hi, gsp_phys_fwsec_wpr2_hi_val(bar0), gsp_phys_fwsec_wpr2_up(bar0) ? "up" : "down"); seq_printf(m, "riscv: STATUS=0x%08x (%sactive)\n", riscv, riscv & GSP_PHYS_RISCV_ACTIVE ? "" : "not "); seq_printf(m, "falcon: GSP_CPUCTL=0x%08x [%s%s] SEC2_CPUCTL=0x%08x [%s%s] GSP_MBOX0=0x%08x GSP_MBOX1=0x%08x FALCON_OS=0x%08x\n", gsp_cpuctl, gsp_cpuctl & GSP_PHYS_CPUCTL_STARTCPU ? "started " : "", gsp_cpuctl & GSP_PHYS_CPUCTL_HALTED ? "halted" : "running", sec2_cpuctl, sec2_cpuctl & GSP_PHYS_CPUCTL_STARTCPU ? "started " : "", sec2_cpuctl & GSP_PHYS_CPUCTL_HALTED ? "halted" : "running", readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX0), readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_MAILBOX1), readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_OS)); seq_printf(m, "scratch14: raw=0x%08x acrVersion=%u booterLoad=%u ahesascReload=%u reloadAcrUnload=%u acrUnloadBooterUnload=%u booterBinaryVersion=%u\n", s14.raw, s14.acr_version, s14.booter_load, s14.ahesasc_booter_reload, s14.booter_reload_acr_unload, s14.acr_unload_booter_unload, s14.booter_binary_version); cmd_tx = gsp_phys_chunk_page_va(q, GSP_PHYS_QUEUE_CMD_OFF / GSP_PHYS_PAGE_SIZE); stat_tx = gsp_phys_chunk_page_va(q, GSP_PHYS_QUEUE_STAT_OFF / GSP_PHYS_PAGE_SIZE); cmd_valid = gsp_phys_state_tx_header(m, "cmd_tx", cmd_tx, GSP_PHYS_QUEUE_CMD_SIZE); stat_valid = gsp_phys_state_tx_header(m, "status_tx", stat_tx, GSP_PHYS_QUEUE_STAT_SIZE); if (cmd_valid) { u32 off = le32_to_cpu(READ_ONCE(cmd_tx->rx_hdr_off)); /* * MSGQ_FLAGS_SWAP_RX: the host's status-queue read pointer * lives in the command TX header at rxHdrOff. Displayed * only; gsp_phys_rx_readptr_publish() owns advancing it. */ seq_printf(m, "status_tx_peer: host status readPtr @cmd+0x%x = %u\n", off, le32_to_cpu(READ_ONCE(*(const __le32 *) ((const u8 *)cmd_tx + off)))); } else { seq_puts(m, "status_tx_peer: host status readPtr unknown (cmd TX header not linked)\n"); } if (stat_valid) { u32 off = le32_to_cpu(READ_ONCE(stat_tx->rx_hdr_off)); u32 flags = le32_to_cpu(READ_ONCE(stat_tx->flags)); /* * With SWAP_RX on the status header the incoming (GSP-RM) * command readPtr is at status+rxHdrOff; without it the peer * pointer is the command queue's own RX header. Read-only. */ if (flags & GSP_PHYS_MSG_FLAGS_SWAP_RX) { seq_printf(m, "cmd_tx_peer: GSP-RM cmd readPtr @status+0x%x = %u (swapRx)\n", off, le32_to_cpu(READ_ONCE(*(const __le32 *) ((const u8 *)stat_tx + off)))); } else if (cmd_valid) { u32 cmd_off = le32_to_cpu(READ_ONCE(cmd_tx->rx_hdr_off)); seq_printf(m, "cmd_tx_peer: GSP-RM cmd readPtr @cmd+0x%x = %u (no swapRx on status)\n", cmd_off, le32_to_cpu(READ_ONCE(*(const __le32 *) ((const u8 *)cmd_tx + cmd_off)))); } else { seq_puts(m, "cmd_tx_peer: GSP-RM cmd readPtr unknown (no swapRx on status and cmd TX header not linked)\n"); } } else { seq_puts(m, "cmd_tx_peer: GSP-RM cmd readPtr unknown (status TX header not linked)\n"); } if (stat_valid) { u32 entry_off = le32_to_cpu(READ_ONCE(stat_tx->entry_off)); u32 msg_count = le32_to_cpu(READ_ONCE(stat_tx->msg_count)); u32 wp = le32_to_cpu(READ_ONCE(stat_tx->write_ptr)); u32 shown = 0, i; /* * The producer publishes writePtr after the element payload; * order the pointer read before the element reads. Read-only: * no readPtr is advanced and nothing is consumed. */ dma_rmb(); seq_printf(m, "status_ring: writePtr slot=%u; last produced elements (max 8):\n", wp); for (i = 1; i <= 8 && i <= msg_count; i++) { u32 slot = (wp + msg_count - i) % msg_count; const struct gsp_phys_msgq_element *e = (const struct gsp_phys_msgq_element *) gsp_phys_chunk_page_va(q, (GSP_PHYS_QUEUE_STAT_OFF + entry_off) / GSP_PHYS_PAGE_SIZE + slot); u32 elem_count = le32_to_cpu(READ_ONCE(e->elem_count)); if (!elem_count) break; /* producer has not reached this slot */ seq_printf(m, " slot=%u func=0x%x result=%u seq=%u count=%u\n", slot, le32_to_cpu(READ_ONCE(e->rpc_function)), le32_to_cpu(READ_ONCE(e->rpc_result)), le32_to_cpu(READ_ONCE(e->seq_num)), elem_count); shown++; } if (!shown) seq_puts(m, " (none produced yet)\n"); } else { seq_puts(m, "status_ring: TX header not linked; no ring walk (read-only)\n"); } seq_printf(m, "queue_dma: shared=0x%llx cmd=0x%llx stat=0x%llx ptes=%u | radix3=0x%llx fmc=0x%llx sig=0x%llx login=0x%llx logrm=0x%llx\n", (u64)gsp_phys_chunk_page_dma(q, 0), (u64)gsp_phys_chunk_page_dma(q, GSP_PHYS_QUEUE_CMD_OFF / GSP_PHYS_PAGE_SIZE), (u64)gsp_phys_chunk_page_dma(q, GSP_PHYS_QUEUE_STAT_OFF / GSP_PHYS_PAGE_SIZE), GSP_PHYS_QUEUE_PTE_COUNT, (u64)gsp_phys_chunk_page_dma(&boot_stage.radix3.table, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.fmc, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.signature, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.login, 0), (u64)gsp_phys_chunk_page_dma(&boot_stage.logrm, 0)); return 0; } static int gsp_phys_state_open(struct inode *inode, struct file *file) { return single_open(file, gsp_phys_state_show, NULL); } static const struct file_operations gsp_phys_state_fops = { .owner = THIS_MODULE, .open = gsp_phys_state_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; /* ============================================================ * 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; } if (!boot_stage.rm_init_done) { pr_info("gsp-phys-boot: gsp-phys-unload-rpc: no-op: GSP-RM init-done (function 0x%x, rpc_result 0) was not observed (primed=%u ran=%u); sending UNLOADING_GUEST_DRIVER before init-done can be dropped\n", GSP_PHYS_MSG_FUNC_GSP_INIT_DONE, boot_stage.primed, boot_stage.ran); 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 = { .owner = THIS_MODULE, .open = simple_open, .write = gsp_phys_unload_rpc_write, .llseek = no_llseek, }; /* * gsp-phys-rm-probe (write-only, mode 0200): writing "1" runs one host RM * probe cycle (GET_GSP_STATIC_INFO, then SET_GUEST_SYSTEM_INFO + one retry * when the first reply is missing) on a live, init-done session. Every other * value or state (parameter off, GPU unclaimed, stage 3d not run, init-done * not observed) is a logged no-op. Serialised with the unload trigger by * gsp_phys_rm_lock; at most one probe cycle per write. WPR2 is never touched. */ static ssize_t gsp_phys_rm_probe_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-rm-probe: write ignored (write \"1\" to probe)\n"); return len; } if (!gsp_phys_boot_enabled()) { pr_info("gsp-phys-boot: gsp-phys-rm-probe: no-op: gsp_phys_boot is not enabled\n"); return len; } if (!gsp_phys_gpu_claimed()) { pr_info("gsp-phys-boot: gsp-phys-rm-probe: no-op: the physical GPU is not claimed\n"); return len; } if (!boot_stage.ran) { pr_info("gsp-phys-boot: gsp-phys-rm-probe: 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; } if (!boot_stage.rm_init_done) { pr_info("gsp-phys-boot: gsp-phys-rm-probe: no-op: GSP-RM init-done (function 0x%x, rpc_result 0) was not observed (primed=%u ran=%u); RM RPCs before init-done can be dropped\n", GSP_PHYS_MSG_FUNC_GSP_INIT_DONE, boot_stage.primed, boot_stage.ran); return len; } bar0 = gsp_phys_gpu_bar0(); if (!bar0) { pr_warn("gsp-phys-boot: gsp-phys-rm-probe: no-op: BAR0 is not mapped\n"); return len; } ret = gsp_phys_rm_probe(bar0); pr_info("gsp-phys-boot: gsp-phys-rm-probe: probe returned %d (last reply function=%u payload=%u B; WPR2 untouched)\n", ret, boot_stage.rm_probe_last_function, boot_stage.rm_probe_last_payload_len); return len; } static const struct file_operations gsp_phys_rm_probe_fops = { .owner = THIS_MODULE, .open = simple_open, .write = gsp_phys_rm_probe_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; } file = debugfs_create_file("gsp-phys-state", 0444, gsp_phys_debugfs_root, NULL, &gsp_phys_state_fops); if (IS_ERR_OR_NULL(file)) { long err = IS_ERR(file) ? PTR_ERR(file) : -ENODEV; /* * Keep the existing trigger registered: only the snapshot is * missing. The caller's warning covers both files. */ pr_warn("gsp-phys-boot: debugfs file gsp-phys-state creation failed: %ld (gsp-phys-unload-rpc stays available)\n", err); return (int)err; } file = debugfs_create_file("gsp-phys-rm-probe", 0200, gsp_phys_debugfs_root, NULL, &gsp_phys_rm_probe_fops); if (IS_ERR_OR_NULL(file)) { long err = IS_ERR(file) ? PTR_ERR(file) : -ENODEV; /* * The probe trigger is optional: the unload trigger and the * state snapshot stay registered. */ pr_warn("gsp-phys-boot: debugfs file gsp-phys-rm-probe creation failed: %ld (unload trigger and state snapshot stay available)\n", err); return (int)err; } pr_info("gsp-phys-boot: debugfs files /sys/kernel/debug/open-iov-vfio/{gsp-phys-state read-only, gsp-phys-unload-rpc write-only, gsp-phys-rm-probe write-only} ready\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) return; if (!boot_stage.kicked) return; pr_info("gsp-phys-boot: release: stage 3d issued a Booter Load kick (RISC-V active=%u); quiescing the GSP falcon before the WPR2 teardown\n", boot_stage.riscv_active); gsp_phys_scratch14_log(bar0, "quiesce-pre-reset"); gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE); gsp_phys_scratch14_log(bar0, "quiesce-post-reset"); pr_info("gsp-phys-boot: release quiesce: GSP_CPUCTL=0x%08x RISCV_STATUS=0x%08x FALCON_OS=0x%08x WPR2_LO=0x%08x WPR2_HI=0x%08x\n", readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_CPUCTL), readl(bar0 + GSP_PHYS_RISCV_STATUS), readl(bar0 + GSP_PHYS_GSP_BASE + GSP_PHYS_FALCON_OS), readl(bar0 + GSP_PHYS_WPR2_ADDR_LO), readl(bar0 + GSP_PHYS_WPR2_ADDR_HI)); if (gsp_phys_fwsec_wpr2_up(bar0)) pr_info("gsp-phys-boot: release: WPR2 still up after the quiesce; the fwsec teardown below owns it\n"); else pr_info("gsp-phys-boot: release: WPR2 already down after the quiesce\n"); } void gsp_phys_boot_staging_free(struct pci_dev *pdev) { if (!pdev) return; if (boot_stage.primed || boot_stage.kicked || boot_stage.ran || boot_stage.fmc.pages || boot_stage.signature.pages || boot_stage.queue.pages || boot_stage.radix3.table.pages || boot_stage.login.pages || boot_stage.logrm.pages || boot_stage.radix3.nr_chunks) pr_info("gsp-phys-boot: release: freeing stage-3d staging (radix3 data + tables, FMC, signature, queue, LOGINIT, LOGRM)\n"); gsp_phys_radix3_free(pdev, &boot_stage.radix3); gsp_phys_chunk_free(pdev, &boot_stage.fmc); gsp_phys_chunk_free(pdev, &boot_stage.signature); gsp_phys_chunk_free(pdev, &boot_stage.queue); gsp_phys_chunk_free(pdev, &boot_stage.login); gsp_phys_chunk_free(pdev, &boot_stage.logrm); memset(&boot_stage, 0, sizeof(boot_stage)); } /* * ABI shape checks. The element check pins the status-queue decoding used by * stage 3e: rpc.function (and rpc_result) must stay at the offsets the * reference message-queue ABI defines. */ static_assert(sizeof(gsp_phys_booter_load_image) == GSP_PHYS_BOOTER_LOAD_IMAGE_SIZE, "Booter Load bindata image size must match the decoded header"); static_assert(sizeof(gsp_phys_gsp_rm_boot_image) == GSP_PHYS_FMC_SIZE, "GSP-RM boot (FMC) bindata image size must be 4096 bytes"); static_assert(GSP_PHYS_BOOTER_LOAD_SIG_OFF + 16 <= GSP_PHYS_BOOTER_LOAD_IMAGE_SIZE, "Booter Load signature patch must fit the image"); static_assert(GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF == 0, "Booter Load non-secure IMEM code starts at image offset 0"); static_assert(GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF == GSP_PHYS_BOOTER_LOAD_NS_CODE_OFF + GSP_PHYS_BOOTER_LOAD_NS_CODE_SIZE, "Booter Load secure IMEM code follows the non-secure block"); static_assert(GSP_PHYS_BOOTER_LOAD_DATA_OFF == GSP_PHYS_BOOTER_LOAD_SEC_CODE_OFF + GSP_PHYS_BOOTER_LOAD_SEC_CODE_SIZE, "Booter Load DMEM data follows the secure code block"); static_assert(GSP_PHYS_BOOTER_LOAD_DATA_OFF + GSP_PHYS_BOOTER_LOAD_DATA_SIZE == GSP_PHYS_BOOTER_LOAD_IMAGE_SIZE, "Booter Load image sections must exactly cover the image"); static_assert(sizeof(struct gsp_phys_libos_arg) == 32, "libos memory-region init argument must be 32 bytes (reference ABI)"); static_assert(sizeof(struct gsp_phys_msgq_init_args) == 32, "MESSAGE_QUEUE_INIT_ARGUMENTS must be 32 bytes (reference ABI)"); static_assert(sizeof(struct gsp_phys_sr_init_args) == 12, "GSP_SR_INIT_ARGUMENTS must be 12 bytes (reference ABI)"); static_assert(sizeof(struct gsp_phys_gsp_args_page) == 72, "GSP_ARGUMENTS_CACHED must be 72 bytes (reference ABI)"); static_assert(sizeof(struct gsp_phys_msgq_tx_header) == 32, "msgqTxHeader must be 32 bytes (reference ABI)"); static_assert(sizeof(struct gsp_phys_msgq_element) == 80, "GSP message-queue element through the full rpc header must be 80 bytes"); static_assert(offsetof(struct gsp_phys_msgq_element, rpc_function) == 60, "rpc.function must live at element offset 60 (reference ABI)"); static_assert(offsetof(struct gsp_phys_msgq_element, rpc_result) == 64, "rpc.rpc_result must live at element offset 64 (reference ABI)"); static_assert(offsetof(struct gsp_phys_msgq_element, rpc_result_private) == 68, "rpc.rpc_result_private must live at element offset 68 (reference ABI)"); 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(GSP_PHYS_MSG_ELEMENTS_MAX == 16, "the reference message cap is 16 elements (GSP_MSG_QUEUE_ELEMENT_SIZE_MAX)"); static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF + GSP_PHYS_MSG_PAYLOAD_MAX == GSP_PHYS_MSG_ELEMENTS_MAX * GSP_PHYS_MSG_SIZE, "the payload cap must fill exactly 16 elements including the header"); static_assert(sizeof(struct gsp_phys_rpc_get_gsp_static_info) == 4, "rpc_get_gsp_static_info_v14_00 request payload must be one u32"); static_assert(sizeof(struct gsp_phys_rpc_set_guest_system_info) == 792, "rpc_set_guest_system_info_v03_00 must be 792 bytes (6 u32 + 3 * 0x100)"); static_assert(offsetof(struct gsp_phys_rpc_set_guest_system_info, guest_driver_version) == 24, "guestDriverVersion must live at payload offset 24 (reference ABI)"); static_assert(offsetof(struct gsp_phys_rpc_set_guest_system_info, guest_version) == 280, "guestVersion must live at payload offset 280 (reference ABI)"); static_assert(offsetof(struct gsp_phys_rpc_set_guest_system_info, guest_title) == 536, "guestTitle must live at payload offset 536 (reference ABI)"); static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF + GSP_PHYS_MSG_RPC_HEADER_SIZE + GSP_PHYS_SCI_MIN_SIZE <= GSP_PHYS_MSG_ELEMENTS_MAX * GSP_PHYS_MSG_SIZE, "a GspStaticConfigInfo reply must fit the 16-element record cap"); static_assert(GSP_PHYS_SCI_H_INTERNAL_SUBDEVICE_OFF + 4 <= GSP_PHYS_SCI_MIN_SIZE, "hInternalSubdevice must live inside the minimum GspStaticConfigInfo"); 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 == GSP_PHYS_MSG_RPC_HEADER_OFF + GSP_PHYS_MSG_RPC_HEADER_SIZE, "the payload starts after the 48-byte element header and 32-byte rpc header"); static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF == sizeof(struct gsp_phys_msgq_element), "sequencer payload must start right after the element rpc header"); static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF_LEGACY < GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF, "the legacy payload offset must precede the reference one"); static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF + GSP_PHYS_SEQ_REQUEST_SIZE <= GSP_PHYS_MSG_SIZE, "sequencer request header must fit one queue element"); static_assert(GSP_PHYS_SEQ_REG_SAVE_SIZE == 8, "sequencer regSaveArea must hold 8 values (reference ABI)"); static_assert(GSP_PHYS_QUEUE_PTE_COUNT == (GSP_PHYS_QUEUE_CMD_SIZE + GSP_PHYS_QUEUE_STAT_SIZE) / GSP_PHYS_PAGE_SIZE + 1, "queue PTE count must be queue pages + the page-table page"); static_assert(GSP_PHYS_QUEUE_PT_SIZE + GSP_PHYS_QUEUE_CMD_SIZE + GSP_PHYS_QUEUE_STAT_SIZE == GSP_PHYS_QUEUE_TOTAL_SIZE, "queue shared buffer size must match the layout"); static_assert(GSP_PHYS_LOG_BUFFER_SIZE % GSP_PHYS_PAGE_SIZE == 0, "libos log buffer size must be page-aligned"); static_assert(GSP_PHYS_LOG_PA_TABLE_OFF + (GSP_PHYS_LOG_BUFFER_PAGES + 1) * 8 <= GSP_PHYS_PAGE_SIZE, "libos log PA table (put pointer + one entry per page) must fit page 0"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, rec_type) == GSP_PHYS_NOCAT_OFF_RECTYPE, "NOCAT recType must live at payload offset 16 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, bugcheck) == GSP_PHYS_NOCAT_OFF_BUGCHECK, "NOCAT bugcheck must live at payload offset 20 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, source) == GSP_PHYS_NOCAT_OFF_SOURCE, "NOCAT source[65] must live at payload offset 24 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, subsystem) == GSP_PHYS_NOCAT_OFF_SUBSYSTEM, "NOCAT subsystem must live at payload offset 92: source[65] ends at 89 and is padded to the next u32 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, error_code) == GSP_PHYS_NOCAT_OFF_ERRORCODE, "NOCAT errorCode u64 must be 8-byte aligned at payload offset 96 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, faulting_engine) == GSP_PHYS_NOCAT_OFF_FAULTENGINE, "NOCAT faultingEngine[65] must live at payload offset 104 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, tdr_reason) == GSP_PHYS_NOCAT_OFF_TDRREASON, "NOCAT tdrReason must live at payload offset 172 (reference ABI)"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, diag_buffer_len) == GSP_PHYS_NOCAT_OFF_DIAGBUFLEN, "NOCAT diagBufferLen must live at payload offset 176 (reference ABI)"); static_assert(sizeof(struct gsp_phys_nocat_journal_record) == GSP_PHYS_NOCAT_FULL_PAYLOAD, "the NOCAT journal record must be 1208 bytes including the 1 KB diag buffer (reference ABI)"); static_assert(GSP_PHYS_NOCAT_OFF_SOURCE + GSP_PHYS_NOCAT_LEN_SOURCE <= GSP_PHYS_NOCAT_OFF_SUBSYSTEM, "NOCAT source must end before subsystem"); static_assert(GSP_PHYS_NOCAT_OFF_FAULTENGINE + GSP_PHYS_NOCAT_LEN_SOURCE <= GSP_PHYS_NOCAT_OFF_TDRREASON, "NOCAT faultingEngine must end before tdrReason"); static_assert(offsetof(struct gsp_phys_nocat_journal_record, diag_buffer) == GSP_PHYS_NOCAT_OFF_DIAGBUF, "NOCAT diagBuffer must live at payload offset 180 (reference ABI)"); /* * GspSystemInfo wire ABI, pinned against the reference header compile * (offsetof/sizeof of kernel/gpu/gsp/gsp_static_config.h, 570.144) and the * tracked ABI checks (gsp_abi_check.c:324-375). Reference sender payload is * 928 bytes (hostPageSize appended at 920), rpc.length = 32 + 928 = 960. */ static_assert(sizeof(struct gsp_phys_gsp_system_info) == 928, "GspSystemInfo must be 928 bytes (reference 570.144 ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, gpu_phys_addr) == 0, "GspSystemInfo.gpuPhysAddr offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, gpu_phys_fb_addr) == 8, "GspSystemInfo.gpuPhysFbAddr offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, gpu_phys_inst_addr) == 16, "GspSystemInfo.gpuPhysInstAddr offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, gpu_phys_io_addr) == 24, "GspSystemInfo.gpuPhysIoAddr offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, nv_domain_bus_device_func) == 32, "GspSystemInfo.nvDomainBusDeviceFunc offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, sim_access_buf_phys_addr) == 40, "GspSystemInfo.simAccessBufPhysAddr offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, notify_op_shared_surface_phys_addr) == 48, "GspSystemInfo.notifyOpSharedSurfacePhysAddr offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pcie_atomics_op_mask) == 56, "GspSystemInfo.pcieAtomicsOpMask offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, console_mem_size) == 64, "GspSystemInfo.consoleMemSize offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, max_user_va) == 72, "GspSystemInfo.maxUserVa offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pci_config_mirror_base) == 80, "GspSystemInfo.pciConfigMirrorBase offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pci_config_mirror_size) == 84, "GspSystemInfo.pciConfigMirrorSize offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pci_device_id) == 88, "GspSystemInfo.PCIDeviceID offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pci_sub_device_id) == 92, "GspSystemInfo.PCISubDeviceID offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pci_revision_id) == 96, "GspSystemInfo.PCIRevisionID offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pcie_atomics_cpl_device_cap_mask) == 100, "GspSystemInfo.pcieAtomicsCplDeviceCapMask offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, oor_arch) == 104, "GspSystemInfo.oorArch offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, cl_pdb_properties) == 112, "GspSystemInfo.clPdbProperties offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, chipset) == 120, "GspSystemInfo.Chipset offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_gpu_behind_bridge) == 124, "GspSystemInfo.bGpuBehindBridge offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_flr_supported) == 125, "GspSystemInfo.bFlrSupported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b64b_bar0_supported) == 126, "GspSystemInfo.b64bBar0Supported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_mnoc_available) == 127, "GspSystemInfo.bMnocAvailable offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, chipset_l1ss_enable) == 128, "GspSystemInfo.chipsetL1ssEnable offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_upstream_l0s_unsupported) == 132, "GspSystemInfo.bUpstreamL0sUnsupported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_upstream_l1_unsupported) == 133, "GspSystemInfo.bUpstreamL1Unsupported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_upstream_l1_por_supported) == 134, "GspSystemInfo.bUpstreamL1PorSupported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_upstream_l1_por_mobile_only) == 135, "GspSystemInfo.bUpstreamL1PorMobileOnly offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_system_has_mux) == 136, "GspSystemInfo.bSystemHasMux offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, upstream_address_valid) == 137, "GspSystemInfo.upstreamAddressValid offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, fhb_bus_info) == 138, "GspSystemInfo.FHBBusInfo offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, chipset_id_info) == 148, "GspSystemInfo.chipsetIDInfo offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, acpi_method_data) == 160, "GspSystemInfo.acpiMethodData offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, hypervisor_type) == 836, "GspSystemInfo.hypervisorType offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_is_passthru) == 840, "GspSystemInfo.bIsPassthru offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, sys_timer_offset_ns) == 848, "GspSystemInfo.sysTimerOffsetNs offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, gsp_vf_info) == 856, "GspSystemInfo.gspVFInfo offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_is_primary) == 896, "GspSystemInfo.bIsPrimary offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, is_grid_build) == 897, "GspSystemInfo.isGridBuild offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, pcie_config_reg_link_cap) == 900, "GspSystemInfo.pcieConfigReg.linkCap offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, grid_build_csp) == 904, "GspSystemInfo.gridBuildCsp offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_preserve_video_memory_allocations) == 908, "GspSystemInfo.bPreserveVideoMemoryAllocations offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_tdr_event_supported) == 909, "GspSystemInfo.bTdrEventSupported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_feature_stretch_vblank_capable) == 910, "GspSystemInfo.bFeatureStretchVblankCapable offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_enable_dynamic_granularity_page_arrays) == 911, "GspSystemInfo.bEnableDynamicGranularityPageArrays offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_clock_boost_supported) == 912, "GspSystemInfo.bClockBoostSupported offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, b_route_disp_intrs_to_cpu) == 913, "GspSystemInfo.bRouteDispIntrsToCPU offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_gsp_system_info, host_page_size) == 920, "GspSystemInfo.hostPageSize offset (reference 570.144 ABI)"); static_assert(GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF + sizeof(struct gsp_phys_gsp_system_info) <= GSP_PHYS_MSG_SIZE, "GspSystemInfo must fit one 4 KB queue element"); static_assert(sizeof(struct gsp_phys_packed_registry_entry) == 16, "PACKED_REGISTRY_ENTRY must be 16 bytes (natural alignment, reference ABI)"); static_assert(offsetof(struct gsp_phys_packed_registry_entry, name_offset) == 0, "PACKED_REGISTRY_ENTRY.nameOffset offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_packed_registry_entry, type) == 4, "PACKED_REGISTRY_ENTRY.type offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_packed_registry_entry, data) == 8, "PACKED_REGISTRY_ENTRY.data offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_packed_registry_entry, length) == 12, "PACKED_REGISTRY_ENTRY.length offset (reference ABI)"); static_assert(sizeof(struct gsp_phys_packed_registry_table) == 8, "PACKED_REGISTRY_TABLE header must be 8 bytes (reference ABI)"); static_assert(offsetof(struct gsp_phys_packed_registry_table, size) == 0, "PACKED_REGISTRY_TABLE.size offset (reference ABI)"); static_assert(offsetof(struct gsp_phys_packed_registry_table, num_entries) == 4, "PACKED_REGISTRY_TABLE.numEntries offset (reference ABI)");