Browse Source

drm_test: add drm_test package

1. add drm_test to test drm feature reference to dvdhrm drm
2. add modeset-xxxxx test app

Signed-off-by: andy.hu <andy.hu@starfivetech.com>
andy.hu 2 years ago
parent
commit
57fa61bc41

+ 1 - 0
package/starfive/Config.in

@@ -6,3 +6,4 @@ source "package/starfive/sf-omx-il/Config.in"
 source "package/starfive/v4l2_test/Config.in"
 source "package/starfive/pp_test/Config.in"
 source "package/starfive/libcamera-apps/Config.in"
+source "package/starfive/drm_test/Config.in"

+ 10 - 0
package/starfive/drm_test/Config.in

@@ -0,0 +1,10 @@
+comment "drm test package"
+
+config BR2_PACKAGE_DRM_TEST
+	bool "drm_test"
+	depends on BR2_PACKAGE_LIBDRM
+	help
+	  Utility for testing drm working on variable modes. This tool based on
+	  https://github.com/dvdhrm/docs.git and 
+	  https://github.com/hexiaolong2008/sample-code.git,
+	  but provides additional features

+ 31 - 0
package/starfive/drm_test/drm_test.mk

@@ -0,0 +1,31 @@
+################################################################################
+#
+# drmtest
+#
+################################################################################
+DRM_TEST_VERSION:=1.0.0
+DRM_TEST_SITE=$(TOPDIR)/package/starfive/drm_test/src
+DRM_TEST_SITE_METHOD=local
+
+define DRM_TEST_BUILD_CMDS
+	$(TARGET_MAKE_ENV) $(MAKE) -C $(@D) -f $(@D)/Makefile
+	$(TARGET_MAKE_ENV) $(MAKE) -C $(@D)/dvdhrm/ -f $(@D)/dvdhrm/Makefile
+endef
+
+define DRM_TEST_INSTALL_TARGET_CMDS
+	$(INSTALL) -D -m 0755 $(@D)/modeset-single-buffer $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/modeset-double-buffer $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/modeset-page-flip $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/modeset-plane-test $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/modeset-atomic-crtc $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/modeset-atomic-plane $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/modeset-dumb $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/dvdhrm/modeset $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/dvdhrm/modeset-double-buffered $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/dvdhrm/modeset-vsync $(TARGET_DIR)/usr/bin/
+	$(INSTALL) -D -m 0755 $(@D)/dvdhrm/modeset-atomic $(TARGET_DIR)/usr/bin/
+
+endef
+
+DRM_TEST_DEPENDENCIES = libdrm
+$(eval $(generic-package))

+ 26 - 0
package/starfive/drm_test/src/Makefile

@@ -0,0 +1,26 @@
+CROSS_CC_PREFIX = riscv64-buildroot-linux-gnu-
+CC  = $(CROSS_CC_PREFIX)gcc
+CXX = $(CROSS_CC_PREFIX)g++
+LINKER = $(CC)
+AR  = $(CROSS_CC_PREFIX)ar
+
+# FLAGS=-I/data/code/fedoral_513/github/freelight-u-sdk_github_old/work/buildroot_initramfs/staging/usr/include/drm/ -ldrm
+FLAGS = -I$(STAGING_DIR)/usr/include/drm/ -ldrm
+FLAGS += -Wall -O2 -D_FILE_OFFSET_BITS=64
+
+all:
+	@echo STAGING_DIR=$(STAGING_DIR)
+	$(CC) -o modeset-single-buffer modeset-single-buffer.c $(FLAGS)
+	$(CC) -o modeset-double-buffer modeset-double-buffer.c $(FLAGS)
+	$(CC) -o modeset-page-flip modeset-page-flip.c $(FLAGS)
+	$(CC) -o modeset-plane-test modeset-plane-test.c $(FLAGS)
+	$(CC) -o modeset-atomic-crtc modeset-atomic-crtc.c $(FLAGS)
+	$(CC) -o modeset-atomic-plane modeset-atomic-plane.c $(FLAGS)
+	$(CC) -o modeset-dumb modeset-dumb.c $(FLAGS)
+
+clean:
+	rm -rf modeset-single-buffer modeset-double-buffer modeset-page-flip \
+	modeset-plane-test modeset-atomic-crtc modeset-dumb
+
+# %.o: %.c
+# 	$(CC) -fPIC -shared $(FLAGS) -Wall -Werror -c $< -o $@ -MD -MF $(@:.o=.dep)

+ 19 - 0
package/starfive/drm_test/src/dvdhrm/Makefile

@@ -0,0 +1,19 @@
+# xxxx-gcc -o dumb dumb.c `pkg-config --cflags --libs libdrm`
+CROSS_CC_PREFIX = riscv64-buildroot-linux-gnu-
+CC  = $(CROSS_CC_PREFIX)gcc
+CXX = $(CROSS_CC_PREFIX)g++
+LINKER = $(CC)
+AR  = $(CROSS_CC_PREFIX)ar
+
+# FLAGS=-I/data/code/fedoral_513/github/freelight-u-sdk_github_old/work/buildroot_initramfs/staging/usr/include/drm/ -ldrm
+FLAGS = -I$(STAGING_DIR)/usr/include/drm/ -ldrm
+FLAGS += -Wall -O2 -D_FILE_OFFSET_BITS=64
+
+all:
+	$(CC) -o modeset modeset.c $(FLAGS)
+	$(CC) -o modeset-double-buffered modeset-double-buffered.c $(FLAGS)
+	$(CC) -o modeset-vsync modeset-vsync.c $(FLAGS)
+	$(CC) -o modeset-atomic modeset-atomic.c $(FLAGS)
+
+clean:
+	rm -rf modeset modeset-double-buffered modeset-vsync modeset-atomic

+ 1204 - 0
package/starfive/drm_test/src/dvdhrm/modeset-atomic.c

