VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/display-drm.cpp@ 92370

Last change on this file since 92370 was 92370, checked in by vboxsync, 3 years ago

Additions: Linux: VBoxDRMClient: fix resizing issue on kernels <= 4.18, bugref:10134.

This commit fixes resizing issue on kernels older than 4.18 letting VBoxDRMClient to pick
up /dev/dri/controlDxx device instead of /dev/dri/renderDxx one. It also provides a bit of
refactoring and documentation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.3 KB
Line 
1/* $Id: display-drm.cpp 92370 2021-11-11 14:07:41Z vboxsync $ */
2/** @file
3 * X11 guest client - VMSVGA emulation resize event pass-through to drm guest
4 * driver.
5 */
6
7/*
8 * Copyright (C) 2016-2020 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/err.h>
47#include <iprt/string.h>
48#include <iprt/initterm.h>
49#include <iprt/message.h>
50#include <unistd.h>
51#include <stdio.h>
52#include <limits.h>
53
54#ifdef RT_OS_LINUX
55# include <sys/ioctl.h>
56#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */
57# include <sys/ioccom.h>
58#endif
59
60/** Ioctl command to query vmwgfx version information. */
61#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION)
62/** Ioctl command to set new screen layout. */
63#define DRM_IOCTL_VMW_UPDATE_LAYOUT _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT)
64/** A driver name which identifies VMWare driver. */
65#define DRM_DRIVER_NAME "vmwgfx"
66/** VMWare driver compatible version number. On previous versions resizing does not seem work. */
67#define DRM_DRIVER_VERSION_MAJOR_MIN (2)
68#define DRM_DRIVER_VERSION_MINOR_MIN (10)
69
70/** VMWare char device driver minor numbers range. */
71#define VMW_CONTROL_DEVICE_MINOR_START (64)
72#define VMW_RENDER_DEVICE_MINOR_START (128)
73#define VMW_RENDER_DEVICE_MINOR_END (192)
74
75/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
76/** @todo if this ever changes, dynamically allocate resizeable arrays in the
77 * context structure. */
78#define VMW_MAX_HEADS (32)
79
80/** DRM version structure. */
81struct DRMVERSION
82{
83 int cMajor;
84 int cMinor;
85 int cPatchLevel;
86 size_t cbName;
87 char *pszName;
88 size_t cbDate;
89 char *pszDate;
90 size_t cbDescription;
91 char *pszDescription;
92};
93AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *));
94
95/** Rectangle structure for geometry of a single screen. */
96struct DRMVMWRECT
97{
98 int32_t x;
99 int32_t y;
100 uint32_t w;
101 uint32_t h;
102};
103AssertCompileSize(struct DRMVMWRECT, 16);
104
105/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The
106 * rects argument is a cast pointer to an array of drm_vmw_rect. */
107struct DRMVMWUPDATELAYOUT
108{
109 uint32_t cOutputs;
110 uint32_t u32Pad;
111 uint64_t ptrRects;
112};
113AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16);
114
115/** These two parameters are mostly unused. Defined here in order to satisfy linking requirements. */
116unsigned g_cRespawn = 0;
117unsigned g_cVerbosity = 0;
118
119/**
120 * Attempts to open DRM device by given path and check if it is
121 * compatible for screen resize.
122 *
123 * @return DRM device handle on success or NIL_RTFILE otherwise.
124 * @param szPathPattern Path name pattern to the DRM device.
125 * @param uInstance Driver / device instance.
126 */
127static RTFILE drmTryDevice(const char *szPathPattern, uint8_t uInstance)
128{
129 int rc = VERR_NOT_FOUND;
130 char szPath[PATH_MAX];
131 struct DRMVERSION vmwgfxVersion;
132 RTFILE hDevice = NIL_RTFILE;
133
134 RT_ZERO(szPath);
135 RT_ZERO(vmwgfxVersion);
136
137 rc = RTStrPrintf(szPath, sizeof(szPath), szPathPattern, uInstance);
138 if (RT_SUCCESS(rc))
139 {
140 rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
141 if (RT_SUCCESS(rc))
142 {
143 char szVmwgfxDriverName[sizeof(DRM_DRIVER_NAME)];
144 RT_ZERO(szVmwgfxDriverName);
145
146 vmwgfxVersion.cbName = sizeof(szVmwgfxDriverName);
147 vmwgfxVersion.pszName = szVmwgfxDriverName;
148
149 /* Query driver version information and check if it can be used for screen resizing. */
150 rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &vmwgfxVersion, sizeof(vmwgfxVersion), NULL);
151 if ( RT_SUCCESS(rc)
152 && strncmp(szVmwgfxDriverName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) == 0
153 && ( vmwgfxVersion.cMajor >= DRM_DRIVER_VERSION_MAJOR_MIN
154 || ( vmwgfxVersion.cMajor == DRM_DRIVER_VERSION_MAJOR_MIN
155 && vmwgfxVersion.cMinor >= DRM_DRIVER_VERSION_MINOR_MIN)))
156 {
157 VBClLogInfo("VBoxDRMClient: found compatible device: %s\n", szPath);
158 }
159 else
160 {
161 RTFileClose(hDevice);
162 hDevice = NIL_RTFILE;
163 rc = VERR_NOT_FOUND;
164 }
165 }
166 }
167 else
168 {
169 VBClLogError("VBoxDRMClient: unable to construct path to DRM device: %Rrc\n", rc);
170 }
171
172 return RT_SUCCESS(rc) ? hDevice : NIL_RTFILE;
173}
174
175/**
176 * Attempts to find and open DRM device to be used for screen resize.
177 *
178 * @return DRM device handle on success or NIL_RTFILE otherwise.
179 */
180static RTFILE drmOpenVmwgfx(void)
181{
182 /* Control devices for drm graphics driver control devices go from
183 * controlD64 to controlD127. Render node devices go from renderD128
184 * to renderD192. The driver takes resize hints via the control device
185 * on pre-4.10 (???) kernels and on the render device on newer ones.
186 * At first, try to find control device and render one if not found.
187 */
188 uint8_t i;
189 RTFILE hDevice = NIL_RTFILE;
190
191 /* Lookup control device. */
192 for (i = VMW_CONTROL_DEVICE_MINOR_START; i < VMW_RENDER_DEVICE_MINOR_START; i++)
193 {
194 hDevice = drmTryDevice("/dev/dri/controlD%u", i);
195 if (hDevice != NIL_RTFILE)
196 return hDevice;
197 }
198
199 /* Lookup render device. */
200 for (i = VMW_RENDER_DEVICE_MINOR_START; i <= VMW_RENDER_DEVICE_MINOR_END; i++)
201 {
202 hDevice = drmTryDevice("/dev/dri/renderD%u", i);
203 if (hDevice != NIL_RTFILE)
204 return hDevice;
205 }
206
207 VBClLogError("VBoxDRMClient: unable to find DRM device\n");
208
209 return hDevice;
210}
211
212static void drmSendHints(RTFILE hDevice, struct DRMVMWRECT *paRects, unsigned cHeads)
213{
214 uid_t curuid = getuid();
215 if (setreuid(0, 0) != 0)
216 perror("setreuid failed during drm ioctl.");
217 int rc;
218 struct DRMVMWUPDATELAYOUT ioctlLayout;
219
220 ioctlLayout.cOutputs = cHeads;
221 ioctlLayout.ptrRects = (uint64_t)paRects;
222 rc = RTFileIoCtl(hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT,
223 &ioctlLayout, sizeof(ioctlLayout), NULL);
224 if (RT_FAILURE(rc) && rc != VERR_INVALID_PARAMETER)
225 VBClLogFatalError("Failure updating layout, rc=%Rrc\n", rc);
226 if (setreuid(curuid, 0) != 0)
227 perror("reset of setreuid failed after drm ioctl.");
228}
229
230int main(int argc, char *argv[])
231{
232 RTFILE hDevice = NIL_RTFILE;
233 static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS];
234 unsigned cEnabledMonitors;
235 /* Do not acknowledge the first event we query for to pick up old events,
236 * e.g. from before a guest reboot. */
237 bool fAck = false;
238
239 /** The name and handle of the PID file. */
240 static const char szPidFile[RTPATH_MAX] = "/var/run/VBoxDRMClient";
241 RTFILE hPidFile;
242
243 int rc = RTR3InitExe(argc, &argv, 0);
244 if (RT_FAILURE(rc))
245 return RTMsgInitFailure(rc);
246
247 rc = VbglR3InitUser();
248 if (RT_FAILURE(rc))
249 VBClLogFatalError("VbglR3InitUser failed: %Rrc", rc);
250
251 /* Check PID file before attempting to initialize anything. */
252 rc = VbglR3PidFile(szPidFile, &hPidFile);
253 if (rc == VERR_FILE_LOCK_VIOLATION)
254 {
255 VBClLogInfo("VBoxDRMClient: already running, exiting\n");
256 return RTEXITCODE_SUCCESS;
257 }
258 else if (RT_FAILURE(rc))
259 {
260 VBClLogError("VBoxDRMClient: unable to lock PID file (%Rrc), exiting\n", rc);
261 return RTEXITCODE_FAILURE;
262 }
263
264 hDevice = drmOpenVmwgfx();
265 if (hDevice == NIL_RTFILE)
266 return VERR_OPEN_FAILED;
267
268 rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
269 if (RT_FAILURE(rc))
270 {
271 VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
272 return VERR_INVALID_HANDLE;
273 }
274 rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
275 if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
276 {
277 return VERR_RESOURCE_BUSY;
278 }
279 if (RT_FAILURE(rc))
280 {
281 VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
282 return VERR_INVALID_HANDLE;
283 }
284
285 for (;;)
286 {
287 uint32_t events;
288 struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
289 uint32_t cDisplaysOut;
290 /* Query the first size without waiting. This lets us e.g. pick up
291 * the last event before a guest reboot when we start again after. */
292 rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck);
293 fAck = true;
294 if (RT_FAILURE(rc))
295 VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
296 if (cDisplaysOut > VMW_MAX_HEADS)
297 VBClLogError("Display change request contained, rc=%Rrc\n", rc);
298 if (cDisplaysOut > 0)
299 {
300 for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
301 {
302 uint32_t idDisplay = aDisplays[i].idDisplay;
303 if (idDisplay >= VMW_MAX_HEADS)
304 continue;
305 aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags;
306 if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
307 {
308 if ((idDisplay == 0) || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
309 {
310 aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin;
311 aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin;
312 } else {
313 aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx;
314 aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin;
315 }
316 aMonitors[idDisplay].cx = aDisplays[i].cx;
317 aMonitors[idDisplay].cy = aDisplays[i].cy;
318 }
319 }
320 /* Create an dense (consisting of enabled monitors only) array to pass to DRM. */
321 cEnabledMonitors = 0;
322 struct DRMVMWRECT aEnabledMonitors[VMW_MAX_HEADS];
323 for (int j = 0; j < VMW_MAX_HEADS; ++j)
324 {
325 if (!(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
326 {
327 aEnabledMonitors[cEnabledMonitors].x = aMonitors[j].xOrigin;
328 aEnabledMonitors[cEnabledMonitors].y = aMonitors[j].yOrigin;
329 aEnabledMonitors[cEnabledMonitors].w = aMonitors[j].cx;
330 aEnabledMonitors[cEnabledMonitors].h = aMonitors[j].cy;
331 if (cEnabledMonitors > 0)
332 aEnabledMonitors[cEnabledMonitors].x = aEnabledMonitors[cEnabledMonitors - 1].x + aEnabledMonitors[cEnabledMonitors - 1].w;
333 ++cEnabledMonitors;
334 }
335 }
336 for (unsigned i = 0; i < cEnabledMonitors; ++i)
337 printf("Monitor %u: %dx%d, (%d, %d)\n", i, (int)aEnabledMonitors[i].w, (int)aEnabledMonitors[i].h,
338 (int)aEnabledMonitors[i].x, (int)aEnabledMonitors[i].y);
339 drmSendHints(hDevice, aEnabledMonitors, cEnabledMonitors);
340 }
341 do
342 {
343 rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, RT_INDEFINITE_WAIT, &events);
344 } while (rc == VERR_INTERRUPTED);
345 if (RT_FAILURE(rc))
346 VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc);
347 }
348
349 /** @todo this code never executed since we do not have yet a clean way to exit
350 * main event loop above. */
351
352 RTFileClose(hDevice);
353
354 VBClLogInfo("VBoxDRMClient: releasing PID file lock\n");
355 VbglR3ClosePidFile(szPidFile, hPidFile);
356
357 return 0;
358}
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