DF-4887 / fix.diff
--- a/sys/dev/drm/amd/amdgpu/cik_ih.c +++ b/sys/dev/drm/amd/amdgpu/cik_ih.c @@ -186,9 +186,18 @@ static void cik_ih_set_interrupt_funcs(str static u32 cik_ih_get_wptr(struct amdgpu_device *adev) { u32 wptr, tmp; wptr = le32_to_cpu(adev->wb.wb[adev->irq.ih.wptr_offs]); + /* IV entries are 16 bytes: a wptr that is not 16-byte aligned comes + * from a hostile or faulting device and would permanently spin the + * interrupt walk (the 16-byte-stepping rptr can never equal it). + * Normalize with a warn. + */ + if ((wptr & ~IH_RB_WPTR__RB_OVERFLOW_MASK) & 0xF) + dev_warn(adev->dev, "IH wptr not 16-byte aligned: 0x%08X\n", wptr); + wptr &= ~0xF; if (wptr & IH_RB_WPTR__RB_OVERFLOW_MASK) { wptr &= ~IH_RB_WPTR__RB_OVERFLOW_MASK; @@ -198,6 +207,10 @@ static u32 cik_ih_get_wptr(struct amdgpu_d * from the last not overwritten vector (wptr + 16). Hopefully * this should allow us to catchup. */ + /* derive rptr from the aligned value so a 4-granular wptr + * phase cannot park rptr within 12 bytes of the ring end + * (out-of-bounds decode read) + */ + wptr &= ~0xF; dev_warn(adev->dev, "IH ring buffer overflow (0x%08X, 0x%08X, 0x%08X)\n", wptr, adev->irq.ih.rptr, (wptr + 16) & adev->irq.ih.ptr_mask); adev->irq.ih.rptr = (wptr + 16) & adev->irq.ih.ptr_mask; --- a/sys/dev/drm/amd/amdgpu/amdgpu_ih.c +++ b/sys/dev/drm/amd/amdgpu/amdgpu_ih.c @@ -130,13 +130,19 @@ */ int amdgpu_ih_process(struct amdgpu_device *adev, struct amdgpu_ih_ring *ih, void (*callback)(struct amdgpu_device *adev, struct amdgpu_ih_ring *ih)) { - u32 wptr; + u32 wptr, count; if (!ih->enabled || adev->shutdown) return IRQ_NONE; wptr = amdgpu_ih_get_wptr(adev); + count = 0; restart_ih: /* is somebody else already processing irqs? */ if (atomic_xchg(&ih->lock, 1)) @@ -151,9 +157,14 @@ int amdgpu_ih_process(struct amdgpu_device /* Order reading of wptr vs. reading of IH ring data */ rmb(); - while (ih->rptr != wptr) { + /* cap the per-invocation walk so a storm or a bad wptr cannot + * hard-spin the interrupt thread; the next interrupt re-enters. + */ + while (ih->rptr != wptr && count++ < (ih->ring_size / 16)) { callback(adev, ih); ih->rptr &= ih->ptr_mask; } + if (ih->rptr != wptr) + dev_warn(adev->dev, "IH walk capped: rptr=0x%08X wptr=0x%08X\n", + ih->rptr, wptr); amdgpu_ih_set_rptr(adev); atomic_set(&ih->lock, 0); |