diff options
author | Keith Packard <keithp@keithp.com> | 2017-12-20 19:09:49 -0800 |
---|---|---|
committer | Keith Packard <keithp@keithp.com> | 2018-01-24 10:51:08 +1100 |
commit | 9e8d2a9450c69f86024d62d3ed0525c744c0717e (patch) | |
tree | c113f231fd7c9930cd3a0c8a7eafe5baca112e93 | |
parent | 91fd74e7cfdd6e9700cbfaef9bb98dc0291a3f7e (diff) |
test: Add randr lease tests
Tests for RRCreateLease and RRFreeLease
Signed-off-by: Keith Packard <keithp@keithp.com>
-rw-r--r-- | configure.ac | 8 | ||||
-rw-r--r-- | test/Makefile.am | 2 | ||||
-rw-r--r-- | test/randr/Makefile.am | 14 | ||||
-rw-r--r-- | test/randr/randr_lease.c | 1066 |
4 files changed, 1089 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index 62d0892e1..bd5815cd0 100644 --- a/configure.ac +++ b/configure.ac @@ -1290,6 +1290,13 @@ if test "x$DRI" = xyes || test "x$DRI2" = xyes || test "x$DRI3" = xyes || test " fi fi +AM_CONDITIONAL(DRM, test "x$DRM" = xyes) + +TEST_RANDR_REQUIRED_LIBS="xcb xcb-randr" +PKG_CHECK_MODULES([TEST_RANDR], $TEST_RANDR_REQUIRED_LIBS) +AC_SUBST(TEST_RANDR_CFLAGS) +AC_SUBST(TEST_RANDR_LIBS) + if test "x$GLX" = xyes; then PKG_CHECK_MODULES([XLIB], [x11]) PKG_CHECK_MODULES([GL], $GLPROTO $LIBGL) @@ -2543,6 +2550,7 @@ hw/kdrive/ephyr/man/Makefile hw/kdrive/src/Makefile hw/xwayland/Makefile test/Makefile +test/randr/Makefile xserver.ent xorg-server.pc ]) diff --git a/test/Makefile.am b/test/Makefile.am index 2dbb2df03..f42b50f5e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,5 +1,5 @@ if ENABLE_UNIT_TESTS -SUBDIRS= . +SUBDIRS= . randr AM_CFLAGS = $(DIX_CFLAGS) @XORG_CFLAGS@ AM_CPPFLAGS = $(XORG_INCS) diff --git a/test/randr/Makefile.am b/test/randr/Makefile.am new file mode 100644 index 000000000..abaf8e6ac --- /dev/null +++ b/test/randr/Makefile.am @@ -0,0 +1,14 @@ +randr_lease_SOURCES = \ + randr_lease.c + +if DRM +RANDR_LEASE=randr_lease +endif + +noinst_PROGRAMS = $(RANDR_LEASE) + +TESTS = $(RANDR_LEASE) + +AM_CFLAGS=$(XORG_CFLAGS) $(TEST_RANDR_CFLAGS) $(LIBDRM_CFLAGS) + +randr_lease_LDADD = $(TEST_RANDR_LIBS) $(LIBDRM_LIBS) diff --git a/test/randr/randr_lease.c b/test/randr/randr_lease.c new file mode 100644 index 000000000..53dc0cb01 --- /dev/null +++ b/test/randr/randr_lease.c @@ -0,0 +1,1066 @@ +/* + * Copyright © 2017 Keith Packard + * + * 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 <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <limits.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <xcb/randr.h> +#include <drm.h> +#include <xf86drm.h> +#include <xf86drmMode.h> + +/* Test X Leases */ + +/* Information about one lease */ +typedef struct { + int fd; + + /* X resources */ + xcb_randr_output_t randr_output; + xcb_randr_crtc_t randr_crtc; + xcb_randr_lease_t randr_lease; + xcb_randr_mode_info_t randr_mode; + + /* DRM resources */ + uint32_t crtc_id; + uint32_t connector_id; + drmModeModeInfo drm_mode; + uint32_t bo_handle; + uint32_t fb_handle; + + /* Frame buffer */ + uint32_t *fb; + uint32_t size; + uint32_t width, height, stride; +} lease_t; + +/* Global test information */ +typedef struct { + xcb_connection_t *conn; + xcb_window_t root; + xcb_timestamp_t config_timestamp; + uint8_t event_base; + uint8_t error_base; + xcb_randr_get_screen_resources_reply_t *gsr_r; +} test_t; + +/* standard VGA mode, in case the target output has no modes */ +xcb_randr_mode_info_t vga_mode = { + .dot_clock = 23750 * 1000, + + .width = 640, + .hsync_start = 664, + .hsync_end = 720, + .htotal = 800, + .hskew = 0, + + .height = 480, + .vsync_start = 483, + .vsync_end = 487, + .vtotal = 500, + + .mode_flags = XCB_RANDR_MODE_FLAG_HSYNC_NEGATIVE | XCB_RANDR_MODE_FLAG_VSYNC_POSITIVE, +}; + +/* Convert an X mode into a DRM mode */ +drmModeModeInfo +randr_mode_to_drm_mode(xcb_randr_mode_info_t *randr_mode) +{ + drmModeModeInfo drm_mode = { + .clock = randr_mode->dot_clock / 1000, + + .hdisplay = randr_mode->width, + .hsync_start = randr_mode->hsync_start, + .hsync_end = randr_mode->hsync_end, + .htotal = randr_mode->htotal, + .hskew = 0, + + .vdisplay = randr_mode->height, + .vsync_start = randr_mode->vsync_start, + .vsync_end = randr_mode->vsync_end, + .vtotal = randr_mode->vtotal, + .vscan = 0, + + .vrefresh = 0, + .flags = randr_mode->mode_flags, + .type = DRM_MODE_TYPE_USERDEF, + + }; + + sprintf(drm_mode.name, "%dx%d", randr_mode->width, randr_mode->height); + + return drm_mode; +} + +/* Handle EINTR from ioctl by looping */ +int +test_ioctl(int fd, unsigned long request, void *arg) +{ + int ret; + + do { + ret = ioctl(fd, request, arg); + } while (ret < 0 && errno == EINTR); + return ret; +} + +/* Draw a rectangle in 32-bpp format */ +void +rect(lease_t *lease, int x, int y, int width, int height, uint32_t color) +{ + uint32_t *scanline = lease->fb + (lease->stride * 8 / 32) * y + x; + + while (height--) { + int w = width; + uint32_t *pixel = scanline; + + scanline += lease->stride * 8 / 32; + + while (w--) + *pixel++ = color; + } +} + +/* Create a buffer object, map it, and create a frame buffer */ +int +create_fb(test_t *test, lease_t *lease) +{ + struct drm_mode_create_dumb create; + + memset(&create, 0, sizeof(create)); + create.width = lease->drm_mode.hdisplay; + create.height = lease->drm_mode.vdisplay; + create.bpp = 32; + + create.handle = 0; + test_ioctl(lease->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); + if (!create.handle) { + printf("\t\tfailed to create scanout bo\n"); + return 0; + } + if (create.size < lease->drm_mode.hdisplay * lease->drm_mode.vdisplay * 32 / 8) { + printf("\t\tscanout buffer too small\n"); + return 0; + } + + lease->bo_handle = create.handle; + + lease->width = lease->drm_mode.hdisplay; + lease->height = lease->drm_mode.vdisplay; + lease->stride = create.pitch; + + struct drm_mode_map_dumb map; + + memset(&map, 0, sizeof(map)); + + map.handle = lease->bo_handle; + + if (test_ioctl(lease->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) { + printf("\t\tmap scanout failed\n"); + return 0; + } + + lease->size = create.size; + lease->fb = mmap(NULL, lease->size, PROT_READ|PROT_WRITE, MAP_SHARED, lease->fd, map.offset); + + if (lease->fb == MAP_FAILED) { + printf("\t\tmmap scanout failed\n"); + return 0; + } + + /* grey */ + rect(lease, 0, 0, lease->width, lease->height, 0x00808080); + + /* with an orange center */ + rect(lease, lease->width / 4, lease->height / 4, lease->width / 2, lease->height / 2, 0x00ffa500); + + if (drmModeAddFB(lease->fd, lease->width, lease->height, 24, 32, lease->stride, lease->bo_handle, + &lease->fb_handle)) { + printf("\t\tadd fb failed\n"); + return 0; + } + + return 1; +} + +static void +destroy_fb(test_t *test, lease_t *lease) +{ + struct drm_mode_destroy_dumb destroy; + + if (lease->fb) { + munmap(lease->fb, lease->size); + drmModeRmFB(lease->fd, lease->fb_handle); + lease->fb = 0; + + memset(&destroy, 0, sizeof(destroy)); + destroy.handle = lease->bo_handle; + test_ioctl(lease->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); + } +} + +/* + * Create a lease for the desired output, select a crtc if none was + * provided. + */ +static int +make_randr_lease(test_t *test, lease_t *lease, xcb_randr_output_t output, xcb_randr_crtc_t crtc, bool whinge, xcb_generic_error_t **error) +{ + xcb_randr_mode_info_t mode_info; + + mode_info.id = XCB_NONE; + + /* Pick a suitable mode */ + xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(test->conn, output, test->gsr_r->config_timestamp); + xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(test->conn, goi_c, NULL); + + if (goi_r->num_modes > 0) { + xcb_randr_mode_t mode = xcb_randr_get_output_info_modes(goi_r)[0]; + + for (xcb_randr_mode_info_iterator_t iter = xcb_randr_get_screen_resources_modes_iterator(test->gsr_r); + iter.rem; + xcb_randr_mode_info_next(&iter)) + { + if (iter.data->id == mode) { + mode_info = *iter.data; + break; + } + } + } + + if (mode_info.id == 0) + mode_info = vga_mode; + + /* Pick a crtc if none was provided */ + if (crtc == XCB_NONE) { + xcb_randr_crtc_t *rc = xcb_randr_get_screen_resources_crtcs(test->gsr_r); + + xcb_randr_crtc_t idle_crtc = XCB_NONE; + xcb_randr_crtc_t active_crtc = XCB_NONE; + + /* Find either a crtc already connected to the desired output or idle */ + for (int c = 0; active_crtc == XCB_NONE && c < test->gsr_r->num_crtcs; c++) { + xcb_randr_get_crtc_info_cookie_t gci_c = xcb_randr_get_crtc_info(test->conn, rc[c], test->gsr_r->config_timestamp); + + xcb_randr_get_crtc_info_reply_t *gci_r = xcb_randr_get_crtc_info_reply(test->conn, gci_c, NULL); + + if (gci_r->mode) { + int num_outputs = xcb_randr_get_crtc_info_outputs_length(gci_r); + xcb_randr_output_t *outputs = xcb_randr_get_crtc_info_outputs(gci_r); + for (int o = 0; o < num_outputs; o++) + if (outputs[o] == output && num_outputs == 1) { + active_crtc = rc[c]; + break; + } + + } else if (idle_crtc == 0) { + int num_possible = xcb_randr_get_crtc_info_possible_length(gci_r); + xcb_randr_output_t *possible = xcb_randr_get_crtc_info_possible(gci_r); + for (int p = 0; p < num_possible; p++) + if (possible[p] == output) { + idle_crtc = rc[c]; + break; + } + } + + free(gci_r); + } + if (active_crtc) + crtc = active_crtc; + else + crtc = idle_crtc; + + } + + if (crtc == XCB_NONE) { + if (whinge) + printf("\t\tcannot find usable CRTC\n"); + return 0; + } + + /* + * Create a RandR lease + */ + + xcb_randr_lease_t randr_lease = xcb_generate_id(test->conn); + + xcb_randr_create_lease_cookie_t cl_c = xcb_randr_create_lease(test->conn, + test->root, + randr_lease, + 1, + 1, + &crtc, + &output); + xcb_randr_create_lease_reply_t *cl_r = xcb_randr_create_lease_reply(test->conn, cl_c, error); + if (!cl_r) { + if (whinge) + printf ("create lease failed\n"); + return 0; + } + + int fd = -1; + if (cl_r->nfd > 0) { + int *rcl_f = xcb_randr_create_lease_reply_fds(test->conn, cl_r); + + fd = rcl_f[0]; + } + free (cl_r); + if (fd < 0) { + if (whinge) + printf("\t\tLease returned invalid fd\n"); + return 0; + } + + lease->fd = fd; + lease->randr_output = output; + lease->randr_crtc = crtc; + lease->randr_mode = mode_info; + lease->randr_lease = randr_lease; + + return 1; +} + +static int +get_drm_resources(test_t *test, lease_t *lease) +{ + /* + * Find the kernel resource IDs for the lease. This is pretty easy + * as there should be only one of each + */ + + drmModeResPtr mode_res = drmModeGetResources(lease->fd); + + if (!mode_res) { + printf("\t\tget mode resources failed\n"); + return 0; + } + + if (mode_res->count_connectors != 1) { + printf("\t\tunexpected DRM connector count %d\n", mode_res->count_connectors); + goto fail_res; + } + + if (mode_res->count_crtcs != 1) { + printf("\t\tunexpected DRM crtc count %d\n", mode_res->count_crtcs); + goto fail_res; + } + + lease->crtc_id = mode_res->crtcs[0]; + lease->connector_id = mode_res->connectors[0]; + lease->drm_mode = randr_mode_to_drm_mode(&lease->randr_mode); + lease->bo_handle = 0; + lease->fb_handle = 0; + + lease->fb = NULL; + lease->size = 0; + lease->width = 0; + lease->height = 0; + lease->stride = 0; + + return 1; + +fail_res: + drmModeFreeResources(mode_res); + + return 0; +} + +static int +init_lease(lease_t *lease) +{ + memset(lease, 0, sizeof (*lease)); + lease->fd = -1; +} + +static int +make_lease(test_t *test, lease_t *lease, xcb_randr_output_t output, xcb_randr_crtc_t crtc) +{ + init_lease(lease); + + if (!make_randr_lease(test, lease, output, crtc, true, NULL)) + return 0; + + if (!get_drm_resources(test, lease)) { + close(lease->fd); + lease->fd = -1; + return 0; + } + + return 1; +} + +static void +free_randr_lease(test_t *test, lease_t *lease) +{ + if (lease->randr_lease) { + xcb_randr_free_lease(test->conn, lease->randr_lease, 0); + free(xcb_get_input_focus_reply(test->conn, xcb_get_input_focus(test->conn), NULL)); + lease->randr_lease = 0; + } +} + +static void +close_kernel_lease(test_t *test, lease_t *lease) +{ + if (lease->fd >= 0) { + close(lease->fd); + lease->fd = -1; + } +} + +static void +close_lease(test_t *test, lease_t *lease) +{ + destroy_fb(test, lease); + close_kernel_lease(test, lease); + free_randr_lease(test, lease); +} + +static int +modeset_lease(test_t *test, lease_t *lease) +{ + int ret; + + ret = drmModeSetCrtc(lease->fd, lease->crtc_id, lease->fb_handle, + 0, 0, &lease->connector_id, 1, &lease->drm_mode); + if (ret) { + printf("\t\tmodeset failed %d %s\n", errno, strerror(errno)); + return 0; + } + + return 1; +} + +static void +describe_error(test_t *test, xcb_generic_error_t *error) +{ + if (error->error_code <= XCB_IMPLEMENTATION) { + printf("\t\tcore error %d\n", error->error_code); + } else + switch (error->error_code - test->error_base) { + case XCB_RANDR_BAD_OUTPUT: + printf("\t\tbad output\n"); break; + case XCB_RANDR_BAD_CRTC: + printf("\t\tbad crtc\n"); break; + case XCB_RANDR_BAD_MODE: + printf("\t\tbad mode\n"); break; + case XCB_RANDR_BAD_PROVIDER: + printf("\t\tbad provider\n"); break; + default: + printf("\t\terror code %d\n", error->error_code); + } +} + +static int +test_init(test_t *test) +{ + memset(test, 0, sizeof(*test)); + + int screen; + + test->conn = xcb_connect(NULL, &screen); + + if (!test->conn) { + printf("\t\tCannot connect to X server\n"); + return 0; + } + + const xcb_setup_t *setup = xcb_get_setup(test->conn); + + /* + * Find our root window + */ + xcb_screen_iterator_t iter; + for (iter = xcb_setup_roots_iterator(setup); iter.rem; xcb_screen_next(&iter)) { + if (screen == 0) { + test->root = iter.data->root; + break; + } + --screen; + } + + const xcb_query_extension_reply_t *qer = xcb_get_extension_data(test->conn, &xcb_randr_id); + + if (!qer) { + printf("\t\tCannot get randr extension data\n"); + xcb_disconnect(test->conn); + return 0; + } + + test->error_base = qer->first_error; + test->event_base = qer->first_event; + + return 1; +} + +static int +test_get_randr_resources(test_t *test) +{ + if (test->gsr_r) { + free(test->gsr_r); + test->gsr_r = NULL; + } + + xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(test->conn, test->root); + + xcb_generic_error_t *error; + + test->gsr_r = xcb_randr_get_screen_resources_reply(test->conn, gsr_c, &error); + + if (!test->gsr_r) { + printf("\t\tget_screen_resources failed\n"); + if (error) + describe_error(test, error); + return 0; + } + + test->config_timestamp = test->gsr_r->config_timestamp; + + return 1; +} + +static void +test_fini(test_t *test) +{ + free(test->gsr_r); + test->gsr_r = NULL; + + xcb_disconnect(test->conn); + test->conn = NULL; +} + +/* Create a lease and display an image */ +static int lease_simple(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + if (!create_fb(test, &lease)) { + close_lease(test, &lease); + return 0; + } + if (!modeset_lease(test, &lease)) { + close_lease(test, &lease); + return 0; + } + sleep(5); /* slightly annoying, but monitors are slow */ + close_lease(test, &lease); + + return 1; +} + +static int lease_close_x_first(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + + free_randr_lease(test, &lease); + close_kernel_lease(test, &lease); + + return 1; +} + +static int lease_close_kernel_first(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + + close_kernel_lease(test, &lease); + free_randr_lease(test, &lease); + + return 1; +} + +static int +lease_not_use_x(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + + /* Attempt to clear and then set a mode */ + xcb_randr_set_crtc_config_cookie_t sccc = xcb_randr_set_crtc_config(test->conn, + lease.randr_crtc, + XCB_CURRENT_TIME, + test->config_timestamp, + 0, 0, + XCB_NONE, + XCB_RANDR_ROTATION_ROTATE_0, + 0, NULL); + + xcb_generic_error_t *error; + + xcb_randr_set_crtc_config_reply_t *sccr = xcb_randr_set_crtc_config_reply(test->conn, + sccc, + &error); + + if (sccr) { + printf("\t\tunexpected successful set config\n"); + goto fail; + } + + if (error) { + if (error->error_code != XCB_ACCESS) { + printf ("Unexpected error code in set config\n"); + describe_error(test, error); + goto fail; + } + } + + sccc = xcb_randr_set_crtc_config(test->conn, + lease.randr_crtc, + XCB_CURRENT_TIME, + test->config_timestamp, + 0, 0, + lease.randr_mode.id, + XCB_RANDR_ROTATION_ROTATE_0, + 1, &lease.randr_output); + + sccr = xcb_randr_set_crtc_config_reply(test->conn, + sccc, + &error); + + if (sccr) { + printf("\t\tunexpected successful set config\n"); + goto fail; + } + + if (error) { + if (error->error_code != XCB_ACCESS) { + printf ("Unexpected error code in set config\n"); + describe_error(test, error); + goto fail; + } + } + + close_lease(test, &lease); + + return 1; + +fail: + close_lease(test, &lease); + return 0; +} + +static int +lease_crtc_no_possible_outputs(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + + xcb_randr_get_crtc_info_cookie_t gcic = xcb_randr_get_crtc_info(test->conn, lease.randr_crtc, test->config_timestamp); + + xcb_generic_error_t *error; + + xcb_randr_get_crtc_info_reply_t *gcir = xcb_randr_get_crtc_info_reply(test->conn, gcic, &error); + + if (!gcir) { + printf("\t\tunexpected error\n"); + describe_error(test, error); + close_lease(test, &lease); + return 0; + } + + if (gcir->num_possible_outputs != 0) { + printf("\t\tnum_possible_outputs %d, not zero\n", gcir->num_outputs); + close_lease(test, &lease); + return 0; + } + + close_lease(test, &lease); + return 1; +} + +static int +lease_check_crtc_disabled(test_t *test, lease_t *lease) +{ + xcb_randr_get_crtc_info_cookie_t gcic = xcb_randr_get_crtc_info(test->conn, lease->randr_crtc, test->config_timestamp); + + xcb_generic_error_t *error; + + xcb_randr_get_crtc_info_reply_t *gcir = xcb_randr_get_crtc_info_reply(test->conn, gcic, &error); + + if (!gcir) { + printf("\t\tunexpected error\n"); + describe_error(test, error); + goto fail; + } + + if (gcir->x != 0) { + printf("\t\tcrtc x %d, not zero\n", gcir->x); + goto fail; + } + + if (gcir->y != 0) { + printf("\t\tcrtc y %d, not zero\n", gcir->y); + goto fail; + } + + if (gcir->width != 0) { + printf("\t\tcrtc width %d, not zero\n", gcir->width); + goto fail; + } + + if (gcir->height != 0) { + printf("\t\tcrtc height %d, not zero\n", gcir->height); + goto fail; + } + + if (gcir->mode != XCB_NONE) { + printf("\t\tcrtc mode %d, not None\n", gcir->mode); + goto fail; + } + + if (gcir->rotation != XCB_RANDR_ROTATION_ROTATE_0) { + printf("\t\tcrtc rotation %d, not ROTATE_0\n", gcir->rotation); + goto fail; + } + + if (gcir->num_outputs != 0) { + printf("\t\tcrtc num_outputs %d, not zero\n", gcir->num_outputs); + goto fail; + } + + return 1; +fail: + return 0; +} + +static int +lease_crtc_disabled(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + + /* Make sure the crtc appears to be 'disabled' */ + if (!lease_check_crtc_disabled(test, &lease)) + goto fail; + + close_lease(test, &lease); + return 1; +fail: + close_lease(test, &lease); + return 0; +} + +static int +lease_check_output_disabled(test_t *test, lease_t *lease) +{ + xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(test->conn, lease->randr_output, test->config_timestamp); + + xcb_generic_error_t *error; + + xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(test->conn, goi_c, &error); + + if (!goi_r) { + printf("\t\tunexpected error\n"); + describe_error(test, error); + goto fail; + } + + if (goi_r->num_crtcs != 0) { + printf("\t\toutput num_crtcs %d not zero\n", goi_r->num_crtcs); + goto fail; + } + + if (goi_r->num_clones != 0) { + printf("\t\toutput num_clones %d not zero\n", goi_r->num_clones); + goto fail; + } + + if (goi_r->connection != XCB_RANDR_CONNECTION_DISCONNECTED) { + printf("\t\toutput conection %d not Disconnected\n", goi_r->connection); + goto fail; + } + + return 1; + +fail: + return 0; +} + +static int +lease_outputs_no_crtcs_clones_connection(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + if (!make_lease(test, &lease, output, XCB_NONE)) + return 0; + + if (!lease_check_output_disabled(test, &lease)) + goto fail; + + close_lease(test, &lease); + return 1; + +fail: + close_lease(test, &lease); + return 0; +} + +/* Lease remains active (in that the crtc and output appear disabled) + * after the X connection has been terminated + */ +static int +lease_active_after_x(test_t *test, xcb_randr_output_t output) +{ + lease_t lease; + + xcb_connection_t *save_conn = test->conn; + + int screen; + + /* Create another X connection */ + xcb_connection_t *second_conn = xcb_connect(NULL, &screen); + + if (!second_conn) { + printf("\t\tcan't reconnect to X server\n"); + goto fail; + } + + test->conn = second_conn; + + if (!make_lease(test, &lease, output, XCB_NONE)) { + goto fail_restore; + } + + /* Close the first connection */ + xcb_disconnect(second_conn); + + /* Our lease ID is no longer valid */ + lease.randr_lease = XCB_NONE; + + /* Restore our original connection */ + test->conn = save_conn; + + /* See if the lease is still active */ + + if (!lease_check_crtc_disabled(test, &lease)) + goto fail; + + if (!lease_check_output_disabled(test, &lease)) + goto fail; + + if (!create_fb(test, &lease)) { + close_lease(test, &lease); + return 0; + } + + if (!modeset_lease(test, &lease)) { + close_lease(test, &lease); + return 0; + } + sleep(5); /* slightly annoying, but monitors are slow */ + + close_lease(test, &lease); + + return 1; + +fail: + + close_lease(test, &lease); + +fail_restore: + + /* Restore our original connection */ + test->conn = save_conn; + + return 0; +} + +static int +lease_access_twice(test_t *test_1, xcb_randr_output_t output) +{ + lease_t lease_1, lease_2; + test_t test_2; + + if (!test_init(&test_2)) + return 0; + + if (!test_get_randr_resources(&test_2)) + return 0; + + init_lease(&lease_1); + init_lease(&lease_2); + + if (!make_lease(test_1, &lease_1, output, XCB_NONE)) + goto fail_test; + + xcb_generic_error_t *error = NULL; + + /* Attempt to create the lease again on a new connection using the + * same resources + */ + + if (make_randr_lease(&test_2, &lease_2, output, lease_1.randr_crtc, false, &error)) { + printf("\t\tcreate lease twice succeeded\n"); + goto fail_lease_1; + } + + if (!error) { + printf("\t\tno error reported from lease failure\n"); + goto fail_lease_1; + } + + if (error->error_code != XCB_ACCESS) { + printf("\t\terror code %d, not Access\n", error->error_code); + goto fail_lease_1; + } + + close_lease(&test_2, &lease_2); + + close_lease(test_1, &lease_1); + + test_fini(&test_2); + + return 1; + +fail_lease_2: + close_lease(&test_2, &lease_2); +fail_lease_1: + close_lease(test_1, &lease_1); + +fail_test: + test_fini(&test_2); + + return 0; +} + +/* + * First, just try to light up the monitors using the leased DRM fd. + * + * Then, test the following assertions: + * + * RRFreeLease + * + * 1. The X lease can be freed before the kernel fd + * + * 2. The kernel fd can be freed before the X lease + * + * RRCreateLease + * + * 1. Leased resources are not usable through X + * + * 2. Leased crtcs are reported as having no outputs + * + * 3. Leased crtcs will show as Disabled + * + * 4. Leased outputs will have no crtcs, no clones and show disconnected + * + * 5. Lease remains in effect until the FD is closed, even if the + * client disconnects from the X server. + * + * 6. Returns an Access error if any resources are already leased to + * another client + */ + +static struct { + const char *name; + int (*func)(test_t *test, xcb_randr_output_t output); +} tests[] = { + + /* Touch test including DRM */ + { "lease_simple", lease_simple }, + + /* RRFreeLease tests */ + { "lease_close_x_first", lease_close_x_first }, + { "lease_close_kernel_first", lease_close_kernel_first }, + + /* RRCreateLease tests */ + { "lease_not_use_x", lease_not_use_x }, + { "lease_crtc_no_possible_outputs", lease_crtc_no_possible_outputs }, + { "lease_crtc_disabled", lease_crtc_disabled }, + { "lease_output_no_crtcs_clones_connection", lease_outputs_no_crtcs_clones_connection }, + { "lease_active_after_x", lease_active_after_x }, + { "lease_access_twice", lease_access_twice }, + + {} +}; + +int main (int argc, char **argv) +{ + int failed = 0; + + for (int t = 0; tests[t].name; t++) { + int screen; + test_t test; + + printf("Starting test %s\n", tests[t].name); + + if (!test_init(&test)) + exit(1); + + for (int o = 0; ; o++) { + + if (!test_get_randr_resources(&test)) + break; + + if (o >= test.gsr_r->num_outputs) + break; + + xcb_randr_output_t *ro = xcb_randr_get_screen_resources_outputs(test.gsr_r); + xcb_randr_output_t output = ro[o]; + xcb_randr_get_output_info_cookie_t goi_c = xcb_randr_get_output_info(test.conn, output, test.config_timestamp); + + xcb_randr_get_output_info_reply_t *goi_r = xcb_randr_get_output_info_reply(test.conn, goi_c, NULL); + + uint8_t *output_name = xcb_randr_get_output_info_name(goi_r); + int output_name_length = xcb_randr_get_output_info_name_length(goi_r); + + if (goi_r->connection != XCB_RANDR_CONNECTION_CONNECTED && goi_r->num_modes == 0) { + printf("\tTest %s %*.*s skipped\n", tests[t].name, + output_name_length, output_name_length, output_name); + } else { + if ((tests[t].func)(&test, output)) { + printf ("\tTest %s %*.*s passed\n", tests[t].name, + output_name_length, output_name_length, output_name); + } else { + printf ("\tTest %s %*.*s failed\n", tests[t].name, + output_name_length, output_name_length, output_name); + ++failed; + } + } + free(goi_r); + } + + test_fini(&test); + } + + exit(failed); +} |