Re: [igt-dev] [PATCH i-g-t 9/9] chamelium: Add a CRC-based display test for randomized planes

Paul Kocialkowski
2018-12-13T14:39:38Z

Hi,

Thanks for the review!

On Thu, 2018-12-06 at 17:52 -0500, Lyude Paul wrote:
> I agree with Maxime, this definitely should be split up into some more
> functions. Some other comments below below
>
> On Thu, 2018-12-06 at 15:11 +0100, Paul Kocialkowski wrote:
> > This introduces a new test for the Chamelium, that sets up planes
> > with randomized properties such as the format, dimensions, position,
> > in-framebuffer offsets and stride. The Chamelium capture is checked
> > against the reference generated by cairo with a CRC.
> >
> > This test also includes testing for some VC4-specific features, such as
> > T-tiled mode (in XR24 format), bandwidth limitation and underrun
> > (that require kernel-side patches that are currently under review).
> >
> > Since this test does not share much with previous CRC-based display
> > tests (especially regarding KMS configuration), most of the code is
> > not shared with other tests.
> >
> > This test can be derived with reproducible properties for regression
> > testing in the future. For now, it serves as a kind of fuzzing test.
> >
> > Signed-off-by: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
> > ---
> > tests/kms_chamelium.c | 331 ++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 331 insertions(+)
> >
> > diff --git a/tests/kms_chamelium.c b/tests/kms_chamelium.c
> > index 7d95a8bc..a4977309 100644
> > --- a/tests/kms_chamelium.c
> > +++ b/tests/kms_chamelium.c
> > @@ -26,6 +26,8 @@
> >
> > #include "config.h"
> > #include "igt.h"
> > +#include "igt_sysfs.h"
> > +#include "igt_vc4.h"
> >
> > #include <fcntl.h>
> > #include <string.h>
> > @@ -703,6 +705,330 @@ test_display_frame_dump(data_t *data, struct
> > chamelium_port *port)
> > drmModeFreeConnector(connector);
> > }
> >
> > +static uint32_t random_plane_formats[] = {
> > + DRM_FORMAT_ABGR8888,
> > + DRM_FORMAT_ARGB1555,
> > + DRM_FORMAT_ARGB8888,
> > + DRM_FORMAT_RGB565,
> > + DRM_FORMAT_BGR565,
> > + DRM_FORMAT_RGB888,
> > + DRM_FORMAT_BGR888,
> > + DRM_FORMAT_XBGR8888,
> > + DRM_FORMAT_XRGB1555,
> > + DRM_FORMAT_XRGB8888,
> > +};
> > +
> > +static void test_display_planes_random(data_t *data,
> > + struct chamelium_port *port,
> > + enum chamelium_check check)
> > +{
> > + igt_output_t *output;
> > + igt_pipe_t *pipe;
> > + 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;
> > + unsigned int overlay_planes_index;
> > + bool bandwidth_exceeded = false;
> > + bool underrun_detected = false;
> > + cairo_surface_t *result_surface;
> > + cairo_surface_t *primary_surface;
> > + cairo_surface_t *overlay_surface;
> > + cairo_t *cr;
> > + int captured_frame_count;
> > + unsigned int i;
> > + char *underrun;
> > + int fb_id;
> > + int debugfs;
> > + int ret;
> > +
> > + igt_assert(check == CHAMELIUM_CHECK_CRC);
> > +
> > + srand(time(NULL));
> > +
> > + reset_state(data, port);
> > +
> > + /* Find the connector and pipe. */
> > + output = prepare_output(data, port);
> > + igt_assert(output);
> Do we actually need this? We're not running igt_assert on the prepare_output()
> function for any of the other tests.

This can indeed be dropped. Looking at prepare_output, a few things
would fail before the returns NULL anyway.

> > +
> > + if (is_vc4_device(data->drm_fd)) {
> > + debugfs = igt_debugfs_dir(data->drm_fd);
> > + igt_assert(debugfs >= 0);
> > + }
> > +
> > + pipe = &data->display.pipes[output->pending_pipe];
> > +
> > + mode = igt_output_get_mode(output);
> > + igt_assert(mode);
> This assert doesn't look like it's needed either

That function can never return NULL anyway, so you're totally right :)

> > +
> > + /* 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);
> > +
> > + igt_plane_set_fb(primary_plane, &primary_fb);
> > +
> > + primary_surface = igt_get_cairo_surface(data->drm_fd, &primary_fb);
> > +
> > + /* Get a framebuffer for the cairo composition result. */
> > + fb_id = igt_create_fb(data->drm_fd, mode->hdisplay,
> > + mode->vdisplay, DRM_FORMAT_XRGB8888,
> > + LOCAL_DRM_FORMAT_MOD_NONE, &result_fb);
> > + igt_assert(fb_id > 0);
> > +
> > + /* Initialize cairo with the result surface as target. */
> > + result_surface = igt_get_cairo_surface(data->drm_fd, &result_fb);
> > +
> > + cr = cairo_create(result_surface);
> > +
> > + /* Blend the primary surface. */
> > + cairo_set_source_surface(cr, primary_surface, 0, 0);
> > + cairo_surface_destroy(primary_surface);
> > + cairo_paint(cr);
> > +
> > + cairo_destroy(cr);
> > +
> > + /* Count available overlay planes. */
> > + for (i = 0; i < pipe->n_planes; i++) {
> > + igt_plane_t *plane = &pipe->planes[i];
> > +
> > + if (plane->type == DRM_PLANE_TYPE_OVERLAY)
> > + overlay_planes_max++;
> > + }
> Why not add a helper for this in a seperate patch? This seems quite
> useful

Yes, defintiely!

> > +
> > + /* Limit the number of planes to a reasonable scene. */
> > + if (overlay_planes_max > 4)
> > + overlay_planes_max = 4;
> > +
> > + 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);
> igt_assert(overlay_fbs)
> > +
> > + for (i = 0, overlay_planes_index = 0;
> > + i < pipe->n_planes && overlay_planes_index <
> > overlay_planes_count;
> > + i++) {
> > + struct igt_fb *overlay_fb =
> > &overlay_fbs[overlay_planes_index];
> > + igt_plane_t *plane = &pipe->planes[i];
> > + struct igt_fb pattern_fb;
> > + uint32_t overlay_fb_w, overlay_fb_h;
> > + int32_t overlay_crtc_x, overlay_crtc_y;
> > + uint32_t overlay_crtc_w, overlay_crtc_h;
> > + uint32_t overlay_src_x, overlay_src_y;
> > + uint32_t overlay_src_w, overlay_src_h;
> > + int overlay_surface_x, overlay_surface_y;
> > + unsigned int index;
> > + unsigned int stride_min;
> > + unsigned int stride;
> > + bool vc4_t_tiled;
> > + uint32_t format;
> > +
> > + if (plane->type != DRM_PLANE_TYPE_OVERLAY)
> > + continue;
> We could also add a helper for looping through planes of specific types,
> since we seem to do that multiple times here and will probably do it in
> future tests at some poit

That would definitely make sense, I'll craft something in that
direction in the next revision.

> > +
> > + /* Randomize width and height in the mode dimensions range. */
> > + overlay_fb_w = (rand() % mode->hdisplay) + 1;
> > + overlay_fb_h = (rand() % mode->vdisplay) + 1;
> > +
> > + /*
> > + * Randomize source offset, but keep at least half of the
> > + * original size.
> > + */
> > + overlay_src_x = rand() % (overlay_fb_w / 2);
> > + overlay_src_y = rand() % (overlay_fb_h / 2);
> > +
> > + /*
> > + * The on-crtc size does not include the source offset, so it
> > + * needs to be subtracted to avoid scaling.
> > + */
> > + overlay_crtc_w = overlay_fb_w - overlay_src_x;
> > + overlay_crtc_h = overlay_fb_h - overlay_src_y;
> > +
> > + /* Same goes for the framebuffer source size. */
> > + overlay_src_w = overlay_crtc_w;
> > + overlay_src_h = overlay_crtc_h;
> > +
> > + /*
> > + * Randomize the on-crtc position and allow the plane to go
> > + * off-display by up to half of its width and height.
> > + */
> > + overlay_crtc_x = (rand() % mode->hdisplay) - overlay_crtc_w /
> > 2;
> > + overlay_crtc_y = (rand() % mode->vdisplay) - overlay_crtc_h /
> > 2;
> > +
> > + igt_debug("Plane %d: on-crtc size %dx%d\n",
> > + overlay_planes_index, overlay_crtc_w,
> > overlay_crtc_h);
> > + igt_debug("Plane %d: on-crtc position %dx%d\n",
> > + overlay_planes_index, overlay_crtc_x,
> > overlay_crtc_y);
> > + igt_debug("Plane %d: in-framebuffer position %dx%d\n",
> > + overlay_planes_index, overlay_src_x, overlay_src_y);
> > +
> > + /* 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 the use of tiled mode with a 1/4 probability. */
> > + index = rand() % 4;
> > +
> > + if (is_vc4_device(data->drm_fd) && index == 0) {
> > + format = DRM_FORMAT_XRGB8888;
> > + vc4_t_tiled = true;
> > +
> > + igt_debug("Plane %d: VC4 T-tiled %s format\n",
> > + overlay_planes_index,
> > igt_format_str(format));
> > + } else {
> > + /* Randomize the format to test. */
> > + index = rand() % ARRAY_SIZE(random_plane_formats);
> > + format = random_plane_formats[index];
> > + vc4_t_tiled = false;
> > +
> > + igt_debug("Plane %d: %s format\n",
> > overlay_planes_index,
> > + igt_format_str(format));
> > + }
> > +
> > + /* Convert the pattern to the test format if needed. */
> > + if (vc4_t_tiled) {
> > + fb_id = igt_vc4_fb_t_tiled_convert(overlay_fb,
> > + &pattern_fb);
> > + igt_assert(fb_id > 0);
> > + } else {
> > + stride_min = overlay_fb_w *
> > + igt_format_plane_bpp(format, 0) / 8;
> > +
> > + /* Randomize the stride with at most twice the
> > minimum. */
> > + stride = (rand() % stride_min) + stride_min;
> > +
> > + /* Pixman requires the stride to be aligned to 32-byte
> > words. */
> > + stride = ALIGN(stride, sizeof(uint32_t));
> > +
> > + igt_debug("Plane %d: using stride %d\n",
> > overlay_planes_index, stride);
> > +
> > + fb_id = igt_fb_convert_with_stride(overlay_fb,
> > + &pattern_fb,
> > + format, stride);
> > + igt_assert(fb_id);
> > + }
> > +
> > + igt_plane_set_fb(plane, overlay_fb);
> > +
> > + overlay_surface = igt_get_cairo_surface(data->drm_fd,
> > + &pattern_fb);
> > +
> > + /* Blend the overlay surface. */
> > + overlay_surface_x = overlay_crtc_x - (int) overlay_src_x;
> > + overlay_surface_y = overlay_crtc_y - (int) overlay_src_y;
> > +
> > + cr = cairo_create(result_surface);
> > +
> > + cairo_set_source_surface(cr, overlay_surface,
> > overlay_surface_x,
> > + overlay_surface_y);
> > + cairo_surface_destroy(overlay_surface);
> > +
> > + /* Clip the surface to a rectangle. */
> > + cairo_rectangle(cr, overlay_crtc_x, overlay_crtc_y,
> > + overlay_crtc_w, overlay_crtc_h);
> > + cairo_clip(cr);
> > +
> > + cairo_paint(cr);
> > +
> > + cairo_destroy(cr);
> > +
> > + /* Configure the plane with framebuffer source coordinates. */
> > + igt_plane_set_position(plane, overlay_crtc_x, overlay_crtc_y);
> > + igt_plane_set_size(plane, overlay_crtc_w, overlay_crtc_h);
> > +
> > + igt_fb_set_position(overlay_fb, plane, overlay_src_x,
> > + overlay_src_y);
> > + igt_fb_set_size(overlay_fb, plane, overlay_src_w,
> > + overlay_src_h);
> > +
> > + igt_remove_fb(data->drm_fd, &pattern_fb);
> > +
> > + overlay_planes_index++;
> > + }
> > +
> > + cairo_surface_destroy(result_surface);
> > +
> > + fb_crc = chamelium_calculate_fb_crc_async_start(data->drm_fd,
> > + &result_fb);
> > +
> > + if (is_vc4_device(data->drm_fd)) {
> > + ret = igt_display_try_commit2(&data->display, COMMIT_ATOMIC);
> > + bandwidth_exceeded = (ret < 0 && errno == ENOSPC);
> > +
> > + igt_debug("Bandwidth limitation exeeded: %s\n",
> > + bandwidth_exceeded ? "Yes" : "No");
> > +
> > + igt_assert(!bandwidth_exceeded);
> > + }
> Why does this need to be handled separately for VC4 devices? We already
> print the return code from a failed atomic commit into the debugging
> output, and I'd assume anyone looking at the output from the failure of
> one of these tests probably knows what that means. Additionally, if we
> wanted to print debugging messages for bandwidth limitations being
> exceeded for atomic commits on vc4, seems like it would probably be
> better to just add that code to igt_display_try_commit2()

