/* * Copyright © 2019 Intel Corporation * * 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. * * Authors: * Madhumitha Tolakanahalli Pradeep * * Manasi Navare * * Tiled display genlocked CRTC test * This test parses the tile information of the connectors that have TILE * property set, sets up the framebuffer with correct offsets corresponding to * the tile offsets and does an atomic modeset with two CRTCs for two * connectors. Page flip event timestamp from each CRTC is collected and * compared to make sure that they occurred in a synchronous manner. * * This test currently supports only horizontally tiled displays, in line with * the displays supported by the kernel at the moment. */ /** * TEST: kms tiled display * Category: Display * Description: Test for Transcoder Port Sync for Display Port Tiled Displays * Driver requirement: i915, xe * Mega feature: General Display Features * Test category: functionality test */ #include "igt.h" #include "poll.h" #include "drm_mode.h" #include "drm_fourcc.h" #include "igt_edid.h" /** * SUBTEST: basic-test-pattern * Description: Make sure the Tiled CRTCs are synchronized and we get page flips * for all tiled CRTCs in one vblank (executes on real panel). * Functionality: tiled_display * * SUBTEST: basic-test-pattern-with-chamelium * Description: Make sure the Tiled CRTCs are synchronized and we get page flips * for all tiled CRTCs in one vblank (executes on chamelium). * Functionality: chamelium, tiled_display */ IGT_TEST_DESCRIPTION("Test for genlocked CRTCs with tiled displays"); typedef struct { igt_output_t *output; igt_tile_info_t tile; enum pipe pipe; drmModeConnectorPtr connector; bool got_page_flip; } data_connector_t; typedef struct { int drm_fd; int num_h_tiles; igt_fb_t fb_test_pattern; igt_display_t display; data_connector_t *conns; struct timeval first_ts; int linetime_us; #ifdef HAVE_CHAMELIUM struct chamelium *chamelium; struct chamelium_port **ports; int port_count; struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT]; #endif } data_t; static int drm_property_is_tile(drmModePropertyPtr prop) { return (strcmp(prop->name, "TILE") ? 0 : 1) && drm_property_type_is(prop, DRM_MODE_PROP_BLOB); } static void get_connector_tile_props(data_t *data, drmModeConnectorPtr conn, igt_tile_info_t *tile) { for (int i = 0; i < conn->count_props; i++) { drmModePropertyBlobPtr blob; drmModePropertyPtr prop; prop = drmModeGetProperty(data->drm_fd, conn->props[i]); igt_assert(prop); blob = NULL; if (drm_property_is_tile(prop)) { blob = drmModeGetPropertyBlob(data->drm_fd, conn->prop_values[i]); if (blob) { igt_parse_connector_tile_blob(blob, tile); drmModeFreePropertyBlob(blob); } } drmModeFreeProperty(prop); if (blob) return; } } static void get_number_of_h_tiles(data_t *data) { igt_tile_info_t tile = {}; drmModeResPtr res; igt_assert(res = drmModeGetResources(data->drm_fd)); for (int i = 0; !data->num_h_tiles && i < res->count_connectors; i++) { drmModeConnectorPtr connector; connector = drmModeGetConnector(data->drm_fd, res->connectors[i]); igt_assert(connector); if (connector->connection == DRM_MODE_CONNECTED) { get_connector_tile_props(data, connector, &tile); data->num_h_tiles = tile.num_h_tile; } drmModeFreeConnector(connector); } drmModeFreeResources(res); } static void get_connectors(data_t *data) { int count = 0; igt_output_t *output; data_connector_t *conns = data->conns; for_each_connected_output(&data->display, output) { conns[count].connector = drmModeGetConnector(data->display.drm_fd, output->id); igt_assert(conns[count].connector); get_connector_tile_props(data, conns[count].connector, &conns[count].tile); if (conns[count].tile.num_h_tile == 0) { drmModeFreeConnector(conns[count].connector); continue; } /* Check if the connectors belong to the same tile group */ if (count > 0) igt_assert(conns[count].tile.tile_group_id == conns[count-1].tile.tile_group_id); count++; } igt_require_f(count == data->num_h_tiles, "All the tiled connectors are not connected\n"); } static void reset_plane(igt_output_t *output) { igt_plane_t *primary; primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); igt_plane_set_fb(primary, NULL); } static void reset_mode(data_t *data) { igt_display_reset(&data->display); igt_display_commit2(&data->display, COMMIT_ATOMIC); } static void test_cleanup(data_t *data) { int count; data_connector_t *conns = data->conns; for (count = 0; count < data->num_h_tiles; count++) { if (conns[count].output) { reset_plane(conns[count].output); igt_output_set_pipe(conns[count].output, PIPE_NONE); } } igt_remove_fb(data->drm_fd, &data->fb_test_pattern); igt_display_commit2(&data->display, COMMIT_ATOMIC); memset(conns, 0, sizeof(data_connector_t) * data->num_h_tiles); } static int mode_linetime_us(const drmModeModeInfo *mode) { return 1000 * mode->htotal / mode->clock; } static void setup_mode(data_t *data) { int count = 0, prev = 0, i = 0; bool pipe_in_use = false, found = false; enum pipe pipe; drmModeModeInfo *mode; igt_output_t *output; data_connector_t *conns = data->conns; /* * The output is set to PIPE_NONE and then assigned a pipe. * This is done to ensure a complete modeset occures every * time the test is run. */ reset_mode(data); for (count = 0; count < data->num_h_tiles; count++) { output = igt_output_from_connector(&data->display, conns[count].connector); for_each_pipe(&data->display, pipe) { pipe_in_use = false; found = false; if (count > 0) { for (prev = count - 1; prev >= 0; prev--) { if (pipe == conns[prev].pipe) { pipe_in_use = true; break; } } if (pipe_in_use) continue; } if (igt_pipe_connector_valid(pipe, output)) { conns[count].pipe = pipe; conns[count].output = output; igt_output_set_pipe(conns[count].output, conns[count].pipe); break; } } igt_require(conns[count].pipe != PIPE_NONE); for (i = 0; i < conns[count].connector->count_modes; i++) { mode = &conns[count].connector->modes[i]; if (mode->vdisplay == conns[count].tile.tile_v_size && mode->hdisplay == conns[count].tile.tile_h_size) { found = true; break; } } igt_require(found); igt_output_override_mode(output, mode); data->linetime_us = mode_linetime_us(mode); } igt_require(intel_pipe_output_combo_valid(&data->display)); igt_display_commit_atomic(&data->display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); } static void setup_framebuffer(data_t *data) { int count; igt_plane_t *primary; int fb_h_size = 0, fb_v_size = 0; data_connector_t *conns = data->conns; for (count = 0; count < data->num_h_tiles; count++) { fb_h_size += conns[count].tile.tile_h_size; /* We support only horizontal tiles, so vertical size is same * for all tiles and needs to be assigned only once. */ if (!fb_v_size) fb_v_size = conns[count].tile.tile_v_size; if (count > 0) igt_assert(conns[count].tile.tile_v_size == conns[count-1].tile.tile_v_size); } igt_create_pattern_fb(data->drm_fd, fb_h_size, fb_v_size, DRM_FORMAT_XBGR8888, DRM_FORMAT_MOD_LINEAR, &data->fb_test_pattern); for (count = 0; count < data->num_h_tiles; count++) { primary = igt_output_get_plane_type(conns[count].output, DRM_PLANE_TYPE_PRIMARY); igt_plane_set_fb(primary, &data->fb_test_pattern); igt_fb_set_size(&data->fb_test_pattern, primary, conns[count].tile.tile_h_size, conns[count].tile.tile_v_size); igt_fb_set_position(&data->fb_test_pattern, primary, (conns[count].tile.tile_h_size * conns[count].tile.tile_h_loc), (conns[count].tile.tile_v_size * conns[count].tile.tile_v_loc)); igt_plane_set_size(primary, conns[count].tile.tile_h_size, conns[count].tile.tile_v_size); } } static data_connector_t *conn_for_crtc(data_t *data, unsigned int crtc_id) { for (int i = 0; i < data->num_h_tiles; i++) { data_connector_t *conn = &data->conns[i]; if (data->display.pipes[conn->pipe].crtc_id == crtc_id) return conn; } return NULL; } static float timeval_float(const struct timeval *tv) { return tv->tv_sec + tv->tv_usec / 1000000.0f; } static void page_flip_handler(int fd, unsigned int seq, unsigned int tv_sec, unsigned int tv_usec, unsigned int crtc_id, void *_data) { data_t *data = _data; data_connector_t *conn = conn_for_crtc(data, crtc_id); struct timeval current_ts = { .tv_sec = tv_sec, .tv_usec = tv_usec, }; struct timeval diff; long usec; if (!timerisset(&data->first_ts)) data->first_ts = current_ts; igt_assert_f(conn, "Got page-flip event for unexpected CRTC %u\n", crtc_id); igt_assert_f(!conn->got_page_flip, "Got two page-flips for CRTC %u\n", crtc_id); igt_debug("Page Flip Event received from CRTC:%d at %.6f\n", crtc_id, timeval_float(¤t_ts)); conn->got_page_flip = true; timersub(¤t_ts, &data->first_ts, &diff); usec = diff.tv_sec * 1000000 + diff.tv_usec; /* * We arbitrarily choose to say that the difference * should be no more than a single scanline. */ igt_fail_on_f(labs(usec) > data->linetime_us, "Mistimed page flip event from CRTC:%d at %.6f (diff %ld usec, max %d usec)\n", crtc_id, timeval_float(¤t_ts), usec, data->linetime_us); } static bool got_all_page_flips(data_t *data) { int i; for (i = 0; i < data->num_h_tiles; i++) { if (!data->conns[i].got_page_flip) return false; } return true; } #ifdef HAVE_CHAMELIUM static void test_with_chamelium(data_t *data) { int i, count = 0; uint8_t htile = 2, vtile = 1; struct edid **edid; data->chamelium = chamelium_init(data->drm_fd, &data->display); igt_require(data->chamelium); data->ports = chamelium_get_ports (data->chamelium, &data->port_count); chamelium_require_connector_present(data->ports, DRM_MODE_CONNECTOR_DisplayPort, data->port_count, 2); edid = igt_kms_get_tiled_edid(htile-1, vtile-1); for (i = 0; i < 2; i++) data->edids[i] = chamelium_new_edid(data->chamelium, edid[i]); for (i = 0; i < data->port_count; i++) { if (chamelium_port_get_type(data->ports[i]) == DRM_MODE_CONNECTOR_DisplayPort) { chamelium_port_set_tiled_edid(data->chamelium, data->ports[i], data->edids[i]); chamelium_plug(data->chamelium, data->ports[i]); chamelium_wait_for_conn_status_change(&data->display, data->chamelium, data->ports[i], DRM_MODE_CONNECTED); count++; } if (count == 2) break; } } #endif static void edid_fill_tile_block(struct edid_ext *ext, int tile, int num_tiles, int hdisplay, int vdisplay) { struct dispid_header *dispid; void *ptr; dispid = ptr = edid_ext_dispid(ext); ptr = dispid_init(ptr); ptr = dispid_block_tiled(ptr, num_tiles, 1, tile, 0, hdisplay, vdisplay, "IGT-TILES"); ptr = dispid_done(dispid, ptr); } static struct edid * edid_with_tile(const struct edid *old_edid, const drmModeModeInfo *mode, int tile, int num_tiles) { struct edid *edid = malloc(edid_get_size(old_edid) + EDID_BLOCK_SIZE); memcpy(edid, old_edid, edid_get_size(old_edid)); edid->extensions_len++; edid_fill_tile_block((struct edid_ext *)&edid[edid->extensions_len], tile, num_tiles, mode->hdisplay, mode->vdisplay); edid_update_checksum(edid); return edid; } static void force_edid_with_tile(data_t *data, igt_output_t *output, const drmModeModeInfo *mode, int tile, int num_tiles) { struct edid *edid; drmModePropertyBlobPtr blob; uint64_t blob_id; kmstest_get_property(data->drm_fd, output->id, DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL, &blob_id, NULL); blob = drmModeGetPropertyBlob(data->drm_fd, blob_id); edid = edid_with_tile(blob->data, mode, tile, num_tiles); drmModeFreePropertyBlob(blob); kmstest_force_edid(data->drm_fd, output->config.connector, edid); free(edid); } static bool mode_equal(const drmModeModeInfo *a, const drmModeModeInfo *b) { return a->hdisplay == b->hdisplay && a->hsync_start == b->hsync_start && a->hsync_end == b->hsync_end && a->htotal == b->htotal && a->vdisplay == b->vdisplay && a->vsync_start == b->vsync_start && a->vsync_end == b->vsync_end && a->vtotal == b->vtotal && a->clock == b->clock && a->flags == b->flags && a->hskew == b->hskew && a->vscan == b->vscan; } static void override_edid(data_t *data) { drmModeModeInfo common_mode = {}; igt_output_t *outputs[8] = {}; igt_output_t *output; int num_outputs = 0; int num_tiles = 0; igt_require(data->display.n_pipes >= 2); for_each_connected_output(&data->display, output) { drmModeModeInfo *mode = igt_output_get_mode(output); if (!common_mode.hdisplay) common_mode = *mode; if (mode_equal(&common_mode, mode)) { outputs[num_outputs] = output; if (++num_outputs >= ARRAY_SIZE(outputs)) break; } } igt_require(num_outputs >= 2); num_tiles = min(num_outputs, data->display.n_pipes); /* disable everything so that we are sure to get a full modeset */ igt_display_reset(&data->display); for (int i = 0; i < num_tiles; i++) force_edid_with_tile(data, outputs[i], &common_mode, i, num_tiles); } static void basic_test(data_t *data, drmEventContext *drm_event, struct pollfd *pfd) { int ret; get_number_of_h_tiles(data); igt_debug("Number of Horizontal Tiles: %d\n", data->num_h_tiles); igt_require(data->num_h_tiles > 0); data->conns = calloc(data->num_h_tiles, sizeof(data_connector_t)); igt_assert(data->conns); get_connectors(data); setup_mode(data); setup_framebuffer(data); timerclear(&data->first_ts); igt_display_commit_atomic(&data->display, DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, data); while (!got_all_page_flips(data)) { ret = poll(pfd, 1, 1000); igt_assert(ret == 1); drmHandleEvent(data->drm_fd, drm_event); } } igt_main { data_t data = {0}; struct pollfd pfd = {0}; drmEventContext drm_event = {0}; igt_fixture { data.drm_fd = drm_open_driver_master(DRIVER_ANY); kmstest_set_vt_graphics_mode(); igt_display_require(&data.display, data.drm_fd); igt_require(data.display.is_atomic); igt_display_require_output(&data.display); igt_display_reset(&data.display); pfd.fd = data.drm_fd; pfd.events = POLLIN; drm_event.version = 3; drm_event.page_flip_handler2 = page_flip_handler; get_number_of_h_tiles(&data); igt_debug("Number of real horizontal tiles: %d\n", data.num_h_tiles); if (data.num_h_tiles == 0) { override_edid(&data); get_number_of_h_tiles(&data); igt_debug("Number of fake horizontal tiles: %d\n", data.num_h_tiles); } igt_require(data.num_h_tiles > 0); } igt_describe("Make sure the Tiled CRTCs are synchronized and we get " "page flips for all tiled CRTCs in one vblank."); igt_subtest("basic-test-pattern") { basic_test(&data, &drm_event, &pfd); test_cleanup(&data); } #ifdef HAVE_CHAMELIUM igt_describe("Make sure the Tiled CRTCs are synchronized and we get " "page flips for all tiled CRTCs in one vblank (executes on chamelium)."); igt_subtest_f("basic-test-pattern-with-chamelium") { int i; test_with_chamelium(&data); basic_test(&data, &drm_event, &pfd); test_cleanup(&data); for (i = 0; i < data.port_count; i++) chamelium_reset_state(&data.display, data.chamelium, data.ports[i], data.ports, data.port_count); } #endif igt_fixture { free(data.conns); kmstest_restore_vt_mode(); igt_display_fini(&data.display); drm_close_driver(data.drm_fd); } }