/* * Copyright (C) 2013 Samsung Electronics Co.Ltd * Authors: * Inki Dae <inki.dae@samsung.com> * * This program 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <time.h> #include <unistd.h> #include <sys/mman.h> #include <linux/stddef.h> #include <xf86drm.h> #include <xf86drmMode.h> #include <libkms.h> #include <drm_fourcc.h> #include "exynos_drm.h" #include "exynos_drmif.h" #include "exynos_fimg2d.h" #define DRM_MODULE_NAME "exynos" static unsigned int screen_width, screen_height; struct connector { uint32_t id; char mode_str[64]; drmModeModeInfo *mode; drmModeEncoder *encoder; int crtc; }; static void connector_find_mode(int fd, struct connector *c, drmModeRes *resources) { drmModeConnector *connector; int i, j; /* First, find the connector & mode */ c->mode = NULL; for (i = 0; i < resources->count_connectors; i++) { connector = drmModeGetConnector(fd, resources->connectors[i]); if (!connector) { fprintf(stderr, "could not get connector %i: %s\n", resources->connectors[i], strerror(errno)); drmModeFreeConnector(connector); continue; } if (!connector->count_modes) { drmModeFreeConnector(connector); continue; } if (connector->connector_id != c->id) { drmModeFreeConnector(connector); continue; } for (j = 0; j < connector->count_modes; j++) { c->mode = &connector->modes[j]; if (!strcmp(c->mode->name, c->mode_str)) break; } /* Found it, break out */ if (c->mode) break; drmModeFreeConnector(connector); } if (!c->mode) { fprintf(stderr, "failed to find mode \"%s\"\n", c->mode_str); return; } /* Now get the encoder */ for (i = 0; i < resources->count_encoders; i++) { c->encoder = drmModeGetEncoder(fd, resources->encoders[i]); if (!c->encoder) { fprintf(stderr, "could not get encoder %i: %s\n", resources->encoders[i], strerror(errno)); drmModeFreeEncoder(c->encoder); continue; } if (c->encoder->encoder_id == connector->encoder_id) break; drmModeFreeEncoder(c->encoder); } if (c->crtc == -1) c->crtc = c->encoder->crtc_id; } static int drm_set_crtc(struct exynos_device *dev, struct connector *c, unsigned int fb_id) { int ret; ret = drmModeSetCrtc(dev->fd, c->crtc, fb_id, 0, 0, &c->id, 1, c->mode); if (ret) drmMsg("failed to set mode: %s\n", strerror(errno)); return ret; } static struct exynos_bo *exynos_create_buffer(struct exynos_device *dev, unsigned long size, unsigned int flags) { struct exynos_bo *bo; bo = exynos_bo_create(dev, size, flags); if (!bo) return bo; if (!exynos_bo_map(bo)) { exynos_bo_destroy(bo); return NULL; } return bo; } /* Allocate buffer and fill it with checkerboard pattern, where the tiles * * have a random color. The caller has to free the buffer. */ static void *create_checkerboard_pattern(unsigned int num_tiles_x, unsigned int num_tiles_y, unsigned int tile_size) { unsigned int *buf; unsigned int x, y, i, j; const unsigned int stride = num_tiles_x * tile_size; if (posix_memalign((void*)&buf, 64, num_tiles_y * tile_size * stride * 4) != 0) return NULL; for (x = 0; x < num_tiles_x; ++x) { for (y = 0; y < num_tiles_y; ++y) { const unsigned int color = 0xff000000 + (random() & 0xffffff); for (i = 0; i < tile_size; ++i) { for (j = 0; j < tile_size; ++j) { buf[x * tile_size + y * stride * tile_size + i + j * stride] = color; } } } } return buf; } static void exynos_destroy_buffer(struct exynos_bo *bo) { exynos_bo_destroy(bo); } static void wait_for_user_input(int last) { printf("press <ENTER> to %s\n", last ? "exit test application" : "skip to next test"); getchar(); } static int g2d_solid_fill_test(struct exynos_device *dev, struct exynos_bo *dst) { struct g2d_context *ctx; struct g2d_image img = {0}; unsigned int count, img_w, img_h; int ret = 0; ctx = g2d_init(dev->fd); if (!ctx) return -EFAULT; img.bo[0] = dst->handle; printf("solid fill test.\n"); srand(time(NULL)); img_w = screen_width; img_h = screen_height; for (count = 0; count < 2; count++) { unsigned int x, y, w, h; x = rand() % (img_w / 2); y = rand() % (img_h / 2); w = rand() % (img_w - x); h = rand() % (img_h - y); img.width = img_w; img.height = img_h; img.stride = img.width * 4; img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; img.color = 0xff000000 + (random() & 0xffffff); ret = g2d_solid_fill(ctx, &img, x, y, w, h); if (ret < 0) goto err_fini; ret = g2d_exec(ctx); if (ret < 0) break; } err_fini: g2d_fini(ctx); return ret; } static int g2d_copy_test(struct exynos_device *dev, struct exynos_bo *src, struct exynos_bo *dst, enum e_g2d_buf_type type) { struct g2d_context *ctx; struct g2d_image src_img = {0}, dst_img = {0}; unsigned int src_x, src_y, dst_x, dst_y, img_w, img_h; unsigned long userptr, size; int ret; ctx = g2d_init(dev->fd); if (!ctx) return -EFAULT; dst_img.bo[0] = dst->handle; src_x = 0; src_y = 0; dst_x = 0; dst_y = 0; img_w = screen_width; img_h = screen_height; switch (type) { case G2D_IMGBUF_GEM: src_img.bo[0] = src->handle; break; case G2D_IMGBUF_USERPTR: size = img_w * img_h * 4; userptr = (unsigned long)malloc(size); if (!userptr) { fprintf(stderr, "failed to allocate userptr.\n"); return -EFAULT; } src_img.user_ptr[0].userptr = userptr; src_img.user_ptr[0].size = size; break; case G2D_IMGBUF_COLOR: default: ret = -EFAULT; goto fail; } printf("copy test with %s.\n", type == G2D_IMGBUF_GEM ? "gem" : "userptr"); src_img.width = img_w; src_img.height = img_h; src_img.stride = src_img.width * 4; src_img.buf_type = type; src_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; src_img.color = 0xffff0000; ret = g2d_solid_fill(ctx, &src_img, src_x, src_y, img_w, img_h); if (ret < 0) goto err_free_userptr; dst_img.width = img_w; dst_img.height = img_h; dst_img.stride = dst_img.width * 4; dst_img.buf_type = G2D_IMGBUF_GEM; dst_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; ret = g2d_copy(ctx, &src_img, &dst_img, src_x, src_y, dst_x, dst_y, img_w - 4, img_h - 4); if (ret < 0) goto err_free_userptr; g2d_exec(ctx); err_free_userptr: if (type == G2D_IMGBUF_USERPTR) if (userptr) free((void *)userptr); fail: g2d_fini(ctx); return ret; } static int g2d_move_test(struct exynos_device *dev, struct exynos_bo *tmp, struct exynos_bo *buf, enum e_g2d_buf_type type) { struct g2d_context *ctx; struct g2d_image img = {0}, tmp_img = {0}; unsigned int img_w, img_h, count; int cur_x, cur_y; void *checkerboard; int ret; static const struct g2d_step { int x, y; } steps[] = { { 1, 0}, { 0, 1}, {-1, 0}, { 0, -1}, { 1, 1}, {-1, -1}, { 1, -1}, {-1, 1}, { 2, 1}, { 1, 2}, {-2, -1}, {-1, -2}, { 2, -1}, { 1, -2}, {-2, 1}, {-1, 2} }; static const unsigned int num_steps = sizeof(steps) / sizeof(struct g2d_step); ctx = g2d_init(dev->fd); if (!ctx) return -EFAULT; img.bo[0] = buf->handle; /* create pattern of half the screen size */ checkerboard = create_checkerboard_pattern(screen_width / 64, screen_height / 64, 32); if (!checkerboard) { ret = -EFAULT; goto fail; } img_w = (screen_width / 64) * 32; img_h = (screen_height / 64) * 32; switch (type) { case G2D_IMGBUF_GEM: memcpy(tmp->vaddr, checkerboard, img_w * img_h * 4); tmp_img.bo[0] = tmp->handle; break; case G2D_IMGBUF_USERPTR: tmp_img.user_ptr[0].userptr = (unsigned long)checkerboard; tmp_img.user_ptr[0].size = img_w * img_h * 4; break; case G2D_IMGBUF_COLOR: default: ret = -EFAULT; goto fail; } /* solid fill framebuffer with white color */ img.width = screen_width; img.height = screen_height; img.stride = screen_width * 4; img.buf_type = G2D_IMGBUF_GEM; img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; img.color = 0xffffffff; /* put checkerboard pattern in the center of the framebuffer */ cur_x = (screen_width - img_w) / 2; cur_y = (screen_height - img_h) / 2; tmp_img.width = img_w; tmp_img.height = img_h; tmp_img.stride = img_w * 4; tmp_img.buf_type = type; tmp_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; ret = g2d_solid_fill(ctx, &img, 0, 0, screen_width, screen_height) || g2d_copy(ctx, &tmp_img, &img, 0, 0, cur_x, cur_y, img_w, img_h); if (!ret) ret = g2d_exec(ctx); if (ret < 0) goto fail; printf("move test with %s.\n", type == G2D_IMGBUF_GEM ? "gem" : "userptr"); srand(time(NULL)); for (count = 0; count < 256; ++count) { const struct g2d_step *s; /* select step and validate it */ while (1) { s = &steps[random() % num_steps]; if (cur_x + s->x < 0 || cur_y + s->y < 0 || cur_x + img_w + s->x >= screen_width || cur_y + img_h + s->y >= screen_height) continue; else break; } ret = g2d_move(ctx, &img, cur_x, cur_y, cur_x + s->x, cur_y + s->y, img_w, img_h); if (!ret) ret = g2d_exec(ctx); if (ret < 0) goto fail; cur_x += s->x; cur_y += s->y; usleep(100000); } fail: g2d_fini(ctx); free(checkerboard); return ret; } static int g2d_copy_with_scale_test(struct exynos_device *dev, struct exynos_bo *src, struct exynos_bo *dst, enum e_g2d_buf_type type) { struct g2d_context *ctx; struct g2d_image src_img = {0}, dst_img = {0}; unsigned int src_x, src_y, img_w, img_h; unsigned long userptr, size; int ret; ctx = g2d_init(dev->fd); if (!ctx) return -EFAULT; dst_img.bo[0] = dst->handle; src_x = 0; src_y = 0; img_w = screen_width; img_h = screen_height; switch (type) { case G2D_IMGBUF_GEM: src_img.bo[0] = src->handle; break; case G2D_IMGBUF_USERPTR: size = img_w * img_h * 4; userptr = (unsigned long)malloc(size); if (!userptr) { fprintf(stderr, "failed to allocate userptr.\n"); return -EFAULT; } src_img.user_ptr[0].userptr = userptr; src_img.user_ptr[0].size = size; break; case G2D_IMGBUF_COLOR: default: ret = -EFAULT; goto fail; } printf("copy and scale test with %s.\n", type == G2D_IMGBUF_GEM ? "gem" : "userptr"); src_img.width = img_w; src_img.height = img_h; src_img.stride = src_img.width * 4; src_img.buf_type = type; src_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; src_img.color = 0xffffffff; ret = g2d_solid_fill(ctx, &src_img, src_x, src_y, img_w , img_h); if (ret < 0) goto err_free_userptr; src_img.color = 0xff00ff00; ret = g2d_solid_fill(ctx, &src_img, 5, 5, 100, 100); if (ret < 0) goto err_free_userptr; dst_img.width = img_w; dst_img.height = img_h; dst_img.buf_type = G2D_IMGBUF_GEM; dst_img.stride = dst_img.width * 4; dst_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; ret = g2d_copy_with_scale(ctx, &src_img, &dst_img, 5, 5, 100, 100, 100, 100, 200, 200, 0); if (ret < 0) goto err_free_userptr; g2d_exec(ctx); err_free_userptr: if (type == G2D_IMGBUF_USERPTR) if (userptr) free((void *)userptr); fail: g2d_fini(ctx); return 0; } static int g2d_blend_test(struct exynos_device *dev, struct exynos_bo *src, struct exynos_bo *dst, enum e_g2d_buf_type type) { struct g2d_context *ctx; struct g2d_image src_img = {0}, dst_img = {0}; unsigned int src_x, src_y, dst_x, dst_y, img_w, img_h; unsigned long userptr, size; int ret; ctx = g2d_init(dev->fd); if (!ctx) return -EFAULT; dst_img.bo[0] = dst->handle; src_x = 0; src_y = 0; dst_x = 0; dst_y = 0; img_w = screen_width; img_h = screen_height; switch (type) { case G2D_IMGBUF_GEM: src_img.bo[0] = src->handle; break; case G2D_IMGBUF_USERPTR: size = img_w * img_h * 4; userptr = (unsigned long)malloc(size); if (!userptr) { fprintf(stderr, "failed to allocate userptr.\n"); return -EFAULT; } src_img.user_ptr[0].userptr = userptr; src_img.user_ptr[0].size = size; break; case G2D_IMGBUF_COLOR: default: ret = -EFAULT; goto fail; } printf("blend test with %s.\n", type == G2D_IMGBUF_GEM ? "gem" : "userptr"); src_img.width = img_w; src_img.height = img_h; src_img.stride = src_img.width * 4; src_img.buf_type = type; src_img.select_mode = G2D_SELECT_MODE_NORMAL; src_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; src_img.color = 0xffffffff; ret = g2d_solid_fill(ctx, &src_img, src_x, src_y, img_w, img_h); if (ret < 0) goto err_free_userptr; src_img.color = 0x770000ff; ret = g2d_solid_fill(ctx, &src_img, 5, 5, 200, 200); if (ret < 0) goto err_free_userptr; dst_img.width = img_w; dst_img.height = img_h; dst_img.stride = dst_img.width * 4; dst_img.buf_type = G2D_IMGBUF_GEM; dst_img.select_mode = G2D_SELECT_MODE_NORMAL; dst_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; dst_img.color = 0xffffffff; ret = g2d_solid_fill(ctx, &dst_img, dst_x, dst_y, img_w, img_h); if (ret < 0) goto err_free_userptr; dst_img.color = 0x77ff0000; ret = g2d_solid_fill(ctx, &dst_img, 105, 105, 200, 200); if (ret < 0) goto err_free_userptr; ret = g2d_blend(ctx, &src_img, &dst_img, 5, 5, 105, 105, 200, 200, G2D_OP_OVER); if (ret < 0) goto err_free_userptr; g2d_exec(ctx); err_free_userptr: if (type == G2D_IMGBUF_USERPTR) if (userptr) free((void *)userptr); fail: g2d_fini(ctx); return 0; } static int g2d_checkerboard_test(struct exynos_device *dev, struct exynos_bo *src, struct exynos_bo *dst, enum e_g2d_buf_type type) { struct g2d_context *ctx; struct g2d_image src_img = {0}, dst_img = {0}; unsigned int src_x, src_y, dst_x, dst_y, img_w, img_h; void *checkerboard = NULL; int ret; ctx = g2d_init(dev->fd); if (!ctx) return -EFAULT; dst_img.bo[0] = dst->handle; src_x = 0; src_y = 0; dst_x = 0; dst_y = 0; checkerboard = create_checkerboard_pattern(screen_width / 32, screen_height / 32, 32); if (checkerboard == NULL) { ret = -1; goto fail; } img_w = screen_width - (screen_width % 32); img_h = screen_height - (screen_height % 32); switch (type) { case G2D_IMGBUF_GEM: memcpy(src->vaddr, checkerboard, img_w * img_h * 4); src_img.bo[0] = src->handle; break; case G2D_IMGBUF_USERPTR: src_img.user_ptr[0].userptr = (unsigned long)checkerboard; src_img.user_ptr[0].size = img_w * img_h * 4; break; case G2D_IMGBUF_COLOR: default: ret = -EFAULT; goto fail; } printf("checkerboard test with %s.\n", type == G2D_IMGBUF_GEM ? "gem" : "userptr"); src_img.width = img_w; src_img.height = img_h; src_img.stride = src_img.width * 4; src_img.buf_type = type; src_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; dst_img.width = screen_width; dst_img.height = screen_height; dst_img.stride = dst_img.width * 4; dst_img.buf_type = G2D_IMGBUF_GEM; dst_img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; src_img.color = 0xff000000; ret = g2d_solid_fill(ctx, &dst_img, src_x, src_y, screen_width, screen_height); if (ret < 0) goto fail; ret = g2d_copy(ctx, &src_img, &dst_img, src_x, src_y, dst_x, dst_y, img_w, img_h); if (ret < 0) goto fail; g2d_exec(ctx); fail: free(checkerboard); g2d_fini(ctx); return ret; } static void usage(char *name) { fprintf(stderr, "usage: %s [-s]\n", name); fprintf(stderr, "-s <connector_id>@<crtc_id>:<mode>\n"); exit(0); } extern char *optarg; static const char optstr[] = "s:"; int main(int argc, char **argv) { struct exynos_device *dev; struct exynos_bo *bo, *src; struct connector con; unsigned int fb_id; uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; drmModeRes *resources; int ret, fd, c; memset(&con, 0, sizeof(struct connector)); if (argc != 3) { usage(argv[0]); return -EINVAL; } while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { case 's': con.crtc = -1; if (sscanf(optarg, "%d:0x%64s", &con.id, con.mode_str) != 2 && sscanf(optarg, "%d@%d:%64s", &con.id, &con.crtc, con.mode_str) != 3) usage(argv[0]); break; default: usage(argv[0]); break; } } fd = drmOpen(DRM_MODULE_NAME, NULL); if (fd < 0) { fprintf(stderr, "failed to open.\n"); return fd; } dev = exynos_device_create(fd); if (!dev) { drmClose(dev->fd); return -EFAULT; } resources = drmModeGetResources(dev->fd); if (!resources) { fprintf(stderr, "drmModeGetResources failed: %s\n", strerror(errno)); ret = -EFAULT; goto err_drm_close; } connector_find_mode(dev->fd, &con, resources); drmModeFreeResources(resources); if (!con.mode) { fprintf(stderr, "failed to find usable connector\n"); ret = -EFAULT; goto err_drm_close; } screen_width = con.mode->hdisplay; screen_height = con.mode->vdisplay; if (screen_width == 0 || screen_height == 0) { fprintf(stderr, "failed to find sane resolution on connector\n"); ret = -EFAULT; goto err_drm_close; } printf("screen width = %d, screen height = %d\n", screen_width, screen_height); bo = exynos_create_buffer(dev, screen_width * screen_height * 4, 0); if (!bo) { ret = -EFAULT; goto err_drm_close; } handles[0] = bo->handle; pitches[0] = screen_width * 4; offsets[0] = 0; ret = drmModeAddFB2(dev->fd, screen_width, screen_height, DRM_FORMAT_XRGB8888, handles, pitches, offsets, &fb_id, 0); if (ret < 0) goto err_destroy_buffer; memset(bo->vaddr, 0xff, screen_width * screen_height * 4); ret = drm_set_crtc(dev, &con, fb_id); if (ret < 0) goto err_rm_fb; ret = g2d_solid_fill_test(dev, bo); if (ret < 0) { fprintf(stderr, "failed to solid fill operation.\n"); goto err_rm_fb; } wait_for_user_input(0); src = exynos_create_buffer(dev, screen_width * screen_height * 4, 0); if (!src) { ret = -EFAULT; goto err_rm_fb; } ret = g2d_copy_test(dev, src, bo, G2D_IMGBUF_GEM); if (ret < 0) { fprintf(stderr, "failed to test copy operation.\n"); goto err_free_src; } wait_for_user_input(0); ret = g2d_move_test(dev, src, bo, G2D_IMGBUF_GEM); if (ret < 0) { fprintf(stderr, "failed to test move operation.\n"); goto err_free_src; } wait_for_user_input(0); ret = g2d_copy_with_scale_test(dev, src, bo, G2D_IMGBUF_GEM); if (ret < 0) { fprintf(stderr, "failed to test copy and scale operation.\n"); goto err_free_src; } wait_for_user_input(0); ret = g2d_checkerboard_test(dev, src, bo, G2D_IMGBUF_GEM); if (ret < 0) { fprintf(stderr, "failed to issue checkerboard test.\n"); goto err_free_src; } wait_for_user_input(1); /* * The blend test uses the userptr functionality of exynos-drm, which * is currently not safe to use. If the kernel hasn't been build with * exynos-iommu support, then the blend test is going to produce (kernel) * memory corruption, eventually leading to a system crash. * * Disable the test for now, until the kernel code has been sanitized. */ #if 0 ret = g2d_blend_test(dev, src, bo, G2D_IMGBUF_USERPTR); if (ret < 0) fprintf(stderr, "failed to test blend operation.\n"); getchar(); #endif err_free_src: if (src) exynos_destroy_buffer(src); err_rm_fb: drmModeRmFB(dev->fd, fb_id); err_destroy_buffer: exynos_destroy_buffer(bo); err_drm_close: drmClose(dev->fd); exynos_device_destroy(dev); return 0; }