@@ -0,0 +1,1204 @@
+/*
+ * modeset-atomic - DRM Atomic-API Modesetting Example
+ * Written 2019 by Ezequiel Garcia <ezequiel@collabora.com>
+ *
+ * Dedicated to the Public Domain.
+ */
+
+/*
+ * DRM Double-Buffered VSync'ed Atomic Modesetting Howto
+ * This example extends modeset-vsync.c, introducing planes and the
+ * atomic API.
+ *
+ * Planes can be used to blend or overlay images on top of a CRTC
+ * framebuffer during the scanout process. Not all hardware provide
+ * planes and the number of planes available is also limited. If there's
+ * not enough planes available or the hardware does not provide them,
+ * users should fallback to composition via GPU or CPU to blend or
+ * overlay the planes. Notice that this render process will result
+ * in delay, what justifies the usage of planes by modern hardware
+ * that needs to be fast.
+ *
+ * There are three types of planes: primary, cursor and overlay. For
+ * compatibility with legacy userspace, the default behavior is to expose
+ * only overlay planes to userspace (we're going to see in the code that
+ * we have to ask to receive all types of planes). A good example of plane
+ * usage is this: imagine a static desktop screen and the user is moving
+ * the cursor around. Only the cursor is moving. Instead of calculating the
+ * complete scene for each time the user moves its cursor, we can update only
+ * the cursor plane and it will be automatically overlayed by the hardware on
+ * top of the primary plane. There's no need of software composition in this
+ * case.
+ *
+ * But there was synchronisation problems related to multiple planes
+ * usage. The KMS API was not atomic, so you'd have to update the primary
+ * plane and then the overlay planes with distinct IOCTL's. This could lead
+ * to tearing and also some trouble related to blocking, so the atomic
+ * API was proposed to fix these problems.
+ *
+ * With the introduction of the KMS atomic API, all the planes could get
+ * updated in a single IOCTL, using drmModeAtomicCommit(). This can be
+ * either asynchronous or fully blocking.
+ *
+ * This example assumes that you are familiar with modeset-vsync. Only
+ * the differences between both files are highlighted here.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <drm_fourcc.h>
+
+/*
+ * A new struct is introduced: drm_object. It stores properties of certain
+ * objects (connectors, CRTC and planes) that are used in atomic modeset setup
+ * and also in atomic page-flips (all planes updated in a single IOCTL).
+ */
+
+struct drm_object {
+	drmModeObjectProperties *props;
+	drmModePropertyRes **props_info;
+	uint32_t id;
+};
+
+struct modeset_buf {
+	uint32_t width;
+	uint32_t height;
+	uint32_t stride;
+	uint32_t size;
+	uint32_t handle;
+	uint8_t *map;
+	uint32_t fb;
+};
+
+struct modeset_output {
+	struct modeset_output *next;
+
+	unsigned int front_buf;
+	struct modeset_buf bufs[2];
+
+	struct drm_object connector;
+	struct drm_object crtc;
+	struct drm_object plane;
+
+	drmModeModeInfo mode;
+	uint32_t mode_blob_id;
+	uint32_t crtc_index;
+
+	bool pflip_pending;
+	bool cleanup;
+
+	uint8_t r, g, b;
+	bool r_up, g_up, b_up;
+};
+static struct modeset_output *output_list = NULL;
+
+/*
+ * modeset_open() changes just a little bit. We now have to set that we're going
+ * to use the KMS atomic API and check if the device is capable of handling it.
+ */
+
+static int modeset_open(int *out, const char *node)
+{
+	int fd, ret;
+	uint64_t cap;
+
+	fd = open(node, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		ret = -errno;
+		fprintf(stderr, "cannot open '%s': %m\n", node);
+		return ret;
+	}
+
+	/* Set that we want to receive all the types of planes in the list. This
+	 * have to be done since, for legacy reasons, the default behavior is to
+	 * expose only the overlay planes to the users. The atomic API only
+	 * works if this is set.
+	 */
+	ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+	if (ret) {
+		fprintf(stderr, "failed to set universal planes cap, %d\n", ret);
+		return ret;
+	}
+
+	/* Here we set that we're going to use the KMS atomic API. It's supposed
+	 * to set the DRM_CLIENT_CAP_UNIVERSAL_PLANES automatically, but it's a
+	 * safe behavior to set it explicitly as we did in the previous
+	 * commands. This is also good for learning purposes.
+	 */
+	ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
+	if (ret) {
+		fprintf(stderr, "failed to set atomic cap, %d", ret);
+		return ret;
+	}
+
+	if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap) < 0 || !cap) {
+		fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
+			node);
+		close(fd);
+		return -EOPNOTSUPP;
+	}
+
+	if (drmGetCap(fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) < 0 || !cap) {
+		fprintf(stderr, "drm device '%s' does not support atomic KMS\n",
+			node);
+		close(fd);
+		return -EOPNOTSUPP;
+	}
+
+	*out = fd;
+	return 0;
+}
+
+/*
+ * get_property_value() is a new function. Given a device, the properties of
+ * an object and a name, search for the value of property 'name'. If we can't
+ * find it, return -1.
+ */
+
+static int64_t get_property_value(int fd, drmModeObjectPropertiesPtr props,
+				  const char *name)
+{
+	drmModePropertyPtr prop;
+	uint64_t value;
+	bool found;
+	int j;
+
+	found = false;
+	for (j = 0; j < props->count_props && !found; j++) {
+		prop = drmModeGetProperty(fd, props->props[j]);
+		if (!strcmp(prop->name, name)) {
+			value = props->prop_values[j];
+			found = true;
+		}
+		drmModeFreeProperty(prop);
+	}
+
+	if (!found)
+		return -1;
+	return value;
+}
+
+/*
+ * get_drm_object_properties() is a new helpfer function that retrieves
+ * the properties of a certain CRTC, plane or connector object.
+ */
+
+static void modeset_get_object_properties(int fd, struct drm_object *obj,
+					  uint32_t type)
+{
+	const char *type_str;
+	unsigned int i;
+
+	obj->props = drmModeObjectGetProperties(fd, obj->id, type);
+	if (!obj->props) {
+		switch(type) {
+			case DRM_MODE_OBJECT_CONNECTOR:
+				type_str = "connector";
+				break;
+			case DRM_MODE_OBJECT_PLANE:
+				type_str = "plane";
+				break;
+			case DRM_MODE_OBJECT_CRTC:
+				type_str = "CRTC";
+				break;
+			default:
+				type_str = "unknown type";
+				break;
+		}
+		fprintf(stderr, "cannot get %s %d properties: %s\n",
+			type_str, obj->id, strerror(errno));
+		return;
+	}
+
+	obj->props_info = calloc(obj->props->count_props, sizeof(obj->props_info));
+	for (i = 0; i < obj->props->count_props; i++)
+		obj->props_info[i] = drmModeGetProperty(fd, obj->props->props[i]);
+}
+
+/*
+ * set_drm_object_property() is a new function. It sets a property value to a
+ * CRTC, plane or connector object.
+ */
+
+static int set_drm_object_property(drmModeAtomicReq *req, struct drm_object *obj,
+				   const char *name, uint64_t value)
+{
+	int i;
+	uint32_t prop_id = 0;
+
+	for (i = 0; i < obj->props->count_props; i++) {
+		if (!strcmp(obj->props_info[i]->name, name)) {
+			prop_id = obj->props_info[i]->prop_id;
+			break;
+		}
+	}
+
+	if (prop_id == 0) {
+		fprintf(stderr, "no object property: %s\n", name);
+		return -EINVAL;
+	}
+
+	return drmModeAtomicAddProperty(req, obj->id, prop_id, value);
+}
+
+/*
+ * modeset_find_crtc() changes a little bit. Now we also have to save the CRTC
+ * index, and not only its id.
+ */
+
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_output *out)
+{
+	drmModeEncoder *enc;
+	unsigned int i, j;
+	uint32_t crtc;
+	struct modeset_output *iter;
+
+	/* first try the currently conected encoder+crtc */
+	if (conn->encoder_id)
+		enc = drmModeGetEncoder(fd, conn->encoder_id);
+	else
+		enc = NULL;
+
+	if (enc) {
+		if (enc->crtc_id) {
+			crtc = enc->crtc_id;
+			for (iter = output_list; iter; iter = iter->next) {
+				if (iter->crtc.id == crtc) {
+					crtc = 0;
+					break;
+				}
+			}
+
+			if (crtc > 0) {
+				drmModeFreeEncoder(enc);
+				out->crtc.id = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	/* If the connector is not currently bound to an encoder or if the
+	 * encoder+crtc is already used by another connector (actually unlikely
+	 * but lets be safe), iterate all other available encoders to find a
+	 * matching CRTC.
+	 */
+	for (i = 0; i < conn->count_encoders; ++i) {
+		enc = drmModeGetEncoder(fd, conn->encoders[i]);
+		if (!enc) {
+			fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
+				i, conn->encoders[i], errno);
+			continue;
+		}
+
+		/* iterate all global CRTCs */
+		for (j = 0; j < res->count_crtcs; ++j) {
+			/* check whether this CRTC works with the encoder */
+			if (!(enc->possible_crtcs & (1 << j)))
+				continue;
+
+			/* check that no other output already uses this CRTC */
+			crtc = res->crtcs[j];
+			for (iter = output_list; iter; iter = iter->next) {
+				if (iter->crtc.id == crtc) {
+					crtc = 0;
+					break;
+				}
+			}
+
+			/* We have found a CRTC, so save it and return. Note
+			 * that we have to save its index as well. The CRTC
+			 * index (not its ID) will be used when searching for a
+			 * suitable plane.
+			 */
+			if (crtc > 0) {
+				fprintf(stdout, "crtc %u found for encoder %u, will need full modeset\n",
+					crtc, conn->encoders[i]);;
+				drmModeFreeEncoder(enc);
+				out->crtc.id = crtc;
+				out->crtc_index = j;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	fprintf(stderr, "cannot find suitable crtc for connector %u\n",
+		conn->connector_id);
+	return -ENOENT;
+}
+
+/*
+ * modeset_find_plane() is a new function. Given a certain combination
+ * of connector+CRTC, it looks for a primary plane for it.
+ */
+
+static int modeset_find_plane(int fd, struct modeset_output *out)
+{
+	drmModePlaneResPtr plane_res;
+	bool found_primary = false;
+	int i, ret = -EINVAL;
+
+	plane_res = drmModeGetPlaneResources(fd);
+	if (!plane_res) {
+		fprintf(stderr, "drmModeGetPlaneResources failed: %s\n",
+				strerror(errno));
+		return -ENOENT;
+	}
+
+	/* iterates through all planes of a certain device */
+	for (i = 0; (i < plane_res->count_planes) && !found_primary; i++) {
+		int plane_id = plane_res->planes[i];
+
+		drmModePlanePtr plane = drmModeGetPlane(fd, plane_id);
+		if (!plane) {
+			fprintf(stderr, "drmModeGetPlane(%u) failed: %s\n", plane_id,
+					strerror(errno));
+			continue;
+		}
+
+		/* check if the plane can be used by our CRTC */
+		if (plane->possible_crtcs & (1 << out->crtc_index)) {
+			drmModeObjectPropertiesPtr props =
+				drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
+
+			/* Get the "type" property to check if this is a primary
+			 * plane. Type property is special, as its enum value is
+			 * defined in UAPI headers. For the properties that are
+			 * not defined in the UAPI headers, we would have to
+			 * give kernel the property name and it would return the
+			 * corresponding enum value. We could also do this for
+			 * the "type" property, but it would make this simple
+			 * example more complex. The reason why defining enum
+			 * values for kernel properties in UAPI headers is
+			 * deprecated is that string names are easier to both
+			 * (userspace and kernel) make unique and keep
+			 * consistent between drivers and kernel versions. But
+			 * in order to not break userspace, some properties were
+			 * left in the UAPI headers as well.
+			 */
+			if (get_property_value(fd, props, "type") == DRM_PLANE_TYPE_PRIMARY) {
+				found_primary = true;
+				out->plane.id = plane_id;
+				ret = 0;
+			}
+
+			drmModeFreeObjectProperties(props);
+		}
+
+		drmModeFreePlane(plane);
+	}
+
+	drmModeFreePlaneResources(plane_res);
+
+	if (found_primary)
+		fprintf(stdout, "found primary plane, id: %d\n", out->plane.id);
+	else
+		fprintf(stdout, "couldn't find a primary plane\n");
+	return ret;
+}
+
+/*
+ * modeset_drm_object_fini() is a new helper function that destroys CRTCs,
+ * connectors and planes
+ */
+
+static void modeset_drm_object_fini(struct drm_object *obj)
+{
+	for (int i = 0; i < obj->props->count_props; i++)
+		drmModeFreeProperty(obj->props_info[i]);
+	free(obj->props_info);
+	drmModeFreeObjectProperties(obj->props);
+}
+
+/*
+ * modeset_setup_objects() is a new function. It helps us to retrieve
+ * connector, CRTC and plane objects properties from the device. These
+ * properties will help us during the atomic modesetting commit, so we save
+ * them in our struct modeset_output object.
+ */
+
+static int modeset_setup_objects(int fd, struct modeset_output *out)
+{
+	struct drm_object *connector = &out->connector;
+	struct drm_object *crtc = &out->crtc;
+	struct drm_object *plane = &out->plane;
+
+	/* retrieve connector properties from the device */
+	modeset_get_object_properties(fd, connector, DRM_MODE_OBJECT_CONNECTOR);
+	if (!connector->props)
+		goto out_conn;
+
+	/* retrieve CRTC properties from the device */
+	modeset_get_object_properties(fd, crtc, DRM_MODE_OBJECT_CRTC);
+	if (!crtc->props)
+		goto out_crtc;
+
+	/* retrieve plane properties from the device */
+	modeset_get_object_properties(fd, plane, DRM_MODE_OBJECT_PLANE);
+	if (!plane->props)
+		goto out_plane;
+
+	return 0;
+
+out_plane:
+	modeset_drm_object_fini(crtc);
+out_crtc:
+	modeset_drm_object_fini(connector);
+out_conn:
+	return -ENOMEM;
+}
+
+/*
+ * modeset_destroy_objects() is a new function. It destroys what we allocate
+ * in modeset_setup_objects().
+ */
+
+static void modeset_destroy_objects(int fd, struct modeset_output *out)
+{
+	modeset_drm_object_fini(&out->connector);
+	modeset_drm_object_fini(&out->crtc);
+	modeset_drm_object_fini(&out->plane);
+}
+
+/*
+ * modeset_create_fb() stays the same.
+ */
+
+static int modeset_create_fb(int fd, struct modeset_buf *buf)
+{
+	struct drm_mode_create_dumb creq;
+	struct drm_mode_destroy_dumb dreq;
+	struct drm_mode_map_dumb mreq;
+	int ret;
+	uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
+
+	/* create dumb buffer */
+	memset(&creq, 0, sizeof(creq));
+	creq.width = buf->width;
+	creq.height = buf->height;
+	creq.bpp = 32;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+	if (ret < 0) {
+		fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
+			errno);
+		return -errno;
+	}
+	buf->stride = creq.pitch;
+	buf->size = creq.size;
+	buf->handle = creq.handle;
+
+	/* create framebuffer object for the dumb-buffer */
+	handles[0] = buf->handle;
+	pitches[0] = buf->stride;
+	ret = drmModeAddFB2(fd, buf->width, buf->height, DRM_FORMAT_RGB565,
+			    handles, pitches, offsets, &buf->fb, 0);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_destroy;
+	}
+
+	/* prepare buffer for memory mapping */
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.handle = buf->handle;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+	if (ret) {
+		fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* perform actual memory mapping */
+	buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+		        fd, mreq.offset);
+	if (buf->map == MAP_FAILED) {
+		fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* clear the framebuffer to 0 */
+	memset(buf->map, 0, buf->size);
+
+	return 0;
+
+err_fb:
+	drmModeRmFB(fd, buf->fb);
+err_destroy:
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = buf->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+	return ret;
+}
+
+/*
+ * modeset_destroy_fb() stays the same.
+ */
+
+static void modeset_destroy_fb(int fd, struct modeset_buf *buf)
+{
+	struct drm_mode_destroy_dumb dreq;
+
+	/* unmap buffer */
+	munmap(buf->map, buf->size);
+
+	/* delete framebuffer */
+	drmModeRmFB(fd, buf->fb);
+
+	/* delete dumb buffer */
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = buf->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+}
+
+/*
+ * modeset_setup_framebuffers() creates framebuffers for the back and front
+ * buffers of a certain output. Also, it copies the connector mode to these
+ * buffers.
+ */
+
+static int modeset_setup_framebuffers(int fd, drmModeConnector *conn,
+				      struct modeset_output *out)
+{
+	int i, ret;
+
+	/* setup the front and back framebuffers */
+	for (i = 0; i < 2; i++) {
+
+		/* copy mode info to buffer */
+		out->bufs[i].width = conn->modes[0].hdisplay;
+		out->bufs[i].height = conn->modes[0].vdisplay;
+
+		/* create a framebuffer for the buffer */
+		ret = modeset_create_fb(fd, &out->bufs[i]);
+		if (ret) {
+			/* the second framebuffer creation failed, so
+			 * we have to destroy the first before returning */
+			if (i == 1)
+				modeset_destroy_fb(fd, &out->bufs[0]);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * modeset_output_destroy() is new. It destroys the objects (connector, crtc and
+ * plane), front and back buffers, the mode blob property and then destroys the
+ * output itself.
+ */
+
+static void modeset_output_destroy(int fd, struct modeset_output *out)
+{
+	/* destroy connector, crtc and plane objects */
+	modeset_destroy_objects(fd, out);
+
+	/* destroy front/back framebuffers */
+	modeset_destroy_fb(fd, &out->bufs[0]);
+	modeset_destroy_fb(fd, &out->bufs[1]);
+
+	/* destroy mode blob property */
+	drmModeDestroyPropertyBlob(fd, out->mode_blob_id);
+
+	free(out);
+}
+
+/*
+ * With a certain combination of connector+CRTC, we look for a suitable primary
+ * plane for it. After that, we retrieve connector, CRTC and plane objects
+ * properties from the device. These objects are used during the atomic modeset
+ * setup (see modeset_atomic_prepare_commit()) and also during the page-flips
+ * (see modeset_draw_out() and modeset_atomic_commit()).
+ *
+ * Besides that, we have to create a blob property that receives the output
+ * mode. When we perform an atomic commit, the driver expects a CRTC property
+ * named "MODE_ID", which points to the id of a blob. This usually happens for
+ * properties that are not simple types. In this particular case, out->mode is a
+ * struct. But we could have another property that expects the id of a blob that
+ * holds an array, for instance.
+ */
+
+static struct modeset_output *modeset_output_create(int fd, drmModeRes *res,
+						    drmModeConnector *conn)
+{
+	int ret;
+	struct modeset_output *out;
+
+	/* creates an output structure */
+	out = malloc(sizeof(*out));
+	memset(out, 0, sizeof(*out));
+	out->connector.id = conn->connector_id;
+
+	/* check if a monitor is connected */
+	if (conn->connection != DRM_MODE_CONNECTED) {
+		fprintf(stderr, "ignoring unused connector %u\n",
+			conn->connector_id);
+		goto out_error;
+	}
+
+	/* check if there is at least one valid mode */
+	if (conn->count_modes == 0) {
+		fprintf(stderr, "no valid mode for connector %u\n",
+			conn->connector_id);
+		goto out_error;
+	}
+
+	/* copy the mode information into our output structure */
+	memcpy(&out->mode, &conn->modes[0], sizeof(out->mode));
+	/* create the blob property using out->mode and save its id in the output*/
+	if (drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode),
+	                              &out->mode_blob_id) != 0) {
+		fprintf(stderr, "couldn't create a blob property\n");
+		goto out_error;
+	}
+	fprintf(stderr, "mode for connector %u is %ux%u\n",
+	        conn->connector_id, out->bufs[0].width, out->bufs[0].height);
+
+	/* find a crtc for this connector */
+	ret = modeset_find_crtc(fd, res, conn, out);
+	if (ret) {
+		fprintf(stderr, "no valid crtc for connector %u\n",
+			conn->connector_id);
+		goto out_blob;
+	}
+
+	/* with a connector and crtc, find a primary plane */
+	ret = modeset_find_plane(fd, out);
+	if (ret) {
+		fprintf(stderr, "no valid plane for crtc %u\n", out->crtc.id);
+		goto out_blob;
+	}
+
+	/* gather properties of our connector, CRTC and planes */
+	ret = modeset_setup_objects(fd, out);
+	if (ret) {
+		fprintf(stderr, "cannot get plane properties\n");
+		goto out_blob;
+	}
+
+	/* setup front/back framebuffers for this CRTC */
+	ret = modeset_setup_framebuffers(fd, conn, out);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffers for connector %u\n",
+			conn->connector_id);
+		goto out_obj;
+	}
+
+	return out;
+
+out_obj:
+	modeset_destroy_objects(fd, out);
+out_blob:
+	drmModeDestroyPropertyBlob(fd, out->mode_blob_id);
+out_error:
+	free(out);
+	return NULL;
+}
+
+/*
+ * modeset_prepare() changes a little bit. Now we use the new function
+ * modeset_output_create() to allocate memory and setup the output.
+ */
+
+static int modeset_prepare(int fd)
+{
+	drmModeRes *res;
+	drmModeConnector *conn;
+	unsigned int i;
+	struct modeset_output *out;
+
+	/* retrieve resources */
+	res = drmModeGetResources(fd);
+	if (!res) {
+		fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",
+			errno);
+		return -errno;
+	}
+
+	/* iterate all connectors */
+	for (i = 0; i < res->count_connectors; ++i) {
+		/* get information for each connector */
+		conn = drmModeGetConnector(fd, res->connectors[i]);
+		if (!conn) {
+			fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
+				i, res->connectors[i], errno);
+			continue;
+		}
+
+		/* create an output structure and free connector data */
+		out = modeset_output_create(fd, res, conn);
+		drmModeFreeConnector(conn);
+		if (!out)
+			continue;
+
+		/* link output into global list */
+		out->next = output_list;
+		output_list = out;
+	}
+	if (!output_list) {
+		fprintf(stderr, "couldn't create any outputs\n");
+		return -1;
+	}
+
+	/* free resources again */
+	drmModeFreeResources(res);
+	return 0;
+}
+
+/*
+ * modeset_atomic_prepare_commit() is new. Here we set the values of properties
+ * (of our connector, CRTC and plane objects) that we want to change in the
+ * atomic commit. These changes are temporarily stored in drmModeAtomicReq *req
+ * until the commit actually happens.
+ */
+
+static int modeset_atomic_prepare_commit(int fd, struct modeset_output *out,
+					 drmModeAtomicReq *req)
+{
+	struct drm_object *plane = &out->plane;
+	struct modeset_buf *buf = &out->bufs[out->front_buf ^ 1];
+
+	/* set id of the CRTC id that the connector is using */
+	if (set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id) < 0)
+		return -1;
+
+	/* set the mode id of the CRTC; this property receives the id of a blob
+	 * property that holds the struct that actually contains the mode info */
+	if (set_drm_object_property(req, &out->crtc, "MODE_ID", out->mode_blob_id) < 0)
+		return -1;
+
+	/* set the CRTC object as active */
+	if (set_drm_object_property(req, &out->crtc, "ACTIVE", 1) < 0)
+		return -1;
+
+	/* set properties of the plane related to the CRTC and the framebuffer */
+	if (set_drm_object_property(req, plane, "FB_ID", buf->fb) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "CRTC_ID", out->crtc.id) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "SRC_X", 0) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "SRC_Y", 0) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "SRC_W", buf->width << 16) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "SRC_H", buf->height << 16) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "CRTC_X", 0) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "CRTC_Y", 0) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "CRTC_W", buf->width) < 0)
+		return -1;
+	if (set_drm_object_property(req, plane, "CRTC_H", buf->height) < 0)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * A short helper function to compute a changing color value. No need to
+ * understand it.
+ */
+
+static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
+{
+	uint8_t next;
+
+	next = cur + (*up ? 1 : -1) * (rand() % mod);
+	if ((*up && next < cur) || (!*up && next > cur)) {
+		*up = !*up;
+		next = cur;
+	}
+
+	return next;
+}
+
+/*
+ * Draw on back framebuffer before the page-flip is requested.
+ */
+
+static void modeset_paint_framebuffer(struct modeset_output *out)
+{
+	struct modeset_buf *buf;
+	unsigned int j, k, off;
+
+	/* draw on back framebuffer */
+	out->r = next_color(&out->r_up, out->r, 5);
+	out->g = next_color(&out->g_up, out->g, 5);
+	out->b = next_color(&out->b_up, out->b, 5);
+	buf = &out->bufs[out->front_buf ^ 1];
+	for (j = 0; j < buf->height; ++j) {
+		for (k = 0; k < buf->width; ++k) {
+			off = buf->stride * j + k * 4;
+			*(uint32_t*)&buf->map[off] =
+				     (out->r << 16) | (out->g << 8) | out->b;
+		}
+	}
+
+}
+
+/*
+ * modeset_draw_out() prepares the framebuffer with the drawing and then it asks
+ * for the driver to perform an atomic commit. This will lead to a page-flip and
+ * the content of the framebuffer will be displayed. In this simple example
+ * we're only using the primary plane, but we could also be updating other
+ * planes in the same atomic commit.
+ *
+ * Just like in modeset_perform_modeset(), we first setup everything with
+ * modeset_atomic_prepare_commit() and then actually perform the atomic commit.
+ * But there are some important differences:
+ *
+ * 1. Here we just want to perform a commit that changes the state of a specific
+ *    output, and in modeset_perform_modeset() we did an atomic commit that was
+ *    supposed to setup all the outputs at once. So there's no need to prepare
+ *    every output before performing the atomic commit. But let's suppose you
+ *    prepare every output and then perform the commit. It should schedule a
+ *    page-flip for all of them, but modeset_draw_out() was called because the
+ *    page-flip for a specific output has finished. The others may not be
+ *    prepared for a page-flip yet (e.g. in the middle of a scanout), so these
+ *    page-flips will fail.
+ *
+ * 2. Here we have already painted the framebuffer and also we don't use the
+ *    flag DRM_MODE_ALLOW_MODESET anymore, since the modeset already happened.
+ *    We could continue to use this flag, as it makes no difference if
+ *    modeset_perform_modeset() is correct and there's no bug in the kernel.
+ *    The flag only allows (it doesn't force) the driver to perform a modeset,
+ *    but we have already performed it in modeset_perform_modeset() and now we
+ *    just want page-flips to occur. If we still need to perform modesets it
+ *    means that we have a bug somewhere, and it may be better to fail than to
+ *    glitch (a modeset can cause unecessary latency and also blank the screen).
+ */
+
+static void modeset_draw_out(int fd, struct modeset_output *out)
+{
+	drmModeAtomicReq *req;
+	int ret, flags;
+
+	/* draw on framebuffer of the output */
+	modeset_paint_framebuffer(out);
+
+	/* prepare output for atomic commit */
+	req = drmModeAtomicAlloc();
+	ret = modeset_atomic_prepare_commit(fd, out, req);
+	if (ret < 0) {
+		fprintf(stderr, "prepare atomic commit failed, %d\n", errno);
+		return;
+	}
+
+	/* We've just draw on the framebuffer, prepared the commit and now it's
+	 * time to perform a page-flip to display its content.
+	 *
+	 * DRM_MODE_PAGE_FLIP_EVENT signalizes that we want to receive a
+	 * page-flip event in the DRM-fd when the page-flip happens. This flag
+	 * is also used in the non-atomic examples, so you're probably familiar
+	 * with it.
+	 *
+	 * DRM_MODE_ATOMIC_NONBLOCK makes the page-flip non-blocking. We don't
+	 * want to be blocked waiting for the commit to happen, since we can use
+	 * this time to prepare a new framebuffer, for instance. We can only do
+	 * this because there are mechanisms to know when the commit is complete
+	 * (like page flip event, explained above).
+	 */
+	flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
+	ret = drmModeAtomicCommit(fd, req, flags, NULL);
+	drmModeAtomicFree(req);
+
+	if (ret < 0) {
+		fprintf(stderr, "atomic commit failed, %d\n", errno);
+		return;
+	}
+	out->front_buf ^= 1;
+	out->pflip_pending = true;
+}
+
+/*
+ * modeset_page_flip_event() changes. Now that we are using page_flip_handler2,
+ * we also receive the CRTC that is responsible for this event. When using the
+ * atomic API we commit multiple CRTC's at once, so we need the information of
+ * what output caused the event in order to schedule a new page-flip for it.
+ */
+
+static void modeset_page_flip_event(int fd, unsigned int frame,
+				    unsigned int sec, unsigned int usec,
+				    unsigned int crtc_id, void *data)
+{
+	struct modeset_output *out, *iter;
+
+	/* find the output responsible for this event */
+	out = NULL;
+	for (iter = output_list; iter; iter = iter->next) {
+		if (iter->crtc.id == crtc_id) {
+			out = iter;
+			break;
+		}
+	}
+	if (out == NULL)
+		return;
+
+	out->pflip_pending = false;
+	if (!out->cleanup)
+		modeset_draw_out(fd, out);
+}
+
+/*
+ * modeset_perform_modeset() is new. First we define what properties have to be
+ * changed and the values that they will receive. To check if the modeset will
+ * work as expected, we perform an atomic commit with the flag
+ * DRM_MODE_ATOMIC_TEST_ONLY. With this flag the DRM driver tests if the atomic
+ * commit would work, but it doesn't commit it to the hardware. After, the same
+ * atomic commit is performed without the TEST_ONLY flag, but not only before we
+ * draw on the framebuffers of the outputs. This is necessary to avoid
+ * displaying unwanted content.
+ *
+ * NOTE: we can't perform an atomic commit without an attached frambeuffer
+ * (even when we have DRM_MODE_ATOMIC_TEST_ONLY). It will simply fail.
+ */
+
+static int modeset_perform_modeset(int fd)
+{
+	int ret, flags;
+	struct modeset_output *iter;
+	drmModeAtomicReq *req;
+
+	/* prepare modeset on all outputs */
+	req = drmModeAtomicAlloc();
+	for (iter = output_list; iter; iter = iter->next) {
+		ret = modeset_atomic_prepare_commit(fd, iter, req);
+		if (ret < 0)
+			break;
+	}
+	if (ret < 0) {
+		fprintf(stderr, "prepare atomic commit failed, %d\n", errno);
+		return ret;
+	}
+
+	/* perform test-only atomic commit */
+	flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET;
+	ret = drmModeAtomicCommit(fd, req, flags, NULL);
+	if (ret < 0) {
+		fprintf(stderr, "test-only atomic commit failed, %d\n", errno);
+		drmModeAtomicFree(req);
+		return ret;
+	}
+
+	/* draw on back framebuffer of all outputs */
+	for (iter = output_list; iter; iter = iter->next) {
+
+		/* colors initialization, this is the first time we're drawing */
+		iter->r = rand() % 0xff;
+		iter->g = rand() % 0xff;
+		iter->b = rand() % 0xff;
+		iter->r_up = iter->g_up = iter->b_up = true;
+
+		modeset_paint_framebuffer(iter);
+	}
+
+	/* initial modeset on all outputs */
+	flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT;
+	ret = drmModeAtomicCommit(fd, req, flags, NULL);
+	if (ret < 0)
+		fprintf(stderr, "modeset atomic commit failed, %d\n", errno);
+
+	drmModeAtomicFree(req);
+
+	return ret;
+}
+
+/*
+ * modeset_draw() changes. If we got here, the modeset already occurred. When
+ * the page-flip for a certain output is done, an event will be fired and we'll
+ * be able to handle it.
+ *
+ * Here we define the function that should handle these events, which is
+ * modeset_page_flip_event(). This function calls modeset_draw_out(), which is
+ * responsible for preparing a new framebuffer and performing another atomic
+ * commit for us.
+ *
+ * Then we have a 5 seconds loop that keeps waiting for the events that are
+ * fired when the page-flip is complete. drmHandleEvent() is reponsible for
+ * reading the events from the fd and to call modeset_page_flip_event() for
+ * each one of them.
+ */
+
+static void modeset_draw(int fd)
+{
+	int ret;
+	fd_set fds;
+	time_t start, cur;
+	struct timeval v;
+	drmEventContext ev;
+
+	/* init variables */
+	srand(time(&start));
+	FD_ZERO(&fds);
+	memset(&v, 0, sizeof(v));
+	memset(&ev, 0, sizeof(ev));
+
+	/* 3 is the first version that allow us to use page_flip_handler2, which
+	 * is just like page_flip_handler but with the addition of passing the
+	 * crtc_id as argument to the function that will handle page-flip events
+	 * (in our case, modeset_page_flip_event()). This is good because we can
+	 * find out for what output the page-flip happened.
+	 *
+	 * The usage of page_flip_handler2 is the reason why we needed to verify
+	 * the support for DRM_CAP_CRTC_IN_VBLANK_EVENT.
+	 */
+	ev.version = 3;
+	ev.page_flip_handler2 = modeset_page_flip_event;
+
+	/* perform modeset using atomic commit */
+	modeset_perform_modeset(fd);
+
+	/* wait 5s for VBLANK or input events */
+	while (time(&cur) < start + 5) {
+		FD_SET(0, &fds);
+		FD_SET(fd, &fds);
+		v.tv_sec = start + 5 - cur;
+
+		ret = select(fd + 1, &fds, NULL, NULL, &v);
+		if (ret < 0) {
+			fprintf(stderr, "select() failed with %d: %m\n", errno);
+			break;
+		} else if (FD_ISSET(0, &fds)) {
+			fprintf(stderr, "exit due to user-input\n");
+			break;
+		} else if (FD_ISSET(fd, &fds)) {
+			/* read the fd looking for events and handle each event
+			 * by calling modeset_page_flip_event() */
+			drmHandleEvent(fd, &ev);
+		}
+	}
+}
+
+/*
+ * modeset_cleanup() stays the same.
+ */
+
+static void modeset_cleanup(int fd)
+{
+	struct modeset_output *iter;
+	drmEventContext ev;
+	int ret;
+
+	/* init variables */
+	memset(&ev, 0, sizeof(ev));
+	ev.version = 3;
+	ev.page_flip_handler2 = modeset_page_flip_event;
+
+	while (output_list) {
+		/* get first output from list */
+		iter = output_list;
+
+		/* if a page-flip is pending, wait for it to complete */
+		iter->cleanup = true;
+		fprintf(stderr, "wait for pending page-flip to complete...\n");
+		while (iter->pflip_pending) {
+			ret = drmHandleEvent(fd, &ev);
+			if (ret)
+				break;
+		}
+
+		/* move head of the list to the next output */
+		output_list = iter->next;
+
+		/* destroy current output */
+		modeset_output_destroy(fd, iter);
+	}
+}
+
+/*
+ * main() also changes. Instead of performing the KMS setup calling
+ * drmModeSetCrtc(), we instead setup it using the atomic API with the
+ * function modeset_perform_modeset(), which is called by modeset_draw().
+ */
+
+int main(int argc, char **argv)
+{
+	int ret, fd;
+	const char *card;
+
+	/* check which DRM device to open */
+	if (argc > 1)
+		card = argv[1];
+	else
+		card = "/dev/dri/card0";
+
+	fprintf(stderr, "using card '%s'\n", card);
+
+	/* open the DRM device */
+	ret = modeset_open(&fd, card);
+	if (ret)
+		goto out_return;
+
+	/* prepare all connectors and CRTCs */
+	ret = modeset_prepare(fd);
+	if (ret)
+		goto out_close;
+
+	/* draw some colors for 5seconds */
+	modeset_draw(fd);
+
+	/* cleanup everything */
+	modeset_cleanup(fd);
+
+	ret = 0;
+
+out_close:
+	close(fd);
+out_return:
+	if (ret) {
+		errno = -ret;
+		fprintf(stderr, "modeset failed with error %d: %m\n", errno);
+	} else {
+		fprintf(stderr, "exiting\n");
+	}
+	return ret;
+}
+
+/*
+ * This is a very simple example to show how to use the KMS atomic API and how
+ * different it is from legacy KMS. Most modern drivers are using the atomic
+ * API, so it is important to have this example.
+ *
+ * Just like vsync'ed double-buffering, the atomic API does not not solve all
+ * the problems that can happen  and you have to figure out the best
+ * implementation for your use case.
+ *
+ * If you want to take a look at more complex examples that makes use of KMS
+ * atomic API, I can recommend you:
+ *
+ * - kms-quads: https://gitlab.freedesktop.org/daniels/kms-quads/
+ *   A more complex (but also well-explained) example that can be used to
+ *   learn how to build a compositor using the atomic API. Supports both
+ *   GL and software rendering.
+ *
+ * - Weston: https://gitlab.freedesktop.org/wayland/weston
+ *   Reference implementation of a Wayland compositor. It's a very
+ *   sophisticated DRM renderer, hard to understand fully as it uses more
+ *   complicated techniques like DRM planes.
+ *
+ * Any feedback is welcome. Feel free to use this code freely for your own
+ * documentation or projects.
+ *
+ *  - Hosted on http://github.com/dvdhrm/docs
+ */

