VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-svga.cpp@ 67954

Last change on this file since 67954 was 67759, checked in by vboxsync, 7 years ago

bugref:8533: Additions/x11: fully support VMSVGA
Use AcquireGuestCaps in the vmsvga VBoxClient service.

This change makes the VBoxClient vmsvga service use the new AcquireGuestCaps
function in the R3 guest library to prevent other clients waiting for
display events. We should change the driver to make this root-only.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.6 KB
Line 
1/* $Id: display-svga.cpp 67759 2017-07-03 14:07:23Z vboxsync $ */
2/** @file
3 * X11 guest client - VMSVGA emulation resize event pass-through to guest
4 * driver.
5 */
6
7/*
8 * Copyright (C) 2016 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19/*
20 * Known things to test when changing this code. All assume a guest with VMSVGA
21 * active and controlled by X11 or Wayland, and Guest Additions installed and
22 * running, unless otherwise stated.
23 * - On Linux 4.6 and later guests, VBoxClient --vmsvga should be running as
24 * root and not as the logged-in user. Dynamic resizing should work for all
25 * screens in any environment which handles kernel resize notifications,
26 * including at log-in screens. Test GNOME Shell Wayland and GNOME Shell
27 * under X.Org or Unity or KDE at the log-in screen and after log-in.
28 * - Linux 4.10 changed the user-kernel-ABI introduced in 4.6: test both.
29 * - On other guests (than Linux 4.6 or later) running X.Org Server 1.3 or
30 * later, VBoxClient --vmsvga should never be running as root, and should run
31 * (and dynamic resizing and screen enable/disable should work for all
32 * screens) whenever a user is logged in to a supported desktop environment.
33 * - On guests running X.Org Server 1.2 or older, VBoxClient --vmsvga should
34 * never run as root and should run whenever a user is logged in to a
35 * supported desktop environment. Dynamic resizing should work for the first
36 * screen, and enabling others should not be possible.
37 * - When VMSVGA is not enabled, VBoxClient --vmsvga should never stay running.
38 */
39
40#include "VBoxClient.h"
41
42#include <VBox/VBoxGuestLib.h>
43
44#include <iprt/assert.h>
45#include <iprt/file.h>
46#include <iprt/string.h>
47
48/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
49#define VMW_MAX_HEADS 32
50
51/* VMWare kernel driver control parts definitions. */
52
53#ifdef RT_OS_LINUX
54# include <sys/ioctl.h>
55#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */
56# include <sys/ioccom.h>
57#endif
58
59#define DRM_DRIVER_NAME "vmwgfx"
60
61/** DRM version structure. */
62struct DRMVERSION
63{
64 int cMajor;
65 int cMinor;
66 int cPatchLevel;
67 size_t cbName;
68 char *pszName;
69 size_t cbDate;
70 char *pszDate;
71 size_t cbDescription;
72 char *pszDescription;
73};
74AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *));
75
76/** Rectangle structure for geometry of a single screen. */
77struct DRMVMWRECT
78{
79 int32_t x;
80 int32_t y;
81 uint32_t w;
82 uint32_t h;
83};
84AssertCompileSize(struct DRMVMWRECT, 16);
85
86#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION)
87
88struct DRMCONTEXT
89{
90 RTFILE hDevice;
91};
92
93static void drmConnect(struct DRMCONTEXT *pContext)
94{
95 unsigned i;
96 RTFILE hDevice;
97
98 if (pContext->hDevice != NIL_RTFILE)
99 VBClFatalError(("%s called with bad argument\n", __func__));
100 /* Try to open the SVGA DRM device. */
101 for (i = 0; i < 128; ++i)
102 {
103 char szPath[64];
104 struct DRMVERSION version;
105 char szName[sizeof(DRM_DRIVER_NAME)];
106 int rc;
107
108 /* Control devices for drm graphics driver control devices go from
109 * controlD64 to controlD127. Render node devices go from renderD128
110 * to renderD192. The driver takes resize hints via the control device
111 * on pre-4.10 kernels and on the render device on newer ones. Try
112 * both types. */
113 if (i % 2 == 0)
114 rc = RTStrPrintf(szPath, sizeof(szPath), "/dev/dri/renderD%u", i / 2 + 128);
115 else
116 rc = RTStrPrintf(szPath, sizeof(szPath), "/dev/dri/controlD%u", i / 2 + 64);
117 if (RT_FAILURE(rc))
118 VBClFatalError(("RTStrPrintf of device path failed, rc=%Rrc\n", rc));
119 rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
120 if (RT_FAILURE(rc))
121 continue;
122 RT_ZERO(version);
123 version.cbName = sizeof(szName);
124 version.pszName = szName;
125 rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &version, sizeof(version), NULL);
126 if ( RT_SUCCESS(rc)
127 && !strncmp(szName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1)
128 && ( version.cMajor > 2
129 || (version.cMajor == 2 && version.cMinor > 9)))
130 break;
131 hDevice = NIL_RTFILE;
132 }
133 pContext->hDevice = hDevice;
134}
135
136/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The
137 * rects argument is a cast pointer to an array of drm_vmw_rect. */
138struct DRMVMWUPDATELAYOUT {
139 uint32_t cOutputs;
140 uint32_t u32Pad;
141 uint64_t ptrRects;
142};
143AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16);
144
145#define DRM_IOCTL_VMW_UPDATE_LAYOUT \
146 _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT)
147
148static void drmSendHints(struct DRMCONTEXT *pContext, struct DRMVMWRECT *paRects,
149 unsigned cHeads)
150{
151 int rc;
152 struct DRMVMWUPDATELAYOUT ioctlLayout;
153
154 if (pContext->hDevice == NIL_RTFILE)
155 VBClFatalError(("%s bad device argument.\n", __func__));
156 ioctlLayout.cOutputs = cHeads;
157 ioctlLayout.ptrRects = (uint64_t)paRects;
158 rc = RTFileIoCtl(pContext->hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT,
159 &ioctlLayout, sizeof(ioctlLayout), NULL);
160 if (RT_FAILURE(rc) && rc != VERR_INVALID_PARAMETER)
161 VBClFatalError(("Failure updating layout, rc=%Rrc\n", rc));
162}
163
164/* VMWare X.Org driver control parts definitions. */
165
166#include <X11/Xlibint.h>
167
168struct X11CONTEXT
169{
170 Display *pDisplay;
171 int hRandRMajor;
172 int hVMWMajor;
173};
174
175static void x11Connect(struct X11CONTEXT *pContext)
176{
177 int dummy;
178
179 if (pContext->pDisplay != NULL)
180 VBClFatalError(("%s called with bad argument\n", __func__));
181 pContext->pDisplay = XOpenDisplay(NULL);
182 if (pContext->pDisplay == NULL)
183 return;
184 if ( !XQueryExtension(pContext->pDisplay, "RANDR",
185 &pContext->hRandRMajor, &dummy, &dummy)
186 || !XQueryExtension(pContext->pDisplay, "VMWARE_CTRL",
187 &pContext->hVMWMajor, &dummy, &dummy))
188 {
189 XCloseDisplay(pContext->pDisplay);
190 pContext->pDisplay = NULL;
191 }
192}
193
194#define X11_VMW_TOPOLOGY_REQ 2
195struct X11VMWRECT /* xXineramaScreenInfo in Xlib headers. */
196{
197 int16_t x;
198 int16_t y;
199 uint16_t w;
200 uint16_t h;
201};
202AssertCompileSize(struct X11VMWRECT, 8);
203
204struct X11REQHEADER
205{
206 uint8_t hMajor;
207 uint8_t idType;
208 uint16_t cd;
209};
210
211struct X11VMWTOPOLOGYREQ
212{
213 struct X11REQHEADER header;
214 uint32_t idX11Screen;
215 uint32_t cScreens;
216 uint32_t u32Pad;
217 struct X11VMWRECT aRects[1];
218};
219AssertCompileSize(struct X11VMWTOPOLOGYREQ, 24);
220
221#define X11_VMW_TOPOLOGY_REPLY_SIZE 32
222
223#define X11_VMW_RESOLUTION_REQUEST 1
224struct X11VMWRESOLUTIONREQ
225{
226 struct X11REQHEADER header;
227 uint32_t idX11Screen;
228 uint32_t x;
229 uint32_t y;
230};
231AssertCompileSize(struct X11VMWRESOLUTIONREQ, 16);
232
233#define X11_VMW_RESOLUTION_REPLY_SIZE 32
234
235#define X11_RANDR_GET_SCREEN_REQUEST 5
236struct X11RANDRGETSCREENREQ
237{
238 struct X11REQHEADER header;
239 uint32_t hWindow;
240};
241AssertCompileSize(struct X11RANDRGETSCREENREQ, 8);
242
243#define X11_RANDR_GET_SCREEN_REPLY_SIZE 32
244
245/* This was a macro in old Xlib versions and a function in newer ones; the
246 * display members touched by the macro were declared as ABI for compatibility
247 * reasons. To simplify building with different generations, we duplicate the
248 * code. */
249static void x11GetRequest(struct X11CONTEXT *pContext, uint8_t hMajor,
250 uint8_t idType, size_t cb, struct X11REQHEADER **ppReq)
251{
252 if (pContext->pDisplay->bufptr + cb > pContext->pDisplay->bufmax)
253 _XFlush(pContext->pDisplay);
254 if (pContext->pDisplay->bufptr + cb > pContext->pDisplay->bufmax)
255 VBClFatalError(("%s display buffer overflow.\n", __func__));
256 if (cb % 4 != 0)
257 VBClFatalError(("%s bad parameter.\n", __func__));
258 pContext->pDisplay->last_req = pContext->pDisplay->bufptr;
259 *ppReq = (struct X11REQHEADER *)pContext->pDisplay->bufptr;
260 (*ppReq)->hMajor = hMajor;
261 (*ppReq)->idType = idType;
262 (*ppReq)->cd = cb / 4;
263 pContext->pDisplay->bufptr += cb;
264 pContext->pDisplay->request++;
265}
266
267static void x11SendHints(struct X11CONTEXT *pContext, struct DRMVMWRECT *pRects,
268 unsigned cRects)
269{
270 unsigned i;
271 struct X11VMWTOPOLOGYREQ *pReqTopology;
272 uint8_t repTopology[X11_VMW_TOPOLOGY_REPLY_SIZE];
273 struct X11VMWRESOLUTIONREQ *pReqResolution;
274 uint8_t repResolution[X11_VMW_RESOLUTION_REPLY_SIZE];
275
276 if (!VALID_PTR(pContext->pDisplay))
277 VBClFatalError(("%s bad display argument.\n", __func__));
278 if (cRects == 0)
279 return;
280 /* Try a topology (multiple screen) request. */
281 x11GetRequest(pContext, pContext->hVMWMajor, X11_VMW_TOPOLOGY_REQ,
282 sizeof(struct X11VMWTOPOLOGYREQ)
283 + sizeof(struct X11VMWRECT) * (cRects - 1),
284 (struct X11REQHEADER **)&pReqTopology);
285 pReqTopology->idX11Screen = DefaultScreen(pContext->pDisplay);
286 pReqTopology->cScreens = cRects;
287 for (i = 0; i < cRects; ++i)
288 {
289 pReqTopology->aRects[i].x = pRects[i].x;
290 pReqTopology->aRects[i].y = pRects[i].y;
291 pReqTopology->aRects[i].w = pRects[i].w;
292 pReqTopology->aRects[i].h = pRects[i].h;
293 }
294 _XSend(pContext->pDisplay, NULL, 0);
295 if (_XReply(pContext->pDisplay, (xReply *)&repTopology, 0, xTrue))
296 return;
297 /* That failed, so try the old single screen set resolution. We prefer
298 * simpler code to negligeably improved efficiency, so we just always try
299 * both requests instead of doing version checks or caching. */
300 x11GetRequest(pContext, pContext->hVMWMajor, X11_VMW_RESOLUTION_REQUEST,
301 sizeof(struct X11VMWTOPOLOGYREQ),
302 (struct X11REQHEADER **)&pReqResolution);
303 pReqResolution->idX11Screen = DefaultScreen(pContext->pDisplay);
304 pReqResolution->x = pRects[0].x;
305 pReqResolution->y = pRects[0].y;
306 if (_XReply(pContext->pDisplay, (xReply *)&repResolution, 0, xTrue))
307 return;
308 /* What now? */
309 VBClFatalError(("%s failed to set resolution\n", __func__));
310}
311
312/** Call RRGetScreenInfo to wake up the server to the new modes. */
313static void x11GetScreenInfo(struct X11CONTEXT *pContext)
314{
315 struct X11RANDRGETSCREENREQ *pReqGetScreen;
316 uint8_t repGetScreen[X11_RANDR_GET_SCREEN_REPLY_SIZE];
317
318 if (!VALID_PTR(pContext->pDisplay))
319 VBClFatalError(("%s bad display argument.\n", __func__));
320 x11GetRequest(pContext, pContext->hRandRMajor, X11_RANDR_GET_SCREEN_REQUEST,
321 sizeof(struct X11RANDRGETSCREENREQ),
322 (struct X11REQHEADER **)&pReqGetScreen);
323 pReqGetScreen->hWindow = DefaultRootWindow(pContext->pDisplay);
324 _XSend(pContext->pDisplay, NULL, 0);
325 if (!_XReply(pContext->pDisplay, (xReply *)&repGetScreen, 0, xTrue))
326 VBClFatalError(("%s failed to set resolution\n", __func__));
327}
328
329static const char *getPidFilePath()
330{
331 return ".vboxclient-display-svga.pid";
332}
333
334static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
335{
336 (void)ppInterface;
337 (void)fDaemonised;
338 struct DRMCONTEXT drmContext = { NIL_RTFILE };
339 struct X11CONTEXT x11Context = { NULL };
340 unsigned i;
341 int rc;
342 uint32_t acx[VMW_MAX_HEADS] = { 0 };
343 uint32_t acy[VMW_MAX_HEADS] = { 0 };
344 uint32_t adx[VMW_MAX_HEADS] = { 0 };
345 uint32_t ady[VMW_MAX_HEADS] = { 0 };
346 uint32_t afEnabled[VMW_MAX_HEADS] = { false };
347 struct DRMVMWRECT aRects[VMW_MAX_HEADS];
348 unsigned cHeads;
349
350 drmConnect(&drmContext);
351 if (drmContext.hDevice == NIL_RTFILE)
352 {
353 x11Connect(&x11Context);
354 if (x11Context.pDisplay == NULL)
355 return VINF_SUCCESS;
356 }
357 /* Initialise the guest library. */
358 rc = VbglR3InitUser();
359 if (RT_FAILURE(rc))
360 VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc));
361 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
362 if (RT_FAILURE(rc))
363 VBClFatalError(("Failed to request display change events, rc=%Rrc\n", rc));
364 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
365 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
366 return VINF_SUCCESS;
367 if (RT_FAILURE(rc))
368 VBClFatalError(("Failed to register resizing support, rc=%Rrc\n", rc));
369 for (;;)
370 {
371 uint32_t events;
372
373 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events);
374 if (RT_FAILURE(rc))
375 VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc));
376 while (rc != VERR_TIMEOUT)
377 {
378 uint32_t cx, cy, cBits, dx, dy, idx;
379 bool fEnabled, fChangeOrigin;
380
381 rc = VbglR3GetDisplayChangeRequest(&cx, &cy, &cBits, &idx, &dx, &dy, &fEnabled, &fChangeOrigin, true);
382 if (RT_FAILURE(rc))
383 VBClFatalError(("Failed to get display change request, rc=%Rrc\n", rc));
384 if (idx < VMW_MAX_HEADS)
385 {
386 acx[idx] = cx;
387 acy[idx] = cy;
388 if (fChangeOrigin)
389 adx[idx] = dx < INT32_MAX ? dx : 0;
390 if (fChangeOrigin)
391 ady[idx] = dy < INT32_MAX ? dy : 0;
392 afEnabled[idx] = fEnabled;
393 }
394 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0, &events);
395 if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
396 VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc));
397 }
398 for (i = 0, cHeads = 0; i < VMW_MAX_HEADS; ++i)
399 {
400 if (afEnabled[i])
401 {
402 aRects[cHeads].x = (int32_t)adx[i];
403 aRects[cHeads].y = (int32_t)ady[i];
404 aRects[cHeads].w = acx[i];
405 aRects[cHeads].h = acy[i];
406 ++cHeads;
407 }
408 }
409 if (drmContext.hDevice != NIL_RTFILE)
410 drmSendHints(&drmContext, aRects, cHeads);
411 else
412 {
413 x11SendHints(&x11Context, aRects, cHeads);
414 x11GetScreenInfo(&x11Context);
415 }
416 }
417}
418
419struct VBCLSERVICE interface =
420{
421 getPidFilePath,
422 VBClServiceDefaultHandler, /* Init */
423 run,
424 VBClServiceDefaultCleanup
425}, *pInterface = &interface;
426
427struct VBCLSERVICE **VBClDisplaySVGAService()
428{
429 return &pInterface;
430}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette