/* * 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 #include #include #include IGT_TEST_DESCRIPTION("Test big framebuffers"); typedef struct { int drm_fd; uint32_t devid; 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; int big_fb_width, big_fb_height; uint64_t ram_size, aper_size, mappable_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 src"); init_buf(data, &dst, dst_fb, "big fb 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); /* * We expect the kernel to limit the max fb * size/stride to something that can still * rendered with the blitter/render engine. */ if (data->render_copy) { data->render_copy(data->batch, NULL, &src, sx, sy, w, h, &dst, dx, dy); } else { w = min(w, src_fb->width - sx); w = min(w, dst_fb->width - dx); h = min(h, src_fb->height - sy); h = min(h, dst_fb->height - dy); intel_blt_copy(data->batch, src.bo, sx, sy, src.stride, dst.bo, dx, dy, dst.stride, w, h, dst.bpp); } 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 bool size_ok(data_t *data, uint64_t size) { /* * The kernel limits scanout to the * mappable portion of ggtt on gmch platforms. */ if ((intel_gen(data->devid) < 5 || IS_VALLEYVIEW(data->devid) || IS_CHERRYVIEW(data->devid)) && size > data->mappable_size / 2) return false; /* * Limit the big fb size to at most half the RAM or half * the aperture size. Could go a bit higher I suppose since * we shouldn't need more than one big fb at a time. */ if (size > data->ram_size / 2 || size > data->aper_size / 2) return false; return true; } 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; /* max fence stride is only 8k bytes on gen3 */ if (intel_gen(data->devid) < 4 && format == DRM_FORMAT_XRGB8888) *width = min(*width, 8192 / 4); igt_calc_fb_size(data->drm_fd, *width, *height, format, modifier, &size, &stride); while (!size_ok(data, size)) { 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 void prep_fb(data_t *data) { if (data->big_fb.fb_id) return; igt_create_fb(data->drm_fd, data->big_fb_width, data->big_fb_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 set_c8_lut(data_t *data) { igt_pipe_t *pipe = &data->display.pipes[data->pipe]; struct drm_color_lut *lut; int i, lut_size = 256; lut = calloc(lut_size, sizeof(lut[0])); /* igt_fb uses RGB332 for C8 */ for (i = 0; i < lut_size; i++) { lut[i].red = ((i & 0xe0) >> 5) * 0xffff / 0x7; lut[i].green = ((i & 0x1c) >> 2) * 0xffff / 0x7; lut[i].blue = ((i & 0x03) >> 0) * 0xffff / 0x3; } igt_pipe_obj_replace_prop_blob(pipe, IGT_CRTC_GAMMA_LUT, lut, lut_size * sizeof(lut[0])); free(lut); } static void unset_lut(data_t *data) { igt_pipe_t *pipe = &data->display.pipes[data->pipe]; igt_pipe_obj_replace_prop_blob(pipe, IGT_CRTC_GAMMA_LUT, NULL, 0); } 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 = data->big_fb_width - small_fb->width; int h = data->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; if (data->rotation != IGT_ROTATION_0 && !igt_plane_has_prop(plane, IGT_PLANE_ROTATION)) return false; /* FIXME need atomic on i965/g4x */ if (data->rotation != IGT_ROTATION_0 && data->rotation != IGT_ROTATION_180 && !data->display.is_atomic) return false; if (igt_plane_has_prop(plane, IGT_PLANE_ROTATION)) 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; } 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) { if (igt_plane_has_prop(plane, IGT_PLANE_ROTATION)) igt_plane_set_rotation(plane, IGT_ROTATION_0); igt_plane_set_fb(plane, NULL); return false; } /* * To speed up skips we delay the big fb creation until * the above rotation related check has been performed. */ prep_fb(data); /* * 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_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; if (data->format == DRM_FORMAT_C8 && !igt_pipe_obj_has_prop(&data->display.pipes[data->pipe], IGT_CRTC_GAMMA_LUT)) return 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, &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); } if (data->format == DRM_FORMAT_C8) set_c8_lut(data); 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; } if (data->format == DRM_FORMAT_C8) unset_lut(data); igt_pipe_crc_free(data->pipe_crc); igt_output_set_pipe(data->output, PIPE_ANY); igt_remove_fb(data->drm_fd, &data->small_fb); return ret; } static void test_scanout(data_t *data) { max_fb_size(data, &data->big_fb_width, &data->big_fb_height, data->format, data->modifier); for_each_pipe_with_valid_output(&data->display, data->pipe, data->output) { if (test_pipe(data)) return; break; } igt_skip("unsupported configuration\n"); } 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 hit 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] = {}; uint32_t format; int ret; /* * gen3 max tiled stride is 8k bytes, but * max fb size of 4k pixels, hence we can't test * with 32bpp and must use 16bpp instead. */ if (intel_gen(data->devid) == 3) format = DRM_FORMAT_RGB565; else format = DRM_FORMAT_XRGB8888; igt_require(igt_display_has_format_mod(&data->display, format, data->modifier)); igt_calc_fb_size(data->drm_fd, data->max_fb_width, data->max_fb_height, format, data->modifier, &size, &strides[0]); bo = gem_create(data->drm_fd, size); igt_require(bo); if (intel_gen(data->devid) < 4) gem_set_tiling(data->drm_fd, bo, igt_fb_mod_to_tiling(data->modifier), strides[0]); ret = __kms_addfb(data->drm_fd, bo, data->max_fb_width, data->max_fb_height, format, 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[] = { { 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; igt_skip_on_simulation(); data.drm_fd = drm_open_driver_master(DRIVER_INTEL); igt_require(is_i915_device(data.drm_fd)); data.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); data.mappable_size = gem_mappable_aperture_size(); igt_info("RAM: %"PRIu64" MiB, GPU address space: %"PRId64" MiB, GGTT mappable size: %"PRId64" MiB\n", data.ram_size >> 20, data.aper_size >> 20, data.mappable_size >> 20); /* * Gen3 render engine is limited to 2kx2k, whereas * the display engine can do 4kx4k. Use the blitter * on gen3 to avoid exceeding the render engine limits. * On gen2 we could use either, but let's go for the * blitter there as well. */ if (intel_gen(data.devid) >= 4) data.render_copy = igt_get_render_copyfunc(data.devid); data.bufmgr = drm_intel_bufmgr_gem_init(data.drm_fd, 4096); data.batch = intel_batchbuffer_alloc(data.bufmgr, data.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(data.format == DRM_FORMAT_C8 || igt_fb_supported_format(data.format)); igt_require(igt_display_has_format_mod(&data.display, data.format, data.modifier)); 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); } }