+ 644 - 0
package/starfive/drm_test/src/dvdhrm/modeset-double-buffered.c

@@ -0,0 +1,644 @@
+/*
+ * modeset - DRM Double-Buffered Modesetting Example
+ *
+ * Written 2012 by David Rheinsberg <david.rheinsberg@gmail.com>
+ * Dedicated to the Public Domain.
+ */
+
+/*
+ * DRM Double-Buffered Modesetting Howto
+ * This example extends the modeset.c howto and introduces double-buffering.
+ * When drawing a new frame into a framebuffer, we should always draw into an
+ * unused buffer and not into the front buffer. If we draw into the front
+ * buffer, we might have drawn half the frame when the display-controller starts
+ * scanning out the next frame. Hence, we see flickering on the screen.
+ * The technique to avoid this is called double-buffering. We have two
+ * framebuffers, the front buffer which is currently used for scanout and a
+ * back-buffer that is used for drawing operations. When a frame is done, we
+ * simply swap both buffers.
+ * Swapping does not mean copying data, instead, only the pointers to the
+ * buffers are swapped.
+ *
+ * Please read modeset.c before reading this file as most of the functions stay
+ * the same. Only the differences are highlighted here.
+ * Also note that triple-buffering or any other number of buffers can be easily
+ * implemented by following the scheme here. However, in this example we limit
+ * the number of buffers to 2 so it is easier to follow.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct modeset_buf;
+struct modeset_dev;
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev);
+static int modeset_create_fb(int fd, struct modeset_buf *buf);
+static void modeset_destroy_fb(int fd, struct modeset_buf *buf);
+static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev);
+static int modeset_open(int *out, const char *node);
+static int modeset_prepare(int fd);
+static void modeset_draw(int fd);
+static void modeset_cleanup(int fd);
+
+/*
+ * modeset_open() stays the same as before.
+ */
+
+static int modeset_open(int *out, const char *node)
+{
+	int fd, ret;
+	uint64_t has_dumb;
+
+	fd = open(node, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		ret = -errno;
+		fprintf(stderr, "cannot open '%s': %m\n", node);
+		return ret;
+	}
+
+	if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 ||
+	    !has_dumb) {
+		fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
+			node);
+		close(fd);
+		return -EOPNOTSUPP;
+	}
+
+	*out = fd;
+	return 0;
+}
+
+/*
+ * Previously, we used the modeset_dev objects to hold buffer informations, too.
+ * Technically, we could have split them but avoided this to make the
+ * example simpler.
+ * However, in this example we need 2 buffers. One back buffer and one front
+ * buffer. So we introduce a new structure modeset_buf which contains everything
+ * related to a single buffer. Each device now gets an array of two of these
+ * buffers.
+ * Each buffer consists of width, height, stride, size, handle, map and fb-id.
+ * They have the same meaning as before.
+ *
+ * Each device also gets a new integer field: front_buf. This field contains the
+ * index of the buffer that is currently used as front buffer / scanout buffer.
+ * In our example it can be 0 or 1. We flip it by using XOR:
+ *   dev->front_buf ^= dev->front_buf
+ *
+ * Everything else stays the same.
+ */
+
+struct modeset_buf {
+	uint32_t width;
+	uint32_t height;
+	uint32_t stride;
+	uint32_t size;
+	uint32_t handle;
+	uint8_t *map;
+	uint32_t fb;
+};
+
+struct modeset_dev {
+	struct modeset_dev *next;
+
+	unsigned int front_buf;
+	struct modeset_buf bufs[2];
+
+	drmModeModeInfo mode;
+	uint32_t conn;
+	uint32_t crtc;
+	drmModeCrtc *saved_crtc;
+};
+
+static struct modeset_dev *modeset_list = NULL;
+
+/*
+ * modeset_prepare() stays the same.
+ */
+
+static int modeset_prepare(int fd)
+{
+	drmModeRes *res;
+	drmModeConnector *conn;
+	unsigned int i;
+	struct modeset_dev *dev;
+	int ret;
+
+	/* retrieve resources */
+	res = drmModeGetResources(fd);
+	if (!res) {
+		fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",
+			errno);
+		return -errno;
+	}
+
+	/* iterate all connectors */
+	for (i = 0; i < res->count_connectors; ++i) {
+		/* get information for each connector */
+		conn = drmModeGetConnector(fd, res->connectors[i]);
+		if (!conn) {
+			fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
+				i, res->connectors[i], errno);
+			continue;
+		}
+
+		/* create a device structure */
+		dev = malloc(sizeof(*dev));
+		memset(dev, 0, sizeof(*dev));
+		dev->conn = conn->connector_id;
+
+		/* call helper function to prepare this connector */
+		ret = modeset_setup_dev(fd, res, conn, dev);
+		if (ret) {
+			if (ret != -ENOENT) {
+				errno = -ret;
+				fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n",
+					i, res->connectors[i], errno);
+			}
+			free(dev);
+			drmModeFreeConnector(conn);
+			continue;
+		}
+
+		/* free connector data and link device into global list */
+		drmModeFreeConnector(conn);
+		dev->next = modeset_list;
+		modeset_list = dev;
+	}
+
+	/* free resources again */
+	drmModeFreeResources(res);
+	return 0;
+}
+
+/*
+ * modeset_setup_dev() sets up all resources for a single device. It mostly
+ * stays the same, but one thing changes: We allocate two framebuffers instead
+ * of one. That is, we call modeset_create_fb() twice.
+ * We also copy the width/height information into both framebuffers so
+ * modeset_create_fb() can use them without requiring a pointer to modeset_dev.
+ */
+
+static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev)
+{
+	int ret;
+
+	/* check if a monitor is connected */
+	if (conn->connection != DRM_MODE_CONNECTED) {
+		fprintf(stderr, "ignoring unused connector %u\n",
+			conn->connector_id);
+		return -ENOENT;
+	}
+
+	/* check if there is at least one valid mode */
+	if (conn->count_modes == 0) {
+		fprintf(stderr, "no valid mode for connector %u\n",
+			conn->connector_id);
+		return -EFAULT;
+	}
+
+	/* copy the mode information into our device structure and into both
+	 * buffers */
+	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
+	dev->bufs[0].width = conn->modes[0].hdisplay;
+	dev->bufs[0].height = conn->modes[0].vdisplay;
+	dev->bufs[1].width = conn->modes[0].hdisplay;
+	dev->bufs[1].height = conn->modes[0].vdisplay;
+	fprintf(stderr, "mode for connector %u is %ux%u\n",
+		conn->connector_id, dev->bufs[0].width, dev->bufs[0].height);
+
+	/* find a crtc for this connector */
+	ret = modeset_find_crtc(fd, res, conn, dev);
+	if (ret) {
+		fprintf(stderr, "no valid crtc for connector %u\n",
+			conn->connector_id);
+		return ret;
+	}
+
+	/* create framebuffer #1 for this CRTC */
+	ret = modeset_create_fb(fd, &dev->bufs[0]);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer for connector %u\n",
+			conn->connector_id);
+		return ret;
+	}
+
+	/* create framebuffer #2 for this CRTC */
+	ret = modeset_create_fb(fd, &dev->bufs[1]);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer for connector %u\n",
+			conn->connector_id);
+		modeset_destroy_fb(fd, &dev->bufs[0]);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * modeset_find_crtc() stays the same.
+ */
+
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev)
+{
+	drmModeEncoder *enc;
+	unsigned int i, j;
+	int32_t crtc;
+	struct modeset_dev *iter;
+
+	/* first try the currently conected encoder+crtc */
+	if (conn->encoder_id)
+		enc = drmModeGetEncoder(fd, conn->encoder_id);
+	else
+		enc = NULL;
+
+	if (enc) {
+		if (enc->crtc_id) {
+			crtc = enc->crtc_id;
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc == crtc) {
+					crtc = -1;
+					break;
+				}
+			}
+
+			if (crtc >= 0) {
+				drmModeFreeEncoder(enc);
+				dev->crtc = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	/* If the connector is not currently bound to an encoder or if the
+	 * encoder+crtc is already used by another connector (actually unlikely
+	 * but lets be safe), iterate all other available encoders to find a
+	 * matching CRTC. */
+	for (i = 0; i < conn->count_encoders; ++i) {
+		enc = drmModeGetEncoder(fd, conn->encoders[i]);
+		if (!enc) {
+			fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
+				i, conn->encoders[i], errno);
+			continue;
+		}
+
+		/* iterate all global CRTCs */
+		for (j = 0; j < res->count_crtcs; ++j) {
+			/* check whether this CRTC works with the encoder */
+			if (!(enc->possible_crtcs & (1 << j)))
+				continue;
+
+			/* check that no other device already uses this CRTC */
+			crtc = res->crtcs[j];
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc == crtc) {
+					crtc = -1;
+					break;
+				}
+			}
+
+			/* we have found a CRTC, so save it and return */
+			if (crtc >= 0) {
+				drmModeFreeEncoder(enc);
+				dev->crtc = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	fprintf(stderr, "cannot find suitable CRTC for connector %u\n",
+		conn->connector_id);
+	return -ENOENT;
+}
+
+/*
+ * modeset_create_fb() is mostly the same as before. Buf instead of writing the
+ * fields of a modeset_dev, we now require a buffer pointer passed as @buf.
+ * Please note that buf->width and buf->height are initialized by
+ * modeset_setup_dev() so we can use them here.
+ */
+
+static int modeset_create_fb(int fd, struct modeset_buf *buf)
+{
+	struct drm_mode_create_dumb creq;
+	struct drm_mode_destroy_dumb dreq;
+	struct drm_mode_map_dumb mreq;
+	int ret;
+
+	/* create dumb buffer */
+	memset(&creq, 0, sizeof(creq));
+	creq.width = buf->width;
+	creq.height = buf->height;
+	creq.bpp = 32;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+	if (ret < 0) {
+		fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
+			errno);
+		return -errno;
+	}
+	buf->stride = creq.pitch;
+	buf->size = creq.size;
+	buf->handle = creq.handle;
+
+	/* create framebuffer object for the dumb-buffer */
+	ret = drmModeAddFB(fd, buf->width, buf->height, 32, 32, buf->stride,
+			   buf->handle, &buf->fb);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_destroy;
+	}
+
+	/* prepare buffer for memory mapping */
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.handle = buf->handle;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+	if (ret) {
+		fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* perform actual memory mapping */
+	buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+		        fd, mreq.offset);
+	if (buf->map == MAP_FAILED) {
+		fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* clear the framebuffer to 0 */
+	memset(buf->map, 0, buf->size);
+
+	return 0;
+
+err_fb:
+	drmModeRmFB(fd, buf->fb);
+err_destroy:
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = buf->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+	return ret;
+}
+
+/*
+ * modeset_destroy_fb() is a new function. It does exactly the reverse of
+ * modeset_create_fb() and destroys a single framebuffer. The modeset.c example
+ * used to do this directly in modeset_cleanup().
+ * We simply unmap the buffer, remove the drm-FB and destroy the memory buffer.
+ */
+
+static void modeset_destroy_fb(int fd, struct modeset_buf *buf)
+{
+	struct drm_mode_destroy_dumb dreq;
+
+	/* unmap buffer */
+	munmap(buf->map, buf->size);
+
+	/* delete framebuffer */
+	drmModeRmFB(fd, buf->fb);
+
+	/* delete dumb buffer */
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = buf->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+}
+
+/*
+ * main() also stays almost exactly the same as before. We only need to change
+ * the way that we initially set the CRTCs. Instead of using the buffer
+ * information from modeset_dev, we now use dev->bufs[iter->front_buf] to get
+ * the current front-buffer and use this framebuffer for drmModeSetCrtc().
+ */
+
+int main(int argc, char **argv)
+{
+	int ret, fd;
+	const char *card;
+	struct modeset_dev *iter;
+	struct modeset_buf *buf;
+
+	/* check which DRM device to open */
+	if (argc > 1)
+		card = argv[1];
+	else
+		card = "/dev/dri/card0";
+
+	fprintf(stderr, "using card '%s'\n", card);
+
+	/* open the DRM device */
+	ret = modeset_open(&fd, card);
+	if (ret)
+		goto out_return;
+
+	/* prepare all connectors and CRTCs */
+	ret = modeset_prepare(fd);
+	if (ret)
+		goto out_close;
+
+	/* perform actual modesetting on each found connector+CRTC */
+	for (iter = modeset_list; iter; iter = iter->next) {
+		iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
+		buf = &iter->bufs[iter->front_buf];
+		ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,
+				     &iter->conn, 1, &iter->mode);
+		if (ret)
+			fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
+				iter->conn, errno);
+	}
+
+	/* draw some colors for 5seconds */
+	modeset_draw(fd);
+
+	/* cleanup everything */
+	modeset_cleanup(fd);
+
+	ret = 0;
+
+out_close:
+	close(fd);
+out_return:
+	if (ret) {
+		errno = -ret;
+		fprintf(stderr, "modeset failed with error %d: %m\n", errno);
+	} else {
+		fprintf(stderr, "exiting\n");
+	}
+	return ret;
+}
+
+/*
+ * A short helper function to compute a changing color value. No need to
+ * understand it.
+ */
+
+static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
+{
+	uint8_t next;
+
+	next = cur + (*up ? 1 : -1) * (rand() % mod);
+	if ((*up && next < cur) || (!*up && next > cur)) {
+		*up = !*up;
+		next = cur;
+	}
+
+	return next;
+}
+
+/*
+ * modeset_draw() is the place where things change. The render-logic is the same
+ * and we still draw a solid-color on the whole screen. However, we now have two
+ * buffers and need to flip between them.
+ *
+ * So before drawing into a framebuffer, we need to find the back-buffer.
+ * Remember, dev->font_buf is the index of the front buffer, so
+ * dev->front_buf ^ 1 is the index of the back buffer. We simply use
+ * dev->bufs[dev->front_buf ^ 1] to get the back-buffer and draw into it.
+ *
+ * After we finished drawing, we need to flip the buffers. We do this with the
+ * same call as we initially set the CRTC: drmModeSetCrtc(). However, we now
+ * pass the back-buffer as new framebuffer as we want to flip them.
+ * The only thing left to do is to change the dev->front_buf index to point to
+ * the new back-buffer (which was previously the front buffer).
+ * We then sleep for a short time period and start drawing again.
+ *
+ * If you run this example, you will notice that there is almost no flickering,
+ * anymore. The buffers are now swapped as a whole so each new frame shows
+ * always the whole new image. If you look carefully, you will notice that the
+ * modeset.c example showed many screen corruptions during redraw-cycles.
+ *
+ * However, this example is still not perfect. Imagine the display-controller is
+ * currently scanning out a new image and we call drmModeSetCrtc()
+ * simultaneously. It will then have the same effect as if we used a single
+ * buffer and we get some tearing. But, the chance that this happens is a lot
+ * less likely as with a single-buffer. This is because there is a long period
+ * between each frame called vertical-blank where the display-controller does
+ * not perform a scanout. If we swap the buffers in this period, we have the
+ * guarantee that there will be no tearing. See the modeset-vsync.c example if
+ * you want to know how you can guarantee that the swap takes place at a
+ * vertical-sync.
+ */
+
+static void modeset_draw(int fd)
+{
+	uint8_t r, g, b;
+	bool r_up, g_up, b_up;
+	unsigned int i, j, k, off;
+	struct modeset_dev *iter;
+	struct modeset_buf *buf;
+	int ret;
+
+	srand(time(NULL));
+	r = rand() % 0xff;
+	g = rand() % 0xff;
+	b = rand() % 0xff;
+	r_up = g_up = b_up = true;
+
+	for (i = 0; i < 50; ++i) {
+		r = next_color(&r_up, r, 20);
+		g = next_color(&g_up, g, 10);
+		b = next_color(&b_up, b, 5);
+
+		for (iter = modeset_list; iter; iter = iter->next) {
+			buf = &iter->bufs[iter->front_buf ^ 1];
+			for (j = 0; j < buf->height; ++j) {
+				for (k = 0; k < buf->width; ++k) {
+					off = buf->stride * j + k * 4;
+					*(uint32_t*)&buf->map[off] =
+						     (r << 16) | (g << 8) | b;
+				}
+			}
+
+			ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,
+					     &iter->conn, 1, &iter->mode);
+			if (ret)
+				fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",
+					iter->conn, errno);
+			else
+				iter->front_buf ^= 1;
+		}
+
+		usleep(100000);
+	}
+}
+
+/*
+ * modeset_cleanup() stays the same as before. But it now calls
+ * modeset_destroy_fb() instead of accessing the framebuffers directly.
+ */
+
+static void modeset_cleanup(int fd)
+{
+	struct modeset_dev *iter;
+
+	while (modeset_list) {
+		/* remove from global list */
+		iter = modeset_list;
+		modeset_list = iter->next;
+
+		/* restore saved CRTC configuration */
+		drmModeSetCrtc(fd,
+			       iter->saved_crtc->crtc_id,
+			       iter->saved_crtc->buffer_id,
+			       iter->saved_crtc->x,
+			       iter->saved_crtc->y,
+			       &iter->conn,
+			       1,
+			       &iter->saved_crtc->mode);
+		drmModeFreeCrtc(iter->saved_crtc);
+
+		/* destroy framebuffers */
+		modeset_destroy_fb(fd, &iter->bufs[1]);
+		modeset_destroy_fb(fd, &iter->bufs[0]);
+
+		/* free allocated memory */
+		free(iter);
+	}
+}
+
+/*
+ * This was a very short extension to the basic modesetting example that shows
+ * how double-buffering is implemented. Double-buffering is the de-facto
+ * standard in any graphics application so any other example will be based on
+ * this. It is important to understand the ideas behind it as the code is pretty
+ * easy and short compared to modeset.c.
+ *
+ * Double-buffering doesn't solve all problems. Vsync'ed page-flips solve most
+ * of the problems that still occur, but has problems on it's own (see
+ * modeset-vsync.c for a discussion).
+ *
+ * If you want more code, I can recommend reading the source-code of:
+ *  - plymouth (which uses dumb-buffers like this example; very easy to understand)
+ *  - kmscon (which uses libuterm to do this)
+ *  - wayland (very sophisticated DRM renderer; hard to understand fully as it
+ *             uses more complicated techniques like DRM planes)
+ *  - xserver (very hard to understand as it is split across many files/projects)
+ *
+ * Any feedback is welcome. Feel free to use this code freely for your own
+ * documentation or projects.
+ *
+ *  - Hosted on http://github.com/dvdhrm/docs
+ *  - Written by David Rheinsberg <david.rheinsberg@gmail.com>
+ */

