drm/vmwgfx: Perform topology validation during atomic modeset.
authorDeepak Rawat <drawat@vmware.com>
Tue, 19 Jun 2018 17:51:15 +0000 (19:51 +0200)
committerThomas Hellstrom <thellstrom@vmware.com>
Tue, 3 Jul 2018 18:37:38 +0000 (20:37 +0200)
This patch adds display (primary) memory validation during modeset
check. Display memory validation are applicable to both SOU and STDU,
so allow both display unit to undergo this check.

Also added check for SVGA_CAP_NO_BB_RESTRICTION capability which lifts
bounding box restriction for STDU.

Signed-off-by: Deepak Rawat <drawat@vmware.com>
Reviewed-by: Thomas Hellstrom <thellstrom@vmware.com>
Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
drivers/gpu/drm/vmwgfx/device_include/svga_reg.h
drivers/gpu/drm/vmwgfx/vmwgfx_kms.c

index 88e72bf..cdd48a3 100644 (file)
@@ -672,8 +672,34 @@ SVGASignedPoint;
  * SVGA_CAP_GBOBJECTS --
  *    Enable guest-backed objects and surfaces.
  *
- * SVGA_CAP_CMD_BUFFERS_3 --
- *    Enable support for command buffers in a mob.
+ * SVGA_CAP_DX --
+ *    Enable support for DX commands, and command buffers in a mob.
+ *
+ * SVGA_CAP_HP_CMD_QUEUE --
+ *    Enable support for the high priority command queue, and the
+ *    ScreenCopy command.
+ *
+ * SVGA_CAP_NO_BB_RESTRICTION --
+ *    Allow ScreenTargets to be defined without regard to the 32-bpp
+ *    bounding-box memory restrictions. ie:
+ *
+ *    The summed memory usage of all screens (assuming they were defined as
+ *    32-bpp) must always be less than the value of the
+ *    SVGA_REG_MAX_PRIMARY_MEM register.
+ *
+ *    If this cap is not present, the 32-bpp bounding box around all screens
+ *    must additionally be under the value of the SVGA_REG_MAX_PRIMARY_MEM
+ *    register.
+ *
+ *    If the cap is present, the bounding box restriction is lifted (and only
+ *    the screen-sum limit applies).
+ *
+ *    (Note that this is a slight lie... there is still a sanity limit on any
+ *     dimension of the topology to be less than SVGA_SCREEN_ROOT_LIMIT, even
+ *     when SVGA_CAP_NO_BB_RESTRICTION is present, but that should be
+ *     large enough to express any possible topology without holes between
+ *     monitors.)
+ *
  */
 
 #define SVGA_CAP_NONE               0x00000000
@@ -699,6 +725,7 @@ SVGASignedPoint;
 #define SVGA_CAP_GBOBJECTS          0x08000000
 #define SVGA_CAP_DX                 0x10000000
 #define SVGA_CAP_HP_CMD_QUEUE       0x20000000
+#define SVGA_CAP_NO_BB_RESTRICTION  0x40000000
 
 #define SVGA_CAP_CMD_RESERVED       0x80000000
 
index 6b8541f..a7c9a01 100644 (file)
@@ -1507,66 +1507,178 @@ err_out:
        return &vfb->base;
 }
 
