diff --git a/hw/vfio/display.c b/hw/vfio/display.c
index 2c2d3e5b71..6ef383b5e8 100644
--- a/hw/vfio/display.c
+++ b/hw/vfio/display.c
@@ -288,44 +288,54 @@ static void vfio_display_dmabuf_update(void *opaque)
     VFIODMABuf *primary, *cursor;
     bool free_bufs = false, new_cursor = false;;
 
-    primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
-    if (primary == NULL) {
-        if (dpy->ramfb) {
-            ramfb_display_update(dpy->con, dpy->ramfb);
-        }
-        return;
+    if (dpy->event_flags & VFIO_IRQ_EVENT_ENABLE) {
+        dpy_update_interval(dpy->con, GUI_REFRESH_INTERVAL_IDLE);
     }
 
-    if (dpy->dmabuf.primary != primary) {
-        dpy->dmabuf.primary = primary;
-        qemu_console_resize(dpy->con,
-                            primary->buf.width, primary->buf.height);
-        dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
-        free_bufs = true;
-    }
+    if (!dpy->event_flags ||
+        (dpy->event_flags & VFIO_IRQ_EVENT_PRIMRAY_PLANE_FLIP)) {
+        primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
+        if (primary == NULL) {
+            if (dpy->ramfb) {
+                ramfb_display_update(dpy->con, dpy->ramfb);
+            }
+            return;
+        }
 
-    cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
-    if (dpy->dmabuf.cursor != cursor) {
-        dpy->dmabuf.cursor = cursor;
-        new_cursor = true;
-        free_bufs = true;
+        if (dpy->dmabuf.primary != primary) {
+            dpy->dmabuf.primary = primary;
+            qemu_console_resize(dpy->con,
+                                primary->buf.width, primary->buf.height);
+            dpy_gl_scanout_dmabuf(dpy->con, &primary->buf);
+            free_bufs = true;
+        }
     }
 
-    if (cursor && (new_cursor || cursor->hot_updates)) {
-        bool have_hot = (cursor->hot_x != 0xffffffff &&
-                         cursor->hot_y != 0xffffffff);
-        dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
-                             cursor->hot_x, cursor->hot_y);
-        cursor->hot_updates = 0;
-    } else if (!cursor && new_cursor) {
-        dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
-    }
+    if (!dpy->event_flags ||
+        (dpy->event_flags & VFIO_IRQ_EVENT_CURSOR_PLANE_FLIP)) {
+        cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
+        if (dpy->dmabuf.cursor != cursor) {
+            dpy->dmabuf.cursor = cursor;
+            new_cursor = true;
+            free_bufs = true;
+        }
+
+        if (cursor && (new_cursor || cursor->hot_updates)) {
+            bool have_hot = (cursor->hot_x != 0xffffffff &&
+                             cursor->hot_y != 0xffffffff);
+            dpy_gl_cursor_dmabuf(dpy->con, &cursor->buf, have_hot,
+                                 cursor->hot_x, cursor->hot_y);
+            cursor->hot_updates = 0;
+        } else if (!cursor && new_cursor) {
+            dpy_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
+        }
 
-    if (cursor && cursor->pos_updates) {
-        dpy_gl_cursor_position(dpy->con,
-                               cursor->pos_x,
-                               cursor->pos_y);
-        cursor->pos_updates = 0;
+        if (cursor && cursor->pos_updates) {
+            dpy_gl_cursor_position(dpy->con,
+                                   cursor->pos_x,
+                                   cursor->pos_y);
+            cursor->pos_updates = 0;
+        }
     }
 
     dpy_gl_update(dpy->con, 0, 0, primary->buf.width, primary->buf.height);
@@ -340,6 +350,7 @@ static const GraphicHwOps vfio_display_dmabuf_ops = {
     .ui_info    = vfio_display_edid_ui_info,
 };
 
+static int vfio_register_display_notifier(VFIOPCIDevice *vdev);
 static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
 {
     if (!display_opengl) {
@@ -355,6 +366,8 @@ static int vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
         vdev->dpy->ramfb = ramfb_setup(DEVICE(vdev), errp);
     }
     vfio_display_edid_init(vdev);
+    vfio_register_display_notifier(vdev);
+
     return 0;
 }
 
@@ -495,6 +508,177 @@ static void vfio_display_region_exit(VFIODisplay *dpy)
 
 /* ---------------------------------------------------------------------- */
 
+static void primary_plane_update(void *opaque)
+{
+    VFIOPCIDevice *vdev = opaque;
+    VFIODisplay *dpy = vdev->dpy;
+
+    if (!event_notifier_test_and_clear(&dpy->pri_event_notifier)) {
+        return;
+    }
+
+    dpy->event_flags |= VFIO_IRQ_EVENT_PRIMRAY_PLANE_FLIP;
+    graphic_hw_refresh(dpy->con);
+    dpy->event_flags &= ~VFIO_IRQ_EVENT_PRIMRAY_PLANE_FLIP;
+}
+
+static void cursor_plane_update(void *opaque)
+{
+    VFIOPCIDevice *vdev = opaque;
+    VFIODisplay *dpy = vdev->dpy;
+    static int times;
+
+    if (!event_notifier_test_and_clear(&dpy->cur_event_notifier)) {
+        return;
+    }
+
+    /* Have to skip some cursor events due to performance impact */
+    if (times++ / 2) {
+        times = 0;
+        dpy->event_flags |= VFIO_IRQ_EVENT_CURSOR_PLANE_FLIP;
+        graphic_hw_refresh(dpy->con);
+        dpy->event_flags &= ~VFIO_IRQ_EVENT_CURSOR_PLANE_FLIP;
+    }
+}
+
+static int register_display_notifier(VFIOPCIDevice *vdev,
+                                     uint32_t type, uint32_t subtype,
+                                     EventNotifier *notifier,
+                                     void (*handler)(void *opaque))
+{
+    struct vfio_irq_info *irq;
+    struct vfio_irq_set *irq_set;
+    int argsz;
+    int32_t *pfd;
+    int ret;
+
+    ret = vfio_get_dev_irq_info(&vdev->vbasedev,
+                                type,
+                                subtype,
+                                &irq);
+    if (ret) {
+        goto out;
+    }
+
+    ret = event_notifier_init(notifier, 0);
+    if (ret) {
+        error_report("vfio: Unable to init event notifier for device request");
+        goto out;
+    }
+
+    argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+    irq_set = g_malloc0(argsz);
+    irq_set->argsz = argsz;
+    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+                     VFIO_IRQ_SET_ACTION_TRIGGER;
+    irq_set->index = irq->index;
+    irq_set->start = 0;
+    irq_set->count = 1;
+    pfd = (int32_t *)&irq_set->data;
+
+    *pfd = event_notifier_get_fd(notifier);
+    qemu_set_fd_handler(*pfd, handler, NULL, vdev);
+
+    ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set);
+    if (ret) {
+        error_report("vfio: Failed to set up device request notification");
+        qemu_set_fd_handler(*pfd, NULL, NULL, vdev);
+        event_notifier_cleanup(notifier);
+    }
+
+    g_free(irq_set);
+
+out:
+    return ret;
+}
+
+static int vfio_register_display_notifier(VFIOPCIDevice *vdev)
+{
+    VFIODisplay *dpy = vdev->dpy;
+    int ret;
+
+    ret = register_display_notifier(vdev, VFIO_IRQ_TYPE_GFX,
+                                    VFIO_IRQ_SUBTYPE_GFX_PRI_PLANE_FLIP,
+                                    &dpy->pri_event_notifier,
+                                    primary_plane_update);
+
+    if (ret) {
+        goto out;
+    }
+
+    ret = register_display_notifier(vdev, VFIO_IRQ_TYPE_GFX,
+                                   VFIO_IRQ_SUBTYPE_GFX_CUR_PLANE_FLIP,
+                                   &dpy->cur_event_notifier,
+                                   cursor_plane_update);
+out:
+    if (ret) {
+        dpy->event_flags = 0;
+    } else {
+        dpy->event_flags = VFIO_IRQ_EVENT_ENABLE;
+    }
+
+    return ret;
+}
+
+static void unregister_display_notifier(VFIOPCIDevice *vdev,
+                                       uint32_t type, uint32_t subtype,
+                                       EventNotifier *notifier)
+{
+    VFIODisplay *dpy = vdev->dpy;
+    int argsz;
+    struct vfio_irq_info *irq;
+    struct vfio_irq_set *irq_set;
+    int32_t *pfd;
+    int ret;
+
+    if (!dpy->event_flags) {
+        return;
+    }
+
+    ret = vfio_get_dev_irq_info(&vdev->vbasedev,
+                                type,
+                                subtype,
+                                &irq);
+    if (ret) {
+        return ;
+    }
+
+    argsz = sizeof(*irq_set) + sizeof(*pfd);
+
+    irq_set = g_malloc0(argsz);
+    irq_set->argsz = argsz;
+    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD |
+                     VFIO_IRQ_SET_ACTION_TRIGGER;
+    irq_set->index = irq->index;
+    irq_set->start = 0;
+    irq_set->count = 1;
+    pfd = (int32_t *)&irq_set->data;
+    *pfd = -1;
+
+    if (ioctl(vdev->vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set)) {
+        error_report("vfio: Failed to de-assign device request fd: %m");
+    }
+    g_free(irq_set);
+    qemu_set_fd_handler(event_notifier_get_fd(notifier),
+                        NULL, NULL, vdev);
+    event_notifier_cleanup(notifier);
+}
+
+static void vfio_unregister_display_notifier(VFIOPCIDevice *vdev)
+{
+    VFIODisplay *dpy = vdev->dpy;
+
+    unregister_display_notifier(vdev, VFIO_IRQ_TYPE_GFX,
+                                VFIO_IRQ_SUBTYPE_GFX_PRI_PLANE_FLIP,
+                                &dpy->pri_event_notifier);
+
+    unregister_display_notifier(vdev, VFIO_IRQ_TYPE_GFX,
+                                VFIO_IRQ_SUBTYPE_GFX_CUR_PLANE_FLIP,
+                                &dpy->cur_event_notifier);
+    dpy->event_flags = false;
+}
+
 int vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
 {
     struct vfio_device_gfx_plane_info probe;
@@ -531,6 +715,7 @@ void vfio_display_finalize(VFIOPCIDevice *vdev)
         return;
     }
 
+    vfio_unregister_display_notifier(vdev);
     graphic_console_close(vdev->dpy->con);
     vfio_display_dmabuf_exit(vdev->dpy);
     vfio_display_region_exit(vdev->dpy);
diff --git a/include/hw/vfio/vfio-common.h b/include/hw/vfio/vfio-common.h
index 5cab6a1b81..a3f03b20e8 100644
--- a/include/hw/vfio/vfio-common.h
+++ b/include/hw/vfio/vfio-common.h
@@ -27,6 +27,7 @@
 #include "qemu/notify.h"
 #include "ui/console.h"
 #include "hw/display/ramfb.h"
+#include "qemu/event_notifier.h"
 #ifdef CONFIG_LINUX
 #include <linux/vfio.h>
 #endif
@@ -145,6 +146,10 @@ typedef struct VFIODMABuf {
     QTAILQ_ENTRY(VFIODMABuf) next;
 } VFIODMABuf;
 
+#define VFIO_IRQ_EVENT_ENABLE              (1 << 0)
+#define VFIO_IRQ_EVENT_PRIMRAY_PLANE_FLIP  (1 << 1)
+#define VFIO_IRQ_EVENT_CURSOR_PLANE_FLIP   (1 << 2)
+
 typedef struct VFIODisplay {
     QemuConsole *con;
     RAMFBState *ramfb;
@@ -152,6 +157,9 @@ typedef struct VFIODisplay {
     struct vfio_region_gfx_edid *edid_regs;
     uint8_t *edid_blob;
     QEMUTimer *edid_link_timer;
+    EventNotifier pri_event_notifier;
+    EventNotifier cur_event_notifier;
+    uint32_t event_flags;
     struct {
         VFIORegion buffer;
         DisplaySurface *surface;