+ 773 - 0
package/starfive/drm_test/src/dvdhrm/modeset-vsync.c

@@ -0,0 +1,773 @@
+/*
+ * modeset - DRM Double-Buffered VSync'ed Modesetting Example
+ *
+ * Written 2012 by David Rheinsberg <david.rheinsberg@gmail.com>
+ * Dedicated to the Public Domain.
+ */
+
+/*
+ * DRM Double-Buffered VSync'ed Modesetting Howto
+ * This example extends modeset-double-buffered.c and introduces page-flips
+ * synced with vertical-blanks (vsync'ed). A vertical-blank is the time-period
+ * when a display-controller pauses from scanning out the framebuffer. After the
+ * vertical-blank is over, the framebuffer is again scanned out line by line and
+ * followed again by a vertical-blank.
+ *
+ * Vertical-blanks are important when changing a framebuffer. We already
+ * introduced double-buffering, so this example shows how we can flip the
+ * buffers during a vertical blank and _not_ during the scanout period.
+ *
+ * This example assumes that you are familiar with modeset-double-buffered. Only
+ * the differences between both files are highlighted here.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct modeset_buf;
+struct modeset_dev;
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev);
+static int modeset_create_fb(int fd, struct modeset_buf *buf);
+static void modeset_destroy_fb(int fd, struct modeset_buf *buf);
+static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev);
+static int modeset_open(int *out, const char *node);
+static int modeset_prepare(int fd);
+static void modeset_draw(int fd);
+static void modeset_draw_dev(int fd, struct modeset_dev *dev);
+static void modeset_cleanup(int fd);
+
+/*
+ * modeset_open() stays the same.
+ */
+
+static int modeset_open(int *out, const char *node)
+{
+	int fd, ret;
+	uint64_t has_dumb;
+
+	fd = open(node, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		ret = -errno;
+		fprintf(stderr, "cannot open '%s': %m\n", node);
+		return ret;
+	}
+
+	if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 ||
+	    !has_dumb) {
+		fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
+			node);
+		close(fd);
+		return -EOPNOTSUPP;
+	}
+
+	*out = fd;
+	return 0;
+}
+
+/*
+ * modeset_buf and modeset_dev stay mostly the same. But 6 new fields are added
+ * to modeset_dev: r, g, b, r_up, g_up, b_up. They are used to compute the
+ * current color that is drawn on this output device. You can ignore them as
+ * they aren't important for this example.
+ * The modeset-double-buffered.c example used exactly the same fields but as
+ * local variables in modeset_draw().
+ *
+ * The \pflip_pending variable is true when a page-flip is currently pending,
+ * that is, the kernel will flip buffers on the next vertical blank. The
+ * \cleanup variable is true if the device is currently cleaned up and no more
+ * pageflips should be scheduled. They are used to synchronize the cleanup
+ * routines.
+ */
+
+struct modeset_buf {
+	uint32_t width;
+	uint32_t height;
+	uint32_t stride;
+	uint32_t size;
+	uint32_t handle;
+	uint8_t *map;
+	uint32_t fb;
+};
+
+struct modeset_dev {
+	struct modeset_dev *next;
+
+	unsigned int front_buf;
+	struct modeset_buf bufs[2];
+
+	drmModeModeInfo mode;
+	uint32_t conn;
+	uint32_t crtc;
+	drmModeCrtc *saved_crtc;
+
+	bool pflip_pending;
+	bool cleanup;
+
+	uint8_t r, g, b;
+	bool r_up, g_up, b_up;
+};
+
+static struct modeset_dev *modeset_list = NULL;
+
+/*
+ * modeset_prepare() stays the same.
+ */
+
+static int modeset_prepare(int fd)
+{
+	drmModeRes *res;
+	drmModeConnector *conn;
+	unsigned int i;
+	struct modeset_dev *dev;
+	int ret;
+
+	/* retrieve resources */
+	res = drmModeGetResources(fd);
+	if (!res) {
+		fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",
+			errno);
+		return -errno;
+	}
+
+	/* iterate all connectors */
+	for (i = 0; i < res->count_connectors; ++i) {
+		/* get information for each connector */
+		conn = drmModeGetConnector(fd, res->connectors[i]);
+		if (!conn) {
+			fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
+				i, res->connectors[i], errno);
+			continue;
+		}
+
+		/* create a device structure */
+		dev = malloc(sizeof(*dev));
+		memset(dev, 0, sizeof(*dev));
+		dev->conn = conn->connector_id;
+
+		/* call helper function to prepare this connector */
+		ret = modeset_setup_dev(fd, res, conn, dev);
+		if (ret) {
+			if (ret != -ENOENT) {
+				errno = -ret;
+				fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n",
+					i, res->connectors[i], errno);
+			}
+			free(dev);
+			drmModeFreeConnector(conn);
+			continue;
+		}
+
+		/* free connector data and link device into global list */
+		drmModeFreeConnector(conn);
+		dev->next = modeset_list;
+		modeset_list = dev;
+	}
+
+	/* free resources again */
+	drmModeFreeResources(res);
+	return 0;
+}
+
+/*
+ * modeset_setup_dev() stays the same.
+ */
+
+static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev)
+{
+	int ret;
+
+	/* check if a monitor is connected */
+	if (conn->connection != DRM_MODE_CONNECTED) {
+		fprintf(stderr, "ignoring unused connector %u\n",
+			conn->connector_id);
+		return -ENOENT;
+	}
+
+	/* check if there is at least one valid mode */
+	if (conn->count_modes == 0) {
+		fprintf(stderr, "no valid mode for connector %u\n",
+			conn->connector_id);
+		return -EFAULT;
+	}
+
+	/* copy the mode information into our device structure and into both
+	 * buffers */
+	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
+	dev->bufs[0].width = conn->modes[0].hdisplay;
+	dev->bufs[0].height = conn->modes[0].vdisplay;
+	dev->bufs[1].width = conn->modes[0].hdisplay;
+	dev->bufs[1].height = conn->modes[0].vdisplay;
+	fprintf(stderr, "mode for connector %u is %ux%u\n",
+		conn->connector_id, dev->bufs[0].width, dev->bufs[0].height);
+
+	/* find a crtc for this connector */
+	ret = modeset_find_crtc(fd, res, conn, dev);
+	if (ret) {
+		fprintf(stderr, "no valid crtc for connector %u\n",
+			conn->connector_id);
+		return ret;
+	}
+
+	/* create framebuffer #1 for this CRTC */
+	ret = modeset_create_fb(fd, &dev->bufs[0]);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer for connector %u\n",
+			conn->connector_id);
+		return ret;
+	}
+
+	/* create framebuffer #2 for this CRTC */
+	ret = modeset_create_fb(fd, &dev->bufs[1]);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer for connector %u\n",
+			conn->connector_id);
+		modeset_destroy_fb(fd, &dev->bufs[0]);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * modeset_find_crtc() stays the same.
+ */
+
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev)
+{
+	drmModeEncoder *enc;
+	unsigned int i, j;
+	int32_t crtc;
+	struct modeset_dev *iter;
+
+	/* first try the currently conected encoder+crtc */
+	if (conn->encoder_id)
+		enc = drmModeGetEncoder(fd, conn->encoder_id);
+	else
+		enc = NULL;
+
+	if (enc) {
+		if (enc->crtc_id) {
+			crtc = enc->crtc_id;
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc == crtc) {
+					crtc = -1;
+					break;
+				}
+			}
+
+			if (crtc >= 0) {
+				drmModeFreeEncoder(enc);
+				dev->crtc = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	/* If the connector is not currently bound to an encoder or if the
+	 * encoder+crtc is already used by another connector (actually unlikely
+	 * but lets be safe), iterate all other available encoders to find a
+	 * matching CRTC. */
+	for (i = 0; i < conn->count_encoders; ++i) {
+		enc = drmModeGetEncoder(fd, conn->encoders[i]);
+		if (!enc) {
+			fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
+				i, conn->encoders[i], errno);
+			continue;
+		}
+
+		/* iterate all global CRTCs */
+		for (j = 0; j < res->count_crtcs; ++j) {
+			/* check whether this CRTC works with the encoder */
+			if (!(enc->possible_crtcs & (1 << j)))
+				continue;
+
+			/* check that no other device already uses this CRTC */
+			crtc = res->crtcs[j];
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc == crtc) {
+					crtc = -1;
+					break;
+				}
+			}
+
+			/* we have found a CRTC, so save it and return */
+			if (crtc >= 0) {
+				drmModeFreeEncoder(enc);
+				dev->crtc = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	fprintf(stderr, "cannot find suitable CRTC for connector %u\n",
+		conn->connector_id);
+	return -ENOENT;
+}
+
+/*
+ * modeset_create_fb() stays the same.
+ */
+
+static int modeset_create_fb(int fd, struct modeset_buf *buf)
+{
+	struct drm_mode_create_dumb creq;
+	struct drm_mode_destroy_dumb dreq;
+	struct drm_mode_map_dumb mreq;
+	int ret;
+
+	/* create dumb buffer */
+	memset(&creq, 0, sizeof(creq));
+	creq.width = buf->width;
+	creq.height = buf->height;
+	creq.bpp = 32;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+	if (ret < 0) {
+		fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
+			errno);
+		return -errno;
+	}
+	buf->stride = creq.pitch;
+	buf->size = creq.size;
+	buf->handle = creq.handle;
+
+	/* create framebuffer object for the dumb-buffer */
+	ret = drmModeAddFB(fd, buf->width, buf->height, 32, 32, buf->stride,
+			   buf->handle, &buf->fb);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_destroy;
+	}
+
+	/* prepare buffer for memory mapping */
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.handle = buf->handle;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+	if (ret) {
+		fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* perform actual memory mapping */
+	buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+		        fd, mreq.offset);
+	if (buf->map == MAP_FAILED) {
+		fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* clear the framebuffer to 0 */
+	memset(buf->map, 0, buf->size);
+
+	return 0;
+
+err_fb:
+	drmModeRmFB(fd, buf->fb);
+err_destroy:
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = buf->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+	return ret;
+}
+
+/*
+ * modeset_destroy_fb() stays the same.
+ */
+
+static void modeset_destroy_fb(int fd, struct modeset_buf *buf)
+{
+	struct drm_mode_destroy_dumb dreq;
+
+	/* unmap buffer */
+	munmap(buf->map, buf->size);
+
+	/* delete framebuffer */
+	drmModeRmFB(fd, buf->fb);
+
+	/* delete dumb buffer */
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = buf->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+}
+
+/*
+ * main() also stays the same.
+ */
+
+int main(int argc, char **argv)
+{
+	int ret, fd;
+	const char *card;
+	struct modeset_dev *iter;
+	struct modeset_buf *buf;
+
+	/* check which DRM device to open */
+	if (argc > 1)
+		card = argv[1];
+	else
+		card = "/dev/dri/card0";
+
+	fprintf(stderr, "using card '%s'\n", card);
+
+	/* open the DRM device */
+	ret = modeset_open(&fd, card);
+	if (ret)
+		goto out_return;
+
+	/* prepare all connectors and CRTCs */
+	ret = modeset_prepare(fd);
+	if (ret)
+		goto out_close;
+
+	/* perform actual modesetting on each found connector+CRTC */
+	for (iter = modeset_list; iter; iter = iter->next) {
+		iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
+		buf = &iter->bufs[iter->front_buf];
+		ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0,
+				     &iter->conn, 1, &iter->mode);
+		if (ret)
+			fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
+				iter->conn, errno);
+	}
+
+	/* draw some colors for 5seconds */
+	modeset_draw(fd);
+
+	/* cleanup everything */
+	modeset_cleanup(fd);
+
+	ret = 0;
+
+out_close:
+	close(fd);
+out_return:
+	if (ret) {
+		errno = -ret;
+		fprintf(stderr, "modeset failed with error %d: %m\n", errno);
+	} else {
+		fprintf(stderr, "exiting\n");
+	}
+	return ret;
+}
+
+/*
+ * modeset_page_flip_event() is a callback-helper for modeset_draw() below.
+ * Please see modeset_draw() for more information.
+ *
+ * Note that this does nothing if the device is currently cleaned up. This
+ * allows to wait for outstanding page-flips during cleanup.
+ */
+
+static void modeset_page_flip_event(int fd, unsigned int frame,
+				    unsigned int sec, unsigned int usec,
+				    void *data)
+{
+	struct modeset_dev *dev = data;
+
+	dev->pflip_pending = false;
+	if (!dev->cleanup)
+		modeset_draw_dev(fd, dev);
+}
+
+/*
+ * modeset_draw() changes heavily from all previous examples. The rendering has
+ * moved into another helper modeset_draw_dev() below, but modeset_draw() is now
+ * responsible of controlling when we have to redraw the outputs.
+ *
+ * So what we do: first redraw all outputs. We initialize the r/g/b/_up
+ * variables of each output first, although, you can safely ignore these.
+ * They're solely used to compute the next color. Then we call
+ * modeset_draw_dev() for each output. This function _always_ redraws the output
+ * and schedules a buffer-swap/flip for the next vertical-blank.
+ * We now have to wait for each vertical-blank to happen so we can draw the next
+ * frame. If a vblank happens, we simply call modeset_draw_dev() again and wait
+ * for the next vblank.
+ *
+ * Note: Different monitors can have different refresh-rates. That means, a
+ * vblank event is always assigned to a CRTC. Hence, we get different vblank
+ * events for each CRTC/modeset_dev that we use. This also means, that our
+ * framerate-controlled color-morphing is different on each monitor. If you want
+ * exactly the same frame on all monitors, we would have to share the
+ * color-values between all devices. However, for simplicity reasons, we don't
+ * do this here.
+ *
+ * So the last piece missing is how we get vblank events. libdrm provides
+ * drmWaitVBlank(), however, we aren't interested in _all_ vblanks, but only in
+ * the vblanks for our page-flips. We could use drmWaitVBlank() but there is a
+ * more convenient way: drmModePageFlip()
+ * drmModePageFlip() schedules a buffer-flip for the next vblank and then
+ * notifies us about it. It takes a CRTC-id, fb-id and an arbitrary
+ * data-pointer and then schedules the page-flip. This is fully asynchronous and
+ * returns immediately.
+ * When the page-flip happens, the DRM-fd will become readable and we can call
+ * drmHandleEvent(). This will read all vblank/page-flip events and call our
+ * modeset_page_flip_event() callback with the data-pointer that we passed to
+ * drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame
+ * is rendered..
+ *
+ *
+ * So modeset_draw() is reponsible of waiting for the page-flip/vblank events
+ * for _all_ currently used output devices and schedule a redraw for them. We
+ * could easily do this in a while (1) { drmHandleEvent() } loop, however, this
+ * example shows how you can use the DRM-fd to integrate this into your own
+ * main-loop. If you aren't familiar with select(), poll() or epoll, please read
+ * it up somewhere else. There is plenty of documentation elsewhere on the
+ * internet.
+ *
+ * So what we do is adding the DRM-fd and the keyboard-input-fd (more precisely:
+ * the stdin FD) to a select-set and then we wait on this set. If the DRM-fd is
+ * readable, we call drmHandleEvents() to handle the page-flip events. If the
+ * input-fd is readable, we exit. So on any keyboard input we exit this loop
+ * (you need to press RETURN after each keyboard input to make this work).
+ */
+
+static void modeset_draw(int fd)
+{
+	int ret;
+	fd_set fds;
+	time_t start, cur;
+	struct timeval v;
+	drmEventContext ev;
+	struct modeset_dev *iter;
+
+	/* init variables */
+	srand(time(&start));
+	FD_ZERO(&fds);
+	memset(&v, 0, sizeof(v));
+	memset(&ev, 0, sizeof(ev));
+	/* Set this to only the latest version you support. Version 2
+	 * introduced the page_flip_handler, so we use that. */
+	ev.version = 2;
+	ev.page_flip_handler = modeset_page_flip_event;
+
+	/* redraw all outputs */
+	for (iter = modeset_list; iter; iter = iter->next) {
+		iter->r = rand() % 0xff;
+		iter->g = rand() % 0xff;
+		iter->b = rand() % 0xff;
+		iter->r_up = iter->g_up = iter->b_up = true;
+
+		modeset_draw_dev(fd, iter);
+	}
+
+	/* wait 5s for VBLANK or input events */
+	while (time(&cur) < start + 5) {
+		FD_SET(0, &fds);
+		FD_SET(fd, &fds);
+		v.tv_sec = start + 5 - cur;
+
+		ret = select(fd + 1, &fds, NULL, NULL, &v);
+		if (ret < 0) {
+			fprintf(stderr, "select() failed with %d: %m\n", errno);
+			break;
+		} else if (FD_ISSET(0, &fds)) {
+			fprintf(stderr, "exit due to user-input\n");
+			break;
+		} else if (FD_ISSET(fd, &fds)) {
+			drmHandleEvent(fd, &ev);
+		}
+	}
+}
+
+/*
+ * A short helper function to compute a changing color value. No need to
+ * understand it.
+ */
+
+static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
+{
+	uint8_t next;
+
+	next = cur + (*up ? 1 : -1) * (rand() % mod);
+	if ((*up && next < cur) || (!*up && next > cur)) {
+		*up = !*up;
+		next = cur;
+	}
+
+	return next;
+}
+
+/*
+ * modeset_draw_dev() is a new function that redraws the screen of a single
+ * output. It takes the DRM-fd and the output devices as arguments, redraws a
+ * new frame and schedules the page-flip for the next vsync.
+ *
+ * This function does the same as modeset_draw() did in the previous examples
+ * but only for a single output device now.
+ * After we are done rendering a frame, we have to swap the buffers. Instead of
+ * calling drmModeSetCrtc() as we did previously, we now want to schedule this
+ * page-flip for the next vertical-blank (vblank). We use drmModePageFlip() for
+ * this. It takes the CRTC-id and FB-id and will asynchronously swap the buffers
+ * when the next vblank occurs. Note that this is done by the kernel, so neither
+ * a thread is started nor any other magic is done in libdrm.
+ * The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a
+ * page-flip event on the DRM-fd when the page-flip happened. The last argument
+ * is a data-pointer that is returned with this event.
+ * If we wouldn't pass this flag, we would not get notified when the page-flip
+ * happened.
+ *
+ * Note: If you called drmModePageFlip() and directly call it again, it will
+ * return EBUSY if the page-flip hasn't happened in between. So you almost
+ * always want to pass DRM_MODE_PAGE_FLIP_EVENT to get notified when the
+ * page-flip happens so you know when to render the next frame.
+ * If you scheduled a page-flip but call drmModeSetCrtc() before the next
+ * vblank, then the scheduled page-flip will become a no-op. However, you will
+ * still get notified when it happens and you still cannot call
+ * drmModePageFlip() again until it finished. So to sum it up: there is no way
+ * to effectively cancel a page-flip.
+ *
+ * If you wonder why drmModePageFlip() takes fewer arguments than
+ * drmModeSetCrtc(), then you should take into account, that drmModePageFlip()
+ * reuses the arguments from drmModeSetCrtc(). So things like connector-ids,
+ * x/y-offsets and so on have to be set via drmModeSetCrtc() first before you
+ * can use drmModePageFlip()! We do this in main() as all the previous examples
+ * did, too.
+ */
+
+static void modeset_draw_dev(int fd, struct modeset_dev *dev)
+{
+	struct modeset_buf *buf;
+	unsigned int j, k, off;
+	int ret;
+
+	dev->r = next_color(&dev->r_up, dev->r, 20);
+	dev->g = next_color(&dev->g_up, dev->g, 10);
+	dev->b = next_color(&dev->b_up, dev->b, 5);
+
+	buf = &dev->bufs[dev->front_buf ^ 1];
+	for (j = 0; j < buf->height; ++j) {
+		for (k = 0; k < buf->width; ++k) {
+			off = buf->stride * j + k * 4;
+			*(uint32_t*)&buf->map[off] =
+				     (dev->r << 16) | (dev->g << 8) | dev->b;
+		}
+	}
+
+	ret = drmModePageFlip(fd, dev->crtc, buf->fb,
+			      DRM_MODE_PAGE_FLIP_EVENT, dev);
+	if (ret) {
+		fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n",
+			dev->conn, errno);
+	} else {
+		dev->front_buf ^= 1;
+		dev->pflip_pending = true;
+	}
+}
+
+/*
+ * modeset_cleanup() stays mostly the same. However, before resetting a CRTC to
+ * its previous state, we wait for any outstanding page-flip to complete. This
+ * isn't strictly neccessary, however, some DRM drivers are known to be buggy if
+ * we call drmModeSetCrtc() if there is a pending page-flip.
+ * Furthermore, we don't want any pending page-flips when our application exist.
+ * Because another application might pick up the DRM device and try to schedule
+ * their own page-flips which might then fail as long as our page-flip is
+ * pending.
+ * So lets be safe here and simply wait for any page-flips to complete. This is
+ * a blocking operation, but it's mostly just <16ms so we can ignore that.
+ */
+
+static void modeset_cleanup(int fd)
+{
+	struct modeset_dev *iter;
+	drmEventContext ev;
+	int ret;
+
+	/* init variables */
+	memset(&ev, 0, sizeof(ev));
+	ev.version = DRM_EVENT_CONTEXT_VERSION;
+	ev.page_flip_handler = modeset_page_flip_event;
+
+	while (modeset_list) {
+		/* remove from global list */
+		iter = modeset_list;
+		modeset_list = iter->next;
+
+		/* if a pageflip is pending, wait for it to complete */
+		iter->cleanup = true;
+		fprintf(stderr, "wait for pending page-flip to complete...\n");
+		while (iter->pflip_pending) {
+			ret = drmHandleEvent(fd, &ev);
+			if (ret)
+				break;
+		}
+
+		/* restore saved CRTC configuration */
+		if (!iter->pflip_pending)
+			drmModeSetCrtc(fd,
+				       iter->saved_crtc->crtc_id,
+				       iter->saved_crtc->buffer_id,
+				       iter->saved_crtc->x,
+				       iter->saved_crtc->y,
+				       &iter->conn,
+				       1,
+				       &iter->saved_crtc->mode);
+		drmModeFreeCrtc(iter->saved_crtc);
+
+		/* destroy framebuffers */
+		modeset_destroy_fb(fd, &iter->bufs[1]);
+		modeset_destroy_fb(fd, &iter->bufs[0]);
+
+		/* free allocated memory */
+		free(iter);
+	}
+}
+
+/*
+ * This example shows how to make the kernel handle page-flips and how to wait
+ * for them in user-space. The select() example here should show you how you can
+ * integrate these loops into your own applications without the need for a
+ * separate modesetting thread.
+ *
+ * However, please note that vsync'ed double-buffering doesn't solve all
+ * problems. Imagine that you cannot render a frame fast enough to satisfy all
+ * vertical-blanks. In this situation, you don't want to wait after scheduling a
+ * page-flip until the vblank happens to draw the next frame. A solution for
+ * this is triple-buffering. It should be farily easy to extend this example to
+ * use triple-buffering, but feel free to contact me if you have any questions
+ * about it.
+ * Also note that the DRM kernel API is quite limited if you want to reschedule
+ * page-flips that haven't happened, yet. You cannot call drmModePageFlip()
+ * twice in a single scanout-period. The behavior of drmModeSetCrtc() while a
+ * page-flip is pending might also be unexpected.
+ * Unfortunately, there is no ultimate solution to all modesetting problems.
+ * This example shows the tools to do vsync'ed page-flips, however, it depends
+ * on your use-case how you have to implement it.
+ *
+ * If you want more code, I can recommend reading the source-code of:
+ *  - plymouth (which uses dumb-buffers like this example; very easy to understand)
+ *  - kmscon (which uses libuterm to do this)
+ *  - wayland (very sophisticated DRM renderer; hard to understand fully as it
+ *             uses more complicated techniques like DRM planes)
+ *  - xserver (very hard to understand as it is split across many files/projects)
+ *
+ * Any feedback is welcome. Feel free to use this code freely for your own
+ * documentation or projects.
+ *
+ *  - Hosted on http://github.com/dvdhrm/docs
+ *  - Written by David Rheinsberg <david.rheinsberg@gmail.com>
+ */

