diff options
| -rw-r--r-- | kernel/gsp_display_engine.c | 317 |
1 files changed, 176 insertions, 141 deletions
diff --git a/kernel/gsp_display_engine.c b/kernel/gsp_display_engine.c index 426d6a1..30257d5 100644 --- a/kernel/gsp_display_engine.c +++ b/kernel/gsp_display_engine.c @@ -168,6 +168,11 @@ static struct { u64 size; u32 addr_space; int found; /* 1 found, 0 not yet tried, -1 given up */ + /* Recent failed handle scans (do not rescan the world). */ + struct { + u32 handle; + unsigned long until; + } nf[4]; } im[2]; } disp; @@ -398,9 +403,121 @@ void gsp_disp_engine_inst_mem(struct nv_vgpu_device *vgpu, u64 pa, u64 size, } /* - * Resolve a context-DMA handle to its surface guest PA using the display - * instance hash table. Prefers the block announced by 0x20800a49; falls - * back to the legacy whole-FB scan when that control was not seen. + * Parse one 64 KB display instance block: 8 KB context-DMA hash table + * (word0 = ht_ObjectHandle, word1 = {chn[31:25], instance[24:14], + * client[13:0]}) + 32-byte object entries at instance*32 holding + * {target, base_lo, base_hi, limit_lo, limit_hi} in 256-byte units. + */ +static int gsp_disp_parse_hash(u32 handle, const u8 *buf, u64 *pa, u32 *limit) +{ + u64 e; + + for (e = 0; e < GSP_DISP_INST_HASH_END; e += 8) { + u32 obj, ctx, inst, chn; + u64 ent; + u32 w0, w1, w2, w3, w4; + + memcpy(&obj, buf + e, 4); + if (obj != handle) + continue; + memcpy(&ctx, buf + e + 4, 4); + inst = (ctx >> 14) & 0x7ff; + chn = (ctx >> 25) & 0x7f; + if (chn > 80) + continue; + if (inst < (GSP_DISP_INST_OBJ_OFF / 0x20) || + inst >= (GSP_DISP_INST_OBJ_END / 0x20)) + continue; + ent = (u64)inst * GSP_DISP_INST_ENTRY_SIZE; + if (ent + GSP_DISP_INST_ENTRY_SIZE > GSP_DISP_INST_OBJ_END) + continue; + memcpy(&w0, buf + ent + 0, 4); + memcpy(&w1, buf + ent + 4, 4); + memcpy(&w2, buf + ent + 8, 4); + memcpy(&w3, buf + ent + 12, 4); + memcpy(&w4, buf + ent + 16, 4); + if ((w0 & 0x3) < 1 || (w0 & 0x3) > 3 || !(w0 & BIT(2))) + continue; + *pa = ((((u64)(w2 & 0x7f)) << 32) | w1) << 8; + *limit = (u32)(((((u64)(w4 & 0x7f)) << 32) | w3) << 8) | 0xfff; + return 0; + } + return -ENOENT; +} + +/* + * The BAR1 aperture is smaller than the FB (256 MB window over 512 MB on + * this vGPU), so the RM writes vidmem through a shifted BAR1 block and the + * announced instance PA does not index the shadow directly. Find the block + * by searching the shadow for the ctxDMA handle itself. + */ +static int gsp_disp_find_block_fb(struct nv_vgpu_device *vgpu, u32 handle, + u64 *base, u64 *pa, u32 *limit) +{ + u8 *fb = vgpu->bars[1].vaddr; + u64 fb_size = nv_vgpu_fb_emul_size(vgpu); + u64 off; + + if (!fb) + return -ENODEV; + for (off = 0; off + GSP_DISP_INST_OBJ_END <= fb_size; off += 8) { + u32 a; + + memcpy(&a, fb + off, 4); + if (a != handle) + continue; + if (!gsp_disp_parse_hash(handle, fb + (off & ~0xffffULL), + pa, limit)) { + *base = off & ~0xffffULL; + return 0; + } + } + return -ENOENT; +} + +/* Same search through guest RAM (the coherent-mapping fallback). */ +static int gsp_disp_find_block_ram(struct nv_vgpu_device *vgpu, u32 handle, + u64 *base, u64 *pa, u32 *limit) +{ + u64 fb_size = nv_vgpu_fb_emul_size(vgpu); + const size_t chunk = 1 << 20; + u8 *buf = kmalloc(chunk, GFP_KERNEL); + u64 off; + int ret = -ENOENT; + + if (!buf) + return -ENOMEM; + for (off = 0; off + chunk <= fb_size; off += chunk) { + size_t i; + + if (vfio_dma_rw(&vgpu->vfio_dev, off, buf, chunk, false)) + continue; + for (i = 0; i + 8 <= chunk; i += 8) { + u32 a; + + memcpy(&a, buf + i, 4); + if (a != handle) + continue; + if (!gsp_disp_parse_hash(handle, + buf + (i & ~0xffffULL), + pa, limit)) { + *base = off + (i & ~0xffffULL); + ret = 0; + goto out; + } + } + } +out: + kfree(buf); + return ret; +} + +/* + * Resolve a context-DMA handle to its surface guest PA. Prefers the block + * announced by 0x20800a49; if the handle is not there (BAR1 aperture shift + * / coherent mapping), searches the shadow and then guest RAM for the block + * that actually contains it. Falls back to the legacy whole-FB scan when + * the announce control was never seen. * Returns 0 on success and fills *pa, *limit (bytes-1). */ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, @@ -438,8 +555,8 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, spin_unlock_irqrestore(&disp.lock, flags); if (have_im) { - u64 e; - const char *im_src = "fb"; + u64 base2 = 0; + const char *src = "announce"; if (!disp.ibuf) return -ENOMEM; @@ -447,156 +564,74 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, GSP_DISP_INST_OBJ_END)) return -ENOENT; - /* - * The RM may write vidmem through a coherent CPU mapping - * instead of the BAR1 aperture, in which case the block lands - * at the same address in guest RAM rather than in the FB - * shadow. If the shadow copy is empty, retry through - * vfio_dma_rw before giving up. - */ - if (im_space == 2) { - u64 nz = 0; - - for (e = 0; e < GSP_DISP_INST_HASH_END; e += 8) { - u32 a = *(u32 *)(disp.ibuf + e); - u32 b = *(u32 *)(disp.ibuf + e + 4); - - if (a || b) - nz++; - } - if (!nz) { - u8 *tmp = kmalloc(GSP_DISP_INST_OBJ_END, - GFP_KERNEL); - - if (tmp) { - if (vfio_dma_rw(&vgpu->vfio_dev, im_pa, - tmp, - GSP_DISP_INST_OBJ_END, - false) == 0) { - for (e = 0; - e < GSP_DISP_INST_HASH_END; - e += 8) { - u32 a = *(u32 *)(tmp + e); - u32 b = *(u32 *)(tmp + e + 4); - - if (a || b) - nz++; - } - if (nz) { - memcpy(disp.ibuf, tmp, - GSP_DISP_INST_OBJ_END); - im_src = "ram"; - } - } - kfree(tmp); - } + if (!gsp_disp_parse_hash(handle, disp.ibuf, pa, limit)) { + if (gsp_disp_dbg && disp.im_logs < 8) { + disp.im_logs++; + pr_info("inst mem: handle 0x%x at announced pa=0x%llx space=%u\n", + handle, im_pa, im_space); } + return 0; } - if (gsp_disp_dbg && disp.im_logs < 8) { - u32 nz = 0, shown = 0; - - for (e = 0; e < GSP_DISP_INST_HASH_END; e += 8) { - u32 a = *(u32 *)(disp.ibuf + e); - u32 b = *(u32 *)(disp.ibuf + e + 4); + /* Skip the expensive scans if this handle failed recently. */ + spin_lock_irqsave(&disp.lock, flags); + for (i = 0; i < ARRAY_SIZE(disp.im); i++) { + int k; - if (!a && !b) - continue; - nz++; - if (shown < 6) { - pr_info("im hash[0x%llx] = %08x %08x\n", - e, a, b); - shown++; + if (disp.im[i].vgpu != vgpu) + continue; + for (k = 0; k < ARRAY_SIZE(disp.im[i].nf); k++) { + if (disp.im[i].nf[k].handle == handle && + time_before(jiffies, + disp.im[i].nf[k].until)) { + spin_unlock_irqrestore(&disp.lock, + flags); + return -ENOENT; } } - disp.im_logs++; - pr_info("inst mem pa=0x%llx space=%u src=%s handle=0x%x hash_nz=%u\n", - im_pa, im_space, im_src, handle, nz); + break; + } + spin_unlock_irqrestore(&disp.lock, flags); - /* - * The announced block may not be where the RM's writes - * actually landed (BAR1 aperture shift). Scan the whole - * emulated FB shadow once for hash-entry-shaped words: - * word1 = {chn[31:25], instance[24:14], client[13:0]}. - * Bounded: stops after 6 candidate blocks. - */ - if (fb) { - u64 blk; - u32 blocks = 0; - - for (blk = 0; - blk + GSP_DISP_INST_OBJ_END <= fb_size && - blocks < 6; blk += 0x10000) { - u32 ee, cand = 0; - u64 first = 0; - - for (ee = 0; - ee < GSP_DISP_INST_HASH_END; - ee += 8) { - u32 w1 = *(u32 *)(fb + blk + - ee + 4); - u32 inst = (w1 >> 14) & - 0x7ff; - u32 chn = (w1 >> 25) & - 0x7f; - - if (w1 && chn <= 80 && - inst >= 0x100 && inst < 0x800) { - if (!cand) - first = blk + ee; - cand++; - } - } - if (cand) { - pr_info("im scan: blk=0x%llx cand=%u first=0x%llx\n", - blk, cand, first); - blocks++; + if (!gsp_disp_find_block_fb(vgpu, handle, &base2, pa, limit)) { + src = "shadow"; + } else if (!gsp_disp_find_block_ram(vgpu, handle, &base2, pa, + limit)) { + src = "ram"; + } else { + spin_lock_irqsave(&disp.lock, flags); + for (i = 0; i < ARRAY_SIZE(disp.im); i++) { + int k; + + if (disp.im[i].vgpu != vgpu) + continue; + for (k = 0; k < ARRAY_SIZE(disp.im[i].nf); k++) { + unsigned long until = + disp.im[i].nf[k].until; + + if (disp.im[i].nf[k].handle == handle || + time_after_eq(jiffies, until)) { + disp.im[i].nf[k].handle = handle; + disp.im[i].nf[k].until = + jiffies + 5 * HZ; + break; } } - pr_info("im scan: done, %u candidate blocks; BAR1_BLOCK=0x%x\n", - blocks, - gsp_disp_bar0_read(vgpu, 0x1704)); + break; } - } - - for (e = 0; e < GSP_DISP_INST_HASH_END; e += 8) { - u32 obj = *(u32 *)(disp.ibuf + e); - u32 ctx = *(u32 *)(disp.ibuf + e + 4); - u32 inst = (ctx >> 14) & 0x3ff; - u32 chn = (ctx >> 25) & 0x7f; - u64 ent; - u32 w0, w1, w2, w3, w4; - - if (obj != handle) - continue; - ent = (u64)inst * GSP_DISP_INST_ENTRY_SIZE; - if (inst < (GSP_DISP_INST_OBJ_OFF / 0x20) || - inst >= (GSP_DISP_INST_OBJ_END / 0x20) || - ent + GSP_DISP_INST_ENTRY_SIZE > - GSP_DISP_INST_OBJ_END) - continue; - w0 = *(u32 *)(disp.ibuf + ent + 0); - w1 = *(u32 *)(disp.ibuf + ent + 4); - w2 = *(u32 *)(disp.ibuf + ent + 8); - w3 = *(u32 *)(disp.ibuf + ent + 12); - w4 = *(u32 *)(disp.ibuf + ent + 16); + spin_unlock_irqrestore(&disp.lock, flags); if (gsp_disp_dbg && disp.im_logs < 8) { disp.im_logs++; - pr_info("im handle 0x%x ctx=0x%x inst=%u chn=%u w0=0x%x w1=0x%x w2=0x%x w3=0x%x w4=0x%x\n", - handle, ctx, inst, chn, w0, w1, w2, - w3, w4); + pr_info("inst mem: handle 0x%x not in any block (announced pa=0x%llx space=%u)\n", + handle, im_pa, im_space); } - if ((w0 & 0x3) < 1 || (w0 & 0x3) > 3 || - !(w0 & BIT(2))) - continue; - *pa = ((((u64)(w2 & 0x7f)) << 32) | w1) << 8; - *limit = (u32)(((((u64)(w4 & 0x7f)) << 32) | w3) << 8) - | 0xfff; - found = true; - break; - } - if (!found) return -ENOENT; + } + if (gsp_disp_dbg && disp.im_logs < 8) { + disp.im_logs++; + pr_info("inst mem: handle 0x%x via %s block 0x%llx -> pa=0x%llx limit=0x%x\n", + handle, src, base2, *pa, *limit); + } return 0; } |
