| /* |
| * Copyright © 2017 Keith Packard |
| * |
| * 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. |
| */ |
| |
| /** @file kms_lease.c |
| * |
| * This is a test of DRM leases |
| */ |
| |
| |
| #include "igt.h" |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <errno.h> |
| #include <time.h> |
| #include <sys/poll.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| |
| #include <libudev.h> |
| |
| #include <drm.h> |
| |
| IGT_TEST_DESCRIPTION("Test of CreateLease."); |
| |
| struct local_drm_mode_create_lease { |
| /** Pointer to array of object ids (__u32) */ |
| __u64 object_ids; |
| /** Number of object ids */ |
| __u32 object_count; |
| /** flags for new FD (O_CLOEXEC, etc) */ |
| __u32 flags; |
| |
| /** Return: unique identifier for lessee. */ |
| __u32 lessee_id; |
| /** Return: file descriptor to new drm_master file */ |
| __u32 fd; |
| }; |
| |
| struct local_drm_mode_list_lessees { |
| /** Number of lessees. |
| * On input, provides length of the array. |
| * On output, provides total number. No |
| * more than the input number will be written |
| * back, so two calls can be used to get |
| * the size and then the data. |
| */ |
| __u32 count_lessees; |
| __u32 pad; |
| |
| /** Pointer to lessees. |
| * pointer to __u64 array of lessee ids |
| */ |
| __u64 lessees_ptr; |
| }; |
| |
| struct local_drm_mode_get_lease { |
| /** Number of leased objects. |
| * On input, provides length of the array. |
| * On output, provides total number. No |
| * more than the input number will be written |
| * back, so two calls can be used to get |
| * the size and then the data. |
| */ |
| __u32 count_objects; |
| __u32 pad; |
| |
| /** Pointer to objects. |
| * pointer to __u32 array of object ids |
| */ |
| __u64 objects_ptr; |
| }; |
| |
| /** |
| * Revoke lease |
| */ |
| struct local_drm_mode_revoke_lease { |
| /** Unique ID of lessee |
| */ |
| __u32 lessee_id; |
| }; |
| |
| |
| #define LOCAL_DRM_IOCTL_MODE_CREATE_LEASE DRM_IOWR(0xC6, struct local_drm_mode_create_lease) |
| #define LOCAL_DRM_IOCTL_MODE_LIST_LESSEES DRM_IOWR(0xC7, struct local_drm_mode_list_lessees) |
| #define LOCAL_DRM_IOCTL_MODE_GET_LEASE DRM_IOWR(0xC8, struct local_drm_mode_get_lease) |
| #define LOCAL_DRM_IOCTL_MODE_REVOKE_LEASE DRM_IOWR(0xC9, struct local_drm_mode_revoke_lease) |
| |
| typedef struct { |
| int fd; |
| uint32_t lessee_id; |
| igt_display_t display; |
| struct igt_fb primary_fb; |
| igt_output_t *output; |
| drmModeModeInfo *mode; |
| } lease_t; |
| |
| typedef struct { |
| lease_t master; |
| enum pipe pipe; |
| uint32_t crtc_id; |
| uint32_t connector_id; |
| uint32_t plane_id; |
| } data_t; |
| |
| static uint32_t pipe_to_crtc_id(igt_display_t *display, enum pipe pipe) |
| { |
| return display->pipes[pipe].crtc_id; |
| } |
| |
| static enum pipe crtc_id_to_pipe(igt_display_t *display, uint32_t crtc_id) |
| { |
| enum pipe pipe; |
| |
| for (pipe = 0; pipe < display->n_pipes; pipe++) |
| if (display->pipes[pipe].crtc_id == crtc_id) |
| return pipe; |
| return -1; |
| } |
| |
| static igt_output_t *connector_id_to_output(igt_display_t *display, uint32_t connector_id) |
| { |
| drmModeConnector connector; |
| |
| connector.connector_id = connector_id; |
| return igt_output_from_connector(display, &connector); |
| } |
| |
| static int prepare_crtc(lease_t *lease, uint32_t connector_id, uint32_t crtc_id) |
| { |
| drmModeModeInfo *mode; |
| igt_display_t *display = &lease->display; |
| igt_output_t *output = connector_id_to_output(display, connector_id); |
| enum pipe pipe = crtc_id_to_pipe(display, crtc_id); |
| igt_plane_t *primary; |
| int ret; |
| |
| if (!output) |
| return -ENOENT; |
| |
| /* select the pipe we want to use */ |
| igt_output_set_pipe(output, pipe); |
| |
| /* create and set the primary plane fb */ |
| mode = igt_output_get_mode(output); |
| igt_create_color_fb(lease->fd, mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, |
| LOCAL_DRM_FORMAT_MOD_NONE, |
| 0.0, 0.0, 0.0, |
| &lease->primary_fb); |
| |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_plane_set_fb(primary, &lease->primary_fb); |
| |
| ret = igt_display_try_commit2(display, COMMIT_LEGACY); |
| |
| if (ret) |
| return ret; |
| |
| igt_wait_for_vblank(lease->fd, pipe); |
| |
| lease->output = output; |
| lease->mode = mode; |
| return 0; |
| } |
| |
| static void cleanup_crtc(lease_t *lease, igt_output_t *output) |
| { |
| igt_display_t *display = &lease->display; |
| igt_plane_t *primary; |
| |
| igt_remove_fb(lease->fd, &lease->primary_fb); |
| |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_plane_set_fb(primary, NULL); |
| |
| igt_output_set_pipe(output, PIPE_ANY); |
| igt_display_commit(display); |
| } |
| |
| static int create_lease(int fd, struct local_drm_mode_create_lease *mcl) |
| { |
| int err = 0; |
| |
| if (igt_ioctl(fd, LOCAL_DRM_IOCTL_MODE_CREATE_LEASE, mcl)) |
| err = -errno; |
| return err; |
| } |
| |
| static int revoke_lease(int fd, struct local_drm_mode_revoke_lease *mrl) |
| { |
| int err = 0; |
| |
| if (igt_ioctl(fd, LOCAL_DRM_IOCTL_MODE_REVOKE_LEASE, mrl)) |
| err = -errno; |
| return err; |
| } |
| |
| static int list_lessees(int fd, struct local_drm_mode_list_lessees *mll) |
| { |
| int err = 0; |
| |
| if (igt_ioctl(fd, LOCAL_DRM_IOCTL_MODE_LIST_LESSEES, mll)) |
| err = -errno; |
| return err; |
| } |
| |
| static int get_lease(int fd, struct local_drm_mode_get_lease *mgl) |
| { |
| int err = 0; |
| |
| if (igt_ioctl(fd, LOCAL_DRM_IOCTL_MODE_GET_LEASE, mgl)) |
| err = -errno; |
| return err; |
| } |
| |
| static int make_lease(data_t *data, lease_t *lease) |
| { |
| uint32_t object_ids[3]; |
| struct local_drm_mode_create_lease mcl; |
| int ret; |
| |
| mcl.object_ids = (uint64_t) (uintptr_t) &object_ids[0]; |
| mcl.object_count = 0; |
| mcl.flags = 0; |
| |
| object_ids[mcl.object_count++] = data->connector_id; |
| object_ids[mcl.object_count++] = data->crtc_id; |
| /* We use universal planes, must add the primary plane */ |
| object_ids[mcl.object_count++] = data->plane_id; |
| |
| ret = create_lease(data->master.fd, &mcl); |
| |
| if (ret) |
| return ret; |
| |
| lease->fd = mcl.fd; |
| lease->lessee_id = mcl.lessee_id; |
| return 0; |
| } |
| |
| static void terminate_lease(lease_t *lease) |
| { |
| close(lease->fd); |
| } |
| |
| static int paint_fb(int drm_fd, struct igt_fb *fb, const char *test_name, |
| const char *mode_format_str, const char *connector_str, const char *pipe_str) |
| { |
| cairo_t *cr; |
| |
| cr = igt_get_cairo_ctx(drm_fd, fb); |
| |
| igt_paint_color_gradient(cr, 0, 0, fb->width, fb->height, 1, 1, 1); |
| igt_paint_test_pattern(cr, fb->width, fb->height); |
| |
| cairo_move_to(cr, fb->width / 2, fb->height / 2); |
| cairo_set_font_size(cr, 36); |
| igt_cairo_printf_line(cr, align_hcenter, 10, "%s", test_name); |
| igt_cairo_printf_line(cr, align_hcenter, 10, "%s", mode_format_str); |
| igt_cairo_printf_line(cr, align_hcenter, 10, "%s", connector_str); |
| igt_cairo_printf_line(cr, align_hcenter, 10, "%s", pipe_str); |
| |
| cairo_destroy(cr); |
| |
| return 0; |
| } |
| |
| static void simple_lease(data_t *data) |
| { |
| lease_t lease; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease), 0); |
| |
| igt_display_require(&lease.display, lease.fd); |
| |
| /* Set a mode on the leased output */ |
| igt_assert_eq(0, prepare_crtc(&lease, data->connector_id, data->crtc_id)); |
| |
| /* Paint something attractive */ |
| paint_fb(lease.fd, &lease.primary_fb, "simple_lease", |
| lease.mode->name, igt_output_name(lease.output), kmstest_pipe_name(data->pipe)); |
| igt_debug_wait_for_keypress("lease"); |
| cleanup_crtc(&lease, |
| connector_id_to_output(&lease.display, data->connector_id)); |
| |
| terminate_lease(&lease); |
| } |
| |
| /* Test listing lessees */ |
| static void lessee_list(data_t *data) |
| { |
| lease_t lease; |
| struct local_drm_mode_list_lessees mll; |
| uint32_t lessees[1]; |
| |
| mll.pad = 0; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease), 0); |
| |
| /* check for nested leases */ |
| mll.count_lessees = 0; |
| mll.lessees_ptr = 0; |
| igt_assert_eq(list_lessees(lease.fd, &mll), 0); |
| igt_assert_eq(mll.count_lessees, 0); |
| |
| /* Get the number of lessees */ |
| mll.count_lessees = 0; |
| mll.lessees_ptr = 0; |
| igt_assert_eq(list_lessees(data->master.fd, &mll), 0); |
| |
| /* Make sure there's a single lessee */ |
| igt_assert_eq(mll.count_lessees, 1); |
| |
| /* invalid ptr */ |
| igt_assert_eq(list_lessees(data->master.fd, &mll), -EFAULT); |
| |
| mll.lessees_ptr = (uint64_t) (uintptr_t) &lessees[0]; |
| |
| igt_assert_eq(list_lessees(data->master.fd, &mll), 0); |
| |
| /* Make sure there's a single lessee */ |
| igt_assert_eq(mll.count_lessees, 1); |
| |
| /* Make sure the listed lease is the same as the one we created */ |
| igt_assert_eq(lessees[0], lease.lessee_id); |
| |
| /* invalid pad */ |
| mll.pad = -1; |
| igt_assert_eq(list_lessees(data->master.fd, &mll), -EINVAL); |
| mll.pad = 0; |
| |
| terminate_lease(&lease); |
| |
| /* Make sure the lease is gone */ |
| igt_assert_eq(list_lessees(data->master.fd, &mll), 0); |
| igt_assert_eq(mll.count_lessees, 0); |
| } |
| |
| /* Test getting the contents of a lease */ |
| static void lease_get(data_t *data) |
| { |
| lease_t lease; |
| struct local_drm_mode_get_lease mgl; |
| int num_leased_obj = 3; |
| uint32_t objects[num_leased_obj]; |
| int o; |
| |
| mgl.pad = 0; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease), 0); |
| |
| /* Get the number of objects */ |
| mgl.count_objects = 0; |
| mgl.objects_ptr = 0; |
| igt_assert_eq(get_lease(lease.fd, &mgl), 0); |
| |
| /* Make sure it's 2 */ |
| igt_assert_eq(mgl.count_objects, num_leased_obj); |
| |
| /* Get the objects */ |
| mgl.objects_ptr = (uint64_t) (uintptr_t) objects; |
| |
| igt_assert_eq(get_lease(lease.fd, &mgl), 0); |
| |
| /* Make sure it's 2 */ |
| igt_assert_eq(mgl.count_objects, num_leased_obj); |
| |
| /* Make sure we got the connector, crtc and plane back */ |
| for (o = 0; o < num_leased_obj; o++) |
| if (objects[o] == data->connector_id) |
| break; |
| |
| igt_assert_neq(o, num_leased_obj); |
| |
| for (o = 0; o < num_leased_obj; o++) |
| if (objects[o] == data->crtc_id) |
| break; |
| |
| igt_assert_neq(o, num_leased_obj); |
| |
| for (o = 0; o < num_leased_obj; o++) |
| if (objects[o] == data->plane_id) |
| break; |
| |
| igt_assert_neq(o, num_leased_obj); |
| |
| /* invalid pad */ |
| mgl.pad = -1; |
| igt_assert_eq(get_lease(lease.fd, &mgl), -EINVAL); |
| mgl.pad = 0; |
| |
| /* invalid pointer */ |
| mgl.objects_ptr = 0; |
| igt_assert_eq(get_lease(lease.fd, &mgl), -EFAULT); |
| |
| terminate_lease(&lease); |
| } |
| |
| static void lease_unleased_crtc(data_t *data) |
| { |
| lease_t lease; |
| enum pipe p; |
| uint32_t bad_crtc_id; |
| drmModeCrtc *crtc; |
| int ret; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease), 0); |
| |
| igt_display_require(&lease.display, lease.fd); |
| |
| /* Find another CRTC that we don't control */ |
| bad_crtc_id = 0; |
| for (p = 0; bad_crtc_id == 0 && p < data->master.display.n_pipes; p++) { |
| if (pipe_to_crtc_id(&data->master.display, p) != data->crtc_id) |
| bad_crtc_id = pipe_to_crtc_id(&data->master.display, p); |
| } |
| |
| /* Give up if there isn't another crtc */ |
| igt_skip_on(bad_crtc_id == 0); |
| |
| /* sanity check */ |
| ret = drmModeSetCrtc(lease.fd, data->crtc_id, 0, 0, 0, NULL, 0, NULL); |
| igt_assert_eq(ret, 0); |
| crtc = drmModeGetCrtc(lease.fd, data->crtc_id); |
| igt_assert(crtc); |
| drmModeFreeCrtc(crtc); |
| |
| /* Attempt to use the unleased crtc id. We need raw ioctl to bypass the |
| * igt_kms helpers. |
| */ |
| ret = drmModeSetCrtc(lease.fd, bad_crtc_id, 0, 0, 0, NULL, 0, NULL); |
| igt_assert_eq(ret, -ENOENT); |
| crtc = drmModeGetCrtc(lease.fd, bad_crtc_id); |
| igt_assert(!crtc); |
| igt_assert_eq(errno, ENOENT); |
| drmModeFreeCrtc(crtc); |
| |
| terminate_lease(&lease); |
| } |
| |
| static void lease_unleased_connector(data_t *data) |
| { |
| lease_t lease; |
| int o; |
| uint32_t bad_connector_id; |
| drmModeConnector *c; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease), 0); |
| |
| igt_display_require(&lease.display, lease.fd); |
| |
| /* Find another connector that we don't control */ |
| bad_connector_id = 0; |
| for (o = 0; bad_connector_id == 0 && o < data->master.display.n_outputs; o++) { |
| if (data->master.display.outputs[o].id != data->connector_id) |
| bad_connector_id = data->master.display.outputs[o].id; |
| } |
| |
| /* Give up if there isn't another connector */ |
| igt_skip_on(bad_connector_id == 0); |
| |
| /* sanity check */ |
| c = drmModeGetConnector(lease.fd, data->connector_id); |
| igt_assert(c); |
| |
| /* Attempt to use the unleased connector id. Note that the |
| */ |
| c = drmModeGetConnector(lease.fd, bad_connector_id); |
| igt_assert(!c); |
| igt_assert_eq(errno, ENOENT); |
| |
| terminate_lease(&lease); |
| } |
| |
| /* Test revocation of lease */ |
| static void lease_revoke(data_t *data) |
| { |
| lease_t lease; |
| struct local_drm_mode_revoke_lease mrl; |
| int ret; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease), 0); |
| |
| igt_display_require(&lease.display, lease.fd); |
| |
| /* try to revoke an invalid lease */ |
| mrl.lessee_id = 0; |
| igt_assert_eq(revoke_lease(data->master.fd, &mrl), -ENOENT); |
| |
| /* try to revoke with the wrong fd */ |
| mrl.lessee_id = lease.lessee_id; |
| igt_assert_eq(revoke_lease(lease.fd, &mrl), -EACCES); |
| |
| /* Revoke the lease using the master fd */ |
| mrl.lessee_id = lease.lessee_id; |
| igt_assert_eq(revoke_lease(data->master.fd, &mrl), 0); |
| |
| /* Try to use the leased objects */ |
| ret = prepare_crtc(&lease, data->connector_id, data->crtc_id); |
| |
| /* Ensure that the expected error is returned */ |
| igt_assert_eq(ret, -ENOENT); |
| |
| terminate_lease(&lease); |
| |
| /* make sure the lease is gone */ |
| mrl.lessee_id = lease.lessee_id; |
| igt_assert_eq(revoke_lease(data->master.fd, &mrl), -ENOENT); |
| } |
| |
| /* Test leasing objects more than once */ |
| static void lease_again(data_t *data) |
| { |
| lease_t lease_a, lease_b; |
| |
| /* Create a valid lease */ |
| igt_assert_eq(make_lease(data, &lease_a), 0); |
| |
| /* Attempt to re-lease the same objects */ |
| igt_assert_eq(make_lease(data, &lease_b), -EBUSY); |
| |
| terminate_lease(&lease_a); |
| |
| /* Now attempt to lease the same objects */ |
| igt_assert_eq(make_lease(data, &lease_b), 0); |
| |
| terminate_lease(&lease_b); |
| } |
| |
| /* Test leasing an invalid connector */ |
| static void lease_invalid_connector(data_t *data) |
| { |
| lease_t lease; |
| uint32_t save_connector_id; |
| int ret; |
| |
| /* Create an invalid lease */ |
| save_connector_id = data->connector_id; |
| data->connector_id = 0xbaadf00d; |
| ret = make_lease(data, &lease); |
| data->connector_id = save_connector_id; |
| igt_assert_eq(ret, -EINVAL); |
| } |
| |
| /* Test leasing an invalid crtc */ |
| static void lease_invalid_crtc(data_t *data) |
| { |
| lease_t lease; |
| uint32_t save_crtc_id; |
| int ret; |
| |
| /* Create an invalid lease */ |
| save_crtc_id = data->crtc_id; |
| data->crtc_id = 0xbaadf00d; |
| ret = make_lease(data, &lease); |
| data->crtc_id = save_crtc_id; |
| igt_assert_eq(ret, -EINVAL); |
| } |
| |
| static void lease_invalid_plane(data_t *data) |
| { |
| lease_t lease; |
| uint32_t save_plane_id; |
| int ret; |
| |
| /* Create an invalid lease */ |
| save_plane_id = data->plane_id; |
| data->plane_id = 0xbaadf00d; |
| ret = make_lease(data, &lease); |
| data->plane_id = save_plane_id; |
| igt_assert_eq(ret, -EINVAL); |
| } |
| |
| |
| static void run_test(data_t *data, void (*testfunc)(data_t *)) |
| { |
| lease_t *master = &data->master; |
| igt_display_t *display = &master->display; |
| igt_output_t *output; |
| enum pipe p; |
| unsigned int valid_tests = 0; |
| |
| for_each_pipe_with_valid_output(display, p, output) { |
| igt_info("Beginning %s on pipe %s, connector %s\n", |
| igt_subtest_name(), |
| kmstest_pipe_name(p), |
| igt_output_name(output)); |
| |
| data->pipe = p; |
| data->crtc_id = pipe_to_crtc_id(display, p); |
| data->connector_id = output->id; |
| data->plane_id = |
| igt_pipe_get_plane_type(&data->master.display.pipes[data->pipe], |
| DRM_PLANE_TYPE_PRIMARY)->drm_plane->plane_id; |
| |
| testfunc(data); |
| |
| igt_info("\n%s on pipe %s, connector %s: PASSED\n\n", |
| igt_subtest_name(), |
| kmstest_pipe_name(p), |
| igt_output_name(output)); |
| |
| valid_tests++; |
| } |
| |
| igt_require_f(valid_tests, |
| "no valid crtc/connector combinations found\n"); |
| } |
| |
| static void invalid_create_leases(data_t *data) |
| { |
| uint32_t object_ids[4]; |
| struct local_drm_mode_create_lease mcl; |
| drmModeRes *resources; |
| int tmp_fd; |
| |
| /* empty lease */ |
| mcl.object_ids = 0; |
| mcl.object_count = 0; |
| mcl.flags = 0; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EINVAL); |
| |
| /* NULL array pointer */ |
| mcl.object_count = 1; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EFAULT); |
| |
| /* nil object */ |
| object_ids[0] = 0; |
| mcl.object_ids = (uint64_t) (uintptr_t) object_ids; |
| mcl.object_count = 1; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -ENOENT); |
| |
| /* no crtc, non-universal_plane */ |
| drmSetClientCap(data->master.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0); |
| object_ids[0] = data->master.display.outputs[0].id; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EINVAL); |
| |
| /* no connector, non-universal_plane */ |
| object_ids[0] = data->master.display.pipes[0].crtc_id; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EINVAL); |
| |
| /* sanity check */ |
| object_ids[0] = data->master.display.pipes[0].crtc_id; |
| object_ids[1] = data->master.display.outputs[0].id; |
| mcl.object_count = 2; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| close(mcl.fd); |
| |
| /* no plane, universal planes */ |
| drmSetClientCap(data->master.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EINVAL); |
| |
| /* sanity check */ |
| object_ids[2] = igt_pipe_get_plane_type(&data->master.display.pipes[0], |
| DRM_PLANE_TYPE_PRIMARY)->drm_plane->plane_id; |
| mcl.object_count = 3; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| close(mcl.fd); |
| |
| /* array overflow, do a small scan around overflow sizes */ |
| for (int i = 1; i <= 4; i++) { |
| mcl.object_count = UINT32_MAX / sizeof(object_ids[0]) + i; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -ENOMEM); |
| } |
| |
| /* sanity check */ |
| mcl.object_count = 3; |
| mcl.flags = O_CLOEXEC | O_NONBLOCK; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| close(mcl.fd); |
| |
| /* invalid flags */ |
| mcl.flags = -1; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EINVAL); |
| |
| /* no subleasing */ |
| mcl.object_count = 3; |
| mcl.flags = 0; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| tmp_fd = mcl.fd; |
| igt_assert_eq(create_lease(tmp_fd, &mcl), -EINVAL); |
| close(tmp_fd); |
| |
| /* no double-leasing */ |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| tmp_fd = mcl.fd; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EBUSY); |
| close(tmp_fd); |
| |
| /* no double leasing */ |
| object_ids[3] = object_ids[2]; |
| mcl.object_count = 4; |
| /* Note: the ENOSPC is from idr double-insertion failing */ |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -ENOSPC); |
| |
| /* no encoder leasing */ |
| resources = drmModeGetResources(data->master.fd); |
| igt_assert(resources); |
| igt_assert(resources->count_encoders > 0); |
| object_ids[3] = resources->encoders[0]; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -EINVAL); |
| drmModeFreeResources(resources); |
| } |
| |
| static void check_crtc_masks(int master_fd, int lease_fd, uint32_t crtc_mask) |
| { |
| drmModeRes *resources; |
| drmModePlaneRes *plane_resources; |
| int i; |
| |
| resources = drmModeGetResources(master_fd); |
| igt_assert(resources); |
| plane_resources = drmModeGetPlaneResources(master_fd); |
| igt_assert(plane_resources); |
| |
| for (i = 0; i < resources->count_encoders; i++) { |
| drmModeEncoder *master_e, *lease_e; |
| bool possible; |
| |
| master_e = drmModeGetEncoder(master_fd, resources->encoders[i]); |
| igt_assert(master_e); |
| lease_e = drmModeGetEncoder(lease_fd, resources->encoders[i]); |
| igt_assert(lease_e); |
| |
| possible = master_e->possible_crtcs & crtc_mask; |
| |
| igt_assert_eq(lease_e->possible_crtcs, |
| possible ? 1 : 0); |
| igt_assert_eq(master_e->possible_crtcs & crtc_mask, |
| possible ? crtc_mask : 0); |
| drmModeFreeEncoder(master_e); |
| drmModeFreeEncoder(lease_e); |
| } |
| |
| for (i = 0; i < plane_resources->count_planes; i++) { |
| drmModePlane *master_p, *lease_p; |
| bool possible; |
| |
| master_p = drmModeGetPlane(master_fd, plane_resources->planes[i]); |
| igt_assert(master_p); |
| lease_p = drmModeGetPlane(lease_fd, plane_resources->planes[i]); |
| igt_assert(lease_p); |
| |
| possible = master_p->possible_crtcs & crtc_mask; |
| |
| igt_assert_eq(lease_p->possible_crtcs, |
| possible ? 1 : 0); |
| igt_assert_eq(master_p->possible_crtcs & crtc_mask, |
| possible ? crtc_mask : 0); |
| drmModeFreePlane(master_p); |
| drmModeFreePlane(lease_p); |
| } |
| |
| drmModeFreePlaneResources(plane_resources); |
| drmModeFreeResources(resources); |
| } |
| |
| static void possible_crtcs_filtering(data_t *data) |
| { |
| uint32_t *object_ids; |
| struct local_drm_mode_create_lease mcl; |
| drmModeRes *resources; |
| drmModePlaneRes *plane_resources; |
| int i; |
| int master_fd = data->master.fd; |
| |
| resources = drmModeGetResources(master_fd); |
| igt_assert(resources); |
| plane_resources = drmModeGetPlaneResources(master_fd); |
| igt_assert(plane_resources); |
| mcl.object_count = resources->count_connectors + |
| plane_resources->count_planes + 1; |
| object_ids = calloc(mcl.object_count, sizeof(*object_ids)); |
| igt_assert(object_ids); |
| |
| for (i = 0; i < resources->count_connectors; i++) |
| object_ids[i] = resources->connectors[i]; |
| |
| for (i = 0; i < plane_resources->count_planes; i++) |
| object_ids[i + resources->count_connectors] = |
| plane_resources->planes[i]; |
| |
| mcl.object_ids = (uint64_t) (uintptr_t) object_ids; |
| mcl.flags = 0; |
| |
| for (i = 0; i < resources->count_crtcs; i++) { |
| int lease_fd; |
| |
| object_ids[mcl.object_count - 1] = |
| resources->crtcs[i]; |
| |
| igt_assert_eq(create_lease(master_fd, &mcl), 0); |
| lease_fd = mcl.fd; |
| |
| drmSetClientCap(lease_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); |
| |
| check_crtc_masks(master_fd, lease_fd, 1 << i); |
| |
| close(lease_fd); |
| } |
| |
| free(object_ids); |
| drmModeFreePlaneResources(plane_resources); |
| drmModeFreeResources(resources); |
| } |
| |
| static bool is_master(int fd) |
| { |
| /* FIXME: replace with drmIsMaster once we bumped libdrm version */ |
| return drmAuthMagic(fd, 0) != -EACCES; |
| } |
| |
| static int _create_simple_lease(int master_fd, data_t *data, int expected_ret) |
| { |
| uint32_t object_ids[3]; |
| struct local_drm_mode_create_lease mcl; |
| |
| object_ids[0] = data->master.display.pipes[0].crtc_id; |
| object_ids[1] = data->master.display.outputs[0].id; |
| object_ids[2] = igt_pipe_get_plane_type(&data->master.display.pipes[0], |
| DRM_PLANE_TYPE_PRIMARY)->drm_plane->plane_id; |
| mcl.object_ids = (uint64_t) (uintptr_t) object_ids; |
| mcl.object_count = 3; |
| mcl.flags = 0; |
| |
| igt_assert_eq(create_lease(master_fd, &mcl), expected_ret); |
| |
| return expected_ret == 0 ? mcl.fd : 0; |
| } |
| |
| static int create_simple_lease(int master_fd, data_t *data) |
| { |
| return _create_simple_lease(master_fd, data, 0); |
| } |
| |
| /* check lease master status in lockdep with lessors, but can't change it |
| * themselves */ |
| static void master_vs_lease(data_t *data) |
| { |
| int lease_fd; |
| |
| lease_fd = create_simple_lease(data->master.fd, data); |
| |
| igt_assert_eq(drmDropMaster(lease_fd), -1); |
| igt_assert_eq(errno, EINVAL); |
| |
| igt_assert(is_master(data->master.fd)); |
| igt_assert(is_master(lease_fd)); |
| |
| do_or_die(drmDropMaster(data->master.fd)); |
| |
| igt_assert(!is_master(data->master.fd)); |
| igt_assert(!is_master(lease_fd)); |
| |
| igt_assert_eq(drmSetMaster(lease_fd), -1); |
| igt_assert_eq(errno, EINVAL); |
| |
| do_or_die(drmSetMaster(data->master.fd)); |
| |
| igt_assert(is_master(data->master.fd)); |
| igt_assert(is_master(lease_fd)); |
| |
| close(lease_fd); |
| } |
| |
| static void multimaster_lease(data_t *data) |
| { |
| int lease_fd, master2_fd, lease2_fd; |
| |
| lease_fd = create_simple_lease(data->master.fd, data); |
| |
| igt_assert(is_master(data->master.fd)); |
| igt_assert(is_master(lease_fd)); |
| |
| master2_fd = drm_open_driver(DRIVER_ANY); |
| |
| igt_assert(!is_master(master2_fd)); |
| |
| _create_simple_lease(master2_fd, data, -EACCES); |
| |
| do_or_die(drmDropMaster(data->master.fd)); |
| do_or_die(drmSetMaster(master2_fd)); |
| |
| igt_assert(!is_master(data->master.fd)); |
| igt_assert(!is_master(lease_fd)); |
| igt_assert(is_master(master2_fd)); |
| |
| drmSetClientCap(master2_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); |
| lease2_fd = create_simple_lease(master2_fd, data); |
| |
| close(master2_fd); /* close is an implicit DropMaster */ |
| igt_assert(!is_master(lease2_fd)); |
| |
| do_or_die(drmSetMaster(data->master.fd)); |
| igt_assert(is_master(data->master.fd)); |
| igt_assert(is_master(lease_fd)); |
| |
| close(lease2_fd); |
| close(lease_fd); |
| } |
| |
| static void implicit_plane_lease(data_t *data) |
| { |
| uint32_t object_ids[3]; |
| struct local_drm_mode_create_lease mcl; |
| struct local_drm_mode_get_lease mgl; |
| |
| uint32_t cursor_id = igt_pipe_get_plane_type(&data->master.display.pipes[0], |
| DRM_PLANE_TYPE_CURSOR)->drm_plane->plane_id; |
| |
| object_ids[0] = data->master.display.pipes[0].crtc_id; |
| object_ids[1] = data->master.display.outputs[0].id; |
| object_ids[2] = igt_pipe_get_plane_type(&data->master.display.pipes[0], |
| DRM_PLANE_TYPE_PRIMARY)->drm_plane->plane_id; |
| mcl.object_ids = (uint64_t) (uintptr_t) object_ids; |
| mcl.object_count = 3; |
| mcl.flags = 0; |
| |
| /* sanity check */ |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| close(mcl.fd); |
| drmSetClientCap(data->master.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0); |
| |
| /* non universal plane automatically adds primary/cursor plane */ |
| mcl.object_count = 2; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), 0); |
| |
| mgl.pad = 0; |
| mgl.count_objects = 0; |
| mgl.objects_ptr = 0; |
| igt_assert_eq(get_lease(mcl.fd, &mgl), 0); |
| |
| igt_assert_eq(mgl.count_objects, 3 + (cursor_id ? 1 : 0)); |
| |
| close(mcl.fd); |
| |
| /* check that implicit lease doesn't lead to confusion when |
| * explicitly adding primary plane */ |
| mcl.object_count = 3; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -ENOSPC); |
| |
| /* same for the cursor */ |
| object_ids[2] = cursor_id; |
| igt_assert_eq(create_lease(data->master.fd, &mcl), -ENOSPC); |
| |
| drmSetClientCap(data->master.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); |
| } |
| |
| static void lease_uevent(data_t *data) |
| { |
| int lease_fd; |
| struct local_drm_mode_list_lessees mll; |
| struct udev_monitor *uevent_monitor; |
| |
| uevent_monitor = igt_watch_hotplug(); |
| |
| igt_flush_hotplugs(uevent_monitor); |
| |
| lease_fd = create_simple_lease(data->master.fd, data); |
| |
| igt_assert(!igt_lease_change_detected(uevent_monitor, 1)); |
| |
| mll.pad = 0; |
| mll.count_lessees = 0; |
| mll.lessees_ptr = 0; |
| igt_assert_eq(list_lessees(data->master.fd, &mll), 0); |
| igt_assert_eq(mll.count_lessees, 1); |
| |
| close(lease_fd); |
| |
| igt_assert(igt_lease_change_detected(uevent_monitor, 1)); |
| |
| mll.lessees_ptr = 0; |
| igt_assert_eq(list_lessees(data->master.fd, &mll), 0); |
| igt_assert_eq(mll.count_lessees, 0); |
| |
| igt_cleanup_hotplug(uevent_monitor); |
| } |
| |
| igt_main |
| { |
| data_t data; |
| const struct { |
| const char *name; |
| void (*func)(data_t *); |
| } funcs[] = { |
| { "simple_lease", simple_lease }, |
| { "lessee_list", lessee_list }, |
| { "lease_get", lease_get }, |
| { "lease_unleased_connector", lease_unleased_connector }, |
| { "lease_unleased_crtc", lease_unleased_crtc }, |
| { "lease_revoke", lease_revoke }, |
| { "lease_again", lease_again }, |
| { "lease_invalid_connector", lease_invalid_connector }, |
| { "lease_invalid_crtc", lease_invalid_crtc }, |
| { "lease_invalid_plane", lease_invalid_plane }, |
| { } |
| }, *f; |
| |
| igt_fixture { |
| data.master.fd = drm_open_driver_master(DRIVER_ANY); |
| kmstest_set_vt_graphics_mode(); |
| igt_display_require(&data.master.display, data.master.fd); |
| } |
| |
| for (f = funcs; f->name; f++) { |
| |
| igt_subtest_f("%s", f->name) { |
| run_test(&data, f->func); |
| } |
| } |
| |
| igt_subtest("invalid-create-leases") |
| invalid_create_leases(&data); |
| |
| igt_subtest("possible-crtcs-filtering") |
| possible_crtcs_filtering(&data); |
| |
| igt_subtest("master-vs-lease") |
| master_vs_lease(&data); |
| |
| igt_subtest("multimaster-lease") |
| multimaster_lease(&data); |
| |
| igt_subtest("implicit-plane-lease") |
| implicit_plane_lease(&data); |
| |
| igt_subtest("lease-uevent") |
| lease_uevent(&data); |
| } |