+ 746 - 0
package/starfive/drm_test/src/dvdhrm/modeset.c

@@ -0,0 +1,746 @@
+/*
+ * modeset - DRM Modesetting Example
+ *
+ * Written 2012 by David Rheinsberg <david.rheinsberg@gmail.com>
+ * Dedicated to the Public Domain.
+ */
+
+/*
+ * DRM Modesetting Howto
+ * This document describes the DRM modesetting API. Before we can use the DRM
+ * API, we have to include xf86drm.h and xf86drmMode.h. Both are provided by
+ * libdrm which every major distribution ships by default. It has no other
+ * dependencies and is pretty small.
+ *
+ * Please ignore all forward-declarations of functions which are used later. I
+ * reordered the functions so you can read this document from top to bottom. If
+ * you reimplement it, you would probably reorder the functions to avoid all the
+ * nasty forward declarations.
+ *
+ * For easier reading, we ignore all memory-allocation errors of malloc() and
+ * friends here. However, we try to correctly handle all other kinds of errors
+ * that may occur.
+ *
+ * All functions and global variables are prefixed with "modeset_*" in this
+ * file. So it should be clear whether a function is a local helper or if it is
+ * provided by some external library.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct modeset_dev;
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev);
+static int modeset_create_fb(int fd, struct modeset_dev *dev);
+static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev);
+static int modeset_open(int *out, const char *node);
+static int modeset_prepare(int fd);
+static void modeset_draw(void);
+static void modeset_cleanup(int fd);
+
+/*
+ * When the linux kernel detects a graphics-card on your machine, it loads the
+ * correct device driver (located in kernel-tree at ./drivers/gpu/drm/<xy>) and
+ * provides two character-devices to control it. Udev (or whatever hotplugging
+ * application you use) will create them as:
+ *     /dev/dri/card0
+ *     /dev/dri/controlID64
+ * We only need the first one. You can hard-code this path into your application
+ * like we do here, but it is recommended to use libudev with real hotplugging
+ * and multi-seat support. However, this is beyond the scope of this document.
+ * Also note that if you have multiple graphics-cards, there may also be
+ * /dev/dri/card1, /dev/dri/card2, ...
+ *
+ * We simply use /dev/dri/card0 here but the user can specify another path on
+ * the command line.
+ *
+ * modeset_open(out, node): This small helper function opens the DRM device
+ * which is given as @node. The new fd is stored in @out on success. On failure,
+ * a negative error code is returned.
+ * After opening the file, we also check for the DRM_CAP_DUMB_BUFFER capability.
+ * If the driver supports this capability, we can create simple memory-mapped
+ * buffers without any driver-dependent code. As we want to avoid any radeon,
+ * nvidia, intel, etc. specific code, we depend on DUMB_BUFFERs here.
+ */
+
+static int modeset_open(int *out, const char *node)
+{
+	int fd, ret;
+	uint64_t has_dumb;
+
+	fd = open(node, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		ret = -errno;
+		fprintf(stderr, "cannot open '%s': %m\n", node);
+		return ret;
+	}
+
+	if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 ||
+	    !has_dumb) {
+		fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
+			node);
+		close(fd);
+		return -EOPNOTSUPP;
+	}
+
+	*out = fd;
+	return 0;
+}
+
+/*
+ * As a next step we need to find our available display devices. libdrm provides
+ * a drmModeRes structure that contains all the needed information. We can
+ * retrieve it via drmModeGetResources(fd) and free it via
+ * drmModeFreeResources(res) again.
+ *
+ * A physical connector on your graphics card is called a "connector". You can
+ * plug a monitor into it and control what is displayed. We are definitely
+ * interested in what connectors are currently used, so we simply iterate
+ * through the list of connectors and try to display a test-picture on each
+ * available monitor.
+ * However, this isn't as easy as it sounds. First, we need to check whether the
+ * connector is actually used (a monitor is plugged in and turned on). Then we
+ * need to find a CRTC that can control this connector. CRTCs are described
+ * later on. After that we create a framebuffer object. If we have all this, we
+ * can mmap() the framebuffer and draw a test-picture into it. Then we can tell
+ * the DRM device to show the framebuffer on the given CRTC with the selected
+ * connector.
+ *
+ * As we want to draw moving pictures on the framebuffer, we actually have to
+ * remember all these settings. Therefore, we create one "struct modeset_dev"
+ * object for each connector+crtc+framebuffer pair that we successfully
+ * initialized and push it into the global device-list.
+ *
+ * Each field of this structure is described when it is first used. But as a
+ * summary:
+ * "struct modeset_dev" contains: {
+ *  - @next: points to the next device in the single-linked list
+ *
+ *  - @width: width of our buffer object
+ *  - @height: height of our buffer object
+ *  - @stride: stride value of our buffer object
+ *  - @size: size of the memory mapped buffer
+ *  - @handle: a DRM handle to the buffer object that we can draw into
+ *  - @map: pointer to the memory mapped buffer
+ *
+ *  - @mode: the display mode that we want to use
+ *  - @fb: a framebuffer handle with our buffer object as scanout buffer
+ *  - @conn: the connector ID that we want to use with this buffer
+ *  - @crtc: the crtc ID that we want to use with this connector
+ *  - @saved_crtc: the configuration of the crtc before we changed it. We use it
+ *                 so we can restore the same mode when we exit.
+ * }
+ */
+
+struct modeset_dev {
+	struct modeset_dev *next;
+
+	uint32_t width;
+	uint32_t height;
+	uint32_t stride;
+	uint32_t size;
+	uint32_t handle;
+	uint8_t *map;
+
+	drmModeModeInfo mode;
+	uint32_t fb;
+	uint32_t conn;
+	uint32_t crtc;
+	drmModeCrtc *saved_crtc;
+};
+
+static struct modeset_dev *modeset_list = NULL;
+
+/*
+ * So as next step we need to actually prepare all connectors that we find. We
+ * do this in this little helper function:
+ *
+ * modeset_prepare(fd): This helper function takes the DRM fd as argument and
+ * then simply retrieves the resource-info from the device. It then iterates
+ * through all connectors and calls other helper functions to initialize this
+ * connector (described later on).
+ * If the initialization was successful, we simply add this object as new device
+ * into the global modeset device list.
+ *
+ * The resource-structure contains a list of all connector-IDs. We use the
+ * helper function drmModeGetConnector() to retrieve more information on each
+ * connector. After we are done with it, we free it again with
+ * drmModeFreeConnector().
+ * Our helper modeset_setup_dev() returns -ENOENT if the connector is currently
+ * unused and no monitor is plugged in. So we can ignore this connector.
+ */
+
+static int modeset_prepare(int fd)
+{
+	drmModeRes *res;
+	drmModeConnector *conn;
+	unsigned int i;
+	struct modeset_dev *dev;
+	int ret;
+
+	/* retrieve resources */
+	res = drmModeGetResources(fd);
+	if (!res) {
+		fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",
+			errno);
+		return -errno;
+	}
+
+	/* iterate all connectors */
+	for (i = 0; i < res->count_connectors; ++i) {
+		/* get information for each connector */
+		conn = drmModeGetConnector(fd, res->connectors[i]);
+		if (!conn) {
+			fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
+				i, res->connectors[i], errno);
+			continue;
+		}
+
+		/* create a device structure */
+		dev = malloc(sizeof(*dev));
+		memset(dev, 0, sizeof(*dev));
+		dev->conn = conn->connector_id;
+
+		/* call helper function to prepare this connector */
+		ret = modeset_setup_dev(fd, res, conn, dev);
+		if (ret) {
+			if (ret != -ENOENT) {
+				errno = -ret;
+				fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n",
+					i, res->connectors[i], errno);
+			}
+			free(dev);
+			drmModeFreeConnector(conn);
+			continue;
+		}
+
+		/* free connector data and link device into global list */
+		drmModeFreeConnector(conn);
+		dev->next = modeset_list;
+		modeset_list = dev;
+	}
+
+	/* free resources again */
+	drmModeFreeResources(res);
+	return 0;
+}
+
+/*
+ * Now we dig deeper into setting up a single connector. As described earlier,
+ * we need to check several things first:
+ *   * If the connector is currently unused, that is, no monitor is plugged in,
+ *     then we can ignore it.
+ *   * We have to find a suitable resolution and refresh-rate. All this is
+ *     available in drmModeModeInfo structures saved for each crtc. We simply
+ *     use the first mode that is available. This is always the mode with the
+ *     highest resolution.
+ *     A more sophisticated mode-selection should be done in real applications,
+ *     though.
+ *   * Then we need to find an CRTC that can drive this connector. A CRTC is an
+ *     internal resource of each graphics-card. The number of CRTCs controls how
+ *     many connectors can be controlled indepedently. That is, a graphics-cards
+ *     may have more connectors than CRTCs, which means, not all monitors can be
+ *     controlled independently.
+ *     There is actually the possibility to control multiple connectors via a
+ *     single CRTC if the monitors should display the same content. However, we
+ *     do not make use of this here.
+ *     So think of connectors as pipelines to the connected monitors and the
+ *     CRTCs are the controllers that manage which data goes to which pipeline.
+ *     If there are more pipelines than CRTCs, then we cannot control all of
+ *     them at the same time.
+ *   * We need to create a framebuffer for this connector. A framebuffer is a
+ *     memory buffer that we can write XRGB32 data into. So we use this to
+ *     render our graphics and then the CRTC can scan-out this data from the
+ *     framebuffer onto the monitor.
+ */
+
+static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev)
+{
+	int ret;
+
+	/* check if a monitor is connected */
+	if (conn->connection != DRM_MODE_CONNECTED) {
+		fprintf(stderr, "ignoring unused connector %u\n",
+			conn->connector_id);
+		return -ENOENT;
+	}
+
+	/* check if there is at least one valid mode */
+	if (conn->count_modes == 0) {
+		fprintf(stderr, "no valid mode for connector %u\n",
+			conn->connector_id);
+		return -EFAULT;
+	}
+
+	/* copy the mode information into our device structure */
+	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
+	dev->width = conn->modes[0].hdisplay;
+	dev->height = conn->modes[0].vdisplay;
+	fprintf(stderr, "mode for connector %u is %ux%u\n",
+		conn->connector_id, dev->width, dev->height);
+    drmMsg("[%s,%d]: connector_id=%d\n",__FUNCTION__,__LINE__, conn->connector_id);
+	/* find a crtc for this connector */
+	ret = modeset_find_crtc(fd, res, conn, dev);
+	if (ret) {
+		fprintf(stderr, "no valid crtc for connector %u\n",
+			conn->connector_id);
+		return ret;
+	}
+
+	/* create a framebuffer for this CRTC */
+	ret = modeset_create_fb(fd, dev);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer for connector %u\n",
+			conn->connector_id);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * modeset_find_crtc(fd, res, conn, dev): This small helper tries to find a
+ * suitable CRTC for the given connector. We have actually have to introduce one
+ * more DRM object to make this more clear: Encoders.
+ * Encoders help the CRTC to convert data from a framebuffer into the right
+ * format that can be used for the chosen connector. We do not have to
+ * understand any more of these conversions to make use of it. However, you must
+ * know that each connector has a limited list of encoders that it can use. And
+ * each encoder can only work with a limited list of CRTCs. So what we do is
+ * trying each encoder that is available and looking for a CRTC that this
+ * encoder can work with. If we find the first working combination, we are happy
+ * and write it into the @dev structure.
+ * But before iterating all available encoders, we first try the currently
+ * active encoder+crtc on a connector to avoid a full modeset.
+ *
+ * However, before we can use a CRTC we must make sure that no other device,
+ * that we setup previously, is already using this CRTC. Remember, we can only
+ * drive one connector per CRTC! So we simply iterate through the "modeset_list"
+ * of previously setup devices and check that this CRTC wasn't used before.
+ * Otherwise, we continue with the next CRTC/Encoder combination.
+ */
+
+static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
+			     struct modeset_dev *dev)
+{
+	drmModeEncoder *enc;
+	unsigned int i, j;
+	int32_t crtc;
+	struct modeset_dev *iter;
+    drmMsg("[%s,%d]: connector_id=%d, encoder_id=%d, res->count_crtcs=%d,count_encoders=%d\n",__FUNCTION__,__LINE__, 
+        conn->connector_id,conn->encoder_id, res->count_crtcs,
+        conn->count_encoders);
+
+	/* first try the currently conected encoder+crtc */
+	if (conn->encoder_id)
+		enc = drmModeGetEncoder(fd, conn->encoder_id);
+	else
+		enc = NULL;
+
+	if (enc) {
+        drmMsg("[%s,%d]: crtc_id=%d\n",__FUNCTION__,__LINE__, enc->crtc_id);
+		if (enc->crtc_id) {
+			crtc = enc->crtc_id;
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc == crtc) {
+					crtc = -1;
+					break;
+				}
+			}
+            drmMsg("[%s,%d]: crtc=%d\n",__FUNCTION__,__LINE__, crtc);
+			if (crtc >= 0) {
+				drmModeFreeEncoder(enc);
+				dev->crtc = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	/* If the connector is not currently bound to an encoder or if the
+	 * encoder+crtc is already used by another connector (actually unlikely
+	 * but lets be safe), iterate all other available encoders to find a
+	 * matching CRTC. */
+	for (i = 0; i < conn->count_encoders; ++i) {
+		enc = drmModeGetEncoder(fd, conn->encoders[i]);
+		if (!enc) {
+			fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
+				i, conn->encoders[i], errno);
+			continue;
+		}
+
+		/* iterate all global CRTCs */
+		for (j = 0; j < res->count_crtcs; ++j) {
+			/* check whether this CRTC works with the encoder */
+			if (!(enc->possible_crtcs & (1 << j)))
+				continue;
+
+			/* check that no other device already uses this CRTC */
+			crtc = res->crtcs[j];
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc == crtc) {
+					crtc = -1;
+					break;
+				}
+			}
+
+			/* we have found a CRTC, so save it and return */
+			if (crtc >= 0) {
+				drmModeFreeEncoder(enc);
+				dev->crtc = crtc;
+				return 0;
+			}
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	fprintf(stderr, "cannot find suitable CRTC for connector %u\n",
+		conn->connector_id);
+	return -ENOENT;
+}
+
+/*
+ * modeset_create_fb(fd, dev): After we have found a crtc+connector+mode
+ * combination, we need to actually create a suitable framebuffer that we can
+ * use with it. There are actually two ways to do that:
+ *   * We can create a so called "dumb buffer". This is a buffer that we can
+ *     memory-map via mmap() and every driver supports this. We can use it for
+ *     unaccelerated software rendering on the CPU.
+ *   * We can use libgbm to create buffers available for hardware-acceleration.
+ *     libgbm is an abstraction layer that creates these buffers for each
+ *     available DRM driver. As there is no generic API for this, each driver
+ *     provides its own way to create these buffers.
+ *     We can then use such buffers to create OpenGL contexts with the mesa3D
+ *     library.
+ * We use the first solution here as it is much simpler and doesn't require any
+ * external libraries. However, if you want to use hardware-acceleration via
+ * OpenGL, it is actually pretty easy to create such buffers with libgbm and
+ * libEGL. But this is beyond the scope of this document.
+ *
+ * So what we do is requesting a new dumb-buffer from the driver. We specify the
+ * same size as the current mode that we selected for the connector.
+ * Then we request the driver to prepare this buffer for memory mapping. After
+ * that we perform the actual mmap() call. So we can now access the framebuffer
+ * memory directly via the dev->map memory map.
+ */
+
+static int modeset_create_fb(int fd, struct modeset_dev *dev)
+{
+	struct drm_mode_create_dumb creq;
+	struct drm_mode_destroy_dumb dreq;
+	struct drm_mode_map_dumb mreq;
+	int ret;
+
+	/* create dumb buffer */
+	memset(&creq, 0, sizeof(creq));
+	creq.width = dev->width;
+	creq.height = dev->height;
+	creq.bpp = 32;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+	if (ret < 0) {
+		fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
+			errno);
+		return -errno;
+	}
+	dev->stride = creq.pitch;
+	dev->size = creq.size;
+	dev->handle = creq.handle;
+
+	/* create framebuffer object for the dumb-buffer */
+	ret = drmModeAddFB(fd, dev->width, dev->height, 32, 32, dev->stride,
+			   dev->handle, &dev->fb);
+	if (ret) {
+		fprintf(stderr, "cannot create framebuffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_destroy;
+	}
+    drmMsg("[%s,%d]: dev->fb=%d\n",__FUNCTION__,__LINE__, dev->fb);
+	/* prepare buffer for memory mapping */
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.handle = dev->handle;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+	if (ret) {
+		fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* perform actual memory mapping */
+	dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+		        fd, mreq.offset);
+	if (dev->map == MAP_FAILED) {
+		fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
+			errno);
+		ret = -errno;
+		goto err_fb;
+	}
+
+	/* clear the framebuffer to 0 */
+	memset(dev->map, 0, dev->size);
+
+	return 0;
+
+err_fb:
+	drmModeRmFB(fd, dev->fb);
+err_destroy:
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = dev->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+	return ret;
+}
+
+/*
+ * Finally! We have a connector with a suitable CRTC. We know which mode we want
+ * to use and we have a framebuffer of the correct size that we can write to.
+ * There is nothing special left to do. We only have to program the CRTC to
+ * connect each new framebuffer to each selected connector for each combination
+ * that we saved in the global modeset_list.
+ * This is done with a call to drmModeSetCrtc().
+ *
+ * So we are ready for our main() function. First we check whether the user
+ * specified a DRM device on the command line, otherwise we use the default
+ * /dev/dri/card0. Then we open the device via modeset_open(). modeset_prepare()
+ * prepares all connectors and we can loop over "modeset_list" and call
+ * drmModeSetCrtc() on every CRTC/connector combination.
+ *
+ * But printing empty black pages is boring so we have another helper function
+ * modeset_draw() that draws some colors into the framebuffer for 5 seconds and
+ * then returns. And then we have all the cleanup functions which correctly free
+ * all devices again after we used them. All these functions are described below
+ * the main() function.
+ *
+ * As a side note: drmModeSetCrtc() actually takes a list of connectors that we
+ * want to control with this CRTC. We pass only one connector, though. As
+ * explained earlier, if we used multiple connectors, then all connectors would
+ * have the same controlling framebuffer so the output would be cloned. This is
+ * most often not what you want so we avoid explaining this feature here.
+ * Furthermore, all connectors will have to run with the same mode, which is
+ * also often not guaranteed. So instead, we only use one connector per CRTC.
+ *
+ * Before calling drmModeSetCrtc() we also save the current CRTC configuration.
+ * This is used in modeset_cleanup() to restore the CRTC to the same mode as was
+ * before we changed it.
+ * If we don't do this, the screen will stay blank after we exit until another
+ * application performs modesetting itself.
+ */
+
+int main(int argc, char **argv)
+{
+	int ret, fd;
+	const char *card;
+	struct modeset_dev *iter;
+
+	/* check which DRM device to open */
+	if (argc > 1)
+		card = argv[1];
+	else
+		card = "/dev/dri/card0";
+
+	fprintf(stderr, "using card '%s'\n", card);
+
+	/* open the DRM device */
+	ret = modeset_open(&fd, card);
+	if (ret)
+		goto out_return;
+
+	/* prepare all connectors and CRTCs */
+	ret = modeset_prepare(fd);
+	if (ret)
+		goto out_close;
+
+	/* perform actual modesetting on each found connector+CRTC */
+	for (iter = modeset_list; iter; iter = iter->next) {
+        drmMsg("[%s,%d]: !!!!!!!!!!!errno=%m. iter->fb=%d \n",__FUNCTION__,__LINE__,iter->fb);
+		iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc);
+		ret = drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0,
+				     &iter->conn, 1, &iter->mode);
+		if (ret) {
+			fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
+				iter->conn, errno);
+            modeset_cleanup(fd);
+        }
+	}
+
+	/* draw some colors for 5seconds */
+	modeset_draw();
+
+	/* cleanup everything */
+	modeset_cleanup(fd);
+
+	ret = 0;
+
+out_close:
+	close(fd);
+out_return:
+	if (ret) {
+		errno = -ret;
+		fprintf(stderr, "modeset failed with error %d: %m\n", errno);
+	} else {
+		fprintf(stderr, "exiting\n");
+	}
+	return ret;
+}
+
+/*
+ * A short helper function to compute a changing color value. No need to
+ * understand it.
+ */
+
+static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
+{
+	uint8_t next;
+
+	next = cur + (*up ? 1 : -1) * (rand() % mod);
+	if ((*up && next < cur) || (!*up && next > cur)) {
+		*up = !*up;
+		next = cur;
+	}
+
+	return next;
+}
+
+/*
+ * modeset_draw(): This draws a solid color into all configured framebuffers.
+ * Every 100ms the color changes to a slightly different color so we get some
+ * kind of smoothly changing color-gradient.
+ *
+ * The color calculation can be ignored as it is pretty boring. So the
+ * interesting stuff is iterating over "modeset_list" and then through all lines
+ * and width. We then set each pixel individually to the current color.
+ *
+ * We do this 50 times as we sleep 100ms after each redraw round. This makes
+ * 50*100ms = 5000ms = 5s so it takes about 5seconds to finish this loop.
+ *
+ * Please note that we draw directly into the framebuffer. This means that you
+ * will see flickering as the monitor might refresh while we redraw the screen.
+ * To avoid this you would need to use two framebuffers and a call to
+ * drmModeSetCrtc() to switch between both buffers.
+ * You can also use drmModePageFlip() to do a vsync'ed pageflip. But this is
+ * beyond the scope of this document.
+ */
+
+static void modeset_draw(void)
+{
+	uint8_t r, g, b;
+	bool r_up, g_up, b_up;
+	unsigned int i, j, k, off;
+	struct modeset_dev *iter;
+
+	srand(time(NULL));
+	r = rand() % 0xff;
+	g = rand() % 0xff;
+	b = rand() % 0xff;
+	r_up = g_up = b_up = true;
+
+	for (i = 0; i < 50; ++i) {
+		r = next_color(&r_up, r, 20);
+		g = next_color(&g_up, g, 10);
+		b = next_color(&b_up, b, 5);
+
+		for (iter = modeset_list; iter; iter = iter->next) {
+			for (j = 0; j < iter->height; ++j) {
+				for (k = 0; k < iter->width; ++k) {
+					off = iter->stride * j + k * 4;
+					*(uint32_t*)&iter->map[off] =
+						     (r << 16) | (g << 8) | b;
+				}
+			}
+		}
+
+		usleep(100000);
+	}
+}
+
+/*
+ * modeset_cleanup(fd): This cleans up all the devices we created during
+ * modeset_prepare(). It resets the CRTCs to their saved states and deallocates
+ * all memory.
+ * It should be pretty obvious how all of this works.
+ */
+
+static void modeset_cleanup(int fd)
+{
+	struct modeset_dev *iter;
+	struct drm_mode_destroy_dumb dreq;
+    int ret = -1;
+
+	while (modeset_list) {
+		/* remove from global list */
+		iter = modeset_list;
+		modeset_list = iter->next;
+
+		/* restore saved CRTC configuration */
+		ret = drmModeSetCrtc(fd,
+			       iter->saved_crtc->crtc_id,
+			       iter->saved_crtc->buffer_id,
+			       iter->saved_crtc->x,
+			       iter->saved_crtc->y,
+			       &iter->conn,
+			       1,
+			       &iter->saved_crtc->mode);
+		if (ret)
+			fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
+				iter->conn, errno);
+		drmModeFreeCrtc(iter->saved_crtc);
+
+		/* unmap buffer */
+		munmap(iter->map, iter->size);
+
+		/* delete framebuffer */
+		drmModeRmFB(fd, iter->fb);
+
+		/* delete dumb buffer */
+		memset(&dreq, 0, sizeof(dreq));
+		dreq.handle = iter->handle;
+		drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+
+		/* free allocated memory */
+		free(iter);
+	}
+}
+
+/*
+ * I hope this was a short but easy overview of the DRM modesetting API. The DRM
+ * API offers much more capabilities including:
+ *  - double-buffering or tripple-buffering (or whatever you want)
+ *  - vsync'ed page-flips
+ *  - hardware-accelerated rendering (for example via OpenGL)
+ *  - output cloning
+ *  - graphics-clients plus authentication
+ *  - DRM planes/overlays/sprites
+ *  - ...
+ * If you are interested in these topics, I can currently only redirect you to
+ * existing implementations, including:
+ *  - plymouth (which uses dumb-buffers like this example; very easy to understand)
+ *  - kmscon (which uses libuterm to do this)
+ *  - wayland (very sophisticated DRM renderer; hard to understand fully as it
+ *             uses more complicated techniques like DRM planes)
+ *  - xserver (very hard to understand as it is split across many files/projects)
+ *
+ * But understanding how modesetting (as described in this document) works, is
+ * essential to understand all further DRM topics.
+ *
+ * Any feedback is welcome. Feel free to use this code freely for your own
+ * documentation or projects.
+ *
+ *  - Hosted on http://github.com/dvdhrm/docs
+ *  - Written by David Rheinsberg <david.rheinsberg@gmail.com>
+ */

