/** * vmw_stdu_crtc_set_config - Sets a mode * * @set: mode parameters * * This function is the device-specific portion of the DRM CRTC mode set. * For the SVGA device, we do this by defining a Screen Target, binding a * GB Surface to that target, and finally update the screen target. * * RETURNS: * 0 on success, error code otherwise */ static int vmw_stdu_crtc_set_config(struct drm_mode_set *set) { struct vmw_private *dev_priv; struct vmw_framebuffer *vfb; struct vmw_screen_target_display_unit *stdu; struct drm_display_mode *mode; struct drm_framebuffer *new_fb; struct drm_crtc *crtc; struct drm_encoder *encoder; struct drm_connector *connector; bool turning_off; int ret; if (!set || !set->crtc) return -EINVAL; crtc = set->crtc; stdu = vmw_crtc_to_stdu(crtc); mode = set->mode; new_fb = set->fb; dev_priv = vmw_priv(crtc->dev); turning_off = set->num_connectors == 0 || !mode || !new_fb; vfb = (new_fb) ? vmw_framebuffer_to_vfb(new_fb) : NULL; if (set->num_connectors > 1) { DRM_ERROR("Too many connectors\n"); return -EINVAL; } if (set->num_connectors == 1 && set->connectors[0] != &stdu->base.connector) { DRM_ERROR("Connectors don't match %p %p\n", set->connectors[0], &stdu->base.connector); return -EINVAL; } if (!turning_off && (set->x + mode->hdisplay > new_fb->width || set->y + mode->vdisplay > new_fb->height)) { DRM_ERROR("Set outside of framebuffer\n"); return -EINVAL; } /* Only one active implicit frame-buffer at a time. */ mutex_lock(&dev_priv->global_kms_state_mutex); if (!turning_off && stdu->base.is_implicit && dev_priv->implicit_fb && !(dev_priv->num_implicit == 1 && stdu->base.active_implicit) && dev_priv->implicit_fb != vfb) { mutex_unlock(&dev_priv->global_kms_state_mutex); DRM_ERROR("Multiple implicit framebuffers not supported.\n"); return -EINVAL; } mutex_unlock(&dev_priv->global_kms_state_mutex); /* Since they always map one to one these are safe */ connector = &stdu->base.connector; encoder = &stdu->base.encoder; if (stdu->defined) { ret = vmw_stdu_bind_st(dev_priv, stdu, NULL); if (ret) return ret; vmw_stdu_unpin_display(stdu); (void) vmw_stdu_update_st(dev_priv, stdu); vmw_kms_del_active(dev_priv, &stdu->base); ret = vmw_stdu_destroy_st(dev_priv, stdu); if (ret) return ret; crtc->primary->fb = NULL; crtc->enabled = false; encoder->crtc = NULL; connector->encoder = NULL; stdu->content_fb_type = SAME_AS_DISPLAY; crtc->x = set->x; crtc->y = set->y; } if (turning_off) return 0; /* * Steps to displaying a surface, assume surface is already * bound: * 1. define a screen target * 2. bind a fb to the screen target * 3. update that screen target (this is done later by * vmw_kms_stdu_do_surface_dirty_or_present) */ /* * Note on error handling: We can't really restore the crtc to * it's original state on error, but we at least update the * current state to what's submitted to hardware to enable * future recovery. */ vmw_svga_enable(dev_priv); ret = vmw_stdu_define_st(dev_priv, stdu, mode, set->x, set->y); if (ret) return ret; crtc->x = set->x; crtc->y = set->y; crtc->mode = *mode; ret = vmw_stdu_bind_fb(dev_priv, crtc, mode, new_fb); if (ret) return ret; vmw_kms_add_active(dev_priv, &stdu->base, vfb); crtc->enabled = true; connector->encoder = encoder; encoder->crtc = crtc; return 0; }
/** * vmw_stdu_crtc_page_flip - Binds a buffer to a screen target * * @crtc: CRTC to attach FB to * @fb: FB to attach * @event: Event to be posted. This event should've been alloced * using k[mz]alloc, and should've been completely initialized. * @page_flip_flags: Input flags. * * If the STDU uses the same display and content buffers, i.e. a true flip, * this function will replace the existing display buffer with the new content * buffer. * * If the STDU uses different display and content buffers, i.e. a blit, then * only the content buffer will be updated. * * RETURNS: * 0 on success, error code on failure */ static int vmw_stdu_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *new_fb, struct drm_pending_vblank_event *event, uint32_t flags) { struct vmw_private *dev_priv = vmw_priv(crtc->dev); struct vmw_screen_target_display_unit *stdu; int ret; if (crtc == NULL) return -EINVAL; dev_priv = vmw_priv(crtc->dev); stdu = vmw_crtc_to_stdu(crtc); crtc->primary->fb = new_fb; stdu->content_fb = new_fb; if (stdu->display_srf) { /* * If the display surface is the same as the content surface * then remove the reference */ if (stdu->content_fb_type == SAME_AS_DISPLAY) { if (stdu->defined) { /* Unbind the current surface */ ret = vmw_stdu_bind_st(dev_priv, stdu, NULL); if (unlikely(ret != 0)) goto err_out; } vmw_stdu_unpin_display(stdu); stdu->display_srf = NULL; } } if (!new_fb) { /* Blanks the display */ (void) vmw_stdu_update_st(dev_priv, stdu); return 0; } if (stdu->content_fb_type == SAME_AS_DISPLAY) { stdu->display_srf = vmw_framebuffer_to_vfbs(new_fb)->surface; ret = vmw_stdu_pin_display(stdu); if (ret) { stdu->display_srf = NULL; goto err_out; } /* Bind display surface */ ret = vmw_stdu_bind_st(dev_priv, stdu, &stdu->display_srf->res); if (unlikely(ret != 0)) goto err_unpin_display_and_content; } /* Update display surface: after this point everything is bound */ ret = vmw_stdu_update_st(dev_priv, stdu); if (unlikely(ret != 0)) return ret; if (event) { struct vmw_fence_obj *fence = NULL; struct drm_file *file_priv = event->base.file_priv; vmw_execbuf_fence_commands(NULL, dev_priv, &fence, NULL); if (!fence) return -ENOMEM; ret = vmw_event_fence_action_queue(file_priv, fence, &event->base, &event->event.tv_sec, &event->event.tv_usec, true); vmw_fence_obj_unreference(&fence); } return ret; err_unpin_display_and_content: vmw_stdu_unpin_display(stdu); err_out: crtc->primary->fb = NULL; stdu->content_fb = NULL; return ret; }
/** * vmw_stdu_bind_fb - Bind an fb to a defined screen target * * @dev_priv: Pointer to a device private struct. * @crtc: The crtc holding the screen target. * @mode: The mode currently used by the screen target. Must be non-NULL. * @new_fb: The new framebuffer to bind. Must be non-NULL. * * RETURNS: * 0 on success, error code on failure. */ static int vmw_stdu_bind_fb(struct vmw_private *dev_priv, struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_framebuffer *new_fb) { struct vmw_screen_target_display_unit *stdu = vmw_crtc_to_stdu(crtc); struct vmw_framebuffer *vfb = vmw_framebuffer_to_vfb(new_fb); struct vmw_surface *new_display_srf = NULL; enum stdu_content_type new_content_type; struct vmw_framebuffer_surface *new_vfbs; int ret; WARN_ON_ONCE(!stdu->defined); new_vfbs = (vfb->dmabuf) ? NULL : vmw_framebuffer_to_vfbs(new_fb); if (new_vfbs && new_vfbs->surface->base_size.width == mode->hdisplay && new_vfbs->surface->base_size.height == mode->vdisplay) new_content_type = SAME_AS_DISPLAY; else if (vfb->dmabuf) new_content_type = SEPARATE_DMA; else new_content_type = SEPARATE_SURFACE; if (new_content_type != SAME_AS_DISPLAY && !stdu->display_srf) { struct vmw_surface content_srf; struct drm_vmw_size display_base_size = {0}; display_base_size.width = mode->hdisplay; display_base_size.height = mode->vdisplay; display_base_size.depth = 1; /* * If content buffer is a DMA buf, then we have to construct * surface info */ if (new_content_type == SEPARATE_DMA) { switch (new_fb->bits_per_pixel) { case 32: content_srf.format = SVGA3D_X8R8G8B8; break; case 16: content_srf.format = SVGA3D_R5G6B5; break; case 8: content_srf.format = SVGA3D_P8; break; default: DRM_ERROR("Invalid format\n"); return -EINVAL; } content_srf.flags = 0; content_srf.mip_levels[0] = 1; content_srf.multisample_count = 0; } else { content_srf = *new_vfbs->surface; } ret = vmw_surface_gb_priv_define(crtc->dev, 0, /* because kernel visible only */ content_srf.flags, content_srf.format, true, /* a scanout buffer */ content_srf.mip_levels[0], content_srf.multisample_count, 0, display_base_size, &new_display_srf); if (unlikely(ret != 0)) { DRM_ERROR("Could not allocate screen target surface.\n"); return ret; } } else if (new_content_type == SAME_AS_DISPLAY) { new_display_srf = vmw_surface_reference(new_vfbs->surface); } if (new_display_srf) { /* Pin new surface before flipping */ ret = vmw_resource_pin(&new_display_srf->res, false); if (ret) goto out_srf_unref; ret = vmw_stdu_bind_st(dev_priv, stdu, &new_display_srf->res); if (ret) goto out_srf_unpin; /* Unpin and unreference old surface */ vmw_stdu_unpin_display(stdu); /* Transfer the reference */ stdu->display_srf = new_display_srf; new_display_srf = NULL; } crtc->primary->fb = new_fb; stdu->content_fb_type = new_content_type; return 0; out_srf_unpin: vmw_resource_unpin(&new_display_srf->res); out_srf_unref: vmw_surface_unreference(&new_display_srf); return ret; }
/** * vmw_stdu_crtc_set_config - Sets a mode * * @set: mode parameters * * This function is the device-specific portion of the DRM CRTC mode set. * For the SVGA device, we do this by defining a Screen Target, binding a * GB Surface to that target, and finally update the screen target. * * RETURNS: * 0 on success, error code otherwise */ static int vmw_stdu_crtc_set_config(struct drm_mode_set *set) { struct vmw_private *dev_priv; struct vmw_screen_target_display_unit *stdu; struct vmw_framebuffer *vfb; struct vmw_framebuffer_surface *new_vfbs; struct drm_display_mode *mode; struct drm_framebuffer *new_fb; struct drm_crtc *crtc; struct drm_encoder *encoder; struct drm_connector *connector; int ret; if (!set || !set->crtc) return -EINVAL; crtc = set->crtc; crtc->x = set->x; crtc->y = set->y; stdu = vmw_crtc_to_stdu(crtc); mode = set->mode; new_fb = set->fb; dev_priv = vmw_priv(crtc->dev); if (set->num_connectors > 1) { DRM_ERROR("Too many connectors\n"); return -EINVAL; } if (set->num_connectors == 1 && set->connectors[0] != &stdu->base.connector) { DRM_ERROR("Connectors don't match %p %p\n", set->connectors[0], &stdu->base.connector); return -EINVAL; } /* Since they always map one to one these are safe */ connector = &stdu->base.connector; encoder = &stdu->base.encoder; /* * After this point the CRTC will be considered off unless a new fb * is bound */ if (stdu->defined) { /* Unbind current surface by binding an invalid one */ ret = vmw_stdu_bind_st(dev_priv, stdu, NULL); if (unlikely(ret != 0)) return ret; /* Update Screen Target, display will now be blank */ if (crtc->primary->fb) { vmw_stdu_update_st(dev_priv, stdu); if (unlikely(ret != 0)) return ret; } crtc->primary->fb = NULL; crtc->enabled = false; encoder->crtc = NULL; connector->encoder = NULL; vmw_stdu_unpin_display(stdu); stdu->content_fb = NULL; stdu->content_fb_type = SAME_AS_DISPLAY; ret = vmw_stdu_destroy_st(dev_priv, stdu); /* The hardware is hung, give up */ if (unlikely(ret != 0)) return ret; } /* Any of these conditions means the caller wants CRTC off */ if (set->num_connectors == 0 || !mode || !new_fb) return 0; if (set->x + mode->hdisplay > new_fb->width || set->y + mode->vdisplay > new_fb->height) { DRM_ERROR("Set outside of framebuffer\n"); return -EINVAL; } stdu->content_fb = new_fb; vfb = vmw_framebuffer_to_vfb(stdu->content_fb); if (vfb->dmabuf) stdu->content_fb_type = SEPARATE_DMA; /* * If the requested mode is different than the width and height * of the FB or if the content buffer is a DMA buf, then allocate * a display FB that matches the dimension of the mode */ if (mode->hdisplay != new_fb->width || mode->vdisplay != new_fb->height || stdu->content_fb_type != SAME_AS_DISPLAY) { struct vmw_surface content_srf; struct drm_vmw_size display_base_size = {0}; struct vmw_surface *display_srf; display_base_size.width = mode->hdisplay; display_base_size.height = mode->vdisplay; display_base_size.depth = 1; /* * If content buffer is a DMA buf, then we have to construct * surface info */ if (stdu->content_fb_type == SEPARATE_DMA) { switch (new_fb->bits_per_pixel) { case 32: content_srf.format = SVGA3D_X8R8G8B8; break; case 16: content_srf.format = SVGA3D_R5G6B5; break; case 8: content_srf.format = SVGA3D_P8; break; default: DRM_ERROR("Invalid format\n"); ret = -EINVAL; goto err_unref_content; } content_srf.flags = 0; content_srf.mip_levels[0] = 1; content_srf.multisample_count = 0; } else { stdu->content_fb_type = SEPARATE_SURFACE; new_vfbs = vmw_framebuffer_to_vfbs(new_fb); content_srf = *new_vfbs->surface; } ret = vmw_surface_gb_priv_define(crtc->dev, 0, /* because kernel visible only */ content_srf.flags, content_srf.format, true, /* a scanout buffer */ content_srf.mip_levels[0], content_srf.multisample_count, 0, display_base_size, &display_srf); if (unlikely(ret != 0)) { DRM_ERROR("Cannot allocate a display FB.\n"); goto err_unref_content; } stdu->display_srf = display_srf; } else { new_vfbs = vmw_framebuffer_to_vfbs(new_fb); stdu->display_srf = new_vfbs->surface; } ret = vmw_stdu_pin_display(stdu); if (unlikely(ret != 0)) { stdu->display_srf = NULL; goto err_unref_content; } vmw_svga_enable(dev_priv); /* * Steps to displaying a surface, assume surface is already * bound: * 1. define a screen target * 2. bind a fb to the screen target * 3. update that screen target (this is done later by * vmw_kms_stdu_do_surface_dirty_or_present) */ ret = vmw_stdu_define_st(dev_priv, stdu); if (unlikely(ret != 0)) goto err_unpin_display_and_content; ret = vmw_stdu_bind_st(dev_priv, stdu, &stdu->display_srf->res); if (unlikely(ret != 0)) goto err_unpin_destroy_st; connector->encoder = encoder; encoder->crtc = crtc; crtc->mode = *mode; crtc->primary->fb = new_fb; crtc->enabled = true; return ret; err_unpin_destroy_st: vmw_stdu_destroy_st(dev_priv, stdu); err_unpin_display_and_content: vmw_stdu_unpin_display(stdu); err_unref_content: stdu->content_fb = NULL; return ret; }