PD#158474: add chromiumos drm-tests
Change-Id: I33b220ec40c8147bc6e6228cecff164f8555582f
Signed-off-by: Yalong Liu <yalong.liu@amlogic.com>
diff --git a/drm-tests/.clang-format b/drm-tests/.clang-format
new file mode 100644
index 0000000..2f51f78
--- /dev/null
+++ b/drm-tests/.clang-format
@@ -0,0 +1,10 @@
+BasedOnStyle: Google
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+BreakBeforeBraces: Linux
+ColumnLimit: 100
+IndentWidth: 8
+TabWidth: 8
+UseTab: Always
+Cpp11BracedListStyle: false
diff --git a/drm-tests/Android.mk b/drm-tests/Android.mk
new file mode 100644
index 0000000..1d83aaf
--- /dev/null
+++ b/drm-tests/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH := $(call my-dir)
+
+#########################
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := nv12_test.c bo.c dev.c modeset.c
+
+LOCAL_MODULE := nv12_test
+
+LOCAL_C_INCLUDES := \
+ external/libdrm \
+ external/libdrm/include/drm
+LOCAL_CFLAGS := -O2 -g -W -Wall
+LOCAL_SYSTEM_SHARED_LIBRARIES := libc
+LOCAL_SHARED_LIBRARIES := libdrm
+
+include $(BUILD_EXECUTABLE)
diff --git a/drm-tests/Makefile b/drm-tests/Makefile
new file mode 100644
index 0000000..8cb1458
--- /dev/null
+++ b/drm-tests/Makefile
@@ -0,0 +1,66 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Pull in chromium os defaults
+OUT ?= $(PWD)/build-opt-local
+
+include common.mk
+
+PC_DEPS = libdrm egl gbm
+PC_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PC_DEPS))
+PC_LIBS := $(shell $(PKG_CONFIG) --libs $(PC_DEPS))
+
+DRM_LIBS = -lGLESv2
+CFLAGS += $(PC_CFLAGS) -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
+CFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+LDLIBS += $(PC_LIBS)
+
+all: \
+ CC_BINARY(atomictest) \
+ CC_BINARY(drm_cursor_test) \
+ CC_BINARY(gamma_test) \
+ CC_BINARY(linear_bo_test) \
+ CC_BINARY(mapped_texture_test) \
+ CC_BINARY(mmap_test) \
+ CC_BINARY(null_platform_test) \
+ CC_BINARY(plane_test) \
+ CC_BINARY(stripe) \
+ CC_BINARY(swrast_test) \
+ CC_BINARY(vgem_test)
+
+ifeq ($(USE_VULKAN),1)
+all: CC_BINARY(vk_glow)
+endif
+
+CC_BINARY(drm_cursor_test): drm_cursor_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+
+CC_BINARY(null_platform_test): null_platform_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(null_platform_test): LDLIBS += $(DRM_LIBS)
+
+CC_BINARY(vgem_test): vgem_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(mmap_test): mmap_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+
+CC_BINARY(linear_bo_test): linear_bo_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(linear_bo_test): LDLIBS += -lGLESv2
+
+CC_BINARY(swrast_test): swrast_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(swrast_test): LDLIBS += -lGLESv2
+
+CC_BINARY(atomictest): atomictest.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(atomictest): CFLAGS += -DUSE_ATOMIC_API
+CC_BINARY(atomictest): LDLIBS += $(DRM_LIBS)
+
+CC_BINARY(gamma_test): gamma_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(gamma_test): LDLIBS += -lm $(DRM_LIBS)
+
+CC_BINARY(plane_test): plane_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(plane_test): LDLIBS += -lm $(DRM_LIBS)
+
+CC_BINARY(mapped_texture_test): mapped_texture_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(mapped_texture_test): LDLIBS += -lGLESv2
+
+ifeq ($(USE_VULKAN),1)
+CC_BINARY(vk_glow): vk_glow.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(vk_glow): LDLIBS += -lm -lvulkan $(DRM_LIBS)
+endif
diff --git a/drm-tests/atomictest.c b/drm-tests/atomictest.c
new file mode 100644
index 0000000..19e6570
--- /dev/null
+++ b/drm-tests/atomictest.c
@@ -0,0 +1,1286 @@
+/*
+ * Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * This file performs some sanity checks on the DRM atomic API. To run a test, please run the
+ * following command:
+ *
+ * atomictest <testname>
+ *
+ * To get a list of possible tests, run:
+ *
+ * atomictest
+ */
+
+#include <getopt.h>
+
+#include "bs_drm.h"
+
+#define CHECK(cond) \
+ do { \
+ if (!(cond)) { \
+ bs_debug_error("check %s failed", #cond); \
+ return -1; \
+ } \
+ } while (0)
+
+#define CHECK_RESULT(ret) \
+ do { \
+ if ((ret) < 0) { \
+ bs_debug_error("failed with error: %d", ret); \
+ return -1; \
+ } \
+ } while (0)
+
+#define CURSOR_SIZE 64
+
+// TODO(gsingh) This is defined in upstream libdrm -- remove when CROS libdrm is updated.
+#ifndef DRM_ROTATE_0
+#define DRM_ROTATE_0 (1UL << 0)
+#endif
+
+#ifndef DRM_REFLECT_Y
+#define DRM_REFLECT_Y (1UL << 5)
+#endif
+
+static const uint32_t yuv_formats[] = {
+ DRM_FORMAT_NV12, DRM_FORMAT_YVU420,
+};
+
+static struct gbm_device *gbm = NULL;
+
+static void page_flip_handler(int fd, unsigned int sequence, unsigned int tv_sec,
+ unsigned int tv_usec, void *user_data)
+{
+ // Nothing to do.
+}
+
+struct atomictest_property {
+ uint32_t pid;
+ uint32_t value;
+};
+
+struct atomictest_plane {
+ drmModePlane drm_plane;
+ struct gbm_bo *bo;
+
+ uint32_t format_idx;
+
+ /* Properties. */
+ struct atomictest_property crtc_id;
+ struct atomictest_property crtc_x;
+ struct atomictest_property crtc_y;
+ struct atomictest_property crtc_w;
+ struct atomictest_property crtc_h;
+ struct atomictest_property fb_id;
+ struct atomictest_property src_x;
+ struct atomictest_property src_y;
+ struct atomictest_property src_w;
+ struct atomictest_property src_h;
+ struct atomictest_property type;
+ struct atomictest_property rotation;
+ struct atomictest_property ctm;
+};
+
+struct atomictest_connector {
+ uint32_t connector_id;
+ struct atomictest_property crtc_id;
+ struct atomictest_property edid;
+ struct atomictest_property dpms;
+};
+
+struct atomictest_crtc {
+ uint32_t crtc_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t *primary_idx;
+ uint32_t *cursor_idx;
+ uint32_t *overlay_idx;
+ uint32_t num_primary;
+ uint32_t num_cursor;
+ uint32_t num_overlay;
+
+ struct atomictest_plane *planes;
+ struct atomictest_property mode_id;
+ struct atomictest_property active;
+};
+
+struct atomictest_mode {
+ uint32_t height;
+ uint32_t width;
+ uint32_t id;
+};
+
+struct atomictest_context {
+ int fd;
+ uint32_t num_crtcs;
+ uint32_t num_connectors;
+ uint32_t num_modes;
+
+ struct atomictest_connector *connectors;
+ struct atomictest_crtc *crtcs;
+ struct atomictest_mode *modes;
+ drmModeAtomicReqPtr pset;
+ drmEventContext drm_event_ctx;
+
+ struct bs_mapper *mapper;
+};
+
+typedef int (*test_function)(struct atomictest_context *ctx, struct atomictest_crtc *crtc);
+
+struct atomictest_testcase {
+ const char *name;
+ test_function test_func;
+};
+
+// clang-format off
+enum draw_format_type {
+ DRAW_NONE = 0,
+ DRAW_STRIPE = 1,
+ DRAW_ELLIPSE = 2,
+ DRAW_CURSOR = 3,
+ DRAW_LINES = 4,
+};
+// clang-format on
+
+static int32_t get_format_idx(struct atomictest_plane *plane, uint32_t format)
+{
+ for (int32_t i = 0; i < plane->drm_plane.count_formats; i++)
+ if (plane->drm_plane.formats[i] == format)
+ return i;
+ return -1;
+}
+
+static void copy_drm_plane(drmModePlane *dest, drmModePlane *src)
+{
+ memcpy(dest, src, sizeof(drmModePlane));
+ dest->formats = calloc(src->count_formats, sizeof(uint32_t));
+ memcpy(dest->formats, src->formats, src->count_formats * sizeof(uint32_t));
+}
+
+static struct atomictest_plane *get_plane(struct atomictest_crtc *crtc, uint32_t idx, uint64_t type)
+{
+ uint32_t index;
+ switch (type) {
+ case DRM_PLANE_TYPE_OVERLAY:
+ index = crtc->overlay_idx[idx];
+ break;
+ case DRM_PLANE_TYPE_PRIMARY:
+ index = crtc->primary_idx[idx];
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ index = crtc->cursor_idx[idx];
+ break;
+ default:
+ bs_debug_error("invalid plane type returned");
+ return NULL;
+ }
+
+ return &crtc->planes[index];
+}
+
+static int draw_to_plane(struct bs_mapper *mapper, struct atomictest_plane *plane,
+ enum draw_format_type pattern)
+{
+ struct gbm_bo *bo = plane->bo;
+ uint32_t format = gbm_bo_get_format(bo);
+ const struct bs_draw_format *draw_format = bs_get_draw_format(format);
+
+ if (draw_format && pattern) {
+ switch (pattern) {
+ case DRAW_STRIPE:
+ CHECK(bs_draw_stripe(mapper, bo, draw_format));
+ break;
+ case DRAW_ELLIPSE:
+ CHECK(bs_draw_ellipse(mapper, bo, draw_format, 0));
+ break;
+ case DRAW_CURSOR:
+ CHECK(bs_draw_cursor(mapper, bo, draw_format));
+ break;
+ case DRAW_LINES:
+ CHECK(bs_draw_lines(mapper, bo, draw_format));
+ break;
+ default:
+ bs_debug_error("invalid draw type");
+ return -1;
+ }
+ } else {
+ // DRM_FORMAT_RGB565 --> red, DRM_FORMAT_BGR565 --> blue,
+ // everything else --> something
+ void *map_data;
+ uint16_t value = 0xF800;
+ void *addr = bs_mapper_map(mapper, bo, 0, &map_data);
+ uint32_t num_shorts = gbm_bo_get_plane_size(bo, 0) / sizeof(uint16_t);
+ uint16_t *pixel = (uint16_t *)addr;
+
+ CHECK(addr);
+ for (uint32_t i = 0; i < num_shorts; i++)
+ pixel[i] = value;
+
+ bs_mapper_unmap(mapper, bo, map_data);
+ }
+
+ return 0;
+}
+
+static int get_prop(int fd, drmModeObjectPropertiesPtr props, const char *name,
+ struct atomictest_property *bs_prop)
+{
+ /* Property ID should always be > 0. */
+ bs_prop->pid = 0;
+ drmModePropertyPtr prop;
+ for (uint32_t i = 0; i < props->count_props; i++) {
+ if (bs_prop->pid)
+ break;
+
+ prop = drmModeGetProperty(fd, props->props[i]);
+ if (prop) {
+ if (!strcmp(prop->name, name)) {
+ bs_prop->pid = prop->prop_id;
+ bs_prop->value = props->prop_values[i];
+ }
+ drmModeFreeProperty(prop);
+ }
+ }
+
+ return (bs_prop->pid == 0) ? -1 : 0;
+}
+
+static int get_connector_props(int fd, struct atomictest_connector *connector,
+ drmModeObjectPropertiesPtr props)
+{
+ CHECK_RESULT(get_prop(fd, props, "CRTC_ID", &connector->crtc_id));
+ CHECK_RESULT(get_prop(fd, props, "EDID", &connector->edid));
+ CHECK_RESULT(get_prop(fd, props, "DPMS", &connector->dpms));
+ return 0;
+}
+
+static int get_crtc_props(int fd, struct atomictest_crtc *crtc, drmModeObjectPropertiesPtr props)
+{
+ CHECK_RESULT(get_prop(fd, props, "MODE_ID", &crtc->mode_id));
+ CHECK_RESULT(get_prop(fd, props, "ACTIVE", &crtc->active));
+ return 0;
+}
+
+static int get_plane_props(int fd, struct atomictest_plane *plane, drmModeObjectPropertiesPtr props)
+{
+ CHECK_RESULT(get_prop(fd, props, "CRTC_ID", &plane->crtc_id));
+ CHECK_RESULT(get_prop(fd, props, "FB_ID", &plane->fb_id));
+ CHECK_RESULT(get_prop(fd, props, "CRTC_X", &plane->crtc_x));
+ CHECK_RESULT(get_prop(fd, props, "CRTC_Y", &plane->crtc_y));
+ CHECK_RESULT(get_prop(fd, props, "CRTC_W", &plane->crtc_w));
+ CHECK_RESULT(get_prop(fd, props, "CRTC_H", &plane->crtc_h));
+ CHECK_RESULT(get_prop(fd, props, "SRC_X", &plane->src_x));
+ CHECK_RESULT(get_prop(fd, props, "SRC_Y", &plane->src_y));
+ CHECK_RESULT(get_prop(fd, props, "SRC_W", &plane->src_w));
+ CHECK_RESULT(get_prop(fd, props, "SRC_H", &plane->src_h));
+ CHECK_RESULT(get_prop(fd, props, "type", &plane->type));
+
+ /*
+ * The atomic API makes no guarantee a property is present in object. This test
+ * requires the above common properties since a plane is undefined without them.
+ * Other properties (i.e: rotation and ctm) are optional.
+ */
+ get_prop(fd, props, "rotation", &plane->rotation);
+ get_prop(fd, props, "PLANE_CTM", &plane->ctm);
+ return 0;
+}
+
+int set_connector_props(struct atomictest_connector *conn, drmModeAtomicReqPtr pset)
+{
+ uint32_t id = conn->connector_id;
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, conn->crtc_id.pid, conn->crtc_id.value));
+ return 0;
+}
+
+int set_crtc_props(struct atomictest_crtc *crtc, drmModeAtomicReqPtr pset)
+{
+ uint32_t id = crtc->crtc_id;
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, crtc->mode_id.pid, crtc->mode_id.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, crtc->active.pid, crtc->active.value));
+ return 0;
+}
+
+int set_plane_props(struct atomictest_plane *plane, drmModeAtomicReqPtr pset)
+{
+ uint32_t id = plane->drm_plane.plane_id;
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->crtc_id.pid, plane->crtc_id.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->fb_id.pid, plane->fb_id.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->crtc_x.pid, plane->crtc_x.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->crtc_y.pid, plane->crtc_y.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->crtc_w.pid, plane->crtc_w.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->crtc_h.pid, plane->crtc_h.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->src_x.pid, plane->src_x.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->src_y.pid, plane->src_y.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->src_w.pid, plane->src_w.value));
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->src_h.pid, plane->src_h.value));
+ if (plane->rotation.pid)
+ CHECK_RESULT(
+ drmModeAtomicAddProperty(pset, id, plane->rotation.pid, plane->rotation.value));
+ if (plane->ctm.pid)
+ CHECK_RESULT(drmModeAtomicAddProperty(pset, id, plane->ctm.pid, plane->ctm.value));
+
+ return 0;
+}
+
+static int remove_plane_fb(struct atomictest_context *ctx, struct atomictest_plane *plane)
+{
+ if (plane->bo && plane->fb_id.value) {
+ CHECK_RESULT(drmModeRmFB(ctx->fd, plane->fb_id.value));
+ gbm_bo_destroy(plane->bo);
+ plane->bo = NULL;
+ plane->fb_id.value = 0;
+ }
+
+ return 0;
+}
+
+static int add_plane_fb(struct atomictest_context *ctx, struct atomictest_plane *plane)
+{
+ if (plane->format_idx < plane->drm_plane.count_formats) {
+ CHECK_RESULT(remove_plane_fb(ctx, plane));
+ uint32_t flags = (plane->type.value == DRM_PLANE_TYPE_CURSOR) ? GBM_BO_USE_CURSOR
+ : GBM_BO_USE_SCANOUT;
+ flags |= GBM_BO_USE_SW_WRITE_RARELY;
+ /* TODO(gsingh): add create with modifiers option. */
+ plane->bo = gbm_bo_create(gbm, plane->crtc_w.value, plane->crtc_h.value,
+ plane->drm_plane.formats[plane->format_idx], flags);
+
+ CHECK(plane->bo);
+ plane->fb_id.value = bs_drm_fb_create_gbm(plane->bo);
+ CHECK(plane->fb_id.value);
+ CHECK_RESULT(set_plane_props(plane, ctx->pset));
+ }
+
+ return 0;
+}
+
+static int init_plane(struct atomictest_context *ctx, struct atomictest_plane *plane,
+ uint32_t format, uint32_t x, uint32_t y, uint32_t w, uint32_t h,
+ uint32_t crtc_id)
+{
+ int32_t idx = get_format_idx(plane, format);
+ if (idx < 0)
+ return -EINVAL;
+
+ plane->format_idx = idx;
+ plane->crtc_x.value = x;
+ plane->crtc_y.value = y;
+ plane->crtc_w.value = w;
+ plane->crtc_h.value = h;
+ plane->src_w.value = plane->crtc_w.value << 16;
+ plane->src_h.value = plane->crtc_h.value << 16;
+ plane->crtc_id.value = crtc_id;
+
+ CHECK_RESULT(add_plane_fb(ctx, plane));
+ return 0;
+}
+
+static int init_yuv_plane(struct atomictest_context *ctx, struct atomictest_plane *plane,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t crtc_id)
+{
+ uint32_t i;
+ for (i = 0; i < BS_ARRAY_LEN(yuv_formats); i++)
+ if (!init_plane(ctx, plane, yuv_formats[i], x, y, w, h, crtc_id))
+ return 0;
+
+ return -EINVAL;
+}
+
+static int disable_plane(struct atomictest_context *ctx, struct atomictest_plane *plane)
+{
+ plane->format_idx = 0;
+ plane->crtc_x.value = 0;
+ plane->crtc_y.value = 0;
+ plane->crtc_w.value = 0;
+ plane->crtc_h.value = 0;
+ plane->src_w.value = 0;
+ plane->src_h.value = 0;
+ plane->crtc_id.value = 0;
+
+ if (plane->rotation.pid)
+ plane->rotation.value = DRM_ROTATE_0;
+ if (plane->ctm.pid)
+ plane->ctm.value = 0;
+
+ CHECK_RESULT(remove_plane_fb(ctx, plane));
+ CHECK_RESULT(set_plane_props(plane, ctx->pset));
+ return 0;
+}
+
+static int move_plane(struct atomictest_context *ctx, struct atomictest_crtc *crtc,
+ struct atomictest_plane *plane, uint32_t dx, uint32_t dy)
+{
+ if (plane->crtc_x.value < (crtc->width - plane->crtc_w.value) &&
+ plane->crtc_y.value < (crtc->height - plane->crtc_h.value)) {
+ plane->crtc_x.value += dx;
+ plane->crtc_y.value += dy;
+ CHECK_RESULT(set_plane_props(plane, ctx->pset));
+ return 0;
+ }
+
+ return -1;
+}
+
+static int scale_plane(struct atomictest_context *ctx, struct atomictest_crtc *crtc,
+ struct atomictest_plane *plane, float dw, float dh)
+{
+ int32_t plane_w = (int32_t)plane->crtc_w.value + dw * plane->crtc_w.value;
+ int32_t plane_h = (int32_t)plane->crtc_h.value + dh * plane->crtc_h.value;
+ if (plane_w > 0 && plane_h > 0 && (plane->crtc_x.value + plane_w < crtc->width) &&
+ (plane->crtc_h.value + plane_h < crtc->height)) {
+ plane->crtc_w.value = BS_ALIGN((uint32_t)plane_w, 2);
+ plane->crtc_h.value = BS_ALIGN((uint32_t)plane_h, 2);
+ CHECK_RESULT(set_plane_props(plane, ctx->pset));
+ return 0;
+ }
+
+ return -1;
+}
+
+static void log(struct atomictest_context *ctx)
+{
+ printf("Committing the following configuration: \n");
+ for (uint32_t i = 0; i < ctx->num_crtcs; i++) {
+ struct atomictest_plane *plane;
+ struct atomictest_crtc *crtc = &ctx->crtcs[i];
+ uint32_t num_planes = crtc->num_primary + crtc->num_cursor + crtc->num_overlay;
+ if (!crtc->active.value)
+ continue;
+
+ printf("----- [CRTC: %u] -----\n", crtc->crtc_id);
+ for (uint32_t j = 0; j < num_planes; j++) {
+ plane = &crtc->planes[j];
+ if (plane->crtc_id.value == crtc->crtc_id && plane->fb_id.value) {
+ uint32_t format = gbm_bo_get_format(plane->bo);
+ char *fourcc = (char *)&format;
+ printf("\t{Plane ID: %u, ", plane->drm_plane.plane_id);
+ printf("Plane format: %c%c%c%c, ", fourcc[0], fourcc[1], fourcc[2],
+ fourcc[3]);
+ printf("Plane type: ");
+ switch (plane->type.value) {
+ case DRM_PLANE_TYPE_OVERLAY:
+ printf("overlay, ");
+ break;
+ case DRM_PLANE_TYPE_PRIMARY:
+ printf("primary, ");
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ printf("cursor, ");
+ break;
+ }
+
+ printf("CRTC_X: %u, CRTC_Y: %u, CRTC_W: %u, CRTC_H: %u}\n",
+ plane->crtc_x.value, plane->crtc_y.value,
+ plane->crtc_w.value, plane->crtc_h.value);
+ }
+ }
+ }
+}
+
+static int test_commit(struct atomictest_context *ctx)
+{
+ return drmModeAtomicCommit(ctx->fd, ctx->pset, DRM_MODE_ATOMIC_TEST_ONLY, NULL);
+}
+
+static int commit(struct atomictest_context *ctx)
+{
+ int ret;
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(ctx->fd, &fds);
+
+ log(ctx);
+ ret = drmModeAtomicCommit(ctx->fd, ctx->pset,
+ DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+ CHECK_RESULT(ret);
+ do {
+ ret = select(ctx->fd + 1, &fds, NULL, NULL, NULL);
+ } while (ret == -1 && errno == EINTR);
+
+ CHECK_RESULT(ret);
+ if (FD_ISSET(ctx->fd, &fds))
+ drmHandleEvent(ctx->fd, &ctx->drm_event_ctx);
+
+ return 0;
+}
+
+static int pageflip_formats(struct atomictest_context *ctx, struct atomictest_crtc *crtc,
+ struct atomictest_plane *plane)
+{
+ uint32_t flags;
+ for (uint32_t i = 0; i < plane->drm_plane.count_formats; i++) {
+ flags = (plane->type.value == DRM_PLANE_TYPE_CURSOR) ? GBM_BO_USE_CURSOR
+ : GBM_BO_USE_SCANOUT;
+ if (!gbm_device_is_format_supported(gbm, plane->drm_plane.formats[i], flags))
+ continue;
+
+ CHECK_RESULT(init_plane(ctx, plane, plane->drm_plane.formats[i], 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+ CHECK_RESULT(draw_to_plane(ctx->mapper, plane, DRAW_ELLIPSE));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ // disable, but don't commit, since we can't have an active CRTC without any planes.
+ CHECK_RESULT(disable_plane(ctx, plane));
+ }
+
+ return 0;
+}
+
+static uint32_t get_connection(struct atomictest_crtc *crtc, uint32_t crtc_index)
+{
+ uint32_t connector_id = 0;
+ uint32_t crtc_mask = 1u << crtc_index;
+ struct bs_drm_pipe pipe = { 0 };
+ struct bs_drm_pipe_plumber *plumber = bs_drm_pipe_plumber_new();
+ bs_drm_pipe_plumber_crtc_mask(plumber, crtc_mask);
+ if (bs_drm_pipe_plumber_make(plumber, &pipe))
+ connector_id = pipe.connector_id;
+
+ bs_drm_pipe_plumber_destroy(&plumber);
+ return connector_id;
+}
+
+static int enable_crtc(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ drmModeAtomicSetCursor(ctx->pset, 0);
+
+ for (uint32_t i = 0; i < ctx->num_connectors; i++) {
+ ctx->connectors[i].crtc_id.value = 0;
+ set_connector_props(&ctx->connectors[i], ctx->pset);
+ }
+
+ for (uint32_t i = 0; i < ctx->num_crtcs; i++) {
+ if (&ctx->crtcs[i] == crtc) {
+ uint32_t connector_id = get_connection(crtc, i);
+ CHECK(connector_id);
+ for (uint32_t j = 0; j < ctx->num_connectors; j++) {
+ if (connector_id == ctx->connectors[j].connector_id) {
+ ctx->connectors[j].crtc_id.value = crtc->crtc_id;
+ set_connector_props(&ctx->connectors[j], ctx->pset);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+
+ int ret = -EINVAL;
+ int cursor = drmModeAtomicGetCursor(ctx->pset);
+
+ for (uint32_t i = 0; i < ctx->num_modes; i++) {
+ struct atomictest_mode *mode = &ctx->modes[i];
+ drmModeAtomicSetCursor(ctx->pset, cursor);
+
+ crtc->mode_id.value = mode->id;
+ crtc->active.value = 1;
+ crtc->width = mode->width;
+ crtc->height = mode->height;
+
+ set_crtc_props(crtc, ctx->pset);
+ ret = drmModeAtomicCommit(ctx->fd, ctx->pset,
+ DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET,
+ NULL);
+ if (!ret)
+ return 0;
+ }
+
+ bs_debug_error("[CRTC:%d]: failed to find mode", crtc->crtc_id);
+ return ret;
+}
+
+static int disable_crtc(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ for (uint32_t i = 0; i < ctx->num_connectors; i++) {
+ ctx->connectors[i].crtc_id.value = 0;
+ set_connector_props(&ctx->connectors[i], ctx->pset);
+ }
+
+ crtc->mode_id.value = 0;
+ crtc->active.value = 0;
+ set_crtc_props(crtc, ctx->pset);
+ int ret = drmModeAtomicCommit(ctx->fd, ctx->pset, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+ CHECK_RESULT(ret);
+ return ret;
+}
+
+static struct atomictest_context *new_context(uint32_t num_connectors, uint32_t num_crtcs,
+ uint32_t num_planes)
+{
+ struct atomictest_context *ctx = calloc(1, sizeof(*ctx));
+
+ ctx->mapper = bs_mapper_gem_new();
+ if (ctx->mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ free(ctx);
+ return NULL;
+ }
+
+ ctx->connectors = calloc(num_connectors, sizeof(*ctx->connectors));
+ ctx->crtcs = calloc(num_crtcs, sizeof(*ctx->crtcs));
+ for (uint32_t i = 0; i < num_crtcs; i++) {
+ ctx->crtcs[i].planes = calloc(num_planes, sizeof(*ctx->crtcs[i].planes));
+ ctx->crtcs[i].overlay_idx = calloc(num_planes, sizeof(uint32_t));
+ ctx->crtcs[i].primary_idx = calloc(num_planes, sizeof(uint32_t));
+ ctx->crtcs[i].cursor_idx = calloc(num_planes, sizeof(uint32_t));
+ }
+
+ ctx->num_connectors = num_connectors;
+ ctx->num_crtcs = num_crtcs;
+ ctx->num_modes = 0;
+ ctx->modes = NULL;
+ ctx->pset = drmModeAtomicAlloc();
+ ctx->drm_event_ctx.version = DRM_EVENT_CONTEXT_VERSION;
+ ctx->drm_event_ctx.page_flip_handler = page_flip_handler;
+
+ return ctx;
+}
+
+static void free_context(struct atomictest_context *ctx)
+{
+ for (uint32_t i = 0; i < ctx->num_crtcs; i++) {
+ uint32_t num_planes = ctx->crtcs[i].num_primary + ctx->crtcs[i].num_cursor +
+ ctx->crtcs[i].num_overlay;
+
+ for (uint32_t j = 0; j < num_planes; j++) {
+ remove_plane_fb(ctx, &ctx->crtcs[i].planes[j]);
+ free(ctx->crtcs[i].planes[j].drm_plane.formats);
+ }
+
+ free(ctx->crtcs[i].planes);
+ free(ctx->crtcs[i].overlay_idx);
+ free(ctx->crtcs[i].cursor_idx);
+ free(ctx->crtcs[i].primary_idx);
+ }
+
+ drmModeAtomicFree(ctx->pset);
+ free(ctx->modes);
+ free(ctx->crtcs);
+ free(ctx->connectors);
+ bs_mapper_destroy(ctx->mapper);
+ free(ctx);
+}
+
+static struct atomictest_context *query_kms(int fd)
+{
+ drmModeRes *res = drmModeGetResources(fd);
+ if (res == NULL) {
+ bs_debug_error("failed to get drm resources");
+ return false;
+ }
+
+ drmModePlaneRes *plane_res = drmModeGetPlaneResources(fd);
+ if (plane_res == NULL) {
+ bs_debug_error("failed to get plane resources");
+ drmModeFreeResources(res);
+ return NULL;
+ }
+
+ struct atomictest_context *ctx =
+ new_context(res->count_connectors, res->count_crtcs, plane_res->count_planes);
+ if (ctx == NULL) {
+ bs_debug_error("failed to allocate atomic context");
+ drmModeFreePlaneResources(plane_res);
+ drmModeFreeResources(res);
+ return NULL;
+ }
+
+ ctx->fd = fd;
+ drmModeObjectPropertiesPtr props = NULL;
+
+ for (uint32_t conn_index = 0; conn_index < res->count_connectors; conn_index++) {
+ uint32_t conn_id = res->connectors[conn_index];
+ ctx->connectors[conn_index].connector_id = conn_id;
+ props = drmModeObjectGetProperties(fd, conn_id, DRM_MODE_OBJECT_CONNECTOR);
+ get_connector_props(fd, &ctx->connectors[conn_index], props);
+
+ drmModeConnector *connector = drmModeGetConnector(fd, conn_id);
+ for (uint32_t mode_index = 0; mode_index < connector->count_modes; mode_index++) {
+ ctx->modes =
+ realloc(ctx->modes, (ctx->num_modes + 1) * sizeof(*ctx->modes));
+ drmModeCreatePropertyBlob(fd, &connector->modes[mode_index],
+ sizeof(drmModeModeInfo),
+ &ctx->modes[ctx->num_modes].id);
+ ctx->modes[ctx->num_modes].width = connector->modes[mode_index].hdisplay;
+ ctx->modes[ctx->num_modes].height = connector->modes[mode_index].vdisplay;
+ ctx->num_modes++;
+ }
+
+ drmModeFreeConnector(connector);
+ drmModeFreeObjectProperties(props);
+ props = NULL;
+ }
+
+ uint32_t crtc_index;
+ for (crtc_index = 0; crtc_index < res->count_crtcs; crtc_index++) {
+ ctx->crtcs[crtc_index].crtc_id = res->crtcs[crtc_index];
+ props =
+ drmModeObjectGetProperties(fd, res->crtcs[crtc_index], DRM_MODE_OBJECT_CRTC);
+ get_crtc_props(fd, &ctx->crtcs[crtc_index], props);
+
+ drmModeFreeObjectProperties(props);
+ props = NULL;
+ }
+
+ uint32_t overlay_idx, primary_idx, cursor_idx, idx;
+
+ for (uint32_t plane_index = 0; plane_index < plane_res->count_planes; plane_index++) {
+ drmModePlane *plane = drmModeGetPlane(fd, plane_res->planes[plane_index]);
+ if (plane == NULL) {
+ bs_debug_error("failed to get plane id %u", plane_res->planes[plane_index]);
+ continue;
+ }
+
+ uint32_t crtc_mask = 0;
+
+ drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(
+ fd, plane_res->planes[plane_index], DRM_MODE_OBJECT_PLANE);
+
+ for (crtc_index = 0; crtc_index < res->count_crtcs; crtc_index++) {
+ crtc_mask = (1 << crtc_index);
+ if (plane->possible_crtcs & crtc_mask) {
+ struct atomictest_crtc *crtc = &ctx->crtcs[crtc_index];
+ cursor_idx = crtc->num_cursor;
+ primary_idx = crtc->num_primary;
+ overlay_idx = crtc->num_overlay;
+ idx = cursor_idx + primary_idx + overlay_idx;
+ copy_drm_plane(&crtc->planes[idx].drm_plane, plane);
+ get_plane_props(fd, &crtc->planes[idx], props);
+ switch (crtc->planes[idx].type.value) {
+ case DRM_PLANE_TYPE_OVERLAY:
+ crtc->overlay_idx[overlay_idx] = idx;
+ crtc->num_overlay++;
+ break;
+ case DRM_PLANE_TYPE_PRIMARY:
+ crtc->primary_idx[primary_idx] = idx;
+ crtc->num_primary++;
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ crtc->cursor_idx[cursor_idx] = idx;
+ crtc->num_cursor++;
+ break;
+ default:
+ bs_debug_error("invalid plane type returned");
+ return NULL;
+ }
+
+ /*
+ * The DRM UAPI states that cursor and overlay framebuffers may be
+ * present after a CRTC disable, so zero this out so we can get a
+ * clean slate.
+ */
+ crtc->planes[idx].fb_id.value = 0;
+ }
+ }
+
+ drmModeFreePlane(plane);
+ drmModeFreeObjectProperties(props);
+ props = NULL;
+ }
+
+ drmModeFreePlaneResources(plane_res);
+ drmModeFreeResources(res);
+ return ctx;
+}
+
+static int test_multiple_planes(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *primary, *overlay, *cursor;
+ for (uint32_t i = 0; i < crtc->num_primary; i++) {
+ bool video = true;
+ uint32_t x, y;
+ for (uint32_t j = 0; j < crtc->num_overlay; j++) {
+ overlay = get_plane(crtc, j, DRM_PLANE_TYPE_OVERLAY);
+ x = crtc->width >> (j + 2);
+ y = crtc->height >> (j + 2);
+ // drmModeAddFB2 requires the height and width are even for sub-sampled YUV
+ // formats.
+ x = BS_ALIGN(x, 2);
+ y = BS_ALIGN(y, 2);
+ if (video && !init_yuv_plane(ctx, overlay, x, y, x, y, crtc->crtc_id)) {
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_STRIPE));
+ video = false;
+ } else {
+ CHECK_RESULT(init_plane(ctx, overlay, DRM_FORMAT_XRGB8888, x, y, x,
+ y, crtc->crtc_id));
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_LINES));
+ }
+ }
+
+ for (uint32_t j = 0; j < crtc->num_cursor; j++) {
+ x = crtc->width >> (j + 2);
+ y = crtc->height >> (j + 2);
+ cursor = get_plane(crtc, j, DRM_PLANE_TYPE_CURSOR);
+ CHECK_RESULT(init_plane(ctx, cursor, DRM_FORMAT_ARGB8888, x, y, CURSOR_SIZE,
+ CURSOR_SIZE, crtc->crtc_id));
+ CHECK_RESULT(draw_to_plane(ctx->mapper, cursor, DRAW_CURSOR));
+ }
+
+ primary = get_plane(crtc, i, DRM_PLANE_TYPE_PRIMARY);
+ CHECK_RESULT(init_plane(ctx, primary, DRM_FORMAT_XRGB8888, 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+ CHECK_RESULT(draw_to_plane(ctx->mapper, primary, DRAW_ELLIPSE));
+
+ uint32_t num_planes = crtc->num_primary + crtc->num_cursor + crtc->num_overlay;
+ int done = 0;
+ struct atomictest_plane *plane;
+ while (!done) {
+ done = 1;
+ for (uint32_t j = 0; j < num_planes; j++) {
+ plane = &crtc->planes[j];
+ if (plane->type.value != DRM_PLANE_TYPE_PRIMARY)
+ done &= move_plane(ctx, crtc, plane, 20, 20);
+ }
+
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6 / 60);
+ }
+
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ /* Disable primary plane and verify overlays show up. */
+ CHECK_RESULT(disable_plane(ctx, primary));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ return 0;
+}
+
+static int test_video_overlay(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *overlay;
+ for (uint32_t i = 0; i < crtc->num_overlay; i++) {
+ overlay = get_plane(crtc, i, DRM_PLANE_TYPE_OVERLAY);
+ if (init_yuv_plane(ctx, overlay, 0, 0, 800, 800, crtc->crtc_id))
+ continue;
+
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_STRIPE));
+ while (!move_plane(ctx, crtc, overlay, 20, 20)) {
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6 / 60);
+ }
+ }
+
+ return 0;
+}
+
+static int test_orientation(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *primary, *overlay;
+ for (uint32_t i = 0; i < crtc->num_overlay; i++) {
+ overlay = get_plane(crtc, i, DRM_PLANE_TYPE_OVERLAY);
+ if (!overlay->rotation.pid)
+ continue;
+
+ CHECK_RESULT(init_plane(ctx, overlay, DRM_FORMAT_XRGB8888, 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+
+ overlay->rotation.value = DRM_ROTATE_0;
+ set_plane_props(overlay, ctx->pset);
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_LINES));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ overlay->rotation.value = DRM_REFLECT_Y;
+ set_plane_props(overlay, ctx->pset);
+ if (!test_commit(ctx)) {
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ CHECK_RESULT(disable_plane(ctx, overlay));
+ }
+
+ for (uint32_t i = 0; i < crtc->num_primary; i++) {
+ primary = get_plane(crtc, i, DRM_PLANE_TYPE_PRIMARY);
+ if (!primary->rotation.pid)
+ continue;
+
+ CHECK_RESULT(init_plane(ctx, primary, DRM_FORMAT_XRGB8888, 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+
+ primary->rotation.value = DRM_ROTATE_0;
+ set_plane_props(primary, ctx->pset);
+ CHECK_RESULT(draw_to_plane(ctx->mapper, primary, DRAW_LINES));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ primary->rotation.value = DRM_REFLECT_Y;
+ set_plane_props(primary, ctx->pset);
+ if (!test_commit(ctx)) {
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ CHECK_RESULT(disable_plane(ctx, primary));
+ }
+
+ return 0;
+}
+
+static int test_plane_ctm(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *primary, *overlay;
+ /*
+ * The blob for the PLANE_CTM propery is a drm_color_ctm.
+ * drm_color_ctm contains a 3x3 u64 matrix, where every element
+ * represents a S31.32 fixed point number.
+ */
+ int64_t identity_ctm[9] = { 0x100000000, 0x0, 0x0, 0x0, 0x100000000,
+ 0x0, 0x0, 0x0, 0x100000000 };
+ int64_t red_shift_ctm[9] = { 0x140000000, 0x0, 0x0, 0x0, 0xC0000000,
+ 0x0, 0x0, 0x0, 0xC0000000 };
+
+ for (uint32_t i = 0; i < crtc->num_overlay; i++) {
+ overlay = get_plane(crtc, i, DRM_PLANE_TYPE_OVERLAY);
+ if (!overlay->ctm.pid)
+ continue;
+
+ CHECK_RESULT(init_plane(ctx, overlay, DRM_FORMAT_XRGB8888, 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+
+ CHECK_RESULT(drmModeCreatePropertyBlob(ctx->fd, identity_ctm, sizeof(identity_ctm),
+ &overlay->ctm.value));
+ set_plane_props(overlay, ctx->pset);
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_LINES));
+ CHECK_RESULT(commit(ctx));
+ CHECK_RESULT(drmModeDestroyPropertyBlob(ctx->fd, overlay->ctm.value));
+ usleep(1e6);
+
+ CHECK_RESULT(drmModeCreatePropertyBlob(ctx->fd, red_shift_ctm,
+ sizeof(red_shift_ctm), &overlay->ctm.value));
+ set_plane_props(overlay, ctx->pset);
+ CHECK_RESULT(commit(ctx));
+ CHECK_RESULT(drmModeDestroyPropertyBlob(ctx->fd, overlay->ctm.value));
+ usleep(1e6);
+
+ CHECK_RESULT(disable_plane(ctx, overlay));
+ }
+
+ for (uint32_t i = 0; i < crtc->num_primary; i++) {
+ primary = get_plane(crtc, i, DRM_PLANE_TYPE_PRIMARY);
+ if (!primary->ctm.pid)
+ continue;
+
+ CHECK_RESULT(init_plane(ctx, primary, DRM_FORMAT_XRGB8888, 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+ CHECK_RESULT(drmModeCreatePropertyBlob(ctx->fd, identity_ctm, sizeof(identity_ctm),
+ &primary->ctm.value));
+ set_plane_props(primary, ctx->pset);
+ CHECK_RESULT(draw_to_plane(ctx->mapper, primary, DRAW_LINES));
+ CHECK_RESULT(commit(ctx));
+ CHECK_RESULT(drmModeDestroyPropertyBlob(ctx->fd, primary->ctm.value));
+ usleep(1e6);
+
+ CHECK_RESULT(drmModeCreatePropertyBlob(ctx->fd, red_shift_ctm,
+ sizeof(red_shift_ctm), &primary->ctm.value));
+ set_plane_props(primary, ctx->pset);
+ CHECK_RESULT(commit(ctx));
+ CHECK_RESULT(drmModeDestroyPropertyBlob(ctx->fd, primary->ctm.value));
+ usleep(1e6);
+
+ CHECK_RESULT(disable_plane(ctx, primary));
+ }
+
+ return 0;
+}
+
+static int test_fullscreen_video(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *primary;
+ for (uint32_t i = 0; i < crtc->num_primary; i++) {
+ primary = get_plane(crtc, i, DRM_PLANE_TYPE_PRIMARY);
+ if (init_yuv_plane(ctx, primary, 0, 0, crtc->width, crtc->height, crtc->crtc_id))
+ continue;
+
+ CHECK_RESULT(draw_to_plane(ctx->mapper, primary, DRAW_STRIPE));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ return 0;
+}
+
+static int test_disable_primary(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *primary, *overlay;
+ for (uint32_t i = 0; i < crtc->num_primary; i++) {
+ for (uint32_t j = 0; j < crtc->num_overlay; j++) {
+ overlay = get_plane(crtc, j, DRM_PLANE_TYPE_OVERLAY);
+ uint32_t x = crtc->width >> (j + 2);
+ uint32_t y = crtc->height >> (j + 2);
+ CHECK_RESULT(init_plane(ctx, overlay, DRM_FORMAT_XRGB8888, x, y, x, y,
+ crtc->crtc_id));
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_LINES));
+ }
+
+ primary = get_plane(crtc, i, DRM_PLANE_TYPE_PRIMARY);
+ CHECK_RESULT(init_plane(ctx, primary, DRM_FORMAT_XRGB8888, 0, 0, crtc->width,
+ crtc->height, crtc->crtc_id));
+ CHECK_RESULT(draw_to_plane(ctx->mapper, primary, DRAW_ELLIPSE));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ /* Disable primary plane. */
+ disable_plane(ctx, primary);
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ return 0;
+}
+
+static int test_overlay_pageflip(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *overlay;
+ for (uint32_t i = 0; i < crtc->num_overlay; i++) {
+ overlay = get_plane(crtc, i, DRM_PLANE_TYPE_OVERLAY);
+ CHECK_RESULT(pageflip_formats(ctx, crtc, overlay));
+ }
+
+ return 0;
+}
+
+static int test_overlay_downscaling(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *overlay;
+ uint32_t w = BS_ALIGN(crtc->width / 2, 2);
+ uint32_t h = BS_ALIGN(crtc->height / 2, 2);
+ for (uint32_t i = 0; i < crtc->num_overlay; i++) {
+ overlay = get_plane(crtc, i, DRM_PLANE_TYPE_OVERLAY);
+ if (init_yuv_plane(ctx, overlay, 0, 0, w, h, crtc->crtc_id)) {
+ CHECK_RESULT(init_plane(ctx, overlay, DRM_FORMAT_XRGB8888, 0, 0, w, h,
+ crtc->crtc_id));
+ }
+
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_LINES));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ while (!scale_plane(ctx, crtc, overlay, -.1f, -.1f) && !test_commit(ctx)) {
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ disable_plane(ctx, overlay);
+ }
+
+ return 0;
+}
+
+static int test_overlay_upscaling(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *overlay;
+ uint32_t w = BS_ALIGN(crtc->width / 4, 2);
+ uint32_t h = BS_ALIGN(crtc->height / 4, 2);
+ for (uint32_t i = 0; i < crtc->num_overlay; i++) {
+ overlay = get_plane(crtc, i, DRM_PLANE_TYPE_OVERLAY);
+ if (init_yuv_plane(ctx, overlay, 0, 0, w, h, crtc->crtc_id)) {
+ CHECK_RESULT(init_plane(ctx, overlay, DRM_FORMAT_XRGB8888, 0, 0, w, h,
+ crtc->crtc_id));
+ }
+
+ CHECK_RESULT(draw_to_plane(ctx->mapper, overlay, DRAW_LINES));
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+
+ while (!scale_plane(ctx, crtc, overlay, .1f, .1f) && !test_commit(ctx)) {
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6);
+ }
+
+ disable_plane(ctx, overlay);
+ }
+
+ return 0;
+}
+
+static int test_primary_pageflip(struct atomictest_context *ctx, struct atomictest_crtc *crtc)
+{
+ struct atomictest_plane *primary;
+ for (uint32_t i = 0; i < crtc->num_primary; i++) {
+ primary = get_plane(crtc, i, DRM_PLANE_TYPE_PRIMARY);
+ CHECK_RESULT(pageflip_formats(ctx, crtc, primary));
+ }
+
+ return 0;
+}
+
+static const struct atomictest_testcase cases[] = {
+ { "disable_primary", test_disable_primary },
+ { "fullscreen_video", test_fullscreen_video },
+ { "multiple_planes", test_multiple_planes },
+ { "overlay_pageflip", test_overlay_pageflip },
+ { "overlay_downscaling", test_overlay_downscaling },
+ { "overlay_upscaling", test_overlay_upscaling },
+ { "primary_pageflip", test_primary_pageflip },
+ { "video_overlay", test_video_overlay },
+ { "orientation", test_orientation },
+ /* CTM stands for Color Transform Matrix. */
+ { "plane_ctm", test_plane_ctm },
+};
+
+static int run_testcase(struct atomictest_context *ctx, struct atomictest_crtc *crtc,
+ test_function func)
+{
+ int cursor = drmModeAtomicGetCursor(ctx->pset);
+ uint32_t num_planes = crtc->num_primary + crtc->num_cursor + crtc->num_overlay;
+
+ int ret = func(ctx, crtc);
+
+ for (uint32_t i = 0; i < num_planes; i++)
+ disable_plane(ctx, &crtc->planes[i]);
+
+ drmModeAtomicSetCursor(ctx->pset, cursor);
+
+ CHECK_RESULT(commit(ctx));
+ usleep(1e6 / 60);
+
+ return ret;
+}
+
+static int run_atomictest(const char *name, uint32_t crtc_mask)
+{
+ int ret = 0;
+ uint32_t num_run = 0;
+ int fd = bs_drm_open_main_display();
+ CHECK_RESULT(fd);
+
+ gbm = gbm_create_device(fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm device");
+ ret = -1;
+ goto destroy_fd;
+ }
+
+ ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+ if (ret) {
+ bs_debug_error("failed to enable DRM_CLIENT_CAP_UNIVERSAL_PLANES");
+ ret = -1;
+ goto destroy_gbm_device;
+ }
+
+ ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
+ if (ret) {
+ bs_debug_error("failed to enable DRM_CLIENT_CAP_ATOMIC");
+ ret = -1;
+ goto destroy_gbm_device;
+ }
+
+ struct atomictest_context *ctx = query_kms(fd);
+ if (!ctx) {
+ bs_debug_error("querying atomictest failed.");
+ ret = -1;
+ goto destroy_gbm_device;
+ }
+
+ struct atomictest_crtc *crtc;
+ for (uint32_t crtc_index = 0; crtc_index < ctx->num_crtcs; crtc_index++) {
+ crtc = &ctx->crtcs[crtc_index];
+ if (!((1 << crtc_index) & crtc_mask))
+ continue;
+
+ for (uint32_t i = 0; i < BS_ARRAY_LEN(cases); i++) {
+ if (strcmp(cases[i].name, name) && strcmp("all", name))
+ continue;
+
+ num_run++;
+ ret = enable_crtc(ctx, crtc);
+ if (ret)
+ goto out;
+
+ ret = run_testcase(ctx, crtc, cases[i].test_func);
+ if (ret)
+ goto out;
+
+ ret = disable_crtc(ctx, crtc);
+ if (ret)
+ goto out;
+ }
+ }
+
+ ret = (num_run == 0);
+
+out:
+ free_context(ctx);
+destroy_gbm_device:
+ gbm_device_destroy(gbm);
+destroy_fd:
+ close(fd);
+
+ return ret;
+}
+
+static const struct option longopts[] = {
+ { "crtc", required_argument, NULL, 'c' },
+ { "test_name", required_argument, NULL, 't' },
+ { "help", no_argument, NULL, 'h' },
+ { 0, 0, 0, 0 },
+};
+
+static void print_help(const char *argv0)
+{
+ printf("usage: %s -t <test_name> -c <crtc_index>\n", argv0);
+ printf("A valid name test is one the following:\n");
+ for (uint32_t i = 0; i < BS_ARRAY_LEN(cases); i++)
+ printf("%s\n", cases[i].name);
+ printf("all\n");
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ char *name = NULL;
+ int32_t crtc_idx = -1;
+ uint32_t crtc_mask = ~0;
+ while ((c = getopt_long(argc, argv, "c:t:h", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'c':
+ if (sscanf(optarg, "%d", &crtc_idx) != 1)
+ goto print;
+ break;
+ case 't':
+ if (name) {
+ free(name);
+ name = NULL;
+ }
+
+ name = strdup(optarg);
+ break;
+ case 'h':
+ goto print;
+ default:
+ goto print;
+ }
+ }
+
+ if (!name)
+ goto print;
+
+ if (crtc_idx >= 0)
+ crtc_mask = 1 << crtc_idx;
+
+ int ret = run_atomictest(name, crtc_mask);
+ if (ret == 0)
+ printf("[ PASSED ] atomictest.%s\n", name);
+ else if (ret < 0)
+ printf("[ FAILED ] atomictest.%s\n", name);
+
+ free(name);
+
+ if (ret > 0)
+ goto print;
+
+ return ret;
+
+print:
+ print_help(argv[0]);
+ return 0;
+}
diff --git a/drm-tests/bsdrm/README.md b/drm-tests/bsdrm/README.md
new file mode 100644
index 0000000..887ca2a
--- /dev/null
+++ b/drm-tests/bsdrm/README.md
@@ -0,0 +1,88 @@
+BS DRM
+===
+
+BS DRM is a library to factor out most of the boilerplate found in programs
+written against DRM.
+
+Dependencies
+---
+- Clang or GCC with `-std=gnu99`
+- `libgbm`
+- `libdrm`
+
+Features
+---
+- Opens DRM devices
+- Creates display pipelines
+- Allocates buffer objects with framebuffers
+- Dumb maps buffer objects
+
+Goals
+---
+(in order of priority)
+
+- Useful
+- Consistent and guessable API
+- Easy to read
+- Code is documentation
+- 0 warnings from Clang or GCC
+- Make errors easy to debug
+- Fun
+
+Code Philosophy
+---
+- This library is meant to be consumed by applications, not other libraries
+- Applications usually can't recover from errors
+- Corollary: returning error codes to applications is a waste of programmer time
+- Badly behaving applications should fail fast
+- Asserts fail fast
+- Use asserts to make bad applications fail fast
+- Make code style consistent with clang-format
+
+Naming Rules
+---
+- File names: <module name>.c
+- Functions: start with bs\_<name of file with extension>\_
+- Constructor functions: suffix with "new"
+- Destructor functions: suffix with "destroy"
+- Conversion functions: suffix with "create"
+- Functions that overload: put overload name at the very end
+
+Build Example
+---
+Define `CC` `CFLAGS` and `LDFLAGS` for your device. Then run the following from the top level directory:
+```
+${CC} ${CFLAGS} -c src/drm_fb.c -o drm_fb.o || exit 1
+${CC} ${CFLAGS} -c src/drm_open.c -o drm_open.o || exit 1
+${CC} ${CFLAGS} -c src/drm_pipe.c -o drm_pipe.o || exit 1
+${CC} ${CFLAGS} -c src/pipe.c -o pipe.o || exit 1
+${CC} ${CFLAGS} -c example/stripe.c -o stripe.o || exit 1
+${CC} ${LDFLAGS} \
+ pipe.o \
+ drm_pipe.o \
+ drm_fb.o \
+ drm_open.o \
+ stripe.o -o bstest || exit 1
+```
+
+Clang Format Example
+---
+If you want to format `gamma_test.c`, first go to the drm-tests source directory and then run:
+
+```
+clang-format -style=file -i gamma_test.c
+```
+
+The `-style=file` argument will cause clang-format to search recursively upwards for the `.clang-format` style file, which is in drm-tests.
+
+Upcoming Features
+---
+- Vsync
+- Atomic Commit
+- Multi-card and multi-monitor
+- Keyboard/Mouse input for debugging
+- Dump buffer output for debugging
+
+What does BS stand for?
+---
+BS does't stand for anything.
diff --git a/drm-tests/bsdrm/example/module.mk b/drm-tests/bsdrm/example/module.mk
new file mode 100644
index 0000000..9d58d5d
--- /dev/null
+++ b/drm-tests/bsdrm/example/module.mk
@@ -0,0 +1,8 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include common.mk
+
+CC_BINARY(stripe): bsdrm/example/stripe.o \
+ CC_STATIC_LIBRARY(libbsdrm.pic.a)
diff --git a/drm-tests/bsdrm/example/stripe.c b/drm-tests/bsdrm/example/stripe.c
new file mode 100644
index 0000000..aefc53a
--- /dev/null
+++ b/drm-tests/bsdrm/example/stripe.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+int main()
+{
+ drmModeConnector *connector;
+ struct bs_drm_pipe pipe = { 0 };
+ struct bs_drm_pipe_plumber *plumber = bs_drm_pipe_plumber_new();
+ bs_drm_pipe_plumber_connector_ptr(plumber, &connector);
+ if (!bs_drm_pipe_plumber_make(plumber, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+ bs_drm_pipe_plumber_destroy(&plumber);
+
+ int fd = pipe.fd;
+ drmModeModeInfo *mode = &connector->modes[0];
+
+ struct gbm_device *gbm = gbm_create_device(fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm");
+ return 1;
+ }
+
+ struct gbm_bo *bos[2];
+ uint32_t ids[2];
+ for (size_t fb_index = 0; fb_index < 2; fb_index++) {
+ bos[fb_index] =
+ gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ if (bos[fb_index] == NULL) {
+ bs_debug_error("failed to allocate frame buffer");
+ return 1;
+ }
+ ids[fb_index] = bs_drm_fb_create_gbm(bos[fb_index]);
+ if (ids[fb_index] == 0) {
+ bs_debug_error("failed to create framebuffer id");
+ return 1;
+ }
+ }
+
+ struct bs_mapper *mapper = bs_mapper_dma_buf_new();
+ if (mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ return 1;
+ }
+
+ for (size_t frame_index = 0; frame_index < 10000; frame_index++) {
+ size_t fb_index = frame_index % 2;
+ struct gbm_bo *bo = bos[fb_index];
+ size_t bo_size = gbm_bo_get_stride(bo) * mode->vdisplay;
+ void *map_data;
+ char *ptr = bs_mapper_map(mapper, bo, 0, &map_data);
+ if (ptr == MAP_FAILED) {
+ bs_debug_error("failed to mmap gbm buffer object");
+ return 1;
+ }
+
+ for (size_t i = 0; i < bo_size / 4; i++) {
+ ptr[i * 4 + 0] = (i + frame_index * 50) % 256;
+ ptr[i * 4 + 1] = (i + frame_index * 50 + 85) % 256;
+ ptr[i * 4 + 2] = (i + frame_index * 50 + 170) % 256;
+ ptr[i * 4 + 3] = 0;
+ }
+ bs_mapper_unmap(mapper, bo, map_data);
+
+ int ret = drmModeSetCrtc(fd, pipe.crtc_id, ids[fb_index], 0 /* x */, 0 /* y */,
+ &pipe.connector_id, 1 /* connector count */, mode);
+ if (ret) {
+ bs_debug_error("failed to set crtc: %d", ret);
+ return 1;
+ }
+ usleep(16667);
+ }
+
+ bs_mapper_destroy(mapper);
+
+ for (size_t fb_index = 0; fb_index < 2; fb_index++) {
+ gbm_bo_destroy(bos[fb_index]);
+ drmModeRmFB(fd, ids[fb_index]);
+ }
+
+ gbm_device_destroy(gbm);
+
+ drmModeFreeConnector(connector);
+ close(fd);
+
+ return 0;
+}
diff --git a/drm-tests/bsdrm/include/bs_drm.h b/drm-tests/bsdrm/include/bs_drm.h
new file mode 100644
index 0000000..53cdbd0
--- /dev/null
+++ b/drm-tests/bsdrm/include/bs_drm.h
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef __BS_DRM_H__
+#define __BS_DRM_H__
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <drm_fourcc.h>
+#include <gbm.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#define bs_rank_skip UINT32_MAX
+
+#define BS_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
+
+#define BS_ALIGN(a, alignment) ((a + (alignment - 1)) & ~(alignment - 1))
+
+// debug.c
+__attribute__((format(printf, 5, 6))) void bs_debug_print(const char *prefix, const char *func,
+ const char *file, int line,
+ const char *format, ...);
+#define bs_debug_error(...) \
+ do { \
+ bs_debug_print("ERROR", __func__, __FILE__, __LINE__, __VA_ARGS__); \
+ } while (0)
+
+int64_t bs_debug_gettime_ns();
+
+// pipe.c
+typedef bool (*bs_make_pipe_piece)(void *context, void *out);
+
+bool bs_pipe_make(void *context, bs_make_pipe_piece *pieces, size_t piece_count, void *out_pipe,
+ size_t pipe_size);
+
+// open.c
+
+// A return value of true causes enumeration to end immediately. fd is always
+// closed after the callback.
+typedef bool (*bs_open_enumerate_func)(void *user, int fd);
+
+// A return value of true causes the filter to return the given fd.
+typedef bool (*bs_open_filter_func)(int fd);
+
+// The fd with the lowest (magnitude) rank is returned. A fd with rank UINT32_MAX is skipped. A fd
+// with rank 0 ends the enumeration early and is returned. On a tie, the fd returned will be
+// arbitrarily chosen from the set of lowest rank fds.
+typedef uint32_t (*bs_open_rank_func)(int fd);
+
+void bs_open_enumerate(const char *format, unsigned start, unsigned end,
+ bs_open_enumerate_func body, void *user);
+int bs_open_filtered(const char *format, unsigned start, unsigned end, bs_open_filter_func filter);
+int bs_open_ranked(const char *format, unsigned start, unsigned end, bs_open_rank_func rank);
+
+// drm_connectors.c
+#define bs_drm_connectors_any UINT32_MAX
+
+// Interleaved arrays in the layout { DRM_MODE_CONNECTOR_*, rank, DRM_MODE_CONNECTOR_*, rank, ... },
+// terminated by { ... 0, 0 }. bs_drm_connectors_any can be used in place of DRM_MODE_CONNECTOR_* to
+// match any connector. bs_rank_skip can be used in place of a rank to indicate that that connector
+// should be skipped.
+
+// Use internal connectors and fallback to any connector
+extern const uint32_t bs_drm_connectors_main_rank[];
+// Use only internal connectors
+extern const uint32_t bs_drm_connectors_internal_rank[];
+// Use only external connectors
+extern const uint32_t bs_drm_connectors_external_rank[];
+
+uint32_t bs_drm_connectors_rank(const uint32_t *ranks, uint32_t connector_type);
+
+// drm_pipe.c
+struct bs_drm_pipe {
+ int fd; // Always owned by the user of this library
+ uint32_t connector_id;
+ uint32_t encoder_id;
+ uint32_t crtc_id;
+};
+struct bs_drm_pipe_plumber;
+
+// A class that makes pipes with certain constraints.
+struct bs_drm_pipe_plumber *bs_drm_pipe_plumber_new();
+void bs_drm_pipe_plumber_destroy(struct bs_drm_pipe_plumber **);
+// Takes ranks in the rank array format from drm_connectors.c. Lifetime of connector_ranks must
+// exceed the plumber
+void bs_drm_pipe_plumber_connector_ranks(struct bs_drm_pipe_plumber *,
+ const uint32_t *connector_ranks);
+// crtc_mask is in the same format as drmModeEncoder.possible_crtcs
+void bs_drm_pipe_plumber_crtc_mask(struct bs_drm_pipe_plumber *, uint32_t crtc_mask);
+// Sets which card fd the plumber should use. The fd remains owned by the caller. If left unset,
+// bs_drm_pipe_plumber_make will try all available cards.
+void bs_drm_pipe_plumber_fd(struct bs_drm_pipe_plumber *, int card_fd);
+// Sets a pointer to store the chosen connector in after a succesful call to
+// bs_drm_pipe_plumber_make. It's optional, but calling drmModeGetConnector yourself can be slow.
+void bs_drm_pipe_plumber_connector_ptr(struct bs_drm_pipe_plumber *, drmModeConnector **ptr);
+// Makes the pipe based on the constraints of the plumber. Returns false if no pipe worked.
+bool bs_drm_pipe_plumber_make(struct bs_drm_pipe_plumber *, struct bs_drm_pipe *pipe);
+
+// Makes any pipe that will work for the given card fd. Returns false if no pipe worked.
+bool bs_drm_pipe_make(int fd, struct bs_drm_pipe *pipe);
+
+// drm_fb.c
+struct bs_drm_fb_builder;
+
+// A builder class used to collect drm framebuffer parameters and then build it.
+struct bs_drm_fb_builder *bs_drm_fb_builder_new();
+void bs_drm_fb_builder_destroy(struct bs_drm_fb_builder **);
+// Copies all available framebuffer parameters from the given buffer object.
+void bs_drm_fb_builder_gbm_bo(struct bs_drm_fb_builder *, struct gbm_bo *bo);
+// Sets the drm format parameter of the resulting framebuffer.
+void bs_drm_fb_builder_format(struct bs_drm_fb_builder *, uint32_t format);
+// Creates the framebuffer ID from the previously set parameters and returns it or 0 if there was a
+// failure.
+uint32_t bs_drm_fb_builder_create_fb(struct bs_drm_fb_builder *);
+
+// Creates a drm framebuffer from the given buffer object and returns the framebuffer's ID on
+// success or 0 on failure.
+uint32_t bs_drm_fb_create_gbm(struct gbm_bo *bo);
+
+// drm_open.c
+// Opens an arbitrary display's card.
+int bs_drm_open_for_display();
+// Opens the main display's card. This falls back to bs_drm_open_for_display().
+int bs_drm_open_main_display();
+int bs_drm_open_vgem();
+
+// egl.c
+struct bs_egl;
+struct bs_egl_fb;
+
+struct bs_egl *bs_egl_new();
+void bs_egl_destroy(struct bs_egl **egl);
+bool bs_egl_setup(struct bs_egl *self);
+bool bs_egl_make_current(struct bs_egl *self);
+
+EGLImageKHR bs_egl_image_create_gbm(struct bs_egl *self, struct gbm_bo *bo);
+void bs_egl_image_destroy(struct bs_egl *self, EGLImageKHR *image);
+bool bs_egl_image_flush_external(struct bs_egl *self, EGLImageKHR image);
+
+EGLSyncKHR bs_egl_create_sync(struct bs_egl *self, EGLenum type, const EGLint *attrib_list);
+EGLint bs_egl_wait_sync(struct bs_egl *self, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout);
+EGLBoolean bs_egl_destroy_sync(struct bs_egl *self, EGLSyncKHR sync);
+
+struct bs_egl_fb *bs_egl_fb_new(struct bs_egl *self, EGLImageKHR image);
+bool bs_egl_target_texture2D(struct bs_egl *self, EGLImageKHR image);
+bool bs_egl_has_extension(const char *extension, const char *extensions);
+void bs_egl_fb_destroy(struct bs_egl_fb **fb);
+GLuint bs_egl_fb_name(struct bs_egl_fb *self);
+
+// gl.c
+// The entry after the last valid binding should have name == NULL. The binding array is terminated
+// by a NULL name.
+struct bs_gl_program_create_binding {
+ // These parameters are passed to glBindAttribLocation
+ GLuint index;
+ const GLchar *name;
+};
+
+GLuint bs_gl_shader_create(GLenum type, const GLchar *src);
+// bindings can be NULL.
+GLuint bs_gl_program_create_vert_frag_bind(const GLchar *vert_src, const GLchar *frag_src,
+ struct bs_gl_program_create_binding *bindings);
+
+// app.c
+struct bs_app;
+
+struct bs_app *bs_app_new();
+void bs_app_destroy(struct bs_app **app);
+int bs_app_fd(struct bs_app *self);
+size_t bs_app_fb_count(struct bs_app *self);
+void bs_app_set_fb_count(struct bs_app *self, size_t fb_count);
+struct gbm_bo *bs_app_fb_bo(struct bs_app *self, size_t index);
+uint32_t bs_app_fb_id(struct bs_app *self, size_t index);
+bool bs_app_setup(struct bs_app *self);
+int bs_app_display_fb(struct bs_app *self, size_t index);
+
+// mmap.c
+struct bs_mapper;
+struct bs_mapper *bs_mapper_dma_buf_new();
+struct bs_mapper *bs_mapper_gem_new();
+struct bs_mapper *bs_mapper_dumb_new(int device_fd);
+void bs_mapper_destroy(struct bs_mapper *mapper);
+void *bs_mapper_map(struct bs_mapper *mapper, struct gbm_bo *bo, size_t plane, void **map_data);
+void bs_mapper_unmap(struct bs_mapper *mapper, struct gbm_bo *bo, void *map_data);
+
+// draw.c
+
+struct bs_draw_format;
+
+bool bs_draw_stripe(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format);
+bool bs_draw_ellipse(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format, float progress);
+bool bs_draw_cursor(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format);
+bool bs_draw_lines(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format);
+const struct bs_draw_format *bs_get_draw_format(uint32_t pixel_format);
+const struct bs_draw_format *bs_get_draw_format_from_name(const char *str);
+uint32_t bs_get_pixel_format(const struct bs_draw_format *format);
+const char *bs_get_format_name(const struct bs_draw_format *format);
+bool bs_parse_draw_format(const char *str, const struct bs_draw_format **format);
+
+#endif
diff --git a/drm-tests/bsdrm/module.mk b/drm-tests/bsdrm/module.mk
new file mode 100644
index 0000000..7e2fa4d
--- /dev/null
+++ b/drm-tests/bsdrm/module.mk
@@ -0,0 +1,5 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include common.mk
diff --git a/drm-tests/bsdrm/src/app.c b/drm-tests/bsdrm/src/app.c
new file mode 100644
index 0000000..c7f28bf
--- /dev/null
+++ b/drm-tests/bsdrm/src/app.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+struct bs_app_fb {
+ struct gbm_bo *bo;
+ uint32_t id;
+};
+
+struct bs_app {
+ bool setup;
+
+ int fd;
+ struct gbm_device *gbm;
+
+ uint32_t connector_id, crtc_id;
+ drmModeModeInfo mode;
+
+ uint32_t fb_format;
+ size_t fb_count;
+ struct bs_app_fb *fbs;
+};
+
+struct bs_app *bs_app_new()
+{
+ struct bs_app *self = calloc(1, sizeof(struct bs_app));
+ assert(self);
+ self->fd = -1;
+ self->fb_format = GBM_FORMAT_XRGB8888;
+ self->fb_count = 2;
+ return self;
+}
+
+void bs_app_destroy(struct bs_app **app)
+{
+ assert(app);
+ struct bs_app *self = *app;
+ assert(self);
+
+ if (self->setup) {
+ if (self->fbs) {
+ for (size_t fb_index = 0; fb_index < self->fb_count; fb_index++) {
+ struct bs_app_fb *fb = &self->fbs[fb_index];
+ gbm_bo_destroy(fb->bo);
+ if (fb->id)
+ drmModeRmFB(self->fd, fb->id);
+ }
+ free(self->fbs);
+ }
+
+ if (self->gbm) {
+ gbm_device_destroy(self->gbm);
+ self->gbm = NULL;
+ }
+
+ if (self->fd >= 0) {
+ close(self->fd);
+ self->fd = -1;
+ }
+ }
+
+ free(self);
+ *app = NULL;
+}
+
+int bs_app_fd(struct bs_app *self)
+{
+ assert(self);
+ assert(self->setup);
+ return self->fd;
+}
+size_t bs_app_fb_count(struct bs_app *self)
+{
+ assert(self);
+ return self->fb_count;
+}
+
+void bs_app_set_fb_count(struct bs_app *self, size_t fb_count)
+{
+ assert(self);
+ assert(!self->setup);
+ assert(fb_count > 0);
+ self->fb_count = fb_count;
+}
+
+struct gbm_bo *bs_app_fb_bo(struct bs_app *self, size_t index)
+{
+ assert(self);
+ assert(self->fbs);
+ assert(index < self->fb_count);
+ return self->fbs[index].bo;
+}
+
+uint32_t bs_app_fb_id(struct bs_app *self, size_t index)
+{
+ assert(self);
+ assert(self->fbs);
+ assert(index < self->fb_count);
+ return self->fbs[index].id;
+}
+
+bool bs_app_setup(struct bs_app *self)
+{
+ assert(self);
+ assert(!self->setup);
+ assert(self->fb_count > 0);
+
+ self->fd = bs_drm_open_main_display();
+ if (self->fd < 0) {
+ bs_debug_error("failed to open card for display");
+ return false;
+ }
+
+ self->gbm = gbm_create_device(self->fd);
+ if (!self->gbm) {
+ bs_debug_error("failed to create gbm");
+ goto close_fd;
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(self->fd, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ goto destroy_device;
+ }
+ self->crtc_id = pipe.crtc_id;
+ self->connector_id = pipe.connector_id;
+
+ drmModeConnector *connector = drmModeGetConnector(self->fd, pipe.connector_id);
+ if (!connector) {
+ bs_debug_error("failed to get connector %u", pipe.connector_id);
+ goto destroy_device;
+ }
+
+ self->mode = connector->modes[0];
+ drmModeFreeConnector(connector);
+
+ self->fbs = calloc(self->fb_count, sizeof(self->fbs[0]));
+ assert(self->fbs);
+ for (size_t fb_index = 0; fb_index < self->fb_count; fb_index++) {
+ struct bs_app_fb *fb = &self->fbs[fb_index];
+ fb->bo = gbm_bo_create(self->gbm, self->mode.hdisplay, self->mode.vdisplay,
+ self->fb_format, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ if (fb->bo == NULL) {
+ bs_debug_error("failed to allocate framebuffer %zu of %zu", fb_index + 1,
+ self->fb_count);
+ goto destroy_fbs;
+ }
+ fb->id = bs_drm_fb_create_gbm(fb->bo);
+ if (fb->id == 0) {
+ bs_debug_error("failed to create framebuffer id %zu of %zu", fb_index + 1,
+ self->fb_count);
+ goto destroy_fbs;
+ }
+ }
+
+ self->setup = true;
+ return true;
+
+destroy_fbs:
+ for (size_t fb_index = 0; fb_index < self->fb_count; fb_index++) {
+ struct bs_app_fb *fb = &self->fbs[fb_index];
+ if (fb->id != 0)
+ drmModeRmFB(self->fd, fb->id);
+ if (fb->bo)
+ gbm_bo_destroy(fb->bo);
+ }
+ free(self->fbs);
+ self->fbs = NULL;
+
+destroy_device:
+ gbm_device_destroy(self->gbm);
+ self->gbm = NULL;
+
+close_fd:
+ close(self->fd);
+ self->fd = -1;
+ return false;
+}
+
+int bs_app_display_fb(struct bs_app *self, size_t index)
+{
+ assert(self);
+ assert(self->fd >= 0);
+ assert(self->fbs);
+ assert(index < self->fb_count);
+ return drmModeSetCrtc(self->fd, self->crtc_id, self->fbs[index].id, 0 /* x */, 0 /* y */,
+ &self->connector_id, 1 /* connector count */, &self->mode);
+}
diff --git a/drm-tests/bsdrm/src/debug.c b/drm-tests/bsdrm/src/debug.c
new file mode 100644
index 0000000..4a2cd2d
--- /dev/null
+++ b/drm-tests/bsdrm/src/debug.c
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+void bs_debug_print(const char *prefix, const char *func, const char *file, int line,
+ const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ fprintf(stderr, "%s:%s():%s:%d:", prefix, func, basename(file), line);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+}
+
+int64_t bs_debug_gettime_ns()
+{
+ struct timespec t;
+ int ret = clock_gettime(CLOCK_MONOTONIC, &t);
+ if (ret)
+ return -1;
+ const int64_t billion = 1000000000;
+ return (int64_t)t.tv_nsec + (int64_t)t.tv_sec * billion;
+}
diff --git a/drm-tests/bsdrm/src/draw.c b/drm-tests/bsdrm/src/draw.c
new file mode 100644
index 0000000..93589d2
--- /dev/null
+++ b/drm-tests/bsdrm/src/draw.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2017 The Chromium OS Authors. All rights reserved.
+
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+struct draw_format_component {
+ float rgb_coeffs[3];
+ float value_offset;
+ uint32_t horizontal_subsample_rate;
+ uint32_t vertical_subsample_rate;
+ uint32_t byte_skip;
+ uint32_t plane_index;
+ uint32_t plane_offset;
+};
+
+#define MAX_COMPONENTS 4
+struct bs_draw_format {
+ uint32_t pixel_format;
+ const char *name;
+ size_t component_count;
+ struct draw_format_component components[MAX_COMPONENTS];
+};
+
+struct draw_data {
+ uint32_t x;
+ uint32_t y;
+ uint32_t w;
+ uint32_t h;
+ float progress;
+ uint8_t out_color[MAX_COMPONENTS];
+};
+
+struct draw_data_lines {
+ struct draw_data base;
+ bool color_olive;
+ uint32_t interval;
+ uint32_t stop;
+};
+
+typedef void (*compute_color_t)(struct draw_data *data);
+
+#define PIXEL_FORMAT_AND_NAME(x) GBM_FORMAT_##x, #x
+static const struct bs_draw_format bs_draw_formats[] = {
+ {
+ PIXEL_FORMAT_AND_NAME(ABGR8888),
+ 4,
+ {
+ { { 1.0f, 0.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 0 },
+ { { 0.0f, 1.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 1 },
+ { { 0.0f, 0.0f, 1.0f }, 0.0f, 1, 1, 4, 0, 2 },
+ { { 0.0f, 0.0f, 0.0f }, 255.0f, 1, 1, 4, 0, 3 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(ARGB8888),
+ 4,
+ {
+ { { 0.0f, 0.0f, 1.0f }, 0.0f, 1, 1, 4, 0, 0 },
+ { { 0.0f, 1.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 1 },
+ { { 1.0f, 0.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 2 },
+ { { 0.0f, 0.0f, 0.0f }, 255.0f, 1, 1, 4, 0, 3 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(BGR888),
+ 3,
+ {
+ { { 1.0f, 0.0f, 0.0f }, 0.0f, 1, 1, 3, 0, 0 },
+ { { 0.0f, 1.0f, 0.0f }, 0.0f, 1, 1, 3, 0, 1 },
+ { { 0.0f, 0.0f, 1.0f }, 0.0f, 1, 1, 3, 0, 2 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(NV12),
+ 3,
+ {
+ { { 0.2567890625f, 0.50412890625f, 0.09790625f }, 16.0f, 1, 1, 1, 0, 0 },
+ { { -0.14822265625f, -0.2909921875f, 0.43921484375f }, 128.0f, 2, 2, 2, 1, 0 },
+ { { 0.43921484375f, -0.3677890625f, -0.07142578125f }, 128.0f, 2, 2, 2, 1, 1 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(NV21),
+ 3,
+ {
+ { { 0.2567890625f, 0.50412890625f, 0.09790625f }, 16.0f, 1, 1, 1, 0, 0 },
+ { { 0.43921484375f, -0.3677890625f, -0.07142578125f }, 128.0f, 2, 2, 2, 1, 0 },
+ { { -0.14822265625f, -0.2909921875f, 0.43921484375f }, 128.0f, 2, 2, 2, 1, 1 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(RGB888),
+ 3,
+ {
+ { { 0.0f, 0.0f, 1.0f }, 0.0f, 1, 1, 3, 0, 0 },
+ { { 0.0f, 1.0f, 0.0f }, 0.0f, 1, 1, 3, 0, 1 },
+ { { 1.0f, 0.0f, 0.0f }, 0.0f, 1, 1, 3, 0, 2 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(XBGR8888),
+ 3,
+ {
+ { { 1.0f, 0.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 0 },
+ { { 0.0f, 1.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 1 },
+ { { 0.0f, 0.0f, 1.0f }, 0.0f, 1, 1, 4, 0, 2 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(XRGB8888),
+ 3,
+ {
+ { { 0.0f, 0.0f, 1.0f }, 0.0f, 1, 1, 4, 0, 0 },
+ { { 0.0f, 1.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 1 },
+ { { 1.0f, 0.0f, 0.0f }, 0.0f, 1, 1, 4, 0, 2 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(UYVY),
+ 3,
+ {
+ { { -0.14822265625f, -0.2909921875f, 0.43921484375f }, 128.0f, 2, 1, 4, 0, 0 },
+ { { 0.2567890625f, 0.50412890625f, 0.09790625f }, 16.0f, 1, 1, 2, 0, 1 },
+ { { 0.43921484375f, -0.3677890625f, -0.07142578125f }, 128.0f, 2, 1, 4, 0, 2 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(YUYV),
+ 3,
+ {
+ { { 0.2567890625f, 0.50412890625f, 0.09790625f }, 16.0f, 1, 1, 2, 0, 0 },
+ { { -0.14822265625f, -0.2909921875f, 0.43921484375f }, 128.0f, 2, 1, 4, 0, 1 },
+ { { 0.43921484375f, -0.3677890625f, -0.07142578125f }, 128.0f, 2, 1, 4, 0, 3 },
+ },
+ },
+ {
+ PIXEL_FORMAT_AND_NAME(YVU420),
+ 3,
+ {
+ { { 0.2567890625f, 0.50412890625f, 0.09790625f }, 16.0f, 1, 1, 1, 0, 0 },
+ { { 0.43921484375f, -0.3677890625f, -0.07142578125f }, 128.0f, 2, 2, 1, 1, 0 },
+ { { -0.14822265625f, -0.2909921875f, 0.43921484375f }, 128.0f, 2, 2, 1, 2, 0 },
+ },
+ },
+};
+
+struct draw_plane {
+ uint32_t row_stride;
+ uint8_t *ptr;
+ void *map_data;
+};
+
+static uint8_t clampbyte(float f)
+{
+ if (f >= 255.0f)
+ return 255;
+ if (f <= 0.0f)
+ return 0;
+ return (uint8_t)f;
+}
+
+uint8_t static convert_color(const struct draw_format_component *comp, uint8_t r, uint8_t g,
+ uint8_t b)
+{
+ return clampbyte(comp->value_offset + r * comp->rgb_coeffs[0] + g * comp->rgb_coeffs[1] +
+ b * comp->rgb_coeffs[2]);
+}
+
+static void unmmap_planes(struct bs_mapper *mapper, struct gbm_bo *bo, size_t num_planes,
+ struct draw_plane *planes)
+{
+ for (uint32_t plane_index = 0; plane_index < num_planes; plane_index++)
+ bs_mapper_unmap(mapper, bo, planes[plane_index].map_data);
+}
+
+static size_t mmap_planes(struct bs_mapper *mapper, struct gbm_bo *bo,
+ struct draw_plane planes[GBM_MAX_PLANES])
+{
+ size_t num_planes = gbm_bo_get_num_planes(bo);
+ for (size_t plane_index = 0; plane_index < num_planes; plane_index++) {
+ struct draw_plane *plane = &planes[plane_index];
+ plane->row_stride = gbm_bo_get_plane_stride(bo, plane_index);
+ plane->ptr = bs_mapper_map(mapper, bo, plane_index, &plane->map_data);
+ if (plane->ptr == MAP_FAILED) {
+ bs_debug_error("failed to mmap plane %zu of buffer object", plane_index);
+ unmmap_planes(mapper, bo, plane_index, planes);
+ return 0;
+ }
+ }
+
+ return num_planes;
+}
+
+static bool draw_color(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format, struct draw_data *data,
+ compute_color_t compute_color_fn)
+{
+ uint8_t *ptr, *converted_colors[MAX_COMPONENTS];
+ struct draw_plane planes[GBM_MAX_PLANES];
+ uint32_t height = data->h = gbm_bo_get_height(bo);
+ uint32_t width = data->w = gbm_bo_get_width(bo);
+
+ size_t num_planes = mmap_planes(mapper, bo, planes);
+ if (num_planes == 0) {
+ bs_debug_error("failed to prepare to draw pattern to buffer object");
+ return false;
+ }
+
+ for (size_t comp_index = 0; comp_index < format->component_count; comp_index++) {
+ converted_colors[comp_index] = calloc(width * height, sizeof(uint8_t));
+ assert(converted_colors[comp_index]);
+ }
+
+ for (uint32_t y = 0; y < height; y++) {
+ data->y = y;
+ for (uint32_t x = 0; x < width; x++) {
+ data->x = x;
+ compute_color_fn(data);
+ for (size_t comp_index = 0; comp_index < format->component_count;
+ comp_index++) {
+ const struct draw_format_component *comp =
+ &format->components[comp_index];
+ ptr = converted_colors[comp_index] + width * y + x;
+ *ptr = convert_color(comp, data->out_color[2], data->out_color[1],
+ data->out_color[0]);
+ }
+ }
+ }
+
+ uint32_t color, samples, offset;
+ uint8_t *rows[MAX_COMPONENTS] = { 0 };
+ for (size_t comp_index = 0; comp_index < format->component_count; comp_index++) {
+ const struct draw_format_component *comp = &format->components[comp_index];
+ struct draw_plane *plane = &planes[comp->plane_index];
+ for (uint32_t y = 0; y < height / comp->vertical_subsample_rate; y++) {
+ rows[comp_index] = plane->ptr + comp->plane_offset + plane->row_stride * y;
+ for (uint32_t x = 0; x < width / comp->horizontal_subsample_rate; x++) {
+ offset = color = samples = 0;
+ for (uint32_t j = 0; j < comp->vertical_subsample_rate; j++) {
+ offset = (y * comp->vertical_subsample_rate + j) * width +
+ x * comp->horizontal_subsample_rate;
+ for (uint32_t i = 0; i < comp->horizontal_subsample_rate;
+ i++) {
+ color += converted_colors[comp_index][offset];
+ samples++;
+ offset++;
+ }
+ }
+
+ *(rows[comp_index] + x * comp->byte_skip) = color / samples;
+ }
+ }
+ }
+
+ unmmap_planes(mapper, bo, num_planes, planes);
+ for (size_t comp_index = 0; comp_index < format->component_count; comp_index++) {
+ free(converted_colors[comp_index]);
+ }
+
+ return true;
+}
+
+static void compute_stripe(struct draw_data *data)
+{
+ const uint32_t striph = data->h / 4;
+ const uint32_t s = data->y / striph;
+ uint8_t r = 0, g = 0, b = 0;
+ switch (s) {
+ case 0:
+ r = g = b = 1;
+ break;
+ case 1:
+ r = 1;
+ break;
+ case 2:
+ g = 1;
+ break;
+ case 3:
+ b = 1;
+ break;
+ default:
+ r = g = b = 0;
+ break;
+ }
+
+ const float i = (float)data->x / (float)data->w * 256.0f;
+ data->out_color[0] = b * i;
+ data->out_color[1] = g * i;
+ data->out_color[2] = r * i;
+ data->out_color[3] = 0;
+}
+
+static void compute_ellipse(struct draw_data *data)
+{
+ float xratio = ((int)data->x - (int)data->w / 2) / ((float)(data->w / 2));
+ float yratio = ((int)data->y - (int)data->h / 2) / ((float)(data->h / 2));
+
+ // If a point is on or inside an ellipse, num <= 1.
+ float num = xratio * xratio + yratio * yratio;
+ uint32_t g = 255 * num;
+
+ if (g < 256) {
+ memset(data->out_color, 0, 4);
+ data->out_color[2] = 0xFF;
+ data->out_color[1] = g;
+ } else {
+ memset(data->out_color, (uint8_t)(data->progress * 255), 4);
+ }
+}
+
+static void compute_cursor(struct draw_data *data)
+{
+ // A white triangle pointing right
+ if (data->y > data->x / 2 && data->y < (data->w - data->x / 2))
+ memset(data->out_color, 0xFF, 4);
+ else
+ memset(data->out_color, 0, 4);
+}
+
+static void compute_lines(struct draw_data *data)
+{
+ struct draw_data_lines *line_data = (struct draw_data_lines *)data;
+ // horizontal stripes on first vertical half, vertical stripes on next half
+ if (line_data->base.y < (line_data->base.h / 2)) {
+ if (line_data->base.x == 0) {
+ line_data->color_olive = false;
+ line_data->interval = 5;
+ line_data->stop = line_data->base.x + line_data->interval;
+ } else if (line_data->base.x >= line_data->stop) {
+ if (line_data->color_olive)
+ line_data->interval += 5;
+
+ line_data->stop += line_data->interval;
+ line_data->color_olive = !line_data->color_olive;
+ }
+ } else {
+ if (line_data->base.y == (line_data->base.h / 2)) {
+ line_data->color_olive = false;
+ line_data->interval = 10;
+ line_data->stop = line_data->base.y + line_data->interval;
+ } else if (line_data->base.y >= line_data->stop) {
+ if (line_data->color_olive)
+ line_data->interval += 5;
+
+ line_data->stop += line_data->interval;
+ line_data->color_olive = !line_data->color_olive;
+ }
+ }
+ if (line_data->color_olive) {
+ // yellowish green color
+ line_data->base.out_color[0] = 0;
+ line_data->base.out_color[1] = 128;
+ line_data->base.out_color[2] = 128;
+ line_data->base.out_color[3] = 0;
+ } else {
+ // fuchsia
+ line_data->base.out_color[0] = 255;
+ line_data->base.out_color[1] = 0;
+ line_data->base.out_color[2] = 255;
+ line_data->base.out_color[3] = 0;
+ }
+}
+
+bool bs_draw_stripe(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format)
+{
+ struct draw_data data = { 0 };
+ return draw_color(mapper, bo, format, &data, compute_stripe);
+}
+
+bool bs_draw_ellipse(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format, float progress)
+{
+ struct draw_data data = { 0 };
+ data.progress = progress;
+ return draw_color(mapper, bo, format, &data, compute_ellipse);
+}
+
+bool bs_draw_cursor(struct bs_mapper *mapper, struct gbm_bo *bo,
+ const struct bs_draw_format *format)
+{
+ struct draw_data data = { 0 };
+ return draw_color(mapper, bo, format, &data, compute_cursor);
+}
+
+bool bs_draw_lines(struct bs_mapper *mapper, struct gbm_bo *bo, const struct bs_draw_format *format)
+{
+ struct draw_data_lines line_data = { { 0 } };
+ return draw_color(mapper, bo, format, &line_data.base, compute_lines);
+}
+
+const struct bs_draw_format *bs_get_draw_format(uint32_t pixel_format)
+{
+ for (size_t format_index = 0; format_index < BS_ARRAY_LEN(bs_draw_formats);
+ format_index++) {
+ const struct bs_draw_format *format = &bs_draw_formats[format_index];
+ if (format->pixel_format == pixel_format)
+ return format;
+ }
+
+ return NULL;
+}
+
+const struct bs_draw_format *bs_get_draw_format_from_name(const char *str)
+{
+ for (size_t format_index = 0; format_index < BS_ARRAY_LEN(bs_draw_formats);
+ format_index++) {
+ const struct bs_draw_format *format = &bs_draw_formats[format_index];
+ if (!strcmp(str, format->name))
+ return format;
+ }
+
+ return NULL;
+}
+
+uint32_t bs_get_pixel_format(const struct bs_draw_format *format)
+{
+ assert(format);
+ return format->pixel_format;
+}
+
+const char *bs_get_format_name(const struct bs_draw_format *format)
+{
+ assert(format);
+ return format->name;
+}
+
+bool bs_parse_draw_format(const char *str, const struct bs_draw_format **format)
+{
+ if (strlen(str) == 4) {
+ const struct bs_draw_format *bs_draw_format = bs_get_draw_format(*(uint32_t *)str);
+ if (bs_draw_format) {
+ *format = bs_draw_format;
+ return true;
+ }
+ } else {
+ const struct bs_draw_format *bs_draw_format = bs_get_draw_format_from_name(str);
+ if (bs_draw_format) {
+ *format = bs_draw_format;
+ return true;
+ }
+ }
+
+ bs_debug_error("format %s is not recognized\n", str);
+ return false;
+}
diff --git a/drm-tests/bsdrm/src/drm_connectors.c b/drm-tests/bsdrm/src/drm_connectors.c
new file mode 100644
index 0000000..a0f8ff2
--- /dev/null
+++ b/drm-tests/bsdrm/src/drm_connectors.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+const uint32_t bs_drm_connectors_main_rank[] = { DRM_MODE_CONNECTOR_LVDS,
+ 0x01,
+ DRM_MODE_CONNECTOR_eDP,
+ 0x02,
+ DRM_MODE_CONNECTOR_DSI,
+ 0x03,
+ DRM_MODE_CONNECTOR_HDMIA,
+ 0x04,
+ bs_drm_connectors_any,
+ 0xFF,
+ 0,
+ 0 };
+
+const uint32_t bs_drm_connectors_internal_rank[] = { DRM_MODE_CONNECTOR_LVDS,
+ 0x01,
+ DRM_MODE_CONNECTOR_eDP,
+ 0x02,
+ DRM_MODE_CONNECTOR_DSI,
+ 0x03,
+ 0,
+ 0 };
+
+const uint32_t bs_drm_connectors_external_rank[] = { DRM_MODE_CONNECTOR_LVDS,
+ bs_rank_skip,
+ DRM_MODE_CONNECTOR_eDP,
+ bs_rank_skip,
+ DRM_MODE_CONNECTOR_DSI,
+ bs_rank_skip,
+ DRM_MODE_CONNECTOR_HDMIA,
+ 0x01,
+ bs_drm_connectors_any,
+ 0x02,
+ 0,
+ 0 };
+
+uint32_t bs_drm_connectors_rank(const uint32_t *ranks, uint32_t connector_type)
+{
+ for (size_t rank_index = 0; ranks[rank_index] != 0; rank_index += 2)
+ if (ranks[rank_index] == bs_drm_connectors_any ||
+ connector_type == ranks[rank_index])
+ return ranks[rank_index + 1];
+ return bs_rank_skip;
+}
diff --git a/drm-tests/bsdrm/src/drm_fb.c b/drm-tests/bsdrm/src/drm_fb.c
new file mode 100644
index 0000000..93655ea
--- /dev/null
+++ b/drm-tests/bsdrm/src/drm_fb.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+#define MAX_PLANE_COUNT 4
+
+struct bs_drm_fb_builder {
+ int fd;
+ uint32_t width;
+ uint32_t height;
+ uint32_t format;
+ size_t plane_count;
+ uint32_t handles[MAX_PLANE_COUNT];
+ uint32_t strides[MAX_PLANE_COUNT];
+ uint32_t offsets[MAX_PLANE_COUNT];
+};
+
+void bs_drm_fb_builder_init(struct bs_drm_fb_builder *self)
+{
+ assert(self);
+ self->fd = -1;
+}
+
+struct bs_drm_fb_builder *bs_drm_fb_builder_new()
+{
+ struct bs_drm_fb_builder *self = calloc(1, sizeof(struct bs_drm_fb_builder));
+ assert(self);
+ bs_drm_fb_builder_init(self);
+ return self;
+}
+
+void bs_drm_fb_builder_destroy(struct bs_drm_fb_builder **self)
+{
+ assert(self);
+ assert(*self);
+ free(*self);
+ *self = NULL;
+}
+
+void bs_drm_fb_builder_gbm_bo(struct bs_drm_fb_builder *self, struct gbm_bo *bo)
+{
+ assert(self);
+ assert(bo);
+
+ struct gbm_device *gbm = gbm_bo_get_device(bo);
+ assert(gbm);
+
+ self->fd = gbm_device_get_fd(gbm);
+ self->width = gbm_bo_get_width(bo);
+ self->height = gbm_bo_get_height(bo);
+ self->format = gbm_bo_get_format(bo);
+ self->plane_count = gbm_bo_get_num_planes(bo);
+ if (self->plane_count > MAX_PLANE_COUNT) {
+ bs_debug_print("WARNING", __func__, __FILE__, __LINE__,
+ "only using first %d planes out of buffer object's %zu planes",
+ MAX_PLANE_COUNT, self->plane_count);
+ self->plane_count = MAX_PLANE_COUNT;
+ }
+
+ for (size_t plane_index = 0; plane_index < self->plane_count; plane_index++) {
+ self->handles[plane_index] = gbm_bo_get_plane_handle(bo, plane_index).u32;
+ self->strides[plane_index] = gbm_bo_get_plane_stride(bo, plane_index);
+ self->offsets[plane_index] = gbm_bo_get_plane_offset(bo, plane_index);
+ }
+}
+
+void bs_drm_fb_builder_format(struct bs_drm_fb_builder *self, uint32_t format)
+{
+ assert(self);
+ self->format = format;
+}
+
+uint32_t bs_drm_fb_builder_create_fb(struct bs_drm_fb_builder *self)
+{
+ assert(self);
+
+ if (self->fd < 0) {
+ bs_debug_error("failed to create drm fb: card fd %d is invalid", self->fd);
+ return 0;
+ }
+
+ if (self->width <= 0 || self->height <= 0) {
+ bs_debug_error("failed to create drm fb: dimensions %ux%u are invalid", self->width,
+ self->height);
+ return 0;
+ }
+
+ if (self->format == 0) {
+ bs_debug_error("failed to create drm fb: 0 format is invalid");
+ return 0;
+ }
+
+ if (self->plane_count == 0 || self->plane_count > MAX_PLANE_COUNT) {
+ bs_debug_error("failed to create drm fb: plane count %zu is invalid\n",
+ self->plane_count);
+ return 0;
+ }
+
+ for (size_t plane_index = self->plane_count; plane_index < MAX_PLANE_COUNT; plane_index++) {
+ self->handles[plane_index] = 0;
+ self->strides[plane_index] = 0;
+ self->offsets[plane_index] = 0;
+ }
+
+ uint32_t fb_id;
+
+ int ret = drmModeAddFB2(self->fd, self->width, self->height, self->format, self->handles,
+ self->strides, self->offsets, &fb_id, 0);
+
+ if (ret) {
+ bs_debug_error("failed to create drm fb: drmModeAddFB2 returned %d", ret);
+ return 0;
+ }
+
+ return fb_id;
+}
+
+uint32_t bs_drm_fb_create_gbm(struct gbm_bo *bo)
+{
+ assert(bo);
+
+ struct bs_drm_fb_builder builder;
+ bs_drm_fb_builder_init(&builder);
+ bs_drm_fb_builder_gbm_bo(&builder, bo);
+ uint32_t fb_id = bs_drm_fb_builder_create_fb(&builder);
+
+ if (!fb_id) {
+ bs_debug_error("failed to create framebuffer from buffer object");
+ return 0;
+ }
+
+ return fb_id;
+}
diff --git a/drm-tests/bsdrm/src/drm_open.c b/drm-tests/bsdrm/src/drm_open.c
new file mode 100644
index 0000000..c6dc2a7
--- /dev/null
+++ b/drm-tests/bsdrm/src/drm_open.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+static bool display_filter(int fd)
+{
+ bool has_connection = false;
+ drmModeRes *res = drmModeGetResources(fd);
+ if (!res)
+ return false;
+
+ if (res->count_crtcs == 0)
+ goto out;
+
+ for (int connector_index = 0; connector_index < res->count_connectors; connector_index++) {
+ drmModeConnector *connector =
+ drmModeGetConnector(fd, res->connectors[connector_index]);
+ if (connector == NULL)
+ continue;
+
+ has_connection =
+ connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0;
+ drmModeFreeConnector(connector);
+ if (has_connection)
+ break;
+ }
+
+out:
+ drmModeFreeResources(res);
+ return has_connection;
+}
+
+int bs_drm_open_for_display()
+{
+ return bs_open_filtered("/dev/dri/card%u", 0, DRM_MAX_MINOR, display_filter);
+}
+
+static bool connector_has_crtc(int fd, drmModeRes *res, drmModeConnector *connector)
+{
+ for (int encoder_index = 0; encoder_index < connector->count_encoders; encoder_index++) {
+ drmModeEncoder *encoder = drmModeGetEncoder(fd, connector->encoders[encoder_index]);
+ if (encoder == NULL)
+ continue;
+
+ uint32_t possible_crtcs = encoder->possible_crtcs;
+ drmModeFreeEncoder(encoder);
+
+ for (int crtc_index = 0; crtc_index < res->count_crtcs; crtc_index++)
+ if ((possible_crtcs & (1 << crtc_index)) != 0)
+ return true;
+ }
+
+ return false;
+}
+
+static uint32_t display_rank_connector_type(uint32_t connector_type)
+{
+ switch (connector_type) {
+ case DRM_MODE_CONNECTOR_LVDS:
+ return 0x01;
+ case DRM_MODE_CONNECTOR_eDP:
+ return 0x02;
+ case DRM_MODE_CONNECTOR_DSI:
+ return 0x03;
+ }
+ return 0xFF;
+}
+
+static uint32_t display_rank(int fd)
+{
+ drmModeRes *res = drmModeGetResources(fd);
+ if (!res)
+ return bs_rank_skip;
+
+ uint32_t best_rank = bs_rank_skip;
+ if (res->count_crtcs == 0)
+ goto out;
+
+ for (int connector_index = 0; connector_index < res->count_connectors; connector_index++) {
+ drmModeConnector *connector =
+ drmModeGetConnector(fd, res->connectors[connector_index]);
+ if (connector == NULL)
+ continue;
+
+ bool has_connection = connector->connection == DRM_MODE_CONNECTED &&
+ connector->count_modes > 0 &&
+ connector_has_crtc(fd, res, connector);
+ if (!has_connection)
+ continue;
+
+ uint32_t rank = display_rank_connector_type(connector->connector_type);
+ if (best_rank > rank)
+ best_rank = rank;
+ drmModeFreeConnector(connector);
+ }
+
+out:
+ drmModeFreeResources(res);
+ return best_rank;
+}
+
+int bs_drm_open_main_display()
+{
+ return bs_open_ranked("/dev/dri/card%u", 0, DRM_MAX_MINOR, display_rank);
+}
+
+static bool vgem_filter(int fd)
+{
+ drmVersion *version = drmGetVersion(fd);
+ if (!version)
+ return false;
+
+ bool is_vgem = (strncmp("vgem", version->name, version->name_len) == 0);
+ drmFreeVersion(version);
+ return is_vgem;
+}
+
+int bs_drm_open_vgem()
+{
+ return bs_open_filtered("/dev/dri/card%u", 0, DRM_MAX_MINOR, vgem_filter);
+}
diff --git a/drm-tests/bsdrm/src/drm_pipe.c b/drm-tests/bsdrm/src/drm_pipe.c
new file mode 100644
index 0000000..653bddc
--- /dev/null
+++ b/drm-tests/bsdrm/src/drm_pipe.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+struct bs_drm_pipe_plumber {
+ int fd;
+ const uint32_t *connector_ranks;
+ uint32_t crtc_mask;
+ drmModeConnector **connector_ptr;
+};
+
+struct fd_connector {
+ int fd;
+ drmModeRes *res;
+ drmModeConnector *connector;
+};
+
+struct pipe_internal {
+ struct fd_connector *connector;
+ size_t next_connector_index;
+ uint32_t connector_rank;
+ uint32_t encoder_id;
+ int next_encoder_index;
+ uint32_t crtc_id;
+ int next_crtc_index;
+};
+
+struct pipe_ctx {
+ size_t connector_count;
+ struct fd_connector *connectors;
+
+ uint32_t crtc_mask;
+ const uint32_t *connector_ranks;
+
+ uint32_t best_rank;
+ size_t first_connector_index;
+};
+
+static bool pipe_piece_connector(void *c, void *p)
+{
+ struct pipe_ctx *ctx = c;
+ struct pipe_internal *pipe = p;
+
+ bool use_connector = false;
+ size_t connector_index;
+ for (connector_index = pipe->next_connector_index == 0 ? ctx->first_connector_index
+ : pipe->next_connector_index;
+ connector_index < ctx->connector_count; connector_index++) {
+ struct fd_connector *fd_connector = &ctx->connectors[connector_index];
+ drmModeConnector *connector = fd_connector->connector;
+ if (connector == NULL)
+ continue;
+
+ use_connector =
+ connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0;
+ if (use_connector && ctx->connector_ranks) {
+ uint32_t rank =
+ bs_drm_connectors_rank(ctx->connector_ranks, connector->connector_type);
+ if (rank >= ctx->best_rank)
+ use_connector = false;
+ pipe->connector_rank = rank;
+ }
+
+ if (use_connector) {
+ pipe->connector = fd_connector;
+ break;
+ }
+ }
+
+ pipe->next_connector_index = connector_index + 1;
+ return use_connector;
+}
+
+static bool pipe_piece_encoder(void *c, void *p)
+{
+ (void)c;
+ struct pipe_internal *pipe = p;
+ int fd = pipe->connector->fd;
+
+ drmModeConnector *connector = pipe->connector->connector;
+ if (connector == NULL)
+ return false;
+
+ int encoder_index = 0;
+ for (encoder_index = pipe->next_encoder_index; encoder_index < connector->count_encoders;
+ encoder_index++) {
+ drmModeEncoder *encoder = drmModeGetEncoder(fd, connector->encoders[encoder_index]);
+ if (encoder == NULL)
+ continue;
+
+ drmModeFreeEncoder(encoder);
+
+ pipe->encoder_id = connector->encoders[encoder_index];
+
+ break;
+ }
+
+ pipe->next_encoder_index = encoder_index + 1;
+ return encoder_index < connector->count_encoders;
+}
+
+static bool pipe_piece_crtc(void *c, void *p)
+{
+ struct pipe_ctx *ctx = c;
+ struct pipe_internal *pipe = p;
+ int fd = pipe->connector->fd;
+ drmModeRes *res = pipe->connector->res;
+
+ drmModeEncoder *encoder = drmModeGetEncoder(fd, pipe->encoder_id);
+ if (encoder == NULL)
+ return false;
+
+ uint32_t possible_crtcs = encoder->possible_crtcs & ctx->crtc_mask;
+ drmModeFreeEncoder(encoder);
+
+ bool use_crtc = false;
+ int crtc_index;
+ for (crtc_index = pipe->next_crtc_index; crtc_index < res->count_crtcs; crtc_index++) {
+ use_crtc = (possible_crtcs & (1 << crtc_index));
+ if (use_crtc) {
+ pipe->crtc_id = res->crtcs[crtc_index];
+ break;
+ }
+ }
+
+ pipe->next_crtc_index = crtc_index;
+ return use_crtc;
+}
+
+static void bs_drm_pipe_plumber_init(struct bs_drm_pipe_plumber *self)
+{
+ assert(self);
+ self->fd = -1;
+ self->crtc_mask = 0xffffffff;
+}
+
+struct bs_drm_pipe_plumber *bs_drm_pipe_plumber_new()
+{
+ struct bs_drm_pipe_plumber *self = calloc(1, sizeof(struct bs_drm_pipe_plumber));
+ assert(self);
+ bs_drm_pipe_plumber_init(self);
+ return self;
+}
+
+void bs_drm_pipe_plumber_destroy(struct bs_drm_pipe_plumber **self)
+{
+ assert(self);
+ assert(*self);
+ free(*self);
+ *self = NULL;
+}
+
+void bs_drm_pipe_plumber_connector_ranks(struct bs_drm_pipe_plumber *self,
+ const uint32_t *connector_ranks)
+{
+ assert(self);
+ self->connector_ranks = connector_ranks;
+}
+
+void bs_drm_pipe_plumber_crtc_mask(struct bs_drm_pipe_plumber *self, uint32_t crtc_mask)
+{
+ assert(self);
+ self->crtc_mask = crtc_mask;
+}
+
+void bs_drm_pipe_plumber_fd(struct bs_drm_pipe_plumber *self, int card_fd)
+{
+ assert(self);
+ self->fd = card_fd;
+}
+
+void bs_drm_pipe_plumber_connector_ptr(struct bs_drm_pipe_plumber *self, drmModeConnector **ptr)
+{
+ assert(self);
+ self->connector_ptr = ptr;
+}
+
+bool bs_drm_pipe_plumber_make(struct bs_drm_pipe_plumber *self, struct bs_drm_pipe *pipe)
+{
+ assert(self);
+ assert(pipe);
+
+ size_t connector_count = 0;
+ size_t fd_count = 0;
+ int fds[DRM_MAX_MINOR];
+ drmModeRes *fd_res[DRM_MAX_MINOR];
+ if (self->fd >= 0) {
+ fds[0] = self->fd;
+ fd_res[0] = drmModeGetResources(fds[0]);
+ if (!fd_res[0])
+ return false;
+ connector_count += fd_res[0]->count_connectors;
+ fd_count++;
+ } else {
+ for (int fd_index = 0; fd_index < DRM_MAX_MINOR; fd_index++) {
+ char *file_path = NULL;
+ int ret = asprintf(&file_path, "/dev/dri/card%d", fd_index);
+ assert(ret != -1);
+ assert(file_path);
+
+ int fd = open(file_path, O_RDWR);
+ free(file_path);
+ if (fd < 0)
+ continue;
+
+ drmModeRes *res = drmModeGetResources(fd);
+ if (!res) {
+ close(fd);
+ continue;
+ }
+ fds[fd_count] = fd;
+ fd_res[fd_count] = res;
+ connector_count += res->count_connectors;
+ fd_count++;
+ }
+ }
+
+ struct fd_connector *connectors = calloc(connector_count, sizeof(struct fd_connector));
+ assert(connectors);
+ size_t connector_index = 0;
+ for (size_t fd_index = 0; fd_index < fd_count; fd_index++) {
+ int fd = fds[fd_index];
+ drmModeRes *res = fd_res[fd_index];
+ for (int fd_conn_index = 0; fd_conn_index < res->count_connectors;
+ fd_conn_index++) {
+ connectors[connector_index].fd = fd;
+ connectors[connector_index].res = res;
+ connectors[connector_index].connector =
+ drmModeGetConnector(fd, res->connectors[fd_conn_index]);
+ connector_index++;
+ }
+ }
+
+ struct pipe_ctx ctx = { connector_count, connectors, self->crtc_mask,
+ self->connector_ranks, bs_rank_skip, 0 };
+ struct pipe_internal pipe_internal;
+ bs_make_pipe_piece pieces[] = { pipe_piece_connector, pipe_piece_encoder, pipe_piece_crtc };
+ bool success = false;
+ while (ctx.best_rank != 0) {
+ struct pipe_internal current_pipe_internal;
+ bool current_success =
+ bs_pipe_make(&ctx, pieces, sizeof(pieces) / sizeof(pieces[0]),
+ ¤t_pipe_internal, sizeof(struct pipe_internal));
+ if (!current_success)
+ break;
+
+ pipe_internal = current_pipe_internal;
+ success = true;
+ if (ctx.connector_ranks == NULL)
+ break;
+
+ ctx.best_rank = current_pipe_internal.connector_rank;
+ ctx.first_connector_index = current_pipe_internal.next_connector_index;
+ }
+
+ if (success) {
+ struct fd_connector *fd_connector = pipe_internal.connector;
+ pipe->fd = fd_connector->fd;
+ pipe->connector_id = fd_connector->connector->connector_id;
+ pipe->encoder_id = pipe_internal.encoder_id;
+ pipe->crtc_id = pipe_internal.crtc_id;
+ if (self->connector_ptr) {
+ *self->connector_ptr = fd_connector->connector;
+ fd_connector->connector = NULL;
+ }
+ }
+
+ for (size_t connector_index = 0; connector_index < connector_count; connector_index++)
+ drmModeFreeConnector(connectors[connector_index].connector);
+ free(connectors);
+
+ for (size_t fd_index = 0; fd_index < fd_count; fd_index++) {
+ int fd = fds[fd_index];
+ if (self->fd != fd && !(success && pipe->fd == fd))
+ close(fd);
+ drmModeFreeResources(fd_res[fd_index]);
+ }
+
+ return success;
+}
+
+bool bs_drm_pipe_make(int fd, struct bs_drm_pipe *pipe)
+{
+ assert(fd >= 0);
+ assert(pipe);
+
+ struct bs_drm_pipe_plumber plumber = { 0 };
+ bs_drm_pipe_plumber_init(&plumber);
+ bs_drm_pipe_plumber_fd(&plumber, fd);
+
+ return bs_drm_pipe_plumber_make(&plumber, pipe);
+}
diff --git a/drm-tests/bsdrm/src/egl.c b/drm-tests/bsdrm/src/egl.c
new file mode 100644
index 0000000..e5ce77d
--- /dev/null
+++ b/drm-tests/bsdrm/src/egl.c
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+static const char *get_egl_error();
+static const char *get_gl_framebuffer_error();
+
+struct bs_egl {
+ bool setup;
+ EGLDisplay display;
+ EGLContext ctx;
+ bool use_image_flush_external;
+ bool use_dma_buf_import_modifiers;
+
+ // Names are the original gl/egl function names with the prefix chopped off.
+ PFNEGLCREATEIMAGEKHRPROC CreateImageKHR;
+ PFNEGLDESTROYIMAGEKHRPROC DestroyImageKHR;
+ PFNEGLIMAGEFLUSHEXTERNALEXTPROC ImageFlushExternal;
+ PFNGLEGLIMAGETARGETTEXTURE2DOESPROC EGLImageTargetTexture2DOES;
+ PFNEGLCREATESYNCKHRPROC CreateSyncKHR;
+ PFNEGLCLIENTWAITSYNCKHRPROC ClientWaitSyncKHR;
+ PFNEGLDESTROYSYNCKHRPROC DestroySyncKHR;
+};
+
+struct bs_egl_fb {
+ GLuint tex;
+ GLuint fb;
+};
+
+struct bs_egl *bs_egl_new()
+{
+ struct bs_egl *self = calloc(1, sizeof(struct bs_egl));
+ assert(self);
+ self->display = EGL_NO_DISPLAY;
+ self->ctx = EGL_NO_CONTEXT;
+ return self;
+}
+
+void bs_egl_destroy(struct bs_egl **egl)
+{
+ assert(egl);
+ struct bs_egl *self = *egl;
+ assert(self);
+
+ if (self->ctx != EGL_NO_CONTEXT) {
+ assert(self->display != EGL_NO_DISPLAY);
+ eglMakeCurrent(self->display, NULL, NULL, NULL);
+ eglDestroyContext(self->display, self->ctx);
+ }
+
+ if (self->display != EGL_NO_DISPLAY)
+ eglTerminate(self->display);
+
+ free(self);
+ *egl = NULL;
+}
+
+bool bs_egl_setup(struct bs_egl *self)
+{
+ assert(self);
+ assert(!self->setup);
+
+ self->CreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
+ self->DestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
+ self->ImageFlushExternal =
+ (PFNEGLIMAGEFLUSHEXTERNALEXTPROC)eglGetProcAddress("eglImageFlushExternalEXT");
+ self->EGLImageTargetTexture2DOES =
+ (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
+ if (!self->CreateImageKHR || !self->DestroyImageKHR || !self->EGLImageTargetTexture2DOES) {
+ bs_debug_error(
+ "eglGetProcAddress returned NULL for a required extension entry point.");
+ return false;
+ }
+
+ self->CreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
+ self->ClientWaitSyncKHR =
+ (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR");
+ self->DestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR");
+
+ self->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (self->display == EGL_NO_DISPLAY) {
+ bs_debug_error("failed to get egl display");
+ return false;
+ }
+
+ if (!eglInitialize(self->display, NULL /* ignore version */, NULL /* ignore version */)) {
+ bs_debug_error("failed to initialize egl: %s\n", get_egl_error());
+ return false;
+ }
+
+ // Get any EGLConfig. We need one to create a context, but it isn't used to create any
+ // surfaces.
+ const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_DONT_CARE, EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT, EGL_NONE };
+ EGLConfig egl_config;
+ EGLint num_configs;
+ if (!eglChooseConfig(self->display, config_attribs, &egl_config, 1,
+ &num_configs /* unused but can't be null */)) {
+ bs_debug_error("eglChooseConfig() failed with error: %s", get_egl_error());
+ goto terminate_display;
+ }
+
+ if (!eglBindAPI(EGL_OPENGL_ES_API)) {
+ bs_debug_error("failed to bind OpenGL ES: %s", get_egl_error());
+ goto terminate_display;
+ }
+
+ const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+
+ self->ctx = eglCreateContext(self->display, egl_config,
+ EGL_NO_CONTEXT /* no shared context */, context_attribs);
+ if (self->ctx == EGL_NO_CONTEXT) {
+ bs_debug_error("failed to create OpenGL ES Context: %s", get_egl_error());
+ goto terminate_display;
+ }
+
+ if (!eglMakeCurrent(self->display, EGL_NO_SURFACE /* no default draw surface */,
+ EGL_NO_SURFACE /* no default draw read */, self->ctx)) {
+ bs_debug_error("failed to make the OpenGL ES Context current: %s", get_egl_error());
+ goto destroy_context;
+ }
+
+ const char *egl_extensions = eglQueryString(self->display, EGL_EXTENSIONS);
+ if (!bs_egl_has_extension("EGL_KHR_image_base", egl_extensions)) {
+ bs_debug_error("EGL_KHR_image_base extension not supported");
+ goto destroy_context;
+ }
+ if (!bs_egl_has_extension("EGL_EXT_image_dma_buf_import", egl_extensions)) {
+ bs_debug_error("EGL_EXT_image_dma_buf_import extension not supported");
+ goto destroy_context;
+ }
+
+ if (!bs_egl_has_extension("EGL_KHR_fence_sync", egl_extensions) &&
+ !bs_egl_has_extension("EGL_KHR_wait_sync", egl_extensions)) {
+ bs_debug_error("EGL_KHR_fence_sync and EGL_KHR_wait_sync extension not supported");
+ goto destroy_context;
+ }
+
+ if (bs_egl_has_extension("EGL_EXT_image_flush_external", egl_extensions)) {
+ if (!self->ImageFlushExternal) {
+ bs_debug_print("WARNING", __func__, __FILE__, __LINE__,
+ "EGL_EXT_image_flush_external extension is supported, but "
+ "eglGetProcAddress returned NULL.");
+ } else {
+ self->use_image_flush_external = true;
+ }
+ }
+ if (bs_egl_has_extension("EGL_EXT_image_dma_buf_import_modifiers", egl_extensions))
+ self->use_dma_buf_import_modifiers = true;
+
+ const char *gl_extensions = (const char *)glGetString(GL_EXTENSIONS);
+ if (!bs_egl_has_extension("GL_OES_EGL_image", gl_extensions)) {
+ bs_debug_error("GL_OES_EGL_image extension not supported");
+ goto destroy_context;
+ }
+
+ self->setup = true;
+
+ return true;
+
+destroy_context:
+ eglDestroyContext(self->display, self->ctx);
+terminate_display:
+ eglTerminate(self->display);
+ self->display = EGL_NO_DISPLAY;
+ return false;
+}
+
+bool bs_egl_make_current(struct bs_egl *self)
+{
+ assert(self);
+ assert(self->display != EGL_NO_DISPLAY);
+ assert(self->ctx != EGL_NO_CONTEXT);
+ return eglMakeCurrent(self->display, EGL_NO_SURFACE /* No default draw surface */,
+ EGL_NO_SURFACE /* No default draw read */, self->ctx);
+}
+
+EGLImageKHR bs_egl_image_create_gbm(struct bs_egl *self, struct gbm_bo *bo)
+{
+ assert(self);
+ assert(self->CreateImageKHR);
+ assert(self->display != EGL_NO_DISPLAY);
+ assert(bo);
+
+ int fds[GBM_MAX_PLANES];
+ for (size_t plane = 0; plane < gbm_bo_get_num_planes(bo); plane++) {
+ fds[plane] = gbm_bo_get_plane_fd(bo, plane);
+ if (fds[plane] < 0) {
+ bs_debug_error("failed to get fb for bo: %d", fds[plane]);
+ return EGL_NO_IMAGE_KHR;
+ }
+ }
+
+ // When the bo has 3 planes with modifier support, it requires 39 components.
+ EGLint khr_image_attrs[39] = {
+ EGL_WIDTH,
+ gbm_bo_get_width(bo),
+ EGL_HEIGHT,
+ gbm_bo_get_height(bo),
+ EGL_LINUX_DRM_FOURCC_EXT,
+ (int)gbm_bo_get_format(bo),
+ EGL_NONE,
+ };
+
+ size_t attrs_index = 6;
+ for (size_t plane = 0; plane < gbm_bo_get_num_planes(bo); plane++) {
+ khr_image_attrs[attrs_index++] = EGL_DMA_BUF_PLANE0_FD_EXT + plane * 3;
+ khr_image_attrs[attrs_index++] = fds[plane];
+ khr_image_attrs[attrs_index++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT + plane * 3;
+ khr_image_attrs[attrs_index++] = gbm_bo_get_plane_offset(bo, plane);
+ khr_image_attrs[attrs_index++] = EGL_DMA_BUF_PLANE0_PITCH_EXT + plane * 3;
+ khr_image_attrs[attrs_index++] = gbm_bo_get_plane_stride(bo, plane);
+ if (self->use_dma_buf_import_modifiers) {
+ const uint64_t modifier = gbm_bo_get_format_modifier(bo);
+ khr_image_attrs[attrs_index++] =
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT + plane * 2;
+ khr_image_attrs[attrs_index++] = modifier & 0xfffffffful;
+ khr_image_attrs[attrs_index++] =
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + plane * 2;
+ khr_image_attrs[attrs_index++] = modifier >> 32;
+ }
+ }
+
+ // Instead of disabling the extension completely, allow it to be used
+ // without creating EGLImages with correct attributes on Tegra.
+ // TODO(gsingh): Remove this once Tegra is end-of-life.
+ const char *egl_vendor = eglQueryString(self->display, EGL_VENDOR);
+ if (self->use_image_flush_external && strcmp(egl_vendor, "NVIDIA")) {
+ khr_image_attrs[attrs_index++] = EGL_IMAGE_EXTERNAL_FLUSH_EXT;
+ khr_image_attrs[attrs_index++] = EGL_TRUE;
+ }
+
+ khr_image_attrs[attrs_index++] = EGL_NONE;
+
+ EGLImageKHR image =
+ self->CreateImageKHR(self->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
+ NULL /* no client buffer */, khr_image_attrs);
+
+ if (image == EGL_NO_IMAGE_KHR) {
+ bs_debug_error("failed to make image from target buffer: %s", get_egl_error());
+ return EGL_NO_IMAGE_KHR;
+ }
+
+ for (size_t plane = 0; plane < gbm_bo_get_num_planes(bo); plane++) {
+ close(fds[plane]);
+ }
+
+ return image;
+}
+
+void bs_egl_image_destroy(struct bs_egl *self, EGLImageKHR *image)
+{
+ assert(self);
+ assert(image);
+ assert(*image != EGL_NO_IMAGE_KHR);
+ assert(self->DestroyImageKHR);
+ self->DestroyImageKHR(self->display, *image);
+ *image = EGL_NO_IMAGE_KHR;
+}
+
+bool bs_egl_image_flush_external(struct bs_egl *self, EGLImageKHR image)
+{
+ assert(self);
+ assert(image != EGL_NO_IMAGE_KHR);
+ if (!self->use_image_flush_external)
+ return true;
+ const EGLAttrib attrs[] = { EGL_NONE };
+ return self->ImageFlushExternal(self->display, image, attrs);
+}
+
+struct bs_egl_fb *bs_egl_fb_new(struct bs_egl *self, EGLImageKHR image)
+{
+ assert(self);
+ assert(self->EGLImageTargetTexture2DOES);
+
+ struct bs_egl_fb *fb = calloc(1, sizeof(struct bs_egl_fb));
+ assert(fb);
+
+ glGenTextures(1, &fb->tex);
+ glBindTexture(GL_TEXTURE_2D, fb->tex);
+ self->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glGenFramebuffers(1, &fb->fb);
+ glBindFramebuffer(GL_FRAMEBUFFER, fb->fb);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->tex, 0);
+
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ bs_debug_error("failed framebuffer check for created target buffer: %s",
+ get_gl_framebuffer_error());
+ glDeleteFramebuffers(1, &fb->fb);
+ glDeleteTextures(1, &fb->tex);
+ free(fb);
+ return NULL;
+ }
+
+ return fb;
+}
+
+void bs_egl_fb_destroy(struct bs_egl_fb **fb)
+{
+ assert(fb);
+ struct bs_egl_fb *self = *fb;
+ assert(self);
+
+ glDeleteFramebuffers(1, &self->fb);
+ glDeleteTextures(1, &self->tex);
+
+ free(self);
+ *fb = NULL;
+}
+
+GLuint bs_egl_fb_name(struct bs_egl_fb *self)
+{
+ assert(self);
+ return self->fb;
+}
+
+bool bs_egl_target_texture2D(struct bs_egl *self, EGLImageKHR image)
+{
+ assert(self);
+ assert(self->EGLImageTargetTexture2DOES);
+ self->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
+ GLint error = glGetError();
+ return (error == GL_NO_ERROR);
+}
+
+EGLSyncKHR bs_egl_create_sync(struct bs_egl *self, EGLenum type, const EGLint *attrib_list)
+{
+ return self->CreateSyncKHR(self->display, type, attrib_list);
+}
+
+EGLint bs_egl_wait_sync(struct bs_egl *self, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout)
+{
+ return self->ClientWaitSyncKHR(self->display, sync, flags, timeout);
+}
+
+EGLBoolean bs_egl_destroy_sync(struct bs_egl *self, EGLSyncKHR sync)
+{
+ return self->DestroySyncKHR(self->display, sync);
+}
+
+bool bs_egl_has_extension(const char *extension, const char *extensions)
+{
+ const char *start, *where, *terminator;
+ start = extensions;
+ for (;;) {
+ where = (char *)strstr((const char *)start, extension);
+ if (!where)
+ break;
+ terminator = where + strlen(extension);
+ if (where == start || *(where - 1) == ' ')
+ if (*terminator == ' ' || *terminator == '\0')
+ return true;
+ start = terminator;
+ }
+ return false;
+}
+
+static const char *get_egl_error()
+{
+ switch (eglGetError()) {
+ case EGL_SUCCESS:
+ return "EGL_SUCCESS";
+ case EGL_NOT_INITIALIZED:
+ return "EGL_NOT_INITIALIZED";
+ case EGL_BAD_ACCESS:
+ return "EGL_BAD_ACCESS";
+ case EGL_BAD_ALLOC:
+ return "EGL_BAD_ALLOC";
+ case EGL_BAD_ATTRIBUTE:
+ return "EGL_BAD_ATTRIBUTE";
+ case EGL_BAD_CONTEXT:
+ return "EGL_BAD_CONTEXT";
+ case EGL_BAD_CONFIG:
+ return "EGL_BAD_CONFIG";
+ case EGL_BAD_CURRENT_SURFACE:
+ return "EGL_BAD_CURRENT_SURFACE";
+ case EGL_BAD_DISPLAY:
+ return "EGL_BAD_DISPLAY";
+ case EGL_BAD_SURFACE:
+ return "EGL_BAD_SURFACE";
+ case EGL_BAD_MATCH:
+ return "EGL_BAD_MATCH";
+ case EGL_BAD_PARAMETER:
+ return "EGL_BAD_PARAMETER";
+ case EGL_BAD_NATIVE_PIXMAP:
+ return "EGL_BAD_NATIVE_PIXMAP";
+ case EGL_BAD_NATIVE_WINDOW:
+ return "EGL_BAD_NATIVE_WINDOW";
+ case EGL_CONTEXT_LOST:
+ return "EGL_CONTEXT_LOST";
+ default:
+ return "EGL_???";
+ }
+}
+
+static const char *get_gl_framebuffer_error()
+{
+ switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
+ case GL_FRAMEBUFFER_COMPLETE:
+ return "GL_FRAMEBUFFER_COMPLETE";
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ return "GL_FRAMEBUFFER_UNSUPPORTED";
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+ return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+ default:
+ return "GL_FRAMEBUFFER_???";
+ }
+}
diff --git a/drm-tests/bsdrm/src/gl.c b/drm-tests/bsdrm/src/gl.c
new file mode 100644
index 0000000..ebd71f8
--- /dev/null
+++ b/drm-tests/bsdrm/src/gl.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+GLuint bs_gl_shader_create(GLenum type, const GLchar *src)
+{
+ assert(src);
+ GLuint shader = glCreateShader(type);
+ if (!shader) {
+ bs_debug_error("failed call to glCreateShader(%d)", type);
+ return 0;
+ }
+
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint status;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE) {
+ GLint log_len = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
+ char *shader_log = calloc(log_len, sizeof(char));
+ assert(shader_log);
+ glGetShaderInfoLog(shader, log_len, NULL, shader_log);
+ bs_debug_error("failed to compile shader: %s", shader_log);
+ free(shader_log);
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+}
+
+GLuint bs_gl_program_create_vert_frag_bind(const GLchar *vert_src, const GLchar *frag_src,
+ struct bs_gl_program_create_binding *bindings)
+{
+ assert(vert_src);
+ assert(frag_src);
+ GLuint program = glCreateProgram();
+ if (!program) {
+ bs_debug_error("failed to create program");
+ return 0;
+ }
+
+ GLuint vert_shader = bs_gl_shader_create(GL_VERTEX_SHADER, vert_src);
+ if (!vert_shader) {
+ bs_debug_error("failed to create vertex shader");
+ glDeleteProgram(program);
+ return 0;
+ }
+
+ GLuint frag_shader = bs_gl_shader_create(GL_FRAGMENT_SHADER, frag_src);
+ if (!frag_shader) {
+ bs_debug_error("failed to create fragment shader");
+ glDeleteShader(vert_shader);
+ glDeleteProgram(program);
+ return 0;
+ }
+
+ glAttachShader(program, vert_shader);
+ glAttachShader(program, frag_shader);
+ if (bindings) {
+ for (size_t binding_index = 0; bindings[binding_index].name != NULL;
+ binding_index++) {
+ const struct bs_gl_program_create_binding *binding =
+ &bindings[binding_index];
+ glBindAttribLocation(program, binding->index, binding->name);
+ }
+ }
+ glLinkProgram(program);
+ glDetachShader(program, vert_shader);
+ glDetachShader(program, frag_shader);
+ glDeleteShader(vert_shader);
+ glDeleteShader(frag_shader);
+
+ GLint status;
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (!status) {
+ GLint log_len = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
+ char *program_log = calloc(log_len, sizeof(char));
+ assert(program_log);
+ glGetProgramInfoLog(program, log_len, NULL, program_log);
+ bs_debug_error("failed to link program: %s", program_log);
+ free(program_log);
+ glDeleteProgram(program);
+ return 0;
+ }
+
+ return program;
+}
diff --git a/drm-tests/bsdrm/src/mmap.c b/drm-tests/bsdrm/src/mmap.c
new file mode 100644
index 0000000..2955899
--- /dev/null
+++ b/drm-tests/bsdrm/src/mmap.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <linux/dma-buf.h>
+#include <sys/ioctl.h>
+
+#include "bs_drm.h"
+
+#define HANDLE_EINTR(x) \
+ ({ \
+ int eintr_wrapper_counter = 0; \
+ int eintr_wrapper_result; \
+ do { \
+ eintr_wrapper_result = (x); \
+ } while (eintr_wrapper_result == -1 && errno == EINTR && \
+ eintr_wrapper_counter++ < 100); \
+ eintr_wrapper_result; \
+ })
+
+struct bs_map_info {
+ size_t plane_index;
+ void *ptr;
+ void *map_data;
+};
+
+typedef void *(*bs_map_t)(struct bs_mapper *mapper, struct gbm_bo *bo, size_t plane,
+ struct bs_map_info *info);
+typedef void (*bs_unmap_t)(struct gbm_bo *bo, struct bs_map_info *info);
+
+struct bs_mapper {
+ bs_map_t map_plane_fn;
+ bs_unmap_t unmap_plane_fn;
+ int device_fd;
+};
+
+static void *dma_buf_map(struct bs_mapper *mapper, struct gbm_bo *bo, size_t plane,
+ struct bs_map_info *info)
+{
+ int drm_prime_fd = gbm_bo_get_plane_fd(bo, plane);
+ uint32_t handle = gbm_bo_get_plane_handle(bo, plane).u32;
+ size_t length = 0;
+
+ for (size_t p = 0; p <= plane; p++) {
+ if (gbm_bo_get_plane_handle(bo, p).u32 == handle)
+ length += gbm_bo_get_plane_size(bo, p);
+ }
+
+ void *ptr = mmap(NULL, length, (PROT_READ | PROT_WRITE), MAP_SHARED, drm_prime_fd, 0);
+ if (ptr == MAP_FAILED) {
+ bs_debug_error("dma-buf mmap returned MAP_FAILED: %d", errno);
+ ptr = MAP_FAILED;
+ } else {
+ ptr += gbm_bo_get_plane_offset(bo, plane);
+ }
+
+ struct dma_buf_sync sync_start = { 0 };
+ sync_start.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;
+ int ret = HANDLE_EINTR(ioctl(drm_prime_fd, DMA_BUF_IOCTL_SYNC, &sync_start));
+ if (ret)
+ bs_debug_error("DMA_BUF_IOCTL_SYNC failed");
+
+ close(drm_prime_fd);
+ return ptr;
+}
+
+static void dma_buf_unmap(struct gbm_bo *bo, struct bs_map_info *info)
+{
+ struct dma_buf_sync sync_end = { 0 };
+ sync_end.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;
+ int drm_prime_fd = gbm_bo_get_plane_fd(bo, info->plane_index);
+ int ret = HANDLE_EINTR(ioctl(drm_prime_fd, DMA_BUF_IOCTL_SYNC, &sync_end));
+ close(drm_prime_fd);
+ if (ret)
+ bs_debug_error("DMA_BUF_IOCTL_SYNC failed");
+
+ void *addr = info->ptr;
+ addr -= gbm_bo_get_plane_offset(bo, info->plane_index);
+ ret = munmap(addr, gbm_bo_get_plane_size(bo, info->plane_index));
+ if (ret)
+ bs_debug_error("dma-buf unmap failed.");
+}
+
+static void *gem_map(struct bs_mapper *mapper, struct gbm_bo *bo, size_t plane,
+ struct bs_map_info *info)
+{
+ uint32_t w = gbm_bo_get_width(bo);
+ uint32_t h = gbm_bo_get_height(bo);
+ uint32_t stride;
+ void *ptr =
+ gbm_bo_map(bo, 0, 0, w, h, GBM_BO_TRANSFER_READ_WRITE, &stride, &info->map_data, plane);
+ return ptr;
+}
+
+static void gem_unmap(struct gbm_bo *bo, struct bs_map_info *info)
+{
+ gbm_bo_unmap(bo, info->map_data);
+}
+
+static void *dumb_map(struct bs_mapper *mapper, struct gbm_bo *bo, size_t plane,
+ struct bs_map_info *info)
+{
+ if (plane) {
+ bs_debug_error("dump map supports only single plane buffer.");
+ return MAP_FAILED;
+ }
+
+ int prime_fd = gbm_bo_get_fd(bo);
+ uint32_t size = gbm_bo_get_plane_size(bo, plane);
+
+ uint32_t bo_handle;
+ int ret = drmPrimeFDToHandle(mapper->device_fd, prime_fd, &bo_handle);
+ if (ret) {
+ bs_debug_error("dump map failed.");
+ return MAP_FAILED;
+ }
+
+ struct drm_mode_map_dumb mmap_arg = { 0 };
+ mmap_arg.handle = bo_handle;
+
+ ret = drmIoctl(mapper->device_fd, DRM_IOCTL_MODE_MAP_DUMB, &mmap_arg);
+ if (ret) {
+ bs_debug_error("failed DRM_IOCTL_MODE_MAP_DUMB: %d", ret);
+ return MAP_FAILED;
+ }
+
+ if (mmap_arg.offset == 0) {
+ bs_debug_error("DRM_IOCTL_MODE_MAP_DUMB returned 0 offset");
+ return MAP_FAILED;
+ }
+
+ void *ptr = mmap(NULL, size, (PROT_READ | PROT_WRITE), MAP_SHARED, mapper->device_fd,
+ mmap_arg.offset);
+
+ if (ptr == MAP_FAILED) {
+ bs_debug_error("mmap returned MAP_FAILED: %d", errno);
+ return MAP_FAILED;
+ }
+
+ close(prime_fd);
+ return ptr;
+}
+
+static void dumb_unmap(struct gbm_bo *bo, struct bs_map_info *info)
+{
+ int ret = munmap(info->ptr, gbm_bo_get_plane_size(bo, info->plane_index));
+ if (ret)
+ bs_debug_error("dump unmap failed.");
+}
+
+struct bs_mapper *bs_mapper_dma_buf_new()
+{
+ struct bs_mapper *mapper = calloc(1, sizeof(struct bs_mapper));
+ assert(mapper);
+ mapper->map_plane_fn = dma_buf_map;
+ mapper->unmap_plane_fn = dma_buf_unmap;
+ mapper->device_fd = -1;
+ return mapper;
+}
+
+struct bs_mapper *bs_mapper_gem_new()
+{
+ struct bs_mapper *mapper = calloc(1, sizeof(struct bs_mapper));
+ assert(mapper);
+ mapper->map_plane_fn = gem_map;
+ mapper->unmap_plane_fn = gem_unmap;
+ mapper->device_fd = -1;
+ return mapper;
+}
+
+struct bs_mapper *bs_mapper_dumb_new(int device_fd)
+{
+ assert(device_fd >= 0);
+ struct bs_mapper *mapper = calloc(1, sizeof(struct bs_mapper));
+ assert(mapper);
+ mapper->map_plane_fn = dumb_map;
+ mapper->unmap_plane_fn = dumb_unmap;
+ mapper->device_fd = dup(device_fd);
+ assert(mapper->device_fd >= 0);
+ return mapper;
+}
+
+void bs_mapper_destroy(struct bs_mapper *mapper)
+{
+ assert(mapper);
+ if (mapper->device_fd >= 0)
+ close(mapper->device_fd);
+
+ free(mapper);
+}
+
+void *bs_mapper_map(struct bs_mapper *mapper, struct gbm_bo *bo, size_t plane, void **map_data)
+{
+ assert(mapper);
+ assert(bo);
+ assert(map_data);
+ struct bs_map_info *info = calloc(1, sizeof(struct bs_map_info));
+ info->plane_index = plane;
+ void *ptr = mapper->map_plane_fn(mapper, bo, plane, info);
+ if (ptr == MAP_FAILED) {
+ free(info);
+ return MAP_FAILED;
+ }
+
+ info->ptr = ptr;
+ *map_data = info;
+ return info->ptr;
+}
+
+void bs_mapper_unmap(struct bs_mapper *mapper, struct gbm_bo *bo, void *map_data)
+{
+ struct bs_map_info *info = map_data;
+ assert(info);
+ mapper->unmap_plane_fn(bo, info);
+ free(info);
+}
diff --git a/drm-tests/bsdrm/src/module.mk b/drm-tests/bsdrm/src/module.mk
new file mode 100644
index 0000000..abdbc39
--- /dev/null
+++ b/drm-tests/bsdrm/src/module.mk
@@ -0,0 +1,21 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include common.mk
+
+CFLAGS += -std=gnu99 -I$(SRC)/bsdrm/include
+
+CC_STATIC_LIBRARY(libbsdrm.pic.a): \
+ bsdrm/src/app.o \
+ bsdrm/src/debug.o \
+ bsdrm/src/draw.o \
+ bsdrm/src/drm_connectors.o \
+ bsdrm/src/drm_fb.o \
+ bsdrm/src/drm_open.o \
+ bsdrm/src/drm_pipe.o \
+ bsdrm/src/egl.o \
+ bsdrm/src/gl.o \
+ bsdrm/src/mmap.o \
+ bsdrm/src/open.o \
+ bsdrm/src/pipe.o
diff --git a/drm-tests/bsdrm/src/open.c b/drm-tests/bsdrm/src/open.c
new file mode 100644
index 0000000..a7cc582
--- /dev/null
+++ b/drm-tests/bsdrm/src/open.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+// Suppresses warnings for our usage of asprintf with one of the parameters.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+void bs_open_enumerate(const char *format, unsigned start, unsigned end,
+ bs_open_enumerate_func body, void *user)
+{
+ assert(end >= start);
+ for (unsigned dev_index = start; dev_index < end; dev_index++) {
+ char *file_path = NULL;
+ int ret = asprintf(&file_path, format, dev_index);
+ if (ret == -1)
+ continue;
+ assert(file_path);
+
+ int fd = open(file_path, O_RDWR);
+ free(file_path);
+ if (fd < 0)
+ continue;
+
+ bool end = body(user, fd);
+ close(fd);
+
+ if (end)
+ return;
+ }
+}
+#pragma GCC diagnostic pop
+
+struct bs_open_filtered_user {
+ bs_open_filter_func filter;
+ int fd;
+};
+
+static bool bs_open_filtered_body(void *user, int fd)
+{
+ struct bs_open_filtered_user *data = (struct bs_open_filtered_user *)user;
+ if (data->filter(fd)) {
+ data->fd = dup(fd);
+ return true;
+ }
+
+ return false;
+}
+
+int bs_open_filtered(const char *format, unsigned start, unsigned end, bs_open_filter_func filter)
+{
+ struct bs_open_filtered_user data = { filter, -1 };
+ bs_open_enumerate(format, start, end, bs_open_filtered_body, &data);
+ return data.fd;
+}
+
+struct bs_open_ranked_user {
+ bs_open_rank_func rank;
+ uint32_t rank_index;
+ int fd;
+};
+
+static bool bs_open_ranked_body(void *user, int fd)
+{
+ struct bs_open_ranked_user *data = (struct bs_open_ranked_user *)user;
+ uint32_t rank_index = data->rank(fd);
+
+ if (data->rank_index > rank_index) {
+ data->rank_index = rank_index;
+ if (data->fd >= 0)
+ close(data->fd);
+ data->fd = dup(fd);
+ }
+
+ return rank_index == 0;
+}
+
+int bs_open_ranked(const char *format, unsigned start, unsigned end, bs_open_rank_func rank)
+{
+ struct bs_open_ranked_user data = { rank, UINT32_MAX, -1 };
+ bs_open_enumerate(format, start, end, bs_open_ranked_body, &data);
+ return data.fd;
+}
diff --git a/drm-tests/bsdrm/src/pipe.c b/drm-tests/bsdrm/src/pipe.c
new file mode 100644
index 0000000..047eac6
--- /dev/null
+++ b/drm-tests/bsdrm/src/pipe.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+bool bs_pipe_make(void *context, bs_make_pipe_piece *pieces, size_t piece_count, void *out_pipe,
+ size_t pipe_size)
+{
+ bool success = false;
+ char *pipe_stack = calloc(pipe_size, piece_count + 1);
+ for (size_t i = 0; i < piece_count;) {
+ char *pipe_ptr = pipe_stack + i * pipe_size;
+ if (pieces[i](context, pipe_ptr)) {
+ i++;
+ memcpy(pipe_ptr + pipe_size, pipe_ptr, pipe_size);
+ continue;
+ }
+
+ if (i == 0)
+ goto out;
+
+ i--;
+ }
+
+ memcpy(out_pipe, pipe_stack + (piece_count - 1) * pipe_size, pipe_size);
+ success = true;
+
+out:
+ free(pipe_stack);
+ return success;
+}
diff --git a/drm-tests/common.mk b/drm-tests/common.mk
new file mode 100644
index 0000000..b8aade5
--- /dev/null
+++ b/drm-tests/common.mk
@@ -0,0 +1,844 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# If this file is part of another source distribution, it's license may be
+# stored in LICENSE.makefile or LICENSE.common.mk.
+#
+# NOTE NOTE NOTE
+# The authoritative common.mk is located in:
+# https://chromium.googlesource.com/chromiumos/platform/common-mk.git
+# Please make all changes there, then copy into place in other repos.
+# NOTE NOTE NOTE
+#
+# This file provides a common architecture for building C/C++ source trees.
+# It uses recursive makefile inclusion to create a single make process which
+# can be built in the source tree or with the build artifacts placed elsewhere.
+#
+# It is fully parallelizable for all targets, including static archives.
+#
+# To use:
+# 1. Place common.mk in your top source level
+# 2. In your top-level Makefile, place "include common.mk" at the top
+# 3. In all subdirectories, create a 'module.mk' file that starts with:
+# include common.mk
+# And then contains the remainder of your targets.
+# 4. All build targets should look like:
+# relative/path/target: relative/path/obj.o
+#
+# See existing makefiles for rule examples.
+#
+# Exported macros:
+# - cc_binary, cxx_binary provide standard compilation steps for binaries
+# - cxx_library, cc_library provide standard compilation steps for
+# shared objects.
+# All of the above optionally take an argument for extra flags.
+# - update_archive creates/updates a given .a target
+#
+# Instead of using the build macros, most users can just use wrapped targets:
+# - CXX_BINARY, CC_BINARY, CC_STATIC_BINARY, CXX_STATIC_BINARY
+# - CXX_LIBRARY, CC_LIBRARY, CC_STATIC_LIBRARY, CXX_STATIC_LIBRARY
+# - E.g., CXX_BINARY(mahbinary): foo.o
+# - object.depends targets may be used when a prerequisite is required for an
+# object file. Because object files result in multiple build artifacts to
+# handle PIC and PIE weirdness. E.g.
+# foo.o.depends: generated/dbus.h
+# - TEST(binary) or TEST(CXX_BINARY(binary)) may be used as a prerequisite
+# for the tests target to trigger an automated test run.
+# - CLEAN(file_or_dir) dependency can be added to 'clean'.
+#
+# If source code is being generated, rules will need to be registered for
+# compiling the objects. This can be done by adding one of the following
+# to the Makefile:
+# - For C source files
+# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/gen_b.o,CC,c))
+# - For C++ source files
+# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/gen_b.o,CXX,cc))
+#
+# Exported targets meant to have prerequisites added to:
+# - all - Your desired targets should be given
+# - tests - Any TEST(test_binary) targets should be given
+# - FORCE - force the given target to run regardless of changes
+# In most cases, using .PHONY is preferred.
+#
+# Possible command line variables:
+# - COLOR=[0|1] to set ANSI color output (default: 1)
+# - VERBOSE=[0|1] to hide/show commands (default: 0)
+# - MODE=[opt|dbg|profiling] (default: opt)
+# opt - Enable optimizations for release builds
+# dbg - Turn down optimization for debugging
+# profiling - Turn off optimization and turn on profiling/coverage
+# support.
+# - ARCH=[x86|arm|supported qemu name] (default: from portage or uname -m)
+# - SPLITDEBUG=[0|1] splits debug info in target.debug (default: 0)
+# If NOSTRIP=1, SPLITDEBUG will never strip the final emitted objects.
+# - NOSTRIP=[0|1] determines if binaries are stripped. (default: 1)
+# NOSTRIP=0 and MODE=opt will also drop -g from the CFLAGS.
+# - VALGRIND=[0|1] runs tests under valgrind (default: 0)
+# - OUT=/path/to/builddir puts all output in given path (default: $PWD)
+# - VALGRIND_ARGS="" supplies extra memcheck arguments
+#
+# Per-target(-ish) variable:
+# - NEEDS_ROOT=[0|1] allows a TEST() target to run with root.
+# Default is 0 unless it is running under QEmu.
+# - NEEDS_MOUNTS=[0|1] allows a TEST() target running on QEmu to get
+# setup mounts in the $(SYSROOT)
+#
+# Caveats:
+# - Directories or files with spaces in them DO NOT get along with GNU Make.
+# If you need them, all uses of dir/notdir/etc will need to have magic
+# wrappers. Proceed at risk to your own sanity.
+# - External CXXFLAGS and CFLAGS should be passed via the environment since
+# this file does not use 'override' to control them.
+# - Our version of GNU Make doesn't seem to support the 'private' variable
+# annotation, so you can't tag a variable private on a wrapping target.
+
+# Behavior configuration variables
+SPLITDEBUG ?= 0
+NOSTRIP ?= 1
+VALGRIND ?= 0
+COLOR ?= 1
+VERBOSE ?= 0
+MODE ?= opt
+ARCH ?= $(shell uname -m)
+NEEDS_ROOT = 0
+NEEDS_MOUNTS = 0
+
+# Put objects in a separate tree based on makefile locations
+# This means you can build a tree without touching it:
+# make -C $SRCDIR # will create ./build-$(MODE)
+# Or
+# make -C $SRCDIR OUT=$PWD
+# This variable is extended on subdir calls and doesn't need to be re-called.
+OUT ?= $(PWD)/
+
+# Make OUT now so we can use realpath.
+$(shell mkdir -p "$(OUT)")
+
+# TODO(wad) Relative paths are resolved against SRC and not the calling dir.
+# Ensure a command-line supplied OUT has a slash
+override OUT := $(realpath $(OUT))/
+
+# SRC is not meant to be set by the end user, but during make call relocation.
+# $(PWD) != $(CURDIR) all the time.
+export SRC ?= $(CURDIR)
+
+# Re-start in the $(OUT) directory if we're not there.
+# We may be invoked using -C or bare and we need to ensure behavior
+# is consistent so we check both PWD vs OUT and PWD vs CURDIR.
+override RELOCATE_BUILD := 0
+ifneq (${PWD}/,${OUT})
+override RELOCATE_BUILD := 1
+endif
+# Make sure we're running with no builtin targets. They cause
+# leakage and mayhem!
+ifneq (${PWD},${CURDIR})
+override RELOCATE_BUILD := 1
+# If we're run from the build dir, don't let it get cleaned up later.
+ifeq (${PWD}/,${OUT})
+$(shell touch "$(PWD)/.dont_delete_on_clean")
+endif
+endif # ifneq (${PWD},${CURDIR}
+
+# "Relocate" if we need to restart without implicit rules.
+ifeq ($(subst r,,$(MAKEFLAGS)),$(MAKEFLAGS))
+override RELOCATE_BUILD := 1
+endif
+
+ifeq (${RELOCATE_BUILD},1)
+# By default, silence build output. Reused below as well.
+QUIET = @
+ifeq ($(VERBOSE),1)
+ QUIET=
+endif
+
+# This target will override all targets, including prerequisites. To avoid
+# calling $(MAKE) once per prereq on the given CMDGOAL, we guard it with a local
+# variable.
+RUN_ONCE := 0
+MAKECMDGOALS ?= all
+# Keep the rules split as newer make does not allow them to be declared
+# on the same line. But the way :: rules work, the _all here will also
+# invoke the %:: rule while retaining "_all" as the default.
+_all::
+%::
+ $(if $(filter 0,$(RUN_ONCE)), \
+ cd "$(OUT)" && \
+ $(MAKE) -r -I "$(SRC)" -f "$(CURDIR)/Makefile" \
+ SRC="$(CURDIR)" OUT="$(OUT)" $(foreach g,$(MAKECMDGOALS),"$(g)"),)
+ $(eval RUN_ONCE := 1)
+pass-to-subcall := 1
+endif
+
+ifeq ($(pass-to-subcall),)
+
+# Only call MODULE if we're in a submodule
+MODULES_LIST := $(filter-out Makefile %.d,$(MAKEFILE_LIST))
+ifeq ($(words $(filter-out Makefile common.mk %.d $(SRC)/Makefile \
+ $(SRC)/common.mk,$(MAKEFILE_LIST))),0)
+
+# All the top-level defines outside of module.mk.
+
+#
+# Helper macros
+#
+
+# Create the directory if it doesn't yet exist.
+define auto_mkdir
+ $(if $(wildcard $(dir $1)),$2,$(QUIET)mkdir -p "$(dir $1)")
+endef
+
+# Creates the actual archive with an index.
+# The target $@ must end with .pic.a or .pie.a.
+define update_archive
+ $(call auto_mkdir,$(TARGET_OR_MEMBER))
+ $(QUIET)# Create the archive in one step to avoid parallel use accessing it
+ $(QUIET)# before all the symbols are present.
+ @$(ECHO) "AR $(subst \
+$(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o)) \
+-> $(subst $(SRC)/,,$(TARGET_OR_MEMBER))"
+ $(QUIET)$(AR) rcs $(TARGET_OR_MEMBER) \
+ $(subst $(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o))
+endef
+
+# Default compile from objects using pre-requisites but filters out
+# subdirs and .d files.
+define cc_binary
+ $(call COMPILE_BINARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+define cxx_binary
+ $(call COMPILE_BINARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+# Default compile from objects using pre-requisites but filters out
+# subdirs and .d files.
+define cc_library
+ $(call COMPILE_LIBRARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+define cxx_library
+ $(call COMPILE_LIBRARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+# Deletes files silently if they exist. Meant for use in any local
+# clean targets.
+define silent_rm
+ $(if $(wildcard $(1)),
+ $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANFILE$(COLOR_RESET) ' && \
+ $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \
+ $(RM) $(1) 2>/dev/null) || true,)
+endef
+define silent_rmdir
+ $(if $(wildcard $(1)),
+ $(if $(wildcard $(1)/*),
+ $(QUIET)# $(1) not empty [$(wildcard $(1)/*)]. Not deleting.,
+ $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANDIR$(COLOR_RESET) ' && \
+ $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \
+ $(RMDIR) $(1) 2>/dev/null) || true),)
+endef
+
+#
+# Default variable values
+#
+
+# Only override toolchain vars if they are from make.
+CROSS_COMPILE ?=
+define override_var
+ifneq ($(filter undefined default,$(origin $1)),)
+$1 = $(CROSS_COMPILE)$2
+endif
+endef
+$(eval $(call override_var,AR,ar))
+$(eval $(call override_var,CC,gcc))
+$(eval $(call override_var,CXX,g++))
+$(eval $(call override_var,OBJCOPY,objcopy))
+$(eval $(call override_var,PKG_CONFIG,pkg-config))
+$(eval $(call override_var,RANLIB,ranlib))
+$(eval $(call override_var,STRIP,strip))
+
+RMDIR ?= rmdir
+ECHO = /bin/echo -e
+
+ifeq ($(lastword $(subst /, ,$(CC))),clang)
+CDRIVER = clang
+else
+CDRIVER = gcc
+endif
+
+ifeq ($(lastword $(subst /, ,$(CXX))),clang++)
+CXXDRIVER = clang
+else
+CXXDRIVER = gcc
+endif
+
+# To update these from an including Makefile:
+# CXXFLAGS += -mahflag # Append to the list
+# CXXFLAGS := -mahflag $(CXXFLAGS) # Prepend to the list
+# CXXFLAGS := $(filter-out badflag,$(CXXFLAGS)) # Filter out a value
+# The same goes for CFLAGS.
+COMMON_CFLAGS-gcc := -fstack-protector-strong -fvisibility=internal -ggdb3 \
+ -Wa,--noexecstack
+COMMON_CFLAGS-clang := -fstack-protector-all -fvisibility=hidden -ggdb
+COMMON_CFLAGS := -Wall -Werror -fno-strict-aliasing -O1 -Wformat=2
+CXXFLAGS += $(COMMON_CFLAGS) $(COMMON_CFLAGS-$(CXXDRIVER))
+CFLAGS += $(COMMON_CFLAGS) $(COMMON_CFLAGS-$(CDRIVER))
+CPPFLAGS += -D_FORTIFY_SOURCE=2
+
+
+ifeq ($(MODE),opt)
+ # Up the optimizations.
+ CFLAGS := $(filter-out -O1,$(CFLAGS)) -O2
+ CXXFLAGS := $(filter-out -O1,$(CXXFLAGS)) -O2
+ # Only drop -g* if symbols aren't desired.
+ ifeq ($(NOSTRIP),0)
+ # TODO: do we want -fomit-frame-pointer on x86?
+ CFLAGS := $(filter-out -ggdb3,$(CFLAGS))
+ CXXFLAGS := $(filter-out -ggdb3,$(CXXFLAGS))
+ endif
+endif
+
+ifeq ($(MODE),profiling)
+ CFLAGS := $(CFLAGS) -O0 -g --coverage
+ CXXFLAGS := $(CXXFLAGS) -O0 -g --coverage
+ LDFLAGS := $(LDFLAGS) --coverage
+endif
+
+LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now
+
+# Fancy helpers for color if a prompt is defined
+ifeq ($(COLOR),1)
+COLOR_RESET = \x1b[0m
+COLOR_GREEN = \x1b[32;01m
+COLOR_RED = \x1b[31;01m
+COLOR_YELLOW = \x1b[33;01m
+endif
+
+# By default, silence build output.
+QUIET = @
+ifeq ($(VERBOSE),1)
+ QUIET=
+endif
+
+#
+# Implementation macros for compile helpers above
+#
+
+# Useful for dealing with pie-broken toolchains.
+# Call make with PIE=0 to disable default PIE use.
+OBJ_PIE_FLAG = -fPIE
+COMPILE_PIE_FLAG = -pie
+ifeq ($(PIE),0)
+ OBJ_PIE_FLAG =
+ COMPILE_PIE_FLAG =
+endif
+
+# Favor member targets first for CXX_BINARY(%) magic.
+# And strip out nested members if possible.
+LP := (
+RP := )
+TARGET_OR_MEMBER = $(lastword $(subst $(LP), ,$(subst $(RP),,$(or $%,$@))))
+
+# Default compile from objects using pre-requisites but filters out
+# all non-.o files.
+define COMPILE_BINARY_implementation
+ @$(ECHO) "LD$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))"
+ $(call auto_mkdir,$(TARGET_OR_MEMBER))
+ $(QUIET)$($(1)) $(COMPILE_PIE_FLAGS) -o $(TARGET_OR_MEMBER) \
+ $(2) $(LDFLAGS) \
+ $(filter %.o %.a,$(^:.o=.pie.o)) \
+ $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \
+ $(LDLIBS)
+ $(call conditional_strip)
+ @$(ECHO) -n "BIN "
+ @$(ECHO) "$(COLOR_GREEN)$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)"
+ @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)"
+endef
+
+# TODO: add version support extracted from PV environment variable
+#ifeq ($(PV),9999)
+#$(warning PV=$(PV). If shared object versions matter, please force PV=.)
+#endif
+# Then add -Wl,-soname,$@.$(PV) ?
+
+# Default compile from objects using pre-requisites but filters out
+# all non-.o values. (Remember to add -L$(OUT) -llib)
+COMMA := ,
+define COMPILE_LIBRARY_implementation
+ @$(ECHO) "SHARED$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))"
+ $(call auto_mkdir,$(TARGET_OR_MEMBER))
+ $(QUIET)$($(1)) -shared -Wl,-E -o $(TARGET_OR_MEMBER) \
+ $(2) $(LDFLAGS) \
+ $(if $(filter %.a,$^),-Wl$(COMMA)--whole-archive,) \
+ $(filter %.o ,$(^:.o=.pic.o)) \
+ $(foreach a,$(filter %.a,$^),-L$(dir $(a)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(a))))) \
+ $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \
+ $(LDLIBS)
+ $(call conditional_strip)
+ @$(ECHO) -n "LIB $(COLOR_GREEN)"
+ @$(ECHO) "$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)"
+ @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)"
+endef
+
+define conditional_strip
+ $(if $(filter 0,$(NOSTRIP)),$(call strip_artifact))
+endef
+
+define strip_artifact
+ @$(ECHO) "STRIP $(subst $(OUT)/,,$(TARGET_OR_MEMBER))"
+ $(if $(filter 1,$(SPLITDEBUG)), @$(ECHO) -n "DEBUG "; \
+ $(ECHO) "$(COLOR_YELLOW)\
+$(subst $(OUT)/,,$(TARGET_OR_MEMBER)).debug$(COLOR_RESET)")
+ $(if $(filter 1,$(SPLITDEBUG)), \
+ $(QUIET)$(OBJCOPY) --only-keep-debug "$(TARGET_OR_MEMBER)" \
+ "$(TARGET_OR_MEMBER).debug")
+ $(if $(filter-out dbg,$(MODE)),$(QUIET)$(STRIP) --strip-unneeded \
+ "$(TARGET_OR_MEMBER)",)
+endef
+
+#
+# Global pattern rules
+#
+
+# Below, the archive member syntax is abused to create fancier
+# syntactic sugar for recipe authors that avoids needed to know
+# subcall options. The downside is that make attempts to look
+# into the phony archives for timestamps. This will cause the final
+# target to be rebuilt/linked on _every_ call to make even when nothing
+# has changed. Until a better way presents itself, we have helpers that
+# do the stat check on make's behalf. Dodgy but simple.
+define old_or_no_timestamp
+ $(if $(realpath $%),,$(1))
+ $(if $(shell find $^ -cnewer "$%" 2>/dev/null),$(1))
+endef
+
+define check_deps
+ $(if $(filter 0,$(words $^)),\
+ $(error Missing dependencies or declaration of $@($%)),)
+endef
+
+# Build a cxx target magically
+CXX_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_binary))
+clean: CLEAN(CXX_BINARY*)
+
+CC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_binary))
+clean: CLEAN(CC_BINARY*)
+
+CXX_STATIC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_binary,-static))
+clean: CLEAN(CXX_STATIC_BINARY*)
+
+CC_STATIC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_binary,-static))
+clean: CLEAN(CC_STATIC_BINARY*)
+
+CXX_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_library))
+clean: CLEAN(CXX_LIBRARY*)
+
+CXX_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_library))
+clean: CLEAN(CC_LIBRARY*)
+
+CC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CXX_STATIC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call update_archive))
+clean: CLEAN(CXX_STATIC_LIBRARY*)
+
+CXX_STATIC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CC_STATIC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call update_archive))
+clean: CLEAN(CC_STATIC_LIBRARY*)
+
+CC_STATIC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+
+TEST(%): % qemu_chroot_install
+ $(call TEST_implementation)
+.PHONY: TEST
+
+# multiple targets with a wildcard need to share an directory.
+# Don't use this directly it just makes sure the directory is removed _after_
+# the files are.
+CLEANFILE(%):
+ $(call silent_rm,$(TARGET_OR_MEMBER))
+.PHONY: CLEANFILE
+
+CLEAN(%): CLEANFILE(%)
+ $(QUIET)# CLEAN($%) meta-target called
+ $(if $(filter-out $(PWD)/,$(dir $(abspath $(TARGET_OR_MEMBER)))), \
+ $(call silent_rmdir,$(dir $(abspath $(TARGET_OR_MEMBER)))),\
+ $(QUIET)# Not deleting $(dir $(abspath $(TARGET_OR_MEMBER))) yet.)
+.PHONY: CLEAN
+
+#
+# Top-level objects and pattern rules
+#
+
+# All objects for .c files at the top level
+C_OBJECTS = $(patsubst $(SRC)/%.c,%.o,$(wildcard $(SRC)/*.c))
+
+
+# All objects for .cxx files at the top level
+CXX_OBJECTS = $(patsubst $(SRC)/%.cc,%.o,$(wildcard $(SRC)/*.cc))
+
+# Note, the catch-all pattern rules don't work in subdirectories because
+# we're building from the $(OUT) directory. At the top-level (here) they will
+# work, but we go ahead and match using the module form. Then we can place a
+# generic pattern rule to capture leakage from the main Makefile. (Later in the
+# file.)
+#
+# The reason target specific pattern rules work well for modules,
+# MODULE_C_OBJECTS, is because it scopes the behavior to the given target which
+# ensures we get a relative directory offset from $(OUT) which otherwise would
+# not match without further magic on a per-subdirectory basis.
+
+# Creates object file rules. Call with eval.
+# $(1) list of .o files
+# $(2) source type (CC or CXX)
+# $(3) source suffix (cc or c)
+# $(4) compiler flag name (CFLAGS or CXXFLAGS)
+# $(5) source dir: _only_ if $(SRC). Leave blank for obj tree.
+define add_object_rules
+$(patsubst %.o,%.pie.o,$(1)): %.pie.o: $(5)%.$(3) %.o.depends
+ $$(call auto_mkdir,$$@)
+ $$(call OBJECT_PATTERN_implementation,$(2),\
+ $$(basename $$@),$$($(4)) $$(CPPFLAGS) $$(OBJ_PIE_FLAG))
+
+$(patsubst %.o,%.pic.o,$(1)): %.pic.o: $(5)%.$(3) %.o.depends
+ $$(call auto_mkdir,$$@)
+ $$(call OBJECT_PATTERN_implementation,$(2),\
+ $$(basename $$@),$$($(4)) $$(CPPFLAGS) -fPIC)
+
+# Placeholder for depends
+$(patsubst %.o,%.o.depends,$(1)):
+ $$(call auto_mkdir,$$@)
+ $$(QUIET)touch "$$@"
+
+$(1): %.o: %.pic.o %.pie.o
+ $$(call auto_mkdir,$$@)
+ $$(QUIET)touch "$$@"
+endef
+
+define OBJECT_PATTERN_implementation
+ @$(ECHO) "$(1) $(subst $(SRC)/,,$<) -> $(2).o"
+ $(call auto_mkdir,$@)
+ $(QUIET)$($(1)) -c -MD -MF $(2).d $(3) -o $(2).o $<
+ $(QUIET)# Wrap all the deps in $$(wildcard) so a missing header
+ $(QUIET)# won't cause weirdness. First we remove newlines and \,
+ $(QUIET)# then wrap it.
+ $(QUIET)sed -i -e :j -e '$$!N;s|\\\s*\n| |;tj' \
+ -e 's|^\(.*\s*:\s*\)\(.*\)$$|\1 $$\(wildcard \2\)|' $(2).d
+endef
+
+# Now actually register handlers for C(XX)_OBJECTS.
+$(eval $(call add_object_rules,$(C_OBJECTS),CC,c,CFLAGS,$(SRC)/))
+$(eval $(call add_object_rules,$(CXX_OBJECTS),CXX,cc,CXXFLAGS,$(SRC)/))
+
+# Disable default pattern rules to help avoid leakage.
+# These may already be handled by '-r', but let's keep it to be safe.
+%: %.o ;
+%.a: %.o ;
+%.o: %.c ;
+%.o: %.cc ;
+
+# NOTE: A specific rule for archive objects is avoided because parallel
+# update of the archive causes build flakiness.
+# Instead, just make the objects the prerequisites and use update_archive
+# To use the foo.a(obj.o) functionality, targets would need to specify the
+# explicit object they expect on the prerequisite line.
+
+#
+# Architecture detection and QEMU wrapping
+#
+
+HOST_ARCH ?= $(shell uname -m)
+override ARCH := $(strip $(ARCH))
+override HOST_ARCH := $(strip $(HOST_ARCH))
+# emake will supply "x86" or "arm" for ARCH, but
+# if uname -m runs and you get x86_64, then this subst
+# will break.
+ifeq ($(subst x86,i386,$(ARCH)),i386)
+ QEMU_ARCH := $(subst x86,i386,$(ARCH)) # x86 -> i386
+else ifeq ($(subst amd64,x86_64,$(ARCH)),x86_64)
+ QEMU_ARCH := $(subst amd64,x86_64,$(ARCH)) # amd64 -> x86_64
+else
+ QEMU_ARCH = $(ARCH)
+endif
+override QEMU_ARCH := $(strip $(QEMU_ARCH))
+
+# If we're cross-compiling, try to use qemu for running the tests.
+ifneq ($(QEMU_ARCH),$(HOST_ARCH))
+ ifeq ($(SYSROOT),)
+ $(info SYSROOT not defined. qemu-based testing disabled)
+ else
+ # A SYSROOT is assumed for QEmu use.
+ USE_QEMU ?= 1
+
+ # Allow 64-bit hosts to run 32-bit without qemu.
+ ifeq ($(HOST_ARCH),x86_64)
+ ifeq ($(QEMU_ARCH),i386)
+ USE_QEMU = 0
+ endif
+ endif
+ endif
+else
+ USE_QEMU ?= 0
+endif
+
+SYSROOT_OUT = $(OUT)
+ifneq ($(SYSROOT),)
+ SYSROOT_OUT = $(subst $(SYSROOT),,$(OUT))
+else
+ # Default to / when all the empty-sysroot logic is done.
+ SYSROOT = /
+endif
+
+
+#
+# Output full configuration at top level
+#
+
+# Don't show on clean
+ifneq ($(MAKECMDGOALS),clean)
+ $(info build configuration:)
+ $(info - OUT=$(OUT))
+ $(info - SRC=$(SRC))
+ $(info - MODE=$(MODE))
+ $(info - SPLITDEBUG=$(SPLITDEBUG))
+ $(info - NOSTRIP=$(NOSTRIP))
+ $(info - VALGRIND=$(VALGRIND))
+ $(info - COLOR=$(COLOR))
+ $(info - ARCH=$(ARCH))
+ $(info - QEMU_ARCH=$(QEMU_ARCH))
+ $(info - SYSROOT=$(SYSROOT))
+ $(info )
+endif
+
+#
+# Standard targets with detection for when they are improperly configured.
+#
+
+# all does not include tests by default
+all:
+ $(QUIET)(test -z "$^" && \
+ $(ECHO) "You must add your targets as 'all' prerequisites") || true
+ $(QUIET)test -n "$^"
+
+# Builds and runs tests for the target arch
+# Run them in parallel
+# After the test have completed, if profiling, run coverage analysis
+tests:
+ifeq ($(MODE),profiling)
+ @$(ECHO) "COVERAGE [$(COLOR_YELLOW)STARTED$(COLOR_RESET)]"
+ $(QUIET)FILES=""; \
+ for GCNO in `find . -name "*.gcno"`; do \
+ GCDA="$${GCNO%.gcno}.gcda"; \
+ if [ -e $${GCDA} ]; then \
+ FILES="$${FILES} $${GCDA}"; \
+ fi \
+ done; \
+ if [ -n "$${FILES}" ]; then \
+ gcov -l $${FILES}; \
+ lcov --capture --directory . \
+ --output-file=lcov-coverage.info; \
+ genhtml lcov-coverage.info \
+ --output-directory lcov-html; \
+ fi
+ @$(ECHO) "COVERAGE [$(COLOR_YELLOW)FINISHED$(COLOR_RESET)]"
+endif
+.PHONY: tests
+
+qemu_clean:
+ $(call if_qemu,$(call silent_rm,$(OUT)/qemu-$(QEMU_ARCH)))
+
+qemu_chroot_install:
+ifeq ($(USE_QEMU),1)
+ $(QUIET)$(ECHO) "QEMU Preparing qemu-$(QEMU_ARCH)"
+ $(QUIET)cp -fu /usr/bin/qemu-$(QEMU_ARCH) $(OUT)/qemu-$(QEMU_ARCH)
+ $(QUIET)chmod a+rx $(OUT)/qemu-$(QEMU_ARCH)
+endif
+.PHONY: qemu_clean qemu_chroot_install
+
+# TODO(wad) Move to -L $(SYSROOT) and fakechroot when qemu-user
+# doesn't hang traversing /proc from SYSROOT.
+QEMU_CMD =
+ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),sudo , )
+MOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) mount, \#)
+UMOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) umount, \#)
+QEMU_LDPATH = $(SYSROOT_LDPATH):/lib64:/lib:/usr/lib64:/usr/lib
+ROOT_CMD_LDPATH = $(SYSROOT_LDPATH):$(SYSROOT)/lib64:
+ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/lib:$(SYSROOT)/usr/lib64:
+ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/usr/lib
+ifeq ($(USE_QEMU),1)
+ export QEMU_CMD = \
+ sudo chroot $(SYSROOT) $(SYSROOT_OUT)qemu-$(QEMU_ARCH) \
+ -drop-ld-preload \
+ -E LD_LIBRARY_PATH="$(QEMU_LDPATH):$(patsubst $(OUT),,$(LD_DIRS))" \
+ -E HOME="$(HOME)" --
+ # USE_QEMU conditional function
+ define if_qemu
+ $(1)
+ endef
+else
+ ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),sudo, ) \
+ LD_LIBRARY_PATH="$(ROOT_CMD_LDPATH):$(LD_DIRS)"
+ define if_qemu
+ $(2)
+ endef
+endif
+
+VALGRIND_CMD =
+ifeq ($(VALGRIND),1)
+ VALGRIND_CMD = /usr/bin/valgrind --tool=memcheck $(VALGRIND_ARGS) --
+endif
+
+define TEST_implementation
+ $(QUIET)$(call TEST_setup)
+ $(QUIET)$(call TEST_run)
+ $(QUIET)$(call TEST_teardown)
+ $(QUIET)exit $$(cat $(OUT)$(TARGET_OR_MEMBER).status.test)
+endef
+
+define TEST_setup
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_YELLOW)SETUP$(COLOR_RESET)]"
+ $(QUIET)# Setup a target-specific results file
+ $(QUIET)(echo 1 > $(OUT)$(TARGET_OR_MEMBER).status.test)
+ $(QUIET)(echo > $(OUT)$(TARGET_OR_MEMBER).cleanup.test)
+ $(QUIET)# No setup if we are not using QEMU
+ $(QUIET)# TODO(wad) this is racy until we use a vfs namespace
+ $(call if_qemu,\
+ $(QUIET)sudo mkdir -p "$(SYSROOT)/proc" "$(SYSROOT)/dev")
+ $(call if_qemu,\
+ $(QUIET)$(MOUNT_CMD) --bind /proc "$(SYSROOT)/proc")
+ $(call if_qemu,\
+ $(QUIET)$(MOUNT_CMD) --bind /dev "$(SYSROOT)/dev")
+ $(call if_qemu,\
+ $(QUIET)(echo "$(UMOUNT_CMD) -l '$(SYSROOT)/proc'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).cleanup.test"))
+ $(call if_qemu,\
+ $(QUIET)(echo "$(UMOUNT_CMD) -l '$(SYSROOT)/dev'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).cleanup.test"))
+endef
+
+define TEST_teardown
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_YELLOW)TEARDOWN$(COLOR_RESET)]"
+ $(call if_qemu, $(QUIET)$(SHELL) "$(OUT)$(TARGET_OR_MEMBER).cleanup.test")
+endef
+
+# Use GTEST_ARGS.[arch] if defined.
+override GTEST_ARGS.real = \
+ $(call if_qemu,$(GTEST_ARGS.qemu.$(QEMU_ARCH)),$(GTEST_ARGS.host.$(HOST_ARCH)))
+
+define TEST_run
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_GREEN)RUN$(COLOR_RESET)]"
+ $(QUIET)(echo 1 > "$(OUT)$(TARGET_OR_MEMBER).status.test")
+ -($(ROOT_CMD) $(QEMU_CMD) $(VALGRIND_CMD) \
+ "$(strip $(call if_qemu, $(SYSROOT_OUT),$(OUT))$(TARGET_OR_MEMBER))" \
+ $(if $(filter-out 0,$(words $(GTEST_ARGS.real))),$(GTEST_ARGS.real),\
+ $(GTEST_ARGS)) && \
+ echo 0 > "$(OUT)$(TARGET_OR_MEMBER).status.test")
+endef
+
+# Recursive list reversal so that we get RMDIR_ON_CLEAN in reverse order.
+define reverse
+$(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1))
+endef
+
+clean: qemu_clean
+clean: CLEAN($(OUT)*.d) CLEAN($(OUT)*.o) CLEAN($(OUT)*.debug)
+clean: CLEAN($(OUT)*.test) CLEAN($(OUT)*.depends)
+clean: CLEAN($(OUT)*.gcno) CLEAN($(OUT)*.gcda) CLEAN($(OUT)*.gcov)
+clean: CLEAN($(OUT)lcov-coverage.info) CLEAN($(OUT)lcov-html)
+
+clean:
+ $(QUIET)# Always delete the containing directory last.
+ $(call silent_rmdir,$(OUT))
+
+FORCE: ;
+# Empty rule for use when no special targets are needed, like large_tests
+NONE:
+
+.PHONY: clean NONE valgrind NONE
+.DEFAULT_GOAL := all
+# Don't let make blow away "intermediates"
+.PRECIOUS: %.pic.o %.pie.o %.a %.pic.a %.pie.a %.test
+
+# Start accruing build info
+OUT_DIRS = $(OUT)
+LD_DIRS = $(OUT)
+SRC_DIRS = $(SRC)
+
+include $(wildcard $(OUT)*.d)
+SUBMODULE_DIRS = $(wildcard $(SRC)/*/module.mk)
+include $(SUBMODULE_DIRS)
+
+
+else ## In duplicate inclusions of common.mk
+
+# Get the current inclusion directory without a trailing slash
+MODULE := $(patsubst %/,%, \
+ $(dir $(lastword $(filter-out %common.mk,$(MAKEFILE_LIST)))))
+MODULE := $(subst $(SRC)/,,$(MODULE))
+MODULE_NAME := $(subst /,_,$(MODULE))
+#VPATH := $(MODULE):$(VPATH)
+
+
+# Depth first
+$(eval OUT_DIRS += $(OUT)$(MODULE))
+$(eval SRC_DIRS += $(OUT)$(MODULE))
+$(eval LD_DIRS := $(LD_DIRS):$(OUT)$(MODULE))
+
+# Add the defaults from this dir to rm_clean
+clean: CLEAN($(OUT)$(MODULE)/*.d) CLEAN($(OUT)$(MODULE)/*.o)
+clean: CLEAN($(OUT)$(MODULE)/*.debug) CLEAN($(OUT)$(MODULE)/*.test)
+clean: CLEAN($(OUT)$(MODULE)/*.depends)
+clean: CLEAN($(OUT)$(MODULE)/*.gcno) CLEAN($(OUT)$(MODULE)/*.gcda)
+clean: CLEAN($(OUT)$(MODULE)/*.gcov) CLEAN($(OUT)lcov-coverage.info)
+clean: CLEAN($(OUT)lcov-html)
+
+$(info + submodule: $(MODULE_NAME))
+# We must eval otherwise they may be dropped.
+MODULE_C_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.c,$(MODULE)/%.o,\
+ $(wildcard $(SRC)/$(MODULE)/*.c))
+$(eval $(MODULE_NAME)_C_OBJECTS ?= $(MODULE_C_OBJECTS))
+MODULE_CXX_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.cc,$(MODULE)/%.o,\
+ $(wildcard $(SRC)/$(MODULE)/*.cc))
+$(eval $(MODULE_NAME)_CXX_OBJECTS ?= $(MODULE_CXX_OBJECTS))
+
+# Note, $(MODULE) is implicit in the path to the %.c.
+# See $(C_OBJECTS) for more details.
+# Register rules for the module objects.
+$(eval $(call add_object_rules,$(MODULE_C_OBJECTS),CC,c,CFLAGS,$(SRC)/))
+$(eval $(call add_object_rules,$(MODULE_CXX_OBJECTS),CXX,cc,CXXFLAGS,$(SRC)/))
+
+# Continue recursive inclusion of module.mk files
+SUBMODULE_DIRS = $(wildcard $(SRC)/$(MODULE)/*/module.mk)
+include $(wildcard $(OUT)$(MODULE)/*.d)
+include $(SUBMODULE_DIRS)
+
+endif
+endif ## pass-to-subcall wrapper for relocating the call directory
diff --git a/drm-tests/drm_cursor_test.c b/drm-tests/drm_cursor_test.c
new file mode 100644
index 0000000..ea8204c
--- /dev/null
+++ b/drm-tests/drm_cursor_test.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+int main(int argc, char **argv)
+{
+ uint32_t cursor_size = 64;
+ if (argc >= 2) {
+ char *end_str;
+ unsigned long new_cursor_size = strtoul(argv[1], &end_str, 0);
+ if (end_str == argv[1] || new_cursor_size == 0 || new_cursor_size > UINT32_MAX) {
+ printf(
+ "usage:\n drm_cursor_test [cursor size]\n\nCursor size defaults to "
+ "%u\n",
+ cursor_size);
+ return 1;
+ }
+ cursor_size = (uint32_t)new_cursor_size;
+ }
+
+ int fd = bs_drm_open_main_display();
+ if (fd < 0) {
+ bs_debug_error("failed to open card for display");
+ return 1;
+ }
+
+ struct gbm_device *gbm = gbm_create_device(fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm");
+ return 1;
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(fd, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+
+ drmModeConnector *connector = drmModeGetConnector(fd, pipe.connector_id);
+ drmModeModeInfo *mode = &connector->modes[0];
+ uint32_t crtc_id = pipe.crtc_id;
+
+ // Restart the cursor position before binding the crtc so that the old cursor position isn't
+ // displayed briefly when the display is turned activated.
+ int ret = drmModeMoveCursor(fd, crtc_id, 0, 0);
+ if (ret) {
+ bs_debug_error("failed to move cursor: %d", ret);
+ return 1;
+ }
+
+ struct gbm_bo *fb_bo =
+ gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_SW_WRITE_RARELY);
+ if (!fb_bo) {
+ bs_debug_error("failed to create buffer object for frame buffer");
+ return 1;
+ }
+
+ struct bs_mapper *mapper = bs_mapper_gem_new();
+ if (mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ return 1;
+ }
+
+ void *map_data;
+ uint8_t *fb_ptr = bs_mapper_map(mapper, fb_bo, 0, &map_data);
+ if (fb_ptr == MAP_FAILED) {
+ bs_debug_error("failed to mmap frame buffer object");
+ return 1;
+ }
+ uint32_t stride = gbm_bo_get_stride(fb_bo);
+ for (size_t y = 0; y < mode->vdisplay; y++) {
+ for (size_t x = 0; x < mode->hdisplay; x++) {
+ // Solid blue fill
+ fb_ptr[y * stride + x * 4 + 0] = 0xff;
+ fb_ptr[y * stride + x * 4 + 1] = 0;
+ fb_ptr[y * stride + x * 4 + 2] = 0;
+ fb_ptr[y * stride + x * 4 + 3] = 0;
+ }
+ }
+ bs_mapper_unmap(mapper, fb_bo, map_data);
+
+ uint32_t fb_id = bs_drm_fb_create_gbm(fb_bo);
+ if (!fb_id) {
+ bs_debug_error("failed to create frame buffer from buffer object");
+ return 1;
+ }
+
+ ret = drmModeSetCrtc(fd, crtc_id, fb_id, 0 /* x */, 0 /* y */, &pipe.connector_id,
+ 1 /* connector count */, mode);
+ if (ret) {
+ bs_debug_error("failed to set crtc: %d", ret);
+ return 1;
+ }
+
+ struct gbm_bo *cursor_bo = gbm_bo_create(gbm, cursor_size, cursor_size, GBM_FORMAT_ARGB8888,
+ GBM_BO_USE_CURSOR | GBM_BO_USE_SW_WRITE_RARELY);
+ if (!cursor_bo) {
+ bs_debug_error("failed to create cursor buffer object");
+ return 1;
+ }
+
+ const struct bs_draw_format *draw_format = bs_get_draw_format(GBM_FORMAT_ARGB8888);
+ if (!draw_format) {
+ bs_debug_error("failed to get draw format");
+ return 1;
+ }
+
+ if (!bs_draw_cursor(mapper, cursor_bo, draw_format)) {
+ bs_debug_error("failed to draw cursor");
+ return 1;
+ }
+
+ ret = drmModeSetCursor(fd, crtc_id, gbm_bo_get_handle(cursor_bo).u32, cursor_size,
+ cursor_size);
+ if (ret) {
+ bs_debug_error("failed to set cursor: %d", ret);
+ return 1;
+ }
+
+ // Divisor chosen so that the test would last about 10 seconds @ 60fps.
+ const uint32_t divisor = 25;
+ const uint32_t xinc = mode->hdisplay / divisor;
+ const uint32_t yinc = mode->vdisplay / divisor;
+ for (uint32_t x = 0; x < divisor; x++) {
+ for (uint32_t y = 0; y < divisor; y++) {
+ ret = drmModeMoveCursor(fd, crtc_id, x * xinc + y, y * yinc + x);
+ if (ret) {
+ bs_debug_error("failed to move cursor: %d", ret);
+ return 1;
+ }
+ usleep(16667);
+ }
+ }
+
+ bs_mapper_destroy(mapper);
+ drmModeRmFB(fd, fb_id);
+ gbm_bo_destroy(fb_bo);
+ gbm_bo_destroy(cursor_bo);
+ gbm_device_destroy(gbm);
+ close(fd);
+
+ return 0;
+}
diff --git a/drm-tests/gamma_test.c b/drm-tests/gamma_test.c
new file mode 100644
index 0000000..80c1907
--- /dev/null
+++ b/drm-tests/gamma_test.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright 2015 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+#include <getopt.h>
+#include <math.h>
+
+#define TABLE_LINEAR 0
+#define TABLE_NEGATIVE 1
+#define TABLE_POW 2
+#define TABLE_STEP 3
+
+#define FLAG_INTERNAL 'i'
+#define FLAG_EXTERNAL 'e'
+#define FLAG_GAMMA 'g'
+#define FLAG_LINEAR 'l'
+#define FLAG_NEGATIVE 'n'
+#define FLAG_TIME 't'
+#define FLAG_CRTCS 'c'
+#define FLAG_PERSIST 'p'
+#define FLAG_STEP 's'
+#define FLAG_HELP 'h'
+
+static struct option command_options[] = { { "internal", no_argument, NULL, FLAG_INTERNAL },
+ { "external", no_argument, NULL, FLAG_EXTERNAL },
+ { "gamma", required_argument, NULL, FLAG_GAMMA },
+ { "linear", no_argument, NULL, FLAG_LINEAR },
+ { "negative", no_argument, NULL, FLAG_NEGATIVE },
+ { "time", required_argument, NULL, FLAG_TIME },
+ { "crtcs", required_argument, NULL, FLAG_CRTCS },
+ { "persist", no_argument, NULL, FLAG_PERSIST },
+ { "step", no_argument, NULL, FLAG_STEP },
+ { "help", no_argument, NULL, FLAG_HELP },
+ { NULL, 0, NULL, 0 } };
+
+static void gamma_linear(uint16_t *table, int size)
+{
+ int i;
+ for (i = 0; i < size; i++) {
+ float v = (float)(i) / (float)(size - 1);
+ v *= 65535.0f;
+ table[i] = (uint16_t)v;
+ }
+}
+
+static void gamma_inv(uint16_t *table, int size)
+{
+ int i;
+ for (i = 0; i < size; i++) {
+ float v = (float)(size - 1 - i) / (float)(size - 1);
+ v *= 65535.0f;
+ table[i] = (uint16_t)v;
+ }
+}
+
+static void gamma_pow(uint16_t *table, int size, float p)
+{
+ int i;
+ for (i = 0; i < size; i++) {
+ float v = (float)(i) / (float)(size - 1);
+ v = pow(v, p);
+ v *= 65535.0f;
+ table[i] = (uint16_t)v;
+ }
+}
+
+static void gamma_step(uint16_t *table, int size)
+{
+ int i;
+ for (i = 0; i < size; i++) {
+ table[i] = (i < size / 2) ? 0 : 65535;
+ }
+}
+
+static void fsleep(double secs)
+{
+ usleep((useconds_t)(1000000.0f * secs));
+}
+
+static drmModeModeInfoPtr find_best_mode(int mode_count, drmModeModeInfoPtr modes)
+{
+ assert(mode_count >= 0);
+ if (mode_count == 0)
+ return NULL;
+
+ assert(modes);
+
+ for (int m = 0; m < mode_count; m++)
+ if (modes[m].type & DRM_MODE_TYPE_PREFERRED)
+ return &modes[m];
+
+ return &modes[0];
+}
+
+static bool draw_pattern(struct bs_mapper *mapper, struct gbm_bo *bo)
+{
+ const uint32_t stride = gbm_bo_get_stride(bo);
+ const uint32_t height = gbm_bo_get_height(bo);
+ const uint32_t bo_size = stride * height;
+ const uint32_t stripw = gbm_bo_get_width(bo) / 256;
+ const uint32_t striph = height / 4;
+
+ void *map_data;
+ uint8_t *bo_ptr = bs_mapper_map(mapper, bo, 0, &map_data);
+ if (bo_ptr == MAP_FAILED) {
+ bs_debug_error("failed to mmap buffer while drawing pattern");
+ return false;
+ }
+
+ bool success = true;
+
+ memset(bo_ptr, 0, bo_size);
+ for (uint32_t s = 0; s < 4; s++) {
+ uint8_t r = 0, g = 0, b = 0;
+ switch (s) {
+ case 0:
+ r = g = b = 1;
+ break;
+ case 1:
+ r = 1;
+ break;
+ case 2:
+ g = 1;
+ break;
+ case 3:
+ b = 1;
+ break;
+ default:
+ assert("invalid strip" && false);
+ success = false;
+ goto out;
+ }
+ for (uint32_t y = s * striph; y < (s + 1) * striph; y++) {
+ uint8_t *row_ptr = &bo_ptr[y * stride];
+ for (uint32_t i = 0; i < 256; i++) {
+ for (uint32_t x = i * stripw; x < (i + 1) * stripw; x++) {
+ row_ptr[x * 4 + 0] = b * i;
+ row_ptr[x * 4 + 1] = g * i;
+ row_ptr[x * 4 + 2] = r * i;
+ row_ptr[x * 4 + 3] = 0;
+ }
+ }
+ }
+ }
+
+out:
+ bs_mapper_unmap(mapper, bo, map_data);
+ return success;
+}
+
+static int set_gamma(int fd, uint32_t crtc_id, int gamma_size, int gamma_table, float gamma)
+{
+ int res;
+ uint16_t *r, *g, *b;
+ r = calloc(gamma_size, sizeof(*r));
+ g = calloc(gamma_size, sizeof(*g));
+ b = calloc(gamma_size, sizeof(*b));
+
+ printf("Setting gamma table %d\n", gamma_table);
+ switch (gamma_table) {
+ case TABLE_LINEAR:
+ gamma_linear(r, gamma_size);
+ gamma_linear(g, gamma_size);
+ gamma_linear(b, gamma_size);
+ break;
+ case TABLE_NEGATIVE:
+ gamma_inv(r, gamma_size);
+ gamma_inv(g, gamma_size);
+ gamma_inv(b, gamma_size);
+ break;
+ case TABLE_POW:
+ gamma_pow(r, gamma_size, gamma);
+ gamma_pow(g, gamma_size, gamma);
+ gamma_pow(b, gamma_size, gamma);
+ break;
+ case TABLE_STEP:
+ gamma_step(r, gamma_size);
+ gamma_step(g, gamma_size);
+ gamma_step(b, gamma_size);
+ break;
+ }
+
+ res = drmModeCrtcSetGamma(fd, crtc_id, gamma_size, r, g, b);
+ if (res)
+ bs_debug_error("drmModeCrtcSetGamma(%d) failed: %s", crtc_id, strerror(errno));
+ free(r);
+ free(g);
+ free(b);
+ return res;
+}
+
+void help(void)
+{
+ printf(
+ "\
+gamma test\n\
+command line options:\
+\n\
+--help - this\n\
+--linear - set linear gamma table\n\
+--negative - set negative linear gamma table\n\
+--step - set step gamma table\n\
+--gamma=f - set pow(gamma) gamma table with gamma=f\n\
+--time=f - set test time\n\
+--crtcs=n - set mask of crtcs to test\n\
+--persist - do not reset gamma table at the end of the test\n\
+--internal - display tests on internal display\n\
+--external - display tests on external display\n\
+");
+}
+
+int main(int argc, char **argv)
+{
+ int internal = 1;
+ int persist = 0;
+ float time = 5.0;
+ float gamma = 2.2f;
+ float table = TABLE_LINEAR;
+ uint32_t crtcs = 0xFFFF;
+
+ for (;;) {
+ int c = getopt_long(argc, argv, "", command_options, NULL);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case FLAG_HELP:
+ help();
+ return 0;
+
+ case FLAG_INTERNAL:
+ internal = 1;
+ break;
+
+ case FLAG_EXTERNAL:
+ internal = 0;
+ break;
+
+ case FLAG_GAMMA:
+ gamma = strtof(optarg, NULL);
+ table = TABLE_POW;
+ break;
+
+ case FLAG_LINEAR:
+ table = TABLE_LINEAR;
+ break;
+
+ case FLAG_NEGATIVE:
+ table = TABLE_NEGATIVE;
+ break;
+
+ case FLAG_STEP:
+ table = TABLE_STEP;
+ break;
+
+ case FLAG_TIME:
+ time = strtof(optarg, NULL);
+ break;
+
+ case FLAG_CRTCS:
+ crtcs = strtoul(optarg, NULL, 0);
+ break;
+
+ case FLAG_PERSIST:
+ persist = 1;
+ break;
+ }
+ }
+
+ drmModeConnector *connector = NULL;
+ struct bs_drm_pipe pipe = { 0 };
+ struct bs_drm_pipe_plumber *plumber = bs_drm_pipe_plumber_new();
+ bs_drm_pipe_plumber_connector_ptr(plumber, &connector);
+ bs_drm_pipe_plumber_crtc_mask(plumber, crtcs);
+ if (!internal)
+ bs_drm_pipe_plumber_connector_ranks(plumber, bs_drm_connectors_external_rank);
+ if (!bs_drm_pipe_plumber_make(plumber, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+
+ int fd = pipe.fd;
+ bs_drm_pipe_plumber_fd(plumber, fd);
+ drmModeRes *resources = drmModeGetResources(fd);
+ if (!resources) {
+ bs_debug_error("failed to get drm resources");
+ return 1;
+ }
+
+ struct gbm_device *gbm = gbm_create_device(fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm");
+ return 1;
+ }
+
+ struct bs_mapper *mapper = bs_mapper_gem_new();
+ if (mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ return 1;
+ }
+
+ uint32_t num_success = 0;
+ for (int c = 0; c < resources->count_crtcs && (crtcs >> c); c++) {
+ int ret;
+ drmModeCrtc *crtc;
+ uint32_t crtc_mask = 1u << c;
+
+ if (!(crtcs & crtc_mask))
+ continue;
+
+ if (connector != NULL) {
+ drmModeFreeConnector(connector);
+ connector = NULL;
+ }
+
+ bs_drm_pipe_plumber_crtc_mask(plumber, crtc_mask);
+ if (!bs_drm_pipe_plumber_make(plumber, &pipe)) {
+ printf("unable to make pipe with crtc mask: %x\n", crtc_mask);
+ continue;
+ }
+
+ crtc = drmModeGetCrtc(fd, pipe.crtc_id);
+ if (!crtc) {
+ bs_debug_error("drmModeGetCrtc(%d) failed: %s\n", pipe.crtc_id,
+ strerror(errno));
+ return 1;
+ }
+ int gamma_size = crtc->gamma_size;
+ drmModeFreeCrtc(crtc);
+
+ if (!gamma_size) {
+ bs_debug_error("CRTC %d has no gamma table", crtc->crtc_id);
+ continue;
+ }
+
+ printf("CRTC:%d gamma size:%d\n", pipe.crtc_id, gamma_size);
+
+ printf("Using CRTC:%u ENCODER:%u CONNECTOR:%u\n", pipe.crtc_id, pipe.encoder_id,
+ pipe.connector_id);
+
+ drmModeModeInfoPtr mode = find_best_mode(connector->count_modes, connector->modes);
+ if (!mode) {
+ bs_debug_error("Could not find mode for CRTC %d", pipe.crtc_id);
+ continue;
+ }
+
+ printf("Using mode %s\n", mode->name);
+
+ printf("Creating buffer %ux%u\n", mode->hdisplay, mode->vdisplay);
+ struct gbm_bo *bo =
+ gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_SW_WRITE_RARELY);
+ if (!bo) {
+ bs_debug_error("failed to create buffer object");
+ return 1;
+ }
+
+ uint32_t fb_id = bs_drm_fb_create_gbm(bo);
+ if (!fb_id) {
+ bs_debug_error("failed to create frame buffer for buffer object");
+ return 1;
+ }
+
+ if (!draw_pattern(mapper, bo)) {
+ bs_debug_error("failed to draw pattern on buffer object");
+ return 1;
+ }
+
+ ret = drmModeSetCrtc(fd, pipe.crtc_id, fb_id, 0, 0, &pipe.connector_id, 1, mode);
+ if (ret < 0) {
+ bs_debug_error("Could not set mode on CRTC %d %s", pipe.crtc_id,
+ strerror(errno));
+ return 1;
+ }
+
+ ret = set_gamma(fd, pipe.crtc_id, gamma_size, table, gamma);
+ if (ret)
+ return ret;
+
+ fsleep(time);
+
+ if (!persist) {
+ ret = set_gamma(fd, pipe.crtc_id, gamma_size, TABLE_LINEAR, 0.0f);
+ if (ret)
+ return ret;
+ }
+
+ ret = drmModeSetCrtc(fd, pipe.crtc_id, 0, 0, 0, NULL, 0, NULL);
+ if (ret < 0) {
+ bs_debug_error("Could disable CRTC %d %s\n", pipe.crtc_id, strerror(errno));
+ return 1;
+ }
+
+ drmModeRmFB(fd, fb_id);
+ gbm_bo_destroy(bo);
+ num_success++;
+ }
+ bs_mapper_destroy(mapper);
+
+ if (connector != NULL) {
+ drmModeFreeConnector(connector);
+ connector = NULL;
+ }
+
+ drmModeFreeResources(resources);
+ bs_drm_pipe_plumber_destroy(&plumber);
+
+ if (!num_success) {
+ bs_debug_error("unable to set gamma table on any CRTC");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/drm-tests/linear_bo_test.c b/drm-tests/linear_bo_test.c
new file mode 100644
index 0000000..6955a01
--- /dev/null
+++ b/drm-tests/linear_bo_test.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+static const useconds_t test_case_display_usec = 2000000;
+
+struct test_case {
+ uint32_t format; /* format for allocating buffer object from GBM*/
+ uint32_t fb_format; /* format used to create DRM framebuffer, 0 indicates same as format */
+ enum gbm_bo_flags usage;
+};
+
+const char *format_to_string(uint32_t format)
+{
+ switch (format) {
+ case GBM_FORMAT_XRGB8888:
+ return "GBM_FORMAT_XRGB8888";
+ case GBM_FORMAT_ARGB8888:
+ return "GBM_FORMAT_ARGB8888";
+ default:
+ return "GBM_FORMAT_????????";
+ }
+}
+
+static void test_case_print(FILE *out, const struct test_case *tcase)
+{
+ fprintf(out, "format=%s usage=", format_to_string(tcase->format));
+ bool first = true;
+ if (tcase->usage & GBM_BO_USE_SCANOUT) {
+ fprintf(out, "GBM_BO_USE_SCANOUT");
+ first = false;
+ }
+ if (tcase->usage & GBM_BO_USE_RENDERING) {
+ fprintf(out, "%sGBM_BO_USE_RENDERING", first ? "" : " | ");
+ first = false;
+ }
+ if (tcase->usage & GBM_BO_USE_LINEAR) {
+ fprintf(out, "%sGBM_BO_USE_LINEAR", first ? "" : " | ");
+ first = false;
+ }
+ if (tcase->fb_format)
+ fprintf(out, " fb_format=%s", format_to_string(tcase->fb_format));
+}
+
+static void test_case_colors(const struct test_case *tcase,
+ float *colors /* sizeof(colors) / sizeof(colors[0]) == 9 */)
+{
+ colors[0] = 0.0f;
+ colors[1] = 1.0f;
+ colors[2] = 0.0f;
+
+ colors[3] = tcase->usage & GBM_BO_USE_SCANOUT ? 1.0f : 0.0f;
+ colors[4] = tcase->usage & GBM_BO_USE_RENDERING ? 0.66f : 0.0f;
+ colors[5] = tcase->usage & GBM_BO_USE_LINEAR ? 1.0f : 0.0f;
+
+ switch (tcase->format) {
+ case GBM_FORMAT_XRGB8888:
+ colors[6] = 1.0f;
+ colors[7] = 1.0f;
+ break;
+ case GBM_FORMAT_ARGB8888:
+ colors[7] = 1.0f;
+ colors[8] = 1.0f;
+ break;
+ default:
+ colors[6] = 0.33f;
+ colors[7] = 0.33f;
+ colors[8] = 0.33f;
+ break;
+ }
+}
+
+static void bo_lines(uint32_t height, float *lines /* sizeof(lines) / sizeof(lines[0]) == 9 */)
+{
+ /*
+ The screen is divided into sections using 3 lines as shown.
+ *----------*
+ |\ | / |
+ | \|/ |
+ | X |
+ | /|\ |
+ |/ | \ |
+ *----------*
+
+ Lines are evaluated as positive or negative in the linear equation:
+ Ax + By - C
+
+ Where the coffecicents A, B, and C appear in the array in the following order:
+ [ A, B, C ]
+ */
+ // negative left of the following lines' intersection
+ lines[0] = 1;
+ lines[1] = 0;
+ lines[2] = height / 2;
+
+ // negative on lower-right triangle section
+ lines[3] = 1;
+ lines[4] = -1;
+ lines[5] = 0;
+
+ // negative on upper-left triangle section
+ lines[6] = 1;
+ lines[7] = 1;
+ lines[8] = height;
+}
+
+static bool test_case_draw_gl(struct bs_egl *egl, const struct test_case *tcase, struct gbm_bo *bo)
+{
+ bool success = true;
+ uint32_t width = gbm_bo_get_width(bo);
+ uint32_t height = gbm_bo_get_height(bo);
+
+ EGLImageKHR image = bs_egl_image_create_gbm(egl, bo);
+ if (image == EGL_NO_IMAGE_KHR) {
+ success = false;
+ bs_debug_error("failed to make image from buffer object");
+ goto out;
+ }
+
+ struct bs_egl_fb *fb = bs_egl_fb_new(egl, image);
+ if (!fb) {
+ success = false;
+ bs_debug_error("failed to make rednering framebuffer for buffer object");
+ bs_egl_image_destroy(egl, &image);
+ goto image_destroy;
+ }
+
+ const GLchar *vert =
+ "attribute vec2 vPosition;\n"
+ "void main() {\n"
+ " gl_Position = vec4(vPosition, 0, 1);\n"
+ "}\n";
+
+ const GLchar *frag =
+ "precision mediump float;\n"
+ "uniform vec3 uColors[3];\n"
+ "uniform vec3 uLines[3];\n"
+ "void main() {\n"
+ " bool left = dot(uLines[0].xy, gl_FragCoord.xy) < uLines[0].z;\n"
+ " bool lower_right = dot(uLines[1].xy, gl_FragCoord.xy) < uLines[1].z;\n"
+ " bool upper_left = dot(uLines[2].xy, gl_FragCoord.xy) < uLines[2].z;\n"
+ " if (left && upper_left)\n"
+ " gl_FragColor = vec4(uColors[0], 1.0);\n"
+ " else if ((left && !upper_left) || (!left && lower_right))\n"
+ " gl_FragColor = vec4(uColors[1], 1.0);\n"
+ " else\n"
+ " gl_FragColor = vec4(uColors[2], 1.0);\n"
+ "}\n";
+
+ struct bs_gl_program_create_binding bindings[] = {
+ { 0, "vPosition" }, { 0, NULL },
+ };
+
+ GLuint program = bs_gl_program_create_vert_frag_bind(vert, frag, bindings);
+ if (!program) {
+ success = false;
+ bs_debug_error("failed to compile test case shader program");
+ goto fb_destroy;
+ }
+ GLint colors_location = glGetUniformLocation(program, "uColors");
+ GLint lines_location = glGetUniformLocation(program, "uLines");
+ if (colors_location == -1 || lines_location == -1) {
+ success = false;
+ bs_debug_error("failed to retrieve uniform location");
+ goto delete_program;
+ }
+
+ GLfloat colors[9];
+ test_case_colors(tcase, colors);
+
+ float lines[9];
+ bo_lines(height, lines);
+
+ // clang-format off
+ const GLfloat verts[] = {
+ -1.0f, -1.0f,
+ 2.0f, -1.0f,
+ -1.0f, 2.0f,
+ 2.0f, 2.0f,
+ };
+ // clang-format on
+
+ glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(fb));
+ glViewport(0, 0, (GLint)width, (GLint)height);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glUseProgram(program);
+ glUniform3fv(colors_location, 3, colors);
+ glUniform3fv(lines_location, 3, lines);
+
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
+ glEnableVertexAttribArray(0);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glFinish();
+
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+delete_program:
+ glDeleteProgram(program);
+fb_destroy:
+ bs_egl_fb_destroy(&fb);
+image_destroy:
+ bs_egl_image_destroy(egl, &image);
+out:
+ return success;
+}
+
+static bool test_case_draw_dma_buf(const struct test_case *tcase, struct bs_mapper *mapper,
+ struct gbm_bo *bo)
+{
+ int bo_fd = gbm_bo_get_fd(bo);
+ if (bo_fd < 0) {
+ bs_debug_error("failed to get fd of bo");
+ return false;
+ }
+ uint32_t width = gbm_bo_get_width(bo);
+ uint32_t height = gbm_bo_get_height(bo);
+ uint32_t stride = gbm_bo_get_stride(bo);
+ void *map_data;
+ uint8_t *ptr = bs_mapper_map(mapper, bo, 0, &map_data);
+ if (ptr == MAP_FAILED) {
+ bs_debug_error("failed to mmap gbm bo");
+ return false;
+ }
+
+ float colors_float[9];
+ test_case_colors(tcase, colors_float);
+ uint8_t colors[9];
+ for (size_t i = 0; i < 9; i++)
+ colors[i] = (uint8_t)(colors_float[i] * 255.0f);
+
+ float lines[9];
+ bo_lines(height, lines);
+
+ for (uint32_t y = 0; y < height; y++) {
+ uint8_t *row_ptr = &ptr[y * stride];
+ for (uint32_t x = 0; x < width; x++) {
+ bool left = lines[0] * (float)x + lines[1] * (float)y < lines[2];
+ bool lower_right = lines[3] * (float)x + lines[4] * (float)y < lines[5];
+ bool upper_left = lines[6] * (float)x + lines[7] * (float)y < lines[8];
+
+ int color_index = 0;
+ if (left && upper_left)
+ color_index = 0;
+ else if ((left && !upper_left) || (!left && lower_right))
+ color_index = 1;
+ else
+ color_index = 2;
+
+ row_ptr[x * 4 + 0] = colors[color_index * 3 + 2];
+ row_ptr[x * 4 + 1] = colors[color_index * 3 + 1];
+ row_ptr[x * 4 + 2] = colors[color_index * 3 + 0];
+ row_ptr[x * 4 + 3] = 0;
+ }
+ }
+
+ bs_mapper_unmap(mapper, bo, map_data);
+
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ const struct test_case tcases[] = {
+ { GBM_FORMAT_XRGB8888, 0, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING },
+ { GBM_FORMAT_XRGB8888, 0, GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR },
+ { GBM_FORMAT_ARGB8888, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING },
+ { GBM_FORMAT_ARGB8888, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR },
+ };
+ const size_t tcase_count = BS_ARRAY_LEN(tcases);
+
+ int display_fd = bs_drm_open_main_display();
+ if (display_fd < 0) {
+ bs_debug_error("failed to open card for display");
+ return 1;
+ }
+
+ struct gbm_device *gbm = gbm_create_device(display_fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm device");
+ return 1;
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(display_fd, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+
+ drmModeConnector *connector = drmModeGetConnector(display_fd, pipe.connector_id);
+ drmModeModeInfo *mode = &connector->modes[0];
+ uint32_t width = mode->hdisplay;
+ uint32_t height = mode->vdisplay;
+
+ struct bs_egl *egl = bs_egl_new();
+ if (!bs_egl_setup(egl)) {
+ bs_debug_error("failed to setup egl context");
+ return 1;
+ }
+
+ uint32_t fbs[BS_ARRAY_LEN(tcases)] = { 0 };
+ struct bs_mapper *mapper = bs_mapper_dma_buf_new();
+ if (mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ return 1;
+ }
+
+ bool all_pass = true;
+ for (size_t tcase_index = 0; tcase_index < tcase_count; tcase_index++) {
+ const struct test_case *tcase = &tcases[tcase_index];
+
+ struct gbm_bo *bo = gbm_bo_create(gbm, width, height, tcase->format, tcase->usage);
+
+ if (!bo) {
+ all_pass = false;
+ printf("failed test case: ");
+ test_case_print(stdout, tcase);
+ printf("\n");
+ continue;
+ }
+
+ struct bs_drm_fb_builder *fb_builder = bs_drm_fb_builder_new();
+ bs_drm_fb_builder_gbm_bo(fb_builder, bo);
+ if (tcase->fb_format)
+ bs_drm_fb_builder_format(fb_builder, tcase->fb_format);
+ fbs[tcase_index] = bs_drm_fb_builder_create_fb(fb_builder);
+ bs_drm_fb_builder_destroy(&fb_builder);
+ if (!fbs[tcase_index]) {
+ bs_debug_error("failed to create framebuffer from buffer object");
+ return 1;
+ }
+
+ if (tcase->usage & GBM_BO_USE_LINEAR) {
+ if (!test_case_draw_dma_buf(tcase, mapper, bo)) {
+ bs_debug_error("failed to draw to buffer using vgem");
+ return 1;
+ }
+ } else if (tcase->usage & GBM_BO_USE_RENDERING) {
+ if (!test_case_draw_gl(egl, tcase, bo)) {
+ bs_debug_error("failed to draw to buffer using GL");
+ return 1;
+ }
+ }
+
+ // Reference held in kernel by the frame buffer.
+ gbm_bo_destroy(bo);
+ }
+
+ bs_mapper_destroy(mapper);
+
+ for (size_t tcase_index = 0; tcase_index < tcase_count; tcase_index++) {
+ const struct test_case *tcase = &tcases[tcase_index];
+ uint32_t fb_id = fbs[tcase_index];
+
+ if (fb_id == 0)
+ continue;
+
+ printf("displaying test case: ");
+ test_case_print(stdout, tcase);
+ printf("\n");
+
+ int ret = drmModeSetCrtc(display_fd, pipe.crtc_id, fb_id, 0 /* x */, 0 /* y */,
+ &pipe.connector_id, 1 /* connector count */, mode);
+ if (ret) {
+ bs_debug_error("failed to set crtc: %d", ret);
+ return 1;
+ }
+ usleep(test_case_display_usec);
+ }
+
+ for (size_t tcase_index = 0; tcase_index < tcase_count; tcase_index++)
+ if (fbs[tcase_index] != 0)
+ drmModeRmFB(display_fd, fbs[tcase_index]);
+
+ bs_egl_destroy(&egl);
+
+ return all_pass ? 0 : 2;
+}
diff --git a/drm-tests/mapped_texture_test.c b/drm-tests/mapped_texture_test.c
new file mode 100644
index 0000000..e1cbac3
--- /dev/null
+++ b/drm-tests/mapped_texture_test.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ The mapped_texture_test test consists of:
+ * Importing external buffers as EGL Images
+ * Drawing an ellipse using the CPU
+ * Binding CPU drawn buffer as a texture and sampling from it
+ * Using KMS to scanout the resultant framebuffer
+ */
+
+#include <getopt.h>
+
+#include "bs_drm.h"
+
+// double buffering
+#define NUM_BUFFERS 2
+
+struct offscreen_buffer {
+ struct gbm_bo *bo;
+ GLuint tex;
+ EGLImageKHR image;
+ const struct bs_draw_format *draw_format;
+};
+
+struct framebuffer {
+ struct gbm_bo *bo;
+ uint32_t fb_id;
+ EGLImageKHR image;
+ struct bs_egl_fb *egl_fb;
+};
+
+struct gl_resources {
+ GLuint program;
+ GLuint vbo;
+};
+
+// clang-format off
+static const GLfloat vertices[] = {
+ // x y u v
+ -0.25f, -0.25f, 0.0f, 0.0f, // Bottom left
+ -0.25f, 0.25f, 0.0f, 1.0f, // Top left
+ 0.25f, 0.25f, 1.0f, 1.0f, // Top right
+ 0.25f, -0.25f, 1.0f, 0.0f, // Bottom Right
+};
+
+static const int binding_xy = 0;
+static const int binding_uv = 1;
+
+static const GLubyte indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+};
+
+// clang-format on
+
+static const GLchar *vert =
+ "attribute vec2 xy;\n"
+ "attribute vec2 uv;\n"
+ "varying vec2 tex_coordinate;\n"
+ "void main() {\n"
+ " gl_Position = vec4(xy, 0, 1);\n"
+ " tex_coordinate = uv;\n"
+ "}\n";
+
+static const GLchar *frag =
+ "precision mediump float;\n"
+ "uniform sampler2D ellipse;\n"
+ "varying vec2 tex_coordinate;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(ellipse, tex_coordinate);\n"
+ "}\n";
+
+static bool create_framebuffer(int display_fd, struct gbm_device *gbm, struct bs_egl *egl,
+ uint32_t width, uint32_t height, struct framebuffer *fb)
+{
+ fb->bo = gbm_bo_create(gbm, width, height, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ if (!fb->bo) {
+ bs_debug_error("failed to create a gbm buffer.");
+ goto delete_gl_fb;
+ }
+
+ fb->fb_id = bs_drm_fb_create_gbm(fb->bo);
+ if (!fb->fb_id) {
+ bs_debug_error("failed to create framebuffer from buffer object");
+ goto delete_gl_image;
+ }
+
+ fb->image = bs_egl_image_create_gbm(egl, fb->bo);
+ if (fb->image == EGL_NO_IMAGE_KHR) {
+ bs_debug_error("failed to make image from buffer object");
+ goto delete_fb;
+ }
+
+ fb->egl_fb = bs_egl_fb_new(egl, fb->image);
+ if (!fb->egl_fb) {
+ bs_debug_error("failed to make rednering framebuffer for buffer object");
+ goto delete_gbm_bo;
+ }
+
+ return true;
+
+delete_gl_fb:
+ bs_egl_fb_destroy(&fb->egl_fb);
+delete_gl_image:
+ bs_egl_image_destroy(egl, &fb->image);
+delete_fb:
+ drmModeRmFB(display_fd, fb->fb_id);
+delete_gbm_bo:
+ gbm_bo_destroy(fb->bo);
+ return false;
+}
+
+static bool add_offscreen_texture(struct gbm_device *gbm, struct bs_egl *egl,
+ struct offscreen_buffer *buffer, uint32_t width, uint32_t height,
+ uint32_t flags)
+{
+ buffer->bo =
+ gbm_bo_create(gbm, width, height, bs_get_pixel_format(buffer->draw_format), flags);
+ if (!buffer->bo) {
+ bs_debug_error("failed to allocate offscreen buffer object: format=%s \n",
+ bs_get_format_name(buffer->draw_format));
+ goto destroy_offscreen_buffer;
+ }
+
+ buffer->image = bs_egl_image_create_gbm(egl, buffer->bo);
+ if (buffer->image == EGL_NO_IMAGE_KHR) {
+ bs_debug_error("failed to create offscreen egl image");
+ goto destroy_offscreen_bo;
+ }
+
+ glActiveTexture(GL_TEXTURE1);
+ glGenTextures(1, &buffer->tex);
+ glBindTexture(GL_TEXTURE_2D, buffer->tex);
+
+ if (!bs_egl_target_texture2D(egl, buffer->image)) {
+ bs_debug_error("failed to import egl image as texture");
+ goto destroy_offscreen_image;
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ return true;
+
+destroy_offscreen_image:
+ glDeleteTextures(1, &buffer->tex);
+ bs_egl_image_destroy(egl, &buffer->image);
+destroy_offscreen_bo:
+ gbm_bo_destroy(buffer->bo);
+destroy_offscreen_buffer:
+ return false;
+}
+
+static bool init_gl(struct bs_egl_fb *fb, uint32_t width, uint32_t height,
+ struct gl_resources *resources)
+{
+ struct bs_gl_program_create_binding bindings[] = {
+ { binding_xy, "xy" }, { binding_uv, "uv" }, { 2, NULL },
+ };
+
+ resources->program = bs_gl_program_create_vert_frag_bind(vert, frag, bindings);
+ if (!resources->program) {
+ bs_debug_error("failed to compile shader program");
+ return false;
+ }
+
+ glGenBuffers(1, &resources->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, resources->vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(fb));
+ glViewport(0, 0, (GLint)width, (GLint)height);
+
+ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glUseProgram(resources->program);
+
+ glUniform1i(glGetUniformLocation(resources->program, "ellipse"), 1);
+ glEnableVertexAttribArray(binding_xy);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
+
+ glEnableVertexAttribArray(binding_uv);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat),
+ (void *)(2 * sizeof(GLfloat)));
+ return true;
+}
+
+static void draw_textured_quad(GLuint tex)
+{
+ glClear(GL_COLOR_BUFFER_BIT);
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indices);
+}
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "format", required_argument, NULL, 'f' },
+ { "dma-buf", no_argument, NULL, 'b' },
+ { "gem", no_argument, NULL, 'g' },
+ { "dumb", no_argument, NULL, 'd' },
+ { "tiled", no_argument, NULL, 't' },
+ { 0, 0, 0, 0 },
+};
+
+static void print_help(const char *argv0)
+{
+ printf("Usage: %s [OPTIONS]\n", argv0);
+ printf(" -h, --help Print help.\n");
+ printf(" -f, --format FOURCC format of texture (defaults to ARGB8888)\n");
+ printf(" -b, --dma-buf Use dma-buf mmap.\n");
+ printf(" -g, --gem Use GEM map(by default).\n");
+ printf(" -d, --dumb Use dump map.\n");
+}
+
+static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec,
+ void *data)
+{
+ int *waiting_for_flip = data;
+ *waiting_for_flip = 0;
+}
+
+void flush_egl(struct bs_egl *egl, EGLImageKHR image)
+{
+ bs_egl_image_flush_external(egl, image);
+ EGLSyncKHR sync = bs_egl_create_sync(egl, EGL_SYNC_FENCE_KHR, NULL);
+ bs_egl_wait_sync(egl, sync, 0, EGL_FOREVER_KHR);
+ bs_egl_destroy_sync(egl, sync);
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 1;
+ int display_fd = bs_drm_open_main_display();
+ if (display_fd < 0) {
+ bs_debug_error("failed to open card for display");
+ goto out;
+ }
+
+ struct offscreen_buffer buffer;
+ buffer.draw_format = bs_get_draw_format_from_name("ARGB8888");
+ struct bs_mapper *mapper = NULL;
+ uint32_t flags = GBM_BO_USE_TEXTURING;
+
+ int c;
+ while ((c = getopt_long(argc, argv, "f:bgdh", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'f':
+ if (!bs_parse_draw_format(optarg, &buffer.draw_format)) {
+ printf("choose the default format ARGB8888\n");
+ }
+ printf("format=%s\n", bs_get_format_name(buffer.draw_format));
+ break;
+ case 'b':
+ mapper = bs_mapper_dma_buf_new();
+ flags |= GBM_BO_USE_LINEAR;
+ printf("using dma-buf mmap\n");
+ break;
+ case 'g':
+ mapper = bs_mapper_gem_new();
+ flags |= GBM_BO_USE_SW_WRITE_OFTEN;
+ printf("using GEM map\n");
+ break;
+ case 'd':
+ mapper = bs_mapper_dumb_new(display_fd);
+ flags |= GBM_BO_USE_LINEAR;
+ printf("using dumb map\n");
+ break;
+ case 'h':
+ default:
+ print_help(argv[0]);
+ goto destroy_display_fd;
+ }
+ }
+
+ // Use gem map mapper by default, in case any arguments aren't selected.
+ if (!mapper) {
+ mapper = bs_mapper_gem_new();
+ flags |= GBM_BO_USE_SW_WRITE_OFTEN;
+ printf("using GEM map\n");
+ }
+
+ if (!mapper) {
+ bs_debug_error("failed to create mapper object");
+ goto destroy_display_fd;
+ }
+
+ uint32_t width;
+ uint32_t height;
+
+ struct gbm_device *gbm = gbm_create_device(display_fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm device");
+ goto destroy_mapper;
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(display_fd, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ goto destroy_gbm_device;
+ }
+
+ drmModeConnector *connector = drmModeGetConnector(display_fd, pipe.connector_id);
+ drmModeModeInfo *mode = &connector->modes[0];
+ width = mode->hdisplay;
+ height = mode->vdisplay;
+
+ struct bs_egl *egl = bs_egl_new();
+ if (!bs_egl_setup(egl)) {
+ bs_debug_error("failed to setup egl context");
+ goto destroy_gbm_device;
+ }
+
+ struct framebuffer fbs[NUM_BUFFERS] = {};
+ uint32_t front_buffer = 0;
+ for (size_t i = 0; i < NUM_BUFFERS; i++) {
+ if (!create_framebuffer(display_fd, gbm, egl, width, height, &fbs[i])) {
+ bs_debug_error("failed to create framebuffer");
+ goto delete_framebuffers;
+ }
+ }
+
+ if (!add_offscreen_texture(gbm, egl, &buffer, width / 4, height / 4, flags)) {
+ bs_debug_error("failed to create offscreen texture");
+ goto destroy_offscreen_buffer;
+ }
+
+ const struct framebuffer *back_fb = &fbs[front_buffer ^ 1];
+ struct gl_resources resources;
+ if (!init_gl(back_fb->egl_fb, width, height, &resources)) {
+ bs_debug_error("failed to initialize GL resources.\n");
+ goto destroy_gl_resources;
+ }
+
+ flush_egl(egl, back_fb->image);
+
+ ret = drmModeSetCrtc(display_fd, pipe.crtc_id, fbs[front_buffer].fb_id, 0 /* x */,
+ 0 /* y */, &pipe.connector_id, 1 /* connector count */, mode);
+ if (ret) {
+ bs_debug_error("failed to set crtc: %d", ret);
+ goto destroy_gl_resources;
+ }
+
+ drmEventContext evctx = {
+ .version = DRM_EVENT_CONTEXT_VERSION, .page_flip_handler = page_flip_handler,
+ };
+ fd_set fds;
+
+ // The test takes about 2 seconds to complete.
+ const size_t test_frames = 120;
+ for (size_t i = 0; i < test_frames; i++) {
+ int waiting_for_flip = 1;
+
+ const struct framebuffer *back_fb = &fbs[front_buffer ^ 1];
+ glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(back_fb->egl_fb));
+ if (!bs_draw_ellipse(mapper, buffer.bo, buffer.draw_format,
+ (float)i / test_frames)) {
+ bs_debug_error("failed to draw to buffer");
+ goto destroy_gl_resources;
+ }
+
+ draw_textured_quad(buffer.tex);
+
+ flush_egl(egl, back_fb->image);
+
+ ret = drmModePageFlip(display_fd, pipe.crtc_id, back_fb->fb_id,
+ DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip);
+ if (ret) {
+ bs_debug_error("failed to queue page flip");
+ goto destroy_gl_resources;
+ }
+
+ while (waiting_for_flip) {
+ FD_ZERO(&fds);
+ FD_SET(0, &fds);
+ FD_SET(display_fd, &fds);
+ int ret = select(display_fd + 1, &fds, NULL, NULL, NULL);
+ if (ret < 0) {
+ bs_debug_error("select err: %s", strerror(errno));
+ goto destroy_gl_resources;
+ } else if (FD_ISSET(0, &fds)) {
+ bs_debug_error("exit due to user-input");
+ goto destroy_gl_resources;
+ } else if (FD_ISSET(display_fd, &fds)) {
+ drmHandleEvent(display_fd, &evctx);
+ }
+ }
+
+ front_buffer ^= 1;
+ }
+
+destroy_gl_resources:
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glUseProgram(0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glDeleteProgram(resources.program);
+ glDeleteBuffers(1, &resources.vbo);
+destroy_offscreen_buffer:
+ glDeleteTextures(1, &buffer.tex);
+ bs_egl_image_destroy(egl, &buffer.image);
+ gbm_bo_destroy(buffer.bo);
+delete_framebuffers:
+ for (size_t i = 0; i < NUM_BUFFERS; i++) {
+ bs_egl_fb_destroy(&fbs[i].egl_fb);
+ bs_egl_image_destroy(egl, &fbs[i].image);
+ drmModeRmFB(display_fd, fbs[i].fb_id);
+ gbm_bo_destroy(fbs[i].bo);
+ }
+ bs_egl_destroy(&egl);
+destroy_gbm_device:
+ gbm_device_destroy(gbm);
+destroy_mapper:
+ bs_mapper_destroy(mapper);
+destroy_display_fd:
+ close(display_fd);
+out:
+ return ret;
+}
diff --git a/drm-tests/mmap_test.c b/drm-tests/mmap_test.c
new file mode 100644
index 0000000..321e33e
--- /dev/null
+++ b/drm-tests/mmap_test.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <getopt.h>
+
+#include "bs_drm.h"
+
+#define BUFFERS 2
+#define NUM_FRAMES 0x40
+
+struct framebuffer {
+ struct gbm_bo *bo;
+ uint32_t id;
+};
+
+struct context {
+ int display_fd;
+ uint32_t crtc_id;
+
+ struct framebuffer fbs[BUFFERS];
+ struct bs_mapper *mapper;
+
+ int vgem_device_fd;
+};
+
+static void disable_psr()
+{
+ const char psr_path[] = "/sys/module/i915/parameters/enable_psr";
+ int psr_fd = open(psr_path, O_WRONLY);
+
+ if (psr_fd < 0)
+ return;
+
+ if (write(psr_fd, "0", 1) == -1) {
+ bs_debug_error("failed to disable psr");
+ } else {
+ printf("disabled psr");
+ }
+
+ close(psr_fd);
+}
+
+static void do_fixes()
+{
+ disable_psr();
+}
+
+#define STEP_SKIP 0
+#define STEP_MMAP 1
+#define STEP_FAULT 2
+#define STEP_FLIP 3
+#define STEP_DRAW 4
+
+static void show_sequence(const int *sequence)
+{
+ int sequence_subindex;
+ printf("starting sequence: ");
+ for (sequence_subindex = 0; sequence_subindex < 4; sequence_subindex++) {
+ switch (sequence[sequence_subindex]) {
+ case STEP_SKIP:
+ break;
+ case STEP_MMAP:
+ printf("mmap ");
+ break;
+ case STEP_FAULT:
+ printf("fault ");
+ break;
+ case STEP_FLIP:
+ printf("flip ");
+ break;
+ case STEP_DRAW:
+ printf("draw ");
+ break;
+ default:
+ bs_debug_error("<unknown step %d> (aborting!)",
+ sequence[sequence_subindex]);
+ abort();
+ break;
+ }
+ }
+ printf("\n");
+}
+
+static void draw(struct context *ctx)
+{
+ // Run the drawing routine with the key driver events in different
+ // sequences.
+ const int sequences[4][4] = {
+ { STEP_MMAP, STEP_FAULT, STEP_FLIP, STEP_DRAW },
+ { STEP_MMAP, STEP_FLIP, STEP_DRAW, STEP_SKIP },
+ { STEP_MMAP, STEP_DRAW, STEP_FLIP, STEP_SKIP },
+ { STEP_FLIP, STEP_MMAP, STEP_DRAW, STEP_SKIP },
+ };
+
+ int sequence_index = 0;
+ int sequence_subindex = 0;
+
+ int fb_idx = 1;
+
+ for (sequence_index = 0; sequence_index < 4; sequence_index++) {
+ show_sequence(sequences[sequence_index]);
+ for (int frame_index = 0; frame_index < NUM_FRAMES; frame_index++) {
+ struct framebuffer *fb = &ctx->fbs[fb_idx];
+ size_t bo_stride = gbm_bo_get_plane_stride(fb->bo, 0);
+ size_t bo_size = gbm_bo_get_plane_size(fb->bo, 0);
+ const uint32_t width = gbm_bo_get_width(fb->bo);
+ const uint32_t height = gbm_bo_get_height(fb->bo);
+ uint32_t *bo_ptr;
+ volatile uint32_t *ptr;
+ void *map_data;
+
+ for (sequence_subindex = 0; sequence_subindex < 4; sequence_subindex++) {
+ switch (sequences[sequence_index][sequence_subindex]) {
+ case STEP_MMAP:
+ bo_ptr = bs_mapper_map(ctx->mapper, fb->bo, 0,
+ &map_data);
+ if (bo_ptr == MAP_FAILED)
+ bs_debug_error("failed to mmap gbm bo");
+
+ ptr = bo_ptr;
+ break;
+
+ case STEP_FAULT:
+ *ptr = 1234567;
+ break;
+
+ case STEP_FLIP:
+ drmModePageFlip(ctx->display_fd, ctx->crtc_id,
+ ctx->fbs[fb_idx].id, 0, NULL);
+ break;
+
+ case STEP_DRAW:
+ for (ptr = bo_ptr;
+ ptr < bo_ptr + (bo_size / sizeof(*bo_ptr));
+ ptr++) {
+ int y = ((void *)ptr - (void *)bo_ptr) /
+ bo_stride;
+ int x = ((void *)ptr - (void *)bo_ptr -
+ bo_stride * y) /
+ sizeof(*ptr);
+ x -= frame_index * (width / NUM_FRAMES);
+ y -= frame_index * (height / NUM_FRAMES);
+ *ptr = 0xff000000;
+ if (x * x + y * y <
+ frame_index * frame_index)
+ *ptr |= (frame_index % 0x100) << 8;
+ else
+ *ptr |= 0xff |
+ (sequence_index * 64 << 16);
+ }
+ break;
+
+ case STEP_SKIP:
+ default:
+ break;
+ }
+ }
+
+ bs_mapper_unmap(ctx->mapper, fb->bo, map_data);
+
+ usleep(1e6 / 120); /* 120 Hz */
+
+ fb_idx = fb_idx ^ 1;
+ }
+ }
+}
+
+static const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "dma-buf", no_argument, NULL, 'b' },
+ { "gem", no_argument, NULL, 'g' },
+ { "dumb", no_argument, NULL, 'd' },
+ { "vgem", no_argument, NULL, 'v' },
+ { "scanout", no_argument, NULL, 's' },
+ { 0, 0, 0, 0 },
+};
+
+static void print_help(const char *argv0)
+{
+ printf("Usage: %s [OPTIONS]\n", argv0);
+ printf(" -h, --help Print help.\n");
+ printf(" -b, --dma-buf Use dma-buf mmap (by default).\n");
+ printf(" -g, --gem Use GEM map.\n");
+ printf(" -d, --dumb Use dump map.\n");
+ printf(" -v, --vgem Use vgem dump map.\n");
+ printf(" -s, --scanout Use buffer optimized for scanout.\n");
+}
+
+int main(int argc, char **argv)
+{
+ struct context ctx = { 0 };
+
+ do_fixes();
+
+ ctx.display_fd = bs_drm_open_main_display();
+ if (ctx.display_fd < 0) {
+ bs_debug_error("failed to open card for display");
+ return 1;
+ }
+
+ struct gbm_device *gbm = gbm_create_device(ctx.display_fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm device");
+ return 1;
+ }
+
+ int c;
+ uint32_t flags = GBM_BO_USE_SCANOUT;
+ while ((c = getopt_long(argc, argv, "bgdvsh", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'b':
+ ctx.mapper = bs_mapper_dma_buf_new();
+ flags |= GBM_BO_USE_LINEAR;
+ printf("started dma-buf mmap.\n");
+ break;
+ case 'g':
+ ctx.mapper = bs_mapper_gem_new();
+ flags |= GBM_BO_USE_SW_READ_OFTEN | GBM_BO_USE_SW_WRITE_OFTEN;
+ printf("started GEM map.\n");
+ break;
+ case 'd':
+ ctx.mapper = bs_mapper_dumb_new(gbm_device_get_fd(gbm));
+ flags |= GBM_BO_USE_LINEAR;
+ printf("started dumb map.\n");
+ break;
+ case 'v':
+ ctx.vgem_device_fd = bs_drm_open_vgem();
+ ctx.mapper = bs_mapper_dumb_new(ctx.vgem_device_fd);
+ printf("started vgem map.\n");
+ break;
+ case 's':
+ flags = GBM_BO_USE_SCANOUT;
+ break;
+ case 'h':
+ default:
+ print_help(argv[0]);
+ return 1;
+ }
+ }
+
+ // Use dma-buf mmap by default, in case any arguments aren't selected.
+ if (!ctx.mapper) {
+ ctx.mapper = bs_mapper_dma_buf_new();
+ printf("started dma-buf mmap.\n");
+ }
+
+ if (ctx.mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ return 1;
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(ctx.display_fd, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+
+ drmModeConnector *connector = drmModeGetConnector(ctx.display_fd, pipe.connector_id);
+ drmModeModeInfo *mode = &connector->modes[0];
+ ctx.crtc_id = pipe.crtc_id;
+
+ printf("display size: %ux%u\n", mode->hdisplay, mode->vdisplay);
+
+ for (size_t fb_index = 0; fb_index < BUFFERS; ++fb_index) {
+ struct framebuffer *fb = &ctx.fbs[fb_index];
+ fb->bo =
+ gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, GBM_FORMAT_XRGB8888, flags);
+
+ if (!fb->bo) {
+ bs_debug_error("failed to create buffer object");
+ return 1;
+ }
+
+ fb->id = bs_drm_fb_create_gbm(fb->bo);
+ if (fb->id == 0) {
+ bs_debug_error("failed to create fb");
+ return 1;
+ }
+ }
+
+ if (drmModeSetCrtc(ctx.display_fd, pipe.crtc_id, ctx.fbs[0].id, 0, 0, &pipe.connector_id, 1,
+ mode)) {
+ bs_debug_error("failed to set CRTC");
+ return 1;
+ }
+
+ draw(&ctx);
+
+ return 0;
+}
diff --git a/drm-tests/null_platform_test.c b/drm-tests/null_platform_test.c
new file mode 100644
index 0000000..9eebfd0
--- /dev/null
+++ b/drm-tests/null_platform_test.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2014 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+#include <getopt.h>
+
+#define NUM_BUFFERS 2
+
+static uint32_t allowed_formats[] = {
+ GBM_FORMAT_XRGB8888, GBM_FORMAT_XBGR8888, GBM_FORMAT_XRGB2101010, GBM_FORMAT_XBGR2101010,
+};
+
+const size_t allowed_formats_length = BS_ARRAY_LEN(allowed_formats);
+
+static GLuint solid_shader_create()
+{
+ const GLchar *vert =
+ "attribute vec4 vPosition;\n"
+ "attribute vec4 vColor;\n"
+ "varying vec4 vFillColor;\n"
+ "void main() {\n"
+ " gl_Position = vPosition;\n"
+ " vFillColor = vColor;\n"
+ "}\n";
+
+ const GLchar *frag =
+ "precision mediump float;\n"
+ "varying vec4 vFillColor;\n"
+ "void main() {\n"
+ " gl_FragColor = vFillColor;\n"
+ "}\n";
+
+ struct bs_gl_program_create_binding bindings[] = {
+ { 0, "vPosition" }, { 1, "vColor" }, { 0, NULL },
+ };
+
+ return bs_gl_program_create_vert_frag_bind(vert, frag, bindings);
+}
+
+static float f(int i)
+{
+ int a = i % 40;
+ int b = (i / 40) % 6;
+ switch (b) {
+ case 0:
+ case 1:
+ return 0.0f;
+ case 3:
+ case 4:
+ return 1.0f;
+ case 2:
+ return (a / 40.0f);
+ case 5:
+ return 1.0f - (a / 40.0f);
+ default:
+ return 0.0f;
+ }
+}
+
+static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec,
+ void *data)
+{
+ int *waiting_for_flip = data;
+ *waiting_for_flip = 0;
+}
+
+static uint32_t find_format(char *fourcc)
+{
+ if (!fourcc || strlen(fourcc) < 4)
+ return 0;
+
+ uint32_t format = fourcc_code(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
+ for (int i = 0; i < allowed_formats_length; i++) {
+ if (allowed_formats[i] == format)
+ return format;
+ }
+
+ return 0;
+}
+
+static const struct option longopts[] = {
+ { "format", required_argument, NULL, 'f' },
+ { "test-page-flip-format-change", required_argument, NULL, 'p' },
+ { "help", no_argument, NULL, 'h' },
+ { 0, 0, 0, 0 },
+};
+
+static void print_help(const char *argv0)
+{
+ char allowed_formats_string[allowed_formats_length * 6];
+ int i;
+ for (i = 0; i < allowed_formats_length; i++) {
+ uint32_t format = allowed_formats[i];
+ sprintf(allowed_formats_string + i * 6, "%.4s, ", (char *)&format);
+ }
+
+ allowed_formats_string[i * 6 - 2] = 0;
+
+ // clang-format off
+ printf("usage: %s [OPTIONS] [drm_device_path]\n", argv0);
+ printf(" -f, --format <format> defines the fb format.\n");
+ printf(" -p, --test-page-flip-format-change <format> test page flips alternating formats.\n");
+ printf(" -h, --help show help\n");
+ printf("\n");
+ printf(" <format> must be one of [ %s ].\n", allowed_formats_string);
+ printf("\n");
+ printf("\n");
+ // clang-format on
+}
+
+int main(int argc, char **argv)
+{
+ int fd = -1;
+ bool help_flag = false;
+ uint32_t format = GBM_FORMAT_XRGB8888;
+ uint32_t test_page_flip_format_change = 0;
+
+ int c = -1;
+ while ((c = getopt_long(argc, argv, "hp:f:", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'p':
+ test_page_flip_format_change = find_format(optarg);
+ if (!test_page_flip_format_change)
+ help_flag = true;
+ break;
+ case 'f':
+ format = find_format(optarg);
+ if (!format)
+ help_flag = true;
+ break;
+ case 'h':
+ help_flag = true;
+ break;
+ }
+ }
+
+ if (help_flag) {
+ print_help(*argv);
+ }
+
+ if (optind < argc) {
+ fd = open(argv[optind], O_RDWR);
+ if (fd < 0) {
+ bs_debug_error("failed to open card %s", argv[optind]);
+ return 1;
+ }
+ } else {
+ fd = bs_drm_open_main_display();
+ if (fd < 0) {
+ bs_debug_error("failed to open card for display");
+ return 1;
+ }
+ }
+
+ struct gbm_device *gbm = gbm_create_device(fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm");
+ return 1;
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(fd, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+
+ drmModeConnector *connector = drmModeGetConnector(fd, pipe.connector_id);
+ assert(connector);
+ drmModeModeInfo *mode = &connector->modes[0];
+
+ struct bs_egl *egl = bs_egl_new();
+ if (!bs_egl_setup(egl)) {
+ bs_debug_error("failed to setup egl context");
+ return 1;
+ }
+
+ struct gbm_bo *bos[NUM_BUFFERS];
+ uint32_t ids[NUM_BUFFERS];
+ struct bs_egl_fb *egl_fbs[NUM_BUFFERS];
+ EGLImageKHR egl_images[NUM_BUFFERS];
+ for (size_t fb_index = 0; fb_index < NUM_BUFFERS; fb_index++) {
+ if (test_page_flip_format_change && fb_index) {
+ format = test_page_flip_format_change;
+ }
+
+ bos[fb_index] = gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, format,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ if (bos[fb_index] == NULL) {
+ bs_debug_error("failed to allocate framebuffer");
+ return 1;
+ }
+
+ ids[fb_index] = bs_drm_fb_create_gbm(bos[fb_index]);
+ if (ids[fb_index] == 0) {
+ bs_debug_error("failed to create framebuffer id");
+ return 1;
+ }
+
+ EGLImageKHR egl_image = bs_egl_image_create_gbm(egl, bos[fb_index]);
+ if (egl_image == EGL_NO_IMAGE_KHR) {
+ bs_debug_error("failed to create EGLImageKHR from framebuffer");
+ return 1;
+ }
+
+ egl_fbs[fb_index] = bs_egl_fb_new(egl, egl_image);
+ if (!egl_fbs[fb_index]) {
+ bs_debug_error("failed to create framebuffer from EGLImageKHR");
+ return 1;
+ }
+ egl_images[fb_index] = egl_image;
+ }
+
+ int ret = drmModeSetCrtc(fd, pipe.crtc_id, ids[0], 0 /* x */, 0 /* y */, &pipe.connector_id,
+ 1 /* connector count */, mode);
+ if (ret) {
+ bs_debug_error("failed to set CRTC");
+ return 1;
+ }
+
+ GLuint program = solid_shader_create();
+ if (!program) {
+ bs_debug_error("failed to create solid shader");
+ return 1;
+ }
+
+ int fb_idx = 1;
+ for (int i = 0; i <= 500; i++) {
+ int waiting_for_flip = 1;
+ // clang-format off
+ GLfloat verts[] = {
+ 0.0f, -0.5f, 0.0f,
+ -0.5f, 0.5f, 0.0f,
+ 0.5f, 0.5f, 0.0f
+ };
+ GLfloat colors[] = {
+ 1.0f, 0.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f, 1.0f
+ };
+ // clang-format on
+
+ glBindFramebuffer(GL_FRAMEBUFFER, bs_egl_fb_name(egl_fbs[fb_idx]));
+ glViewport(0, 0, (GLint)mode->hdisplay, (GLint)mode->vdisplay);
+
+ glClearColor(f(i), f(i + 80), f(i + 160), 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glUseProgram(program);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, verts);
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, colors);
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ usleep(1e6 / 120); /* 120 Hz */
+ glFinish();
+
+ if (!bs_egl_image_flush_external(egl, egl_images[fb_idx])) {
+ bs_debug_error("failed to call image_flush_external");
+ return 1;
+ }
+
+ ret = drmModePageFlip(fd, pipe.crtc_id, ids[fb_idx], DRM_MODE_PAGE_FLIP_EVENT,
+ &waiting_for_flip);
+ if (ret) {
+ bs_debug_error("failed page flip: %d", ret);
+ return 1;
+ }
+
+ while (waiting_for_flip) {
+ drmEventContext evctx = {
+ .version = DRM_EVENT_CONTEXT_VERSION,
+ .page_flip_handler = page_flip_handler,
+ };
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+
+ ret = select(fd + 1, &fds, NULL, NULL, NULL);
+ if (ret < 0) {
+ bs_debug_error("select err: %s", strerror(errno));
+ return 1;
+ } else if (ret == 0) {
+ bs_debug_error("select timeout");
+ return 1;
+ }
+ ret = drmHandleEvent(fd, &evctx);
+ if (ret) {
+ bs_debug_error("failed to wait for page flip: %d", ret);
+ return 1;
+ }
+ }
+ fb_idx = fb_idx ^ 1;
+ }
+
+ for (size_t fb_index = 0; fb_index < NUM_BUFFERS; fb_index++) {
+ bs_egl_fb_destroy(&egl_fbs[fb_index]);
+ bs_egl_image_destroy(egl, &egl_images[fb_index]);
+ drmModeRmFB(fd, ids[fb_idx]);
+ gbm_bo_destroy(bos[fb_index]);
+ }
+
+ bs_egl_destroy(&egl);
+ gbm_device_destroy(gbm);
+ close(fd);
+ return 0;
+}
diff --git a/drm-tests/plane_test.c b/drm-tests/plane_test.c
new file mode 100644
index 0000000..bb54f52
--- /dev/null
+++ b/drm-tests/plane_test.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright 2015 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <ctype.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "bs_drm.h"
+
+#define MAX_TEST_PLANES 4
+
+static int64_t ns_since(struct timespec *since)
+{
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ return (int64_t)(now.tv_sec - since->tv_sec) * 1000000000 + (int64_t)now.tv_nsec -
+ (int64_t)since->tv_nsec;
+}
+
+static drmModeModeInfoPtr find_best_mode(int mode_count, drmModeModeInfoPtr modes)
+{
+ if (mode_count <= 0 || modes == NULL)
+ return NULL;
+
+ for (int m = 0; m < mode_count; m++)
+ if (modes[m].type & DRM_MODE_TYPE_PREFERRED)
+ return &modes[m];
+
+ return &modes[0];
+}
+
+static bool is_format_supported(uint32_t format_count, uint32_t *formats, uint32_t format)
+{
+ for (uint32_t i = 0; i < format_count; i++)
+ if (formats[i] == format)
+ return true;
+ return false;
+}
+
+static bool find_overlay_planes(int fd, uint32_t crtc_id, size_t plane_count, uint32_t *formats,
+ uint32_t *plane_ids)
+{
+ drmModeRes *res = drmModeGetResources(fd);
+ if (res == NULL) {
+ bs_debug_error("failed to get drm resources");
+ return false;
+ }
+
+ uint32_t crtc_mask = 0;
+ for (int crtc_index = 0; crtc_index < res->count_crtcs; crtc_index++) {
+ if (res->crtcs[crtc_index] == crtc_id) {
+ crtc_mask = (1 << crtc_index);
+ break;
+ }
+ }
+ if (crtc_mask == 0) {
+ bs_debug_error("invalid crtc id %u", crtc_id);
+ drmModeFreeResources(res);
+ return false;
+ }
+
+ drmModePlaneRes *plane_res = drmModeGetPlaneResources(fd);
+ if (plane_res == NULL) {
+ bs_debug_error("failed to get plane resources");
+ drmModeFreeResources(res);
+ return false;
+ }
+
+ for (size_t out_index = 0; out_index < plane_count; out_index++)
+ plane_ids[out_index] = 0;
+
+ size_t remaining_out = plane_count;
+ for (uint32_t plane_index = 0; remaining_out > 0 && plane_index < plane_res->count_planes;
+ plane_index++) {
+ drmModePlane *plane = drmModeGetPlane(fd, plane_res->planes[plane_index]);
+ if (plane == NULL) {
+ bs_debug_error("failed to get plane id %u", plane_res->planes[plane_index]);
+ continue;
+ }
+
+ size_t out_index;
+ for (out_index = 0; out_index < plane_count; out_index++) {
+ if (plane_ids[out_index] == 0 &&
+ is_format_supported(plane->count_formats, plane->formats,
+ formats[out_index]))
+ break;
+ }
+
+ if ((plane->possible_crtcs & crtc_mask) == 0 || out_index == plane_count) {
+ drmModeFreePlane(plane);
+ continue;
+ }
+
+ drmModeObjectPropertiesPtr props =
+ drmModeObjectGetProperties(fd, plane->plane_id, DRM_MODE_OBJECT_PLANE);
+ for (uint32_t prop_index = 0; prop_index < props->count_props; ++prop_index) {
+ drmModePropertyPtr prop = drmModeGetProperty(fd, props->props[prop_index]);
+ if (strcmp(prop->name, "type")) {
+ drmModeFreeProperty(prop);
+ continue;
+ }
+
+ uint64_t desired_value = 0;
+ bool has_value = false;
+ for (int enum_index = 0; enum_index < prop->count_enums; enum_index++) {
+ struct drm_mode_property_enum *penum = &prop->enums[enum_index];
+ if (!strcmp(penum->name, "Overlay")) {
+ desired_value = penum->value;
+ has_value = true;
+ break;
+ }
+ }
+ drmModeFreeProperty(prop);
+
+ if (has_value && desired_value == props->prop_values[prop_index]) {
+ plane_ids[out_index] = plane->plane_id;
+ remaining_out--;
+ }
+
+ break;
+ }
+
+ drmModeFreeObjectProperties(props);
+ drmModeFreePlane(plane);
+ }
+
+ drmModeFreePlaneResources(plane_res);
+ drmModeFreeResources(res);
+
+ return remaining_out == 0;
+}
+
+struct test_plane {
+ const struct bs_draw_format *format;
+
+ bool has_bo_size;
+ uint32_t bo_w;
+ uint32_t bo_h;
+
+ bool has_dst_position;
+ int32_t dst_x;
+ int32_t dst_y;
+
+ int32_t dst_center_x;
+ int32_t dst_center_y;
+
+ int32_t dst_vx;
+ int32_t dst_vy;
+
+ bool has_dst_size;
+ uint32_t dst_w;
+ uint32_t dst_h;
+
+ uint32_t dst_min_w;
+ uint32_t dst_max_w;
+ uint32_t dst_min_h;
+ uint32_t dst_max_h;
+
+ bool has_dst_scale;
+ uint32_t dst_downscale_factor;
+ uint32_t dst_upscale_factor;
+
+ int32_t src_x;
+ int32_t src_y;
+
+ int32_t src_vx;
+ int32_t src_vy;
+
+ bool has_src_size;
+ uint32_t src_w;
+ uint32_t src_h;
+
+ struct gbm_bo *bo;
+ uint32_t fb_id;
+};
+
+static bool update_test_plane(int step, int32_t max_x, int32_t max_y, struct test_plane *tp)
+{
+ bool needs_set = (step == 0);
+ if (tp->has_dst_scale) {
+ float scale_factor = (sinf(step / 120.0f) / 2.0f + 0.5f);
+ scale_factor = powf(scale_factor, 4);
+ tp->dst_w = tp->dst_min_w + scale_factor * (tp->dst_max_w - tp->dst_min_w);
+ tp->dst_h = tp->dst_min_h + scale_factor * (tp->dst_max_h - tp->dst_min_h);
+ needs_set = true;
+ }
+
+ if (tp->dst_vx != 0) {
+ tp->dst_center_x += tp->dst_vx;
+ if (tp->dst_center_x > max_x || tp->dst_center_x < 0) {
+ tp->dst_vx = -tp->dst_vx;
+ tp->dst_x += tp->dst_vx * 2;
+ }
+ needs_set = true;
+ }
+ tp->dst_x = tp->dst_center_x - tp->dst_w / 2;
+
+ if (tp->dst_vy != 0) {
+ tp->dst_center_y += tp->dst_vy;
+ if (tp->dst_center_y > max_y || tp->dst_center_y < 0) {
+ tp->dst_vy = -tp->dst_vy;
+ tp->dst_y += tp->dst_vy * 2;
+ }
+ needs_set = true;
+ }
+ tp->dst_y = tp->dst_center_y - tp->dst_h / 2;
+
+ if (tp->src_vx != 0) {
+ tp->src_x += tp->src_vx;
+ if (tp->src_x + tp->src_w > gbm_bo_get_width(tp->bo) || tp->src_x < 0) {
+ tp->src_vx = -tp->src_vx;
+ tp->src_x += tp->src_vx * 2;
+ }
+ needs_set = true;
+ }
+
+ if (tp->src_vy != 0) {
+ tp->src_y += tp->src_vy;
+ if (tp->src_y + tp->src_h > gbm_bo_get_height(tp->bo) || tp->src_y < 0) {
+ tp->src_vy = -tp->src_vy;
+ tp->src_y += tp->src_vy * 2;
+ }
+ needs_set = true;
+ }
+ return needs_set;
+}
+
+static bool parse_size(const char *str, uint32_t *w, uint32_t *h)
+{
+ if (sscanf(str, "%ux%u", w, h) == 2)
+ return true;
+
+ printf("unrecognized size format \"%s\"\n", str);
+
+ return false;
+}
+
+static bool parse_scale(const char *str, uint32_t *down, uint32_t *up)
+{
+ if (sscanf(str, "%u/%u", down, up) == 2)
+ return true;
+
+ printf("unrecognized scale format \"%s\"\n", str);
+
+ return false;
+}
+
+static bool parse_rect(const char *str, int32_t *x, int32_t *y, uint32_t *w, uint32_t *h,
+ bool *has_position, bool *has_size)
+{
+ if (sscanf(str, "%d,%d,%u,%u", x, y, w, h) == 4) {
+ if (has_position)
+ *has_position = true;
+ if (has_size)
+ *has_size = true;
+ return true;
+ }
+
+ if (sscanf(str, "%d,%d", x, y) == 2) {
+ if (has_position)
+ *has_position = true;
+ if (has_size)
+ *has_size = false;
+ return true;
+ }
+
+ if (sscanf(str, "%ux%u", w, h) == 2) {
+ if (has_position)
+ *has_position = false;
+ if (has_size)
+ *has_size = true;
+ return true;
+ }
+
+ printf("unrecognized rectangle format \"%s\"\n", str);
+
+ return false;
+}
+
+static const struct option longopts[] = {
+ { "plane", no_argument, NULL, 'p' },
+ { "format", required_argument, NULL, 'f' },
+ { "size", required_argument, NULL, 'z' },
+ { "scale", required_argument, NULL, 'c' },
+ { "translate", no_argument, NULL, 't' },
+ { "src", required_argument, NULL, 's' },
+ { "dst", required_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { 0, 0, 0, 0 },
+};
+
+static void print_help(const char *argv0)
+{
+ // clang-format off
+ printf("usage: %s [OPTIONS]\n", argv0);
+ printf(" -p, --plane indicates that subsequent parameters are for a new plane\n");
+ printf(" -f, --format FOURCC format of source buffer (defaults to NV12)\n");
+ printf(" -z, --size WIDTHxHEIGHT size of the source buffer (defaults to screen size)\n");
+ printf(" -c, --scale DOWN/UP scale plane over time between (1/DOWN)x and UPx\n");
+ printf(" -t, --translate translate plane over time\n");
+ printf(" -s, --src RECT source rectangle (defaults to full buffer)\n");
+ printf(" -d, --dst RECT destination rectangle (defaults to buffer size centered in screen)\n");
+ printf(" -h, --help show help\n");
+ printf("\n");
+ printf("The format of RECT arguments is X,Y or X,Y,WIDTH,HEIGHT or WIDTHxHEIGHT.\n");
+ printf("To test more than one plane, separate plane arguments with -p. For example:\n");
+ printf(" %s --format NV12 --size 400x400 -p --format XR24 --size 100x100 --translate\n", argv0);
+ printf("\n");
+ // clang-format on
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 0;
+ bool help_flag = false;
+ size_t test_planes_count = 1;
+ struct test_plane test_planes[MAX_TEST_PLANES] = { { 0 } };
+ uint32_t test_planes_formats[MAX_TEST_PLANES] = { 0 };
+ uint32_t test_planes_ids[MAX_TEST_PLANES] = { 0 };
+
+ int c;
+ while ((c = getopt_long(argc, argv, "pf:z:c:ts:d:h", longopts, NULL)) != -1) {
+ struct test_plane *current_plane = &test_planes[test_planes_count - 1];
+ switch (c) {
+ case 'p':
+ test_planes_count++;
+ if (test_planes_count > MAX_TEST_PLANES) {
+ printf("only %d planes are allowed\n", MAX_TEST_PLANES);
+ return 1;
+ }
+ break;
+ case 'f':
+ if (!bs_parse_draw_format(optarg, ¤t_plane->format))
+ return 1;
+ break;
+ case 'z':
+ if (!parse_size(optarg, ¤t_plane->bo_w, ¤t_plane->bo_h))
+ return 1;
+ current_plane->has_bo_size = true;
+ break;
+ case 'c':
+ if (!parse_scale(optarg, ¤t_plane->dst_downscale_factor,
+ ¤t_plane->dst_upscale_factor))
+ return 1;
+ current_plane->has_dst_scale = true;
+ break;
+ case 't':
+ current_plane->dst_vx = 7;
+ current_plane->dst_vy = 7;
+ break;
+ case 's':
+ if (!parse_rect(optarg, ¤t_plane->src_x,
+ ¤t_plane->src_y, ¤t_plane->src_w,
+ ¤t_plane->src_h, NULL,
+ ¤t_plane->has_src_size))
+ return 1;
+ break;
+ case 'd':
+ if (!parse_rect(optarg, ¤t_plane->dst_x,
+ ¤t_plane->dst_y, ¤t_plane->dst_w,
+ ¤t_plane->dst_h,
+ ¤t_plane->has_dst_position,
+ ¤t_plane->has_dst_size))
+ return 1;
+ break;
+ case '?':
+ ret = 1;
+ case 'h':
+ help_flag = true;
+ break;
+ }
+ }
+
+ if (help_flag)
+ print_help(argv[0]);
+
+ if (ret)
+ return ret;
+
+ drmModeConnector *connector;
+ struct bs_drm_pipe pipe = { 0 };
+ struct bs_drm_pipe_plumber *plumber = bs_drm_pipe_plumber_new();
+ bs_drm_pipe_plumber_connector_ranks(plumber, bs_drm_connectors_internal_rank);
+ bs_drm_pipe_plumber_connector_ptr(plumber, &connector);
+ if (!bs_drm_pipe_plumber_make(plumber, &pipe)) {
+ bs_debug_error("failed to make pipe");
+ return 1;
+ }
+ bs_drm_pipe_plumber_destroy(&plumber);
+
+ drmModeModeInfo *mode_ptr = find_best_mode(connector->count_modes, connector->modes);
+ if (!mode_ptr) {
+ bs_debug_error("failed to find preferred mode");
+ return 1;
+ }
+ drmModeModeInfo mode = *mode_ptr;
+ drmModeFreeConnector(connector);
+ printf("Using mode %s\n", mode.name);
+
+ printf("Using CRTC:%u ENCODER:%u CONNECTOR:%u\n", pipe.crtc_id, pipe.encoder_id,
+ pipe.connector_id);
+
+ struct gbm_device *gbm = gbm_create_device(pipe.fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm");
+ return 1;
+ }
+
+ struct gbm_bo *bg_bo = gbm_bo_create(gbm, mode.hdisplay, mode.vdisplay, GBM_FORMAT_XRGB8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_SW_WRITE_RARELY);
+ if (!bg_bo) {
+ bs_debug_error("failed to create background buffer ojbect");
+ return 1;
+ }
+
+ struct bs_mapper *mapper = bs_mapper_gem_new();
+ if (mapper == NULL) {
+ bs_debug_error("failed to create mapper object");
+ return 1;
+ }
+
+ void *map_data;
+ void *bo_ptr = bs_mapper_map(mapper, bg_bo, 0, &map_data);
+ if (bo_ptr == MAP_FAILED) {
+ bs_debug_error("failed to mmap background buffer object");
+ return 1;
+ }
+ memset(bo_ptr, 0, gbm_bo_get_height(bg_bo) * gbm_bo_get_stride(bg_bo));
+ bs_mapper_unmap(mapper, bg_bo, map_data);
+
+ uint32_t crtc_fb_id = bs_drm_fb_create_gbm(bg_bo);
+ if (!crtc_fb_id) {
+ bs_debug_error("failed to create frame buffer for buffer object");
+ return 1;
+ }
+
+ for (size_t test_plane_index = 0; test_plane_index < test_planes_count;
+ test_plane_index++) {
+ struct test_plane *tp = &test_planes[test_plane_index];
+
+ if (!tp->format)
+ tp->format = bs_get_draw_format(GBM_FORMAT_NV12);
+
+ test_planes_formats[test_plane_index] = bs_get_pixel_format(tp->format);
+
+ if (!tp->has_bo_size) {
+ tp->bo_w = mode.hdisplay;
+ tp->bo_h = mode.vdisplay;
+ }
+
+ if (!tp->has_src_size) {
+ tp->src_w = tp->bo_w;
+ tp->src_h = tp->bo_h;
+ }
+
+ if (!tp->has_dst_size) {
+ tp->dst_w = tp->bo_w;
+ tp->dst_h = tp->bo_h;
+ }
+
+ if (tp->has_dst_position) {
+ tp->dst_center_x = tp->dst_x + mode.hdisplay / 2;
+ tp->dst_center_y = tp->dst_y + mode.vdisplay / 2;
+ } else {
+ tp->dst_center_x = mode.hdisplay / 2;
+ tp->dst_center_y = mode.vdisplay / 2;
+ tp->dst_x = tp->dst_center_x - tp->dst_w / 2;
+ tp->dst_y = tp->dst_center_y - tp->dst_h / 2;
+ }
+
+ if (tp->has_dst_scale) {
+ tp->dst_min_w = tp->dst_w / tp->dst_downscale_factor;
+ tp->dst_max_w = tp->dst_w * tp->dst_upscale_factor;
+ tp->dst_min_h = tp->dst_h / tp->dst_downscale_factor;
+ tp->dst_max_h = tp->dst_h * tp->dst_upscale_factor;
+ }
+
+ printf("Creating buffer %ux%u %s\n", tp->bo_w, tp->bo_h,
+ bs_get_format_name(tp->format));
+ tp->bo = gbm_bo_create(gbm, tp->bo_w, tp->bo_h, bs_get_pixel_format(tp->format),
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_SW_WRITE_RARELY);
+ if (!tp->bo) {
+ bs_debug_error("failed to create buffer object");
+ return 1;
+ }
+
+ tp->fb_id = bs_drm_fb_create_gbm(tp->bo);
+ if (!tp->fb_id) {
+ bs_debug_error("failed to create plane frame buffer for buffer object");
+ return 1;
+ }
+
+ if (!bs_draw_stripe(mapper, tp->bo, tp->format)) {
+ bs_debug_error("failed to draw pattern to buffer object");
+ return 1;
+ }
+ }
+
+ if (!find_overlay_planes(pipe.fd, pipe.crtc_id, test_planes_count, test_planes_formats,
+ test_planes_ids)) {
+ bs_debug_error("failed to find overlay planes for given formats");
+ return 1;
+ }
+
+ ret = drmModeSetCrtc(pipe.fd, pipe.crtc_id, crtc_fb_id, 0, 0, &pipe.connector_id, 1, &mode);
+ if (ret < 0) {
+ bs_debug_error("Could not set mode on CRTC %d %s", pipe.crtc_id, strerror(errno));
+ return 1;
+ }
+
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ const int64_t ten_seconds_in_ns = 10000000000;
+ for (int i = 0; ns_since(&start) < ten_seconds_in_ns; i++) {
+ for (size_t test_plane_index = 0; test_plane_index < test_planes_count;
+ test_plane_index++) {
+ struct test_plane *current_plane = &test_planes[test_plane_index];
+
+ bool needs_set =
+ update_test_plane(i, mode.hdisplay, mode.vdisplay, current_plane);
+ if (!needs_set)
+ continue;
+
+ ret = drmModeSetPlane(
+ pipe.fd, test_planes_ids[test_plane_index], pipe.crtc_id,
+ current_plane->fb_id, 0 /* flags */, current_plane->dst_x,
+ current_plane->dst_y, current_plane->dst_w, current_plane->dst_h,
+ current_plane->src_x << 16, current_plane->src_y << 16,
+ current_plane->src_w << 16, current_plane->src_h << 16);
+
+ if (ret) {
+ bs_debug_error(
+ "failed to set plane "
+ "%d:\ndst[x,y,w,h]=%d,%d,%u,%u\nsrc[x,y,w,h]=%d,%d,%u,%u",
+ ret, current_plane->dst_x, current_plane->dst_y,
+ current_plane->dst_w, current_plane->dst_h,
+ current_plane->src_x, current_plane->src_y,
+ current_plane->src_w, current_plane->src_h);
+ return 1;
+ }
+ }
+ usleep(1000000 / 60);
+ }
+
+ ret = drmModeSetCrtc(pipe.fd, pipe.crtc_id, 0, 0, 0, NULL, 0, NULL);
+ if (ret < 0) {
+ bs_debug_error("Could not disable CRTC %d %s", pipe.crtc_id, strerror(errno));
+ return 1;
+ }
+
+ bs_mapper_destroy(mapper);
+ for (size_t test_plane_index = 0; test_plane_index < test_planes_count;
+ test_plane_index++) {
+ struct test_plane *current_plane = &test_planes[test_plane_index];
+ drmModeRmFB(pipe.fd, current_plane->fb_id);
+ gbm_bo_destroy(current_plane->bo);
+ }
+ drmModeRmFB(pipe.fd, crtc_fb_id);
+ gbm_bo_destroy(bg_bo);
+ gbm_device_destroy(gbm);
+ close(pipe.fd);
+
+ return 0;
+}
diff --git a/drm-tests/rk3288_hdmitables.py b/drm-tests/rk3288_hdmitables.py
new file mode 100755
index 0000000..99d0f23
--- /dev/null
+++ b/drm-tests/rk3288_hdmitables.py
@@ -0,0 +1,454 @@
+#!/usr/bin/env python2
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+
+# Things seem to magically change in the tables at these TMDS rates.
+# Specifically looking at NO pixel repetition in the table:
+#
+# 0 - 44.9 - output divider is 0b11
+# 49.5 - 90.0 - output divider is 0b10
+# 94.5 - 182.75 - output divider is 0b01
+# 185.625 - - output divider is 0b00
+#
+# You can also notice that MPLL charge pump settings change at similar times.
+
+RATE1 = 46000000
+RATE2 = 92000000
+RATE3 = 184000000
+
+
+def make_mpll(rate, depth, pixel_rep=0):
+ assert pixel_rep == 0, "Untested with non-zero pixel rep and probably wrong"
+
+ tmds = (rate * depth) / 8. * (pixel_rep + 1)
+
+ if depth == 8:
+ prep_div = 0
+ elif depth == 10:
+ prep_div = 1
+ elif depth == 12:
+ prep_div = 2
+ elif depth == 16:
+ prep_div = 3
+
+ # Rates higher than 340MHz are HDMI 2.0
+ # From tables, tmdsmhl_cntrl is 100% correlated with HDMI 1.4 vs 2.0
+ if tmds <= 340000000:
+ opmode = 0 # HDMI 1.4
+ tmdsmhl_cntrl = 0x0
+ else:
+ opmode = 1 # HDMI 2.0
+ tmdsmhl_cntrl = 0x3
+
+ # Keep the rate within the proper range with the output divider control
+ if tmds <= RATE1:
+ n_cntrl = 0x3 # output divider: 0b11
+ elif tmds <= RATE2:
+ n_cntrl = 0x2 # output divider: 0b10
+ elif tmds <= RATE3:
+ n_cntrl = 0x1 # output divider: 0b01
+ else:
+ n_cntrl = 0x0 # output divider: 0b00
+
+ # Need to make the dividers work out
+ #
+ # This could be done algorithmically, but let's not for now. We show the
+ # math to make this work out below as an assert.
+ if n_cntrl == 0x3:
+ if depth == 8:
+ fbdiv2_cntrl = 0x2 # feedback div1: / 2 (no +1)
+ fbdiv1_cntrl = 0x3 # feedback div2: / 4
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 10:
+ fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
+ fbdiv1_cntrl = 0x1 # feedback div2: / 2
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 12:
+ fbdiv2_cntrl = 0x3 # feedback div1: / 3 (no +1)
+ fbdiv1_cntrl = 0x3 # feedback div2: / 4
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 16:
+ # Guess:
+ fbdiv2_cntrl = 0x4 # feedback div1: / 4 (no +1)
+ fbdiv1_cntrl = 0x3 # feedback div2: / 4
+ ref_cntrl = 0x0 # input divider: / 1
+
+ elif n_cntrl == 0x2:
+ if depth == 8:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x3 # feedback div2: / 4
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 10:
+ fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
+ fbdiv1_cntrl = 0x0 # feedback div2: / 1
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 12:
+ fbdiv2_cntrl = 0x2 # feedback div1: / 2 (no +1)
+ fbdiv1_cntrl = 0x2 # feedback div2: / 3
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 16:
+ fbdiv2_cntrl = 0x2 # feedback div1: / 2 (no +1)
+ fbdiv1_cntrl = 0x3 # feedback div2: / 4
+ ref_cntrl = 0x0 # input divider: / 1
+
+ elif n_cntrl == 0x1:
+ if depth == 8:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x1 # feedback div2: / 2
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 10:
+ fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
+ fbdiv1_cntrl = 0x0 # feedback div2: / 1
+ ref_cntrl = 0x1 # input divider: / 2
+ elif depth == 12:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x2 # feedback div2: / 3
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 16:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x3 # feedback div2: / 4
+ ref_cntrl = 0x0 # input divider: / 1
+
+ elif n_cntrl == 0x0:
+ if depth == 8:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x0 # feedback div2: / 1
+ ref_cntrl = 0x0 # input divider: / 1
+ elif depth == 10:
+ fbdiv2_cntrl = 0x5 # feedback div1: / 5 (no +1)
+ fbdiv1_cntrl = 0x0 # feedback div2: / 1
+ ref_cntrl = 0x3 # input divider: / 4
+ elif depth == 12:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x2 # feedback div2: / 3
+ ref_cntrl = 0x1 # input divider: / 2
+ elif depth == 16:
+ fbdiv2_cntrl = 0x1 # feedback div1: / 1 (no +1)
+ fbdiv1_cntrl = 0x1 # feedback div2: / 2
+ ref_cntrl = 0x0 # input divider: / 1
+
+ # Double check with math; this formula derived from the table.
+ total_div = (fbdiv2_cntrl * (fbdiv1_cntrl + 1) * (1 << (3 - n_cntrl)) /
+ (ref_cntrl + 1))
+ assert depth == total_div, \
+ "Error with rate=%d, tmds=%d, depth=%d, n_cntrl=%d, pixel_rep=%d" % (
+ rate, tmds, depth, n_cntrl, pixel_rep)
+
+ # Could be done by math, but this makes it more obvious I think...
+ if n_cntrl == 3:
+ gmp_cntrl = 0
+ elif n_cntrl == 2:
+ gmp_cntrl = 1
+ elif n_cntrl == 1:
+ gmp_cntrl = 2
+ elif n_cntrl == 0:
+ gmp_cntrl = 3
+
+ return ((n_cntrl << 0) |
+ (ref_cntrl << 2) |
+ (fbdiv1_cntrl << 4) |
+ (fbdiv2_cntrl << 6) |
+ (opmode << 9) |
+ (tmdsmhl_cntrl << 11) |
+ (prep_div << 13),
+ gmp_cntrl)
+
+def do_mpll_loop():
+ mpll_cfg_table = {}
+ last_mpll_cfg = None
+ last_rate = None
+
+ for rate in xrange(13500000, 600001000, 1000):
+ for8bpp = make_mpll(rate, 8)
+ for10bpp = make_mpll(rate, 10)
+ for12bpp = make_mpll(rate, 12)
+
+ mpll_cfg = (for8bpp, for10bpp, for12bpp)
+ if (mpll_cfg != last_mpll_cfg) and (last_rate is not None):
+ mpll_cfg_table[last_rate] = last_mpll_cfg
+
+ last_rate = rate
+ last_mpll_cfg = mpll_cfg
+
+ mpll_cfg_table[last_rate] = last_mpll_cfg
+
+ print "\t",
+ for rate in sorted(mpll_cfg_table.keys()):
+ print ("{\n"
+ "\t\t%d, {\n"
+ "\t\t\t{ %#06x, %#06x },\n"
+ "\t\t\t{ %#06x, %#06x },\n"
+ "\t\t\t{ %#06x, %#06x },\n"
+ "\t\t},\n"
+ "\t}, ") % (
+ rate,
+ mpll_cfg_table[rate][0][0], mpll_cfg_table[rate][0][1],
+ mpll_cfg_table[rate][1][0], mpll_cfg_table[rate][1][1],
+ mpll_cfg_table[rate][2][0], mpll_cfg_table[rate][2][1]),
+ print
+
+def CLK_SLOP(clk): return ((clk) / 1000)
+def CLK_PLUS_SLOP(clk): return ((clk) + CLK_SLOP(clk))
+def CLK_MINUS_SLOP(clk): return ((clk) - CLK_SLOP(clk))
+
+def make_cur_ctr(rate, depth, pixel_rep=0):
+ assert pixel_rep == 0, "Untested with non-zero pixel rep and probably wrong"
+
+ tmds = (rate * depth) / 8. * (pixel_rep + 1)
+
+ adjust_for_jittery_pll = True
+
+ # If the PIXEL clock (not the TMDS rate) is using the special 594 PLL
+ # and is slow enough, we can use normal rates...
+ if ((CLK_MINUS_SLOP(74250000) <= rate <= CLK_PLUS_SLOP(74250000)) or
+ (CLK_MINUS_SLOP(148500000) <= rate <= CLK_PLUS_SLOP(148500000))):
+ adjust_for_jittery_pll = False
+
+ # If rate is slow enough then our jitter isn't a huge issue.
+ # ...allowable clock jitter is 362.3 or higher and we're OK there w/ plenty of
+ # margin as long as we're careful about our PLL settings.
+ if rate <= 79000000:
+ adjust_for_jittery_pll = False
+
+ if not adjust_for_jittery_pll:
+ # This is as documented
+ if tmds <= RATE1: # 46000000
+ return 0x18
+ elif tmds <= RATE2: # 92000000
+ return 0x28
+
+ # I have no idea why the below is true, but it is the simplest rule I could
+ # come up with that matched the tables...
+ if depth == 8:
+ if tmds <= 340000000:
+ # HDMI 1.4
+ return 0x38
+ # HDMI 2.0
+ return 0x18
+ elif depth == 16:
+ if tmds < 576000000:
+ return 0x38
+ return 0x28
+ else:
+ return 0x38
+
+ # The output of rk3288 PLL is the source of the HDMI's MPLL. Apparently
+ # the rk3288 PLL is too jittery. We can lower the PLL bandwidth of MPLL
+ # to compensate.
+ #
+ # Where possible, we try to use the MPLL bandwidth suggested by Synopsis
+ # and we just use lower bandwidth when testing has shown that it's needed.
+ # We try to stick to 0x28 and 0x18 since those numbers are present in
+ # Synopsis tables. We go down to 0x08 if needed and finally to 0x00.
+
+ if rate <= 79000000:
+ # Supposed to be 0x28 here, but we'll do 0x18 to reduce jitter
+ return 0x18
+ elif rate <= 118000000:
+ # Supposed to be 0x28/0x38 here, but we'll do 0x08 to reduce jitter
+ return 0x08
+ # Any higher clock rates go to bandwidth = 0
+ return 0
+
+def do_curr_ctrl_loop():
+ cur_ctrl_table = {}
+ last_cur_ctrl = None
+ last_rate = None
+
+ for rate in xrange(13500000, 600001000, 1000):
+ for8bpp = make_cur_ctr(rate, 8)
+ for10bpp = make_cur_ctr(rate, 10)
+ for12bpp = make_cur_ctr(rate, 12)
+
+ cur_ctrl = (for8bpp, for10bpp, for12bpp)
+ if (cur_ctrl != last_cur_ctrl) and (last_rate is not None):
+ cur_ctrl_table[last_rate] = last_cur_ctrl
+
+ last_rate = rate
+ last_cur_ctrl = cur_ctrl
+
+ cur_ctrl_table[last_rate] = last_cur_ctrl
+
+ print "\t",
+ for rate in sorted(cur_ctrl_table.keys()):
+ print ("{\n"
+ "\t\t%d, { %#06x, %#06x, %#06x },\n"
+ "\t}, ") % (
+ rate,
+ cur_ctrl_table[rate][0],
+ cur_ctrl_table[rate][1],
+ cur_ctrl_table[rate][2]),
+ print
+
+
+
+# From HDMI spec
+VPH_RXTERM = 3.3
+RXTERM = 50
+
+def get_phy_preemphasis(symon, traon, trbon):
+ if (symon, traon, trbon) == (0, 0, 0):
+ assert False, "Not valid?"
+ elif (symon, traon, trbon) == (1, 0, 0):
+ preemph = 0.00
+ elif (symon, traon, trbon) == (1, 0, 1):
+ # Numbers match examples better if I assume .25 / 3 rather than .08
+ preemph = 0.25 / 3
+ elif (symon, traon, trbon) == (1, 1, 0):
+ # Numbers match examples better if I assume .50 / 3 rather than .17
+ preemph = 0.50 / 3
+ elif (symon, traon, trbon) == (1, 1, 1):
+ preemph = 0.25
+ else:
+ assert False, "Not valid"
+
+ return preemph
+
+def phy_lvl_to_voltages(lvl, preemph, rterm):
+ v_lo = VPH_RXTERM - (.772 - 0.01405 * lvl)
+ v_swing = ((VPH_RXTERM - v_lo) * (1 - preemph) /
+ (1 + (RXTERM * (1 + preemph)) / (2 * rterm)))
+ v_hi = v_lo + v_swing
+
+ return v_lo, v_swing, v_hi
+
+def print_phy_config(symbol, term, vlev):
+ ck_symon = bool(symbol & (1 << 0))
+ tx_trbon = bool(symbol & (1 << 1))
+ tx_traon = bool(symbol & (1 << 2))
+ tx_symon = bool(symbol & (1 << 3))
+
+ slopeboost = {
+ 0: "no slope boost",
+ 1: " 5-10% decrease on TMDS rise/fall times",
+ 2: "10-20% decrease on TMDS rise/fall times",
+ 3: "20-35% decrease on TMDS rise/fall times",
+ }[(symbol >> 4) & 0x3]
+
+ override = bool(symbol & (1 << 15))
+
+ rterm = (50, 57.14, 66.67, 80, 100, 133, 200)[term]
+
+ sup_ck_lvl = (vlev >> 0) & 0x1f
+ sup_tx_lvl = (vlev >> 5) & 0x1f
+
+ preemph = get_phy_preemphasis(tx_symon, tx_traon, tx_trbon)
+
+ print "symbol=%#06x, term=%#06x, vlev=%#06x" % (symbol, term, vlev)
+ for name, lvl in [("ck", sup_ck_lvl), ("tx", sup_tx_lvl)]:
+ v_lo, v_swing, v_hi = phy_lvl_to_voltages(lvl, preemph, rterm)
+ print " %s: lvl = %2d, term=%3d, vlo = %.2f, vhi=%.2f, vswing = %.2f, %s" % (
+ name, lvl, rterm, v_lo, v_hi, v_swing, slopeboost)
+
+#def calc_ideal_phy_lvl(swing_mv, preemph, rterm):
+ #"""Get the ideal "lvl" for the given swing, preemph, and termination.
+
+ #This might not be integral, but and might not fit the 0-31 range.
+ #"""
+ #v_lo = (VPH_RXTERM -
+ #v_swing / (1 - preemph) -
+ #(v_swing * RXTERM * (1 + preemph)) / (2 * rterm * (1 - preemph)))
+ #lvl = (.772 - (VPH_RXTERM - v_lo)) / 0.01405
+
+ #return lvl
+
+def do_phy_config_list(rate=16500000):
+ # From HDMI spec
+ VPH_RXTERM = 3.3
+ RXTERM = 50
+
+ # Set to True to print even things that don't meet requirements.
+ print_invalid = False
+
+ # Totally a guess based on what's in IMX6DQRM
+ if rate <= 165000000:
+ symon = 1 # tx_symon
+ traon = 0 # tx_traon
+ trbon = 0 # tx_trbon
+ else:
+ symon = 1 # tx_symon
+ traon = 0 # tx_traon
+ trbon = 1 # tx_trbon
+
+ print "Guessing symon, traon, trbon based on rate: (%d, %d, %d)" % (
+ symon, traon, trbon)
+
+ preemph = get_phy_preemphasis(symon, traon, trbon)
+
+ # Notes:
+ # - swing needs to be between .4 and .6
+ # - If <= 165MHz, vhi is (VPH_RXTERM + .01) thru (VPH_RXTERM - .01)
+ # - If > 165MHz, vhi is (VPH_RXTERM + .01) thru (VPH_RXTERM - .2)
+ # - If <= 165MHz, vlo is (VPH_RXTERM - .4) thru (VPH_RXTERM - .6)
+ # - If > 165MHz, vlo is (VPH_RXTERM - .4) thru (VPH_RXTERM - .7)
+ #
+ # TODO: I'm not sure we can actually reach vhi of 3.3 +/- .01
+ # TODO: How do we pick amongst all of these?
+
+ if rate <= 165000000:
+ v_hi_min = 3.19 # Should be (VPH_RXTERM - .01), but not possible?
+ v_hi_max = (VPH_RXTERM + .01)
+ v_lo_min = (VPH_RXTERM - .6)
+ v_lo_max = (VPH_RXTERM - .4)
+ else:
+ v_hi_min = (VPH_RXTERM - .2)
+ v_hi_max = (VPH_RXTERM + .01)
+ v_lo_min = (VPH_RXTERM - .7)
+ v_lo_max = (VPH_RXTERM - .4)
+
+ for lvl in xrange(0, 31):
+ for rterm in (50, 57.14, 66.67, 80, 100, 133, 200):
+ v_lo, v_swing, v_hi = phy_lvl_to_voltages(lvl, preemph, rterm)
+
+ if (print_invalid or
+ ((.4 <= v_swing <= .6) and
+ (v_hi_min <= v_hi <= v_hi_max) and
+ (v_lo_min <= v_lo <= v_lo_max))):
+ print "lvl = %2d, term=%3d, vlo = %.2f, vhi=%.2f, vswing = %.2f" % (
+ lvl, rterm, v_lo, v_hi, v_swing)
+
+# Examples:
+#
+# $ ./rk3288_hdmitables.py mpll
+# $ ./rk3288_hdmitables.py curr
+# $ ./rk3288_hdmitables.py phy_list 165000000
+# $ ./rk3288_hdmitables.py phy_list 600000000
+# $ ./rk3288_hdmitables.py phy_print 0x8009, 0x0005, 0x01ad
+
+def main(todo, *args):
+ if todo == "mpll":
+ do_mpll_loop()
+ elif todo == "curr":
+ do_curr_ctrl_loop()
+ elif todo == "phy_list":
+ (rate,) = args
+ rate = int(rate, 0)
+ do_phy_config_list(rate)
+ elif todo == "phy_print":
+ (symbol, term, vlev) = args
+ symbol = int(symbol.rstrip(","), 0)
+ term = int(term.rstrip(","), 0)
+ vlev = int(vlev.rstrip(","), 0)
+
+ print_phy_config(symbol, term, vlev)
+
+ # These ought to match the tables in the docs. They are close, but not
+ # perfect. ...but my math is definitely right since v_lo doesn't match
+ # and v_lo should be very simple. Even if we try to find more significant
+ # digits for 0.772 and 0.01405 we still can't make it match, so I'm assuming
+ # that they rounded somewhere in their math...
+
+ #print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(19, get_phy_preemphasis(1, 0, 0), 100)
+ #print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(10, get_phy_preemphasis(1, 0, 0), 100)
+ #print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages( 6, get_phy_preemphasis(1, 0, 1), 100)
+
+ #print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(21, get_phy_preemphasis(1, 0, 0), 133)
+ #print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages(13, get_phy_preemphasis(1, 0, 0), 133)
+ #print "%.3f, %.3f, %.3f" % phy_lvl_to_voltages( 8, get_phy_preemphasis(1, 0, 1), 133)
+
+
+if __name__ == '__main__':
+ main(*sys.argv[1:])
diff --git a/drm-tests/swrast_test.c b/drm-tests/swrast_test.c
new file mode 100644
index 0000000..9cce1ad
--- /dev/null
+++ b/drm-tests/swrast_test.c
@@ -0,0 +1,323 @@
+/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "bs_drm.h"
+
+const char *get_gl_error()
+{
+ switch (glGetError()) {
+ case GL_NO_ERROR:
+ return "No error has been recorded.";
+ case GL_INVALID_ENUM:
+ return "An unacceptable value is specified for an enumerated argument. The "
+ "offending command is ignored and has no other side effect than to "
+ "set the error flag.";
+ case GL_INVALID_VALUE:
+ return "A numeric argument is out of range. The offending command is "
+ "ignored and has no other side effect than to set the error flag.";
+ case GL_INVALID_OPERATION:
+ return "The specified operation is not allowed in the current state. The "
+ "offending command is ignored and has no other side effect than to "
+ "set the error flag.";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "The command is trying to render to or read from the framebuffer "
+ "while the currently bound framebuffer is not framebuffer complete "
+ "(i.e. the return value from glCheckFramebufferStatus is not "
+ "GL_FRAMEBUFFER_COMPLETE). The offending command is ignored and has "
+ "no other side effect than to set the error flag.";
+ case GL_OUT_OF_MEMORY:
+ return "There is not enough memory left to execute the command. The state "
+ "of the GL is undefined, except for the state of the error flags, "
+ "after this error is recorded.";
+ default:
+ return "Unknown error";
+ }
+}
+
+const char *get_egl_error()
+{
+ switch (eglGetError()) {
+ case EGL_SUCCESS:
+ return "The last function succeeded without error.";
+ case EGL_NOT_INITIALIZED:
+ return "EGL is not initialized, or could not be initialized, for the "
+ "specified EGL display connection.";
+ case EGL_BAD_ACCESS:
+ return "EGL cannot access a requested resource (for example a context is "
+ "bound in another thread).";
+ case EGL_BAD_ALLOC:
+ return "EGL failed to allocate resources for the requested operation.";
+ case EGL_BAD_ATTRIBUTE:
+ return "An unrecognized attribute or attribute value was passed in the "
+ "attribute list.";
+ case EGL_BAD_CONTEXT:
+ return "An EGLContext argument does not name a valid EGL rendering "
+ "context.";
+ case EGL_BAD_CONFIG:
+ return "An EGLConfig argument does not name a valid EGL frame buffer "
+ "configuration.";
+ case EGL_BAD_CURRENT_SURFACE:
+ return "The current surface of the calling thread is a window, pixel "
+ "buffer or pixmap that is no longer valid.";
+ case EGL_BAD_DISPLAY:
+ return "An EGLDisplay argument does not name a valid EGL display "
+ "connection.";
+ case EGL_BAD_SURFACE:
+ return "An EGLSurface argument does not name a valid surface (window, "
+ "pixel buffer or pixmap) configured for GL rendering.";
+ case EGL_BAD_MATCH:
+ return "Arguments are inconsistent (for example, a valid context requires "
+ "buffers not supplied by a valid surface).";
+ case EGL_BAD_PARAMETER:
+ return "One or more argument values are invalid.";
+ case EGL_BAD_NATIVE_PIXMAP:
+ return "A NativePixmapType argument does not refer to a valid native "
+ "pixmap.";
+ case EGL_BAD_NATIVE_WINDOW:
+ return "A NativeWindowType argument does not refer to a valid native "
+ "window.";
+ case EGL_CONTEXT_LOST:
+ return "A power management event has occurred. The application must "
+ "destroy all contexts and reinitialise OpenGL ES state and objects "
+ "to continue rendering.";
+ default:
+ return "Unknown error";
+ }
+}
+
+struct context {
+ unsigned width;
+ unsigned height;
+ EGLDisplay egl_display;
+ EGLContext egl_ctx;
+
+ unsigned gl_fb;
+ unsigned gl_rb;
+};
+
+float f(int i)
+{
+ int a = i % 40;
+ int b = (i / 40) % 6;
+ switch (b) {
+ case 0:
+ case 1:
+ return 0.0f;
+ case 3:
+ case 4:
+ return 1.0f;
+ case 2:
+ return (a / 40.0f);
+ case 5:
+ return 1.0f - (a / 40.0f);
+ default:
+ return 0.0f;
+ }
+}
+
+void draw(struct context *ctx)
+{
+ int i;
+ const GLchar *vertexShaderStr =
+ "attribute vec4 vPosition;\n"
+ "attribute vec4 vColor;\n"
+ "varying vec4 vFillColor;\n"
+ "void main() {\n"
+ " gl_Position = vPosition;\n"
+ " vFillColor = vColor;\n"
+ "}\n";
+ const GLchar *fragmentShaderStr =
+ "precision mediump float;\n"
+ "varying vec4 vFillColor;\n"
+ "void main() {\n"
+ " gl_FragColor = vFillColor;\n"
+ "}\n";
+ GLint vertexShader, fragmentShader, program, status;
+
+ vertexShader = glCreateShader(GL_VERTEX_SHADER);
+ if (!vertexShader) {
+ fprintf(stderr, "Failed to create vertex shader. Error=0x%x\n", glGetError());
+ return;
+ }
+ glShaderSource(vertexShader, 1, &vertexShaderStr, NULL);
+ glCompileShader(vertexShader);
+ glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ fprintf(stderr, "Failed to compile vertex shader. Error=0x%x\n", glGetError());
+ return;
+ }
+
+ fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+ if (!fragmentShader) {
+ fprintf(stderr, "Failed to create fragment shader. Error=0x%x\n", glGetError());
+ return;
+ }
+ glShaderSource(fragmentShader, 1, &fragmentShaderStr, NULL);
+ glCompileShader(fragmentShader);
+ glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ fprintf(stderr, "Failed to compile fragment shader. Error=0x%x\n", glGetError());
+ return;
+ }
+
+ program = glCreateProgram();
+ if (!program) {
+ fprintf(stderr, "Failed to create program.\n");
+ return;
+ }
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glBindAttribLocation(program, 0, "vPosition");
+ glBindAttribLocation(program, 1, "vColor");
+ glLinkProgram(program);
+ glGetShaderiv(program, GL_LINK_STATUS, &status);
+ if (!status) {
+ fprintf(stderr, "Failed to link program.\n");
+ return;
+ }
+
+ glViewport(0, 0, (GLint)ctx->width, (GLint)ctx->height);
+
+ for (i = 0; i <= 500; i++) {
+ GLfloat verts[] = { 0.0f, -0.5f, 0.0f, -0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.0f };
+ GLfloat colors[] = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f };
+
+ glClearColor(f(i), f(i + 80), f(i + 160), 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glUseProgram(program);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, verts);
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, colors);
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ usleep(1e6 / 120); /* 120 Hz */
+ glFinish();
+
+ unsigned char pixels[4];
+ glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+ printf("color = %hhu %hhu %hhu %hhu\n", pixels[0], pixels[1], pixels[2], pixels[3]);
+ }
+
+ glDeleteProgram(program);
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 0;
+ struct context ctx;
+ EGLint egl_major, egl_minor;
+ const char *extensions;
+ EGLint num_configs;
+ EGLConfig egl_config;
+
+ ctx.width = 800;
+ ctx.height = 600;
+
+ const EGLint config_attribs[] = { EGL_RED_SIZE,
+ 1,
+ EGL_GREEN_SIZE,
+ 1,
+ EGL_BLUE_SIZE,
+ 1,
+ EGL_DEPTH_SIZE,
+ 1,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_SURFACE_TYPE,
+ EGL_DONT_CARE,
+ EGL_NONE };
+ const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+
+ ctx.egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (ctx.egl_display == EGL_NO_DISPLAY) {
+ fprintf(stderr, "failed to get egl display\n");
+ ret = 1;
+ goto fail;
+ }
+
+ if (!eglInitialize(ctx.egl_display, &egl_major, &egl_minor)) {
+ fprintf(stderr, "failed to initialize egl: %s\n", get_egl_error());
+ ret = 1;
+ goto terminate_display;
+ }
+
+ printf("EGL %d.%d\n", egl_major, egl_minor);
+ printf("EGL %s\n", eglQueryString(ctx.egl_display, EGL_VERSION));
+
+ extensions = eglQueryString(ctx.egl_display, EGL_EXTENSIONS);
+ printf("EGL Extensions: %s\n", extensions);
+
+ if (!eglChooseConfig(ctx.egl_display, config_attribs, NULL, 0, &num_configs)) {
+ fprintf(stderr, "eglChooseConfig() failed with error: %x\n", eglGetError());
+ goto terminate_display;
+ }
+ if (!eglChooseConfig(ctx.egl_display, config_attribs, &egl_config, 1, &num_configs)) {
+ fprintf(stderr, "eglChooseConfig() failed with error: %x\n", eglGetError());
+ goto terminate_display;
+ }
+
+ if (!eglBindAPI(EGL_OPENGL_ES_API)) {
+ fprintf(stderr, "failed to bind OpenGL ES: %s\n", get_egl_error());
+ ret = 1;
+ goto terminate_display;
+ }
+
+ if (bs_egl_has_extension("EGL_KHR_no_config_context", extensions)) {
+ ctx.egl_ctx =
+ eglCreateContext(ctx.egl_display, NULL /* No Config */,
+ EGL_NO_CONTEXT /* No shared context */, context_attribs);
+ } else {
+ ctx.egl_ctx =
+ eglCreateContext(ctx.egl_display, egl_config,
+ EGL_NO_CONTEXT /* No shared context */, context_attribs);
+ }
+
+ if (ctx.egl_ctx == EGL_NO_CONTEXT) {
+ fprintf(stderr, "failed to create OpenGL ES Context: %s\n", get_egl_error());
+ ret = 1;
+ goto terminate_display;
+ }
+
+ if (!eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE /* No default draw surface */,
+ EGL_NO_SURFACE /* No default draw read */, ctx.egl_ctx)) {
+ fprintf(stderr, "failed to make the OpenGL ES Context current: %s\n",
+ get_egl_error());
+ ret = 1;
+ goto destroy_context;
+ }
+
+ printf("GL extensions: %s\n", glGetString(GL_EXTENSIONS));
+
+ glGenFramebuffers(1, &ctx.gl_fb);
+ glBindFramebuffer(GL_FRAMEBUFFER, ctx.gl_fb);
+ glGenRenderbuffers(1, &ctx.gl_rb);
+ glBindRenderbuffer(GL_RENDERBUFFER, ctx.gl_rb);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, ctx.width, ctx.height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ctx.gl_rb);
+
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ fprintf(stderr, "failed to create framebuffer: %s\n", get_gl_error());
+ ret = 1;
+ goto delete_gl_buffers;
+ }
+
+ draw(&ctx);
+
+delete_gl_buffers:
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glDeleteFramebuffers(1, &ctx.gl_fb);
+ glDeleteRenderbuffers(1, &ctx.gl_rb);
+destroy_context:
+ eglMakeCurrent(ctx.egl_display, NULL, NULL, NULL);
+ eglDestroyContext(ctx.egl_display, ctx.egl_ctx);
+terminate_display:
+ eglTerminate(ctx.egl_display);
+fail:
+ return ret;
+}
diff --git a/drm-tests/vgem_test.c b/drm-tests/vgem_test.c
new file mode 100644
index 0000000..5e85751
--- /dev/null
+++ b/drm-tests/vgem_test.c
@@ -0,0 +1,381 @@
+/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This is a test meant to exercise the VGEM DRM kernel module's PRIME
+ * import/export functions. It will create a gem buffer object, mmap, write, and
+ * then verify it. Then the test will repeat that with the same gem buffer, but
+ * exported and then imported. Finally, a new gem buffer object is made in a
+ * different driver which exports into VGEM and the mmap, write, verify sequence
+ * is repeated on that.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xf86drm.h>
+
+#include "bs_drm.h"
+
+#define WITH_COLOR 1
+
+#if WITH_COLOR
+#define ANSI_COLOR_RED "\x1b[31m"
+#define ANSI_COLOR_GREEN "\x1b[32m"
+#define ANSI_COLOR_YELLOW "\x1b[33m"
+#define ANSI_COLOR_BLUE "\x1b[34m"
+#define ANSI_COLOR_MAGENTA "\x1b[35m"
+#define ANSI_COLOR_CYAN "\x1b[36m"
+#define ANSI_COLOR_RESET "\x1b[0m"
+#else
+#define ANSI_COLOR_RED ""
+#define ANSI_COLOR_GREEN ""
+#define ANSI_COLOR_YELLOW ""
+#define ANSI_COLOR_BLUE ""
+#define ANSI_COLOR_MAGENTA ""
+#define ANSI_COLOR_CYAN ""
+#define ANSI_COLOR_RESET ""
+#endif
+
+#define FAIL_COLOR ANSI_COLOR_RED "failed" ANSI_COLOR_RESET
+#define SUCCESS_COLOR(x) ANSI_COLOR_GREEN x ANSI_COLOR_RESET
+
+const uint32_t g_bo_pattern = 0xdeadbeef;
+const char g_dev_card_path_format[] = "/dev/dri/card%d";
+
+int create_vgem_bo(int fd, size_t size, uint32_t *handle)
+{
+ struct drm_mode_create_dumb create;
+ int ret;
+
+ memset(&create, 0, sizeof(create));
+ create.height = size;
+ create.width = 1;
+ create.bpp = 8;
+
+ ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+ if (ret)
+ return ret;
+
+ assert(create.size >= size);
+
+ *handle = create.handle;
+
+ return 0;
+}
+
+void *mmap_dumb_bo(int fd, int handle, size_t size)
+{
+ struct drm_mode_map_dumb mmap_arg;
+ void *ptr;
+ int ret;
+
+ memset(&mmap_arg, 0, sizeof(mmap_arg));
+
+ mmap_arg.handle = handle;
+
+ ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mmap_arg);
+ assert(ret == 0);
+ assert(mmap_arg.offset != 0);
+
+ ptr = mmap(NULL, size, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, mmap_arg.offset);
+
+ return ptr;
+}
+
+void write_pattern(volatile uint32_t *bo_ptr, size_t bo_size)
+{
+ volatile uint32_t *ptr;
+ for (ptr = bo_ptr; ptr < bo_ptr + (bo_size / sizeof(*bo_ptr)); ptr++) {
+ *ptr = g_bo_pattern;
+ }
+}
+
+bool verify_pattern(volatile uint32_t *bo_ptr, size_t bo_size)
+{
+ volatile uint32_t *ptr;
+ for (ptr = bo_ptr; ptr < bo_ptr + (bo_size / sizeof(*bo_ptr)); ptr++) {
+ if (*ptr != g_bo_pattern) {
+ fprintf(stderr,
+ "buffer object verify " FAIL_COLOR " at offset %td = 0x%X\n",
+ (void *)ptr - (void *)bo_ptr, *ptr);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static const char help_text[] =
+ "Usage: %s [OPTIONS]\n"
+ " -h Print this help.\n"
+ " -d [DEVICE] Open the given vgem device file (defaults to trying all cards under "
+ "/dev/dri/).\n"
+ " -c [SIZE] Create a buffer objects of the given size in bytes.\n";
+
+void print_help(const char *argv0)
+{
+ printf(help_text, argv0);
+}
+
+static const char optstr[] = "hd:c:";
+
+int main(int argc, char *argv[])
+{
+ int ret = 0;
+ const char *device_file = NULL;
+ long bo_size = 65536;
+ bool test_pattern = true;
+ bool export_to_fd = true;
+ bool import_to_handle = true;
+ bool import_foreign = true;
+
+ int c;
+ while ((c = getopt(argc, argv, optstr)) != -1) {
+ switch (c) {
+ case 'h':
+ print_help(argv[0]);
+ exit(0);
+ break;
+ case 'd':
+ device_file = optarg;
+ break;
+ case 'c':
+ bo_size = atol(optarg);
+ break;
+ default:
+ print_help(argv[0]);
+ return 1;
+ }
+ }
+
+ int vgem_fd = -1;
+ if (device_file)
+ vgem_fd = open(device_file, O_RDWR);
+ else
+ vgem_fd = bs_drm_open_vgem();
+
+ if (vgem_fd < 0) {
+ perror(FAIL_COLOR " to open vgem device");
+ return 1;
+ }
+
+ printf(SUCCESS_COLOR("opened") " vgem device\n");
+
+ uint32_t bo_handle;
+ if (bo_size > 0) {
+ ret = create_vgem_bo(vgem_fd, bo_size, &bo_handle);
+ if (ret) {
+ fprintf(stderr, FAIL_COLOR " to create bo: %d\n", ret);
+ ret = 1;
+ goto close_vgem_fd;
+ }
+ printf(SUCCESS_COLOR("created") " vgem buffer object, handle = %u\n", bo_handle);
+ }
+
+ if (test_pattern) {
+ if (bo_size <= 0) {
+ fprintf(stderr,
+ "buffer object must be created before it can be written to\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ volatile uint32_t *bo_ptr = mmap_dumb_bo(vgem_fd, bo_handle, bo_size);
+ if (bo_ptr == MAP_FAILED) {
+ fprintf(stderr, FAIL_COLOR " to map buffer object\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("mapped") " vgem buffer object\n");
+
+ write_pattern(bo_ptr, bo_size);
+
+ assert(!munmap((uint32_t *)bo_ptr, bo_size));
+
+ printf(SUCCESS_COLOR("wrote") " to vgem buffer object\n");
+
+ bo_ptr = mmap_dumb_bo(vgem_fd, bo_handle, bo_size);
+
+ if (!verify_pattern(bo_ptr, bo_size)) {
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ assert(!munmap((uint32_t *)bo_ptr, bo_size));
+
+ printf(SUCCESS_COLOR("verified") " vgem buffer object writes\n");
+ }
+
+ int prime_fd;
+ if (export_to_fd) {
+ if (bo_size <= 0) {
+ fprintf(stderr, "created buffer object required to perform export\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ ret = drmPrimeHandleToFD(vgem_fd, bo_handle, O_CLOEXEC, &prime_fd);
+ if (ret) {
+ fprintf(stderr, FAIL_COLOR " to export buffer object: %d\n", ret);
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("exported") " vgem buffer object, fd = %d\n", prime_fd);
+ }
+
+ uint32_t imported_handle;
+ if (import_to_handle) {
+ if (bo_size <= 0 || !export_to_fd) {
+ fprintf(stderr, "exported buffer object required to perform import\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ ret = drmPrimeFDToHandle(vgem_fd, prime_fd, &imported_handle);
+ if (ret) {
+ fprintf(stderr, FAIL_COLOR " to import buffer object: %d\n", ret);
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("imported") " to vgem buffer object, handle = %d\n",
+ imported_handle);
+
+ volatile uint32_t *bo_ptr = mmap_dumb_bo(vgem_fd, imported_handle, bo_size);
+ if (bo_ptr == MAP_FAILED) {
+ fprintf(stderr, FAIL_COLOR " to map imported buffer object\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("mapped") " imported vgem buffer object\n");
+
+ if (!verify_pattern(bo_ptr, bo_size)) {
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ assert(!munmap((uint32_t *)bo_ptr, bo_size));
+
+ printf(SUCCESS_COLOR("verified") " imported vgem buffer object writes\n");
+ }
+
+ if (import_foreign) {
+ int dumb_fd = open("/dev/dri/card0", O_RDWR);
+ if (dumb_fd == -1) {
+ perror(FAIL_COLOR " to open non-vgem card\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("opened") " non-vgem device\n");
+
+ struct drm_mode_create_dumb create;
+
+ memset(&create, 0, sizeof(create));
+ create.width = 640;
+ create.height = 480;
+ create.bpp = 32;
+
+ ret = drmIoctl(dumb_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+ if (ret) {
+ fprintf(stderr, FAIL_COLOR " to create non-vgem buffer object: %d\n", ret);
+ ret = 1;
+ goto close_dumb_fd;
+ }
+
+ printf(SUCCESS_COLOR("created") " non-vgem buffer object, handle = %u\n",
+ create.handle);
+
+ assert(create.size == create.width * create.height * 4);
+
+ int foreign_prime_fd;
+ ret = drmPrimeHandleToFD(dumb_fd, create.handle, O_CLOEXEC, &foreign_prime_fd);
+ if (ret) {
+ fprintf(stderr, FAIL_COLOR " to export non-vgem buffer object: %d\n", ret);
+ ret = 1;
+ goto close_dumb_fd;
+ }
+
+ printf(SUCCESS_COLOR("exported") " non-vgem buffer object, fd = %d\n",
+ foreign_prime_fd);
+
+ uint32_t foreign_imported_handle;
+ ret = drmPrimeFDToHandle(vgem_fd, foreign_prime_fd, &foreign_imported_handle);
+ if (ret) {
+ fprintf(stderr, FAIL_COLOR " to import non-vgem buffer object: %d\n", ret);
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("imported") " to vgem buffer object, handle = %d\n",
+ foreign_imported_handle);
+
+ volatile uint32_t *bo_ptr =
+ mmap_dumb_bo(vgem_fd, foreign_imported_handle, create.size);
+ if (bo_ptr == MAP_FAILED) {
+ fprintf(stderr, FAIL_COLOR " to map imported non-vgem buffer object\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("mapped") " imported non-vgem vgem buffer object\n");
+
+ write_pattern(bo_ptr, create.size);
+
+ assert(!munmap((uint32_t *)bo_ptr, create.size));
+
+ printf(SUCCESS_COLOR("wrote") " to non-vgem buffer object\n");
+
+ bo_ptr = mmap_dumb_bo(vgem_fd, foreign_imported_handle, create.size);
+ if (bo_ptr == MAP_FAILED) {
+ fprintf(stderr, FAIL_COLOR " to map imported non-vgem buffer object\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ if (!verify_pattern(bo_ptr, create.size)) {
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ assert(!munmap((uint32_t *)bo_ptr, create.size));
+
+ printf(SUCCESS_COLOR("verified") " imported non-vgem buffer object writes\n");
+
+ bo_ptr = mmap_dumb_bo(dumb_fd, create.handle, create.size);
+ if (bo_ptr == MAP_FAILED) {
+ fprintf(stderr, FAIL_COLOR " to map non-vgem buffer object\n");
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ if (!verify_pattern(bo_ptr, create.size)) {
+ ret = 1;
+ goto close_vgem_fd;
+ }
+
+ printf(SUCCESS_COLOR("verified") " non-vgem buffer object writes\n");
+
+ assert(!munmap((uint32_t *)bo_ptr, create.size));
+
+ close_dumb_fd:
+ close(dumb_fd);
+ }
+
+close_vgem_fd:
+ close(vgem_fd);
+ return ret;
+}
diff --git a/drm-tests/vk_glow.c b/drm-tests/vk_glow.c
new file mode 100644
index 0000000..d2e8a35
--- /dev/null
+++ b/drm-tests/vk_glow.c
@@ -0,0 +1,520 @@
+/*
+ * Copyright 2016 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Intel's Vulkan driver exposes an unregistered extension function,
+// vkCreateDmaBufImageINTEL. There is no extension associated with the
+// function (as of 2016-09-21). The Intel developers added the function during
+// Vulkan's early early days, in Mesa's first Vulkan commit on 2015-05-08, to
+// provide a convenient way to test the driver. The Vulkan API had no extension
+// mechanism yet in that early pre-1.0 timeframe.
+//
+// We use vkCreateDmaBufImageINTEL, despite its ambiguous status, because there
+// does not yet exist a good alternative for importing a dma_buf as a VkImage.
+//
+// The Vulkan validation layer (VK_LAYER_LUNARG_standard_validation) does not
+// understand vkCreateDmaBufImageINTEL. To run with the validation layer,
+// #undef USE_vkCreateDmaBufImageINTEL and rebuild.
+#undef USE_vkCreateDmaBufImageINTEL
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <vulkan/vulkan.h>
+
+#ifdef USE_vkCreateDmaBufImageINTEL
+#include <vulkan/vulkan_intel.h>
+#endif
+
+#include "bs_drm.h"
+
+// Used for double-buffering.
+struct frame {
+ struct gbm_bo *bo;
+ int bo_prime_fd;
+ uint32_t drm_fb_id;
+ VkDeviceMemory vk_memory;
+ VkImage vk_image;
+ VkImageView vk_image_view;
+ VkFramebuffer vk_framebuffer;
+ VkCommandBuffer vk_cmd_buf;
+};
+
+#define check_vk_success(result, vk_func) \
+ __check_vk_success(__FILE__, __LINE__, __func__, (result), (vk_func))
+
+static void __check_vk_success(const char *file, int line, const char *func, VkResult result,
+ const char *vk_func)
+{
+ if (result == VK_SUCCESS)
+ return;
+
+ bs_debug_print("ERROR", func, file, line, "%s failed with VkResult(%d)", vk_func, result);
+ exit(EXIT_FAILURE);
+}
+
+static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec,
+ void *data)
+{
+ bool *waiting_for_flip = data;
+ *waiting_for_flip = false;
+}
+
+// Choose the first physical device. Exit on failure.
+VkPhysicalDevice choose_physical_device(VkInstance inst)
+{
+ uint32_t n_phys_devs;
+ VkResult res;
+
+ res = vkEnumeratePhysicalDevices(inst, &n_phys_devs, NULL);
+ check_vk_success(res, "vkEnumeratePhysicalDevices");
+
+ if (n_phys_devs == 0) {
+ fprintf(stderr, "No available VkPhysicalDevices\n");
+ exit(EXIT_FAILURE);
+ }
+
+ VkPhysicalDevice phys_devs[n_phys_devs];
+ res = vkEnumeratePhysicalDevices(inst, &n_phys_devs, phys_devs);
+ check_vk_success(res, "vkEnumeratePhysicalDevices");
+
+ // Print information about all available devices. This helps debugging
+ // when bringing up Vulkan on a new system.
+ printf("Available VkPhysicalDevices:\n");
+
+ for (uint32_t i = 0; i < n_phys_devs; ++i) {
+ VkPhysicalDeviceProperties props;
+
+ vkGetPhysicalDeviceProperties(phys_devs[i], &props);
+
+ printf(" VkPhysicalDevice %u:\n", i);
+ printf(" apiVersion: %u.%u.%u\n", VK_VERSION_MAJOR(props.apiVersion),
+ VK_VERSION_MINOR(props.apiVersion), VK_VERSION_PATCH(props.apiVersion));
+ printf(" driverVersion: %u\n", props.driverVersion);
+ printf(" vendorID: 0x%x\n", props.vendorID);
+ printf(" deviceID: 0x%x\n", props.deviceID);
+ printf(" deviceName: %s\n", props.deviceName);
+ printf(" pipelineCacheUUID: %x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x\n",
+ props.pipelineCacheUUID[0], props.pipelineCacheUUID[1],
+ props.pipelineCacheUUID[2], props.pipelineCacheUUID[3],
+ props.pipelineCacheUUID[4], props.pipelineCacheUUID[5],
+ props.pipelineCacheUUID[6], props.pipelineCacheUUID[7],
+ props.pipelineCacheUUID[8], props.pipelineCacheUUID[9],
+ props.pipelineCacheUUID[10], props.pipelineCacheUUID[11],
+ props.pipelineCacheUUID[12], props.pipelineCacheUUID[13],
+ props.pipelineCacheUUID[14], props.pipelineCacheUUID[15]);
+ }
+
+ printf("Chose VkPhysicalDevice 0\n");
+ fflush(stdout);
+
+ return phys_devs[0];
+}
+
+// Return the index of a graphics-enabled queue family. Return UINT32_MAX on
+// failure.
+uint32_t choose_gfx_queue_family(VkPhysicalDevice phys_dev)
+{
+ uint32_t family_idx = UINT32_MAX;
+ VkQueueFamilyProperties *props = NULL;
+ uint32_t n_props = 0;
+
+ vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &n_props, NULL);
+
+ props = calloc(sizeof(props[0]), n_props);
+ if (!props) {
+ bs_debug_error("out of memory");
+ exit(EXIT_FAILURE);
+ }
+
+ vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &n_props, props);
+
+ // Choose the first graphics queue.
+ for (uint32_t i = 0; i < n_props; ++i) {
+ if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && props[i].queueCount > 0) {
+ family_idx = i;
+ break;
+ }
+ }
+
+ free(props);
+ return family_idx;
+}
+
+int main(int argc, char **argv)
+{
+ VkInstance inst;
+ VkPhysicalDevice phys_dev;
+ uint32_t gfx_queue_family_idx;
+ VkDevice dev;
+ VkQueue gfx_queue;
+ VkRenderPass pass;
+ VkCommandPool cmd_pool;
+ VkResult res;
+ struct frame frames[2];
+ int err;
+
+ int dev_fd = bs_drm_open_main_display();
+ if (dev_fd < 0) {
+ bs_debug_error("failed to open display device");
+ exit(EXIT_FAILURE);
+ }
+
+ struct gbm_device *gbm = gbm_create_device(dev_fd);
+ if (!gbm) {
+ bs_debug_error("failed to create gbm_device");
+ exit(EXIT_FAILURE);
+ }
+
+ struct bs_drm_pipe pipe = { 0 };
+ if (!bs_drm_pipe_make(dev_fd, &pipe)) {
+ bs_debug_error("failed to make drm pipe");
+ exit(EXIT_FAILURE);
+ }
+
+ drmModeConnector *connector = drmModeGetConnector(dev_fd, pipe.connector_id);
+ if (!connector) {
+ bs_debug_error("drmModeGetConnector failed");
+ exit(EXIT_FAILURE);
+ }
+
+ drmModeModeInfo *mode = &connector->modes[0];
+
+ res = vkCreateInstance(
+ &(VkInstanceCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo =
+ &(VkApplicationInfo){
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .apiVersion = VK_MAKE_VERSION(1, 0, 0),
+ },
+ },
+ /*pAllocator*/ NULL, &inst);
+ check_vk_success(res, "vkCreateInstance");
+
+ phys_dev = choose_physical_device(inst);
+
+ gfx_queue_family_idx = choose_gfx_queue_family(phys_dev);
+ if (gfx_queue_family_idx == UINT32_MAX) {
+ bs_debug_error(
+ "VkPhysicalDevice exposes no VkQueueFamilyProperties "
+ "with graphics");
+ exit(EXIT_FAILURE);
+ }
+
+ res = vkCreateDevice(phys_dev,
+ &(VkDeviceCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount = 1,
+ .pQueueCreateInfos =
+ (VkDeviceQueueCreateInfo[]){
+ {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex = gfx_queue_family_idx,
+ .queueCount = 1,
+ .pQueuePriorities = (float[]){ 1.0f },
+
+ },
+ },
+ },
+ /*pAllocator*/ NULL, &dev);
+ check_vk_success(res, "vkCreateDevice");
+
+#if USE_vkCreateDmaBufImageINTEL
+ PFN_vkCreateDmaBufImageINTEL bs_vkCreateDmaBufImageINTEL =
+ (void *)vkGetDeviceProcAddr(dev, "vkCreateDmaBufImageINTEL");
+ if (bs_vkCreateDmaBufImageINTEL == NULL) {
+ bs_debug_error("vkGetDeviceProcAddr(\"vkCreateDmaBufImageINTEL\') failed");
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ vkGetDeviceQueue(dev, gfx_queue_family_idx, /*queueIndex*/ 0, &gfx_queue);
+
+ res = vkCreateCommandPool(dev,
+ &(VkCommandPoolCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
+ VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = gfx_queue_family_idx,
+ },
+ /*pAllocator*/ NULL, &cmd_pool);
+ check_vk_success(res, "vkCreateCommandPool");
+
+ res = vkCreateRenderPass(
+ dev,
+ &(VkRenderPassCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments =
+ (VkAttachmentDescription[]){
+ {
+ .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ .samples = 1,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout = VK_IMAGE_LAYOUT_GENERAL,
+ },
+ },
+ .subpassCount = 1,
+ .pSubpasses =
+ (VkSubpassDescription[]){
+ {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments =
+ (VkAttachmentReference[]){
+ {
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ },
+ },
+ },
+ },
+ },
+ /*pAllocator*/ NULL, &pass);
+ check_vk_success(res, "vkCreateRenderPass");
+
+ for (int i = 0; i < BS_ARRAY_LEN(frames); ++i) {
+ struct frame *fr = &frames[i];
+
+ fr->bo = gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, GBM_FORMAT_XBGR8888,
+ GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
+ if (fr->bo == NULL) {
+ bs_debug_error("failed to create framebuffer's gbm_bo");
+ return 1;
+ }
+
+ fr->drm_fb_id = bs_drm_fb_create_gbm(fr->bo);
+ if (fr->drm_fb_id == 0) {
+ bs_debug_error("failed to create drm framebuffer id");
+ return 1;
+ }
+
+ fr->bo_prime_fd = gbm_bo_get_fd(fr->bo);
+ if (fr->bo_prime_fd < 0) {
+ bs_debug_error("failed to get prime fd for gbm_bo");
+ return 1;
+ }
+
+#if USE_vkCreateDmaBufImageINTEL
+ res = bs_vkCreateDmaBufImageINTEL(
+ dev,
+ &(VkDmaBufImageCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL,
+ .fd = fr->bo_prime_fd,
+ .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ .extent = (VkExtent3D){ mode->hdisplay, mode->hdisplay, 1 },
+ .strideInBytes = gbm_bo_get_stride(fr->bo),
+ },
+ /*pAllocator*/ NULL, &fr->vk_memory, &fr->vk_image);
+ check_vk_success(res, "vkCreateDmaBufImageINTEL");
+#else
+ res = vkCreateImage(dev,
+ &(VkImageCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ .extent = (VkExtent3D){ mode->hdisplay, mode->hdisplay, 1 },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = 1,
+ .tiling = VK_IMAGE_TILING_LINEAR,
+ .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .queueFamilyIndexCount = 1,
+ .pQueueFamilyIndices = (uint32_t[]){ gfx_queue_family_idx },
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ },
+ /*pAllocator*/ NULL, &fr->vk_image);
+ check_vk_success(res, "vkCreateImage");
+
+ VkMemoryRequirements mem_reqs;
+ vkGetImageMemoryRequirements(dev, fr->vk_image, &mem_reqs);
+
+ res = vkAllocateMemory(dev,
+ &(VkMemoryAllocateInfo){
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = mem_reqs.size,
+
+ // Simply choose the first available memory type.
+ // We need neither performance nor mmap, so all
+ // memory types are equally good.
+ .memoryTypeIndex = ffs(mem_reqs.memoryTypeBits) - 1,
+ },
+ /*pAllocator*/ NULL, &fr->vk_memory);
+ check_vk_success(res, "vkAllocateMemory");
+
+ res = vkBindImageMemory(dev, fr->vk_image, fr->vk_memory,
+ /*memoryOffset*/ 0);
+ check_vk_success(res, "vkBindImageMemory");
+#endif
+
+ res = vkCreateImageView(dev,
+ &(VkImageViewCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = fr->vk_image,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ .components =
+ (VkComponentMapping){
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange =
+ (VkImageSubresourceRange){
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ },
+ /*pAllocator*/ NULL, &fr->vk_image_view);
+ check_vk_success(res, "vkCreateImageView");
+
+ res = vkCreateFramebuffer(dev,
+ &(VkFramebufferCreateInfo){
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = pass,
+ .attachmentCount = 1,
+ .pAttachments = (VkImageView[]){ fr->vk_image_view },
+ .width = mode->hdisplay,
+ .height = mode->vdisplay,
+ .layers = 1,
+ },
+ /*pAllocator*/ NULL, &fr->vk_framebuffer);
+ check_vk_success(res, "vkCreateFramebuffer");
+
+ res = vkAllocateCommandBuffers(
+ dev,
+ &(VkCommandBufferAllocateInfo){
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = cmd_pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1,
+ },
+ &fr->vk_cmd_buf);
+ check_vk_success(res, "vkAllocateCommandBuffers");
+ }
+
+ // We set the screen mode using framebuffer 0. Then the first page flip
+ // waits on framebuffer 1.
+ err = drmModeSetCrtc(dev_fd, pipe.crtc_id, frames[0].drm_fb_id,
+ /*x*/ 0, /*y*/ 0, &pipe.connector_id, /*connector_count*/ 1, mode);
+ if (err) {
+ bs_debug_error("drmModeSetCrtc failed: %d", err);
+ exit(EXIT_FAILURE);
+ }
+
+ // We set an upper bound on the render loop so we can run this in
+ // from a testsuite.
+ for (int i = 1; i < 500; ++i) {
+ struct frame *fr = &frames[i % BS_ARRAY_LEN(frames)];
+
+ // vkBeginCommandBuffer implicity resets the command buffer due
+ // to VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT.
+ res = vkBeginCommandBuffer(fr->vk_cmd_buf,
+ &(VkCommandBufferBeginInfo){
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ });
+ check_vk_success(res, "vkBeginCommandBuffer");
+
+ // Cycle along the circumference of the RGB color wheel.
+ VkClearValue clear_color = {
+ .color =
+ {
+ .float32 =
+ {
+ 0.5f + 0.5f * sinf(2 * M_PI * i / 240.0f),
+ 0.5f + 0.5f * sinf(2 * M_PI * i / 240.0f +
+ (2.0f / 3.0f * M_PI)),
+ 0.5f + 0.5f * sinf(2 * M_PI * i / 240.0f +
+ (4.0f / 3.0f * M_PI)),
+ 1.0f,
+ },
+ },
+ };
+
+ vkCmdBeginRenderPass(fr->vk_cmd_buf,
+ &(VkRenderPassBeginInfo){
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = pass,
+ .framebuffer = fr->vk_framebuffer,
+ .renderArea =
+ (VkRect2D){
+ .offset = { 0, 0 },
+ .extent = { mode->hdisplay, mode->vdisplay },
+ },
+ .clearValueCount = 1,
+ .pClearValues = (VkClearValue[]){ clear_color },
+ },
+ VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdEndRenderPass(fr->vk_cmd_buf);
+
+ res = vkEndCommandBuffer(fr->vk_cmd_buf);
+ check_vk_success(res, "vkEndCommandBuffer");
+
+ res =
+ vkQueueSubmit(gfx_queue,
+ /*submitCount*/ 1,
+ (VkSubmitInfo[]){
+ {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = (VkCommandBuffer[]){ fr->vk_cmd_buf },
+ },
+ },
+ VK_NULL_HANDLE);
+ check_vk_success(res, "vkQueueSubmit");
+
+ res = vkQueueWaitIdle(gfx_queue);
+ check_vk_success(res, "vkQueueWaitIdle");
+
+ bool waiting_for_flip = true;
+ err = drmModePageFlip(dev_fd, pipe.crtc_id, fr->drm_fb_id, DRM_MODE_PAGE_FLIP_EVENT,
+ &waiting_for_flip);
+ if (err) {
+ bs_debug_error("failed page flip: error=%d", err);
+ exit(EXIT_FAILURE);
+ }
+
+ while (waiting_for_flip) {
+ drmEventContext ev_ctx = {
+ .version = DRM_EVENT_CONTEXT_VERSION,
+ .page_flip_handler = page_flip_handler,
+ };
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(dev_fd, &fds);
+
+ int n_fds = select(dev_fd + 1, &fds, NULL, NULL, NULL);
+ if (n_fds < 0) {
+ bs_debug_error("select() failed on page flip: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ } else if (n_fds == 0) {
+ bs_debug_error("select() timeout on page flip");
+ exit(EXIT_FAILURE);
+ }
+
+ err = drmHandleEvent(dev_fd, &ev_ctx);
+ if (err) {
+ bs_debug_error(
+ "drmHandleEvent failed while "
+ "waiting for page flip: error=%d",
+ err);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ return 0;
+}