DF-4182 / rflip_wedge.c
/* * 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); } |