| /* |
| * Copyright © 2016 Red Hat Inc. |
| * |
| * 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: |
| * Lyude Paul <lyude@redhat.com> |
| */ |
| |
| #include "config.h" |
| #include "igt.h" |
| #include "igt_vc4.h" |
| #include "igt_edid.h" |
| #include "igt_eld.h" |
| #include "igt_infoframe.h" |
| |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <string.h> |
| #include <stdatomic.h> |
| |
| enum test_modeset_mode { |
| TEST_MODESET_ON, |
| TEST_MODESET_ON_OFF, |
| TEST_MODESET_OFF, |
| }; |
| |
| typedef struct { |
| struct chamelium *chamelium; |
| struct chamelium_port **ports; |
| igt_display_t display; |
| int port_count; |
| |
| int drm_fd; |
| |
| struct chamelium_edid *edids[IGT_CUSTOM_EDID_COUNT]; |
| } data_t; |
| |
| #define ONLINE_TIMEOUT 20 /* seconds */ |
| |
| #define HPD_STORM_PULSE_INTERVAL_DP 100 /* ms */ |
| #define HPD_STORM_PULSE_INTERVAL_HDMI 200 /* ms */ |
| |
| #define HPD_TOGGLE_COUNT_VGA 5 |
| #define HPD_TOGGLE_COUNT_DP_HDMI 15 |
| #define HPD_TOGGLE_COUNT_FAST 3 |
| |
| static void |
| get_connectors_link_status_failed(data_t *data, bool *link_status_failed) |
| { |
| drmModeConnector *connector; |
| uint64_t link_status; |
| drmModePropertyPtr prop; |
| int p; |
| |
| for (p = 0; p < data->port_count; p++) { |
| connector = chamelium_port_get_connector(data->chamelium, |
| data->ports[p], false); |
| |
| igt_assert(kmstest_get_property(data->drm_fd, |
| connector->connector_id, |
| DRM_MODE_OBJECT_CONNECTOR, |
| "link-status", NULL, |
| &link_status, &prop)); |
| |
| link_status_failed[p] = link_status == DRM_MODE_LINK_STATUS_BAD; |
| |
| drmModeFreeProperty(prop); |
| drmModeFreeConnector(connector); |
| } |
| } |
| |
| /* Wait for hotplug and return the remaining time left from timeout */ |
| static bool wait_for_hotplug(struct udev_monitor *mon, int *timeout) |
| { |
| struct timespec start, end; |
| int elapsed; |
| bool detected; |
| |
| igt_assert_eq(igt_gettime(&start), 0); |
| detected = igt_hotplug_detected(mon, *timeout); |
| igt_assert_eq(igt_gettime(&end), 0); |
| |
| elapsed = igt_time_elapsed(&start, &end); |
| igt_assert_lte(0, elapsed); |
| *timeout = max(0, *timeout - elapsed); |
| |
| return detected; |
| } |
| |
| static void |
| wait_for_connector_after_hotplug(data_t *data, struct udev_monitor *mon, |
| struct chamelium_port *port, |
| drmModeConnection status) |
| { |
| int timeout = CHAMELIUM_HOTPLUG_TIMEOUT; |
| int hotplug_count = 0; |
| |
| igt_debug("Waiting for %s to get %s after a hotplug event...\n", |
| chamelium_port_get_name(port), |
| kmstest_connector_status_str(status)); |
| |
| while (timeout > 0) { |
| if (!wait_for_hotplug(mon, &timeout)) |
| break; |
| |
| hotplug_count++; |
| |
| if (chamelium_reprobe_connector(&data->display, data->chamelium, |
| port) == status) |
| return; |
| } |
| |
| igt_assert_f(false, "Timed out waiting for %s to get %s after a hotplug. Current state %s hotplug_count %d\n", |
| chamelium_port_get_name(port), |
| kmstest_connector_status_str(status), |
| kmstest_connector_status_str(chamelium_reprobe_connector(&data->display, data->chamelium, port)), hotplug_count); |
| } |
| |
| |
| static int chamelium_vga_modes[][2] = { |
| { 1600, 1200 }, |
| { 1920, 1200 }, |
| { 1920, 1080 }, |
| { 1680, 1050 }, |
| { 1280, 1024 }, |
| { 1280, 960 }, |
| { 1440, 900 }, |
| { 1280, 800 }, |
| { 1024, 768 }, |
| { 1360, 768 }, |
| { 1280, 720 }, |
| { 800, 600 }, |
| { 640, 480 }, |
| { -1, -1 }, |
| }; |
| |
| static bool |
| prune_vga_mode(data_t *data, drmModeModeInfo *mode) |
| { |
| int i = 0; |
| |
| while (chamelium_vga_modes[i][0] != -1) { |
| if (mode->hdisplay == chamelium_vga_modes[i][0] && |
| mode->vdisplay == chamelium_vga_modes[i][1]) |
| return false; |
| |
| i++; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| check_analog_bridge(data_t *data, struct chamelium_port *port) |
| { |
| drmModePropertyBlobPtr edid_blob = NULL; |
| drmModeConnector *connector = chamelium_port_get_connector( |
| data->chamelium, port, false); |
| uint64_t edid_blob_id; |
| const struct edid *edid; |
| char edid_vendor[3]; |
| |
| if (chamelium_port_get_type(port) != DRM_MODE_CONNECTOR_VGA) { |
| drmModeFreeConnector(connector); |
| return false; |
| } |
| |
| igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id, |
| DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL, |
| &edid_blob_id, NULL)); |
| igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd, |
| edid_blob_id)); |
| |
| edid = (const struct edid *) edid_blob->data; |
| edid_get_mfg(edid, edid_vendor); |
| |
| drmModeFreePropertyBlob(edid_blob); |
| drmModeFreeConnector(connector); |
| |
| /* Analog bridges provide their own EDID */ |
| if (edid_vendor[0] != 'I' || edid_vendor[1] != 'G' || |
| edid_vendor[2] != 'T') |
| return true; |
| |
| return false; |
| } |
| |
| static void chamelium_paint_xr24_pattern(uint32_t *data, |
| size_t width, size_t height, |
| size_t stride, size_t block_size) |
| { |
| uint32_t colors[] = { 0xff000000, |
| 0xffff0000, |
| 0xff00ff00, |
| 0xff0000ff, |
| 0xffffffff }; |
| unsigned i, j; |
| |
| for (i = 0; i < height; i++) |
| for (j = 0; j < width; j++) |
| *(data + i * stride / 4 + j) = colors[((j / block_size) + (i / block_size)) % 5]; |
| } |
| |
| static int chamelium_get_pattern_fb(data_t *data, size_t width, size_t height, |
| uint32_t fourcc, size_t block_size, |
| struct igt_fb *fb) |
| { |
| int fb_id; |
| void *ptr; |
| |
| igt_assert(fourcc == DRM_FORMAT_XRGB8888); |
| |
| fb_id = igt_create_fb(data->drm_fd, width, height, fourcc, |
| DRM_FORMAT_MOD_LINEAR, fb); |
| igt_assert(fb_id > 0); |
| |
| ptr = igt_fb_map_buffer(fb->fd, fb); |
| igt_assert(ptr); |
| |
| chamelium_paint_xr24_pattern(ptr, width, height, fb->strides[0], |
| block_size); |
| igt_fb_unmap_buffer(fb, ptr); |
| |
| return fb_id; |
| } |
| |
| static void |
| enable_output(data_t *data, |
| struct chamelium_port *port, |
| igt_output_t *output, |
| drmModeModeInfo *mode, |
| struct igt_fb *fb) |
| { |
| igt_display_t *display = output->display; |
| igt_plane_t *primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| drmModeConnector *connector = chamelium_port_get_connector( |
| data->chamelium, port, false); |
| |
| igt_assert(primary); |
| |
| igt_plane_set_size(primary, mode->hdisplay, mode->vdisplay); |
| igt_plane_set_fb(primary, fb); |
| igt_output_override_mode(output, mode); |
| |
| /* Clear any color correction values that might be enabled */ |
| if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_DEGAMMA_LUT)) |
| igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_DEGAMMA_LUT, NULL, 0); |
| if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_GAMMA_LUT)) |
| igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_GAMMA_LUT, NULL, 0); |
| if (igt_pipe_obj_has_prop(primary->pipe, IGT_CRTC_CTM)) |
| igt_pipe_obj_replace_prop_blob(primary->pipe, IGT_CRTC_CTM, NULL, 0); |
| |
| igt_display_commit2(display, COMMIT_ATOMIC); |
| |
| if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_VGA) |
| usleep(250000); |
| |
| drmModeFreeConnector(connector); |
| } |
| |
| static enum pipe get_pipe_for_output(igt_display_t *display, igt_output_t *output) |
| { |
| enum pipe pipe; |
| |
| for_each_pipe(display, pipe) { |
| if (igt_pipe_connector_valid(pipe, output)) { |
| return pipe; |
| } |
| } |
| |
| igt_assert_f(false, "No pipe found for output %s\n", |
| igt_output_name(output)); |
| } |
| |
| static void create_fb_for_mode(data_t *data, struct igt_fb *fb, drmModeModeInfo *mode) |
| { |
| int fb_id; |
| |
| fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, 64, fb); |
| |
| igt_assert(fb_id > 0); |
| } |
| |
| static drmModeModeInfo get_mode_for_port(struct chamelium *chamelium, |
| struct chamelium_port *port) |
| { |
| drmModeConnector *connector = chamelium_port_get_connector(chamelium, |
| port, false); |
| drmModeModeInfo mode; |
| igt_assert(&connector->modes[0] != NULL); |
| memcpy(&mode, &connector->modes[0], sizeof(mode)); |
| drmModeFreeConnector(connector); |
| return mode; |
| } |
| |
| static igt_output_t *get_output_for_port(data_t *data, |
| struct chamelium_port *port) |
| { |
| drmModeConnector *connector = chamelium_port_get_connector(data->chamelium, |
| port, false); |
| igt_output_t *output = igt_output_from_connector(&data->display, |
| connector); |
| drmModeFreeConnector(connector); |
| igt_assert(output != NULL); |
| return output; |
| } |
| |
| static const char test_hotplug_for_each_pipe_desc[] = |
| "Check that we get uevents and updated connector status on " |
| "hotplug and unplug for each pipe with valid output"; |
| static void |
| test_hotplug_for_each_pipe(data_t *data, struct chamelium_port *port) |
| { |
| igt_output_t *output; |
| enum pipe pipe; |
| struct udev_monitor *mon = igt_watch_uevents(); |
| |
| chamelium_reset_state(&data->display, |
| data->chamelium, |
| port, |
| data->ports, |
| data->port_count); |
| |
| igt_hpd_storm_set_threshold(data->drm_fd, 0); |
| /* Disconnect if any port got connected */ |
| chamelium_unplug(data->chamelium, port); |
| wait_for_connector_after_hotplug(data, mon, port, |
| DRM_MODE_DISCONNECTED); |
| |
| for_each_pipe(&data->display, pipe) { |
| igt_flush_uevents(mon); |
| /* Check if we get a sysfs hotplug event */ |
| chamelium_plug(data->chamelium, port); |
| wait_for_connector_after_hotplug(data, mon, port, |
| DRM_MODE_CONNECTED); |
| igt_flush_uevents(mon); |
| output = get_output_for_port(data, port); |
| |
| /* If pipe is valid for output then set it */ |
| if (igt_pipe_connector_valid(pipe, output)) { |
| igt_output_set_pipe(output, pipe); |
| igt_display_commit2(&data->display, COMMIT_ATOMIC); |
| } |
| |
| chamelium_unplug(data->chamelium, port); |
| wait_for_connector_after_hotplug(data, mon, port, |
| DRM_MODE_DISCONNECTED); |
| igt_flush_uevents(mon); |
| } |
| |
| igt_cleanup_uevents(mon); |
| igt_hpd_storm_reset(data->drm_fd); |
| } |
| |
| static const char test_basic_hotplug_desc[] = |
| "Check that we get uevents and updated connector status on " |
| "hotplug and unplug"; |
| static void |
| test_hotplug(data_t *data, struct chamelium_port *port, int toggle_count, |
| enum test_modeset_mode modeset_mode) |
| { |
| int i; |
| enum pipe pipe; |
| struct igt_fb fb = {0}; |
| drmModeModeInfo mode; |
| struct udev_monitor *mon = igt_watch_uevents(); |
| igt_output_t *output = get_output_for_port(data, port); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, NULL, |
| data->ports, data->port_count); |
| |
| |
| igt_hpd_storm_set_threshold(data->drm_fd, 0); |
| |
| for (i = 0; i < toggle_count; i++) { |
| igt_flush_uevents(mon); |
| |
| /* Check if we get a sysfs hotplug event */ |
| chamelium_plug(data->chamelium, port); |
| |
| wait_for_connector_after_hotplug(data, mon, port, |
| DRM_MODE_CONNECTED); |
| igt_flush_uevents(mon); |
| |
| if (modeset_mode == TEST_MODESET_ON_OFF || |
| (modeset_mode == TEST_MODESET_ON && i == 0 )) { |
| if (i == 0) { |
| /* We can only get mode and pipe once we are connected */ |
| pipe = get_pipe_for_output(&data->display, output); |
| mode = get_mode_for_port(data->chamelium, port); |
| create_fb_for_mode(data, &fb, &mode); |
| } |
| |
| igt_output_set_pipe(output, pipe); |
| enable_output(data, port, output, &mode, &fb); |
| } |
| |
| /* Now check if we get a hotplug from disconnection */ |
| chamelium_unplug(data->chamelium, port); |
| |
| wait_for_connector_after_hotplug(data, mon, port, |
| DRM_MODE_DISCONNECTED); |
| |
| igt_flush_uevents(mon); |
| |
| if (modeset_mode == TEST_MODESET_ON_OFF) { |
| igt_output_set_pipe(output, PIPE_NONE); |
| igt_display_commit2(&data->display, COMMIT_ATOMIC); |
| } |
| } |
| |
| igt_cleanup_uevents(mon); |
| igt_hpd_storm_reset(data->drm_fd); |
| igt_remove_fb(data->drm_fd, &fb); |
| } |
| |
| static void set_edid(data_t *data, struct chamelium_port *port, |
| enum igt_custom_edid_type edid) |
| { |
| chamelium_port_set_edid(data->chamelium, port, data->edids[edid]); |
| } |
| |
| static const char igt_custom_edid_type_read_desc[] = |
| "Make sure the EDID exposed by KMS is the same as the screen's"; |
| static void |
| igt_custom_edid_type_read(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid) |
| { |
| drmModePropertyBlobPtr edid_blob = NULL; |
| drmModeConnector *connector = chamelium_port_get_connector( |
| data->chamelium, port, false); |
| size_t raw_edid_size; |
| const struct edid *raw_edid; |
| uint64_t edid_blob_id; |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| set_edid(data, port, edid); |
| chamelium_plug(data->chamelium, port); |
| chamelium_wait_for_conn_status_change(&data->display, data->chamelium, |
| port, DRM_MODE_CONNECTED); |
| |
| igt_skip_on(check_analog_bridge(data, port)); |
| |
| igt_assert(kmstest_get_property(data->drm_fd, connector->connector_id, |
| DRM_MODE_OBJECT_CONNECTOR, "EDID", NULL, |
| &edid_blob_id, NULL)); |
| igt_assert(edid_blob_id != 0); |
| igt_assert(edid_blob = drmModeGetPropertyBlob(data->drm_fd, |
| edid_blob_id)); |
| |
| raw_edid = chamelium_edid_get_raw(data->edids[edid], port); |
| raw_edid_size = edid_get_size(raw_edid); |
| igt_assert(memcmp(raw_edid, edid_blob->data, raw_edid_size) == 0); |
| |
| drmModeFreePropertyBlob(edid_blob); |
| drmModeFreeConnector(connector); |
| } |
| |
| static void |
| try_suspend_resume_hpd(data_t *data, struct chamelium_port *port, |
| enum igt_suspend_state state, enum igt_suspend_test test, |
| struct udev_monitor *mon, bool connected) |
| { |
| drmModeConnection target_state = connected ? DRM_MODE_DISCONNECTED : |
| DRM_MODE_CONNECTED; |
| int timeout = CHAMELIUM_HOTPLUG_TIMEOUT; |
| int delay; |
| int p; |
| |
| igt_flush_uevents(mon); |
| |
| delay = igt_get_autoresume_delay(state) * 1000 / 2; |
| |
| if (port) { |
| chamelium_schedule_hpd_toggle(data->chamelium, port, delay, |
| !connected); |
| } else { |
| for (p = 0; p < data->port_count; p++) { |
| port = data->ports[p]; |
| chamelium_schedule_hpd_toggle(data->chamelium, port, |
| delay, !connected); |
| } |
| |
| port = NULL; |
| } |
| |
| igt_system_suspend_autoresume(state, test); |
| igt_assert(wait_for_hotplug(mon, &timeout)); |
| chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT); |
| |
| if (port) { |
| igt_assert_eq(chamelium_reprobe_connector(&data->display, |
| data->chamelium, |
| port), |
| target_state); |
| } else { |
| for (p = 0; p < data->port_count; p++) { |
| drmModeConnection current_state; |
| |
| port = data->ports[p]; |
| /* |
| * There could be as many hotplug events sent by |
| * driver as connectors we scheduled an HPD toggle on |
| * above, depending on timing. So if we're not seeing |
| * the expected connector state try to wait for an HPD |
| * event for each connector/port. |
| */ |
| current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port); |
| if (p > 0 && current_state != target_state) { |
| igt_assert(wait_for_hotplug(mon, &timeout)); |
| current_state = chamelium_reprobe_connector(&data->display, data->chamelium, port); |
| } |
| |
| igt_assert_eq(current_state, target_state); |
| } |
| |
| port = NULL; |
| } |
| } |
| |
| static const char test_suspend_resume_hpd_desc[] = |
| "Toggle HPD during suspend, check that uevents are sent and connector " |
| "status is updated"; |
| static void |
| test_suspend_resume_hpd(data_t *data, struct chamelium_port *port, |
| enum igt_suspend_state state, |
| enum igt_suspend_test test) |
| { |
| struct udev_monitor *mon = igt_watch_uevents(); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| /* Make sure we notice new connectors after resuming */ |
| try_suspend_resume_hpd(data, port, state, test, mon, false); |
| |
| /* Now make sure we notice disconnected connectors after resuming */ |
| try_suspend_resume_hpd(data, port, state, test, mon, true); |
| |
| igt_cleanup_uevents(mon); |
| } |
| |
| static const char test_suspend_resume_hpd_common_desc[] = |
| "Toggle HPD during suspend on all connectors, check that uevents are " |
| "sent and connector status is updated"; |
| static void |
| test_suspend_resume_hpd_common(data_t *data, enum igt_suspend_state state, |
| enum igt_suspend_test test) |
| { |
| struct udev_monitor *mon = igt_watch_uevents(); |
| struct chamelium_port *port; |
| int p; |
| |
| for (p = 0; p < data->port_count; p++) { |
| port = data->ports[p]; |
| igt_debug("Testing port %s\n", chamelium_port_get_name(port)); |
| } |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, NULL, |
| data->ports, data->port_count); |
| |
| /* Make sure we notice new connectors after resuming */ |
| try_suspend_resume_hpd(data, NULL, state, test, mon, false); |
| |
| /* Now make sure we notice disconnected connectors after resuming */ |
| try_suspend_resume_hpd(data, NULL, state, test, mon, true); |
| |
| igt_cleanup_uevents(mon); |
| } |
| |
| static const char test_suspend_resume_edid_change_desc[] = |
| "Simulate a screen being unplugged and another screen being plugged " |
| "during suspend, check that a uevent is sent and connector status is " |
| "updated"; |
| static void |
| test_suspend_resume_edid_change(data_t *data, struct chamelium_port *port, |
| enum igt_suspend_state state, |
| enum igt_suspend_test test, |
| enum igt_custom_edid_type edid, |
| enum igt_custom_edid_type alt_edid) |
| { |
| struct udev_monitor *mon = igt_watch_uevents(); |
| bool link_status_failed[2][data->port_count]; |
| int p; |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| /* Catch the event and flush all remaining ones. */ |
| igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT)); |
| igt_flush_uevents(mon); |
| |
| /* First plug in the port */ |
| set_edid(data, port, edid); |
| chamelium_plug(data->chamelium, port); |
| igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT)); |
| |
| chamelium_wait_for_conn_status_change(&data->display, data->chamelium, |
| port, DRM_MODE_CONNECTED); |
| |
| /* |
| * Change the edid before we suspend. On resume, the machine should |
| * notice the EDID change and fire a hotplug event. |
| */ |
| set_edid(data, port, alt_edid); |
| |
| get_connectors_link_status_failed(data, link_status_failed[0]); |
| |
| igt_flush_uevents(mon); |
| |
| igt_system_suspend_autoresume(state, test); |
| igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT)); |
| chamelium_assert_reachable(data->chamelium, ONLINE_TIMEOUT); |
| |
| get_connectors_link_status_failed(data, link_status_failed[1]); |
| |
| for (p = 0; p < data->port_count; p++) |
| igt_skip_on(!link_status_failed[0][p] && link_status_failed[1][p]); |
| } |
| |
| static igt_output_t * |
| prepare_output(data_t *data, struct chamelium_port *port, enum igt_custom_edid_type edid) |
| { |
| igt_display_t *display = &data->display; |
| igt_output_t *output; |
| enum pipe pipe; |
| |
| /* The chamelium's default EDID has a lot of resolutions, way more then |
| * we need to test. Additionally the default EDID doesn't support HDMI |
| * audio. |
| */ |
| set_edid(data, port, edid); |
| |
| chamelium_plug(data->chamelium, port); |
| chamelium_wait_for_conn_status_change(&data->display, data->chamelium, |
| port, DRM_MODE_CONNECTED); |
| |
| igt_display_reset(display); |
| |
| output = get_output_for_port(data, port); |
| |
| /* Refresh pipe to update connected status */ |
| igt_output_set_pipe(output, PIPE_NONE); |
| |
| pipe = get_pipe_for_output(display, output); |
| igt_output_set_pipe(output, pipe); |
| |
| return output; |
| } |
| |
| static void do_test_display(data_t *data, struct chamelium_port *port, |
| igt_output_t *output, drmModeModeInfo *mode, |
| uint32_t fourcc, enum chamelium_check check, |
| int count) |
| { |
| struct chamelium_fb_crc_async_data *fb_crc; |
| struct igt_fb frame_fb, fb; |
| int i, fb_id, captured_frame_count; |
| int frame_id; |
| |
| fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, 64, &fb); |
| igt_assert(fb_id > 0); |
| |
| frame_id = igt_fb_convert(&frame_fb, &fb, fourcc, |
| DRM_FORMAT_MOD_LINEAR); |
| igt_assert(frame_id > 0); |
| |
| if (check == CHAMELIUM_CHECK_CRC) |
| fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd, |
| &fb); |
| |
| enable_output(data, port, output, mode, &frame_fb); |
| |
| if (check == CHAMELIUM_CHECK_CRC) { |
| igt_crc_t *expected_crc; |
| igt_crc_t *crc; |
| |
| /* We want to keep the display running for a little bit, since |
| * there's always the potential the driver isn't able to keep |
| * the display running properly for very long |
| */ |
| chamelium_capture(data->chamelium, port, 0, 0, 0, 0, count); |
| crc = chamelium_read_captured_crcs(data->chamelium, |
| &captured_frame_count); |
| |
| igt_assert(captured_frame_count == count); |
| |
| igt_debug("Captured %d frames\n", captured_frame_count); |
| |
| expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc); |
| |
| for (i = 0; i < captured_frame_count; i++) |
| chamelium_assert_crc_eq_or_dump(data->chamelium, |
| expected_crc, &crc[i], |
| &fb, i); |
| |
| free(expected_crc); |
| free(crc); |
| } else if (check == CHAMELIUM_CHECK_ANALOG || |
| check == CHAMELIUM_CHECK_CHECKERBOARD) { |
| struct chamelium_frame_dump *dump; |
| |
| igt_assert(count == 1); |
| |
| dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0, |
| 0, 0); |
| |
| if (check == CHAMELIUM_CHECK_ANALOG) |
| chamelium_crop_analog_frame(dump, mode->hdisplay, |
| mode->vdisplay); |
| |
| chamelium_assert_frame_match_or_dump(data->chamelium, port, |
| dump, &fb, check); |
| chamelium_destroy_frame_dump(dump); |
| } |
| |
| igt_remove_fb(data->drm_fd, &frame_fb); |
| igt_remove_fb(data->drm_fd, &fb); |
| } |
| |
| static const char test_display_one_mode_desc[] = |
| "Pick the first mode of the IGT base EDID, display and capture a few " |
| "frames, then check captured frames are correct"; |
| static void test_display_one_mode(data_t *data, struct chamelium_port *port, |
| uint32_t fourcc, enum chamelium_check check, |
| int count) |
| { |
| drmModeConnector *connector; |
| drmModeModeInfo *mode; |
| igt_output_t *output; |
| igt_plane_t *primary; |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE); |
| connector = chamelium_port_get_connector(data->chamelium, port, false); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| igt_require(igt_plane_has_format_mod(primary, fourcc, DRM_FORMAT_MOD_LINEAR)); |
| |
| mode = &connector->modes[0]; |
| if (check == CHAMELIUM_CHECK_ANALOG) { |
| bool bridge = check_analog_bridge(data, port); |
| |
| igt_assert(!(bridge && prune_vga_mode(data, mode))); |
| } |
| |
| do_test_display(data, port, output, mode, fourcc, check, count); |
| |
| drmModeFreeConnector(connector); |
| } |
| |
| static const char test_display_all_modes_desc[] = |
| "For each mode of the IGT base EDID, display and capture a few " |
| "frames, then check captured frames are correct"; |
| static void test_display_all_modes(data_t *data, struct chamelium_port *port, |
| uint32_t fourcc, enum chamelium_check check, |
| int count) |
| { |
| bool bridge; |
| int i, count_modes; |
| |
| if (check == CHAMELIUM_CHECK_ANALOG) |
| bridge = check_analog_bridge(data, port); |
| |
| i = 0; |
| do { |
| igt_output_t *output; |
| igt_plane_t *primary; |
| drmModeConnector *connector; |
| drmModeModeInfo *mode; |
| |
| /* |
| * let's reset state each mode so we will get the |
| * HPD pulses realibably |
| */ |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| /* |
| * modes may change due to mode pruining and link issues, so we |
| * need to refresh the connector |
| */ |
| output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE); |
| connector = chamelium_port_get_connector(data->chamelium, port, |
| false); |
| primary = igt_output_get_plane_type(output, |
| DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| igt_require(igt_plane_has_format_mod(primary, fourcc, |
| DRM_FORMAT_MOD_LINEAR)); |
| |
| /* we may skip some modes due to above but that's ok */ |
| count_modes = connector->count_modes; |
| if (i >= count_modes) |
| break; |
| |
| mode = &connector->modes[i]; |
| |
| if (check == CHAMELIUM_CHECK_ANALOG && bridge && |
| prune_vga_mode(data, mode)) |
| continue; |
| |
| do_test_display(data, port, output, mode, fourcc, check, |
| count); |
| drmModeFreeConnector(connector); |
| } while (++i < count_modes); |
| } |
| |
| static const char test_display_frame_dump_desc[] = |
| "For each mode of the IGT base EDID, display and capture a few " |
| "frames, then download the captured frames and compare them " |
| "bit-by-bit to the sent ones"; |
| static void |
| test_display_frame_dump(data_t *data, struct chamelium_port *port) |
| { |
| |
| int i, count_modes; |
| |
| i = 0; |
| do { |
| igt_output_t *output; |
| igt_plane_t *primary; |
| struct igt_fb fb; |
| struct chamelium_frame_dump *frame; |
| drmModeModeInfo *mode; |
| drmModeConnector *connector; |
| int fb_id, j; |
| |
| /* |
| * let's reset state each mode so we will get the |
| * HPD pulses realibably |
| */ |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| /* |
| * modes may change due to mode pruining and link issues, so we |
| * need to refresh the connector |
| */ |
| output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE); |
| connector = chamelium_port_get_connector(data->chamelium, port, |
| false); |
| primary = igt_output_get_plane_type(output, |
| DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| /* we may skip some modes due to above but that's ok */ |
| count_modes = connector->count_modes; |
| if (i >= count_modes) |
| break; |
| |
| mode = &connector->modes[i]; |
| |
| fb_id = igt_create_color_pattern_fb(data->drm_fd, |
| mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_MOD_LINEAR, |
| 0, 0, 0, &fb); |
| igt_assert(fb_id > 0); |
| |
| enable_output(data, port, output, mode, &fb); |
| |
| igt_debug("Reading frame dumps from Chamelium...\n"); |
| chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 5); |
| for (j = 0; j < 5; j++) { |
| frame = chamelium_read_captured_frame(data->chamelium, |
| j); |
| chamelium_assert_frame_eq(data->chamelium, frame, &fb); |
| chamelium_destroy_frame_dump(frame); |
| } |
| |
| igt_remove_fb(data->drm_fd, &fb); |
| drmModeFreeConnector(connector); |
| } while (++i < count_modes); |
| } |
| |
| #define MODE_CLOCK_ACCURACY 0.05 /* 5% */ |
| |
| static void check_mode(struct chamelium *chamelium, struct chamelium_port *port, |
| drmModeModeInfo *mode) |
| { |
| struct chamelium_video_params video_params = {0}; |
| double mode_clock; |
| int mode_hsync_offset, mode_vsync_offset; |
| int mode_hsync_width, mode_vsync_width; |
| int mode_hsync_polarity, mode_vsync_polarity; |
| |
| chamelium_port_get_video_params(chamelium, port, &video_params); |
| |
| mode_clock = (double) mode->clock / 1000; |
| |
| if (chamelium_port_get_type(port) == DRM_MODE_CONNECTOR_DisplayPort) { |
| /* this is what chamelium understands as offsets for DP */ |
| mode_hsync_offset = mode->htotal - mode->hsync_start; |
| mode_vsync_offset = mode->vtotal - mode->vsync_start; |
| } else { |
| /* and this is what they are for other connectors */ |
| mode_hsync_offset = mode->hsync_start - mode->hdisplay; |
| mode_vsync_offset = mode->vsync_start - mode->vdisplay; |
| } |
| |
| mode_hsync_width = mode->hsync_end - mode->hsync_start; |
| mode_vsync_width = mode->vsync_end - mode->vsync_start; |
| |
| mode_hsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC); |
| mode_vsync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC); |
| |
| igt_debug("Checking video mode:\n"); |
| igt_debug("clock: got %f, expected %f ± %f%%\n", |
| video_params.clock, mode_clock, MODE_CLOCK_ACCURACY * 100); |
| igt_debug("hactive: got %d, expected %d\n", |
| video_params.hactive, mode->hdisplay); |
| igt_debug("vactive: got %d, expected %d\n", |
| video_params.vactive, mode->vdisplay); |
| igt_debug("hsync_offset: got %d, expected %d\n", |
| video_params.hsync_offset, mode_hsync_offset); |
| igt_debug("vsync_offset: got %d, expected %d\n", |
| video_params.vsync_offset, mode_vsync_offset); |
| igt_debug("htotal: got %d, expected %d\n", |
| video_params.htotal, mode->htotal); |
| igt_debug("vtotal: got %d, expected %d\n", |
| video_params.vtotal, mode->vtotal); |
| igt_debug("hsync_width: got %d, expected %d\n", |
| video_params.hsync_width, mode_hsync_width); |
| igt_debug("vsync_width: got %d, expected %d\n", |
| video_params.vsync_width, mode_vsync_width); |
| igt_debug("hsync_polarity: got %d, expected %d\n", |
| video_params.hsync_polarity, mode_hsync_polarity); |
| igt_debug("vsync_polarity: got %d, expected %d\n", |
| video_params.vsync_polarity, mode_vsync_polarity); |
| |
| if (!isnan(video_params.clock)) { |
| igt_assert(video_params.clock > |
| mode_clock * (1 - MODE_CLOCK_ACCURACY)); |
| igt_assert(video_params.clock < |
| mode_clock * (1 + MODE_CLOCK_ACCURACY)); |
| } |
| igt_assert(video_params.hactive == mode->hdisplay); |
| igt_assert(video_params.vactive == mode->vdisplay); |
| igt_assert(video_params.hsync_offset == mode_hsync_offset); |
| igt_assert(video_params.vsync_offset == mode_vsync_offset); |
| igt_assert(video_params.htotal == mode->htotal); |
| igt_assert(video_params.vtotal == mode->vtotal); |
| igt_assert(video_params.hsync_width == mode_hsync_width); |
| igt_assert(video_params.vsync_width == mode_vsync_width); |
| igt_assert(video_params.hsync_polarity == mode_hsync_polarity); |
| igt_assert(video_params.vsync_polarity == mode_vsync_polarity); |
| } |
| |
| static const char test_mode_timings_desc[] = |
| "For each mode of the IGT base EDID, perform a modeset and check the " |
| "mode detected by the Chamelium receiver matches the mode we set"; |
| static void test_mode_timings(data_t *data, struct chamelium_port *port) |
| { |
| int i, count_modes; |
| |
| i = 0; |
| igt_require(chamelium_supports_get_video_params(data->chamelium)); |
| do { |
| igt_output_t *output; |
| igt_plane_t *primary; |
| drmModeConnector *connector; |
| drmModeModeInfo *mode; |
| int fb_id; |
| struct igt_fb fb; |
| |
| /* |
| * let's reset state each mode so we will get the |
| * HPD pulses realibably |
| */ |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| /* |
| * modes may change due to mode pruining and link issues, so we |
| * need to refresh the connector |
| */ |
| output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE); |
| connector = chamelium_port_get_connector(data->chamelium, port, false); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| /* we may skip some modes due to above but that's ok */ |
| count_modes = connector->count_modes; |
| if (i >= count_modes) |
| break; |
| |
| mode = &connector->modes[i]; |
| |
| fb_id = igt_create_color_pattern_fb(data->drm_fd, |
| mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_MOD_LINEAR, |
| 0, 0, 0, &fb); |
| igt_assert(fb_id > 0); |
| |
| enable_output(data, port, output, mode, &fb); |
| |
| /* Trigger the FSM */ |
| chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 0); |
| |
| check_mode(data->chamelium, port, mode); |
| |
| igt_remove_fb(data->drm_fd, &fb); |
| drmModeFreeConnector(connector); |
| } while (++i < count_modes); |
| } |
| |
| struct vic_mode { |
| int hactive, vactive; |
| int vrefresh; /* Hz */ |
| uint32_t picture_ar; |
| }; |
| |
| /* Maps Video Identification Codes to a mode */ |
| static const struct vic_mode vic_modes[] = { |
| [16] = { |
| .hactive = 1920, |
| .vactive = 1080, |
| .vrefresh = 60, |
| .picture_ar = DRM_MODE_PICTURE_ASPECT_16_9, |
| }, |
| }; |
| |
| /* Maps aspect ratios to their mode flag */ |
| static const uint32_t mode_ar_flags[] = { |
| [DRM_MODE_PICTURE_ASPECT_16_9] = DRM_MODE_FLAG_PIC_AR_16_9, |
| }; |
| |
| static enum infoframe_avi_picture_aspect_ratio |
| get_infoframe_avi_picture_ar(uint32_t aspect_ratio) |
| { |
| /* The AVI picture aspect ratio field only supports 4:3 and 16:9 */ |
| switch (aspect_ratio) { |
| case DRM_MODE_PICTURE_ASPECT_4_3: |
| return INFOFRAME_AVI_PIC_AR_4_3; |
| case DRM_MODE_PICTURE_ASPECT_16_9: |
| return INFOFRAME_AVI_PIC_AR_16_9; |
| default: |
| return INFOFRAME_AVI_PIC_AR_UNSPECIFIED; |
| } |
| } |
| |
| static bool vic_mode_matches_drm(const struct vic_mode *vic_mode, |
| drmModeModeInfo *drm_mode) |
| { |
| uint32_t ar_flag = mode_ar_flags[vic_mode->picture_ar]; |
| |
| return vic_mode->hactive == drm_mode->hdisplay && |
| vic_mode->vactive == drm_mode->vdisplay && |
| vic_mode->vrefresh == drm_mode->vrefresh && |
| ar_flag == (drm_mode->flags & DRM_MODE_FLAG_PIC_AR_MASK); |
| } |
| |
| static const char test_display_aspect_ratio_desc[] = |
| "Pick a mode with a picture aspect-ratio, capture AVI InfoFrames and " |
| "check they include the relevant fields"; |
| static void test_display_aspect_ratio(data_t *data, struct chamelium_port *port) |
| { |
| igt_output_t *output; |
| igt_plane_t *primary; |
| drmModeConnector *connector; |
| drmModeModeInfo *mode; |
| int fb_id, i; |
| struct igt_fb fb; |
| bool found, ok; |
| struct chamelium_infoframe *infoframe; |
| struct infoframe_avi infoframe_avi; |
| uint8_t vic = 16; /* TODO: test more VICs */ |
| const struct vic_mode *vic_mode; |
| uint32_t aspect_ratio; |
| enum infoframe_avi_picture_aspect_ratio frame_ar; |
| |
| igt_require(chamelium_supports_get_last_infoframe(data->chamelium)); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| output = prepare_output(data, port, IGT_CUSTOM_EDID_ASPECT_RATIO); |
| connector = chamelium_port_get_connector(data->chamelium, port, false); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| vic_mode = &vic_modes[vic]; |
| aspect_ratio = vic_mode->picture_ar; |
| |
| found = false; |
| igt_assert(connector->count_modes > 0); |
| for (i = 0; i < connector->count_modes; i++) { |
| mode = &connector->modes[i]; |
| |
| if (vic_mode_matches_drm(vic_mode, mode)) { |
| found = true; |
| break; |
| } |
| } |
| igt_assert_f(found, |
| "Failed to find mode with the correct aspect ratio\n"); |
| |
| fb_id = igt_create_color_pattern_fb(data->drm_fd, |
| mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_MOD_LINEAR, |
| 0, 0, 0, &fb); |
| igt_assert(fb_id > 0); |
| |
| enable_output(data, port, output, mode, &fb); |
| |
| infoframe = chamelium_get_last_infoframe(data->chamelium, port, |
| CHAMELIUM_INFOFRAME_AVI); |
| igt_assert_f(infoframe, "AVI InfoFrame not received\n"); |
| |
| ok = infoframe_avi_parse(&infoframe_avi, infoframe->version, |
| infoframe->payload, infoframe->payload_size); |
| igt_assert_f(ok, "Failed to parse AVI InfoFrame\n"); |
| |
| frame_ar = get_infoframe_avi_picture_ar(aspect_ratio); |
| |
| igt_debug("Checking AVI InfoFrame\n"); |
| igt_debug("Picture aspect ratio: got %d, expected %d\n", |
| infoframe_avi.picture_aspect_ratio, frame_ar); |
| igt_debug("Video Identification Code (VIC): got %d, expected %d\n", |
| infoframe_avi.vic, vic); |
| |
| igt_assert(infoframe_avi.picture_aspect_ratio == frame_ar); |
| igt_assert(infoframe_avi.vic == vic); |
| |
| chamelium_infoframe_destroy(infoframe); |
| igt_remove_fb(data->drm_fd, &fb); |
| drmModeFreeConnector(connector); |
| } |
| |
| |
| /* Playback parameters control the audio signal we synthesize and send */ |
| #define PLAYBACK_CHANNELS 2 |
| #define PLAYBACK_SAMPLES 1024 |
| |
| /* Capture paremeters control the audio signal we receive */ |
| #define CAPTURE_SAMPLES 2048 |
| |
| #define AUDIO_TIMEOUT 2000 /* ms */ |
| /* A streak of 3 gives confidence that the signal is good. */ |
| #define MIN_STREAK 3 |
| |
| #define FLATLINE_AMPLITUDE 0.1 /* normalized, ie. in [0, 1] */ |
| #define FLATLINE_AMPLITUDE_ACCURACY 0.001 /* ± 0.1 % of the full amplitude */ |
| #define FLATLINE_ALIGN_ACCURACY 0 /* number of samples */ |
| |
| /* TODO: enable >48KHz rates, these are not reliable */ |
| static int test_sampling_rates[] = { |
| 32000, |
| 44100, |
| 48000, |
| /* 88200, */ |
| /* 96000, */ |
| /* 176400, */ |
| /* 192000, */ |
| }; |
| |
| static int test_sampling_rates_count = sizeof(test_sampling_rates) / sizeof(int); |
| |
| /* Test frequencies (Hz): a sine signal will be generated for each. |
| * |
| * Depending on the sampling rate chosen, it might not be possible to properly |
| * detect the generated sine (see Nyquist–Shannon sampling theorem). |
| * Frequencies that can't be reliably detected will be automatically pruned in |
| * #audio_signal_add_frequency. For instance, the 80KHz frequency can only be |
| * tested with a 192KHz sampling rate. |
| */ |
| static int test_frequencies[] = { |
| 300, |
| 600, |
| 1200, |
| 10000, |
| 80000, |
| }; |
| |
| static int test_frequencies_count = sizeof(test_frequencies) / sizeof(int); |
| |
| static const snd_pcm_format_t test_formats[] = { |
| SND_PCM_FORMAT_S16_LE, |
| SND_PCM_FORMAT_S24_LE, |
| SND_PCM_FORMAT_S32_LE, |
| }; |
| |
| static const size_t test_formats_count = sizeof(test_formats) / sizeof(test_formats[0]); |
| |
| struct audio_state { |
| struct alsa *alsa; |
| struct chamelium *chamelium; |
| struct chamelium_port *port; |
| struct chamelium_stream *stream; |
| |
| /* The capture format is only available after capture has started. */ |
| struct { |
| snd_pcm_format_t format; |
| int channels; |
| int rate; |
| } playback, capture; |
| |
| char *name; |
| struct audio_signal *signal; /* for frequencies test only */ |
| int channel_mapping[CHAMELIUM_MAX_AUDIO_CHANNELS]; |
| |
| size_t recv_pages; |
| int msec; |
| |
| int dump_fd; |
| char *dump_path; |
| |
| pthread_t thread; |
| atomic_bool run; |
| atomic_bool positive; /* for pulse test only */ |
| }; |
| |
| static void audio_state_init(struct audio_state *state, data_t *data, |
| struct alsa *alsa, struct chamelium_port *port, |
| snd_pcm_format_t format, int channels, int rate) |
| { |
| memset(state, 0, sizeof(*state)); |
| state->dump_fd = -1; |
| |
| state->alsa = alsa; |
| state->chamelium = data->chamelium; |
| state->port = port; |
| |
| state->playback.format = format; |
| state->playback.channels = channels; |
| state->playback.rate = rate; |
| |
| alsa_configure_output(alsa, format, channels, rate); |
| |
| state->stream = chamelium_stream_init(); |
| igt_assert_f(state->stream, |
| "Failed to initialize Chamelium stream client\n"); |
| } |
| |
| static void audio_state_fini(struct audio_state *state) |
| { |
| chamelium_stream_deinit(state->stream); |
| free(state->name); |
| } |
| |
| static void *run_audio_thread(void *data) |
| { |
| struct alsa *alsa = data; |
| |
| alsa_run(alsa, -1); |
| return NULL; |
| } |
| |
| static void audio_state_start(struct audio_state *state, const char *name) |
| { |
| int ret; |
| bool ok; |
| size_t i, j; |
| enum chamelium_stream_realtime_mode stream_mode; |
| char dump_suffix[64]; |
| |
| free(state->name); |
| state->name = strdup(name); |
| state->recv_pages = 0; |
| state->msec = 0; |
| |
| igt_debug("Starting %s test with playback format %s, " |
| "sampling rate %d Hz and %d channels\n", |
| name, snd_pcm_format_name(state->playback.format), |
| state->playback.rate, state->playback.channels); |
| |
| chamelium_start_capturing_audio(state->chamelium, state->port, false); |
| |
| stream_mode = CHAMELIUM_STREAM_REALTIME_STOP_WHEN_OVERFLOW; |
| ok = chamelium_stream_dump_realtime_audio(state->stream, stream_mode); |
| igt_assert_f(ok, "Failed to start streaming audio capture\n"); |
| |
| /* Start playing audio */ |
| state->run = true; |
| ret = pthread_create(&state->thread, NULL, |
| run_audio_thread, state->alsa); |
| igt_assert_f(ret == 0, "Failed to start audio playback thread\n"); |
| |
| /* The Chamelium device only supports this PCM format. */ |
| state->capture.format = SND_PCM_FORMAT_S32_LE; |
| |
| /* Only after we've started playing audio, we can retrieve the capture |
| * format used by the Chamelium device. */ |
| chamelium_get_audio_format(state->chamelium, state->port, |
| &state->capture.rate, |
| &state->capture.channels); |
| if (state->capture.rate == 0) { |
| igt_debug("Audio receiver doesn't indicate the capture " |
| "sampling rate, assuming it's %d Hz\n", |
| state->playback.rate); |
| state->capture.rate = state->playback.rate; |
| } |
| |
| chamelium_get_audio_channel_mapping(state->chamelium, state->port, |
| state->channel_mapping); |
| /* Make sure we can capture all channels we send. */ |
| for (i = 0; i < state->playback.channels; i++) { |
| ok = false; |
| for (j = 0; j < state->capture.channels; j++) { |
| if (state->channel_mapping[j] == i) { |
| ok = true; |
| break; |
| } |
| } |
| igt_assert_f(ok, "Cannot capture all channels\n"); |
| } |
| |
| if (igt_frame_dump_is_enabled()) { |
| snprintf(dump_suffix, sizeof(dump_suffix), |
| "capture-%s-%s-%dch-%dHz", |
| name, snd_pcm_format_name(state->playback.format), |
| state->playback.channels, state->playback.rate); |
| |
| state->dump_fd = audio_create_wav_file_s32_le(dump_suffix, |
| state->capture.rate, |
| state->capture.channels, |
| &state->dump_path); |
| igt_assert_f(state->dump_fd >= 0, |
| "Failed to create audio dump file\n"); |
| } |
| } |
| |
| static void audio_state_receive(struct audio_state *state, |
| int32_t **recv, size_t *recv_len) |
| { |
| bool ok; |
| size_t page_count; |
| size_t recv_size; |
| |
| ok = chamelium_stream_receive_realtime_audio(state->stream, |
| &page_count, |
| recv, recv_len); |
| igt_assert_f(ok, "Failed to receive audio from stream server\n"); |
| |
| state->msec = state->recv_pages * *recv_len |
| / (double) state->capture.channels |
| / (double) state->capture.rate * 1000; |
| state->recv_pages++; |
| |
| if (state->dump_fd >= 0) { |
| recv_size = *recv_len * sizeof(int32_t); |
| igt_assert_f(write(state->dump_fd, *recv, recv_size) == recv_size, |
| "Failed to write to audio dump file\n"); |
| } |
| } |
| |
| static void audio_state_stop(struct audio_state *state, bool success) |
| { |
| bool ok; |
| int ret; |
| struct chamelium_audio_file *audio_file; |
| enum igt_log_level log_level; |
| |
| igt_debug("Stopping audio playback\n"); |
| state->run = false; |
| ret = pthread_join(state->thread, NULL); |
| igt_assert_f(ret == 0, "Failed to join audio playback thread\n"); |
| |
| ok = chamelium_stream_stop_realtime_audio(state->stream); |
| igt_assert_f(ok, "Failed to stop streaming audio capture\n"); |
| |
| audio_file = chamelium_stop_capturing_audio(state->chamelium, |
| state->port); |
| if (audio_file) { |
| igt_debug("Audio file saved on the Chamelium in %s\n", |
| audio_file->path); |
| chamelium_destroy_audio_file(audio_file); |
| } |
| |
| if (state->dump_fd >= 0) { |
| close(state->dump_fd); |
| state->dump_fd = -1; |
| |
| if (success) { |
| /* Test succeeded, no need to keep the captured data */ |
| unlink(state->dump_path); |
| } else |
| igt_debug("Saved captured audio data to %s\n", |
| state->dump_path); |
| free(state->dump_path); |
| state->dump_path = NULL; |
| } |
| |
| if (success) |
| log_level = IGT_LOG_DEBUG; |
| else |
| log_level = IGT_LOG_CRITICAL; |
| |
| igt_log(IGT_LOG_DOMAIN, log_level, "Audio %s test result for format %s, " |
| "sampling rate %d Hz and %d channels: %s\n", |
| state->name, snd_pcm_format_name(state->playback.format), |
| state->playback.rate, state->playback.channels, |
| success ? "ALL GREEN" : "FAILED"); |
| |
| } |
| |
| static void check_audio_infoframe(struct audio_state *state) |
| { |
| struct chamelium_infoframe *infoframe; |
| struct infoframe_audio infoframe_audio; |
| struct infoframe_audio expected = {0}; |
| bool ok; |
| |
| if (!chamelium_supports_get_last_infoframe(state->chamelium)) { |
| igt_debug("Skipping audio InfoFrame check: " |
| "Chamelium board doesn't support GetLastInfoFrame\n"); |
| return; |
| } |
| |
| expected.coding_type = INFOFRAME_AUDIO_CT_PCM; |
| expected.channel_count = state->playback.channels; |
| expected.sampling_freq = state->playback.rate; |
| expected.sample_size = snd_pcm_format_width(state->playback.format); |
| |
| infoframe = chamelium_get_last_infoframe(state->chamelium, state->port, |
| CHAMELIUM_INFOFRAME_AUDIO); |
| if (infoframe == NULL && state->playback.channels <= 2) { |
| /* Audio InfoFrames are optional for mono and stereo audio */ |
| igt_debug("Skipping audio InfoFrame check: " |
| "no InfoFrame received\n"); |
| return; |
| } |
| igt_assert_f(infoframe != NULL, "no audio InfoFrame received\n"); |
| |
| ok = infoframe_audio_parse(&infoframe_audio, infoframe->version, |
| infoframe->payload, infoframe->payload_size); |
| chamelium_infoframe_destroy(infoframe); |
| igt_assert_f(ok, "failed to parse audio InfoFrame\n"); |
| |
| igt_debug("Checking audio InfoFrame:\n"); |
| igt_debug("coding_type: got %d, expected %d\n", |
| infoframe_audio.coding_type, expected.coding_type); |
| igt_debug("channel_count: got %d, expected %d\n", |
| infoframe_audio.channel_count, expected.channel_count); |
| igt_debug("sampling_freq: got %d, expected %d\n", |
| infoframe_audio.sampling_freq, expected.sampling_freq); |
| igt_debug("sample_size: got %d, expected %d\n", |
| infoframe_audio.sample_size, expected.sample_size); |
| |
| if (infoframe_audio.coding_type != INFOFRAME_AUDIO_CT_UNSPECIFIED) |
| igt_assert(infoframe_audio.coding_type == expected.coding_type); |
| if (infoframe_audio.channel_count >= 0) |
| igt_assert(infoframe_audio.channel_count == expected.channel_count); |
| if (infoframe_audio.sampling_freq >= 0) |
| igt_assert(infoframe_audio.sampling_freq == expected.sampling_freq); |
| if (infoframe_audio.sample_size >= 0) |
| igt_assert(infoframe_audio.sample_size == expected.sample_size); |
| } |
| |
| static int |
| audio_output_frequencies_callback(void *data, void *buffer, int samples) |
| { |
| struct audio_state *state = data; |
| double *tmp; |
| size_t len; |
| |
| len = samples * state->playback.channels; |
| tmp = malloc(len * sizeof(double)); |
| audio_signal_fill(state->signal, tmp, samples); |
| audio_convert_to(buffer, tmp, len, state->playback.format); |
| free(tmp); |
| |
| return state->run ? 0 : -1; |
| } |
| |
| static bool test_audio_frequencies(struct audio_state *state) |
| { |
| int freq, step; |
| int32_t *recv, *buf; |
| double *channel; |
| size_t i, j, streak; |
| size_t recv_len, buf_len, buf_cap, channel_len; |
| bool success; |
| int capture_chan; |
| |
| state->signal = audio_signal_init(state->playback.channels, |
| state->playback.rate); |
| igt_assert_f(state->signal, "Failed to initialize audio signal\n"); |
| |
| /* We'll choose different frequencies per channel to make sure they are |
| * independent from each other. To do so, we'll add a different offset |
| * to the base frequencies for each channel. We need to choose a big |
| * enough offset so that we're sure to detect mixed up channels. We |
| * choose an offset of two 2 bins in the final FFT to enforce a clear |
| * difference. |
| * |
| * Note that we assume capture_rate == playback_rate. We'll assert this |
| * later on. We cannot retrieve the capture rate before starting |
| * playing audio, so we don't really have the choice. |
| */ |
| step = 2 * state->playback.rate / CAPTURE_SAMPLES; |
| for (i = 0; i < test_frequencies_count; i++) { |
| for (j = 0; j < state->playback.channels; j++) { |
| freq = test_frequencies[i] + j * step; |
| audio_signal_add_frequency(state->signal, freq, j); |
| } |
| } |
| audio_signal_synthesize(state->signal); |
| |
| alsa_register_output_callback(state->alsa, |
| audio_output_frequencies_callback, state, |
| PLAYBACK_SAMPLES); |
| |
| audio_state_start(state, "frequencies"); |
| |
| igt_assert_f(state->capture.rate == state->playback.rate, |
| "Capture rate (%dHz) doesn't match playback rate (%dHz)\n", |
| state->capture.rate, state->playback.rate); |
| |
| /* Needs to be a multiple of 128, because that's the number of samples |
| * we get per channel each time we receive an audio page from the |
| * Chamelium device. |
| * |
| * Additionally, this value needs to be high enough to guarantee we |
| * capture a full period of each sine we generate. If we capture 2048 |
| * samples at a 192KHz sampling rate, we get a full period for a >94Hz |
| * sines. For lower sampling rates, the capture duration will be |
| * longer. |
| */ |
| channel_len = CAPTURE_SAMPLES; |
| channel = malloc(sizeof(double) * channel_len); |
| |
| buf_cap = state->capture.channels * channel_len; |
| buf = malloc(sizeof(int32_t) * buf_cap); |
| buf_len = 0; |
| |
| recv = NULL; |
| recv_len = 0; |
| |
| success = false; |
| streak = 0; |
| while (!success && state->msec < AUDIO_TIMEOUT) { |
| audio_state_receive(state, &recv, &recv_len); |
| |
| memcpy(&buf[buf_len], recv, recv_len * sizeof(int32_t)); |
| buf_len += recv_len; |
| |
| if (buf_len < buf_cap) |
| continue; |
| igt_assert(buf_len == buf_cap); |
| |
| igt_debug("Detecting audio signal, t=%d msec\n", state->msec); |
| |
| for (j = 0; j < state->playback.channels; j++) { |
| capture_chan = state->channel_mapping[j]; |
| igt_assert(capture_chan >= 0); |
| igt_debug("Processing channel %zu (captured as " |
| "channel %d)\n", j, capture_chan); |
| |
| audio_extract_channel_s32_le(channel, channel_len, |
| buf, buf_len, |
| state->capture.channels, |
| capture_chan); |
| |
| if (audio_signal_detect(state->signal, |
| state->capture.rate, j, |
| channel, channel_len)) |
| streak++; |
| else |
| streak = 0; |
| } |
| |
| buf_len = 0; |
| |
| success = streak == MIN_STREAK * state->playback.channels; |
| } |
| |
| audio_state_stop(state, success); |
| |
| free(recv); |
| free(buf); |
| free(channel); |
| audio_signal_fini(state->signal); |
| |
| check_audio_infoframe(state); |
| |
| return success; |
| } |
| |
| static int audio_output_flatline_callback(void *data, void *buffer, |
| int samples) |
| { |
| struct audio_state *state = data; |
| double *tmp; |
| size_t len, i; |
| |
| len = samples * state->playback.channels; |
| tmp = malloc(len * sizeof(double)); |
| for (i = 0; i < len; i++) |
| tmp[i] = (state->positive ? 1 : -1) * FLATLINE_AMPLITUDE; |
| audio_convert_to(buffer, tmp, len, state->playback.format); |
| free(tmp); |
| |
| return state->run ? 0 : -1; |
| } |
| |
| static bool detect_flatline_amplitude(double *buf, size_t buf_len, bool pos) |
| { |
| double expected, min, max; |
| size_t i; |
| bool ok; |
| |
| min = max = NAN; |
| for (i = 0; i < buf_len; i++) { |
| if (isnan(min) || buf[i] < min) |
| min = buf[i]; |
| if (isnan(max) || buf[i] > max) |
| max = buf[i]; |
| } |
| |
| expected = (pos ? 1 : -1) * FLATLINE_AMPLITUDE; |
| ok = (min >= expected - FLATLINE_AMPLITUDE_ACCURACY && |
| max <= expected + FLATLINE_AMPLITUDE_ACCURACY); |
| if (ok) |
| igt_debug("Flatline wave amplitude detected\n"); |
| else |
| igt_debug("Flatline amplitude not detected (min=%f, max=%f)\n", |
| min, max); |
| return ok; |
| } |
| |
| static ssize_t detect_falling_edge(double *buf, size_t buf_len) |
| { |
| size_t i; |
| |
| for (i = 0; i < buf_len; i++) { |
| if (buf[i] < 0) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| /** test_audio_flatline: |
| * |
| * Send a constant value (one positive, then a negative one) and check that: |
| * |
| * - The amplitude of the flatline is correct |
| * - All channels switch from a positive signal to a negative one at the same |
| * time (ie. all channels are aligned) |
| */ |
| static bool test_audio_flatline(struct audio_state *state) |
| { |
| bool success, amp_success, align_success; |
| int32_t *recv; |
| size_t recv_len, i, channel_len; |
| ssize_t j; |
| int streak, capture_chan; |
| double *channel; |
| int falling_edges[CHAMELIUM_MAX_AUDIO_CHANNELS]; |
| |
| alsa_register_output_callback(state->alsa, |
| audio_output_flatline_callback, state, |
| PLAYBACK_SAMPLES); |
| |
| /* Start by sending a positive signal */ |
| state->positive = true; |
| |
| audio_state_start(state, "flatline"); |
| |
| for (i = 0; i < state->playback.channels; i++) |
| falling_edges[i] = -1; |
| |
| recv = NULL; |
| recv_len = 0; |
| amp_success = false; |
| streak = 0; |
| while (!amp_success && state->msec < AUDIO_TIMEOUT) { |
| audio_state_receive(state, &recv, &recv_len); |
| |
| igt_debug("Detecting audio signal, t=%d msec\n", state->msec); |
| |
| for (i = 0; i < state->playback.channels; i++) { |
| capture_chan = state->channel_mapping[i]; |
| igt_assert(capture_chan >= 0); |
| igt_debug("Processing channel %zu (captured as " |
| "channel %d)\n", i, capture_chan); |
| |
| channel_len = audio_extract_channel_s32_le(NULL, 0, |
| recv, recv_len, |
| state->capture.channels, |
| capture_chan); |
| channel = malloc(channel_len * sizeof(double)); |
| audio_extract_channel_s32_le(channel, channel_len, |
| recv, recv_len, |
| state->capture.channels, |
| capture_chan); |
| |
| /* Check whether the amplitude is fine */ |
| if (detect_flatline_amplitude(channel, channel_len, |
| state->positive)) |
| streak++; |
| else |
| streak = 0; |
| |
| /* If we're now sending a negative signal, detect the |
| * falling edge */ |
| j = detect_falling_edge(channel, channel_len); |
| if (!state->positive && j >= 0) { |
| falling_edges[i] = recv_len * state->recv_pages |
| + j; |
| } |
| |
| free(channel); |
| } |
| |
| amp_success = streak == MIN_STREAK * state->playback.channels; |
| |
| if (amp_success && state->positive) { |
| /* Switch to a negative signal after we've detected the |
| * positive one. */ |
| state->positive = false; |
| amp_success = false; |
| streak = 0; |
| igt_debug("Switching to negative square wave\n"); |
| } |
| } |
| |
| /* Check alignment between all channels by comparing the index of the |
| * falling edge. */ |
| align_success = true; |
| for (i = 0; i < state->playback.channels; i++) { |
| if (falling_edges[i] < 0) { |
| igt_critical("Falling edge not detected for channel %zu\n", |
| i); |
| align_success = false; |
| continue; |
| } |
| |
| if (abs(falling_edges[0] - falling_edges[i]) > |
| FLATLINE_ALIGN_ACCURACY) { |
| igt_critical("Channel alignment mismatch: " |
| "channel 0 has a falling edge at index %d " |
| "while channel %zu has index %d\n", |
| falling_edges[0], i, falling_edges[i]); |
| align_success = false; |
| } |
| } |
| |
| success = amp_success && align_success; |
| audio_state_stop(state, success); |
| |
| free(recv); |
| |
| return success; |
| } |
| |
| static bool check_audio_configuration(struct alsa *alsa, snd_pcm_format_t format, |
| int channels, int sampling_rate) |
| { |
| if (!alsa_test_output_configuration(alsa, format, channels, |
| sampling_rate)) { |
| igt_debug("Skipping test with format %s, sampling rate %d Hz " |
| "and %d channels because at least one of the " |
| "selected output devices doesn't support this " |
| "configuration\n", |
| snd_pcm_format_name(format), |
| sampling_rate, channels); |
| return false; |
| } |
| /* TODO: the Chamelium device sends a malformed signal for some audio |
| * configurations. See crbug.com/950917 */ |
| if ((format != SND_PCM_FORMAT_S16_LE && sampling_rate >= 44100) || |
| channels > 2) { |
| igt_debug("Skipping test with format %s, sampling rate %d Hz " |
| "and %d channels because the Chamelium device " |
| "doesn't support this configuration\n", |
| snd_pcm_format_name(format), |
| sampling_rate, channels); |
| return false; |
| } |
| return true; |
| } |
| |
| static const char test_display_audio_desc[] = |
| "Playback various audio signals with various audio formats/rates, " |
| "capture them and check they are correct"; |
| static void |
| test_display_audio(data_t *data, struct chamelium_port *port, |
| const char *audio_device, enum igt_custom_edid_type edid) |
| { |
| bool run, success; |
| struct alsa *alsa; |
| int ret; |
| igt_output_t *output; |
| igt_plane_t *primary; |
| struct igt_fb fb; |
| drmModeModeInfo *mode; |
| drmModeConnector *connector; |
| int fb_id, i, j; |
| int channels, sampling_rate; |
| snd_pcm_format_t format; |
| struct audio_state state; |
| |
| igt_require(alsa_has_exclusive_access()); |
| |
| /* Old Chamelium devices need an update for DisplayPort audio and |
| * chamelium_get_audio_format support. */ |
| igt_require(chamelium_has_audio_support(data->chamelium, port)); |
| |
| alsa = alsa_init(); |
| igt_assert(alsa); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| output = prepare_output(data, port, edid); |
| connector = chamelium_port_get_connector(data->chamelium, port, false); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| /* Enable the output because the receiver won't try to receive audio if |
| * it doesn't receive video. */ |
| igt_assert(connector->count_modes > 0); |
| mode = &connector->modes[0]; |
| |
| fb_id = igt_create_color_pattern_fb(data->drm_fd, |
| mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_MOD_LINEAR, |
| 0, 0, 0, &fb); |
| igt_assert(fb_id > 0); |
| |
| enable_output(data, port, output, mode, &fb); |
| |
| run = false; |
| success = true; |
| for (i = 0; i < test_sampling_rates_count; i++) { |
| for (j = 0; j < test_formats_count; j++) { |
| ret = alsa_open_output(alsa, audio_device); |
| igt_assert_f(ret >= 0, "Failed to open ALSA output\n"); |
| |
| /* TODO: playback on all 8 available channels (this |
| * isn't supported by Chamelium devices yet, see |
| * https://crbug.com/950917) */ |
| format = test_formats[j]; |
| channels = PLAYBACK_CHANNELS; |
| sampling_rate = test_sampling_rates[i]; |
| |
| if (!check_audio_configuration(alsa, format, channels, |
| sampling_rate)) |
| continue; |
| |
| run = true; |
| |
| audio_state_init(&state, data, alsa, port, |
| format, channels, sampling_rate); |
| success &= test_audio_frequencies(&state); |
| success &= test_audio_flatline(&state); |
| audio_state_fini(&state); |
| |
| alsa_close_output(alsa); |
| } |
| } |
| |
| /* Make sure we tested at least one frequency and format. */ |
| igt_assert(run); |
| /* Make sure all runs were successful. */ |
| igt_assert(success); |
| |
| igt_remove_fb(data->drm_fd, &fb); |
| |
| drmModeFreeConnector(connector); |
| |
| free(alsa); |
| } |
| |
| static const char test_display_audio_edid_desc[] = |
| "Plug a connector with an EDID suitable for audio, check ALSA's " |
| "EDID-Like Data reports the correct audio parameters"; |
| static void |
| test_display_audio_edid(data_t *data, struct chamelium_port *port, |
| enum igt_custom_edid_type edid) |
| { |
| igt_output_t *output; |
| igt_plane_t *primary; |
| struct igt_fb fb; |
| drmModeModeInfo *mode; |
| drmModeConnector *connector; |
| int fb_id; |
| struct eld_entry eld; |
| struct eld_sad *sad; |
| |
| igt_require(eld_is_supported()); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| output = prepare_output(data, port, edid); |
| connector = chamelium_port_get_connector(data->chamelium, port, false); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| /* Enable the output because audio cannot be played on inactive |
| * connectors. */ |
| igt_assert(connector->count_modes > 0); |
| mode = &connector->modes[0]; |
| |
| fb_id = igt_create_color_pattern_fb(data->drm_fd, |
| mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_MOD_LINEAR, |
| 0, 0, 0, &fb); |
| igt_assert(fb_id > 0); |
| |
| enable_output(data, port, output, mode, &fb); |
| |
| igt_assert(eld_get_igt(&eld)); |
| igt_assert(eld.sads_len == 1); |
| |
| sad = &eld.sads[0]; |
| igt_assert(sad->coding_type == CEA_SAD_FORMAT_PCM); |
| igt_assert(sad->channels == 2); |
| igt_assert(sad->rates == (CEA_SAD_SAMPLING_RATE_32KHZ | |
| CEA_SAD_SAMPLING_RATE_44KHZ | CEA_SAD_SAMPLING_RATE_48KHZ)); |
| igt_assert(sad->bits == (CEA_SAD_SAMPLE_SIZE_16 | |
| CEA_SAD_SAMPLE_SIZE_20 | CEA_SAD_SAMPLE_SIZE_24)); |
| |
| igt_remove_fb(data->drm_fd, &fb); |
| |
| drmModeFreeConnector(connector); |
| } |
| |
| static void randomize_plane_stride(data_t *data, |
| uint32_t width, uint32_t height, |
| uint32_t format, uint64_t modifier, |
| size_t *stride) |
| { |
| size_t stride_min; |
| uint32_t max_tile_w = 4, tile_w, tile_h; |
| int i; |
| struct igt_fb dummy; |
| |
| stride_min = width * igt_format_plane_bpp(format, 0) / 8; |
| |
| /* Randomize the stride to less than twice the minimum. */ |
| *stride = (rand() % stride_min) + stride_min; |
| |
| /* |
| * Create a dummy FB to determine bpp for each plane, and calculate |
| * the maximum tile width from that. |
| */ |
| igt_create_fb(data->drm_fd, 64, 64, format, modifier, &dummy); |
| for (i = 0; i < dummy.num_planes; i++) { |
| igt_get_fb_tile_size(data->drm_fd, modifier, dummy.plane_bpp[i], &tile_w, &tile_h); |
| |
| if (tile_w > max_tile_w) |
| max_tile_w = tile_w; |
| } |
| igt_remove_fb(data->drm_fd, &dummy); |
| |
| /* |
| * Pixman requires the stride to be aligned to 32-bits, which is |
| * reflected in the initial value of max_tile_w and the hw |
| * may require a multiple of tile width, choose biggest of the 2. |
| */ |
| *stride = ALIGN(*stride, max_tile_w); |
| } |
| |
| static void update_tiled_modifier(igt_plane_t *plane, uint32_t width, |
| uint32_t height, uint32_t format, |
| uint64_t *modifier) |
| { |
| if (*modifier == DRM_FORMAT_MOD_BROADCOM_SAND256) { |
| /* Randomize the column height to less than twice the minimum. */ |
| size_t column_height = (rand() % height) + height; |
| |
| igt_debug("Selecting VC4 SAND256 tiling with column height %ld\n", |
| column_height); |
| |
| *modifier = DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(column_height); |
| } |
| } |
| |
| static void randomize_plane_setup(data_t *data, igt_plane_t *plane, |
| drmModeModeInfo *mode, |
| uint32_t *width, uint32_t *height, |
| uint32_t *format, uint64_t *modifier, |
| bool allow_yuv) |
| { |
| int min_dim; |
| uint32_t idx[plane->format_mod_count]; |
| unsigned int count = 0; |
| unsigned int i; |
| |
| /* First pass to count the supported formats. */ |
| for (i = 0; i < plane->format_mod_count; i++) |
| if (igt_fb_supported_format(plane->formats[i]) && |
| (allow_yuv || !igt_format_is_yuv(plane->formats[i]))) |
| idx[count++] = i; |
| |
| igt_assert(count > 0); |
| |
| i = idx[rand() % count]; |
| *format = plane->formats[i]; |
| *modifier = plane->modifiers[i]; |
| |
| update_tiled_modifier(plane, *width, *height, *format, modifier); |
| |
| /* |
| * Randomize width and height in the mode dimensions range. |
| * |
| * Restrict to a min of 2 * min_dim, this way src_w/h are always at |
| * least min_dim, because src_w = width - (rand % w / 2). |
| * |
| * Use a minimum dimension of 16 for YUV, because planar YUV |
| * subsamples the UV plane. |
| */ |
| min_dim = igt_format_is_yuv(*format) ? 16 : 8; |
| |
| *width = max((rand() % mode->hdisplay) + 1, 2 * min_dim); |
| *height = max((rand() % mode->vdisplay) + 1, 2 * min_dim); |
| } |
| |
| static void configure_plane(igt_plane_t *plane, uint32_t src_w, uint32_t src_h, |
| uint32_t src_x, uint32_t src_y, uint32_t crtc_w, |
| uint32_t crtc_h, int32_t crtc_x, int32_t crtc_y, |
| struct igt_fb *fb) |
| { |
| igt_plane_set_fb(plane, fb); |
| |
| igt_plane_set_position(plane, crtc_x, crtc_y); |
| igt_plane_set_size(plane, crtc_w, crtc_h); |
| |
| igt_fb_set_position(fb, plane, src_x, src_y); |
| igt_fb_set_size(fb, plane, src_w, src_h); |
| } |
| |
| static void randomize_plane_coordinates(data_t *data, igt_plane_t *plane, |
| drmModeModeInfo *mode, |
| struct igt_fb *fb, |
| uint32_t *src_w, uint32_t *src_h, |
| uint32_t *src_x, uint32_t *src_y, |
| uint32_t *crtc_w, uint32_t *crtc_h, |
| int32_t *crtc_x, int32_t *crtc_y, |
| bool allow_scaling) |
| { |
| bool is_yuv = igt_format_is_yuv(fb->drm_format); |
| uint32_t width = fb->width, height = fb->height; |
| double ratio; |
| int ret; |
| |
| /* Randomize source offset in the first half of the original size. */ |
| *src_x = rand() % (width / 2); |
| *src_y = rand() % (height / 2); |
| |
| /* The source size only includes the active source area. */ |
| *src_w = width - *src_x; |
| *src_h = height - *src_y; |
| |
| if (allow_scaling) { |
| *crtc_w = (rand() % mode->hdisplay) + 1; |
| *crtc_h = (rand() % mode->vdisplay) + 1; |
| |
| /* |
| * Don't bother with scaling if dimensions are quite close in |
| * order to get non-scaling cases more frequently. Also limit |
| * scaling to 3x to avoid agressive filtering that makes |
| * comparison less reliable, and don't go above 2x downsampling |
| * to avoid possible hw limitations. |
| */ |
| |
| ratio = ((double) *crtc_w / *src_w); |
| if (ratio < 0.5) |
| *src_w = *crtc_w * 2; |
| else if (ratio > 0.8 && ratio < 1.2) |
| *crtc_w = *src_w; |
| else if (ratio > 3.0) |
| *crtc_w = *src_w * 3; |
| |
| ratio = ((double) *crtc_h / *src_h); |
| if (ratio < 0.5) |
| *src_h = *crtc_h * 2; |
| else if (ratio > 0.8 && ratio < 1.2) |
| *crtc_h = *src_h; |
| else if (ratio > 3.0) |
| *crtc_h = *src_h * 3; |
| } else { |
| *crtc_w = *src_w; |
| *crtc_h = *src_h; |
| } |
| |
| if (*crtc_w != *src_w || *crtc_h != *src_h) { |
| /* |
| * When scaling is involved, make sure to not go off-bounds or |
| * scaled clipping may result in decimal dimensions, that most |
| * drivers don't support. |
| */ |
| if (*crtc_w < mode->hdisplay) |
| *crtc_x = rand() % (mode->hdisplay - *crtc_w); |
| else |
| *crtc_x = 0; |
| |
| if (*crtc_h < mode->vdisplay) |
| *crtc_y = rand() % (mode->vdisplay - *crtc_h); |
| else |
| *crtc_y = 0; |
| } else { |
| /* |
| * Randomize the on-crtc position and allow the plane to go |
| * off-display by less than half of its on-crtc dimensions. |
| */ |
| *crtc_x = (rand() % mode->hdisplay) - *crtc_w / 2; |
| *crtc_y = (rand() % mode->vdisplay) - *crtc_h / 2; |
| } |
| |
| configure_plane(plane, *src_w, *src_h, *src_x, *src_y, |
| *crtc_w, *crtc_h, *crtc_x, *crtc_y, fb); |
| ret = igt_display_try_commit_atomic(&data->display, |
| DRM_MODE_ATOMIC_TEST_ONLY | |
| DRM_MODE_ATOMIC_ALLOW_MODESET, |
| NULL); |
| if (!ret) |
| return; |
| |
| /* Coordinates are logged in the dumped debug log, so only report w/h on failure here. */ |
| igt_assert_f(ret != -ENOSPC,"Failure in testcase, invalid coordinates on a %ux%u fb\n", width, height); |
| |
| /* Make YUV coordinates a multiple of 2 and retry the math. */ |
| if (is_yuv) { |
| *src_x &= ~1; |
| *src_y &= ~1; |
| *src_w &= ~1; |
| *src_h &= ~1; |
| /* To handle 1:1 scaling, clear crtc_w/h too. */ |
| *crtc_w &= ~1; |
| *crtc_h &= ~1; |
| |
| if (*crtc_x < 0 && (*crtc_x & 1)) |
| (*crtc_x)++; |
| else |
| *crtc_x &= ~1; |
| |
| /* If negative, round up to 0 instead of down */ |
| if (*crtc_y < 0 && (*crtc_y & 1)) |
| (*crtc_y)++; |
| else |
| *crtc_y &= ~1; |
| |
| configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, |
| *crtc_h, *crtc_x, *crtc_y, fb); |
| ret = igt_display_try_commit_atomic(&data->display, |
| DRM_MODE_ATOMIC_TEST_ONLY | |
| DRM_MODE_ATOMIC_ALLOW_MODESET, |
| NULL); |
| if (!ret) |
| return; |
| } |
| |
| igt_assert(!ret || allow_scaling); |
| igt_info("Scaling ratio %g / %g failed, trying without scaling.\n", |
| ((double) *crtc_w / *src_w), ((double) *crtc_h / *src_h)); |
| |
| *crtc_w = *src_w; |
| *crtc_h = *src_h; |
| |
| configure_plane(plane, *src_w, *src_h, *src_x, *src_y, *crtc_w, |
| *crtc_h, *crtc_x, *crtc_y, fb); |
| igt_display_commit_atomic(&data->display, |
| DRM_MODE_ATOMIC_TEST_ONLY | |
| DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); |
| } |
| |
| static void blit_plane_cairo(data_t *data, cairo_surface_t *result, |
| uint32_t src_w, uint32_t src_h, |
| uint32_t src_x, uint32_t src_y, |
| uint32_t crtc_w, uint32_t crtc_h, |
| int32_t crtc_x, int32_t crtc_y, |
| struct igt_fb *fb) |
| { |
| cairo_surface_t *surface; |
| cairo_surface_t *clipped_surface; |
| cairo_t *cr; |
| |
| surface = igt_get_cairo_surface(data->drm_fd, fb); |
| |
| if (src_x || src_y) { |
| clipped_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, |
| src_w, src_h); |
| |
| cr = cairo_create(clipped_surface); |
| |
| cairo_translate(cr, -1. * src_x, -1. * src_y); |
| |
| cairo_set_source_surface(cr, surface, 0, 0); |
| |
| cairo_paint(cr); |
| cairo_surface_flush(clipped_surface); |
| |
| cairo_destroy(cr); |
| } else { |
| clipped_surface = surface; |
| } |
| |
| cr = cairo_create(result); |
| |
| cairo_translate(cr, crtc_x, crtc_y); |
| |
| if (src_w != crtc_w || src_h != crtc_h) { |
| cairo_scale(cr, (double) crtc_w / src_w, |
| (double) crtc_h / src_h); |
| } |
| |
| cairo_set_source_surface(cr, clipped_surface, 0, 0); |
| cairo_surface_destroy(clipped_surface); |
| |
| if (src_w != crtc_w || src_h != crtc_h) { |
| cairo_pattern_set_filter(cairo_get_source(cr), |
| CAIRO_FILTER_BILINEAR); |
| cairo_pattern_set_extend(cairo_get_source(cr), |
| CAIRO_EXTEND_NONE); |
| } |
| |
| cairo_paint(cr); |
| cairo_surface_flush(result); |
| |
| cairo_destroy(cr); |
| } |
| |
| static void prepare_randomized_plane(data_t *data, |
| drmModeModeInfo *mode, |
| igt_plane_t *plane, |
| struct igt_fb *overlay_fb, |
| unsigned int index, |
| cairo_surface_t *result_surface, |
| bool allow_scaling, bool allow_yuv) |
| { |
| struct igt_fb pattern_fb; |
| uint32_t overlay_fb_w, overlay_fb_h; |
| uint32_t overlay_src_w, overlay_src_h; |
| uint32_t overlay_src_x, overlay_src_y; |
| int32_t overlay_crtc_x, overlay_crtc_y; |
| uint32_t overlay_crtc_w, overlay_crtc_h; |
| uint32_t format; |
| uint64_t modifier; |
| size_t stride; |
| bool tiled; |
| int fb_id; |
| |
| randomize_plane_setup(data, plane, mode, &overlay_fb_w, &overlay_fb_h, |
| &format, &modifier, allow_yuv); |
| |
| tiled = (modifier != DRM_FORMAT_MOD_LINEAR); |
| igt_debug("Plane %d: framebuffer size %dx%d %s format (%s)\n", |
| index, overlay_fb_w, overlay_fb_h, |
| igt_format_str(format), tiled ? "tiled" : "linear"); |
| |
| /* Get a pattern framebuffer for the overlay plane. */ |
| fb_id = chamelium_get_pattern_fb(data, overlay_fb_w, overlay_fb_h, |
| DRM_FORMAT_XRGB8888, 32, &pattern_fb); |
| igt_assert(fb_id > 0); |
| |
| randomize_plane_stride(data, overlay_fb_w, overlay_fb_h, |
| format, modifier, &stride); |
| |
| igt_debug("Plane %d: stride %ld\n", index, stride); |
| |
| fb_id = igt_fb_convert_with_stride(overlay_fb, &pattern_fb, format, |
| modifier, stride); |
| igt_assert(fb_id > 0); |
| |
| randomize_plane_coordinates(data, plane, mode, overlay_fb, |
| &overlay_src_w, &overlay_src_h, |
| &overlay_src_x, &overlay_src_y, |
| &overlay_crtc_w, &overlay_crtc_h, |
| &overlay_crtc_x, &overlay_crtc_y, |
| allow_scaling); |
| |
| igt_debug("Plane %d: in-framebuffer size %dx%d\n", index, |
| overlay_src_w, overlay_src_h); |
| igt_debug("Plane %d: in-framebuffer position %dx%d\n", index, |
| overlay_src_x, overlay_src_y); |
| igt_debug("Plane %d: on-crtc size %dx%d\n", index, |
| overlay_crtc_w, overlay_crtc_h); |
| igt_debug("Plane %d: on-crtc position %dx%d\n", index, |
| overlay_crtc_x, overlay_crtc_y); |
| |
| blit_plane_cairo(data, result_surface, overlay_src_w, overlay_src_h, |
| overlay_src_x, overlay_src_y, |
| overlay_crtc_w, overlay_crtc_h, |
| overlay_crtc_x, overlay_crtc_y, &pattern_fb); |
| |
| /* Remove the original pattern framebuffer. */ |
| igt_remove_fb(data->drm_fd, &pattern_fb); |
| } |
| |
| static const char test_display_planes_random_desc[] = |
| "Setup a few overlay planes with random parameters, capture the frame " |
| "and check it matches the expected output"; |
| static void test_display_planes_random(data_t *data, |
| struct chamelium_port *port, |
| enum chamelium_check check) |
| { |
| igt_output_t *output; |
| drmModeModeInfo *mode; |
| igt_plane_t *primary_plane; |
| struct igt_fb primary_fb; |
| struct igt_fb result_fb; |
| struct igt_fb *overlay_fbs; |
| igt_crc_t *crc; |
| igt_crc_t *expected_crc; |
| struct chamelium_fb_crc_async_data *fb_crc; |
| unsigned int overlay_planes_max = 0; |
| unsigned int overlay_planes_count; |
| cairo_surface_t *result_surface; |
| int captured_frame_count; |
| bool allow_scaling; |
| bool allow_yuv; |
| unsigned int i; |
| unsigned int fb_id; |
| |
| switch (check) { |
| case CHAMELIUM_CHECK_CRC: |
| allow_scaling = false; |
| allow_yuv = false; |
| break; |
| case CHAMELIUM_CHECK_CHECKERBOARD: |
| allow_scaling = true; |
| allow_yuv = true; |
| break; |
| default: |
| igt_assert(false); |
| } |
| |
| srand(time(NULL)); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| /* Find the connector and pipe. */ |
| output = prepare_output(data, port, IGT_CUSTOM_EDID_BASE); |
| |
| mode = igt_output_get_mode(output); |
| |
| /* Get a framebuffer for the primary plane. */ |
| primary_plane = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary_plane); |
| |
| fb_id = chamelium_get_pattern_fb(data, mode->hdisplay, mode->vdisplay, |
| DRM_FORMAT_XRGB8888, 64, &primary_fb); |
| igt_assert(fb_id > 0); |
| |
| /* Get a framebuffer for the cairo composition result. */ |
| fb_id = igt_create_fb(data->drm_fd, mode->hdisplay, |
| mode->vdisplay, DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_MOD_LINEAR, &result_fb); |
| igt_assert(fb_id > 0); |
| |
| result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb); |
| |
| /* Paint the primary framebuffer on the result surface. */ |
| blit_plane_cairo(data, result_surface, 0, 0, 0, 0, 0, 0, 0, 0, |
| &primary_fb); |
| |
| /* Configure the primary plane. */ |
| igt_plane_set_fb(primary_plane, &primary_fb); |
| |
| overlay_planes_max = |
| igt_output_count_plane_type(output, DRM_PLANE_TYPE_OVERLAY); |
| |
| /* Limit the number of planes to a reasonable scene. */ |
| overlay_planes_max = min(overlay_planes_max, 4u); |
| |
| overlay_planes_count = (rand() % overlay_planes_max) + 1; |
| igt_debug("Using %d overlay planes\n", overlay_planes_count); |
| |
| overlay_fbs = calloc(sizeof(struct igt_fb), overlay_planes_count); |
| |
| for (i = 0; i < overlay_planes_count; i++) { |
| struct igt_fb *overlay_fb = &overlay_fbs[i]; |
| igt_plane_t *plane = |
| igt_output_get_plane_type_index(output, |
| DRM_PLANE_TYPE_OVERLAY, |
| i); |
| igt_assert(plane); |
| |
| prepare_randomized_plane(data, mode, plane, overlay_fb, i, |
| result_surface, allow_scaling, |
| allow_yuv); |
| } |
| |
| cairo_surface_destroy(result_surface); |
| |
| if (check == CHAMELIUM_CHECK_CRC) |
| fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd, |
| &result_fb); |
| |
| igt_display_commit2(&data->display, COMMIT_ATOMIC); |
| |
| if (check == CHAMELIUM_CHECK_CRC) { |
| chamelium_capture(data->chamelium, port, 0, 0, 0, 0, 1); |
| crc = chamelium_read_captured_crcs(data->chamelium, |
| &captured_frame_count); |
| |
| igt_assert(captured_frame_count == 1); |
| |
| expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc); |
| |
| chamelium_assert_crc_eq_or_dump(data->chamelium, |
| expected_crc, crc, |
| &result_fb, 0); |
| |
| free(expected_crc); |
| free(crc); |
| } else if (check == CHAMELIUM_CHECK_CHECKERBOARD) { |
| struct chamelium_frame_dump *dump; |
| |
| dump = chamelium_port_dump_pixels(data->chamelium, port, 0, 0, |
| 0, 0); |
| chamelium_assert_frame_match_or_dump(data->chamelium, port, |
| dump, &result_fb, check); |
| chamelium_destroy_frame_dump(dump); |
| } |
| |
| for (i = 0; i < overlay_planes_count; i++) |
| igt_remove_fb(data->drm_fd, &overlay_fbs[i]); |
| |
| free(overlay_fbs); |
| |
| igt_remove_fb(data->drm_fd, &primary_fb); |
| igt_remove_fb(data->drm_fd, &result_fb); |
| } |
| |
| static const char test_hpd_without_ddc_desc[] = |
| "Disable DDC on a VGA connector, check we still get a uevent on hotplug"; |
| static void |
| test_hpd_without_ddc(data_t *data, struct chamelium_port *port) |
| { |
| struct udev_monitor *mon = igt_watch_uevents(); |
| |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| igt_flush_uevents(mon); |
| |
| /* Disable the DDC on the connector and make sure we still get a |
| * hotplug |
| */ |
| chamelium_port_set_ddc_state(data->chamelium, port, false); |
| chamelium_plug(data->chamelium, port); |
| |
| igt_assert(igt_hotplug_detected(mon, CHAMELIUM_HOTPLUG_TIMEOUT)); |
| igt_assert_eq(chamelium_reprobe_connector(&data->display, |
| data->chamelium, port), |
| DRM_MODE_CONNECTED); |
| |
| igt_cleanup_uevents(mon); |
| } |
| |
| static const char test_hpd_storm_detect_desc[] = |
| "Trigger a series of hotplugs in a very small timeframe to simulate a" |
| "bad cable, check the kernel falls back to polling to avoid a hotplug " |
| "storm"; |
| static void |
| test_hpd_storm_detect(data_t *data, struct chamelium_port *port, int width) |
| { |
| struct udev_monitor *mon; |
| int count = 0; |
| |
| igt_require_hpd_storm_ctl(data->drm_fd); |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| igt_hpd_storm_set_threshold(data->drm_fd, 1); |
| chamelium_fire_hpd_pulses(data->chamelium, port, width, 10); |
| igt_assert(igt_hpd_storm_detected(data->drm_fd)); |
| |
| mon = igt_watch_uevents(); |
| chamelium_fire_hpd_pulses(data->chamelium, port, width, 10); |
| |
| /* |
| * Polling should have been enabled by the HPD storm at this point, |
| * so we should only get at most 1 hotplug event |
| */ |
| igt_until_timeout(5) |
| count += igt_hotplug_detected(mon, 1); |
| igt_assert_lt(count, 2); |
| |
| igt_cleanup_uevents(mon); |
| igt_hpd_storm_reset(data->drm_fd); |
| } |
| |
| static const char test_hpd_storm_disable_desc[] = |
| "Disable HPD storm detection, trigger a storm and check the kernel " |
| "doesn't detect one"; |
| static void |
| test_hpd_storm_disable(data_t *data, struct chamelium_port *port, int width) |
| { |
| igt_require_hpd_storm_ctl(data->drm_fd); |
| igt_modeset_disable_all_outputs(&data->display); |
| chamelium_reset_state(&data->display, data->chamelium, |
| port, data->ports, data->port_count); |
| |
| igt_hpd_storm_set_threshold(data->drm_fd, 0); |
| chamelium_fire_hpd_pulses(data->chamelium, port, |
| width, 10); |
| igt_assert(!igt_hpd_storm_detected(data->drm_fd)); |
| |
| igt_hpd_storm_reset(data->drm_fd); |
| } |
| |
| #define for_each_port(p, port) \ |
| for (p = 0, port = data.ports[p]; \ |
| p < data.port_count; \ |
| p++, port = data.ports[p]) |
| |
| #define connector_subtest(name__, type__) \ |
| igt_subtest(name__) \ |
| for_each_port(p, port) \ |
| if (chamelium_port_get_type(port) == \ |
| DRM_MODE_CONNECTOR_ ## type__) |
| |
| #define connector_dynamic_subtest(name__, type__) \ |
| igt_subtest_with_dynamic(name__) \ |
| for_each_port(p, port) \ |
| if (chamelium_port_get_type(port) == \ |
| DRM_MODE_CONNECTOR_ ## type__) |
| |
| |
| static data_t data; |
| |
| IGT_TEST_DESCRIPTION("Tests requiring a Chamelium board"); |
| igt_main |
| { |
| struct chamelium_port *port; |
| int p; |
| size_t i; |
| |
| igt_fixture { |
| /* So fbcon doesn't try to reprobe things itself */ |
| kmstest_set_vt_graphics_mode(); |
| |
| data.drm_fd = drm_open_driver_master(DRIVER_ANY); |
| igt_display_require(&data.display, data.drm_fd); |
| igt_require(data.display.is_atomic); |
| |
| /* |
| * XXX: disabling modeset, can be removed when |
| * igt_display_require will start doing this for us |
| */ |
| igt_display_commit2(&data.display, COMMIT_ATOMIC); |
| |
| /* we need to initalize chamelium after igt_display_require */ |
| data.chamelium = chamelium_init(data.drm_fd); |
| igt_require(data.chamelium); |
| |
| data.ports = chamelium_get_ports(data.chamelium, |
| &data.port_count); |
| |
| for (i = 0; i < IGT_CUSTOM_EDID_COUNT; ++i) { |
| data.edids[i] = chamelium_new_edid(data.chamelium, |
| igt_kms_get_custom_edid(i)); |
| } |
| } |
| |
| igt_describe("DisplayPort tests"); |
| igt_subtest_group { |
| igt_fixture { |
| chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_DisplayPort, |
| data.port_count, 1); |
| } |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("dp-hpd", DisplayPort) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_DP_HDMI, |
| TEST_MODESET_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("dp-hpd-fast", DisplayPort) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("dp-hpd-enable-disable-mode", DisplayPort) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_ON_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("dp-hpd-with-enabled-mode", DisplayPort) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_ON); |
| |
| igt_describe(igt_custom_edid_type_read_desc); |
| connector_subtest("dp-edid-read", DisplayPort) { |
| igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE); |
| igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT); |
| } |
| |
| igt_describe(test_suspend_resume_hpd_desc); |
| connector_subtest("dp-hpd-after-suspend", DisplayPort) |
| test_suspend_resume_hpd(&data, port, |
| SUSPEND_STATE_MEM, |
| SUSPEND_TEST_NONE); |
| |
| igt_describe(test_suspend_resume_hpd_desc); |
| connector_subtest("dp-hpd-after-hibernate", DisplayPort) |
| test_suspend_resume_hpd(&data, port, |
| SUSPEND_STATE_DISK, |
| SUSPEND_TEST_DEVICES); |
| |
| igt_describe(test_hpd_storm_detect_desc); |
| connector_subtest("dp-hpd-storm", DisplayPort) |
| test_hpd_storm_detect(&data, port, |
| HPD_STORM_PULSE_INTERVAL_DP); |
| |
| igt_describe(test_hpd_storm_disable_desc); |
| connector_subtest("dp-hpd-storm-disable", DisplayPort) |
| test_hpd_storm_disable(&data, port, |
| HPD_STORM_PULSE_INTERVAL_DP); |
| |
| igt_describe(test_suspend_resume_edid_change_desc); |
| connector_subtest("dp-edid-change-during-suspend", DisplayPort) |
| test_suspend_resume_edid_change(&data, port, |
| SUSPEND_STATE_MEM, |
| SUSPEND_TEST_NONE, |
| IGT_CUSTOM_EDID_BASE, |
| IGT_CUSTOM_EDID_ALT); |
| |
| igt_describe(test_suspend_resume_edid_change_desc); |
| connector_subtest("dp-edid-change-during-hibernate", DisplayPort) |
| test_suspend_resume_edid_change(&data, port, |
| SUSPEND_STATE_DISK, |
| SUSPEND_TEST_DEVICES, |
| IGT_CUSTOM_EDID_BASE, |
| IGT_CUSTOM_EDID_ALT); |
| |
| igt_describe(test_display_all_modes_desc); |
| connector_subtest("dp-crc-single", DisplayPort) |
| test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_CRC, 1); |
| |
| igt_describe(test_display_one_mode_desc); |
| connector_subtest("dp-crc-fast", DisplayPort) |
| test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_CRC, 1); |
| |
| igt_describe(test_display_all_modes_desc); |
| connector_subtest("dp-crc-multiple", DisplayPort) |
| test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_CRC, 3); |
| |
| igt_describe(test_display_frame_dump_desc); |
| connector_subtest("dp-frame-dump", DisplayPort) |
| test_display_frame_dump(&data, port); |
| |
| igt_describe(test_mode_timings_desc); |
| connector_subtest("dp-mode-timings", DisplayPort) |
| test_mode_timings(&data, port); |
| |
| igt_describe(test_display_audio_desc); |
| connector_subtest("dp-audio", DisplayPort) |
| test_display_audio(&data, port, "HDMI", |
| IGT_CUSTOM_EDID_DP_AUDIO); |
| |
| igt_describe(test_display_audio_edid_desc); |
| connector_subtest("dp-audio-edid", DisplayPort) |
| test_display_audio_edid(&data, port, |
| IGT_CUSTOM_EDID_DP_AUDIO); |
| |
| igt_describe(test_hotplug_for_each_pipe_desc); |
| connector_subtest("dp-hpd-for-each-pipe", DisplayPort) |
| test_hotplug_for_each_pipe(&data, port); |
| } |
| |
| igt_describe("HDMI tests"); |
| igt_subtest_group { |
| igt_fixture { |
| chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_HDMIA, |
| data.port_count, 1); |
| } |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("hdmi-hpd", HDMIA) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_DP_HDMI, |
| TEST_MODESET_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("hdmi-hpd-fast", HDMIA) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("hdmi-hpd-enable-disable-mode", HDMIA) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_ON_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("hdmi-hpd-with-enabled-mode", HDMIA) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_ON); |
| |
| igt_describe(igt_custom_edid_type_read_desc); |
| connector_subtest("hdmi-edid-read", HDMIA) { |
| igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE); |
| igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT); |
| } |
| |
| igt_describe(test_suspend_resume_hpd_desc); |
| connector_subtest("hdmi-hpd-after-suspend", HDMIA) |
| test_suspend_resume_hpd(&data, port, |
| SUSPEND_STATE_MEM, |
| SUSPEND_TEST_NONE); |
| |
| igt_describe(test_suspend_resume_hpd_desc); |
| connector_subtest("hdmi-hpd-after-hibernate", HDMIA) |
| test_suspend_resume_hpd(&data, port, |
| SUSPEND_STATE_DISK, |
| SUSPEND_TEST_DEVICES); |
| |
| igt_describe(test_hpd_storm_detect_desc); |
| connector_subtest("hdmi-hpd-storm", HDMIA) |
| test_hpd_storm_detect(&data, port, |
| HPD_STORM_PULSE_INTERVAL_HDMI); |
| |
| igt_describe(test_hpd_storm_disable_desc); |
| connector_subtest("hdmi-hpd-storm-disable", HDMIA) |
| test_hpd_storm_disable(&data, port, |
| HPD_STORM_PULSE_INTERVAL_HDMI); |
| |
| igt_describe(test_suspend_resume_edid_change_desc); |
| connector_subtest("hdmi-edid-change-during-suspend", HDMIA) |
| test_suspend_resume_edid_change(&data, port, |
| SUSPEND_STATE_MEM, |
| SUSPEND_TEST_NONE, |
| IGT_CUSTOM_EDID_BASE, |
| IGT_CUSTOM_EDID_ALT); |
| |
| igt_describe(test_suspend_resume_edid_change_desc); |
| connector_subtest("hdmi-edid-change-during-hibernate", HDMIA) |
| test_suspend_resume_edid_change(&data, port, |
| SUSPEND_STATE_DISK, |
| SUSPEND_TEST_DEVICES, |
| IGT_CUSTOM_EDID_BASE, |
| IGT_CUSTOM_EDID_ALT); |
| |
| igt_describe(test_display_all_modes_desc); |
| connector_subtest("hdmi-crc-single", HDMIA) |
| test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_CRC, 1); |
| |
| igt_describe(test_display_one_mode_desc); |
| connector_subtest("hdmi-crc-fast", HDMIA) |
| test_display_one_mode(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_CRC, 1); |
| |
| igt_describe(test_display_all_modes_desc); |
| connector_subtest("hdmi-crc-multiple", HDMIA) |
| test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_CRC, 3); |
| |
| igt_describe(test_display_one_mode_desc); |
| connector_dynamic_subtest("hdmi-crc-nonplanar-formats", HDMIA) { |
| int k; |
| igt_output_t *output; |
| igt_plane_t *primary; |
| |
| output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| for (k = 0; k < primary->format_mod_count; k++) { |
| if (!igt_fb_supported_format(primary->formats[k])) |
| continue; |
| |
| if (igt_format_is_yuv(primary->formats[k])) |
| continue; |
| |
| if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR) |
| continue; |
| |
| igt_dynamic_f("%s", igt_format_str(primary->formats[k])) |
| test_display_one_mode(&data, port, primary->formats[k], |
| CHAMELIUM_CHECK_CRC, 1); |
| } |
| } |
| |
| igt_describe(test_display_planes_random_desc); |
| connector_subtest("hdmi-crc-planes-random", HDMIA) |
| test_display_planes_random(&data, port, |
| CHAMELIUM_CHECK_CRC); |
| |
| igt_describe(test_display_one_mode_desc); |
| connector_dynamic_subtest("hdmi-cmp-planar-formats", HDMIA) { |
| int k; |
| igt_output_t *output; |
| igt_plane_t *primary; |
| |
| output = prepare_output(&data, port, IGT_CUSTOM_EDID_BASE); |
| primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); |
| igt_assert(primary); |
| |
| for (k = 0; k < primary->format_mod_count; k++) { |
| if (!igt_fb_supported_format(primary->formats[k])) |
| continue; |
| |
| if (!igt_format_is_yuv(primary->formats[k])) |
| continue; |
| |
| if (primary->modifiers[k] != DRM_FORMAT_MOD_LINEAR) |
| continue; |
| |
| igt_dynamic_f("%s", igt_format_str(primary->formats[k])) |
| test_display_one_mode(&data, port, primary->formats[k], |
| CHAMELIUM_CHECK_CHECKERBOARD, 1); |
| } |
| } |
| |
| igt_describe(test_display_planes_random_desc); |
| connector_subtest("hdmi-cmp-planes-random", HDMIA) |
| test_display_planes_random(&data, port, |
| CHAMELIUM_CHECK_CHECKERBOARD); |
| |
| igt_describe(test_display_frame_dump_desc); |
| connector_subtest("hdmi-frame-dump", HDMIA) |
| test_display_frame_dump(&data, port); |
| |
| igt_describe(test_mode_timings_desc); |
| connector_subtest("hdmi-mode-timings", HDMIA) |
| test_mode_timings(&data, port); |
| |
| igt_describe(test_display_audio_desc); |
| connector_subtest("hdmi-audio", HDMIA) |
| test_display_audio(&data, port, "HDMI", |
| IGT_CUSTOM_EDID_HDMI_AUDIO); |
| |
| igt_describe(test_display_audio_edid_desc); |
| connector_subtest("hdmi-audio-edid", HDMIA) |
| test_display_audio_edid(&data, port, |
| IGT_CUSTOM_EDID_HDMI_AUDIO); |
| |
| igt_describe(test_display_aspect_ratio_desc); |
| connector_subtest("hdmi-aspect-ratio", HDMIA) |
| test_display_aspect_ratio(&data, port); |
| |
| igt_describe(test_hotplug_for_each_pipe_desc); |
| connector_subtest("hdmi-hpd-for-each-pipe", HDMIA) |
| test_hotplug_for_each_pipe(&data, port); |
| } |
| |
| igt_describe("VGA tests"); |
| igt_subtest_group { |
| igt_fixture { |
| chamelium_require_connector_present(data.ports, DRM_MODE_CONNECTOR_VGA, |
| data.port_count, 1); |
| } |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("vga-hpd", VGA) |
| test_hotplug(&data, port, HPD_TOGGLE_COUNT_VGA, |
| TEST_MODESET_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("vga-hpd-fast", VGA) |
| test_hotplug(&data, port, HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("vga-hpd-enable-disable-mode", VGA) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_ON_OFF); |
| |
| igt_describe(test_basic_hotplug_desc); |
| connector_subtest("vga-hpd-with-enabled-mode", VGA) |
| test_hotplug(&data, port, |
| HPD_TOGGLE_COUNT_FAST, |
| TEST_MODESET_ON); |
| |
| igt_describe(igt_custom_edid_type_read_desc); |
| connector_subtest("vga-edid-read", VGA) { |
| igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_BASE); |
| igt_custom_edid_type_read(&data, port, IGT_CUSTOM_EDID_ALT); |
| } |
| |
| igt_describe(test_suspend_resume_hpd_desc); |
| connector_subtest("vga-hpd-after-suspend", VGA) |
| test_suspend_resume_hpd(&data, port, |
| SUSPEND_STATE_MEM, |
| SUSPEND_TEST_NONE); |
| |
| igt_describe(test_suspend_resume_hpd_desc); |
| connector_subtest("vga-hpd-after-hibernate", VGA) |
| test_suspend_resume_hpd(&data, port, |
| SUSPEND_STATE_DISK, |
| SUSPEND_TEST_DEVICES); |
| |
| igt_describe(test_hpd_without_ddc_desc); |
| connector_subtest("vga-hpd-without-ddc", VGA) |
| test_hpd_without_ddc(&data, port); |
| |
| igt_describe(test_display_all_modes_desc); |
| connector_subtest("vga-frame-dump", VGA) |
| test_display_all_modes(&data, port, DRM_FORMAT_XRGB8888, |
| CHAMELIUM_CHECK_ANALOG, 1); |
| } |
| |
| igt_describe("Tests that operate on all connectors"); |
| igt_subtest_group { |
| |
| igt_fixture { |
| igt_require(data.port_count); |
| } |
| |
| igt_describe(test_suspend_resume_hpd_common_desc); |
| igt_subtest("common-hpd-after-suspend") |
| test_suspend_resume_hpd_common(&data, |
| SUSPEND_STATE_MEM, |
| SUSPEND_TEST_NONE); |
| |
| igt_describe(test_suspend_resume_hpd_common_desc); |
| igt_subtest("common-hpd-after-hibernate") |
| test_suspend_resume_hpd_common(&data, |
| SUSPEND_STATE_DISK, |
| SUSPEND_TEST_DEVICES); |
| } |
| |
| igt_describe(test_hotplug_for_each_pipe_desc); |
| connector_subtest("vga-hpd-for-each-pipe", VGA) |
| test_hotplug_for_each_pipe(&data, port); |
| |
| igt_fixture { |
| igt_display_fini(&data.display); |
| close(data.drm_fd); |
| } |
| } |