summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_display_engine.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/gsp_display_engine.c')
-rw-r--r--kernel/gsp_display_engine.c317
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;
}