From 8c5127f550efca6d1fa992f898922de109116c4e Mon Sep 17 00:00:00 2001 From: MicroFish <2609948707@qq.com> Date: Tue, 3 Nov 2026 03:34:39 +0800 Subject: [PATCH 1/3] drm/virtio: validate EDID and host scanout modes The connector published whatever the host returned: a short or failed EDID read was treated as an absent display, mode sizes were trusted up to unbounded values, and the initial modeset only walked the connector mode list in registration order, so a bogus host mode could be picked over the monitor's preferred one. Validate the EDID reply before parsing it, keep the EDID modes for the boot chain with the preferred timing first, reject host scanout geometry outside 0..8192, and order the initial modeset by EDID preferred, remaining EDID modes, host scanout modes, the bootloader geometry and finally the configured default. The plane atomic check rejects modes and layouts the 2D scanout cannot scale or address, and a failed initial modeset now fails KMS init instead of leaving a half-configured pipeline. Signed-off-by: MicroFish <2609948707@qq.com> --- drivers/gpu/drm/virtio/virtgpu_kms.c | 359 +++++++++++++++---- include/drivers/gpu/drm/virtio/virtgpu_drv.h | 5 + 2 files changed, 288 insertions(+), 76 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index b27300e8..16753a50 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -8,10 +8,12 @@ * */ +#include #include #include #include #include +#include #if CONFIG_VIRTIO_GPU && CONFIG_DRM && CONFIG_VIRTIO_PCI @@ -20,10 +22,10 @@ static struct virtio_gpu_device *vgdev_flush_ctx; static struct virtio_gpu_object *vgdev_flush_obj; static raw_spinlock_t vgdev_flush_lock; -/* Mode config helpers */ static enum drm_connector_status virtgpu_connector_detect(struct drm_connector *connector, bool force); static int virtgpu_connector_get_modes(struct drm_connector *connector); static int virtgpu_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +static uint64_t virtgpu_mode_area(const struct drm_display_mode *mode); static void virtgpu_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state); static int virtgpu_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_framebuffer *fb); static void virtgpu_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_crtc_state *old_state); @@ -31,12 +33,17 @@ static int virtgpu_crtc_atomic_disable(struct drm_crtc *cr static int virtgpu_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, uint32_t flags); static int virtgpu_crtc_cursor_set(struct drm_crtc *crtc, struct drm_gem_object *gem, uint32_t width, uint32_t height, int32_t hot_x, int32_t hot_y); static int virtgpu_crtc_cursor_move(struct drm_crtc *crtc, int32_t x, int32_t y); +static int virtgpu_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state); static const uint32_t virtgpu_formats[] = { DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, }; +static const struct drm_plane_helper_funcs virtgpu_plane_helpers = { + .atomic_check = virtgpu_plane_atomic_check, +}; + static const struct drm_crtc_helper_funcs virtgpu_crtc_helpers = { .mode_set = virtgpu_crtc_atomic_flush, .page_flip = virtgpu_crtc_page_flip, @@ -56,11 +63,34 @@ static const struct drm_connector_helper_funcs virtgpu_conn_helpers = { .mode_valid = virtgpu_connector_mode_valid, }; +/* Reject modes and layouts the 2D scanout cannot scale or address. */ +static int virtgpu_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) +{ + struct drm_crtc_state *crtc_state = state->crtc_state; + struct drm_framebuffer *fb = state->fb; + struct virtio_gpu_object *obj; + + if (!fb) return 0; + + /* The host scanout takes the framebuffer size as the output size; no scaler. */ + if (crtc_state && crtc_state->active && ((uint32_t)crtc_state->mode.hdisplay != fb->width || (uint32_t)crtc_state->mode.vdisplay != fb->height)) return -EINVAL; + + /* The host scanout cannot crop or pan; the committed view must cover the whole buffer. */ + if (!drm_plane_state_full_frame(fb, state)) return -EINVAL; + + /* A blob carries its own layout; a 2D resource is scanned out through its packed stride. */ + obj = (fb->obj[0] && fb->obj[0]->dev == plane->dev) ? to_virtio_gpu_object(fb->obj[0]) : NULL; + if (!obj) return -EINVAL; + if (!obj->created_blob && (fb->width > obj->width || fb->height > obj->height || fb->pitches[0] != obj->stride || fb->offsets[0] || !virtgpu_2d_formats_compatible(obj->format, fb->format))) + return -EINVAL; + + return 0; +} + /* Panic-time flush guard: skip while an ordinary control submission is active. */ static bool virtgpu_display_flush_guard(void) { struct virtio_gpu_device *vgdev = vgdev_flush_ctx; - if (!vgdev) return true; return !mutex_is_locked(&vgdev->ctrlq_cmd_lock); } @@ -70,50 +100,54 @@ static enum drm_connector_status virtgpu_connector_detect(struct drm_connector * { struct drm_device *dev = connector->dev; struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; - (void)force; /* VirtIO-GPU is always connected in a VM */ return vgdev->num_scanouts > 0 ? connector_status_connected : connector_status_disconnected; } -/* - * Fetch the EDID for scanout 0 from the host and, if valid, publish it as - * the connector's EDID property blob and add the EDID-derived modes. - * - * Returns the number of modes added, or 0 if no EDID / invalid EDID. - */ +/* Publish the host EDID and its modes; returns modes added, 0 when none, negative on a read fault. */ static int virtgpu_connector_get_edid_modes(struct drm_connector *connector) { struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)connector->dev->dev_private; struct edid *edid; + ilist_node_t *node; int edid_size = 0; int count = 0; int ret; + vgdev->edid_mode_count = 0; if (!vgdev->has_edid) return 0; /* The host EDID response is capped at 1024 bytes by the virtio protocol. */ edid = malloc(1024); - if (!edid) return 0; + if (!edid) return -ENOMEM; ret = virtgpu_cmd_get_edid(vgdev, 0, edid, &edid_size); - if (ret || edid_size <= 0) { + if (ret) { + free(edid); + return ret; + } + if (edid_size <= 0) { free(edid); return 0; } + /* A partial EDID is a malformed reply, not an absent display. */ + if (edid_size < EDID_LENGTH) { + free(edid); + return -EIO; + } + /* Reject a truncated EDID whose header overstates its size. */ if ((size_t)((unsigned)edid->extensions + 1) * EDID_LENGTH > (size_t)edid_size) { - DRM_ERROR("Connector: EDID size mismatch (declared %u blocks, received %d bytes)\n", edid->extensions + 1, edid_size); free(edid); - return 0; + return -EIO; } if (!drm_edid_is_valid(edid)) { - DRM_ERROR("Connector: EDID invalid (%d bytes)\n", edid_size); free(edid); - return 0; + return -EINVAL; } /* Publish the raw EDID as the connector's EDID property blob. */ @@ -125,8 +159,17 @@ static int virtgpu_connector_get_edid_modes(struct drm_connector *connector) /* Fill in the connector display info derived from the EDID. */ drm_edid_to_display_info(connector, edid); - count = drm_add_edid_modes(connector, edid); + + /* Keep a flagged timing first so the cap cannot drop the monitor's preferred mode. */ + for (node = connector->modes.next; node && node != &connector->modes && vgdev->edid_mode_count < VIRTGPU_MAX_EDID_MODES; node = node->next) { + struct drm_display_mode *mode = container_of(node, struct drm_display_mode, head); + if (mode->type & DRM_MODE_TYPE_PREFERRED) vgdev->edid_modes[vgdev->edid_mode_count++] = mode; + } + for (node = connector->modes.next; node && node != &connector->modes && vgdev->edid_mode_count < VIRTGPU_MAX_EDID_MODES; node = node->next) { + struct drm_display_mode *mode = container_of(node, struct drm_display_mode, head); + if (!(mode->type & DRM_MODE_TYPE_PREFERRED)) vgdev->edid_modes[vgdev->edid_mode_count++] = mode; + } if (count > 0) { char name[14]; @@ -138,12 +181,54 @@ static int virtgpu_connector_get_edid_modes(struct drm_connector *connector) return count; } +/* Describe @width x @height with a synthetic timing. */ +static void virtgpu_mode_fill(struct drm_display_mode *mode, uint32_t width, uint32_t height, uint32_t vrefresh) +{ + memset(mode, 0, sizeof(*mode)); + + (void)snprintf(mode->name, DRM_DISPLAY_MODE_LEN - 1, "%ux%u", (unsigned)width, (unsigned)height); + mode->name[DRM_DISPLAY_MODE_LEN - 1] = '\0'; + mode->hdisplay = (int)width; + mode->hsync_start = (int)width + 80; + mode->hsync_end = (int)width + 160; + mode->htotal = (int)width + 320; + mode->vdisplay = (int)height; + mode->vsync_start = (int)height + 3; + mode->vsync_end = (int)height + 6; + mode->vtotal = (int)height + 32; + mode->vrefresh = (int)(vrefresh ? vrefresh : 60); + + /* Pixel clock includes blanking totals, so userspace derives the refresh. */ + mode->clock = (int)((uint64_t)mode->htotal * (uint64_t)mode->vtotal * (uint64_t)mode->vrefresh / 1000ULL); + mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC; + mode->type = DRM_MODE_TYPE_DRIVER; + mode->status = MODE_OK; +} + +/* Make @preferred the only DRM_MODE_TYPE_PREFERRED mode of the connector. */ +static void virtgpu_connector_set_preferred(struct drm_connector *connector, struct drm_display_mode *preferred) +{ + ilist_node_t *node; + + for (node = connector->modes.next; node && node != &connector->modes; node = node->next) { + struct drm_display_mode *mode = container_of(node, struct drm_display_mode, head); + if (mode == preferred) { + mode->type |= DRM_MODE_TYPE_PREFERRED; + } else { + mode->type &= ~DRM_MODE_TYPE_PREFERRED; + } + } +} + /* Fill the connector mode list from host display info or EDID. */ static int virtgpu_connector_get_modes(struct drm_connector *connector) { - struct drm_device *dev = connector->dev; - struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; + struct drm_device *dev = connector->dev; + struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; + struct drm_display_mode *edid_preferred = NULL; ilist_node_t *node; + bool host_preferred = false; + int edid_modes; int i; /* virtio-gpu has no hotplug: probe the EDID/scanout modes once. */ @@ -152,7 +237,9 @@ static int virtgpu_connector_get_modes(struct drm_connector *connector) /* Query display info from host if not already done */ if (vgdev->num_scanouts == 0) virtgpu_cmd_get_display_info(vgdev); - /* Drop modes from an earlier failed probe, if any. */ + /* The mode pointers die with the list rebuilt below. */ + vgdev->edid_mode_count = 0; + for (node = connector->modes.next; node != &connector->modes;) { struct drm_display_mode *m = container_of(node, struct drm_display_mode, head); @@ -161,40 +248,43 @@ static int virtgpu_connector_get_modes(struct drm_connector *connector) } /* Publish EDID modes (preferred first) plus the host scanout modes. */ - (void)virtgpu_connector_get_edid_modes(connector); + edid_modes = virtgpu_connector_get_edid_modes(connector); + if (edid_modes < 0) DRM_ERROR("connector %s: failed to read EDID (%d), continuing without it.\n", connector->name, edid_modes); + + for (i = 0; i < vgdev->edid_mode_count; i++) { + if (vgdev->edid_modes[i]->type & DRM_MODE_TYPE_PREFERRED) { + edid_preferred = vgdev->edid_modes[i]; + break; + } + } + + /* Without a flagged preference the console leads with the largest EDID mode. */ + if (!edid_preferred) { + for (i = 0; i < vgdev->edid_mode_count; i++) + if (!edid_preferred || virtgpu_mode_area(vgdev->edid_modes[i]) > virtgpu_mode_area(edid_preferred)) edid_preferred = vgdev->edid_modes[i]; + } for (i = 0; i < vgdev->num_scanouts && i < VIRTGPU_MAX_SCANOUTS; i++) { struct virtio_gpu_display_mode *dm = &vgdev->scanouts[i]; struct drm_display_mode *mode; - if (!dm->enabled) continue; + if (dm->width <= 0 || dm->height <= 0 || dm->width > 8192 || dm->height > 8192) { + plogk("virtgpu: scanout %d: host reports invalid geometry %dx%d, skipped.\n", i, dm->width, dm->height); + continue; + } mode = drm_mode_create(dev); if (!mode) { DRM_ERROR("out of memory allocating display mode.\n"); return -ENOMEM; } + virtgpu_mode_fill(mode, (uint32_t)dm->width, (uint32_t)dm->height, (uint32_t)dm->vrefresh); - (void)snprintf(mode->name, DRM_DISPLAY_MODE_LEN - 1, "%dx%d", dm->width, dm->height); - mode->name[DRM_DISPLAY_MODE_LEN - 1] = '\0'; - mode->hdisplay = dm->width; - mode->hsync_start = dm->width + 80; - mode->hsync_end = dm->width + 160; - mode->htotal = dm->width + 320; - mode->vdisplay = dm->height; - mode->vsync_start = dm->height + 3; - mode->vsync_end = dm->height + 6; - mode->vtotal = dm->height + 32; - mode->vrefresh = dm->vrefresh ? dm->vrefresh : 60; - - /* Pixel clock includes blanking totals, so userspace derives 60 Hz. */ - mode->clock = (int)((uint64_t)mode->htotal * (uint64_t)mode->vtotal * (uint64_t)mode->vrefresh / 1000ULL); - mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC; - mode->type = DRM_MODE_TYPE_DRIVER; - mode->status = MODE_OK; - - if (i == 0) mode->type |= DRM_MODE_TYPE_PREFERRED; - + /* The host scanout leads only when the EDID offers no mode. */ + if (!edid_preferred && !host_preferred) { + mode->type |= DRM_MODE_TYPE_PREFERRED; + host_preferred = true; + } drm_mode_probed_add(connector, mode); } @@ -204,12 +294,15 @@ static int virtgpu_connector_get_modes(struct drm_connector *connector) DRM_ERROR("out of memory adding fallback mode.\n"); return -ENOMEM; } + + /* The synthesized scanout only keeps the connector present; the boot chain skips it. */ vgdev->num_scanouts = 1; - vgdev->scanouts[0].enabled = true; + vgdev->scanouts[0].enabled = false; vgdev->scanouts[0].width = CONFIG_DRM_DEFAULT_WIDTH; vgdev->scanouts[0].height = CONFIG_DRM_DEFAULT_HEIGHT; vgdev->scanouts[0].vrefresh = 60; } + if (edid_preferred) virtgpu_connector_set_preferred(connector, edid_preferred); vgdev->modes_probed = true; return 0; @@ -274,18 +367,21 @@ static int virtgpu_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_st /* Page flip helper for legacy ioctl */ static int virtgpu_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, uint32_t flags) { - struct drm_device *dev = crtc->dev; - struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; + struct drm_device *dev = crtc->dev; + struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; + struct drm_framebuffer *old_fb = crtc->primary->state->fb; int ret; (void)event; (void)flags; - ret = virtgpu_page_flip(vgdev, fb, crtc->primary->state->fb); + /* A flip must not silently change the mode the CRTC reports. */ + if (fb && crtc->mode.hdisplay && ((uint32_t)crtc->mode.hdisplay != fb->width || (uint32_t)crtc->mode.vdisplay != fb->height)) return -EINVAL; + + ret = virtgpu_page_flip(vgdev, fb, old_fb); if (ret) return ret; /* The committed plane state, not the ioctl lookup, owns scanout. */ - struct drm_framebuffer *old_fb = crtc->primary->state->fb; if (old_fb != fb) { drm_framebuffer_get(fb); crtc->primary->state->fb = fb; @@ -430,7 +526,7 @@ static int virtgpu_kms_initial_try_mode(struct virtio_gpu_device *vgdev, struct goto err_free_obj; } - /* A mode the device rejects (bad size, etc.) fails here -> next mode. */ + /* Host rejection of this mode fails here; the caller tries the next. */ ret = virtgpu_page_flip(vgdev, fb, NULL); if (ret) goto err_free_fb; @@ -438,12 +534,13 @@ static int virtgpu_kms_initial_try_mode(struct virtio_gpu_device *vgdev, struct *out_fb = fb; return 0; err_free_fb: - /* cleanup's drm_gem_object_put() frees obj when its refcount drops. */ + /* Unregister, then drop the registered reference; the final put releases the GEM object. */ drm_framebuffer_cleanup(fb); drm_framebuffer_put(fb); return ret; err_free_obj: - virtgpu_gem_free_object(&obj->base); + /* Put releases the mmap offset slot before the driver frees obj. */ + drm_gem_object_put(&obj->base); return ret; } @@ -504,39 +601,145 @@ static int virtgpu_kms_initial_commit(struct virtio_gpu_device *vgdev, struct dr return 0; } -/* Initial modeset: try modes preferred-first, else keep the bootloader framebuffer. Non-fatal on failure. */ -static int virtgpu_kms_initial_modeset(struct virtio_gpu_device *vgdev) +/* Pixel area of a mode, used to order the EDID fallbacks. */ +static uint64_t virtgpu_mode_area(const struct drm_display_mode *mode) +{ + return (uint64_t)(uint32_t)mode->hdisplay * (uint32_t)mode->vdisplay; +} + +/* Sort EDID modes from the largest pixel area down. */ +static void virtgpu_edid_sort(struct drm_display_mode **modes, int count) +{ + int i; + int j; + + for (i = 1; i < count; i++) { + struct drm_display_mode *key = modes[i]; + for (j = i - 1; j >= 0 && virtgpu_mode_area(modes[j]) < virtgpu_mode_area(key); j--) modes[j + 1] = modes[j]; + modes[j + 1] = key; + } +} + +/* The EDID preferred mode, else the largest one; @modes must be sorted. */ +static struct drm_display_mode *virtgpu_edid_preferred(struct virtio_gpu_device *vgdev) +{ + int i; + + for (i = 0; i < vgdev->edid_mode_count; i++) + if (vgdev->edid_modes[i]->type & DRM_MODE_TYPE_PREFERRED) return vgdev->edid_modes[i]; + return vgdev->edid_mode_count ? vgdev->edid_modes[0] : NULL; +} + +/* True when @width x @height was already attempted. */ +static bool virtgpu_geometry_tried(const uint32_t (*tried)[2], unsigned count, uint32_t width, uint32_t height) +{ + unsigned i; + + for (i = 0; i < count; i++) + if (tried[i][0] == width && tried[i][1] == height) return true; + return false; +} + +/* Try one mode for the console, remembering its geometry. */ +static int virtgpu_kms_try_mode(struct virtio_gpu_device *vgdev, struct drm_connector *connector, struct drm_display_mode *mode, uint32_t (*tried)[2], unsigned *tried_count, int *last_ret) { - struct drm_device *dev = vgdev->drm_dev; - struct drm_mode_config *config = &dev->mode_config; - struct drm_connector *conn = NULL; - struct drm_display_mode *mode; struct virtio_gpu_object *obj; struct drm_framebuffer *fb; - ilist_node_t *node; - int ret = -ENODEV; + uint32_t width = (uint32_t)mode->hdisplay; + uint32_t height = (uint32_t)mode->vdisplay; + int ret; + + if (!width || !height) return -EINVAL; + if (virtgpu_geometry_tried(tried, *tried_count, width, height)) return 1; + if (*tried_count < VIRTGPU_MAX_EDID_MODES + VIRTGPU_MAX_SCANOUTS + 2) { + tried[*tried_count][0] = width; + tried[*tried_count][1] = height; + (*tried_count)++; + } + + ret = virtgpu_kms_initial_try_mode(vgdev, mode, &obj, &fb); + if (ret) { + *last_ret = ret; + return ret; + } + + ret = virtgpu_kms_initial_commit(vgdev, connector, mode, obj, fb); + if (!ret) return 0; + + drm_framebuffer_cleanup(fb); + drm_framebuffer_put(fb); + *last_ret = ret; + return ret; +} + +/* Try a synthetic mode of the given geometry. */ +static int virtgpu_kms_try_geometry(struct virtio_gpu_device *vgdev, struct drm_connector *connector, uint32_t width, uint32_t height, uint32_t (*tried)[2], unsigned *tried_count, int *last_ret) +{ + struct drm_display_mode mode; + if (!width || !height || width > 8192 || height > 8192) return -EINVAL; + + virtgpu_mode_fill(&mode, width, height, 60); + return virtgpu_kms_try_mode(vgdev, connector, &mode, tried, tried_count, last_ret); +} + +/* Boot modeset in fallback order: EDID modes, boot geometry, configured default. */ +static int virtgpu_kms_initial_modeset(struct virtio_gpu_device *vgdev) +{ + struct drm_device *dev = vgdev->drm_dev; + struct drm_mode_config *config = &dev->mode_config; + struct drm_connector *conn = NULL; + struct drm_display_mode *preferred; + struct limine_framebuffer *boot; + uint32_t tried[VIRTGPU_MAX_EDID_MODES + VIRTGPU_MAX_SCANOUTS + 2][2]; + unsigned tried_count = 0; + ilist_node_t *node; + int last_ret = -ENODEV; + int i; /* Find the first connected connector */ for (node = config->connector_list.next; node != &config->connector_list; node = node->next) { - conn = container_of(node, struct drm_connector, head); - if (conn->status == connector_status_connected) break; + struct drm_connector *candidate = container_of(node, struct drm_connector, head); + if (candidate->status == connector_status_connected) { + conn = candidate; + break; + } } if (!conn) { DRM_INFO("No connected connector - skipping initial modeset.\n"); return -ENODEV; } - /* Try EDID preferred, then remaining EDID/driver modes, in list order. */ - for (node = conn->modes.next; node != &conn->modes; node = node->next) { - mode = container_of(node, struct drm_display_mode, head); - ret = virtgpu_kms_initial_try_mode(vgdev, mode, &obj, &fb); - if (!ret) return virtgpu_kms_initial_commit(vgdev, conn, mode, obj, fb); + /* The EDID preferred mode, then every other EDID mode, largest first. */ + virtgpu_edid_sort(vgdev->edid_modes, vgdev->edid_mode_count); + preferred = virtgpu_edid_preferred(vgdev); + + if (preferred && !virtgpu_kms_try_mode(vgdev, conn, preferred, tried, &tried_count, &last_ret)) return 0; + for (i = 0; i < vgdev->edid_mode_count; i++) { + if (vgdev->edid_modes[i] == preferred) continue; + if (!virtgpu_kms_try_mode(vgdev, conn, vgdev->edid_modes[i], tried, &tried_count, &last_ret)) return 0; } - /* No usable mode: clear the scanout so nothing half-configured is displayed. */ - virtgpu_cmd_set_scanout(vgdev, 0, NULL); - DRM_INFO("Initial modeset: no usable mode (%d)\n", ret); - return ret; + /* Host scanout modes are advertised by the display too; they precede the boot geometry. */ + for (i = 0; i < vgdev->num_scanouts && i < VIRTGPU_MAX_SCANOUTS; i++) { + struct virtio_gpu_display_mode *dm = &vgdev->scanouts[i]; + struct drm_display_mode mode; + + if (!dm->enabled || dm->width <= 0 || dm->height <= 0 || dm->width > 8192 || dm->height > 8192) continue; + virtgpu_mode_fill(&mode, (uint32_t)dm->width, (uint32_t)dm->height, (uint32_t)dm->vrefresh); + if (!virtgpu_kms_try_mode(vgdev, conn, &mode, tried, &tried_count, &last_ret)) return 0; + } + + /* The geometry the bootloader left on the host window. */ + boot = get_framebuffer(); + if (boot && boot->width && boot->height && boot->width <= 8192 && boot->height <= 8192) { + if (!virtgpu_kms_try_geometry(vgdev, conn, (uint32_t)boot->width, (uint32_t)boot->height, tried, &tried_count, &last_ret)) return 0; + } + + /* The configured console mode. */ + if (!virtgpu_kms_try_geometry(vgdev, conn, CONFIG_DRM_DEFAULT_WIDTH, CONFIG_DRM_DEFAULT_HEIGHT, tried, &tried_count, &last_ret)) return 0; + + DRM_WARN("initial modeset: no usable mode (%d)\n", last_ret); + return last_ret; } /* Set up the full KMS pipeline: plane, CRTC, encoder and connector. */ @@ -551,7 +754,11 @@ int virtgpu_kms_init(struct virtio_gpu_device *vgdev) /* Query display info from host */ ret = virtgpu_cmd_get_display_info(vgdev); - if (ret) plogk("virtgpu: Failed to get display info: %d (using defaults)\n", ret); + if (ret) { + /* An unknown scanout count lets get_modes() synthesize the fallback mode. */ + vgdev->num_scanouts = 0; + plogk("virtgpu: Failed to get display info: %d (using defaults)\n", ret); + } /* Primary plane */ @@ -563,7 +770,7 @@ int virtgpu_kms_init(struct virtio_gpu_device *vgdev) memset(primary, 0, sizeof(*primary)); vgdev->kms_primary = primary; - ret = drm_plane_init(dev, primary, 1, NULL, virtgpu_formats, sizeof(virtgpu_formats) / sizeof(virtgpu_formats[0]), NULL, DRM_PLANE_TYPE_PRIMARY, "virtgpu-primary"); + ret = drm_plane_init(dev, primary, 1, (void *)&virtgpu_plane_helpers, virtgpu_formats, sizeof(virtgpu_formats) / sizeof(virtgpu_formats[0]), NULL, DRM_PLANE_TYPE_PRIMARY, "virtgpu-primary"); if (ret) { DRM_ERROR("Failed to init primary plane: %d\n", ret); free(primary); @@ -694,12 +901,9 @@ int virtgpu_kms_init(struct virtio_gpu_device *vgdev) DRM_INFO("virtgpu: KMS pipeline up, %d mode(s)\n", mode_count); } - /* - * Perform an initial modeset so the display is live immediately. - * Failure is non-fatal - the KMS pipeline remains registered. - */ + /* The initial modeset runs before drm_dev_register() publishes the node. */ ret = virtgpu_kms_initial_modeset(vgdev); - if (ret) DRM_INFO("Initial modeset deferred (display will activate on first userspace commit)\n"); + if (ret) return ret; return 0; } @@ -707,7 +911,10 @@ int virtgpu_kms_init(struct virtio_gpu_device *vgdev) /* Disable the scanout when the KMS pipeline is torn down. */ void virtgpu_kms_fini(struct virtio_gpu_device *vgdev) { - virtgpu_cmd_set_scanout(vgdev, 0, NULL); + if (!vgdev) return; + + /* Disable only the scanout we set; a failed probe leaves the boot one alone. */ + if (vgdev->current_fb) (void)virtgpu_cmd_set_scanout(vgdev, 0, NULL); } #endif diff --git a/include/drivers/gpu/drm/virtio/virtgpu_drv.h b/include/drivers/gpu/drm/virtio/virtgpu_drv.h index 7c3f4a8f..34f0184f 100644 --- a/include/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/include/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -131,6 +131,7 @@ #define VIRTGPU_RESOURCE_ID_INVALID 0 #define VIRTGPU_MAX_CONTEXT_RINGS 64 +#define VIRTGPU_MAX_EDID_MODES 64 // EDID modes kept for the boot fallback chain #define VIRTGPU_DEBUG_NAME_MAX 64 /* Largest command batch submitted to the control queue in one kick. */ @@ -659,6 +660,10 @@ struct virtio_gpu_device { struct virtio_gpu_display_mode scanouts[16]; bool modes_probed; // EDID/scanout probed once + /* EDID modes of the last connector probe, for the boot fallback chain. */ + struct drm_display_mode *edid_modes[VIRTGPU_MAX_EDID_MODES]; + int edid_mode_count; + /* Resource ID allocator (monotonic) */ spinlock_t resource_idr_lock; uint32_t next_resource_id; From 9a69f7a58a23764689ecf05d51e8cbe8c5c628c0 Mon Sep 17 00:00:00 2001 From: MicroFish <2609948707@qq.com> Date: Tue, 3 Nov 2026 03:42:12 +0800 Subject: [PATCH 2/3] drm/virtio: keep the console scanout across client teardown Closing the last DRM client disabled the host scanout even though the kernel console still draws into that framebuffer, leaving the display blank until another modeset or flip put a scanout back. The close path now clears the client cursor and flips the console framebuffer back onto the scanout instead of disabling it. Pin the console framebuffer at the initial modeset so rmfb cannot free the buffer the console draws into, detach the console flush hooks when KMS is torn down, and let a console flush reclaim the scanout if a client flipped its own buffer onto it. A dirty update from the committed primary framebuffer also takes the scanout back, and the flip error paths report which step failed. Signed-off-by: MicroFish <2609948707@qq.com> --- drivers/gpu/drm/core/drm_drv.c | 5 +- drivers/gpu/drm/virtio/virtgpu.c | 48 ++++++++++-- drivers/gpu/drm/virtio/virtgpu_kms.c | 77 +++++++++++--------- include/drivers/gpu/drm/virtio/virtgpu_drv.h | 1 + 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/drivers/gpu/drm/core/drm_drv.c b/drivers/gpu/drm/core/drm_drv.c index d43052c2..736ddcc0 100644 --- a/drivers/gpu/drm/core/drm_drv.c +++ b/drivers/gpu/drm/core/drm_drv.c @@ -306,7 +306,10 @@ int drm_dev_register(struct drm_device *dev, uint64_t flags) return 0; } -/* drm_kms_console_handoff - publish a committed scanout framebuffer to the console */ +/* + * drm_kms_console_handoff - publish a committed scanout framebuffer to the console. + * TODO: the console reclaims the scanout on every flush; revisit once DRM master or VT switching exists. + */ void drm_kms_console_handoff(struct drm_device *dev, struct drm_framebuffer *fb) { struct drm_gem_object *obj; diff --git a/drivers/gpu/drm/virtio/virtgpu.c b/drivers/gpu/drm/virtio/virtgpu.c index 1a95130f..3b6bdfd0 100644 --- a/drivers/gpu/drm/virtio/virtgpu.c +++ b/drivers/gpu/drm/virtio/virtgpu.c @@ -265,13 +265,20 @@ static void virtgpu_postclose(struct drm_device *dev, struct drm_file *file) } } -/* Disable the scanout when the last client closes. */ +/* Keep the console scanout alive after the last client closes. */ static void virtgpu_lastclose(struct drm_device *dev) { struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; - /* Disable scanout on last close */ - virtgpu_cmd_set_scanout(vgdev, 0, NULL); + if (!vgdev) return; + if (vgdev->kms_crtc && vgdev->kms_crtc->cursor_obj) { + /* The host composites the cursor over the scanout; it leaves with the client that set it. */ + int ret = virtgpu_cmd_update_cursor(vgdev, (uint32_t)vgdev->kms_crtc->index, NULL, 0, 0, 0, 0); + if (ret) plogk("virtgpu: failed to clear the client cursor: %d\n", ret); + } + + /* Take the scanout back before the core removes the client's framebuffers. */ + if (vgdev->console_fb && vgdev->current_fb != vgdev->console_fb) (void)virtgpu_page_flip(vgdev, vgdev->console_fb, NULL); } /* Tear down the device and release all driver resources. */ @@ -333,11 +340,26 @@ static struct drm_driver virtgpu_drm_driver = { .dumb_destroy = drm_gem_dumb_destroy, }; +/* True when @fb is the committed primary framebuffer of an active CRTC. */ +static bool virtgpu_fb_is_scanout(struct virtio_gpu_device *vgdev, struct drm_framebuffer *fb) +{ + struct drm_device *dev = vgdev->drm_dev; + ilist_node_t *node; + + if (!dev) return false; + for (node = dev->mode_config.crtc_list.next; node != &dev->mode_config.crtc_list; node = node->next) { + struct drm_crtc *crtc = container_of(node, struct drm_crtc, head); + if (crtc->primary && crtc->primary->state && crtc->primary->state->fb == fb && crtc->state && crtc->state->active) return true; + } + return false; +} + /* KMS framebuffer damage */ static int virtgpu_dirty_fb(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned int flags, unsigned int color, struct drm_clip_rect *clips, unsigned int num_clips) { struct virtio_gpu_device *vgdev; struct virtio_gpu_object *obj; + int ret; struct virtio_gpu_rect rects[VIRTGPU_DIRTY_MAX_RECTS]; uint64_t offsets[VIRTGPU_DIRTY_MAX_RECTS]; struct virtio_gpu_rect flush_rect = {0}; @@ -356,7 +378,7 @@ static int virtgpu_dirty_fb(struct drm_framebuffer *fb, struct drm_file *file_pr * Off-screen buffers will be uploaded in full when they are flipped * onto the scanout, so avoid wasting host bandwidth here. */ - if (obj != vgdev->current_scanout_obj) return 0; + if (obj != vgdev->current_scanout_obj && !virtgpu_fb_is_scanout(vgdev, fb)) return 0; if (!virtgpu_2d_formats_compatible(obj->format, fb->format)) return -EINVAL; if (!num_clips) { rects[0] = (struct virtio_gpu_rect) {0, 0, fb->width, fb->height}; @@ -399,6 +421,12 @@ static int virtgpu_dirty_fb(struct drm_framebuffer *fb, struct drm_file *file_pr } } + /* A committed primary may have lost the scanout to the console; valid damage takes it back. */ + if (obj != vgdev->current_scanout_obj) { + ret = virtgpu_page_flip(vgdev, fb, NULL); + if (ret) return ret; + } + /* * Blob scanouts share guest backing directly with the host. They were * created with RESOURCE_CREATE_BLOB, so TRANSFER_TO_HOST_2D is invalid; @@ -1029,7 +1057,10 @@ int virtgpu_page_flip(struct virtio_gpu_device *vgdev, struct drm_framebuffer *f obj->format = fb->format; obj->stride = fb->pitches[0]; ret = virtgpu_cmd_set_scanout_blob(vgdev, scanout_id, obj); - if (ret) return ret; + if (ret) { + plogk("virtgpu: flip: blob update failed: %d\n", ret); + return ret; + } vgdev->current_scanout_obj = obj; vgdev->current_fb = fb; vgdev->current_scanout_width = fb->width; @@ -1051,7 +1082,7 @@ int virtgpu_page_flip(struct virtio_gpu_device *vgdev, struct drm_framebuffer *f || vgdev->current_scanout_offset != fb->offsets[0]; ret = virtgpu_cmd_update_scanout_2d(vgdev, scanout_id, obj, fb->width, fb->height, obj != vgdev->current_scanout_obj || old_fb == NULL || layout_changed); if (ret) { - DRM_ERROR("Flip: batched update failed: %d\n", ret); + plogk("virtgpu: flip: batched update failed: %d\n", ret); return ret; } @@ -1063,7 +1094,10 @@ int virtgpu_page_flip(struct virtio_gpu_device *vgdev, struct drm_framebuffer *f } else { /* Disable scanout */ ret = virtgpu_cmd_set_scanout(vgdev, scanout_id, NULL); - if (ret) return ret; + if (ret) { + plogk("virtgpu: flip: scanout disable failed: %d\n", ret); + return ret; + } vgdev->current_scanout_obj = NULL; vgdev->current_scanout_width = vgdev->current_scanout_height = 0; vgdev->current_scanout_stride = vgdev->current_scanout_offset = 0; diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c index 16753a50..c831479e 100644 --- a/drivers/gpu/drm/virtio/virtgpu_kms.c +++ b/drivers/gpu/drm/virtio/virtgpu_kms.c @@ -326,14 +326,14 @@ static void virtgpu_encoder_atomic_check(struct drm_encoder *encoder, struct drm (void)conn_state; } -/* On modeset, push the new framebuffer to the scanout. */ +/* On modeset, push the new framebuffer to the scanout; a NULL fb drops it. */ static int virtgpu_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_framebuffer *fb) { struct drm_device *dev = crtc->dev; struct virtio_gpu_device *vgdev = (struct virtio_gpu_device *)dev->dev_private; - if (!fb) return 0; - return virtgpu_page_flip(vgdev, fb, NULL); + /* Pass the live framebuffer so an unchanged layout skips the redundant SET_SCANOUT. */ + return virtgpu_page_flip(vgdev, fb, vgdev->current_fb); } /* Enable the CRTC output and deliver any pending vblank event. */ @@ -378,8 +378,11 @@ static int virtgpu_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer /* A flip must not silently change the mode the CRTC reports. */ if (fb && crtc->mode.hdisplay && ((uint32_t)crtc->mode.hdisplay != fb->width || (uint32_t)crtc->mode.vdisplay != fb->height)) return -EINVAL; - ret = virtgpu_page_flip(vgdev, fb, old_fb); - if (ret) return ret; + /* The console or another client may own the scanout; only the framebuffer actually on it may disable the output. */ + if (fb || !old_fb || vgdev->current_fb == old_fb) { + ret = virtgpu_page_flip(vgdev, fb, old_fb); + if (ret) return ret; + } /* The committed plane state, not the ioctl lookup, owns scanout. */ if (old_fb != fb) { @@ -392,29 +395,28 @@ static int virtgpu_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer return 0; } -/* - * Flush callback invoked by the video subsystem after fbcon draws. - * Transfers the guest-side framebuffer to the host GPU resource and - * flushes the scanout so the new pixels become visible. - */ +/* fbcon flush: send the damaged rect to the host and flush the scanout. */ static void virtgpu_kms_flush_fb(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - struct virtio_gpu_rect damage; - uint64_t offset; + struct virtio_gpu_device *vgdev = vgdev_flush_ctx; + struct virtio_gpu_rect damage; + uint64_t offset; - if (!vgdev_flush_ctx || !vgdev_flush_obj) return; + if (!vgdev || !vgdev_flush_obj) return; if (x >= vgdev_flush_obj->width || y >= vgdev_flush_obj->height || !width || !height) return; if (width > vgdev_flush_obj->width - x) width = vgdev_flush_obj->width - x; if (height > vgdev_flush_obj->height - y) height = vgdev_flush_obj->height - y; - /* - * printk is backed by this same scanout. A GPU error emitted while a - * flush is unwinding therefore re-enters this callback. Damage is - * best-effort (the refresh worker retries changed rows), so drop only the - * recursive/concurrent flush instead of deadlocking controlq. - */ + /* printk shares this scanout: drop recursive flushes instead of deadlocking. */ if (!raw_spin_trylock(&vgdev_flush_lock)) return; + /* A client may own the scanout; the next console flush puts it back. */ + if (vgdev->console_fb && vgdev->current_scanout_obj != vgdev_flush_obj) { + (void)virtgpu_page_flip(vgdev, vgdev->console_fb, NULL); + raw_spin_unlock(&vgdev_flush_lock); + return; + } + damage = (struct virtio_gpu_rect) {x, y, width, height}; offset = (uint64_t)y * vgdev_flush_obj->stride + (uint64_t)x * sizeof(uint32_t); { @@ -425,7 +427,7 @@ static void virtgpu_kms_flush_fb(uint32_t x, uint32_t y, uint32_t width, uint32_ if (!flush_fail_logged) { flush_fail_logged = true; - DRM_ERROR("Console flush failed: %d (rect %ux%u+%u+%u)\n", flush_ret, width, height, x, y); + plogk("virtgpu: console flush failed: %d (rect %ux%u+%u+%u)\n", flush_ret, width, height, x, y); } } } @@ -440,12 +442,7 @@ static int virtgpu_crtc_cursor_set(struct drm_crtc *crtc, struct drm_gem_object int ret; if (obj) { - /* - * The VirtIO cursor command requires a 64x64 resource but does not - * impose a DRM fourcc. Xorg allocates cursor storage through the - * dumb-buffer API, which creates XRGB8888 objects. Accept both - * advertised 32-bit KMS formats here. - */ + /* The host wants a 64x64 cursor; Xorg hands us XRGB8888 dumb buffers, accept both. */ if (width != 64 || height != 64 || gem->size < 64 * 64 * 4 || obj->width != 64 || obj->height != 64 || (obj->format != DRM_FORMAT_XRGB8888 && obj->format != DRM_FORMAT_ARGB8888) || obj->created_3d || obj->created_blob) return -EINVAL; @@ -586,13 +583,13 @@ static int virtgpu_kms_initial_commit(struct virtio_gpu_device *vgdev, struct dr primary->crtc_id = crtc ? crtc->base.id : 0; } - /* - * Attach the scanout flush hooks to the DRM device, then let the DRM - * core hand the kernel console over to this framebuffer. The driver - * only reports its scanout to DRM; all console/TTY work is core-owned. - */ - vgdev_flush_ctx = vgdev; - vgdev_flush_obj = obj; + /* Attach the console flush hooks and hand the console to the DRM core. */ + vgdev_flush_ctx = vgdev; + vgdev_flush_obj = obj; + + /* RMFB on the console framebuffer would free the buffer the console draws into. */ + drm_framebuffer_get(fb); + vgdev->console_fb = fb; dev->fb_console_flush = virtgpu_kms_flush_fb; dev->fb_console_flush_guard = virtgpu_display_flush_guard; drm_kms_console_handoff(dev, fb); @@ -908,10 +905,22 @@ int virtgpu_kms_init(struct virtio_gpu_device *vgdev) return 0; } -/* Disable the scanout when the KMS pipeline is torn down. */ +/* Detach the console and disable our scanout when the KMS pipeline is torn down. */ void virtgpu_kms_fini(struct virtio_gpu_device *vgdev) { if (!vgdev) return; + if (vgdev->drm_dev) { + vgdev->drm_dev->fb_console_flush = NULL; + vgdev->drm_dev->fb_console_flush_guard = NULL; + } + + vgdev_flush_ctx = NULL; + vgdev_flush_obj = NULL; + + if (vgdev->console_fb) { + drm_framebuffer_put(vgdev->console_fb); + vgdev->console_fb = NULL; + } /* Disable only the scanout we set; a failed probe leaves the boot one alone. */ if (vgdev->current_fb) (void)virtgpu_cmd_set_scanout(vgdev, 0, NULL); diff --git a/include/drivers/gpu/drm/virtio/virtgpu_drv.h b/include/drivers/gpu/drm/virtio/virtgpu_drv.h index 34f0184f..6756baba 100644 --- a/include/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/include/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -677,6 +677,7 @@ struct virtio_gpu_device { /* Current framebuffer for scanout */ struct drm_framebuffer *current_fb; + struct drm_framebuffer *console_fb; // console scanout, pinned until teardown struct virtio_gpu_object *current_scanout_obj; uint32_t current_scanout_width; uint32_t current_scanout_height; From a5eacf9db0f7206b8b6f1d7c42cacf8daa6eea44 Mon Sep 17 00:00:00 2001 From: MicroFish <2609948707@qq.com> Date: Tue, 3 Nov 2026 03:42:12 +0800 Subject: [PATCH 3/3] drm/virtio: avoid sleeping command waits without tick or IRQ The control and cursor wait loops took the wait-queue path whenever the scheduler was up, even when interrupts were disabled or the completion IRQ was unavailable. The timed wait then has no timer tick or completion IRQ left to wake it, so the caller can sleep with no wakeup event instead of polling the queue. Only defer to the wait queue when the timer tick and the completion IRQ can both run, and only yield between polls in that state; otherwise keep polling to the deadline. Each log reason also declared a static ratelimit state with the same name in the same switch scope, which is a redefinition. Give every reason its own state so the file builds with the driver enabled and each failure keeps its own log budget. Signed-off-by: MicroFish <2609948707@qq.com> --- drivers/gpu/drm/virtio/virtgpu_vq.c | 44 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index 1d0af631..bc28acce 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -8,6 +8,7 @@ * */ +#include #include #include #include @@ -251,6 +252,8 @@ int virtgpu_ctrl_cmd_batch(struct virtio_gpu_device *vgdev, struct virtgpu_vq_co uint32_t completed = 0; uint32_t timeout = 0; uint32_t fast_polls = 0; + bool can_yield; + bool defer_waits; uint32_t len; uint32_t log_index = 0; uint32_t log_type = 0; @@ -350,13 +353,18 @@ int virtgpu_ctrl_cmd_batch(struct virtio_gpu_device *vgdev, struct virtgpu_vq_co /* One doorbell covers every avail entry published above. */ virtqueue_kick(vq); uint64_t overall_deadline = __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE) ? sched_ticks() + VIRTGPU_QUEUE_TIMEOUT_TICKS : 0; + + /* A sleeping wait needs the timer tick and the completion IRQ; without them only polling can finish. */ + can_yield = __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE) && (get_rflags() & X86_EFLAGS_IF); + defer_waits = vgdev->irq_enabled && can_yield; + while (completed < submitted) { if (virtqueue_get_buf(vq, &len)) { completed++; timeout = 0; continue; } - if (vgdev->irq_enabled && __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE)) { + if (defer_waits) { if (fast_polls++ < VIRTGPU_FAST_POLL_COUNT) { cpu_relax(); compiler_barrier(); @@ -402,7 +410,7 @@ int virtgpu_ctrl_cmd_batch(struct virtio_gpu_device *vgdev, struct virtgpu_vq_co ret = -EIO; goto out_unlock; } - if ((timeout & 0x3fffU) == 0 && __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE)) { + if ((timeout & 0x3fffU) == 0 && can_yield) { sched_yield(); } else { cpu_relax(); @@ -464,26 +472,27 @@ int virtgpu_ctrl_cmd_batch(struct virtio_gpu_device *vgdev, struct virtgpu_vq_co * print while owning the command gate: doing so recursively submits another * control command and deadlocks the task that must release this gate. */ + static DEFINE_RATELIMIT_STATE(ratelimit_no_desc, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + static DEFINE_RATELIMIT_STATE(ratelimit_queue_add, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + static DEFINE_RATELIMIT_STATE(ratelimit_timeout, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + static DEFINE_RATELIMIT_STATE(ratelimit_bad_response, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + static DEFINE_RATELIMIT_STATE(ratelimit_bad_fence, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + switch (log_reason) { case CTRL_LOG_NO_DESCRIPTORS : - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("virtgpu: Ctrl_cmd_batch: not enough free descriptors (count=%u, num_free=%u)\n", count, vq->num_free); + if (ratelimit_allow(&ratelimit_no_desc)) plogk("virtgpu: Ctrl_cmd_batch: not enough free descriptors (count=%u, num_free=%u)\n", count, vq->num_free); break; case CTRL_LOG_QUEUE_ADD : - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("virtgpu: Ctrl_cmd_batch: queue add failed (index=%u, err=%d)\n", log_index, log_error); + if (ratelimit_allow(&ratelimit_queue_add)) plogk("virtgpu: Ctrl_cmd_batch: queue add failed (index=%u, err=%d)\n", log_index, log_error); break; case CTRL_LOG_TIMEOUT : - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("virtgpu: Timed out waiting for GPU command batch (%u/%u complete)\n", completed, submitted); + if (ratelimit_allow(&ratelimit_timeout)) plogk("virtgpu: Timed out waiting for GPU command batch (%u/%u complete)\n", completed, submitted); break; case CTRL_LOG_BAD_RESPONSE : - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("virtgpu: GPU command 0x%04x returned 0x%04x, expected 0x%04x\n", log_type, log_reply, log_expected); + if (ratelimit_allow(&ratelimit_bad_response)) plogk("virtgpu: GPU command 0x%04x returned 0x%04x, expected 0x%04x\n", log_type, log_reply, log_expected); break; case CTRL_LOG_BAD_FENCE : - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("virtgpu: GPU command 0x%04x returned an invalid fence response.\n", log_type); + if (ratelimit_allow(&ratelimit_bad_fence)) plogk("virtgpu: GPU command 0x%04x returned an invalid fence response.\n", log_type); break; default : break; @@ -535,6 +544,8 @@ int virtgpu_cursor_cmd(struct virtio_gpu_device *vgdev, void *cmd, int cmd_size) uint64_t dma_phys; void *dma_cmd; bool pooled; + bool can_yield; + bool defer_waits; if (!vgdev || !cmd || cmd_size < (int)sizeof(struct virtio_gpu_ctrl_hdr)) return -EINVAL; @@ -559,10 +570,15 @@ int virtgpu_cursor_cmd(struct virtio_gpu_device *vgdev, void *cmd, int cmd_size) uint32_t len; virtqueue_kick(&vgdev->cursorq); uint64_t overall_deadline = __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE) ? sched_ticks() + VIRTGPU_QUEUE_TIMEOUT_TICKS : 0; + + /* A sleeping wait needs the timer tick and the completion IRQ; without them only polling can finish. */ + can_yield = __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE) && (get_rflags() & X86_EFLAGS_IF); + defer_waits = vgdev->irq_enabled && can_yield; + for (;;) { void *completed = virtqueue_get_buf(&vgdev->cursorq, &len); if (completed == dma_cmd) break; - if (vgdev->irq_enabled && __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE)) { + if (defer_waits) { if (fast_polls++ < VIRTGPU_FAST_POLL_COUNT) { cpu_relax(); compiler_barrier(); @@ -593,7 +609,7 @@ int virtgpu_cursor_cmd(struct virtio_gpu_device *vgdev, void *cmd, int cmd_size) ret = -EIO; break; } - if ((timeout & 0x3fffU) == 0 && __atomic_load_n(&scheduler.started, __ATOMIC_ACQUIRE)) { + if ((timeout & 0x3fffU) == 0 && can_yield) { sched_yield(); } else { cpu_relax();