diff options
-rw-r--r-- | lib/igt_psr.c | 29 | ||||
-rw-r--r-- | lib/igt_psr.h | 2 | ||||
-rw-r--r-- | tests/i915/kms_psr_stress_test.c | 377 | ||||
-rw-r--r-- | tests/meson.build | 1 |
4 files changed, 409 insertions, 0 deletions
diff --git a/lib/igt_psr.c b/lib/igt_psr.c index 2b73e809..a2d88031 100644 --- a/lib/igt_psr.c +++ b/lib/igt_psr.c @@ -330,3 +330,32 @@ void i915_psr2_sel_fetch_restore(int drm_fd) psr_set(drm_fd, debugfs_fd, PSR_MODE_2_SEL_FETCH); close(debugfs_fd); } + +/** + * psr_get_mode + * + * Return the current PSR mode. + */ +enum psr_mode psr_get_mode(int debugfs_fd) +{ + char buf[PSR_STATUS_MAX_LEN]; + int ret; + + + ret = igt_debugfs_simple_read(debugfs_fd, "i915_edp_psr_status", buf, + sizeof(buf)); + if (ret < 0) { + igt_info("Could not read i915_edp_psr_status: %s\n", + strerror(-ret)); + return PSR_DISABLED; + } + + if (strstr(buf, "PSR2 selective fetch: enabled")) + return PSR_MODE_2_SEL_FETCH; + else if (strstr(buf, "PSR2 enabled")) + return PSR_MODE_2; + else if (strstr(buf, "PSR1 enabled")) + return PSR_MODE_1; + + return PSR_DISABLED; +} diff --git a/lib/igt_psr.h b/lib/igt_psr.h index 76cd26c6..12ffc9d6 100644 --- a/lib/igt_psr.h +++ b/lib/igt_psr.h @@ -34,6 +34,7 @@ enum psr_mode { PSR_MODE_1, PSR_MODE_2, PSR_MODE_2_SEL_FETCH, + PSR_DISABLED, }; bool psr_disabled_check(int debugfs_fd); @@ -46,6 +47,7 @@ bool psr_disable(int device, int debugfs_fd); bool psr_sink_support(int device, int debugfs_fd, enum psr_mode mode); bool psr2_wait_su(int debugfs_fd, uint16_t *num_su_blocks); void psr_print_debugfs(int debugfs_fd); +enum psr_mode psr_get_mode(int debugfs_fd); bool i915_psr2_selective_fetch_check(int drm_fd); diff --git a/tests/i915/kms_psr_stress_test.c b/tests/i915/kms_psr_stress_test.c new file mode 100644 index 00000000..daac41d4 --- /dev/null +++ b/tests/i915/kms_psr_stress_test.c @@ -0,0 +1,377 @@ +#include "igt.h" +#include "igt_sysfs.h" +#include "igt_psr.h" +#include <errno.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/timerfd.h> + +#define INVALIDATES_PER_SEC 15 +#define FLIPS_PER_SEC 30 +#define SECS_TO_COMPLETE_TEST 10 + +#define OVERLAY_SIZE 500 +#define OVERLAY_POSITION 250 + +#define FRAMEBUFFERS_LEN 60 + +#define DRAW_METHOD IGT_DRAW_BLT + +typedef struct { + int drm_fd; + int debugfs_fd; + struct buf_ops *bops; + igt_display_t display; + drmModeModeInfo *mode; + igt_output_t *output; + + struct igt_fb primary_fb[FRAMEBUFFERS_LEN]; + struct igt_fb overlay_fb[FRAMEBUFFERS_LEN]; + + uint8_t flip_fb_in_use; + uint8_t invalidate_progress; + + int invalidate_timerfd; + int flip_timerfd; + int completed_timerfd; + + /* + * There is 2 subtest, one that flips primary and invalidates overlay + * and other that invalidates primary and flips overlay. + */ + bool flip_primary; + + enum psr_mode initial_state; +} data_t; + +struct color { + union { + uint32_t val; + struct { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; + }; +}; + +static void setup_output(data_t *data) +{ + igt_display_t *display = &data->display; + igt_output_t *output; + enum pipe pipe; + + igt_display_require(&data->display, data->drm_fd); + + for_each_pipe_with_valid_output(display, pipe, output) { + drmModeConnectorPtr c = output->config.connector; + + if (c->connector_type != DRM_MODE_CONNECTOR_eDP) + continue; + + igt_output_set_pipe(output, pipe); + data->output = output; + data->mode = igt_output_get_mode(output); + + return; + } + + igt_require(data->output); +} + +static void primary_draw(data_t *data, struct igt_fb *fb, uint8_t i) +{ + uint32_t x, y, w, h; + struct color cl; + + x = 0; + y = 500; + w = data->mode->hdisplay / FRAMEBUFFERS_LEN; + w *= i; + h = OVERLAY_SIZE; + + cl.a = 0xff; + + if (w == 0) { + cl.r = cl.g = cl.b = 128; + y = 0; + w = data->mode->hdisplay; + h = data->mode->vdisplay; + } else { + cl.r = cl.b = 0x00; + cl.g = 0xff; + } + + igt_draw_rect_fb(data->drm_fd, data->bops, 0, fb, DRAW_METHOD, x, y, + w, h, cl.val); +} + +static void overlay_draw(data_t *data, struct igt_fb *fb, uint8_t i) +{ + uint32_t x, y, w, h; + struct color cl; + + x = 0; + y = 0; + w = OVERLAY_SIZE; + h = OVERLAY_SIZE / FRAMEBUFFERS_LEN; + h *= i; + + cl.a = 0xff; + + if (h == 0) { + cl.r = cl.g = cl.b = 0xff; + h = OVERLAY_SIZE; + } else { + cl.r = 0xff; + cl.g = cl.b = 0x0; + } + + igt_draw_rect_fb(data->drm_fd, data->bops, 0, fb, DRAW_METHOD, x, y, + w, h, cl.val); +} + +static void prepare(data_t *data) +{ + struct itimerspec interval; + igt_plane_t *plane; + int r, i; + + if (data->flip_primary) { + for (i = 0; i < ARRAY_SIZE(data->primary_fb); i++) { + igt_create_color_fb(data->drm_fd, data->mode->hdisplay, + data->mode->vdisplay, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR, 0.0, 0.0, + 0.0, &data->primary_fb[i]); + primary_draw(data, &data->primary_fb[i], 0); + primary_draw(data, &data->primary_fb[i], i); + } + + igt_create_color_fb(data->drm_fd, OVERLAY_SIZE, OVERLAY_SIZE, + DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, + 0.0, 0.0, 0.0, &data->overlay_fb[0]); + overlay_draw(data, &data->overlay_fb[0], 0); + } else { + igt_create_color_fb(data->drm_fd, data->mode->hdisplay, + data->mode->vdisplay, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR, 0.0, 0.0, 0.0, + &data->primary_fb[0]); + primary_draw(data, &data->primary_fb[0], 0); + + for (i = 0; i < ARRAY_SIZE(data->overlay_fb); i++) { + igt_create_color_fb(data->drm_fd, OVERLAY_SIZE, + OVERLAY_SIZE, DRM_FORMAT_XRGB8888, + DRM_FORMAT_MOD_LINEAR, 0.0f, 0.0f, + 0.0f, &data->overlay_fb[i]); + overlay_draw(data, &data->overlay_fb[i], 0); + overlay_draw(data, &data->overlay_fb[i], i); + } + } + + plane = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_PRIMARY); + igt_plane_set_fb(plane, &data->primary_fb[0]); + + plane = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_OVERLAY); + igt_plane_set_fb(plane, &data->overlay_fb[0]); + igt_plane_set_position(plane, -(OVERLAY_SIZE / 2), 350); + + igt_display_commit2(&data->display, COMMIT_ATOMIC); + data->flip_fb_in_use = data->invalidate_progress = 0; + + /* Arm timers */ + interval.it_value.tv_nsec = NSEC_PER_SEC / INVALIDATES_PER_SEC; + interval.it_value.tv_sec = 0; + interval.it_interval.tv_nsec = interval.it_value.tv_nsec; + interval.it_interval.tv_sec = interval.it_value.tv_sec; + r = timerfd_settime(data->invalidate_timerfd, 0, &interval, NULL); + igt_require_f(r != -1, "Error setting invalidate_timerfd\n"); + + interval.it_value.tv_nsec = NSEC_PER_SEC / FLIPS_PER_SEC; + interval.it_value.tv_sec = 0; + interval.it_interval.tv_nsec = interval.it_value.tv_nsec; + interval.it_interval.tv_sec = interval.it_value.tv_sec; + r = timerfd_settime(data->flip_timerfd, 0, &interval, NULL); + igt_require_f(r != -1, "Error setting flip_timerfd\n"); + + interval.it_value.tv_nsec = 0; + interval.it_value.tv_sec = SECS_TO_COMPLETE_TEST; + interval.it_interval.tv_nsec = interval.it_value.tv_nsec; + interval.it_interval.tv_sec = interval.it_value.tv_sec; + r = timerfd_settime(data->completed_timerfd, 0, &interval, NULL); + igt_require_f(r != -1, "Error setting completed_timerfd\n"); + + data->initial_state = psr_get_mode(data->debugfs_fd); + igt_require(data->initial_state != PSR_DISABLED); + igt_require(psr_wait_entry(data->debugfs_fd, data->initial_state)); +} + +static void cleanup(data_t *data) +{ + struct itimerspec interval; + igt_plane_t *plane; + uint8_t i; + + plane = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_PRIMARY); + igt_plane_set_fb(plane, NULL); + plane = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_OVERLAY); + igt_plane_set_fb(plane, NULL); + igt_display_commit2(&data->display, COMMIT_ATOMIC); + + for (i = 0; i < ARRAY_SIZE(data->primary_fb); i++) + igt_remove_fb(data->drm_fd, &data->primary_fb[i]); + + for (i = 0; i < ARRAY_SIZE(data->overlay_fb); i++) + igt_remove_fb(data->drm_fd, &data->overlay_fb[i]); + + /* Disarm timers */ + interval.it_value.tv_nsec = 0; + interval.it_value.tv_sec = 0; + interval.it_interval.tv_nsec = interval.it_value.tv_nsec; + interval.it_interval.tv_sec = interval.it_value.tv_sec; + timerfd_settime(data->invalidate_timerfd, 0, &interval, NULL); + timerfd_settime(data->flip_timerfd, 0, &interval, NULL); + timerfd_settime(data->completed_timerfd, 0, &interval, NULL); +} + +static void invalidate(data_t *data) +{ + if (data->flip_primary) + overlay_draw(data, &data->overlay_fb[0], data->invalidate_progress); + else + primary_draw(data, &data->primary_fb[0], data->invalidate_progress); + + data->invalidate_progress++; + if (data->invalidate_progress == (FRAMEBUFFERS_LEN + 1)) + data->invalidate_progress = 0; +} + +static void flip(data_t *data) +{ + uint8_t next = data->flip_fb_in_use + 1; + struct igt_fb *fb; + igt_plane_t *plane; + + if (next == FRAMEBUFFERS_LEN) + next = 0; + + if (data->flip_primary) { + plane = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_PRIMARY); + fb = &data->primary_fb[next]; + } else { + plane = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_OVERLAY); + fb = &data->overlay_fb[next]; + } + + igt_plane_set_fb(plane, fb); + igt_display_commit2(&data->display, COMMIT_ATOMIC); + data->flip_fb_in_use = next; +} + +static void run(data_t *data) +{ + struct pollfd pfd[3]; + bool loop = true; + + pfd[0].fd = data->invalidate_timerfd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + + pfd[1].fd = data->flip_timerfd; + pfd[1].events = POLLIN; + pfd[1].revents = 0; + + pfd[2].fd = data->completed_timerfd; + pfd[2].events = POLLIN; + pfd[2].revents = 0; + + while (loop) { + int i, r = poll(pfd, ARRAY_SIZE(pfd), -1); + + if (r < 0) + break; + if (r == 0) + continue; + + for (i = 0; i < ARRAY_SIZE(pfd); i++) { + uint64_t exp; + + if (pfd[i].revents == 0) + continue; + + pfd[i].revents = 0; + r = read(pfd[i].fd, &exp, sizeof(exp)); + if (r != sizeof(uint64_t) || exp == 0) + continue; + + if (pfd[i].fd == data->invalidate_timerfd) + invalidate(data); + else if (pfd[i].fd == data->flip_timerfd) + flip(data); + else if (pfd[i].fd == data->completed_timerfd) + loop = false; + } + } + + /* Check if after all this stress the PSR is still in the same state */ + igt_assert(psr_get_mode(data->debugfs_fd) == data->initial_state); +} + +igt_main +{ + data_t data = {}; + + igt_fixture { + data.drm_fd = drm_open_driver_master(DRIVER_INTEL); + data.debugfs_fd = igt_debugfs_dir(data.drm_fd); + data.bops = buf_ops_create(data.drm_fd); + kmstest_set_vt_graphics_mode(); + + igt_require_f(psr_sink_support(data.drm_fd, data.debugfs_fd, + PSR_MODE_1), + "Sink does not support PSR\n"); + + setup_output(&data); + + data.invalidate_timerfd = timerfd_create(CLOCK_MONOTONIC, 0); + igt_require(data.invalidate_timerfd != -1); + data.flip_timerfd = timerfd_create(CLOCK_MONOTONIC, 0); + igt_require(data.flip_timerfd != -1); + data.completed_timerfd = timerfd_create(CLOCK_MONOTONIC, 0); + igt_require(data.completed_timerfd != -1); + } + + /* + * TODO: add cursor plane to the test to mimic even more real user + * usage cases + */ + igt_describe("Mix page flips in primary plane and frontbuffer writes " + "to overlay plane and check for warnings, underruns or " + "PSR state changes"); + igt_subtest("flip-primary-invalidate-overlay") { + data.flip_primary = true; + prepare(&data); + run(&data); + cleanup(&data); + } + + igt_describe("Mix frontbuffer writes to the primary plane and page " + "flips in the overlay plane and check for warnings, " + "underruns or PSR state changes"); + igt_subtest("invalidate-primary-flip-overlay") { + data.flip_primary = false; + prepare(&data); + run(&data); + cleanup(&data); + } + + igt_fixture { + buf_ops_destroy(data.bops); + igt_display_fini(&data.display); + close(data.debugfs_fd); + close(data.drm_fd); + } +}
\ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index 3af4576f..a152d2a0 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -243,6 +243,7 @@ i915_progs = [ 'kms_psr', 'kms_psr2_su', 'kms_psr2_sf', + 'kms_psr_stress_test', 'kms_pwrite_crc', 'sysfs_clients', 'sysfs_defaults', |