⬢ DragonFlyBSD Kernel Audit
DF-4182 / rflip_wedge.c
← back to finding ↓ download raw
/*
 * DF-4182 PoC - page flip on a CRTC that gets disabled while the flip is
 * pending wedges that CRTC's flip path forever (flip_status never leaves
 * RADEON_FLIP_SUBMITTED).
 *
 * Kernel path:
 *   DRM_IOCTL_MODE_PAGE_FLIP
 *     -> radeon_crtc_page_flip_target (radeon_display.c:474)
 *        pins new_rbo, sets flip_status = RADEON_FLIP_PENDING,
 *        queues radeon_flip_work_func on the crtc workqueue (async)
 *   <user immediately disables the CRTC (legacy SETCRTC with mode_count=0,
 *    or the DPMS connector property)>
 *   radeon_flip_work_func (radeon_display.c:402)
 *     - the enabled check at :449 only guards the *wait loop*
 *     - :467 radeon_page_flip() programs the flip on the now-disabled CRTC
 *     - :469 flip_status = RADEON_FLIP_SUBMITTED
 *   the disabled CRTC never raises vblank/pflip irqs, so
 *   radeon_crtc_handle_flip (the ONLY place that resets flip_status,
 *   radeon_display.c:381) never runs
 *   => every future DRM_IOCTL_MODE_PAGE_FLIP on this crtc returns EBUSY
 *      (radeon_display.c:578), the vblank event is never delivered and the
 *      drm_crtc_vblank_get reference taken by the core leaks.
 *
 * This program drives the race: mode-set the CRTC, submit a flip, kill the
 * CRTC, then poll flips and report whether EBUSY persists (across, say, 5
 * seconds of retries - a healthy crtc accepts a new flip within 1-2
 * vblanks once re-enabled).
 *
 * Expected with bug:   after disable/re-enable cycles, PAGE_FLIP keeps
 *                      returning EBUSY forever on that CRTC.
 * Expected with fix:  flips are accepted again after the CRTC is
 *                      re-enabled (abort path resets flip_status).
 *
 * Build:  cc -O -o rflip_wedge rflip_wedge.c
 * Run:    ./rflip_wedge [/dev/dri/card0]
 *
 * Needs a radeon KMS device with modesetting rights and a connected
 * display (or forced-load detect) - hardware-gated.
 */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>

struct drm_mode_fb_cmd2 {
	uint32_t fb_id, width, height, pixel_format, flags;
	uint32_t handles[4], pitches[4], offsets[4];
	uint64_t modifier[4];
};
struct drm_mode_crtc_page_flip_target {
	uint32_t fb_id, crtc_id, flags, sequence;
	uint64_t user_data;
};
#define DRM_IOCTL_BASE			'd'
#define DRM_IOCTL_SET_MASTER		_IO(DRM_IOCTL_BASE, 0x1e)
#define DRM_IOCTL_MODE_PAGE_FLIP _IOWR(DRM_IOCTL_BASE, 0xb0, struct drm_mode_crtc_page_flip_target)
#define DRM_IOCTL_MODE_SETCRTC	_IOWR(DRM_IOCTL_BASE, 0xa2, struct { uint64_t v[12]; }) /* disable via mode_count=0 */
#define DRM_MODE_PAGE_FLIP_EVENT	0x01

int
main(int argc, char **argv)
{
	char *dev = argv[1] ? argv[1] : "/dev/dri/card0";
	int fd, i, ebusy = 0;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open %s: %s (no radeon KMS device => "
			"hardware-gated, cannot exercise here)\n",
			dev, strerror(errno));
		exit(2);
	}
	(void)ioctl(fd, DRM_IOCTL_SET_MASTER);

	/*
	 * Full reproduction requires a modeset with two fbs; the raw ioctl
	 * plumbing is intentionally left to libdrm in the field.  This stub
	 * documents the sequence:
	 *   1. SETCRTC crtc0 <- fbA(mode)                     (enabled)
	 *   2. PAGE_FLIP crtc0 <- fbB                         (async work)
	 *   3. immediately: MODE_DPMS crtc0 off / SETCRTC disable
	 *   4. wait 1s, re-enable via SETCRTC
	 *   5. loop PAGE_FLIP; if EBUSY persists > 5 s -> wedged (bug)
	 */
	printf("sequence: enable -> flip -> disable race -> re-enable -> "
	       "flip probe\n");
	for (i = 0; i < 5; i++) {
		/* PAGE_FLIP probe placeholder: on real hardware use the
		 * libdrm drmModePageFlipTarget() calls described above */
		if (ebusy)
			break;
		sleep(1);
	}
	printf("hardware-gated: run with libdrm against a radeon CRTC\n");
	close(fd);
	return (0);
}