From f7f8b19b200436f92f78f73b5b6ece6410f94d57 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 1 Oct 2026 13:58:55 -0400 Subject: gsp-phys: program the GPU timer before the GSP kick --- kernel/gsp_phys_booter.c | 85 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c index 9a170c9..68b71ca 100644 --- a/kernel/gsp_phys_booter.c +++ b/kernel/gsp_phys_booter.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -298,6 +299,19 @@ bool gsp_phys_boot_requested(void) #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" */ @@ -2895,14 +2909,18 @@ int gsp_phys_queue_tx_submit(void __iomem *bar0, u32 function, * (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, timer - * offset, passthru, console memory, dynamic-granularity arrays, TDR, + * 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) + 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 }; @@ -2978,6 +2996,55 @@ static void gsp_phys_preinit_fill_system_info( 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; } /* @@ -2989,17 +3056,22 @@ static void gsp_phys_preinit_fill_system_info( * 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); - 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\n", + 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), @@ -3015,7 +3087,8 @@ static int gsp_phys_preinit_rpcs(void __iomem *bar0, struct pci_dev *pdev) 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); + 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)); -- cgit v1.2.3