Commit 13f7aac9 authored by Drew DeVault's avatar Drew DeVault
Browse files

Xwayland: add DRM leasing support

This is based on the unstable DRM leasing protocol currently under
review on wayland-devel. One unfortunate problem with this approach is
that we have to open the DRM node ourselves and enumerate the modes of
the connectors available for lease. We'll be able to refactor this
somewhat once the kernel supports zero-object DRM leases, which I intend
to implement in the near future.

This takes the approach suggested by Pekka in which no attempt is made
to correlate leasable connectors with a wl_output/xdg_output.
Accordingly, this also assumes that all leasable connectors are
non-desktop outputs.

Successfully tested with xrgears -w direct and SteamVR on an HTC Vive.
parent 18d3131f
Pipeline #401721 failed with stages
in 8 minutes and 8 seconds
......@@ -95,6 +95,8 @@ Xwayland_DEPENDENCIES = $(glamor_lib) $(XWAYLAND_LIBS)
endif
Xwayland_built_sources += \
drm-lease-unstable-v1-client-protocol.h \
drm-lease-unstable-v1-protocol.c \
relative-pointer-unstable-v1-client-protocol.h \
relative-pointer-unstable-v1-protocol.c \
pointer-constraints-unstable-v1-client-protocol.h \
......@@ -133,6 +135,11 @@ $(Xwayland_SOURCES): $(Xwayland_built_sources)
relink:
$(AM_V_at)rm -f Xwayland$(EXEEXT) && $(MAKE) Xwayland$(EXEEXT)
drm-lease-unstable-v1-protocol.c : $(srcdir)/drm-lease-unstable-v1.xml
$(AM_V_GEN)$(WAYLAND_SCANNER) @SCANNER_ARG@ < $< > $@
drm-lease-unstable-v1-client-protocol.h : $(srcdir)/drm-lease-unstable-v1.xml
$(AM_V_GEN)$(WAYLAND_SCANNER) client-header < $< > $@
relative-pointer-unstable-v1-protocol.c : $(WAYLAND_PROTOCOLS_DATADIR)/unstable/relative-pointer/relative-pointer-unstable-v1.xml
$(AM_V_GEN)$(WAYLAND_SCANNER) @SCANNER_ARG@ < $< > $@
relative-pointer-unstable-v1-client-protocol.h : $(WAYLAND_PROTOCOLS_DATADIR)/unstable/relative-pointer/relative-pointer-unstable-v1.xml
......
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="drm_lease_v1">
<copyright>
Copyright © 2018 NXP
Copyright © 2019 Status Research &amp; Development GmbH.
Copyright © 2021 Xaver Hugl
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 (including the next
paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
</copyright>
<interface name="wp_drm_lease_device_v1" version="1">
<description summary="lease device">
This protocol is used by Wayland compositors which act as Direct
Renderering Manager (DRM) masters to lease DRM resources to Wayland
clients.
The compositor will advertise one wp_drm_lease_device_v1 global for each
DRM node. Some time after a client binds to the wp_drm_lease_device_v1
global, the compositor will send a drm_fd event followed by zero, one or
more connector events.
The compositor will indicate when a device is gone by removing the global
via a wl_registry.global_remove event. Upon receiving this event, the
client should destroy any matching wp_drm_lease_device_v1 object.
To destroy a wp_drm_lease_device_v1 object, the client must first issue
a release request. Upon receiving this request, the compositor will
immediately send a released event and destroy the object. The client must
continue to process and discard drm_fd and connector events until it
receives the released event. Upon receiving the released event, the
client can safely cleanup any client-side resources.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<request name="create_lease_request">
<description summary="create a lease request object">
Creates a lease request object.
See the documentation for wp_drm_lease_request_v1 for details.
</description>
<arg name="id" type="new_id" interface="wp_drm_lease_request_v1" />
</request>
<request name="release">
<description summary="release this object">
Indicates the client no longer wishes to use this object. In response
the compositor will immediately send the released event and destroy
this object. It can however not guarantee that the client won't receive
connector events before the released event. The client must not send any
requests after this one, doing so will raise a wl_display error.
Existing connectors, lease request and leases will not be affected.
</description>
</request>
<event name="drm_fd">
<description summary="open a non-master fd for this DRM node">
The compositor will send this event when the wp_drm_lease_device_v1
global is bound, although there are no guarantees as to how long this
takes - the compositor might need to wait until regaining DRM master.
The included fd is a non-master DRM file descriptor opened for this
device and the compositor must not authenticate it.
The purpose of this event is to give the client the ability to
query DRM and discover information which may help them pick the
appropriate DRM device or select the appropriate connectors therein.
</description>
<arg name="fd" type="fd" summary="DRM file descriptor" />
</event>
<event name="connector">
<description summary="advertise connectors available for leases">
The compositor will use this event to advertise connectors available for
lease by clients. This object may be passed into a lease request to
indicate the client would like to lease that connector, see
wp_drm_lease_request_v1.request_connector for details. While the
compositor will make a best effort to not send disconnected connectors,
no guarantees can be made.
The compositor must send the drm_fd event before sending connectors.
After the drm_fd event it will send all available connectors but may
send additional connectors at any time.
</description>
<arg name="id" type="new_id" interface="wp_drm_lease_connector_v1" />
</event>
<event name="done">
<description summary="signals grouping of connectors">
The compositor will send this event to indicate that it has sent all
currently available connectors after the client binds to the global or
when it updates the connector list, for example on hotplug, drm master
change or when a leased connector becomes available again. It will
similarly send this event to group wp_drm_lease_connector_v1.withdrawn
events of connectors of this device.
</description>
</event>
<event name="released">
<description summary="the compositor has finished using the device">
This event is sent in response to the release request and indicates
that the compositor is done sending connector events.
The compositor will destroy this object immediately after sending the
event and it will become invalid. The client should release any
resources associated with this device after receiving this event.
</description>
</event>
</interface>
<interface name="wp_drm_lease_connector_v1" version="1">
<description summary="a leasable DRM connector">
Represents a DRM connector which is available for lease. These objects are
created via wp_drm_lease_device_v1.connector events, and should be passed
to lease requests via wp_drm_lease_request_v1.request_connector.
</description>
<event name="name">
<description summary="name">
The compositor sends this event once the connector is created to
indicate the name of this connector. This will not change for the
duration of the Wayland session, but is not guaranteed to be consistent
between sessions.
</description>
<arg name="name" type="string" summary="connector name" />
</event>
<event name="description">
<description summary="description">
The compositor sends this event once the connector is created to provide
a human-readable description for this connector, which may be presented
to the user. The compositor may send this event multiple times over the
lifetime of this object to reflect changes in the description.
</description>
<arg name="description" type="string" summary="connector description" />
</event>
<event name="connector_id">
<description summary="connector_id">
The compositor sends this event once the connector is created to
indicate the DRM ID which represents the underlying connector that is
being offered. Note that the final lease may include additional object
IDs, such as CRTCs and planes.
</description>
<arg name="connector_id" type="uint" summary="DRM Connector ID" />
</event>
<event name="done">
<description summary="all properties have been sent">
This event is sent after all properties of a connector have been sent.
This allows changes to the properties to be seen as atomic even if they
happen via multiple events.
</description>
</event>
<event name="withdrawn">
<description summary="lease offer withdrawn">
Sent to indicate that the compositor will no longer honor requests for
DRM leases which include this connector. The client may still issue a
lease request including this connector, but the compositor will send
wp_drm_lease_v1.finished without issuing a lease fd. Compositors are
encouraged to send this event when they lose access to connector, for
example when the connector is hot-unplugged, when the connector gets
leased to a client or when the compositor loses DRM master.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy connector">
The client may send this request to indicate that it will not use this
connector. Clients are encouraged to send this after receiving the
"withdrawn" event so that the server can release the resources
associated with this connector offer. Neither existing lease requests
nor leases will be affected.
</description>
</request>
</interface>
<interface name="wp_drm_lease_request_v1" version="1">
<description summary="DRM lease request">
A client that wishes to lease DRM resources will attach the list of
connectors advertised with wp_drm_lease_device_v1.connector that they
wish to lease, then use wp_drm_lease_request_v1.submit to submit the
request.
</description>
<enum name="error">
<entry name="wrong_device" value="0"
summary="requested a connector from a different lease device"/>
<entry name="duplicate_connector" value="1"
summary="requested a connector twice"/>
<entry name="empty_lease" value="2"
summary="requested a lease without requesting a connector"/>
</enum>
<request name="request_connector">
<description summary="request a connector for this lease">
Indicates that the client would like to lease the given connector.
This is only used as a suggestion, the compositor may choose to
include any resources in the lease it issues, or change the set of
leased resources at any time. Compositors are however encouraged to
include the requested connector and other resources necessary
to drive the connected output in the lease.
Requesting a connector that was created from a different lease device
than this lease request raises the wrong_device error. Requesting a
connector twice will raise the duplicate_connector error.
</description>
<arg name="connector" type="object"
interface="wp_drm_lease_connector_v1" />
</request>
<request name="submit" type="destructor">
<description summary="submit the lease request">
Submits the lease request and creates a new wp_drm_lease_v1 object.
After calling submit the compositor will immediately destroy this
object, issuing any more requests will cause a wl_diplay error.
The compositor doesn't make any guarantees about the events of the
lease object, clients cannot expect an immediate response.
Not requesting any connectors before submitting the lease request
will raise the empty_lease error.
</description>
<arg name="id" type="new_id" interface="wp_drm_lease_v1" />
</request>
</interface>
<interface name="wp_drm_lease_v1" version="1">
<description summary="a DRM lease">
A DRM lease object is used to transfer the DRM file descriptor to the
client and manage the lifetime of the lease.
Some time after the wp_drm_lease_v1 object is created, the compositor
will reply with the lease request's result. If the lease request is
granted, the compositor will send a lease_fd event. If the lease request
is denied, the compositor will send a finished event without a lease_fd
event.
</description>
<event name="lease_fd">
<description summary="shares the DRM file descriptor">
This event returns a file descriptor suitable for use with DRM-related
ioctls. The client should use drmModeGetLease to enumerate the DRM
objects which have been leased to them. The compositor guarantees it
will not use the leased DRM objects itself until it sends the finished
event. If the compositor cannot or will not grant a lease for the
requested connectors, it will not send this event, instead sending the
finished event.
The compositor will send this event at most once during this objects
lifetime.
</description>
<arg name="leased_fd" type="fd" summary="leased DRM file descriptor" />
</event>
<event name="finished">
<description summary="sent when the lease has been revoked">
The compositor uses this event to either reject a lease request, or if
it previously sent a lease_fd, to notify the client that the lease has
been revoked. If the client requires a new lease, they should destroy
this object and submit a new lease request. The compositor will send
no further events for this object after sending the finish event.
Compositors should revoke the lease when any of the leased resources
become unavailable, namely when a hot-unplug occurs or when the
compositor loses DRM master.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroys the lease object">
The client should send this to indicate that it no longer wishes to use
this lease. The compositor should use drmModeRevokeLease on the
appropriate file descriptor, if necessary.
</description>
</request>
</interface>
</protocol>
......@@ -108,6 +108,9 @@ if build_glamor
xwayland_glamor += glamor
endif
srcs += client_header.process('drm-lease-unstable-v1.xml')
srcs += code.process('drm-lease-unstable-v1.xml')
wayland_inc = [ inc, ]
if build_glx
wayland_inc += glx_inc
......
......@@ -25,7 +25,15 @@
#include <xwayland-config.h>
#ifdef WITH_LIBDRM
#include <xf86drm.h>
#include <xf86drmMode.h>
#endif
#include "X11/Xatom.h"
#include <fcntl.h>
#include <randrstr.h>
#include <sys/stat.h>
#include <X11/Xatom.h>
#include "xwayland-cvt.h"
......@@ -633,11 +641,12 @@ static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_handle_description,
};
static int output_name_serial;
struct xwl_output *
xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id)
{
struct xwl_output *xwl_output;
static int serial;
char name[256];
xwl_output = calloc(1, sizeof *xwl_output);
......@@ -656,7 +665,7 @@ xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id)
xwl_output->server_output_id = id;
wl_output_add_listener(xwl_output->output, &output_listener, xwl_output);
snprintf(name, sizeof name, "XWAYLAND%d", serial++);
snprintf(name, sizeof name, "XWAYLAND%d", output_name_serial++);
xwl_output->xwl_screen = xwl_screen;
xwl_output->randr_crtc = RRCrtcCreate(xwl_screen->screen, xwl_output);
......@@ -703,7 +712,8 @@ xwl_output_destroy(struct xwl_output *xwl_output)
{
if (xwl_output->xdg_output)
zxdg_output_v1_destroy(xwl_output->xdg_output);
wl_output_destroy(xwl_output->output);
if (xwl_output->output)
wl_output_destroy(xwl_output->output);
free(xwl_output);
}
......@@ -825,6 +835,97 @@ xwl_randr_mode_destroy(ScreenPtr pScreen, RRModePtr mode)
}
#endif
static void
drm_lease_handle_lease_fd(void *data,
struct wp_drm_lease_v1 *wp_drm_lease_v1,
int32_t leased_fd)
{
struct xwl_drm_lease *lease = (struct xwl_drm_lease *)data;
lease->answered = TRUE;
lease->fd = leased_fd;
}
static void
drm_lease_handle_finished(void *data,
struct wp_drm_lease_v1 *wp_drm_lease_v1)
{
struct xwl_drm_lease *lease = (struct xwl_drm_lease *)data;
lease->answered = TRUE;
if (lease->fd > 0) {
RRLeaseTerminated(lease->rrLease);
lease->fd = 0;
}
}
static struct wp_drm_lease_v1_listener drm_lease_listener = {
drm_lease_handle_lease_fd,
drm_lease_handle_finished,
};
static int
xwl_randr_create_lease(ScreenPtr screen, RRLeasePtr rrLease, int *fd)
{
struct xwl_screen *xwl_screen;
struct wp_drm_lease_request_v1 *req;
struct xwl_drm_lease *lease_private;
struct xwl_output *output;
int i;
xwl_screen = xwl_screen_get(screen);
if (xwl_screen->drm_lease_device == NULL) {
ErrorF("Attempted to create DRM lease without wp_drm_lease_device_v1");
return BadMatch;
}
req = wp_drm_lease_device_v1_create_lease_request(
xwl_screen->drm_lease_device);
for (i = 0; i < rrLease->numOutputs; ++i) {
output = rrLease->outputs[i]->devPrivate;
if (!output || !output->lease_connector) {
return BadValue;
}
wp_drm_lease_request_v1_request_connector(req, output->lease_connector);
}
lease_private = calloc(1, sizeof(struct xwl_drm_lease));
lease_private->lease = wp_drm_lease_request_v1_submit(req);
lease_private->rrLease = rrLease;
wp_drm_lease_v1_add_listener(lease_private->lease,
&drm_lease_listener, lease_private);
while (!lease_private->answered) {
/* Wait for compositor to send along some kind of response */
xwl_sync_events(xwl_screen);
}
if (lease_private->fd <= 0) {
ErrorF("Did not receive lease fd from Wayland server");
return BadValue;
}
rrLease->devPrivate = lease_private;
*fd = lease_private->fd;
return Success;
}
static void
xwl_randr_terminate_lease(ScreenPtr screen, RRLeasePtr lease)
{
struct xwl_drm_lease *lease_private = lease->devPrivate;
if (lease_private) {
wp_drm_lease_v1_destroy(lease_private->lease);
free(lease_private);
lease_private->fd = 0;
}
RRLeaseTerminated(lease);
}
Bool
xwl_screen_init_output(struct xwl_screen *xwl_screen)
{
......@@ -852,6 +953,9 @@ xwl_screen_init_output(struct xwl_screen *xwl_screen)
rp->rrModeDestroy = xwl_randr_mode_destroy;
#endif
rp->rrCreateLease = xwl_randr_create_lease;
rp->rrTerminateLease = xwl_randr_terminate_lease;
return TRUE;
}
......@@ -860,6 +964,12 @@ xwl_output_get_xdg_output(struct xwl_output *xwl_output)
{
struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
if (!xwl_output->output) {
/* This can happen when an output is created from a leasable DRM
* connector */
return;
}
xwl_output->xdg_output =
zxdg_output_manager_v1_get_xdg_output (xwl_screen->xdg_output_manager,
xwl_output->output);
......@@ -879,3 +989,248 @@ xwl_screen_init_xdg_output(struct xwl_screen *xwl_screen)
xorg_list_for_each_entry(it, &xwl_screen->output_list, link)
xwl_output_get_xdg_output(it);
}
static void
lease_connector_handle_name(void *data,
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1,
const char *name)
{
/* This space deliberately left blank */
}
static void
lease_connector_handle_description(void *data,
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1,
const char *description)
{
/* This space deliberately left blank */
}
static RRModePtr *
xwl_get_rrmodes_from_connector_id(int drm, uint32_t connector_id, int *nmode, int *npref)
{
#ifdef WITH_LIBDRM
drmModeConnectorPtr conn;
drmModeModeInfoPtr kmode;
RRModePtr *rrmodes;
int pref, i;
*nmode = *npref = 0;
conn = drmModeGetConnector(drm, connector_id);
if (!conn) {
close(drm);
ErrorF("drmModeGetConnector failed");
return NULL;
}
rrmodes = xallocarray(conn->count_modes, sizeof(RRModePtr));
if (!rrmodes) {
close(drm);
ErrorF("Failed to allocate connector modes");
return NULL;
}
/* This spaghetti brought to you courtesey of xf86RandrR12.c
* It adds preferred modes first, then non-preferred modes */
for (pref = 1; pref >= 0; pref--) {
for (i = 0; i < conn->count_modes; ++i) {
kmode = &conn->modes[i];
if ((pref != 0) == ((kmode->type & DRM_MODE_TYPE_PREFERRED) != 0)) {
xRRModeInfo modeInfo;
RRModePtr rrmode;
modeInfo.nameLength = strlen(kmode->name);
modeInfo.width = kmode->hdisplay;
modeInfo.dotClock = kmode->clock * 1000;
modeInfo.hSyncStart = kmode->hsync_start;
modeInfo.hSyncEnd = kmode->hsync_end;
modeInfo.hTotal = kmode->htotal;
modeInfo.hSkew = kmode->hskew;
modeInfo.height = kmode->vdisplay;
modeInfo.vSyncStart = kmode->vsync_start;
modeInfo.vSyncEnd = kmode->vsync_end;
modeInfo.vTotal = kmode->vtotal;
modeInfo.modeFlags = kmode->flags;
rrmode = RRModeGet(&modeInfo, kmode->name);
if (rrmode) {
rrmodes[*nmode] = rrmode;
*nmode = *nmode + 1;
*npref = *npref + pref;
}
}
}
}
close(drm);
return rrmodes;
#else
*nmode = *npref = 0;
return NULL;
#endif
}
static void
lease_connector_handle_connector_id(void *data,
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1,
uint32_t connector_id)
{
struct xwl_output *output;
Atom name;
INT32 value;
int err;
int nmode, npref;
RRModePtr *rrmodes;
value = connector_id;
output = (struct xwl_output *)data;
name = MakeAtom("CONNECTOR_ID", 12, TRUE);
if (name != BAD_RESOURCE) {
err = RRConfigureOutputProperty(output->randr_output, name,
FALSE, FALSE, TRUE,
1, &value);
if (err != 0) {
ErrorF("RRConfigureOutputProperty error, %d\n", err);
return;
}
err = RRChangeOutputProperty(output->randr_output, name,
XA_INTEGER, 32, PropModeReplace, 1,
&value, FALSE, FALSE);
if (err != 0) {
ErrorF("RRChangeOutputProperty error, %d\n", err);
return;
}
}
rrmodes = xwl_get_rrmodes_from_connector_id(output->xwl_screen->drm_lease_fd,
connector_id, &nmode, &npref);
if (rrmodes != NULL)
RROutputSetModes(output->randr_output, rrmodes, nmode, npref);
free(rrmodes);
}
static void
lease_connector_handle_done(void *data,