+/**
+ * vmw_kms_check_display_memory - Validates display memory required for a
+ * topology
+ * @dev: DRM device
+ * @num_rects: number of drm_rect in rects
+ * @rects: array of drm_rect representing the topology to validate indexed by
+ * crtc index.
+ *
+ * Returns:
+ * 0 on success otherwise negative error code
+ */
+static int vmw_kms_check_display_memory(struct drm_device *dev,
+                                       uint32_t num_rects,
+                                       struct drm_rect *rects)
+{
+       struct vmw_private *dev_priv = vmw_priv(dev);
+       struct drm_mode_config *mode_config = &dev->mode_config;
+       struct drm_rect bounding_box = {0};
+       u64 total_pixels = 0, pixel_mem, bb_mem;
+       int i;
+
+       for (i = 0; i < num_rects; i++) {
+               /*
+                * Currently this check is limiting the topology within max
+                * texture/screentarget size. This should change in future when
+                * user-space support multiple fb with topology.
+                */
+               if (rects[i].x1 < 0 ||  rects[i].y1 < 0 ||
+                   rects[i].x2 > mode_config->max_width ||
+                   rects[i].y2 > mode_config->max_height) {
+                       DRM_ERROR("Invalid GUI layout.\n");
+                       return -EINVAL;
+               }
+
+               /* Bounding box upper left is at (0,0). */
+               if (rects[i].x2 > bounding_box.x2)
+                       bounding_box.x2 = rects[i].x2;
+
+               if (rects[i].y2 > bounding_box.y2)
+                       bounding_box.y2 = rects[i].y2;
+
+               total_pixels += (u64) drm_rect_width(&rects[i]) *
+                       (u64) drm_rect_height(&rects[i]);
+       }
+
+       /* Virtual svga device primary limits are always in 32-bpp. */
+       pixel_mem = total_pixels * 4;
+
+       /*
+        * For HV10 and below prim_bb_mem is vram size. When
+        * SVGA_REG_MAX_PRIMARY_BOUNDING_BOX_MEM is not present vram size is
+        * limit on primary bounding box
+        */
+       if (pixel_mem > dev_priv->prim_bb_mem) {
+               DRM_ERROR("Combined output size too large.\n");
+               return -EINVAL;
+       }
 
+       /* SVGA_CAP_NO_BB_RESTRICTION is available for STDU only. */
+       if (dev_priv->active_display_unit != vmw_du_screen_target ||
+           !(dev_priv->capabilities & SVGA_CAP_NO_BB_RESTRICTION)) {
+               bb_mem = (u64) bounding_box.x2 * bounding_box.y2 * 4;
+
+               if (bb_mem > dev_priv->prim_bb_mem) {
+                       DRM_ERROR("Topology is beyond supported limits.\n");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
 
 /**
- * vmw_kms_atomic_check_modeset- validate state object for modeset changes
- *
+ * vmw_kms_check_topology - Validates topology in drm_atomic_state
  * @dev: DRM device
  * @state: the driver state object
  *
- * This is a simple wrapper around drm_atomic_helper_check_modeset() for
- * us to assign a value to mode->crtc_clock so that
- * drm_calc_timestamping_constants() won't throw an error message
- *
- * RETURNS
- * Zero for success or -errno
+ * Returns:
+ * 0 on success otherwise negative error code
  */
-static int
-vmw_kms_atomic_check_modeset(struct drm_device *dev,
-                            struct drm_atomic_state *state)
+static int vmw_kms_check_topology(struct drm_device *dev,
+                                 struct drm_atomic_state *state)
 {
-       struct drm_crtc_state *new_crtc_state;
-       struct drm_plane_state *new_plane_state;
-       struct drm_plane *plane;
+       struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+       struct drm_rect *rects;
        struct drm_crtc *crtc;
-       struct vmw_private *dev_priv = vmw_priv(dev);
-       int i, ret, cpp = 0;
+       uint32_t i;
+       int ret = 0;
 
-       ret = drm_atomic_helper_check(dev, state);
+       rects = kcalloc(dev->mode_config.num_crtc, sizeof(struct drm_rect),
+                       GFP_KERNEL);
+       if (!rects)
+               return -ENOMEM;
 
-       /* If this is not a STDU, then no more checking is necessary */
-       if (ret || dev_priv->active_display_unit != vmw_du_screen_target)
-               return ret;
+       drm_for_each_crtc(crtc, dev) {
+               struct vmw_display_unit *du = vmw_crtc_to_du(crtc);
+               struct drm_crtc_state *crtc_state = crtc->state;
 
-       for_each_new_plane_in_state(state, plane, new_plane_state, i) {
-               if (new_plane_state->fb) {
-                       int current_cpp = new_plane_state->fb->pitches[0] /
-                                         new_plane_state->fb->width;
+               i = drm_crtc_index(crtc);
 
-                       if (cpp == 0)
-                               cpp = current_cpp;
-                       else if (current_cpp != cpp)
-                               return -EINVAL;
+               if (crtc_state && crtc_state->enable) {
+                       /*
+                        * There could be a race condition with update of gui_x/
+                        * gui_y. Those are protected by dev->mode_config.mutex.
+                        */
+                       rects[i].x1 = du->gui_x;
+                       rects[i].y1 = du->gui_y;
+                       rects[i].x2 = du->gui_x + crtc_state->mode.hdisplay;
+                       rects[i].y2 = du->gui_y + crtc_state->mode.vdisplay;
                }
        }
 
-       for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
-               unsigned long requested_bb_mem = 0;
+       /* Determine change to topology due to new atomic state */
+       for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
+                                     new_crtc_state, i) {
+               struct vmw_display_unit *du = vmw_crtc_to_du(crtc);
+
+               if (!new_crtc_state->enable && old_crtc_state->enable) {
+                       rects[i].x1 = 0;
+                       rects[i].y1 = 0;
+                       rects[i].x2 = 0;
+                       rects[i].y2 = 0;
+               }
 
-               if (drm_atomic_crtc_needs_modeset(new_crtc_state)) {
-                       requested_bb_mem += new_crtc_state->mode.hdisplay *
-                                           new_crtc_state->mode.vdisplay *
-                                           cpp;
+               if (new_crtc_state->enable) {
+                       /* If display unit is not active cannot enable CRTC */
+                       if (!du->pref_active) {
+                               ret = -EINVAL;
+                               goto clean;
+                       }
 
-                       if (requested_bb_mem > dev_priv->prim_bb_mem)
-                               return -EINVAL;
+                       rects[i].x1 = du->gui_x;
+                       rects[i].y1 = du->gui_y;
+                       rects[i].x2 = du->gui_x + new_crtc_state->mode.hdisplay;
+                       rects[i].y2 = du->gui_y + new_crtc_state->mode.vdisplay;
                }
        }
 
+       ret = vmw_kms_check_display_memory(dev, dev->mode_config.num_crtc,
+                                          rects);
+
+clean:
+       kfree(rects);
        return ret;
 }
 
+/**
+ * vmw_kms_atomic_check_modeset- validate state object for modeset changes
+ *
+ * @dev: DRM device
+ * @state: the driver state object
+ *
+ * This is a simple wrapper around drm_atomic_helper_check_modeset() for
+ * us to assign a value to mode->crtc_clock so that
+ * drm_calc_timestamping_constants() won't throw an error message
+ *
+ * Returns:
+ * Zero for success or -errno
+ */
+static int
+vmw_kms_atomic_check_modeset(struct drm_device *dev,
+                            struct drm_atomic_state *state)
+{
+       int ret;
+
+       ret = drm_atomic_helper_check(dev, state);
+       if (ret)
+               return ret;
+
+       return vmw_kms_check_topology(dev, state);
+}
+
 static const struct drm_mode_config_funcs vmw_kms_funcs = {
        .fb_create = vmw_kms_fb_create,
        .atomic_check = vmw_kms_atomic_check_modeset,