+ 158 - 0
package/starfive/drm_test/src/modeset-atomic-crtc.c

@@ -0,0 +1,158 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct buffer_object {
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t handle;
+    uint32_t size;
+    uint8_t *vaddr;
+    uint32_t fb_id;
+};
+
+struct buffer_object buf;
+
+static int modeset_create_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_create_dumb create = {};
+    struct drm_mode_map_dumb map = {};
+
+    create.width = bo->width;
+    create.height = bo->height;
+    create.bpp = 32;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+
+    bo->pitch = create.pitch;
+    bo->size = create.size;
+    bo->handle = create.handle;
+    // {24, 32}
+    drmModeAddFB(fd, bo->width, bo->height, 32, 32, bo->pitch,
+               bo->handle, &bo->fb_id);
+
+    map.handle = create.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
+
+    bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
+            MAP_SHARED, fd, map.offset);
+
+    memset(bo->vaddr, 0xff, bo->size);
+
+    return 0;
+}
+
+static void modeset_destroy_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_destroy_dumb destroy = {};
+
+    drmModeRmFB(fd, bo->fb_id);
+
+    munmap(bo->vaddr, bo->size);
+
+    destroy.handle = bo->handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
+}
+
+static uint32_t get_property_id(int fd, drmModeObjectProperties *props,
+                const char *name)
+{
+    drmModePropertyPtr property;
+    uint32_t i, id = 0;
+
+    for (i = 0; i < props->count_props; i++) {
+        property = drmModeGetProperty(fd, props->props[i]);
+        if (!strcmp(property->name, name))
+            id = property->prop_id;
+        drmModeFreeProperty(property);
+
+        if (id)
+            break;
+    }
+
+    return id;
+}
+
+int main(int argc, char **argv)
+{
+    int fd;
+    drmModeConnector *conn;
+    drmModeRes *res;
+    drmModePlaneRes *plane_res;
+    drmModeObjectProperties *props;
+    drmModeAtomicReq *req;
+    uint32_t conn_id;
+    uint32_t crtc_id;
+    uint32_t plane_id;
+    uint32_t blob_id;
+    uint32_t property_crtc_id;
+    uint32_t property_mode_id;
+    uint32_t property_active;
+
+    fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+
+    res = drmModeGetResources(fd);
+    crtc_id = res->crtcs[0];
+    conn_id = res->connectors[0];
+
+    drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+    plane_res = drmModeGetPlaneResources(fd);
+    plane_id = plane_res->planes[0];
+
+    conn = drmModeGetConnector(fd, conn_id);
+    buf.width = conn->modes[0].hdisplay;
+    buf.height = conn->modes[0].vdisplay;
+
+    modeset_create_fb(fd, &buf);
+
+    drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
+
+    props = drmModeObjectGetProperties(fd, conn_id,    DRM_MODE_OBJECT_CONNECTOR);
+    property_crtc_id = get_property_id(fd, props, "CRTC_ID");
+    drmModeFreeObjectProperties(props);
+
+    props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
+    property_active = get_property_id(fd, props, "ACTIVE");
+    property_mode_id = get_property_id(fd, props, "MODE_ID");
+    drmModeFreeObjectProperties(props);
+
+    drmModeCreatePropertyBlob(fd, &conn->modes[0],
+                sizeof(conn->modes[0]), &blob_id);
+
+    req = drmModeAtomicAlloc();
+    drmModeAtomicAddProperty(req, crtc_id, property_active, 1);
+    drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id);
+    drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id);
+    drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+    drmModeAtomicFree(req);
+
+    printf("drmModeAtomicCommit SetCrtc\n");
+    getchar();
+
+    drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,
+            50, 50, 320, 320,
+            0, 0, 320 << 16, 320 << 16);
+
+    printf("drmModeSetPlane\n");
+    getchar();
+
+    modeset_destroy_fb(fd, &buf);
+
+    drmModeFreeConnector(conn);
+    drmModeFreePlaneResources(plane_res);
+    drmModeFreeResources(res);
+
+    close(fd);
+
+    return 0;
+}

