/* * Copyright 2007-2011 by VMware, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name of the copyright holder(s) * and author(s) shall not be used in advertising or otherwise to promote * the sale, use or other dealings in this Software without prior written * authorization from the copyright holder(s) and author(s). * */ /* * vmwarevideo.c -- * * Xv extension support. * See http://www.xfree86.org/current/DESIGN16.html * */ #include "xorg-server.h" #include "xf86xv.h" #include "fourcc.h" #define debug_printf(...) /* * We can't incude svga_types.h due to conflicting types for Bool. */ typedef int64_t int64; typedef uint64_t uint64; typedef int32_t int32; typedef uint32_t uint32; typedef int16_t int16; typedef uint16_t uint16; typedef int8_t int8; typedef uint8_t uint8; #include "../src/svga_reg.h" #include "../src/svga_escape.h" #include "../src/svga_overlay.h" #include #include "xf86drm.h" #include "vmwgfx_drm.h" #include "vmwgfx_drmi.h" #include "vmwgfx_driver.h" #include "vmwgfx_hosted.h" #define MAKE_ATOM(a) MakeAtom(a, sizeof(a) - 1, TRUE) /* * Number of videos that can be played simultaneously */ #define VMWARE_VID_NUM_PORTS 1 /* * Using a dark shade as the default colorKey */ #define VMWARE_VIDEO_COLORKEY 0x100701 /* * Maximum dimensions */ #define VMWARE_VID_MAX_WIDTH 2048 #define VMWARE_VID_MAX_HEIGHT 2048 #define VMWARE_VID_NUM_ENCODINGS 1 static XF86VideoEncodingRec vmwareVideoEncodings[] = { { 0, "XV_IMAGE", VMWARE_VID_MAX_WIDTH, VMWARE_VID_MAX_HEIGHT, {1, 1} } }; #define VMWARE_VID_NUM_FORMATS 2 static XF86VideoFormatRec vmwareVideoFormats[] = { { 16, TrueColor}, { 24, TrueColor} }; #define VMWARE_VID_NUM_IMAGES 3 static XF86ImageRec vmwareVideoImages[] = { XVIMAGE_YV12, XVIMAGE_YUY2, XVIMAGE_UYVY }; #define VMWARE_VID_NUM_ATTRIBUTES 2 static XF86AttributeRec vmwareVideoAttributes[] = { { XvGettable | XvSettable, 0x000000, 0xffffff, "XV_COLORKEY" }, { XvGettable | XvSettable, 0, 1, "XV_AUTOPAINT_COLORKEY" } }; /* * Video frames are stored in a circular list of buffers. * Must be power or two, See vmw_video_port_play. */ #define VMWARE_VID_NUM_BUFFERS 1 /* * Defines the structure used to hold and pass video data to the host */ struct vmw_video_buffer { int size; void *data; struct vmwgfx_dmabuf *buf; }; /** * Structure representing a single video stream, aka port. * * Ports maps one to one to a SVGA stream. Port is just * what Xv calls a SVGA stream. */ struct vmwgfx_overlay_port { /* * Function prototype same as XvPutImage. * * This is either set to vmw_video_port_init or vmw_video_port_play. * At init this function is set to port_init. In port_init we set it * to port_play and call it, after initializing the struct. */ int (*play)(ScrnInfoPtr, struct vmwgfx_overlay_port *, short, short, short, short, short, short, short, short, int, unsigned char*, short, short, RegionPtr, DrawablePtr); /* values to go into the SVGAOverlayUnit */ uint32 streamId; uint32 colorKey; uint32 flags; /* round robin of buffers */ unsigned currBuf; struct vmw_video_buffer bufs[VMWARE_VID_NUM_BUFFERS]; /* properties that applies to all buffers */ int size; int pitches[3]; int offsets[3]; /* things for X */ RegionRec clipBoxes; Bool isAutoPaintColorkey; int drm_fd; }; /* * Callback functions exported to Xv, prefixed with vmw_xv_*. */ static int vmw_xv_put_image(ScrnInfoPtr pScrn, short src_x, short src_y, short drw_x, short drw_y, short src_w, short src_h, short drw_w, short drw_h, int image, unsigned char *buf, short width, short height, Bool sync, RegionPtr clipBoxes, pointer data, DrawablePtr dst); static void vmw_xv_stop_video(ScrnInfoPtr pScrn, pointer data, Bool Cleanup); static int vmw_xv_query_image_attributes(ScrnInfoPtr pScrn, int format, unsigned short *width, unsigned short *height, int *pitches, int *offsets); static int vmw_xv_set_port_attribute(ScrnInfoPtr pScrn, Atom attribute, INT32 value, pointer data); static int vmw_xv_get_port_attribute(ScrnInfoPtr pScrn, Atom attribute, INT32 *value, pointer data); static void vmw_xv_query_best_size(ScrnInfoPtr pScrn, Bool motion, short vid_w, short vid_h, short drw_w, short drw_h, unsigned int *p_w, unsigned int *p_h, pointer data); /* * Local functions. */ static int vmw_video_port_init(ScrnInfoPtr pScrn, struct vmwgfx_overlay_port *port, short src_x, short src_y, short drw_x, short drw_y, short src_w, short src_h, short drw_w, short drw_h, int format, unsigned char *buf, short width, short height, RegionPtr clipBoxes, DrawablePtr pDraw); static int vmw_video_port_play(ScrnInfoPtr pScrn, struct vmwgfx_overlay_port *port, short src_x, short src_y, short drw_x, short drw_y, short src_w, short src_h, short drw_w, short drw_h, int format, unsigned char *buf, short width, short height, RegionPtr clipBoxes, DrawablePtr pDraw); static void vmw_video_port_cleanup(ScrnInfoPtr pScrn, struct vmwgfx_overlay_port *port); static int vmw_video_buffer_alloc(int drm_fd, int size, struct vmw_video_buffer *out); static int vmw_video_buffer_free(struct vmw_video_buffer *out); static struct vmwgfx_overlay_port * vmwgfx_overlay_port_create(int drm_fd, ScreenPtr pScreen) { struct vmwgfx_overlay_port *port = calloc(1, sizeof(*port)); if (!port) return NULL; port->drm_fd = drm_fd; port->play = vmw_video_port_init; port->flags = SVGA_VIDEO_FLAG_COLORKEY; port->colorKey = VMWARE_VIDEO_COLORKEY; port->isAutoPaintColorkey = TRUE; return port; } void vmw_video_free_adaptor(XF86VideoAdaptorPtr adaptor, Bool free_ports) { if (free_ports) { int i; for(i=0; inPorts; ++i) { free(adaptor->pPortPrivates[i].ptr); } } free(adaptor->pPortPrivates); xf86XVFreeVideoAdaptorRec(adaptor); } /* *----------------------------------------------------------------------------- * * vmw_video_init_adaptor -- * * Initializes a XF86VideoAdaptor structure with the capabilities and * functions supported by this video driver. * * Results: * On success initialized XF86VideoAdaptor struct or NULL on error * * Side effects: * None. * *----------------------------------------------------------------------------- */ XF86VideoAdaptorPtr vmw_video_init_adaptor(ScrnInfoPtr pScrn) { XF86VideoAdaptorPtr adaptor; modesettingPtr ms = modesettingPTR(pScrn); int i; DevUnion *dev_unions; uint32_t ntot, nfree; if (vmwgfx_is_hosted(ms->hdriver)) return NULL; if (vmwgfx_num_streams(ms->fd, &ntot, &nfree) != 0) { debug_printf("No stream ioctl support\n"); return NULL; } if (nfree == 0) { debug_printf("No free streams\n"); return NULL; } adaptor = xf86XVAllocateVideoAdaptorRec(pScrn); dev_unions = calloc(VMWARE_VID_NUM_PORTS, sizeof(DevUnion)); if (adaptor == NULL || dev_unions == NULL) { xf86XVFreeVideoAdaptorRec(adaptor); free(dev_unions); return NULL; } adaptor->type = XvInputMask | XvImageMask | XvWindowMask; /** * Note: CLIP_TO_VIEWPORT was removed from the flags, since with the * crtc/output based modesetting, the viewport is not updated on * RandR modeswitches. Hence the video may incorrectly be clipped away. * The correct approach, (if needed) would be to clip against the * scanout area union of all active crtcs. Revisit if needed. */ adaptor->flags = VIDEO_OVERLAID_IMAGES; adaptor->name = "VMware Overlay Video Engine"; adaptor->nEncodings = VMWARE_VID_NUM_ENCODINGS; adaptor->pEncodings = vmwareVideoEncodings; adaptor->nFormats = VMWARE_VID_NUM_FORMATS; adaptor->pFormats = vmwareVideoFormats; adaptor->nPorts = VMWARE_VID_NUM_PORTS; adaptor->pPortPrivates = dev_unions; for (i = 0; i < VMWARE_VID_NUM_PORTS; ++i) { struct vmwgfx_overlay_port *priv = vmwgfx_overlay_port_create(ms->fd, pScrn->pScreen); adaptor->pPortPrivates[i].ptr = (pointer) priv; } adaptor->nAttributes = VMWARE_VID_NUM_ATTRIBUTES; adaptor->pAttributes = vmwareVideoAttributes; adaptor->nImages = VMWARE_VID_NUM_IMAGES; adaptor->pImages = vmwareVideoImages; adaptor->PutVideo = NULL; adaptor->PutStill = NULL; adaptor->GetVideo = NULL; adaptor->GetStill = NULL; adaptor->StopVideo = vmw_xv_stop_video; adaptor->SetPortAttribute = vmw_xv_set_port_attribute; adaptor->GetPortAttribute = vmw_xv_get_port_attribute; adaptor->QueryBestSize = vmw_xv_query_best_size; adaptor->PutImage = vmw_xv_put_image; adaptor->QueryImageAttributes = vmw_xv_query_image_attributes; return adaptor; } /* *----------------------------------------------------------------------------- * * vmw_video_port_init -- * * Initializes a video stream in response to the first PutImage() on a * video stream. The process goes as follows: * - Figure out characteristics according to format * - Allocate offscreen memory * - Pass on video to Play() functions * * Results: * Success or XvBadAlloc on failure. * * Side effects: * Video stream is initialized and its first frame sent to the host * (done by VideoPlay() function called at the end) * *----------------------------------------------------------------------------- */ static int vmw_video_port_init(ScrnInfoPtr pScrn, struct vmwgfx_overlay_port *port, short src_x, short src_y, short drw_x, short drw_y, short src_w, short src_h, short drw_w, short drw_h, int format, unsigned char *buf, short width, short height, RegionPtr clipBoxes, DrawablePtr pDraw) { unsigned short w, h; int i, ret; debug_printf("\t%s: id %d, format %d\n", __func__, port->streamId, format); ret = vmwgfx_claim_stream(port->drm_fd, &port->streamId); if (ret != 0) return XvBadAlloc; w = width; h = height; /* init all the format attributes, used for buffers */ port->size = vmw_xv_query_image_attributes(pScrn, format, &w, &h, port->pitches, port->offsets); if (port->size == -1) { ret = XvBadAlloc; goto out_bad_size; } for (i = 0; i < VMWARE_VID_NUM_BUFFERS; ++i) { ret = vmw_video_buffer_alloc(port->drm_fd, port->size, &port->bufs[i]); if (ret != Success) goto out_no_buffer; } port->currBuf = 0; REGION_NULL(pScrn->pScreen, &port->clipBoxes); port->play = vmw_video_port_play; return port->play(pScrn, port, src_x, src_y, drw_x, drw_y, src_w, src_h, drw_w, drw_h, format, buf, width, height, clipBoxes, pDraw); out_no_buffer: while(i-- != 0) { vmw_video_buffer_free(&port->bufs[i]); } out_bad_size: (void) vmwgfx_unref_stream(port->drm_fd, port->streamId); return ret; } /* *----------------------------------------------------------------------------- * * vmw_video_port_play -- * * Sends all the attributes associated with the video frame using the * FIFO ESCAPE mechanism to the host. * * Results: * Always returns Success. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static int vmw_video_port_play(ScrnInfoPtr pScrn, struct vmwgfx_overlay_port *port, short src_x, short src_y, short drw_x, short drw_y, short src_w, short src_h, short drw_w, short drw_h, int format, unsigned char *buf, short width, short height, RegionPtr clipBoxes, DrawablePtr pDraw) { struct drm_vmw_control_stream_arg arg; unsigned short w, h; int size; int ret; debug_printf("\t%s: enter\n", __func__); w = width; h = height; /* we don't update the ports size */ size = vmw_xv_query_image_attributes(pScrn, format, &w, &h, port->pitches, port->offsets); if (size != port->size) { vmw_xv_stop_video(pScrn, port, TRUE); return port->play(pScrn, port, src_x, src_y, drw_x, drw_y, src_w, src_h, drw_w, drw_h, format, buf, width, height, clipBoxes, pDraw); } memcpy(port->bufs[port->currBuf].data, buf, port->size); memset(&arg, 0, sizeof(arg)); arg.stream_id = port->streamId; arg.enabled = TRUE; arg.flags = port->flags; arg.color_key = port->colorKey; arg.handle = port->bufs[port->currBuf].buf->handle; arg.format = format; arg.size = port->size; arg.width = w; arg.height = h; arg.src.x = src_x; arg.src.y = src_y; arg.src.w = src_w; arg.src.h = src_h; arg.dst.x = drw_x; arg.dst.y = drw_y; arg.dst.w = drw_w; arg.dst.h = drw_h; arg.pitch[0] = port->pitches[0]; arg.pitch[1] = port->pitches[1]; arg.pitch[2] = port->pitches[2]; arg.offset = 0; /* * Update the clipList and paint the colorkey, if required. */ if (!REGION_EQUAL(pScrn->pScreen, &port->clipBoxes, clipBoxes)) { REGION_COPY(pScrn->pScreen, &port->clipBoxes, clipBoxes); if (port->isAutoPaintColorkey) { if (pDraw->type == DRAWABLE_WINDOW) { xf86XVFillKeyHelperDrawable(pDraw, port->colorKey, clipBoxes); DamageDamageRegion(pDraw, clipBoxes); } else { xf86XVFillKeyHelper(pScrn->pScreen, port->colorKey, clipBoxes); } } } xorg_flush(pScrn->pScreen); ret = drmCommandWrite(port->drm_fd, DRM_VMW_CONTROL_STREAM, &arg, sizeof(arg)); if (ret) { vmw_video_port_cleanup(pScrn, port); return XvBadAlloc; } if (++(port->currBuf) >= VMWARE_VID_NUM_BUFFERS) port->currBuf = 0; return Success; } /* *----------------------------------------------------------------------------- * * vmw_video_port_cleanup -- * * Frees up all resources (if any) taken by a video stream. * * Results: * None. * * Side effects: * Same as above. * *----------------------------------------------------------------------------- */ static void vmw_video_port_cleanup(ScrnInfoPtr pScrn, struct vmwgfx_overlay_port *port) { int i; debug_printf("\t%s: enter\n", __func__); if (port->play == vmw_video_port_init) return; port->play = vmw_video_port_init; (void) vmwgfx_unref_stream(port->drm_fd, port->streamId); for (i = 0; i < VMWARE_VID_NUM_BUFFERS; i++) { vmw_video_buffer_free(&port->bufs[i]); } REGION_UNINIT(pScreen->pScreen, &port->clipBoxes); } /* *----------------------------------------------------------------------------- * * vmw_video_buffer_alloc -- * * Allocates and map a kernel buffer to be used as data storage. * * Results: * XvBadAlloc on failure, otherwise Success. * * Side effects: * Calls into the kernel, sets members of out. * *----------------------------------------------------------------------------- */ static int vmw_video_buffer_alloc(int drm_fd, int size, struct vmw_video_buffer *out) { out->buf = vmwgfx_dmabuf_alloc(drm_fd, size); if (!out->buf) return XvBadAlloc; out->data = vmwgfx_dmabuf_map(out->buf); if (!out->data) { vmwgfx_dmabuf_destroy(out->buf); out->buf = NULL; return XvBadAlloc; } out->size = size; debug_printf("\t\t%s: allocated buffer %p of size %i\n", __func__, out, size); return Success; } /* *----------------------------------------------------------------------------- * * vmw_video_buffer_free -- * * Frees and unmaps an allocated kernel buffer. * * Results: * Success. * * Side effects: * Calls into the kernel, sets members of out to 0. * *----------------------------------------------------------------------------- */ static int vmw_video_buffer_free(struct vmw_video_buffer *out) { if (out->size == 0) return Success; vmwgfx_dmabuf_unmap(out->buf); vmwgfx_dmabuf_destroy(out->buf); out->buf = NULL; out->data = NULL; out->size = 0; debug_printf("\t\t%s: freed buffer %p\n", __func__, out); return Success; } /* *----------------------------------------------------------------------------- * * vmw_xv_put_image -- * * Main video playback function. It copies the passed data which is in * the specified format (e.g. FOURCC_YV12) into the overlay. * * If sync is TRUE the driver should not return from this * function until it is through reading the data from buf. * * Results: * Success or XvBadAlloc on failure * * Side effects: * Video port will be played(initialized if 1st frame) on success * or will fail on error. * *----------------------------------------------------------------------------- */ static int vmw_xv_put_image(ScrnInfoPtr pScrn, short src_x, short src_y, short drw_x, short drw_y, short src_w, short src_h, short drw_w, short drw_h, int format, unsigned char *buf, short width, short height, Bool sync, RegionPtr clipBoxes, pointer data, DrawablePtr dst) { struct vmwgfx_overlay_port *port = data; debug_printf("%s: enter (%u, %u) (%ux%u) (%u, %u) (%ux%u) (%ux%u)\n", __func__, src_x, src_y, src_w, src_h, drw_x, drw_y, drw_w, drw_h, width, height); return port->play(pScrn, port, src_x, src_y, drw_x, drw_y, src_w, src_h, drw_w, drw_h, format, buf, width, height, clipBoxes, dst); } /* *----------------------------------------------------------------------------- * * vmw_xv_stop_video -- * * Called when we should stop playing video for a particular stream. If * Cleanup is FALSE, the "stop" operation is only temporary, and thus we * don't do anything. If Cleanup is TRUE we kill the video port by * sending a message to the host and freeing up the stream. * * Results: * None. * * Side effects: * See above. * *----------------------------------------------------------------------------- */ static void vmw_xv_stop_video(ScrnInfoPtr pScrn, pointer data, Bool cleanup) { struct vmwgfx_overlay_port *port = data; debug_printf("%s: cleanup is %s\n", __func__, cleanup ? "TRUE" : "FALSE"); REGION_EMPTY(pScrn->pScreen, &port->clipBoxes); if (!cleanup) return; vmw_video_port_cleanup(pScrn, port); } /* *----------------------------------------------------------------------------- * * vmw_xv_query_image_attributes -- * * From the spec: This function is called to let the driver specify how data * for a particular image of size width by height should be stored. * Sometimes only the size and corrected width and height are needed. In * that case pitches and offsets are NULL. * * Results: * The size of the memory required for the image, or -1 on error. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static int vmw_xv_query_image_attributes(ScrnInfoPtr pScrn, int format, unsigned short *width, unsigned short *height, int *pitches, int *offsets) { INT32 size, tmp; if (*width > VMWARE_VID_MAX_WIDTH) { *width = VMWARE_VID_MAX_WIDTH; } if (*height > VMWARE_VID_MAX_HEIGHT) { *height = VMWARE_VID_MAX_HEIGHT; } *width = (*width + 1) & ~1; if (offsets != NULL) { offsets[0] = 0; } switch (format) { case FOURCC_YV12: *height = (*height + 1) & ~1; size = (*width + 3) & ~3; if (pitches) { pitches[0] = size; } size *= *height; if (offsets) { offsets[1] = size; } tmp = ((*width >> 1) + 3) & ~3; if (pitches) { pitches[1] = pitches[2] = tmp; } tmp *= (*height >> 1); size += tmp; if (offsets) { offsets[2] = size; } size += tmp; break; case FOURCC_UYVY: case FOURCC_YUY2: size = *width * 2; if (pitches) { pitches[0] = size; } size *= *height; break; default: debug_printf("Query for invalid video format %d\n", format); return -1; } return size; } /* *----------------------------------------------------------------------------- * * vmw_xv_set_port_attribute -- * * From the spec: A port may have particular attributes such as colorKey, hue, * saturation, brightness or contrast. Xv clients set these * attribute values by sending attribute strings (Atoms) to the server. * * Results: * Success if the attribute exists and XvBadAlloc otherwise. * * Side effects: * The respective attribute gets the new value. * *----------------------------------------------------------------------------- */ static int vmw_xv_set_port_attribute(ScrnInfoPtr pScrn, Atom attribute, INT32 value, pointer data) { struct vmwgfx_overlay_port *port = data; Atom xvColorKey = MAKE_ATOM("XV_COLORKEY"); Atom xvAutoPaint = MAKE_ATOM("XV_AUTOPAINT_COLORKEY"); if (attribute == xvColorKey) { debug_printf("%s: Set colorkey:0x%x\n", __func__, (unsigned)value); port->colorKey = value; } else if (attribute == xvAutoPaint) { debug_printf("%s: Set autoPaint: %s\n", __func__, value? "TRUE": "FALSE"); port->isAutoPaintColorkey = value; } else { return XvBadAlloc; } return Success; } /* *----------------------------------------------------------------------------- * * vmw_xv_get_port_attribute -- * * From the spec: A port may have particular attributes such as hue, * saturation, brightness or contrast. Xv clients get these * attribute values by sending attribute strings (Atoms) to the server * * Results: * Success if the attribute exists and XvBadAlloc otherwise. * * Side effects: * "value" contains the requested attribute on success. * *----------------------------------------------------------------------------- */ static int vmw_xv_get_port_attribute(ScrnInfoPtr pScrn, Atom attribute, INT32 *value, pointer data) { struct vmwgfx_overlay_port *port = data; Atom xvColorKey = MAKE_ATOM("XV_COLORKEY"); Atom xvAutoPaint = MAKE_ATOM("XV_AUTOPAINT_COLORKEY"); if (attribute == xvColorKey) { *value = port->colorKey; } else if (attribute == xvAutoPaint) { *value = port->isAutoPaintColorkey; } else { return XvBadAlloc; } return Success; } /* *----------------------------------------------------------------------------- * * vmw_xv_query_best_size -- * * From the spec: QueryBestSize provides the client with a way to query what * the destination dimensions would end up being if they were to request * that an area vid_w by vid_h from the video stream be scaled to rectangle * of drw_w by drw_h on the screen. Since it is not expected that all * hardware will be able to get the target dimensions exactly, it is * important that the driver provide this function. * * This function seems to never be called, but to be on the safe side * we apply the same logic that QueryImageAttributes has for width * and height. * * Results: * None. * * Side effects: * None. * *----------------------------------------------------------------------------- */ static void vmw_xv_query_best_size(ScrnInfoPtr pScrn, Bool motion, short vid_w, short vid_h, short drw_w, short drw_h, unsigned int *p_w, unsigned int *p_h, pointer data) { *p_w = (drw_w + 1) & ~1; *p_h = drw_h; return; }