You're right, after all the following call will produce more or less
the same result and there is nothing really specific to the VC4 there.
I'll drop it in the future.

> > +
> > + igt_display_commit2(&data->display, COMMIT_ATOMIC);
> > +
> > + 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);
> > +
> > + if (is_vc4_device(data->drm_fd)) {
> > + underrun = igt_sysfs_get(debugfs, "underrun");
> > + igt_assert(underrun);
> > +
> > + underrun_detected = (underrun[0] == 'Y');
> > + free(underrun);
> > +
> > + igt_debug("Underrun detected: %s\n",
> > + underrun_detected ? "Yes" : "No");
> > +
> > + igt_assert(!underrun_detected);
> > +
> > + close(debugfs);
> > + }
> This will definitely be used again in the future, this should also go
> into a helper for vc4.

Since we're having problems with underrun testing and the Chamelium, I
think I'll drop that part for now and maybe re-introduce it as a vc4
helper (it's specific to the vc4 driver in the latest iteration) later.

> > +
> > + expected_crc = chamelium_calculate_fb_crc_async_finish(fb_crc);
> > +
> > + chamelium_assert_crc_eq_or_dump(data->chamelium,
> > + expected_crc, crc,
> > + &result_fb, i);
> > +
> > + igt_debug("reference CRC: %s\n", igt_crc_to_string(expected_crc));
> > + igt_debug("calculated CRC: %s\n", igt_crc_to_string(crc));
> Why not just add this debugging output to
> chamelium_assert_crc_eq_or_dump()?

Yes, let's do that.

> > +
> > + free(expected_crc);
> > + free(crc);
> > +
> > + for (i = 0, overlay_planes_index = 0;
> > + i < pipe->n_planes && overlay_planes_index <
> > overlay_planes_count;
> > + i++) {
> > + igt_plane_t *plane = &pipe->planes[i];
> > + struct igt_fb *overlay_fb =
> > &overlay_fbs[overlay_planes_index];
> > +
> > + if (plane->type != DRM_PLANE_TYPE_OVERLAY)
> > + continue;
> > +
> > + igt_remove_fb(data->drm_fd, overlay_fb);
> > +
> > + overlay_planes_index++;
> > + }
> > +
> > + free(overlay_fbs);
> > +
> > + igt_remove_fb(data->drm_fd, &primary_fb);
> > + igt_remove_fb(data->drm_fd, &result_fb);
> > +}
> > +
> > static void
> > test_hpd_without_ddc(data_t *data, struct chamelium_port *port)
> > {
> > @@ -981,6 +1307,11 @@ igt_main
> > test_display_one_mode(&data, port, DRM_FORMAT_NV12,
> > CHAMELIUM_CHECK_ANALOG, 1);
> >
> > + connector_subtest("hdmi-crc-planes-random", HDMIA)
> > + test_display_planes_random(&data, port,
> > + CHAMELIUM_CHECK_CRC);
> > +
> > +
> > connector_subtest("hdmi-frame-dump", HDMIA)
> > test_display_frame_dump(&data, port);
> > }

Cheers,

Paul

--
Paul Kocialkowski, Bootlin (formerly Free Electrons)
Embedded Linux and kernel engineering