+ 191 - 0
package/starfive/drm_test/src/modeset-atomic-plane.c

@@ -0,0 +1,191 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct buffer_object {
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t handle;
+    uint32_t size;
+    uint8_t *vaddr;
+    uint32_t fb_id;
+};
+
+struct buffer_object buf;
+
+static int modeset_create_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_create_dumb create = {};
+    struct drm_mode_map_dumb map = {};
+
+    create.width = bo->width;
+    create.height = bo->height;
+    create.bpp = 32;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+
+    bo->pitch = create.pitch;
+    bo->size = create.size;
+    bo->handle = create.handle;
+    // {24, 32}
+    drmModeAddFB(fd, bo->width, bo->height, 32, 32, bo->pitch,
+               bo->handle, &bo->fb_id);
+
+    map.handle = create.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
+
+    bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
+            MAP_SHARED, fd, map.offset);
+
+    memset(bo->vaddr, 0xff, bo->size);
+
+    return 0;
+}
+
+static void modeset_destroy_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_destroy_dumb destroy = {};
+
+    drmModeRmFB(fd, bo->fb_id);
+
+    munmap(bo->vaddr, bo->size);
+
+    destroy.handle = bo->handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
+}
+
+static uint32_t get_property_id(int fd, drmModeObjectProperties *props,
+                const char *name)
+{
+    drmModePropertyPtr property;
+    uint32_t i, id = 0;
+
+    for (i = 0; i < props->count_props; i++) {
+        property = drmModeGetProperty(fd, props->props[i]);
+        if (!strcmp(property->name, name))
+            id = property->prop_id;
+        drmModeFreeProperty(property);
+
+        if (id)
+            break;
+    }
+
+    return id;
+}
+
+int main(int argc, char **argv)
+{
+    int fd;
+    drmModeConnector *conn;
+    drmModeRes *res;
+    drmModePlaneRes *plane_res;
+    drmModeObjectProperties *props;
+    drmModeAtomicReq *req;
+    uint32_t conn_id;
+    uint32_t crtc_id;
+    uint32_t plane_id;
+    uint32_t blob_id;
+    uint32_t property_crtc_id;
+    uint32_t property_mode_id;
+    uint32_t property_active;
+    uint32_t property_fb_id;
+    uint32_t property_crtc_x;
+    uint32_t property_crtc_y;
+    uint32_t property_crtc_w;
+    uint32_t property_crtc_h;
+    uint32_t property_src_x;
+    uint32_t property_src_y;
+    uint32_t property_src_w;
+    uint32_t property_src_h;
+
+    fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+
+    res = drmModeGetResources(fd);
+    crtc_id = res->crtcs[0];
+    conn_id = res->connectors[0];
+
+    drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+    plane_res = drmModeGetPlaneResources(fd);
+    plane_id = plane_res->planes[0];
+
+    conn = drmModeGetConnector(fd, conn_id);
+    buf.width = conn->modes[0].hdisplay;
+    buf.height = conn->modes[0].vdisplay;
+
+    modeset_create_fb(fd, &buf);
+
+    drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
+
+    props = drmModeObjectGetProperties(fd, conn_id,    DRM_MODE_OBJECT_CONNECTOR);
+    property_crtc_id = get_property_id(fd, props, "CRTC_ID");
+    drmModeFreeObjectProperties(props);
+
+    props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);
+    property_active = get_property_id(fd, props, "ACTIVE");
+    property_mode_id = get_property_id(fd, props, "MODE_ID");
+    drmModeFreeObjectProperties(props);
+
+    drmModeCreatePropertyBlob(fd, &conn->modes[0],
+                sizeof(conn->modes[0]), &blob_id);
+
+    req = drmModeAtomicAlloc();
+    drmModeAtomicAddProperty(req, crtc_id, property_active, 1);
+    drmModeAtomicAddProperty(req, crtc_id, property_mode_id, blob_id);
+    drmModeAtomicAddProperty(req, conn_id, property_crtc_id, crtc_id);
+    drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
+    drmModeAtomicFree(req);
+
+    printf("drmModeAtomicCommit SetCrtc\n");
+    getchar();
+
+    props = drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
+    property_crtc_id = get_property_id(fd, props, "CRTC_ID");
+    property_fb_id = get_property_id(fd, props, "FB_ID");
+    property_crtc_x = get_property_id(fd, props, "CRTC_X");
+    property_crtc_y = get_property_id(fd, props, "CRTC_Y");
+    property_crtc_w = get_property_id(fd, props, "CRTC_W");
+    property_crtc_h = get_property_id(fd, props, "CRTC_H");
+    property_src_x = get_property_id(fd, props, "SRC_X");
+    property_src_y = get_property_id(fd, props, "SRC_Y");
+    property_src_w = get_property_id(fd, props, "SRC_W");
+    property_src_h = get_property_id(fd, props, "SRC_H");
+    drmModeFreeObjectProperties(props);
+
+    req = drmModeAtomicAlloc();
+    drmModeAtomicAddProperty(req, plane_id, property_crtc_id, crtc_id);
+    drmModeAtomicAddProperty(req, plane_id, property_fb_id, buf.fb_id);
+    drmModeAtomicAddProperty(req, plane_id, property_crtc_x, 50);
+    drmModeAtomicAddProperty(req, plane_id, property_crtc_y, 50);
+    drmModeAtomicAddProperty(req, plane_id, property_crtc_w, 320);
+    drmModeAtomicAddProperty(req, plane_id, property_crtc_h, 320);
+    drmModeAtomicAddProperty(req, plane_id, property_src_x, 0);
+    drmModeAtomicAddProperty(req, plane_id, property_src_y, 0);
+    drmModeAtomicAddProperty(req, plane_id, property_src_w, 320 << 16);
+    drmModeAtomicAddProperty(req, plane_id, property_src_h, 320 << 16);
+    //memset(buf.vaddr, 0x0f, buf.size);
+    drmModeAtomicCommit(fd, req, 0, NULL);
+    drmModeAtomicFree(req);
+
+    printf("drmModeAtomicCommit SetPlane\n");
+    getchar();
+
+    modeset_destroy_fb(fd, &buf);
+
+    drmModeFreeConnector(conn);
+    drmModeFreePlaneResources(plane_res);
+    drmModeFreeResources(res);
+
+    close(fd);
+
+    return 0;
+}

