diff options
Diffstat (limited to 'kernel/gsp_sema_service.c')
| -rw-r--r-- | kernel/gsp_sema_service.c | 1218 |
1 files changed, 1156 insertions, 62 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 2f453fa..10d6b80 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -43,6 +43,7 @@ #include "nvidia-vgpu-vfio.h" #include "gsp_sema_service.h" +#include "gsp_rm_emulation.h" #define SEMA_MAP_MAX 65536 #define SEMA_MAP_SIZE_BITS 16 @@ -82,6 +83,18 @@ #define HOST_SEM_OP_RELEASE 1 #define HOST_SEM_OP_REDUCTION 6 +/* + * SET_REPORT_SEMAPHORE_A..D: four contiguous words pushed after each other + * { offset_upper[7:0], offset_lower[31:0], payload, operation } and shared + * by the TURING and later classes. RELEASE (0) ends the push. + */ +#define HOST_M_REPORT_SEM_A 0x1b00 +#define HOST_M_REPORT_SEM_B 0x1b04 +#define HOST_M_REPORT_SEM_C 0x1b08 +#define HOST_M_REPORT_SEM_D 0x1b0c +#define HOST_RS_OP_MASK 0x3 +#define HOST_RS_OP_RELEASE 0 + struct sema_map_entry { u64 gpu_page; /* GPU VA, page aligned */ u64 pa_page; /* guest PA, page aligned */ @@ -91,7 +104,7 @@ struct sema_map_entry { /* registered channels (GPFIFO-driven execution) */ /* ------------------------------------------------------------------ */ -#define SVC_MAX_CHANNELS 24 +#define SVC_MAX_CHANNELS 128 #define SVC_GPFIFO_ENTRY_SIZE 8 #define SVC_PUSH_MAX (256 * 1024) @@ -103,12 +116,17 @@ struct svc_channel { bool valid; u32 h_object; u32 engine_type; + u32 cid; /* RM channel id (doorbell token vector) */ u64 gpfifo_va; u32 gpfifo_entries; u64 userd_pa; u32 userd_as; /* NV_ADDR_SYSMEM / NV_ADDR_FBMEM */ + bool userd_ok; /* userd_pa reads a plausible put/get */ + bool logged; /* one state log per channel */ u64 instance_pa; - u64 notifier_pa; + u64 notifier_pa; /* error notifier: NvNotification slots */ + u32 notifier_as; + u64 notified; /* work-submit-token notifications completed */ u32 last_get; /* next GPFIFO entry we have still to run */ u64 submissions; /* GPFIFO entries executed */ }; @@ -122,6 +140,37 @@ struct svc_channel { static unsigned long svc_hot[BITS_TO_LONGS(SEMA_HOT_PAGES)]; static u32 svc_map_hash[SEMA_MAP_MAX * 2]; +/* VA spaces whose page directory the driver published / learned VA->PA pairs. */ +#define SVC_MAX_VASPACES 8 +#define SVC_LAT_MAX 65536 + +/* + * Pinned VA->PA translations: translations a session has verified by hand + * (debug injection) and that must not be evicted by the learned cache, which + * is content-addressed and gets thrashed by the lookup stream. + */ +#define SVC_PIN_SLOTS 4096 + +struct svc_pin { + u64 va_page; + u64 pa_page; +}; + +struct svc_vaspace { + bool valid; + u32 h_vaspace; + u64 pdb; + u32 entries; /* root entry count */ + int ap; /* SVC_APERTURE_* of the directory itself */ +}; + +struct svc_lat { + bool valid; + u64 va; + u64 addr; + int ap; /* SVC_APERTURE_* */ +}; + static void svc_mark_hot(u64 pa) { u64 page = pa >> SEMA_PAGE_SHIFT; @@ -133,6 +182,7 @@ static void svc_mark_hot(u64 pa) struct sema_svc { struct task_struct *task; bool stop; + struct completion stopped; u64 direct_base; u32 map_n; @@ -144,7 +194,17 @@ struct sema_svc { spinlock_t chan_lock; struct svc_channel chan[SVC_MAX_CHANNELS]; + /* VA spaces whose page directory the driver published */ + spinlock_t vas_lock; + struct svc_vaspace vas[SVC_MAX_VASPACES]; + struct svc_lat lat[SVC_LAT_MAX]; + struct svc_pin pin[SVC_PIN_SLOTS]; + u32 pin_n; + /* stats */ + u64 walks_ok; + u64 walks_miss; + u64 lat_hits; u64 pairs_seen; u64 pages_decoded; u64 pushes_seen; @@ -162,6 +222,12 @@ struct sema_svc { u64 gpfifo_entries_done; /* entries consumed via GPFIFO */ u64 gpfifo_nomap; /* GPFIFO/pushbuf VA not mapped */ u64 vidmem_writes; /* semaphores written in vidmem */ + u64 gpfifo_log; /* channel-state log budget */ + u64 gpfifo_entry_log; /* per-entry log budget */ + u64 doorbell_count; /* NV_VIRTUAL_FUNCTION_DOORBELL pokes */ + u32 doorbell_token; /* last token written */ + u32 doorbell_log; /* token log budget */ + bool doorbell_pending; /* pump on next service pass */ }; static struct sema_svc svc; @@ -249,19 +315,26 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc) ch->valid = true; ch->h_object = desc->h_object; ch->engine_type = desc->engine_type; + ch->cid = desc->cid; ch->gpfifo_va = desc->gpfifo_va; ch->gpfifo_entries = desc->gpfifo_entries; ch->userd_pa = desc->userd_pa; ch->userd_as = desc->userd_as ? desc->userd_as : SVC_APERTURE_SYS; ch->instance_pa = desc->instance_pa; ch->notifier_pa = desc->notifier_pa; + ch->notifier_as = desc->notifier_as ? desc->notifier_as + : SVC_APERTURE_SYS; } spin_unlock_irqrestore(&svc.chan_lock, flags); if (ch) - pr_info("sema-svc: channel 0x%x registered gpfifo=0x%llx entries=%u userd=0x%llx\n", + pr_info("sema-svc: channel 0x%x registered gpfifo=0x%llx entries=%u userd=0x%llx eng=%u cid=%u notif=0x%llx\n", desc->h_object, desc->gpfifo_va, desc->gpfifo_entries, - desc->userd_pa); + desc->userd_pa, desc->engine_type, desc->cid, + desc->notifier_pa); + else + pr_warn_ratelimited("sema-svc: channel 0x%x dropped: channel table full (%d)\n", + desc->h_object, SVC_MAX_CHANNELS); } void gsp_sema_service_channel_del(u32 h_object) @@ -285,30 +358,604 @@ void gsp_sema_service_channel_del(u32 h_object) } /* + * The driver publishes the page directory of a GPU VA space it owns (it builds + * the page tables itself and hands their base address to the GPU). Store it so + * the service can translate that space's VAs directly. + */ +void gsp_sema_service_vaspace_add(const struct sema_vaspace_desc *desc) +{ + struct svc_vaspace *vs = NULL; + unsigned long flags; + int i; + + if (!desc || !desc->pdb || !desc->num_entries) + return; + + spin_lock_irqsave(&svc.vas_lock, flags); + for (i = 0; i < SVC_MAX_VASPACES; i++) { + if (svc.vas[i].valid && svc.vas[i].h_vaspace == desc->h_vaspace) { + vs = &svc.vas[i]; + break; + } + if (!vs && !svc.vas[i].valid) + vs = &svc.vas[i]; + } + if (vs) { + vs->valid = true; + vs->h_vaspace = desc->h_vaspace; + vs->pdb = desc->pdb; + vs->entries = desc->num_entries; + vs->ap = desc->b_vidmem ? SVC_APERTURE_FB : SVC_APERTURE_SYS; + /* A new directory invalidates what we learned from the old one. */ + memset(svc.lat, 0, sizeof(svc.lat)); + } + spin_unlock_irqrestore(&svc.vas_lock, flags); + + if (vs) + pr_info("sema-svc: VA space 0x%x page directory 0x%llx entries=%u aperture=%s\n", + desc->h_vaspace, desc->pdb, desc->num_entries, + desc->b_vidmem ? "vidmem" : "sysmem"); +} + +void gsp_sema_service_vaspace_del(u32 h_vaspace) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&svc.vas_lock, flags); + for (i = 0; i < SVC_MAX_VASPACES; i++) { + if (svc.vas[i].valid && svc.vas[i].h_vaspace == h_vaspace) { + svc.vas[i].valid = false; + memset(svc.lat, 0, sizeof(svc.lat)); + pr_info("sema-svc: VA space 0x%x page directory dropped\n", + h_vaspace); + break; + } + } + spin_unlock_irqrestore(&svc.vas_lock, flags); +} + +/* * The client's vidmem VAs are its window base plus the vidmem offset, and the * backing store is the emulated BAR1 buffer. Everything else that resolves * goes through the discovered guest-RAM map. + * + * The window base is not 0x200000000: the observed layout places the first + * user vidmem allocation at offset 0x80000 (the low 512 KB of the frame + * buffer are reserved), so VA 0x200215000 backs vidmem offset 0x195000. + * Verified against the guest's own view of its GPFIFO rings and USERD. */ -#define SVC_VIDMEM_VA_BASE 0x200000000ULL +#define SVC_VIDMEM_VA_BASE 0x200080000ULL +#define SVC_VIDMEM_VA_LO 0x200000000ULL /* anything in the window below + * the base is still vidmem, but + * never maps into the buffer */ static int svc_map_lookup(u64 gpu_va, u64 *pa); -static int svc_resolve_va(struct nv_vgpu_device *vgpu, u64 va, u64 *addr, - u32 *aperture) +/* ------------------------------------------------------------------ */ +/* client page-table walk */ +/* ------------------------------------------------------------------ */ +/* + * The driver builds its own page tables for the GPU VA spaces it owns and + * publishes each top-level directory through SET_PAGE_DIRECTORY. With those + * directories the service can translate the client's GPU VAs directly - the + * authoritative answer, and independent of any heuristic discovery. + * + * The format is the GP10X/VER2 GMMU layout used from Pascal through Ampere: + * + * PD3 [48:47] -> PD2 [46:38] -> PD1 [37:29] -> PD0 [28:21] + * dual entry (16 bytes): + * big half -> PT_BIG [20:16] + * small half -> PT_4K [20:12] + * + * A directory entry carries the aperture in bits 2:1 (1 = video memory, + * 2 = system coherent, 3 = system non-coherent) and the child address in bits + * 53:8 at 4 KB granularity (bits 53:4 at 256 B granularity in the big half of + * a dual entry). Bit 0 of a leaf entry is VALID; bit 0 of a directory entry is + * IS_PTE, so a set bit means the level terminates in a page here. + */ +#define SVC_MMU_VA_BITS 49 +#define SVC_MMU_AP_VID 1 /* in directory entries */ +#define SVC_MMU_AP_SYS_COH 2 +#define SVC_MMU_AP_SYS_NC 3 +#define SVC_MMU_PTE_AP_VID 0 /* in page table entries */ +#define SVC_MMU_PTE_AP_PEER 1 +#define SVC_MMU_PTE_AP_SYS_COH 2 +#define SVC_MMU_PTE_AP_SYS_NC 3 + +static u64 svc_pte_addr(u64 e, bool vidmem) { - if (!svc_map_lookup(va, addr)) { - *aperture = 0; /* guest RAM */ + if (vidmem) + return (e & 0x00000001FFFFFFFF00ULL); + return (e & 0x003FFFFFFFFF00ULL); +} + +/* Return the file/buffer selector (SVC_APERTURE_*) for an entry bit-field. */ +static int svc_pte_aperture(u32 ap) +{ + if (ap == SVC_MMU_PTE_AP_VID) + return SVC_APERTURE_FB; + if (ap == SVC_MMU_PTE_AP_PEER) + return -ENODEV; + return SVC_APERTURE_SYS; +} + +static int svc_pde_aperture(u32 ap) +{ + if (ap == SVC_MMU_AP_VID) + return SVC_APERTURE_FB; + if (ap == SVC_MMU_AP_SYS_COH || ap == SVC_MMU_AP_SYS_NC) + return SVC_APERTURE_SYS; + return -ENODEV; +} + +/* + * A translated address is only usable if it lies inside the aperture that + * backs it: guest RAM up to the module's RAM limit for system memory, and the + * emulated vidmem buffer for video memory. Rejecting out-of-range results also + * stops a bogus page-table chain from being mistaken for a translation. + */ +static bool svc_ap_ok(struct nv_vgpu_device *vgpu, u64 addr, int ap) +{ + if (ap == SVC_APERTURE_FB) + return vgpu->bars[1].vaddr && addr + 8 <= vgpu->bars[1].size; + return addr + 8 <= SEMA_PA_MAX; +} + +static int svc_read_ent(struct nv_vgpu_device *vgpu, u64 table, u64 off, + int ap, u64 *out) +{ + if (!svc_ap_ok(vgpu, table + off, ap)) + return -ENOENT; + if (svc_read_ap(vgpu, table + off, + ap == SVC_APERTURE_FB ? SVC_APERTURE_FB + : SVC_APERTURE_SYS, + out, sizeof(*out))) + return -ENOENT; + return 0; +} + +/* + * Leaf lookup under a table: read the PTE at `index`, validate it and return + * the backing address plus its aperture. + */ +static int svc_pte_leaf(struct nv_vgpu_device *vgpu, u64 table, int tap, + u32 index, u64 *addr, int *ap) +{ + u64 e; + int eap; + + if (svc_read_ent(vgpu, table, 8 * index, tap, &e)) + return -ENOENT; + if (!(e & 1)) /* VALID */ + return -ENOENT; + eap = svc_pte_aperture((e >> 1) & 0x3); + if (eap < 0) + return -ENOENT; + *addr = svc_pte_addr(e, eap == SVC_APERTURE_FB); + if (!svc_ap_ok(vgpu, *addr, eap)) + return -ENOENT; + *ap = eap; + return 0; +} + +/* + * Walk the four-level page table rooted at `pdb` for one VA. Directory levels + * hold single 8-byte entries (PD3 root -> PD2 -> PD1) except PD0, whose entry + * is the 16-byte dual entry (big half first, small half second). + */ +static int svc_gmmu_walk(struct nv_vgpu_device *vgpu, u64 pdb, u32 entries, + u64 va, int tap, u64 *addr, int *ap, bool trace) +{ + u64 table = pdb; + u32 root_bits, idx, level; + u64 big, small; + int sap, bap; + + root_bits = ilog2(entries ? entries : 1); + if (root_bits < 1 || root_bits > 12) + return -ENOENT; + + if (!table) + return -ENOENT; + + for (level = 0; level < 3; level++) { + u64 e; + u32 bits, shift; + int eap; + + if (level == 0) { + bits = root_bits; + shift = SVC_MMU_VA_BITS - root_bits; + } else { + bits = 9; + shift = 29 + 9 * (2 - level); + } + idx = (u32)((va >> shift) & ((1u << bits) - 1)); + if (svc_read_ent(vgpu, table, 8 * idx, tap, &e)) { + if (trace) + pr_info("sema-walk: L%u read fail table=0x%llx idx=%u\n", + level, table, idx); + return -ENOENT; + } + if (trace) + pr_info("sema-walk: L%u e=0x%016llx idx=%u\n", + level, e, idx); + /* A page at a directory level would cover at least 2^38 bytes; + * the driver never maps those, so treat it as a false chain. */ + if (e & 1) + return -ENOENT; + eap = svc_pde_aperture((e >> 1) & 0x3); + if (eap < 0) + return -ENOENT; + tap = eap; + table = svc_pte_addr(e, eap == SVC_APERTURE_FB); + if (!svc_ap_ok(vgpu, table, eap)) + return -ENOENT; + } + + /* PD0: one dual entry per index (big half then small half). */ + idx = (va >> 21) & 0xff; + if (svc_read_ap(vgpu, table + 16 * idx, + tap == SVC_APERTURE_FB ? SVC_APERTURE_FB + : SVC_APERTURE_SYS, + &big, sizeof(big)) || + svc_read_ap(vgpu, table + 16 * idx + 8, + tap == SVC_APERTURE_FB ? SVC_APERTURE_FB + : SVC_APERTURE_SYS, + &small, sizeof(small))) + return -ENOENT; + + /* Small half: 4 KB page table under it. */ + sap = svc_pde_aperture(((small >> 1) & 0x3)); + if (sap >= 0 && !(small & 1)) { + u64 t = svc_pte_addr(small, sap == SVC_APERTURE_FB); + + if (t && !svc_pte_leaf(vgpu, t, sap, (va >> 12) & 0x1ff, + addr, ap)) { + *addr += va & 0xfff; + return 0; + } + } + + /* Big half: a 2 MB page here, or a large (64 KB) page table. */ + if (big & 1) { + bap = svc_pte_aperture((big >> 1) & 0x3); + if (bap >= 0) { + *addr = svc_pte_addr(big, bap == SVC_APERTURE_FB) + + (va & ((1ULL << 21) - 1)); + *ap = bap; + return 0; + } + } + bap = svc_pde_aperture((big >> 1) & 0x3); + if (bap >= 0) { + u64 t = svc_pte_addr(big, bap == SVC_APERTURE_FB); + + if (t && !svc_pte_leaf(vgpu, t, bap, (va >> 16) & 0x1f, + addr, ap)) { + *addr += va & 0xffff; + return 0; + } + } + return -ENOENT; +} + +/* + * Page directories the driver published (SET_PAGE_DIRECTORY). + */ +static int svc_walk_vaspaces(struct nv_vgpu_device *vgpu, u64 va, + u64 *addr, int *ap) +{ + unsigned long flags; + int i, rc = -ENOENT; + bool trace = svc.walks_ok + svc.walks_miss < 24; + + spin_lock_irqsave(&svc.vas_lock, flags); + for (i = 0; i < SVC_MAX_VASPACES; i++) { + int alt; + + if (!svc.vas[i].valid) + continue; + if (va >> SVC_MMU_VA_BITS) + break; + if (trace) + pr_info("sema-walk[%d]: va=0x%llx pdb=0x%llx entries=%u\n", + i, va, svc.vas[i].pdb, svc.vas[i].entries); + /* + * Try the aperture the driver named first, then the other one: + * the aperture encoding has changed between driver generations + * and a wrong guess only costs a rejected walk. + */ + alt = svc.vas[i].ap == SVC_APERTURE_FB ? SVC_APERTURE_SYS + : SVC_APERTURE_FB; + if (!svc_gmmu_walk(vgpu, svc.vas[i].pdb, svc.vas[i].entries, va, + svc.vas[i].ap, addr, ap, trace) || + !svc_gmmu_walk(vgpu, svc.vas[i].pdb, svc.vas[i].entries, va, + alt, addr, ap, trace)) { + svc.walks_ok++; + rc = 0; + break; + } + } + if (rc) + svc.walks_miss++; + spin_unlock_irqrestore(&svc.vas_lock, flags); + return rc; +} + +/* + * Self-discovering the page directory. + * + * The published directories are not always usable (a guest can publish a + * directory location the host never gets to see written), so as a fallback + * find the directory by verification: take pairs of GPU VAs whose backing + * guest page is already known (the discovered VA->PA map), and accept the + * first guest page that, used as a top-level directory, reproduces several of + * those translations exactly. A wrong candidate cannot pass that test, so no + * layout assumption is needed to trust the result; only the entry encoding + * (which the walk itself applies consistently) is shared. + */ +static u64 svc_pd_base; +static int svc_pd_ap; +static u32 svc_pd_entries; +static u8 svc_pd_tried; +static u8 svc_pd_found; +#define SVC_PD_SAMPLES 8 +#define SVC_PD_MIN_HITS 4 + +static u32 svc_pd_cands; +static u32 svc_pd_best; + +static void svc_pd_try_candidate(struct nv_vgpu_device *vgpu, u64 cand, + int ap, const u64 *sva, const u64 *spa, + u32 nsamp) +{ + u32 i, hits = 0; + + svc_pd_cands++; + + for (i = 0; i < nsamp; i++) { + u64 a = 0; + int eap = 0; + + if (!svc_gmmu_walk(vgpu, cand, 4, sva[i], ap, &a, &eap, false) && + (a & ~0xfffULL) == (spa[i] & ~0xfffULL)) + hits++; + } + if (hits > svc_pd_best) + svc_pd_best = hits; + if (hits >= SVC_PD_MIN_HITS) { + svc_pd_base = cand; + svc_pd_ap = ap; + svc_pd_entries = 4; + svc_pd_found = 1; + pr_info("sema-svc: discovered page directory 0x%llx (%s), %u/%u sample translations verified\n", + cand, ap == SVC_APERTURE_FB ? "vidmem" : "ram", + hits, nsamp); + } +} + +static void svc_pd_discover(struct nv_vgpu_device *vgpu) +{ + u64 sva[SVC_PD_SAMPLES], spa[SVC_PD_SAMPLES]; + u32 i, n = 0, step; + u64 chunk = 64 * 1024; + u8 *buf; + int pass; + + if (svc_pd_tried) + return; + svc_pd_tried = 1; + + if (svc.map_n < SVC_PD_SAMPLES) + return; + step = svc.map_n / SVC_PD_SAMPLES; + for (i = 0; i < SVC_PD_SAMPLES; i++) { + sva[i] = svc.map[i * step].gpu_page; + spa[i] = svc.map[i * step].pa_page; + if (sva[i] >> SVC_MMU_VA_BITS) + return; /* outside the walkable VA range */ + n++; + } + if (!n) + return; + + /* Guest RAM first, then the emulated vidmem. Page directories are page + * aligned, and every VA here is below 2^47, so the root entry that + * matters is the first one of the page. */ + for (pass = 0; pass < 2 && !svc_pd_found; pass++) { + int ap = pass == 0 ? SVC_APERTURE_SYS : SVC_APERTURE_FB; + u64 limit = pass == 0 ? SEMA_PA_MAX : vgpu->bars[1].size; + u8 *fb = (u8 *)vgpu->bars[1].vaddr; + u64 gpa; + + buf = kmalloc(chunk, GFP_KERNEL); + if (!buf) + return; + for (gpa = 0; gpa < limit && !svc_pd_found; gpa += chunk) { + u32 len = (u32)min_t(u64, chunk, limit - gpa); + u64 off; + + if (pass == 0) { + if (svc_read(vgpu, gpa, buf, len)) + continue; + } else { + if (!fb) + break; + memcpy(buf, fb + gpa, len); + } + for (off = 0; off + 8 <= len && !svc_pd_found; + off += SEMA_PAGE_SIZE) { + u64 e; + + memcpy(&e, buf + off, sizeof(e)); + if (!(e & 1) && ((e >> 1) & 0x3)) + svc_pd_try_candidate(vgpu, gpa + off, + ap, sva, spa, n); + } + } + kfree(buf); + } + pr_info("sema-svc: page directory discovery: %s (base=0x%llx ap=%s cands=%u best=%u/%u)\n", + svc_pd_found ? "found" : "not found", svc_pd_base, + svc_pd_ap == SVC_APERTURE_FB ? "vidmem" : "ram", + svc_pd_cands, svc_pd_best, n); +} + +static int svc_walk_discovered(struct nv_vgpu_device *vgpu, u64 va, + u64 *addr, int *ap) +{ + if (!svc_pd_found) + return -ENOENT; + return svc_gmmu_walk(vgpu, svc_pd_base, svc_pd_entries, va, + svc_pd_ap, addr, ap, false); +} + +/* Learned translations: a walk costs a handful of guest reads, so cache the + * successes and answer repeats without touching the page tables again. A cache + * line covers a whole page, so arrays of semaphores in one page all resolve. */ +static int svc_lat_lookup(u64 va, u64 *addr, int *ap) +{ + struct svc_lat *l = &svc.lat[(va >> SEMA_PAGE_SHIFT) % SVC_LAT_MAX]; + + if (l->valid && + (l->va & ~(u64)(SEMA_PAGE_SIZE - 1)) == + (va & ~(u64)(SEMA_PAGE_SIZE - 1))) { + *addr = (l->addr & ~(u64)(SEMA_PAGE_SIZE - 1)) | + (va & (SEMA_PAGE_SIZE - 1)); + *ap = l->ap; + svc.lat_hits++; + return 0; + } + return -ENOENT; +} + +static void svc_lat_store(u64 va, u64 addr, int ap) +{ + struct svc_lat *l = &svc.lat[(va >> SEMA_PAGE_SHIFT) % SVC_LAT_MAX]; + + l->va = va & ~(u64)(SEMA_PAGE_SIZE - 1); + l->addr = addr & ~(u64)(SEMA_PAGE_SIZE - 1); + l->ap = ap; + l->valid = true; +} + +static int svc_pin_lookup(u64 va, u64 *pa) +{ + u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); + u32 mask = SVC_PIN_SLOTS - 1; + u32 h = (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) & mask; + u32 probe; + + for (probe = 0; probe < 8; probe++) { + struct svc_pin *p = &svc.pin[(h + probe) & mask]; + + if (!p->va_page) + return -ENOENT; + if (p->va_page == page) { + *pa = p->pa_page | (va & (SEMA_PAGE_SIZE - 1)); + return 0; + } + } + return -ENOENT; +} + +static void svc_pin_store(u64 va, u64 pa) +{ + u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); + u32 mask = SVC_PIN_SLOTS - 1; + u32 h = (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) & mask; + u32 probe; + + for (probe = 0; probe < 8; probe++) { + struct svc_pin *p = &svc.pin[(h + probe) & mask]; + + if (!p->va_page || p->va_page == page) { + if (!p->va_page) + svc.pin_n++; + p->va_page = page; + p->pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1); + return; + } + } +} + +/* + * Translate a GPU VA using trusted sources only: a pinned translation, the + * learned cache, the discovered map, the directories the driver published and + * the self-discovered directory. The final vidmem window convention is + * deliberately NOT part of this path: it answers with a vidmem offset for any + * VA above the window base, including sysmem-backed VAs, so callers that must + * not act on a guess (push execution) use this variant. + */ +static int svc_lookup_trusted(struct nv_vgpu_device *vgpu, u64 va, u64 *addr, + int *ap) +{ + u64 pa = 0; + + if (!svc_pin_lookup(va, addr)) { + *ap = SVC_APERTURE_SYS; + return 0; + } + if (!svc_lat_lookup(va, addr, ap)) + return 0; + if (!svc_map_lookup(va, &pa)) { + *addr = pa; + *ap = SVC_APERTURE_SYS; + svc_lat_store(va, pa, SVC_APERTURE_SYS); + return 0; + } + if (!svc_walk_vaspaces(vgpu, va & ~0xfffULL, addr, ap)) { + *addr += va & 0xfff; + svc_lat_store(va, *addr, *ap); return 0; } + if (!svc_walk_discovered(vgpu, va & ~0xfffULL, addr, ap)) { + *addr += va & 0xfff; + svc_lat_store(va, *addr, *ap); + return 0; + } + if (!svc_pd_tried) { + svc_pd_discover(vgpu); + if (!svc_walk_discovered(vgpu, va & ~0xfffULL, addr, ap)) { + *addr += va & 0xfff; + svc_lat_store(va, *addr, *ap); + return 0; + } + } + return -ENOENT; +} + +/* + * Translate a GPU VA: the trusted sources, then the vidmem window convention + * (a structural guess that is correct for the driver's vidmem window). + */ +static int svc_lookup(struct nv_vgpu_device *vgpu, u64 va, u64 *addr, int *ap) +{ + if (!svc_lookup_trusted(vgpu, va, addr, ap)) + return 0; if (va >= SVC_VIDMEM_VA_BASE && va - SVC_VIDMEM_VA_BASE + 4 <= vgpu->bars[1].size) { *addr = va - SVC_VIDMEM_VA_BASE; - *aperture = 1; /* emulated vidmem */ + *ap = SVC_APERTURE_FB; + svc_lat_store(va, *addr, *ap); return 0; } return -ENOENT; } +static int svc_resolve_va(struct nv_vgpu_device *vgpu, u64 va, u64 *addr, + u32 *aperture) +{ + int ap = SVC_APERTURE_SYS; + + if (svc_lookup(vgpu, va, addr, &ap)) + return -ENOENT; + *aperture = (ap == SVC_APERTURE_FB) ? 1 : 0; + return 0; +} + static int svc_sem_read(struct nv_vgpu_device *vgpu, u64 va, u32 *val) { u64 addr = 0; @@ -316,11 +963,9 @@ static int svc_sem_read(struct nv_vgpu_device *vgpu, u64 va, u32 *val) if (svc_resolve_va(vgpu, va, &addr, &ap)) return -ENOENT; - if (ap) { - memcpy(val, vgpu->bars[1].vaddr + addr, sizeof(*val)); - return 0; - } - return svc_read(vgpu, addr, val, sizeof(*val)); + return svc_read_ap(vgpu, addr, + ap ? SVC_APERTURE_FB : SVC_APERTURE_SYS, + val, sizeof(*val)); } static int svc_sem_write(struct nv_vgpu_device *vgpu, u64 va, u32 val) @@ -330,12 +975,11 @@ static int svc_sem_write(struct nv_vgpu_device *vgpu, u64 va, u32 val) if (svc_resolve_va(vgpu, va, &addr, &ap)) return -ENOENT; - if (ap) { - memcpy(vgpu->bars[1].vaddr + addr, &val, sizeof(val)); + if (ap) svc.vidmem_writes++; - return 0; - } - return svc_write(vgpu, addr, &val, sizeof(val)); + return svc_write_ap(vgpu, addr, + ap ? SVC_APERTURE_FB : SVC_APERTURE_SYS, + &val, sizeof(val)); } /* ------------------------------------------------------------------ */ @@ -357,7 +1001,7 @@ struct base_candidate { * vas[CPU] followed by vas[GPU]), collected raw for pass 2. */ #define PAIR_MAX 32768 -struct raw_pair { u64 cpu_va; u64 gpu_va; }; +struct raw_pair { u64 cpu_va; u64 gpu_va; u8 valid_hdr; }; static void svc_scan_pass(struct nv_vgpu_device *vgpu, u8 *buf, struct base_candidate *cands, u32 *ncand, @@ -404,8 +1048,32 @@ static void svc_scan_pass(struct nv_vgpu_device *vgpu, u8 *buf, b >= SEMA_VA_LO && b < SEMA_VA_HI && (a & 0xfff) == 0 && (b & 0xfff) == 0 && *npairs < PAIR_MAX) { + u8 ok = 0; + + /* + * UVM's uvm_rm_mem_t stores vas[UVM_ID_CPU] + * (offset 48) directly before vas[UVM_ID_GPU0] + * (offset 56), so a pair taken from a live rm_mem + * is preceded by the header {u32 type; bitmap + * mapped_on}. Validate it when it fits in the same + * page: freed allocations and unrelated tables + * that happen to hold KVA/VA pairs do not match, + * and a stale entry for the same VA must not + * shadow the live one. + */ + if (off >= 48) { + u32 type; + u64 mapped_on; + + memcpy(&type, buf + off - 48, 4); + memcpy(&mapped_on, buf + off - 40, 8); + if ((type == 0 || type == 1) && + (mapped_on & 0x3) == 0x3) + ok = 1; + } pairs[*npairs].cpu_va = a; pairs[*npairs].gpu_va = b; + pairs[*npairs].valid_hdr = ok; (*npairs)++; } } @@ -424,6 +1092,22 @@ static void svc_add_map(u64 gpu_va, u64 pa) } } +/* Add one scanned {CPU KVA, GPU VA} pair to the map. */ +static void svc_add_pair(u64 base, const struct raw_pair *p) +{ + u64 pa; + + if (p->cpu_va < base) + return; + pa = p->cpu_va - base; + if (pa >= 0x140000000ULL || (pa & 0xfff)) + return; + if (p->gpu_va < SEMA_VA_LO || p->gpu_va >= SEMA_VA_HI) + return; + svc_add_map(p->gpu_va, pa); + svc.pairs_seen++; +} + static int svc_map_cmp(const void *a, const void *b) { const struct sema_map_entry *x = a, *y = b; @@ -495,6 +1179,28 @@ static int svc_map_lookup(u64 gpu_va, u64 *pa) return -ENOENT; } +/* Binary search over a sorted sub-range (used while the tail is unsorted). */ +static int svc_map_lookup_range(u64 gpu_va, u32 from, u32 to, u64 *pa) +{ + u64 gpu_page = gpu_va & ~(u64)(SEMA_PAGE_SIZE - 1); + int lo = (int)from, hi = (int)to - 1; + + while (lo <= hi) { + int mid = (lo + hi) / 2; + + if (svc.map[mid].gpu_page == gpu_page) { + *pa = svc.map[mid].pa_page + + (gpu_va & (SEMA_PAGE_SIZE - 1)); + return 0; + } + if (svc.map[mid].gpu_page < gpu_page) + lo = mid + 1; + else + hi = mid - 1; + } + return -ENOENT; +} + static void svc_rebuild(struct nv_vgpu_device *vgpu) { static u8 *buf; @@ -521,32 +1227,47 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu) } if (best_base && best_count >= 2) { + u32 n_valid; + svc.direct_base = best_base; + + /* + * Live rm_mem-style records first (dedup is first-wins), then + * headerless candidates only for VAs the validated set does not + * cover - a freed allocation or an unrelated table holding a + * stale pair for the same VA must not shadow the live record. + */ + for (i = 0; i < npairs; i++) { + if (pairs[i].valid_hdr) + svc_add_pair(best_base, &pairs[i]); + } + n_valid = svc.map_n; + sort(svc.map, n_valid, sizeof(svc.map[0]), svc_map_cmp, NULL); for (i = 0; i < npairs; i++) { - u64 cpu = pairs[i].cpu_va; - u64 pa; + u64 dummy; - if (cpu < best_base) - continue; - pa = cpu - best_base; - if (pa >= 0x140000000ULL || (pa & 0xfff)) + if (pairs[i].valid_hdr) continue; - if (pairs[i].gpu_va < SEMA_VA_LO || - pairs[i].gpu_va >= SEMA_VA_HI) + if (!svc_map_lookup_range(pairs[i].gpu_va, 0, n_valid, + &dummy)) continue; - svc_add_map(pairs[i].gpu_va, pa); - svc.pairs_seen++; + svc_add_pair(best_base, &pairs[i]); } } + /* Stale entries from a previous rebuild (wrong PA for a reused VA) + * must not outlive the map they were derived from. */ + memset(svc.lat, 0, sizeof(svc.lat)); + svc_map_finalize(); svc.builds++; svc.last_build = jiffies; - pr_info("sema-svc: rebuild #%llu base=0x%llx (count=%u) pairs=%u map=%u seen=%llu applied=%llu miss=%llu stale=%llu jump=%llu\n", + pr_info("sema-svc: rebuild #%llu base=0x%llx (count=%u) pairs=%u map=%u seen=%llu applied=%llu miss=%llu stale=%llu jump=%llu walks=%llu/%llu lat=%llu\n", svc.builds, best_base, best_count, npairs, svc.map_n, svc.releases_seen, svc.releases_applied, svc.releases_missed, - svc.releases_stale, svc.releases_jumped); + svc.releases_stale, svc.releases_jumped, + svc.walks_ok, svc.walks_ok + svc.walks_miss, svc.lat_hits); pr_info("sema-svc: pages=%llu pushes=%llu pending=%llu nomap=%llu memset=%llu/%llu/%llu\n", svc.pages_decoded, svc.pushes_seen, svc.pushes_pending, svc.pushes_nomap, svc.memsets_done, svc.memsets_nomap, @@ -631,6 +1352,8 @@ struct svc_push_state { u32 have_remap; u32 release_dma; u32 sem_inc; + u32 rs_upper, rs_lower, rs_payload; + u32 have_rs_addr, have_rs_payload; }; /* ------------------------------------------------------------------ */ @@ -925,11 +1648,73 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf, continue; } - if (subch != 4) { + /* + * SET_REPORT_SEMAPHORE_A..D (all TURING+ classes): one run of + * four words {offset_upper, offset_lower, payload, operation} + * or the same words written one at a time. A RELEASE operation + * ends the push: run the collected work, then write the + * payload to the semaphore (forward progress only, so recycled + * buffers stay harmless). + */ + if (addr >= HOST_M_REPORT_SEM_A && addr <= HOST_M_REPORT_SEM_D) { + u32 vv[4] = { v0, v1, v2, v3 }; + u32 k, n = count > 4 ? 4 : count; + + for (k = 0; k < n; k++) { + switch (addr + 4 * k) { + case HOST_M_REPORT_SEM_A: + st.rs_upper = vv[k] & 0xff; + st.have_rs_addr = 1; + break; + case HOST_M_REPORT_SEM_B: + st.rs_lower = vv[k]; + st.have_rs_addr = 1; + break; + case HOST_M_REPORT_SEM_C: + st.rs_payload = vv[k]; + st.have_rs_payload = 1; + break; + case HOST_M_REPORT_SEM_D: { + u64 va; + u32 cur = 0; + + if ((vv[k] & HOST_RS_OP_MASK) != + HOST_RS_OP_RELEASE || + !st.have_rs_addr || + !st.have_rs_payload || + !st.rs_payload) + break; + va = ((u64)st.rs_upper << 32) | + st.rs_lower; + if (va < SEMA_VA_LO || va >= SEMA_VA_HI) + break; + if (page_pa) + svc_mark_hot(page_pa); + svc.pushes_seen++; + if (svc_sem_read(vgpu, va, &cur)) + break; + if (st.rs_payload <= cur) + break; + svc.pushes_pending++; + svc_run_ops(vgpu, ops, n_ops); + n_ops = 0; + if (!svc_sem_write(vgpu, va, + st.rs_payload)) + svc.releases_applied++; + break; + } + default: + break; + } + } off += 4 + count * 4; continue; } + if (subch != 4) { + off += 4 + count * 4; + continue; + } switch (addr) { case CE_M_SET_SEMAPHORE_A: st.sem_va_hi = v0; @@ -1253,11 +2038,181 @@ static void svc_apply_mapped(struct nv_vgpu_device *vgpu) static int svc_read_va(struct nv_vgpu_device *vgpu, u64 va, void *buf, size_t len) { - u64 pa; + u8 *p = buf; + + while (len) { + u64 pa = 0; + size_t chunk = min_t(size_t, len, + SEMA_PAGE_SIZE - (va & (SEMA_PAGE_SIZE - 1))); + int ap = SVC_APERTURE_SYS; + + if (svc_lookup(vgpu, va, &pa, &ap)) + return -ENOENT; + if (svc_read_ap(vgpu, pa, + ap == SVC_APERTURE_FB ? SVC_APERTURE_FB + : SVC_APERTURE_SYS, + p, chunk)) + return -EIO; + p += chunk; + va += chunk; + len -= chunk; + } + return 0; +} + +/* + * Read `len` bytes at a GPU VA through a trusted translation only. Push text + * must never be fetched through the vidmem window convention: a sysmem-backed + * pushbuffer VA above the window base answers with a vidmem offset and the + * caller would decode zeros and mark work done that never ran. + */ +static int svc_read_va_trusted(struct nv_vgpu_device *vgpu, u64 va, void *buf, + size_t len) +{ + u8 *p = buf; + + while (len) { + u64 pa = 0; + size_t chunk = min_t(size_t, len, + SEMA_PAGE_SIZE - (va & (SEMA_PAGE_SIZE - 1))); + int ap = SVC_APERTURE_SYS; - if (svc_map_lookup(va, &pa)) + if (svc_lookup_trusted(vgpu, va, &pa, &ap)) + return -ENOENT; + if (svc_read_ap(vgpu, pa, + ap == SVC_APERTURE_FB ? SVC_APERTURE_FB + : SVC_APERTURE_SYS, + p, chunk)) + return -EIO; + p += chunk; + va += chunk; + len -= chunk; + } + return 0; +} + +/* + * Read the GPFIFO get/put pair from a candidate USERD. Returns 0 when the + * values are plausible for a channel with `entries` GPFIFO slots. + */ +static int svc_userd_probe(struct nv_vgpu_device *vgpu, u64 pa, u32 as, + u32 entries, u32 *put, u32 *get) +{ + u32 p = 0, g = 0; + + if (!pa) + return -EINVAL; + if (svc_read_ap(vgpu, pa + SVC_USERD_GP_PUT, as, &p, sizeof(p))) + return -ENOENT; + if (svc_read_ap(vgpu, pa + SVC_USERD_GP_GET, as, &g, sizeof(g))) return -ENOENT; - return svc_read(vgpu, pa, buf, len); + if (p > entries || g > p) + return -EINVAL; + if (!p && !g) + return -EAGAIN; /* plausible but idle: keep looking */ + *put = p; + *get = g; + return 0; +} + +/* + * Locate a channel's USERD. The allocation parameters carry an offset that is + * relative to a client pool the emulation cannot see, so it is only one + * candidate: for the combined GPFIFO+USERD allocations the control block sits + * 0x2000 bytes into the same 0x3000 chunk as the ring (observed layout). + * Whichever candidate reads a coherent get/put pair wins and is cached. An + * idle candidate (0/0) is not accepted until it shows activity, so a zeroed + * page is never mistaken for the control block. + */ +static int svc_chan_userd(struct nv_vgpu_device *vgpu, struct svc_channel *ch, + u64 ring_pa, int ring_ap, u32 *put, u32 *get) +{ + u64 cand[4]; + u32 as[4], nc = 0, i; + + if (ring_ap == SVC_APERTURE_FB && ring_pa) { + cand[nc] = ring_pa + 0x2000; as[nc++] = SVC_APERTURE_FB; + cand[nc] = ring_pa + 0x1000; as[nc++] = SVC_APERTURE_FB; + } + cand[nc] = ch->userd_pa; as[nc++] = ch->userd_as; + + for (i = 0; i < nc; i++) { + if (!cand[i]) + continue; + if (!svc_userd_probe(vgpu, cand[i], as[i], ch->gpfifo_entries, + put, get)) { + ch->userd_pa = cand[i]; + ch->userd_as = as[i]; + ch->userd_ok = true; + return 0; + } + } + return -ENOENT; +} + +/* + * Complete a channel's work-submission notification the way the GPU does. + * + * The driver marks the channel's WORK_SUBMIT_TOKEN notification slot + * in-progress (status 0xffff, info32 = work-submit token) and then polls the + * notification memory for it to come back cleared - the GPU clearing it is + * the acknowledgement that the submission was picked up. The emulated GPU + * never did that, so libcuda's context setup spun forever. Write the cleared + * notification when the slot is busy. + * + * NvNotification layout (16 bytes): timestamp[2], info32, info16, status. + */ +#define SVC_NOTIFY_SIZE 16 +#define SVC_NOTIFY_STATUS_OFF 14 +#define SVC_NOTIFY_INFO32_OFF 8 +#define SVC_NOTIFY_TOKEN_SLOT 1 /* NV_CHANNELGPFIFO_NOTIFICATION_TYPE_WORK_SUBMIT_TOKEN */ + +static void svc_notify_complete(struct nv_vgpu_device *vgpu, + struct svc_channel *ch) +{ + u8 zero[SVC_NOTIFY_SIZE + SVC_NOTIFY_SIZE] = {0}; + u64 base = ch->notifier_pa; + u32 status = 0, info32 = 0; + int slot, busy = 0; + + if (!ch->notifier_pa) + return; + + /* Both the error slot (0) and the work-submit-token slot (1) must read + * as completed; the client polls one of them per submission. */ + for (slot = 0; slot <= SVC_NOTIFY_TOKEN_SLOT && !busy; slot++) { + u64 pa = base + slot * SVC_NOTIFY_SIZE; + + status = 0; + info32 = 0; + if (svc_read_ap(vgpu, pa + SVC_NOTIFY_STATUS_OFF, + ch->notifier_as, &status, 2) || + svc_read_ap(vgpu, pa + SVC_NOTIFY_INFO32_OFF, + ch->notifier_as, &info32, 4)) + continue; + if (status || info32) + busy = 1; + } + if (!busy) + return; + + /* + * Clear both slots: they are adjacent, and whichever one the client is + * polling must read as completed for a healthy channel. + */ + if (!svc_write_ap(vgpu, base, ch->notifier_as, zero, sizeof(zero))) { + ch->notified++; + if (ch->notified <= 4 || (ch->notified % 10000) == 0) + pr_info("sema-chan: notifier cleared h=0x%x info32=0x%x status=0x%x (n=%llu)\n", + ch->h_object, info32, status, ch->notified); + + /* + * The GPU consumed the submission: post the channel's event so + * the client's CPU-side RM signals the OS event the submitting + * thread waits on (a real GPU would raise an interrupt here). + */ + gsp_rm_post_channel_event(vgpu, ch->h_object); + } } /* @@ -1284,18 +2239,39 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) for (i = 0; i < n; i++) { struct svc_channel *ch = &local[i]; + u64 ring_pa = 0; + int ring_ap = SVC_APERTURE_SYS; + int rrc; u32 put = 0, get = 0, guard = 0; - if (!ch->userd_pa) - continue; + svc_notify_complete(vgpu, ch); - if (svc_read_ap(vgpu, ch->userd_pa + SVC_USERD_GP_PUT, - ch->userd_as, &put, sizeof(put))) { - svc.gpfifo_nomap++; + if (!ch->gpfifo_va || !ch->gpfifo_entries) continue; + + rrc = svc_lookup(vgpu, ch->gpfifo_va, &ring_pa, &ring_ap); + + if (!ch->userd_ok) { + if (svc_chan_userd(vgpu, ch, rrc ? 0 : ring_pa, + rrc ? -1 : ring_ap, &put, &get)) + continue; + } else { + if (svc_read_ap(vgpu, ch->userd_pa + SVC_USERD_GP_PUT, + ch->userd_as, &put, sizeof(put))) + continue; + (void)svc_read_ap(vgpu, + ch->userd_pa + SVC_USERD_GP_GET, + ch->userd_as, &get, sizeof(get)); + } + + if (!ch->logged) { + pr_info("sema-chan: h=0x%x eng=%u cid=%u gpfifo=0x%llx ent=%u userd=0x%llx as=%u put=%u get=%u ring rc=%d pa=0x%llx ap=%d\n", + ch->h_object, ch->engine_type, ch->cid, + ch->gpfifo_va, + ch->gpfifo_entries, ch->userd_pa, ch->userd_as, + put, get, rrc, ring_pa, ring_ap); + ch->logged = true; } - (void)svc_read_ap(vgpu, ch->userd_pa + SVC_USERD_GP_GET, - ch->userd_as, &get, sizeof(get)); if (put >= ch->gpfifo_entries) put %= ch->gpfifo_entries; @@ -1305,6 +2281,9 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) u64 va, idx = ch->last_get; u32 len, e0, e1, g; + if (rrc) + break; /* cannot read the ring at all */ + if (svc_read_va(vgpu, ch->gpfifo_va + idx * SVC_GPFIFO_ENTRY_SIZE, @@ -1320,17 +2299,29 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) /* GP_ENTRY1.LENGTH[30:10], in dwords */ len = ((e1 >> 10) & 0x1fffff) << 2; + if (svc.gpfifo_entry_log < 32) { + pr_info("sema-chan: entry[%llu] raw=%08x%08x va=0x%llx len=%u\n", + idx, e1, e0, va, len); + svc.gpfifo_entry_log++; + } + if (va && len && len <= SVC_PUSH_MAX) { u8 *pb = kmalloc(len, GFP_KERNEL); if (pb) { - if (!svc_read_va(vgpu, va, pb, len)) { + if (!svc_read_va_trusted(vgpu, va, pb, + len)) { svc_decode_stream(vgpu, pb, len, 0); ch->submissions++; svc.gpfifo_entries_done++; } else { + /* Keep the entry pending: the + * mapping may resolve once the + * page tables are filled. */ svc.gpfifo_nomap++; + kfree(pb); + break; } kfree(pb); } @@ -1348,6 +2339,11 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) for (k = 0; k < SVC_MAX_CHANNELS; k++) { if (svc.chan[k].valid && svc.chan[k].h_object == ch->h_object) { + svc.chan[k].userd_pa = ch->userd_pa; + svc.chan[k].userd_as = ch->userd_as; + svc.chan[k].userd_ok = ch->userd_ok; + svc.chan[k].logged = ch->logged; + svc.chan[k].notified = ch->notified; svc.chan[k].last_get = ch->last_get; svc.chan[k].submissions = ch->submissions; break; @@ -1383,19 +2379,30 @@ static void svc_handle_probe(struct nv_vgpu_device *vgpu) probe_va, probe_va & ~(u64)(SEMA_PAGE_SIZE - 1), svc.direct_base, svc.map_n); - if (svc_map_lookup(probe_va, &pa)) { - pr_info("sema-probe: NO MAP ENTRY for page 0x%llx\n", - probe_va & ~(u64)(SEMA_PAGE_SIZE - 1)); - } else { - pr_info("sema-probe: pa=0x%llx\n", pa); - for (i = 0; i < probe_n; i++) { - u32 v = 0; + { + int ap = SVC_APERTURE_SYS; - if (svc_read(vgpu, pa + 4ULL * i, &v, sizeof(v))) { - pr_info("sema-probe: [%u] read failed\n", i); - break; + if (svc_lookup(vgpu, probe_va, &pa, &ap)) { + pr_info("sema-probe: NO ENTRY for page 0x%llx (walks %llu/%llu)\n", + probe_va & ~(u64)(SEMA_PAGE_SIZE - 1), + svc.walks_ok, svc.walks_ok + svc.walks_miss); + } else { + pr_info("sema-probe: pa=0x%llx aperture=%s\n", pa, + ap == SVC_APERTURE_FB ? "vidmem" : "ram"); + for (i = 0; i < probe_n; i++) { + u32 v = 0; + + if (svc_read_ap(vgpu, pa + 4ULL * i, + ap == SVC_APERTURE_FB ? + SVC_APERTURE_FB : + SVC_APERTURE_SYS, + &v, sizeof(v))) { + pr_info("sema-probe: [%u] read failed\n", + i); + break; + } + pr_info("sema-probe: [%u] 0x%08x\n", i, v); } - pr_info("sema-probe: [%u] 0x%08x\n", i, v); } } @@ -1422,6 +2429,59 @@ int gsp_sema_service_probe(u64 gpu_va, u32 count) return 0; } +/* + * Debug injection: teach the service one {GPU VA -> guest PA} translation. + * Some client mappings (user VA == GPU VA, sysmem backed) are not + * discoverable from the structures the service scans; this lets a session + * feed a pair that was verified from the guest and watch push execution use + * it. + */ +static struct completion map_add_done; +static u64 map_add_va; +static u64 map_add_pa; +static u64 map_add_len; +static bool map_add_pending; + +static void svc_handle_map_add(void) +{ + u64 off; + + if (!map_add_pending) + return; + + for (off = 0; off < (map_add_len ? map_add_len : 1); + off += SEMA_PAGE_SIZE) { + svc_pin_store(map_add_va + off, map_add_pa + off); + svc_lat_store(map_add_va + off, map_add_pa + off, + SVC_APERTURE_SYS); + } + pr_info("sema-svc: injected mapping va=0x%llx -> pa=0x%llx len=0x%llx (sysmem)\n", + map_add_va, map_add_pa, map_add_len); + map_add_pending = false; + complete(&map_add_done); +} + +int gsp_sema_service_add_map(u64 gpu_va, u64 pa, u64 len) +{ + if (!svc.task) + return -ENODEV; + + if (len > (64ULL << 20)) + return -EINVAL; + + init_completion(&map_add_done); + map_add_va = gpu_va; + map_add_pa = pa; + map_add_len = len; + map_add_pending = true; + + if (!wait_for_completion_timeout(&map_add_done, 3 * HZ)) { + map_add_pending = false; + return -ETIMEDOUT; + } + return 0; +} + static int svc_thread(void *arg) { struct nv_vgpu_device *vgpu; @@ -1449,6 +2509,7 @@ static int svc_thread(void *arg) } svc_handle_probe(vgpu); + svc_handle_map_add(); if (!svc.direct_base || time_after(jiffies, svc.last_build + 10 * HZ)) @@ -1458,8 +2519,10 @@ static int svc_thread(void *arg) * Channel-driven execution: notice submissions as they are made * and run the pushed work immediately, so the guest's UVM * progress waits complete in milliseconds rather than in the - * time it takes the fallback scan to find the push. + * time it takes the fallback scan to find the push. A doorbell + * token means a channel was just scheduled; run everything now. */ + svc.doorbell_pending = false; svc_pump_channels(vgpu); /* @@ -1484,15 +2547,39 @@ static int svc_thread(void *arg) pr_info("sema-svc: thread stopping (pairs=%llu releases_seen=%llu applied=%llu map=%u)\n", svc.pairs_seen, svc.releases_seen, svc.releases_applied, svc.map_n); + complete(&svc.stopped); return 0; } +/* + * Work-submission doorbell (NV_VIRTUAL_FUNCTION_DOORBELL, token at bits 11:0 + * VECTOR / 22:16 RUNLIST_ID). The guest channel pokes the token after it has + * written the puller GPFIFO entry, so a pending token means "run this + * channel's entries now". Called from the VFIO write path (atomic context): + * record the token and let the service thread do the work. + */ +void gsp_sema_service_doorbell(u32 token) +{ + svc.doorbell_token = token; + svc.doorbell_count++; + svc.doorbell_pending = true; + + if (svc.doorbell_log < 64) { + pr_info("sema-svc: doorbell token=0x%x runlist=%u vector=%u (count=%llu)\n", + token, (token >> 16) & 0x7f, token & 0xfff, + svc.doorbell_count); + svc.doorbell_log++; + } +} + int gsp_sema_service_start(void) { if (svc.task) return 0; svc.stop = false; + init_completion(&svc.stopped); spin_lock_init(&svc.chan_lock); + spin_lock_init(&svc.vas_lock); svc.task = kthread_run(svc_thread, NULL, "gsp-sema-svc"); if (IS_ERR(svc.task)) { int ret = PTR_ERR(svc.task); @@ -1503,11 +2590,18 @@ int gsp_sema_service_start(void) return 0; } +/* + * Stop the service thread without kthread_stop(): the thread can also end on + * its own if it ever faults, and joining a task that is already gone faults in + * the caller (which wedges the whole module). The completion tells us the + * thread function has returned, whatever ended it. + */ void gsp_sema_service_stop(void) { - if (svc.task) { - svc.stop = true; - kthread_stop(svc.task); - svc.task = NULL; - } + if (!svc.task) + return; + svc.stop = true; + if (!wait_for_completion_timeout(&svc.stopped, 5 * HZ)) + pr_err("sema-svc: thread did not stop within 5s\n"); + svc.task = NULL; } |
