[igt-dev] [PATCH i-g-t v2 5/5] tests/kms_big_fb: Make sure huge fbs work correctly

Ville Syrjala
2019-04-26T16:50:26Z

From: Ville Syrjälä <ville.syrjala@linux.intel.com>

Add various tests to excercise huge framebuffers. First some basic
sanity checks that the kernel accepts/rejects good/bad addfb2 ioctls,
and finally actual scanout tests to make sure we scan out the correct
thing when panning around inside large framebuffers.

The implementation is i915 specific for now since I chose to use
rendercopy when generating the framebuffer contents. Using the normal
cairo stuff was just too slow when dealing with 1GiB framebuffers.
It shouldn't be too hard to plug in some other mechanisms if someone
else wants to reuse this test.

v2: Add igt_require(format+mod) for the addfb/overflow tests
Unset plane fb after TEST_ONLY fail
Limit max fb to at most 1/2 RAM or GPU address space
Tweak coords to avoid fails with 64bpp linear with 4k screen
Make coords even for 90/270 rotated 16bpp
Store bpp as uint8_t instead of wasting an entire char*

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
tests/Makefile.sources | 1 +
tests/kms_big_fb.c | 598 +++++++++++++++++++++++++++++++++++++++++
tests/meson.build | 1 +
3 files changed, 600 insertions(+)
create mode 100644 tests/kms_big_fb.c

diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index 7f921f6c5988..2d5c929e32fc 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -27,6 +27,7 @@ TESTS_progs = \
kms_atomic_interruptible \
kms_atomic_transition \
kms_available_modes_crc \
+ kms_big_fb \
kms_busy \
kms_ccs \
kms_color \
diff --git a/tests/kms_big_fb.c b/tests/kms_big_fb.c
new file mode 100644
index 000000000000..dc5467ad2414
--- /dev/null
+++ b/tests/kms_big_fb.c
@@ -0,0 +1,598 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "igt.h"
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+IGT_TEST_DESCRIPTION("Test big framebuffers");
+
+typedef struct {
+ int drm_fd;
+ igt_display_t display;
+ enum pipe pipe;
+ igt_output_t *output;
+ igt_plane_t *plane;
+ igt_pipe_crc_t *pipe_crc;
+ struct igt_fb small_fb, big_fb;
+ uint32_t format;
+ uint64_t modifier;
+ int width, height;
+ igt_rotation_t rotation;
+ int max_fb_width, max_fb_height;
+ uint64_t ram_size, aper_size;
+ igt_render_copyfunc_t render_copy;
+ drm_intel_bufmgr *bufmgr;
+ struct intel_batchbuffer *batch;
+} data_t;
+
+static void init_buf(data_t *data,
+ struct igt_buf *buf,
+ const struct igt_fb *fb,
+ const char *name)
+{
+ igt_assert_eq(fb->offsets[0], 0);
+
+ buf->bo = gem_handle_to_libdrm_bo(data->bufmgr, data->drm_fd,
+ name, fb->gem_handle);
+ buf->tiling = igt_fb_mod_to_tiling(fb->modifier);
+ buf->stride = fb->strides[0];
+ buf->bpp = fb->plane_bpp[0];
+ buf->size = fb->size;
+}
+
+static void fini_buf(struct igt_buf *buf)
+{
+ drm_intel_bo_unreference(buf->bo);
+}
+
+static void copy_pattern(data_t *data,
+ struct igt_fb *dst_fb, int dx, int dy,
+ struct igt_fb *src_fb, int sx, int sy,
+ int w, int h)
+{
+ struct igt_buf src = {}, dst = {};
+
+ init_buf(data, &src, src_fb, "big fb rendercopy src");
+ init_buf(data, &dst, dst_fb, "big fb rendercopy dst");
+
+ gem_set_domain(data->drm_fd, dst_fb->gem_handle,
+ I915_GEM_DOMAIN_GTT, I915_GEM_DOMAIN_GTT);
+ gem_set_domain(data->drm_fd, src_fb->gem_handle,
+ I915_GEM_DOMAIN_GTT, 0);
+
+ data->render_copy(data->batch, NULL, &src, sx, sy, w, h, &dst, dx, dy);
+
+ fini_buf(&dst);
+ fini_buf(&src);
+}
+
+static void generate_pattern(data_t *data,
+ struct igt_fb *fb,
+ int w, int h)
+{
+ struct igt_fb pat_fb;
+
+ igt_create_pattern_fb(data->drm_fd, w, h,
+ data->format, data->modifier,
+ &pat_fb);
+
+ for (int y = 0; y < fb->height; y += h) {
+ for (int x = 0; x < fb->width; x += w) {
+ copy_pattern(data, fb, x, y,
+ &pat_fb, 0, 0,
+ pat_fb.width, pat_fb.height);
+ w++;
+ h++;
+ }
+ }
+
+ igt_remove_fb(data->drm_fd, &pat_fb);
+}
+
+static void max_fb_size(data_t *data, int *width, int *height,
+ uint32_t format, uint64_t modifier)
+{
+ unsigned int stride;
+ uint64_t size;
+ int i = 0;
+
+ *width = data->max_fb_width;
+ *height = data->max_fb_height;
+
+ igt_calc_fb_size(data->drm_fd, *width, *height,
+ format, modifier, &size, &stride);
+
+ /*
+ * Limit the big fb size to at most half the RAM bor half
+ * the aperture size. Could go a bit higher I suppose since
+ * we shouldn't need more than one big fb at a time.
+ */
+ while (size > data->ram_size / 2 || size > data->aper_size / 2) {
+ if (i++ & 1)
+ *width >>= 1;
+ else
+ *height >>= 1;
+
+ igt_calc_fb_size(data->drm_fd, *width, *height,
+ format, modifier, &size, &stride);
+ }
+
+ igt_info("Max usable framebuffer size for format "IGT_FORMAT_FMT" / modifier 0x%"PRIx64": %dx%d\n",
+ IGT_FORMAT_ARGS(format), modifier,
+ *width, *height);
+}
+
+static bool test_plane(data_t *data)
+{
+ igt_plane_t *plane = data->plane;
+ struct igt_fb *small_fb = &data->small_fb;
+ struct igt_fb *big_fb = &data->big_fb;
+ int w = big_fb->width - small_fb->width;
+ int h = big_fb->height - small_fb->height;
+ struct {
+ int x, y;
+ } coords[] = {
+ /* bunch of coordinates pulled out of thin air */
+ { 0, 0, },
+ { w * 4 / 7, h / 5, },
+ { w * 3 / 7, h / 3, },
+ { w / 2, h / 2, },
+ { w / 3, h * 3 / 4, },
+ { w, h, },
+ };
+
+ if (!igt_plane_has_format_mod(plane, data->format, data->modifier))
+ return false;
+
+ igt_plane_set_rotation(plane, data->rotation);
+ igt_plane_set_position(plane, 0, 0);
+
+ for (int i = 0; i < ARRAY_SIZE(coords); i++) {
+ igt_crc_t small_crc, big_crc;
+ int x = coords[i].x;
+ int y = coords[i].y;
+
+ /* Hardware limitation */
+ if (data->format == DRM_FORMAT_RGB565 &&
+ (data->rotation == IGT_ROTATION_90 ||
+ data->rotation == IGT_ROTATION_270)) {
+ x &= ~1;
+ y &= ~1;
+ }
+
+ /*
+ * Make a 1:1 copy of the desired part of the big fb
+ * rather than try to render the same pattern (translated
+ * accordinly) again via cairo. Something in cairo's
+ * rendering pipeline introduces slight differences into
+ * the result if we try that, and so the crc will not match.
+ */
+ copy_pattern(data, small_fb, 0, 0, big_fb, x, y,
+ small_fb->width, small_fb->height);
+
+ igt_plane_set_fb(plane, small_fb);
+ igt_plane_set_size(plane, data->width, data->height);
+
+ /*
+ * Try to check that the rotation+format+modifier
+ * combo is supported.
+ */
+ if (i == 0 && data->display.is_atomic &&
+ igt_display_try_commit_atomic(&data->display,
+ DRM_MODE_ATOMIC_TEST_ONLY,
+ NULL) != 0) {
+ igt_plane_set_rotation(plane, IGT_ROTATION_0);
+ igt_plane_set_fb(plane, NULL);
+ igt_skip("unsupported plane configuration\n");
+ }
+
+ igt_display_commit2(&data->display, data->display.is_atomic ?
+ COMMIT_ATOMIC : COMMIT_UNIVERSAL);
+
+
+ igt_pipe_crc_collect_crc(data->pipe_crc, &small_crc);
+
+ igt_plane_set_fb(plane, big_fb);
+ igt_fb_set_position(big_fb, plane, x, y);
+ igt_fb_set_size(big_fb, plane, small_fb->width, small_fb->height);
+ igt_plane_set_size(plane, data->width, data->height);
+ igt_display_commit2(&data->display, data->display.is_atomic ?
+ COMMIT_ATOMIC : COMMIT_UNIVERSAL);
+
+ igt_pipe_crc_collect_crc(data->pipe_crc, &big_crc);
+
+ igt_plane_set_fb(plane, NULL);
+
+ igt_assert_crc_equal(&big_crc, &small_crc);
+
+ }
+
+ return true;
+}
+
+static bool test_pipe(data_t *data)
+{
+ drmModeModeInfo *mode;
+ igt_plane_t *primary;
+ int width, height;
+ bool ret = false;
+
+ mode = igt_output_get_mode(data->output);
+
+ data->width = mode->hdisplay;
+ data->height = mode->vdisplay;
+
+ width = mode->hdisplay;
+ height = mode->vdisplay;
+ if (data->rotation == IGT_ROTATION_90 ||
+ data->rotation == IGT_ROTATION_270)
+ igt_swap(width, height);
+
+ igt_create_color_fb(data->drm_fd, width, height,
+ data->format, data->modifier,
+ 0, 1, 0, &data->small_fb);
+
+ igt_output_set_pipe(data->output, data->pipe);
+
+ primary = igt_output_get_plane_type(data->output, DRM_PLANE_TYPE_PRIMARY);
+ igt_plane_set_fb(primary, NULL);
+
+ if (!data->display.is_atomic) {
+ struct igt_fb fb;
+
+ igt_create_fb(data->drm_fd, mode->hdisplay, mode->vdisplay,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
+ &fb);
+
+ /* legacy setcrtc needs an fb */
+ igt_plane_set_fb(primary, &data->small_fb);
+ igt_display_commit2(&data->display, COMMIT_LEGACY);
+
+ igt_plane_set_fb(primary, NULL);
+ igt_display_commit2(&data->display, COMMIT_UNIVERSAL);
+
+ igt_remove_fb(data->drm_fd, &fb);
+ }
+
+ igt_display_commit2(&data->display, data->display.is_atomic ?
+ COMMIT_ATOMIC : COMMIT_UNIVERSAL);
+
+ data->pipe_crc = igt_pipe_crc_new(data->drm_fd, data->pipe,
+ INTEL_PIPE_CRC_SOURCE_AUTO);
+
+ for_each_plane_on_pipe(&data->display, data->pipe, data->plane) {
+ ret = test_plane(data);
+ if (ret)
+ break;
+ }
+
+ igt_pipe_crc_free(data->pipe_crc);
+
+ igt_output_set_pipe(data->output, PIPE_ANY);
+ igt_display_commit2(&data->display, data->display.is_atomic ?
+ COMMIT_ATOMIC : COMMIT_UNIVERSAL);
+
+ igt_remove_fb(data->drm_fd, &data->small_fb);
+
+ return ret;
+}
+
+static void test_scanout(data_t *data)
+{
+ for_each_pipe_with_valid_output(&data->display, data->pipe, data->output) {
+ if (test_pipe(data))
+ return;
+ }
+
+ igt_skip("no valid crtc/connector combinations found\n");
+}
+
+static void prep_fb(data_t *data)
+{
+ int width, height;
+
+ if (data->big_fb.fb_id)
+ return;
+
+ max_fb_size(data, &width, &height,
+ data->format, data->modifier);
+
+ igt_create_fb(data->drm_fd, width, height,
+ data->format, data->modifier,
+ &data->big_fb);
+
+ generate_pattern(data, &data->big_fb, 640, 480);
+}
+
+static void cleanup_fb(data_t *data)
+{
+ igt_remove_fb(data->drm_fd, &data->big_fb);
+ data->big_fb.fb_id = 0;
+}
+
+static void
+test_size_overflow(data_t *data)
+{
+ uint32_t fb_id;
+ uint32_t bo;
+ uint32_t offsets[4] = {};
+ uint32_t strides[4] = { 256*1024, };
+ int ret;
+
+ igt_require(igt_display_has_format_mod(&data->display,
+ DRM_FORMAT_XRGB8888,
+ data->modifier));
+
+ /*
+ * Try to hit a specific integer overflow in i915 fb size
+ * calculations. 256k * 16k == 1<<32 which is checked
+ * against the bo size. The check should fail on account
+ * of the bo being smaller, but due to the overflow the
+ * computed fb size is 0 and thus the check never trips.
+ */
+ igt_require(data->max_fb_width >= 16383 &&
+ data->max_fb_height >= 16383);
+
+ bo = gem_create(data->drm_fd, (1ULL << 32) - 4096);
+ igt_require(bo);
+
+ ret = __kms_addfb(data->drm_fd, bo,
+ 16383, 16383,
+ DRM_FORMAT_XRGB8888,
+ data->modifier,
+ strides, offsets, 1,
+ DRM_MODE_FB_MODIFIERS, &fb_id);
+
+ igt_assert_neq(ret, 0);
+
+ gem_close(data->drm_fd, bo);
+}
+
+static void
+test_size_offset_overflow(data_t *data)
+{
+ uint32_t fb_id;
+ uint32_t bo;
+ uint32_t offsets[4] = {};
+ uint32_t strides[4] = { 8192, };
+ int ret;
+
+ igt_require(igt_display_has_format_mod(&data->display,
+ DRM_FORMAT_NV12,
+ data->modifier));
+
+ /*
+ * Try to a specific integer overflow in i915 fb size
+ * calculations. This time it's offsets[1] + the tile
+ * aligned chroma plane size that overflows and
+ * incorrectly passes the bo size check.
+ */
+ igt_require(igt_display_has_format_mod(&data->display,
+ DRM_FORMAT_NV12,
+ data->modifier));
+
+ bo = gem_create(data->drm_fd, (1ULL << 32) - 4096);
+ igt_require(bo);
+
+ offsets[0] = 0;
+ offsets[1] = (1ULL << 32) - 8192 * 4096;
+
+ ret = __kms_addfb(data->drm_fd, bo,
+ 8192, 8188,
+ DRM_FORMAT_NV12,
+ data->modifier,
+ strides, offsets, 1,
+ DRM_MODE_FB_MODIFIERS, &fb_id);
+ igt_assert_neq(ret, 0);
+
+ gem_close(data->drm_fd, bo);
+}
+
+static int rmfb(int fd, uint32_t id)
+{
+ int err;
+
+ err = 0;
+ if (igt_ioctl(fd, DRM_IOCTL_MODE_RMFB, &id))
+ err = -errno;
+
+ errno = 0;
+ return err;
+}
+
+static void
+test_addfb(data_t *data)
+{
+ uint64_t size;
+ uint32_t fb_id;
+ uint32_t bo;
+ uint32_t offsets[4] = {};
+ uint32_t strides[4] = {};
+ int ret;
+
+ igt_require(igt_display_has_format_mod(&data->display,
+ DRM_FORMAT_XRGB8888,
+ data->modifier));
+
+ igt_calc_fb_size(data->drm_fd,
+ data->max_fb_width,
+ data->max_fb_height,
+ DRM_FORMAT_XRGB8888,
+ data->modifier,
+ &size, &strides[0]);
+
+ bo = gem_create(data->drm_fd, size);
+ igt_require(bo);
+
+ ret = __kms_addfb(data->drm_fd, bo,
+ data->max_fb_width,
+ data->max_fb_height,
+ DRM_FORMAT_XRGB8888,
+ data->modifier,
+ strides, offsets, 1,
+ DRM_MODE_FB_MODIFIERS, &fb_id);
+ igt_assert_eq(ret, 0);
+
+ rmfb(data->drm_fd, fb_id);
+ gem_close(data->drm_fd, bo);
+}
+
+static data_t data;
+
+static const struct {
+ uint64_t modifier;
+ const char *name;
+} modifiers[] = {
+ { DRM_FORMAT_MOD_LINEAR, "linear", },
+ { I915_FORMAT_MOD_X_TILED, "x-tiled", },
+ { I915_FORMAT_MOD_Y_TILED, "y-tiled", },
+ { I915_FORMAT_MOD_Yf_TILED, "yf-tiled", },
+};
+
+static const struct {
+ uint32_t format;
+ uint8_t bpp;
+} formats[] = {
+ /* FIXME igt_fb doesn't support C8 currently */
+ { DRM_FORMAT_C8, 8, },
+ { DRM_FORMAT_RGB565, 16, },
+ { DRM_FORMAT_XRGB8888, 32, },
+ { DRM_FORMAT_XBGR16161616F, 64, },
+};
+
+static const struct {
+ igt_rotation_t rotation;
+ uint16_t angle;
+} rotations[] = {
+ { IGT_ROTATION_0, 0, },
+ { IGT_ROTATION_90, 90, },
+ { IGT_ROTATION_180, 180, },
+ { IGT_ROTATION_270, 270, },
+};
+
+igt_main
+{
+ igt_fixture {
+ drmModeResPtr res;
+ uint32_t devid;
+
+ igt_skip_on_simulation();
+
+ data.drm_fd = drm_open_driver_master(DRIVER_INTEL);
+
+ igt_require(is_i915_device(data.drm_fd));
+
+ devid = intel_get_drm_devid(data.drm_fd);
+
+ kmstest_set_vt_graphics_mode();
+
+ igt_require_pipe_crc(data.drm_fd);
+ igt_display_require(&data.display, data.drm_fd);
+
+ res = drmModeGetResources(data.drm_fd);
+ igt_assert(res);
+
+ data.max_fb_width = res->max_width;
+ data.max_fb_height = res->max_height;
+
+ drmModeFreeResources(res);
+
+ igt_info("Max driver framebuffer size %dx%d\n",
+ data.max_fb_width, data.max_fb_height);
+
+ data.ram_size = intel_get_total_ram_mb() << 20;
+ data.aper_size = gem_aperture_size(data.drm_fd);
+
+ igt_info("RAM: %"PRIu64" MiB, GPU address space: %"PRId64" MiB\n",
+ data.ram_size >> 20, data.aper_size >> 20);
+
+ data.render_copy = igt_get_render_copyfunc(devid);
+ igt_require(data.render_copy);
+
+ data.bufmgr = drm_intel_bufmgr_gem_init(data.drm_fd, 4096);
+ data.batch = intel_batchbuffer_alloc(data.bufmgr, devid);
+ }
+
+ /*
+ * Skip linear as it doesn't hit the overflow we want
+ * on account of the tile height being effectively one,
+ * and thus the kenrnel rounding up to the next tile
+ * height won't do anything.
+ */
+ for (int i = 1; i < ARRAY_SIZE(modifiers); i++) {
+ igt_subtest_f("%s-addfb-size-overflow",
+ modifiers[i].name) {
+ data.modifier = modifiers[i].modifier;
+ test_size_overflow(&data);
+ }
+ }
+
+ for (int i = 1; i < ARRAY_SIZE(modifiers); i++) {
+ igt_subtest_f("%s-addfb-size-offset-overflow",
+ modifiers[i].name) {
+ data.modifier = modifiers[i].modifier;
+ test_size_offset_overflow(&data);
+ }
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(modifiers); i++) {
+ igt_subtest_f("%s-addfb", modifiers[i].name) {
+ data.modifier = modifiers[i].modifier;
+
+ test_addfb(&data);
+ }
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(modifiers); i++) {
+ data.modifier = modifiers[i].modifier;
+
+ for (int j = 0; j < ARRAY_SIZE(formats); j++) {
+ data.format = formats[j].format;
+
+ for (int k = 0; k < ARRAY_SIZE(rotations); k++) {
+ data.rotation = rotations[k].rotation;
+
+ igt_subtest_f("%s-%dbpp-rotate-%d", modifiers[i].name,
+ formats[j].bpp, rotations[k].angle) {
+ igt_require(igt_fb_supported_format(data.format));
+ igt_require(igt_display_has_format_mod(&data.display, data.format, data.modifier));
+ prep_fb(&data);
+ test_scanout(&data);
+ }
+ }
+
+ igt_fixture
+ cleanup_fb(&data);
+ }
+ }
+
+ igt_fixture {
+ igt_display_fini(&data.display);
+
+ intel_batchbuffer_free(data.batch);
+ drm_intel_bufmgr_destroy(data.bufmgr);
+ }
+}
diff --git a/tests/meson.build b/tests/meson.build
index 711979b4a1c2..ad8444875302 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -14,6 +14,7 @@ test_progs = [
'kms_atomic_interruptible',
'kms_atomic_transition',
'kms_available_modes_crc',
+ 'kms_big_fb',
'kms_busy',
'kms_ccs',
'kms_color',
--
2.21.0