@@ -803,6 +803,11 @@ vmw_du_plane_duplicate_state(struct drm_plane *plane)
vps->pinned = 0;
+ /* Mapping is managed by prepare_fb/cleanup_fb */
+ memset(&vps->guest_map, 0, sizeof(vps->guest_map));
+ memset(&vps->host_map, 0, sizeof(vps->host_map));
+ vps->cpp = 0;
+
/* Each ref counted resource needs to be acquired again */
if (vps->surf)
(void) vmw_surface_reference(vps->surf);
@@ -861,6 +866,17 @@ vmw_du_plane_destroy_state(struct drm_plane *plane,
struct vmw_plane_state *vps = vmw_plane_state_to_vps(state);
+ /* Should have been freed by cleanup_fb */
+ if (vps->guest_map.virtual) {
+ DRM_ERROR("Guest mapping not freed\n");
+ ttm_bo_kunmap(&vps->guest_map);
+ }
+
+ if (vps->host_map.virtual) {
+ DRM_ERROR("Host mapping not freed\n");
+ ttm_bo_kunmap(&vps->host_map);
+ }
+
if (vps->surf)
vmw_surface_unreference(&vps->surf);
@@ -1435,6 +1451,25 @@ static int vmw_kms_new_framebuffer_dmabuf(struct vmw_private *dev_priv,
return ret;
}
+
+/**
+ * vmw_kms_srf_ok - check if a surface can be created
+ *
+ * @width: requested width
+ * @height: requested height
+ *
+ * Surfaces need to be less than texture size
+ */
+static bool
+vmw_kms_srf_ok(struct vmw_private *dev_priv, uint32_t width, uint32_t height)
+{
+ if (width > dev_priv->texture_max_width ||
+ height > dev_priv->texture_max_height)
+ return false;
+
+ return true;
+}
+
/**
* vmw_kms_new_framebuffer - Create a new framebuffer.
*
@@ -1463,7 +1498,8 @@ vmw_kms_new_framebuffer(struct vmw_private *dev_priv,
* therefore, wrap the DMA buf in a surface so we can use the
* SurfaceCopy command.
*/
- if (dmabuf && only_2d &&
+ if (vmw_kms_srf_ok(dev_priv, mode_cmd->width, mode_cmd->height) &&
+ dmabuf && only_2d &&
dev_priv->active_display_unit == vmw_du_screen_target) {
ret = vmw_create_dmabuf_proxy(dev_priv->dev, mode_cmd,
dmabuf, &surface);
@@ -1556,6 +1592,16 @@ static struct drm_framebuffer *vmw_kms_fb_create(struct drm_device *dev,
if (ret)
goto err_out;
+
+ if (!bo &&
+ !vmw_kms_srf_ok(dev_priv, mode_cmd->width, mode_cmd->height)) {
+ DRM_ERROR("Surface size cannot exceed %dx%d",
+ dev_priv->texture_max_width,
+ dev_priv->texture_max_height);
+ goto err_out;
+ }
+
+
vfb = vmw_kms_new_framebuffer(dev_priv, bo, surface,
!(dev_priv->capabilities & SVGA_CAP_3D),
mode_cmd);
@@ -1602,10 +1648,13 @@ vmw_kms_atomic_check_modeset(struct drm_device *dev,
{
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
+ struct vmw_private *dev_priv = vmw_priv(dev);
int i, ret;
for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ unsigned long requested_bb_mem = 0;
+
if (crtc_state->mode.crtc_clock == 0) {
/*
* Our virtual device does not have a dot clock,
@@ -1613,6 +1662,19 @@ vmw_kms_atomic_check_modeset(struct drm_device *dev,
*/
crtc_state->mode.crtc_clock = crtc_state->mode.clock;
}
+
+ if (dev_priv->active_display_unit == vmw_du_screen_target) {
+ if (crtc->primary->fb) {
+ int cpp = crtc->primary->fb->pitches[0] /
+ crtc->primary->fb->width;
+
+ requested_bb_mem += crtc->mode.hdisplay * cpp *
+ crtc->mode.vdisplay;
+ }
+
+ if (requested_bb_mem > dev_priv->prim_bb_mem)
+ return -EINVAL;
+ }
}
ret = drm_atomic_helper_check_modeset(dev, state);
@@ -173,6 +173,10 @@ struct vmw_plane_state {
unsigned long dmabuf_size;
int pinned;
+
+ /* For CPU Blit */
+ struct ttm_bo_kmap_obj host_map, guest_map;
+ unsigned int cpp;
};
@@ -109,6 +109,10 @@ struct vmw_screen_target_display_unit {
s32 display_width, display_height;
bool defined;
+
+ /* For CPU Blit */
+ struct ttm_bo_kmap_obj host_map, guest_map;
+ unsigned int cpp;
};
@@ -636,6 +640,129 @@ static void vmw_stdu_dmabuf_fifo_commit(struct vmw_kms_dirty *dirty)
ddirty->right = ddirty->bottom = S32_MIN;
}
+
+/**
+ * vmw_stdu_dmabuf_cpu_clip - Callback to encode a CPU blit
+ *
+ * @dirty: The closure structure.
+ *
+ * This function calculates the bounding box for all the incoming clips
+ */
+static void vmw_stdu_dmabuf_cpu_clip(struct vmw_kms_dirty *dirty)
+{
+ struct vmw_stdu_dirty *ddirty =
+ container_of(dirty, struct vmw_stdu_dirty, base);
+
+ dirty->num_hits = 1;
+
+ /* Calculate bounding box */
+ ddirty->left = min_t(s32, ddirty->left, dirty->unit_x1);
+ ddirty->top = min_t(s32, ddirty->top, dirty->unit_y1);
+ ddirty->right = max_t(s32, ddirty->right, dirty->unit_x2);
+ ddirty->bottom = max_t(s32, ddirty->bottom, dirty->unit_y2);
+}
+
+
+/**
+ * vmw_stdu_dmabuf_cpu_commit - Callback to do a CPU blit from DMAbuf
+ *
+ * @dirty: The closure structure.
+ *
+ * For the special case when we cannot create a proxy surface in a
+ * 2D VM, we have to do a CPU blit ourselves.
+ */
+static void vmw_stdu_dmabuf_cpu_commit(struct vmw_kms_dirty *dirty)
+{
+ struct vmw_stdu_dirty *ddirty =
+ container_of(dirty, struct vmw_stdu_dirty, base);
+ struct vmw_screen_target_display_unit *stdu =
+ container_of(dirty->unit, typeof(*stdu), base);
+ s32 width, height;
+ s32 src_pitch, dst_pitch;
+ u8 *src, *dst;
+ bool not_used;
+
+
+ if (!dirty->num_hits)
+ return;
+
+ width = ddirty->right - ddirty->left;
+ height = ddirty->bottom - ddirty->top;
+
+ if (width == 0 || height == 0)
+ return;
+
+
+ /* Assume we are blitting from Host (display_srf) to Guest (dmabuf) */
+ src_pitch = stdu->display_srf->base_size.width * stdu->cpp;
+ src = ttm_kmap_obj_virtual(&stdu->host_map, ¬_used);
+ src += dirty->unit_y1 * src_pitch + dirty->unit_x1 * stdu->cpp;
+
+ dst_pitch = ddirty->pitch;
+ dst = ttm_kmap_obj_virtual(&stdu->guest_map, ¬_used);
+ dst += dirty->fb_y * dst_pitch + dirty->fb_x * stdu->cpp;
+
+
+ /* Figure out the real direction */
+ if (ddirty->transfer == SVGA3D_WRITE_HOST_VRAM) {
+ u8 *tmp;
+ s32 tmp_pitch;
+
+ tmp = src;
+ tmp_pitch = src_pitch;
+
+ src = dst;
+ src_pitch = dst_pitch;
+
+ dst = tmp;
+ dst_pitch = tmp_pitch;
+ }
+
+ /* CPU Blit */
+ while (height-- > 0) {
+ memcpy(dst, src, width * stdu->cpp);
+ dst += dst_pitch;
+ src += src_pitch;
+ }
+
+ if (ddirty->transfer == SVGA3D_WRITE_HOST_VRAM) {
+ struct vmw_private *dev_priv;
+ struct vmw_stdu_update *cmd;
+ struct drm_clip_rect region;
+ int ret;
+
+ /* We are updating the actual surface, not a proxy */
+ region.x1 = ddirty->left;
+ region.x2 = ddirty->right;
+ region.y1 = ddirty->top;
+ region.y2 = ddirty->bottom;
+ ret = vmw_kms_update_proxy(
+ (struct vmw_resource *) &stdu->display_srf->res,
+ (const struct drm_clip_rect *) ®ion, 1, 1);
+ if (ret)
+ goto out_cleanup;
+
+
+ dev_priv = vmw_priv(stdu->base.crtc.dev);
+ cmd = vmw_fifo_reserve(dev_priv, sizeof(*cmd));
+
+ if (!cmd) {
+ DRM_ERROR("Cannot reserve FIFO space to update STDU");
+ goto out_cleanup;
+ }
+
+ vmw_stdu_populate_update(cmd, stdu->base.unit,
+ ddirty->left, ddirty->right,
+ ddirty->top, ddirty->bottom);
+
+ vmw_fifo_commit(dev_priv, sizeof(*cmd));
+ }
+
+out_cleanup:
+ ddirty->left = ddirty->top = S32_MAX;
+ ddirty->right = ddirty->bottom = S32_MIN;
+}
+
/**
* vmw_kms_stdu_dma - Perform a DMA transfer between a dma-buffer backed
* framebuffer and the screen target system.
@@ -694,6 +821,13 @@ int vmw_kms_stdu_dma(struct vmw_private *dev_priv,
if (to_surface)
ddirty.base.fifo_reserve_size += sizeof(struct vmw_stdu_update);
+ /* 2D VMs cannot use SVGA_3D_CMD_SURFACE_DMA so do CPU blit instead */
+ if (!(dev_priv->capabilities & SVGA_CAP_3D)) {
+ ddirty.base.fifo_commit = vmw_stdu_dmabuf_cpu_commit;
+ ddirty.base.clip = vmw_stdu_dmabuf_cpu_clip;
+ ddirty.base.fifo_reserve_size = 0;
+ }
+
ret = vmw_kms_helper_dirty(dev_priv, vfb, clips, vclips,
0, 0, num_clips, increment, &ddirty.base);
vmw_kms_helper_buffer_finish(dev_priv, file_priv, buf, NULL,
@@ -960,12 +1094,19 @@ vmw_stdu_primary_plane_cleanup_fb(struct drm_plane *plane,
{
struct vmw_plane_state *vps = vmw_plane_state_to_vps(old_state);
+ if (vps->guest_map.virtual)
+ ttm_bo_kunmap(&vps->guest_map);
+
+ if (vps->host_map.virtual)
+ ttm_bo_kunmap(&vps->host_map);
+
if (vps->surf)
WARN_ON(!vps->pinned);
vmw_du_plane_cleanup_fb(plane, old_state);
vps->content_fb_type = SAME_AS_DISPLAY;
+ vps->cpp = 0;
}
@@ -986,6 +1127,7 @@ static int
vmw_stdu_primary_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *new_state)
{
+ struct vmw_private *dev_priv = vmw_priv(plane->dev);
struct drm_framebuffer *new_fb = new_state->fb;
struct vmw_framebuffer *vfb;
struct vmw_plane_state *vps = vmw_plane_state_to_vps(new_state);
@@ -1111,8 +1253,54 @@ vmw_stdu_primary_plane_prepare_fb(struct drm_plane *plane,
}
vps->content_fb_type = new_content_type;
+
+ /*
+ * This should only happen if the DMA buf is too large to create a
+ * proxy surface for.
+ * If we are a 2D VM with a DMA buffer then we have to use CPU blit
+ * so cache these mappings
+ */
+ if (vps->content_fb_type == SEPARATE_DMA &&
+ !(dev_priv->capabilities & SVGA_CAP_3D)) {
+
+ struct vmw_framebuffer_dmabuf *new_vfbd;
+
+ new_vfbd = vmw_framebuffer_to_vfbd(new_fb);
+
+ ret = ttm_bo_reserve(&new_vfbd->buffer->base, false, false,
+ NULL);
+ if (ret)
+ goto out_srf_unpin;
+
+ ret = ttm_bo_kmap(&new_vfbd->buffer->base, 0,
+ new_vfbd->buffer->base.num_pages,
+ &vps->guest_map);
+
+ ttm_bo_unreserve(&new_vfbd->buffer->base);
+
+ if (ret) {
+ DRM_ERROR("Failed to map content buffer to CPU\n");
+ goto out_srf_unpin;
+ }
+
+ ret = ttm_bo_kmap(&vps->surf->res.backup->base, 0,
+ vps->surf->res.backup->base.num_pages,
+ &vps->host_map);
+ if (ret) {
+ DRM_ERROR("Failed to map display buffer to CPU\n");
+ ttm_bo_kunmap(&vps->guest_map);
+ goto out_srf_unpin;
+ }
+
+ vps->cpp = new_fb->pitches[0] / new_fb->width;
+ }
+
return 0;
+out_srf_unpin:
+ vmw_resource_unpin(&vps->surf->res);
+ vps->pinned--;
+
out_srf_unref:
vmw_surface_unreference(&vps->surf);
return ret;
@@ -1146,6 +1334,9 @@ vmw_stdu_primary_plane_atomic_update(struct drm_plane *plane,
stdu->display_srf = vps->surf;
stdu->content_fb_type = vps->content_fb_type;
+ stdu->cpp = vps->cpp;
+ memcpy(&stdu->guest_map, &vps->guest_map, sizeof(vps->guest_map));
+ memcpy(&stdu->host_map, &vps->host_map, sizeof(vps->host_map));
if (!stdu->defined)
return;
@@ -1411,6 +1602,17 @@ int vmw_kms_stdu_init_display(struct vmw_private *dev_priv)
dev_priv->active_display_unit = vmw_du_screen_target;
+ if (!(dev_priv->capabilities & SVGA_CAP_3D)) {
+ /*
+ * Given various display aspect ratios, there's no way to
+ * estimate these using prim_bb_mem. So just set these to
+ * something arbitrarily large and we will reject any layout
+ * that doesn't fit prim_bb_mem later
+ */
+ dev->mode_config.max_width = 16384;
+ dev->mode_config.max_height = 16384;
+ }
+
vmw_kms_create_implicit_placement_property(dev_priv, false);
for (i = 0; i < VMWGFX_NUM_DISPLAY_UNITS; ++i) {