⬢ DragonFlyBSD Kernel Audit
DF-4182 / fix.diff
← back to finding ↓ download raw
fix for DF-4182: radeon_flip_work_func programs a page flip and sets
flip_status = RADEON_FLIP_SUBMITTED even when the CRTC was disabled while
the flip was pending.  A flip on a disabled CRTC never completes (its
vblank/pflip interrupts are off), and nothing else in the driver ever
resets flip_status, so the CRTC's page-flip path is wedged forever
(-EBUSY for every subsequent flip), the queued vblank event is never
delivered, and the drm_crtc_vblank_get reference taken by the ioctl core
leaks.

Fix: abort the flip cleanly when the CRTC is disabled at programming time:
reset flip_status/flip_work, drop the vblank reference, deliver the event,
unpin the new BO (kept in struct radeon_flip_work for the abort path) and
queue the existing unpin work.

Files: sys/dev/drm/radeon/radeon.h, sys/dev/drm/radeon/radeon_display.c
--- a/sys/dev/drm/radeon/radeon.h
+++ b/sys/dev/drm/radeon/radeon.h
@@ -750,6 +750,7 @@
 	uint64_t			base;
 	struct drm_pending_vblank_event *event;
 	struct radeon_bo		*old_rbo;
+	struct radeon_bo		*new_rbo;	/* pinned by page_flip_target, unpinned on abort */
 	struct dma_fence		*fence;
 	bool				async;
 };
--- a/sys/dev/drm/radeon/radeon_display.c
+++ b/sys/dev/drm/radeon/radeon_display.c
@@ -460,6 +460,30 @@
 	/* We borrow the event spin lock for protecting flip_status */
 	spin_lock_irqsave(&crtc->dev->event_lock, flags);
 
+	/*
+	 * If the CRTC was disabled while the flip was pending, abort it:
+	 * a flip programmed on a disabled CRTC never completes, leaving
+	 * flip_status == RADEON_FLIP_SUBMITTED forever, which wedges every
+	 * future page flip on this CRTC with -EBUSY and leaks the vblank
+	 * reference taken by the ioctl core.
+	 */
+	if (!radeon_crtc->enabled) {
+		radeon_crtc->flip_status = RADEON_FLIP_NONE;
+		radeon_crtc->flip_work = NULL;
+		spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+		up_read(&rdev->exclusive_lock);
+		drm_crtc_vblank_put(&radeon_crtc->base);
+		if (work->event)
+			drm_crtc_send_vblank_event(&radeon_crtc->base,
+						   work->event);
+		if (work->new_rbo && !radeon_bo_reserve(work->new_rbo, false)) {
+			radeon_bo_unpin(work->new_rbo);
+			radeon_bo_unreserve(work->new_rbo);
+		}
+		queue_work(radeon_crtc->flip_queue, &work->unpin_work);
+		return;
+	}
+
 	/* set the proper interrupt */
 	radeon_irq_kms_pflip_irq_get(rdev, radeon_crtc->crtc_id);
 
@@ -532,6 +556,7 @@
 	work->fence = dma_fence_get(reservation_object_get_excl(new_rbo->tbo.resv));
 	radeon_bo_get_tiling_flags(new_rbo, &tiling_flags, NULL);
 	radeon_bo_unreserve(new_rbo);
+	work->new_rbo = new_rbo;
 
 	if (!ASIC_IS_AVIVO(rdev)) {
 		/* crtc offset is from display base addr not FB location */