diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-23 06:31:44 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-23 06:31:44 -0400 |
| commit | 7fdbe8713c8cf746cd1a15e93e0de39d333905c7 (patch) | |
| tree | 7850a202badb31c9ba20ba33ea0896ac4819c77b /kernel | |
| parent | d7e578b41b3c9d1ff6f5f705a27fe80c6e3a6520 (diff) | |
| download | open-iov-7fdbe8713c8cf746cd1a15e93e0de39d333905c7.tar.gz open-iov-7fdbe8713c8cf746cd1a15e93e0de39d333905c7.zip | |
kernel: execute pushed CE work (PTE memsets, copies) and track semaphores
The progress service now decodes push buffers as method streams and performs
the work the driver pushes, not just the tracking semaphore:
- C5B5 method decoder: SET_REMAP_CONST_A/B + COMPONENTS + OFFSET_OUT +
LINE_LENGTH_IN + LAUNCH_DMA = memset (into vidmem or guest RAM);
OFFSET_IN + OFFSET_OUT + LAUNCH_DMA = copy; SET_SEMAPHORE_A/B/PAYLOAD +
LAUNCH_DMA = release. Contiguous method runs (count 2/4) are applied in
full - OFFSET_IN/OUT arrive as one run.
- Pushes span page boundaries, so pages are decoded as method streams
without a start marker. Collected operations execute only when the
push's release is reached and still pending (payload ahead of the
semaphore), so recycled buffers replaying old pushes are skipped and
their stale memsets cannot overwrite newer state.
- VA->PA map is first-wins: stale copies of freed allocations and
unrelated adjacent u64 pairs produced bogus entries that hijacked a
good mapping and sent semaphore writes to the wrong page.
- dump_guest module parameter (hex dump of guest PA, kthread via
completion; kthread_stop before the thread runs loses the work).
With this the guest no longer stalls in uvm_tracker_wait and no longer
oopses in the UVM path; the failure moved to a userspace segfault inside
libcuda (NULL-0x38) after the driver's page allocations.
Also add tools/cuda-ab: run one deterministic CUDA workload on the host
(bare metal oracle) and in the guest, then compare the stable result
lines. The binary links the CUDA runtime statically so the guest only
needs the driver's libcuda.so.
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/gsp_sema_service.c | 487 | ||||
| -rw-r--r-- | kernel/main.c | 89 |
2 files changed, 573 insertions, 3 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 68a890c..1ed8c99 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -82,6 +82,13 @@ struct sema_svc { /* stats */ u64 pairs_seen; + u64 pages_decoded; + u64 pushes_seen; + u64 pushes_pending; + u64 pushes_nomap; + u64 memsets_done; + u64 memsets_nomap; + u64 memsets_fail; u64 releases_seen; u64 releases_applied; u64 releases_missed; /* no VA->PA entry */ @@ -187,11 +194,16 @@ static void svc_add_map(u64 gpu_va, u64 pa) u64 pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1); u32 i; + /* + * First-wins: the guest keeps stale copies of freed allocations around, + * and unrelated adjacent u64 pairs can look like {cpu_va, gpu_va}. Both + * produce bogus mappings for a VA that already has a good one, and + * overwriting it would send every semaphore write to the wrong page. + * The first pair seen for a VA is the live allocation. + */ for (i = 0; i < svc.map_n; i++) { - if (svc.map[i].gpu_page == gpu_page) { - svc.map[i].pa_page = pa_page; + if (svc.map[i].gpu_page == gpu_page) return; - } } if (svc.map_n < SEMA_MAP_MAX) { svc.map[svc.map_n].gpu_page = gpu_page; @@ -264,6 +276,10 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu) 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); + 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, + svc.memsets_fail); /* show a few entries so mappings can be verified from dmesg */ { u32 shown; @@ -279,6 +295,464 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu) /* push application */ /* ------------------------------------------------------------------ */ +/* C5B5 (TURING_DMA_COPY_A) methods, from the class interface. */ +#define CE_M_SET_SEMAPHORE_A 0x240 +#define CE_M_SET_SEMAPHORE_B 0x244 +#define CE_M_SET_SEMAPHORE_PAY 0x248 +#define CE_M_SET_SRC_PHYS_MODE 0x260 +#define CE_M_SET_DST_PHYS_MODE 0x264 +#define CE_M_LAUNCH_DMA 0x300 +#define CE_M_OFFSET_IN_UPPER 0x400 +#define CE_M_OFFSET_IN_LOWER 0x404 +#define CE_M_OFFSET_OUT_UPPER 0x408 +#define CE_M_OFFSET_OUT_LOWER 0x40c +#define CE_M_LINE_LENGTH_IN 0x418 +#define CE_M_SET_REMAP_CONST_A 0x700 +#define CE_M_SET_REMAP_CONST_B 0x704 +#define CE_M_SET_REMAP_COMPONENTS 0x708 + +/* LAUNCH_DMA fields. */ +#define DMA_DATA_TRANSFER_TYPE(v) ((v) & 0x3) +#define DMA_SEMAPHORE_TYPE(v) (((v) >> 3) & 0x3) +#define DMA_DST_MEMORY_LAYOUT_PITCH(v) (((v) >> 8) & 0x1) +#define DMA_REMAP_ENABLE(v) (((v) >> 10) & 0x1) +#define DMA_SRC_TYPE_PHYSICAL(v) (((v) >> 12) & 0x1) +#define DMA_DST_TYPE_PHYSICAL(v) (((v) >> 13) & 0x1) +#define DMA_SEM_REDUCTION(v) (((v) >> 14) & 0xf) +#define DMA_SEM_REDUCTION_EN(v) (((v) >> 19) & 0x1) +#define SEM_RELEASE_ONE_WORD 1 +#define SEM_REDUCTION_INC 6 + +/* Aperture targets (SET_*_PHYS_MODE). */ +#define CE_APERTURE_VID 0 +#define CE_APERTURE_SYS 1 +#define CE_APERTURE_SYS_NC 2 + +#define SVC_MAX_MEMSETS 32 + +struct svc_memset_op { + u32 type; /* 0 = fill, 1 = copy */ + u64 dst; + u64 value; /* fill value, up to 8 bytes */ + u32 elem; /* 1, 4 or 8 */ + u32 count; /* elements */ + u32 phys; /* DST_TYPE physical */ + u32 aperture; /* when physical */ + u64 src; /* copy source */ + u64 bytes; /* copy length */ + u32 src_phys; + u32 src_aperture; +}; + +/* Decoder state carried across the methods of one push. */ +struct svc_push_state { + u32 remap_a, remap_b, remap_comp; + u32 dst_phys_mode, src_phys_mode; + u32 have_dst_mode, have_src_mode; + u64 offset_out, offset_in; + u32 have_in, have_out; + u32 line_length; + u32 sem_va_hi, sem_va_lo, sem_payload; + u32 have_sem; + u32 have_remap; + u32 release_dma; + u32 sem_inc; +}; + +/* ------------------------------------------------------------------ */ +/* CE execution */ +/* ------------------------------------------------------------------ */ + +/* + * Write `count` elements of `value` (element size `elem`) starting at + * `addr`, in vidmem or guest RAM depending on the aperture DST_TYPE + * selected. Returns 0 on success. + */ +static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem, + u32 count, u32 phys, u32 aperture) +{ + u8 pat[8]; + u8 bounce[256]; + size_t total = (size_t)elem * count; + size_t o; + + if (elem != 1 && elem != 2 && elem != 4 && elem != 8) + return -EINVAL; + if (!count || total > (256ULL << 20)) + return -EINVAL; + + memset(pat, 0, sizeof(pat)); + memcpy(pat, &value, elem); + + if (!phys) { + /* Virtual destination: translate through the VA->PA map. */ + u64 pa; + + if (svc_map_lookup(addr, &pa)) + return -ENOENT; + addr = pa; + aperture = CE_APERTURE_SYS; + } + + if (aperture == CE_APERTURE_VID) { + u8 *base = vgpu->bars[1].vaddr; + size_t size = vgpu->bars[1].size; + + if (!base || addr + total > size) + return -EINVAL; + for (o = 0; o + 8 <= total; o += 8) + memcpy(base + addr + o, pat, 8); + if (o < total) + memcpy(base + addr + o, pat, total - o); + return 0; + } + + /* System memory: repeat the element through a bounce buffer. */ + for (o = 0; o < total; ) { + size_t chunk = total - o; + size_t i; + + if (chunk > sizeof(bounce)) + chunk = sizeof(bounce); + for (i = 0; i + 8 <= chunk; i += 8) + memcpy(bounce + i, pat, 8); + if (i < chunk) + memcpy(bounce + i, pat, chunk - i); + if (svc_write(vgpu, addr + o, bounce, chunk)) + return -EIO; + o += chunk; + } + return 0; +} + +/* Byte copy between two CE addresses (src/dst decoding mirrors svc_fill). */ +static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total, + u32 dst_phys, u32 dst_aperture, u32 src_phys, u32 src_aperture) +{ + u8 bounce[256]; + size_t o; + + if (!total || total > (256ULL << 20)) + return -EINVAL; + + if (!dst_phys) { + u64 pa; + + if (svc_map_lookup(dst, &pa)) + return -ENOENT; + dst = pa; + dst_aperture = CE_APERTURE_SYS; + } + if (!src_phys) { + u64 pa; + + if (svc_map_lookup(src, &pa)) + return -ENOENT; + src = pa; + src_aperture = CE_APERTURE_SYS; + } + + for (o = 0; o < total; ) { + size_t chunk = total - o; + u32 i; + + if (chunk > sizeof(bounce)) + chunk = sizeof(bounce); + + if (src_aperture == CE_APERTURE_VID) { + u8 *base = vgpu->bars[1].vaddr; + size_t size = vgpu->bars[1].size; + + if (!base || src + o + chunk > size) + return -EINVAL; + memcpy(bounce, base + src + o, chunk); + } else { + if (svc_read(vgpu, src + o, bounce, chunk)) + return -EIO; + } + + if (dst_aperture == CE_APERTURE_VID) { + u8 *base = vgpu->bars[1].vaddr; + size_t size = vgpu->bars[1].size; + + if (!base || dst + o + chunk > size) + return -EINVAL; + memcpy(base + dst + o, bounce, chunk); + } else { + if (svc_write(vgpu, dst + o, bounce, chunk)) + return -EIO; + } + (void)i; + o += chunk; + } + return 0; +} + +/* Execute a decoded operation list (fills and copies). */ +static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops, + u32 n_ops) +{ + u32 i; + + for (i = 0; i < n_ops; i++) { + struct svc_memset_op *m = &ops[i]; + int rc; + + if (m->type == 1) + rc = svc_copy(vgpu, m->dst, m->src, m->bytes, + m->phys, m->aperture, + m->src_phys, m->src_aperture); + else + rc = svc_fill(vgpu, m->dst, m->value, m->elem, + m->count, m->phys, m->aperture); + + if (rc == 0) + svc.memsets_done++; + else if (rc == -ENOENT) + svc.memsets_nomap++; + else + svc.memsets_fail++; + if (svc.memsets_done <= 8) + pr_info("sema-svc: %s dst=0x%llx value=0x%llx elem=%u count=%u bytes=%llu phys=%u ap=%u src=0x%llx rc=%d\n", + m->type == 1 ? "memcopy" : "memset", + m->dst, m->value, m->elem, m->count, + m->bytes, m->phys, m->aperture, m->src, rc); + } +} + +/* + * Decode one page as a method stream and execute the pushes it contains. + * + * Push buffers are written back to back and pushes can span page + * boundaries, so there is no reliable "push start" marker to anchor on: + * decode the whole page as a method stream, carrying decoder state across + * the methods. Collected memory operations are executed only when the + * push's tracking semaphore release is reached and is still pending + * (payload ahead of the semaphore's current value) - recycled buffers + * replay old pushes whose semaphore is already satisfied, and those are + * skipped, so their stale memsets never overwrite newer state. + */ +static void svc_decode_page(struct nv_vgpu_device *vgpu, u8 *buf, u64 pa_page) +{ + struct svc_push_state st; + struct svc_memset_op ops[SVC_MAX_MEMSETS]; + u32 n_ops = 0; + u32 off = 0; + + memset(&st, 0, sizeof(st)); + svc.pages_decoded++; + + while (off + 4 <= SEMA_PAGE_SIZE) { + u32 hdr = 0, op, count, subch, addr; + u32 v0 = 0, v1 = 0, v2 = 0; + + memcpy(&hdr, buf + off, 4); + op = (hdr >> 29) & 0x7; + count = (hdr >> 16) & 0xfff; + subch = (hdr >> 13) & 0x7; + addr = (hdr & 0x1fff) << 2; + + if (op != 1 || !count || count > 64 || + off + 4 + count * 4 > SEMA_PAGE_SIZE) { + off += 4; /* resync */ + continue; + } + + if (count >= 1) + memcpy(&v0, buf + off + 4, 4); + if (count >= 2) + memcpy(&v1, buf + off + 8, 4); + if (count >= 3) + memcpy(&v2, buf + off + 12, 4); + + /* + * OFFSET_IN_UPPER/LOWER and OFFSET_OUT_UPPER/LOWER are + * contiguous (0x400..0x40c) and the driver pushes them as a + * single INC_METHOD run (count 2 or 4). Apply every word of + * the run, otherwise the low halves stay zero. + */ + if (addr >= CE_M_OFFSET_IN_UPPER && + addr <= CE_M_OFFSET_OUT_LOWER) { + u32 vv[4] = { v0, v1, v2, 0 }; + u32 k, n = count > 4 ? 4 : count; + + if (n == 4) + memcpy(&vv[3], buf + off + 16, 4); + for (k = 0; k < n; k++) { + switch (addr + 4 * k) { + case CE_M_OFFSET_IN_UPPER: + st.offset_in = + (st.offset_in & 0xffffffffULL) | + ((u64)(vv[k] & 0x1ffff) << 32); + st.have_in = 1; + break; + case CE_M_OFFSET_IN_LOWER: + st.offset_in = + (st.offset_in & ~0xffffffffULL) | + vv[k]; + st.have_in = 1; + break; + case CE_M_OFFSET_OUT_UPPER: + st.offset_out = + (st.offset_out & 0xffffffffULL) | + ((u64)(vv[k] & 0x1ffff) << 32); + st.have_out = 1; + break; + case CE_M_OFFSET_OUT_LOWER: + st.offset_out = + (st.offset_out & ~0xffffffffULL) | + vv[k]; + st.have_out = 1; + break; + default: + break; + } + } + off += 4 + count * 4; + continue; + } + + if (subch == 0 && addr == 0) { + /* SET_OBJECT on the channel subchannel: a push starts */ + n_ops = 0; + memset(&st, 0, sizeof(st)); + 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; + if (count >= 3) { + st.sem_va_lo = v1; + st.sem_payload = v2; + st.have_sem = 1; + } + break; + case CE_M_SET_SEMAPHORE_B: + st.sem_va_lo = v0; + st.have_sem = 1; + break; + case CE_M_SET_SEMAPHORE_PAY: + st.sem_payload = v0; + st.have_sem = 1; + break; + case CE_M_SET_REMAP_CONST_A: + if (count >= 3) { + st.remap_a = v0; + st.remap_b = v1; + st.remap_comp = v2; + st.have_remap = 1; + } + break; + case CE_M_SET_SRC_PHYS_MODE: + st.src_phys_mode = v0 & 0x3; + st.have_src_mode = 1; + break; + case CE_M_SET_DST_PHYS_MODE: + st.dst_phys_mode = v0 & 0x3; + st.have_dst_mode = 1; + break; + case CE_M_LINE_LENGTH_IN: + st.line_length = v0; + break; + case CE_M_LAUNCH_DMA: { + u32 dma = v0; + u32 transfer = DMA_DATA_TRANSFER_TYPE(dma); + + if (transfer != 0 && st.line_length && st.have_out) { + if (DMA_REMAP_ENABLE(dma) && st.have_remap && + DMA_DST_MEMORY_LAYOUT_PITCH(dma) && + n_ops < SVC_MAX_MEMSETS) { + u32 csz = (st.remap_comp >> 16) & 0x3; + u32 ndst = (st.remap_comp >> 24) & 0x3; + struct svc_memset_op *m = &ops[n_ops]; + + m->type = 0; + m->dst = st.offset_out; + m->value = ndst >= 1 ? + (((u64)st.remap_b << 32) | + st.remap_a) : st.remap_b; + m->elem = (csz + 1) * (ndst + 1); + m->count = st.line_length; + m->phys = DMA_DST_TYPE_PHYSICAL(dma); + m->aperture = st.have_dst_mode ? + st.dst_phys_mode : CE_APERTURE_VID; + n_ops++; + } else if (!DMA_REMAP_ENABLE(dma) && + st.have_in && + n_ops < SVC_MAX_MEMSETS) { + struct svc_memset_op *m = &ops[n_ops]; + + m->type = 1; + m->dst = st.offset_out; + m->bytes = st.line_length; + m->phys = DMA_DST_TYPE_PHYSICAL(dma); + m->aperture = st.have_dst_mode ? + st.dst_phys_mode : CE_APERTURE_VID; + m->src = st.offset_in; + m->src_phys = + DMA_SRC_TYPE_PHYSICAL(dma); + m->src_aperture = st.have_src_mode ? + st.src_phys_mode : CE_APERTURE_VID; + n_ops++; + } + } + + if (DMA_SEMAPHORE_TYPE(dma) == + SEM_RELEASE_ONE_WORD) { + u64 sem_va, sem_pa = 0; + u32 cur = 0; + int pending = 0; + + svc.pushes_seen++; + sem_va = ((u64)st.sem_va_hi << 32) | + st.sem_va_lo; + + if (st.have_sem && st.sem_payload && + sem_va >= SEMA_VA_LO && sem_va < SEMA_VA_HI && + !svc_map_lookup(sem_va, &sem_pa)) { + if (svc_read(vgpu, sem_pa, &cur, + sizeof(cur))) + svc.pushes_nomap++; + else if (st.sem_payload > cur) + pending = 1; + } + + if (pending) { + svc.pushes_pending++; + svc_run_ops(vgpu, ops, n_ops); + if (DMA_SEM_REDUCTION_EN(dma) && + DMA_SEM_REDUCTION(dma) == + SEM_REDUCTION_INC) + cur += st.sem_payload; + else + cur = st.sem_payload; + if (!svc_write(vgpu, sem_pa, &cur, + sizeof(cur))) + svc.releases_applied++; + } + /* release ends the push */ + n_ops = 0; + } + break; + } + default: + break; + } + off += 4 + count * 4; + } +} + +/* ------------------------------------------------------------------ */ +/* push application */ +/* ------------------------------------------------------------------ */ + /* * Look for CE semaphore-release method groups inside mapped pages: * @@ -298,6 +772,13 @@ static u32 svc_apply_page(struct nv_vgpu_device *vgpu, u8 *buf, u64 pa_page) if (svc_read(vgpu, pa_page, buf, SEMA_PAGE_SIZE)) return 0; + /* + * Decoded execution: walk the page as a method stream and perform the + * pushed work (PTE memsets, semaphore releases) for pushes that are + * still pending. + */ + svc_decode_page(vgpu, buf, pa_page); + for (off = 0; off + 16 <= SEMA_PAGE_SIZE; off += 4) { u32 hdr, hi, lo, payload; u64 va, pa, cur; diff --git a/kernel/main.c b/kernel/main.c index 637c79b..0a1ebc2 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -2560,5 +2560,94 @@ static const struct kernel_param_ops nv_vgpu_dump_vidmem_ops = { module_param_cb(dump_vidmem, &nv_vgpu_dump_vidmem_ops, NULL, 0200); MODULE_PARM_DESC(dump_vidmem, "Dump emulated vidmem range (hex offset,hex len)"); +/* + * Debug: hex-dump a guest physical range (kthread: guest-DMA needs it): + * echo 0x<gpa>,0x<len> > .../parameters/dump_guest + */ +struct nv_vgpu_dump_guest_req { + u64 gpa; + unsigned long len; + struct completion done; +}; + +static int nv_vgpu_dump_guest_thread(void *arg) +{ + struct nv_vgpu_dump_guest_req *req = arg; + struct nv_vgpu_device *vgpu; + u32 *buf; + unsigned long i; + + buf = kmalloc(256, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + mutex_lock(&nv_vgpu_driver.lock); + list_for_each_entry(vgpu, &nv_vgpu_driver.devices, list) { + u64 o; + + if (atomic_read(&vgpu->vfio_dev_open_count) <= 0) + continue; + pr_info("gsp-resman-vfio: guest dump 0x%llx..0x%llx\n", + req->gpa, req->gpa + req->len); + for (o = 0; o < req->len; o += 256) { + unsigned long chunk = min_t(unsigned long, 256, + req->len - o); + + if (vfio_dma_rw(&vgpu->vfio_dev, req->gpa + o, buf, + chunk, false)) { + pr_info("gsp-resman-vfio: guest dump read failed at 0x%llx\n", + req->gpa + o); + break; + } + for (i = 0; i < chunk / 4; i += 4) + pr_info("gsp-resman-vfio: 0x%llx: %08x %08x %08x %08x\n", + req->gpa + o + i * 4, buf[i], buf[i + 1], + buf[i + 2], buf[i + 3]); + } + } + mutex_unlock(&nv_vgpu_driver.lock); + + kfree(buf); + complete(&req->done); + return 0; +} + +static int nv_vgpu_dump_guest_set(const char *val, const struct kernel_param *kp) +{ + static struct nv_vgpu_dump_guest_req req; + struct task_struct *task; + char buf[64]; + char *comma; + + memset(&req, 0, sizeof(req)); + strscpy(buf, val, sizeof(buf)); + comma = strchr(buf, ','); + if (comma) + *comma = '\0'; + if (kstrtoull(buf, 0, &req.gpa)) + return -EINVAL; + req.len = 0x100; + if (comma && kstrtoul(comma + 1, 0, &req.len)) + return -EINVAL; + if (!req.len) + req.len = 0x100; + if (req.len > 0x4000) + req.len = 0x4000; + + init_completion(&req.done); + task = kthread_run(nv_vgpu_dump_guest_thread, &req, "vgpu-dump"); + if (IS_ERR(task)) + return PTR_ERR(task); + wait_for_completion(&req.done); + return 0; +} + +static const struct kernel_param_ops nv_vgpu_dump_guest_ops = { + .set = nv_vgpu_dump_guest_set, +}; + +module_param_cb(dump_guest, &nv_vgpu_dump_guest_ops, NULL, 0200); +MODULE_PARM_DESC(dump_guest, "Hex-dump a guest physical range (hex gpa,hex len)"); + module_init(nv_vgpu_init); module_exit(nv_vgpu_exit); |
