VirtualBox

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

Last change on this file since 67733 was 67379, checked in by vboxsync, 8 years ago

bugref:8533: Additions/x11: fully support VMSVGA
Support dynamic resizing with Linux 4.10 and later guests

Linux 4.10 changed the kernel-user-space-ABI used to tell the kernel driver
about new dynamic modes. This change makes VBoxClient work with the new ABI.
We do not test for kernel versions but just try both and see what works.

  • 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 67379 2017-06-13 15:27:41Z 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 int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
330{
331 (void)ppInterface;
332 (void)fDaemonised;
333 struct DRMCONTEXT drmContext = { NIL_RTFILE };
334 struct X11CONTEXT x11Context = { NULL };
335 unsigned i;
336 int rc;
337 uint32_t acx[VMW_MAX_HEADS] = { 0 };
338 uint32_t acy[VMW_MAX_HEADS] = { 0 };
339 uint32_t adx[VMW_MAX_HEADS] = { 0 };
340 uint32_t ady[VMW_MAX_HEADS] = { 0 };
341 uint32_t afEnabled[VMW_MAX_HEADS] = { false };
342 struct DRMVMWRECT aRects[VMW_MAX_HEADS];
343 unsigned cHeads;
344
345 drmConnect(&drmContext);
346 if (drmContext.hDevice == NIL_RTFILE)
347 {
348 x11Connect(&x11Context);
349 if (x11Context.pDisplay == NULL)
350 return VINF_SUCCESS;
351 }
352 /* Initialise the guest library. */
353 rc = VbglR3InitUser();
354 if (RT_FAILURE(rc))
355 VBClFatalError(("Failed to connect to the VirtualBox kernel service, rc=%Rrc\n", rc));
356 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
357 if (RT_FAILURE(rc))
358 VBClFatalError(("Failed to request display change events, rc=%Rrc\n", rc));
359 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
360 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
361 return VINF_SUCCESS;
362 if (RT_FAILURE(rc))
363 VBClFatalError(("Failed to register resizing support, rc=%Rrc\n", rc));
364 for (;;)
365 {
366 uint32_t events;
367
368 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events);
369 if (RT_FAILURE(rc))
370 VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc));
371 while (rc != VERR_TIMEOUT)
372 {
373 uint32_t cx, cy, cBits, dx, dy, idx;
374 bool fEnabled, fChangeOrigin;
375
376 rc = VbglR3GetDisplayChangeRequest(&cx, &cy, &cBits, &idx, &dx, &dy, &fEnabled, &fChangeOrigin, true);
377 if (RT_FAILURE(rc))
378 VBClFatalError(("Failed to get display change request, rc=%Rrc\n", rc));
379 if (idx < VMW_MAX_HEADS)
380 {
381 acx[idx] = cx;
382 acy[idx] = cy;
383 if (fChangeOrigin)
384 adx[idx] = dx < INT32_MAX ? dx : 0;
385 if (fChangeOrigin)
386 ady[idx] = dy < INT32_MAX ? dy : 0;
387 afEnabled[idx] = fEnabled;
388 }
389 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0, &events);
390 if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
391 VBClFatalError(("Failure waiting for event, rc=%Rrc\n", rc));
392 }
393 for (i = 0, cHeads = 0; i < VMW_MAX_HEADS; ++i)
394 {
395 if (afEnabled[i])
396 {
397 aRects[cHeads].x = (int32_t)adx[i];
398 aRects[cHeads].y = (int32_t)ady[i];
399 aRects[cHeads].w = acx[i];
400 aRects[cHeads].h = acy[i];
401 ++cHeads;
402 }
403 }
404 if (drmContext.hDevice != NIL_RTFILE)
405 drmSendHints(&drmContext, aRects, cHeads);
406 else
407 {
408 x11SendHints(&x11Context, aRects, cHeads);
409 x11GetScreenInfo(&x11Context);
410 }
411 }
412}
413
414struct VBCLSERVICE interface =
415{
416 NULL, /* No pidfile needed, as we use acquire capability for exclusion. */
417 VBClServiceDefaultHandler, /* Init */
418 run,
419 VBClServiceDefaultCleanup,
420 true /* fDaemonise */
421}, *pInterface = &interface;
422
423struct VBCLSERVICE **VBClDisplaySVGAService()
424{
425 return &pInterface;
426}
Note: See TracBrowser for help on using the repository browser.

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