+ 122 - 0
package/starfive/drm_test/src/modeset-double-buffer.c

@@ -0,0 +1,122 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct buffer_object {
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t handle;
+    uint32_t size;
+    uint32_t *vaddr;
+    uint32_t fb_id;
+};
+
+struct buffer_object buf[2];
+
+static int modeset_create_fb(int fd, struct buffer_object *bo, uint32_t color)
+{
+    struct drm_mode_create_dumb create = {};
+    struct drm_mode_map_dumb map = {};
+    uint32_t i;
+
+    create.width = bo->width;
+    create.height = bo->height;
+    create.bpp = 32;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+
+    bo->pitch = create.pitch;
+    bo->size = create.size;
+    bo->handle = create.handle;
+    // {24, 32}
+    drmModeAddFB(fd, bo->width, bo->height, 32, 32, bo->pitch,
+               bo->handle, &bo->fb_id);
+
+    map.handle = create.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
+
+    bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
+            MAP_SHARED, fd, map.offset);
+
+    for (i = 0; i < (bo->size / 4); i++)
+        bo->vaddr[i] = color;
+
+    return 0;
+}
+
+static void modeset_destroy_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_destroy_dumb destroy = {};
+
+    drmModeRmFB(fd, bo->fb_id);
+
+    munmap(bo->vaddr, bo->size);
+
+    destroy.handle = bo->handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
+}
+
+int main(int argc, char **argv)
+{
+    int fd;
+    drmModeConnector *conn;
+    drmModeRes *res;
+    uint32_t conn_id;
+    uint32_t crtc_id;
+    int ret = -1;
+
+    fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+
+    res = drmModeGetResources(fd);
+    crtc_id = res->crtcs[0];
+    conn_id = res->connectors[0];
+
+    conn = drmModeGetConnector(fd, conn_id);
+    buf[0].width = conn->modes[0].hdisplay;
+    buf[0].height = conn->modes[0].vdisplay;
+    buf[1].width = conn->modes[0].hdisplay;
+    buf[1].height = conn->modes[0].vdisplay;
+
+    modeset_create_fb(fd, &buf[0], 0xFF0000);
+    modeset_create_fb(fd, &buf[1], 0x0000FF);
+
+    ret = drmModeSetCrtc(fd, crtc_id, buf[0].fb_id,
+            0, 0, &conn_id, 1, &conn->modes[0]);
+    if (ret) {
+        fprintf(stderr, "1. cannot set CRTC for connector %u (%d): %m\n",
+                crtc_id, errno);
+    }
+
+    getchar();
+    int cnt = 10;
+    while (cnt--) {
+        ret = drmModeSetCrtc(fd, crtc_id, buf[cnt%2].fb_id,
+            0, 0, &conn_id, 1, &conn->modes[0]);
+        if (ret) {
+            fprintf(stderr, "2. cannot set CRTC for connector %u (%d): %m\n",
+                    crtc_id, errno);
+        }
+        usleep(500 * 1000);
+    }
+
+    getchar();
+    modeset_destroy_fb(fd, &buf[1]);
+    modeset_destroy_fb(fd, &buf[0]);
+
+    drmModeFreeConnector(conn);
+    drmModeFreeResources(res);
+
+    close(fd);
+
+    return 0;
+}

+ 49 - 0
package/starfive/drm_test/src/modeset-dumb.c

@@ -0,0 +1,49 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+int main(int argc, char **argv)
+{
+    int fd;
+    char *vaddr;
+    struct drm_mode_create_dumb create_req = {};
+    struct drm_mode_destroy_dumb destroy_req = {};
+    struct drm_mode_map_dumb map_req = {};
+
+    fd = open("/dev/dri/card0", O_RDWR);
+
+    create_req.bpp = 32;
+    create_req.width = 240;
+    create_req.height = 320;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
+    printf("create dumb: handle = %u, pitch = %u, size = %llu\n",
+            create_req.handle, create_req.pitch, create_req.size);
+
+    map_req.handle = create_req.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
+    printf("get mmap offset 0x%llx\n", map_req.offset);
+
+    vaddr = mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset);
+    strcpy(vaddr, "This is a dumb buffer!");
+    munmap(vaddr, create_req.size);
+
+    vaddr = mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
+    printf("read from mmap: %s\n", vaddr);
+    munmap(vaddr, create_req.size);
+
+    getchar();
+
+    destroy_req.handle = create_req.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
+    close(fd);
+
+    return 0;
+}

+ 142 - 0
package/starfive/drm_test/src/modeset-page-flip.c

@@ -0,0 +1,142 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <signal.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct buffer_object {
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t handle;
+    uint32_t size;
+    uint32_t *vaddr;
+    uint32_t fb_id;
+};
+
+struct buffer_object buf[2] = {0};
+static int terminate = 0;
+
+static int modeset_create_fb(int fd, struct buffer_object *bo, uint32_t color)
+{
+    struct drm_mode_create_dumb create = {};
+    struct drm_mode_map_dumb map = {};
+    uint32_t i;
+
+    create.width = bo->width;
+    create.height = bo->height;
+    create.bpp = 32;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+
+    bo->pitch = create.pitch;
+    bo->size = create.size;
+    bo->handle = create.handle;
+    // {24, 32}
+    drmModeAddFB(fd, bo->width, bo->height, 32, 32, bo->pitch,
+               bo->handle, &bo->fb_id);
+
+    map.handle = create.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
+
+    bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
+            MAP_SHARED, fd, map.offset);
+
+    for (i = 0; i < (bo->size / 4); i++)
+        bo->vaddr[i] = color;
+
+    return 0;
+}
+
+static void modeset_destroy_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_destroy_dumb destroy = {};
+
+    drmModeRmFB(fd, bo->fb_id);
+
+    munmap(bo->vaddr, bo->size);
+
+    destroy.handle = bo->handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
+}
+
+static void modeset_page_flip_handler(int fd, uint32_t frame,
+                    uint32_t sec, uint32_t usec,
+                    void *data)
+{
+    static int i = 0;
+    uint32_t crtc_id = *(uint32_t *)data;
+
+    drmMsg("[%s,%d]: fd=%d, sec=%d, usec=%d,crtc_id=%d\n",__FUNCTION__,__LINE__,
+            fd, sec, usec, crtc_id);
+
+    i ^= 1;
+
+    drmModePageFlip(fd, crtc_id, buf[i].fb_id,
+            DRM_MODE_PAGE_FLIP_EVENT, data);
+
+    usleep(500000);
+}
+
+static void sigint_handler(int arg)
+{
+    terminate = 1;
+}
+
+int main(int argc, char **argv)
+{
+    int fd;
+    drmEventContext ev = {};
+    drmModeConnector *conn;
+    drmModeRes *res;
+    uint32_t conn_id;
+    uint32_t crtc_id;
+
+    signal(SIGINT, sigint_handler);
+
+    ev.version = DRM_EVENT_CONTEXT_VERSION;
+    ev.page_flip_handler = modeset_page_flip_handler;
+
+    fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+
+    res = drmModeGetResources(fd);
+    crtc_id = res->crtcs[0];
+    conn_id = res->connectors[0];
+
+    conn = drmModeGetConnector(fd, conn_id);
+    buf[0].width = conn->modes[0].hdisplay;
+    buf[0].height = conn->modes[0].vdisplay;
+    buf[1].width = conn->modes[0].hdisplay;
+    buf[1].height = conn->modes[0].vdisplay;
+
+    modeset_create_fb(fd, &buf[0], 0xff0000);
+    modeset_create_fb(fd, &buf[1], 0x0000ff);
+
+    drmModeSetCrtc(fd, crtc_id, buf[0].fb_id,
+            0, 0, &conn_id, 1, &conn->modes[0]);
+
+    drmModePageFlip(fd, crtc_id, buf[0].fb_id,
+            DRM_MODE_PAGE_FLIP_EVENT, &crtc_id);
+
+    while (!terminate) {
+        drmHandleEvent(fd, &ev);
+    }
+
+    modeset_destroy_fb(fd, &buf[1]);
+    modeset_destroy_fb(fd, &buf[0]);
+
+    drmModeFreeConnector(conn);
+    drmModeFreeResources(res);
+
+    close(fd);
+
+    return 0;
+}

+ 113 - 0
package/starfive/drm_test/src/modeset-plane-test.c

@@ -0,0 +1,113 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+struct buffer_object {
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t handle;
+    uint32_t size;
+    uint8_t *vaddr;
+    uint32_t fb_id;
+};
+
+struct buffer_object buf;
+
+static int modeset_create_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_create_dumb create = {};
+    struct drm_mode_map_dumb map = {};
+
+    create.width = bo->width;
+    create.height = bo->height;
+    create.bpp = 32;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+
+    bo->pitch = create.pitch;
+    bo->size = create.size;
+    bo->handle = create.handle;
+    // {24, 32}
+    drmModeAddFB(fd, bo->width, bo->height, 32, 32, bo->pitch,
+               bo->handle, &bo->fb_id);
+
+    map.handle = create.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
+
+    bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
+            MAP_SHARED, fd, map.offset);
+
+    memset(bo->vaddr, 0x55, bo->size);
+
+    return 0;
+}
+
+static void modeset_destroy_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_destroy_dumb destroy = {};
+
+    drmModeRmFB(fd, bo->fb_id);
+
+    munmap(bo->vaddr, bo->size);
+
+    destroy.handle = bo->handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
+}
+
+int main(int argc, char **argv)
+{
+    int fd;
+    drmModeConnector *conn;
+    drmModeRes *res;
+    drmModePlaneRes *plane_res;
+    uint32_t conn_id;
+    uint32_t crtc_id;
+    uint32_t plane_id;
+
+    fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+
+    res = drmModeGetResources(fd);
+    crtc_id = res->crtcs[0];
+    conn_id = res->connectors[0];
+
+    drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+    plane_res = drmModeGetPlaneResources(fd);
+    plane_id = plane_res->planes[0];
+
+    conn = drmModeGetConnector(fd, conn_id);
+    buf.width = conn->modes[0].hdisplay;
+    buf.height = conn->modes[0].vdisplay;
+
+    modeset_create_fb(fd, &buf);
+
+    drmModeSetCrtc(fd, crtc_id, buf.fb_id,
+            0, 0, &conn_id, 1, &conn->modes[0]);
+
+    getchar();
+
+    drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,
+            50, 50, 320, 320,
+            100, 150, 320 << 16, 320 << 16);
+
+    getchar();
+
+    modeset_destroy_fb(fd, &buf);
+
+    drmModeFreeConnector(conn);
+    drmModeFreePlaneResources(plane_res);
+    drmModeFreeResources(res);
+
+    close(fd);
+
+    return 0;
+}

+ 135 - 0
package/starfive/drm_test/src/modeset-single-buffer.c

@@ -0,0 +1,135 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <signal.h>
+
+struct buffer_object {
+    uint32_t width;
+    uint32_t height;
+    uint32_t pitch;
+    uint32_t handle;
+    uint32_t size;
+    uint8_t *vaddr;
+    uint32_t fb_id;
+};
+
+struct buffer_object buf;
+
+static int modeset_create_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_create_dumb create = {};
+    struct drm_mode_map_dumb map = {};
+
+    create.width = bo->width;
+    create.height = bo->height;
+    create.bpp = 32;
+    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
+    bo->pitch = create.pitch;
+    bo->size = create.size;
+    bo->handle = create.handle;
+    // {24, 32}
+    drmModeAddFB(fd, bo->width, bo->height, 32, 32, bo->pitch,
+               bo->handle, &bo->fb_id);
+    map.handle = create.handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
+    bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
+            MAP_SHARED, fd, map.offset);
+    //memset(bo->vaddr, 0xff, bo->size);
+    int i =0;
+    for (i=0;i<bo->size;i+=4) {
+        bo->vaddr[i]   = 0x0F;
+        bo->vaddr[i+1] = 0x00;
+        bo->vaddr[i+2] = 0xF0;
+        bo->vaddr[i+3] = 0x00;
+    }
+    return 0;
+}
+
+static void modeset_destroy_fb(int fd, struct buffer_object *bo)
+{
+    struct drm_mode_destroy_dumb destroy = {};
+
+    drmModeRmFB(fd, bo->fb_id);
+    munmap(bo->vaddr, bo->size);
+    destroy.handle = bo->handle;
+    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
+}
+
+static int terminate = 0;
+static void sigint_handler(int arg)
+{
+    terminate = 1;
+}
+int main(int argc, char **argv)
+{
+    int fd;
+    drmModeConnector *conn;
+    drmModeRes *res;
+    uint32_t conn_id;
+    uint32_t crtc_id;
+    int ret = -1;
+    int i =0;
+
+    signal(SIGINT, sigint_handler);
+    fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
+    res = drmModeGetResources(fd);
+    crtc_id = res->crtcs[0];
+    conn_id = res->connectors[0];
+    conn = drmModeGetConnector(fd, conn_id);
+    buf.width = conn->modes[0].hdisplay;
+    buf.height = conn->modes[0].vdisplay;
+    modeset_create_fb(fd, &buf);
+    ret = drmModeSetCrtc(fd, crtc_id, buf.fb_id,
+            0, 0, &conn_id, 1, &conn->modes[0]);
+    if (ret) {
+        fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n",
+                crtc_id, errno);
+    }
+
+    getchar();
+
+    uint32_t cnt = 10;
+    uint8_t v1 = 0xFF;
+    uint8_t v2 = 0x00;
+    uint8_t v3 = 0x00;
+    uint8_t v4 = 0x00;
+    while (!terminate && cnt--) {
+        for (i=0;i<buf.size;i+=4) {
+#if 0
+            if (cnt % 2) {
+                buf.vaddr[i]   = 0x0F;
+                buf.vaddr[i+1] = 0x00;
+                buf.vaddr[i+2] = 0x00;
+                buf.vaddr[i+3] = 0xFF;
+            } else {
+                buf.vaddr[i]   = 0x00;
+                buf.vaddr[i+1] = 0xF0;
+                buf.vaddr[i+2] = 0xFF;
+                buf.vaddr[i+3] = 0x00;
+            }
+#else
+            buf.vaddr[i]   = v1 - cnt * 20;
+            buf.vaddr[i+1] = v2 + cnt * 20;
+            buf.vaddr[i+2] = v3;
+            buf.vaddr[i+3] = v4;
+#endif
+        }
+        sleep(1);
+    }
+
+    modeset_destroy_fb(fd, &buf);
+    drmModeFreeConnector(conn);
+    drmModeFreeResources(res);
+    close(fd);
+    return 0;
+}