# 3D and 2D acceleration for the Intel Poulsbo GMA500 (PowerVR SGX535). # # The in-tree gma500 driver is modeset and framebuffer only: it has no way to # submit render work to the SGX core, so the part runs everything through # llvmpipe. This adds the render side - the MMU and its page tables, the # parameter heap, the scene and its context, the kick and fire sequences, the # 2D blit engine and the DRM interface a userspace driver submits through - # and the MSVDX video decoder block alongside it. # # The userspace half is the Gallium driver in the mesa package; the two share # sgx_drm.h as their interface and have to be updated together. # # Written from hardware measurement against the vendor's own DDK for the # register meanings. Signed-off-by: Rene Rebe diff -urNp a/drivers/gpu/drm/gma500/Makefile b/drivers/gpu/drm/gma500/Makefile --- a/drivers/gpu/drm/gma500/Makefile 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/Makefile 2026-09-08 10:56:21.838775048 +0200 @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 # -# KMS driver for the GMA500 +# gma500_gfx, out of tree, with the 3D core's render interface built into it. +# psb_lid.o is absent upstream; midx_bios.o does not exist here either. # +# Copyright (C) 2026 René Rebe gma500_gfx-y += \ backlight.o \ @@ -21,6 +23,7 @@ gma500_gfx-y += \ intel_i2c.o \ mid_bios.o \ mmu.o \ + msvdx.o \ oaktrail_device.o \ oaktrail_crtc.o \ oaktrail_hdmi.o \ @@ -34,9 +37,12 @@ gma500_gfx-y += \ psb_intel_lvds.o \ psb_intel_modes.o \ psb_intel_sdvo.o \ - psb_irq.o + psb_irq.o \ + psb_sgx_render.o gma500_gfx-$(CONFIG_ACPI) += opregion.o gma500_gfx-$(CONFIG_DRM_FBDEV_EMULATION) += fbdev.o -obj-$(CONFIG_DRM_GMA500) += gma500_gfx.o +obj-m += gma500_gfx.o + +ccflags-y := -I$(src) diff -urNp a/drivers/gpu/drm/gma500/fbdev.c b/drivers/gpu/drm/gma500/fbdev.c --- a/drivers/gpu/drm/gma500/fbdev.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/fbdev.c 2026-09-08 10:56:21.845775143 +0200 @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /************************************************************************** * Copyright (c) 2007-2011, Intel Corporation. + * Copyright (c) 2026, René Rebe * All Rights Reserved. * **************************************************************************/ @@ -166,6 +167,15 @@ try_psb_gem_create: drm_fb_helper_fill_info(info, fb_helper, sizes); + /* The 3D core renders into this same memory when a client asks for the + * scanout, so record what describes it. */ + dev_priv->scanout.offset = backing->offset; + dev_priv->scanout.pitch = pitch; + dev_priv->scanout.width = sizes->surface_width; + dev_priv->scanout.height = sizes->surface_height; + dev_priv->scanout.size = obj->size; + dev_priv->scanout.valid = sizes->surface_bpp == 32; + info->fix.smem_start = dev_priv->stolen_base + backing->offset; info->fix.smem_len = obj->size; info->fix.ywrapstep = 0; diff -urNp a/drivers/gpu/drm/gma500/gem.c b/drivers/gpu/drm/gma500/gem.c --- a/drivers/gpu/drm/gma500/gem.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/gem.c 2026-09-08 10:56:21.846775157 +0200 @@ -3,6 +3,7 @@ * psb GEM interface * * Copyright (c) 2011, Intel Corporation. + * Copyright (C) 2026 René Rebe * * Authors: Alan Cox * @@ -40,8 +41,8 @@ int psb_gem_pin(struct psb_gem_object *p if (drm_WARN_ONCE(dev, ret, "dma_resv_lock() failed, ret=%d\n", ret)) return ret; - if (pobj->in_gart || pobj->stolen) - goto out; /* already mapped */ + if (pobj->in_gart || pobj->stolen || pobj->pages) + goto out; /* already mapped, or still resident from before */ pages = drm_gem_get_pages(obj); if (IS_ERR(pages)) { @@ -75,9 +76,6 @@ void psb_gem_unpin(struct psb_gem_object { struct drm_gem_object *obj = &pobj->base; struct drm_device *dev = obj->dev; - struct drm_psb_private *dev_priv = to_drm_psb_private(dev); - u32 gpu_base = dev_priv->gtt.gatt_start; - unsigned long npages; int ret; ret = dma_resv_lock(obj->resv, NULL); @@ -88,8 +86,45 @@ void psb_gem_unpin(struct psb_gem_object --pobj->in_gart; - if (pobj->in_gart || pobj->stolen) - goto out; + /* The pages stay. Releasing them here meant a page flip paid for the + * whole framebuffer twice over: set_pages_array_wb() and a free on the + * buffer leaving scanout, then a fresh allocation, a zeroing and + * set_pages_array_wc() on the one entering it - 900 pages each way for + * 1280x720, every frame, because a double buffered client alternates + * two objects and each drops to zero as the other is pinned. + * + * Measured with perf on glmark2: change_page_attr_set_clr() and the + * memset behind the reallocation were the two largest costs in the + * kernel, and they are paid by every scene, which is why even a bare + * clear could not go faster. + * + * Keeping them costs no GTT: psb_gem_create() reserves the object's + * GTT range for its whole life, so the range is already spoken for + * whether or not pages are inserted in it. What it does hold is the + * backing pages, until the object is destroyed - which for a + * framebuffer is what was wanted anyway. psb_gem_release_pages() does + * the teardown from the free path instead. */ + dma_resv_unlock(obj->resv); +} + +/* What an object costs to give back is its pages' cache attribute, reset one + * array at a time, so both the count and the pages are worth reading. */ +static unsigned int sgx_gem_frees; +module_param_named(sgx_gem_frees, sgx_gem_frees, uint, 0444); +static unsigned int sgx_gem_free_pages; +module_param_named(sgx_gem_free_pages, sgx_gem_free_pages, uint, 0444); + +/* Undo what psb_gem_pin() set up. The caller must not hold the reservation. */ +static void psb_gem_release_pages(struct psb_gem_object *pobj) +{ + struct drm_gem_object *obj = &pobj->base; + struct drm_device *dev = obj->dev; + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + u32 gpu_base = dev_priv->gtt.gatt_start; + unsigned long npages; + + if (!pobj->pages || pobj->stolen) + return; npages = obj->size / PAGE_SIZE; @@ -98,13 +133,12 @@ void psb_gem_unpin(struct psb_gem_object psb_gtt_remove_pages(dev_priv, &pobj->resource); /* Reset caching flags */ + sgx_gem_free_pages += npages; set_pages_array_wb(pobj->pages, npages); drm_gem_put_pages(obj, pobj->pages, true, false); pobj->pages = NULL; - -out: - dma_resv_unlock(obj->resv); + pobj->pages_flushed = false; } static vm_fault_t psb_gem_fault(struct vm_fault *vmf); @@ -113,10 +147,14 @@ static void psb_gem_free_object(struct d { struct psb_gem_object *pobj = to_psb_gem_object(obj); + sgx_gem_frees++; /* Undo the mmap pin if we are destroying the object */ if (pobj->mmapping) psb_gem_unpin(pobj); + /* And release what the last unpin deliberately left resident. */ + psb_gem_release_pages(pobj); + drm_gem_object_release(obj); WARN_ON(pobj->in_gart && !pobj->stolen); @@ -136,6 +174,11 @@ static const struct drm_gem_object_funcs .vm_ops = &psb_gem_vm_ops, }; +bool psb_gem_is_psb_object(const struct drm_gem_object *obj) +{ + return obj && obj->funcs == &psb_gem_object_funcs; +} + struct psb_gem_object * psb_gem_create(struct drm_device *dev, u64 size, const char *name, bool stolen, u32 align) { diff -urNp a/drivers/gpu/drm/gma500/gem.h b/drivers/gpu/drm/gma500/gem.h --- a/drivers/gpu/drm/gma500/gem.h 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/gem.h 2026-09-08 10:56:21.847775171 +0200 @@ -27,6 +27,10 @@ struct psb_gem_object { bool stolen; /* Backed from stolen RAM */ bool mmapping; /* Is mmappable */ struct page **pages; /* Backing pages if present */ + /* The pages have been flushed out of the kernel's direct map since + * they were obtained. Whoever writes them through a cached mapping + * afterwards flushes that range itself. */ + bool pages_flushed; }; static inline struct psb_gem_object *to_psb_gem_object(struct drm_gem_object *obj) @@ -40,6 +44,12 @@ psb_gem_create(struct drm_device *dev, u int psb_gem_pin(struct psb_gem_object *pobj); void psb_gem_unpin(struct psb_gem_object *pobj); +/* Whether a GEM object is one of these rather than some other kind the same + * device also hands out. to_psb_gem_object() is a container_of and will + * happily reinterpret anything, so anything that did not create the object + * itself has to ask first. */ +bool psb_gem_is_psb_object(const struct drm_gem_object *obj); + /* * Memory management */ diff -urNp a/drivers/gpu/drm/gma500/gma_device.c b/drivers/gpu/drm/gma500/gma_device.c --- a/drivers/gpu/drm/gma500/gma_device.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/gma_device.c 2026-09-08 10:56:21.848775184 +0200 @@ -24,6 +24,12 @@ void gma_get_core_freq(struct drm_device pci_read_config_dword(pci_root, 0xD4, &clock); pci_dev_put(pci_root); + /* Bit 7 of the same word is the memory controller clock, which the + * display FIFO watermarks are computed against - psbGetMemClock() in + * xserver-xorg-video-psb-0.32.1/src/psb_crtc.c:313-340 reads it from + * this very register. */ + dev_priv->mem_freq = (clock & 0x80) ? 133 : 100; + switch (clock & 0x07) { case 0: dev_priv->core_freq = 100; diff -urNp a/drivers/gpu/drm/gma500/gma_display.c b/drivers/gpu/drm/gma500/gma_display.c --- a/drivers/gpu/drm/gma500/gma_display.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/gma_display.c 2026-09-08 10:56:21.849775198 +0200 @@ -55,6 +55,27 @@ void gma_wait_for_vblank(struct drm_devi mdelay(20); } +/* dev_priv->scanout describes the console framebuffer the 3D core renders + * straight into, and psb_fbdev_driver_fbdev_probe() fills it in once. A mode + * set can put a different buffer on the pipe - a resolution change under X, a + * client's own framebuffer, a re-probed console at another size - and the + * record then names geometry that is no longer being displayed. Anything + * rendering at it would write at the wrong pitch into memory nothing scans + * out, so the record is only valid while the pipe really carries the buffer it + * describes. */ +static void gma_scanout_track(struct drm_psb_private *dev_priv, + const struct psb_gem_object *pobj, + const struct drm_framebuffer *fb) +{ + dev_priv->scanout.valid = pobj->stolen && + dev_priv->scanout.size && + pobj->offset == dev_priv->scanout.offset && + fb->pitches[0] == dev_priv->scanout.pitch && + fb->width == dev_priv->scanout.width && + fb->height == dev_priv->scanout.height && + fb->format->cpp[0] == 4; +} + int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y, struct drm_framebuffer *old_fb) { @@ -75,6 +96,7 @@ int gma_pipe_set_base(struct drm_crtc *c /* no fb bound */ if (!fb) { dev_err(dev->dev, "No FB bound\n"); + dev_priv->scanout.valid = false; goto gma_pipe_cleaner; } @@ -130,6 +152,8 @@ int gma_pipe_set_base(struct drm_crtc *c REG_READ(map->surf); } + gma_scanout_track(dev_priv, pobj, fb); + gma_pipe_cleaner: /* If there was a previous display we can now unpin it */ if (old_fb) @@ -319,11 +343,12 @@ void gma_crtc_dpms(struct drm_crtc *crtc break; } - if (IS_CDV(dev)) - dev_priv->ops->update_wm(dev, crtc); - - /* Set FIFO watermarks */ + /* Set FIFO watermarks. The arbiter's split first: the watermark levels + * are computed from the share it gives each plane. */ REG_WRITE(DSPARB, 0x3F3E); + + if (dev_priv->ops->update_wm) + dev_priv->ops->update_wm(dev, crtc); } static int gma_crtc_cursor_set(struct drm_crtc *crtc, diff -urNp a/drivers/gpu/drm/gma500/mmu.c b/drivers/gpu/drm/gma500/mmu.c --- a/drivers/gpu/drm/gma500/mmu.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/mmu.c 2026-09-08 10:56:21.856775293 +0200 @@ -7,8 +7,6 @@ #include #include -#include - #include "mmu.h" #include "psb_drv.h" #include "psb_reg.h" diff -urNp a/drivers/gpu/drm/gma500/msvdx.c b/drivers/gpu/drm/gma500/msvdx.c --- a/drivers/gpu/drm/gma500/msvdx.c 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/msvdx.c 2026-09-08 10:56:21.858775320 +0200 @@ -0,0 +1,1008 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MSVDX/VXD video decode: bringing the block up and running the host/firmware + * message ring. + * + * The sequence is the vendor's, from the GPL driver that shipped against this + * machine's msvdx_fw.bin (upstream/src/psb-kernel-source_4.42.0-0ubuntu2~1010um5/ + * psb-kernel-source/psb_msvdxinit.c and psb_msvdx.c): clocks on, MMU + * bypass off, RENDEC pointed at two buffers and initialised, firmware + * uploaded into the MTX's code and data RAM and read back, the communication + * area zeroed, the program counter set and the thread started, then wait for + * the firmware to write its signature. + * + * What is different here, and deliberately: no interrupt line. The host + * interrupt stays masked and the message pump polls MSVDX_INTERRUPT_STATUS, + * which still latches - the same thing psb_poll_mtx_irq() does during the + * vendor's own bring-up. A decode is synchronous either way, and this keeps + * the block out of the display driver's shared interrupt path until there is + * hardware evidence that the rest of it works. + * + * Copyright (C) 2026 René Rebe + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "psb_drv.h" +#include "msvdx.h" +#include "msvdx_drm.h" +#include "msvdx_msg.h" +#include "msvdx_reg.h" + +#define MSVDX_FW_NAME "msvdx_fw.bin" + +/* The vendor polls a register a thousand times at 100 us (psb_msvdxinit.c:51-70). + * Kept, because the firmware's signature genuinely takes a while to appear. */ +#define MSVDX_POLL_COUNT 1000 +#define MSVDX_POLL_DELAY_US 100 + +static u32 msvdx_rd(struct msvdx_device *m, u32 off) +{ + return ioread32(m->regs + off); +} + +static void msvdx_wr(struct msvdx_device *m, u32 val, u32 off) +{ + iowrite32(val, m->regs + off); +} + +static int msvdx_wait_reg(struct msvdx_device *m, u32 off, u32 want, u32 mask) +{ + unsigned int i; + + for (i = 0; i < MSVDX_POLL_COUNT; i++) { + if ((msvdx_rd(m, off) & mask) == want) + return 0; + udelay(MSVDX_POLL_DELAY_US); + } + return -ETIMEDOUT; +} + +/* ---------------------------------------------------------------- the MMU */ + +/* + * The decoder's MMU takes the same two-level, 1024-entry, 4 KiB-page tables + * the SGX's does - the vendor drove both from one psb_mmu.c and handed the + * decoder the same page directory - so the entry encoding comes from + * sgx_mmu.h rather than being derived again. What differs is who points the + * hardware at the directory: for the SGX the host writes a base register, for + * the decoder the directory's physical address travels in every message. + */ +static void msvdx_flush_range(void *p, size_t n) +{ + clflush_cache_range(p, n); +} + +static int msvdx_mmu_init(struct sgx_mmu *mmu) +{ + memset(mmu, 0, sizeof(*mmu)); + /* The directory's address goes into a 32-bit message field and every + * page table entry is a 32-bit frame number, so nothing here may sit + * above 4 GiB. */ + mmu->pd = (u32 *)get_zeroed_page(GFP_KERNEL | __GFP_DMA32); + if (!mmu->pd) + return -ENOMEM; + mmu->pd_phys = virt_to_phys(mmu->pd); + if ((u64)mmu->pd_phys >> 32) { + free_page((unsigned long)mmu->pd); + mmu->pd = NULL; + return -ENOMEM; + } + msvdx_flush_range(mmu->pd, PAGE_SIZE); + return 0; +} + +static void msvdx_mmu_fini(struct sgx_mmu *mmu) +{ + unsigned int i; + + for (i = 0; i < SGX_MMU_PDES; i++) + if (mmu->pt[i]) + free_page((unsigned long)mmu->pt[i]); + if (mmu->pd) + free_page((unsigned long)mmu->pd); + memset(mmu, 0, sizeof(*mmu)); +} + +static u32 *msvdx_mmu_table(struct sgx_mmu *mmu, u64 va) +{ + unsigned int pde = sgx_mmu_pde_of(va); + u32 *pt; + + if (mmu->pt[pde]) + return mmu->pt[pde]; + + pt = (u32 *)get_zeroed_page(GFP_KERNEL | __GFP_DMA32); + if (!pt) + return NULL; + if ((u64)virt_to_phys(pt) >> 32) { + free_page((unsigned long)pt); + return NULL; + } + msvdx_flush_range(pt, PAGE_SIZE); + mmu->pt[pde] = pt; + mmu->pd[pde] = ((u32)(virt_to_phys(pt) >> SGX_PTE_SHIFT) << SGX_PTE_SHIFT) | + SGX_PTE_VALID; + msvdx_flush_range(&mmu->pd[pde], sizeof(mmu->pd[pde])); + return pt; +} + +static struct page **msvdx_obj_pages(struct drm_gem_object *obj) +{ + return to_drm_gem_shmem_obj(obj)->pages; +} + +static int msvdx_mmu_map(struct sgx_mmu *mmu, struct drm_gem_object *obj, + u64 gpu_va, u64 offset, u64 size, bool read_only, + bool unmap) +{ + struct page **pages = msvdx_obj_pages(obj); + u64 i, n = size >> SGX_PTE_SHIFT; + + if (!unmap && !pages) + return -EINVAL; + + /* The pages are write-combining for userspace but still cached in the + * kernel's direct map; a line written back later lands on top of what + * the decoder read. The SGX side learned this the hard way. */ + if (!unmap) + drm_clflush_pages(&pages[offset >> SGX_PTE_SHIFT], n); + + for (i = 0; i < n; i++) { + u64 va = gpu_va + (i << SGX_PTE_SHIFT); + u32 *pt = msvdx_mmu_table(mmu, va); + unsigned int idx = sgx_mmu_pte_of(va); + + if (!pt) + return -ENOMEM; + if (unmap) + pt[idx] = 0; + else + pt[idx] = sgx_mmu_pte(page_to_pfn(pages[(offset >> SGX_PTE_SHIFT) + i]), + read_only ? 0x2 : 0); + msvdx_flush_range(&pt[idx], sizeof(pt[idx])); + } + return 0; +} + +/* ------------------------------------------------------------- the address + * space bookkeeping */ + +static struct msvdx_binding *msvdx_find(struct msvdx_file *mf, u64 va, u64 size) +{ + struct msvdx_binding *b; + + if (!size) + return NULL; + list_for_each_entry(b, &mf->bindings, head) + if (va >= b->gpu_va && size <= b->size && + va - b->gpu_va <= b->size - size) + return b; + return NULL; +} + +static bool msvdx_overlaps(struct msvdx_file *mf, u64 va, u64 size) +{ + struct msvdx_binding *b; + + list_for_each_entry(b, &mf->bindings, head) + if (va < b->gpu_va + b->size && b->gpu_va < va + size) + return true; + return false; +} + +/* ------------------------------------------------------------ the firmware */ + +static void msvdx_mtx_write_core_reg(struct msvdx_device *m, u32 reg, u32 val) +{ + msvdx_wr(m, val, MSVDX_MTX_REGISTER_READ_WRITE_DATA); + msvdx_wr(m, reg & ~(MSVDX_MTX_RW_REQ_RNW | MSVDX_MTX_RW_REQ_DREADY), + MSVDX_MTX_REGISTER_READ_WRITE_REQUEST); + msvdx_wait_reg(m, MSVDX_MTX_REGISTER_READ_WRITE_REQUEST, + MSVDX_MTX_RW_REQ_DREADY, MSVDX_MTX_RW_REQ_DREADY); +} + +/* Walk the MTX's RAM banks writing, or reading back and comparing, one word at + * a time. The bank id changes with the address, which is why the control + * register is rewritten whenever it does (psb_msvdxinit.c:121-168). */ +static int msvdx_ram_xfer(struct msvdx_device *m, u32 mem, u32 bank_size, + u32 addr, unsigned int words, const u32 *data, + bool verify) +{ + u32 saved = msvdx_rd(m, MSVDX_MTX_RAM_ACCESS_CONTROL); + u32 cur_bank = ~0u; + unsigned int i; + int ret = 0; + + if (msvdx_wait_reg(m, MSVDX_MTX_RAM_ACCESS_STATUS, 1, ~0u)) + return -ETIMEDOUT; + + for (i = 0; i < words; i++) { + u32 bank = mem + (addr / bank_size); + + if (bank != cur_bank) { + u32 ctrl = (bank << MSVDX_MTX_RAM_ACC_MCMID_SHIFT) | + ((addr >> 2) << MSVDX_MTX_RAM_ACC_ADDR_SHIFT) | + MSVDX_MTX_RAM_ACC_MCMAI | + (verify ? MSVDX_MTX_RAM_ACC_MCMR : 0); + + msvdx_wr(m, ctrl, MSVDX_MTX_RAM_ACCESS_CONTROL); + cur_bank = bank; + } + addr += 4; + + if (!verify) + msvdx_wr(m, data[i], MSVDX_MTX_RAM_ACCESS_DATA_TRANSFER); + if (msvdx_wait_reg(m, MSVDX_MTX_RAM_ACCESS_STATUS, 1, ~0u)) { + ret = -ETIMEDOUT; + break; + } + if (verify && + msvdx_rd(m, MSVDX_MTX_RAM_ACCESS_DATA_TRANSFER) != data[i]) { + DRM_ERROR("msvdx: firmware readback differs at word %u\n", i); + ret = -EIO; + break; + } + } + msvdx_wr(m, saved, MSVDX_MTX_RAM_ACCESS_CONTROL); + return ret; +} + +static void msvdx_comms_reset(struct msvdx_device *m) +{ + u32 flags = m->rev >= MSVDX_POULSBO_D1 ? MSVDX_NODE_FLAGS_D1 + : MSVDX_NODE_FLAGS_D0; + + msvdx_wr(m, flags, MSVDX_COMMS_OFFSET_FLAGS); + msvdx_wr(m, 0, MSVDX_COMMS_MSG_COUNTER); + msvdx_wr(m, 0, MSVDX_COMMS_SIGNATURE); + msvdx_wr(m, 0, MSVDX_COMMS_TO_HOST_RD_INDEX); + msvdx_wr(m, 0, MSVDX_COMMS_TO_HOST_WRT_INDEX); + msvdx_wr(m, 0, MSVDX_COMMS_TO_MTX_RD_INDEX); + msvdx_wr(m, 0, MSVDX_COMMS_TO_MTX_WRT_INDEX); + msvdx_wr(m, 0, MSVDX_COMMS_FW_STATUS); +} + +static int msvdx_load_fw(struct msvdx_device *m) +{ + const struct firmware *raw = NULL; + const struct msvdx_fw_header *hdr; + const u32 *text, *data; + u32 bank_size; + int ret; + + ret = request_firmware(&raw, MSVDX_FW_NAME, m->ddev->dev); + if (ret) { + DRM_INFO("msvdx: no " MSVDX_FW_NAME " (%d); decode unavailable\n", + ret); + return ret; + } + + hdr = (const struct msvdx_fw_header *)raw->data; + if (msvdx_fw_header_bad(hdr, raw->size)) { + DRM_ERROR("msvdx: " MSVDX_FW_NAME " is not a version %d image of %zu bytes\n", + MSVDX_FW_VERSION, raw->size); + ret = -EINVAL; + goto out; + } + + msvdx_wr(m, MSVDX_MTX_SOFT_RESET_BIT, MSVDX_MTX_SOFT_RESET); + msvdx_comms_reset(m); + + bank_size = 1u << (((msvdx_rd(m, MSVDX_MTX_RAM_BANK) & + MSVDX_MTX_RAM_BANK_SIZE_MASK) >> + MSVDX_MTX_RAM_BANK_SIZE_SHIFT) + 2); + if (!bank_size) { + ret = -EIO; + goto out; + } + + text = (const u32 *)(raw->data + sizeof(*hdr)); + data = text + hdr->text_size; + + ret = msvdx_ram_xfer(m, MSVDX_MTX_CORE_CODE_MEM, bank_size, + MSVDX_MTX_PC_START - MSVDX_MTX_CODE_BASE, + hdr->text_size, text, false); + if (!ret) + ret = msvdx_ram_xfer(m, MSVDX_MTX_CORE_DATA_MEM, bank_size, + hdr->data_location - MSVDX_MTX_DATA_BASE, + hdr->data_size, data, false); + if (!ret) + ret = msvdx_ram_xfer(m, MSVDX_MTX_CORE_CODE_MEM, bank_size, + MSVDX_MTX_PC_START - MSVDX_MTX_CODE_BASE, + hdr->text_size, text, true); + if (!ret) + ret = msvdx_ram_xfer(m, MSVDX_MTX_CORE_DATA_MEM, bank_size, + hdr->data_location - MSVDX_MTX_DATA_BASE, + hdr->data_size, data, true); + if (ret) + goto out; + + msvdx_mtx_write_core_reg(m, MSVDX_MTX_PC, MSVDX_MTX_PC_START); + msvdx_wr(m, MSVDX_MTX_ENABLE_BIT, MSVDX_MTX_ENABLE); + + ret = msvdx_wait_reg(m, MSVDX_COMMS_SIGNATURE, + MSVDX_COMMS_SIGNATURE_VALUE, ~0u); + if (ret) + DRM_ERROR("msvdx: firmware started but never signed on\n"); +out: + release_firmware(raw); + return ret; +} + +/* ------------------------------------------------------------ the ring */ + +/* + * Post one message. From psb_mtx_send() (psb_msvdx.c:329-404), with the + * vendor's two BUG_ON()s turned into refusals - a full ring is a caller's + * mistake, not a reason to take the machine down. + */ +static int msvdx_mtx_send(struct msvdx_device *m, const u32 *msg) +{ + unsigned int words = msvdx_msg_words(msvdx_msg_size(msg)); + unsigned int rd, wr, pad, i; + + if (!words || words > MSVDX_NUM_WORDS_MTX_BUF) + return -EINVAL; + + /* The ring is in VEC local RAM, which is not readable with the clocks + * off (psb_msvdx.c:337-338). */ + msvdx_wr(m, MSVDX_CLK_ENABLE_ALL, MSVDX_MAN_CLK_ENABLE); + + rd = msvdx_rd(m, MSVDX_COMMS_TO_MTX_RD_INDEX); + wr = msvdx_rd(m, MSVDX_COMMS_TO_MTX_WRT_INDEX); + if (rd >= MSVDX_NUM_WORDS_MTX_BUF || wr >= MSVDX_NUM_WORDS_MTX_BUF) + return -EIO; + + pad = msvdx_ring_pad_words(wr, MSVDX_NUM_WORDS_MTX_BUF, words); + if (pad) { + u32 padmsg[2]; + + /* Filling the tail while the read index is at zero would leave + * write == read, which the firmware reads as an empty ring. */ + if (!rd || !msvdx_ring_fits(rd, wr, MSVDX_NUM_WORDS_MTX_BUF, + pad + words)) + return -EBUSY; + /* A padding message is a header and nothing else, so only its + * first word carries anything. */ + msvdx_pad_msg_build(padmsg, pad << 2); + for (i = 0; i < pad; i++, wr++) + msvdx_wr(m, i ? 0 : padmsg[0], + MSVDX_COMMS_TO_MTX_BUF + (wr << 2)); + wr = 0; + msvdx_wr(m, wr, MSVDX_COMMS_TO_MTX_WRT_INDEX); + } + + if (!msvdx_ring_fits(rd, wr, MSVDX_NUM_WORDS_MTX_BUF, words)) + return -EBUSY; + + for (i = 0; i < words; i++) { + msvdx_wr(m, msg[i], MSVDX_COMMS_TO_MTX_BUF + (wr << 2)); + if (++wr == MSVDX_NUM_WORDS_MTX_BUF) + wr = 0; + } + msvdx_wr(m, wr, MSVDX_COMMS_TO_MTX_WRT_INDEX); + + msvdx_wr(m, MSVDX_CLK_ENABLE_ALL, MSVDX_MAN_CLK_ENABLE); + msvdx_wr(m, 1, MSVDX_MTX_KICKI); + return 0; +} + +/* Read one message off the to-host ring, or -EAGAIN if it is empty. */ +static int msvdx_mtx_recv(struct msvdx_device *m, u32 *msg, unsigned int max) +{ + unsigned int rd, wr, words, i; + + rd = msvdx_rd(m, MSVDX_COMMS_TO_HOST_RD_INDEX); + wr = msvdx_rd(m, MSVDX_COMMS_TO_HOST_WRT_INDEX); + if (rd == wr) + return -EAGAIN; + if (rd >= MSVDX_NUM_WORDS_HOST_BUF || wr >= MSVDX_NUM_WORDS_HOST_BUF) + return -EIO; + + msg[0] = msvdx_rd(m, MSVDX_COMMS_TO_HOST_BUF + (rd << 2)); + words = msvdx_msg_words(msvdx_msg_size(msg)); + if (!words || words > max) + return -EIO; + if (++rd >= MSVDX_NUM_WORDS_HOST_BUF) + rd = 0; + + for (i = 1; i < words; i++) { + msg[i] = msvdx_rd(m, MSVDX_COMMS_TO_HOST_BUF + (rd << 2)); + if (++rd >= MSVDX_NUM_WORDS_HOST_BUF) + rd = 0; + } + msvdx_wr(m, rd, MSVDX_COMMS_TO_HOST_RD_INDEX); + return 0; +} + +/* + * Wait for the firmware to say something. + * + * The host interrupt line is masked, so this polls the status register the way + * psb_poll_mtx_irq() does; the MTX bit is cleared as soon as it is seen + * because it is the ring, not the bit, that carries the message. + */ +static int msvdx_wait_msg(struct msvdx_device *m, u32 *msg, unsigned int max, + unsigned long end) +{ + for (;;) { + u32 stat = msvdx_rd(m, MSVDX_INTERRUPT_STATUS); + int ret; + + if (stat & MSVDX_INT_MMU_FAULT_MASK) { + msvdx_wr(m, stat, MSVDX_INTERRUPT_CLEAR); + DRM_ERROR("msvdx: MMU fault, status %08x\n", stat); + return -EFAULT; + } + if (stat & MSVDX_INT_MTX_MASK) + msvdx_wr(m, MSVDX_INT_MTX_MASK, MSVDX_INTERRUPT_CLEAR); + + ret = msvdx_mtx_recv(m, msg, max); + if (ret != -EAGAIN) + return ret; + if (time_after(jiffies, end)) + return -ETIMEDOUT; + usleep_range(100, 200); + } +} + +static unsigned long msvdx_deadline(unsigned int timeout_ms) +{ + return jiffies + msecs_to_jiffies(timeout_ms ?: 1000); +} + +/* A decode ends on one of these; anything else the firmware says on the way - + * an ACK, a test response - is not the answer to this command. */ +static bool msvdx_is_reply(unsigned int id) +{ + switch (id) { + case MSVDX_MSGID_CMD_COMPLETED: + case MSVDX_MSGID_CMD_COMPLETED_BATCH: + case MSVDX_MSGID_CMD_FAILED: + case MSVDX_MSGID_CMD_UNSUPPORTED: + case MSVDX_MSGID_CMD_HW_PANIC: + case MSVDX_MSGID_DEBLOCK_REQUIRED: + return true; + } + return false; +} + +/* ------------------------------------------------------------- per file */ + +static struct msvdx_file *msvdx_file_find(struct msvdx_device *m, + struct drm_file *file) +{ + struct msvdx_file *mf; + + list_for_each_entry(mf, &m->files, head) + if (mf->file == file) + return mf; + return NULL; +} + +static int msvdx_rendec_bind(struct msvdx_device *m, struct msvdx_file *mf) +{ + int ret; + + /* The RENDEC base registers are written once, so their addresses have + * to translate in every directory the firmware is ever given. */ + ret = msvdx_mmu_map(&mf->mmu, m->ccb0, MSVDX_VM_RENDEC_A, 0, + MSVDX_RENDEC_A_SIZE, false, false); + if (ret) + return ret; + ret = msvdx_mmu_map(&mf->mmu, m->ccb1, MSVDX_VM_RENDEC_B, 0, + MSVDX_RENDEC_B_SIZE, false, false); + if (ret) + return ret; + mf->rendec_bound = true; + return 0; +} + +static struct msvdx_file *msvdx_file_get(struct drm_device *dev, + struct drm_file *file) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct msvdx_device *m = dev_priv->msvdx; + struct msvdx_file *mf; + + if (!m || !m->hw_up) + return ERR_PTR(-ENODEV); + + mutex_lock(&m->files_lock); + mf = msvdx_file_find(m, file); + if (mf) + goto out; + + mf = kzalloc(sizeof(*mf), GFP_KERNEL); + if (!mf) { + mf = ERR_PTR(-ENOMEM); + goto out; + } + mf->file = file; + mutex_init(&mf->lock); + INIT_LIST_HEAD(&mf->bindings); + if (msvdx_mmu_init(&mf->mmu)) { + kfree(mf); + mf = ERR_PTR(-ENOMEM); + goto out; + } + if (msvdx_rendec_bind(m, mf)) { + msvdx_mmu_fini(&mf->mmu); + kfree(mf); + mf = ERR_PTR(-ENOMEM); + goto out; + } + list_add(&mf->head, &m->files); +out: + mutex_unlock(&m->files_lock); + return mf; +} + +static void msvdx_file_free(struct msvdx_file *mf) +{ + struct msvdx_binding *b, *tmp; + + list_for_each_entry_safe(b, tmp, &mf->bindings, head) { + msvdx_mmu_map(&mf->mmu, b->obj, b->gpu_va, b->offset, b->size, + b->read_only, true); + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(b->obj)); + drm_gem_object_put(b->obj); + list_del(&b->head); + kfree(b); + } + msvdx_mmu_fini(&mf->mmu); + mutex_destroy(&mf->lock); + kfree(mf); +} + +void msvdx_file_release(struct drm_device *dev, struct drm_file *file) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct msvdx_device *m = dev_priv->msvdx; + struct msvdx_file *mf; + + if (!m) + return; + mutex_lock(&m->files_lock); + mf = msvdx_file_find(m, file); + if (mf) + list_del(&mf->head); + mutex_unlock(&m->files_lock); + if (mf) + msvdx_file_free(mf); +} + +/* ------------------------------------------------------------- the ioctls */ + +int msvdx_ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct msvdx_device *m = dev_priv->msvdx; + struct drm_msvdx_get_param *args = data; + + if (!m) + return -ENODEV; + if (args->pad) + return -EINVAL; + + switch (args->param) { + case MSVDX_PARAM_REVISION: + args->value = m->rev; + return 0; + case MSVDX_PARAM_FW_LOADED: + args->value = m->fw_loaded; + return 0; + case MSVDX_PARAM_STANDARDS: + /* Empty, and it will stay empty until the per-slice command + * stream for a standard is established rather than copied. */ + args->value = MSVDX_STANDARDS_SUPPORTED; + return 0; + case MSVDX_PARAM_VM_START: + args->value = MSVDX_VM_USER_START; + return 0; + case MSVDX_PARAM_VM_SIZE: + args->value = MSVDX_VM_USER_SIZE; + return 0; + } + return -EINVAL; +} + +int msvdx_ioctl_vm_bind(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_msvdx_vm_bind *args = data; + struct msvdx_file *mf; + struct msvdx_binding *b, *tmp; + struct drm_gem_object *obj; + int ret; + + if (args->flags & ~(u32)(MSVDX_BIND_UNBIND | MSVDX_BIND_READ_ONLY)) + return -EINVAL; + if (!args->size || (args->size | args->gpu_va | args->offset) & (PAGE_SIZE - 1)) + return -EINVAL; + if (args->gpu_va < MSVDX_VM_USER_START || + args->size > MSVDX_VM_USER_SIZE || + args->gpu_va - MSVDX_VM_USER_START > MSVDX_VM_USER_SIZE - args->size) + return -EINVAL; + + mf = msvdx_file_get(dev, file); + if (IS_ERR(mf)) + return PTR_ERR(mf); + + mutex_lock(&mf->lock); + + if (args->flags & MSVDX_BIND_UNBIND) { + ret = -ENOENT; + list_for_each_entry_safe(b, tmp, &mf->bindings, head) { + if (b->gpu_va != args->gpu_va) + continue; + msvdx_mmu_map(&mf->mmu, b->obj, b->gpu_va, b->offset, + b->size, b->read_only, true); + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(b->obj)); + drm_gem_object_put(b->obj); + list_del(&b->head); + kfree(b); + ret = 0; + break; + } + goto out; + } + + if (msvdx_overlaps(mf, args->gpu_va, args->size)) { + ret = -EBUSY; + goto out; + } + + obj = drm_gem_object_lookup(file, args->handle); + if (!obj) { + ret = -ENOENT; + goto out; + } + if (args->offset > obj->size || obj->size - args->offset < args->size) { + ret = -EINVAL; + goto out_put; + } + ret = drm_gem_shmem_pin(to_drm_gem_shmem_obj(obj)); + if (ret) + goto out_put; + + b = kzalloc(sizeof(*b), GFP_KERNEL); + if (!b) { + ret = -ENOMEM; + goto out_unpin; + } + b->obj = obj; + b->gpu_va = args->gpu_va; + b->offset = args->offset; + b->size = args->size; + b->read_only = args->flags & MSVDX_BIND_READ_ONLY; + + ret = msvdx_mmu_map(&mf->mmu, obj, b->gpu_va, b->offset, b->size, + b->read_only, false); + if (ret) { + kfree(b); + goto out_unpin; + } + list_add(&b->head, &mf->bindings); + mutex_unlock(&mf->lock); + return 0; + +out_unpin: + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(obj)); +out_put: + drm_gem_object_put(obj); +out: + mutex_unlock(&mf->lock); + return ret; +} + +int msvdx_ioctl_ping(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct msvdx_device *m = dev_priv->msvdx; + struct drm_msvdx_ping *args = data; + u32 msg[MSVDX_NUM_WORDS_HOST_BUF]; + u32 out[2] = { 0, 0 }; + int ret; + + if (!m || !m->hw_up || !m->fw_loaded) + return -ENODEV; + if (args->flags || args->pad) + return -EINVAL; + + msvdx_test_msg_build(out); + + mutex_lock(&m->lock); + ret = msvdx_mtx_send(m, out); + if (!ret) + ret = msvdx_wait_msg(m, msg, ARRAY_SIZE(msg), + msvdx_deadline(args->timeout_ms)); + mutex_unlock(&m->lock); + if (ret) + return ret; + + args->reply_id = msvdx_msg_id(msg); + return 0; +} + +/* + * One slice. + * + * Everything this refuses, it refuses because getting it wrong produces a + * picture rather than an error. The standard is checked against what the + * driver has established programming for - nothing, today - so a caller + * cannot get a decode out of this by supplying an operating mode it read off + * a capture. + */ +int msvdx_ioctl_decode(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct msvdx_device *m = dev_priv->msvdx; + struct drm_msvdx_decode *args = data; + struct msvdx_render_params p = {}; + struct msvdx_file *mf; + u32 msg[MSVDX_NUM_WORDS_HOST_BUF]; + u32 out[MSVDX_RENDER_MSG_SIZE / 4]; + int ret; + + if (!m || !m->hw_up || !m->fw_loaded) + return -ENODEV; + if (args->flags || args->pad || args->pad2) + return -EINVAL; + if (!args->operating_mode) + return -EINVAL; + if (args->standard > MSVDX_STD_VC1) + return -EINVAL; + + /* MSVDX_PARAM_STANDARDS reports the same mask, so a caller that asked + * first is never surprised here. */ + if (!(BIT(args->standard) & MSVDX_STANDARDS_SUPPORTED)) + return -EOPNOTSUPP; + + mf = msvdx_file_get(dev, file); + if (IS_ERR(mf)) + return PTR_ERR(mf); + + mutex_lock(&mf->lock); + /* The firmware dereferences this; if it is not mapped the decoder + * walks into an MMU fault instead of decoding. */ + if (!msvdx_find(mf, args->lldma_gpu_va, (u64)args->lldma_words << 2)) { + mutex_unlock(&mf->lock); + return -EINVAL; + } + + p.buffer_size = args->lldma_words; + p.mmu_ptd = (u32)mf->mmu.pd_phys | MSVDX_MMUPTD_INVALIDATE; + p.lldma_va = (u32)args->lldma_gpu_va; + p.context = args->context; + p.operating_mode = args->operating_mode; + p.first_mb_in_slice = args->first_mb_in_slice; + p.last_mb_in_frame = args->last_mb_in_frame; + p.flags = MSVDX_RENDER_HOST_INT; + mutex_unlock(&mf->lock); + + mutex_lock(&m->lock); + p.fence = ++m->fence_next; + if (msvdx_render_msg_build(out, &p)) { + mutex_unlock(&m->lock); + return -EINVAL; + } + ret = msvdx_mtx_send(m, out); + if (!ret) { + unsigned long end = msvdx_deadline(args->timeout_ms); + + do { + ret = msvdx_wait_msg(m, msg, ARRAY_SIZE(msg), end); + } while (!ret && !msvdx_is_reply(msvdx_msg_id(msg))); + } + mutex_unlock(&m->lock); + + if (ret == -ETIMEDOUT) { + args->status = MSVDX_DECODE_TIMEOUT; + return 0; + } + if (ret) + return ret; + + args->fw_msg_id = msvdx_msg_id(msg); + switch (args->fw_msg_id) { + case MSVDX_MSGID_CMD_COMPLETED: + case MSVDX_MSGID_CMD_COMPLETED_BATCH: + /* A completion for another fence is not this one's answer. */ + if (msvdx_completed_fence(msg) != p.fence) { + args->status = MSVDX_DECODE_FAILED; + break; + } + args->status = MSVDX_DECODE_COMPLETED; + break; + case MSVDX_MSGID_CMD_HW_PANIC: + args->status = MSVDX_DECODE_PANIC; + args->irq_status = msvdx_failed_irqstatus(msg); + break; + default: + args->status = MSVDX_DECODE_FAILED; + args->irq_status = msvdx_failed_irqstatus(msg); + break; + } + return 0; +} + +/* --------------------------------------------------------------- bring-up */ + +static int msvdx_alloc_ccb(struct drm_device *dev, struct drm_gem_object **out, + size_t size) +{ + struct drm_gem_shmem_object *shmem; + int ret; + + shmem = drm_gem_shmem_create(dev, size); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + if (shmem->base.filp) + mapping_set_gfp_mask(shmem->base.filp->f_mapping, + GFP_KERNEL | __GFP_DMA32); + ret = drm_gem_shmem_pin(shmem); + if (ret) { + drm_gem_object_put(&shmem->base); + return ret; + } + *out = &shmem->base; + return 0; +} + +static void msvdx_free_ccb(struct drm_gem_object **obj) +{ + if (!*obj) + return; + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(*obj)); + drm_gem_object_put(*obj); + *obj = NULL; +} + +/* RENDEC wants page counts, and both buffers are page multiples by + * construction (psb_msvdxinit.c:541-570). */ +static void msvdx_rendec_program(struct msvdx_device *m) +{ + u32 val; + + msvdx_wr(m, (u32)MSVDX_VM_RENDEC_A, MSVDX_RENDEC_BASE_ADDR0); + msvdx_wr(m, (u32)MSVDX_VM_RENDEC_B, MSVDX_RENDEC_BASE_ADDR1); + + val = ((MSVDX_RENDEC_A_SIZE / 4096) << MSVDX_RENDEC_BUF_SIZE0_SHIFT) | + ((MSVDX_RENDEC_B_SIZE / 4096) << MSVDX_RENDEC_BUF_SIZE1_SHIFT); + msvdx_wr(m, val, MSVDX_RENDEC_BUFFER_SIZE); + + val = (0u << MSVDX_RENDEC_CTRL1_DECODE_START_SHIFT) | + (1u << MSVDX_RENDEC_CTRL1_BURST_SIZE_W_SHIFT) | + (1u << MSVDX_RENDEC_CTRL1_BURST_SIZE_R_SHIFT) | + MSVDX_RENDEC_CTRL1_EXTERNAL_MEMORY; + msvdx_wr(m, val, MSVDX_RENDEC_CONTROL1); + + msvdx_wr(m, MSVDX_RENDEC_CONTEXT_INIT, MSVDX_RENDEC_CONTEXT0); + msvdx_wr(m, MSVDX_RENDEC_CONTEXT_INIT, MSVDX_RENDEC_CONTEXT1); + msvdx_wr(m, MSVDX_RENDEC_CONTEXT_INIT, MSVDX_RENDEC_CONTEXT2); + msvdx_wr(m, MSVDX_RENDEC_CONTEXT_INIT, MSVDX_RENDEC_CONTEXT3); + msvdx_wr(m, MSVDX_RENDEC_CONTEXT_INIT, MSVDX_RENDEC_CONTEXT4); + msvdx_wr(m, MSVDX_RENDEC_CONTEXT_INIT, MSVDX_RENDEC_CONTEXT5); + + msvdx_wr(m, MSVDX_RENDEC_CTRL0_INITIALISE, MSVDX_RENDEC_CONTROL0); +} + +static int msvdx_reset(struct msvdx_device *m) +{ + int ret; + + msvdx_wr(m, MSVDX_SW_RESET_ALL, MSVDX_CONTROL); + ret = msvdx_wait_reg(m, MSVDX_CONTROL, 0, MSVDX_CONTROL_SOFT_RESET); + if (ret) + return ret; + + /* The line stays masked; the status register still latches, which is + * what the message pump polls. */ + msvdx_wr(m, 0, MSVDX_HOST_INTERRUPT_ENABLE); + msvdx_wr(m, 0xffffffff, MSVDX_INTERRUPT_CLEAR); + return 0; +} + +int msvdx_init(struct drm_device *dev) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct pci_dev *pdev = to_pci_dev(dev->dev); + struct msvdx_device *m; + int ret; + + m = devm_kzalloc(dev->dev, sizeof(*m), GFP_KERNEL); + if (!m) + return -ENOMEM; + + m->ddev = dev; + mutex_init(&m->lock); + mutex_init(&m->files_lock); + INIT_LIST_HEAD(&m->files); + m->rev = pdev->revision; + + dev_priv->msvdx_reg = ioremap(pci_resource_start(pdev, 0) + + MSVDX_MMIO_OFFSET, MSVDX_MMIO_SIZE); + if (!dev_priv->msvdx_reg) + return -ENOMEM; + m->regs = dev_priv->msvdx_reg; + + ret = msvdx_reset(m); + if (ret) + goto err_unmap; + + msvdx_wr(m, MSVDX_CLK_ENABLE_ALL, MSVDX_MAN_CLK_ENABLE); + /* MMU_CONTROL0's requestor bits are bypasses: a set bit means that + * requestor does not translate. Zero turns the MMU on. */ + msvdx_wr(m, 0, MSVDX_MMU_CONTROL0); + + ret = msvdx_alloc_ccb(dev, &m->ccb0, MSVDX_RENDEC_A_SIZE); + if (ret) + goto err_unmap; + ret = msvdx_alloc_ccb(dev, &m->ccb1, MSVDX_RENDEC_B_SIZE); + if (ret) + goto err_ccb; + + msvdx_rendec_program(m); + m->hw_up = true; + + /* A missing firmware file is not a failure: the block is up, PING and + * DECODE say -ENODEV, and the display driver carries on. */ + if (!msvdx_load_fw(m)) + m->fw_loaded = true; + + msvdx_wr(m, MSVDX_CLK_ENABLE_MINIMAL, MSVDX_MAN_CLK_ENABLE); + dev_priv->msvdx = m; + + DRM_INFO("msvdx: rev %02x up, firmware %s\n", m->rev, + m->fw_loaded ? "loaded" : "absent"); + return 0; + +err_ccb: + msvdx_free_ccb(&m->ccb0); +err_unmap: + iounmap(dev_priv->msvdx_reg); + dev_priv->msvdx_reg = NULL; + return ret; +} + +void msvdx_fini(struct drm_device *dev) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct msvdx_device *m = dev_priv->msvdx; + struct msvdx_file *mf, *tmp; + + if (!m) + return; + + list_for_each_entry_safe(mf, tmp, &m->files, head) { + list_del(&mf->head); + msvdx_file_free(mf); + } + + msvdx_reset(m); + msvdx_wr(m, 0, MSVDX_MAN_CLK_ENABLE); + msvdx_free_ccb(&m->ccb1); + msvdx_free_ccb(&m->ccb0); + + mutex_destroy(&m->files_lock); + mutex_destroy(&m->lock); + + iounmap(dev_priv->msvdx_reg); + dev_priv->msvdx_reg = NULL; + dev_priv->msvdx = NULL; +} diff -urNp a/drivers/gpu/drm/gma500/msvdx.h b/drivers/gpu/drm/gma500/msvdx.h --- a/drivers/gpu/drm/gma500/msvdx.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/msvdx.h 2026-09-08 10:56:21.859775334 +0200 @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * The MSVDX/VXD video decoder, inside gma500. + * + * gma500 owns the PCI device and the register windows; this drives the third + * one. The decoder is not a command-stream engine - the host loads firmware + * onto an MTX core and then only ever posts messages to it - so almost all of + * this file is bring-up, a ring, and validation of the addresses a message + * hands the firmware. + * + * Copyright (C) 2026 René Rebe + */ +#ifndef _MSVDX_H_ +#define _MSVDX_H_ + +#include +#include +#include +#include + +#include +#include +#include + +#include "sgx_mmu.h" + +/* + * The decoder's address space. + * + * Its own, not the SGX's: the two blocks have separate MMUs and the page + * directory travels in the message. The layout keeps clear of every SGX window + * (0x30000000, 0x40000000, 0x80000000) so that a later driver which does share + * one directory between the two engines is a change of owner, not of address. + * + * The RENDEC buffers are the driver's and sit below the user window, because + * their addresses go into device registers once and must therefore mean the + * same thing in every directory the firmware is ever pointed at. + */ +#define MSVDX_VM_RENDEC_A 0x4f000000ull +#define MSVDX_VM_RENDEC_B 0x4f400000ull +#define MSVDX_VM_USER_START 0x50000000ull +#define MSVDX_VM_USER_SIZE 0x10000000ull + +/* + * Which standards DECODE will accept, as 1 << MSVDX_STD_*. + * + * Empty. The message layer is established for every standard the block can do + * - it is the same message - but the per-slice command stream the message + * points at is not, for any of them, in any source in this tree. Filling this + * in means establishing that stream, not relaxing this constant. + */ +#define MSVDX_STANDARDS_SUPPORTED 0u + +struct msvdx_binding { + struct list_head head; + struct drm_gem_object *obj; + u64 gpu_va; + u64 offset; + u64 size; + bool read_only; +}; + +/* One address space, per file, for the same reason the SGX's is: a render node + * is opened by several unprivileged clients and one of them must not be able + * to name another's picture. */ +struct msvdx_file { + struct list_head head; + struct drm_file *file; + struct mutex lock; + struct sgx_mmu mmu; + struct list_head bindings; + bool rendec_bound; +}; + +struct msvdx_device { + struct drm_device *ddev; + void __iomem *regs; + + struct mutex lock; /* one message at a time on the ring */ + bool hw_up; + bool fw_loaded; + u8 rev; + + /* RENDEC command-expansion buffers. The block writes them itself; the + * host only says where they are and how big. */ + struct drm_gem_object *ccb0; + struct drm_gem_object *ccb1; + + u32 fence_next; + + struct mutex files_lock; + struct list_head files; +}; + +int msvdx_init(struct drm_device *dev); +void msvdx_fini(struct drm_device *dev); +void msvdx_file_release(struct drm_device *dev, struct drm_file *file); + +int msvdx_ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file); +int msvdx_ioctl_vm_bind(struct drm_device *dev, void *data, + struct drm_file *file); +int msvdx_ioctl_ping(struct drm_device *dev, void *data, + struct drm_file *file); +int msvdx_ioctl_decode(struct drm_device *dev, void *data, + struct drm_file *file); + +#endif /* _MSVDX_H_ */ diff -urNp a/drivers/gpu/drm/gma500/msvdx_drm.h b/drivers/gpu/drm/gma500/msvdx_drm.h --- a/drivers/gpu/drm/gma500/msvdx_drm.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/msvdx_drm.h 2026-09-08 10:56:21.859775334 +0200 @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: MIT */ +/* + * UAPI for the MSVDX/VXD video decoder on Poulsbo, on the same render node as + * the SGX. + * + * The decoder is a firmware-driven block: the host allocates the buffers a + * picture needs, maps them into the decoder's address space, and posts one + * FW_VA_RENDER message per slice. There is no register stream, so there is no + * register whitelist here; what has to be validated instead is that every + * device address the message hands the firmware falls inside a binding this + * file made, because the firmware dereferences all of them. + * + * The decoder has its own page directory, not the SGX's: the two blocks have + * separate MMUs and the page-directory address travels in the message rather + * than in a register the host owns. So this carries its own VM_BIND, and GEM + * handles - which are per file, not per engine - come from DRM_IOCTL_SGX_GEM_NEW. + * + * DECODE refuses anything it cannot honestly program. That is deliberate and + * is the whole shape of this interface: a wrong picture and a refusal are not + * equally good outcomes, and the fields whose encoding is not established for + * this revision are named in work/feat-msvdx/README.md rather than filled with + * a captured constant. + * + * Copyright (C) 2026 René Rebe + */ +#ifndef _UAPI_MSVDX_DRM_H_ +#define _UAPI_MSVDX_DRM_H_ + +#ifdef __KERNEL__ +#include +#else +#include "drm.h" +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Numbered above the SGX's 0x00..0x09 so the two engines share one table + * without either having to renumber. */ +#define DRM_MSVDX_GET_PARAM 0x10 +#define DRM_MSVDX_VM_BIND 0x11 +#define DRM_MSVDX_PING 0x12 +#define DRM_MSVDX_DECODE 0x13 + +/* + * Bind a GEM object into the decoder's address space at an address userspace + * picks, the same relocation-free contract the SGX side uses. + */ +struct drm_msvdx_vm_bind { + __u32 handle; + __u32 flags; +#define MSVDX_BIND_UNBIND (1 << 0) +/* The firmware only reads it - slice data, the LLDMA chain, reference + * pictures. Without this the mapping is writable, which the output surface and + * the macroblock-parameter buffer need. */ +#define MSVDX_BIND_READ_ONLY (1 << 1) + __u64 gpu_va; + __u64 offset; + __u64 size; +}; + +/* + * Ask the firmware to answer. Sends FW_VA_TEST1 and returns the id of the + * message that came back. + * + * This exists because every part of the path below a decode - the firmware + * being alive, both rings, the write-index kick, the MTX interrupt, the + * message pump - can be proved without programming a single decode field, and + * proving them separately is what keeps a silent decoder from being three + * possible faults at once. + */ +struct drm_msvdx_ping { + __u32 timeout_ms; + __u32 reply_id; /* out: MSVDX_MSGID_* */ + __u32 flags; /* must be 0 */ + __u32 pad; +}; + +/* + * One slice. + * + * A picture is one or more of these; the firmware accumulates them and signals + * completion on the one whose last_mb_in_frame it has reached. Every address + * is a device address in this file's decoder address space. + */ +struct drm_msvdx_decode { + __u64 bo_handles; /* every object the slice touches */ + __u32 bo_count; + __u32 flags; /* must be 0 */ + + /* The linked-list DMA chain the firmware walks to fetch this slice's + * command and bitstream data, and its length in 32-bit words. Both + * fields are established - offset 0x08 and the twelve-bit field at + * 0x02 of FW_VA_RENDER - but the layout of what lldma_gpu_va points at + * is not, for this revision, in any source in this tree. The kernel + * therefore checks it is bound and passes it through; it cannot check + * that it is well formed. */ + __u64 lldma_gpu_va; + __u32 lldma_words; + + /* FW_VA_RENDER's OPERATING_MODE word, which selects the standard and + * the VEC/VDMC configuration. Its encoding is not established here. + * Zero is refused: a decode with no operating mode is not a decode. */ + __u32 operating_mode; + + __u32 first_mb_in_slice; + __u32 last_mb_in_frame; + /* The firmware's stream context. It appears in FW_VA_RENDER at 0x0c + * and comes back in FW_VA_DEBLOCK_REQUIRED; nothing establishes how a + * host is meant to allocate one, so the kernel treats it as opaque and + * only requires that it stay constant across a picture. */ + __u32 context; + __u32 timeout_ms; + + /* Which standard this slice is, so the kernel can refuse one it has no + * established programming for rather than handing the firmware a mode + * word nobody has justified. */ + __u32 standard; +#define MSVDX_STD_MPEG2 0 +#define MSVDX_STD_MPEG4 1 +#define MSVDX_STD_H264 2 +#define MSVDX_STD_VC1 3 + __u32 pad; + + __u32 status; /* out: MSVDX_DECODE_* */ +#define MSVDX_DECODE_COMPLETED 0 +#define MSVDX_DECODE_FAILED 1 /* VA_MSGID_CMD_FAILED */ +#define MSVDX_DECODE_PANIC 2 /* VA_MSGID_CMD_HW_PANIC */ +#define MSVDX_DECODE_TIMEOUT 3 + __u32 fw_msg_id; /* out: the message that came back */ + __u32 irq_status; /* out: on failure, the firmware's copy */ + __u32 pad2; +}; + +struct drm_msvdx_get_param { + __u32 param; +/* The PCI revision, which decides the device-node flag set the firmware is + * started with: 0x5 is D0, 0x6 and up D1. */ +#define MSVDX_PARAM_REVISION 0 +#define MSVDX_PARAM_FW_LOADED 1 +/* A bitmask of 1 << MSVDX_STD_*: the standards this driver will accept in a + * DECODE. It is not what the silicon can do - it is what has been established + * well enough to program without guessing, and it is currently empty. A + * userspace that decides what to offer by asking cannot promise a decode this + * kernel would refuse. */ +#define MSVDX_PARAM_STANDARDS 2 +#define MSVDX_PARAM_VM_START 3 +#define MSVDX_PARAM_VM_SIZE 4 + __u32 pad; + __u64 value; /* out */ +}; + +#define DRM_IOCTL_MSVDX_GET_PARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_MSVDX_GET_PARAM, struct drm_msvdx_get_param) +#define DRM_IOCTL_MSVDX_VM_BIND DRM_IOW (DRM_COMMAND_BASE + DRM_MSVDX_VM_BIND, struct drm_msvdx_vm_bind) +#define DRM_IOCTL_MSVDX_PING DRM_IOWR(DRM_COMMAND_BASE + DRM_MSVDX_PING, struct drm_msvdx_ping) +#define DRM_IOCTL_MSVDX_DECODE DRM_IOWR(DRM_COMMAND_BASE + DRM_MSVDX_DECODE, struct drm_msvdx_decode) + +#if defined(__cplusplus) +} +#endif + +#endif /* _UAPI_MSVDX_DRM_H_ */ diff -urNp a/drivers/gpu/drm/gma500/msvdx_msg.h b/drivers/gpu/drm/gma500/msvdx_msg.h --- a/drivers/gpu/drm/gma500/msvdx_msg.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/msvdx_msg.h 2026-09-08 10:56:21.860775348 +0200 @@ -0,0 +1,280 @@ +/* The host/firmware message ring on MSVDX, and the messages that go over it. + * + * The host never programs a decode register directly. It writes fixed-layout + * messages into a circular buffer in the VEC's local RAM - which is mapped + * into the MSVDX MMIO window, so a "register" write at 0x2cc0..0x2fff is a + * write into that RAM - bumps a write index, and kicks the MTX. The firmware + * answers on a second buffer in the same area and raises the MTX interrupt. + * + * This is pure arithmetic and byte layout, so it lives in a header the module + * and a host test share verbatim, the way sgx_check.h does. Every offset, + * mask and message id is transcribed from the Poulsbo-era GPL driver, + * upstream/src/psb-kernel-source_4.42.0-0ubuntu2~1010um5/psb-kernel-source/ + * psb_msvdx.h - the release that shipped against this machine's msvdx_fw.bin. + * + * Copyright (C) 2026 René Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _MSVDX_MSG_H_ +#define _MSVDX_MSG_H_ + +/* The communication area (psb_msvdx.h:194-218). Both rings hold 100 words; + * the area ends at 0x3000 and the vendor header has a #error on it. */ +#define MSVDX_COMMS_SIGNATURE_VALUE 0xa5a5a5a5u +#define MSVDX_NUM_WORDS_HOST_BUF 100 +#define MSVDX_NUM_WORDS_MTX_BUF 100 + +#define MSVDX_COMMS_AREA_ADDR 0x02cc0 + +#define MSVDX_COMMS_FW_STATUS (MSVDX_COMMS_AREA_ADDR - 0x10) +#define MSVDX_COMMS_SCRATCH (MSVDX_COMMS_AREA_ADDR - 0x08) +#define MSVDX_COMMS_MSG_COUNTER (MSVDX_COMMS_AREA_ADDR - 0x04) +#define MSVDX_COMMS_SIGNATURE (MSVDX_COMMS_AREA_ADDR + 0x00) +#define MSVDX_COMMS_TO_HOST_BUF_SIZE (MSVDX_COMMS_AREA_ADDR + 0x04) +#define MSVDX_COMMS_TO_HOST_RD_INDEX (MSVDX_COMMS_AREA_ADDR + 0x08) +#define MSVDX_COMMS_TO_HOST_WRT_INDEX (MSVDX_COMMS_AREA_ADDR + 0x0c) +#define MSVDX_COMMS_TO_MTX_BUF_SIZE (MSVDX_COMMS_AREA_ADDR + 0x10) +#define MSVDX_COMMS_TO_MTX_RD_INDEX (MSVDX_COMMS_AREA_ADDR + 0x14) +#define MSVDX_COMMS_OFFSET_FLAGS (MSVDX_COMMS_AREA_ADDR + 0x18) +#define MSVDX_COMMS_TO_MTX_WRT_INDEX (MSVDX_COMMS_AREA_ADDR + 0x1c) +#define MSVDX_COMMS_TO_HOST_BUF (MSVDX_COMMS_AREA_ADDR + 0x20) +#define MSVDX_COMMS_TO_MTX_BUF (MSVDX_COMMS_TO_HOST_BUF + \ + (MSVDX_NUM_WORDS_HOST_BUF << 2)) +#define MSVDX_COMMS_AREA_END (MSVDX_COMMS_TO_MTX_BUF + \ + (MSVDX_NUM_WORDS_MTX_BUF << 2)) + +/* MSVDX_COMMS_FW_STATUS: the firmware says nothing is on the hardware + * (psb_msvdx.h:74). Only meaningful together with an empty to-MTX ring. */ +#define MSVDX_FW_STATUS_HW_IDLE 0x00000001u + +/* Message ids (psb_msvdx.h:280-326, enum at 298-326). 0x80 starts host->MTX, 0xc0 MTX->host. */ +#define MSVDX_MSGID_PADDING 0x00 +#define MSVDX_MSGID_INIT 0x80 +#define MSVDX_MSGID_RENDER 0x81 +#define MSVDX_MSGID_DEBLOCK 0x82 +#define MSVDX_MSGID_OOLD 0x83 +#define MSVDX_MSGID_TEST1 0x84 +#define MSVDX_MSGID_TEST2 0x85 +#define MSVDX_MSGID_RENDER_MC_INTERRUPT 0x86 +#define MSVDX_MSGID_CMD_COMPLETED 0xc0 +#define MSVDX_MSGID_CMD_COMPLETED_BATCH 0xc1 +#define MSVDX_MSGID_DEBLOCK_REQUIRED 0xc2 +#define MSVDX_MSGID_TEST_RESPONCE 0xc3 +#define MSVDX_MSGID_ACK 0xc4 +#define MSVDX_MSGID_CMD_FAILED 0xc5 +#define MSVDX_MSGID_CMD_UNSUPPORTED 0xc6 +#define MSVDX_MSGID_CMD_HW_PANIC 0xc7 + +/* Message sizes in bytes (psb_msvdx.h:292, 330, 420, 454, 496, 522). A generic + * message's first byte is its own size and its second its id, which is how the + * ring is walked without knowing the message. */ +#define MSVDX_PADMSG_SIZE 2 +#define MSVDX_RENDER_MSG_SIZE 32 +#define MSVDX_CMD_COMPLETED_SIZE 12 +#define MSVDX_CMD_FAILED_SIZE 12 +#define MSVDX_DEBLOCK_REQUIRED_SIZE 8 +#define MSVDX_HW_PANIC_SIZE 12 + +/* FW_VA_RENDER, flags word: ask the firmware to raise the host interrupt when + * this command completes. It comes back in the completion's flags, and the + * vendor driver treats its presence there as "the hardware is free again" + * (psb_msvdx.h:59, psb_msvdx.c:530-535). */ +#define MSVDX_RENDER_HOST_INT 0x00004000u + +/* The MMU page-directory word carries a request in its low bit: the address is + * 4 KiB aligned, so bit 0 is free, and setting it asks the firmware to + * invalidate the decoder's translation cache before it starts + * (psb_msvdx.c:131-141). */ +#define MSVDX_MMUPTD_INVALIDATE 0x00000001u + +/* BUFFER_SIZE is twelve bits (psb_msvdx.h:348-354). */ +#define MSVDX_RENDER_BUFFER_SIZE_MAX 0x0fffu + +/* + * The render message, as the caller describes it. + * + * Everything here is established as a field: its offset, width and mask are in + * the vendor header. What is *not* established for this revision is what two + * of them have to contain - operating_mode's codec encoding, and the layout of + * the linked-list DMA chain lldma_va points at. Those are the caller's + * problem, and driver/gma500/msvdx.c refuses a message whose lldma_va does not + * fall inside a binding rather than letting the decoder walk into a fault. + */ +struct msvdx_render_params { + unsigned int buffer_size; /* words of LLDMA payload; 12 bits */ + unsigned int mmu_ptd; /* page directory, bit 0 = invalidate */ + unsigned int lldma_va; /* device VA of the LLDMA chain */ + unsigned int context; /* firmware-side stream context id */ + unsigned int fence; /* echoed in the completion */ + unsigned int operating_mode; /* VEC/VDMC mode word */ + unsigned int first_mb_in_slice; + unsigned int last_mb_in_frame; + unsigned int flags; +}; + +/* Generic header accessors. The ring is words, the header is bytes, and the + * device is little-endian as the host is. */ +static inline unsigned int msvdx_msg_size(const unsigned int *msg) +{ + return msg[0] & 0xffu; +} + +static inline unsigned int msvdx_msg_id(const unsigned int *msg) +{ + return (msg[0] >> 8) & 0xffu; +} + +static inline unsigned int msvdx_msg_words(unsigned int bytes) +{ + return (bytes + 3) / 4; +} + +/* Build a padding message that fills the tail of the ring. The vendor sends + * one whenever a real message would wrap (psb_msvdx.c:358-377). */ +static inline void msvdx_pad_msg_build(unsigned int *msg, unsigned int bytes) +{ + msg[0] = (bytes & 0xffu) | (MSVDX_MSGID_PADDING << 8); + msg[1] = 0; +} + +/* + * Build FW_VA_RENDER into eight words. + * + * Returns 0, or -1 if a field does not fit the width the hardware gives it - + * a truncated macroblock count or buffer size decodes the wrong picture, so it + * is refused here rather than masked. + */ +static inline int msvdx_render_msg_build(unsigned int *msg, + const struct msvdx_render_params *p) +{ + if (p->buffer_size > MSVDX_RENDER_BUFFER_SIZE_MAX) + return -1; + if (p->first_mb_in_slice > 0xffffu || p->last_mb_in_frame > 0xffffu) + return -1; + if (p->lldma_va & 3u) /* ALIGNMENT (4) */ + return -1; + + msg[0] = (MSVDX_RENDER_MSG_SIZE & 0xffu) | + (MSVDX_MSGID_RENDER << 8) | + ((p->buffer_size & MSVDX_RENDER_BUFFER_SIZE_MAX) << 16); + msg[1] = p->mmu_ptd; + msg[2] = p->lldma_va; + msg[3] = p->context; + msg[4] = p->fence; + msg[5] = p->operating_mode; + msg[6] = (p->first_mb_in_slice & 0xffffu) | + ((p->last_mb_in_frame & 0xffffu) << 16); + msg[7] = p->flags; + return 0; +} + +/* Build FW_VA_TEST1: header only. The firmware answers, which is the cheapest + * proof that both rings, the kick and the interrupt all work. */ +static inline void msvdx_test_msg_build(unsigned int *msg) +{ + msg[0] = (4u & 0xffu) | (MSVDX_MSGID_TEST1 << 8); +} + +/* Completion and failure readers (psb_msvdx.h:420-520). */ +static inline unsigned int msvdx_completed_fence(const unsigned int *msg) +{ + return msg[1]; +} + +static inline unsigned int msvdx_completed_flags(const unsigned int *msg) +{ + return msg[2]; +} + +static inline unsigned int msvdx_failed_fence(const unsigned int *msg) +{ + return msg[1]; +} + +static inline unsigned int msvdx_failed_irqstatus(const unsigned int *msg) +{ + return msg[2]; +} + +static inline unsigned int msvdx_deblock_required_context(const unsigned int *msg) +{ + return msg[1]; +} + +/* + * Ring arithmetic, from psb_mtx_send() (psb_msvdx.c:329-404). + * + * words_free is the vendor's formula. The strict comparison in + * msvdx_ring_fits() is not: the vendor lets a message fill the ring exactly, + * after which the write index equals the read index and the firmware cannot + * tell a full ring from an empty one. One word is left unused instead. + */ +static inline unsigned int msvdx_ring_words_free(unsigned int rd, + unsigned int wr, + unsigned int size) +{ + return wr >= rd ? size - (wr - rd) : rd - wr; +} + +static inline int msvdx_ring_fits(unsigned int rd, unsigned int wr, + unsigned int size, unsigned int words) +{ + return words < msvdx_ring_words_free(rd, wr, size); +} + +/* Words of padding needed before a message of this length may be written, or + * zero when it does not wrap. */ +static inline unsigned int msvdx_ring_pad_words(unsigned int wr, + unsigned int size, + unsigned int words) +{ + return wr + words > size ? size - wr : 0; +} + +/* + * The firmware image. + * + * msvdx_fw.bin is four header words followed by text and data, both counted in + * 32-bit words (psb_msvdxinit.c:41-47, 237-276). This machine's copy is + * version 2, 0xb19 text words and 0x416 data words linked at 0x82882c80, which + * is 15564 bytes - the size recorded in BINARY-STACK-INVENTORY.md. + */ +struct msvdx_fw_header { + unsigned int ver; + unsigned int text_size; + unsigned int data_size; + unsigned int data_location; +}; + +#define MSVDX_FW_VERSION 0x02 +/* The MTX data window's base; the image's link address must be inside it. Same + * number as msvdx_reg.h's, repeated so this header stands alone for the host + * test (psb_msvdx.h:104). */ +#define MSVDX_MTX_DATA_BASE_CHECK 0x82880000u + +/* Nonzero if the image is not one this driver may upload. Size is checked + * against the header's own counts, which is what stops a truncated or + * substituted file from being written into the MTX. */ +static inline int msvdx_fw_header_bad(const struct msvdx_fw_header *h, + unsigned long size) +{ + unsigned long want; + + if (size < sizeof(*h)) + return 1; + if (h->ver != MSVDX_FW_VERSION) + return 1; + /* Both counts are word counts and the sum must not overflow the + * unsigned long the caller measured the file with. */ + if (h->text_size > (1u << 24) || h->data_size > (1u << 24)) + return 1; + want = sizeof(*h) + 4ul * h->text_size + 4ul * h->data_size; + if (want != size) + return 1; + if (h->data_location < MSVDX_MTX_DATA_BASE_CHECK) + return 1; + return 0; +} + +#endif /* _MSVDX_MSG_H_ */ diff -urNp a/drivers/gpu/drm/gma500/msvdx_reg.h b/drivers/gpu/drm/gma500/msvdx_reg.h --- a/drivers/gpu/drm/gma500/msvdx_reg.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/msvdx_reg.h 2026-09-08 10:56:21.861775361 +0200 @@ -0,0 +1,163 @@ +/* The MSVDX/VXD video decoder's register map. + * + * MSVDX is a second processor on the same die and the same PCI function as the + * SGX: an MTX RISC core running downloadable firmware, a VEC entropy decoder + * with its own local RAM, a VDMC/VDEB back end, a RENDEC command-expansion + * block with two external buffers, and its own MMU using the same two-level + * page tables the SGX does. The host does not drive the decode directly - it + * posts messages to the firmware through a ring in the VEC's local RAM. + * + * Every offset and mask here is transcribed from the Poulsbo-era GPL kernel + * driver, upstream/src/psb-kernel-source_4.42.0-0ubuntu2~1010um5/ + * psb-kernel-source/psb_msvdx.h - the driver that shipped against the very + * msvdx_fw.bin this machine loads, so its numbers are for this revision and + * not a later one. Where a name only exists in the Medfield-era drop + * (upstream/openpvrsgx/drivers/gpu/drm/pvrsgx/1.7.862890/imgv/psb_msvdx.h) it + * is marked as such, because that part is not this part. + * + * Copyright (C) 2026 René Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _MSVDX_REG_H_ +#define _MSVDX_REG_H_ + +/* The MMIO window, from psb_drv.h:108-109 of the same driver: BAR0 + 0x50000, + * 32 KiB. gma500 maps BAR0 at PSB_VDC_OFFSET and the SGX at 0x40000; this is + * the third window on the same bar. */ +#define MSVDX_MMIO_OFFSET 0x50000 +#define MSVDX_MMIO_SIZE 0x8000 + +/* MTX core (psb_msvdx.h:158-166). */ +#define MSVDX_MTX_ENABLE 0x0000 +#define MSVDX_MTX_KICKI 0x0088 +#define MSVDX_MTX_REGISTER_READ_WRITE_DATA 0x00f8 +#define MSVDX_MTX_REGISTER_READ_WRITE_REQUEST 0x00fc +#define MSVDX_MTX_RAM_ACCESS_DATA_TRANSFER 0x0104 +#define MSVDX_MTX_RAM_ACCESS_CONTROL 0x0108 +#define MSVDX_MTX_RAM_ACCESS_STATUS 0x010c +#define MSVDX_MTX_SOFT_RESET 0x0200 + +/* Core (psb_msvdx.h:168-175). */ +#define MSVDX_CONTROL 0x0600 +#define MSVDX_INTERRUPT_STATUS 0x0608 +#define MSVDX_INTERRUPT_CLEAR 0x060c +#define MSVDX_HOST_INTERRUPT_ENABLE 0x0610 +#define MSVDX_MAN_CLK_ENABLE 0x0620 +#define MSVDX_MMU_CONTROL0 0x0680 +#define MSVDX_MTX_RAM_BANK 0x06f0 + +/* RENDEC (psb_msvdx.h:177-189). */ +#define MSVDX_RENDEC_CONTROL0 0x0868 +#define MSVDX_RENDEC_CONTROL1 0x086c +#define MSVDX_RENDEC_BUFFER_SIZE 0x0870 +#define MSVDX_RENDEC_BASE_ADDR0 0x0874 +#define MSVDX_RENDEC_BASE_ADDR1 0x0878 +#define MSVDX_RENDEC_READ_DATA 0x0898 +#define MSVDX_RENDEC_CONTEXT0 0x0950 +#define MSVDX_RENDEC_CONTEXT1 0x0954 +#define MSVDX_RENDEC_CONTEXT2 0x0958 +#define MSVDX_RENDEC_CONTEXT3 0x095c +#define MSVDX_RENDEC_CONTEXT4 0x0960 +#define MSVDX_RENDEC_CONTEXT5 0x0964 + +/* The one MSVDX_CMDS offset established anywhere in this tree: the Medfield + * driver pokes it to unblock the RENDEC after a stalled slice (imgv + * psb_msvdx.h:323, used at imgv psb_msvdx.c:862-871). It places the CMDS + * register block in the MMIO window at 0x1000; nothing else about that block + * is established here - see the README. */ +#define MSVDX_CMDS_END_SLICE_PICTURE 0x1404 + +/* Field masks (psb_msvdx.h:141-156, 220-278). */ +#define MSVDX_MAN_CLK_CORE 0x00000001u +#define MSVDX_MAN_CLK_VDEB_PROCESS 0x00000002u +#define MSVDX_MAN_CLK_VDEB_ACCESS 0x00000004u +#define MSVDX_MAN_CLK_VDMC 0x00000008u +#define MSVDX_MAN_CLK_VEC_ENTDEC 0x00000010u +#define MSVDX_MAN_CLK_VEC_ITRANS 0x00000020u +#define MSVDX_MAN_CLK_MTX 0x00000040u + +#define MSVDX_CLK_ENABLE_ALL (MSVDX_MAN_CLK_CORE | \ + MSVDX_MAN_CLK_VDEB_PROCESS | \ + MSVDX_MAN_CLK_VDEB_ACCESS | \ + MSVDX_MAN_CLK_VDMC | \ + MSVDX_MAN_CLK_VEC_ENTDEC | \ + MSVDX_MAN_CLK_VEC_ITRANS | \ + MSVDX_MAN_CLK_MTX) +#define MSVDX_CLK_ENABLE_MINIMAL (MSVDX_MAN_CLK_CORE | MSVDX_MAN_CLK_MTX) + +#define MSVDX_CONTROL_SOFT_RESET 0x00000100u +#define MSVDX_CONTROL_FE_SOFT_RESET 0x00010000u +#define MSVDX_CONTROL_BE_SOFT_RESET 0x00100000u +#define MSVDX_CONTROL_VEC_MEMIF_SOFT_RESET 0x01000000u +#define MSVDX_CONTROL_VEC_RENDEC_DEC_SOFT_RESET 0x10000000u +#define MSVDX_SW_RESET_ALL (MSVDX_CONTROL_SOFT_RESET | \ + MSVDX_CONTROL_FE_SOFT_RESET | \ + MSVDX_CONTROL_BE_SOFT_RESET | \ + MSVDX_CONTROL_VEC_MEMIF_SOFT_RESET | \ + MSVDX_CONTROL_VEC_RENDEC_DEC_SOFT_RESET) + +#define MSVDX_INT_MMU_FAULT_MASK 0x00000f00u +#define MSVDX_INT_MTX_MASK 0x00004000u + +#define MSVDX_MTX_RW_REQ_DREADY 0x80000000u +#define MSVDX_MTX_RW_REQ_RNW 0x00010000u + +#define MSVDX_MTX_RAM_ACC_MCMID_SHIFT 20 +#define MSVDX_MTX_RAM_ACC_MCMID_MASK 0x0ff00000u +#define MSVDX_MTX_RAM_ACC_ADDR_SHIFT 2 +#define MSVDX_MTX_RAM_ACC_ADDR_MASK 0x000ffffcu +#define MSVDX_MTX_RAM_ACC_MCMAI 0x00000002u +#define MSVDX_MTX_RAM_ACC_MCMR 0x00000001u + +#define MSVDX_MTX_ENABLE_BIT 0x00000001u +#define MSVDX_MTX_SOFT_RESET_BIT 0x00000001u + +#define MSVDX_MTX_RAM_BANK_SIZE_SHIFT 16 +#define MSVDX_MTX_RAM_BANK_SIZE_MASK 0x000f0000u + +#define MSVDX_RENDEC_BUF_SIZE0_SHIFT 0 +#define MSVDX_RENDEC_BUF_SIZE1_SHIFT 16 +#define MSVDX_RENDEC_CTRL1_DECODE_START_SHIFT 0 +#define MSVDX_RENDEC_CTRL1_BURST_SIZE_R_SHIFT 16 +#define MSVDX_RENDEC_CTRL1_BURST_SIZE_W_SHIFT 18 +#define MSVDX_RENDEC_CTRL1_EXTERNAL_MEMORY 0x01000000u +#define MSVDX_RENDEC_CTRL0_INITIALISE 0x00000001u + +/* The RENDEC context seed the vendor driver writes to all six contexts + * (psb_msvdxinit.c:560-565). Its field meanings are not established. */ +#define MSVDX_RENDEC_CONTEXT_INIT 0x00101010u + +/* MTX memory areas and the firmware's link addresses (psb_msvdx.h:103-108). */ +#define MSVDX_MTX_CORE_CODE_MEM 0x10 +#define MSVDX_MTX_CORE_DATA_MEM 0x18 +#define MSVDX_MTX_CODE_BASE 0x80900000u +#define MSVDX_MTX_DATA_BASE 0x82880000u +#define MSVDX_MTX_PC_START 0x80900000u + +/* MTX internal register selector; PC is (r0, u5) (psb_msvdx.h:110-111). */ +#define MSVDX_MTX_INTERNAL_REG(r, u) (((r) << 4) | (u)) +#define MSVDX_MTX_PC MSVDX_MTX_INTERNAL_REG(0, 5) + +/* RENDEC buffer sizes as the Poulsbo driver sized them (psb_msvdx.h:113-114). + * The BUFFER_SIZE register counts 4 KiB pages, so both must be page + * multiples. */ +#define MSVDX_RENDEC_A_SIZE (2 * 1024 * 1024) +#define MSVDX_RENDEC_B_SIZE (MSVDX_RENDEC_A_SIZE / 4) + +/* Device-node flags the host seeds into the COMMS area before starting the + * MTX; they differ by stepping (psb_msvdx.h:58-67, psb_msvdxinit.c:299-310). */ +#define MSVDX_NODE_FLAG_MMU_NONOPT_INV 0x00000002u +#define MSVDX_NODE_FLAG_MMU_HW_INVALIDATION 0x00000020u +#define MSVDX_NODE_FLAG_BRN23154_BLOCK_ON_FE 0x00000200u +#define MSVDX_NODE_FLAGS_D0 (MSVDX_NODE_FLAG_MMU_NONOPT_INV | \ + MSVDX_NODE_FLAG_MMU_HW_INVALIDATION |\ + MSVDX_NODE_FLAG_BRN23154_BLOCK_ON_FE) +#define MSVDX_NODE_FLAGS_D1 (MSVDX_NODE_FLAG_MMU_HW_INVALIDATION |\ + MSVDX_NODE_FLAG_BRN23154_BLOCK_ON_FE) + +/* PCI revision ids (psb_msvdx.h:70-71). */ +#define MSVDX_POULSBO_D0 0x5 +#define MSVDX_POULSBO_D1 0x6 + +#endif /* _MSVDX_REG_H_ */ diff -urNp a/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c b/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c --- a/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c 2026-09-08 10:56:21.864775402 +0200 @@ -98,7 +98,6 @@ static int xfer_read(struct i2c_adapter struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap); struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; u32 temp; - int ret; i2c_dev->status = I2C_STAT_INIT; i2c_dev->msg = pmsg; @@ -110,14 +109,9 @@ static int xfer_read(struct i2c_adapter HDMI_WRITE(HDMI_HI2CHCR, temp); HDMI_READ(HDMI_HI2CHCR); - while (i2c_dev->status != I2C_TRANSACTION_DONE) { - ret = wait_for_completion_interruptible_timeout(&i2c_dev->complete, + while (i2c_dev->status != I2C_TRANSACTION_DONE) + wait_for_completion_interruptible_timeout(&i2c_dev->complete, 10 * HZ); - if (ret < 0) - return ret; - if (!ret) - return -ETIMEDOUT; - } return 0; } @@ -136,7 +130,7 @@ static int oaktrail_hdmi_i2c_access(stru { struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap); struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev; - int i, ret = 0; + int i; mutex_lock(&i2c_dev->i2c_lock); @@ -148,11 +142,9 @@ static int oaktrail_hdmi_i2c_access(stru for (i = 0; i < num; i++) { if (pmsg->len && pmsg->buf) { if (pmsg->flags & I2C_M_RD) - ret = xfer_read(adap, pmsg); + xfer_read(adap, pmsg); else - ret = xfer_write(adap, pmsg); - if (ret) - break; + xfer_write(adap, pmsg); } pmsg++; /* next message */ } @@ -162,9 +154,6 @@ static int oaktrail_hdmi_i2c_access(stru mutex_unlock(&i2c_dev->i2c_lock); - if (ret) - return ret; - return i; } diff -urNp a/drivers/gpu/drm/gma500/power.c b/drivers/gpu/drm/gma500/power.c --- a/drivers/gpu/drm/gma500/power.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/power.c 2026-09-08 10:56:21.868775457 +0200 @@ -34,6 +34,7 @@ #include "psb_reg.h" #include "psb_intel_reg.h" #include "psb_irq.h" +#include "psb_sgx_render.h" #include #include @@ -180,6 +181,8 @@ int gma_power_suspend(struct device *_de struct pci_dev *pdev = to_pci_dev(_dev); struct drm_device *dev = pci_get_drvdata(pdev); + /* The render core first: a deferred render sleeps on the interrupt. */ + psb_sgx_suspend(&to_drm_psb_private(dev)->sgx); gma_irq_uninstall(dev); gma_suspend_display(dev); gma_suspend_pci(pdev); @@ -200,6 +203,8 @@ int gma_power_resume(struct device *_dev gma_resume_pci(pdev); gma_resume_display(pdev); gma_irq_install(dev); + /* The render core came back with the island, at its reset values. */ + psb_sgx_resume(&to_drm_psb_private(dev)->sgx); return 0; } diff -urNp a/drivers/gpu/drm/gma500/psb_device.c b/drivers/gpu/drm/gma500/psb_device.c --- a/drivers/gpu/drm/gma500/psb_device.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_device.c 2026-09-08 10:56:21.869775470 +0200 @@ -7,6 +7,8 @@ #include #include +#include +#include #include "gma_device.h" #include "intel_bios.h" @@ -78,6 +80,159 @@ static int psb_backlight_setup(struct dr } /* + * Display FIFO watermarks + * + * Transcribed from psbCalculateWaterMark() and psbSetWatermarks() in + * xserver-xorg-video-psb-0.32.1/src/psb_crtc.c:342-515, with the register + * layouts from src/psb_driver.h:1074-1148 and the constants from + * src/i810_reg.h:2287-2303. Nothing here programmed them, so every mode + * ran on whatever the firmware left behind for the panel it booted with, + * and a wider or faster pipe than that underruns the display FIFO. + */ + +#define PSB_WM_QUEUE_SIZE 16 +#define PSB_WM_SR_EXIT_LATENCY 100 +#define PSB_WM_BITS_PER_CACHE_LINE 512 +#define PSB_WM_FIFO_A_LATENCY 12 +#define PSB_WM_FIFO_B_LATENCY 10 +#define PSB_WM_DUAL_A_LATENCY 15 +#define PSB_WM_MAX_FIFO_START 128 +#define PSB_WM_PRECISION 10000 +#define PSB_WM_ROUNDING 5000 + +/* What one pipe drains from its FIFO while the memory controller is away for + * @latency of its own clocks, in FIFO entries. */ +static u32 psb_wm_drain(struct drm_psb_private *dev_priv, u32 latency, + u32 dotclock_mhz, u32 bpp) +{ + u32 per_line = PSB_WM_BITS_PER_CACHE_LINE / bpp; + u32 ratio = (dotclock_mhz * PSB_WM_PRECISION) / dev_priv->mem_freq; + + return ((latency * (ratio / per_line)) + PSB_WM_ROUNDING) / + PSB_WM_PRECISION; +} + +/* The level the plane has to refill from, given the FIFO it starts with. A + * mode the FIFO cannot cover at all drains past zero; the vendor lets that + * wrap, which writes a large watermark into a small field. Clamp instead and + * say so - the mode is beyond what this pipe can feed. */ +static u32 psb_wm_level(struct drm_device *dev, u32 start, bool sr_exit, + u32 latency, u32 dotclock_mhz, u32 bpp) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + u32 drain; + + if (!dotclock_mhz || !bpp || !dev_priv->mem_freq) + return 0; + + drain = psb_wm_drain(dev_priv, PSB_WM_QUEUE_SIZE * latency, + dotclock_mhz, bpp); + if (sr_exit) + drain += psb_wm_drain(dev_priv, PSB_WM_SR_EXIT_LATENCY, + dotclock_mhz, bpp); + + if (drain >= start) { + drm_dbg_kms(dev, "display FIFO too small: %u of %u entries drain at %u MHz\n", + drain, start, dotclock_mhz); + return 0; + } + + return start - drain; +} + +/* The pipe's dot clock in MHz and its bits per pixel, or zero for both when + * the plane is not scanning anything out. */ +static void psb_wm_pipe(struct drm_device *dev, int pipe, u32 *mhz, u32 *bpp) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct drm_crtc *crtc = dev_priv->pipe_to_crtc_mapping[pipe]; + const struct psb_offset *map = &dev_priv->regmap[pipe]; + + *mhz = 0; + *bpp = 0; + + if (!crtc || !crtc->primary->fb) + return; + if (!(REG_READ(map->cntr) & DISPLAY_PLANE_ENABLE)) + return; + + *mhz = crtc->mode.clock / 1000; + *bpp = crtc->primary->fb->format->cpp[0] * 8; +} + +static void psb_update_wm(struct drm_device *dev, struct drm_crtc *unused) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + u32 mhz_a, bpp_a, mhz_b, bpp_b, b_start, c_start, wm_a1, wm_a2, val; + + if (!dev_priv->mem_freq) + return; + + psb_wm_pipe(dev, 0, &mhz_a, &bpp_a); + psb_wm_pipe(dev, 1, &mhz_b, &bpp_b); + + /* Neither plane is on, so there is nothing to feed */ + if (!bpp_a && !bpp_b) + return; + + if (bpp_a && bpp_b) { + u32 wm_b1, wm_b2; + + /* Each plane gets the share of the FIFO the arbiter gave it */ + val = REG_READ(DSPARB); + b_start = val & 0x7f; + c_start = (val >> 7) & 0x7f; + if (c_start <= b_start) + return; + + wm_a1 = psb_wm_level(dev, b_start, true, PSB_WM_DUAL_A_LATENCY, + mhz_a, bpp_a); + wm_a2 = psb_wm_level(dev, b_start, false, PSB_WM_DUAL_A_LATENCY, + mhz_a, bpp_a); + wm_b1 = psb_wm_level(dev, c_start - b_start, true, + PSB_WM_FIFO_B_LATENCY, mhz_b, bpp_b); + wm_b2 = psb_wm_level(dev, c_start - b_start, false, + PSB_WM_FIFO_B_LATENCY, mhz_b, bpp_b); + + val = REG_READ(FW_BLC1); + val &= ~((FW_BLC1_WM1_MASK << FW_BLC1_DISPA_WM1_SHIFT) | + (FW_BLC1_WM1_MASK << FW_BLC1_DISPB_WM1_SHIFT)); + val |= (wm_a1 & FW_BLC1_WM1_MASK) << FW_BLC1_DISPA_WM1_SHIFT; + val |= (wm_b1 & FW_BLC1_WM1_MASK) << FW_BLC1_DISPB_WM1_SHIFT; + REG_WRITE(FW_BLC1, val); + + /* One field for both planes, so the tighter of the two */ + val = REG_READ(FW_BLC_SELF); + val &= ~(FW_BLC_SELF_DISPAB_WM2_MASK << + FW_BLC_SELF_DISPAB_WM2_SHIFT); + val |= (min(wm_a2, wm_b2) & FW_BLC_SELF_DISPAB_WM2_MASK) << + FW_BLC_SELF_DISPAB_WM2_SHIFT; + REG_WRITE(FW_BLC_SELF, val); + } else { + /* One plane, so it has the whole FIFO */ + u32 mhz = bpp_a ? mhz_a : mhz_b; + u32 bpp = bpp_a ? bpp_a : bpp_b; + u32 latency = bpp_a ? PSB_WM_FIFO_A_LATENCY : + PSB_WM_FIFO_B_LATENCY; + + wm_a1 = psb_wm_level(dev, PSB_WM_MAX_FIFO_START, true, latency, + mhz, bpp); + wm_a2 = psb_wm_level(dev, PSB_WM_MAX_FIFO_START, false, latency, + mhz, bpp); + + val = REG_READ(FW_BLC3); + val &= ~((FW_BLC3_WM1_STATUS_MASK << FW_BLC3_WM1_STATUS0_SHIFT) | + (FW_BLC3_WM1_STATUS_MASK << FW_BLC3_WM1_STATUS1_SHIFT)); + val |= (wm_a2 & FW_BLC3_WM1_STATUS_MASK) << + FW_BLC3_WM1_STATUS0_SHIFT; + val |= (wm_a1 & FW_BLC3_WM1_STATUS_MASK) << + FW_BLC3_WM1_STATUS1_SHIFT; + REG_WRITE(FW_BLC3, val); + } + REG_READ(FW_BLC1); +} + +/* * Provide the Poulsbo specific chip logic and low level methods * for power management */ @@ -180,6 +335,10 @@ static int psb_restore_display_registers drm_connector_list_iter_end(&conn_iter); drm_modeset_unlock_all(dev); + + /* The restore above writes the pipe registers straight back rather than + * going through DPMS, so nothing recomputed the watermarks. */ + psb_update_wm(dev, NULL); return 0; } @@ -282,6 +441,8 @@ const struct psb_ops psb_chip_ops = { .backlight_set = psb_intel_lvds_set_brightness, .backlight_name = "psb-bl", + .update_wm = psb_update_wm, + .init_pm = psb_init_pm, .save_regs = psb_save_display_registers, .restore_regs = psb_restore_display_registers, diff -urNp a/drivers/gpu/drm/gma500/psb_drv.c b/drivers/gpu/drm/gma500/psb_drv.c --- a/drivers/gpu/drm/gma500/psb_drv.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_drv.c 2026-09-08 10:56:21.870775484 +0200 @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /************************************************************************** * Copyright (c) 2007-2011, Intel Corporation. + * Copyright (c) 2026, René Rebe * All Rights Reserved. * Copyright (c) 2008, Tungsten Graphics, Inc. Cedar Park, TX., USA. * All Rights Reserved. @@ -24,6 +25,7 @@ #include #include #include +#include #include #include @@ -31,6 +33,7 @@ #include "gem.h" #include "intel_bios.h" #include "mid_bios.h" +#include "msvdx.h" #include "power.h" #include "psb_drv.h" #include "psb_intel_reg.h" @@ -91,8 +94,8 @@ MODULE_DEVICE_TABLE(pci, pciidlist); /* * Standard IOCTLs. */ -static const struct drm_ioctl_desc psb_ioctls[] = { -}; +/* The render interface lives in psb_sgx_render.c; gma500 had no ioctls of its + * own, so the driver's table is that one. */ /** * psb_spank - reset the 2D engine @@ -167,6 +170,10 @@ static void psb_driver_unload(struct drm { struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + /* Before the SGX mapping goes: the 3D core is still using it. */ + psb_sgx_fini(dev); + msvdx_fini(dev); + /* TODO: Kill vblank etc here */ gma_backlight_exit(dev); @@ -408,6 +415,17 @@ static int psb_driver_load(struct drm_de return ret; psb_intel_opregion_enable_asle(dev); + /* The 3D core, on the window mapped above. A failure here is not fatal + * to the display: the render node simply will not work. */ + ret = psb_sgx_init(dev); + if (ret) + dev_warn(dev->dev, "the 3D core did not come up: %d\n", ret); + + /* And the video decoder, on its own window. Also not fatal. */ + ret = msvdx_init(dev); + if (ret) + dev_warn(dev->dev, "the video decoder did not come up: %d\n", ret); + return devm_add_action_or_reset(dev->dev, psb_device_release, dev); out_err: @@ -503,13 +521,16 @@ static const struct file_operations psb_ }; static const struct drm_driver driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_RENDER, + + .open = psb_sgx_open, + .postclose = psb_sgx_postclose, - .num_ioctls = ARRAY_SIZE(psb_ioctls), + .num_ioctls = PSB_SGX_NUM_IOCTLS, .dumb_create = psb_gem_dumb_create, PSB_FBDEV_DRIVER_OPS, - .ioctls = psb_ioctls, + .ioctls = psb_sgx_ioctls, .fops = &psb_gem_fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, diff -urNp a/drivers/gpu/drm/gma500/psb_drv.h b/drivers/gpu/drm/gma500/psb_drv.h --- a/drivers/gpu/drm/gma500/psb_drv.h 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_drv.h 2026-09-08 10:56:21.871775497 +0200 @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /************************************************************************** * Copyright (c) 2007-2011, Intel Corporation. + * Copyright (c) 2026, René Rebe * All Rights Reserved. * **************************************************************************/ @@ -14,6 +15,7 @@ #include #include "gtt.h" +#include "psb_sgx_render.h" #include "intel_bios.h" #include "mmu.h" #include "oaktrail.h" @@ -185,6 +187,7 @@ struct drm_fb_helper; struct drm_fb_helper_surface_size; +struct msvdx_device; struct opregion_header; struct opregion_acpi; @@ -407,6 +410,23 @@ struct drm_psb_private { /* Register base */ uint8_t __iomem *sgx_reg; + /* the 3D core's state; see psb_sgx_render.h */ + struct sgx_device sgx; + + /* Where the console framebuffer is, so the 3D core can render straight + * into it. It is stolen memory, so it is physically contiguous and one + * base address describes it. */ + struct { + u32 offset; /* into stolen memory */ + u32 pitch, width, height; + u32 size; + bool valid; + } scanout; + /* The video decoder's window and state; see msvdx.h. The macros below + * have named msvdx_reg since the driver was imported, but nothing had + * mapped it. */ + uint8_t __iomem *msvdx_reg; + struct msvdx_device *msvdx; uint8_t __iomem *vdc_reg; uint8_t __iomem *aux_reg; /* Auxillary vdc pipe regs */ uint16_t lpc_gpio_base; @@ -476,6 +496,7 @@ struct drm_psb_private { u32 mipi_ctrl_display; unsigned int core_freq; + unsigned int mem_freq; /* memory controller clock, MHz */ uint32_t iLVDS_enable; /* MID specific */ diff -urNp a/drivers/gpu/drm/gma500/psb_intel_display.c b/drivers/gpu/drm/gma500/psb_intel_display.c --- a/drivers/gpu/drm/gma500/psb_intel_display.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_intel_display.c 2026-09-08 10:56:21.872775511 +0200 @@ -151,7 +151,9 @@ static int psb_intel_crtc_mode_set(struc if (!ok) { DRM_ERROR("Couldn't find PLL settings for mode! target: %d, actual: %d", adjusted_mode->clock, clock.dot); - return 0; + /* Nothing below has run, so the pipe still carries the previous + * mode's timings. Reporting success let the helper commit it. */ + return -EINVAL; } fp = clock.n << 16 | clock.m1 << 8 | clock.m2; @@ -197,6 +199,32 @@ static int psb_intel_crtc_mode_set(struc /* setup pipeconf */ pipeconf = REG_READ(map->conf); + /* + * Pipe A takes two pixels per core clock above nine tenths of the + * display core speed; it cannot fetch one per clock beyond that, and + * beyond twice the core clock not even a doubled pipe can. Pipe B has + * no such mode. Same rule and threshold as the vendor's driver, at + * xserver-xorg-video-psb-0.32.1/src/psb_crtc.c:1072. The pipe runs at + * the unmultiplied clock, so this is mode->clock and not the + * SDVO-multiplied adjusted_mode->clock. Nothing has been written yet. + */ + if (pipe == 0 && dev_priv->core_freq) { + pipeconf &= ~PIPEACONF_DOUBLE_WIDE; + if (mode->clock > dev_priv->core_freq * 900) { + if (mode->clock > dev_priv->core_freq * 2000) { + DRM_ERROR("%d kHz is past twice the %u MHz core clock\n", + mode->clock, dev_priv->core_freq); + return -EINVAL; + } + if (mode->hdisplay & 1) { + DRM_ERROR("odd width %d cannot go on a double wide pipe\n", + mode->hdisplay); + return -EINVAL; + } + pipeconf |= PIPEACONF_DOUBLE_WIDE; + } + } + /* Set up the display plane register */ dspcntr = DISPPLANE_GAMMA_ENABLE; diff -urNp a/drivers/gpu/drm/gma500/psb_intel_lvds.c b/drivers/gpu/drm/gma500/psb_intel_lvds.c --- a/drivers/gpu/drm/gma500/psb_intel_lvds.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_intel_lvds.c 2026-09-08 10:56:21.873775525 +0200 @@ -9,6 +9,7 @@ */ #include +#include #include #include @@ -217,12 +218,31 @@ static void psb_intel_lvds_set_power(str return; } + /* Nothing to do if the panel is already in the state being asked for. + * Rendering has no business sequencing the panel at all, and a modeset + * that changes only the scanout address reaches here through the + * encoder's prepare and commit - which powered the panel down and back + * up around every one of them. A perf profile of glmark2 put 19% of + * the client's CPU in ioread32 inside these two loops, on a part whose + * vertex transform runs on that same CPU. + * + * The waits below are sleeping and bounded for the same reason, and + * because neither had a timeout at all: a panel that never reported + * the state hung the modeset in the kernel. */ + if (!!(REG_READ(PP_STATUS) & PP_ON) == on) { + if (on) + psb_intel_lvds_set_backlight(dev, + mode_dev->backlight_duty_cycle); + gma_power_end(dev); + return; + } if (on) { REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) | POWER_TARGET_ON); - do { - pp_status = REG_READ(PP_STATUS); - } while ((pp_status & PP_ON) == 0); + if (readl_poll_timeout(dev_priv->vdc_reg + PP_STATUS, + pp_status, pp_status & PP_ON, + 200, 500000)) + dev_err(dev->dev, "panel did not report power on\n"); psb_intel_lvds_set_backlight(dev, mode_dev->backlight_duty_cycle); @@ -231,9 +251,10 @@ static void psb_intel_lvds_set_power(str REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & ~POWER_TARGET_ON); - do { - pp_status = REG_READ(PP_STATUS); - } while (pp_status & PP_ON); + if (readl_poll_timeout(dev_priv->vdc_reg + PP_STATUS, + pp_status, !(pp_status & PP_ON), + 200, 500000)) + dev_err(dev->dev, "panel did not report power off\n"); } gma_power_end(dev); diff -urNp a/drivers/gpu/drm/gma500/psb_intel_reg.h b/drivers/gpu/drm/gma500/psb_intel_reg.h --- a/drivers/gpu/drm/gma500/psb_intel_reg.h 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_intel_reg.h 2026-09-08 10:56:21.875775552 +0200 @@ -563,8 +563,20 @@ #define PIPE_PIXEL_MASK 0x00ffffff #define PIPE_PIXEL_SHIFT 0 +/* Poulsbo's display FIFO watermarks live here, not in the DSPFW block below. + * Layouts from xserver-xorg-video-psb-0.32.1/src/psb_driver.h:1097-1148. */ +#define FW_BLC1 0x20d8 +#define FW_BLC1_DISPA_WM1_SHIFT 0 +#define FW_BLC1_DISPB_WM1_SHIFT 16 +#define FW_BLC1_WM1_MASK 0xff #define FW_BLC_SELF 0x20e0 +#define FW_BLC_SELF_DISPAB_WM2_SHIFT 0 +#define FW_BLC_SELF_DISPAB_WM2_MASK 0xff #define FW_BLC_SELF_EN (1<<15) +#define FW_BLC3 0x20ec +#define FW_BLC3_WM1_STATUS1_SHIFT 0 +#define FW_BLC3_WM1_STATUS0_SHIFT 8 +#define FW_BLC3_WM1_STATUS_MASK 0xff #define DSPARB 0x70030 #define DSPFW1 0x70034 diff -urNp a/drivers/gpu/drm/gma500/psb_intel_sdvo.c b/drivers/gpu/drm/gma500/psb_intel_sdvo.c --- a/drivers/gpu/drm/gma500/psb_intel_sdvo.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_intel_sdvo.c 2026-09-08 10:56:21.876775566 +0200 @@ -27,6 +27,7 @@ */ #include +#include #include #include #include @@ -403,6 +404,10 @@ static const struct _sdvo_cmd_name { #define IS_SDVOB(reg) (reg == SDVOB) #define SDVO_NAME(svdo) (IS_SDVOB((svdo)->sdvo_reg) ? "SDVOB" : "SDVOC") +/* What the SDVO port itself can carry, in kHz, after the pixel multiplier */ +#define SDVO_PORT_CLOCK_MIN 100000 +#define SDVO_PORT_CLOCK_MAX 200000 + static void psb_intel_sdvo_debug_write(struct psb_intel_sdvo *psb_intel_sdvo, u8 cmd, const void *args, int args_len) { @@ -567,7 +572,7 @@ log_fail: return false; } -static int psb_intel_sdvo_get_pixel_multiplier(struct drm_display_mode *mode) +static int psb_intel_sdvo_get_pixel_multiplier(const struct drm_display_mode *mode) { if (mode->clock >= 100000) return 1; @@ -884,11 +889,114 @@ static void psb_intel_sdvo_dump_hdmi_buf } #endif -static bool psb_intel_sdvo_set_avi_infoframe(struct psb_intel_sdvo *psb_intel_sdvo) +/* The HDMI connector this encoder drives, or NULL when the sink is DVI. */ +static struct drm_connector * +psb_intel_sdvo_hdmi_connector(struct psb_intel_sdvo *psb_intel_sdvo) { - DRM_INFO("HDMI is not supported yet"); + struct drm_device *dev = psb_intel_sdvo->base.base.dev; + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector, *found = NULL; - return false; + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->encoder != &psb_intel_sdvo->base.base) + continue; + if (connector->connector_type != DRM_MODE_CONNECTOR_HDMIA) + continue; + found = connector; + break; + } + drm_connector_list_iter_end(&conn_iter); + + return found; +} + +/* The buffer size the device reports is one based, and zero means zero. */ +static bool psb_intel_sdvo_get_hbuf_size(struct psb_intel_sdvo *psb_intel_sdvo, + u8 *hbuf_size) +{ + if (!psb_intel_sdvo_get_value(psb_intel_sdvo, SDVO_CMD_GET_HBUF_INFO, + hbuf_size, 1)) + return false; + + if (*hbuf_size) + (*hbuf_size)++; + + return true; +} + +static bool psb_intel_sdvo_write_infoframe(struct psb_intel_sdvo *psb_intel_sdvo, + unsigned int if_index, u8 tx_rate, + const u8 *data, unsigned int length) +{ + u8 set_buf_index[2] = { if_index, 0 }; + u8 hbuf_size, tmp[8]; + unsigned int i; + + if (!psb_intel_sdvo_set_value(psb_intel_sdvo, SDVO_CMD_SET_HBUF_INDEX, + set_buf_index, 2)) + return false; + + if (!psb_intel_sdvo_get_hbuf_size(psb_intel_sdvo, &hbuf_size)) + return false; + + if (hbuf_size < length) + return false; + + /* The whole buffer, so no tail of a previous frame is left in it */ + for (i = 0; i < hbuf_size; i += 8) { + memset(tmp, 0, sizeof tmp); + if (i < length) + memcpy(tmp, data + i, min_t(unsigned int, 8, length - i)); + + if (!psb_intel_sdvo_set_value(psb_intel_sdvo, + SDVO_CMD_SET_HBUF_DATA, tmp, 8)) + return false; + } + + return psb_intel_sdvo_set_value(psb_intel_sdvo, SDVO_CMD_SET_HBUF_TXRATE, + &tx_rate, 1); +} + +/* An HDMI sink is told the format, aspect ratio and quantisation range of what + * it is being sent; without an AVI infoframe it has to guess, and a television + * guessing wrong is what puts a PC desktop out at limited range or stretched. + * The colorimetry we set alongside this is SDVO_COLORIMETRY_RGB256, so the + * frame has to say full range to match. */ +static bool psb_intel_sdvo_set_avi_infoframe(struct psb_intel_sdvo *psb_intel_sdvo, + const struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = psb_intel_sdvo->base.base.dev; + u8 sdvo_data[HDMI_INFOFRAME_SIZE(AVI)]; + struct hdmi_avi_infoframe frame; + struct drm_connector *connector; + ssize_t len; + int ret; + + connector = psb_intel_sdvo_hdmi_connector(psb_intel_sdvo); + if (!connector) + return false; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, + adjusted_mode); + if (ret) { + drm_dbg_kms(dev, "no AVI infoframe for this mode: %d\n", ret); + return false; + } + + drm_hdmi_avi_infoframe_quant_range(&frame, connector, adjusted_mode, + HDMI_QUANTIZATION_RANGE_FULL); + + len = hdmi_avi_infoframe_pack(&frame, sdvo_data, sizeof sdvo_data); + if (len < 0) { + drm_dbg_kms(dev, "AVI infoframe would not pack: %zd\n", len); + return false; + } + + return psb_intel_sdvo_write_infoframe(psb_intel_sdvo, + SDVO_HBUF_INDEX_AVI_IF, + SDVO_HBUF_TX_VSYNC, + sdvo_data, len); } static bool psb_intel_sdvo_set_tv_format(struct psb_intel_sdvo *psb_intel_sdvo) @@ -1044,7 +1152,10 @@ static void psb_intel_sdvo_mode_set(stru psb_intel_sdvo_set_encode(psb_intel_sdvo, SDVO_ENCODE_HDMI); psb_intel_sdvo_set_colorimetry(psb_intel_sdvo, SDVO_COLORIMETRY_RGB256); - psb_intel_sdvo_set_avi_infoframe(psb_intel_sdvo); + if (!psb_intel_sdvo_set_avi_infoframe(psb_intel_sdvo, + adjusted_mode)) + drm_warn(dev, "%s: the sink gets no AVI infoframe\n", + SDVO_NAME(psb_intel_sdvo)); } else psb_intel_sdvo_set_encode(psb_intel_sdvo, SDVO_ENCODE_DVI); @@ -1163,6 +1274,7 @@ static enum drm_mode_status psb_intel_sd const struct drm_display_mode *mode) { struct psb_intel_sdvo *psb_intel_sdvo = intel_attached_sdvo(connector); + int port_clock; if (mode->flags & DRM_MODE_FLAG_DBLSCAN) return MODE_NO_DBLESCAN; @@ -1173,6 +1285,19 @@ static enum drm_mode_status psb_intel_sd if (psb_intel_sdvo->pixel_clock_max < mode->clock) return MODE_CLOCK_HIGH; + /* The port runs at the multiplied clock and carries 100-200 MHz: below + * the window there is no larger multiplier left to reach it with, above + * it there is nothing to divide by. The vendor's driver refuses the + * same window - xserver-xorg-video-psb-0.32.1/src/psb_crtc.c:735-747 + * chooses the multiplier inside it and src/psb_sdvo.c:3933-3938 fails + * the clock rate outside it. Ours picked 1x for anything at or above + * 100 MHz and programmed the DPLL for it whatever the device claimed. */ + port_clock = mode->clock * psb_intel_sdvo_get_pixel_multiplier(mode); + if (port_clock < SDVO_PORT_CLOCK_MIN) + return MODE_CLOCK_LOW; + if (port_clock > SDVO_PORT_CLOCK_MAX) + return MODE_CLOCK_HIGH; + if (psb_intel_sdvo->is_lvds) { if (mode->hdisplay > psb_intel_sdvo->sdvo_lvds_fixed_mode->hdisplay) return MODE_PANEL; @@ -1991,7 +2116,13 @@ psb_intel_sdvo_dvi_init(struct psb_intel intel_connector = &psb_intel_sdvo_connector->base; connector = &intel_connector->base; - // connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; + /* Poulsbo raises no hot-plug interrupt for this port - psb_chip_ops has + * no ->hotplug, so gma_irq_preinstall() leaves _PSB_IRQ_DISP_HOTSYNC + * masked. Without this the poll helper skips the connector too and a + * plug is never noticed at all. detect() asks the SDVO device over the + * control bus, which is what the vendor driver polls as well. */ + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; encoder->encoder_type = DRM_MODE_ENCODER_TMDS; connector->connector_type = DRM_MODE_CONNECTOR_DVID; diff -urNp a/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h b/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h --- a/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_intel_sdvo_regs.h 2026-09-08 10:56:21.877775579 +0200 @@ -703,6 +703,8 @@ struct psb_intel_sdvo_enhancements_arg { #define SDVO_CMD_SET_AUDIO_STAT 0x91 #define SDVO_CMD_GET_AUDIO_STAT 0x92 #define SDVO_CMD_SET_HBUF_INDEX 0x93 + #define SDVO_HBUF_INDEX_ELD 0 + #define SDVO_HBUF_INDEX_AVI_IF 1 #define SDVO_CMD_GET_HBUF_INDEX 0x94 #define SDVO_CMD_GET_HBUF_INFO 0x95 #define SDVO_CMD_SET_HBUF_AV_SPLIT 0x96 diff -urNp a/drivers/gpu/drm/gma500/psb_irq.c b/drivers/gpu/drm/gma500/psb_irq.c --- a/drivers/gpu/drm/gma500/psb_irq.c 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_irq.c 2026-09-08 10:56:21.877775579 +0200 @@ -6,6 +6,8 @@ * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to * develop this driver. * + * Copyright (C) 2026 René Rebe + * **************************************************************************/ #include @@ -151,7 +153,7 @@ static void gma_vdc_interrupt(struct drm static void gma_sgx_interrupt(struct drm_device *dev, u32 stat_1, u32 stat_2) { struct drm_psb_private *dev_priv = to_drm_psb_private(dev); - u32 val, addr; + u32 val, addr, ack_1; if (stat_1 & _PSB_CE_TWOD_COMPLETE) val = PSB_RSGX32(PSB_CR_2D_BLIT_STATUS); @@ -189,10 +191,53 @@ static void gma_sgx_interrupt(struct drm } } - /* Clear bits */ - PSB_WSGX32(stat_1, PSB_CR_EVENT_HOST_CLEAR); - PSB_WSGX32(stat_2, PSB_CR_EVENT_HOST_CLEAR2); + /* Clear only what this handler is enabled for. Writing back every bit + * read let a 2D blit acknowledge PIXELBE_END_RENDER out from under the + * render wait, which polls for it. + * + * Record what is about to be cleared before clearing it. The clear is + * exactly what makes the bit invisible to a poll, so every bit this + * handler takes out of EVENT_STATUS has to be handed to the waiter + * some other way - which is what turns the hazard above into the + * mechanism the sleeping render wait runs on. Recording the whole + * cleared set rather than one chosen event keeps the invariant true + * however the enable mask is changed, including while it is changing. */ + ack_1 = stat_1 & PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); + if (ack_1) { + spin_lock(&dev_priv->sgx.event_lock); + dev_priv->sgx.event_pending |= ack_1; + spin_unlock(&dev_priv->sgx.event_lock); + } + + PSB_WSGX32(ack_1, PSB_CR_EVENT_HOST_CLEAR); + PSB_WSGX32(stat_2 & PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE2), + PSB_CR_EVENT_HOST_CLEAR2); PSB_RSGX32(PSB_CR_EVENT_HOST_CLEAR2); + + /* After the acknowledge, so a waiter that runs immediately reads a + * status word the hardware has already been told about. */ + if (ack_1) + wake_up_all(&dev_priv->sgx.event_wq); +} + +/* Which SGX events raise the host interrupt line. EVENT_HOST_ENABLE masks the + * line, not the status register, so an event left out of it is still polled + * for perfectly well - and one put into it can no longer be polled for, + * because this handler acknowledges it first. + * + * sgx_cold_init() clears both enable registers as part of bringing the core + * up, which runs after gma_irq_postinstall() and again after every recovery. + * Whatever wants an interrupt has to be re-armed there; this is what does it. + */ +void gma_sgx_irq_enable(struct drm_device *dev, u32 events) +{ + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + unsigned long irqflags; + + spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); + PSB_WSGX32(events, PSB_CR_EVENT_HOST_ENABLE); + PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); /* Post */ + spin_unlock_irqrestore(&dev_priv->irqmask_lock, irqflags); } static irqreturn_t gma_irq_handler(int irq, void *arg) @@ -283,9 +328,12 @@ void gma_irq_postinstall(struct drm_devi spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); - /* Enable 2D and MMU fault interrupts */ + /* Enable 2D and MMU fault interrupts, plus whatever the 3D core has + * asked for - it is armed from sgx_cold_init(), which runs later, but + * carrying it here keeps the two from depending on that order. */ PSB_WSGX32(_PSB_CE2_BIF_REQUESTER_FAULT, PSB_CR_EVENT_HOST_ENABLE2); - PSB_WSGX32(_PSB_CE_TWOD_COMPLETE, PSB_CR_EVENT_HOST_ENABLE); + PSB_WSGX32(_PSB_CE_TWOD_COMPLETE | dev_priv->sgx.event_irq_mask, + PSB_CR_EVENT_HOST_ENABLE); PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); /* Post */ /* This register is safe even if display island is off */ diff -urNp a/drivers/gpu/drm/gma500/psb_irq.h b/drivers/gpu/drm/gma500/psb_irq.h --- a/drivers/gpu/drm/gma500/psb_irq.h 2026-08-16 23:32:26.000000000 +0200 +++ b/drivers/gpu/drm/gma500/psb_irq.h 2026-09-08 10:56:21.878775593 +0200 @@ -7,6 +7,8 @@ * Benjamin Defnet * Rajesh Poornachandran * + * Copyright (C) 2026 René Rebe + * **************************************************************************/ #ifndef _PSB_IRQ_H_ @@ -18,6 +20,7 @@ struct drm_device; void gma_irq_preinstall(struct drm_device *dev); void gma_irq_postinstall(struct drm_device *dev); int gma_irq_install(struct drm_device *dev); +void gma_sgx_irq_enable(struct drm_device *dev, u32 events); void gma_irq_uninstall(struct drm_device *dev); int gma_crtc_enable_vblank(struct drm_crtc *crtc); diff -urNp a/drivers/gpu/drm/gma500/psb_sgx_render.c b/drivers/gpu/drm/gma500/psb_sgx_render.c --- a/drivers/gpu/drm/gma500/psb_sgx_render.c 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/psb_sgx_render.c 2026-09-08 10:56:21.880775620 +0200 @@ -0,0 +1,4733 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PowerVR SGX535 render node for Poulsbo. + * + * This is the kernel half of the interface in driver/uapi/sgx_drm.h. It is not + * a working driver: nothing here touches the SGX yet. What it does implement is + * the parts that have to be right before the hardware logic is worth moving in + * - object lifetime, the address space, and the validation the old psb driver + * never had - so that the UAPI can be exercised and argued with rather than + * frozen on paper. + * + * Deliberately different from psb in three ways, each for a reason measured in + * this project rather than assumed: + * + * - Userspace assigns GPU addresses through VM_BIND and the kernel validates + * them. psb applied 77 TA and 16 raster relocations per frame. + * - Every register offset in a submitted stream is checked against a + * whitelist. psb submitted whatever userspace wrote. + * - The scene and the out-of-memory recovery stay in the kernel, so there is + * no oom_cmds and no scene cookie in the UAPI. That recovery drops geometry + * silently when it is wrong, which is not a failure mode to delegate. + * + * Copyright (C) 2026 René Rebe + */ +#include +#include +#include +#include +#include +#include + +#include +#include "psb_drv.h" +#include "psb_irq.h" +#include +#include +#include +#include +#include +#include + +#include "sgx_drm.h" +#include "msvdx.h" +#include "msvdx_drm.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include "sgx_check.h" +#include "sgx_mmu.h" +#include "sgx_kick.h" +#include "sgx_scene.h" +#include "sgx_cookie.h" +#include "sgx_fire.h" +#include "sgx_heap.h" +#include "sgx_init.h" +#include "sgx_use.h" +#include "sgx_twod.h" +#include "gem.h" + +/* Where the driver's own structures live inside the scene window. These are + * the addresses tools/baremetal/sgxtri.c uses and renders with. */ +/* PDS_EXEC_BASE is the start of the PDS window's 256 MiB region, not the + * window start: sgxtri programs PSB_MEM_PDS_START, one page below the + * first binding at 0x20010000. */ +#define SGX_PDS_EXEC_VA 0x20000000u +#define SGX_SCENE_VA 0x40800000ull +/* Scenes in a file's pool sit a slot apart. A slot has to hold the largest + * scene the cookie can describe, which is a megabyte at 4096x4096. */ +#define SGX_SCENE_SLOT_SPAN 0x00400000ull +#define SGX_SCENES_PER_FILE 2 +#define SGX_DPM_PT_VA 0x41000000ull +/* The out-of-memory resume programs a page table of its own, separate from + * both the scene and the DPM's. sgxtri puts it at the same address + * (GPU_OOM_PT, sgxtri.c:2690) and gives it one page; the DPM writes 17 + * eight-byte region records into it. */ +/* The parameter heap, owned by the device. Sixty-four megabytes mapped, of + * which the DPM is told a quarter - the same ratio the heap was allocated at + * when userspace still owned it, which leaves the tiler headroom to overrun + * into. The start must not move: the DPM indexes pages from the base of the + * 256 MiB region. */ +#define SGX_PARAM_VA 0x31000000ull +#define SGX_PARAM_BYTES (64ull << 20) + +#define SGX_OOM_PT_VA 0x40700000ull +#define SGX_OOM_PT_BYTES 0x88u + +/* Where the 2D block's requests are based. + * + * A megabyte below the surface window, which is where a client's bindings + * start, so offset 0 of the 2D address space is a page no client can name and + * the driver owns. It used to be the surface window's own start - the address + * the Gallium context binds every render target at - and that was wrong in + * both directions: the engine touches offset 0 of its window, so the driver + * had to map something there, and the page it mapped aliased whatever the + * client had at 0x80000000. A render aimed at a target that was not bound + * then landed in the driver's page and was lost without a fault. + * + * BIF_TWOD_REQ_BASE takes an address in 1 MiB units (sgx535defs.h:3005) and + * is the 2D requestor's alone - the 3D data masters offset through + * BIF_3D_REQ_BASE and PDS_EXEC_BASE - so moving it cannot reach the 3D path. + * The 28-bit offset field still covers 0x80000000 to 0x8ff00000, which is + * every address the winsys allocates surfaces at. */ +#define SGX_TWOD_BASE_VA 0x7ff00000ull + +/* The DPM's page table is indexed by the heap page's index inside its 256 MiB + * region, so it has to reach the last page the heap occupies - and not extend + * past it. sgxtri sizes it exactly that way (frame_ta_mem_load): first index + * plus page count, times the entry width. A table larger than the heap gives + * the DPM entries for pages that do not exist, and the tiler then faults on a + * page index of 0xffff. */ +/* Sizing it any larger is not harmless: the table's extent is what bounds the + * DPM, so a table with room for pages the heap does not have gets them + * allocated, and the tiler then writes past the end of the heap with nothing + * mapped there and no out-of-memory event - the DPM believed it had the room. + * sgxtri's frame_ta_mem_load() sizes it the same way. */ +#define SGX_DPM_PT_ENTRY_BYTES 32u +#define SGX_DPM_PT_REGION_MASK 0x0fffffffull + +static u64 sgx_dpm_pt_bytes(u64 param_va, u32 mapped_pages) +{ + u64 first = (param_va & SGX_DPM_PT_REGION_MASK) >> 12; + + return PAGE_ALIGN((first + mapped_pages) * SGX_DPM_PT_ENTRY_BYTES); +} + +/* What the DPM is told it has, which is not the whole bound heap. + * + * sgxtri maps four times its estimate and hands the DPM the estimate alone; + * the rest is headroom the tiler can overrun into and the out-of-memory path + * can grow into. Mapping exactly what the DPM is told means any overrun lands + * on nothing mapped, and the tiler faults with no out-of-memory event, because + * from the DPM's side there was room. Same constants as + * gallium/sgx_context.h. */ +#define SGX_DPM_RESERVE_PAGES 0x600u +/* Userspace maps this many times what the DPM is told, so the tiler has room + * to overrun; the same ratio bounds what the DPM may be given here. Kept in + * step with gallium/sgx_context.h. */ +#define SGX_DPM_CAP_MULTIPLIER 4u +#define SGX_DPM_MIN_WORK_PAGES 0x41u +#define SGX_DPM_BYTES_PER_MTILE 4096u +#define SGX_DPM_BYTES_PER_PRIM 256u +#define SGX_DPM_PRIM_SPREAD 4u +#define SGX_DPM_MAX_PRIMS 4096u + +/* Time the parts of a submit and report the average every 64 frames. */ +/* Reset the ISP before every render. A ZLS format change needs one; doing it + * per frame is what this measures. */ +/* Map the scanout cached for the GPU, as it always was. Off-screen buffers are + * mapped uncached, and rendering into them is an order of magnitude quicker. */ +static bool sgx_scanout_cached = true; +module_param_named(sgx_scanout_cached, sgx_scanout_cached, bool, 0644); + +/* Passes a register poll spins before it sleeps. Reads of these registers are + * uncached and a render is tens of milliseconds, so a long wait spends the + * whole of it on the bus; the default is held high because the DPM's heap load + * is a handshake that fails if it sleeps. Tunable so a render-only workload + * can be measured against it without a rebuild. */ +static unsigned int sgx_poll_spin = SGX_POLL_SPIN_MAX; +module_param_named(sgx_poll_spin, sgx_poll_spin, uint, 0644); +MODULE_PARM_DESC(sgx_poll_spin, + "passes a register poll spins before it sleeps"); + +/* The completion waits get their own budget. A render is milliseconds long, so + * spinning a handshake's worth of uncached reads out on the bus only takes the + * core away from the client that is drawing the next frame. */ +static unsigned int sgx_evt_spin = SGX_EVENT_SPIN_MAX; +module_param_named(sgx_evt_spin, sgx_evt_spin, uint, 0644); +MODULE_PARM_DESC(sgx_evt_spin, + "passes a completion wait spins before it sleeps"); + +/* How much graphics translation table a render node's clients may hold in + * SGX_BO_SCANOUT objects between them. The GTT is a few megabytes the display + * engine has to share, psb_gem_create() takes an object's share of it for the + * object's whole life, and an unprivileged client can ask for one per call - + * so without a bound a client can starve the display of a framebuffer. */ +static unsigned int sgx_scanout_gtt_mb = 32; +module_param_named(sgx_scanout_gtt_mb, sgx_scanout_gtt_mb, uint, 0644); +MODULE_PARM_DESC(sgx_scanout_gtt_mb, + "GTT megabytes all render clients together may hold in scanout objects (0 refuses them)"); + +static bool sgx_isp_reset = true; +module_param_named(sgx_isp_reset, sgx_isp_reset, bool, 0644); + +/* Make an ordinary pass reuse the depth its predecessor stored instead of + * starting every tile at the background depth. A bitmask so each half can be + * measured on its own: 1 arms the DPM's Z load/store the way a recovery does, + * 2 logs the region headers the tiler wrote, 4 sets + * EURASIA_REGIONHEADER0_ZLOADENABLE in them - the hardware's only per-region + * depth load switch, and one the command stream cannot reach because the + * tiler writes those headers itself. */ +static unsigned int sgx_zls; +module_param_named(sgx_zls, sgx_zls, uint, 0644); +MODULE_PARM_DESC(sgx_zls, + "1 DPM Z load/store on ordinary submits, 2 log region headers, 4 set their Z load bit"); + +#define SGX_ZLS_DPM 1u +#define SGX_ZLS_LOG_RGN 2u +#define SGX_ZLS_MARK_RGN 4u +#define SGX_ZLS_BASE_TILED 8u +#define SGX_ZLS_BASE_LINEAR 16u + +/* GPU address of the depth surface, for the two region-header ZLS base + * layouts above. Only a probe: the address belongs in the submit, not in a + * parameter. */ +static unsigned int sgx_zls_base; +module_param_named(sgx_zls_base, sgx_zls_base, uint, 0644); + +/* Mask-plane accumulation across partial renders - the vendor's SPM. A partial + * render stores the ISP's mask plane beside the depth and stencil it already + * stores, and every later partial render of the same macro tile, and the render + * that finishes the scene, load it back; without it a tile the previous partial + * render did not cover is indistinguishable from one it did. + * + * Off by default and refusable, because getting a partial render wrong locks + * the part hard enough to need a power cycle. A bitmask so the store and the + * load can be measured apart, which is the lesson of the two-level ZLS enable: + * 1 stores the plane, 2 loads it, 4 makes the partial render process empty + * regions the way the vendor's does. Nothing here invents a store or a load - + * see sgx_spm_store()/sgx_spm_load() in sgx_scene.h - so a client whose ZLSCTL + * asks for neither is unaffected whatever this is set to. */ +static unsigned int sgx_spm; +module_param_named(sgx_spm, sgx_spm, uint, 0644); +MODULE_PARM_DESC(sgx_spm, + "partial-render mask plane: 1 store it, 2 load it back, 4 process empty regions (default 0, off)"); + +/* Region headers are twelve bytes: dword 0 carries the position, the macro + * tile and the load/store bits, dword 1 the control base, dword 2 the ZLS + * base. sgx_scene_info() puts the array at cookie[7] with one entry per + * parameter tile - four or sixteen per macro tile, by cookie[0]. */ +#define SGX_RH_DWORDS 3u +#define SGX_RH_ZLOADENABLE 0x20000000u +#define SGX_RH_ZSTOREENABLE 0x40000000u + +static bool sgx_timing; +module_param_named(sgx_timing, sgx_timing, bool, 0644); + +static bool sgx_regdump; +module_param_named(sgx_regdump, sgx_regdump, bool, 0644); + +/* Only dump for a submit of this width, so the frame under test is caught + * rather than whichever one the server happened to fire next. */ +static unsigned int sgx_regdump_w; +module_param_named(sgx_regdump_w, sgx_regdump_w, uint, 0644); + +static bool sgx_bif_flush = true; +module_param_named(sgx_bif_flush, sgx_bif_flush, bool, 0644); + +/* Give each client its own parameter heap and its own DPM page table, at the + * addresses every client sees, instead of one heap shared by all of them. + * + * Off by default because the switch has never run on hardware: it re-issues + * the DPM's register load without the INIT handshake, and a DPM that does not + * take it fires a tiler at a free list that describes another client's pages, + * which locks the core hard enough to need a power cycle. On, the isolation + * argument in work/audit-kernel-fixes/README.md holds; off, it does not. */ +static bool sgx_heap_per_client; +/* Read-only after load. A client that bound a shared heap and one that bound + * its own cannot both be right about who owns the DPM, so the mode cannot + * change while clients exist; each file latches it at its first bind anyway. */ +module_param_named(sgx_heap_per_client, sgx_heap_per_client, bool, 0444); +MODULE_PARM_DESC(sgx_heap_per_client, + "one parameter heap per client rather than one shared by all of them"); + +/* Pause the BIF and wait out its outstanding reads around an invalidate, the + * way the vendor's microkernel does on a running core. */ +static bool sgx_bif_pause = true; +module_param_named(sgx_bif_pause, sgx_bif_pause, bool, 0644); + +/* Microseconds. The vendor polls to MAX_HW_TIME_US; a BIF that has not + * retired its reads in a millisecond is not going to. */ +#define SGX_BIF_DRAIN_TRIES 1000u + +static bool sgx_pte_watch; +module_param_named(sgx_pte_watch, sgx_pte_watch, bool, 0644); + +static bool sgx_dpm_watch; +module_param_named(sgx_dpm_watch, sgx_dpm_watch, bool, 0644); +MODULE_PARM_DESC(sgx_dpm_watch, + "log the DPM's counters and what the frame before left in the scene"); + +static unsigned int sgx_dpm_prims = SGX_DPM_MAX_PRIMS; +module_param_named(sgx_dpm_prims, sgx_dpm_prims, uint, 0644); +MODULE_PARM_DESC(dpm_prims, + "primitives the DPM heap estimate is sized for (0 uses the bound heap)"); + +static u32 sgx_dpm_pages(u32 mtiles, u32 bound_pages) +{ + u64 work = (u64)mtiles * SGX_DPM_BYTES_PER_MTILE + + (u64)sgx_dpm_prims * SGX_DPM_BYTES_PER_PRIM * + SGX_DPM_PRIM_SPREAD; + u32 pages = (u32)((work + 4095u) >> 12); + + if (!sgx_dpm_prims) + return bound_pages; + + if (pages < SGX_DPM_MIN_WORK_PAGES) + pages = SGX_DPM_MIN_WORK_PAGES; + pages += SGX_DPM_RESERVE_PAGES; + return pages; +} + +#undef DRIVER_NAME +#undef DRIVER_DESC +#define DRIVER_NAME "sgx" +#define DRIVER_DESC "PowerVR SGX535 render node" + +/* The four address windows the SGX reaches through different requestors, each + * with its own base register. They are not one flat space, so a binding says + * which it is for and the kernel keeps them apart. Values follow the layout + * tools/baremetal/sgxtri.c drives the hardware with. */ +static const struct { + u64 start, size; + const char *name; +} sgx_windows[] = { + /* Moved up to make room below it. Nothing programs this window's start + * into a base register - the parameter heap's address is handed to the + * DPM explicitly - so it is free to sit anywhere the page tables cover. */ + /* The size is not a round number and must not be trimmed to one. The + * DPM indexes the heap by page index inside the 256 MiB region and + * wraps at 0xffff, reaching that page whether the heap owns it or not. + * A base of 0x31000000 puts the first page at index 0x1000, so the + * window has to run all the way to 0x40000000 for index 0xffff to have + * RAM behind it. At 0x0ee00000 it stopped at index 0xfdff and every + * frame that reached the wrap faulted at 0x3ffff000 with no + * out-of-memory event, because from the DPM's side there was room. + * 0x40000000 is where the scene window starts, so nothing is lost. */ + [SGX_VM_PARAM] = { 0x31000000ull, 0x0ee00000ull, "parameter heap" }, + [SGX_VM_PDS] = { 0x20010000ull, 0x00ff0000ull, "heap, USSE, PDS" }, + /* The fixed context objects - target, depth and the two texture units + * - sit at the bottom of this window and reserve eighty megabytes of + * it between them, so only the top forty-eight were left for + * everything a client allocates. Quake 3 ran out of textures there. + * Nothing sits above this window, and the addresses are only ever + * reached through BIF_TWOD_REQ_BASE, so it can simply be longer. */ + [SGX_VM_SURFACE] = { 0x80000000ull, 0x20000000ull, "render targets" }, + /* Sixteen megabytes, which is what the 24-bit offset fields the + * relocations write can reach from BIF_3D_REQ_BASE. It was one, and a + * megabyte is also the size of a single display-list buffer, so a GL + * client's first allocation was refused and it could not draw. The + * start must not move: the hardware subtracts the base register from + * those offsets. */ + [SGX_VM_RASTGEOM] = { 0x30000000ull, 0x01000000ull, "raster geometry" }, + /* The scene is not in the parameter window. sgxtri puts the scene at + * 0x40800000, the DPM's page table at 0x41000000 and the out-of-memory + * resume's private table at 0x40700000, all separate from the + * parameter heap at 0x30100000 - and a submit needs both. Four windows + * could not express that. */ + [SGX_VM_SCENE] = { 0x40000000ull, 0x02000000ull, "scene and DPM tables" }, +}; + +/* The widest window, which is the largest object that could ever be bound. */ +static u64 sgx_vm_max_object_bytes(void) +{ + u64 max = 0; + unsigned int w; + + for (w = 0; w < ARRAY_SIZE(sgx_windows); w++) + if (sgx_windows[w].size > max) + max = sgx_windows[w].size; + return max; +} + +/* The address space is per *file*, not per device. + * + * A render node is meant to be opened by several unprivileged clients at once. + * One address space shared between them means one client's bind can collide + * with another's, and one client can name an address another client bound - + * which is the whole isolation property a render node is supposed to have. + * This was device-wide until it was noticed; nothing in the host tests could + * see it, because they open one file. + */ +/* One scene of a file's pool. The cookie persists across frames because the + * fire path writes back into dwords 13 to 15 - the allocation context and + * whether headroom came back - and rebuilding it every submit threw that away, + * so the DPM treated each frame as a fresh scene and never reused a page. + * + * The scene and the DPM's page table are the driver's, not the caller's: the + * cookie is built here from the render target size, so the memory it describes + * belongs here too, and userspace naming them would let it name two objects + * the kernel has to trust. */ +struct sgx_scene { + u32 cookie[SGX_C_COOKIE_DWORDS]; + u32 w, h; + u32 samples; /* along one axis, 1 or 2 */ + u32 need, clear_start, clear_pages; + void *cpu; /* mapped, so the region array can be cleared */ + struct drm_gem_object *obj; + u64 va, size; + int ctx; /* the context it last fired on, or -1 */ + bool cleared; /* its region array is already zero */ +}; + +struct sgx_file { + /* Every open file, so a recovery can reach them all. The register + * reset is core-wide; the scene state it does not reach is per file, + * and clearing only the one whose frame failed left every other + * client's next frame taking the partial-render branch. */ + struct list_head node; + struct mutex vm_lock; + struct rb_root vm[ARRAY_SIZE(sgx_windows)]; + struct sgx_mmu mmu; + + /* A pool of scenes cycled a frame at a time, as psb_scene.c does: + * consecutive frames land in different scene buffers, so one is not + * being rebuilt while the hardware still has the other. */ + struct sgx_scene scenes[SGX_SCENES_PER_FILE]; + unsigned int cur_scene; + /* The private table the out-of-memory resume swaps the DPM onto, and + * its mapping. Both were left unset, so the resume programmed a page + * table at address zero - which is the fault the recovery used to end + * in (sgxtri.c:3980-3985 records the same bug on the bare-metal + * side). */ + /* Whether the device's shared parameter heap and DPM page table have + * been bound into this client's address space yet. */ + bool shared_bound; + + /* This client's own parameter heap and DPM page table, when + * sgx_heap_per_client is on. Bound at the same addresses the shared + * pair uses, so nothing above the MMU changes; what changes is which + * pages are behind them. */ + struct drm_gem_object *heap_obj; + struct drm_gem_object *heap_pt_obj; + u32 heap_cookie[16]; + bool heap_ready; + bool heap_private; /* the mode this file bound under */ + + /* GTT this file's SGX_BO_SCANOUT objects hold, given back at close. */ + atomic64_t scanout_gtt; + + struct drm_gem_object *oom_pt_obj; + u64 oom_pt_va; + void *oom_pt_cpu; + + /* The page the 2D block writes its sequence number into, one per file + * because it is reached through the file's own page tables. */ + struct drm_gem_object *twod_fence_obj; + u64 twod_fence_va; + void *twod_fence_cpu; + + struct sgx_use_regs use; + + /* The ISP's eight visibility counters, summed over every render this + * file has fired. The registers are cleared each time they are read, + * so what a caller sees here is a running total it takes differences + * of - the vendor's microkernel keeps the same running total in the + * client's own buffer (3d.asm:714-776). */ + u64 vistest[SGX_VISTEST_REGS]; +}; + + +static void sgx_core_restart(struct sgx_device *sgx); +static void sgx_dpm_reset(struct sgx_device *sgx); +static void sgx_stall_recover(struct sgx_device *sgx, struct sgx_file *sf, + const char *what); +static int sgx_render_wait(struct drm_device *dev, struct sgx_device *sgx); +static int sgx_render_wait_only(struct drm_device *dev, struct sgx_device *sgx); +static int sgx_twod_drain(struct drm_device *dev, struct sgx_device *sgx); + +static bool sgx_recover = true; +module_param_named(sgx_recover, sgx_recover, bool, 0644); + +/* How many out-of-memory recoveries one frame may take. sgxtri's oom_max() + * defaults to the same 64: a scene that has not fitted after that many partial + * renders is not going to. */ +static unsigned int sgx_max_oom = 64; +module_param_named(sgx_max_oom, sgx_max_oom, uint, 0644); +MODULE_PARM_DESC(sgx_max_oom, + "out-of-memory recoveries allowed within one frame"); + +/* How many consecutive failed frames pass before one warning is logged. + * Nothing is refused on it: a frame that did not finish says nothing about + * the next one, and every submit after it is still tried. */ +static unsigned int sgx_max_stalls = 8; +module_param_named(sgx_max_stalls, sgx_max_stalls, uint, 0644); +MODULE_PARM_DESC(sgx_max_stalls, + "consecutive failed frames after which the warning is logged"); +MODULE_PARM_DESC(recover, "reset the core after a frame that did not finish"); + +/* Whether a submit may come back once the render is fired instead of once it + * has ended. The master switch: userspace asks per submit with + * SGX_SUBMIT_DEFER_WAIT, and clearing this refuses every such request, so a + * machine can be booted back onto the synchronous path without rebuilding + * anything above the kernel. */ +static bool sgx_async = true; +module_param_named(sgx_async, sgx_async, bool, 0644); +MODULE_PARM_DESC(sgx_async, + "let a submit return once the render is fired, with the wait deferred"); + +/* How many of the part's two hardware scene contexts to use. + * + * EUR_CR_DPM_STATE_CONTEXT_ID selects one of two for each of the DPM's three + * sides, so two is what the hardware has; one is the A/B that says whether a + * defect follows the context rather than the frame, and is what + * driver/kernel/sgx_drv.c does unconditionally. + * + * SGX_CTX_MODE_TABLES additionally points the DPM's state and control table + * bases at the scene about to be bound, before the bind clears them. The + * vendor's DPMStateClear "clears the state table only at the address already + * setup" (sgx_utils.asm:2018-2019) and nothing on the ordinary path programs + * that address, so after an out-of-memory recovery it still names the scene + * that recovered - another client's, in another address space. Off by + * default: it changes what the hardware is told on every frame and has not + * run on the part. + * + * Refused rather than clamped if it is anything else; a value nobody meant + * should not silently become a mode. */ +#define SGX_CTX_MODE_ONE 1 +#define SGX_CTX_MODE_TWO 2 +#define SGX_CTX_MODE_TABLES 3 +static unsigned int sgx_scene_ctx = SGX_CTX_MODE_TWO; +module_param_named(sgx_scene_ctx, sgx_scene_ctx, uint, 0444); +MODULE_PARM_DESC(sgx_scene_ctx, + "hardware scene contexts: 1 one, 2 both (default), 3 both plus per-scene DPM tables"); + +/* One line a frame naming the context it took. Two clients alternating is the + * only way to see that the second context is reached at all - the register + * write is inside the fire and nothing else reports it. */ +static bool sgx_scene_ctx_log; +module_param_named(sgx_scene_ctx_log, sgx_scene_ctx_log, bool, 0644); + +/* Let a frame start while the one before it is still rendering, when the two + * would sit in different hardware scene contexts. That is what the part's two + * contexts are for - the DPM state and control tables, the MTE output-vertex + * memory and the tail-pointer cache are per context, so reprogramming one does + * not disturb a render in the other. Only within one file: a different file + * needs the MMU rearmed, which is per device and would go out from under the + * render. Off by default - a submit fired into state the core is still using + * locks the part. */ +static bool sgx_ctx_overlap; +module_param_named(sgx_ctx_overlap, sgx_ctx_overlap, bool, 0644); +MODULE_PARM_DESC(sgx_ctx_overlap, + "start a frame in the free scene context while the previous render finishes (same file only)"); +MODULE_PARM_DESC(sgx_scene_ctx_log, + "log the hardware scene context each frame takes"); + +/* The 2D block. Off refuses the blit ioctl and the parameter that advertises + * it, so userspace falls back to the paths it has today. */ +static bool sgx_twod = true; +module_param_named(sgx_twod, sgx_twod, bool, 0644); +MODULE_PARM_DESC(sgx_twod, "accept 2D blits (default on)"); + +/* Whether a 2D job may come back once the stream is on the engine instead of + * once it has run. The master switch: userspace asks per job with + * SGX_BLIT_DEFER_WAIT and clearing this refuses every such request, the same + * way sgx_async governs a deferred render. */ +static bool sgx_twod_async = true; +module_param_named(sgx_twod_async, sgx_twod_async, bool, 0644); +MODULE_PARM_DESC(sgx_twod_async, + "let a 2D job return once the stream is on the engine (default on)"); + +/* How the BIF is flushed before a 2D job reads. + * + * sgx_mmu_invalidate() pulses INVALDC and FLUSH in one write, and its comment + * says psb_mmu_flush() does the same - which it does not: the vendor writes + * one bit or the other, never both (mmu.c, psb_mmu_flush()). A combination + * the vendor never issues is not one this part is known to honour, and if + * FLUSH is the bit that pushes the BIF's data cache out then a 2D requestor + * reading what the 3D core just wrote is reading stale memory. + * + * 1 pulses FLUSH on its own and then INVALDC on its own, the vendor's shape, + * before every job; 0 leaves the job with the combined pulse sgx_mmu_arm() + * already does. A parameter rather than a change to sgx_mmu_invalidate(), + * because the 3D path works as it is and this is the path that does not. + * + * Measured on the part and it changed nothing: the blank cells were a render + * that faulted, not a job that read stale memory. So it is off. It stays + * because the mismatch with the vendor is real and worth having a switch for, + * not because it is known to matter. + */ +static unsigned int sgx_twod_bif; +module_param_named(sgx_twod_bif, sgx_twod_bif, uint, 0644); +MODULE_PARM_DESC(sgx_twod_bif, + "flush the BIF the vendor's way before a 2D job: 1 separate pulses, 0 off (default off)"); + +/* Flush the CPU's caches over every extent the stream reads or writes before + * the job is pushed. A surface written through a write-combining mapping + * needs a store fence rather than this, and the caller now does that; a + * surface that was ever mapped cached needs this. Off by default because it + * costs a walk of the object's pages per job; on, it is the strongest thing + * the kernel can do about a source that is not in memory yet. */ +static bool sgx_twod_flush_src; +module_param_named(sgx_twod_flush_src, sgx_twod_flush_src, bool, 0644); +MODULE_PARM_DESC(sgx_twod_flush_src, + "clflush every surface a 2D job names before pushing the stream"); + +/* Report what the CPU can see at the extents the job reads, at the moment the + * job is pushed: the address, the page table entry it resolves through, and + * the first dwords of that page. If the page holds the pixels and the blit + * still comes out blank, the defect is not that the source is missing from + * memory - and that is worth knowing in one run rather than three. */ +static bool sgx_twod_probe; +module_param_named(sgx_twod_probe, sgx_twod_probe, bool, 0644); +MODULE_PARM_DESC(sgx_twod_probe, + "log what the CPU sees at a 2D job's surfaces as it is pushed"); + +/* A 2D job has no progress signature - BLIT_STATUS counts fences, not + * pixels - so this is a plain bound. A 2048x2048 copy is 16 MiB. */ +static unsigned int sgx_twod_timeout_ms = 1000; +module_param_named(sgx_twod_timeout_ms, sgx_twod_timeout_ms, uint, 0644); +MODULE_PARM_DESC(sgx_twod_timeout_ms, + "milliseconds a 2D job may take (default 1000)"); + +/* Refuse a frame that names an object with no address rather than firing it + * at one nothing backs. The winsys will not submit such a buffer, so this + * only fires when the two have drifted - and it turns a fault the log cannot + * attribute into a refusal that names the handle. */ +static bool sgx_bind_check = true; +module_param_named(sgx_bind_check, sgx_bind_check, bool, 0644); +MODULE_PARM_DESC(sgx_bind_check, + "refuse a submit naming an object that is not bound (default on)"); + +static bool sgx_reset = true; +module_param_named(sgx_reset, sgx_reset, bool, 0644); + +/* Bring the BIF up the way SGXReset() does: its registers written while it is + * still held in reset, the BIF released on its own, and the rest of the + * pipeline last. 0 is the order this driver used before. */ +static bool sgx_reset_order = true; +module_param_named(sgx_reset_order, sgx_reset_order, bool, 0644); +MODULE_PARM_DESC(reset, + "reset the core through D3hot at probe and when the last client closes"); + +static const char *sgx_fence_get_name(struct dma_fence *f) +{ + return DRIVER_NAME; +} + +/* A fence is signalled before it is handed out and a submit that asks for one + * waits in line, so there is no timeline to wait on beyond the ioctl itself. + * That is honest rather than convenient: the hardware has no completion + * interrupt wired up here, so a fence that claimed to be pending would be a + * fence nothing could ever signal. */ +static const struct dma_fence_ops sgx_fence_ops = { + .get_driver_name = sgx_fence_get_name, + .get_timeline_name = sgx_fence_get_name, +}; + +struct sgx_binding { + struct rb_node node; + u64 gpu_va, size; + struct drm_gem_object *obj; + u32 window; +}; + +static __maybe_unused unsigned int sgx_reg_rd(void *priv, unsigned int off) +{ + return readl((void __iomem *)priv + off); +} + +static __maybe_unused void sgx_reg_wr(void *priv, unsigned int off, unsigned int val) +{ + writel(val, (void __iomem *)priv + off); +} + +static __maybe_unused unsigned long long sgx_reg_now(void *priv) +{ + (void)priv; + return ktime_to_us(ktime_get()); +} + +static __maybe_unused void sgx_kick_env_init(struct sgx_kick_env *e, struct sgx_device *sgx, + unsigned int use_ctrl) +{ + e->priv = (void *)sgx->regs; + e->rd = sgx_reg_rd; + e->wr = sgx_reg_wr; + e->now_us = sgx_reg_now; + e->use_ctrl = use_ctrl; + e->spin_max = sgx_poll_spin; + e->ev_spin_max = sgx_evt_spin; +} + +/* Unnamed: the page index the DPM is handing out. It is what says how close + * the heap is to the end of its range. */ +#define SGX_R_DPM_ALLOC_PTR 0x0700 +#define SGX_DPM_WRAP_MARGIN 128u + +/* The SGX state is a member of gma500's private data. One device, one driver, + * so this is a lookup and not a container_of on a drm_device this code owns. */ +static struct sgx_device *psb_sgx_of(struct drm_device *dev) +{ + return &to_drm_psb_private(dev)->sgx; +} + +static int sgx_check_stream(const u32 *s, u32 dwords) +{ + int r = sgx_stream_bad_at(s, dwords, NULL); + + return r == 0 ? 0 : (r < 0 ? -EINVAL : -EACCES); +} + +/* ---- address space ---- */ + +static struct sgx_binding *sgx_vm_find(struct rb_root *root, u64 va, u64 size) +{ + struct rb_node *n = root->rb_node; + + while (n) { + struct sgx_binding *b = rb_entry(n, struct sgx_binding, node); + + if (va + size <= b->gpu_va) + n = n->rb_left; + else if (va >= b->gpu_va + b->size) + n = n->rb_right; + else + return b; /* overlaps */ + } + return NULL; +} + +static int sgx_vm_insert(struct rb_root *root, struct sgx_binding *ins) +{ + struct rb_node **n = &root->rb_node, *parent = NULL; + + while (*n) { + struct sgx_binding *b = rb_entry(*n, struct sgx_binding, node); + + parent = *n; + if (ins->gpu_va + ins->size <= b->gpu_va) + n = &(*n)->rb_left; + else if (ins->gpu_va >= b->gpu_va + b->size) + n = &(*n)->rb_right; + else + return -EEXIST; + } + rb_link_node(&ins->node, parent, n); + rb_insert_color(&ins->node, root); + return 0; +} + +/* ---- ioctls ---- */ + +/* Every object here is mapped write-combining, and set_pages_array_wc() can + * only change the attributes of pages the kernel keeps in its direct map. On + * 32-bit, anything above the lowmem boundary is highmem and has none, so as + * soon as shmem handed out a high page the change faulted in + * __cpa_process_fault() - a NULL dereference inside drm_gem_shmem_pin(). It + * only showed on a full-RAM boot; mem=880M leaves no highmem to hand out. + * Ask shmem for lowmem, the same way gem.c does for the legacy objects. */ +/* Take this object's share of the scanout GTT budget, or refuse. The file's + * own tally is what the close gives back; an object that outlives its file + * keeps the GTT but no longer the charge, which errs towards refusing. */ +static int sgx_scanout_gtt_charge(struct sgx_device *sgx, struct sgx_file *sf, + u64 bytes) +{ + u64 cap = (u64)sgx_scanout_gtt_mb << 20; + + if (atomic64_add_return(bytes, &sgx->scanout_gtt) > cap) { + atomic64_sub(bytes, &sgx->scanout_gtt); + return -ENOSPC; + } + atomic64_add(bytes, &sf->scanout_gtt); + return 0; +} + +static void sgx_scanout_gtt_uncharge(struct sgx_device *sgx, + struct sgx_file *sf, u64 bytes) +{ + atomic64_sub(bytes, &sf->scanout_gtt); + atomic64_sub(bytes, &sgx->scanout_gtt); +} + +static void sgx_shmem_lowmem(struct drm_gem_shmem_object *shmem) +{ + if (!IS_ERR_OR_NULL(shmem) && shmem->base.filp) + mapping_set_gfp_mask(shmem->base.filp->f_mapping, + GFP_KERNEL | __GFP_DMA32); +} + +static int sgx_ioctl_gem_new(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_gem_new *args = data; + struct sgx_file *sf = file->driver_priv; + struct drm_gem_shmem_object *shmem; + int ret; + + if (!sf) + return -EINVAL; + + if (!args->size || (args->size & ~PAGE_MASK)) + return -EINVAL; + /* size is a __u64 and every allocator below takes a size_t. On the + * 32-bit build the high half was dropped, so 0x100001000 asked for one + * page and the caller then had a handle it believed was four + * gigabytes. Nothing can be bound larger than the widest window, so + * refuse above that and the truncation cannot be constructed. */ + if (args->size > sgx_vm_max_object_bytes()) + return -EINVAL; + /* drm_sgx_gem_new is fully packed - size, flags, handle - so there is + * no pad here to check. */ + if (!sgx_flags_ok(args->flags, SGX_BO_SCANOUT | SGX_BO_CPU_CACHED)) + return -EINVAL; + + /* SCANOUT is not a hint. The display engine reaches a framebuffer + * through psb_gem_pin(), which walks a struct psb_gem_object and needs + * the GTT resource psb_gem_create() reserves - hand it a shmem object + * instead and the fields after the base are reinterpreted, which ends + * as a BUG in psb_gtt_insert_pages() rather than a wrong picture. */ + if (args->flags & SGX_BO_SCANOUT) { + struct sgx_device *sgx = psb_sgx_of(dev); + struct psb_gem_object *pobj; + u64 want = PAGE_ALIGN(args->size); + + /* The GTT this takes is held until the object is freed, and a + * client can ask again without limit. Charge it before the + * allocation, so nothing is reserved that the budget then + * refuses. */ + ret = sgx_scanout_gtt_charge(sgx, sf, want); + if (ret) + return ret; + pobj = psb_gem_create(dev, args->size, "sgx-scanout", false, + PAGE_SIZE); + if (IS_ERR(pobj)) { + sgx_scanout_gtt_uncharge(sgx, sf, want); + return PTR_ERR(pobj); + } + ret = drm_gem_handle_create(file, &pobj->base, &args->handle); + drm_gem_object_put(&pobj->base); + if (ret) + sgx_scanout_gtt_uncharge(sgx, sf, want); + return ret; + } + + shmem = drm_gem_shmem_create(dev, args->size); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + sgx_shmem_lowmem(shmem); + + /* The parameter heap is read by the DPM without snooping, so a cached + * mapping there is a correctness bug rather than a performance choice. + * Uncached is the default and CPU_CACHED has to be asked for. */ + shmem->map_wc = !(args->flags & SGX_BO_CPU_CACHED); + + ret = drm_gem_handle_create(file, &shmem->base, &args->handle); + drm_gem_object_put(&shmem->base); + return ret; +} + +static int sgx_ioctl_gem_map(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_gem_map *args = data; + struct drm_gem_object *obj; + int ret; + + if (!sgx_pad_ok(args->pad)) + return -EINVAL; + obj = drm_gem_object_lookup(file, args->handle); + if (!obj) + return -ENOENT; + ret = drm_gem_create_mmap_offset(obj); + if (!ret) + args->offset = drm_vma_node_offset_addr(&obj->vma_node); + drm_gem_object_put(obj); + return ret; +} + +static int sgx_ioctl_gem_wait(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_gem_wait *args = data; + struct drm_gem_object *obj; + unsigned long timeout; + long ret; + + /* No flag has a meaning yet, and one accepted now cannot be given one + * later: the caller would ask an old kernel for behaviour it silently + * did not have. */ + if (!sgx_flags_ok(args->flags, 0)) + return -EINVAL; + /* nsecs_to_jiffies() returns an unsigned long, so on the 32-bit build + * a timeout beyond about 49 days wrapped and a long wait returned at + * once. Anything that large is the infinite wait the caller meant. */ + if (args->timeout_ns < 0) + timeout = MAX_SCHEDULE_TIMEOUT; + else { + u64 j = nsecs_to_jiffies64(args->timeout_ns); + + timeout = j >= MAX_SCHEDULE_TIMEOUT ? MAX_SCHEDULE_TIMEOUT + : (unsigned long)j; + } + obj = drm_gem_object_lookup(file, args->handle); + if (!obj) + return -ENOENT; + /* A deferred render is the one thing on this device that can still be + * reading the object, and it hangs off no reservation - so waiting on + * the reservation alone would report an object idle while the core has + * it. */ + { + struct sgx_device *sgx = psb_sgx_of(dev); + + mutex_lock(&sgx->fire_lock); + (void)sgx_render_wait(dev, sgx); + mutex_unlock(&sgx->fire_lock); + } + ret = dma_resv_wait_timeout(obj->resv, DMA_RESV_USAGE_READ, true, + timeout); + drm_gem_object_put(obj); + if (ret == 0) + return -ETIMEDOUT; + return ret < 0 ? ret : 0; +} + +/* The page directory and its tables are ordinary pages the hardware walks by + * physical address, so they must be lowmem and they must be flushed out of the + * CPU's caches after every write - psb_mmu.c clflushes each line it touches + * and so does the bare-metal implementation. Skipping that leaves the SGX + * walking a stale directory, which faults at an address that looks nothing + * like the one that was mapped. */ +static void sgx_mmu_flush_range(void *p, size_t n) +{ + clflush_cache_range(p, n); +} + +static int sgx_mmu_init(struct sgx_mmu *m) +{ + memset(m, 0, sizeof(*m)); + m->pd = (u32 *)get_zeroed_page(GFP_KERNEL); + if (!m->pd) + return -ENOMEM; + m->pd_phys = virt_to_phys(m->pd); + sgx_mmu_flush_range(m->pd, PAGE_SIZE); + return 0; +} + +static void sgx_mmu_fini(struct sgx_mmu *m) +{ + unsigned int i; + + for (i = 0; i < SGX_MMU_PDES; i++) + if (m->pt[i]) + free_page((unsigned long)m->pt[i]); + if (m->pd) + free_page((unsigned long)m->pd); + memset(m, 0, sizeof(*m)); +} + +static u32 *sgx_mmu_table(struct sgx_mmu *m, u64 va) +{ + unsigned int pde = sgx_mmu_pde_of(va); + u32 *pt; + + if (m->pt[pde]) + return m->pt[pde]; + pt = (u32 *)get_zeroed_page(GFP_KERNEL); + if (!pt) + return NULL; + sgx_mmu_flush_range(pt, PAGE_SIZE); + m->pt[pde] = pt; + m->pd[pde] = (u32)(virt_to_phys(pt) & ~0xfffUL) | SGX_PTE_VALID; + sgx_mmu_flush_range(&m->pd[pde], sizeof(m->pd[pde])); + return pt; +} + +/* Map an object's pages at its GPU address, or clear them again. */ +/* Stolen memory is one contiguous physical range, so there is no page array to + * walk - the frame numbers run on from the base. */ +static int sgx_mmu_bind_phys(struct sgx_mmu *m, u64 gpu_va, phys_addr_t phys, + u64 size) +{ + u64 i, n = size >> SGX_PTE_SHIFT; + + for (i = 0; i < n; i++) { + u64 va = gpu_va + (i << SGX_PTE_SHIFT); + u32 *pt = sgx_mmu_table(m, va); + unsigned int idx = sgx_mmu_pte_of(va); + + if (!pt) + return -ENOMEM; + /* The PSB_MMU_* type encoding, not the entry bits: cached is + * bit 0 there and SGX_PTE_CACHED is 0x8, so passing the entry + * bits set cached unconditionally - through SGX_PTE_VALID, + * which is bit 0 - and discarded the flag the parameter was + * meant to carry. sgx_scanout_cached produced the same page + * table either way. */ + pt[idx] = sgx_mmu_pte((u32)((phys >> SGX_PTE_SHIFT) + i), + sgx_scanout_cached ? 1 : 0); + sgx_mmu_flush_range(&pt[idx], sizeof(pt[idx])); + } + return 0; +} + +/* Two kinds of object reach these paths: what the SGX allocates for itself is + * a shmem object, and what has to be scanned out is one of gma500's, because + * only those carry the GTT resource the display engine pins. */ +static int sgx_obj_pin(struct drm_gem_object *obj) +{ + if (psb_gem_is_psb_object(obj)) + return psb_gem_pin(to_psb_gem_object(obj)); + return drm_gem_shmem_pin(to_drm_gem_shmem_obj(obj)); +} + +static void sgx_obj_unpin(struct drm_gem_object *obj) +{ + if (psb_gem_is_psb_object(obj)) + psb_gem_unpin(to_psb_gem_object(obj)); + else + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(obj)); +} + +static struct page **sgx_obj_pages(struct drm_gem_object *obj) +{ + if (psb_gem_is_psb_object(obj)) + return to_psb_gem_object(obj)->pages; + return to_drm_gem_shmem_obj(obj)->pages; +} + +/* Pages whose cache lines the bind path has flushed, which is what a menu + * draw was spending its time on. */ +static unsigned int sgx_clflush_pages; +module_param_named(sgx_clflush_pages, sgx_clflush_pages, uint, 0444); + +static int sgx_mmu_bind(struct sgx_mmu *m, struct drm_gem_object *obj, + u64 gpu_va, u64 offset, u64 size, bool unbind) +{ + struct page **pages; + u64 i, n = size >> SGX_PTE_SHIFT; + + pages = sgx_obj_pages(obj); + if (!unbind && !pages) + return -EINVAL; + + /* These pages are mapped write-combining for userspace, but they are + * still cached in the kernel's direct map from whatever they were + * before. A dirty line surviving there can be written back over what + * the GPU is reading, which is a frame that fails for no reason the + * hardware can report - and it gets likelier the more memory the + * session has cycled through. The bare-metal reference never sees it: + * its pool is one region, mapped once. */ + /* Once per set of pages, not once per bind. The dirty lines this is + * guarding against are whatever the pages held before they became + * this object's; nothing puts new ones there afterwards, because the + * only cached writes to them are the few sites that flush their own + * range. Flushing on every bind cost half of the X server's time + * drawing a menu - drm_clflush_page was 45% of it - because glamor + * binds its destination again for every operation. */ + if (!unbind && !to_psb_gem_object(obj)->pages_flushed) { + sgx_clflush_pages += n; + drm_clflush_pages(&pages[offset >> SGX_PTE_SHIFT], n); + if (!offset && (n << SGX_PTE_SHIFT) >= obj->size) + to_psb_gem_object(obj)->pages_flushed = true; + } + + /* How scattered this object is, physically. The bare-metal reference + * hands the DPM one contiguous, megabyte-aligned block; a GEM object + * is whatever the page allocator had. If that matters to the DPM this + * is the number that will track the failures. */ + if (!unbind && n > 1) { + u64 runs = 1; + + for (i = 1; i < n; i++) + if (page_to_pfn(pages[(offset >> SGX_PTE_SHIFT) + i]) != + page_to_pfn(pages[(offset >> SGX_PTE_SHIFT) + i - 1]) + 1) + runs++; + pr_debug("sgx: bind 0x%08llx: %llu pages in %llu run(s)\n", + gpu_va, n, runs); + } + + for (i = 0; i < n; i++) { + u64 va = gpu_va + (i << SGX_PTE_SHIFT); + u32 *pt = sgx_mmu_table(m, va); + unsigned int idx = sgx_mmu_pte_of(va); + + if (!pt) + return -ENOMEM; + if (unbind) + pt[idx] = 0; + else + pt[idx] = sgx_mmu_pte( + page_to_pfn(pages[(offset >> SGX_PTE_SHIFT) + i]), + 0); + sgx_mmu_flush_range(&pt[idx], sizeof(pt[idx])); + } + return 0; +} + +/* psb_mmu.c:144-150 - the TLB invalidate is a pulse, not a one-shot. */ +/* psb_reset.c:81 - INT_STAT is write-1-to-clear and the fault address latches + * until BIF_CTRL's CLEAR_FAULT is pulsed. Without this a report names a fault + * that already happened, possibly from before this module was even loaded. */ +static void sgx_fault_clear(struct sgx_device *sgx) +{ + u32 ctrl; + u32 was = readl(sgx->regs + SGX_CR_BIF_INT_STAT); + + /* Unconditional, and a short pulse. Skipping it when no fault is + * latched measured worse - the pulse evidently does more for the BIF + * than clear the status - and the long hold gma500 uses belongs in + * sgx_spank() on the recovery path, not in front of every kick. */ + writel(was, sgx->regs + SGX_CR_BIF_INT_STAT); + ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + writel(ctrl | SGX_CB_CTRL_CLEAR_FAULT, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + writel(ctrl & ~SGX_CB_CTRL_CLEAR_FAULT, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); +} + +/* Xpsb_sgx_check_lockup: eleven progress registers XORed into a signature. + * If it moves, the core is working, however slowly; if three samples agree + * with each other and with an earlier one, nothing is advancing. This is what + * tells a timeout that is too short from a core that is stuck. */ +#define SGX_R_TA_SIG_BASE 0x0308 +#define SGX_R_ISP_SIG_BASE 0x04cc +#define SGX_R_MISC_SIG 0x0b08 + +/* The ISP's visibility counters: EUR_CR_ISP_VISTEST_CTRL, whose low eight bits + * are CLEAR0..CLEAR7, and EUR_CR_ISP_VISTEST_VISIBLE0..7 four bytes apart + * (sgx535defs.h:1352-1415). Read-only from here; the only write is the clear. + */ +#define SGX_R_ISP_VISTEST_CTRL 0x0494 +#define SGX_R_ISP_VISTEST_VISIBLE0 0x0498 +#define SGX_R_ISP_VISTEST_CLEAR_ALL 0xffu + +/* What the vendor's microkernel does once a render has ended: read all eight, + * add them into the running total, then clear all eight in one write + * (3d.asm:714-776, the read-add-write followed by the CLEAR0..7 store at + * :767). Accumulating rather than replacing is what makes a query that spans a + * frame split count every part of itself - the vendor accumulates "after every + * render including SPM partial renders" (3d.asm:1170-1191) and on this core + * cannot be told not to (sgxkick_client.c:2344-2351). */ +static void sgx_vistest_harvest(struct sgx_device *sgx, struct sgx_file *sf) +{ + unsigned int i; + + if (!sgx->regs) + return; + for (i = 0; sf && i < SGX_VISTEST_REGS; i++) + sf->vistest[i] += readl(sgx->regs + SGX_R_ISP_VISTEST_VISIBLE0 + + 4 * i); + writel(SGX_R_ISP_VISTEST_CLEAR_ALL, sgx->regs + SGX_R_ISP_VISTEST_CTRL); + (void)readl(sgx->regs + SGX_R_ISP_VISTEST_CTRL); +} + +static u32 sgx_progress_sig(struct sgx_device *sgx) +{ + return readl(sgx->regs + SGX_R_ISP_SIG_BASE + 0x18) ^ + readl(sgx->regs + SGX_R_ISP_SIG_BASE + 0x14) ^ + readl(sgx->regs + SGX_R_ISP_SIG_BASE + 0x00) ^ + readl(sgx->regs + SGX_R_ISP_SIG_BASE + 0x04) ^ + readl(sgx->regs + SGX_R_ISP_SIG_BASE + 0x08) ^ + readl(sgx->regs + SGX_R_ISP_SIG_BASE + 0x0c) ^ + readl(sgx->regs + SGX_R_TA_SIG_BASE + 0x00) ^ + readl(sgx->regs + SGX_R_TA_SIG_BASE + 0x04) ^ + readl(sgx->regs + SGX_R_TA_SIG_BASE + 0x08) ^ + readl(sgx->regs + SGX_R_TA_SIG_BASE + 0x0c) ^ + (readl(sgx->regs + SGX_R_MISC_SIG) & 0xffff); +} + +/* psb_reset.c:66-100 decodes these two registers. Reported when something + * times out, because "did not finish" on its own says nothing about why - and + * an MMU fault names the address and the requestor that asked for it. */ +/* What the file's own address space says about an address, which is the + * question a fault asks and the registers cannot answer: is it inside a + * binding this client made, and does its page table entry resolve? + * + * A render that faults inside its own render target's slot has had that + * target unbound out from under it, and nothing in the register dump says so - + * "MMU fault at 0x80002000" reads like a stray address until this line says + * that nothing is bound within a megabyte of it. */ +static void sgx_report_address(struct sgx_device *sgx, struct sgx_file *sf, + u32 addr) +{ + unsigned int w, pde = sgx_mmu_pde_of(addr); + u32 *pt; + u32 pte; + + if (!sf) + return; + pt = sf->mmu.pt[pde]; + pte = pt ? pt[sgx_mmu_pte_of(addr)] : 0; + for (w = 0; w < SGX_VM_COUNT; w++) { + struct sgx_binding *b; + + if (!sgx_in_window(addr, 1, sgx_windows[w].start, + sgx_windows[w].size)) + continue; + b = sgx_vm_find(&sf->vm[w], addr, 1); + if (b) + drm_err(sgx->ddev, + " 0x%08x is %llu byte(s) into the %s binding at 0x%08llx (%llu bytes), pte 0x%08x\n", + addr, (u64)addr - b->gpu_va, sgx_windows[w].name, + b->gpu_va, b->size, pte); + else + drm_err(sgx->ddev, + " 0x%08x is in the %s window and NOTHING IS BOUND THERE, pte 0x%08x\n", + addr, sgx_windows[w].name, pte); + return; + } + drm_err(sgx->ddev, " 0x%08x is outside every window, pte 0x%08x\n", + addr, pte); +} + +static void sgx_report_faults_for(struct sgx_device *sgx, struct sgx_file *sf, + const char *where) +{ + static const char * const req[] = { + NULL, "cache", "TA", "VDM", "2D", "PBE", "TSP", "ISP", + "USSE/PDS", "host" + }; + u32 stat = readl(sgx->regs + SGX_CR_BIF_INT_STAT); + u32 addr = readl(sgx->regs + SGX_CR_BIF_FAULT); + char who[64] = ""; + unsigned int i; + + if (!stat) { + drm_err(sgx->ddev, "%s: no MMU fault (BIF_INT_STAT 0)\n", + where); + return; + } + for (i = 1; i < ARRAY_SIZE(req); i++) + if (stat & (1u << i)) + strlcat(who, req[i], sizeof(who)), + strlcat(who, " ", sizeof(who)); + drm_err(sgx->ddev, + "%s: MMU fault at 0x%08x, requestors: %s(BIF_INT_STAT 0x%08x)\n", + where, addr & ~0xfffu, who, stat); + sgx_report_address(sgx, sf, addr & ~0xfffu); + /* Whether the 2D block has run at all, and how recently: a fault that + * only appears with the engine enabled wants that number beside it. */ + drm_err(sgx->ddev, + " 2D: %llu call(s), %llu refused, %llu job(s) run, %llu blit dword(s), %llu stall(s)\n", + sgx->twod_calls, sgx->twod_refused, sgx->twod_jobs, + sgx->twod_dwords, sgx->twod_stalls); + /* The status latches: leaving it set made every later stall report + * this same fault again, so a count of reports was not a count of + * faults. BRN 23944 also wants the page fault cleared before the BIF + * is reset, and recovery follows this. */ + { + u32 ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + + writel(ctrl | SGX_CB_CTRL_CLEAR_FAULT, + sgx->regs + SGX_CR_BIF_CTRL); + wmb(); + writel(ctrl & ~SGX_CB_CTRL_CLEAR_FAULT, + sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + } +} + +static void sgx_report_faults(struct sgx_device *sgx, const char *where) +{ + sgx_report_faults_for(sgx, NULL, where); +} + +/* Wait for the reads the BIF has already issued to retire. + * + * An invalidate is a pulse, and a read that was in flight when it happened + * comes back afterwards carrying a translation made from the *old* directory - + * which is then what the cache holds. The vendor's own invalidate on a running + * core pauses the BIF, waits for BIF_MEM_REQ_STAT's read count to reach zero + * and only then pulses the bit (usedefs.h:1791-1810); its reset path polls the + * same register after the pulse (sgxreset.c:303-325). Neither is compiled for + * this part in the vendor build, because the vendor never re-points one + * directory: SGX535 has sixteen BIF_DIR_LIST_BASE registers and gives each + * memory context its own. This driver has one and re-points it per submit, so + * it is in the position the guarded code was written for. */ +static void sgx_bif_drain(struct sgx_device *sgx) +{ + unsigned int i; + + for (i = 0; i < SGX_BIF_DRAIN_TRIES; i++) { + if (!(readl(sgx->regs + SGX_CR_BIF_MEM_REQ_STAT) & + SGX_CB_MEM_REQ_READS)) + return; + udelay(1); + } + sgx->bif_drain_timeouts++; + drm_warn_once(sgx->ddev, + "the BIF still had reads outstanding %u us after a pause; MEM_REQ_STAT 0x%08x\n", + SGX_BIF_DRAIN_TRIES, + readl(sgx->regs + SGX_CR_BIF_MEM_REQ_STAT)); +} + +static void sgx_mmu_invalidate(struct sgx_device *sgx) +{ + u32 ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + u32 base = ctrl; + + /* INVALDC is the page-directory invalidate and FLUSH the page-table + * one (sgx535defs.h:2714-2720); BRN 27251 says this core's FLUSH is + * not to be relied on, and the vendor's microkernel issues INVALDC for + * both cases (usedefs.h:1823-1829). Dropping only the directory left a + * client translating through the page tables of whichever file + * submitted before it - which the drain below is the explanation for, + * so FLUSH stays only as something to take back out. */ + u32 bits = SGX_CB_CTRL_INVALDC | + (sgx_bif_flush ? SGX_CB_CTRL_FLUSH : 0u); + + if (sgx_bif_pause) { + base = ctrl | SGX_CB_CTRL_PAUSE; + writel(base, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + sgx_bif_drain(sgx); + } + writel(base | bits, sgx->regs + SGX_CR_BIF_CTRL); + wmb(); + writel(base & ~bits, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + if (sgx_bif_pause) { + writel(ctrl & ~SGX_CB_CTRL_PAUSE, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + } +} + +/* Point the BIF at a directory. Both hardware contexts get the same one. The + * bank field layout is not documented anywhere, so whichever context a + * requestor picks is then correct - which is what psb_drv.c:637-639 does. */ +static void sgx_mmu_point(struct sgx_device *sgx, unsigned long pd_phys) +{ + writel((u32)pd_phys, sgx->regs + SGX_CR_BIF_DIR_LIST_BASE0); + writel((u32)pd_phys, sgx->regs + SGX_CR_BIF_DIR_LIST_BASE1 + 4); + wmb(); + /* Read the bases back before the invalidate. wmb() orders the writes + * as the CPU issues them; it does not wait for the device to take + * them, and a posted write still in flight when the pulse arrives + * means the cache is invalidated and then refilled from the directory + * that is being replaced. The vendor's microkernel reads the register + * back for the same reason (sgx_utils.asm:3525-3539). */ + (void)readl(sgx->regs + SGX_CR_BIF_DIR_LIST_BASE0); + (void)readl(sgx->regs + SGX_CR_BIF_DIR_LIST_BASE1 + 4); + sgx_mmu_invalidate(sgx); +} + +static void sgx_mmu_arm(struct sgx_device *sgx, struct sgx_mmu *m) +{ + sgx_mmu_point(sgx, m->pd_phys); + + /* Read the directory back once. If the hardware did not take it, + * every translation fails and the first requestor to try reports a + * fault at whatever it asked for - which says nothing about the real + * cause unless this is checked. */ + if (!m->verified) { + u32 back = readl(sgx->regs + SGX_CR_BIF_DIR_LIST_BASE0); + u32 ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + + /* The tiler's addresses and the render's: the target, the + * depth buffer and the two texture units sit at fixed + * addresses the frame names in every relocation, so a render + * that faults is most often one of them not being mapped. */ + static const u32 probe[] = { 0x20010000, 0x20800000, 0x30000000, + 0x30100000, 0x40000000, 0x40800000, + 0x80000000, 0x81200000, 0x82400000, + 0x83a00000 }; + unsigned int q; + + drm_info(sgx->ddev, + "page directory 0x%08lx, reads back 0x%08x, BIF_CTRL 0x%08x\n", + m->pd_phys, back, ctrl); + for (q = 0; q < ARRAY_SIZE(probe); q++) { + unsigned int pde = sgx_mmu_pde_of(probe[q]); + u32 *pt = m->pt[pde]; + + drm_info(sgx->ddev, " va 0x%08x: pde=0x%08x pte=0x%08x\n", + probe[q], m->pd[pde], + pt ? pt[sgx_mmu_pte_of(probe[q])] : 0xdeadbeef); + } + m->verified = true; + } +} + +/* Let the SGX's requestors translate. The field is inverted, so this clears + * bits. Nothing did it, and an untranslated tiler kicks and never finishes. */ +static void sgx_mmu_enable_requestors(struct sgx_device *sgx) +{ + u32 ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + + writel(ctrl & ~SGX_MMU_ER_MASK, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); +} + +/* Allocate one driver-owned object and map it into a window at a fixed + * address. Fixed rather than allocated because these are the addresses the + * bare-metal implementation uses, and the DPM's own tables sit at known + * offsets from them. */ +/* Record a range the driver owns, so a client cannot bind over it. + * + * The overlap test consults sf->vm[] alone, and the scene, the DPM's page + * table, the parameter heap and the scanout were bound straight through the + * MMU without ever appearing there - so a VM_BIND at any of their addresses + * was accepted and replaced their entries, which the uapi header says is + * refused. A reservation carries no object; the unbind path refuses to drop + * one and the teardown does not put it. + * + * The caller holds vm_lock. Every caller is under fire_lock as well, and + * VM_BIND takes vm_lock before fire_lock, so taking vm_lock here was the + * other half of a deadlock. */ +static int sgx_vm_reserve(struct sgx_file *sf, u64 va, u64 size) +{ + struct sgx_binding *b; + unsigned int w; + + lockdep_assert_held(&sf->vm_lock); + for (w = 0; w < SGX_VM_COUNT; w++) + if (sgx_in_window(va, size, sgx_windows[w].start, + sgx_windows[w].size)) + break; + /* Outside every window there is nothing a client could bind. */ + if (w == SGX_VM_COUNT) + return 0; + if (sgx_vm_find(&sf->vm[w], va, size)) + return 0; /* already reserved */ + b = kzalloc(sizeof(*b), GFP_KERNEL); + if (!b) + return -ENOMEM; + b->gpu_va = va; + b->size = size; + b->obj = NULL; + b->window = w; + if (sgx_vm_insert(&sf->vm[w], b)) { + kfree(b); + return -EBUSY; + } + return 0; +} + +static int sgx_own_object(struct drm_device *dev, struct sgx_file *sf, + u64 va, u64 size, struct drm_gem_object **out, + u64 *out_va) +{ + struct drm_gem_shmem_object *shmem; + int ret; + + shmem = drm_gem_shmem_create(dev, size); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + sgx_shmem_lowmem(shmem); + ret = drm_gem_shmem_pin(shmem); + if (ret) { + drm_gem_object_put(&shmem->base); + return ret; + } + ret = sgx_mmu_bind(&sf->mmu, &shmem->base, va, 0, size, false); + if (ret) { + drm_gem_shmem_unpin(shmem); + drm_gem_object_put(&shmem->base); + return ret; + } + *out = &shmem->base; + *out_va = va; + return sgx_vm_reserve(sf, va, size); +} + +static void sgx_drop_object(struct sgx_file *sf, struct drm_gem_object **obj, + u64 va, u64 size) +{ + if (!*obj) + return; + sgx_mmu_bind(&sf->mmu, *obj, va, 0, size, true); + sgx_obj_unpin(*obj); + drm_gem_object_put(*obj); + *obj = NULL; +} + +/* The shmem helper's vmap and vunmap are the _locked variants: they assert the + * object's reservation and count uses under it. Every object mapped here is + * the driver's own and reached under fire_lock, so nothing has raced - but the + * contract is the reservation, and lockdep says so on the first map. */ +static int sgx_obj_vmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + int ret; + + ret = dma_resv_lock(obj->resv, NULL); + if (ret) + return ret; + ret = drm_gem_shmem_vmap_locked(to_drm_gem_shmem_obj(obj), map); + dma_resv_unlock(obj->resv); + return ret; +} + +static void sgx_obj_vunmap(struct drm_gem_object *obj, void *vaddr) +{ + struct iosys_map map; + + iosys_map_set_vaddr(&map, vaddr); + dma_resv_lock(obj->resv, NULL); + drm_gem_shmem_vunmap_locked(to_drm_gem_shmem_obj(obj), &map); + dma_resv_unlock(obj->resv); +} + +/* The scene has to be at least as large as the cookie says, and it grows with + * the render target, so it is allocated on the first submit that needs it and + * reallocated if a later one needs more. */ +/* The scene's mapping is held for as long as the object is, so it has to go + * back before the object does or the shmem helper warns and the pages stay + * pinned. */ +static void sgx_scene_unmap(struct sgx_scene *sc) +{ + if (!sc->cpu || !sc->obj) + return; + sgx_obj_vunmap(sc->obj, sc->cpu); + sc->cpu = NULL; +} + +/* One client's own parameter heap and DPM page table, bound at the addresses + * every client uses. + * + * The pages behind them are this client's alone, so the binned geometry the + * tiler writes is not in any other client's address space - which is what the + * vendor gets from putting the parameter buffer in a DEVICE_MEMORY_HEAP_PERCONTEXT + * heap (sgxinit.c:1837-1846, devicemem.c:200), its default build. + * + * The DPM page table is seeded from the one the single INIT handshake filled. + * That table is a free list the vendor's host builds in software rather than + * anything the hardware writes (InitialisePBBlockPageTable, sgxpb.c:260-308), + * so a copy of it is a valid starting state for a heap of the same size at the + * same address - and copying is what the earlier attempt at a per-client heap + * lacked, which is why its tiler walked into an empty table. */ +static int sgx_heap_private(struct drm_device *dev, struct sgx_device *sgx, + struct sgx_file *sf, u64 pt_size) +{ + struct drm_gem_shmem_object *shmem; + struct iosys_map map; + int ret; + + if (!sf->heap_obj) { + shmem = drm_gem_shmem_create(dev, SGX_PARAM_BYTES); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + sgx_shmem_lowmem(shmem); + ret = drm_gem_shmem_pin(shmem); + if (ret) { + drm_gem_object_put(&shmem->base); + return ret; + } + sf->heap_obj = &shmem->base; + } + if (!sf->heap_pt_obj) { + shmem = drm_gem_shmem_create(dev, pt_size); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + sgx_shmem_lowmem(shmem); + ret = drm_gem_shmem_pin(shmem); + if (ret) { + drm_gem_object_put(&shmem->base); + return ret; + } + sf->heap_pt_obj = &shmem->base; + } + ret = sgx_mmu_bind(&sf->mmu, sf->heap_obj, SGX_PARAM_VA, 0, + SGX_PARAM_BYTES, false); + if (ret) + return ret; + ret = sgx_mmu_bind(&sf->mmu, sf->heap_pt_obj, SGX_DPM_PT_VA, 0, + pt_size, false); + if (ret) + return ret; + + /* Nothing to seed from until the one INIT load has run; the client + * that runs it seeds from its own table afterwards. */ + if (!sgx->dpm_pt_seed || sf->heap_ready) + return 0; + ret = sgx_obj_vmap(sf->heap_pt_obj, &map); + if (ret) + return ret; + memcpy(map.vaddr, sgx->dpm_pt_seed, sgx->dpm_pt_seed_size); + /* The DPM reads this without snooping. */ + drm_clflush_virt_range(map.vaddr, sgx->dpm_pt_seed_size); + sgx_obj_vunmap(sf->heap_pt_obj, map.vaddr); + memcpy(sf->heap_cookie, sgx->dpm_cookie, sizeof(sf->heap_cookie)); + sf->heap_ready = true; + return 0; +} + +/* Create the device's parameter heap and DPM page table if they do not exist, + * and bind them into this client's address space. + * + * Every client sees them at the same addresses, so the one heap load the + * hardware accepts per boot describes memory that every later client can + * reach. The objects belong to the device and outlive any file. */ +static int sgx_shared_bind(struct drm_device *dev, struct sgx_device *sgx, + struct sgx_file *sf) +{ + struct drm_gem_shmem_object *shmem; + u64 pt_size; + int ret; + + /* The addresses are the same either way; only the pages behind them + * differ, so everything above the MMU is unchanged. */ + pt_size = sgx_dpm_pt_bytes(SGX_PARAM_VA, + (u32)(SGX_PARAM_BYTES >> 12)); + sgx->param_va = SGX_PARAM_VA; + sgx->param_size = SGX_PARAM_BYTES; + sgx->dpm_pt_va = SGX_DPM_PT_VA; + sgx->dpm_pt_size = pt_size; + + if (!sgx_heap_per_client && !sgx->param_obj) { + shmem = drm_gem_shmem_create(dev, SGX_PARAM_BYTES); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + sgx_shmem_lowmem(shmem); + ret = drm_gem_shmem_pin(shmem); + if (ret) { + drm_gem_object_put(&shmem->base); + return ret; + } + sgx->param_obj = &shmem->base; + } + if (!sgx_heap_per_client && !sgx->dpm_pt_obj) { + shmem = drm_gem_shmem_create(dev, pt_size); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + sgx_shmem_lowmem(shmem); + ret = drm_gem_shmem_pin(shmem); + if (ret) { + drm_gem_object_put(&shmem->base); + return ret; + } + sgx->dpm_pt_obj = &shmem->base; + } + if (sf->shared_bound) + return 0; + sf->heap_private = sgx_heap_per_client; + if (sf->heap_private) { + ret = sgx_heap_private(dev, sgx, sf, pt_size); + if (ret) + return ret; + } else { + ret = sgx_mmu_bind(&sf->mmu, sgx->param_obj, SGX_PARAM_VA, 0, + sgx->param_size, false); + if (ret) + return ret; + ret = sgx_mmu_bind(&sf->mmu, sgx->dpm_pt_obj, SGX_DPM_PT_VA, 0, + sgx->dpm_pt_size, false); + if (ret) + return ret; + } + ret = sgx_vm_reserve(sf, SGX_PARAM_VA, sgx->param_size); + if (ret) + return ret; + ret = sgx_vm_reserve(sf, SGX_DPM_PT_VA, sgx->dpm_pt_size); + if (ret) + return ret; + sf->shared_bound = true; + return 0; +} + +static void sgx_oom_pt_unmap(struct sgx_file *sf) +{ + if (!sf->oom_pt_cpu || !sf->oom_pt_obj) + return; + sgx_obj_vunmap(sf->oom_pt_obj, sf->oom_pt_cpu); + sf->oom_pt_cpu = NULL; +} + +/* psb_validate_scene_pool() drops a scene whose size changed and allocates a + * new one rather than keeping the buffer and re-describing it, so the macro + * tile array a smaller cookie now points into is never the larger scene's + * leftovers. */ +static int sgx_scene_ensure(struct drm_device *dev, struct sgx_file *sf, + struct sgx_scene *sc, u32 scene_size) +{ + u64 va = SGX_SCENE_VA + + (u64)(sc - sf->scenes) * SGX_SCENE_SLOT_SPAN; + int ret = 0; + + if (scene_size > SGX_SCENE_SLOT_SPAN) { + drm_err(dev, "a %u byte scene does not fit a slot\n", + scene_size); + return -ENOSPC; + } + if (!sc->obj || sc->size != PAGE_ALIGN(scene_size)) { + sgx_scene_unmap(sc); + sgx_drop_object(sf, &sc->obj, sc->va, sc->size); + ret = sgx_own_object(dev, sf, va, PAGE_ALIGN(scene_size), + &sc->obj, &sc->va); + if (ret) + return ret; + sc->size = PAGE_ALIGN(scene_size); + sc->cpu = NULL; + sc->cleared = false; + } + + return ret; +} + +/* The recovery's private page table, allocated the first time a recovery needs + * one rather than with the scene. Every frame paid for it otherwise, and doing + * it on the ordinary path is what stopped the DPM taking the heap at all. */ +static int sgx_oom_pt_ensure(struct drm_device *dev, struct sgx_file *sf) +{ + struct iosys_map map; + int ret; + + if (sf->oom_pt_cpu) + return 0; + ret = sgx_own_object(dev, sf, SGX_OOM_PT_VA, PAGE_SIZE, + &sf->oom_pt_obj, &sf->oom_pt_va); + if (ret) + return ret; + ret = sgx_obj_vmap(sf->oom_pt_obj, &map); + if (ret) { + drm_err(dev, "cannot map the recovery page table: %d\n", ret); + return ret; + } + sf->oom_pt_cpu = map.vaddr; + memset(sf->oom_pt_cpu, 0, SGX_OOM_PT_BYTES); + return 0; +} + +/* Drop every binding and reservation a file holds. */ +static void sgx_vm_release(struct sgx_file *sf) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sf->vm); i++) { + struct rb_node *n = rb_first(&sf->vm[i]); + + while (n) { + struct sgx_binding *b = + rb_entry(n, struct sgx_binding, node); + + n = rb_next(n); + /* A reservation carries no object: the driver's own + * mapping is torn down with the objects it owns. */ + if (b->obj) { + sgx_mmu_bind(&sf->mmu, b->obj, b->gpu_va, 0, + b->size, true); + sgx_obj_unpin(b->obj); + } + rb_erase(&b->node, &sf->vm[i]); + if (b->obj) + drm_gem_object_put(b->obj); + kfree(b); + } + } +} + +/* The addresses the driver's own objects sit at are the file's from the + * start: the shared parameter heap and the DPM's table, the recovery's page + * table and both scene slots. Reserved at the first submit instead, a client + * could bind there first; the driver's bind then replaced the client's + * entries while the client's record stayed in the tree, and its later unbind + * cleared the driver's mapping. Now such a bind is refused outright. */ +static int sgx_vm_reserve_fixed(struct sgx_file *sf) +{ + unsigned int i; + int ret; + + mutex_lock(&sf->vm_lock); + ret = sgx_vm_reserve(sf, SGX_PARAM_VA, SGX_PARAM_BYTES); + if (!ret) + ret = sgx_vm_reserve(sf, SGX_DPM_PT_VA, + sgx_dpm_pt_bytes(SGX_PARAM_VA, + (u32)(SGX_PARAM_BYTES >> 12))); + if (!ret) + ret = sgx_vm_reserve(sf, SGX_OOM_PT_VA, PAGE_SIZE); + for (i = 0; !ret && i < SGX_SCENES_PER_FILE; i++) + ret = sgx_vm_reserve(sf, SGX_SCENE_VA + i * SGX_SCENE_SLOT_SPAN, + SGX_SCENE_SLOT_SPAN); + mutex_unlock(&sf->vm_lock); + return ret; +} + +static int sgx_open(struct drm_device *dev, struct drm_file *file) +{ + struct sgx_file *sf = kzalloc(sizeof(*sf), GFP_KERNEL); + struct sgx_device *sgx = psb_sgx_of(dev); + unsigned int i; + int ret; + + if (!sf) + return -ENOMEM; + mutex_init(&sf->vm_lock); + for (i = 0; i < ARRAY_SIZE(sf->vm); i++) + sf->vm[i] = RB_ROOT; + for (i = 0; i < SGX_SCENES_PER_FILE; i++) + sf->scenes[i].ctx = -1; + if (sgx_mmu_init(&sf->mmu)) { + mutex_destroy(&sf->vm_lock); + kfree(sf); + return -ENOMEM; + } + ret = sgx_vm_reserve_fixed(sf); + if (ret) { + sgx_vm_release(sf); + sgx_mmu_fini(&sf->mmu); + mutex_destroy(&sf->vm_lock); + kfree(sf); + return ret; + } + /* Published only once it is whole. It was on sgx->files and counted in + * open_count before the page tables existed, and the failure path then + * freed it while it was still linked and without dropping the count - + * and DRM does not call postclose for an open that failed. A later + * client that stalls walks sgx->files and reads the freed file, and + * the leaked count defeats the last-client reset for good. */ + mutex_lock(&sgx->fire_lock); + sgx->open_count++; + list_add_tail(&sf->node, &sgx->files); + mutex_unlock(&sgx->fire_lock); + file->driver_priv = sf; + return 0; +} + +/* A client that exits without unbinding leaves its bindings behind, and each + * one holds a reference on its object. Without this they live until the module + * is unloaded - and the addresses stay occupied, so the next client cannot use + * them either. */ +static void sgx_postclose(struct drm_device *dev, struct drm_file *file) +{ + struct sgx_file *sf = file->driver_priv; + unsigned int i; + + if (!sf) + return; + { + struct sgx_device *sgx = psb_sgx_of(dev); + + mutex_lock(&sgx->fire_lock); + /* A render still on the core reads this file's page tables and + * the objects below, and both go away here - and so does a 2D + * job, whose sequence page is one of them. */ + (void)sgx_render_wait(dev, sgx); + /* A hardware context still naming one of this file's scenes + * would match a later scene that reused the address. Under + * fire_lock, which is what the pool is protected by. */ + for (i = 0; i < SGX_SCENES_PER_FILE; i++) + sgx_ctx_forget(&sgx->ctx, &sf->scenes[i]); + if (sgx->twod_pend.sf == sf) + sgx->twod_pend.active = false; + list_del(&sf->node); + /* The directory the BIF holds is this file's, and it is freed + * below. Until the next submit re-arms, anything the core + * translates would walk a recycled page; give it an empty + * directory instead, which faults. */ + if (sgx->regs) + sgx_mmu_point(sgx, sgx->null_pd_phys); + /* The DPM's registers describe this file's heap, and its pages + * go away below. Left named, the next client's submit would + * skip the handover and bin into a free list for memory that + * is no longer mapped. */ + if (sgx->dpm_owner == sf) + sgx->dpm_owner = NULL; + mutex_unlock(&sgx->fire_lock); + } + sgx_vm_release(sf); + for (i = 0; i < SGX_SCENES_PER_FILE; i++) { + struct sgx_scene *sc = &sf->scenes[i]; + + sgx_scene_unmap(sc); + sgx_drop_object(sf, &sc->obj, sc->va, sc->size); + } + sgx_oom_pt_unmap(sf); + sgx_drop_object(sf, &sf->oom_pt_obj, sf->oom_pt_va, PAGE_SIZE); + /* This client's heap and the free list that described it. The + * ownership was dropped above, under fire_lock. */ + if (sf->heap_obj) { + sgx_mmu_bind(&sf->mmu, sf->heap_obj, SGX_PARAM_VA, 0, + SGX_PARAM_BYTES, true); + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(sf->heap_obj)); + drm_gem_object_put(sf->heap_obj); + sf->heap_obj = NULL; + } + if (sf->heap_pt_obj) { + sgx_mmu_bind(&sf->mmu, sf->heap_pt_obj, SGX_DPM_PT_VA, 0, + psb_sgx_of(dev)->dpm_pt_size, true); + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(sf->heap_pt_obj)); + drm_gem_object_put(sf->heap_pt_obj); + sf->heap_pt_obj = NULL; + } + if (sf->twod_fence_cpu && sf->twod_fence_obj) + sgx_obj_vunmap(sf->twod_fence_obj, sf->twod_fence_cpu); + sf->twod_fence_cpu = NULL; + sgx_drop_object(sf, &sf->twod_fence_obj, SGX_TWOD_BASE_VA, PAGE_SIZE); + sgx_mmu_fini(&sf->mmu); + atomic64_sub(atomic64_read(&sf->scanout_gtt), + &psb_sgx_of(dev)->scanout_gtt); + mutex_destroy(&sf->vm_lock); + kfree(sf); + file->driver_priv = NULL; + + /* Last one out puts the core back the way probe found it. */ + { + struct sgx_device *sgx = psb_sgx_of(dev); + bool last; + + mutex_lock(&sgx->fire_lock); + if (sgx->open_count) + sgx->open_count--; + last = !sgx->open_count && sgx->dpm_dirty; + if (last) + sgx->dpm_dirty = false; + mutex_unlock(&sgx->fire_lock); + if (last && sgx_reset) + sgx_core_restart(sgx); + } +} + +static int sgx_ioctl_vm_bind(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_vm_bind *args = data; + struct sgx_file *sf = file->driver_priv; + struct sgx_device *sgx = psb_sgx_of(dev); + struct drm_gem_object *obj; + struct sgx_binding *b; + int ret = 0; + + if (args->window >= ARRAY_SIZE(sgx_windows)) + return -EINVAL; + if (!sgx_flags_ok(args->flags, SGX_BIND_UNBIND)) + return -EINVAL; + if (!sgx_pad_ok(args->pad)) + return -EINVAL; + if (!args->size || (args->size & ~PAGE_MASK) || + (args->gpu_va & ~PAGE_MASK) || (args->offset & ~PAGE_MASK)) + return -EINVAL; + if (!sgx_in_window(args->gpu_va, args->size, + sgx_windows[args->window].start, + sgx_windows[args->window].size)) + return -ERANGE; + + obj = drm_gem_object_lookup(file, args->handle); + if (!obj) + return -ENOENT; + /* By subtraction: both are u64 from userspace and only the alignment + * of the offset is checked, so the sum wraps and a range far past the + * object passes. sgx_mmu_bind() then walks the page array from that + * offset. */ + if (args->offset > obj->size || + args->size > obj->size - args->offset) { + drm_gem_object_put(obj); + return -EINVAL; + } + + if (!sf) { + drm_gem_object_put(obj); + return -EINVAL; + } + + /* Against the fire, not only against other binds. + * + * A bind rewrites page-table entries and pulses the BIF's invalidate, + * and the submit holds fire_lock across the whole tiler-and-render + * sequence without taking vm_lock - so a client rebinding a texture + * at the fixed address the frame names could move the translation + * out from under a render already reading it. Ordinary for a client + * that changes texture between frames, which is why the one that does + * it constantly is the one whose renders did not finish. The submit + * takes the two in this order as well; nothing takes them the other + * way round. */ + mutex_lock(&sf->vm_lock); + mutex_lock(&sgx->fire_lock); + /* And against a render that was fired and not waited for: holding + * fire_lock no longer means the core is idle, so the translation this + * is about to change could still be one a render is walking. */ + (void)sgx_render_wait(dev, sgx); + if (args->flags & SGX_BIND_UNBIND) { + b = sgx_vm_find(&sf->vm[args->window], args->gpu_va, args->size); + if (!b || b->obj != obj) { + /* Naming an address a different object holds is a + * userspace bookkeeping error, and refusing it + * silently drops the frame that follows. */ + drm_err(dev, + "unbind: win %u va 0x%llx size %llu handle %u: %s\n", + args->window, + (unsigned long long)args->gpu_va, + (unsigned long long)args->size, args->handle, + b ? "another object is bound there" : + "nothing is bound there"); + ret = -ENOENT; + } else { + sgx_mmu_bind(&sf->mmu, b->obj, b->gpu_va, 0, b->size, + true); + sgx_obj_unpin(b->obj); + rb_erase(&b->node, &sf->vm[args->window]); + drm_gem_object_put(b->obj); + kfree(b); + } + } else { + b = kzalloc(sizeof(*b), GFP_KERNEL); + if (!b) { + ret = -ENOMEM; + } else { + b->gpu_va = args->gpu_va; + b->size = args->size; + b->obj = obj; + b->window = args->window; + ret = sgx_vm_insert(&sf->vm[args->window], b); + if (!ret) { + /* The pages have to exist before they can be + * mapped; a shmem object allocates them + * lazily. On failure the binding comes back + * out of the tree, so there is exactly one + * kfree on every path. */ + ret = sgx_obj_pin(obj); + if (!ret) + ret = sgx_mmu_bind(&sf->mmu, obj, + args->gpu_va, + args->offset, + args->size, false); + if (ret) + rb_erase(&b->node, + &sf->vm[args->window]); + } + if (ret) + kfree(b); + else + drm_gem_object_get(obj); + } + } + /* The BIF caches translations, and the invalidate was only being + * pulsed when the directory is installed. Binding a different object + * at an address something was already bound at then left the old + * translation in place: the render target and the texture both live at + * addresses the frame's relocations name, so rebinding at them is + * ordinary rather than exceptional, and what came back was the object + * that used to be there. */ + if (!ret) + sgx_mmu_invalidate(sgx); + mutex_unlock(&sgx->fire_lock); + mutex_unlock(&sf->vm_lock); + drm_gem_object_put(obj); + return ret; +} + +/* Does any single binding in the parameter window cover scene_size bytes? The + * scene is one contiguous allocation - cookie[7] through cookie[11] are offsets + * into it - so a submit whose heap is too small points the DPM's page and state + * tables past the end of the object, which faults rather than renders. */ +/* The first binding in a window, and its size. Used to find the scene and the + * parameter heap without userspace naming them again in the submit. */ +static u64 __maybe_unused sgx_window_va(struct sgx_file *sf, + unsigned int window, u64 *size) +{ + struct rb_node *n; + u64 va = 0; + + if (!sf) + return 0; + mutex_lock(&sf->vm_lock); + n = rb_first(&sf->vm[window]); + if (n) { + struct sgx_binding *b = rb_entry(n, struct sgx_binding, node); + + va = b->gpu_va; + if (size) + *size = b->size; + } + mutex_unlock(&sf->vm_lock); + return va; +} + + +/* How long a fire may take before the core is declared stuck. sgxtri waits + * two seconds, which is what this was; a glmark2 scene of several thousand + * triangles at this part's fill rate needs longer, and gave up mid-render with + * the progress signature still changing - "core still working". The wait only + * bounds how quickly a genuinely locked core is noticed, so it is cheap to be + * generous, and tunable for a caller that wants it tighter. */ +/* A second. Nothing this part renders correctly takes that long, so a fire + * that does is a frame that failed - waiting ten seconds on it only delayed + * the recovery and left the client blocked in the ioctl meanwhile. */ +static unsigned int sgx_fire_timeout_ms = 1000; +module_param_named(sgx_fire_timeout_ms, sgx_fire_timeout_ms, uint, 0644); +MODULE_PARM_DESC(sgx_fire_timeout_ms, + "milliseconds a tiler or render fire may take (default 1000)"); + +#define SGX_FIRE_TIMEOUT_US (sgx_fire_timeout_ms * 1000u) + +/* How many further waits a render may have while it is visibly still + * advancing. A locked core is still noticed in one timeout, because its + * progress signature does not move; a slow one is no longer destroyed. */ +/* One wait slice, and how many consecutive frozen checks declare the core + * stopped. A stall is then noticed in about a tenth of a second rather than at + * the whole timeout, and a slow render is never cut off while it advances. */ +#define SGX_FIRE_SLICE_US 20000u +#define SGX_FIRE_FROZEN_MAX 3u + +/* The same figure, as a parameter, so a suspected false freeze can be told + * from a real one without a rebuild: raise it and see whether the frame comes + * back. */ +static unsigned int sgx_frozen_max = SGX_FIRE_FROZEN_MAX; +module_param_named(sgx_frozen_max, sgx_frozen_max, uint, 0644); +MODULE_PARM_DESC(sgx_frozen_max, + "consecutive frozen checks that declare the core stopped " + "(default 3, one per 20 ms slice)"); + +static unsigned int sgx_fire_extensions = 16; +module_param_named(sgx_fire_extensions, sgx_fire_extensions, uint, 0644); +MODULE_PARM_DESC(sgx_fire_extensions, + "further timeouts a render that is still advancing may have " + "(default 16)"); + +/* Whether the core is getting anywhere: the progress signature sampled three + * times over 400 us. Identical three times is a core that has stopped. */ +static bool sgx_making_progress(struct sgx_device *sgx) +{ + u32 a = sgx_progress_sig(sgx), b, c; + + udelay(200); + b = sgx_progress_sig(sgx); + udelay(200); + c = sgx_progress_sig(sgx); + return !(a == b && b == c); +} + +/* Whether a render sleeps on the interrupt or spins on EVENT_STATUS. + * + * Spinning out a 5.4 ms render costs the whole 5.4 ms of a core, and the + * vertex path this part runs is software on that same core: a perf profile of + * ioquake3 put 23.9% of all CPU time inside this module, nearly all of it + * under sgx_poll_reg(). The parameter is live - it is re-read once a frame and + * the hardware enable mask follows it - so the old behaviour is one write to + * sysfs away on a running machine. */ +static bool sgx_irq_wait = true; +module_param_named(sgx_irq_wait, sgx_irq_wait, bool, 0644); +MODULE_PARM_DESC(sgx_irq_wait, + "sleep on the render-complete interrupt instead of polling " + "for it (default on)"); + +/* How often a sleeping wait re-reads EVENT_STATUS regardless. The interrupt is + * what normally ends the wait; this bounds the damage if one is lost, or if + * this part turns out not to raise the line for END_RENDER at all - the wait + * then degrades to a sleeping poll at this period instead of failing. */ +static unsigned int sgx_irq_recheck_us = 2000; +module_param_named(sgx_irq_recheck_us, sgx_irq_recheck_us, uint, 0644); +MODULE_PARM_DESC(sgx_irq_recheck_us, + "how often a sleeping wait re-reads the status register " + "(default 2000)"); + +/* What the driver asks the interrupt line for. Only the render's completion: + * an event added here can no longer be polled for anywhere in the driver, + * because gma_sgx_interrupt() acknowledges it out of EVENT_STATUS before any + * poll can see it. TA_FINISHED, TA_TERMINATE, the DPM handshakes and the two + * out-of-memory bits stay polled and stay visible. */ +#define SGX_IRQ_EVENTS (SGX_F_EV_END_RENDER | SGX_2D_EV_COMPLETE) + +/* Line the hardware enable mask up with the parameter. Called under fire_lock + * from the arm, so it cannot race a wait of its own. + * + * The two orders are not interchangeable. Enabling publishes the software mask + * first, disabling takes the hardware down first, so the only state the window + * can be caught in is "hardware quiet, driver expecting an interrupt" - which + * the status re-read covers. The reverse, the handler acknowledging an event + * the driver has stopped listening for, would lose it outright. */ +static void sgx_irq_sync(struct sgx_device *sgx) +{ + u32 want = sgx_irq_wait ? SGX_IRQ_EVENTS : 0; + + if (READ_ONCE(sgx->event_irq_mask) == want) + return; + if (want) { + WRITE_ONCE(sgx->event_irq_mask, want); + gma_sgx_irq_enable(sgx->ddev, want); + } else { + gma_sgx_irq_enable(sgx->ddev, 0); + WRITE_ONCE(sgx->event_irq_mask, 0); + } +} + +/* Drop any record of the event before the fire that will raise it, and pick up + * a change to the parameter while there. The handler records every event it + * acknowledges, including ones nothing was waiting for, so a bit left here + * from an earlier frame would end the coming wait before it started. */ +static void sgx_event_arm(struct sgx_device *sgx, u32 ev) +{ + unsigned long flags; + + sgx_irq_sync(sgx); + spin_lock_irqsave(&sgx->event_lock, flags); + sgx->event_pending &= ~ev; + spin_unlock_irqrestore(&sgx->event_lock, flags); +} + +static bool sgx_event_take(struct sgx_device *sgx, u32 ev) +{ + unsigned long flags; + bool got; + + spin_lock_irqsave(&sgx->event_lock, flags); + got = (sgx->event_pending & ev) == ev; + sgx->event_pending &= ~ev; + spin_unlock_irqrestore(&sgx->event_lock, flags); + return got; +} + +/* The sleeping form of sgx_wait_event()/sgx_wait_event_oom(): give the core up + * until the interrupt handler records the event, waking every + * sgx_irq_recheck_us to read the status register as well. + * + * The race a sleeping wait has to survive is the event arriving before the + * sleeper is on the queue - between the fire and this call, or between two + * passes of the loop. It cannot be lost. The handler stores into event_pending + * under event_lock and only then wakes; wait_event_timeout() queues the task + * before it evaluates the condition. A record that lands before the queueing + * is read by the condition, one that lands after it delivers a wakeup, and one + * that landed for an earlier frame was dropped by sgx_event_arm(). The status + * re-read at the top of every pass covers the remaining case, an interrupt + * that never comes at all. + * + * oom_ok mirrors sgx_wait_event_oom(): report an exhausted parameter heap + * rather than waiting the timeout out on a tiler that is not going to finish. + */ +static int sgx_wait_event_sleep(struct sgx_device *sgx, u32 ev, + unsigned int timeout_us, bool oom_ok) +{ + const struct sgx_kick_env *e = &sgx->scene.k; + u64 start = ktime_to_us(ktime_get()); + + for (;;) { + bool irq = sgx_event_take(sgx, ev); + u32 st = e->rd(e->priv, SGX_K_EVENT_STATUS); + u64 waited; + unsigned int rest; + unsigned long tick; + + if (irq || (st & ev) == ev) { + /* The handler acknowledged the event itself, so this + * write is usually a no-op on a bit that is already + * clear; the out-of-memory bits are not on the + * interrupt line and are still latched, and they are + * dropped here exactly as sgx_wait_event() does. */ + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, + ev | (st & SGX_F_EV_OOM_MASK)); + if (irq) + sgx->irq_events++; + else + sgx->poll_events++; + return 0; + } + if (oom_ok) { + if (st & SGX_F_EV_OOM_HARD) { + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, + st & SGX_F_EV_OOM_MASK); + return 1; + } + if (st & SGX_F_EV_DPM_MEM_THRESH) + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, + SGX_F_EV_DPM_MEM_THRESH); + } + waited = ktime_to_us(ktime_get()) - start; + if (waited >= timeout_us) + return -EBUSY; + rest = (unsigned int)(timeout_us - waited); + if (sgx_irq_recheck_us && sgx_irq_recheck_us < rest) + rest = sgx_irq_recheck_us; + /* At least one tick, so a sub-jiffy remainder still sleeps + * rather than turning the loop back into a spin. The recheck + * period is therefore a jiffy at best; that only bounds the + * fallback, since the interrupt is what normally ends the + * wait and does not wait for a tick. */ + tick = usecs_to_jiffies(rest); + if (!tick) + tick = 1; + wait_event_timeout(sgx->event_wq, + (READ_ONCE(sgx->event_pending) & ev) == ev, + tick); + } +} + +/* One wait for an event, sleeping when it is on the interrupt line and the + * parameter allows it and spinning as before when it is not. */ +static int sgx_wait_render(struct sgx_device *sgx, u32 ev, + unsigned int timeout_us) +{ + if ((READ_ONCE(sgx->event_irq_mask) & ev) == ev) + return sgx_wait_event_sleep(sgx, ev, timeout_us, false); + return sgx_wait_event(&sgx->scene.k, ev, timeout_us); +} + +/* sgx_wait_event_no_oom() with the same choice of wait: inside a recovery + * there is no tiler task to report an out-of-memory against, so it is re-armed + * a bounded number of times rather than answered. */ +static int sgx_wait_render_no_oom(struct sgx_device *sgx, u32 ev, + unsigned int timeout_us) +{ + unsigned int tries; + + if ((READ_ONCE(sgx->event_irq_mask) & ev) != ev) + return sgx_wait_event_no_oom(&sgx->scene.k, ev, timeout_us); + + for (tries = 0; tries < SGX_F_NO_OOM_RETRIES; tries++) { + int ret = sgx_wait_event_sleep(sgx, ev, timeout_us, true); + + if (ret <= 0) + return ret; + } + return -EBUSY; +} + +/* One out-of-memory recovery: a partial render, then the tiler resumed. + * + * Transcribed from frame_oom_cycle() in tools/baremetal/sgxtri.c:4522-4609, + * which is what recovers every scene the bare-metal side renders. The phases, + * in the order the hardware needs them: + * + * 1 stop the tiler and pick the mode sgx_oom_abort() + * 2 wait for it to acknowledge TA_TERMINATE + * 3 raster what is already binned answers RASTER + * 4 drain the DPM across a context swap answers TA + * 5 resume the tiler sgx_oom_partial(), via XHW_OOM + * + * A first out-of-memory takes both raster passes; a later one only the second, + * because cookie[13] bit 31 is already set by then. The flags carry + * XHW_OOM from here to the end of the frame, which is what routes the closing + * fire into the resume rather than into an ordinary bind. + * + * SGX_TA_ZLS_BASE is programmed first. The resume enables the DPM's Z + * load/store, but the only place that register is written is the clean-bind + * branch, which every recovery fire skips - so it would otherwise carry + * whatever the last clean bind left. sgxtri:4529-4540 does the same and + * records that on hardware it made no measurable difference; it is transcribed + * because leaving it out is a divergence, not because it is known to matter. + */ +/* The tiler writes the region headers, so a depth load can only be asked for + * between the tiler finishing and the render firing. Returns 0 if the array is + * not reachable. */ +static u32 *sgx_scene_regions(struct sgx_scene *sc, const u32 *cookie, u32 *n) +{ + /* One header per sample tile, so a 2x2 render has four times as many + * as the macro-tile geometry alone would say. */ + u32 count = (cookie[0] ? 16u : 4u) * cookie[1] * + (sc->samples ? sc->samples * sc->samples : 1u); + + if (!sc->cpu || !count || + (u64)cookie[7] + (u64)count * SGX_RH_DWORDS * 4u > sc->size) + return NULL; + *n = count; + return (u32 *)((char *)sc->cpu + cookie[7]); +} + +static int sgx_oom_cycle(struct drm_device *dev, struct sgx_device *sgx, + struct sgx_file *sf, struct sgx_scene *sc, + const u32 *ras, u32 ras_dwords, + const u32 *oom, u32 oom_dwords, u32 heap_va, + u32 *fire_flags, unsigned int n) +{ + u32 *cookie = sc->cookie; + u32 bca = 0, rca = 0, oflags = 0; + unsigned int pass; + int ret; + + ret = sgx_oom_pt_ensure(dev, sf); + if (ret) + return ret; + /* Per file, so it follows the address space rather than probe. */ + sgx->scene.oom_page_table = (u32)sf->oom_pt_va; + sgx->scene.regions = sf->oom_pt_cpu; + + writel(heap_va + cookie[9], sgx->regs + SGX_S_TA_ZLS_BASE); + + sgx_oom_abort(&sgx->scene, cookie, &bca, &rca, &oflags); + *fire_flags |= oflags; + if (bca != SGX_S_RASTER_BLOCK || rca != SGX_S_TA) { + drm_err(dev, "the abort asked for %u/%u, not a partial render\n", + bca, rca); + return -EIO; + } + + if (sgx_wait_event_no_oom(&sgx->scene.k, SGX_F_EV_TA_TERMINATE, + SGX_FIRE_TIMEOUT_US)) { + drm_err(dev, "the tiler never acknowledged the abort\n"); + return -EBUSY; + } + + for (pass = 0; pass < 2; pass++) { + sgx_event_arm(sgx, SGX_F_EV_END_RENDER); + ret = sgx_fire_raster(&sgx->scene, (unsigned int)sc->ctx, + ras, ras_dwords, + cookie, heap_va, oom, oom_dwords, + *fire_flags, &rca); + if (ret) + return ret; + if (sgx_wait_render_no_oom(sgx, SGX_F_EV_END_RENDER, + SGX_FIRE_TIMEOUT_US)) { + drm_err(dev, "the partial render did not finish\n"); + return -EBUSY; + } + /* A pass that answered RASTER freed nothing, so there is no + * handback to wait for; anything else has to drain before the + * tiler is let go, or it bins into pages still being + * reclaimed. */ + if (rca != SGX_S_RASTER && + sgx_wait_event_no_oom(&sgx->scene.k, + SGX_F_EV_DPM_3D_MEM_FREE, + SGX_FIRE_TIMEOUT_US)) { + drm_err(dev, "the partial render never freed its pages\n"); + return -EBUSY; + } + if (rca == SGX_S_TA) + break; + if (rca != SGX_S_RASTER) { + drm_err(dev, "the partial render answered %u\n", rca); + return -EIO; + } + } + if (rca != SGX_S_TA) { + drm_err(dev, "the partial render never handed the tiler back\n"); + return -EIO; + } + + /* The resume, reached because fire_flags still carries XHW_OOM. There + * is no TA kick on this path - the tiler is let go by the closing + * DPM_MODE write inside sgx_oom_partial(). */ + ret = sgx_scene_switch_fire(&sgx->scene, *fire_flags, + (unsigned int)sc->ctx, cookie, + heap_va, SGX_S_SCENE_ENGINE_TA, + SGX_S_SCENE_FLAG_SETUP | + SGX_S_SCENE_FLAG_SETUP_ONLY | + SGX_S_SCENE_FLAG_DIRTY | + SGX_S_SCENE_FLAG_COMPLETE, NULL, 0, &rca); + if (ret) + return ret; + if (sgx_dpm_watch) + drm_info(dev, "recovery %u: %s, ZLSCTL %08x\n", n, + (cookie[13] & 0x40000000u) ? + "came back with under 32 pages" : "headroom recovered", + readl(sgx->regs + SGX_S_DPM_STATE)); + return 0; +} + +/* Hand back a fence for the frame just fired. It is created signalled, which + * is only true because asking for one makes the submit wait in line for the + * render however SGX_SUBMIT_DEFER_WAIT was set - see the fire path. */ +static int sgx_signalled_fence_fd(struct sgx_device *sgx, __s32 *out) +{ + struct dma_fence *f; + struct sync_file *sf; + int fd; + + *out = -1; + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return -ENOMEM; + dma_fence_init(f, &sgx_fence_ops, &sgx->fence_lock, sgx->fence_ctx, + ++sgx->fence_seqno); + dma_fence_signal(f); + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + dma_fence_put(f); + return fd; + } + sf = sync_file_create(f); + dma_fence_put(f); + if (!sf) { + put_unused_fd(fd); + return -ENOMEM; + } + fd_install(fd, sf->file); + *out = fd; + return 0; +} + +/* Wait out a render the submit fired but did not stay for. + * + * Everything that has to follow the fire is here: the progress-checked wait + * for END_RENDER, the fault report and the recovery when it never arrives, the + * timing, and the dealloc handshake the next frame's tiler depends on. + * Splitting it from the fire is what lets the ioctl come back once the work is + * queued. + * + * Called with fire_lock held, and with no other lock: the recovery inside it + * takes the same ones the fire path does. Every path that can touch what the + * render is still reading goes through here first - the next submit before it + * reprograms anything, a rebind, the wait ioctl, close. */ +/* Drain the device: a 2D job that was fired and not waited for, then a render + * that was. Every path that has to see memory settled goes through here. + * + * The 2D job first, and not because the two can both be outstanding - they + * cannot, since a submit drains both before it fires and a job drains the + * render before it pushes - but because it is the cheaper test. */ +static int sgx_render_wait(struct drm_device *dev, struct sgx_device *sgx) +{ + int t = sgx_twod_drain(dev, sgx); + int r = sgx_render_wait_only(dev, sgx); + + return r ? r : t; +} + +static int sgx_render_wait_only(struct drm_device *dev, struct sgx_device *sgx) +{ + struct sgx_file *sf = sgx->pend.sf; + struct sgx_scene *sc = sgx->pend.sc; + ktime_t t_in = sgx->pend.t_in; + ktime_t t_ta = sgx->pend.t_ta; + ktime_t t_ras = sgx->pend.t_ras; + u32 rca = sgx->pend.rca; + u32 width = sgx->pend.w, height = sgx->pend.h; + bool vistest = sgx->pend.vistest; + int ret; + + if (!sgx->pend.active) + return 0; + /* The mapping can go away while a frame is outstanding, and the wait + * is nothing but register reads. Nothing is recoverable from here, so + * the frame is dropped rather than polled at an unmapped address. */ + if (!sgx->regs) { + sgx_ctx_put(&sgx->ctx, sgx->pend.ctx); + sgx->pend.ctx = -1; + sgx->pend.active = false; + return -ENODEV; + } + sgx_kick_env_init(&sgx->scene.k, sgx, sgx->use_ctrl); + + ret = sgx_wait_render(sgx, SGX_F_EV_END_RENDER, + SGX_FIRE_SLICE_US); + /* A render that is still advancing is slow, not stuck. It used + * to be recovered anyway, which aborts the sweep part way: the + * tiles that had finished kept their pixels and the rest kept + * what was loaded back, so the frame came out split down the + * middle with the far side wrong. That is what ioquake3's + * heaviest frames looked like - and they need several seconds + * at this part's fill rate. Ten seconds outright was tried + * before and reverted because it delays noticing a core that + * really is stuck; extending only while the signature moves + * keeps that notice at one timeout. */ + if (ret) { + unsigned int waited = SGX_FIRE_SLICE_US; + unsigned int cap = SGX_FIRE_TIMEOUT_US * + (1u + sgx_fire_extensions); + unsigned int frozen = 0; + + /* Short slices with a progress check between them. + * A core that has stopped is then noticed in tens of + * milliseconds instead of at the timeout - there is no + * interrupt for a stall, only the BIF fault has one - + * while a render that is merely slow is left alone for + * as long as its signature keeps moving. */ + while (ret && waited < cap) { + if (sgx_making_progress(sgx)) + frozen = 0; + else if (++frozen >= (sgx_frozen_max ? sgx_frozen_max : + SGX_FIRE_FROZEN_MAX)) + break; + ret = sgx_wait_render(sgx, + SGX_F_EV_END_RENDER, + SGX_FIRE_SLICE_US); + waited += SGX_FIRE_SLICE_US; + } + if (sgx_timing) + drm_info(dev, "render %s after %u us\n", + ret ? (frozen >= (sgx_frozen_max ? + sgx_frozen_max : + SGX_FIRE_FROZEN_MAX) ? + "stopped" : "timed out") : + "finished", waited); + } + /* Before anything below can take a lock or recover: the counters are + * this render's and the next fire has to start from zero. A render + * that did not finish is harvested too - the count is then partial, + * which the caller learns from the lost-render report, but leaving the + * registers loaded would add this frame to the next one's. + * + * Nothing clears the counters during a partial render, so a query that + * spans one keeps its count. The vendor loses it: without + * SGX_FEATURE_VISTEST_IN_MEMORY its partial render clears all eight + * (spm.asm:2276-2287). Do not copy that here. */ + if (vistest) + sgx_vistest_harvest(sgx, sf); + if (sgx_timing) { + ktime_t now = ktime_get(); + + s64 r = ktime_us_delta(now, t_ras); + + sgx->t_pre += ktime_us_delta(t_ta, t_in); + sgx->t_ta += ktime_us_delta(t_ras, t_ta); + sgx->t_ras += r; + /* An average over submits of two different jobs - a + * client's scene and the server's composite of it - + * hides which one costs. */ + if (!sgx->t_n || r < sgx->t_ras_min) + sgx->t_ras_min = r; + if (!sgx->t_n || r > sgx->t_ras_max) + sgx->t_ras_max = r; + if (++sgx->t_n >= 64) { + /* Named, because a deferred wait does not measure the + * render: it measures the fire to whenever something + * came back for it, and the caller's own CPU work is + * inside that. */ + drm_info(dev, + "timing over %u frames: setup %llu us, tiler %llu us, render %llu us (min %lld max %lld, %ux%u, %s wait)\n", + sgx->t_n, div_u64(sgx->t_pre, sgx->t_n), + div_u64(sgx->t_ta, sgx->t_n), + div_u64(sgx->t_ras, sgx->t_n), + sgx->t_ras_min, sgx->t_ras_max, + width, height, + sgx_async ? "deferred" : "in-line"); + sgx->t_pre = sgx->t_ta = sgx->t_ras = 0; + sgx->t_n = 0; + } + } + if (sgx_regdump && + (!sgx_regdump_w || width == sgx_regdump_w) && + sc->cpu) { + const u32 *p = (const u32 *)((char *)sc->cpu + + ((size_t)sc->clear_start << 12)); + size_t k, dw = (size_t)sc->clear_pages << 10; + struct iosys_map pm; + const u32 *heap = NULL; + unsigned int shown = 0; + + struct drm_gem_object *po = sf->heap_private ? sf->heap_obj + : sgx->param_obj; + + if (po && !sgx_obj_vmap(po, &pm)) + heap = pm.vaddr; + + for (k = 0; k < dw; k++) { + if (!p[k]) + continue; + drm_info(dev, "rgn %u %08x\n", + (unsigned)k, p[k]); + /* The record's low 24 bits name parameter + * memory in 32-byte units; show what the ISP + * reads from the block. */ + if (heap && shown < 3) { + u64 at = (u64)(p[k] & 0x00ffffffu) << 5; + unsigned int q; + + shown++; + if (at + 64 > sgx->param_size) + continue; + for (q = 0; q < 16; q++) + drm_info(dev, + "par %llu+%u %08x\n", + at, q * 4, + heap[(at >> 2) + q]); + } + } + if (heap) + sgx_obj_vunmap(po, (void *)heap); + } + if (sgx_regdump && + (!sgx_regdump_w || width == sgx_regdump_w)) { + static const struct { u32 first, last; } blk[] = { + { 0x0000, 0x00fc }, { 0x0150, 0x03fc }, + { 0x0400, 0x0bfc }, { 0x0c00, 0x0ffc }, + }; + unsigned int b, o; + + for (b = 0; b < ARRAY_SIZE(blk); b++) + for (o = blk[b].first; o <= blk[b].last; o += 4) + drm_info(dev, "pre %04x %08x\n", o, + readl(sgx->regs + o)); + sgx_regdump = false; + } + + if (ret) { + u32 ev = readl(sgx->regs + SGX_K_EVENT_STATUS); + u32 s1 = sgx_progress_sig(sgx), s2, s3; + + udelay(200); + s2 = sgx_progress_sig(sgx); + udelay(200); + s3 = sgx_progress_sig(sgx); + + if (ev & SGX_F_EV_OOM_MASK) + drm_err(dev, + "render ran out of parameter memory (events 0x%08x)\n", + ev); + else + drm_err(dev, + "render did not finish (events 0x%08x), core %s: 0x%08x 0x%08x 0x%08x\n", + ev, + (s1 == s2 && s2 == s3) ? + "stalled" : "still working", + s1, s2, s3); + drm_err(dev, + " frame %llu for %s[%d]; DPM free 0x%08x state 0x%08x, TA state 0x%08x, ISP ctrl 0x%08x\n", + sgx->frames, current->comm, current->pid, + readl(sgx->regs + SGX_S_DPM_FREE), + readl(sgx->regs + SGX_S_DPM_STATE), + readl(sgx->regs + SGX_S_TA_STATE), + readl(sgx->regs + SGX_S_ISP_CTRL)); + drm_err(dev, " events2 0x%08x events3 0x%08x\n", + readl(sgx->regs + SGX_CR_EVENT_STATUS2), + readl(sgx->regs + 0x0138)); + /* CLKGATESTATUS names the block that still has work, + * which is what tells a stalled unit from an idle + * core; the rest say what it is waiting on. */ + drm_err(dev, + " clkgate 0x%08x (isp %u tsp %u ta %u dpm %u use %u), bif req 0x%08x bank 0x%08x, pds pc 0x%08x, 3d 0x%08x, 2d 0x%08x\n", + readl(sgx->regs + SGX_CR_CLKGATESTATUS), + !!(readl(sgx->regs + SGX_CR_CLKGATESTATUS) & SGX_CR_CLKGATE_ISP), + !!(readl(sgx->regs + SGX_CR_CLKGATESTATUS) & SGX_CR_CLKGATE_TSP), + !!(readl(sgx->regs + SGX_CR_CLKGATESTATUS) & SGX_CR_CLKGATE_TA), + !!(readl(sgx->regs + SGX_CR_CLKGATESTATUS) & SGX_CR_CLKGATE_DPM), + !!(readl(sgx->regs + SGX_CR_CLKGATESTATUS) & SGX_CR_CLKGATE_USE), + readl(sgx->regs + SGX_CR_BIF_MEM_REQ_STAT), + readl(sgx->regs + SGX_CR_BIF_BANK_STATUS), + readl(sgx->regs + SGX_CR_PDS_PC_BASE), + readl(sgx->regs + SGX_S_3D_STATUS), + readl(sgx->regs + SGX_CR_2D_BLIT_STATUS)); + sgx_report_faults_for(sgx, sf, "render"); + sgx_stall_recover(sgx, sf, + "a render that did not finish"); + goto done; + } + + /* The render ending is only half of a frame. psb_raster_done() + * runs when RASTER_DONE and DEALLOC have both arrived, and + * DEALLOC is DPM_3D_MEM_FREE: the 3D core telling the DPM it + * has finished handing parameter pages back. Without this wait + * the next frame's tiler bins into pages still being reclaimed + * underneath it. A fire that answered RASTER frees nothing, so + * that case is skipped - psb_schedule.c:718-720 synthesises it + * rather than waiting. */ + if (sgx_dpm_watch) + drm_info(dev, + "frame %llu ended: DPM free 0x%08x state 0x%08x\n", + sgx->frames, + readl(sgx->regs + SGX_S_DPM_FREE), + readl(sgx->regs + SGX_S_DPM_STATE)); + if (rca != SGX_S_RASTER) { + ret = sgx_wait_event(&sgx->scene.k, + SGX_F_EV_DPM_3D_MEM_FREE, + SGX_FIRE_TIMEOUT_US); + if (ret) { + drm_err(dev, + "the 3D core never freed its parameter memory\n"); + sgx_report_faults_for(sgx, sf, "dealloc"); + sgx_stall_recover(sgx, sf, + "a deallocation that did not finish"); + goto done; + } + } + + sgx->stalls = 0; +done: + /* The render is over however it ended, so the context it held is free. + * Left held, the next submit would be refused a context rather than + * given the one nothing is using. */ + sgx_ctx_put(&sgx->ctx, sgx->pend.ctx); + sgx->pend.ctx = -1; + sgx->pend.active = false; + return ret; +} + +/* Keep a copy of the DPM page table the one INIT handshake filled, so a later + * client's own table can start from it. Taken before the first frame runs, + * because the first frame is what changes it. */ +static int sgx_dpm_seed_capture(struct sgx_device *sgx, struct sgx_file *sf) +{ + struct drm_gem_object *obj = sf->heap_private ? sf->heap_pt_obj + : sgx->dpm_pt_obj; + struct iosys_map map; + int ret; + + if (!sf->heap_private || sgx->dpm_pt_seed || !obj) + return 0; + sgx->dpm_pt_seed = vmalloc(sgx->dpm_pt_size); + if (!sgx->dpm_pt_seed) + return -ENOMEM; + ret = sgx_obj_vmap(obj, &map); + if (ret) { + vfree(sgx->dpm_pt_seed); + sgx->dpm_pt_seed = NULL; + return ret; + } + memcpy(sgx->dpm_pt_seed, map.vaddr, sgx->dpm_pt_size); + sgx_obj_vunmap(obj, map.vaddr); + sgx->dpm_pt_seed_size = sgx->dpm_pt_size; + memcpy(sf->heap_cookie, sgx->dpm_cookie, sizeof(sf->heap_cookie)); + sf->heap_ready = true; + return 0; +} + +/* Hand the DPM to this client's heap. + * + * The registers are device-wide and the pages behind them are not: the base, + * span and tail the load writes are addresses in the submitting file's own + * page tables, and the free list they describe is in that file's own DPM page + * table. Re-issuing the register load - without INIT, which the hardware takes + * once per power-on - is what makes them describe this client's heap. It is + * the same save and restore the vendor's microkernel does on a pointer compare + * against the loaded HWPBDesc (sgx_utils.asm:978-1097, 3d.asm:2554-2610). + * + * Safe only between frames, which is where it runs: the caller holds + * fire_lock and has already waited out any deferred render, including the + * DPM_3D_MEM_FREE that hands the parameter pages back - so the free list is + * whole and its initial head and tail are the state, not a stale copy of it. + * That is why there is no read-back of the DPM's counters to match the + * vendor's Store half. */ +static int sgx_dpm_take(struct drm_device *dev, struct sgx_device *sgx, + struct sgx_file *sf) +{ + int ret; + + if (!sf->heap_private || sgx->dpm_owner == sf) + return 0; + /* Nothing to hand over yet: the INIT load has not run, or this client + * bound its heap before there was a table to seed it from. */ + if (!sf->heap_ready) { + drm_err(dev, "the client's parameter heap was never seeded\n"); + return -EIO; + } + ret = sgx_ta_mem_load(&sgx->scene.k, (u32)SGX_DPM_PT_VA, + (u32)SGX_PARAM_VA, + SGX_H_MEM_FLAG_TA | SGX_H_MEM_FLAG_RASTER | + SGX_H_MEM_FLAG_HOSTA | SGX_H_MEM_FLAG_HOSTD, + sf->heap_cookie); + if (ret) { + drm_err(dev, "the DPM did not take this client's heap\n"); + sgx_stall_recover(sgx, sf, "a heap handover the DPM refused"); + return ret; + } + sgx->dpm_owner = sf; + return 0; +} + +/* Is this object bound anywhere in the file's address space? */ +static bool sgx_obj_is_bound(struct sgx_file *sf, struct drm_gem_object *obj) +{ + unsigned int w; + + for (w = 0; w < SGX_VM_COUNT; w++) { + struct rb_node *n; + + for (n = rb_first(&sf->vm[w]); n; n = rb_next(n)) { + struct sgx_binding *b = + rb_entry(n, struct sgx_binding, node); + + if (b->obj == obj) + return true; + } + } + return false; +} + +/* Every handle the caller names has to be one it holds - and, for a frame, + * one it has an address for. + * + * A submit carries absolute addresses and no relocations, so userspace and + * the kernel have to agree about where each object is. When they do not, the + * frame fires at an address nothing backs and the part reports an MMU fault + * from whichever unit asked - which is a page number and a requestor, and + * says nothing about whose buffer it was or that a binding had gone. The + * winsys refuses to submit a buffer it thinks is unbound, so a handle that + * arrives here with no binding is exactly that disagreement, and naming it is + * worth an ioctl's return. + * + * bound is false for the handle checks that are not a frame's. */ +static int sgx_check_handles(struct drm_device *dev, struct drm_file *file, + struct sgx_file *sf, u64 uptr, u32 count, + bool bound) +{ + u32 *handles; + u32 k; + + if (!count) + return 0; + /* A sanity bound on what userspace may name, not a hardware one - the + * list is copied with memdup_user() and nothing downstream is sized by + * it. Sixty-four was costing ioquake3 a whole frame per swap: the + * driver's own texture budget is set below it, and a frame that samples + * more distinct textures than fit ends there. At four bytes a handle + * this is a kilobyte. */ + if (count > SGX_SUBMIT_MAX_HANDLES) + return -EINVAL; + handles = memdup_user(u64_to_user_ptr(uptr), count * sizeof(u32)); + if (IS_ERR(handles)) + return PTR_ERR(handles); + for (k = 0; k < count; k++) { + struct drm_gem_object *o = drm_gem_object_lookup(file, handles[k]); + + if (!o) { + kfree(handles); + return -ENOENT; + } + if (bound && sgx_bind_check && sf && + !sgx_obj_is_bound(sf, o)) { + drm_err(dev, + "submit: object %u (handle %u of %u) is not bound in this address space; the frame would fault at whatever address it names\n", + handles[k], k + 1, count); + drm_gem_object_put(o); + kfree(handles); + return -EINVAL; + } + drm_gem_object_put(o); + } + kfree(handles); + return 0; +} + +static int sgx_ioctl_submit(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_submit *args = data; + struct sgx_device *sgx = psb_sgx_of(dev); + struct sgx_file *sf = file->driver_priv; + struct sgx_cookie_caps caps = { 0, 0 }; /* SGX535 rev 1.2.1 */ + struct sgx_scene *sc; + ktime_t t_in = ktime_get(), t_ta = 0; + u32 *cookie; + /* Samples along each axis, 1 or 2 - the part has no other mode. */ + u32 samples = (args->flags & SGX_SUBMIT_MSAA_2X2) ? 2u : 1u; + u32 *ta = NULL, *ras = NULL, *oom = NULL; + u32 fire_flags = SGX_S_FIRE_FLAG_RASTER_DEALLOC; + unsigned int noom = 0; + u32 rca = 0; + u64 heap_va; + int ret; + + if (!sgx_flags_ok(args->flags, SGX_SUBMIT_NO_PRESENT | + SGX_SUBMIT_FENCE_OUT | + SGX_SUBMIT_DEFER_WAIT | + SGX_SUBMIT_VISTEST | + SGX_SUBMIT_MSAA_2X2)) + return -EINVAL; + /* Nothing here waits on a fence, so a caller handing one over would be + * told its ordering was honoured when it was not. Refused instead. */ + if (args->in_sync_fd >= 0) + return -EINVAL; + if (!args->width || !args->height || + args->width > 2048 || args->height > 2048) + return -EINVAL; + if (!args->ta_stream_count || args->ta_stream_count > 4096 || + args->raster_stream_count > 4096 || + args->oom_stream_count > 4096) + return -EINVAL; + if (!sgx_pad_ok(args->oom_pad)) + return -EINVAL; + + /* The object list. Userspace names every object the frame touches, and + * until now the kernel carried the field and ignored it - which made + * the winsys filling it in, and the tests asserting it arrived, + * assertions about nothing. + * + * Checking it is cheap and it is the difference between a submit that + * references an object this client bound and one that references a + * handle it made up. */ + ret = sgx_check_handles(dev, file, sf, args->bo_handles, + args->bo_count, true); + if (ret) + return ret; + + ta = memdup_user(u64_to_user_ptr(args->ta_stream), + args->ta_stream_count * sizeof(u32)); + if (IS_ERR(ta)) + return PTR_ERR(ta); + ret = sgx_check_stream(ta, args->ta_stream_count); + if (ret) + goto out; + + if (args->raster_stream_count) { + ras = memdup_user(u64_to_user_ptr(args->raster_stream), + args->raster_stream_count * sizeof(u32)); + if (IS_ERR(ras)) { + ret = PTR_ERR(ras); + ras = NULL; + goto out; + } + ret = sgx_check_stream(ras, args->raster_stream_count); + if (ret) + goto out; + } + + if (args->oom_stream_count) { + oom = memdup_user(u64_to_user_ptr(args->oom_stream), + args->oom_stream_count * sizeof(u32)); + if (IS_ERR(oom)) { + ret = PTR_ERR(oom); + oom = NULL; + goto out; + } + ret = sgx_check_stream(oom, args->oom_stream_count); + if (ret) + goto out; + } + + /* Everything above is validation. Below is the frame. + * + * The scene lives at the parameter heap's GPU address: cookie[7] + * through cookie[11] are offsets into it, so the fire path needs the + * base and the cookie together or it programs the DPM at nothing. */ + /* The fire path's offset is the *scene*, not the parameter heap: + * cookie[7] through cookie[11] are offsets into the scene, and on this + * hardware the two live in different windows. */ + /* Both locks, in the order VM_BIND and SCANOUT take them. The shared + * heap, the scene and the recovery's page table are all reserved in + * sf->vm[] from under fire_lock, so the address-space lock has to be + * held before it - taken inside, it waited on a VM_BIND that was + * holding it and waiting for fire_lock. */ + mutex_lock(&sf->vm_lock); + mutex_lock(&sgx->fire_lock); + /* Checked inside the lock, not before it: the mapping can go away + * between the test and the first register write otherwise. */ + if (!sgx->regs) { + ret = -ENODEV; + goto unlock_early; + } + /* The frame before this one may still be on the core. Everything below + * rearms the MMU, clears a scene and reprograms the DPM out from under + * it, so it is drained here - before the first of that, and not at the + * "unlock" label, which is only reachable once this frame owns a + * hardware scene. + * + * A failure is the *previous* frame's, and its recovery has already + * reset the core; this frame is refused rather than fired into the + * middle of that, which is what a failed submit has always meant here - + * the caller drops it and builds the next one from scratch. */ + /* Skipped only when the incoming frame can take the context the render + * is not in, and belongs to the same file, so nothing per device moves + * under it. sgx_ctx_take() below still refuses if no context is free, + * and the wait then happens where it always did. */ + if (!(sgx_ctx_overlap && sgx->pend.active && sgx->pend.sf == sf && + sgx_ctx_free_other(&sgx->ctx, sgx->pend.ctx))) { + ret = sgx_render_wait(dev, sgx); + if (ret) + goto unlock_early; + } else { + sgx->ctx.overlaps++; + } + + /* The scene cookie: macro-tile geometry and the heap layout the fire + * path reads. Userspace does not supply it and must not - the recovery + * path depends on the sizes matching what the DPM was programmed with. + * + * Built once per geometry and kept: the fire writes scene state back + * into it, and handing it a fresh one every frame is what made the DPM + * allocate new parameter pages for each and never reuse any. + * + * Under both locks and after the drain: a size change drops the scene + * object and allocates another, and two threads of one process + * submitting at once raced here, one freeing what the other had just + * taken and both writing the same page tables. */ + sc = &sf->scenes[sf->cur_scene]; + cookie = sc->cookie; + /* The sample count belongs in the key: a scene built for one sample + * has a quarter of the region headers a 2x2 render indexes, and + * reusing it would have the tiler write past the array. */ + if (sc->w != args->width || sc->h != args->height || + sc->samples != samples) { + ret = sgx_scene_info_ms(&caps, args->width, args->height, + samples, samples, cookie, &sc->need, + &sc->clear_start, &sc->clear_pages); + if (ret) + goto unlock_early; + sc->w = args->width; + sc->h = args->height; + sc->samples = samples; + sc->cleared = false; + } + ret = sgx_scene_ensure(dev, sf, sc, sc->need); + if (ret) + goto unlock_early; + heap_va = sc->va; + /* psb_schedule.c:150-188: a scene keeps the hardware context it last + * fired on if nothing has taken it since, otherwise it takes the least + * recently returned one, so two busy clients alternate rather than one + * of them owning a context for the life of the file. + * + * A refusal here means every context still belongs to a render that + * was never waited for, which the drain above should have settled. It + * is an error rather than a context taken anyway: firing a second + * scene into the DPM context a running render is deallocating from + * locks the part. */ + ret = sgx_ctx_take(&sgx->ctx, sc); + if (ret < 0) { + drm_err(dev, "no free hardware scene context\n"); + goto unlock_early; + } + sc->ctx = ret; + ret = 0; + if (sgx_scene_ctx_log) + drm_info(dev, + "frame %llu: file %p scene %u on context %d (%lu kept, %lu switched, %lu refused, %lu overlapped)\n", + sgx->frames, sf, sf->cur_scene, sc->ctx, + sgx->ctx.reuses, sgx->ctx.switches, + sgx->ctx.refusals, sgx->ctx.overlaps); + + /* Set before anything is programmed, not after it has worked: a submit + * that fails is exactly the one that leaves the core needing a restart, + * and marking it only on success meant the next client inherited the + * wreckage. */ + /* The scene's region array is cleared at the start of every frame. + * sgxtri does it in frame_scene_clear() and nothing here did: the + * records from the frame before were still in place, and the DPM + * tracks its pages through them, so it never took any back. */ + if (sc->obj && sc->clear_pages && !sc->cleared) { + if (!sc->cpu) { + struct iosys_map map; + + ret = sgx_obj_vmap(sc->obj, &map); + if (ret) { + drm_err(dev, "cannot map the scene: %d\n", ret); + goto unlock; + } + sc->cpu = map.vaddr; + } + { + size_t at = (size_t)sc->clear_start << 12; + size_t n = (size_t)sc->clear_pages << 12; + + if (at + n > sc->size) { + drm_err(dev, + "scene clear %zu+%zu past a %llu byte scene\n", + at, n, sc->size); + ret = -EINVAL; + goto unlock; + } + /* What the frame before left in the range about to be + * cleared. If a frame with two draw records leaves + * more behind than one with a single record, and it + * grows, that is the accumulation the failure looks + * like - and if it does not, the scene is not where + * whatever runs out is kept. */ + if (sgx_dpm_watch) { + const u32 *p = (const u32 *) + ((char *)sc->cpu + at); + size_t i, nz = 0, dw = n / 4; + u32 last = 0; + + for (i = 0; i < dw; i++) + if (p[i]) { + nz++; + last = (u32)i; + } + drm_info(dev, + "scene: %zu of %zu dwords live, highest at %u\n", + nz, dw, last); + } + memset((char *)sc->cpu + at, 0, n); + /* The scene is ordinary cached memory and the GPU + * reads it without snooping, so the zeroes have to be + * out of the CPU's cache before the tiler looks. */ + drm_clflush_virt_range((char *)sc->cpu + at, n); + } + } + + sgx->dpm_dirty = true; + sgx->frames++; + + /* A frame that stalls is recovered and tried again. The core comes back + * from sgx_stall_recover() in the state a fresh one is in, so a retry + * is a first attempt rather than a second one on damaged hardware, and + * a transient stall costs latency instead of a frame. It is bounded: + * a frame the hardware will never finish must fail, not spin. */ + ret = 0; + sgx_kick_env_init(&sgx->scene.k, sgx, sgx->use_ctrl); + sgx->scene.isp_reset = sgx_isp_reset; + sgx->scene.zls = (sgx_zls & SGX_ZLS_DPM) != 0; + /* Re-read per submit, so the parameter can be turned off under a client + * that is already running rather than only at load. */ + sgx->scene.spm = sgx_spm; + /* Point the hardware at this file's address space. Every submit does + * it: another file may have run in between, and its directory is not + * this one. */ + /* The device's parameter heap and DPM page table go into this client's + * address space before its directory is armed, so the addresses the + * single heap load named resolve for every client that submits. */ + ret = sgx_shared_bind(dev, sgx, sf); + if (ret) { + drm_err(dev, "cannot bind the shared parameter heap: %d\n", + ret); + goto unlock; + } + sgx_mmu_arm(sgx, &sf->mmu); + if (sgx_pte_watch) { + u64 tva = sgx_windows[SGX_VM_SURFACE].start; + unsigned int pde = sgx_mmu_pde_of(tva); + u32 *pt = sf->mmu.pt[pde]; + + drm_info(dev, + "file %p ctx %u pd 0x%08lx target va 0x%08llx -> pte 0x%08x\n", + sf, sc->ctx, sf->mmu.pd_phys, tva, + pt ? pt[sgx_mmu_pte_of(tva)] : 0xdeadbeefu); + } + /* and this file's USE base registers, for the same reason */ + sgx_use_arm(&sgx->scene.k, &sf->use); + + + /* The DPM has to know where the parameter heap is before a scene can + * be tiled into it. Once per *device*, not per file: the hardware + * takes the handshake a single time per boot, and the heap it is given + * is the device's, bound into every client at the same address. */ + if (!sgx->dpm_loaded) { + u64 param_sz = sgx->param_size; + u64 param_va = sgx->param_va; + u64 pt_va = sgx->dpm_pt_va; + u32 sz; + + if (!param_va || !pt_va) { + ret = -ENOSPC; + goto unlock; + } + { + u32 have = (u32)(param_sz >> 12); + /* Sized for the whole mapped heap, not for this + * frame. The load happens once per boot - the + * hardware refuses a second INIT handshake - so + * whatever it is told has to serve every client that + * follows, including ones with much larger render + * targets. Sizing it from the first frame's cookie + * meant a 128x128 test set the budget and the next + * client ran the tiler off the end of it. */ + u32 dp = sgx_dpm_pages(cookie[1], have); + + /* A quarter of the mapped heap, which is exactly the + * ratio the heap is allocated at: userspace maps four + * times what the DPM is told so the tiler has room to + * overrun into. Telling it the whole heap removed that + * headroom and the tiler faulted off the end; telling + * it the first frame's estimate let a 128x128 client + * set the budget for every client after it. */ + if (dp < have / SGX_DPM_CAP_MULTIPLIER) + dp = have / SGX_DPM_CAP_MULTIPLIER; + drm_info(dev, "DPM load: cookie[1] %u, have %u, dp %u (quarter %u)\n", + cookie[1], have, dp, + have / SGX_DPM_CAP_MULTIPLIER); + + /* Refuse rather than trim. Handing the DPM fewer pages + * than the scene needs does not make it bin less: the + * tiler runs off the end of the heap, faults, and the + * core is then wedged until the machine is power + * cycled - no soft reset, D3hot or fault clear brings + * it back. An error the caller can act on is worth + * more than a frame it will not get anyway. */ + if (dp > have) { + drm_err(dev, + "parameter heap too small for %ux%u: %u pages needed, %u bound\n", + args->width, args->height, dp, have); + ret = -ENOSPC; + goto unlock; + } + ret = sgx_ta_mem_info(dp, sgx->dpm_cookie, &sz); + if (ret) + goto unlock; + drm_dbg(dev, + "DPM: heap 0x%08llx %llu pages mapped, %u given; pt 0x%08llx %llu bytes\n", + param_va, param_sz >> 12, dp, pt_va, + sgx->dpm_pt_size); + drm_dbg(dev, + "DPM cookie: pages %u avail %u tail %u, mtiles %u\n", + sgx->dpm_cookie[2], sgx->dpm_cookie[3], + sgx->dpm_cookie[6], cookie[1]); + } + ret = sgx_ta_mem_load(&sgx->scene.k, (u32)pt_va, (u32)param_va, + SGX_H_MEM_FLAG_TA | + SGX_H_MEM_FLAG_RASTER | + SGX_H_MEM_FLAG_HOSTA | + SGX_H_MEM_FLAG_HOSTD | + SGX_H_MEM_FLAG_INIT, sgx->dpm_cookie); + /* Once this starts it does not stop: a client that stalled the + * render and went away leaves the DPM holding the heap, and + * every later load is refused. Measured 2026-09-04: a module + * reload does not clear it even though both resets do run - + * the device reaches D3hot (PMCSR 0x0003, No_Soft_Reset clear) + * and SGX_CS_RESET_DPM is in the mask. A warm reboot does + * clear it, so the power cycle this asks for elsewhere is + * more than it needs. */ + if (ret) { + drm_err(dev, "the DPM did not take the heap\n"); + sgx_stall_recover(sgx, sf, "a heap the DPM refused"); + goto unlock; + } + sgx->dpm_loaded = true; + sgx->dpm_pages = sgx->dpm_cookie[2]; + sgx->dpm_owner = sf; + ret = sgx_dpm_seed_capture(sgx, sf); + if (ret) + goto unlock; + /* What the DPM ended up with, to hold against sgxtri's. */ + drm_dbg(dev, + "DPM regs: base 0x%08x span 0x%08x tail 0x%08x global 0x%08x\n", + readl(sgx->regs + SGX_H_DPM_TA_BASE), + readl(sgx->regs + SGX_H_DPM_TA_BASE + 4), + readl(sgx->regs + SGX_H_DPM_TA_TAIL_2), + readl(sgx->regs + SGX_H_DPM_TA_GLOBAL)); + drm_dbg(dev, + "DPM regs: localA 0x%08x localB 0x%08x localC 0x%08x tailreg 0x%08x\n", + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_A), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_B), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_C), + readl(sgx->regs + SGX_H_DPM_TA_TAIL)); + } + + ret = sgx_dpm_take(dev, sgx, sf); + if (ret) + goto unlock; + + /* What the DPM has left, per submit. A frame with more than one draw + * record does not get all its parameter pages back and the third such + * frame in a file fails; which of these counters walks says where the + * pages are being recorded, and that is a measurement rather than + * another guess. */ + if (sgx_dpm_watch) + drm_info(dev, + "DPM: tail 0x%08x global 0x%08x A 0x%08x B 0x%08x C 0x%08x tailreg 0x%08x\n", + readl(sgx->regs + SGX_H_DPM_TA_TAIL_2), + readl(sgx->regs + SGX_H_DPM_TA_GLOBAL), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_A), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_B), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_C), + readl(sgx->regs + SGX_H_DPM_TA_TAIL)); + + /* Everything the frame reads - the state heap, the shader code, the + * draw records, the geometry - was written by userspace through + * write-combining mappings, and a syscall does not drain the write + * combining buffers. Without a fence here the tiler can read what was + * in memory before the frame was built, which faults nowhere and shows + * up as a frame that simply never finishes. */ + wmb(); + sgx_fault_clear(sgx); + /* The clean bind about to run clears the DPM's state and control + * tables for this context at the addresses the registers already hold, + * and the only path that ever programs them is an out-of-memory + * recovery - so they name whichever scene recovered last, in whichever + * address space it belonged to. Naming this scene's own is what makes + * the clear land inside the scene it is clearing. */ + if (sgx_scene_ctx == SGX_CTX_MODE_TABLES) { + writel((u32)heap_va + cookie[10], + sgx->regs + SGX_S_DPM_PAGE_TABLE); + writel((u32)heap_va + cookie[11], + sgx->regs + SGX_S_DPM_STATE_TABLE); + } + t_ta = ktime_get(); + ret = sgx_fire_ta(&sgx->scene, (unsigned int)sc->ctx, ta, + args->ta_stream_count, + cookie, + (u32)heap_va, oom, args->oom_stream_count, + fire_flags, &rca); + if (ret) + goto unlock; + /* A scene bigger than the parameter heap is not an error: the tiler + * says so, the scene is rendered as far as it got, the pages that + * frees are handed back and the tiler carries on into them. Without + * this loop that report was indistinguishable from a tiler that hung, + * and the frame was thrown away. */ + for (;;) { + ret = sgx_wait_event_oom(&sgx->scene.k, SGX_F_EV_TA_FINISHED, + SGX_FIRE_TIMEOUT_US); + if (ret <= 0) + break; + if (!args->raster_stream_count || !args->oom_stream_count) { + drm_err(dev, + "out of parameter memory with no way to recover: %u raster, %u out-of-memory dword(s)\n", + args->raster_stream_count, + args->oom_stream_count); + ret = -ENOSPC; + break; + } + if (++noom > sgx_max_oom) { + drm_err(dev, + "the scene did not fit after %u recoveries\n", + noom - 1); + ret = -ENOSPC; + break; + } + ret = sgx_oom_cycle(dev, sgx, sf, sc, ras, + args->raster_stream_count, oom, + args->oom_stream_count, (u32)heap_va, + &fire_flags, noom); + if (ret) + break; + } + if (noom && !ret) + sgx->recoveries += noom; + /* After the tiler, not before it. Reading these at the top of a submit + * shows the same values whether or not the frame before consumed + * anything and gave it back, which is the flaw in the first pass of + * this measurement: what a frame costs is only visible once its own + * tiling is done. */ + /* And the DPM's page table, which is a buffer object this driver owns + * rather than a register it cannot see. If a frame with two draw + * records leaves more of it written than one with a single record, + * that is the per-block cost the failure has the shape of. */ + if (sgx_dpm_watch && !ret) { + struct drm_gem_object *pt = sf->heap_private ? sf->heap_pt_obj + : sgx->dpm_pt_obj; + struct iosys_map m; + + if (pt && !sgx_obj_vmap(pt, &m)) { + const u32 *p = m.vaddr; + size_t i, nz = 0, dw = sgx->dpm_pt_size / 4; + u32 hi = 0; + + for (i = 0; i < dw; i++) + if (p[i]) { + nz++; + hi = (u32)i; + } + drm_info(dev, "DPM pt: %zu of %zu dwords set, highest %u\n", + nz, dw, hi); + sgx_obj_vunmap(pt, m.vaddr); + } + } + if (sgx_dpm_watch && !ret) + drm_info(dev, + "after TA: tail 0x%08x global 0x%08x A 0x%08x B 0x%08x C 0x%08x tailreg 0x%08x\n", + readl(sgx->regs + SGX_H_DPM_TA_TAIL_2), + readl(sgx->regs + SGX_H_DPM_TA_GLOBAL), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_A), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_B), + readl(sgx->regs + SGX_H_DPM_TA_LOCAL_C), + readl(sgx->regs + SGX_H_DPM_TA_TAIL)); + if (ret) { + u32 ev = readl(sgx->regs + SGX_K_EVENT_STATUS); + + if (ev & SGX_F_EV_OOM_MASK) + drm_err(dev, + "tiler ran out of parameter memory (events 0x%08x)\n", + ev); + else + drm_err(dev, "tiler did not finish (events 0x%08x)\n", + ev); + sgx_report_faults_for(sgx, sf, "tiler"); + sgx_stall_recover(sgx, sf, "a tiler that did not finish"); + goto unlock; + } + + if (sgx_zls & (SGX_ZLS_LOG_RGN | SGX_ZLS_MARK_RGN)) { + u32 nrgn = 0, *rh = sgx_scene_regions(sc, cookie, &nrgn); + u32 i; + + for (i = 0; rh && i < nrgn; i++) { + u32 *r = rh + i * SGX_RH_DWORDS; + + if ((sgx_zls & SGX_ZLS_LOG_RGN) && + (r[0] | r[1] | r[2])) + drm_info(dev, "region %u: %08x %08x %08x\n", + i, r[0], r[1], r[2]); + if (sgx_zls & SGX_ZLS_MARK_RGN) + r[0] |= SGX_RH_ZLOADENABLE; + /* Region header 2 names where this region's Z lives, + * as (addr >> 4) in 24 bits under the BIF's ZLS + * window. The tiler leaves it zero. */ + if (sgx_zls & SGX_ZLS_BASE_TILED) + r[2] = ((sgx_zls_base + i * 1024u) >> 4) & + 0x00ffffffu; + else if (sgx_zls & SGX_ZLS_BASE_LINEAR) { + u32 x = (r[0] >> 16) & 0xffu, y = r[0] & 0xffu; + u32 at = sgx_zls_base + + (y << 3) * args->width * 4u + + ((x << 3) << 2); + + r[2] = (at >> 4) & 0x00ffffffu; + } + } + /* The scene is cached and the GPU does not snoop. */ + if (rh && (sgx_zls & SGX_ZLS_MARK_RGN)) + drm_clflush_virt_range(rh, + nrgn * SGX_RH_DWORDS * 4u); + } + + if (args->raster_stream_count) { + wmb(); + /* The fault register latches, and the tiler phase above may + * have left one in it - so without this the render's fault + * report names an address the render never asked for. */ + sgx_fault_clear(sgx); + /* Before the fire, not after it: the record the interrupt + * handler leaves has to be empty when the event that fills it + * is raised, and a render can finish inside the fire. */ + sgx_event_arm(sgx, SGX_F_EV_END_RENDER); + ret = sgx_fire_raster(&sgx->scene, (unsigned int)sc->ctx, ras, + args->raster_stream_count, cookie, + (u32)heap_va, oom, + args->oom_stream_count, fire_flags, + &rca); + if (ret) + goto unlock; + /* The render is on the core. What the wait needs goes with it, + * because the thread that comes back for it is whichever one + * takes fire_lock next and that may be another client's. */ + sgx->pend.active = true; + sgx->pend.sf = sf; + sgx->pend.sc = sc; + sgx->pend.ctx = sc->ctx; + sgx->pend.rca = rca; + sgx->pend.w = args->width; + sgx->pend.h = args->height; + sgx->pend.t_in = t_in; + sgx->pend.t_ta = t_ta; + sgx->pend.t_ras = ktime_get(); + sgx->pend.vistest = (args->flags & SGX_SUBMIT_VISTEST) != 0; + /* Deferred only when the caller asked for it and nothing in + * this submit needs the answer now. A fence handed out before + * the render has ended would be a lie, and everything that + * reads one takes it for done. */ + if (!sgx_async || !(args->flags & SGX_SUBMIT_DEFER_WAIT) || + (args->flags & SGX_SUBMIT_FENCE_OUT)) { + ret = sgx_render_wait(dev, sgx); + if (ret) + goto unlock; + } + } else { + sgx->stalls = 0; + } + +unlock: + /* Least recently used again, and the pool moves on so the next frame + * does not rebuild the scene this one just rendered from - + * psb_scene.c:281 advances cur_scene on the final pass the same way. + * The scene keeps its region array only if the frame got that far. + * + * A render left on the core keeps its context instead: it is still + * deallocating that scene's parameter pages in it, and the thread that + * waits releases it. */ + if (sc->ctx >= 0) { + if (sgx->pend.active && sgx->pend.sc == sc) + sgx_ctx_hold(&sgx->ctx, sc->ctx, sc); + else + sgx_ctx_put(&sgx->ctx, sc->ctx); + } + sc->cleared = false; + sf->cur_scene = (sf->cur_scene + 1) % SGX_SCENES_PER_FILE; +unlock_early: + mutex_unlock(&sgx->fire_lock); + mutex_unlock(&sf->vm_lock); + if (ret) + goto out; + + if (!(args->flags & SGX_SUBMIT_FENCE_OUT)) { + args->out_sync_fd = -1; + goto out; + } + ret = sgx_signalled_fence_fd(sgx, &args->out_sync_fd); +out: + kfree(ta); + kfree(ras); + kfree(oom); + return ret; +} + +static int sgx_ioctl_use_base(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_use_base *args = data; + struct sgx_file *sf = file->driver_priv; + u32 reg = 0, off = 0; + int ret; + + (void)dev; + if (!sf) + return -EINVAL; + if (args->data_master > SGX_USE_DM_PIXEL) + return -EINVAL; + if (!args->size || args->gpu_va > 0xffffffffull) + return -EINVAL; + + mutex_lock(&sf->vm_lock); + ret = sgx_use_grab(&sf->use, (u32)args->gpu_va, args->size, + args->data_master, ®, &off); + mutex_unlock(&sf->vm_lock); + if (ret) + return ret; + args->reg = reg; + args->offset = off; + return 0; +} + +/* Drain the device: come back once the render the last submit fired has ended. + * + * This is the other half of SGX_SUBMIT_DEFER_WAIT. The submit no longer waits, + * so userspace has to say where it needs the result - before it reads a + * surface back, before it overwrites the state, the shader code or a texture + * the render is still reading, and before it hands a buffer to anything else. + * Cheap when nothing is outstanding: the wait returns on the first test. */ +static int sgx_ioctl_wait(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_wait *args = data; + struct sgx_device *sgx = psb_sgx_of(dev); + int ret; + + (void)file; + if (!sgx_flags_ok(args->flags, 0) || !sgx_pad_ok(args->pad)) + return -EINVAL; + mutex_lock(&sgx->fire_lock); + ret = sgx_render_wait(dev, sgx); + mutex_unlock(&sgx->fire_lock); + return ret; +} + +/* The ISP's visibility counters, as running totals for this file. + * + * The counters are device registers on this core - SGX535 has no + * SGX_FEATURE_VISTEST_IN_MEMORY (sgxfeaturedefs.h:189-232) - and a submitted + * stream is register writes, so userspace has no way to read one. This is that + * way. What it returns is not the register: it is what the harvest above has + * summed, so the difference between two reads is what the renders between them + * counted, however many frame splits that was. + * + * The totals are per file, and the harvest attributes each render to the file + * that fired it, so two clients counting into the same register index do not + * mix. Two contexts on one file share the totals, which is why the register + * pool userspace allocates from is per file as well. */ +static int sgx_ioctl_vistest(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_vistest *args = data; + struct sgx_device *sgx = psb_sgx_of(dev); + struct sgx_file *sf = file->driver_priv; + unsigned int i; + int ret = 0; + + if (!sgx_flags_ok(args->flags, SGX_VISTEST_WAIT)) + return -EINVAL; + mutex_lock(&sgx->fire_lock); + if (args->flags & SGX_VISTEST_WAIT) + ret = sgx_render_wait(dev, sgx); + /* Only this file's own render matters: another client's outstanding + * frame cannot add to these totals, and reporting it as pending would + * make a caller poll for something that will never change. */ + args->pending = (sgx->pend.active && sgx->pend.sf == sf) ? 1 : 0; + for (i = 0; i < SGX_VISTEST_REGS; i++) + args->count[i] = sf->vistest[i]; + mutex_unlock(&sgx->fire_lock); + return ret; +} + +/* ---- the 2D block ---- + * + * A separate engine from the 3D pipeline: no scene, no PDS, no shaders. It + * takes block-header commands through a slave port in the register window and + * addresses memory through the BIF as offsets from BIF_TWOD_REQ_BASE, which + * sgx_cold_init() points at the surface window's start. The BIF translates + * those through whatever directory DIR_LIST_BASE0 holds, so a 2D job runs on + * the same page tables as a render, and it runs behind the same lock. + * + * Ordering is by construction rather than by fence: a job drains the render + * the last submit left on the core before it touches the page-directory + * register - the render may be another file's, and re-arming under it faults + * - and it does not return until its own sequence number has landed in + * memory. A blit into a target a deferred render writes therefore waits for + * it, and a render sampling a blit's destination is submitted after the blit + * has ended. The DDK's ukernel keeps the same rule under + * USE_SUPPORT_NO_TA2D_OVERLAP, 2d.asm:449-458: no 2D while a tiler runs. + * + * Completion is memory-visible, the way psb_blit_sequence() made it: the + * kernel appends a 1x1 fill of the sequence number into the page at the + * request base - its own, and the one the engine's offset-0 touch lands on - + * then FENCE and FLUSH. The FLUSH raises EVENT_STATUS bit 27 - "flush 2d + * blits and generate interrupt", sgxdefs.h:8865 - which ends the sleep; the + * word in the page is what says the job is done, because BLIT_STATUS[23:0] + * counts FENCE and FLUSH blocks and says nothing about the blits between + * them (work/twod-cursor/README.md). + */ + +/* The sequence page is the page at the request base: outside every window, so + * no client can bind it and no user offset resolves to it - a surface word of + * zero names it and the binding lookup below refuses it. The pixel sits away + * from offset 0, which is what the engine touches of its own accord. */ +#define SGX_TWOD_FENCE_PIXEL 0x800u + +static int sgx_twod_fence_ensure(struct drm_device *dev, struct sgx_file *sf) +{ + struct iosys_map map; + int ret; + + if (sf->twod_fence_cpu) + return 0; + ret = sgx_own_object(dev, sf, SGX_TWOD_BASE_VA, PAGE_SIZE, + &sf->twod_fence_obj, &sf->twod_fence_va); + if (ret) + return ret; + ret = sgx_obj_vmap(sf->twod_fence_obj, &map); + if (ret) { + drm_err(dev, "cannot map the 2D sequence page: %d\n", ret); + return ret; + } + sf->twod_fence_cpu = map.vaddr; + memset(sf->twod_fence_cpu, 0, PAGE_SIZE); + drm_clflush_virt_range(sf->twod_fence_cpu, PAGE_SIZE); + return 0; +} + +/* The engine writes the page and the CPU reads it: through the cache would + * read what was there before. */ +static u32 sgx_twod_fence_read(struct sgx_file *sf) +{ + u32 *p = (u32 *)((char *)sf->twod_fence_cpu + SGX_TWOD_FENCE_PIXEL); + + drm_clflush_virt_range(p, sizeof(*p)); + return READ_ONCE(*p); +} + +/* What the checker asks: does this file have [va, va + size) bound in the + * surface window, whole and inside one binding? The sequence page is the + * kernel's and is refused whatever the tree says. */ +/* The extents the checker approved, which are exactly the ones the engine + * will touch: the range callback sees every one of them. Kept so the flush + * and the probe below can act on them without parsing the stream again. */ +#define SGX_TWOD_MAX_RANGES 16u + +struct sgx_twod_ranges { + struct sgx_file *sf; + u64 fence_va; + struct { u64 va, size; } r[SGX_TWOD_MAX_RANGES]; + unsigned int n; +}; + +static int sgx_twod_range_ok(void *priv, unsigned long long va, + unsigned long long size) +{ + struct sgx_twod_ranges *r = priv; + struct sgx_binding *b; + + if (!size || va + size < va) + return 0; + if (sgx_overlaps(va, size, r->fence_va, PAGE_SIZE)) + return 0; + b = sgx_vm_find(&r->sf->vm[SGX_VM_SURFACE], va, size); + if (!b || b->gpu_va > va || va + size > b->gpu_va + b->size) + return 0; + if (r->n < SGX_TWOD_MAX_RANGES) { + r->r[r->n].va = va; + r->r[r->n].size = size; + r->n++; + } + return 1; +} + +/* Push whatever the CPU holds for an extent out to memory. The pages are the + * object's, found through the binding the checker already matched. */ +static void sgx_twod_flush_range(struct sgx_file *sf, u64 va, u64 size) +{ + struct sgx_binding *b = sgx_vm_find(&sf->vm[SGX_VM_SURFACE], va, size); + struct page **pages; + u64 first, n; + + if (!b || !b->obj) + return; + pages = sgx_obj_pages(b->obj); + if (!pages) + return; + first = (va - b->gpu_va) >> SGX_PTE_SHIFT; + n = (((va - b->gpu_va) & ~PAGE_MASK) + size + PAGE_SIZE - 1) >> + SGX_PTE_SHIFT; + if ((first + n) << SGX_PTE_SHIFT > b->size) + return; + drm_clflush_pages(&pages[first], n); +} + +/* What the CPU can read at an address the engine is about to read, through + * the file's own page tables - so a page the engine cannot reach reports as + * much rather than reporting the object's. */ +static void sgx_twod_probe_at(struct drm_device *dev, struct sgx_file *sf, + u64 va) +{ + unsigned int pde = sgx_mmu_pde_of(va); + u32 *pt = sf->mmu.pt[pde]; + u32 pte = pt ? pt[sgx_mmu_pte_of(va)] : 0; + u32 w[4] = { 0, 0, 0, 0 }; + void *k; + + if (!(pte & SGX_PTE_VALID)) { + drm_info(dev, + "2D probe: va 0x%08llx pde 0x%08x pte 0x%08x - not mapped\n", + va, sf->mmu.pd[pde], pte); + return; + } + k = kmap_local_page(pfn_to_page(pte >> SGX_PTE_SHIFT)); + if (k) { + char *at = (char *)k + (va & ~PAGE_MASK); + + clflush_cache_range(at, sizeof w); + memcpy(w, at, sizeof w); + kunmap_local(k); + } + drm_info(dev, + "2D probe: va 0x%08llx pte 0x%08x -> %08x %08x %08x %08x\n", + va, pte, w[0], w[1], w[2], w[3]); +} + +/* The BIF, the vendor's way: one bit per pulse. psb_mmu_flush() writes FLUSH + * or INVALDC and never both, and the combined write sgx_mmu_invalidate() + * makes is a shape no vendor code issues. */ +static void sgx_twod_bif_flush(struct sgx_device *sgx) +{ + u32 ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + + writel(ctrl | SGX_CB_CTRL_FLUSH, sgx->regs + SGX_CR_BIF_CTRL); + wmb(); + writel(ctrl & ~SGX_CB_CTRL_FLUSH, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + + writel(ctrl | SGX_CB_CTRL_INVALDC, sgx->regs + SGX_CR_BIF_CTRL); + wmb(); + writel(ctrl & ~SGX_CB_CTRL_INVALDC, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); +} + +/* psb_sgx.c:130-191: wait for the input FIFO to have room for a chunk, write + * it a dword per slave-port address, and read the last one back to push the + * writes out of the posting buffer. The DDK's ukernel writes with no space + * check at all (2d.asm:829-848); the vendor kernel checked, and so does this. */ +static int sgx_twod_push(struct sgx_device *sgx, const u32 *w, unsigned int n) +{ + u64 start = ktime_to_us(ktime_get()); + + while (n) { + unsigned int chunk = min(n, SGX_2D_FIFO_CHUNK), i; + + while ((readl(sgx->regs + SGX_2D_SOCIF) & + SGX_2D_SOCIF_FREE_MASK) < chunk) { + if (ktime_to_us(ktime_get()) - start > + (u64)sgx_twod_timeout_ms * 1000u) + return -EBUSY; + usleep_range(20, 50); + } + for (i = 0; i < chunk; i++) + writel(w[i], sgx->regs + SGX_2D_SLAVE_PORT + i * 4); + (void)readl(sgx->regs + SGX_2D_SLAVE_PORT + (chunk - 1) * 4); + w += chunk; + n -= chunk; + } + return 0; +} + +static bool sgx_twod_idle(struct sgx_device *sgx) +{ + return !(readl(sgx->regs + SGX_2D_BLIT_STATUS) & SGX_2D_STATUS_BUSY) && + (readl(sgx->regs + SGX_2D_SOCIF) & SGX_2D_SOCIF_FREE_MASK) == + SGX_2D_SOCIF_EMPTY; +} + +static void sgx_twod_report(struct sgx_device *sgx, const char *what) +{ + drm_err(sgx->ddev, + "2D %s: BLIT_STATUS 0x%08x, SOCIF 0x%08x, events 0x%08x, BIF_CTRL 0x%08x, TWOD_REQ_BASE 0x%08x\n", + what, readl(sgx->regs + SGX_2D_BLIT_STATUS), + readl(sgx->regs + SGX_2D_SOCIF), + readl(sgx->regs + SGX_K_EVENT_STATUS), + readl(sgx->regs + SGX_CR_BIF_CTRL), + readl(sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE)); + sgx_report_faults(sgx, what); +} + +/* One wait for the completion event, sleeping when it is on the interrupt + * line and spinning when it is not - the same choice sgx_wait_render() makes. */ +static int sgx_twod_wait_event(struct sgx_device *sgx, unsigned int timeout_us) +{ + if ((READ_ONCE(sgx->event_irq_mask) & SGX_2D_EV_COMPLETE) == + SGX_2D_EV_COMPLETE) + return sgx_wait_event_sleep(sgx, SGX_2D_EV_COMPLETE, timeout_us, + false); + return sgx_wait_event(&sgx->scene.k, SGX_2D_EV_COMPLETE, timeout_us); +} + +/* Until the sequence number is in the page. An engine that has gone idle + * without writing it consumed the stream and did nothing - the shape a + * mis-sized block has - and is reported as that rather than as a timeout. */ +static int sgx_twod_wait(struct drm_device *dev, struct sgx_device *sgx, + struct sgx_file *sf, u32 seq) +{ + unsigned int waited = 0, idle = 0; + + for (;;) { + if (sgx_twod_fence_read(sf) == seq) + return 0; + (void)sgx_twod_wait_event(sgx, SGX_FIRE_SLICE_US); + if (sgx_twod_fence_read(sf) == seq) + return 0; + waited += SGX_FIRE_SLICE_US; + if (sgx_twod_idle(sgx)) { + if (++idle >= 2) { + drm_err(dev, + "2D job %u: engine idle after %u us, sequence never written (page holds %u)\n", + seq, waited, sgx_twod_fence_read(sf)); + return -EIO; + } + } else { + idle = 0; + } + if (waited >= sgx_twod_timeout_ms * 1000u) { + drm_err(dev, "2D job %u did not finish in %u us\n", seq, + waited); + return -EBUSY; + } + } +} + +/* Wait out a job the ioctl pushed and did not stay for. Nothing but register + * and memory reads, so it is safe from any of the drain sites; a job that + * never lands is reported and recovered the way a render that does not finish + * is. Called with fire_lock held. */ +static int sgx_twod_drain(struct drm_device *dev, struct sgx_device *sgx) +{ + struct sgx_file *sf = sgx->twod_pend.sf; + u32 seq = sgx->twod_pend.seq; + int ret; + + if (!sgx->twod_pend.active) + return 0; + sgx->twod_pend.active = false; + if (!sgx->regs || !sf || !sf->twod_fence_cpu) + return -ENODEV; + ret = sgx_twod_wait(dev, sgx, sf, seq); + if (sgx_timing) + sgx->twod_us += ktime_us_delta(ktime_get(), sgx->twod_pend.t0); + if (ret) { + sgx_twod_report(sgx, "deferred job failed"); + sgx->twod_stalls++; + sgx_stall_recover(sgx, sf, "a 2D job that did not finish"); + drm_err(dev, "2D block after recovery: %s\n", + sgx_twod_idle(sgx) ? "idle" : "still busy"); + return -EIO; + } + return 0; +} + +static int sgx_ioctl_blit(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_blit *args = data; + struct sgx_device *sgx = psb_sgx_of(dev); + struct sgx_file *sf = file->driver_priv; + struct sgx_2d_report rep = { 0, NULL }; + struct sgx_twod_ranges ranges; + u64 base = SGX_TWOD_BASE_VA; + ktime_t t0; + u32 *stream; + u32 seq, n = args->stream_count; + int ret, chk; + + sgx->twod_calls++; + if (!sgx_twod) { + sgx->twod_refused++; + return -ENODEV; + } + if (!sf) { + sgx->twod_refused++; + return -EINVAL; + } + if (!sgx_flags_ok(args->flags, SGX_BLIT_FENCE_OUT | + SGX_BLIT_DEFER_WAIT) || + !sgx_pad_ok(args->pad)) + return -EINVAL; + /* The fence is created signalled, so a job that hands one out has to + * have run. Asking for both is a caller that has misunderstood. */ + if ((args->flags & SGX_BLIT_DEFER_WAIT) && + (args->flags & SGX_BLIT_FENCE_OUT)) + return -EINVAL; + if (args->in_sync_fd >= 0) + return -EINVAL; + if (!n || n > SGX_2D_MAX_DWORDS) + return -EINVAL; + ret = sgx_check_handles(dev, file, sf, args->bo_handles, + args->bo_count, false); + if (ret) { + sgx->twod_refused++; + return ret; + } + stream = kmalloc_array(n + SGX_2D_TRAILER_DWORDS, sizeof(u32), + GFP_KERNEL); + if (!stream) + return -ENOMEM; + if (copy_from_user(stream, u64_to_user_ptr(args->stream), + n * sizeof(u32))) { + kfree(stream); + return -EFAULT; + } + + /* Both locks in the submit's order. The bindings the stream is checked + * against must not move until the engine has finished with them. */ + mutex_lock(&sf->vm_lock); + mutex_lock(&sgx->fire_lock); + if (!sgx->regs) { + ret = -ENODEV; + goto unlock; + } + /* The render, not a job of our own: the engine consumes streams in + * the order they are pushed, so a job still running is a job this one + * follows. What may not run under it is a render, which shares the + * page-directory register this is about to arm. */ + ret = sgx_render_wait_only(dev, sgx); + if (ret) + goto unlock; + ret = sgx_twod_fence_ensure(dev, sf); + if (ret) + goto unlock; + memset(&ranges, 0, sizeof ranges); + ranges.sf = sf; + ranges.fence_va = sf->twod_fence_va; + chk = sgx_2d_check(stream, n, base, sgx_twod_range_ok, &ranges, &rep); + if (chk) { + drm_err(dev, "2D stream refused at dword %u (0x%08x): %s\n", + rep.at, rep.at < n ? stream[rep.at] : 0, rep.why); + sgx->twod_refused++; + ret = chk < 0 ? -EINVAL : -EACCES; + goto unlock; + } + /* Zero is what a fresh page holds, so it is never a sequence. */ + seq = ++sgx->twod_seq; + if (!seq) + seq = ++sgx->twod_seq; + sgx_2d_trailer(stream + n, + (u32)(sf->twod_fence_va - base) + SGX_TWOD_FENCE_PIXEL, + seq); + + sgx_kick_env_init(&sgx->scene.k, sgx, sgx->use_ctrl); + sgx_mmu_arm(sgx, &sf->mmu); + /* sgx535defs.h:2735 - BIF_CTRL bit 11 bypasses the MMU for the 2D + * requestor, and an untranslated offset is a physical address: that + * is how a bare-metal run wrote into kernel memory. Cleared at init; + * confirmed here, because nothing else is worth a wrong answer. */ + if (readl(sgx->regs + SGX_CR_BIF_CTRL) & SGX_MMU_ER_MASK) { + drm_warn(dev, "2D: BIF requestors were bypassing the MMU\n"); + sgx_mmu_enable_requestors(sgx); + } + if (readl(sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE) != (u32)base) { + drm_warn(dev, "2D: request base was 0x%08x\n", + readl(sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE)); + writel((u32)base, sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE); + (void)readl(sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE); + } + /* A job is synchronous, so the engine is idle here unless a stall + * left it otherwise - and the block does not recover on its own. */ + if (!sgx_twod_idle(sgx)) { + sgx_twod_report(sgx, "block busy before a job"); + sgx_stall_recover(sgx, sf, "a 2D block that was still busy"); + if (!sgx_twod_idle(sgx)) { + ret = -EBUSY; + goto unlock; + } + } + + /* The stream and the surfaces were written through write-combining + * mappings; the fault register latches; the event record must be + * empty before the event that fills it can be raised. + * + * This fence drains this CPU's write-combining buffers, which is not + * the whole of it: a buffer filled by a thread that has since been + * scheduled elsewhere is drained by a fence on the CPU it left, and + * only the caller can issue that. sgx_twod_flush() does, the way + * sgx_flush() does before a frame. */ + wmb(); + { + unsigned int i; + + if (sgx_twod_flush_src) + for (i = 0; i < ranges.n; i++) + sgx_twod_flush_range(sf, ranges.r[i].va, + ranges.r[i].size); + if (sgx_twod_probe) + for (i = 0; i < ranges.n && i < 2u; i++) + sgx_twod_probe_at(dev, sf, ranges.r[i].va); + } + /* Between the render's writes and the engine's reads. The two go + * through the same BIF but not through the same requestor, and a job + * that reads what a render wrote is what all four of the display + * server's blank copies had in common. */ + if (sgx_twod_bif) + sgx_twod_bif_flush(sgx); + sgx_fault_clear(sgx); + sgx_event_arm(sgx, SGX_2D_EV_COMPLETE); + t0 = ktime_get(); + ret = sgx_twod_push(sgx, stream, n + SGX_2D_TRAILER_DWORDS); + if (ret) { + drm_err(dev, "2D job %u: the input FIFO never drained\n", seq); + } else if (sgx_twod_async && (args->flags & SGX_BLIT_DEFER_WAIT)) { + /* On the engine. What the wait needs goes with it, because + * whoever comes back for it is whichever thread takes + * fire_lock next - and that may be another client. */ + sgx->twod_pend.active = true; + sgx->twod_pend.sf = sf; + sgx->twod_pend.seq = seq; + sgx->twod_pend.t0 = t0; + sgx->twod_jobs++; + sgx->twod_dwords += n; + goto unlock; + } else { + ret = sgx_twod_wait(dev, sgx, sf, seq); + } + if (ret) { + sgx_twod_report(sgx, "job failed"); + sgx->twod_stalls++; + /* The vendor has no 2D-only recovery: SGXReset() resets every + * block, TWOD included, and programs the request base inside + * the reset (sgxreset.c:190-510). sgx_spank() is that. */ + sgx_stall_recover(sgx, sf, "a 2D job that did not finish"); + drm_err(dev, "2D block after recovery: %s\n", + sgx_twod_idle(sgx) ? "idle" : "still busy"); + ret = -EIO; + goto unlock; + } + sgx->twod_jobs++; + sgx->twod_dwords += n; + if (sgx_timing) { + sgx->twod_us += ktime_us_delta(ktime_get(), t0); + if ((sgx->twod_jobs & 255) == 0) + drm_info(dev, + "2D: %llu call(s), %llu refused, %llu job(s), %llu dwords, %llu us on the engine\n", + sgx->twod_calls, sgx->twod_refused, + sgx->twod_jobs, sgx->twod_dwords, sgx->twod_us); + } +unlock: + mutex_unlock(&sgx->fire_lock); + mutex_unlock(&sf->vm_lock); + kfree(stream); + if (ret) + return ret; + if (!(args->flags & SGX_BLIT_FENCE_OUT)) { + args->out_sync_fd = -1; + return 0; + } + return sgx_signalled_fence_fd(sgx, &args->out_sync_fd); +} + +static int sgx_ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_get_param *args = data; + + switch (args->param) { + case SGX_PARAM_CORE_ID: + /* The register, not a constant: the old 0x01130000 did not + * match its layout and would have read as revision 1.19.0. */ + args->value = psb_sgx_of(dev)->core_rev; + return 0; + case SGX_PARAM_CORE_CLOCK: + args->value = 200000000; /* measured on the message bus */ + return 0; + case SGX_PARAM_VM_START: + if (args->index >= ARRAY_SIZE(sgx_windows)) + return -EINVAL; + args->value = sgx_windows[args->index].start; + return 0; + case SGX_PARAM_VM_SIZE: + if (args->index >= ARRAY_SIZE(sgx_windows)) + return -EINVAL; + args->value = sgx_windows[args->index].size; + return 0; + case SGX_PARAM_TWOD_BASE: + if (!sgx_twod) + return -EINVAL; + args->value = SGX_TWOD_BASE_VA; + return 0; + default: + return -EINVAL; + } +} + +/* Who may take the console framebuffer as a render target. + * + * The mapping is read-write and it is the memory the display is scanning out, + * so whoever holds it can read every pixel on the screen and write any pixel + * on it. That is not a render-node privilege: a render node exists to be + * opened by any local process, and it was DRM_RENDER_ALLOW. + * + * 0 nobody - the ioctl is refused and no caller can reach the framebuffer + * 1 the DRM master alone, which is the display server that owns the screen + * 2 an authenticated client of the master (default). This is the bar the + * X server already sets for reading the screen through XGetImage, so it + * grants nothing the session did not already have + * 3 any render-node client, which is what this used to be + * + * It fails closed: an unknown value is treated as 0. */ +static unsigned int sgx_scanout_policy = 2; +module_param_named(sgx_scanout_policy, sgx_scanout_policy, uint, 0644); +MODULE_PARM_DESC(sgx_scanout_policy, + "who may map the console framebuffer: 0 nobody, 1 the DRM master, 2 an authenticated client (default), 3 any render client"); + +static int sgx_scanout_permitted(struct drm_file *file) +{ + switch (sgx_scanout_policy) { + case 3: + return 0; + case 2: + if (!drm_is_render_client(file)) + return 0; /* DRM_AUTH already checked by the core */ + return -EACCES; + case 1: + return drm_is_current_master(file) ? 0 : -EACCES; + default: + return -EACCES; + } +} + +/* Hand the caller the console framebuffer as a render target. + * + * The buffer is gma500's and stays gma500's: this maps it into the caller's + * GPU address space at the render-target window and nothing more. Rendering + * into it is then rendering into what the display is scanning out, with no + * copy anywhere. */ +static int sgx_ioctl_scanout(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_sgx_scanout *args = data; + struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct sgx_file *sf = file->driver_priv; + u64 va = sgx_windows[SGX_VM_SURFACE].start; + phys_addr_t phys; + u64 size; + int ret; + + ret = sgx_scanout_permitted(file); + if (ret) + return ret; + if (!sf || !sgx_pad_ok(args->pad)) + return -EINVAL; + if (!dev_priv->scanout.valid) + return -ENODEV; + + phys = dev_priv->stolen_base + dev_priv->scanout.offset; + size = PAGE_ALIGN(dev_priv->scanout.size); + if (!sgx_in_window(va, size, sgx_windows[SGX_VM_SURFACE].start, + sgx_windows[SGX_VM_SURFACE].size)) + return -ENOSPC; + + mutex_lock(&sf->vm_lock); + mutex_lock(&psb_sgx_of(dev)->fire_lock); + (void)sgx_render_wait(dev, psb_sgx_of(dev)); + ret = sgx_mmu_bind_phys(&sf->mmu, va, phys, size); + mutex_unlock(&psb_sgx_of(dev)->fire_lock); + if (!ret) + ret = sgx_vm_reserve(sf, va, size); + mutex_unlock(&sf->vm_lock); + if (ret) + return ret; + + args->gpu_va = va; + args->pitch = dev_priv->scanout.pitch; + args->width = dev_priv->scanout.width; + args->height = dev_priv->scanout.height; + args->size = dev_priv->scanout.size; + drm_dbg(dev, "scanout at phys 0x%llx mapped at 0x%08llx, %ux%u pitch %u\n", + (u64)phys, va, args->width, args->height, args->pitch); + return 0; +} + +const struct drm_ioctl_desc psb_sgx_ioctls[] = { + DRM_IOCTL_DEF_DRV(SGX_GEM_NEW, sgx_ioctl_gem_new, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_GEM_MAP, sgx_ioctl_gem_map, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_GEM_WAIT, sgx_ioctl_gem_wait, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_VM_BIND, sgx_ioctl_vm_bind, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_SUBMIT, sgx_ioctl_submit, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_GET_PARAM, sgx_ioctl_get_param, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_USE_BASE, sgx_ioctl_use_base, DRM_RENDER_ALLOW), + /* DRM_AUTH is what makes policy 2 mean anything: the core checks it + * for a primary-node caller and skips it for a render-node one, which + * sgx_scanout_permitted() then refuses. */ + DRM_IOCTL_DEF_DRV(SGX_SCANOUT, sgx_ioctl_scanout, + DRM_RENDER_ALLOW | DRM_AUTH), + DRM_IOCTL_DEF_DRV(SGX_WAIT, sgx_ioctl_wait, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_BLIT, sgx_ioctl_blit, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(SGX_VISTEST, sgx_ioctl_vistest, DRM_RENDER_ALLOW), + /* The video decoder shares the render node; nothing in msvdx.c touches + * the display either. */ + DRM_IOCTL_DEF_DRV(MSVDX_GET_PARAM, msvdx_ioctl_get_param, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MSVDX_VM_BIND, msvdx_ioctl_vm_bind, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MSVDX_PING, msvdx_ioctl_ping, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(MSVDX_DECODE, msvdx_ioctl_decode, DRM_RENDER_ALLOW), +}; + +DEFINE_DRM_GEM_FOPS(sgx_fops); + +/* A true reset, so a bad state does not cost a reboot. + * + * The block soft reset in sgx_cold_init() does not recover a DPM left + * mid-operation by a failed submit: the next heap load times out, and so does + * every one after it. The device's PCI power management capability does + * recover it. Its No_Soft_Reset bit is clear, which means a D3hot to D0 + * transition performs an internal reset and does not preserve state - which is + * the whole point here - and the kernel already picks that method for this + * device ("pm" in its reset_method). + * + * The punit route gma500 uses on Cedarview is not available: the message bus + * on this host bridge answers 0x80000000 to every port and offset, so the + * graphics island's APM base cannot be read and the island cannot be gated + * directly. + */ +static void sgx_hw_reset(struct pci_dev *pdev) +{ + if (!sgx_reset) + return; + /* The transition is done by hand rather than through + * pci_reset_function(), which takes the device lock - and probe is + * already called holding it, so asking for it here deadlocks insmod in + * pci_dev_lock with no way out but a power cycle. */ + pci_save_state(pdev); + if (pci_set_power_state(pdev, PCI_D3hot)) { + dev_warn(&pdev->dev, "could not enter D3hot; not reset\n"); + pci_restore_state(pdev); + return; + } + msleep(20); + { + /* current_state is what the core believes; PMCSR is what the + * device reports. A transition the device did not make is a + * reset that did not happen. */ + u16 pmcsr = 0; + int pm = pdev->pm_cap; + + if (pm) + pci_read_config_word(pdev, pm + PCI_PM_CTRL, &pmcsr); + dev_info(&pdev->dev, "in D3hot: core says D%d, PMCSR 0x%04x\n", + pdev->current_state, pmcsr); + } + if (pci_set_power_state(pdev, PCI_D0)) + dev_warn(&pdev->dev, "could not return to D0\n"); + else + dev_info(&pdev->dev, "reset through D3hot (No_Soft_Reset is clear)\n"); + pci_restore_state(pdev); + msleep(20); +} + +#define SGX_R_ISP_CTRL 0x0414 +#define SGX_S_TA_STATE 0x0274 +#define SGX_S_DPM_STATE 0x0480 +#define SGX_R_DPM_ALLOC_MODE 0x0630 +#define SGX_R_DPM_CONTEXT 0x063c +#define SGX_R_DPM_PARTIAL 0x0658 +#define SGX_R_DPM_ZLS_ENABLE 0x065c + +/* sgxtri's frame_dpm_reset(). A frame that did not finish leaves the DPM's + * allocation mode walked into another context, DPM_PARTIAL and DPM_CONTEXT set + * for a partial render, two bits stuck in DPM_STATE and ISP_CTRL missing bit 8 + * - none of which the next frame's clean-scene bind undoes. Rendering on top + * of that produces a wrong frame, or none, even when nothing was ever short of + * memory. The list was established by diffing every register the recovery + * touches between a clean frame's end and a recovered one's; see the comment + * on frame_dpm_reset() in tools/baremetal/sgxtri.c. + * + * The allocation mode is written even when it matches, because the DPM acts on + * the write and the cached copy is no longer what the hardware holds - which + * is also why this belongs only on the recovery path. sgxtri calls it when a + * frame came back out of memory and nowhere else; calling it before every load + * issues a spurious DPM action per frame, and measured 0 renders in 10 where + * leaving it out gave 9. */ +static void sgx_dpm_reset(struct sgx_device *sgx) +{ + writel(0, sgx->regs + SGX_R_DPM_ZLS_ENABLE); + writel(0, sgx->regs + SGX_R_DPM_PARTIAL); + writel(0, sgx->regs + SGX_R_DPM_CONTEXT); + sgx->scene.alloc_mode = sgx->alloc_mode0; + writel(sgx->alloc_mode0, sgx->regs + SGX_R_DPM_ALLOC_MODE); + /* The mask plane goes with the depth load it rides on. Left latched it + * would have the next clean frame read back a plane no partial render + * stored, which is worse than not accumulating one. */ + writel(readl(sgx->regs + SGX_S_DPM_STATE) & + ~(6u | SGX_S_ZLS_LOADMASK | SGX_S_ZLS_STOREMASK), + sgx->regs + SGX_S_DPM_STATE); + writel((readl(sgx->regs + SGX_R_ISP_CTRL) & ~0x100u) | + (sgx->isp_ctrl0 & 0x100u), sgx->regs + SGX_R_ISP_CTRL); +} + +/* gma500's psb_spank(): reset every block, then hold CLEAR_FAULT long enough + * for the BIF to actually drop the fault. The timing is theirs - a read-back + * between assert and deassert is not enough. */ +static void sgx_spank(struct sgx_device *sgx) +{ + u32 ctrl; + + writel(SGX_CS_RESET_BIF | SGX_CS_RESET_DPM | SGX_CS_RESET_TA | + SGX_CS_RESET_USE | SGX_CS_RESET_ISP | SGX_CS_RESET_TSP | + SGX_CS_RESET_TWOD, sgx->regs + SGX_CR_SOFT_RESET); + (void)readl(sgx->regs + SGX_CR_SOFT_RESET); + msleep(1); + writel(0, sgx->regs + SGX_CR_SOFT_RESET); + wmb(); + + ctrl = readl(sgx->regs + SGX_CR_BIF_CTRL); + writel(ctrl | SGX_CB_CTRL_CLEAR_FAULT, sgx->regs + SGX_CR_BIF_CTRL); + wmb(); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + msleep(1); + writel(ctrl & ~SGX_CB_CTRL_CLEAR_FAULT, sgx->regs + SGX_CR_BIF_CTRL); + (void)readl(sgx->regs + SGX_CR_BIF_CTRL); + writel((u32)SGX_TWOD_BASE_VA, sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE); +} + +/* sgxtri's sgx_cold_init(). The module used to program only the vopt/USE side + * and leave the rest as found, which worked only because a bare-metal run had + * cold-initialised the core first; from a clean boot the ISP faulted at + * 0x00101000, its 0x30101000 fetch missing BIF_3D_REQ_BASE. */ +static void sgx_cold_init(struct sgx_device *sgx) +{ + unsigned int i; + u32 val; + + if (readl(sgx->regs + SGX_CR_CLKGATECTL) != SGX_CLKGATE_ALL_ON) { + writel(SGX_CLKGATE_ALL_ON, sgx->regs + SGX_CR_CLKGATECTL); + (void)readl(sgx->regs + SGX_CR_CLKGATECTL); + } + + /* psb_regman.c:133-166 - every managed USE base starts at 0, DM PIXEL. */ + for (i = 0; i < SGX_USE_REG_NUM; i++) + writel((u32)SGX_CUC_DM_PIXEL << SGX_CUC_BASE_DM_SHIFT, + sgx->regs + SGX_CR_USE_CODE_BASE(SGX_USE_REG_FIRST + i)); + + val = SGX_CS_RESET_BIF | SGX_CS_RESET_DPM | SGX_CS_RESET_TA | + SGX_CS_RESET_USE | SGX_CS_RESET_ISP | SGX_CS_RESET_TSP | + SGX_CS_RESET_TWOD; + writel(val, sgx->regs + SGX_CR_SOFT_RESET); + (void)readl(sgx->regs + SGX_CR_SOFT_RESET); + usleep_range(1000, 2000); + + /* SGXReset()'s order, which is not the one this had. The vendor holds + * the BIF in reset while it clears BIF_CTRL, the bank registers and + * every directory-list base (sgxreset.c:374-386, :58-98), releases the + * BIF alone (:493), then writes the real bases (:500-511) and + * invalidates (:514) before letting the rest of the pipeline go + * (:519). Here everything came out of reset first and the bases were + * written to a BIF that was already arbitrating - so a request in + * flight could be answered from the base it was about to be given, and + * the state that answered it was whatever the last frame left. + * + * sgx_reset_order=0 restores the old sequence, which is the way to + * tell a bring-up failure caused by this from one that is not. */ + if (sgx_reset_order) { + writel(0, sgx->regs + SGX_CR_BIF_BANK0); + writel(0, sgx->regs + SGX_CR_BIF_BANK1); + writel(0, sgx->regs + SGX_CR_BIF_DIR_LIST_BASE0); + writel(0, sgx->regs + SGX_CR_BIF_DIR_LIST_BASE1 + 4); + (void)readl(sgx->regs + SGX_CR_BIF_BANK1); + + /* Release the BIF and nothing else. */ + writel(val & ~SGX_CS_RESET_BIF, sgx->regs + SGX_CR_SOFT_RESET); + (void)readl(sgx->regs + SGX_CR_SOFT_RESET); + usleep_range(100, 200); + } + + /* psb_sgx.c:306-307, psb_drv.c:665-666 - the 2D, PDS and 3D data + * masters each offset their requests by one of these. */ + writel((u32)SGX_TWOD_BASE_VA, sgx->regs + SGX_CR_BIF_TWOD_REQ_BASE); + writel(SGX_PDS_EXEC_VA, sgx->regs + SGX_CR_PDS_EXEC_BASE); + writel((u32)sgx_windows[SGX_VM_RASTGEOM].start, + sgx->regs + SGX_CR_BIF_3D_REQ_BASE); + (void)readl(sgx->regs + SGX_CR_BIF_3D_REQ_BASE); + + /* Everything out of reset, and only now. */ + writel(0, sgx->regs + SGX_CR_SOFT_RESET); + (void)readl(sgx->regs + SGX_CR_SOFT_RESET); + sgx_fault_clear(sgx); + + if (!sgx_reset_order) { + writel(0, sgx->regs + SGX_CR_BIF_BANK0); + writel(0, sgx->regs + SGX_CR_BIF_BANK1); + (void)readl(sgx->regs + SGX_CR_BIF_BANK1); + } + + /* The visibility counters survive the block reset above, and nothing + * else zeroes them, so the first armed render would carry whatever the + * firmware or the previous driver left. */ + writel(SGX_R_ISP_VISTEST_CLEAR_ALL, sgx->regs + SGX_R_ISP_VISTEST_CTRL); + (void)readl(sgx->regs + SGX_R_ISP_VISTEST_CTRL); + + writel(0, sgx->regs + SGX_CR_EVENT_HOST_ENABLE); + writel(0, sgx->regs + SGX_CR_EVENT_HOST_ENABLE2); + writel(readl(sgx->regs + SGX_CR_EVENT_STATUS), + sgx->regs + SGX_CR_EVENT_HOST_CLEAR); + writel(readl(sgx->regs + SGX_CR_EVENT_STATUS2), + sgx->regs + SGX_CR_EVENT_HOST_CLEAR2); + (void)readl(sgx->regs + SGX_CR_EVENT_STATUS); + + /* The two writes above take the interrupt line down with them, and + * this runs after gma_irq_postinstall() and again after every + * recovery - which is why the SGX has had no interrupts at all since + * the module started asking for them. Put back what the render wait + * needs. The record goes with it: the status word was just wiped, so + * anything still pending here has no hardware event behind it. */ + { + unsigned long flags; + + spin_lock_irqsave(&sgx->event_lock, flags); + sgx->event_pending = 0; + sgx->event_irq_mask = 0; + spin_unlock_irqrestore(&sgx->event_lock, flags); + } + sgx_irq_sync(sgx); +} + +/* A frame that did not finish leaves the core mid-scene: the TA still holds + * the parameter heap, the ISP still holds the render, and the next client + * inherits both. Reset the blocks the way gma500 does, clear the fault it + * left, and bring the core back up so the next submit starts from a known + * state instead of the wreckage of this one. + * + * The DPM has to be reloaded afterwards, so the per-file flag is dropped: the + * heap this client had is no longer what the hardware holds. */ +/* Everything the core needs to come back to a known state, with no policy of + * its own: the register bring-up, the DPM, and the per-file scene state the + * reset invalidates. Shared by a stalled frame and a system resume, which want + * exactly the same work for different reasons. */ +static void sgx_core_reinit(struct sgx_device *sgx) +{ + unsigned int i; + struct sgx_kick_env e; + + sgx_spank(sgx); + sgx_kick_env_init(&e, sgx, 0); + sgx_cold_init(sgx); + sgx_mmu_enable_requestors(sgx); + sgx_set_vopt(&e, sgx->vopt); + sgx->use_ctrl = sgx_hw_init(&e, sgx->vopt); + /* sgx_hw_init() has just written the DPM's context id register to + * zero. The shadow has to follow it: sgx_dpm_reset() below writes + * alloc_mode0 back to the register, and taking that from a shadow the + * failed frame left put the DPM's allocation, deallocation and + * store/load sides on whichever context that frame used - including + * for the heap load that follows, which is once per power-on. */ + sgx->scene.alloc_mode = 0; + sgx->alloc_mode0 = 0; + sgx->isp_ctrl0 = readl(sgx->regs + SGX_R_ISP_CTRL); + sgx_dpm_reset(sgx); + sgx->dpm_loaded = false; + /* Both contexts' DPM state tables went with the reset, so no scene may + * take one back expecting to find its own, and no client still owns + * the heap the reset cleared. */ + sgx_ctx_pool_reset(&sgx->ctx); + sgx->dpm_owner = NULL; + /* The recovery's own state, which the register reset does not reach: + * left set, the next frame's raster fire takes the partial-render + * branch instead of the plain one. + * + * Every file, not only the one whose frame failed. The reset above is + * core-wide, so it invalidates what every client has on the hardware - + * and clearing one client's scenes left the next frame of every other + * client stalling too. One bad frame from a game took the X server + * down with it, which is why the screen stayed black rather than + * losing a frame. */ + { + struct sgx_file *f; + + list_for_each_entry(f, &sgx->files, node) + for (i = 0; i < SGX_SCENES_PER_FILE; i++) { + f->scenes[i].cookie[13] = 0; + f->scenes[i].cookie[14] = 0; + f->scenes[i].cleared = false; + } + } + sgx->dpm_dirty = false; +} + +/* A system resume. The graphics island lost power, so every register is back + * at its reset value and the heap the DPM held is gone - while dpm_loaded + * still says it is there, the load is skipped and every frame faults until the + * module is reloaded. Nothing else reaches this state, because the load is + * once per power-on. */ +void psb_sgx_resume(struct sgx_device *sgx) +{ + if (!sgx->regs) + return; + mutex_lock(&sgx->fire_lock); + sgx_core_reinit(sgx); + mutex_unlock(&sgx->fire_lock); +} + +/* A system suspend. A submit may have come back with its render still on the + * core, and nothing else drains it before the island loses power: the frame + * is lost either way, but pend stays armed across the resume, and the next + * thing to take fire_lock then waits a full timeout on a core that was reset + * underneath it and recovers a stall that never happened. Before the interrupt + * is uninstalled, because the wait sleeps on it. */ +void psb_sgx_suspend(struct sgx_device *sgx) +{ + if (!sgx->regs) + return; + mutex_lock(&sgx->fire_lock); + (void)sgx_render_wait(sgx->ddev, sgx); + mutex_unlock(&sgx->fire_lock); +} + +static void sgx_stall_recover(struct sgx_device *sgx, struct sgx_file *sf, + const char *what) +{ + if (!sgx_recover) + return; + /* Counted and reported, not acted on: one frame that did not finish + * says nothing about the next one, and refusing every submit after it + * left a client rendering nothing for the rest of its life over a + * single bad frame. The core is recovered each time instead. */ + if (++sgx->stalls > sgx_max_stalls) + drm_warn_once(sgx->ddev, + "%u frames in a row did not finish; the core is recovered each time\n", + sgx->stalls - 1); + drm_warn(sgx->ddev, "recovering the core after %s\n", what); + sgx_core_reinit(sgx); + (void)sf; +} + +/* Put the core back to just-probed state: the PCI reset that actually clears + * the DPM, then the same bring-up probe does. Called when the last client has + * gone, so nothing is mid-frame. */ +static void sgx_core_restart(struct sgx_device *sgx) +{ + struct sgx_kick_env e; + + if (!sgx->regs) + return; + if (sgx->pdev) + sgx_hw_reset(sgx->pdev); + sgx_kick_env_init(&e, sgx, 0); + sgx_cold_init(sgx); + sgx_mmu_enable_requestors(sgx); + sgx_set_vopt(&e, sgx->vopt); + sgx->use_ctrl = sgx_hw_init(&e, sgx->vopt); + sgx->scene.alloc_mode = 0; + sgx->alloc_mode0 = 0; + sgx->isp_ctrl0 = readl(sgx->regs + SGX_R_ISP_CTRL); + sgx->stalls = 0; + sgx_ctx_pool_reset(&sgx->ctx); +} + +/* Bring the 3D core up. Called from psb_driver_load() once gma500 has mapped + * the SGX window, so this neither claims a device nor registers a drm_device: + * there is one of each already. */ +int psb_sgx_init(struct drm_device *dev) +{ + struct sgx_device *sgx = psb_sgx_of(dev); + void __iomem *regs = to_drm_psb_private(dev)->sgx_reg; + int ret; + + /* Before anything is allocated, so a refusal has nothing to undo. */ + if (sgx_scene_ctx < SGX_CTX_MODE_ONE || + sgx_scene_ctx > SGX_CTX_MODE_TABLES) { + drm_err(dev, "sgx_scene_ctx=%u is not 1, 2 or 3\n", + sgx_scene_ctx); + return -EINVAL; + } + + sgx->ddev = dev; + /* Without this the reset below, and the one when the last client + * closes, are both skipped - sgx_core_restart() guards on it. The DPM + * then keeps the heap it was handed across a module reload, refuses + * the next one with "the DPM did not take the heap", and every frame + * after that renders nothing. Only a power cycle cleared it. */ + sgx->pdev = dev_is_pci(dev->dev) ? to_pci_dev(dev->dev) : NULL; + + ret = drmm_mutex_init(sgx->ddev, &sgx->fire_lock); + if (ret) + return ret; + sgx->null_pd = (u32 *)get_zeroed_page(GFP_KERNEL); + if (!sgx->null_pd) + return -ENOMEM; + sgx->null_pd_phys = virt_to_phys(sgx->null_pd); + sgx_mmu_flush_range(sgx->null_pd, PAGE_SIZE); + spin_lock_init(&sgx->fence_lock); + spin_lock_init(&sgx->event_lock); + init_waitqueue_head(&sgx->event_wq); + sgx->fence_ctx = dma_fence_context_alloc(1); + sgx->regs = regs; + + /* The hardware scene contexts, all free, as psb_scheduler_init() does. */ + INIT_LIST_HEAD(&sgx->files); + sgx->pend.ctx = -1; + if (WARN_ON(sgx_ctx_pool_init(&sgx->ctx, + sgx_scene_ctx == SGX_CTX_MODE_ONE ? + 1u : SGX_CTX_COUNT))) + return -EINVAL; + + /* Bring the core up before anything can submit to it. Nothing did this + * until now, and a tiler kicked on an uninitialised core simply never + * finishes. */ + { + struct sgx_kick_env e; + + /* What sgx_reset has always promised and never did: the DPM's + * heap load is a handshake with no teardown, so a core that + * comes up still holding the last load refuses the next one. */ + if (sgx->pdev) + sgx_hw_reset(sgx->pdev); + sgx_kick_env_init(&e, sgx, 0); + sgx_cold_init(sgx); + sgx_mmu_enable_requestors(sgx); + sgx_set_vopt(&e, sgx->vopt); + sgx->use_ctrl = sgx_hw_init(&e, sgx->vopt); + sgx->scene.alloc_mode = 0; + sgx->alloc_mode0 = 0; + sgx->isp_ctrl0 = readl(sgx->regs + SGX_R_ISP_CTRL); + sgx->core_rev = readl(sgx->regs + SGX_I_CORE_REVISION); + sgx->hw_up = true; + drm_info(sgx->ddev, "core up, revision 0x%08x, USE_CTRL 0x%08x\n", + sgx->core_rev, sgx->use_ctrl); + } + + return 0; +} + +/* gma500 owns the mapping and the drm_device, so there is nothing to undo here + * beyond leaving the core somewhere the next load can bring it up from. */ +void psb_sgx_fini(struct drm_device *dev) +{ + struct sgx_device *sgx = psb_sgx_of(dev); + + /* The register mapping goes away below, and a deferred render is + * waited for by reading registers. On ddev, because a probe that + * failed before psb_sgx_init() got to drmm_mutex_init() still comes + * through here and that mutex is then only zeroes. */ + if (sgx->ddev) { + mutex_lock(&sgx->fire_lock); + (void)sgx_render_wait(dev, sgx); + mutex_unlock(&sgx->fire_lock); + } + if (sgx->regs && sgx->hw_up) + sgx_spank(sgx); + sgx->hw_up = false; + sgx->regs = NULL; + /* The parameter heap and the DPM's page table belong to the device and + * outlive every client, so this is where they go. */ + vfree(sgx->dpm_pt_seed); + sgx->dpm_pt_seed = NULL; + sgx->dpm_pt_seed_size = 0; + if (sgx->dpm_pt_obj) { + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(sgx->dpm_pt_obj)); + drm_gem_object_put(sgx->dpm_pt_obj); + sgx->dpm_pt_obj = NULL; + } + if (sgx->param_obj) { + drm_gem_shmem_unpin(to_drm_gem_shmem_obj(sgx->param_obj)); + drm_gem_object_put(sgx->param_obj); + sgx->param_obj = NULL; + } + if (sgx->null_pd) { + free_page((unsigned long)sgx->null_pd); + sgx->null_pd = NULL; + } + sgx->dpm_loaded = false; +} + + +/* The header hands gma500 a constant; this is what keeps it honest. */ +static void __maybe_unused psb_sgx_ioctl_count_check(void) +{ + BUILD_BUG_ON(ARRAY_SIZE(psb_sgx_ioctls) != PSB_SGX_NUM_IOCTLS); +} + +int psb_sgx_open(struct drm_device *dev, struct drm_file *file) +{ + return sgx_open(dev, file); +} + +void psb_sgx_postclose(struct drm_device *dev, struct drm_file *file) +{ + sgx_postclose(dev, file); + msvdx_file_release(dev, file); +} diff -urNp a/drivers/gpu/drm/gma500/psb_sgx_render.h b/drivers/gpu/drm/gma500/psb_sgx_render.h --- a/drivers/gpu/drm/gma500/psb_sgx_render.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/psb_sgx_render.h 2026-09-08 10:56:21.880775620 +0200 @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * The 3D core's render interface, inside gma500. + * + * gma500 owns the PCI device, the register mapping and the drm_device; this is + * the part of it that drives the SGX and exposes a render node. Keeping the two + * in one driver is what lets a machine run the display and the 3D core at the + * same time - two modules cannot both bind the device. + * + * Copyright (C) 2026 René Rebe + */ +#ifndef _PSB_SGX_RENDER_H_ +#define _PSB_SGX_RENDER_H_ + +#include +#include +#include + +#include +#include +#include + +#include "msvdx_drm.h" +#include "sgx_init.h" +#include "sgx_scene.h" +#include "sgx_scenectx.h" +#include "sgx_use.h" + +struct sgx_scene; +struct sgx_file; + +struct sgx_device { + struct drm_device *ddev; /* gma500's, not ours */ + void __iomem *regs; /* the mapping gma500 published; not ours */ + + /* One set of hardware registers, so one submit at a time. Two frames + * interleaved would each program the DPM out from under the other. */ + struct mutex fire_lock; + struct sgx_scene_env scene; + + u8 vopt[SGX_VOPT_N]; /* the core's errata workarounds */ + u32 use_ctrl; /* what sgx_hw_init() settled on */ + u32 core_rev; /* EUR_CR_CORE_REVISION, read at probe */ + bool hw_up; + + spinlock_t fence_lock; + u64 fence_ctx; + u64 fence_seqno; + + /* Completion events delivered by the interrupt handler instead of + * polled. gma_sgx_interrupt() records every bit it acknowledges here + * and wakes the waiter, because acknowledging one is what takes it out + * of EVENT_STATUS - a poll that ran afterwards would never see it. + * event_irq_mask is what the driver asked the hardware to raise a line + * for; a waiter for anything outside it still polls. */ + wait_queue_head_t event_wq; + spinlock_t event_lock; + u32 event_pending; + u32 event_irq_mask; + u64 irq_events; /* waits ended by the interrupt */ + u64 poll_events; /* waits ended by the status re-read */ + /* Invalidates where the BIF never retired its outstanding reads. Not + * zero means the drain is not doing what it is there for, and the + * invalidate is landing on a directory cache that is still being + * refilled from the directory being replaced. */ + u64 bif_drain_timeouts; + + /* The DPM's heap load is a handshake with no matching teardown, so the + * state it leaves outlives the client that made it and accumulates: + * eight or so clients in and the tiler stops finishing. Resetting the + * core once the last one has gone is what keeps that from being the + * caller's problem. */ + /* What a clean cold init leaves behind, so a recovery can put it back: + * the DPM's allocation mode and the ISP control bit a partial render + * clears. sgxtri keeps the same two as alloc_mode0 and isp_ctrl0. */ + u32 alloc_mode0, isp_ctrl0; + + struct pci_dev *pdev; /* NULL on the auxiliary path */ + unsigned int open_count; + /* GTT held by every client's SGX_BO_SCANOUT objects together. The GTT + * is the display's, an object holds its share for life, and a render + * client can ask for one per call - so it is budgeted. */ + atomic64_t scanout_gtt; + u64 frames; /* submits since the core came up */ + bool dpm_dirty; + + /* Recovering the core is itself a reset of blocks that may still be + * mastering, so doing it once a frame - which is what a client that + * exhausts parameter memory every frame provokes - locks the machine + * hard. Count consecutive failures and stop submitting instead. */ + unsigned int stalls; + u64 recoveries; /* partial renders since the core came up */ + + /* The DPM's heap load is device-wide, not per file. It is a handshake + * carrying SGX_H_MEM_FLAG_INIT that the hardware accepts once per + * power-on, so a second client re-issuing it is refused - which is + * what stopped a GL client from running alongside the X server's own + * context. One load serves every file: the page directory is re-armed + * per submit, so the heap the DPM holds resolves to the submitting + * client's own pages. */ + bool dpm_loaded; + /* The part's two hardware scene contexts, least-recently-used the way + * psb_schedule.c does: a scene keeps the context it last fired on if + * nothing took it in the meantime, and a scene that needs one takes + * the least recently returned. Pinning a context per file put the + * server on one and every client on the other. + * + * A context whose render has been fired and not waited for belongs to + * that render until the wait: the DPM is still deallocating that + * scene's parameter pages in it. sgx_scene_ctx says how many of the + * two to use. */ + struct sgx_ctx_pool ctx; + /* Every open file. A recovery resets the core for all of them, so the + * per-file scene state it cannot reach has to be cleared for all of + * them too. */ + struct list_head files; + /* Submit timing, averaged over a run of frames. */ + u64 t_pre, t_ta, t_ras; + s64 t_ras_min, t_ras_max; + unsigned int t_n; + + /* A render that was fired and not waited for. + * + * The core runs one frame at a time, so there is at most one, and it + * belongs to the device rather than to the file that fired it: the + * thread that comes back for it is whichever one takes fire_lock next, + * and that may be another client. Everything the wait needs is here + * because the submit that recorded it has long returned. */ + struct { + bool active; + struct sgx_file *sf; + struct sgx_scene *sc; + int ctx; /* the hardware scene context it holds */ + u32 rca; + u32 w, h; + ktime_t t_in, t_ta, t_ras; + /* SGX_SUBMIT_VISTEST: the frame armed objects for the ISP's + * visibility counters, so the wait harvests them. */ + bool vistest; + } pend; + + u32 dpm_pages; + u32 dpm_cookie[16]; + + /* The parameter heap and the DPM's page table, owned by the device + * rather than by a client. + * + * They have to be: the load is one handshake per boot, and what it + * hands the DPM is one heap described by one table. A per-client heap + * meant the second client had different pages behind the same address + * and a page table the load never filled, so its tiler walked into + * nothing - which is why a run only ever meant anything on a freshly + * booted machine. One heap, bound into every client's address space at + * the same place, is what makes the single load serve them all. */ + struct drm_gem_object *param_obj; + u64 param_va, param_size; + struct drm_gem_object *dpm_pt_obj; + u64 dpm_pt_va, dpm_pt_size; + + /* With sgx_heap_per_client the two above are unused and each file + * carries its own pair. The registers are still device-wide, so the + * device tracks whose heap they currently describe and the submit + * re-loads them when that is not the submitting client's. + * + * dpm_pt_seed is a copy of the DPM page table as the one INIT + * handshake left it. The hardware takes that handshake once per + * power-on, so a later client's table cannot be filled by repeating + * it - it is seeded from this instead. */ + struct sgx_file *dpm_owner; + void *dpm_pt_seed; + size_t dpm_pt_seed_size; + + /* An empty page directory, installed when a file closes so the BIF + * never holds one that has been freed. */ + u32 *null_pd; + unsigned long null_pd_phys; + + /* The 2D block: the sequence number its last job wrote, and counts. */ + u32 twod_seq; + /* Calls counts every entry into the ioctl, jobs only the ones the + * engine ran. The two were one number, and a report of "0 jobs" then + * meant either that nothing had asked or that everything asked had + * been refused - which are opposite diagnoses. */ + u64 twod_calls, twod_refused; + u64 twod_jobs, twod_dwords, twod_us, twod_stalls; + /* A job that was pushed and not waited for. One record, because the + * engine consumes streams in order: waiting for the newest sequence + * is waiting for every job behind it. The file is here because the + * page the sequence lands in is that file's. */ + struct { + bool active; + struct sgx_file *sf; + u32 seq; + ktime_t t0; + } twod_pend; +}; + +int psb_sgx_init(struct drm_device *dev); +void psb_sgx_fini(struct drm_device *dev); +/* A system resume: the island lost power, so the core is brought back up and + * the DPM marked unloaded. Without it dpm_loaded stays true over a suspend and + * every frame afterwards faults. */ +void psb_sgx_resume(struct sgx_device *sgx); +/* A system suspend: wait out a render the last submit left on the core. */ +void psb_sgx_suspend(struct sgx_device *sgx); +/* The render node's interface; nothing in it touches the display, so every + * entry is DRM_RENDER_ALLOW. */ +extern const struct drm_ioctl_desc psb_sgx_ioctls[]; + +/* drm_driver is const, so the count has to be a constant too. The definition + * carries a BUILD_BUG_ON against ARRAY_SIZE, so the two cannot drift. + * + * DRM_IOCTL_DEF_DRV indexes by command number, so this is one past the highest + * one defined, not the number of entries: the SGX's own run to the visibility + * test at 0x0a, the video decoder's start at 0x10, and the slots between are + * NULL, which drm_ioctl() refuses with -EINVAL. */ +#define PSB_SGX_NUM_IOCTLS (DRM_MSVDX_DECODE + 1) +int psb_sgx_open(struct drm_device *dev, struct drm_file *file); +void psb_sgx_postclose(struct drm_device *dev, struct drm_file *file); + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_check.h b/drivers/gpu/drm/gma500/sgx_check.h --- a/drivers/gpu/drm/gma500/sgx_check.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_check.h 2026-09-08 10:56:21.881775634 +0200 @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * The two pieces of sgx_drv.c that are pure logic: which registers a submitted + * stream may write, and where a binding is allowed to land. They live in a + * header so the module and a host test can share them verbatim - the whitelist + * is a security boundary, and testing it only once the hardware is wired up + * would be leaving it untested for as long as that takes. + * + * Copyright (C) 2026 René Rebe + */ +#ifndef _SGX_CHECK_H_ +#define _SGX_CHECK_H_ + +/* + * Register whitelist. + * + * A submitted stream is {offset, value} pairs, which is what the hardware + * consumes, but an unchecked one lets userspace write any register on the + * device. These ranges are not guessed: they are every register the frame + * generators in tools/xpsb-open actually emit, enumerated and checked by + * test/sgx_realframe_test.c so the list cannot quietly stop matching what a + * working frame needs. + * + * An earlier version of this was three round ranges and was wrong in both + * directions - it refused 0x0800, 0x0a5c-0x0a64 and 0x0cb0, which a real + * raster stream writes, and it allowed the whole DPM block. That second part + * mattered more: 0x062c is DPM_PAGE_TABLE, and pointing it somewhere wrong is + * exactly the fault the out-of-memory work spent days on. The DPM belongs to + * the kernel's scene logic and no submitted stream has any business there. + */ +static const struct { unsigned int first, last; } sgx_reg_ok[] = { + { 0x0204, 0x0250 }, /* TA: parameter base, vertex source, kick */ + /* ISP and 3D, including the background object. Split around 0x0428, + * EUR_CR_ISP_START_RENDER: a stream that pulses it starts the render + * itself, before the scene is bound, and no real frame writes it - + * the kernel's kick sequence is the only place it belongs. */ + { 0x0400, 0x0424 }, + { 0x042c, 0x04dc }, + { 0x0800, 0x0800 }, + { 0x0a5c, 0x0a64 }, + { 0x0cb0, 0x0cb0 }, /* BIF 3D flush, which the raster stream emits */ +}; + +static inline int sgx_reg_allowed(unsigned int off) +{ + unsigned int i; + + if (off & 3) + return 0; + for (i = 0; i < sizeof sgx_reg_ok / sizeof sgx_reg_ok[0]; i++) + if (off >= sgx_reg_ok[i].first && off <= sgx_reg_ok[i].last) + return 1; + return 0; +} + +/* Nonzero if the stream is not acceptable: -1 if it is not whole {offset,value} + * pairs, 1 if an offset is outside the whitelist, in which case *bad - when + * given - is the dword index of that offset. Zero means acceptable. */ +static inline int sgx_stream_bad_at(const unsigned int *s, unsigned int dwords, + unsigned int *bad) +{ + unsigned int i; + + if (dwords & 1) + return -1; /* not whole pairs */ + for (i = 0; i < dwords; i += 2) + if (!sgx_reg_allowed(s[i])) { + if (bad) + *bad = i; + return 1; + } + return 0; +} + +/* Does [va, va+size) sit inside the window? */ +static inline int sgx_in_window(unsigned long long va, unsigned long long size, + unsigned long long start, + unsigned long long wsize) +{ + if (!size) + return 0; + if (va < start) + return 0; + if (va + size < va) /* wrap */ + return 0; + return va + size <= start + wsize; +} + +/* Do two bindings overlap? */ +static inline int sgx_overlaps(unsigned long long a, unsigned long long alen, + unsigned long long b, unsigned long long blen) +{ + return a < b + blen && b < a + alen; +} + +/* Reserved fields must be zero and unknown flags must be refused. + * + * Not a style rule. A field that is ignored today cannot be given a meaning + * tomorrow: an old kernel accepts a new flag, ignores it, and does something + * other than what the caller asked - silently. Refusing what is not understood + * is what makes the interface extensible at all, and it is the first thing + * asked of a new UAPI. + */ +static inline int sgx_flags_ok(unsigned int flags, unsigned int known) +{ + return (flags & ~known) == 0; +} + +static inline int sgx_pad_ok(unsigned int pad) +{ + return pad == 0; +} + +#endif /* _SGX_CHECK_H_ */ diff -urNp a/drivers/gpu/drm/gma500/sgx_cookie.h b/drivers/gpu/drm/gma500/sgx_cookie.h --- a/drivers/gpu/drm/gma500/sgx_cookie.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_cookie.h 2026-09-08 10:56:21.882775647 +0200 @@ -0,0 +1,187 @@ +/* SGX scene cookie - the macro-tile geometry and heap layout a fire needs. + * + * A transcription of xpsb_scene_info() and xpsb_ta_mem_info() in + * tools/xpsb-open/xpsb_hw.c, from Xpsb_scene_info at 0x3a40 and + * Xpsb_ta_mem_info at 0x3d70. + * + * Unlike sgx_kick.h and sgx_scene.h this touches no register at all: it is + * arithmetic on the render target size, producing the sixteen dwords that + * sgx_scene_switch_fire() consumes. That makes it exhaustively testable rather + * than sampled, and test/sgx_cookie_test.c does sweep it exhaustively over + * every render target size the hardware accepts. + * + * The two workaround flags are per core revision. SGX535 rev 1.2.1 - the only + * part this project has - enables neither, so those paths are transcribed from + * the binary and swept against it but have never run on silicon. They are + * marked where they occur. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_COOKIE_H_ +#define _SGX_COOKIE_H_ + +#define SGX_C_ALIGN4(v) (((v) + 3u) & ~3u) + +/* The scene cookie is sixteen dwords. Twelve come from here; cookie[13], + * cookie[14] and cookie[15] are the out-of-memory state, owned by the abort + * handler and the fire path, and are zeroed at scene setup. */ +#define SGX_C_COOKIE_DWORDS 16 + +struct sgx_cookie_caps { + int large_scene; /* vopt 18: >2047 scenes keep 2x2 macro tiles */ + int wide_mtile; /* vopt 20: 2 tiles per macro tile, not 1/2 */ +}; + +static inline unsigned int sgx_roundup_pow2(unsigned int v) +{ + unsigned int m, p; + + if (!v) + return 0; + m = v & 0x0fffffffu; + p = 1; + while (m > p) + p += p; + return p; +} + +/* A macro tile is a multiple of four tiles a side at one sample, and of two + * when that axis carries two samples: SetMT4Mode() and SetMT16Mode() in + * sgxrender_targets.c:587-666 pick the granularity from + * ui16MSAASamplesInX/Y. The vendor binary this file transcribes only ever + * built one-sample scenes, so the two-sample arm comes from the DDK. */ +#define SGX_C_ALIGN_MT(v, s) ((s) == 1u ? SGX_C_ALIGN4(v) : \ + (((v) + 1u) & ~1u)) + +/* Xpsb_scene_info at 0x3a40, with the sample count the vendor binary had no + * argument for. sx and sy are samples along each axis, 1 or 2: the part + * reaches 1x1 and 2x2 and nothing else, and refusing anything here is what + * keeps a scene from being described at a size the tiler will overrun. + * + * What is in sample tiles and what is in pixel tiles: + * + * cookie[2], cookie[3], cookie[4] are the macro-tile registers, which stay + * in pixel tiles - the part scales them itself from EUR_CR_TE_AA + * (sgxkick_client.c:1083-1089 hands EUR_CR_TE_MTILE1/2 the unmultiplied + * values while :1067-1077 sets the AA bits). + * + * cookie[5] and cookie[6] are the screen and pixel extents, also pixel + * (EUR_CR_TE_SCREEN and EUR_CR_MTE_SCREEN, sgxkick_client.c:1090-1105). + * + * The tail-pointer array and the region-header array are one entry per + * *sample* tile, so both grow by sx*sy: sgxrender_targets.c:2408-2409 + * multiplies tiles-per-macrotile by the sample counts before :2433-2435 + * sizes the region array and :2465-2468 sizes the tail pointers. + * + * Returns the parameter-buffer size the scene needs and the page range the + * caller has to clear before the tiler runs. */ +static inline int sgx_scene_info_ms(const struct sgx_cookie_caps *caps, + unsigned int w, unsigned int h, + unsigned int sx, unsigned int sy, + unsigned int *cookie, unsigned int *size, + unsigned int *clear_p_start, + unsigned int *clear_num_pages) +{ + unsigned int tx = (w + 15u) >> 4, ty = (h + 15u) >> 4; + unsigned int mx, my, ex, ey, upt, px, py, pow2, base; + + if ((sx != 1u && sx != 2u) || (sy != 1u && sy != 2u)) + return -22; /* -EINVAL */ + mx = SGX_C_ALIGN_MT((tx + 1u) >> 1, sx); + my = SGX_C_ALIGN_MT((ty + 1u) >> 1, sy); + + if ((w > 0x7ff || h > 0x7ff) && !caps->large_scene) { + /* 4x4 macro tiles. Reachable on this part - a render target + * wider or taller than 2047 takes it. */ + cookie[0] = 0x80000000; + upt = 4; + mx = SGX_C_ALIGN_MT((tx + 3u) >> 2, sx); + my = SGX_C_ALIGN_MT((ty + 3u) >> 2, sy); + ex = mx; + ey = my; + cookie[2] = 0x80000000u | (mx << 22) | (mx << 13) | (mx * 3u); + cookie[3] = (my << 22) | (my << 13) | (my * 3u); + } else { + cookie[0] = 0; + upt = 2; + if (caps->wide_mtile) { + /* never taken on SGX535 rev 1.2.1 */ + ex = SGX_C_ALIGN4(tx * 2u); + ey = SGX_C_ALIGN4(ty * 2u); + if (ex > 0xff) + ex = 0xff; + if (ey > 0xff) + ey = 0xff; + } else { + ex = mx; + ey = my; + } + cookie[2] = (ex << 22) | (ex << 12) | ex; + cookie[3] = (ey << 22) | (ey << 12) | ey; + } + + cookie[1] = mx * my; + cookie[4] = ex * ey; + cookie[5] = ((ty - 1u) << 12) | (tx - 1u); + cookie[6] = ((h - 1u) << 12) | (w - 1u); + cookie[8] = 0; + + px = upt * mx * sx; + py = upt * my * sy; + pow2 = sgx_roundup_pow2(px); + if (sgx_roundup_pow2(py) > pow2) + pow2 = sgx_roundup_pow2(py); + cookie[7] = ((pow2 * pow2 * 4u) + 0xfffu) & ~0xfffu; + + base = cookie[7] + px * py * 12u; + cookie[9] = base; + cookie[10] = base + 0x50; + cookie[11] = base + 0x50 + 0x90; + cookie[12] = 0; + cookie[13] = 0; + cookie[14] = 0; + + *size = base + 0x50 + 0x90 + 0x40; + *clear_p_start = cookie[8] >> 12; + *clear_num_pages = ((cookie[7] + 0xfffu) >> 12) - *clear_p_start; + return 0; +} + +/* One sample a pixel, which is what everything but a multisampled render + * asks for. */ +static inline int sgx_scene_info(const struct sgx_cookie_caps *caps, + unsigned int w, unsigned int h, + unsigned int *cookie, unsigned int *size, + unsigned int *clear_p_start, + unsigned int *clear_num_pages) +{ + return sgx_scene_info_ms(caps, w, h, 1u, 1u, cookie, size, + clear_p_start, clear_num_pages); +} + +/* Xpsb_ta_mem_info at 0x3d70. This only validates: psb discards the size and + * allocates its own parameter heap, so the responder sets a floor rather than + * choosing the heap, and an undersized request is the one thing it refuses. */ +static inline int sgx_ta_mem_info(unsigned int pages, unsigned int *cookie, + unsigned int *size) +{ + unsigned int avail, i; + + *size = 0x620000; + if (pages <= 0x61f) + return -12; /* -ENOMEM */ + + avail = pages - 0x600; + cookie[2] = pages; + cookie[3] = avail; + cookie[4] = avail; + cookie[5] = avail; + cookie[6] = (avail >= 0x41) ? (pages - 0x640) : 0; + for (i = 7; i < 13; i++) + cookie[i] = 0; + return 0; +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_drm.h b/drivers/gpu/drm/gma500/sgx_drm.h --- a/drivers/gpu/drm/gma500/sgx_drm.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_drm.h 2026-09-08 10:56:21.882775647 +0200 @@ -0,0 +1 @@ +../uapi/sgx_drm.h \ No newline at end of file diff -urNp a/drivers/gpu/drm/gma500/sgx_fire.h b/drivers/gpu/drm/gma500/sgx_fire.h --- a/drivers/gpu/drm/gma500/sgx_fire.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_fire.h 2026-09-08 10:56:21.883775661 +0200 @@ -0,0 +1,229 @@ +/* One frame, from a validated stream to a rendered scene. + * + * The phase order here is transcribed from frame_submit() and + * frame_raster_fire() in tools/baremetal/sgxtri.c, which is what renders every + * frame this project has produced. Unlike sgx_kick.h and sgx_scene.h it is not + * compiled from that source at build time - the reference is entangled with + * sgxtri's frame struct, its out-of-memory loop and its event waits, so there + * is nothing to extract. This is a transcription checked by reading, and the + * test asserts the sequence it is supposed to produce rather than comparing + * against a second implementation. That is a weaker guarantee than the other + * two headers carry and is worth knowing when reading a pass. + * + * The out-of-memory recovery is driven from psb_sgx_render.c rather than here: + * sgx_scene.h has the phases and this file has the waits, but the loop that + * decides how many recoveries to allow needs the frame's streams and its + * scene, which only the submit path holds. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_FIRE_H_ +#define _SGX_FIRE_H_ + +#ifdef __KERNEL__ +#include +#endif + +#include "sgx_scene.h" + +#define SGX_F_SOFT_RESET 0x0080 +#define SGX_F_RESET_ISP 0x00000020u /* _PSB_CS_RESET_ISP_RESET, bit 5 */ + +/* Events the two passes end on, from psb_reg.h. */ +/* psb_irq.c:249-252 - the DPM raises these when the parameter heap runs out. + * A wait that only tests its own event reports "did not finish" for what is + * really out-of-memory, which is a different problem with a different fix. */ +#define SGX_F_EV_DPM_OOM_MT (1u << 1) +#define SGX_F_EV_DPM_OOM_GBL (1u << 2) +#define SGX_F_EV_DPM_MEM_THRESH (1u << 3) +#define SGX_F_EV_OOM_MASK (SGX_F_EV_DPM_OOM_MT | SGX_F_EV_DPM_OOM_GBL | \ + SGX_F_EV_DPM_MEM_THRESH) +/* Only these two mean the heap is actually exhausted. The threshold is a + * warning the DPM raises well before that, on frames that go on to finish + * perfectly well - recovering on it aborts a healthy tiler mid-scene, which + * renders nothing and reports no fault, because the recovery itself + * succeeds. */ +#define SGX_F_EV_OOM_HARD (SGX_F_EV_DPM_OOM_MT | SGX_F_EV_DPM_OOM_GBL) + +#define SGX_F_EV_TA_FINISHED 0x00002000u +/* _PSB_CE_TA_TERMINATE, psb_reg.h:80 - the tiler acknowledging the abort. */ +#define SGX_F_EV_TA_TERMINATE 0x00001000u +#define SGX_F_EV_END_RENDER 0x00040000u +/* psb_schedule.c:863-865 - the 3D core telling the DPM it has finished handing + * parameter pages back. The render ending is only half of a frame; the other + * half is this. */ +#define SGX_F_EV_DPM_3D_MEM_FREE 0x00000001u + +/* Apply a validated {offset,value} stream. Validation is the caller's job and + * has already happened by here - this writes what it is given. */ +static inline void sgx_apply_stream(const struct sgx_kick_env *e, + const unsigned int *s, unsigned int dwords) +{ + unsigned int i; + + for (i = 0; i + 1 < dwords; i += 2) + e->wr(e->priv, s[i], s[i + 1]); +} + +/* The tiler pass: apply the TA stream, bind the scene and start the tiler. + * + * A fresh scene arrives CLEARED and the last pass clears that bit again, so + * SETUP is all that is left and sgx_scene_switch_fire() takes its clean-scene + * branch. */ +static inline int sgx_fire_ta(struct sgx_scene_env *sc, unsigned int hw_context, + const unsigned int *ta, + unsigned int ta_dwords, unsigned int *cookie, + unsigned int offset, + const unsigned int *oom, unsigned int oom_dwords, + unsigned int fire_flags, unsigned int *rca) +{ + sgx_apply_stream(&sc->k, ta, ta_dwords); + return sgx_scene_switch_fire(sc, fire_flags, hw_context, + cookie, offset, + SGX_S_SCENE_ENGINE_TA, + SGX_S_SCENE_FLAG_SETUP, oom, oom_dwords, + rca); +} + +/* The raster pass. + * + * The ISP reset comes first: a ZLS format change needs one, and the reset is + * two writes rather than one because the bit does not self-clear. + * + * The flags are 0x0f. The tiler completing sets DIRTY and COMPLETE, and the + * fire adds SETUP and SETUP_ONLY because the same hardware scene is still + * bound. SETUP_ONLY is what skips the page-table rebind and its event + * handshake inside sgx_scene_fire_common() - without it the raster pass + * rebinds a scene that is already bound and waits for an event that will not + * come. */ +static inline int sgx_fire_raster(struct sgx_scene_env *sc, + unsigned int hw_context, + const unsigned int *ras, + unsigned int ras_dwords, unsigned int *cookie, + unsigned int offset, + const unsigned int *oom, + unsigned int oom_dwords, + unsigned int fire_flags, unsigned int *rca) +{ + const struct sgx_kick_env *e = &sc->k; + + if (sc->isp_reset) { + e->wr(e->priv, SGX_F_SOFT_RESET, SGX_F_RESET_ISP); + e->wr(e->priv, SGX_F_SOFT_RESET, 0); + } + sgx_apply_stream(e, ras, ras_dwords); + + *rca = SGX_S_RETURN; + return sgx_scene_switch_fire(sc, fire_flags, hw_context, + cookie, offset, + SGX_S_SCENE_ENGINE_RASTER, + SGX_S_SCENE_FLAG_SETUP | + SGX_S_SCENE_FLAG_SETUP_ONLY | + SGX_S_SCENE_FLAG_DIRTY | + SGX_S_SCENE_FLAG_COMPLETE, + oom, oom_dwords, rca); +} + +/* Wait for one of the two completion events and acknowledge it. Returns + * -EBUSY if it never arrives, which on this hardware means the GPU is not + * coming back on its own. */ +static inline int sgx_wait_event(const struct sgx_kick_env *e, + unsigned int ev, unsigned int timeout_us) +{ + unsigned int st; + int ret = sgx_poll_reg_spin(e, SGX_K_EVENT_STATUS, ev, ev, timeout_us, + sgx_ev_spin(e)); + + if (ret) + return ret; + /* Acknowledge the out-of-memory bits raised in the same status word, + * not only the event waited for. They are sticky: left set, every + * later wait sees them, and the DPM does not carry on recycling + * parameter memory - the page index walks off the end of the heap + * instead of coming back round. The kernel's own handler drops them + * the same way, because the task they belong to has already been + * dispatched by the time they are read. */ + st = e->rd(e->priv, SGX_K_EVENT_STATUS); + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, ev | (st & SGX_F_EV_OOM_MASK)); + return 0; +} + +/* Wait for an event, but come back early if the DPM says it ran out of + * parameter memory. + * + * Returns 0 when the event arrived, 1 when the out-of-memory bits came up + * first, and -EBUSY on a timeout. sgx_wait_event() cannot express the middle + * case, so every out-of-memory looked like a tiler that did not finish - and + * the fix for the two is not the same one. + * + * Transcribed from wait_event() in tools/baremetal/sgxtri.c:3846-3938. The OOM + * bits are acknowledged here, as they are there, because the recovery has to + * see a clear status word before it fires anything. */ +static inline int sgx_wait_event_oom(const struct sgx_kick_env *e, + unsigned int ev, unsigned int timeout_us) +{ + unsigned long long start = e->now_us(e->priv); + unsigned int smax = sgx_ev_spin(e); + unsigned int spins = 0; + + for (;;) { + unsigned int st = e->rd(e->priv, SGX_K_EVENT_STATUS); + + if ((st & ev) == ev) { + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, + ev | (st & SGX_F_EV_OOM_MASK)); + return 0; + } + if (st & SGX_F_EV_OOM_HARD) { + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, + st & SGX_F_EV_OOM_MASK); + return 1; + } + /* Acknowledged so it does not stay latched, but not acted on: + * the tiler is still running and still expected to finish. */ + if (st & SGX_F_EV_DPM_MEM_THRESH) + e->wr(e->priv, SGX_K_EVENT_HOST_CLEAR, + SGX_F_EV_DPM_MEM_THRESH); + /* Batched for the same reason as sgx_poll_reg(): the clock is + * an uncached HPET read and one per pass cost more than the + * wait. */ + if ((spins >= smax || (spins & 0xffu) == 0u) && + e->now_us(e->priv) - start >= timeout_us) + return -16; /* -EBUSY */ + spins++; +#ifdef __KERNEL__ + cpu_relax(); + if (spins >= smax) { + usleep_range(50, 200); + cond_resched(); + } +#endif + } +} + +/* The same wait with the out-of-memory bits dropped rather than reported. + * + * Inside a recovery there is no tiler task current, so the DPM can raise them + * again with nothing to do about it; sgxtri re-arms a bounded number of times + * (wait_event_no_oom(), sgxtri.c:3946-3960) rather than treating them as an + * answer. Unbounded, a scene that raises one per pass never leaves the loop. */ +#define SGX_F_NO_OOM_RETRIES 16 + +static inline int sgx_wait_event_no_oom(const struct sgx_kick_env *e, + unsigned int ev, + unsigned int timeout_us) +{ + unsigned int tries; + + for (tries = 0; tries < SGX_F_NO_OOM_RETRIES; tries++) { + int ret = sgx_wait_event_oom(e, ev, timeout_us); + + if (ret <= 0) + return ret; + } + return -16; +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_heap.h b/drivers/gpu/drm/gma500/sgx_heap.h --- a/drivers/gpu/drm/gma500/sgx_heap.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_heap.h 2026-09-08 10:56:21.884775674 +0200 @@ -0,0 +1,127 @@ +/* Binding the parameter heap to the DPM's clients. + * + * A scene fire assumes the DPM already knows where the parameter heap is and + * how much of it it may use. Nothing in this driver told it - so the first + * real submit would have fired a tiler with no parameter memory. This is that + * step, transcribed from xpsb_ta_mem_load() in tools/xpsb-open/xpsb_hw.c, + * which comes from Xpsb_ta_mem_load at 0x3df0. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_HEAP_H_ +#define _SGX_HEAP_H_ + +#include "sgx_kick.h" +#include "sgx_cookie.h" + +#define SGX_H_DPM_HOSTD_BASE 0x0608 +#define SGX_H_DPM_HOSTA_BASE 0x0610 +#define SGX_H_DPM_TA_BASE 0x0618 +#define SGX_H_DPM_TA_LOCAL_A 0x0620 +#define SGX_H_DPM_TA_LOCAL_B 0x0624 +#define SGX_H_DPM_TA_LOCAL_C 0x0628 +#define SGX_H_DPM_TA_GLOBAL 0x0638 +#define SGX_H_DPM_TA_TAIL_2 0x0648 +#define SGX_H_DPM_RASTER_TAIL 0x064c +#define SGX_H_DPM_HOSTD_TAIL 0x0650 +#define SGX_H_DPM_HOSTA_TAIL 0x0654 +#define SGX_H_DPM_RASTER_GLOBAL 0x0660 +#define SGX_H_DPM_TA_TAIL 0x0668 +#define SGX_H_DPM_RASTER_BASE 0x0600 +#define SGX_H_DPM_LOAD_RASTER 0x0680 +#define SGX_H_DPM_LOAD_TA 0x0684 +#define SGX_H_DPM_LOAD_HOSTA 0x0688 +#define SGX_H_DPM_LOAD_HOSTD 0x0690 +#define SGX_H_DPM_LOAD_KICK 0x06a8 + +#define SGX_H_EVENT_STATUS2 0x0118 +#define SGX_H_EVENT_HOST_CLEAR2 0x0114 +#define SGX_H_EV_TA_LOAD 0x00400000u + +#define SGX_H_MEM_FLAG_TA (1u << 0) +#define SGX_H_MEM_FLAG_RASTER (1u << 1) +#define SGX_H_MEM_FLAG_HOSTA (1u << 2) +#define SGX_H_MEM_FLAG_HOSTD (1u << 3) +#define SGX_H_MEM_FLAG_INIT (1u << 4) +#define SGX_H_MEM_FLAG_NEW_PT_OFFSET (1u << 5) + +/* sgx_ta_mem_info() lives in sgx_cookie.h - it is pure arithmetic and is + * swept there against every page count. */ + +/* Xpsb_ta_mem_load at 0x3df0. pt_offset is the GPU address of the DPM's page + * table, param_offset that of the parameter heap; the low 28 bits are what the + * DPM works in, which is why the mask is here and not in the caller. */ +static inline int sgx_ta_mem_load(const struct sgx_kick_env *e, + unsigned int pt_offset, + unsigned int param_offset, + unsigned int flags, unsigned int *cookie) +{ + unsigned int kick = 0, span, tail; + int ret; + + if (flags & (SGX_H_MEM_FLAG_INIT | SGX_H_MEM_FLAG_NEW_PT_OFFSET)) + cookie[0] = pt_offset; + + if (flags & SGX_H_MEM_FLAG_INIT) { + unsigned int a = param_offset & 0x0fffffffu; + + cookie[1] = a; + cookie[10] = a >> 12; + cookie[11] = cookie[2] + (a >> 12) - 1u; + cookie[12] = cookie[11] - 1u; + } + + span = (cookie[11] << 16) | cookie[10]; + tail = cookie[12] << 16; + + if (flags & SGX_H_MEM_FLAG_TA) { + kick |= 1; + e->wr(e->priv, SGX_H_DPM_TA_BASE, cookie[0]); + e->wr(e->priv, SGX_H_DPM_TA_BASE + 4, span); + e->wr(e->priv, SGX_H_DPM_TA_TAIL_2, tail); + e->wr(e->priv, SGX_H_DPM_TA_GLOBAL, + (cookie[8] | cookie[7]) & 0xffff); + e->wr(e->priv, SGX_H_DPM_TA_LOCAL_A, cookie[4] & 0xffff); + e->wr(e->priv, SGX_H_DPM_TA_LOCAL_B, cookie[5] & 0xffff); + e->wr(e->priv, SGX_H_DPM_TA_LOCAL_C, cookie[3] & 0xffff); + e->wr(e->priv, SGX_H_DPM_TA_TAIL, cookie[6] & 0xffff); + e->wr(e->priv, SGX_H_DPM_LOAD_TA, 1); + } + if (flags & SGX_H_MEM_FLAG_RASTER) { + kick |= 2; + e->wr(e->priv, SGX_H_DPM_RASTER_BASE, cookie[0]); + e->wr(e->priv, SGX_H_DPM_RASTER_BASE + 4, span); + e->wr(e->priv, SGX_H_DPM_RASTER_TAIL, tail); + e->wr(e->priv, SGX_H_DPM_RASTER_GLOBAL, + (cookie[8] | cookie[7]) & 0xffff); + e->wr(e->priv, SGX_H_DPM_LOAD_RASTER, 1); + } + if (flags & SGX_H_MEM_FLAG_HOSTA) { + kick |= 4; + e->wr(e->priv, SGX_H_DPM_HOSTA_BASE, cookie[0]); + e->wr(e->priv, SGX_H_DPM_HOSTA_BASE + 4, span); + e->wr(e->priv, SGX_H_DPM_HOSTA_TAIL, tail); + e->wr(e->priv, SGX_H_DPM_LOAD_HOSTA, 1); + } + if (flags & SGX_H_MEM_FLAG_HOSTD) { + kick |= 4; /* the binary reuses bit 2 here */ + e->wr(e->priv, SGX_H_DPM_HOSTD_BASE, cookie[0]); + e->wr(e->priv, SGX_H_DPM_HOSTD_BASE + 4, span); + e->wr(e->priv, SGX_H_DPM_HOSTD_TAIL, tail); + e->wr(e->priv, SGX_H_DPM_LOAD_HOSTD, 1); + } + + ret = sgx_kick_wait_clear(e, 0, 0, 0, SGX_H_EVENT_STATUS2, kick, kick, + SGX_H_EVENT_HOST_CLEAR2, kick); + if (ret || !(flags & SGX_H_MEM_FLAG_INIT)) + return ret; + + return sgx_kick_wait_clear(e, 1, SGX_H_DPM_LOAD_KICK, 1, + SGX_K_EVENT_STATUS, SGX_H_EV_TA_LOAD, + SGX_H_EV_TA_LOAD, SGX_K_EVENT_HOST_CLEAR, + SGX_H_EV_TA_LOAD); +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_init.h b/drivers/gpu/drm/gma500/sgx_init.h --- a/drivers/gpu/drm/gma500/sgx_init.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_init.h 2026-09-08 10:56:21.884775674 +0200 @@ -0,0 +1,91 @@ +/* Bringing the SGX up. + * + * Nothing in this driver did this. A submit programmed the DPM and kicked the + * tiler on a core whose clock gating, USE control, parameter-memory limits and + * allocation mode had never been written - so the tiler was kicked and did not + * finish, which is exactly what that looks like. + * + * Transcribed from xpsb_set_vopt() and xpsb_sgx_initialize() in + * tools/xpsb-open/xpsb_hw.c, from Xpsb_set_vopt at 0x4af0 and + * Xpsb_sgx_initialize at 0x3820. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_INIT_H_ +#define _SGX_INIT_H_ + +#include "sgx_kick.h" + +#define SGX_I_CORE_REVISION 0x0014 +#define SGX_I_USE_CTRL 0x0804 +#define SGX_I_TSP_CTRL 0x0a00 +#define SGX_I_MTE_CTRL 0x0a58 +#define SGX_I_DPM_LIMIT_A 0x0a74 +#define SGX_I_DPM_LIMIT_B 0x0a78 +#define SGX_I_DPM_LIMIT_C 0x0a7c +#define SGX_I_DPM_LIMIT_D 0x0a80 +#define SGX_I_DPM_THRESH_A 0x0aac +#define SGX_I_DPM_THRESH_B 0x0abc +#define SGX_I_DPM_ALLOC_MODE 0x0630 +#define SGX_I_BIF_CTRL2 0x0ca0 +#define SGX_I_UNKNOWN_13C 0x013c + +#define SGX_VOPT_N 42 +#define SGX_VOPT_DPM_LIMIT 8 +#define SGX_VOPT_CLKGATE 38 + +/* The core revision selects a set of errata workarounds. The key is + * 100 + minor*10 + maintenance and the enables cascade, so a lower revision + * turns on everything a higher one does. SGX535 rev 1.2.1 gives 121, which + * enables nothing - but the table is here because the driver should not + * silently assume the part it is on. */ +static inline void sgx_set_vopt(const struct sgx_kick_env *e, u8 *vopt) +{ + u32 rev = e->rd(e->priv, SGX_I_CORE_REVISION); + u32 key = 100u + ((rev >> 8) & 0xff) * 10u + (rev & 0xff); + int from, i; + + memset(vopt, 0, SGX_VOPT_N); + switch (key) { + case 107: from = 2; break; + case 108: from = 1; break; + case 109: from = 0; break; + case 111: + case 113: from = 13; break; + default: from = -1; break; + } + if (from >= 0) + for (i = from; i < SGX_VOPT_N; i++) + vopt[i] = 1; + if (key == 111 || key == 113) + vopt[31] = 1; +} + +/* Xpsb_sgx_initialize at 0x3820. Returns the USE control word the kick + * sequences need, which is why it is a return value rather than a side + * effect - sgx_kick.h takes it as an argument. */ +static inline u32 sgx_hw_init(const struct sgx_kick_env *e, const u8 *vopt) +{ + u32 use_ctrl = 0xffff; + + sgx_wrb(e, SGX_I_BIF_CTRL2, 0xc07c); + e->wr(e->priv, SGX_I_UNKNOWN_13C, 0); + e->wr(e->priv, SGX_I_DPM_LIMIT_C, 0); + sgx_wrb(e, SGX_I_DPM_LIMIT_D, 0); + e->wr(e->priv, SGX_I_DPM_LIMIT_A, + vopt[SGX_VOPT_DPM_LIMIT] ? 0x5021900u : 0x5188200u); + sgx_wrb(e, SGX_I_DPM_LIMIT_B, 0); + e->wr(e->priv, SGX_I_DPM_THRESH_A, 0x44); + e->wr(e->priv, SGX_I_DPM_THRESH_B, 0x0c); + if (vopt[SGX_VOPT_CLKGATE]) + use_ctrl = 0x100ffff; + sgx_wrb(e, SGX_I_USE_CTRL, use_ctrl); + sgx_wrb(e, SGX_I_TSP_CTRL, 0x7c000); + e->wr(e->priv, SGX_I_DPM_ALLOC_MODE, 0); + e->wr(e->priv, SGX_I_MTE_CTRL, 0); + return use_ctrl; +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_kick.h b/drivers/gpu/drm/gma500/sgx_kick.h --- a/drivers/gpu/drm/gma500/sgx_kick.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_kick.h 2026-09-08 10:56:21.885775688 +0200 @@ -0,0 +1,219 @@ +/* SGX kick sequences - the register order that starts the tiler and the + * renderer. + * + * This is a transcription of xpsb_kick_ta() and xpsb_kick_render() in + * tools/xpsb-open/xpsb_hw.c, which are themselves transcribed from + * Xpsb_closed_kick_ta at 0x4f10 and Xpsb_closed_kick_render at 0x5040. That + * code runs today on bare metal and every frame this project has rendered went + * through it, so it is the tested article and this file must not diverge from + * it. test/sgx_kick_test.c holds both implementations against each other and + * compares the emitted register traffic write for write. + * + * The reason it exists separately at all is that the userspace version derefs a + * volatile pointer and calls gettimeofday(), neither of which belongs in a + * kernel module. Register access and the clock are injected instead, so the + * same sequence serves readl/writel in the module and a recording harness in + * the host test. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_KICK_H_ +#define _SGX_KICK_H_ + +#ifdef __KERNEL__ +#include +#endif + +#define SGX_K_USE_CTRL 0x0804 +#define SGX_K_PDS_CTRL 0x0a08 +/* Named from the vendor's own SGX535 register header, which is in the tree at + * upstream/openpvrsgx/.../services4/srvkm/hwdefs/sgx535defs.h. These were + * guessed as flushes before it was read, and two of them are not flushes at + * all - which is worth spelling out, because "the kick flushes the BIF" was + * reasoning built on the wrong name. */ +#define SGX_K_PDS_INV1 0x0ad4 /* EUR_CR_PDS_INV1, data-store cache */ +#define SGX_K_PDS_INV_CSC 0x0ae0 /* EUR_CR_PDS_INV_CSC, code/state */ +#define SGX_K_BIF_TA_REQ_BASE 0x0c90 /* EUR_CR_BIF_TA_REQ_BASE, not a flush */ +#define SGX_K_TA_KICK 0x0200 +#define SGX_K_3D_KICK 0x0428 +#define SGX_K_3D_CTRL 0x043c +#define SGX_K_EVENT_STATUS 0x012c +#define SGX_K_EVENT_HOST_CLEAR 0x0134 +#define SGX_K_EVENT_STATUS3 0x0138 +#define SGX_K_EVENT_HOST_CLEAR3 0x0140 + +/* EUR_CR_EVENT_STATUS bit 26, MADD_CACHE_INVALCOMPLETE: the data/texel cache + * invalidate has finished. Not a "kick done" bit - the kick asks for the + * invalidate and waits for this, which is why every kick already invalidates + * the texel cache and a stale texel is not something to go looking for. */ +#define SGX_K_EV_CACHE_INVAL 0x04000000u +#define SGX_K_TIMEOUT_US 100000u + +/* Everything the sequences need from the world outside them. now_us must be + * monotonic; the module passes ktime, the test passes a counter it advances + * itself so the timeout path is reachable without waiting for it. */ +/* Uncached register reads, so every pass of a long wait costs the bus. The + * yield is held back rather than taken on every pass because several of these + * polls are handshakes that answer within microseconds, and the DPM's heap + * load fails outright if they sleep. */ +#define SGX_POLL_SPIN_MAX 4096u + +/* A completion wait is a different shape of wait. A render runs for + * milliseconds, so a handshake's budget of uncached reads buys nothing there + * and spends a core the frame could have drawn with. */ +#define SGX_EVENT_SPIN_MAX 64u + +struct sgx_kick_env { + void *priv; + unsigned int (*rd)(void *priv, unsigned int off); + void (*wr)(void *priv, unsigned int off, unsigned int val); + unsigned long long (*now_us)(void *priv); + unsigned int use_ctrl; + /* Passes to spin before the poll below starts sleeping. Zero takes + * the default. */ + unsigned int spin_max; + /* The same for a completion wait, which gives up spinning far + * sooner. Zero takes the default. */ + unsigned int ev_spin_max; +}; + +/* Write then read back, which is how the binary orders every kick. The + * read-back is not decorative: it is what keeps a posted write from being + * overtaken by the kick that follows it. */ +static inline void sgx_wrb(const struct sgx_kick_env *e, unsigned int off, + unsigned int val) +{ + e->wr(e->priv, off, val); + (void)e->rd(e->priv, off); +} + +/* Xpsb_poll_reg_for_value at 0x4ca0. The binary also accumulates a histogram of + * the two DPM state registers on every pass; that is instrumentation with no + * effect on the hardware and is not reproduced. */ +static inline int sgx_poll_reg_spin(const struct sgx_kick_env *e, + unsigned int off, unsigned int mask, + unsigned int val, unsigned int timeout_us, + unsigned int smax) +{ + unsigned long long start = e->now_us(e->priv); + unsigned int spins = 0; + + for (;;) { + if ((e->rd(e->priv, off) & mask) == val) + return 0; + /* The clock is an uncached HPET read on this platform, and one + * per pass dominated the machine: a quarter of all CPU time + * while a frame rendered. The deadlines here are milliseconds, + * so checking once per batch is as good and costs nothing. + * Every pass once the wait is a sleeping one, where it is + * cheap beside the sleep. */ + if ((spins >= smax || (spins & 0xffu) == 0u) && + e->now_us(e->priv) - start >= timeout_us) + return -16; /* -EBUSY */ + spins++; + /* Poulsbo is a single core and the long waits here are entered + * under the device-wide fire lock with a two-second budget. + * Spinning one out uninterrupted starves everything else on + * the machine, which presents as a locked-up system rather + * than a frame that failed. + * + * The yield is held back until the wait is clearly a long one. + * Several of these polls are handshakes that answer within + * microseconds, and sleeping through one loses it: rescheduling + * on every pass made the DPM's heap load fail outright. */ +#ifdef __KERNEL__ + cpu_relax(); + /* Clearly a long wait by now - a render takes hundreds of + * milliseconds - so give the core up instead of spinning it + * out. Yielding without sleeping still charged the wait to + * this task and left nothing for the X server to draw with. */ + if (spins >= smax) { + usleep_range(50, 200); + cond_resched(); + } +#endif + } +} + +/* The handshake budget. Several of these polls answer within microseconds and + * the DPM's heap load fails outright if they sleep, so it stays high. */ +static inline int sgx_poll_reg(const struct sgx_kick_env *e, unsigned int off, + unsigned int mask, unsigned int val, + unsigned int timeout_us) +{ + return sgx_poll_reg_spin(e, off, mask, val, timeout_us, + e->spin_max ? e->spin_max : + SGX_POLL_SPIN_MAX); +} + +static inline unsigned int sgx_ev_spin(const struct sgx_kick_env *e) +{ + return e->ev_spin_max ? e->ev_spin_max : SGX_EVENT_SPIN_MAX; +} + +/* Xpsb_er_kick_wait_clear at 0x4e10: optionally poke a register, wait for a + * status bit, acknowledge it, then wait for the acknowledge to take. A failed + * wait is reported but does not stop the acknowledge from being issued - the + * binary does it in that order and a frame that skipped the acknowledge would + * leave the event latched for the next one. */ +static inline int sgx_kick_wait_clear(const struct sgx_kick_env *e, int do_kick, + unsigned int kick_off, + unsigned int kick_val, + unsigned int stat_off, unsigned int mask, + unsigned int val, unsigned int clear_off, + unsigned int clear_val) +{ + int ret = 0; + + if (do_kick) + sgx_wrb(e, kick_off, kick_val); + + if (sgx_poll_reg(e, stat_off, mask, val, SGX_K_TIMEOUT_US)) + ret = -16; + + sgx_wrb(e, clear_off, clear_val); + (void)sgx_poll_reg(e, stat_off, mask, 0, SGX_K_TIMEOUT_US); + return ret; +} + +/* The preamble both kicks share: flush the bus interface, then the two TA + * flush registers, then hand the USSE its control word. */ +static inline void sgx_kick_preamble(const struct sgx_kick_env *e) +{ + sgx_kick_wait_clear(e, 1, SGX_K_PDS_INV1, 1, + SGX_K_EVENT_STATUS3, 0x44, 0x44, + SGX_K_EVENT_HOST_CLEAR3, 0x44); + sgx_kick_wait_clear(e, 1, SGX_K_PDS_INV_CSC, 1, + SGX_K_EVENT_STATUS3, 1, 1, + SGX_K_EVENT_HOST_CLEAR3, 1); + sgx_kick_wait_clear(e, 1, SGX_K_USE_CTRL, e->use_ctrl | 0x10000000, + SGX_K_EVENT_STATUS, SGX_K_EV_CACHE_INVAL, + SGX_K_EV_CACHE_INVAL, SGX_K_EVENT_HOST_CLEAR, + SGX_K_EV_CACHE_INVAL); +} + +/* Xpsb_closed_kick_ta at 0x4f10. The BIF flush is the one difference from the + * render kick and it comes first, before the preamble. */ +static inline int sgx_kick_ta(const struct sgx_kick_env *e) +{ + e->wr(e->priv, SGX_K_BIF_TA_REQ_BASE, 0x30000000); + sgx_kick_preamble(e); + e->wr(e->priv, SGX_K_PDS_CTRL, 1); + sgx_wrb(e, SGX_K_TA_KICK, 1); + return 0; +} + +/* Xpsb_closed_kick_render at 0x5040: the same preamble, no BIF flush, and + * 3D_CTRL is armed before PDS_CTRL. */ +static inline int sgx_kick_render(const struct sgx_kick_env *e) +{ + sgx_kick_preamble(e); + e->wr(e->priv, SGX_K_3D_CTRL, 1); + e->wr(e->priv, SGX_K_PDS_CTRL, 1); + sgx_wrb(e, SGX_K_3D_KICK, 1); + return 0; +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_mmu.h b/drivers/gpu/drm/gma500/sgx_mmu.h --- a/drivers/gpu/drm/gma500/sgx_mmu.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_mmu.h 2026-09-08 10:56:21.886775702 +0200 @@ -0,0 +1,133 @@ +/* The SGX MMU: turning a binding into page table entries. + * + * VM_BIND used to record a binding in an rb-tree and stop there, which is + * bookkeeping - the hardware knew nothing about it, so a submit would fire the + * tiler at addresses that translate to nothing. This is the part that makes a + * binding real. + * + * Two levels, 1024 entries each, 4 KiB pages, exactly as psb_mmu.c does it and + * as tools/baremetal/sgxtri.c reimplements it - that implementation renders + * frames on this hardware, so the encodings here are transcribed from it + * rather than derived again. + * + * Invalid entries are zero. psb_mmu_alloc_pd() calls that trapping page + * faults: a stray access lands in BIF_FAULT with an address, instead of + * silently reading whatever page a non-zero invalid entry happened to name. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_MMU_H_ +#define _SGX_MMU_H_ + +#include + +#define SGX_MMU_PDES 1024 +#define SGX_MMU_PTES 1024 +#define SGX_PTE_SHIFT 12 +#define SGX_PDE_SHIFT 22 + +#define SGX_PTE_VALID 0x0001u +#define SGX_PTE_WO 0x0002u +#define SGX_PTE_RO 0x0004u +#define SGX_PTE_CACHED 0x0008u + +/* The BIF registers that point the hardware at a directory and flush it. */ +#define SGX_CR_BIF_CTRL 0x0c00 +/* sgx535defs.h:2714-2723. Both INVALDC and FLUSH are translation-cache + * operations, not data ones: INVALDC drops the page-*directory* cache, FLUSH + * the page-table-entry cache. PAUSE stops the BIF taking new requests, which + * is what makes a drain of the outstanding ones terminate. */ +#define SGX_CB_CTRL_PAUSE (1u << 1) +#define SGX_CB_CTRL_INVALDC (1u << 3) +#define SGX_CB_CTRL_FLUSH (1u << 2) +#define SGX_CB_CTRL_CLEAR_FAULT (1u << 4) +/* sgx535defs.h:3060 - reads the BIF has issued and not yet retired. */ +#define SGX_CB_MEM_REQ_READS 0x000000ffu +#define SGX_CR_BIF_INT_STAT 0x0c04 +#define SGX_CR_BIF_FAULT 0x0c08 +#define SGX_CR_BIF_DIR_LIST_BASE0 0x0c84 +#define SGX_CR_BIF_DIR_LIST_BASE1 0x0c38 +#define SGX_CR_BIF_BANK0 0x0c78 +#define SGX_CR_BIF_BANK1 0x0c7c + +/* psb_mmu.c:909-923 - the requestor field is inverted: a *set* bit disables + * that requestor's translation. Leaving it as the hardware comes up means the + * SGX does not go through the MMU at all, which is a tiler that kicks and + * never finishes. */ + +/* The three base registers the 3D and 2D data masters offset their requests + * by, and the block reset. sgxtri's sgx_cold_init() programs all of them; + * leaving BIF_3D_REQ_BASE at 0 makes the ISP fetch 0x30101000 as 0x00101000. */ +#define SGX_CR_CLKGATECTL 0x0000 +#define SGX_CLKGATE_ALL_ON 0x01111111u +#define SGX_CR_SOFT_RESET 0x0080 +#define SGX_CS_RESET_BIF (1u << 0) +#define SGX_CS_RESET_TWOD (1u << 1) +#define SGX_CS_RESET_DPM (1u << 2) +#define SGX_CS_RESET_TA (1u << 3) +#define SGX_CS_RESET_USE (1u << 4) +#define SGX_CS_RESET_ISP (1u << 5) +#define SGX_CS_RESET_TSP (1u << 6) +#define SGX_CR_PDS_EXEC_BASE 0x0ab8 +#define SGX_CR_BIF_TWOD_REQ_BASE 0x0c88 +#define SGX_CR_BIF_3D_REQ_BASE 0x0cac + +/* psb_irq.c:238-239 - masking the line leaves the status registers working, + * which is what lets the module poll them. */ +#define SGX_CR_EVENT_HOST_ENABLE2 0x0110 +#define SGX_CR_EVENT_HOST_CLEAR2 0x0114 +#define SGX_CR_EVENT_STATUS2 0x0118 +/* Named from the vendor header, sgx535defs.h. Read when a render times out: + * CLKGATESTATUS says which block still has work, the rest what it waits on. */ +#define SGX_CR_CLKGATESTATUS 0x0004 +#define SGX_CR_CLKGATE_ISP 0x00000010u +#define SGX_CR_CLKGATE_TSP 0x00000100u +#define SGX_CR_CLKGATE_TA 0x00001000u +#define SGX_CR_CLKGATE_DPM 0x00010000u +#define SGX_CR_CLKGATE_USE 0x00100000u +#define SGX_CR_PDS_PC_BASE 0x0b2c +#define SGX_CR_BIF_MEM_REQ_STAT 0x0ca8 +#define SGX_CR_BIF_BANK_STATUS 0x0cb4 +#define SGX_CR_2D_BLIT_STATUS 0x0e04 +#define SGX_CR_EVENT_STATUS 0x012c +#define SGX_CR_EVENT_HOST_ENABLE 0x0130 +#define SGX_CR_EVENT_HOST_CLEAR 0x0134 + +#define SGX_MMU_ER_MASK 0x0001ff00u + +/* One address space. Per file, because the address space is. */ +struct sgx_mmu { + u32 *pd; /* the page directory page */ + unsigned long pd_phys; + u32 *pt[SGX_MMU_PDES]; /* page tables, allocated on demand */ + bool verified; /* the directory readback was logged */ +}; + +/* psb_mmu.c:210-222. The type argument uses the PSB_MMU_*_MEMORY encoding, + * which is deliberately not the same as the entry bits. */ +static inline u32 sgx_mmu_pte(u32 pfn, int type) +{ + u32 mask = SGX_PTE_VALID; + + if (type & 0x1) + mask |= SGX_PTE_CACHED; + if (type & 0x2) + mask |= SGX_PTE_RO; + if (type & 0x4) + mask |= SGX_PTE_WO; + return (pfn << SGX_PTE_SHIFT) | mask; +} + +static inline unsigned sgx_mmu_pde_of(u64 va) +{ + return (unsigned)(va >> SGX_PDE_SHIFT) & (SGX_MMU_PDES - 1); +} + +static inline unsigned sgx_mmu_pte_of(u64 va) +{ + return (unsigned)(va >> SGX_PTE_SHIFT) & (SGX_MMU_PTES - 1); +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_scene.h b/drivers/gpu/drm/gma500/sgx_scene.h --- a/drivers/gpu/drm/gma500/sgx_scene.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_scene.h 2026-09-08 10:56:21.887775715 +0200 @@ -0,0 +1,519 @@ +/* SGX scene binding and fire - the register work that has to precede a kick. + * + * A transcription of emit_scene_setup(), scene_fire_common(), + * xpsb_oom_partial() and xpsb_scene_switch_fire() in + * tools/xpsb-open/xpsb_hw.c, which come in turn from Xpsb_scene_switch_fire at + * 0x4550 and the unnamed helper at 0x4030. That code renders frames on real + * hardware today, so it is the tested article; this file must not diverge from + * it, and test/sgx_scene_test.c compares the two write for write across every + * reachable path rather than trusting the transcription. + * + * As in sgx_kick.h, register access and the clock are injected so the same + * sequence serves readl/writel in the module and a recording harness on the + * host. The debug printing in the bare-metal version is left out: it touches no + * register and so cannot change what the hardware sees, which the trace + * comparison also demonstrates rather than assumes. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_SCENE_H_ +#define _SGX_SCENE_H_ + +#include "sgx_kick.h" + +#define SGX_S_TA_MTILE_X 0x0208 +#define SGX_S_TA_MTILE_Y 0x020c +#define SGX_S_TA_MTILE_SIZE 0x0210 +#define SGX_S_TA_MTILE_STRIDE 0x0214 +#define SGX_S_TA_REGION_BASE 0x021c +#define SGX_S_TA_PARAM_BASE 0x0220 +#define SGX_S_TA_START 0x0224 +#define SGX_S_TA_ZLS_BASE 0x0234 +#define SGX_S_TA_PIXEL_EXTENT 0x0248 +#define SGX_S_TA_CTRL2 0x024c +#define SGX_S_TA_STATE 0x0274 + +#define SGX_S_3D_STATUS 0x0404 +#define SGX_S_3D_PARAM_BASE 0x0408 +#define SGX_S_ISP_CTRL 0x0414 +#define SGX_S_DPM_STATE 0x0480 + +#define SGX_S_DPM_TA_LOCAL_B 0x0624 +#define SGX_S_DPM_PAGE_TABLE 0x062c +#define SGX_S_DPM_ALLOC_MODE 0x0630 +#define SGX_S_DPM_STATE_TABLE 0x0634 +#define SGX_S_DPM_CONTEXT 0x063c +#define SGX_S_DPM_PARTIAL 0x0658 +#define SGX_S_DPM_ZLS_ENABLE 0x065c +#define SGX_S_DPM_STATUS 0x0720 +#define SGX_S_DPM_FREE 0x0724 +#define SGX_S_DPM_REQUEST 0x0694 +#define SGX_S_DPM_REQUEST2 0x0698 +#define SGX_S_DPM_MODE 0x069c +/* EUR_CR_BIF_ZLS_REQ_BASE, from the vendor's sgx535defs.h - a requestor base, + * not a flush. The render kick pulses no BIF flush at all. */ +#define SGX_S_BIF_ZLS_REQ_BASE 0x0cb0 + +/* EUR_CR_ISP_ZLSCTL (0x0480) load and store enables, from the vendor's + * sgx535defs.h:1281-1323. Bits 21 and 22 are the mask plane - the ISP's + * per-pixel record of which pixels a partial render has already written, the + * one piece of the vendor's SPM the driver does not do. */ +#define SGX_S_ZLS_SLOADEN 0x00000002u +#define SGX_S_ZLS_ZLOADEN 0x00000004u +#define SGX_S_ZLS_ZSTOREEN 0x00000010u +#define SGX_S_ZLS_SSTOREEN 0x00000080u +#define SGX_S_ZLS_LOADMASK 0x00200000u +#define SGX_S_ZLS_STOREMASK 0x00400000u + +/* sgx_spm, per bit. Store and load are separate because the vendor's two + * halves run on different renders and either alone is a measurable change. */ +#define SGX_S_SPM_STORE 1u +#define SGX_S_SPM_LOAD 2u +#define SGX_S_SPM_PROCESS_EMPTY 4u + +#define SGX_S_EV_DPM_A 0x00000010u +#define SGX_S_EV_DPM_B 0x00000020u +#define SGX_S_EV_DPM_C 0x00000040u +#define SGX_S_EV_DPM_D 0x00000200u +#define SGX_S_EV_TA_SETUP 0x00100a40u + +#define SGX_S_SCENE_FLAG_DIRTY (1u << 0) +#define SGX_S_SCENE_FLAG_COMPLETE (1u << 1) +#define SGX_S_SCENE_FLAG_SETUP (1u << 2) +#define SGX_S_SCENE_FLAG_SETUP_ONLY (1u << 3) +/* The raster pass hands the parameter memory back when this is set. Without it + * the DPM keeps every page a frame binned into, and an animation walks off the + * end of the heap after a fixed number of frames. */ +#define SGX_S_FIRE_FLAG_RASTER_DEALLOC (1u << 0) +#define SGX_S_FIRE_FLAG_XHW_OOM (1u << 24) +#define SGX_S_SCENE_ENGINE_TA 0 +#define SGX_S_SCENE_ENGINE_RASTER 1 + +/* scheduler completion actions, from psb_reg.h */ +#define SGX_S_RASTER_BLOCK 0 +#define SGX_S_RASTER 1 +#define SGX_S_RETURN 2 +#define SGX_S_TA 3 + +/* Everything the scene work needs beyond register access. alloc_mode mirrors + * SGX_DPM_ALLOC_MODE so a redundant write can be skipped, which is not an + * optimisation - the DPM acts on the write, not the value. */ +struct sgx_scene_env { + struct sgx_kick_env k; + unsigned int alloc_mode; + int isp_reset; /* reset the ISP before each render */ + int zls; /* keep the DPM's Z load/store on for an + * ordinary fire, not only a recovery */ + unsigned int oom_page_table; /* GPU address of the private DPM table */ + unsigned int (*regions)[2]; /* its CPU mapping, 17 records, or NULL */ + unsigned int spm; /* mask-plane accumulation, SGX_S_SPM_* */ +}; + +/* The mask plane rides with the depth store and the depth load rather than + * being enabled on its own: the vendor's partial render stores it whenever it + * stores depth (spm.asm:2620-2628) and loads it only on a macro tile a partial + * render has already touched (spm.asm:2634-2643), which is the condition the + * caller here already has in cookie[13]. Neither is ever invented - if the + * client's ZLSCTL carries no depth store or no depth load there is no plane to + * write or nothing to read, and asking for one addresses memory the client + * never sized. */ +static inline unsigned int sgx_spm_store(const struct sgx_scene_env *s, + unsigned int zls) +{ + if ((s->spm & SGX_S_SPM_STORE) && (zls & SGX_S_ZLS_ZSTOREEN)) + zls |= SGX_S_ZLS_STOREMASK; + return zls; +} + +static inline unsigned int sgx_spm_load(const struct sgx_scene_env *s, + unsigned int zls) +{ + if ((s->spm & SGX_S_SPM_LOAD) && (zls & SGX_S_ZLS_ZLOADEN)) + zls |= SGX_S_ZLS_LOADMASK; + return zls; +} + +static inline void sgx_set_alloc_mode(struct sgx_scene_env *s, unsigned int v) +{ + if (v == s->alloc_mode) + return; + s->alloc_mode = v; + s->k.wr(s->k.priv, SGX_S_DPM_ALLOC_MODE, v); +} + +/* The DPM's Z load and store, which the vendor only ever turns on for an + * out-of-memory recovery. With it set the DPM marks the region headers the + * tiler writes with EURASIA_REGIONHEADER0_ZLOAD/ZSTOREENABLE, which is the + * hardware's only per-region depth load switch - the ISP's own 0x0480 carries + * the ZLS formats and nothing that enables a load. cookie[9] is the ZLS area's + * offset inside the scene. Off unless sgx_zls says otherwise. */ +static inline void sgx_zls_arm(struct sgx_scene_env *s, + const unsigned int *cookie, unsigned int offset) +{ + const struct sgx_kick_env *e = &s->k; + + if (!s->zls) + return; + e->wr(e->priv, SGX_S_TA_ZLS_BASE, offset + cookie[9]); + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 1); +} + +/* Program the macro-tile geometry from a scene cookie. Shared by the clean TA + * setup and by the fire helper. Returns the region base it consumed. */ +static inline unsigned int sgx_emit_scene_setup(const struct sgx_kick_env *e, + const unsigned int *cookie, + unsigned int offset) +{ + e->wr(e->priv, SGX_S_TA_MTILE_X, cookie[2]); + e->wr(e->priv, SGX_S_TA_MTILE_Y, cookie[3]); + e->wr(e->priv, SGX_S_TA_MTILE_STRIDE, cookie[4]); + e->wr(e->priv, SGX_S_TA_MTILE_SIZE, cookie[5]); + e->wr(e->priv, SGX_S_TA_PIXEL_EXTENT, cookie[6]); + offset += cookie[7]; + e->wr(e->priv, SGX_S_TA_REGION_BASE, offset); + return offset; +} + +/* The unnamed helper at 0x4030, reached from both fire paths. It binds the + * scene, and on a raster fire decides whether this is a normal render or the + * second half of an out-of-memory partial render. */ +static inline int sgx_scene_fire_common(struct sgx_scene_env *s, + unsigned int *cookie, int is_raster, + unsigned int offset, unsigned int flags, + const unsigned int *oom_cmds, + unsigned int num_oom_cmds, + unsigned int *rca) +{ + const struct sgx_kick_env *e = &s->k; + unsigned int wait_ev = 0, dpm_state = 0, extra = 0, mask, ctx_val; + unsigned int i, n; + + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + + if ((flags & (SGX_S_SCENE_FLAG_DIRTY | SGX_S_SCENE_FLAG_SETUP_ONLY)) == + SGX_S_SCENE_FLAG_DIRTY) { + e->wr(e->priv, SGX_S_DPM_PAGE_TABLE, offset + cookie[10]); + e->wr(e->priv, SGX_S_DPM_STATE_TABLE, offset + cookie[11]); + e->wr(e->priv, SGX_S_DPM_REQUEST, 4); + e->wr(e->priv, SGX_S_DPM_REQUEST2, 4); + wait_ev = 0x120; + if (!(flags & SGX_S_SCENE_FLAG_COMPLETE)) { + e->wr(e->priv, SGX_S_TA_PARAM_BASE, offset + cookie[8]); + e->wr(e->priv, SGX_S_TA_ZLS_BASE, offset + cookie[9]); + e->wr(e->priv, SGX_S_TA_CTRL2, 2); + sgx_wrb(e, SGX_S_TA_START, 0x80000000); + wait_ev = 0x200920; + } + } + + if (!(flags & SGX_S_SCENE_FLAG_SETUP)) + goto out; + + if (!is_raster) { + sgx_emit_scene_setup(e, cookie, offset); + goto out; + } + if (is_raster != 1) + goto out; + + *rca = SGX_S_RETURN; + if (cookie[14] == 0) + goto fire; + + if (!(cookie[13] & 0x80000000u) && num_oom_cmds != 0) { + /* First pass of an OOM recovery: render what the tile arrays + * already hold, then come back for the rest. */ + cookie[13] |= 0x80000000u; + e->wr(e->priv, SGX_S_DPM_PARTIAL, 0); + e->wr(e->priv, SGX_S_DPM_CONTEXT, 0); + *rca = SGX_S_RASTER; + e->wr(e->priv, SGX_S_3D_STATUS, 3); + goto tail; + } + + cookie[13] |= 0x80000000u; + dpm_state = e->rd(e->priv, SGX_S_DPM_STATE); + if ((dpm_state & 0x30000) == 0x30000) + e->wr(e->priv, SGX_S_BIF_ZLS_REQ_BASE, 0x30000000); + /* PROCESSEMPTY. The vendor sets it for every SPM render (spm.asm:2653, + * unconditional once BRN 23870 is preprocessed for this core), because + * a region with no objects in this pass still has to run its Z/S and + * mask store or the tile is left stale for the next one. Cleared here + * otherwise, which is what the bare-metal recovery was transcribed + * from. */ + if (s->spm & SGX_S_SPM_PROCESS_EMPTY) + e->wr(e->priv, SGX_S_ISP_CTRL, + e->rd(e->priv, SGX_S_ISP_CTRL) | 0x100u); + else + e->wr(e->priv, SGX_S_ISP_CTRL, + e->rd(e->priv, SGX_S_ISP_CTRL) & ~0x100u); + + if (cookie[14] == 2) { + if ((cookie[13] & 0xffff) != 0) { + /* The resume, and the vendor's render that finishes + * the scene: load what the partial renders stored, + * mask included, and add no store of its own + * (3d.asm:3255-3268). */ + dpm_state = sgx_spm_load(s, dpm_state | 6); + e->wr(e->priv, SGX_S_DPM_STATE, dpm_state); + n = num_oom_cmds >> 1; + for (i = 0; i < n; i++) + e->wr(e->priv, oom_cmds[2 * i], + oom_cmds[2 * i + 1]); + } + cookie[14] = 0; + cookie[13] = 0; + *rca = SGX_S_RETURN; + goto fire; + } + + /* Drain the three DPM phases, swapping the allocation context in the + * middle, so the partially built scene can be rasterised on its own. */ + ctx_val = (s->alloc_mode & ~3u) | ((s->alloc_mode & 4) ? 1u : 2u); + sgx_set_alloc_mode(s, ctx_val); + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, ((cookie[15] >> 4) ^ 1u) & 1u); + e->wr(e->priv, SGX_S_DPM_PAGE_TABLE, offset + cookie[10]); + + e->wr(e->priv, SGX_S_DPM_REQUEST, 1); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_A, + SGX_S_EV_DPM_A, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_A)) + goto fail; + e->wr(e->priv, SGX_S_DPM_REQUEST, 2); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_C, + SGX_S_EV_DPM_C, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_C)) + goto fail; + + sgx_set_alloc_mode(s, ctx_val ^ 1u); + e->wr(e->priv, SGX_S_DPM_REQUEST, 4); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_B, + SGX_S_EV_DPM_B, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_B)) { + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + return -22; /* -EINVAL */ + } + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + + *rca = SGX_S_TA; + cookie[14] = 2; + if (cookie[15] & 0x10) { + dpm_state |= 6; + mask = 0xffff; + extra = 0; + ctx_val = 3; + } else { + unsigned int slot = cookie[15] & 0xf; + + mask = 1u << slot; + extra = slot * cookie[1] * 12u; + ctx_val = 2; + } + if (cookie[13] & mask) { + n = num_oom_cmds >> 1; + for (i = 0; i < n; i++) + e->wr(e->priv, oom_cmds[2 * i], oom_cmds[2 * i + 1]); + /* This macro tile has been partially rendered before, so there + * is a mask plane to consume - the vendor's per-render-target + * SGXMKIF_HWRTDATA_RT_STATUS_SPMRENDER, per macro tile. */ + dpm_state = sgx_spm_load(s, dpm_state | 6); + } else { + cookie[13] |= mask; + } + dpm_state = sgx_spm_store(s, dpm_state | 0x90); + e->wr(e->priv, SGX_S_DPM_STATE, dpm_state); + e->wr(e->priv, SGX_S_DPM_CONTEXT, ctx_val); + e->wr(e->priv, SGX_S_DPM_PARTIAL, 1); + goto tail; + +fire: + e->wr(e->priv, SGX_S_DPM_CONTEXT, 3); + e->wr(e->priv, SGX_S_DPM_PARTIAL, 0); + extra = 0; +tail: + offset += cookie[7]; + e->wr(e->priv, SGX_S_3D_PARAM_BASE, offset + extra); +out: + if (!wait_ev) + return 0; + return sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, wait_ev, + wait_ev, SGX_K_EVENT_HOST_CLEAR, wait_ev); +fail: + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + return -22; /* -EINVAL */ +} + +/* Xpsb_oom_abort at 0x3cf0, transcribed from xpsb_oom_abort() in + * tools/xpsb-open/xpsb_hw.c:734-758: stop the tiler and choose the recovery + * mode. It does not grow the parameter heap - the answer it gives back asks + * for a partial render, which rasters what is already binned and then resumes + * the tiler into the pages that frees. + * + * cookie[15] latches the DPM status: bit 4 means a global abort, every macro + * tile, and bits 3:0 name the one the DPM picked otherwise. cookie[13] bit 30 + * is set by the previous resume when almost no headroom came back, and + * escalates the next abort to global - which is what stops a scene from + * recovering forever without making progress. */ +static inline int sgx_oom_abort(struct sgx_scene_env *s, unsigned int *cookie, + unsigned int *bca, unsigned int *rca, + unsigned int *oflags) +{ + const struct sgx_kick_env *e = &s->k; + unsigned int status = e->rd(e->priv, SGX_S_DPM_STATUS); + + cookie[15] = status; + if (cookie[13] & 0x40000000u) { + if (!(status & 0x10)) + cookie[15] = 0x10; + sgx_wrb(e, SGX_S_DPM_MODE, 4); + } else { + sgx_wrb(e, SGX_S_DPM_MODE, (status & 0x10) ? 4 : 2); + } + + *bca = SGX_S_RASTER_BLOCK; + *rca = SGX_S_TA; + *oflags = SGX_S_FIRE_FLAG_XHW_OOM; + cookie[14] = 1; + return 0; +} + +/* The out-of-memory branch of Xpsb_scene_switch_fire at 0x47b8: the tiler + * resume, phase five of the partial render. The DPM rewrites its page table + * once per allocation context, so the high halves of the per-context tile + * counters are saved across the swap and put back. Whether any headroom came + * back is recorded in cookie[13] bit 30, which is what stops the next + * out-of-memory from livelocking. */ +static inline int sgx_oom_partial(struct sgx_scene_env *s, unsigned int *cookie) +{ + const struct sgx_kick_env *e = &s->k; + unsigned int save[16] = { 0 }, n, i, slot; + int save_all = (cookie[15] & 0x10) != 0; + + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, ((cookie[15] >> 4) ^ 1u) & 1u); + sgx_set_alloc_mode(s, (s->alloc_mode & ~1u) | + ((s->alloc_mode >> 1) & 1u)); + e->wr(e->priv, SGX_S_DPM_PAGE_TABLE, s->oom_page_table); + e->wr(e->priv, SGX_S_DPM_REQUEST, 1); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_A, + SGX_S_EV_DPM_A, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_A)) + goto fail; + + slot = cookie[15] & 0xf; + n = save_all ? ((cookie[0] == 0) ? 4u : 16u) : 0u; + if (s->regions) { + if (save_all) + for (i = 0; i < n; i++) + save[i] = s->regions[i][1] & 0xffff0000u; + else + save[slot] = s->regions[slot][1] & 0xffff0000u; + } + + sgx_set_alloc_mode(s, (s->alloc_mode & ~1u) | + ((s->alloc_mode >> 2) & 1u)); + e->wr(e->priv, SGX_S_DPM_REQUEST, 1); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_A, + SGX_S_EV_DPM_A, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_A)) + goto fail; + + if (s->regions) { + if (save_all) + for (i = 0; i < n; i++) + s->regions[i][1] = + (s->regions[i][1] & 0xffff) | save[i]; + else + s->regions[slot][1] = + (s->regions[slot][1] & 0xffff) | save[slot]; + } + + e->wr(e->priv, SGX_S_DPM_REQUEST, 4); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_B, + SGX_S_EV_DPM_B, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_B)) + goto fail; + e->wr(e->priv, SGX_S_DPM_REQUEST2, 2); + if (sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_DPM_D, + SGX_S_EV_DPM_D, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_DPM_D)) + goto fail; + + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + if (((e->rd(e->priv, SGX_S_DPM_TA_LOCAL_B) & 0xffff) - + (e->rd(e->priv, SGX_S_DPM_FREE) >> 16)) <= 0x1f) + cookie[13] |= 0x40000000u; + else + cookie[13] &= ~0x40000000u; + sgx_wrb(e, SGX_S_DPM_MODE, 1); + return 0; + +fail: + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + return 0; +} + +/* Xpsb_scene_switch_fire at 0x4550. */ +static inline int sgx_scene_switch_fire(struct sgx_scene_env *s, + unsigned int fire_flags, + unsigned int hw_context, + unsigned int *cookie, + unsigned int offset, unsigned int engine, + unsigned int flags, + const unsigned int *oom_cmds, + unsigned int num_oom_cmds, + unsigned int *rca) +{ + const struct sgx_kick_env *e = &s->k; + unsigned int ctx_bit = (hw_context == 1) ? 1u : 0u; + unsigned int base = (s->alloc_mode & ~1u) | ctx_bit; + + if (engine == SGX_S_SCENE_ENGINE_RASTER) { + sgx_set_alloc_mode(s, (base & ~2u) | (ctx_bit << 1)); + if ((flags & (SGX_S_SCENE_FLAG_DIRTY | + SGX_S_SCENE_FLAG_COMPLETE)) != + (SGX_S_SCENE_FLAG_DIRTY | SGX_S_SCENE_FLAG_COMPLETE)) + return 0; + if (sgx_scene_fire_common(s, cookie, 1, offset, flags, + oom_cmds, num_oom_cmds, rca) == -22) + return 0; + sgx_zls_arm(s, cookie, offset); + sgx_kick_render(e); + return 0; + } + if (engine != SGX_S_SCENE_ENGINE_TA) + return 0; + + sgx_set_alloc_mode(s, (base & ~4u) | (ctx_bit << 2)); + + if (fire_flags & SGX_S_FIRE_FLAG_XHW_OOM) + return sgx_oom_partial(s, cookie); + + if (flags & SGX_S_SCENE_FLAG_DIRTY) { + sgx_scene_fire_common(s, cookie, 0, offset, flags, NULL, 0, rca); + sgx_zls_arm(s, cookie, offset); + sgx_kick_ta(e); + return 0; + } + + /* Clean scene: bind the parameter buffer and start the tiler. */ + e->wr(e->priv, SGX_S_TA_PARAM_BASE, offset + cookie[8]); + e->wr(e->priv, SGX_S_DPM_ZLS_ENABLE, 0); + sgx_zls_arm(s, cookie, offset); + e->wr(e->priv, SGX_S_DPM_REQUEST, 2); + e->wr(e->priv, SGX_S_DPM_REQUEST2, 2); + e->wr(e->priv, SGX_S_TA_CTRL2, 1); + sgx_wrb(e, SGX_S_TA_START, 0x80000000); + if (flags & SGX_S_SCENE_FLAG_SETUP) { + sgx_emit_scene_setup(e, cookie, offset); + e->wr(e->priv, SGX_S_TA_STATE, 0); + } + sgx_kick_wait_clear(e, 0, 0, 0, SGX_K_EVENT_STATUS, SGX_S_EV_TA_SETUP, + SGX_S_EV_TA_SETUP, SGX_K_EVENT_HOST_CLEAR, + SGX_S_EV_TA_SETUP); + sgx_kick_ta(e); + return 0; +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_scenectx.h b/drivers/gpu/drm/gma500/sgx_scenectx.h --- a/drivers/gpu/drm/gma500/sgx_scenectx.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_scenectx.h 2026-09-08 10:56:21.887775715 +0200 @@ -0,0 +1,217 @@ +/* The part's two hardware scene contexts. + * + * EUR_CR_DPM_STATE_CONTEXT_ID (sgx535defs.h:1605-1614) carries three one-bit + * fields, and each one says which of two scene contexts one side of the DPM is + * working in: ALLOC the tiler's page allocations, DALLOC the render's + * deallocations, LS the state store/load/clear task. One bit is where the + * count of two comes from - it is not a constant chosen here. + * + * The vendor's microkernel keeps exactly two software slots against those + * bits, sContext0RTData and sContext1RTData (sgx_mkif.h:303-306), picks the + * slot already holding the render target, else a free one, else the one the + * other engine is not using (3d.asm:2451-2492) - which is the policy this + * pool implements. + * + * What is per context rather than per device is the DPM state table, the DPM + * control table, the MTE object tail pointer memory and the TE tail pointer + * cache: StoreDPMContext (sgx_utils.asm:2390-2560) saves those four and + * nothing else. The parameter buffer - the free lists, the page tables and the + * page accounting - is not among them, and on this revision FIX_HW_BRN_25910 + * forces the tiler's and the render's parameter buffer register sets to hold + * the same buffer (sgx_utils.asm:1089-1094, 1193-1204). Two scenes therefore + * cannot be in flight over separate parameter memory here, which is why this + * pool hands a context to one frame at a time and refuses when both are held. + * + * No register access: the caller writes the context bits through + * sgx_scene.h's allocation mode, so this is pure policy and is swept on the + * host by test/sgx_scenectx_test.c. + * + * Copyright (C) 2026 René Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_SCENECTX_H_ +#define _SGX_SCENECTX_H_ + +/* EUR_CR_DPM_STATE_CONTEXT_ID and its three selectors. sgx_scene.h calls the + * same register SGX_S_DPM_ALLOC_MODE and builds the word; these are here so + * the field a caller means is named rather than shifted in place. */ +#define SGX_CTX_STATE_CONTEXT_ID 0x0630u +#define SGX_CTX_ID_LS_MASK 0x00000001u +#define SGX_CTX_ID_DALLOC_MASK 0x00000002u +#define SGX_CTX_ID_ALLOC_MASK 0x00000004u + +/* Two, because each selector is one bit wide. */ +#define SGX_CTX_COUNT (SGX_CTX_ID_LS_MASK + 1u) + +/* One context. owner is the scene that last fired on it, held so a scene that + * comes back finds its own DPM state still there; busy is the scene whose + * render is still on the core, which is what makes the context unavailable + * rather than merely used. stamp orders the free ones least-recently-returned + * first. */ +struct sgx_ctx_slot { + const void *owner; + const void *busy; + unsigned long stamp; +}; + +struct sgx_ctx_pool { + struct sgx_ctx_slot slot[SGX_CTX_COUNT]; + unsigned int usable; /* 1 or SGX_CTX_COUNT */ + unsigned long clock; + unsigned long reuses; /* frames that kept the context they had */ + unsigned long switches; /* frames that took the other one */ + unsigned long refusals; /* frames refused because both were held */ + unsigned long overlaps; /* frames started while another still rendered */ +}; + +/* Is a context other than the one named free to take? The caller uses this to + * decide whether it may skip draining the render in `busy` - true means the + * incoming frame has somewhere to go that the render is not using. */ +static inline bool sgx_ctx_free_other(const struct sgx_ctx_pool *p, int busy) +{ + unsigned int i, n = p->usable < SGX_CTX_COUNT ? p->usable : SGX_CTX_COUNT; + + if (busy < 0) + return false; + for (i = 0; i < n; i++) + if ((int)i != busy && !p->slot[i].busy) + return true; + return false; +} + +/* usable is the module's answer to how many contexts to use. Out of range is + * refused, not clamped: a caller that asked for three has a bug, and running + * it on two would hide it. */ +static inline int sgx_ctx_pool_init(struct sgx_ctx_pool *p, unsigned int usable) +{ + unsigned int i; + + if (!usable || usable > SGX_CTX_COUNT) + return -22; /* -EINVAL */ + for (i = 0; i < SGX_CTX_COUNT; i++) { + p->slot[i].owner = 0; + p->slot[i].busy = 0; + p->slot[i].stamp = 0; + } + p->usable = usable; + p->clock = 0; + p->reuses = 0; + p->switches = 0; + p->refusals = 0; + return 0; +} + +/* A core recovery destroys both contexts' DPM state, so nothing is owned and + * nothing is outstanding afterwards. Keeping an owner across it would let a + * scene take back a context whose state tables no longer describe it. */ +static inline void sgx_ctx_pool_reset(struct sgx_ctx_pool *p) +{ + unsigned int i; + + for (i = 0; i < SGX_CTX_COUNT; i++) { + p->slot[i].owner = 0; + p->slot[i].busy = 0; + p->slot[i].stamp = 0; + } + p->clock = 0; +} + +/* Take a context for this scene. Returns its number, or -EBUSY when every + * usable context still belongs to a render that has not been waited for - + * which the caller answers by draining, never by taking one anyway. */ +static inline int sgx_ctx_take(struct sgx_ctx_pool *p, const void *scene) +{ + unsigned int i, n = p->usable < SGX_CTX_COUNT ? p->usable : SGX_CTX_COUNT; + unsigned long oldest = 0; + int best = -1; + + for (i = 0; i < n; i++) + if (p->slot[i].owner == scene && !p->slot[i].busy) { + p->reuses++; + return (int)i; + } + for (i = 0; i < n; i++) { + if (p->slot[i].busy) + continue; + if (best < 0 || p->slot[i].stamp < oldest) { + best = (int)i; + oldest = p->slot[i].stamp; + } + } + if (best < 0) { + p->refusals++; + return -16; /* -EBUSY */ + } + if (p->slot[best].owner != scene) + p->switches++; + /* A scene names one context. The stale claim on the other has to go or + * a later take would find two slots answering to this scene and the + * choice between them would be the array order. A slot whose render is + * still outstanding keeps its name: that render is this scene's. */ + for (i = 0; i < SGX_CTX_COUNT; i++) + if (i != (unsigned int)best && p->slot[i].owner == scene && + !p->slot[i].busy) + p->slot[i].owner = 0; + p->slot[best].owner = scene; + return (int)best; +} + +/* The frame is done with the context and no render of its is outstanding. */ +static inline void sgx_ctx_put(struct sgx_ctx_pool *p, int ctx) +{ + unsigned int i; + + if (ctx < 0 || (unsigned int)ctx >= SGX_CTX_COUNT) + return; + p->slot[ctx].busy = 0; + p->slot[ctx].stamp = ++p->clock; + /* The scene may have taken the other context while this one's render + * was outstanding - take could not drop the claim then. The later + * claim is the live one, so this becomes free rather than a second + * context answering to the same scene. */ + for (i = 0; i < SGX_CTX_COUNT; i++) + if (i != (unsigned int)ctx && p->slot[ctx].owner && + p->slot[i].owner == p->slot[ctx].owner) + p->slot[ctx].owner = 0; +} + +/* The frame fired a render on this context and did not wait for it. Until the + * wait, the context is not available to anything else: handing it out would + * put a second scene's tiler on the DPM context the running render is + * deallocating from. */ +static inline void sgx_ctx_hold(struct sgx_ctx_pool *p, int ctx, + const void *scene) +{ + if (ctx < 0 || (unsigned int)ctx >= SGX_CTX_COUNT) + return; + p->slot[ctx].busy = scene; +} + +/* A scene is going away: no context may go on naming it, or a later scene at + * the same address would be taken for it. */ +static inline void sgx_ctx_forget(struct sgx_ctx_pool *p, const void *scene) +{ + unsigned int i; + + for (i = 0; i < SGX_CTX_COUNT; i++) { + if (p->slot[i].owner == scene) + p->slot[i].owner = 0; + if (p->slot[i].busy == scene) + p->slot[i].busy = 0; + } +} + +/* Is a render outstanding on any context? The submit path asks before it + * reprograms anything the running render still reads. */ +static inline int sgx_ctx_any_busy(const struct sgx_ctx_pool *p) +{ + unsigned int i; + + for (i = 0; i < SGX_CTX_COUNT; i++) + if (p->slot[i].busy) + return 1; + return 0; +} + +#endif diff -urNp a/drivers/gpu/drm/gma500/sgx_twod.h b/drivers/gpu/drm/gma500/sgx_twod.h --- a/drivers/gpu/drm/gma500/sgx_twod.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_twod.h 2026-09-08 10:56:21.888775729 +0200 @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * The SGX535 2D block: command encoding, and the check a submitted stream + * has to pass before the kernel writes it to the slave port. + * + * The engine takes variable-length "block header" commands as dwords through + * PSB_SGX_2D_SLAVE_PORT, and addresses memory through the BIF as offsets from + * BIF_TWOD_REQ_BASE. Every encoding below is transcribed from psb_reg.h of the + * GPL psb-kernel-source (kept in this tree as driver/gma500/psb_reg.h) and + * cross-checked against xserver-xorg-video-psb-0.32.1/src/psb_accel.c, which + * drove this engine on this silicon, and against tools/baremetal/sgxtri.c + * stage twod, which ran it again here (work/twod-cursor/README.md). + * + * Pure logic, so the module and a host test share it verbatim: the checker is + * a security boundary - a stream it passes reaches memory through the client's + * own page tables - and it is tested before the hardware is. + * + * Copyright (C) 2026 René Rebe + */ +#ifndef _SGX_TWOD_H_ +#define _SGX_TWOD_H_ + +/* Registers. psb_drv.h:63, psb_reg.h:152-161 */ +#define SGX_2D_SLAVE_PORT 0x4000u +#define SGX_2D_SOCIF 0x0e18u +#define SGX_2D_SOCIF_FREE_MASK 0x000000ffu /* dwords the FIFO takes */ +#define SGX_2D_SOCIF_EMPTY 0x00000080u +#define SGX_2D_BLIT_STATUS 0x0e04u +#define SGX_2D_STATUS_BUSY 0x01000000u +#define SGX_2D_STATUS_COMPLETE_MASK 0x00ffffffu /* FENCE and FLUSH blocks retired */ +/* psb_sgx.c:160 - at most this many dwords between free-space checks. */ +#define SGX_2D_FIFO_CHUNK 0x60u +/* EVENT_STATUS bit 27, psb_reg.h:74. */ +#define SGX_2D_EV_COMPLETE 0x08000000u + +/* Block headers, psb_reg.h:173-188: object type in bits [31:28]. */ +#define SGX_2D_BH_MASK 0xf0000000u +#define SGX_2D_CLIP_BH 0x00000000u +#define SGX_2D_PAT_BH 0x10000000u +#define SGX_2D_CTRL_BH 0x20000000u +#define SGX_2D_SRC_OFF_BH 0x30000000u +#define SGX_2D_MASK_OFF_BH 0x40000000u +#define SGX_2D_FENCE_BH 0x70000000u +#define SGX_2D_BLIT_BH 0x80000000u +#define SGX_2D_SRC_SURF_BH 0x90000000u +#define SGX_2D_DST_SURF_BH 0xa0000000u +#define SGX_2D_PAT_SURF_BH 0xb0000000u +#define SGX_2D_SRC_PAL_BH 0xc0000000u +#define SGX_2D_PAT_PAL_BH 0xd0000000u +#define SGX_2D_MASK_SURF_BH 0xe0000000u +#define SGX_2D_FLUSH_BH 0xf0000000u + +/* Surface words, psb_reg.h:380-408 (source) and :450-469 (destination): the + * format in bits [18:15], the byte stride in [14:0], then one dword carrying + * the byte offset from BIF_TWOD_REQ_BASE in bits [27:2]. The same format + * codes serve both ends for the RGB layouts. */ +#define SGX_2D_SURF_FORMAT_MASK 0x00078000u +#define SGX_2D_SURF_STRIDE_MASK 0x00007fffu +#define SGX_2D_SURF_RESERVED 0x0ff80000u +#define SGX_2D_ADDR_MASK 0x0ffffffcu +#define SGX_2D_REACH 0x10000000u /* 28-bit offset field */ +#define SGX_2D_FMT_332RGB 0x00030000u +#define SGX_2D_FMT_4444ARGB 0x00038000u +#define SGX_2D_FMT_555RGB 0x00040000u +#define SGX_2D_FMT_1555ARGB 0x00048000u +#define SGX_2D_FMT_565RGB 0x00050000u +#define SGX_2D_FMT_0888ARGB 0x00058000u +#define SGX_2D_FMT_8888ARGB 0x00060000u + +/* Source offset, psb_reg.h:276-279: x in [23:12], y in [11:0]. The blit's + * start and size words use the same split, psb_reg.h:355-371. */ +#define SGX_2D_XY_MASK 0x00ffffffu +#define SGX_2D_COORD_MAX 0xfffu +#define SGX_2D_XY(x, y) (((unsigned int)(x) << 12) | (unsigned int)(y)) + +/* The blit command word, psb_reg.h:297-341. */ +#define SGX_2D_ROT_MASK (3u << 25) +#define SGX_2D_ROT_NONE (0u << 25) +#define SGX_2D_COPYORDER_MASK (3u << 23) +#define SGX_2D_COPYORDER_TL2BR (0u << 23) +#define SGX_2D_COPYORDER_BR2TL (1u << 23) +#define SGX_2D_COPYORDER_TR2BL (2u << 23) +#define SGX_2D_COPYORDER_BL2TR (3u << 23) +#define SGX_2D_DSTCK_MASK 0x00600000u +#define SGX_2D_SRCCK_MASK 0x00180000u +#define SGX_2D_CLIP_ENABLE 0x00040000u +#define SGX_2D_ALPHA_ENABLE 0x00020000u +#define SGX_2D_USE_PAT 0x00010000u +#define SGX_2D_USE_FILL 0x00000000u +#define SGX_2D_ROP3B_SHIFT 8 +#define SGX_2D_ROP3A_SHIFT 0 +#define SGX_2D_BLIT_RESERVED 0x08000000u +/* work/twod-cursor/README.md: with only ROP3A set the blit is silently + * consumed - the engine waits on a mask surface that is never bound. */ +#define SGX_2D_ROP3(op) (((unsigned int)(op) << SGX_2D_ROP3A_SHIFT) | \ + ((unsigned int)(op) << SGX_2D_ROP3B_SHIFT)) +#define SGX_2D_ROP_SRCCOPY 0xccu +#define SGX_2D_ROP_PATCOPY 0xf0u + +/* One blit and its state, and the largest stream a submit may carry. Nine + * dwords per copy - both surfaces re-described, offset, four for the blit - + * gives a display server 1800 rectangles per ioctl. */ +#define SGX_2D_MAX_DWORDS 16384u +/* What the kernel appends: a surface, a 1x1 fill of the sequence number + * into its own page, FENCE and FLUSH. */ +#define SGX_2D_TRAILER_DWORDS 8u + +/* Bytes per pixel of a surface word's format, or 0 for a format the checker + * does not let through: palettes and alpha-only need a PAL or MASK block + * that is refused below, and the YUV layouts have no measured extent. */ +static inline unsigned int sgx_2d_format_bpp(unsigned int fmt) +{ + switch (fmt & SGX_2D_SURF_FORMAT_MASK) { + case SGX_2D_FMT_332RGB: + return 1; + case SGX_2D_FMT_4444ARGB: + case SGX_2D_FMT_555RGB: + case SGX_2D_FMT_1555ARGB: + case SGX_2D_FMT_565RGB: + return 2; + case SGX_2D_FMT_0888ARGB: + case SGX_2D_FMT_8888ARGB: + return 4; + default: + return 0; + } +} + +/* Whether a ROP3 reads the source. The code's bit index is (P<<2)|(S<<1)|D + * - SRCCOPY 0xcc sets exactly the S=1 bits - so it depends on S when the + * S=1 half differs from the S=0 half. */ +static inline int sgx_2d_rop_reads_src(unsigned int rop) +{ + return ((rop >> 2) & 0x33u) != (rop & 0x33u); +} + +static inline unsigned int sgx_2d_surf_word(unsigned int bh, unsigned int fmt, + unsigned int stride) +{ + return bh | (fmt & SGX_2D_SURF_FORMAT_MASK) | + (stride & SGX_2D_SURF_STRIDE_MASK); +} + +/* + * A binding lookup the checker asks for every extent a blit touches: nonzero + * if [va, va + size) lies wholly inside memory the caller may reach. The kernel + * answers from the file's surface bindings; a test answers from a table. + */ +typedef int (*sgx_2d_range_fn)(void *priv, unsigned long long va, + unsigned long long size); + +struct sgx_2d_report { + unsigned int at; /* dword index of the offending word */ + const char *why; +}; + +struct sgx_2d_surf_state { + unsigned int fmt, bpp, stride; + unsigned long long va; /* base + offset, once set */ + int set; +}; + +/* The rectangle a blit walks, in pixels: for a copy order that runs backwards + * the start names the far corner (pvr_copy() in xf86-video-gma500, + * psbExaSuperCopy() at psb_accel.c:1000-1005). Returns 0 if it does not fit + * the surface's rows or the 12-bit field. */ +static inline int sgx_2d_rect(unsigned int order, unsigned int x, + unsigned int y, unsigned int w, unsigned int h, + unsigned int *x0, unsigned int *y0, + unsigned int *x1, unsigned int *y1) +{ + int xback = order == SGX_2D_COPYORDER_BR2TL || + order == SGX_2D_COPYORDER_TR2BL; + int yback = order == SGX_2D_COPYORDER_BR2TL || + order == SGX_2D_COPYORDER_BL2TR; + + if (!w || !h || w > SGX_2D_COORD_MAX + 1u || h > SGX_2D_COORD_MAX + 1u) + return 0; + if (xback) { + if (x < w - 1u) + return 0; + *x0 = x - (w - 1u); + *x1 = x; + } else { + if (x + (w - 1u) > SGX_2D_COORD_MAX) + return 0; + *x0 = x; + *x1 = x + (w - 1u); + } + if (yback) { + if (y < h - 1u) + return 0; + *y0 = y - (h - 1u); + *y1 = y; + } else { + if (y + (h - 1u) > SGX_2D_COORD_MAX) + return 0; + *y0 = y; + *y1 = y + (h - 1u); + } + return 1; +} + +/* The rows a rectangle spans, whole, held against the caller's bindings. Whole + * rows rather than the pixels alone, because what the engine prefetches along + * a row is not documented; every surface this driver allocates is a whole + * number of rows. */ +static inline int sgx_2d_extent_ok(const struct sgx_2d_surf_state *s, + unsigned int x1, unsigned int y0, + unsigned int y1, sgx_2d_range_fn ok, + void *priv) +{ + unsigned long long lo, hi; + + if ((unsigned long long)(x1 + 1u) * s->bpp > s->stride) + return 0; + lo = s->va + (unsigned long long)y0 * s->stride; + hi = s->va + (unsigned long long)(y1 + 1u) * s->stride; + return ok(priv, lo, hi - lo); +} + +static inline int sgx_2d_refuse(struct sgx_2d_report *rep, unsigned int at, + const char *why, int code) +{ + if (rep) { + rep->at = at; + rep->why = why; + } + return code; +} + +/* + * Check a stream. Zero means acceptable, -1 malformed (a block cut short, a + * reserved bit set, a size of zero), 1 refused (a block the kernel does not + * let userspace emit, a surface outside the caller's bindings, a blit that + * reads a source that was never bound). + * + * Only blocks whose length is certain are parsed: DST_SURF and SRC_SURF (two + * dwords), SRC_OFF, FENCE and FLUSH (one), and BLIT with USE_PAT clear (four: + * command, fill colour, start, size). Every other block is refused, because a + * block whose length is misjudged puts the parser out of step with the engine + * - the fill colour of the next blit is then an address, which is exactly + * what work/twod-cursor/README.md saw fault at 0x8eadb000. CLIP_BH in + * particular does not take the two data words psb_reg.h suggests. + * + * base is the GPU address BIF_TWOD_REQ_BASE holds; a surface word carries + * an offset from it. The sequence-number page the kernel appends is not the + * stream's to name: the caller's range function refuses it. + */ +static inline int sgx_2d_check(const unsigned int *s, unsigned int n, + unsigned long long base, sgx_2d_range_fn ok, + void *priv, struct sgx_2d_report *rep) +{ + struct sgx_2d_surf_state dst = { 0, 0, 0, 0, 0 }; + struct sgx_2d_surf_state src = { 0, 0, 0, 0, 0 }; + unsigned int sx = 0, sy = 0; + int src_off = 0; + unsigned int i = 0; + + if (!s || !n || n > SGX_2D_MAX_DWORDS) + return sgx_2d_refuse(rep, 0, "empty or oversized stream", -1); + while (i < n) { + unsigned int w = s[i]; + + switch (w & SGX_2D_BH_MASK) { + case SGX_2D_DST_SURF_BH: + case SGX_2D_SRC_SURF_BH: { + struct sgx_2d_surf_state *st = + (w & SGX_2D_BH_MASK) == SGX_2D_DST_SURF_BH ? + &dst : &src; + unsigned int bpp = sgx_2d_format_bpp(w); + unsigned int stride = w & SGX_2D_SURF_STRIDE_MASK; + unsigned int addr; + + if (i + 2 > n) + return sgx_2d_refuse(rep, i, "surface block cut short", -1); + if (w & SGX_2D_SURF_RESERVED) + return sgx_2d_refuse(rep, i, "reserved surface bits", -1); + if (!bpp) + return sgx_2d_refuse(rep, i, "surface format", 1); + if (!stride || (stride & 3u) || stride < bpp) + return sgx_2d_refuse(rep, i, "surface stride", -1); + addr = s[i + 1]; + if (addr & ~SGX_2D_ADDR_MASK) + return sgx_2d_refuse(rep, i + 1, "surface address bits", -1); + st->fmt = w & SGX_2D_SURF_FORMAT_MASK; + st->bpp = bpp; + st->stride = stride; + st->va = base + addr; + st->set = 1; + if (st == &src) + src_off = 0; /* a new source wants its offset */ + i += 2; + break; + } + case SGX_2D_SRC_OFF_BH: + if (w & ~(SGX_2D_BH_MASK | SGX_2D_XY_MASK)) + return sgx_2d_refuse(rep, i, "reserved offset bits", -1); + sx = (w >> 12) & SGX_2D_COORD_MAX; + sy = w & SGX_2D_COORD_MAX; + src_off = 1; + i += 1; + break; + case SGX_2D_FENCE_BH: + case SGX_2D_FLUSH_BH: + /* psb_reg.h:290: the low 28 bits are ignored, so they + * are required clear rather than left to mean something + * on another revision. */ + if (w & ~SGX_2D_BH_MASK) + return sgx_2d_refuse(rep, i, "fence or flush data bits", -1); + i += 1; + break; + case SGX_2D_BLIT_BH: { + unsigned int rop = w & 0xffu; + unsigned int order = w & SGX_2D_COPYORDER_MASK; + unsigned int xy, wh, x0, y0, x1, y1; + + if (i + 4 > n) + return sgx_2d_refuse(rep, i, "blit block cut short", -1); + if (w & SGX_2D_BLIT_RESERVED) + return sgx_2d_refuse(rep, i, "reserved blit bits", -1); + /* Rotation swaps the source's extent; keying and + * blending need a CTRL block; clipping needs CLIP; a + * pattern needs PAT_SURF. None are let through, so the + * extent below is the whole of what the engine reads. */ + if (w & SGX_2D_ROT_MASK) + return sgx_2d_refuse(rep, i, "rotation", 1); + if (w & (SGX_2D_DSTCK_MASK | SGX_2D_SRCCK_MASK)) + return sgx_2d_refuse(rep, i, "colour key", 1); + if (w & (SGX_2D_CLIP_ENABLE | SGX_2D_ALPHA_ENABLE)) + return sgx_2d_refuse(rep, i, "clip or alpha enable", 1); + if (w & SGX_2D_USE_PAT) + return sgx_2d_refuse(rep, i, "pattern surface", 1); + if (((w >> SGX_2D_ROP3B_SHIFT) & 0xffu) != rop) + return sgx_2d_refuse(rep, i, "rop halves differ", 1); + xy = s[i + 2]; + wh = s[i + 3]; + if ((xy | wh) & ~SGX_2D_XY_MASK) + return sgx_2d_refuse(rep, i + 2, "reserved rectangle bits", -1); + if (!dst.set) + return sgx_2d_refuse(rep, i, "blit before a destination", 1); + if (!sgx_2d_rect(order, (xy >> 12) & SGX_2D_COORD_MAX, + xy & SGX_2D_COORD_MAX, + (wh >> 12) & SGX_2D_COORD_MAX, + wh & SGX_2D_COORD_MAX, + &x0, &y0, &x1, &y1)) + return sgx_2d_refuse(rep, i + 3, "rectangle", -1); + if (!sgx_2d_extent_ok(&dst, x1, y0, y1, ok, priv)) + return sgx_2d_refuse(rep, i, "destination outside the caller's bindings", 1); + if (sgx_2d_rop_reads_src(rop)) { + if (!src.set || !src_off) + return sgx_2d_refuse(rep, i, "source rop without a source and offset", 1); + if (!sgx_2d_rect(order, sx, sy, + (wh >> 12) & SGX_2D_COORD_MAX, + wh & SGX_2D_COORD_MAX, + &x0, &y0, &x1, &y1)) + return sgx_2d_refuse(rep, i, "source rectangle", -1); + if (!sgx_2d_extent_ok(&src, x1, y0, y1, ok, priv)) + return sgx_2d_refuse(rep, i, "source outside the caller's bindings", 1); + } + i += 4; + break; + } + default: + return sgx_2d_refuse(rep, i, "block type not accepted from userspace", 1); + } + } + return 0; +} + +/* The trailer the kernel appends: bind its own page as a 4-byte-stride + * 8888 surface and fill one pixel with the sequence number, then FENCE and + * FLUSH in the order pvr_copy() emits them. psb_blit_sequence() in the old + * driver's psb_sgx.c:198 wrote its fence the same way. */ +static inline void sgx_2d_trailer(unsigned int *w, unsigned int page_off, + unsigned int seq) +{ + w[0] = sgx_2d_surf_word(SGX_2D_DST_SURF_BH, SGX_2D_FMT_8888ARGB, 4u); + w[1] = page_off & SGX_2D_ADDR_MASK; + w[2] = SGX_2D_BLIT_BH | SGX_2D_ROT_NONE | SGX_2D_COPYORDER_TL2BR | + SGX_2D_USE_FILL | SGX_2D_ROP3(SGX_2D_ROP_PATCOPY); + w[3] = seq; + w[4] = SGX_2D_XY(0, 0); + w[5] = SGX_2D_XY(1, 1); + w[6] = SGX_2D_FENCE_BH; + w[7] = SGX_2D_FLUSH_BH; +} + +#endif /* _SGX_TWOD_H_ */ diff -urNp a/drivers/gpu/drm/gma500/sgx_use.h b/drivers/gpu/drm/gma500/sgx_use.h --- a/drivers/gpu/drm/gma500/sgx_use.h 1970-01-01 01:00:00.000000000 +0100 +++ b/drivers/gpu/drm/gma500/sgx_use.h 2026-09-08 10:56:21.889775743 +0200 @@ -0,0 +1,99 @@ +/* USE base register allocation. + * + * Thirteen registers, 3..15, each naming a 512 KiB window that shader code is + * fetched through. A PDS program says "register 5, offset 0x120", not an + * address - so nothing can run until a register covers the code. + * + * They are a global hardware resource and they are not in the submit + * whitelist, deliberately: a client able to write them could point another + * client's code fetch at its own memory. So the kernel owns them, which is + * what psb_regman.c did, and userspace asks. + * + * Transcribed from use_grab() in tools/baremetal/sgxtri.c, which is + * psb_regman.c:75-77 - the base is shifted right by seven and the data master + * sits in bits 26:25. + * + * Copyright (C) 2026 Rene Rebe + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#ifndef _SGX_USE_H_ +#define _SGX_USE_H_ + +#include "sgx_kick.h" + +#define SGX_USE_REG_FIRST 3 +#define SGX_USE_REG_NUM 13 +#define SGX_USE_OFFSET_MASK 0x0007ffffu +#define SGX_USE_OFFSET_SIZE (SGX_USE_OFFSET_MASK + 1u) + +#define SGX_CR_USE_CODE_BASE(i) (0x0a0cu + ((unsigned)(i) << 2)) +#define SGX_CUC_BASE_DM_SHIFT 25 +#define SGX_CUC_DM_PIXEL 1 +#define SGX_CUC_BASE_ADDR_ALIGNSHIFT 7 + +struct sgx_use_regs { + u32 base[SGX_USE_REG_NUM]; + u8 dm[SGX_USE_REG_NUM]; + u8 used[SGX_USE_REG_NUM]; +}; + +/* Find a register that already covers this range with the same data master, + * or claim a free one. Returns 0 and fills reg and offset, or -ENOSPC when + * all thirteen are taken by ranges that do not fit. */ +static inline int sgx_use_grab(struct sgx_use_regs *u, u32 base, u32 size, + unsigned int dm, u32 *reg, u32 *offset) +{ + int i, free_slot = -1; + + for (i = 0; i < SGX_USE_REG_NUM; i++) { + if (!u->used[i]) { + if (free_slot < 0) + free_slot = i; + continue; + } + if (u->dm[i] == dm && u->base[i] <= base && + base - u->base[i] < SGX_USE_OFFSET_SIZE && + size < SGX_USE_OFFSET_SIZE - (base - u->base[i])) { + *reg = SGX_USE_REG_FIRST + i; + *offset = base - u->base[i]; + return 0; + } + } + if (free_slot < 0) + return -28; /* -ENOSPC */ + + i = free_slot; + u->base[i] = base & ~SGX_USE_OFFSET_MASK; + /* A range that straddles the 512 KiB window cannot be covered by one + * register, and silently returning the wrong one would fetch the + * wrong code. Written as a subtraction because base + size is u32 + * arithmetic: a size near 2^32 wrapped and passed both tests. */ + if (!(u->base[i] <= base && + base - u->base[i] < SGX_USE_OFFSET_SIZE && + size < SGX_USE_OFFSET_SIZE - (base - u->base[i]))) + return -22; /* -EINVAL */ + u->used[i] = 1; + u->dm[i] = (u8)dm; + *reg = SGX_USE_REG_FIRST + i; + *offset = base - u->base[i]; + return 0; +} + +/* Write the claimed registers to the hardware. Done at submit, with the rest + * of the per-file state, because another file's assignment may be in them. */ +static inline void sgx_use_arm(const struct sgx_kick_env *e, + const struct sgx_use_regs *u) +{ + int i; + + for (i = 0; i < SGX_USE_REG_NUM; i++) { + if (!u->used[i]) + continue; + e->wr(e->priv, SGX_CR_USE_CODE_BASE(SGX_USE_REG_FIRST + i), + (u->base[i] >> SGX_CUC_BASE_ADDR_ALIGNSHIFT) | + ((u32)u->dm[i] << SGX_CUC_BASE_DM_SHIFT)); + } +} + +#endif