diff options
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/gsp_display_engine.c | 237 |
1 files changed, 197 insertions, 40 deletions
diff --git a/kernel/gsp_display_engine.c b/kernel/gsp_display_engine.c index 30257d5..e79a9f0 100644 --- a/kernel/gsp_display_engine.c +++ b/kernel/gsp_display_engine.c @@ -99,7 +99,8 @@ /* NV_DISP_NOTIFIER__0_STATUS FINISHED lives at 31:30. */ #define GSP_DISP_NOTIFIER_FINISHED 0x80000000u #define GSP_DISP_NOTIFIER_CTRL_NOTIFY BIT(12) -#define GSP_DISP_NOTIFIER_CTRL_OFFSET(c) (((c) >> 4) & 0xff) +/* The CONTROL offset field is in 16-byte units; expose it in bytes. */ +#define GSP_DISP_NOTIFIER_CTRL_OFFSET(c) ((((c) >> 4) & 0xff) * 16) /* Display instance memory (v03_00 layout). */ #define GSP_DISP_INST_HASH_OFF 0x0000 @@ -131,6 +132,7 @@ struct gsp_disp_chan { u32 notifier_ctrl; u64 notifier_pa; bool notifier_pa_valid; + u8 notifier_target; /* instance-table target: 1 FB, 2/3 sysmem */ u32 notifier_writes; u32 notifier_posts; u32 update_logs; @@ -221,6 +223,59 @@ static int gsp_disp_write(struct nv_vgpu_device *vgpu, u64 gpa, void *buf, return vfio_dma_rw(&vgpu->vfio_dev, gpa, buf, len, true); } +/* + * A notifier whose context-DMA object targets ADDR_FBMEM (1) lives in the + * emulated FB shadow; targets 2/3 are guest sysmem. The instance table + * stores vidmem offsets, but accept a physical FB address by rebasing it + * on vgpu->fb_base when the direct interpretation is out of range. + */ +static bool gsp_disp_fb_off(struct nv_vgpu_device *vgpu, u64 pa, size_t len, + u64 *off) +{ + u64 fb_size = nv_vgpu_fb_emul_size(vgpu); + + if (!vgpu->bars[1].vaddr) + return false; + if (pa + len <= fb_size) { + *off = pa; + return true; + } + if (vgpu->fb_base && pa >= vgpu->fb_base && + pa - vgpu->fb_base + len <= fb_size) { + *off = pa - vgpu->fb_base; + return true; + } + return false; +} + +static int gsp_disp_notifier_read(struct nv_vgpu_device *vgpu, u64 pa, + u32 target, void *buf, size_t len) +{ + u64 off; + + if (target == 1) { + if (!gsp_disp_fb_off(vgpu, pa, len, &off)) + return -ENOENT; + memcpy(buf, (u8 *)vgpu->bars[1].vaddr + off, len); + return 0; + } + return gsp_disp_read(vgpu, pa, buf, len); +} + +static int gsp_disp_notifier_write(struct nv_vgpu_device *vgpu, u64 pa, + u32 target, void *buf, size_t len) +{ + u64 off; + + if (target == 1) { + if (!gsp_disp_fb_off(vgpu, pa, len, &off)) + return -ENOENT; + memcpy((u8 *)vgpu->bars[1].vaddr + off, buf, len); + return 0; + } + return gsp_disp_write(vgpu, pa, buf, len); +} + /* BAR0 shadow access. The guest mmap writes it without our lock, so the * register words are read with READ_ONCE; writes are single u32 stores. */ static u32 gsp_disp_bar0_read(struct nv_vgpu_device *vgpu, u32 off) @@ -313,7 +368,7 @@ static int gsp_disp_instmem_find(struct nv_vgpu_device *vgpu, u32 handle) e < GSP_DISP_INST_HASH_END; e += 8) { u32 obj = *(u32 *)(fb + blk + e); u32 ctx = *(u32 *)(fb + blk + e + 4); - u32 inst = (ctx >> 14) & 0x3ff; + u32 inst = (ctx >> 14) & 0x7ff; u32 chn = (ctx >> 25) & 0x7f; u64 ent; @@ -407,8 +462,10 @@ void gsp_disp_engine_inst_mem(struct nv_vgpu_device *vgpu, u64 pa, u64 size, * (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. + * *limit is the byte END address, matching the table encoding. */ -static int gsp_disp_parse_hash(u32 handle, const u8 *buf, u64 *pa, u32 *limit) +static int gsp_disp_parse_hash(u32 handle, const u8 *buf, u64 *pa, + u64 *limit, u32 *target) { u64 e; @@ -439,7 +496,8 @@ static int gsp_disp_parse_hash(u32 handle, const u8 *buf, u64 *pa, u32 *limit) 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; + *limit = ((((u64)(w4 & 0x7f)) << 32) | w3) << 8; + *target = w0 & 0x3; return 0; } return -ENOENT; @@ -452,7 +510,8 @@ static int gsp_disp_parse_hash(u32 handle, const u8 *buf, u64 *pa, u32 *limit) * 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) + u64 *base, u64 *pa, u64 *limit, + u32 *target) { u8 *fb = vgpu->bars[1].vaddr; u64 fb_size = nv_vgpu_fb_emul_size(vgpu); @@ -467,7 +526,7 @@ static int gsp_disp_find_block_fb(struct nv_vgpu_device *vgpu, u32 handle, if (a != handle) continue; if (!gsp_disp_parse_hash(handle, fb + (off & ~0xffffULL), - pa, limit)) { + pa, limit, target)) { *base = off & ~0xffffULL; return 0; } @@ -477,7 +536,8 @@ static int gsp_disp_find_block_fb(struct nv_vgpu_device *vgpu, u32 handle, /* 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 *base, u64 *pa, u64 *limit, + u32 *target) { u64 fb_size = nv_vgpu_fb_emul_size(vgpu); const size_t chunk = 1 << 20; @@ -500,7 +560,7 @@ static int gsp_disp_find_block_ram(struct nv_vgpu_device *vgpu, u32 handle, continue; if (!gsp_disp_parse_hash(handle, buf + (i & ~0xffffULL), - pa, limit)) { + pa, limit, target)) { *base = off + (i & ~0xffffULL); ret = 0; goto out; @@ -513,15 +573,89 @@ out: } /* + * The guest RM writes the display instance block through its BAR2 aperture + * (memdescMapInternal -> BAR2), which vgsp exposes as a flat host-RAM + * shadow. BAR2 offsets are only 4 KiB-aligned, so locate the hash entry by + * handle and derive the block base from its position: the table is the + * first 8 KiB of the block, hence the entry lies in the 4 KiB page at + * block+0 or block+0x1000. + */ +static int gsp_disp_find_block_bar2(struct nv_vgpu_device *vgpu, u32 handle, + u64 *base, u64 *pa, u64 *limit, + u32 *target) +{ + u8 *bar2 = vgpu->bars[2].vaddr; + u64 size = vgpu->bars[2].size; + u64 s; + + if (!bar2 || !size) + return -ENODEV; + for (s = 0; s + 8 <= size; s += 8) { + u32 obj, ctx, inst; + u64 cand[2]; + int c; + + memcpy(&obj, bar2 + s, 4); + if (obj != handle) + continue; + memcpy(&ctx, bar2 + s + 4, 4); + inst = (ctx >> 14) & 0x7ff; + if (inst < (GSP_DISP_INST_OBJ_OFF / 0x20) || + inst >= (GSP_DISP_INST_OBJ_END / 0x20)) + continue; + + cand[0] = s & ~0xfffULL; + cand[1] = cand[0] - 0x1000; + for (c = 0; c < 2; c++) { + u64 b = cand[c]; + u64 ent, base64, lim64; + u32 w0, w1, w2, w3, w4, tgt; + + if (s < b || s - b >= 0x2000) + continue; + ent = b + (u64)inst * GSP_DISP_INST_ENTRY_SIZE; + if (ent + 5 * 4 > size) + continue; + memcpy(&w0, bar2 + ent + 0, 4); + memcpy(&w1, bar2 + ent + 4, 4); + memcpy(&w2, bar2 + ent + 8, 4); + memcpy(&w3, bar2 + ent + 12, 4); + memcpy(&w4, bar2 + ent + 16, 4); + tgt = w0 & 0x3; + if (tgt < 1 || tgt > 3) + continue; + base64 = ((((u64)(w2 & 0x7f)) << 32) | w1) << 8; + lim64 = ((((u64)(w4 & 0x7f)) << 32) | w3) << 8; + if (lim64 <= base64 || lim64 - base64 > (1 << 20)) + continue; + *base = b; + *pa = base64; + *limit = lim64; + *target = tgt; + if (disp.im_logs < 8) { + disp.im_logs++; + pr_info("inst mem: handle 0x%x via bar2 block 0x%llx -> base=0x%llx limit=0x%llx target=%u\n", + handle, (unsigned long long)b, + (unsigned long long)base64, + (unsigned long long)lim64, tgt); + } + return 0; + } + } + return -ENOENT; +} + +/* * 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). + * / coherent mapping / BAR2 write path), searches the shadow, then guest + * RAM, then the BAR2 shadow 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 (byte end address) and + * *target (1 = FB, 2/3 = guest sysmem). */ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, - u64 *pa, u32 *limit) + u64 *pa, u64 *limit, u32 *target) { u64 fb_size = nv_vgpu_fb_emul_size(vgpu); u8 *fb = vgpu->bars[1].vaddr; @@ -538,7 +672,8 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, return -ENODEV; if (gsp_disp_notifier_pa) { *pa = gsp_disp_notifier_pa; - *limit = 0xfff; + *limit = *pa + 0x1000; + *target = 2; return 0; } @@ -564,7 +699,7 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, GSP_DISP_INST_OBJ_END)) return -ENOENT; - if (!gsp_disp_parse_hash(handle, disp.ibuf, pa, limit)) { + if (!gsp_disp_parse_hash(handle, disp.ibuf, pa, limit, target)) { 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", @@ -593,11 +728,16 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, } spin_unlock_irqrestore(&disp.lock, flags); - if (!gsp_disp_find_block_fb(vgpu, handle, &base2, pa, limit)) { + if (!gsp_disp_find_block_fb(vgpu, handle, &base2, pa, limit, + target)) { src = "shadow"; } else if (!gsp_disp_find_block_ram(vgpu, handle, &base2, pa, - limit)) { + limit, target)) { src = "ram"; + } else if (!gsp_disp_find_block_bar2(vgpu, handle, &base2, pa, + limit, target)) { + /* The BAR2 scan emits its own single log line. */ + return 0; } else { spin_lock_irqsave(&disp.lock, flags); for (i = 0; i < ARRAY_SIZE(disp.im); i++) { @@ -629,8 +769,8 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, } 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); + pr_info("inst mem: handle 0x%x via %s block 0x%llx -> pa=0x%llx limit=0x%llx target=%u\n", + handle, src, base2, *pa, *limit, *target); } return 0; } @@ -645,8 +785,16 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, } } if (slot < 0 || !disp.im[slot].found) { - if (gsp_disp_instmem_find(vgpu, handle)) + if (gsp_disp_instmem_find(vgpu, handle)) { + /* + * The guest may write the instance block through its + * BAR2 aperture instead of the FB shadow. + */ + if (!gsp_disp_find_block_bar2(vgpu, handle, &base, pa, + limit, target)) + return 0; return -ENOENT; + } slot = -1; for (i = 0; i < ARRAY_SIZE(disp.im); i++) if (disp.im[i].vgpu == vgpu) { @@ -665,7 +813,7 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, u64 e = base + i; u32 obj = *(u32 *)(fb + e); u32 ctx = *(u32 *)(fb + e + 4); - u32 inst = (ctx >> 14) & 0x3ff; + u32 inst = (ctx >> 14) & 0x7ff; u64 ent; u32 w0, w1, w2, w3, w4; @@ -683,7 +831,8 @@ static int gsp_disp_ctxdma_pa(struct nv_vgpu_device *vgpu, u32 handle, 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; + *limit = ((((u64)(w4 & 0x7f)) << 32) | w3) << 8; + *target = w0 & 0x3; found = true; break; } @@ -855,17 +1004,19 @@ static void gsp_disp_method(struct nv_vgpu_device *vgpu, /* Resolve the surface now: the handle is the instance-table * key and the surface is bound before any notifier wait. */ if (value && gsp_disp_finish) { - u64 pa = 0; - u32 lim = 0; + u64 pa = 0, lim = 0; + u32 target = 0; - if (gsp_disp_ctxdma_pa(vgpu, value, &pa, &lim) == 0) { + if (gsp_disp_ctxdma_pa(vgpu, value, &pa, &lim, + &target) == 0) { ch->notifier_pa = pa; + ch->notifier_target = target; ch->notifier_pa_valid = true; } } - pr_info("h=0x%x push=0x%x SET_NOTIFIER_DMA handle=0x%x -> pa=0x%llx valid=%d\n", + pr_info("h=0x%x push=0x%x SET_NOTIFIER_DMA handle=0x%x -> pa=0x%llx target=%u valid=%d\n", ch->hclass, push_off, value, ch->notifier_pa, - ch->notifier_pa_valid); + ch->notifier_target, ch->notifier_pa_valid); return; } if (addr == notifier_ctrl) { @@ -878,8 +1029,9 @@ static void gsp_disp_method(struct nv_vgpu_device *vgpu, return; } if (addr == GSP_DISP_M_UPDATE) { - u32 off_words = GSP_DISP_NOTIFIER_CTRL_OFFSET(ch->notifier_ctrl); - u64 pa = ch->notifier_pa + (u64)off_words * 4; + u32 off_bytes = GSP_DISP_NOTIFIER_CTRL_OFFSET(ch->notifier_ctrl); + u64 pa = ch->notifier_pa + off_bytes; + u32 target = ch->notifier_target; u32 finished = GSP_DISP_NOTIFIER_FINISHED; bool armed = (ch->notifier_ctrl & GSP_DISP_NOTIFIER_CTRL_NOTIFY) != 0; @@ -897,22 +1049,25 @@ static void gsp_disp_method(struct nv_vgpu_device *vgpu, * push buffer. Nothing else is ever written. */ if (gsp_disp_pa_is_push_buffer(pa & ~0xfffULL) || - gsp_disp_read(vgpu, pa, &before, sizeof(before)) || + gsp_disp_notifier_read(vgpu, pa, target, &before, + sizeof(before)) || (before != 0 && before != GSP_DISP_NOTIFIER_FINISHED)) { if (ch->update_logs < 8) { ch->update_logs++; - pr_warn("h=0x%x UPDATE notifier pa=0x%llx rejected (val=0x%x)\n", - ch->hclass, pa, before); + pr_warn("h=0x%x UPDATE notifier pa=0x%llx target=%u rejected (val=0x%x)\n", + ch->hclass, pa, target, before); } - } else if (gsp_disp_write(vgpu, pa, &finished, - sizeof(finished)) == 0) { + } else if (gsp_disp_notifier_write(vgpu, pa, target, + &finished, + sizeof(finished)) == 0) { ch->notifier_writes++; wrote = true; - gsp_disp_read(vgpu, pa, &after, sizeof(after)); + gsp_disp_notifier_read(vgpu, pa, target, &after, + sizeof(after)); if (after != finished && ch->update_logs < 8) { ch->update_logs++; - pr_warn("h=0x%x notifier readback 0x%x at pa=0x%llx\n", - ch->hclass, after, pa); + pr_warn("h=0x%x notifier readback 0x%x at pa=0x%llx target=%u\n", + ch->hclass, after, pa, target); } } } @@ -930,10 +1085,11 @@ static void gsp_disp_method(struct nv_vgpu_device *vgpu, } if (ch->update_logs < 8 || gsp_disp_dbg) { ch->update_logs++; - pr_info("h=0x%x push=0x%x UPDATE armed=%d handle=0x%x notifier=0x%llx/%u -> %s (posts=%u)\n", + pr_info("h=0x%x push=0x%x UPDATE armed=%d handle=0x%x notifier=0x%llx/%u target=%u -> %s (posts=%u)\n", ch->hclass, push_off, armed, - ch->notifier_handle, ch->notifier_pa, off_words, - wrote ? "FINISHED" : "none", ch->notifier_posts); + ch->notifier_handle, ch->notifier_pa, off_bytes, + target, wrote ? "FINISHED" : "none", + ch->notifier_posts); } } } @@ -1201,6 +1357,7 @@ void gsp_disp_engine_pump(struct nv_vgpu_device *vgpu) c->notifier_handle = s->notifier_handle; c->notifier_ctrl = s->notifier_ctrl; c->notifier_pa = s->notifier_pa; + c->notifier_target = s->notifier_target; c->notifier_pa_valid = s->notifier_pa_valid; c->notifier_writes = s->notifier_writes; c->notifier_posts = s->notifier_posts; |
