1/*
2 * Copyright (C) 2006-2017 Oracle Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23#include <VBoxVideoGuest.h>
24#include <VBoxVideoVBE.h>
25#include <HGSMIChannels.h>
26
27#ifndef VBOX_GUESTR3XF86MOD
28# include <VBoxVideoIPRT.h>
29#endif
30
31/**
32 * Gets the count of virtual monitors attached to the guest via an HGSMI
33 * command
34 *
35 * @returns the right count on success or 1 on failure.
36 * @param  pCtx  the context containing the heap to use
37 */
38DECLHIDDEN(uint32_t) VBoxHGSMIGetMonitorCount(PHGSMIGUESTCOMMANDCONTEXT pCtx)
39{
40    /* Query the configured number of displays. */
41    uint32_t cDisplays = 0;
42    VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_MONITOR_COUNT, &cDisplays);
43    // LogFunc(("cDisplays = %d\n", cDisplays));
44    if (cDisplays == 0 || cDisplays > VBOX_VIDEO_MAX_SCREENS)
45        /* Host reported some bad value. Continue in the 1 screen mode. */
46        cDisplays = 1;
47    return cDisplays;
48}
49
50
51/**
52 * Returns the size of the video RAM in bytes.
53 *
54 * @returns the size
55 */
56DECLHIDDEN(uint32_t) VBoxVideoGetVRAMSize(void)
57{
58    /** @note A 32bit read on this port returns the VRAM size. */
59    return VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA);
60}
61
62
63/**
64 * Check whether this hardware allows the display width to have non-multiple-
65 * of-eight values.
66 *
67 * @returns true if any width is allowed, false otherwise.
68 */
69DECLHIDDEN(bool) VBoxVideoAnyWidthAllowed(void)
70{
71    unsigned DispiId;
72    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID);
73    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_ANYX);
74    DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
75    return (DispiId == VBE_DISPI_ID_ANYX);
76}
77
78
79/**
80 * Tell the host about how VRAM is divided up between each screen via an HGSMI
81 * command.  It is acceptable to specify identical data for each screen if
82 * they share a single framebuffer.
83 *
84 * @returns iprt status code, either VERR_NO_MEMORY or the status returned by
85 *          @a pfnFill
86 * @todo  What was I thinking of with that callback function?  It
87 *        would be much simpler to just pass in a structure in normal
88 *        memory and copy it.
89 * @param  pCtx      the context containing the heap to use
90 * @param  u32Count  the number of screens we are activating
91 * @param  pfnFill   a callback which initialises the VBVAINFOVIEW structures
92 *                   for all screens
93 * @param  pvData    context data for @a pfnFill
94 */
95DECLHIDDEN(int) VBoxHGSMISendViewInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx,
96                                      uint32_t u32Count,
97                                      PFNHGSMIFILLVIEWINFO pfnFill,
98                                      void *pvData)
99{
100    int rc;
101    /* Issue the screen info command. */
102    void *p = VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAINFOVIEW) * u32Count,
103                                   HGSMI_CH_VBVA, VBVA_INFO_VIEW);
104    if (p)
105    {
106        VBVAINFOVIEW *pInfo = (VBVAINFOVIEW *)p;
107        rc = pfnFill(pvData, pInfo, u32Count);
108        if (RT_SUCCESS(rc))
109            VBoxHGSMIBufferSubmit (pCtx, p);
110        VBoxHGSMIBufferFree(pCtx, p);
111    }
112    else
113        rc = VERR_NO_MEMORY;
114    return rc;
115}
116
117
118/**
119 * Set a video mode using port registers.  This must be done for the first
120 * screen before every HGSMI modeset and also works when HGSM is not enabled.
121 * @param  cWidth      the mode width
122 * @param  cHeight     the mode height
123 * @param  cVirtWidth  the mode pitch
124 * @param  cBPP        the colour depth of the mode
125 * @param  fFlags      flags for the mode.  These will be or-ed with the
126 *                     default _ENABLED flag, so unless you are restoring
127 *                     a saved mode or have special requirements you can pass
128 *                     zero here.
129 * @param  cx          the horizontal panning offset
130 * @param  cy          the vertical panning offset
131 */
132DECLHIDDEN(void) VBoxVideoSetModeRegisters(uint16_t cWidth, uint16_t cHeight,
133                                           uint16_t cVirtWidth, uint16_t cBPP,
134                                           uint16_t fFlags, uint16_t cx,
135                                           uint16_t cy)
136{
137    /* set the mode characteristics */
138    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_XRES);
139    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cWidth);
140    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_YRES);
141    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cHeight);
142    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
143                                VBE_DISPI_INDEX_VIRT_WIDTH);
144    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cVirtWidth);
145    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_BPP);
146    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cBPP);
147    /* enable the mode */
148    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
149                                VBE_DISPI_INDEX_ENABLE);
150    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA,
151                                fFlags | VBE_DISPI_ENABLED);
152    /* Panning registers */
153    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
154                                VBE_DISPI_INDEX_X_OFFSET);
155    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cx);
156    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
157                                VBE_DISPI_INDEX_Y_OFFSET);
158    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cy);
159    /** @todo read from the port to see if the mode switch was successful */
160}
161
162
163/**
164 * Get the video mode for the first screen using the port registers.  All
165 * parameters are optional
166 * @returns  true if the VBE mode returned is active, false if we are in VGA
167 *           mode
168 * @note  If anyone else needs additional register values just extend the
169 *        function with additional parameters and fix any existing callers.
170 * @param  pcWidth      where to store the mode width
171 * @param  pcHeight     where to store the mode height
172 * @param  pcVirtWidth  where to store the mode pitch
173 * @param  pcBPP        where to store the colour depth of the mode
174 * @param  pfFlags      where to store the flags for the mode
175 */
176DECLHIDDEN(bool) VBoxVideoGetModeRegisters(uint16_t *pcWidth, uint16_t *pcHeight,
177                                           uint16_t *pcVirtWidth, uint16_t *pcBPP,
178                                           uint16_t *pfFlags)
179{
180    uint16_t fFlags;
181
182    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
183                                VBE_DISPI_INDEX_ENABLE);
184    fFlags = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
185    if (pcWidth)
186    {
187        VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
188                                    VBE_DISPI_INDEX_XRES);
189        *pcWidth = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
190    }
191    if (pcHeight)
192    {
193        VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
194                                    VBE_DISPI_INDEX_YRES);
195        *pcHeight = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
196    }
197    if (pcVirtWidth)
198    {
199        VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
200                                    VBE_DISPI_INDEX_VIRT_WIDTH);
201        *pcVirtWidth = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
202    }
203    if (pcBPP)
204    {
205        VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
206                                    VBE_DISPI_INDEX_BPP);
207        *pcBPP = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
208    }
209    if (pfFlags)
210        *pfFlags = fFlags;
211    return !!(fFlags & VBE_DISPI_ENABLED);
212}
213
214
215/**
216 * Disable our extended graphics mode and go back to VGA mode.
217 */
218DECLHIDDEN(void) VBoxVideoDisableVBE(void)
219{
220    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX,
221                                VBE_DISPI_INDEX_ENABLE);
222    VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, 0);
223}
224
225
226/**
227 * Set a video mode via an HGSMI request.  The views must have been
228 * initialised first using @a VBoxHGSMISendViewInfo and if the mode is being
229 * set on the first display then it must be set first using registers.
230 * @param  pCtx      The context containing the heap to use.
231 * @param  cDisplay  the screen number
232 * @param  cOriginX  the horizontal displacement relative to the first screen
233 * @param  cOriginY  the vertical displacement relative to the first screen
234 * @param  offStart  the offset of the visible area of the framebuffer
235 *                   relative to the framebuffer start
236 * @param  cbPitch   the offset in bytes between the starts of two adjecent
237 *                   scan lines in video RAM
238 * @param  cWidth    the mode width
239 * @param  cHeight   the mode height
240 * @param  cBPP      the colour depth of the mode
241 * @param  fFlags    flags
242 */
243DECLHIDDEN(void) VBoxHGSMIProcessDisplayInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx,
244                                             uint32_t cDisplay,
245                                             int32_t  cOriginX,
246                                             int32_t  cOriginY,
247                                             uint32_t offStart,
248                                             uint32_t cbPitch,
249                                             uint32_t cWidth,
250                                             uint32_t cHeight,
251                                             uint16_t cBPP,
252                                             uint16_t fFlags)
253{
254    /* Issue the screen info command. */
255    void *p = VBoxHGSMIBufferAlloc(pCtx,
256                                   sizeof (VBVAINFOSCREEN),
257                                   HGSMI_CH_VBVA,
258                                   VBVA_INFO_SCREEN);
259    if (!p)
260    {
261        // LogFunc(("HGSMIHeapAlloc failed\n"));
262    }
263    else
264    {
265        VBVAINFOSCREEN *pScreen = (VBVAINFOSCREEN *)p;
266
267        pScreen->u32ViewIndex    = cDisplay;
268        pScreen->i32OriginX      = cOriginX;
269        pScreen->i32OriginY      = cOriginY;
270        pScreen->u32StartOffset  = offStart;
271        pScreen->u32LineSize     = cbPitch;
272        pScreen->u32Width        = cWidth;
273        pScreen->u32Height       = cHeight;
274        pScreen->u16BitsPerPixel = cBPP;
275        pScreen->u16Flags        = fFlags;
276
277        VBoxHGSMIBufferSubmit(pCtx, p);
278
279        VBoxHGSMIBufferFree(pCtx, p);
280    }
281}
282
283
284/** Report the rectangle relative to which absolute pointer events should be
285 *  expressed.  This information remains valid until the next VBVA resize event
286 *  for any screen, at which time it is reset to the bounding rectangle of all
287 *  virtual screens.
288 * @param  pCtx      The context containing the heap to use.
289 * @param  cOriginX  Upper left X coordinate relative to the first screen.
290 * @param  cOriginY  Upper left Y coordinate relative to the first screen.
291 * @param  cWidth    Rectangle width.
292 * @param  cHeight   Rectangle height.
293 * @returns  iprt status code.
294 * @returns  VERR_NO_MEMORY      HGSMI heap allocation failed.
295 */
296DECLHIDDEN(int)      VBoxHGSMIUpdateInputMapping(PHGSMIGUESTCOMMANDCONTEXT pCtx, int32_t  cOriginX, int32_t  cOriginY,
297                                                 uint32_t cWidth, uint32_t cHeight)
298{
299    int rc = VINF_SUCCESS;
300    VBVAREPORTINPUTMAPPING *p;
301    // Log(("%s: cOriginX=%d, cOriginY=%d, cWidth=%u, cHeight=%u\n", __PRETTY_FUNCTION__, (int)cOriginX, (int)cOriginX,
302    //      (unsigned)cWidth, (unsigned)cHeight));
303
304    /* Allocate the IO buffer. */
305    p = (VBVAREPORTINPUTMAPPING *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAREPORTINPUTMAPPING), HGSMI_CH_VBVA,
306                                                       VBVA_REPORT_INPUT_MAPPING);
307    if (p)
308    {
309        /* Prepare data to be sent to the host. */
310        p->x  = cOriginX;
311        p->y  = cOriginY;
312        p->cx = cWidth;
313        p->cy = cHeight;
314        rc = VBoxHGSMIBufferSubmit(pCtx, p);
315        /* Free the IO buffer. */
316        VBoxHGSMIBufferFree(pCtx, p);
317    }
318    else
319        rc = VERR_NO_MEMORY;
320    // LogFunc(("rc = %d\n", rc));
321    return rc;
322}
323
324
325/**
326 * Get most recent video mode hints.
327 * @param  pCtx      the context containing the heap to use
328 * @param  cScreens  the number of screens to query hints for, starting at 0.
329 * @param  paHints   array of VBVAMODEHINT structures for receiving the hints.
330 * @returns  iprt status code
331 * @returns  VERR_NO_MEMORY      HGSMI heap allocation failed.
332 * @returns  VERR_NOT_SUPPORTED  Host does not support this command.
333 */
334DECLHIDDEN(int) VBoxHGSMIGetModeHints(PHGSMIGUESTCOMMANDCONTEXT pCtx,
335                                      unsigned cScreens, VBVAMODEHINT *paHints)
336{
337    int rc;
338    void *p;
339
340    AssertPtr(paHints);
341    if (!paHints)
342        return VERR_INVALID_POINTER;
343
344    p = VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAQUERYMODEHINTS)
345                                       + cScreens * sizeof(VBVAMODEHINT),
346                             HGSMI_CH_VBVA, VBVA_QUERY_MODE_HINTS);
347    if (!p)
348    {
349        // LogFunc(("HGSMIHeapAlloc failed\n"));
350        return VERR_NO_MEMORY;
351    }
352    else
353    {
354        VBVAQUERYMODEHINTS *pQuery   = (VBVAQUERYMODEHINTS *)p;
355
356        pQuery->cHintsQueried        = cScreens;
357        pQuery->cbHintStructureGuest = sizeof(VBVAMODEHINT);
358        pQuery->rc                   = VERR_NOT_SUPPORTED;
359
360        VBoxHGSMIBufferSubmit(pCtx, p);
361        rc = pQuery->rc;
362        if (RT_SUCCESS(rc))
363            memcpy(paHints, ((uint8_t *)p) + sizeof(VBVAQUERYMODEHINTS),
364                   cScreens * sizeof(VBVAMODEHINT));
365
366        VBoxHGSMIBufferFree(pCtx, p);
367    }
368    return rc;
369}
370
371
372/**
373 * Query the supported flags in VBVAINFOSCREEN::u16Flags.
374 *
375 * @returns The mask of VBVA_SCREEN_F_* flags or 0 if host does not support the request.
376 * @param  pCtx  the context containing the heap to use
377 */
378DECLHIDDEN(uint16_t) VBoxHGSMIGetScreenFlags(PHGSMIGUESTCOMMANDCONTEXT pCtx)
379{
380    uint32_t u32Flags = 0;
381    int rc = VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_SCREEN_FLAGS, &u32Flags);
382    // LogFunc(("u32Flags = 0x%x rc %Rrc\n", u32Flags, rc));
383    if (RT_FAILURE(rc) || u32Flags > UINT16_MAX)
384        u32Flags = 0;
385    return (uint16_t)u32Flags;
386}
387