diff --git a/package/starfive/Config.in b/package/starfive/Config.in old mode 100644 new mode 100755 index 8d055523..94f927e3 --- a/package/starfive/Config.in +++ b/package/starfive/Config.in @@ -10,3 +10,4 @@ source "package/starfive/mailbox-test/Config.in" source "package/starfive/e24-test/Config.in" source "package/starfive/stfisp_setfile/Config.in.host" source "package/starfive/img-gpu-powervr/Config.in" +source "package/starfive/drm_test/Config.in" diff --git a/package/starfive/drm_test/Config.in b/package/starfive/drm_test/Config.in new file mode 100644 index 00000000..5ea62e7c --- /dev/null +++ b/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 diff --git a/package/starfive/drm_test/drm_test.mk b/package/starfive/drm_test/drm_test.mk new file mode 100644 index 00000000..a79539a4 --- /dev/null +++ b/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)) diff --git a/package/starfive/drm_test/src/Makefile b/package/starfive/drm_test/src/Makefile new file mode 100644 index 00000000..6216d203 --- /dev/null +++ b/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) diff --git a/package/starfive/drm_test/src/dvdhrm/Makefile b/package/starfive/drm_test/src/dvdhrm/Makefile new file mode 100644 index 00000000..741c102e --- /dev/null +++ b/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 diff --git a/package/starfive/drm_test/src/dvdhrm/modeset-atomic.c b/package/starfive/drm_test/src/dvdhrm/modeset-atomic.c new file mode 100755 index 00000000..7ab8e299 --- /dev/null +++ b/package/starfive/drm_test/src/dvdhrm/modeset-atomic.c @@ -0,0 +1,1207 @@ +/* + * modeset-atomic - DRM Atomic-API Modesetting Example + * Written 2019 by Ezequiel Garcia + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +/* + * 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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + 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 + */ diff --git a/package/starfive/drm_test/src/dvdhrm/modeset-double-buffered.c b/package/starfive/drm_test/src/dvdhrm/modeset-double-buffered.c new file mode 100755 index 00000000..7a725834 --- /dev/null +++ b/package/starfive/drm_test/src/dvdhrm/modeset-double-buffered.c @@ -0,0 +1,647 @@ +/* + * modeset - DRM Double-Buffered Modesetting Example + * + * Written 2012 by David Rheinsberg + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + 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 + */ diff --git a/package/starfive/drm_test/src/dvdhrm/modeset-vsync.c b/package/starfive/drm_test/src/dvdhrm/modeset-vsync.c new file mode 100755 index 00000000..7db621c1 --- /dev/null +++ b/package/starfive/drm_test/src/dvdhrm/modeset-vsync.c @@ -0,0 +1,776 @@ +/* + * modeset - DRM Double-Buffered VSync'ed Modesetting Example + * + * Written 2012 by David Rheinsberg + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + 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 + */ diff --git a/package/starfive/drm_test/src/dvdhrm/modeset.c b/package/starfive/drm_test/src/dvdhrm/modeset.c new file mode 100755 index 00000000..5bfee205 --- /dev/null +++ b/package/starfive/drm_test/src/dvdhrm/modeset.c @@ -0,0 +1,749 @@ +/* + * modeset - DRM Modesetting Example + * + * Written 2012 by David Rheinsberg + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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/) 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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + 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 + */ diff --git a/package/starfive/drm_test/src/modeset-atomic-crtc.c b/package/starfive/drm_test/src/modeset-atomic-crtc.c new file mode 100755 index 00000000..db4dea09 --- /dev/null +++ b/package/starfive/drm_test/src/modeset-atomic-crtc.c @@ -0,0 +1,161 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + + 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; +} diff --git a/package/starfive/drm_test/src/modeset-atomic-plane.c b/package/starfive/drm_test/src/modeset-atomic-plane.c new file mode 100755 index 00000000..c6f83ae0 --- /dev/null +++ b/package/starfive/drm_test/src/modeset-atomic-plane.c @@ -0,0 +1,194 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + + 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; +} diff --git a/package/starfive/drm_test/src/modeset-double-buffer.c b/package/starfive/drm_test/src/modeset-double-buffer.c new file mode 100755 index 00000000..0a52c378 --- /dev/null +++ b/package/starfive/drm_test/src/modeset-double-buffer.c @@ -0,0 +1,125 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + + 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; +} diff --git a/package/starfive/drm_test/src/modeset-dumb.c b/package/starfive/drm_test/src/modeset-dumb.c new file mode 100755 index 00000000..e4bda5fa --- /dev/null +++ b/package/starfive/drm_test/src/modeset-dumb.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + + 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; +} diff --git a/package/starfive/drm_test/src/modeset-page-flip.c b/package/starfive/drm_test/src/modeset-page-flip.c new file mode 100755 index 00000000..2300f041 --- /dev/null +++ b/package/starfive/drm_test/src/modeset-page-flip.c @@ -0,0 +1,145 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + + 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; +} diff --git a/package/starfive/drm_test/src/modeset-plane-test.c b/package/starfive/drm_test/src/modeset-plane-test.c new file mode 100755 index 00000000..843228f5 --- /dev/null +++ b/package/starfive/drm_test/src/modeset-plane-test.c @@ -0,0 +1,116 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + + 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; +} diff --git a/package/starfive/drm_test/src/modeset-single-buffer.c b/package/starfive/drm_test/src/modeset-single-buffer.c new file mode 100755 index 00000000..172ef707 --- /dev/null +++ b/package/starfive/drm_test/src/modeset-single-buffer.c @@ -0,0 +1,138 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRM_MODULE_NAME "starfive" +#define DRM_BUSID NULL + +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;isize;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 = drmOpen(DRM_MODULE_NAME, DRM_BUSID); + 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