Hello,
this series exposes async execution of G2D command buffers to userspace. Also includes is a small performance analysis test, which can also be used to stress test the engine. The async operation is of course also tested.
Please review and let me know what I can improve.
v3: Rewrote handling of vendor-specific events. The series is now based on [1]. Also added two small fixes (error handling, break statement).
v4: Minor modification to the vendor-event handling code (drop opaque ptr). Series is based on this version [2] of the patch. Another two small fixes (buffer pixelformat, g2d_fini) added. The pixelformat issue surfaced after the recent changes to the Exynos mixer code. Also fixed a thread bug in the event test (application should always exit properly now).
With best wishes, Tobias
[1] https://patchwork.kernel.org/patch/6262541/ [2] https://patchwork.kernel.org/patch/6391301/
Tobias Jakobi (9): exynos: Introduce exynos_handle_event() tests/exynos: add fimg2d performance analysis exynos/fimg2d: add g2d_config_event exynos: fimg2d: add g2d_exec2 tests/exynos: add fimg2d event test exynos: fimg2d: fix return codes tests/exynos: replace return by break tests/exynos: use XRGB8888 for framebuffer exynos/fimg2d: simplify g2d_fini()
exynos/exynos-symbol-check | 3 + exynos/exynos_drm.c | 28 ++++ exynos/exynos_drm.h | 12 ++ exynos/exynos_drmif.h | 26 +++ exynos/exynos_fimg2d.c | 65 +++++--- exynos/exynos_fimg2d.h | 8 + tests/exynos/Makefile.am | 26 ++- tests/exynos/exynos_fimg2d_event.c | 326 +++++++++++++++++++++++++++++++++++++ tests/exynos/exynos_fimg2d_perf.c | 320 ++++++++++++++++++++++++++++++++++++ tests/exynos/exynos_fimg2d_test.c | 4 +- 10 files changed, 793 insertions(+), 25 deletions(-) create mode 100644 tests/exynos/exynos_fimg2d_event.c create mode 100644 tests/exynos/exynos_fimg2d_perf.c
Used to handle kernel events specific to the Exynos platform. Currently only G2D events are handled.
v2: Adapt to container approach. v3: Add exynos_handle_event() to Exynos symbol test. Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- exynos/exynos-symbol-check | 1 + exynos/exynos_drm.c | 28 ++++++++++++++++++++++++++++ exynos/exynos_drm.h | 12 ++++++++++++ exynos/exynos_drmif.h | 26 ++++++++++++++++++++++++++ 4 files changed, 67 insertions(+)
diff --git a/exynos/exynos-symbol-check b/exynos/exynos-symbol-check index 1a1be89..c3ddbe4 100755 --- a/exynos/exynos-symbol-check +++ b/exynos/exynos-symbol-check @@ -22,6 +22,7 @@ exynos_device_destroy exynos_prime_fd_to_handle exynos_prime_handle_to_fd exynos_vidi_connection +exynos_handle_event g2d_blend g2d_copy g2d_copy_with_scale diff --git a/exynos/exynos_drm.c b/exynos/exynos_drm.c index df9b8ed..7a400ad 100644 --- a/exynos/exynos_drm.c +++ b/exynos/exynos_drm.c @@ -42,6 +42,8 @@ #include "exynos_drm.h" #include "exynos_drmif.h"
+#define U642VOID(x) ((void *)(unsigned long)(x)) + /* * Create exynos drm device object. * @@ -374,3 +376,29 @@ exynos_vidi_connection(struct exynos_device *dev, uint32_t connect,
return 0; } + +static void +exynos_handle_vendor(int fd, struct drm_event *e, void *ctx) +{ + struct drm_exynos_g2d_event *g2d; + struct exynos_event_context *ectx = ctx; + + switch (e->type) { + case DRM_EXYNOS_G2D_EVENT: + if (ectx->version < 1 || ectx->g2d_event_handler == NULL) + break; + g2d = (struct drm_exynos_g2d_event *)e; + ectx->g2d_event_handler(fd, g2d->cmdlist_no, g2d->tv_sec, + g2d->tv_usec, U642VOID(g2d->user_data)); + break; + + default: + break; + } +} + +int +exynos_handle_event(struct exynos_device *dev, struct exynos_event_context *ctx) +{ + return drmHandleEvent2(dev->fd, &ctx->base, exynos_handle_vendor); +} diff --git a/exynos/exynos_drm.h b/exynos/exynos_drm.h index 256c02f..c3af0ac 100644 --- a/exynos/exynos_drm.h +++ b/exynos/exynos_drm.h @@ -157,4 +157,16 @@ struct drm_exynos_g2d_exec { #define DRM_IOCTL_EXYNOS_G2D_EXEC DRM_IOWR(DRM_COMMAND_BASE + \ DRM_EXYNOS_G2D_EXEC, struct drm_exynos_g2d_exec)
+/* EXYNOS specific events */ +#define DRM_EXYNOS_G2D_EVENT 0x80000000 + +struct drm_exynos_g2d_event { + struct drm_event base; + __u64 user_data; + __u32 tv_sec; + __u32 tv_usec; + __u32 cmdlist_no; + __u32 reserved; +}; + #endif diff --git a/exynos/exynos_drmif.h b/exynos/exynos_drmif.h index c7c1d44..626e399 100644 --- a/exynos/exynos_drmif.h +++ b/exynos/exynos_drmif.h @@ -54,6 +54,25 @@ struct exynos_bo { uint32_t name; };
+#define EXYNOS_EVENT_CONTEXT_VERSION 1 + +/* + * Exynos Event Context structure. + * + * @base: base context (for core events). + * @version: version info similar to the one in 'drmEventContext'. + * @g2d_event_handler: handler for G2D events. + */ +struct exynos_event_context { + drmEventContext base; + + int version; + + void (*g2d_event_handler)(int fd, unsigned int cmdlist_no, + unsigned int tv_sec, unsigned int tv_usec, + void *user_data); +}; + /* * device related functions: */ @@ -83,4 +102,11 @@ int exynos_prime_fd_to_handle(struct exynos_device *dev, int fd, int exynos_vidi_connection(struct exynos_device *dev, uint32_t connect, uint32_t ext, void *edid);
+/* + * event handling related functions: + */ +int exynos_handle_event(struct exynos_device *dev, + struct exynos_event_context *ctx); + + #endif /* EXYNOS_DRMIF_H_ */
Currently only fast solid color clear performance is measured. A large buffer is allocated and solid color clear operations are executed on it with randomly chosen properties (position and size of the region, clear color). Execution time is measured and output together with the amount of pixels processed.
The 'simple' variant only executes one G2D command buffer at a time, while the 'multi' variant executes multiple ones. This can be used to measure setup/exec overhead.
The test also serves a stability check. If clocks/voltages are too high or low respectively, the test quickly reveals this.
v2: Add GPLv2 header, argument handling and documentation. Tool is only installed when requested.
Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- tests/exynos/Makefile.am | 19 ++- tests/exynos/exynos_fimg2d_perf.c | 320 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 tests/exynos/exynos_fimg2d_perf.c
diff --git a/tests/exynos/Makefile.am b/tests/exynos/Makefile.am index b21d016..e82d199 100644 --- a/tests/exynos/Makefile.am +++ b/tests/exynos/Makefile.am @@ -5,16 +5,31 @@ AM_CFLAGS = \ -I $(top_srcdir)/exynos \ -I $(top_srcdir)
+bin_PROGRAMS = +noinst_PROGRAMS = + if HAVE_LIBKMS if HAVE_INSTALL_TESTS -bin_PROGRAMS = \ +bin_PROGRAMS += \ exynos_fimg2d_test else -noinst_PROGRAMS = \ +noinst_PROGRAMS += \ exynos_fimg2d_test endif endif
+if HAVE_INSTALL_TESTS +bin_PROGRAMS += \ + exynos_fimg2d_perf +else +noinst_PROGRAMS += \ + exynos_fimg2d_perf +endif + +exynos_fimg2d_perf_LDADD = \ + $(top_builddir)/libdrm.la \ + $(top_builddir)/exynos/libdrm_exynos.la + exynos_fimg2d_test_LDADD = \ $(top_builddir)/libdrm.la \ $(top_builddir)/libkms/libkms.la \ diff --git a/tests/exynos/exynos_fimg2d_perf.c b/tests/exynos/exynos_fimg2d_perf.c new file mode 100644 index 0000000..e52e0d8 --- /dev/null +++ b/tests/exynos/exynos_fimg2d_perf.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2015 - Tobias Jakobi + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 2 of the License, + * or (at your option) any later version. + * + * It is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with it. If not, see http://www.gnu.org/licenses/. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <getopt.h> + +#include <xf86drm.h> + +#include "exynos_drm.h" +#include "exynos_drmif.h" +#include "exynos_fimg2d.h" + +static int output_mathematica = 0; + +static int fimg2d_perf_simple(struct exynos_bo *bo, struct g2d_context *ctx, + unsigned buf_width, unsigned buf_height, unsigned iterations) +{ + struct timespec tspec = { 0 }; + struct g2d_image img = { 0 }; + + unsigned long long g2d_time; + unsigned i; + int ret = 0; + + img.width = buf_width; + img.height = buf_height; + img.stride = buf_width * 4; + img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; + img.buf_type = G2D_IMGBUF_GEM; + img.bo[0] = bo->handle; + + srand(time(NULL)); + + printf("starting simple G2D performance test\n"); + printf("buffer width = %u, buffer height = %u, iterations = %u\n", + buf_width, buf_height, iterations); + + if (output_mathematica) + putchar('{'); + + for (i = 0; i < iterations; ++i) { + unsigned x, y, w, h; + + x = rand() % buf_width; + y = rand() % buf_height; + + if (x == (buf_width - 1)) + x -= 1; + if (y == (buf_height - 1)) + y -= 1; + + w = rand() % (buf_width - x); + h = rand() % (buf_height - y); + + if (w == 0) w = 1; + if (h == 0) h = 1; + + img.color = rand(); + + ret = g2d_solid_fill(ctx, &img, x, y, w, h); + + clock_gettime(CLOCK_MONOTONIC, &tspec); + + if (ret == 0) + ret = g2d_exec(ctx); + + if (ret != 0) { + fprintf(stderr, "error: iteration %u failed (x = %u, y = %u, w = %u, h = %u)\n", + i, x, y, w, h); + break; + } else { + struct timespec end = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &end); + + g2d_time = (end.tv_sec - tspec.tv_sec) * 1000000000ULL; + g2d_time += (end.tv_nsec - tspec.tv_nsec); + + if (output_mathematica) { + if (i != 0) putchar(','); + printf("{%u,%llu}", w * h, g2d_time); + } else { + printf("num_pixels = %u, usecs = %llu\n", w * h, g2d_time); + } + } + } + + if (output_mathematica) + printf("}\n"); + + return ret; +} + +static int fimg2d_perf_multi(struct exynos_bo *bo, struct g2d_context *ctx, + unsigned buf_width, unsigned buf_height, unsigned iterations, unsigned batch) +{ + struct timespec tspec = { 0 }; + struct g2d_image *images; + + unsigned long long g2d_time; + unsigned i, j; + int ret = 0; + + images = calloc(batch, sizeof(struct g2d_image)); + for (i = 0; i < batch; ++i) { + images[i].width = buf_width; + images[i].height = buf_height; + images[i].stride = buf_width * 4; + images[i].color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; + images[i].buf_type = G2D_IMGBUF_GEM; + images[i].bo[0] = bo->handle; + } + + srand(time(NULL)); + + printf("starting multi G2D performance test (batch size = %u)\n", batch); + printf("buffer width = %u, buffer height = %u, iterations = %u\n", + buf_width, buf_height, iterations); + + if (output_mathematica) + putchar('{'); + + for (i = 0; i < iterations; ++i) { + unsigned num_pixels = 0; + + for (j = 0; j < batch; ++j) { + unsigned x, y, w, h; + + x = rand() % buf_width; + y = rand() % buf_height; + + if (x == (buf_width - 1)) + x -= 1; + if (y == (buf_height - 1)) + y -= 1; + + w = rand() % (buf_width - x); + h = rand() % (buf_height - y); + + if (w == 0) w = 1; + if (h == 0) h = 1; + + images[j].color = rand(); + + num_pixels += w * h; + + ret = g2d_solid_fill(ctx, &images[j], x, y, w, h); + if (ret != 0) + break; + } + + clock_gettime(CLOCK_MONOTONIC, &tspec); + + if (ret == 0) + ret = g2d_exec(ctx); + + if (ret != 0) { + fprintf(stderr, "error: iteration %u failed (num_pixels = %u)\n", i, num_pixels); + break; + break; + } else { + struct timespec end = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &end); + + g2d_time = (end.tv_sec - tspec.tv_sec) * 1000000000ULL; + g2d_time += (end.tv_nsec - tspec.tv_nsec); + + if (output_mathematica) { + if (i != 0) putchar(','); + printf("{%u,%llu}", num_pixels, g2d_time); + } else { + printf("num_pixels = %u, usecs = %llu\n", num_pixels, g2d_time); + } + } + } + + if (output_mathematica) + printf("}\n"); + + return ret; +} + +static void usage(const char *name) +{ + fprintf(stderr, "usage: %s [-ibwh]\n\n", name); + + fprintf(stderr, "\t-i <number of iterations>\n"); + fprintf(stderr, "\t-b <size of a batch> (default = 3)\n\n"); + + fprintf(stderr, "\t-w <buffer width> (default = 4096)\n"); + fprintf(stderr, "\t-h <buffer height> (default = 4096)\n\n"); + + fprintf(stderr, "\t-M <enable Mathematica styled output>\n"); + + exit(0); +} + +int main(int argc, char **argv) +{ + int fd, ret, c, parsefail; + + struct exynos_device *dev; + struct g2d_context *ctx; + struct exynos_bo *bo; + + unsigned int iters = 0, batch = 3; + unsigned int bufw = 4096, bufh = 4096; + + ret = 0; + parsefail = 0; + + while ((c = getopt(argc, argv, "i:b:w:h:M")) != -1) { + switch (c) { + case 'i': + if (sscanf(optarg, "%u", &iters) != 1) + parsefail = 1; + break; + case 'b': + if (sscanf(optarg, "%u", &batch) != 1) + parsefail = 1; + break; + case 'w': + if (sscanf(optarg, "%u", &bufw) != 1) + parsefail = 1; + break; + case 'h': + if (sscanf(optarg, "%u", &bufh) != 1) + parsefail = 1; + break; + case 'M': + output_mathematica = 1; + break; + default: + parsefail = 1; + break; + } + } + + if (parsefail || (argc == 1) || (iters == 0)) + usage(argv[0]); + + if (bufw > 4096 || bufh > 4096) { + fprintf(stderr, "error: buffer width/height should be less than 4096.\n"); + ret = -1; + + goto out; + } + + if (bufw == 0 || bufh == 0) { + fprintf(stderr, "error: buffer width/height should be non-zero.\n"); + ret = -1; + + goto out; + } + + fd = drmOpen("exynos", NULL); + if (fd < 0) { + fprintf(stderr, "error: failed to open drm\n"); + ret = -1; + + goto out; + } + + dev = exynos_device_create(fd); + if (dev == NULL) { + fprintf(stderr, "error: failed to create device\n"); + ret = -2; + + goto fail; + } + + ctx = g2d_init(fd); + if (ctx == NULL) { + fprintf(stderr, "error: failed to init G2D\n"); + ret = -3; + + goto g2d_fail; + } + + bo = exynos_bo_create(dev, bufw * bufh * 4, 0); + if (bo == NULL) { + fprintf(stderr, "error: failed to create bo\n"); + ret = -4; + + goto bo_fail; + } + + ret = fimg2d_perf_simple(bo, ctx, bufw, bufh, iters); + + if (ret == 0) + ret = fimg2d_perf_multi(bo, ctx, bufw, bufh, iters, batch); + + exynos_bo_destroy(bo); + +bo_fail: + g2d_fini(ctx); + +g2d_fail: + exynos_device_destroy(dev); + +fail: + drmClose(fd); + +out: + return ret; +}
This enables us to pass command buffers to the kernel which trigger an event on the DRM fd upon completion. The final goal is to enable asynchronous operation of the G2D engine, similar to async page flips.
Passing the event userdata pointer through the G2D context was chosen to not change the current API (e.g. by adding a userdata argument to each public functions).
v2: Add g2d_config_event() to Exynos symbol test. Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- exynos/exynos-symbol-check | 1 + exynos/exynos_fimg2d.c | 27 +++++++++++++++++++++++++-- exynos/exynos_fimg2d.h | 2 ++ 3 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/exynos/exynos-symbol-check b/exynos/exynos-symbol-check index c3ddbe4..bb12315 100755 --- a/exynos/exynos-symbol-check +++ b/exynos/exynos-symbol-check @@ -27,6 +27,7 @@ g2d_blend g2d_copy g2d_copy_with_scale g2d_exec +g2d_config_event g2d_fini g2d_init g2d_scale_and_blend diff --git a/exynos/exynos_fimg2d.c b/exynos/exynos_fimg2d.c index 86ae898..3a5f20a 100644 --- a/exynos/exynos_fimg2d.c +++ b/exynos/exynos_fimg2d.c @@ -202,8 +202,15 @@ static int g2d_flush(struct g2d_context *ctx) cmdlist.cmd_buf = (uint64_t)(uintptr_t)&ctx->cmd_buf[0]; cmdlist.cmd_nr = ctx->cmd_nr; cmdlist.cmd_buf_nr = ctx->cmd_buf_nr; - cmdlist.event_type = G2D_EVENT_NOT; - cmdlist.user_data = 0; + + if (ctx->event_userdata) { + cmdlist.event_type = G2D_EVENT_NONSTOP; + cmdlist.user_data = (uint64_t)(uintptr_t)(ctx->event_userdata); + ctx->event_userdata = NULL; + } else { + cmdlist.event_type = G2D_EVENT_NOT; + cmdlist.user_data = 0; + }
ctx->cmd_nr = 0; ctx->cmd_buf_nr = 0; @@ -259,6 +266,22 @@ void g2d_fini(struct g2d_context *ctx) }
/** + * g2d_config_event - setup userdata configuration for a g2d event. + * The next invocation of a g2d call (e.g. g2d_solid_fill) is + * then going to flag the command buffer as 'nonstop'. + * Completion of the command buffer execution can then be + * determined by using drmHandleEvent on the DRM fd. + * The userdata is 'consumed' in the process. + * + * @ctx: a pointer to g2d_context structure. + * @userdata: a pointer to the user data + */ +void g2d_config_event(struct g2d_context *ctx, void *userdata) +{ + ctx->event_userdata = userdata; +} + +/** * g2d_exec - start the dma to process all commands summited by g2d_flush(). * * @ctx: a pointer to g2d_context structure. diff --git a/exynos/exynos_fimg2d.h b/exynos/exynos_fimg2d.h index 9db0c88..421249d 100644 --- a/exynos/exynos_fimg2d.h +++ b/exynos/exynos_fimg2d.h @@ -298,10 +298,12 @@ struct g2d_context { unsigned int cmd_nr; unsigned int cmd_buf_nr; unsigned int cmdlist_nr; + void *event_userdata; };
struct g2d_context *g2d_init(int fd); void g2d_fini(struct g2d_context *ctx); +void g2d_config_event(struct g2d_context *ctx, void *userdata); int g2d_exec(struct g2d_context *ctx); int g2d_solid_fill(struct g2d_context *ctx, struct g2d_image *img, unsigned int x, unsigned int y, unsigned int w,
This is a more 'flexible' version of g2d_exec allowing to pass some flags which modify the behaviour of g2d_exec. Currently only the 'async' operation flag is supported.
v2: Add g2d_exec2() to Exynos symbol test. Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- exynos/exynos-symbol-check | 1 + exynos/exynos_fimg2d.c | 15 +++++++++++++-- exynos/exynos_fimg2d.h | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/exynos/exynos-symbol-check b/exynos/exynos-symbol-check index bb12315..2bb7718 100755 --- a/exynos/exynos-symbol-check +++ b/exynos/exynos-symbol-check @@ -27,6 +27,7 @@ g2d_blend g2d_copy g2d_copy_with_scale g2d_exec +g2d_exec2 g2d_config_event g2d_fini g2d_init diff --git a/exynos/exynos_fimg2d.c b/exynos/exynos_fimg2d.c index 3a5f20a..b1fd0a5 100644 --- a/exynos/exynos_fimg2d.c +++ b/exynos/exynos_fimg2d.c @@ -288,13 +288,24 @@ void g2d_config_event(struct g2d_context *ctx, void *userdata) */ int g2d_exec(struct g2d_context *ctx) { - struct drm_exynos_g2d_exec exec; + return g2d_exec2(ctx, G2D_EXEC_FLAG_NORMAL); +} + +/** + * g2d_exec2 - same as 'g2d_exec' but allow to pass some flags. + * + * @ctx: a pointer to g2d_context structure. + */ +int g2d_exec2(struct g2d_context *ctx, unsigned int flags) +{ + struct drm_exynos_g2d_exec exec = {0}; int ret;
if (ctx->cmdlist_nr == 0) return -EINVAL;
- exec.async = 0; + if (flags & G2D_EXEC_FLAG_ASYNC) + exec.async = 1;
ret = drmIoctl(ctx->fd, DRM_IOCTL_EXYNOS_G2D_EXEC, &exec); if (ret < 0) { diff --git a/exynos/exynos_fimg2d.h b/exynos/exynos_fimg2d.h index 421249d..8f9ba23 100644 --- a/exynos/exynos_fimg2d.h +++ b/exynos/exynos_fimg2d.h @@ -187,6 +187,11 @@ enum e_g2d_acoeff_mode { G2D_ACOEFF_MODE_MASK };
+enum e_g2d_exec_flag { + G2D_EXEC_FLAG_NORMAL = (0 << 0), + G2D_EXEC_FLAG_ASYNC = (1 << 0) +}; + union g2d_point_val { unsigned int val; struct { @@ -305,6 +310,7 @@ struct g2d_context *g2d_init(int fd); void g2d_fini(struct g2d_context *ctx); void g2d_config_event(struct g2d_context *ctx, void *userdata); int g2d_exec(struct g2d_context *ctx); +int g2d_exec2(struct g2d_context *ctx, unsigned int flags); int g2d_solid_fill(struct g2d_context *ctx, struct g2d_image *img, unsigned int x, unsigned int y, unsigned int w, unsigned int h);
This tests async processing of G2D jobs. A separate thread is spawned to monitor the DRM fd for events and check whether a G2D job was completed.
v2: Add GPLv2 header, argument handling and documentation. Test is only installed when requested. v3: Allocate G2D jobs with calloc which fixes 'busy' being potentially uninitialized. Also enable timeout for poll() in the monitor thread. This fixes pthread_join() not working because of poll() not returning.
Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- tests/exynos/Makefile.am | 11 +- tests/exynos/exynos_fimg2d_event.c | 326 +++++++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 tests/exynos/exynos_fimg2d_event.c
diff --git a/tests/exynos/Makefile.am b/tests/exynos/Makefile.am index e82d199..357d6b8 100644 --- a/tests/exynos/Makefile.am +++ b/tests/exynos/Makefile.am @@ -20,16 +20,23 @@ endif
if HAVE_INSTALL_TESTS bin_PROGRAMS += \ - exynos_fimg2d_perf + exynos_fimg2d_perf \ + exynos_fimg2d_event else noinst_PROGRAMS += \ - exynos_fimg2d_perf + exynos_fimg2d_perf \ + exynos_fimg2d_event endif
exynos_fimg2d_perf_LDADD = \ $(top_builddir)/libdrm.la \ $(top_builddir)/exynos/libdrm_exynos.la
+exynos_fimg2d_event_LDADD = \ + $(top_builddir)/libdrm.la \ + $(top_builddir)/exynos/libdrm_exynos.la \ + -lpthread + exynos_fimg2d_test_LDADD = \ $(top_builddir)/libdrm.la \ $(top_builddir)/libkms/libkms.la \ diff --git a/tests/exynos/exynos_fimg2d_event.c b/tests/exynos/exynos_fimg2d_event.c new file mode 100644 index 0000000..c03dcff --- /dev/null +++ b/tests/exynos/exynos_fimg2d_event.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2015 - Tobias Jakobi + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 2 of the License, + * or (at your option) any later version. + * + * It is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with it. If not, see http://www.gnu.org/licenses/. + */ + +#include <unistd.h> +#include <poll.h> + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <getopt.h> + +#include <pthread.h> + +#include <xf86drm.h> + +#include "exynos_drm.h" +#include "exynos_drmif.h" +#include "exynos_fimg2d.h" + +struct g2d_job { + unsigned int id; + unsigned int busy; +}; + +struct exynos_evhandler { + struct pollfd fds; + struct exynos_event_context evctx; +}; + +struct threaddata { + unsigned int stop; + struct exynos_device *dev; + struct exynos_evhandler evhandler; +}; + +static void g2d_event_handler(int fd, unsigned int cmdlist_no, unsigned int tv_sec, + unsigned int tv_usec, void *user_data) +{ + struct g2d_job *job = user_data; + + fprintf(stderr, "info: g2d job (id = %u, cmdlist number = %u) finished!\n", + job->id, cmdlist_no); + + job->busy = 0; +} + +static void setup_g2d_event_handler(struct exynos_evhandler *evhandler, int fd) +{ + evhandler->fds.fd = fd; + evhandler->fds.events = POLLIN; + evhandler->evctx.base.version = DRM_EVENT_CONTEXT_VERSION; + evhandler->evctx.version = EXYNOS_EVENT_CONTEXT_VERSION; + evhandler->evctx.g2d_event_handler = g2d_event_handler; +} + +static void* threadfunc(void *arg) { + const int timeout = 0; + struct threaddata *data; + + data = arg; + + while (1) { + if (data->stop) break; + + usleep(500); + + data->evhandler.fds.revents = 0; + + if (poll(&data->evhandler.fds, 1, timeout) < 0) + continue; + + if (data->evhandler.fds.revents & (POLLHUP | POLLERR)) + continue; + + if (data->evhandler.fds.revents & POLLIN) + exynos_handle_event(data->dev, &data->evhandler.evctx); + } + + pthread_exit(0); +} + +/* + * We need to wait until all G2D jobs are finished, otherwise we + * potentially remove a BO which the engine still operates on. + * This results in the following kernel message: + * [drm:exynos_drm_gem_put_dma_addr] *ERROR* failed to lookup gem object. + * Also any subsequent BO allocations fail then with: + * [drm:exynos_drm_alloc_buf] *ERROR* failed to allocate buffer. + */ +static void wait_all_jobs(struct g2d_job* jobs, unsigned num_jobs) +{ + unsigned i; + + for (i = 0; i < num_jobs; ++i) { + while (jobs[i].busy) + usleep(500); + } + +} + +static struct g2d_job* free_job(struct g2d_job* jobs, unsigned num_jobs) +{ + unsigned i; + + for (i = 0; i < num_jobs; ++i) { + if (jobs[i].busy == 0) + return &jobs[i]; + } + + return NULL; +} + +static int g2d_work(struct g2d_context *ctx, struct g2d_image *img, + unsigned num_jobs, unsigned iterations) +{ + struct g2d_job *jobs = calloc(num_jobs, sizeof(struct g2d_job)); + int ret; + unsigned i; + + /* setup job ids */ + for (i = 0; i < num_jobs; ++i) + jobs[i].id = i; + + for (i = 0; i < iterations; ++i) { + unsigned x, y, w, h; + + struct g2d_job *j = NULL; + + while (1) { + j = free_job(jobs, num_jobs); + + if (j) + break; + else + usleep(500); + } + + x = rand() % img->width; + y = rand() % img->height; + + if (x == (img->width - 1)) + x -= 1; + if (y == (img->height - 1)) + y -= 1; + + w = rand() % (img->width - x); + h = rand() % (img->height - y); + + if (w == 0) w = 1; + if (h == 0) h = 1; + + img->color = rand(); + + j->busy = 1; + g2d_config_event(ctx, j); + + ret = g2d_solid_fill(ctx, img, x, y, w, h); + + if (ret == 0) + g2d_exec2(ctx, G2D_EXEC_FLAG_ASYNC); + + if (ret != 0) { + fprintf(stderr, "error: iteration %u (x = %u, x = %u, x = %u, x = %u) failed\n", + i, x, y, w, h); + break; + } + } + + wait_all_jobs(jobs, num_jobs); + free(jobs); + + return 0; +} + +static void usage(const char *name) +{ + fprintf(stderr, "usage: %s [-ijwh]\n\n", name); + + fprintf(stderr, "\t-i <number of iterations>\n"); + fprintf(stderr, "\t-j <number of G2D jobs> (default = 4)\n\n"); + + fprintf(stderr, "\t-w <buffer width> (default = 4096)\n"); + fprintf(stderr, "\t-h <buffer height> (default = 4096)\n"); + + exit(0); +} + +int main(int argc, char **argv) +{ + int fd, ret, c, parsefail; + + pthread_t event_thread; + struct threaddata event_data = {0}; + + struct exynos_device *dev; + struct g2d_context *ctx; + struct exynos_bo *bo; + + struct g2d_image img = {0}; + + unsigned int iters = 0, njobs = 4; + unsigned int bufw = 4096, bufh = 4096; + + ret = 0; + parsefail = 0; + + while ((c = getopt(argc, argv, "i:j:w:h:")) != -1) { + switch (c) { + case 'i': + if (sscanf(optarg, "%u", &iters) != 1) + parsefail = 1; + break; + case 'j': + if (sscanf(optarg, "%u", &njobs) != 1) + parsefail = 1; + break; + case 'w': + if (sscanf(optarg, "%u", &bufw) != 1) + parsefail = 1; + break; + case 'h': + if (sscanf(optarg, "%u", &bufh) != 1) + parsefail = 1; + break; + default: + parsefail = 1; + break; + } + } + + if (parsefail || (argc == 1) || (iters == 0)) + usage(argv[0]); + + if (bufw > 4096 || bufh > 4096) { + fprintf(stderr, "error: buffer width/height should be less than 4096.\n"); + ret = -1; + + goto out; + } + + if (bufw == 0 || bufh == 0) { + fprintf(stderr, "error: buffer width/height should be non-zero.\n"); + ret = -1; + + goto out; + } + + fd = drmOpen("exynos", NULL); + if (fd < 0) { + fprintf(stderr, "error: failed to open drm\n"); + ret = -1; + + goto out; + } + + dev = exynos_device_create(fd); + if (dev == NULL) { + fprintf(stderr, "error: failed to create device\n"); + ret = -2; + + goto fail; + } + + ctx = g2d_init(fd); + if (ctx == NULL) { + fprintf(stderr, "error: failed to init G2D\n"); + ret = -3; + + goto g2d_fail; + } + + bo = exynos_bo_create(dev, bufw * bufh * 4, 0); + if (bo == NULL) { + fprintf(stderr, "error: failed to create bo\n"); + ret = -4; + + goto bo_fail; + } + + /* setup g2d image object */ + img.width = bufw; + img.height = bufh; + img.stride = bufw * 4; + img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; + img.buf_type = G2D_IMGBUF_GEM; + img.bo[0] = bo->handle; + + event_data.dev = dev; + setup_g2d_event_handler(&event_data.evhandler, fd); + + pthread_create(&event_thread, NULL, threadfunc, &event_data); + + ret = g2d_work(ctx, &img, njobs, iters); + if (ret != 0) + fprintf(stderr, "error: g2d_work failed\n"); + + event_data.stop = 1; + pthread_join(event_thread, NULL); + + exynos_bo_destroy(bo); + +bo_fail: + g2d_fini(ctx); + +g2d_fail: + exynos_device_destroy(dev); + +fail: + drmClose(fd); + +out: + return ret; +}
Even if flushing the command buffer doesn't succeed, the G2D calls would still return zero. Fix this by just passing the flush return code.
Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- exynos/exynos_fimg2d.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/exynos/exynos_fimg2d.c b/exynos/exynos_fimg2d.c index b1fd0a5..9bf2aa9 100644 --- a/exynos/exynos_fimg2d.c +++ b/exynos/exynos_fimg2d.c @@ -364,9 +364,7 @@ g2d_solid_fill(struct g2d_context *ctx, struct g2d_image *img, bitblt.data.fast_solid_color_fill_en = 1; g2d_add_cmd(ctx, BITBLT_COMMAND_REG, bitblt.val);
- g2d_flush(ctx); - - return 0; + return g2d_flush(ctx); }
/** @@ -449,9 +447,7 @@ g2d_copy(struct g2d_context *ctx, struct g2d_image *src, rop4.data.unmasked_rop3 = G2D_ROP3_SRC; g2d_add_cmd(ctx, ROP4_REG, rop4.val);
- g2d_flush(ctx); - - return 0; + return g2d_flush(ctx); }
/** @@ -561,9 +557,7 @@ g2d_copy_with_scale(struct g2d_context *ctx, struct g2d_image *src, pt.data.y = dst_y + dst_h; g2d_add_cmd(ctx, DST_RIGHT_BOTTOM_REG, pt.val);
- g2d_flush(ctx); - - return 0; + return g2d_flush(ctx); }
/** @@ -670,9 +664,7 @@ g2d_blend(struct g2d_context *ctx, struct g2d_image *src, pt.data.y = dst_y + h; g2d_add_cmd(ctx, DST_RIGHT_BOTTOM_REG, pt.val);
- g2d_flush(ctx); - - return 0; + return g2d_flush(ctx); }
/** @@ -800,7 +792,5 @@ g2d_scale_and_blend(struct g2d_context *ctx, struct g2d_image *src, pt.data.y = dst_y + dst_h; g2d_add_cmd(ctx, DST_RIGHT_BOTTOM_REG, pt.val);
- g2d_flush(ctx); - - return 0; + return g2d_flush(ctx); }
The 'usage' function already does exit(0), so that this 'return -EINVAL' is never called. Just put a break there to avoid confusion.
Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- tests/exynos/exynos_fimg2d_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/exynos/exynos_fimg2d_test.c b/tests/exynos/exynos_fimg2d_test.c index e64bb32..64dacfa 100644 --- a/tests/exynos/exynos_fimg2d_test.c +++ b/tests/exynos/exynos_fimg2d_test.c @@ -689,7 +689,7 @@ int main(int argc, char **argv) break; default: usage(argv[0]); - return -EINVAL; + break; } }
This matches the G2D color mode that is used in the entire code. The previous (incorrect) RGBA8888 would only work since the Exynos mixer did its configuration based on the bpp, and not based on the actual pixelformat.
Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- tests/exynos/exynos_fimg2d_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/exynos/exynos_fimg2d_test.c b/tests/exynos/exynos_fimg2d_test.c index 64dacfa..ce32045 100644 --- a/tests/exynos/exynos_fimg2d_test.c +++ b/tests/exynos/exynos_fimg2d_test.c @@ -745,7 +745,7 @@ int main(int argc, char **argv) offsets[0] = 0;
ret = drmModeAddFB2(dev->fd, screen_width, screen_height, - DRM_FORMAT_RGBA8888, handles, + DRM_FORMAT_XRGB8888, handles, pitches, offsets, &fb_id, 0); if (ret < 0) goto err_destroy_buffer;
free()ing a nullptr is a noop, so remove the check.
Signed-off-by: Tobias Jakobi tjakobi@math.uni-bielefeld.de --- exynos/exynos_fimg2d.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/exynos/exynos_fimg2d.c b/exynos/exynos_fimg2d.c index 9bf2aa9..8527dde 100644 --- a/exynos/exynos_fimg2d.c +++ b/exynos/exynos_fimg2d.c @@ -261,8 +261,7 @@ struct g2d_context *g2d_init(int fd)
void g2d_fini(struct g2d_context *ctx) { - if (ctx) - free(ctx); + free(ctx); }
/**
dri-devel@lists.freedesktop.org