mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2025-12-26 15:50:05 +01:00
On DRM lease connector withdrawn event, Xwayland would free the corresponding xwl_output offered for lease. However, the pointer is still referenced from the rrLease->outputs[], meaning that trying to clean up the RANDR DRM leases as done with commitef181265(xwayland: Clean up drm lease when terminating) would cause a use after free and random crashes. To avoid that issue, on the connector withdraw event, set the connector withdrawn flag but do not to remove (i.e. free) the xwayland output if its is offered for lease. Then, once the lease is terminated, check for the xwl_outputs with a withdrawn connector and remove them (once we have no use for them anymore. Note that we cannot do that cleanup from xwl_randr_terminate_lease() as removing the xwl_output will free the RRcrtc resources, which checks for leases in XRANDR, and calls RRTerminateLease(), which chains back to xwl_randr_terminate_lease(). v2: Use a "withdrawn_connector" flag to mark outputs to remove (Xaver) Signed-off-by: Olivier Fourdan <ofourdan@redhat.com> Reviewed-by: Xaver Hugl <xaver.hugl@kde.org> fixes:ef181265- xwayland: Clean up drm lease when terminating See-also: https://gitlab.freedesktop.org/xorg/xserver/-/issues/946 See-also: https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1130 Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1482>
484 lines
16 KiB
C
484 lines
16 KiB
C
/*
|
|
* Copyright © 2020 Drew Devault
|
|
* Copyright © 2021 Xaver Hugl
|
|
*
|
|
* Permission to use, copy, modify, distribute, and sell this software
|
|
* and its documentation for any purpose is hereby granted without
|
|
* fee, provided that the above copyright notice appear in all copies
|
|
* and that both that copyright notice and this permission notice
|
|
* appear in supporting documentation, and that the name of the
|
|
* copyright holders not be used in advertising or publicity
|
|
* pertaining to distribution of the software without specific,
|
|
* written prior permission. The copyright holders make no
|
|
* representations about the suitability of this software for any
|
|
* purpose. It is provided "as is" without express or implied
|
|
* warranty.
|
|
*
|
|
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
|
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
|
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include <xwayland-config.h>
|
|
|
|
#ifdef WITH_LIBDRM
|
|
#include <xf86drm.h>
|
|
#include <xf86drmMode.h>
|
|
#endif
|
|
|
|
#include "randrstr_priv.h"
|
|
#include "xwayland-drm-lease.h"
|
|
#include "xwayland-screen.h"
|
|
#include "xwayland-output.h"
|
|
|
|
static void
|
|
xwl_randr_lease_cleanup_outputs(RRLeasePtr rrLease)
|
|
{
|
|
struct xwl_output *output;
|
|
int i;
|
|
|
|
for (i = 0; i < rrLease->numOutputs; ++i) {
|
|
output = rrLease->outputs[i]->devPrivate;
|
|
if (output) {
|
|
output->lease = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
xwl_randr_lease_free_outputs(RRLeasePtr rrLease)
|
|
{
|
|
struct xwl_output *xwl_output;
|
|
int i;
|
|
|
|
for (i = 0; i < rrLease->numOutputs; ++i) {
|
|
xwl_output = rrLease->outputs[i]->devPrivate;
|
|
if (xwl_output && xwl_output->withdrawn_connector) {
|
|
rrLease->outputs[i]->devPrivate = NULL;
|
|
xwl_output_remove(xwl_output);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
drm_lease_handle_lease_fd(void *data,
|
|
struct wp_drm_lease_v1 *wp_drm_lease_v1,
|
|
int32_t lease_fd)
|
|
{
|
|
struct xwl_drm_lease *lease = (struct xwl_drm_lease *)data;
|
|
|
|
lease->fd = lease_fd;
|
|
AttendClient(lease->client);
|
|
}
|
|
|
|
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;
|
|
|
|
if (lease->fd >= 0) {
|
|
RRTerminateLease(lease->rrLease);
|
|
} else {
|
|
AttendClient(lease->client);
|
|
xwl_randr_lease_cleanup_outputs(lease->rrLease);
|
|
}
|
|
|
|
/* Free the xwl_outputs that have been withdrawn while lease-able */
|
|
xwl_randr_lease_free_outputs(lease->rrLease);
|
|
}
|
|
|
|
static struct wp_drm_lease_v1_listener drm_lease_listener = {
|
|
.lease_fd = drm_lease_handle_lease_fd,
|
|
.finished = drm_lease_handle_finished,
|
|
};
|
|
|
|
void
|
|
xwl_randr_get_lease(ClientPtr client, ScreenPtr screen, RRLeasePtr *rrLease, int *fd)
|
|
{
|
|
struct xwl_screen *xwl_screen;
|
|
struct xwl_drm_lease *lease;
|
|
xwl_screen = xwl_screen_get(screen);
|
|
|
|
xorg_list_for_each_entry(lease, &xwl_screen->drm_leases, link) {
|
|
if (lease->client == client) {
|
|
*rrLease = lease->rrLease;
|
|
*fd = lease->fd;
|
|
if (lease->fd < 0)
|
|
xorg_list_del(&lease->link);
|
|
return;
|
|
}
|
|
}
|
|
*rrLease = NULL;
|
|
*fd = -1;
|
|
}
|
|
|
|
int
|
|
xwl_randr_request_lease(ClientPtr client, ScreenPtr screen, RRLeasePtr rrLease)
|
|
{
|
|
struct xwl_screen *xwl_screen;
|
|
struct wp_drm_lease_request_v1 *req;
|
|
struct xwl_drm_lease *lease_private;
|
|
struct xwl_drm_lease_device *lease_device = NULL;
|
|
struct xwl_drm_lease_device *device_data;
|
|
struct xwl_output *output;
|
|
int i;
|
|
|
|
xwl_screen = xwl_screen_get(screen);
|
|
|
|
if (xorg_list_is_empty(&xwl_screen->drm_lease_devices)) {
|
|
ErrorF("Attempted to create DRM lease without wp_drm_lease_device_v1\n");
|
|
return BadMatch;
|
|
}
|
|
|
|
for (i = 0; i < rrLease->numOutputs; ++i) {
|
|
output = rrLease->outputs[i]->devPrivate;
|
|
if (!output || !output->lease_connector || output->lease) {
|
|
return BadValue;
|
|
}
|
|
}
|
|
|
|
xorg_list_for_each_entry(device_data, &xwl_screen->drm_lease_devices, link) {
|
|
Bool connectors_of_device = FALSE;
|
|
for (i = 0; i < rrLease->numOutputs; ++i) {
|
|
output = rrLease->outputs[i]->devPrivate;
|
|
if (output->lease_device == device_data) {
|
|
connectors_of_device = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (connectors_of_device) {
|
|
if (lease_device != NULL) {
|
|
ErrorF("Attempted to create DRM lease from multiple devices\n");
|
|
return BadValue;
|
|
}
|
|
lease_device = device_data;
|
|
}
|
|
}
|
|
|
|
req = wp_drm_lease_device_v1_create_lease_request(
|
|
lease_device->drm_lease_device);
|
|
lease_private = calloc(1, sizeof(struct xwl_drm_lease));
|
|
for (i = 0; i < rrLease->numOutputs; ++i) {
|
|
output = rrLease->outputs[i]->devPrivate;
|
|
output->lease = lease_private;
|
|
wp_drm_lease_request_v1_request_connector(req, output->lease_connector);
|
|
}
|
|
lease_private->fd = -1;
|
|
lease_private->lease = wp_drm_lease_request_v1_submit(req);
|
|
lease_private->rrLease = rrLease;
|
|
lease_private->client = client;
|
|
rrLease->devPrivate = lease_private;
|
|
|
|
wp_drm_lease_v1_add_listener(lease_private->lease,
|
|
&drm_lease_listener, lease_private);
|
|
xorg_list_add(&lease_private->link, &xwl_screen->drm_leases);
|
|
|
|
ResetCurrentRequest(client);
|
|
client->sequence--;
|
|
IgnoreClient(client);
|
|
|
|
return Success;
|
|
}
|
|
|
|
void
|
|
xwl_randr_terminate_lease(ScreenPtr screen, RRLeasePtr lease)
|
|
{
|
|
struct xwl_drm_lease *lease_private = lease->devPrivate;
|
|
|
|
if (lease_private) {
|
|
xwl_randr_lease_cleanup_outputs(lease);
|
|
xorg_list_del(&lease_private->link);
|
|
if (lease_private->fd >= 0)
|
|
close(lease_private->fd);
|
|
wp_drm_lease_v1_destroy(lease_private->lease);
|
|
free(lease_private);
|
|
lease->devPrivate = NULL;
|
|
}
|
|
|
|
RRLeaseTerminated(lease);
|
|
}
|
|
|
|
static void
|
|
lease_connector_handle_name(void *data,
|
|
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1,
|
|
const char *name)
|
|
{
|
|
struct xwl_output *xwl_output = data;
|
|
char rr_output_name[MAX_OUTPUT_NAME] = { 0 };
|
|
|
|
snprintf(rr_output_name, MAX_OUTPUT_NAME, "lease-%s", name);
|
|
xwl_output_set_name(xwl_output, rr_output_name);
|
|
}
|
|
|
|
static void
|
|
lease_connector_handle_description(void *data,
|
|
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1,
|
|
const char *description)
|
|
{
|
|
/* This space is deliberately left blank */
|
|
}
|
|
|
|
static RRModePtr *
|
|
xwl_get_rrmodes_from_connector_id(int drm, int32_t connector_id, int *nmode, int *npref)
|
|
{
|
|
#ifdef WITH_LIBDRM
|
|
drmModeConnectorPtr conn;
|
|
drmModeModeInfoPtr kmode;
|
|
RRModePtr *rrmodes;
|
|
int pref, i;
|
|
|
|
*nmode = *npref = 0;
|
|
|
|
conn = drmModeGetConnectorCurrent(drm, connector_id);
|
|
if (!conn) {
|
|
ErrorF("drmModeGetConnector for connector %d failed\n", connector_id);
|
|
return NULL;
|
|
}
|
|
rrmodes = xallocarray(conn->count_modes, sizeof(RRModePtr));
|
|
if (!rrmodes) {
|
|
ErrorF("Failed to allocate connector modes\n");
|
|
drmModeFreeConnector(conn);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* workaround: there could be no preferred mode that got added */
|
|
if (*nmode > 0 && *npref == 0)
|
|
*npref = 1;
|
|
|
|
drmModeFreeConnector(conn);
|
|
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->lease_device->drm_read_only_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,
|
|
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1)
|
|
{
|
|
/* This space is deliberately left blank */
|
|
}
|
|
|
|
static void
|
|
lease_connector_handle_withdrawn(void *data,
|
|
struct wp_drm_lease_connector_v1 *wp_drm_lease_connector_v1)
|
|
{
|
|
struct xwl_output *xwl_output = data;
|
|
|
|
xwl_output->withdrawn_connector = TRUE;
|
|
|
|
/* Not removing the xwl_output if currently leased with Wayland */
|
|
if (xwl_output->lease)
|
|
return;
|
|
|
|
xwl_output_remove(xwl_output);
|
|
}
|
|
|
|
static const struct wp_drm_lease_connector_v1_listener lease_connector_listener = {
|
|
.name = lease_connector_handle_name,
|
|
.description = lease_connector_handle_description,
|
|
.connector_id = lease_connector_handle_connector_id,
|
|
.withdrawn = lease_connector_handle_withdrawn,
|
|
.done = lease_connector_handle_done,
|
|
};
|
|
|
|
static void
|
|
drm_lease_device_handle_drm_fd(void *data,
|
|
struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1,
|
|
int fd)
|
|
{
|
|
((struct xwl_drm_lease_device *)data)->drm_read_only_fd = fd;
|
|
}
|
|
|
|
static void
|
|
drm_lease_device_handle_connector(void *data,
|
|
struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1,
|
|
struct wp_drm_lease_connector_v1 *connector)
|
|
{
|
|
struct xwl_drm_lease_device *lease_device = data;
|
|
struct xwl_screen *xwl_screen = lease_device->xwl_screen;
|
|
struct xwl_output *xwl_output;
|
|
char name[MAX_OUTPUT_NAME] = { 0 };
|
|
|
|
xwl_output = calloc(1, sizeof *xwl_output);
|
|
if (xwl_output == NULL) {
|
|
ErrorF("%s ENOMEM\n", __func__);
|
|
return;
|
|
}
|
|
|
|
xwl_output->lease_device = lease_device;
|
|
xwl_output->xwl_screen = xwl_screen;
|
|
xwl_output->lease_connector = connector;
|
|
xwl_output->randr_crtc = RRCrtcCreate(xwl_screen->screen, xwl_output);
|
|
if (!xwl_output->randr_crtc) {
|
|
ErrorF("Failed creating RandR CRTC\n");
|
|
goto err;
|
|
}
|
|
RRCrtcSetRotations(xwl_output->randr_crtc, ALL_ROTATIONS);
|
|
xwl_output->randr_output = RROutputCreate(xwl_screen->screen,
|
|
name, MAX_OUTPUT_NAME, xwl_output);
|
|
snprintf(name, MAX_OUTPUT_NAME, "XWAYLAND%d",
|
|
xwl_screen_get_next_output_serial(xwl_screen));
|
|
xwl_output_set_name(xwl_output, name);
|
|
|
|
if (!xwl_output->randr_output) {
|
|
ErrorF("Failed creating RandR Output\n");
|
|
goto err;
|
|
}
|
|
|
|
RRCrtcGammaSetSize(xwl_output->randr_crtc, 256);
|
|
RROutputSetCrtcs(xwl_output->randr_output, &xwl_output->randr_crtc, 1);
|
|
RROutputSetConnection(xwl_output->randr_output, RR_Connected);
|
|
RROutputSetNonDesktop(xwl_output->randr_output, TRUE);
|
|
xwl_output->randr_output->devPrivate = xwl_output;
|
|
|
|
wp_drm_lease_connector_v1_add_listener(connector,
|
|
&lease_connector_listener,
|
|
xwl_output);
|
|
|
|
xorg_list_append(&xwl_output->link, &xwl_screen->output_list);
|
|
return;
|
|
|
|
err:
|
|
if (xwl_output->randr_crtc)
|
|
RRCrtcDestroy(xwl_output->randr_crtc);
|
|
free(xwl_output);
|
|
}
|
|
|
|
static void
|
|
drm_lease_device_handle_released(void *data,
|
|
struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1)
|
|
{
|
|
struct xwl_drm_lease_device *lease_device = data;
|
|
xwl_screen_destroy_drm_lease_device(lease_device->xwl_screen, wp_drm_lease_device_v1);
|
|
}
|
|
|
|
static void
|
|
drm_lease_device_handle_done(void *data,
|
|
struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1)
|
|
{
|
|
/* This space is deliberately left blank */
|
|
}
|
|
|
|
static const struct wp_drm_lease_device_v1_listener drm_lease_device_listener = {
|
|
.drm_fd = drm_lease_device_handle_drm_fd,
|
|
.connector = drm_lease_device_handle_connector,
|
|
.released = drm_lease_device_handle_released,
|
|
.done = drm_lease_device_handle_done,
|
|
};
|
|
|
|
void
|
|
xwl_screen_add_drm_lease_device(struct xwl_screen *xwl_screen, uint32_t id)
|
|
{
|
|
struct wp_drm_lease_device_v1 *lease_device = wl_registry_bind(
|
|
xwl_screen->registry, id, &wp_drm_lease_device_v1_interface, 1);
|
|
struct xwl_drm_lease_device *device_data = malloc(sizeof(struct xwl_drm_lease_device));
|
|
|
|
device_data->drm_lease_device = lease_device;
|
|
device_data->xwl_screen = xwl_screen;
|
|
device_data->drm_read_only_fd = -1;
|
|
device_data->id = id;
|
|
xorg_list_add(&device_data->link, &xwl_screen->drm_lease_devices);
|
|
wp_drm_lease_device_v1_add_listener(lease_device,
|
|
&drm_lease_device_listener,
|
|
device_data);
|
|
}
|
|
|
|
void
|
|
xwl_screen_destroy_drm_lease_device(struct xwl_screen *xwl_screen,
|
|
struct wp_drm_lease_device_v1 *wp_drm_lease_device_v1)
|
|
{
|
|
struct xwl_drm_lease_device *device_data;
|
|
|
|
xorg_list_for_each_entry(device_data, &xwl_screen->drm_lease_devices, link) {
|
|
if (device_data->drm_lease_device == wp_drm_lease_device_v1) {
|
|
wp_drm_lease_device_v1_destroy(wp_drm_lease_device_v1);
|
|
xorg_list_del(&device_data->link);
|
|
if (device_data->drm_read_only_fd >= 0)
|
|
close(device_data->drm_read_only_fd);
|
|
free(device_data);
|
|
return;
|
|
}
|
|
}
|
|
}
|