On Wed, Apr 3, 2024 at 2:28 AM Zack Rusin <zack.rusin@xxxxxxxxxxxx> wrote: > > crc checksums are used to validate the output. Normally they're part > of the actual display hardware but on virtual stack there's nothing > to automatically generate them. > > Implement crc generation for the vmwgfx stack. This works only on > screen targets, where it's possibly to easily make sure that the > guest side contents of the surface matches the host sides output. > > Just like the vblank support, crc generation can only be enabled via: > guestinfo.vmwgfx.vkms_enable = "TRUE" > option in the vmx file. > > Makes IGT's kms_pipe_crc_basic pass and allows a huge number of other > IGT tests which require CRC generation of the output to actually run > on vmwgfx. Makes it possible to actually validate a lof of the kms and > drm functionality with vmwgfx. > > Signed-off-by: Zack Rusin <zack.rusin@xxxxxxxxxxxx> > --- > drivers/gpu/drm/vmwgfx/vmwgfx_drv.c | 1 + > drivers/gpu/drm/vmwgfx/vmwgfx_drv.h | 2 + > drivers/gpu/drm/vmwgfx/vmwgfx_kms.c | 31 +- > drivers/gpu/drm/vmwgfx/vmwgfx_kms.h | 15 +- > drivers/gpu/drm/vmwgfx/vmwgfx_resource.c | 32 +- > drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c | 22 +- > drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c | 453 ++++++++++++++++++++++- > drivers/gpu/drm/vmwgfx/vmwgfx_vkms.h | 28 +- > 8 files changed, 550 insertions(+), 34 deletions(-) > > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c > index e34c48fd25d4..89d3679d2608 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c > @@ -1198,6 +1198,7 @@ static void vmw_driver_unload(struct drm_device *dev) > > vmw_svga_disable(dev_priv); > > + vmw_vkms_cleanup(dev_priv); > vmw_kms_close(dev_priv); > vmw_overlay_close(dev_priv); > > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h > index 4f5d7d13c4aa..ddbceaa31b59 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h > @@ -616,6 +616,7 @@ struct vmw_private { > uint32 *devcaps; > > bool vkms_enabled; > + struct workqueue_struct *crc_workq; > > /* > * mksGuestStat instance-descriptor and pid arrays > @@ -811,6 +812,7 @@ void vmw_resource_mob_attach(struct vmw_resource *res); > void vmw_resource_mob_detach(struct vmw_resource *res); > void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t start, > pgoff_t end); > +int vmw_resource_clean(struct vmw_resource *res); > int vmw_resources_clean(struct vmw_bo *vbo, pgoff_t start, > pgoff_t end, pgoff_t *num_prefault); > > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c > index e763cf0e6cfc..e33e5993d8fc 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c > @@ -40,14 +40,14 @@ > > void vmw_du_init(struct vmw_display_unit *du) > { > - hrtimer_init(&du->vkms.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); > - du->vkms.timer.function = &vmw_vkms_vblank_simulate; > + vmw_vkms_crtc_init(&du->crtc); > } > > void vmw_du_cleanup(struct vmw_display_unit *du) > { > struct vmw_private *dev_priv = vmw_priv(du->primary.dev); > - hrtimer_cancel(&du->vkms.timer); > + > + vmw_vkms_crtc_cleanup(&du->crtc); > drm_plane_cleanup(&du->primary); > if (vmw_cmd_supported(dev_priv)) > drm_plane_cleanup(&du->cursor.base); > @@ -963,6 +963,7 @@ int vmw_du_crtc_atomic_check(struct drm_crtc *crtc, > void vmw_du_crtc_atomic_begin(struct drm_crtc *crtc, > struct drm_atomic_state *state) > { > + vmw_vkms_crtc_atomic_begin(crtc, state); > } > > /** > @@ -2029,6 +2030,29 @@ vmw_kms_create_hotplug_mode_update_property(struct vmw_private *dev_priv) > "hotplug_mode_update", 0, 1); > } > > +static void > +vmw_atomic_commit_tail(struct drm_atomic_state *old_state) > +{ > + struct vmw_private *vmw = vmw_priv(old_state->dev); > + struct drm_crtc *crtc; > + struct drm_crtc_state *old_crtc_state; > + int i; > + > + drm_atomic_helper_commit_tail(old_state); > + > + if (vmw->vkms_enabled) { > + for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) { > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + (void)old_crtc_state; > + flush_work(&du->vkms.crc_generator_work); > + } > + } > +} > + > +static const struct drm_mode_config_helper_funcs vmw_mode_config_helpers = { > + .atomic_commit_tail = vmw_atomic_commit_tail, > +}; > + > int vmw_kms_init(struct vmw_private *dev_priv) > { > struct drm_device *dev = &dev_priv->drm; > @@ -2048,6 +2072,7 @@ int vmw_kms_init(struct vmw_private *dev_priv) > dev->mode_config.max_width = dev_priv->texture_max_width; > dev->mode_config.max_height = dev_priv->texture_max_height; > dev->mode_config.preferred_depth = dev_priv->assume_16bpp ? 16 : 32; > + dev->mode_config.helper_private = &vmw_mode_config_helpers; > > drm_mode_create_suggested_offset_properties(dev); > vmw_kms_create_hotplug_mode_update_property(dev_priv); > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h > index 9e83a1553286..bf9931e3a728 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h > @@ -378,9 +378,22 @@ struct vmw_display_unit { > int set_gui_y; > > struct { > + struct work_struct crc_generator_work; > struct hrtimer timer; > ktime_t period_ns; > - struct drm_pending_vblank_event *event; > + > + /* protects concurrent access to the vblank handler */ > + atomic_t atomic_lock; > + /* protected by @atomic_lock */ > + bool crc_enabled; > + struct vmw_surface *surface; > + > + /* protects concurrent access to the crc worker */ > + spinlock_t crc_state_lock; > + /* protected by @crc_state_lock */ > + bool crc_pending; > + u64 frame_start; > + u64 frame_end; > } vkms; > }; > > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c > index ca300c7427d2..848dba09981b 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_resource.c > @@ -1064,6 +1064,22 @@ void vmw_resource_dirty_update(struct vmw_resource *res, pgoff_t start, > end << PAGE_SHIFT); > } > > +int vmw_resource_clean(struct vmw_resource *res) > +{ > + int ret = 0; > + > + if (res->res_dirty) { > + if (!res->func->clean) > + return -EINVAL; > + > + ret = res->func->clean(res); > + if (ret) > + return ret; > + res->res_dirty = false; > + } > + return ret; > +} > + > /** > * vmw_resources_clean - Clean resources intersecting a mob range > * @vbo: The mob buffer object > @@ -1080,6 +1096,7 @@ int vmw_resources_clean(struct vmw_bo *vbo, pgoff_t start, > unsigned long res_start = start << PAGE_SHIFT; > unsigned long res_end = end << PAGE_SHIFT; > unsigned long last_cleaned = 0; > + int ret; > > /* > * Find the resource with lowest backup_offset that intersects the > @@ -1106,18 +1123,9 @@ int vmw_resources_clean(struct vmw_bo *vbo, pgoff_t start, > * intersecting the range. > */ > while (found) { > - if (found->res_dirty) { > - int ret; > - > - if (!found->func->clean) > - return -EINVAL; > - > - ret = found->func->clean(found); > - if (ret) > - return ret; > - > - found->res_dirty = false; > - } > + ret = vmw_resource_clean(found); > + if (ret) > + return ret; > last_cleaned = found->guest_memory_offset + found->guest_memory_size; > cur = rb_next(&found->mob_node); > if (!cur) > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c b/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c > index 665bde7e0be0..2041c4d48daa 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_stdu.c > @@ -409,11 +409,6 @@ static void vmw_stdu_crtc_mode_set_nofb(struct drm_crtc *crtc) > crtc->x, crtc->y); > } > > - > -static void vmw_stdu_crtc_helper_prepare(struct drm_crtc *crtc) > -{ > -} > - > static void vmw_stdu_crtc_atomic_disable(struct drm_crtc *crtc, > struct drm_atomic_state *state) > { > @@ -783,6 +778,9 @@ static const struct drm_crtc_funcs vmw_stdu_crtc_funcs = { > .enable_vblank = vmw_vkms_enable_vblank, > .disable_vblank = vmw_vkms_disable_vblank, > .get_vblank_timestamp = vmw_vkms_get_vblank_timestamp, > + .get_crc_sources = vmw_vkms_get_crc_sources, > + .set_crc_source = vmw_vkms_set_crc_source, > + .verify_crc_source = vmw_vkms_verify_crc_source, > }; > > > @@ -1414,6 +1412,17 @@ vmw_stdu_primary_plane_atomic_update(struct drm_plane *plane, > vmw_fence_obj_unreference(&fence); > } > > +static void > +vmw_stdu_crtc_atomic_flush(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct vmw_private *vmw = vmw_priv(crtc->dev); > + struct vmw_screen_target_display_unit *stdu = vmw_crtc_to_stdu(crtc); > + > + if (vmw->vkms_enabled) > + vmw_vkms_set_crc_surface(crtc, stdu->display_srf); > + vmw_vkms_crtc_atomic_flush(crtc, state); > +} > > static const struct drm_plane_funcs vmw_stdu_plane_funcs = { > .update_plane = drm_atomic_helper_update_plane, > @@ -1454,11 +1463,10 @@ drm_plane_helper_funcs vmw_stdu_primary_plane_helper_funcs = { > }; > > static const struct drm_crtc_helper_funcs vmw_stdu_crtc_helper_funcs = { > - .prepare = vmw_stdu_crtc_helper_prepare, > .mode_set_nofb = vmw_stdu_crtc_mode_set_nofb, > .atomic_check = vmw_du_crtc_atomic_check, > .atomic_begin = vmw_du_crtc_atomic_begin, > - .atomic_flush = vmw_vkms_crtc_atomic_flush, > + .atomic_flush = vmw_stdu_crtc_atomic_flush, > .atomic_enable = vmw_vkms_crtc_atomic_enable, > .atomic_disable = vmw_stdu_crtc_atomic_disable, > }; > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c > index ff76bfd70f91..5138a7107897 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c > @@ -28,33 +28,179 @@ > > #include "vmwgfx_vkms.h" > > +#include "vmwgfx_bo.h" > #include "vmwgfx_drv.h" > #include "vmwgfx_kms.h" > #include "vmwgfx_vkms.h" > > +#include "vmw_surface_cache.h" > + > #include <drm/drm_crtc.h> > +#include <drm/drm_debugfs_crc.h> > #include <drm/drm_print.h> > #include <drm/drm_vblank.h> > > +#include <linux/crc32.h> > +#include <linux/delay.h> > + > #define GUESTINFO_VBLANK "guestinfo.vmwgfx.vkms_enable" > > -enum hrtimer_restart > +static int > +vmw_surface_sync(struct vmw_private *vmw, > + struct vmw_surface *surf) > +{ > + int ret; > + struct vmw_fence_obj *fence = NULL; > + struct vmw_bo *bo = surf->res.guest_memory_bo; > + > + vmw_resource_clean(&surf->res); > + > + ret = ttm_bo_reserve(&bo->tbo, false, false, NULL); > + if (ret != 0) { > + drm_warn(&vmw->drm, "%s: failed reserve\n", __func__); > + goto done; > + } > + > + ret = vmw_execbuf_fence_commands(NULL, vmw, &fence, NULL); > + if (ret != 0) { > + drm_warn(&vmw->drm, "%s: failed execbuf\n", __func__); > + ttm_bo_unreserve(&bo->tbo); > + goto done; > + } > + > + dma_fence_wait(&fence->base, false); > + dma_fence_put(&fence->base); > + > + ttm_bo_unreserve(&bo->tbo); > +done: > + return ret; > +} > + > +static int > +compute_crc(struct drm_crtc *crtc, > + struct vmw_surface *surf, > + u32 *crc) > +{ > + u8 *mapped_surface; > + struct vmw_bo *bo = surf->res.guest_memory_bo; > + const struct SVGA3dSurfaceDesc *desc = > + vmw_surface_get_desc(surf->metadata.format); > + u32 row_pitch_bytes; > + SVGA3dSize blocks; > + u32 y; > + > + *crc = 0; > + > + vmw_surface_get_size_in_blocks(desc, &surf->metadata.base_size, &blocks); > + row_pitch_bytes = blocks.width * desc->pitchBytesPerBlock; > + WARN_ON(!bo); > + mapped_surface = vmw_bo_map_and_cache(bo); > + > + for (y = 0; y < blocks.height; y++) { > + *crc = crc32_le(*crc, mapped_surface, row_pitch_bytes); > + mapped_surface += row_pitch_bytes; > + } > + > + vmw_bo_unmap(bo); > + > + return 0; > +} > + > +static void > +crc_generate_worker(struct work_struct *work) > +{ > + struct vmw_display_unit *du = > + container_of(work, struct vmw_display_unit, vkms.crc_generator_work); > + struct drm_crtc *crtc = &du->crtc; > + struct vmw_private *vmw = vmw_priv(crtc->dev); > + bool crc_pending; > + u64 frame_start, frame_end; > + u32 crc32 = 0; > + struct vmw_surface *surf = 0; > + int ret; > + > + spin_lock_irq(&du->vkms.crc_state_lock); > + crc_pending = du->vkms.crc_pending; > + spin_unlock_irq(&du->vkms.crc_state_lock); > + > + /* > + * We raced with the vblank hrtimer and previous work already computed > + * the crc, nothing to do. > + */ > + if (!crc_pending) > + return; > + > + spin_lock_irq(&du->vkms.crc_state_lock); > + surf = du->vkms.surface; > + spin_unlock_irq(&du->vkms.crc_state_lock); > + > + if (vmw_surface_sync(vmw, surf)) { > + drm_warn(crtc->dev, "CRC worker wasn't able to sync the crc surface!\n"); > + return; > + } > + > + ret = compute_crc(crtc, surf, &crc32); > + if (ret) > + return; > + > + spin_lock_irq(&du->vkms.crc_state_lock); > + frame_start = du->vkms.frame_start; > + frame_end = du->vkms.frame_end; > + crc_pending = du->vkms.crc_pending; > + du->vkms.frame_start = 0; > + du->vkms.frame_end = 0; > + du->vkms.crc_pending = false; > + spin_unlock_irq(&du->vkms.crc_state_lock); > + > + /* > + * The worker can fall behind the vblank hrtimer, make sure we catch up. > + */ > + while (frame_start <= frame_end) > + drm_crtc_add_crc_entry(crtc, true, frame_start++, &crc32); > +} > + > +static enum hrtimer_restart > vmw_vkms_vblank_simulate(struct hrtimer *timer) > { > struct vmw_display_unit *du = container_of(timer, struct vmw_display_unit, vkms.timer); > struct drm_crtc *crtc = &du->crtc; > + struct vmw_private *vmw = vmw_priv(crtc->dev); > + struct vmw_surface *surf = NULL; > u64 ret_overrun; > - bool ret; > + bool locked, ret; > > ret_overrun = hrtimer_forward_now(&du->vkms.timer, > du->vkms.period_ns); > if (ret_overrun != 1) > - DRM_WARN("%s: vblank timer overrun\n", __func__); > + drm_dbg_driver(crtc->dev, "vblank timer missed %lld frames.\n", > + ret_overrun - 1); > > + locked = vmw_vkms_vblank_trylock(crtc); > ret = drm_crtc_handle_vblank(crtc); > - /* Don't queue timer again when vblank is disabled. */ > - if (!ret) > - return HRTIMER_NORESTART; > + WARN_ON(!ret); > + if (!locked) > + return HRTIMER_RESTART; > + surf = du->vkms.surface; > + vmw_vkms_unlock(crtc); > + > + if (du->vkms.crc_enabled && surf) { > + u64 frame = drm_crtc_accurate_vblank_count(crtc); > + > + spin_lock(&du->vkms.crc_state_lock); > + if (!du->vkms.crc_pending) > + du->vkms.frame_start = frame; > + else > + drm_dbg_driver(crtc->dev, > + "crc worker falling behind, frame_start: %llu, frame_end: %llu\n", > + du->vkms.frame_start, frame); > + du->vkms.frame_end = frame; > + du->vkms.crc_pending = true; > + spin_unlock(&du->vkms.crc_state_lock); > + > + ret = queue_work(vmw->crc_workq, &du->vkms.crc_generator_work); > + if (!ret) > + drm_dbg_driver(crtc->dev, "Composer worker already queued\n"); > + } > > return HRTIMER_RESTART; > } > @@ -78,8 +224,21 @@ vmw_vkms_init(struct vmw_private *vmw) > if (!ret && vmw->vkms_enabled) { > ret = drm_vblank_init(&vmw->drm, VMWGFX_NUM_DISPLAY_UNITS); > vmw->vkms_enabled = (ret == 0); > - drm_info(&vmw->drm, "vkms_enabled = %d\n", vmw->vkms_enabled); > } > + > + vmw->crc_workq = alloc_ordered_workqueue("vmwgfx_crc_generator", 0); > + if (!vmw->crc_workq) { > + drm_warn(&vmw->drm, "crc workqueue allocation failed. Disabling vkms."); > + vmw->vkms_enabled = false; > + } > + if (vmw->vkms_enabled) > + drm_info(&vmw->drm, "VKMS enabled\n"); > +} > + > +void > +vmw_vkms_cleanup(struct vmw_private *vmw) > +{ > + destroy_workqueue(vmw->crc_workq); > } > > bool > @@ -133,6 +292,8 @@ vmw_vkms_enable_vblank(struct drm_crtc *crtc) > > drm_calc_timestamping_constants(crtc, &crtc->mode); > > + hrtimer_init(&du->vkms.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); > + du->vkms.timer.function = &vmw_vkms_vblank_simulate; > du->vkms.period_ns = ktime_set(0, vblank->framedur_ns); > hrtimer_start(&du->vkms.timer, du->vkms.period_ns, HRTIMER_MODE_REL); > > @@ -148,7 +309,46 @@ vmw_vkms_disable_vblank(struct drm_crtc *crtc) > if (!vmw->vkms_enabled) > return; > > - hrtimer_try_to_cancel(&du->vkms.timer); > + hrtimer_cancel(&du->vkms.timer); > + du->vkms.surface = NULL; > + du->vkms.period_ns = ktime_set(0, 0); > +} > + > +enum vmw_vkms_lock_state { > + VMW_VKMS_LOCK_UNLOCKED = 0, > + VMW_VKMS_LOCK_MODESET = 1, > + VMW_VKMS_LOCK_VBLANK = 2 > +}; > + > +void > +vmw_vkms_crtc_init(struct drm_crtc *crtc) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + > + atomic_set(&du->vkms.atomic_lock, VMW_VKMS_LOCK_UNLOCKED); > + spin_lock_init(&du->vkms.crc_state_lock); > + > + INIT_WORK(&du->vkms.crc_generator_work, crc_generate_worker); > + du->vkms.surface = NULL; > +} > + > +void > +vmw_vkms_crtc_cleanup(struct drm_crtc *crtc) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + > + WARN_ON(work_pending(&du->vkms.crc_generator_work)); > + hrtimer_cancel(&du->vkms.timer); > +} > + > +void > +vmw_vkms_crtc_atomic_begin(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct vmw_private *vmw = vmw_priv(crtc->dev); > + > + if (vmw->vkms_enabled) > + vmw_vkms_modeset_lock(crtc); > } > > void > @@ -170,6 +370,9 @@ vmw_vkms_crtc_atomic_flush(struct drm_crtc *crtc, > > crtc->state->event = NULL; > } > + > + if (vmw->vkms_enabled) > + vmw_vkms_unlock(crtc); > } An efficiency/codegen-related nitpick that spans two of the changes in this set: The predicate here -- vmw->vmks_enabled, is also used in the previous if-then statement as: if (vmw->vkms_enabled && crtc->state->event) { ... } if (vmw->vkms_enabled) vmw_vkms_unlock(crtc); Basically, all work in vmw_vkms_crtc_atomic_flush() is conditioned on that particular predicate, so we can just as well early-out on vkms_enabled low. Alternatively, if early-out is to be avoided, we can fetch the predicate just once, as we don't expect it to change for the duration of the function. > > void > @@ -191,3 +394,237 @@ vmw_vkms_crtc_atomic_disable(struct drm_crtc *crtc, > if (vmw->vkms_enabled) > drm_crtc_vblank_off(crtc); > } > + > +static bool > +is_crc_supported(struct drm_crtc *crtc) > +{ > + struct vmw_private *vmw = vmw_priv(crtc->dev); > + > + if (!vmw->vkms_enabled) > + return false; > + > + if (vmw->active_display_unit != vmw_du_screen_target) > + return false; > + > + return true; > +} > + > +static const char * const pipe_crc_sources[] = {"auto"}; > + > +static int > +crc_parse_source(const char *src_name, > + bool *enabled) > +{ > + int ret = 0; > + > + if (!src_name) { > + *enabled = false; > + } else if (strcmp(src_name, "auto") == 0) { > + *enabled = true; > + } else { > + *enabled = false; > + ret = -EINVAL; > + } > + > + return ret; > +} > + > +const char *const * > +vmw_vkms_get_crc_sources(struct drm_crtc *crtc, > + size_t *count) > +{ > + *count = 0; > + if (!is_crc_supported(crtc)) > + return NULL; > + > + *count = ARRAY_SIZE(pipe_crc_sources); > + return pipe_crc_sources; > +} > + > +int > +vmw_vkms_verify_crc_source(struct drm_crtc *crtc, > + const char *src_name, > + size_t *values_cnt) > +{ > + bool enabled; > + > + if (!is_crc_supported(crtc)) > + return -EINVAL; > + > + if (crc_parse_source(src_name, &enabled) < 0) { > + drm_dbg_driver(crtc->dev, "unknown source '%s'\n", src_name); > + return -EINVAL; > + } > + > + *values_cnt = 1; > + > + return 0; > +} > + > +int > +vmw_vkms_set_crc_source(struct drm_crtc *crtc, > + const char *src_name) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + bool enabled, prev_enabled, locked; > + int ret; > + > + if (!is_crc_supported(crtc)) > + return -EINVAL; > + > + ret = crc_parse_source(src_name, &enabled); > + > + if (enabled) > + drm_crtc_vblank_get(crtc); > + > + locked = vmw_vkms_modeset_lock_relaxed(crtc); > + prev_enabled = du->vkms.crc_enabled; > + du->vkms.crc_enabled = enabled; > + if (locked) > + vmw_vkms_unlock(crtc); > + > + if (prev_enabled) > + drm_crtc_vblank_put(crtc); > + > + return ret; > +} > + > +void > +vmw_vkms_set_crc_surface(struct drm_crtc *crtc, > + struct vmw_surface *surf) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + struct vmw_private *vmw = vmw_priv(crtc->dev); > + > + if (vmw->vkms_enabled) { > + WARN_ON(atomic_read(&du->vkms.atomic_lock) != VMW_VKMS_LOCK_MODESET); > + du->vkms.surface = surf; > + } > +} > + > +/** > + * vmw_vkms_lock_max_delay - Return the max wait for the vkms lock > + * @du: The vmw_display_unit from which to grab the vblank timings > + * > + * Returns the maximum wait time used to acquire the vkms lock. By > + * default uses a time of a single frame and in case where vblank > + * was not initialized for the display unit 1/60th of a second. > + */ > +static inline u64 > +vmw_vkms_lock_max_wait_ns(struct vmw_display_unit *du) > +{ > + s64 nsecs = ktime_to_ns(du->vkms.period_ns); > + > + return (nsecs > 0) ? nsecs : 16666666; > +} > + > +/** > + * vmw_vkms_modeset_lock - Protects access to crtc during modeset > + * @crtc: The crtc to lock for vkms > + * > + * This function prevents the VKMS timers/callbacks from being called > + * while a modeset operation is in process. We don't want the callbacks > + * e.g. the vblank simulator to be trying to access incomplete state > + * so we need to make sure they execute only when the modeset has > + * finished. > + * > + * Normally this would have been done with a spinlock but locking the > + * entire atomic modeset with vmwgfx is impossible because kms prepare > + * executes non-atomic ops (e.g. vmw_validation_prepare holds a mutex to > + * guard various bits of state). Which means that we need to synchronize > + * atomic context (the vblank handler) with the non-atomic entirity > + * of kms - so use an atomic_t to track which part of vkms has access > + * to the basic vkms state. > + */ > +void > +vmw_vkms_modeset_lock(struct drm_crtc *crtc) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + const u64 nsecs_delay = 10; > + const u64 MAX_NSECS_DELAY = vmw_vkms_lock_max_wait_ns(du); > + u64 total_delay = 0; > + int ret; > + > + do { > + ret = atomic_cmpxchg(&du->vkms.atomic_lock, > + VMW_VKMS_LOCK_UNLOCKED, > + VMW_VKMS_LOCK_MODESET); > + if (ret == VMW_VKMS_LOCK_UNLOCKED || total_delay >= MAX_NSECS_DELAY) > + break; > + ndelay(nsecs_delay); > + total_delay += nsecs_delay; > + } while (1); > + > + if (total_delay >= MAX_NSECS_DELAY) { > + drm_warn(crtc->dev, "VKMS lock expired! total_delay = %lld, ret = %d, cur = %d\n", > + total_delay, ret, atomic_read(&du->vkms.atomic_lock)); > + } > +} > + > +/** > + * vmw_vkms_modeset_lock_relaxed - Protects access to crtc during modeset > + * @crtc: The crtc to lock for vkms > + * > + * Much like vmw_vkms_modeset_lock except that when the crtc is currently > + * in a modeset it will return immediately. > + * > + * Returns true if actually locked vkms to modeset or false otherwise. > + */ > +bool > +vmw_vkms_modeset_lock_relaxed(struct drm_crtc *crtc) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + const u64 nsecs_delay = 10; > + const u64 MAX_NSECS_DELAY = vmw_vkms_lock_max_wait_ns(du); > + u64 total_delay = 0; > + int ret; > + > + do { > + ret = atomic_cmpxchg(&du->vkms.atomic_lock, > + VMW_VKMS_LOCK_UNLOCKED, > + VMW_VKMS_LOCK_MODESET); > + if (ret == VMW_VKMS_LOCK_UNLOCKED || > + ret == VMW_VKMS_LOCK_MODESET || > + total_delay >= MAX_NSECS_DELAY) > + break; > + ndelay(nsecs_delay); > + total_delay += nsecs_delay; > + } while (1); > + > + if (total_delay >= MAX_NSECS_DELAY) { > + drm_warn(crtc->dev, "VKMS relaxed lock expired!\n"); > + return false; > + } > + > + return ret == VMW_VKMS_LOCK_UNLOCKED; > +} > + > +/** > + * vmw_vkms_vblank_trylock - Protects access to crtc during vblank > + * @crtc: The crtc to lock for vkms > + * > + * Tries to lock vkms for vblank, returns immediately. > + * > + * Returns true if locked vkms to vblank or false otherwise. > + */ > +bool > +vmw_vkms_vblank_trylock(struct drm_crtc *crtc) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + u32 ret; > + > + ret = atomic_cmpxchg(&du->vkms.atomic_lock, > + VMW_VKMS_LOCK_UNLOCKED, > + VMW_VKMS_LOCK_VBLANK); > + > + return ret == VMW_VKMS_LOCK_UNLOCKED; > +} > + > +void > +vmw_vkms_unlock(struct drm_crtc *crtc) > +{ > + struct vmw_display_unit *du = vmw_crtc_to_du(crtc); > + > + /* Release flag; mark it as unlocked. */ > + atomic_set(&du->vkms.atomic_lock, VMW_VKMS_LOCK_UNLOCKED); > +} > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.h b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.h > index 0f6d4ab9ebe3..69ddd33a8444 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.h > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.h > @@ -32,22 +32,44 @@ > #include <linux/hrtimer_types.h> > #include <linux/types.h> > > -struct vmw_private; > -struct drm_crtc; > struct drm_atomic_state; > +struct drm_crtc; > +struct vmw_private; > +struct vmw_surface; > > void vmw_vkms_init(struct vmw_private *vmw); > +void vmw_vkms_cleanup(struct vmw_private *vmw); > + > +void vmw_vkms_modeset_lock(struct drm_crtc *crtc); > +bool vmw_vkms_modeset_lock_relaxed(struct drm_crtc *crtc); > +bool vmw_vkms_vblank_trylock(struct drm_crtc *crtc); > +void vmw_vkms_unlock(struct drm_crtc *crtc); > + > bool vmw_vkms_get_vblank_timestamp(struct drm_crtc *crtc, > int *max_error, > ktime_t *vblank_time, > bool in_vblank_irq); > int vmw_vkms_enable_vblank(struct drm_crtc *crtc); > void vmw_vkms_disable_vblank(struct drm_crtc *crtc); > + > +void vmw_vkms_crtc_init(struct drm_crtc *crtc); > +void vmw_vkms_crtc_cleanup(struct drm_crtc *crtc); > +void vmw_vkms_crtc_atomic_begin(struct drm_crtc *crtc, > + struct drm_atomic_state *state); > void vmw_vkms_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); > -enum hrtimer_restart vmw_vkms_vblank_simulate(struct hrtimer *timer); > void vmw_vkms_crtc_atomic_enable(struct drm_crtc *crtc, > struct drm_atomic_state *state); > void vmw_vkms_crtc_atomic_disable(struct drm_crtc *crtc, > struct drm_atomic_state *state); > > +const char *const *vmw_vkms_get_crc_sources(struct drm_crtc *crtc, > + size_t *count); > +int vmw_vkms_verify_crc_source(struct drm_crtc *crtc, > + const char *src_name, > + size_t *values_cnt); > +int vmw_vkms_set_crc_source(struct drm_crtc *crtc, > + const char *src_name); > +void vmw_vkms_set_crc_surface(struct drm_crtc *crtc, > + struct vmw_surface *surf); > + > #endif > -- > 2.40.1 > Regards, Martin