Message ID | 20180720113329.9789-1-benjamin.gaignard@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Friday, 2018-07-20 13:33:29 +0200, Benjamin Gaignard wrote: > This is a modetest like tool but using atomic API. > > With modetest_atomic it is mandatory to specify a mode ("-s") > and a plane ("-P") to display a pattern on screen. > > "-v" does a loop swapping between two framebuffers for each > active planes. > > modetest_atomic doesn't offer cursor support > > Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> > --- > > The code is based on modetest and keep most of it infrastructure > like arguments parsing, finding properties id from their name, > resources dumping or the general way of working. > It duplicates modetest code but adding compilation flags or > conditional tests everywhere in modetest would have made it > more complex and unreadable. I don't have an opinion on whether duplicating the test is the right thing, but if you do, please also duplicate the lines in tests/modetest/meson.build :) > > Creating modetest_atomic could allow to test atomic API without > need to use "big" frameworks like weston, drm_hwcomposer or igt > with all their dependencies. > kmscube could also be used to test atomic API but it need EGL. > > It have been tested (only) on stm driver with one or two planes > actived. > > tests/modetest/Makefile.am | 13 +- > tests/modetest/Makefile.sources | 7 + > tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 1564 insertions(+), 2 deletions(-) > create mode 100644 tests/modetest/modetest_atomic.c > > diff --git a/tests/modetest/Makefile.am b/tests/modetest/Makefile.am > index 4b296c83..8f697bb3 100644 > --- a/tests/modetest/Makefile.am > +++ b/tests/modetest/Makefile.am > @@ -10,10 +10,12 @@ AM_CFLAGS += \ > > if HAVE_INSTALL_TESTS > bin_PROGRAMS = \ > - modetest > + modetest \ > + modetest_atomic > else > noinst_PROGRAMS = \ > - modetest > + modetest \ > + modetest_atomic > endif > > modetest_SOURCES = $(MODETEST_FILES) > @@ -22,3 +24,10 @@ modetest_LDADD = \ > $(top_builddir)/libdrm.la \ > $(top_builddir)/tests/util/libutil.la \ > $(CAIRO_LIBS) > + > +modetest_atomic_SOURCES = $(MODETEST_ATOMIC_FILES) > + > +modetest_atomic_LDADD = \ > + $(top_builddir)/libdrm.la \ > + $(top_builddir)/tests/util/libutil.la \ > + $(CAIRO_LIBS) > diff --git a/tests/modetest/Makefile.sources b/tests/modetest/Makefile.sources > index 399af0df..0a1df4c0 100644 > --- a/tests/modetest/Makefile.sources > +++ b/tests/modetest/Makefile.sources > @@ -4,3 +4,10 @@ MODETEST_FILES := \ > cursor.c \ > cursor.h \ > modetest.c > + > +MODETEST_ATOMIC_FILES := \ > + buffers.c \ > + buffers.h \ > + cursor.c \ > + cursor.h \ > + modetest_atomic.c > diff --git a/tests/modetest/modetest_atomic.c b/tests/modetest/modetest_atomic.c > new file mode 100644 > index 00000000..8c877860 > --- /dev/null > +++ b/tests/modetest/modetest_atomic.c > @@ -0,0 +1,1546 @@ > +/* > + * DRM based mode setting test program > + * Copyright 2008 Tungsten Graphics > + * Jakob Bornecrantz <jakob@tungstengraphics.com> > + * Copyright 2008 Intel Corporation > + * Jesse Barnes <jesse.barnes@intel.com> > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the "Software"), > + * to deal in the Software without restriction, including without limitation > + * the rights to use, copy, modify, merge, publish, distribute, sublicense, > + * and/or sell copies of the Software, and to permit persons to whom the > + * Software is furnished to do so, subject to the following conditions: > + * > + * The above copyright notice and this permission notice shall be included in > + * all copies or substantial portions of the Software. > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE > + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS > + * IN THE SOFTWARE. > + */ > + > +/* > + * This fairly simple test program dumps output in a similar format to the > + * "xrandr" tool everyone knows & loves. It's necessarily slightly different > + * since the kernel separates outputs into encoder and connector structures, > + * each with their own unique ID. The program also allows test testing of the > + * memory management and mode setting APIs by allowing the user to specify a > + * connector and mode to use for mode setting. If all works as expected, a > + * blue background should be painted on the monitor attached to the specified > + * connector after the selected mode is set. > + * > + * TODO: use cairo to write the mode info on the selected output once > + * the mode has been programmed, along with possible test patterns. > + */ > + > +#ifdef HAVE_CONFIG_H > +#include "config.h" > +#endif > + > +#include <assert.h> > +#include <ctype.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <stdint.h> > +#include <inttypes.h> > +#include <unistd.h> > +#include <string.h> > +#include <strings.h> > +#include <errno.h> > +#include <poll.h> > +#include <sys/time.h> > +#ifdef HAVE_SYS_SELECT_H > +#include <sys/select.h> > +#endif > + > +#include "xf86drm.h" > +#include "xf86drmMode.h" > +#include "drm_fourcc.h" > + > +#include "util/common.h" > +#include "util/format.h" > +#include "util/kms.h" > +#include "util/pattern.h" > + > +#include "buffers.h" > + > +struct crtc { > + drmModeCrtc *crtc; > + drmModeObjectProperties *props; > + drmModePropertyRes **props_info; > + drmModeModeInfo *mode; > +}; > + > +struct encoder { > + drmModeEncoder *encoder; > +}; > + > +struct connector { > + drmModeConnector *connector; > + drmModeObjectProperties *props; > + drmModePropertyRes **props_info; > + char *name; > +}; > + > +struct fb { > + drmModeFB *fb; > +}; > + > +struct plane { > + drmModePlane *plane; > + drmModeObjectProperties *props; > + drmModePropertyRes **props_info; > +}; > + > +struct resources { > + drmModeRes *res; > + drmModePlaneRes *plane_res; > + > + struct crtc *crtcs; > + struct encoder *encoders; > + struct connector *connectors; > + struct fb *fbs; > + struct plane *planes; > +}; > + > +struct device { > + int fd; > + > + struct resources *resources; > + drmModeAtomicReq *req; > +}; > + > +static inline int64_t U642I64(uint64_t val) > +{ > + return (int64_t)*((int64_t *)&val); > +} > + > +#define bit_name_fn(res) \ > +const char * res##_str(int type) { \ > + unsigned int i; \ > + const char *sep = ""; \ > + for (i = 0; i < ARRAY_SIZE(res##_names); i++) { \ > + if (type & (1 << i)) { \ > + printf("%s%s", sep, res##_names[i]); \ > + sep = ", "; \ > + } \ > + } \ > + return NULL; \ > +} > + > +static const char *mode_type_names[] = { > + "builtin", > + "clock_c", > + "crtc_c", > + "preferred", > + "default", > + "userdef", > + "driver", > +}; > + > +static bit_name_fn(mode_type) > + > +static const char *mode_flag_names[] = { > + "phsync", > + "nhsync", > + "pvsync", > + "nvsync", > + "interlace", > + "dblscan", > + "csync", > + "pcsync", > + "ncsync", > + "hskew", > + "bcast", > + "pixmux", > + "dblclk", > + "clkdiv2" > +}; > + > +static bit_name_fn(mode_flag) > + > +static void dump_fourcc(uint32_t fourcc) > +{ > + printf(" %c%c%c%c", > + fourcc, > + fourcc >> 8, > + fourcc >> 16, > + fourcc >> 24); > +} > + > +static void dump_encoders(struct device *dev) > +{ > + drmModeEncoder *encoder; > + int i; > + > + printf("Encoders:\n"); > + printf("id\tcrtc\ttype\tpossible crtcs\tpossible clones\t\n"); > + for (i = 0; i < dev->resources->res->count_encoders; i++) { > + encoder = dev->resources->encoders[i].encoder; > + if (!encoder) > + continue; > + > + printf("%d\t%d\t%s\t0x%08x\t0x%08x\n", > + encoder->encoder_id, > + encoder->crtc_id, > + util_lookup_encoder_type_name(encoder->encoder_type), > + encoder->possible_crtcs, > + encoder->possible_clones); > + } > + printf("\n"); > +} > + > +static void dump_mode(drmModeModeInfo *mode) > +{ > + printf(" %s %d %d %d %d %d %d %d %d %d %d", > + mode->name, > + mode->vrefresh, > + mode->hdisplay, > + mode->hsync_start, > + mode->hsync_end, > + mode->htotal, > + mode->vdisplay, > + mode->vsync_start, > + mode->vsync_end, > + mode->vtotal, > + mode->clock); > + > + printf(" flags: "); > + mode_flag_str(mode->flags); > + printf("; type: "); > + mode_type_str(mode->type); > + printf("\n"); > +} > + > +static void dump_blob(struct device *dev, uint32_t blob_id) > +{ > + uint32_t i; > + unsigned char *blob_data; > + drmModePropertyBlobPtr blob; > + > + blob = drmModeGetPropertyBlob(dev->fd, blob_id); > + if (!blob) { > + printf("\n"); > + return; > + } > + > + blob_data = blob->data; > + > + for (i = 0; i < blob->length; i++) { > + if (i % 16 == 0) > + printf("\n\t\t\t"); > + printf("%.2hhx", blob_data[i]); > + } > + printf("\n"); > + > + drmModeFreePropertyBlob(blob); > +} > + > +static void dump_prop(struct device *dev, drmModePropertyPtr prop, > + uint32_t prop_id, uint64_t value) > +{ > + int i; > + printf("\t%d", prop_id); > + if (!prop) { > + printf("\n"); > + return; > + } > + > + printf(" %s:\n", prop->name); > + > + printf("\t\tflags:"); > + if (prop->flags & DRM_MODE_PROP_PENDING) > + printf(" pending"); > + if (prop->flags & DRM_MODE_PROP_IMMUTABLE) > + printf(" immutable"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) > + printf(" signed range"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_RANGE)) > + printf(" range"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_ENUM)) > + printf(" enum"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_BITMASK)) > + printf(" bitmask"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) > + printf(" blob"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_OBJECT)) > + printf(" object"); > + printf("\n"); > + > + if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) { > + printf("\t\tvalues:"); > + for (i = 0; i < prop->count_values; i++) > + printf(" %"PRId64, U642I64(prop->values[i])); > + printf("\n"); > + } > + > + if (drm_property_type_is(prop, DRM_MODE_PROP_RANGE)) { > + printf("\t\tvalues:"); > + for (i = 0; i < prop->count_values; i++) > + printf(" %"PRIu64, prop->values[i]); > + printf("\n"); > + } > + > + if (drm_property_type_is(prop, DRM_MODE_PROP_ENUM)) { > + printf("\t\tenums:"); > + for (i = 0; i < prop->count_enums; i++) > + printf(" %s=%llu", prop->enums[i].name, > + prop->enums[i].value); > + printf("\n"); > + } else if (drm_property_type_is(prop, DRM_MODE_PROP_BITMASK)) { > + printf("\t\tvalues:"); > + for (i = 0; i < prop->count_enums; i++) > + printf(" %s=0x%llx", prop->enums[i].name, > + (1LL << prop->enums[i].value)); > + printf("\n"); > + } else { > + assert(prop->count_enums == 0); > + } > + > + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) { > + printf("\t\tblobs:\n"); > + for (i = 0; i < prop->count_blobs; i++) > + dump_blob(dev, prop->blob_ids[i]); > + printf("\n"); > + } else { > + assert(prop->count_blobs == 0); > + } > + > + printf("\t\tvalue:"); > + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) > + dump_blob(dev, value); > + else if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) > + printf(" %"PRId64"\n", value); > + else > + printf(" %"PRIu64"\n", value); > +} > + > +static void dump_connectors(struct device *dev) > +{ > + int i, j; > + > + printf("Connectors:\n"); > + printf("id\tencoder\tstatus\t\tname\t\tsize (mm)\tmodes\tencoders\n"); > + for (i = 0; i < dev->resources->res->count_connectors; i++) { > + struct connector *_connector = &dev->resources->connectors[i]; > + drmModeConnector *connector = _connector->connector; > + if (!connector) > + continue; > + > + printf("%d\t%d\t%s\t%-15s\t%dx%d\t\t%d\t", > + connector->connector_id, > + connector->encoder_id, > + util_lookup_connector_status_name(connector->connection), > + _connector->name, > + connector->mmWidth, connector->mmHeight, > + connector->count_modes); > + > + for (j = 0; j < connector->count_encoders; j++) > + printf("%s%d", j > 0 ? ", " : "", connector->encoders[j]); > + printf("\n"); > + > + if (connector->count_modes) { > + printf(" modes:\n"); > + printf("\tname refresh (Hz) hdisp hss hse htot vdisp " > + "vss vse vtot)\n"); > + for (j = 0; j < connector->count_modes; j++) > + dump_mode(&connector->modes[j]); > + } > + > + if (_connector->props) { > + printf(" props:\n"); > + for (j = 0; j < (int)_connector->props->count_props; j++) > + dump_prop(dev, _connector->props_info[j], > + _connector->props->props[j], > + _connector->props->prop_values[j]); > + } > + } > + printf("\n"); > +} > + > +static void dump_crtcs(struct device *dev) > +{ > + int i; > + uint32_t j; > + > + printf("CRTCs:\n"); > + printf("id\tfb\tpos\tsize\n"); > + for (i = 0; i < dev->resources->res->count_crtcs; i++) { > + struct crtc *_crtc = &dev->resources->crtcs[i]; > + drmModeCrtc *crtc = _crtc->crtc; > + if (!crtc) > + continue; > + > + printf("%d\t%d\t(%d,%d)\t(%dx%d)\n", > + crtc->crtc_id, > + crtc->buffer_id, > + crtc->x, crtc->y, > + crtc->width, crtc->height); > + dump_mode(&crtc->mode); > + > + if (_crtc->props) { > + printf(" props:\n"); > + for (j = 0; j < _crtc->props->count_props; j++) > + dump_prop(dev, _crtc->props_info[j], > + _crtc->props->props[j], > + _crtc->props->prop_values[j]); > + } else { > + printf(" no properties found\n"); > + } > + } > + printf("\n"); > +} > + > +static void dump_framebuffers(struct device *dev) > +{ > + drmModeFB *fb; > + int i; > + > + printf("Frame buffers:\n"); > + printf("id\tsize\tpitch\n"); > + for (i = 0; i < dev->resources->res->count_fbs; i++) { > + fb = dev->resources->fbs[i].fb; > + if (!fb) > + continue; > + > + printf("%u\t(%ux%u)\t%u\n", > + fb->fb_id, > + fb->width, fb->height, > + fb->pitch); > + } > + printf("\n"); > +} > + > +static void dump_planes(struct device *dev) > +{ > + unsigned int i, j; > + > + printf("Planes:\n"); > + printf("id\tcrtc\tfb\tCRTC x,y\tx,y\tgamma size\tpossible crtcs\n"); > + > + if (!dev->resources->plane_res) > + return; > + > + for (i = 0; i < dev->resources->plane_res->count_planes; i++) { > + struct plane *plane = &dev->resources->planes[i]; > + drmModePlane *ovr = plane->plane; > + if (!ovr) > + continue; > + > + printf("%d\t%d\t%d\t%d,%d\t\t%d,%d\t%-8d\t0x%08x\n", > + ovr->plane_id, ovr->crtc_id, ovr->fb_id, > + ovr->crtc_x, ovr->crtc_y, ovr->x, ovr->y, > + ovr->gamma_size, ovr->possible_crtcs); > + > + if (!ovr->count_formats) > + continue; > + > + printf(" formats:"); > + for (j = 0; j < ovr->count_formats; j++) > + dump_fourcc(ovr->formats[j]); > + printf("\n"); > + > + if (plane->props) { > + printf(" props:\n"); > + for (j = 0; j < plane->props->count_props; j++) > + dump_prop(dev, plane->props_info[j], > + plane->props->props[j], > + plane->props->prop_values[j]); > + } else { > + printf(" no properties found\n"); > + } > + } > + printf("\n"); > + > + return; > +} > + > +static void free_resources(struct resources *res) > +{ > + int i; > + > + if (!res) > + return; > + > +#define free_resource(_res, __res, type, Type) \ > + do { \ > + if (!(_res)->type##s) \ > + break; \ > + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ > + if (!(_res)->type##s[i].type) \ > + break; \ > + drmModeFree##Type((_res)->type##s[i].type); \ > + } \ > + free((_res)->type##s); \ > + } while (0) > + > +#define free_properties(_res, __res, type) \ > + do { \ > + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ > + drmModeFreeObjectProperties(res->type##s[i].props); \ > + free(res->type##s[i].props_info); \ > + } \ > + } while (0) > + > + if (res->res) { > + free_properties(res, res, crtc); > + > + free_resource(res, res, crtc, Crtc); > + free_resource(res, res, encoder, Encoder); > + > + for (i = 0; i < res->res->count_connectors; i++) > + free(res->connectors[i].name); > + > + free_resource(res, res, connector, Connector); > + free_resource(res, res, fb, FB); > + > + drmModeFreeResources(res->res); > + } > + > + if (res->plane_res) { > + free_properties(res, plane_res, plane); > + > + free_resource(res, plane_res, plane, Plane); > + > + drmModeFreePlaneResources(res->plane_res); > + } > + > + free(res); > +} > + > +static struct resources *get_resources(struct device *dev) > +{ > + struct resources *res; > + int i; > + > + res = calloc(1, sizeof(*res)); > + if (res == 0) > + return NULL; > + > + drmSetClientCap(dev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); > + > + res->res = drmModeGetResources(dev->fd); > + if (!res->res) { > + fprintf(stderr, "drmModeGetResources failed: %s\n", > + strerror(errno)); > + goto error; > + } > + > + res->crtcs = calloc(res->res->count_crtcs, sizeof(*res->crtcs)); > + res->encoders = calloc(res->res->count_encoders, sizeof(*res->encoders)); > + res->connectors = calloc(res->res->count_connectors, sizeof(*res->connectors)); > + res->fbs = calloc(res->res->count_fbs, sizeof(*res->fbs)); > + > + if (!res->crtcs || !res->encoders || !res->connectors || !res->fbs) > + goto error; > + > +#define get_resource(_res, __res, type, Type) \ > + do { \ > + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ > + (_res)->type##s[i].type = \ > + drmModeGet##Type(dev->fd, (_res)->__res->type##s[i]); \ > + if (!(_res)->type##s[i].type) \ > + fprintf(stderr, "could not get %s %i: %s\n", \ > + #type, (_res)->__res->type##s[i], \ > + strerror(errno)); \ > + } \ > + } while (0) > + > + get_resource(res, res, crtc, Crtc); > + get_resource(res, res, encoder, Encoder); > + get_resource(res, res, connector, Connector); > + get_resource(res, res, fb, FB); > + > + /* Set the name of all connectors based on the type name and the per-type ID. */ > + for (i = 0; i < res->res->count_connectors; i++) { > + struct connector *connector = &res->connectors[i]; > + drmModeConnector *conn = connector->connector; > + int num; > + > + num = asprintf(&connector->name, "%s-%u", > + util_lookup_connector_type_name(conn->connector_type), > + conn->connector_type_id); > + if (num < 0) > + goto error; > + } > + > +#define get_properties(_res, __res, type, Type) \ > + do { \ > + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ > + struct type *obj = &res->type##s[i]; \ > + unsigned int j; \ > + obj->props = \ > + drmModeObjectGetProperties(dev->fd, obj->type->type##_id, \ > + DRM_MODE_OBJECT_##Type); \ > + if (!obj->props) { \ > + fprintf(stderr, \ > + "could not get %s %i properties: %s\n", \ > + #type, obj->type->type##_id, \ > + strerror(errno)); \ > + continue; \ > + } \ > + obj->props_info = calloc(obj->props->count_props, \ > + sizeof(*obj->props_info)); \ > + if (!obj->props_info) \ > + continue; \ > + for (j = 0; j < obj->props->count_props; ++j) \ > + obj->props_info[j] = \ > + drmModeGetProperty(dev->fd, obj->props->props[j]); \ > + } \ > + } while (0) > + > + get_properties(res, res, crtc, CRTC); > + get_properties(res, res, connector, CONNECTOR); > + > + for (i = 0; i < res->res->count_crtcs; ++i) > + res->crtcs[i].mode = &res->crtcs[i].crtc->mode; > + > + res->plane_res = drmModeGetPlaneResources(dev->fd); > + if (!res->plane_res) { > + fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", > + strerror(errno)); > + return res; > + } > + > + res->planes = calloc(res->plane_res->count_planes, sizeof(*res->planes)); > + if (!res->planes) > + goto error; > + > + get_resource(res, plane_res, plane, Plane); > + get_properties(res, plane_res, plane, PLANE); > + > + return res; > + > +error: > + free_resources(res); > + return NULL; > +} > + > +static int get_crtc_index(struct device *dev, uint32_t id) > +{ > + int i; > + > + for (i = 0; i < dev->resources->res->count_crtcs; ++i) { > + drmModeCrtc *crtc = dev->resources->crtcs[i].crtc; > + if (crtc && crtc->crtc_id == id) > + return i; > + } > + > + return -1; > +} > + > +static drmModeConnector *get_connector_by_name(struct device *dev, const char *name) > +{ > + struct connector *connector; > + int i; > + > + for (i = 0; i < dev->resources->res->count_connectors; i++) { > + connector = &dev->resources->connectors[i]; > + > + if (strcmp(connector->name, name) == 0) > + return connector->connector; > + } > + > + return NULL; > +} > + > +static drmModeConnector *get_connector_by_id(struct device *dev, uint32_t id) > +{ > + drmModeConnector *connector; > + int i; > + > + for (i = 0; i < dev->resources->res->count_connectors; i++) { > + connector = dev->resources->connectors[i].connector; > + if (connector && connector->connector_id == id) > + return connector; > + } > + > + return NULL; > +} > + > +static drmModeEncoder *get_encoder_by_id(struct device *dev, uint32_t id) > +{ > + drmModeEncoder *encoder; > + int i; > + > + for (i = 0; i < dev->resources->res->count_encoders; i++) { > + encoder = dev->resources->encoders[i].encoder; > + if (encoder && encoder->encoder_id == id) > + return encoder; > + } > + > + return NULL; > +} > + > +/* ----------------------------------------------------------------------------- > + * Pipes and planes > + */ > + > +/* > + * Mode setting with the kernel interfaces is a bit of a chore. > + * First you have to find the connector in question and make sure the > + * requested mode is available. > + * Then you need to find the encoder attached to that connector so you > + * can bind it with a free crtc. > + */ > +struct pipe_arg { > + const char **cons; > + uint32_t *con_ids; > + unsigned int num_cons; > + uint32_t crtc_id; > + char mode_str[64]; > + char format_str[5]; > + unsigned int vrefresh; > + unsigned int fourcc; > + drmModeModeInfo *mode; > + struct crtc *crtc; > + unsigned int fb_id[2], current_fb_id; > + struct timeval start; > + > + int swap_count; > +}; > + > +struct plane_arg { > + uint32_t plane_id; /* the id of plane to use */ > + uint32_t crtc_id; /* the id of CRTC to bind to */ > + bool has_position; > + int32_t x, y; > + uint32_t w, h; > + double scale; > + unsigned int fb_id; > + unsigned int old_fb_id; > + struct bo *bo; > + struct bo *old_bo; > + char format_str[5]; /* need to leave room for terminating \0 */ > + unsigned int fourcc; > +}; > + > +static drmModeModeInfo * > +connector_find_mode(struct device *dev, uint32_t con_id, const char *mode_str, > + const unsigned int vrefresh) > +{ > + drmModeConnector *connector; > + drmModeModeInfo *mode; > + int i; > + > + connector = get_connector_by_id(dev, con_id); > + if (!connector || !connector->count_modes) > + return NULL; > + > + for (i = 0; i < connector->count_modes; i++) { > + mode = &connector->modes[i]; > + if (!strcmp(mode->name, mode_str)) { > + /* If the vertical refresh frequency is not specified then return the > + * first mode that match with the name. Else, return the mode that match > + * the name and the specified vertical refresh frequency. > + */ > + if (vrefresh == 0) > + return mode; > + else if (mode->vrefresh == vrefresh) > + return mode; > + } > + } > + > + return NULL; > +} > + > +static struct crtc *pipe_find_crtc(struct device *dev, struct pipe_arg *pipe) > +{ > + uint32_t possible_crtcs = ~0; > + uint32_t active_crtcs = 0; > + unsigned int crtc_idx; > + unsigned int i; > + int j; > + > + for (i = 0; i < pipe->num_cons; ++i) { > + uint32_t crtcs_for_connector = 0; > + drmModeConnector *connector; > + drmModeEncoder *encoder; > + int idx; > + > + connector = get_connector_by_id(dev, pipe->con_ids[i]); > + if (!connector) > + return NULL; > + > + for (j = 0; j < connector->count_encoders; ++j) { > + encoder = get_encoder_by_id(dev, connector->encoders[j]); > + if (!encoder) > + continue; > + > + crtcs_for_connector |= encoder->possible_crtcs; > + > + idx = get_crtc_index(dev, encoder->crtc_id); > + if (idx >= 0) > + active_crtcs |= 1 << idx; > + } > + > + possible_crtcs &= crtcs_for_connector; > + } > + > + if (!possible_crtcs) > + return NULL; > + > + /* Return the first possible and active CRTC if one exists, or the first > + * possible CRTC otherwise. > + */ > + if (possible_crtcs & active_crtcs) > + crtc_idx = ffs(possible_crtcs & active_crtcs); > + else > + crtc_idx = ffs(possible_crtcs); > + > + return &dev->resources->crtcs[crtc_idx - 1]; > +} > + > +static int pipe_find_crtc_and_mode(struct device *dev, struct pipe_arg *pipe) > +{ > + drmModeModeInfo *mode = NULL; > + int i; > + > + pipe->mode = NULL; > + > + for (i = 0; i < (int)pipe->num_cons; i++) { > + mode = connector_find_mode(dev, pipe->con_ids[i], > + pipe->mode_str, pipe->vrefresh); > + if (mode == NULL) { > + fprintf(stderr, > + "failed to find mode \"%s\" for connector %s\n", > + pipe->mode_str, pipe->cons[i]); > + return -EINVAL; > + } > + } > + > + /* If the CRTC ID was specified, get the corresponding CRTC. Otherwise > + * locate a CRTC that can be attached to all the connectors. > + */ > + if (pipe->crtc_id != (uint32_t)-1) { > + for (i = 0; i < dev->resources->res->count_crtcs; i++) { > + struct crtc *crtc = &dev->resources->crtcs[i]; > + > + if (pipe->crtc_id == crtc->crtc->crtc_id) { > + pipe->crtc = crtc; > + break; > + } > + } > + } else { > + pipe->crtc = pipe_find_crtc(dev, pipe); > + } > + > + if (!pipe->crtc) { > + fprintf(stderr, "failed to find CRTC for pipe\n"); > + return -EINVAL; > + } > + > + pipe->mode = mode; > + pipe->crtc->mode = mode; > + > + return 0; > +} > + > +/* ----------------------------------------------------------------------------- > + * Properties > + */ > + > +struct property_arg { > + uint32_t obj_id; > + uint32_t obj_type; > + char name[DRM_PROP_NAME_LEN+1]; > + uint32_t prop_id; > + uint64_t value; > +}; > + > +static void set_property(struct device *dev, struct property_arg *p) > +{ > + drmModeObjectProperties *props = NULL; > + drmModePropertyRes **props_info = NULL; > + const char *obj_type; > + int ret; > + int i; > + > + p->obj_type = 0; > + p->prop_id = 0; > + > +#define find_object(_res, __res, type, Type) \ > + do { \ > + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ > + struct type *obj = &(_res)->type##s[i]; \ > + if (obj->type->type##_id != p->obj_id) \ > + continue; \ > + p->obj_type = DRM_MODE_OBJECT_##Type; \ > + obj_type = #Type; \ > + props = obj->props; \ > + props_info = obj->props_info; \ > + } \ > + } while(0) \ > + > + find_object(dev->resources, res, crtc, CRTC); > + if (p->obj_type == 0) > + find_object(dev->resources, res, connector, CONNECTOR); > + if (p->obj_type == 0) > + find_object(dev->resources, plane_res, plane, PLANE); > + if (p->obj_type == 0) { > + fprintf(stderr, "Object %i not found, can't set property\n", > + p->obj_id); > + return; > + } > + > + if (!props) { > + fprintf(stderr, "%s %i has no properties\n", > + obj_type, p->obj_id); > + return; > + } > + > + for (i = 0; i < (int)props->count_props; ++i) { > + if (!props_info[i]) > + continue; > + if (strcmp(props_info[i]->name, p->name) == 0) > + break; > + } > + > + if (i == (int)props->count_props) { > + fprintf(stderr, "%s %i has no %s property\n", > + obj_type, p->obj_id, p->name); > + return; > + } > + > + p->prop_id = props->props[i]; > + > + ret = drmModeAtomicAddProperty(dev->req, p->obj_id, p->prop_id, p->value); > + if (ret < 0) > + fprintf(stderr, "failed to set %s %i property %s to %" PRIu64 ": %s\n", > + obj_type, p->obj_id, p->name, p->value, strerror(errno)); > +} > + > +/* -------------------------------------------------------------------------- */ > + > +/*static bool format_support(const drmModePlanePtr ovr, uint32_t fmt) > +{ > + unsigned int i; > + > + for (i = 0; i < ovr->count_formats; ++i) { > + if (ovr->formats[i] == fmt) > + return true; > + } > + > + return false; > +}*/ > + > +static void add_property(struct device *dev, uint32_t obj_id, > + const char *name, uint64_t value) > +{ > + struct property_arg p; > + > + p.obj_id = obj_id; > + strcpy(p.name, name); > + p.value = value; > + > + set_property(dev, &p); > +} > + > +static int set_plane(struct device *dev, struct plane_arg *p, > + int pattern, bool update) > +{ > + uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; > + struct bo *plane_bo; > + int crtc_x, crtc_y, crtc_w, crtc_h; > + struct crtc *crtc = NULL; > + unsigned int i; > + unsigned int old_fb_id; > + > + /* Find an unused plane which can be connected to our CRTC. Find the > + * CRTC index first, then iterate over available planes. > + */ > + for (i = 0; i < (unsigned int)dev->resources->res->count_crtcs; i++) { > + if (p->crtc_id == dev->resources->res->crtcs[i]) { > + crtc = &dev->resources->crtcs[i]; > + break; > + } > + } > + > + if (!crtc) { > + fprintf(stderr, "CRTC %u not found\n", p->crtc_id); > + return -1; > + } > + > + if (!update) > + fprintf(stderr, "testing %dx%d@%s on plane %u, crtc %u\n", > + p->w, p->h, p->format_str, p->plane_id, p->crtc_id); > + > + plane_bo = p->old_bo; > + p->old_bo = p->bo; > + > + if (!plane_bo) { > + plane_bo = bo_create(dev->fd, p->fourcc, p->w, p->h, > + handles, pitches, offsets, pattern); > + > + if (plane_bo == NULL) > + return -1; > + > + if (drmModeAddFB2(dev->fd, p->w, p->h, p->fourcc, > + handles, pitches, offsets, &p->fb_id, 0)) { > + fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); > + return -1; > + } > + } > + > + p->bo = plane_bo; > + > + old_fb_id = p->fb_id; > + p->old_fb_id = old_fb_id; > + > + crtc_w = p->w * p->scale; > + crtc_h = p->h * p->scale; > + if (!p->has_position) { > + /* Default to the middle of the screen */ > + crtc_x = (crtc->mode->hdisplay - crtc_w) / 2; > + crtc_y = (crtc->mode->vdisplay - crtc_h) / 2; > + } else { > + crtc_x = p->x; > + crtc_y = p->y; > + } > + > + add_property(dev, p->plane_id, "FB_ID", p->fb_id); > + add_property(dev, p->plane_id, "CRTC_ID", p->crtc_id); > + add_property(dev, p->plane_id, "SRC_X", 0); > + add_property(dev, p->plane_id, "SRC_Y", 0); > + add_property(dev, p->plane_id, "SRC_W", p->w << 16); > + add_property(dev, p->plane_id, "SRC_H", p->h << 16); > + add_property(dev, p->plane_id, "CRTC_X", crtc_x); > + add_property(dev, p->plane_id, "CRTC_Y", crtc_y); > + add_property(dev, p->plane_id, "CRTC_W", crtc_w); > + add_property(dev, p->plane_id, "CRTC_H", crtc_h); > + > + return 0; > +} > + > +static void set_planes(struct device *dev, struct plane_arg *p, > + unsigned int count, bool update) > +{ > + unsigned int i, pattern = UTIL_PATTERN_SMPTE; > + > + /* set up planes */ > + for (i = 0; i < count; i++) { > + if (i > 0) > + pattern = UTIL_PATTERN_TILES; > + > + if (set_plane(dev, &p[i], pattern, update)) > + return; > + } > +} > + > +static void clear_planes(struct device *dev, struct plane_arg *p, unsigned int count) > +{ > + unsigned int i; > + > + for (i = 0; i < count; i++) { > + add_property(dev, p[i].plane_id, "FB_ID", 0); > + add_property(dev, p[i].plane_id, "CRTC_ID", 0); > + add_property(dev, p[i].plane_id, "SRC_X", 0); > + add_property(dev, p[i].plane_id, "SRC_Y", 0); > + add_property(dev, p[i].plane_id, "SRC_W", 0); > + add_property(dev, p[i].plane_id, "SRC_H", 0); > + add_property(dev, p[i].plane_id, "CRTC_X", 0); > + add_property(dev, p[i].plane_id, "CRTC_Y", 0); > + add_property(dev, p[i].plane_id, "CRTC_W", 0); > + add_property(dev, p[i].plane_id, "CRTC_H", 0); > + } > +} > + > +static void clear_FB(struct device *dev, struct plane_arg *p, unsigned int count) > +{ > + unsigned int i; > + > + for (i = 0; i < count; i++) { > + if (p[i].fb_id) { > + drmModeRmFB(dev->fd, p[i].fb_id); > + p[i].fb_id = 0; > + } > + if (p[i].old_fb_id) { > + drmModeRmFB(dev->fd, p[i].old_fb_id); > + p[i].old_fb_id = 0; > + } > + if (p[i].bo) { > + bo_destroy(p[i].bo); > + p[i].bo = NULL; > + } > + if (p[i].old_bo) { > + bo_destroy(p[i].old_bo); > + p[i].old_bo = NULL; > + } > + > + } > +} > + > +static void set_mode(struct device *dev, struct pipe_arg *pipes, unsigned int count) > +{ > + unsigned int i; > + unsigned int j; > + int ret; > + > + for (i = 0; i < count; i++) { > + struct pipe_arg *pipe = &pipes[i]; > + > + ret = pipe_find_crtc_and_mode(dev, pipe); > + if (ret < 0) > + continue; > + } > + > + for (i = 0; i < count; i++) { > + struct pipe_arg *pipe = &pipes[i]; > + uint32_t blob_id; > + > + if (pipe->mode == NULL) > + continue; > + > + printf("setting mode %s-%dHz@%s on connectors ", > + pipe->mode_str, pipe->mode->vrefresh, pipe->format_str); > + for (j = 0; j < pipe->num_cons; ++j) { > + printf("%s, ", pipe->cons[j]); > + add_property(dev, pipe->con_ids[j], "CRTC_ID", pipe->crtc->crtc->crtc_id); > + } > + printf("crtc %d\n", pipe->crtc->crtc->crtc_id); > + > + drmModeCreatePropertyBlob(dev->fd, pipe->mode, sizeof(*pipe->mode), &blob_id); > + add_property(dev, pipe->crtc->crtc->crtc_id, "MODE_ID", blob_id); > + add_property(dev, pipe->crtc->crtc->crtc_id, "ACTIVE", 1); > + } > +} > + > +static void clear_mode(struct device *dev, struct pipe_arg *pipes, unsigned int count) > +{ > + unsigned int i; > + unsigned int j; > + > + for (i = 0; i < count; i++) { > + struct pipe_arg *pipe = &pipes[i]; > + > + if (pipe->mode == NULL) > + continue; > + > + for (j = 0; j < pipe->num_cons; ++j) > + add_property(dev, pipe->con_ids[j], "CRTC_ID",0); > + > + add_property(dev, pipe->crtc->crtc->crtc_id, "MODE_ID", 0); > + add_property(dev, pipe->crtc->crtc->crtc_id, "ACTIVE", 0); > + } > + > +} > + > +#define min(a, b) ((a) < (b) ? (a) : (b)) > + > +static int parse_connector(struct pipe_arg *pipe, const char *arg) > +{ > + unsigned int len; > + unsigned int i; > + const char *p; > + char *endp; > + > + pipe->vrefresh = 0; > + pipe->crtc_id = (uint32_t)-1; > + strcpy(pipe->format_str, "XR24"); > + > + /* Count the number of connectors and allocate them. */ > + pipe->num_cons = 1; > + for (p = arg; *p && *p != ':' && *p != '@'; ++p) { > + if (*p == ',') > + pipe->num_cons++; > + } > + > + pipe->con_ids = calloc(pipe->num_cons, sizeof(*pipe->con_ids)); > + pipe->cons = calloc(pipe->num_cons, sizeof(*pipe->cons)); > + if (pipe->con_ids == NULL || pipe->cons == NULL) > + return -1; > + > + /* Parse the connectors. */ > + for (i = 0, p = arg; i < pipe->num_cons; ++i, p = endp + 1) { > + endp = strpbrk(p, ",@:"); > + if (!endp) > + break; > + > + pipe->cons[i] = strndup(p, endp - p); > + > + if (*endp != ',') > + break; > + } > + > + if (i != pipe->num_cons - 1) > + return -1; > + > + /* Parse the remaining parameters. */ > + if (*endp == '@') { > + arg = endp + 1; > + pipe->crtc_id = strtoul(arg, &endp, 10); > + } > + if (*endp != ':') > + return -1; > + > + arg = endp + 1; > + > + /* Search for the vertical refresh or the format. */ > + p = strpbrk(arg, "-@"); > + if (p == NULL) > + p = arg + strlen(arg); > + len = min(sizeof pipe->mode_str - 1, (unsigned int)(p - arg)); > + strncpy(pipe->mode_str, arg, len); > + pipe->mode_str[len] = '\0'; > + > + if (*p == '-') { > + pipe->vrefresh = strtoul(p + 1, &endp, 10); > + p = endp; > + } > + > + if (*p == '@') { > + strncpy(pipe->format_str, p + 1, 4); > + pipe->format_str[4] = '\0'; > + } > + > + pipe->fourcc = util_format_fourcc(pipe->format_str); > + if (pipe->fourcc == 0) { > + fprintf(stderr, "unknown format %s\n", pipe->format_str); > + return -1; > + } > + > + return 0; > +} > + > +static int parse_plane(struct plane_arg *plane, const char *p) > +{ > + char *end; > + > + plane->plane_id = strtoul(p, &end, 10); > + if (*end != '@') > + return -EINVAL; > + > + p = end + 1; > + plane->crtc_id = strtoul(p, &end, 10); > + if (*end != ':') > + return -EINVAL; > + > + p = end + 1; > + plane->w = strtoul(p, &end, 10); > + if (*end != 'x') > + return -EINVAL; > + > + p = end + 1; > + plane->h = strtoul(p, &end, 10); > + > + if (*end == '+' || *end == '-') { > + plane->x = strtol(end, &end, 10); > + if (*end != '+' && *end != '-') > + return -EINVAL; > + plane->y = strtol(end, &end, 10); > + > + plane->has_position = true; > + } > + > + if (*end == '*') { > + p = end + 1; > + plane->scale = strtod(p, &end); > + if (plane->scale <= 0.0) > + return -EINVAL; > + } else { > + plane->scale = 1.0; > + } > + > + if (*end == '@') { > + p = end + 1; > + if (strlen(p) != 4) > + return -EINVAL; > + > + strcpy(plane->format_str, p); > + } else { > + strcpy(plane->format_str, "XR24"); > + } > + > + plane->fourcc = util_format_fourcc(plane->format_str); > + if (plane->fourcc == 0) { > + fprintf(stderr, "unknown format %s\n", plane->format_str); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int parse_property(struct property_arg *p, const char *arg) > +{ > + if (sscanf(arg, "%d:%32[^:]:%" SCNu64, &p->obj_id, p->name, &p->value) != 3) > + return -1; > + > + p->obj_type = 0; > + p->name[DRM_PROP_NAME_LEN] = '\0'; > + > + return 0; > +} > + > +static void usage(char *name) > +{ > + fprintf(stderr, "usage: %s [-cDdefMPpsCvw]\n", name); > + > + fprintf(stderr, "\n Query options:\n\n"); > + fprintf(stderr, "\t-c\tlist connectors\n"); > + fprintf(stderr, "\t-e\tlist encoders\n"); > + fprintf(stderr, "\t-f\tlist framebuffers\n"); > + fprintf(stderr, "\t-p\tlist CRTCs and planes (pipes)\n"); > + > + fprintf(stderr, "\n Test options:\n\n"); > + fprintf(stderr, "\t-P <plane_id>@<crtc_id>:<w>x<h>[+<x>+<y>][*<scale>][@<format>]\tset a plane\n"); > + fprintf(stderr, "\t-s <connector_id>[,<connector_id>][@<crtc_id>]:<mode>[-<vrefresh>][@<format>]\tset a mode\n"); > + fprintf(stderr, "\t-v\ttest loop\n"); > + fprintf(stderr, "\t-w <obj_id>:<prop_name>:<value>\tset property\n"); > + > + fprintf(stderr, "\n Generic options:\n\n"); > + fprintf(stderr, "\t-d\tdrop master after mode set\n"); > + fprintf(stderr, "\t-M module\tuse the given driver\n"); > + fprintf(stderr, "\t-D device\tuse the given device\n"); > + > + fprintf(stderr, "\n\tDefault is to dump all info.\n"); > + exit(0); > +} > + > +static int pipe_resolve_connectors(struct device *dev, struct pipe_arg *pipe) > +{ > + drmModeConnector *connector; > + unsigned int i; > + uint32_t id; > + char *endp; > + > + for (i = 0; i < pipe->num_cons; i++) { > + id = strtoul(pipe->cons[i], &endp, 10); > + if (endp == pipe->cons[i]) { > + connector = get_connector_by_name(dev, pipe->cons[i]); > + if (!connector) { > + fprintf(stderr, "no connector named '%s'\n", > + pipe->cons[i]); > + return -ENODEV; > + } > + > + id = connector->connector_id; > + } > + > + pipe->con_ids[i] = id; > + } > + > + return 0; > +} > + > +static char optstr[] = "cdD:efM:P:ps:vw:"; > + > +int main(int argc, char **argv) > +{ > + struct device dev; > + > + int c; > + int encoders = 0, connectors = 0, crtcs = 0, planes = 0, framebuffers = 0; > + int drop_master = 0; > + int test_loop = 0; > + char *device = NULL; > + char *module = NULL; > + unsigned int i; > + unsigned int count = 0, plane_count = 0; > + unsigned int prop_count = 0; > + struct pipe_arg *pipe_args = NULL; > + struct plane_arg *plane_args = NULL; > + struct property_arg *prop_args = NULL; > + unsigned int args = 0; > + int ret; > + > + memset(&dev, 0, sizeof dev); > + > + opterr = 0; > + while ((c = getopt(argc, argv, optstr)) != -1) { > + args++; > + > + switch (c) { > + case 'c': > + connectors = 1; > + break; > + case 'D': > + device = optarg; > + args--; > + break; > + case 'd': > + drop_master = 1; > + break; > + case 'e': > + encoders = 1; > + break; > + case 'f': > + framebuffers = 1; > + break; > + case 'M': > + module = optarg; > + /* Preserve the default behaviour of dumping all information. */ > + args--; > + break; > + case 'P': > + plane_args = realloc(plane_args, > + (plane_count + 1) * sizeof *plane_args); > + if (plane_args == NULL) { > + fprintf(stderr, "memory allocation failed\n"); > + return 1; > + } > + memset(&plane_args[plane_count], 0, sizeof(*plane_args)); > + > + if (parse_plane(&plane_args[plane_count], optarg) < 0) > + usage(argv[0]); > + > + plane_count++; > + break; > + case 'p': > + crtcs = 1; > + planes = 1; > + break; > + case 's': > + pipe_args = realloc(pipe_args, > + (count + 1) * sizeof *pipe_args); > + if (pipe_args == NULL) { > + fprintf(stderr, "memory allocation failed\n"); > + return 1; > + } > + memset(&pipe_args[count], 0, sizeof(*pipe_args)); > + > + if (parse_connector(&pipe_args[count], optarg) < 0) > + usage(argv[0]); > + > + count++; > + break; > + case 'v': > + test_loop = 1; > + break; > + case 'w': > + prop_args = realloc(prop_args, > + (prop_count + 1) * sizeof *prop_args); > + if (prop_args == NULL) { > + fprintf(stderr, "memory allocation failed\n"); > + return 1; > + } > + memset(&prop_args[prop_count], 0, sizeof(*prop_args)); > + > + if (parse_property(&prop_args[prop_count], optarg) < 0) > + usage(argv[0]); > + > + prop_count++; > + break; > + default: > + usage(argv[0]); > + break; > + } > + } > + > + if (!args) > + encoders = connectors = crtcs = planes = framebuffers = 1; > + > + dev.fd = util_open(device, module); > + if (dev.fd < 0) > + return -1; > + > + ret = drmSetClientCap(dev.fd, DRM_CLIENT_CAP_ATOMIC, 1); > + if (ret) { > + fprintf(stderr, "no atomic modesetting support: %s\n", strerror(errno)); > + drmClose(dev.fd); > + return -1; > + } > + > + dev.resources = get_resources(&dev); > + if (!dev.resources) { > + drmClose(dev.fd); > + return 1; > + } > + > + for (i = 0; i < count; i++) { > + if (pipe_resolve_connectors(&dev, &pipe_args[i]) < 0) { > + free_resources(dev.resources); > + drmClose(dev.fd); > + return 1; > + } > + } > + > +#define dump_resource(dev, res) if (res) dump_##res(dev) > + > + dump_resource(&dev, encoders); > + dump_resource(&dev, connectors); > + dump_resource(&dev, crtcs); > + dump_resource(&dev, planes); > + dump_resource(&dev, framebuffers); > + > + dev.req = drmModeAtomicAlloc(); > + > + for (i = 0; i < prop_count; ++i) > + set_property(&dev, &prop_args[i]); > + > + if (count && plane_count) { > + uint64_t cap = 0; > + > + ret = drmGetCap(dev.fd, DRM_CAP_DUMB_BUFFER, &cap); > + if (ret || cap == 0) { > + fprintf(stderr, "driver doesn't support the dumb buffer API\n"); > + drmModeAtomicFree(dev.req); > + return 1; > + } > + > + set_mode(&dev, pipe_args, count); > + set_planes(&dev, plane_args, plane_count, false); > + > + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); > + if (ret) { > + fprintf(stderr, "Atomic Commit failed [1]\n"); > + return 1; > + } > + > + gettimeofday(&pipe_args->start, NULL); > + pipe_args->swap_count = 0; > + > + while (test_loop) { > + drmModeAtomicFree(dev.req); > + dev.req = drmModeAtomicAlloc(); > + set_planes(&dev, plane_args, plane_count, true); > + > + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); > + if (ret) { > + fprintf(stderr, "Atomic Commit failed [2]\n"); > + return 1; > + } > + > + pipe_args->swap_count++; > + if (pipe_args->swap_count == 60) { > + struct timeval end; > + double t; > + > + gettimeofday(&end, NULL); > + t = end.tv_sec + end.tv_usec * 1e-6 - > + (pipe_args->start.tv_sec + pipe_args->start.tv_usec * 1e-6); > + fprintf(stderr, "freq: %.02fHz\n", pipe_args->swap_count / t); > + pipe_args->swap_count = 0; > + pipe_args->start = end; > + } > + } > + > + if (drop_master) > + drmDropMaster(dev.fd); > + > + getchar(); > + > + drmModeAtomicFree(dev.req); > + dev.req = drmModeAtomicAlloc(); > + > + clear_mode(&dev, pipe_args, count); > + clear_planes(&dev, plane_args, plane_count); > + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); > + if (ret) { > + fprintf(stderr, "Atomic Commit failed\n"); > + return 1; > + } > + > + clear_FB(&dev, plane_args, plane_count); > + } > + > + drmModeAtomicFree(dev.req); > + free_resources(dev.resources); > + > + return 0; > +} > -- > 2.15.0 > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/dri-devel
2018-07-23 18:30 GMT+02:00 Eric Engestrom <eric.engestrom@intel.com>: > On Friday, 2018-07-20 13:33:29 +0200, Benjamin Gaignard wrote: >> This is a modetest like tool but using atomic API. >> >> With modetest_atomic it is mandatory to specify a mode ("-s") >> and a plane ("-P") to display a pattern on screen. >> >> "-v" does a loop swapping between two framebuffers for each >> active planes. >> >> modetest_atomic doesn't offer cursor support >> >> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> >> --- >> >> The code is based on modetest and keep most of it infrastructure >> like arguments parsing, finding properties id from their name, >> resources dumping or the general way of working. >> It duplicates modetest code but adding compilation flags or >> conditional tests everywhere in modetest would have made it >> more complex and unreadable. > > I don't have an opinion on whether duplicating the test is the right > thing, but if you do, please also duplicate the lines in > tests/modetest/meson.build :) You are right, I will also make it compile for Android. > >> >> Creating modetest_atomic could allow to test atomic API without >> need to use "big" frameworks like weston, drm_hwcomposer or igt >> with all their dependencies. >> kmscube could also be used to test atomic API but it need EGL. >> >> It have been tested (only) on stm driver with one or two planes >> actived. >> >> tests/modetest/Makefile.am | 13 +- >> tests/modetest/Makefile.sources | 7 + >> tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 1564 insertions(+), 2 deletions(-) >> create mode 100644 tests/modetest/modetest_atomic.c >> >> diff --git a/tests/modetest/Makefile.am b/tests/modetest/Makefile.am >> index 4b296c83..8f697bb3 100644 >> --- a/tests/modetest/Makefile.am >> +++ b/tests/modetest/Makefile.am >> @@ -10,10 +10,12 @@ AM_CFLAGS += \ >> >> if HAVE_INSTALL_TESTS >> bin_PROGRAMS = \ >> - modetest >> + modetest \ >> + modetest_atomic >> else >> noinst_PROGRAMS = \ >> - modetest >> + modetest \ >> + modetest_atomic >> endif >> >> modetest_SOURCES = $(MODETEST_FILES) >> @@ -22,3 +24,10 @@ modetest_LDADD = \ >> $(top_builddir)/libdrm.la \ >> $(top_builddir)/tests/util/libutil.la \ >> $(CAIRO_LIBS) >> + >> +modetest_atomic_SOURCES = $(MODETEST_ATOMIC_FILES) >> + >> +modetest_atomic_LDADD = \ >> + $(top_builddir)/libdrm.la \ >> + $(top_builddir)/tests/util/libutil.la \ >> + $(CAIRO_LIBS) >> diff --git a/tests/modetest/Makefile.sources b/tests/modetest/Makefile.sources >> index 399af0df..0a1df4c0 100644 >> --- a/tests/modetest/Makefile.sources >> +++ b/tests/modetest/Makefile.sources >> @@ -4,3 +4,10 @@ MODETEST_FILES := \ >> cursor.c \ >> cursor.h \ >> modetest.c >> + >> +MODETEST_ATOMIC_FILES := \ >> + buffers.c \ >> + buffers.h \ >> + cursor.c \ >> + cursor.h \ >> + modetest_atomic.c >> diff --git a/tests/modetest/modetest_atomic.c b/tests/modetest/modetest_atomic.c >> new file mode 100644 >> index 00000000..8c877860 >> --- /dev/null >> +++ b/tests/modetest/modetest_atomic.c >> @@ -0,0 +1,1546 @@ >> +/* >> + * DRM based mode setting test program >> + * Copyright 2008 Tungsten Graphics >> + * Jakob Bornecrantz <jakob@tungstengraphics.com> >> + * Copyright 2008 Intel Corporation >> + * Jesse Barnes <jesse.barnes@intel.com> >> + * >> + * Permission is hereby granted, free of charge, to any person obtaining a >> + * copy of this software and associated documentation files (the "Software"), >> + * to deal in the Software without restriction, including without limitation >> + * the rights to use, copy, modify, merge, publish, distribute, sublicense, >> + * and/or sell copies of the Software, and to permit persons to whom the >> + * Software is furnished to do so, subject to the following conditions: >> + * >> + * The above copyright notice and this permission notice shall be included in >> + * all copies or substantial portions of the Software. >> + * >> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR >> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, >> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE >> + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER >> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING >> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS >> + * IN THE SOFTWARE. >> + */ >> + >> +/* >> + * This fairly simple test program dumps output in a similar format to the >> + * "xrandr" tool everyone knows & loves. It's necessarily slightly different >> + * since the kernel separates outputs into encoder and connector structures, >> + * each with their own unique ID. The program also allows test testing of the >> + * memory management and mode setting APIs by allowing the user to specify a >> + * connector and mode to use for mode setting. If all works as expected, a >> + * blue background should be painted on the monitor attached to the specified >> + * connector after the selected mode is set. >> + * >> + * TODO: use cairo to write the mode info on the selected output once >> + * the mode has been programmed, along with possible test patterns. >> + */ >> + >> +#ifdef HAVE_CONFIG_H >> +#include "config.h" >> +#endif >> + >> +#include <assert.h> >> +#include <ctype.h> >> +#include <stdbool.h> >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <stdint.h> >> +#include <inttypes.h> >> +#include <unistd.h> >> +#include <string.h> >> +#include <strings.h> >> +#include <errno.h> >> +#include <poll.h> >> +#include <sys/time.h> >> +#ifdef HAVE_SYS_SELECT_H >> +#include <sys/select.h> >> +#endif >> + >> +#include "xf86drm.h" >> +#include "xf86drmMode.h" >> +#include "drm_fourcc.h" >> + >> +#include "util/common.h" >> +#include "util/format.h" >> +#include "util/kms.h" >> +#include "util/pattern.h" >> + >> +#include "buffers.h" >> + >> +struct crtc { >> + drmModeCrtc *crtc; >> + drmModeObjectProperties *props; >> + drmModePropertyRes **props_info; >> + drmModeModeInfo *mode; >> +}; >> + >> +struct encoder { >> + drmModeEncoder *encoder; >> +}; >> + >> +struct connector { >> + drmModeConnector *connector; >> + drmModeObjectProperties *props; >> + drmModePropertyRes **props_info; >> + char *name; >> +}; >> + >> +struct fb { >> + drmModeFB *fb; >> +}; >> + >> +struct plane { >> + drmModePlane *plane; >> + drmModeObjectProperties *props; >> + drmModePropertyRes **props_info; >> +}; >> + >> +struct resources { >> + drmModeRes *res; >> + drmModePlaneRes *plane_res; >> + >> + struct crtc *crtcs; >> + struct encoder *encoders; >> + struct connector *connectors; >> + struct fb *fbs; >> + struct plane *planes; >> +}; >> + >> +struct device { >> + int fd; >> + >> + struct resources *resources; >> + drmModeAtomicReq *req; >> +}; >> + >> +static inline int64_t U642I64(uint64_t val) >> +{ >> + return (int64_t)*((int64_t *)&val); >> +} >> + >> +#define bit_name_fn(res) \ >> +const char * res##_str(int type) { \ >> + unsigned int i; \ >> + const char *sep = ""; \ >> + for (i = 0; i < ARRAY_SIZE(res##_names); i++) { \ >> + if (type & (1 << i)) { \ >> + printf("%s%s", sep, res##_names[i]); \ >> + sep = ", "; \ >> + } \ >> + } \ >> + return NULL; \ >> +} >> + >> +static const char *mode_type_names[] = { >> + "builtin", >> + "clock_c", >> + "crtc_c", >> + "preferred", >> + "default", >> + "userdef", >> + "driver", >> +}; >> + >> +static bit_name_fn(mode_type) >> + >> +static const char *mode_flag_names[] = { >> + "phsync", >> + "nhsync", >> + "pvsync", >> + "nvsync", >> + "interlace", >> + "dblscan", >> + "csync", >> + "pcsync", >> + "ncsync", >> + "hskew", >> + "bcast", >> + "pixmux", >> + "dblclk", >> + "clkdiv2" >> +}; >> + >> +static bit_name_fn(mode_flag) >> + >> +static void dump_fourcc(uint32_t fourcc) >> +{ >> + printf(" %c%c%c%c", >> + fourcc, >> + fourcc >> 8, >> + fourcc >> 16, >> + fourcc >> 24); >> +} >> + >> +static void dump_encoders(struct device *dev) >> +{ >> + drmModeEncoder *encoder; >> + int i; >> + >> + printf("Encoders:\n"); >> + printf("id\tcrtc\ttype\tpossible crtcs\tpossible clones\t\n"); >> + for (i = 0; i < dev->resources->res->count_encoders; i++) { >> + encoder = dev->resources->encoders[i].encoder; >> + if (!encoder) >> + continue; >> + >> + printf("%d\t%d\t%s\t0x%08x\t0x%08x\n", >> + encoder->encoder_id, >> + encoder->crtc_id, >> + util_lookup_encoder_type_name(encoder->encoder_type), >> + encoder->possible_crtcs, >> + encoder->possible_clones); >> + } >> + printf("\n"); >> +} >> + >> +static void dump_mode(drmModeModeInfo *mode) >> +{ >> + printf(" %s %d %d %d %d %d %d %d %d %d %d", >> + mode->name, >> + mode->vrefresh, >> + mode->hdisplay, >> + mode->hsync_start, >> + mode->hsync_end, >> + mode->htotal, >> + mode->vdisplay, >> + mode->vsync_start, >> + mode->vsync_end, >> + mode->vtotal, >> + mode->clock); >> + >> + printf(" flags: "); >> + mode_flag_str(mode->flags); >> + printf("; type: "); >> + mode_type_str(mode->type); >> + printf("\n"); >> +} >> + >> +static void dump_blob(struct device *dev, uint32_t blob_id) >> +{ >> + uint32_t i; >> + unsigned char *blob_data; >> + drmModePropertyBlobPtr blob; >> + >> + blob = drmModeGetPropertyBlob(dev->fd, blob_id); >> + if (!blob) { >> + printf("\n"); >> + return; >> + } >> + >> + blob_data = blob->data; >> + >> + for (i = 0; i < blob->length; i++) { >> + if (i % 16 == 0) >> + printf("\n\t\t\t"); >> + printf("%.2hhx", blob_data[i]); >> + } >> + printf("\n"); >> + >> + drmModeFreePropertyBlob(blob); >> +} >> + >> +static void dump_prop(struct device *dev, drmModePropertyPtr prop, >> + uint32_t prop_id, uint64_t value) >> +{ >> + int i; >> + printf("\t%d", prop_id); >> + if (!prop) { >> + printf("\n"); >> + return; >> + } >> + >> + printf(" %s:\n", prop->name); >> + >> + printf("\t\tflags:"); >> + if (prop->flags & DRM_MODE_PROP_PENDING) >> + printf(" pending"); >> + if (prop->flags & DRM_MODE_PROP_IMMUTABLE) >> + printf(" immutable"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) >> + printf(" signed range"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_RANGE)) >> + printf(" range"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_ENUM)) >> + printf(" enum"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_BITMASK)) >> + printf(" bitmask"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) >> + printf(" blob"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_OBJECT)) >> + printf(" object"); >> + printf("\n"); >> + >> + if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) { >> + printf("\t\tvalues:"); >> + for (i = 0; i < prop->count_values; i++) >> + printf(" %"PRId64, U642I64(prop->values[i])); >> + printf("\n"); >> + } >> + >> + if (drm_property_type_is(prop, DRM_MODE_PROP_RANGE)) { >> + printf("\t\tvalues:"); >> + for (i = 0; i < prop->count_values; i++) >> + printf(" %"PRIu64, prop->values[i]); >> + printf("\n"); >> + } >> + >> + if (drm_property_type_is(prop, DRM_MODE_PROP_ENUM)) { >> + printf("\t\tenums:"); >> + for (i = 0; i < prop->count_enums; i++) >> + printf(" %s=%llu", prop->enums[i].name, >> + prop->enums[i].value); >> + printf("\n"); >> + } else if (drm_property_type_is(prop, DRM_MODE_PROP_BITMASK)) { >> + printf("\t\tvalues:"); >> + for (i = 0; i < prop->count_enums; i++) >> + printf(" %s=0x%llx", prop->enums[i].name, >> + (1LL << prop->enums[i].value)); >> + printf("\n"); >> + } else { >> + assert(prop->count_enums == 0); >> + } >> + >> + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) { >> + printf("\t\tblobs:\n"); >> + for (i = 0; i < prop->count_blobs; i++) >> + dump_blob(dev, prop->blob_ids[i]); >> + printf("\n"); >> + } else { >> + assert(prop->count_blobs == 0); >> + } >> + >> + printf("\t\tvalue:"); >> + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) >> + dump_blob(dev, value); >> + else if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) >> + printf(" %"PRId64"\n", value); >> + else >> + printf(" %"PRIu64"\n", value); >> +} >> + >> +static void dump_connectors(struct device *dev) >> +{ >> + int i, j; >> + >> + printf("Connectors:\n"); >> + printf("id\tencoder\tstatus\t\tname\t\tsize (mm)\tmodes\tencoders\n"); >> + for (i = 0; i < dev->resources->res->count_connectors; i++) { >> + struct connector *_connector = &dev->resources->connectors[i]; >> + drmModeConnector *connector = _connector->connector; >> + if (!connector) >> + continue; >> + >> + printf("%d\t%d\t%s\t%-15s\t%dx%d\t\t%d\t", >> + connector->connector_id, >> + connector->encoder_id, >> + util_lookup_connector_status_name(connector->connection), >> + _connector->name, >> + connector->mmWidth, connector->mmHeight, >> + connector->count_modes); >> + >> + for (j = 0; j < connector->count_encoders; j++) >> + printf("%s%d", j > 0 ? ", " : "", connector->encoders[j]); >> + printf("\n"); >> + >> + if (connector->count_modes) { >> + printf(" modes:\n"); >> + printf("\tname refresh (Hz) hdisp hss hse htot vdisp " >> + "vss vse vtot)\n"); >> + for (j = 0; j < connector->count_modes; j++) >> + dump_mode(&connector->modes[j]); >> + } >> + >> + if (_connector->props) { >> + printf(" props:\n"); >> + for (j = 0; j < (int)_connector->props->count_props; j++) >> + dump_prop(dev, _connector->props_info[j], >> + _connector->props->props[j], >> + _connector->props->prop_values[j]); >> + } >> + } >> + printf("\n"); >> +} >> + >> +static void dump_crtcs(struct device *dev) >> +{ >> + int i; >> + uint32_t j; >> + >> + printf("CRTCs:\n"); >> + printf("id\tfb\tpos\tsize\n"); >> + for (i = 0; i < dev->resources->res->count_crtcs; i++) { >> + struct crtc *_crtc = &dev->resources->crtcs[i]; >> + drmModeCrtc *crtc = _crtc->crtc; >> + if (!crtc) >> + continue; >> + >> + printf("%d\t%d\t(%d,%d)\t(%dx%d)\n", >> + crtc->crtc_id, >> + crtc->buffer_id, >> + crtc->x, crtc->y, >> + crtc->width, crtc->height); >> + dump_mode(&crtc->mode); >> + >> + if (_crtc->props) { >> + printf(" props:\n"); >> + for (j = 0; j < _crtc->props->count_props; j++) >> + dump_prop(dev, _crtc->props_info[j], >> + _crtc->props->props[j], >> + _crtc->props->prop_values[j]); >> + } else { >> + printf(" no properties found\n"); >> + } >> + } >> + printf("\n"); >> +} >> + >> +static void dump_framebuffers(struct device *dev) >> +{ >> + drmModeFB *fb; >> + int i; >> + >> + printf("Frame buffers:\n"); >> + printf("id\tsize\tpitch\n"); >> + for (i = 0; i < dev->resources->res->count_fbs; i++) { >> + fb = dev->resources->fbs[i].fb; >> + if (!fb) >> + continue; >> + >> + printf("%u\t(%ux%u)\t%u\n", >> + fb->fb_id, >> + fb->width, fb->height, >> + fb->pitch); >> + } >> + printf("\n"); >> +} >> + >> +static void dump_planes(struct device *dev) >> +{ >> + unsigned int i, j; >> + >> + printf("Planes:\n"); >> + printf("id\tcrtc\tfb\tCRTC x,y\tx,y\tgamma size\tpossible crtcs\n"); >> + >> + if (!dev->resources->plane_res) >> + return; >> + >> + for (i = 0; i < dev->resources->plane_res->count_planes; i++) { >> + struct plane *plane = &dev->resources->planes[i]; >> + drmModePlane *ovr = plane->plane; >> + if (!ovr) >> + continue; >> + >> + printf("%d\t%d\t%d\t%d,%d\t\t%d,%d\t%-8d\t0x%08x\n", >> + ovr->plane_id, ovr->crtc_id, ovr->fb_id, >> + ovr->crtc_x, ovr->crtc_y, ovr->x, ovr->y, >> + ovr->gamma_size, ovr->possible_crtcs); >> + >> + if (!ovr->count_formats) >> + continue; >> + >> + printf(" formats:"); >> + for (j = 0; j < ovr->count_formats; j++) >> + dump_fourcc(ovr->formats[j]); >> + printf("\n"); >> + >> + if (plane->props) { >> + printf(" props:\n"); >> + for (j = 0; j < plane->props->count_props; j++) >> + dump_prop(dev, plane->props_info[j], >> + plane->props->props[j], >> + plane->props->prop_values[j]); >> + } else { >> + printf(" no properties found\n"); >> + } >> + } >> + printf("\n"); >> + >> + return; >> +} >> + >> +static void free_resources(struct resources *res) >> +{ >> + int i; >> + >> + if (!res) >> + return; >> + >> +#define free_resource(_res, __res, type, Type) \ >> + do { \ >> + if (!(_res)->type##s) \ >> + break; \ >> + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ >> + if (!(_res)->type##s[i].type) \ >> + break; \ >> + drmModeFree##Type((_res)->type##s[i].type); \ >> + } \ >> + free((_res)->type##s); \ >> + } while (0) >> + >> +#define free_properties(_res, __res, type) \ >> + do { \ >> + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ >> + drmModeFreeObjectProperties(res->type##s[i].props); \ >> + free(res->type##s[i].props_info); \ >> + } \ >> + } while (0) >> + >> + if (res->res) { >> + free_properties(res, res, crtc); >> + >> + free_resource(res, res, crtc, Crtc); >> + free_resource(res, res, encoder, Encoder); >> + >> + for (i = 0; i < res->res->count_connectors; i++) >> + free(res->connectors[i].name); >> + >> + free_resource(res, res, connector, Connector); >> + free_resource(res, res, fb, FB); >> + >> + drmModeFreeResources(res->res); >> + } >> + >> + if (res->plane_res) { >> + free_properties(res, plane_res, plane); >> + >> + free_resource(res, plane_res, plane, Plane); >> + >> + drmModeFreePlaneResources(res->plane_res); >> + } >> + >> + free(res); >> +} >> + >> +static struct resources *get_resources(struct device *dev) >> +{ >> + struct resources *res; >> + int i; >> + >> + res = calloc(1, sizeof(*res)); >> + if (res == 0) >> + return NULL; >> + >> + drmSetClientCap(dev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); >> + >> + res->res = drmModeGetResources(dev->fd); >> + if (!res->res) { >> + fprintf(stderr, "drmModeGetResources failed: %s\n", >> + strerror(errno)); >> + goto error; >> + } >> + >> + res->crtcs = calloc(res->res->count_crtcs, sizeof(*res->crtcs)); >> + res->encoders = calloc(res->res->count_encoders, sizeof(*res->encoders)); >> + res->connectors = calloc(res->res->count_connectors, sizeof(*res->connectors)); >> + res->fbs = calloc(res->res->count_fbs, sizeof(*res->fbs)); >> + >> + if (!res->crtcs || !res->encoders || !res->connectors || !res->fbs) >> + goto error; >> + >> +#define get_resource(_res, __res, type, Type) \ >> + do { \ >> + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ >> + (_res)->type##s[i].type = \ >> + drmModeGet##Type(dev->fd, (_res)->__res->type##s[i]); \ >> + if (!(_res)->type##s[i].type) \ >> + fprintf(stderr, "could not get %s %i: %s\n", \ >> + #type, (_res)->__res->type##s[i], \ >> + strerror(errno)); \ >> + } \ >> + } while (0) >> + >> + get_resource(res, res, crtc, Crtc); >> + get_resource(res, res, encoder, Encoder); >> + get_resource(res, res, connector, Connector); >> + get_resource(res, res, fb, FB); >> + >> + /* Set the name of all connectors based on the type name and the per-type ID. */ >> + for (i = 0; i < res->res->count_connectors; i++) { >> + struct connector *connector = &res->connectors[i]; >> + drmModeConnector *conn = connector->connector; >> + int num; >> + >> + num = asprintf(&connector->name, "%s-%u", >> + util_lookup_connector_type_name(conn->connector_type), >> + conn->connector_type_id); >> + if (num < 0) >> + goto error; >> + } >> + >> +#define get_properties(_res, __res, type, Type) \ >> + do { \ >> + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ >> + struct type *obj = &res->type##s[i]; \ >> + unsigned int j; \ >> + obj->props = \ >> + drmModeObjectGetProperties(dev->fd, obj->type->type##_id, \ >> + DRM_MODE_OBJECT_##Type); \ >> + if (!obj->props) { \ >> + fprintf(stderr, \ >> + "could not get %s %i properties: %s\n", \ >> + #type, obj->type->type##_id, \ >> + strerror(errno)); \ >> + continue; \ >> + } \ >> + obj->props_info = calloc(obj->props->count_props, \ >> + sizeof(*obj->props_info)); \ >> + if (!obj->props_info) \ >> + continue; \ >> + for (j = 0; j < obj->props->count_props; ++j) \ >> + obj->props_info[j] = \ >> + drmModeGetProperty(dev->fd, obj->props->props[j]); \ >> + } \ >> + } while (0) >> + >> + get_properties(res, res, crtc, CRTC); >> + get_properties(res, res, connector, CONNECTOR); >> + >> + for (i = 0; i < res->res->count_crtcs; ++i) >> + res->crtcs[i].mode = &res->crtcs[i].crtc->mode; >> + >> + res->plane_res = drmModeGetPlaneResources(dev->fd); >> + if (!res->plane_res) { >> + fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", >> + strerror(errno)); >> + return res; >> + } >> + >> + res->planes = calloc(res->plane_res->count_planes, sizeof(*res->planes)); >> + if (!res->planes) >> + goto error; >> + >> + get_resource(res, plane_res, plane, Plane); >> + get_properties(res, plane_res, plane, PLANE); >> + >> + return res; >> + >> +error: >> + free_resources(res); >> + return NULL; >> +} >> + >> +static int get_crtc_index(struct device *dev, uint32_t id) >> +{ >> + int i; >> + >> + for (i = 0; i < dev->resources->res->count_crtcs; ++i) { >> + drmModeCrtc *crtc = dev->resources->crtcs[i].crtc; >> + if (crtc && crtc->crtc_id == id) >> + return i; >> + } >> + >> + return -1; >> +} >> + >> +static drmModeConnector *get_connector_by_name(struct device *dev, const char *name) >> +{ >> + struct connector *connector; >> + int i; >> + >> + for (i = 0; i < dev->resources->res->count_connectors; i++) { >> + connector = &dev->resources->connectors[i]; >> + >> + if (strcmp(connector->name, name) == 0) >> + return connector->connector; >> + } >> + >> + return NULL; >> +} >> + >> +static drmModeConnector *get_connector_by_id(struct device *dev, uint32_t id) >> +{ >> + drmModeConnector *connector; >> + int i; >> + >> + for (i = 0; i < dev->resources->res->count_connectors; i++) { >> + connector = dev->resources->connectors[i].connector; >> + if (connector && connector->connector_id == id) >> + return connector; >> + } >> + >> + return NULL; >> +} >> + >> +static drmModeEncoder *get_encoder_by_id(struct device *dev, uint32_t id) >> +{ >> + drmModeEncoder *encoder; >> + int i; >> + >> + for (i = 0; i < dev->resources->res->count_encoders; i++) { >> + encoder = dev->resources->encoders[i].encoder; >> + if (encoder && encoder->encoder_id == id) >> + return encoder; >> + } >> + >> + return NULL; >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * Pipes and planes >> + */ >> + >> +/* >> + * Mode setting with the kernel interfaces is a bit of a chore. >> + * First you have to find the connector in question and make sure the >> + * requested mode is available. >> + * Then you need to find the encoder attached to that connector so you >> + * can bind it with a free crtc. >> + */ >> +struct pipe_arg { >> + const char **cons; >> + uint32_t *con_ids; >> + unsigned int num_cons; >> + uint32_t crtc_id; >> + char mode_str[64]; >> + char format_str[5]; >> + unsigned int vrefresh; >> + unsigned int fourcc; >> + drmModeModeInfo *mode; >> + struct crtc *crtc; >> + unsigned int fb_id[2], current_fb_id; >> + struct timeval start; >> + >> + int swap_count; >> +}; >> + >> +struct plane_arg { >> + uint32_t plane_id; /* the id of plane to use */ >> + uint32_t crtc_id; /* the id of CRTC to bind to */ >> + bool has_position; >> + int32_t x, y; >> + uint32_t w, h; >> + double scale; >> + unsigned int fb_id; >> + unsigned int old_fb_id; >> + struct bo *bo; >> + struct bo *old_bo; >> + char format_str[5]; /* need to leave room for terminating \0 */ >> + unsigned int fourcc; >> +}; >> + >> +static drmModeModeInfo * >> +connector_find_mode(struct device *dev, uint32_t con_id, const char *mode_str, >> + const unsigned int vrefresh) >> +{ >> + drmModeConnector *connector; >> + drmModeModeInfo *mode; >> + int i; >> + >> + connector = get_connector_by_id(dev, con_id); >> + if (!connector || !connector->count_modes) >> + return NULL; >> + >> + for (i = 0; i < connector->count_modes; i++) { >> + mode = &connector->modes[i]; >> + if (!strcmp(mode->name, mode_str)) { >> + /* If the vertical refresh frequency is not specified then return the >> + * first mode that match with the name. Else, return the mode that match >> + * the name and the specified vertical refresh frequency. >> + */ >> + if (vrefresh == 0) >> + return mode; >> + else if (mode->vrefresh == vrefresh) >> + return mode; >> + } >> + } >> + >> + return NULL; >> +} >> + >> +static struct crtc *pipe_find_crtc(struct device *dev, struct pipe_arg *pipe) >> +{ >> + uint32_t possible_crtcs = ~0; >> + uint32_t active_crtcs = 0; >> + unsigned int crtc_idx; >> + unsigned int i; >> + int j; >> + >> + for (i = 0; i < pipe->num_cons; ++i) { >> + uint32_t crtcs_for_connector = 0; >> + drmModeConnector *connector; >> + drmModeEncoder *encoder; >> + int idx; >> + >> + connector = get_connector_by_id(dev, pipe->con_ids[i]); >> + if (!connector) >> + return NULL; >> + >> + for (j = 0; j < connector->count_encoders; ++j) { >> + encoder = get_encoder_by_id(dev, connector->encoders[j]); >> + if (!encoder) >> + continue; >> + >> + crtcs_for_connector |= encoder->possible_crtcs; >> + >> + idx = get_crtc_index(dev, encoder->crtc_id); >> + if (idx >= 0) >> + active_crtcs |= 1 << idx; >> + } >> + >> + possible_crtcs &= crtcs_for_connector; >> + } >> + >> + if (!possible_crtcs) >> + return NULL; >> + >> + /* Return the first possible and active CRTC if one exists, or the first >> + * possible CRTC otherwise. >> + */ >> + if (possible_crtcs & active_crtcs) >> + crtc_idx = ffs(possible_crtcs & active_crtcs); >> + else >> + crtc_idx = ffs(possible_crtcs); >> + >> + return &dev->resources->crtcs[crtc_idx - 1]; >> +} >> + >> +static int pipe_find_crtc_and_mode(struct device *dev, struct pipe_arg *pipe) >> +{ >> + drmModeModeInfo *mode = NULL; >> + int i; >> + >> + pipe->mode = NULL; >> + >> + for (i = 0; i < (int)pipe->num_cons; i++) { >> + mode = connector_find_mode(dev, pipe->con_ids[i], >> + pipe->mode_str, pipe->vrefresh); >> + if (mode == NULL) { >> + fprintf(stderr, >> + "failed to find mode \"%s\" for connector %s\n", >> + pipe->mode_str, pipe->cons[i]); >> + return -EINVAL; >> + } >> + } >> + >> + /* If the CRTC ID was specified, get the corresponding CRTC. Otherwise >> + * locate a CRTC that can be attached to all the connectors. >> + */ >> + if (pipe->crtc_id != (uint32_t)-1) { >> + for (i = 0; i < dev->resources->res->count_crtcs; i++) { >> + struct crtc *crtc = &dev->resources->crtcs[i]; >> + >> + if (pipe->crtc_id == crtc->crtc->crtc_id) { >> + pipe->crtc = crtc; >> + break; >> + } >> + } >> + } else { >> + pipe->crtc = pipe_find_crtc(dev, pipe); >> + } >> + >> + if (!pipe->crtc) { >> + fprintf(stderr, "failed to find CRTC for pipe\n"); >> + return -EINVAL; >> + } >> + >> + pipe->mode = mode; >> + pipe->crtc->mode = mode; >> + >> + return 0; >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * Properties >> + */ >> + >> +struct property_arg { >> + uint32_t obj_id; >> + uint32_t obj_type; >> + char name[DRM_PROP_NAME_LEN+1]; >> + uint32_t prop_id; >> + uint64_t value; >> +}; >> + >> +static void set_property(struct device *dev, struct property_arg *p) >> +{ >> + drmModeObjectProperties *props = NULL; >> + drmModePropertyRes **props_info = NULL; >> + const char *obj_type; >> + int ret; >> + int i; >> + >> + p->obj_type = 0; >> + p->prop_id = 0; >> + >> +#define find_object(_res, __res, type, Type) \ >> + do { \ >> + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ >> + struct type *obj = &(_res)->type##s[i]; \ >> + if (obj->type->type##_id != p->obj_id) \ >> + continue; \ >> + p->obj_type = DRM_MODE_OBJECT_##Type; \ >> + obj_type = #Type; \ >> + props = obj->props; \ >> + props_info = obj->props_info; \ >> + } \ >> + } while(0) \ >> + >> + find_object(dev->resources, res, crtc, CRTC); >> + if (p->obj_type == 0) >> + find_object(dev->resources, res, connector, CONNECTOR); >> + if (p->obj_type == 0) >> + find_object(dev->resources, plane_res, plane, PLANE); >> + if (p->obj_type == 0) { >> + fprintf(stderr, "Object %i not found, can't set property\n", >> + p->obj_id); >> + return; >> + } >> + >> + if (!props) { >> + fprintf(stderr, "%s %i has no properties\n", >> + obj_type, p->obj_id); >> + return; >> + } >> + >> + for (i = 0; i < (int)props->count_props; ++i) { >> + if (!props_info[i]) >> + continue; >> + if (strcmp(props_info[i]->name, p->name) == 0) >> + break; >> + } >> + >> + if (i == (int)props->count_props) { >> + fprintf(stderr, "%s %i has no %s property\n", >> + obj_type, p->obj_id, p->name); >> + return; >> + } >> + >> + p->prop_id = props->props[i]; >> + >> + ret = drmModeAtomicAddProperty(dev->req, p->obj_id, p->prop_id, p->value); >> + if (ret < 0) >> + fprintf(stderr, "failed to set %s %i property %s to %" PRIu64 ": %s\n", >> + obj_type, p->obj_id, p->name, p->value, strerror(errno)); >> +} >> + >> +/* -------------------------------------------------------------------------- */ >> + >> +/*static bool format_support(const drmModePlanePtr ovr, uint32_t fmt) >> +{ >> + unsigned int i; >> + >> + for (i = 0; i < ovr->count_formats; ++i) { >> + if (ovr->formats[i] == fmt) >> + return true; >> + } >> + >> + return false; >> +}*/ >> + >> +static void add_property(struct device *dev, uint32_t obj_id, >> + const char *name, uint64_t value) >> +{ >> + struct property_arg p; >> + >> + p.obj_id = obj_id; >> + strcpy(p.name, name); >> + p.value = value; >> + >> + set_property(dev, &p); >> +} >> + >> +static int set_plane(struct device *dev, struct plane_arg *p, >> + int pattern, bool update) >> +{ >> + uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; >> + struct bo *plane_bo; >> + int crtc_x, crtc_y, crtc_w, crtc_h; >> + struct crtc *crtc = NULL; >> + unsigned int i; >> + unsigned int old_fb_id; >> + >> + /* Find an unused plane which can be connected to our CRTC. Find the >> + * CRTC index first, then iterate over available planes. >> + */ >> + for (i = 0; i < (unsigned int)dev->resources->res->count_crtcs; i++) { >> + if (p->crtc_id == dev->resources->res->crtcs[i]) { >> + crtc = &dev->resources->crtcs[i]; >> + break; >> + } >> + } >> + >> + if (!crtc) { >> + fprintf(stderr, "CRTC %u not found\n", p->crtc_id); >> + return -1; >> + } >> + >> + if (!update) >> + fprintf(stderr, "testing %dx%d@%s on plane %u, crtc %u\n", >> + p->w, p->h, p->format_str, p->plane_id, p->crtc_id); >> + >> + plane_bo = p->old_bo; >> + p->old_bo = p->bo; >> + >> + if (!plane_bo) { >> + plane_bo = bo_create(dev->fd, p->fourcc, p->w, p->h, >> + handles, pitches, offsets, pattern); >> + >> + if (plane_bo == NULL) >> + return -1; >> + >> + if (drmModeAddFB2(dev->fd, p->w, p->h, p->fourcc, >> + handles, pitches, offsets, &p->fb_id, 0)) { >> + fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); >> + return -1; >> + } >> + } >> + >> + p->bo = plane_bo; >> + >> + old_fb_id = p->fb_id; >> + p->old_fb_id = old_fb_id; >> + >> + crtc_w = p->w * p->scale; >> + crtc_h = p->h * p->scale; >> + if (!p->has_position) { >> + /* Default to the middle of the screen */ >> + crtc_x = (crtc->mode->hdisplay - crtc_w) / 2; >> + crtc_y = (crtc->mode->vdisplay - crtc_h) / 2; >> + } else { >> + crtc_x = p->x; >> + crtc_y = p->y; >> + } >> + >> + add_property(dev, p->plane_id, "FB_ID", p->fb_id); >> + add_property(dev, p->plane_id, "CRTC_ID", p->crtc_id); >> + add_property(dev, p->plane_id, "SRC_X", 0); >> + add_property(dev, p->plane_id, "SRC_Y", 0); >> + add_property(dev, p->plane_id, "SRC_W", p->w << 16); >> + add_property(dev, p->plane_id, "SRC_H", p->h << 16); >> + add_property(dev, p->plane_id, "CRTC_X", crtc_x); >> + add_property(dev, p->plane_id, "CRTC_Y", crtc_y); >> + add_property(dev, p->plane_id, "CRTC_W", crtc_w); >> + add_property(dev, p->plane_id, "CRTC_H", crtc_h); >> + >> + return 0; >> +} >> + >> +static void set_planes(struct device *dev, struct plane_arg *p, >> + unsigned int count, bool update) >> +{ >> + unsigned int i, pattern = UTIL_PATTERN_SMPTE; >> + >> + /* set up planes */ >> + for (i = 0; i < count; i++) { >> + if (i > 0) >> + pattern = UTIL_PATTERN_TILES; >> + >> + if (set_plane(dev, &p[i], pattern, update)) >> + return; >> + } >> +} >> + >> +static void clear_planes(struct device *dev, struct plane_arg *p, unsigned int count) >> +{ >> + unsigned int i; >> + >> + for (i = 0; i < count; i++) { >> + add_property(dev, p[i].plane_id, "FB_ID", 0); >> + add_property(dev, p[i].plane_id, "CRTC_ID", 0); >> + add_property(dev, p[i].plane_id, "SRC_X", 0); >> + add_property(dev, p[i].plane_id, "SRC_Y", 0); >> + add_property(dev, p[i].plane_id, "SRC_W", 0); >> + add_property(dev, p[i].plane_id, "SRC_H", 0); >> + add_property(dev, p[i].plane_id, "CRTC_X", 0); >> + add_property(dev, p[i].plane_id, "CRTC_Y", 0); >> + add_property(dev, p[i].plane_id, "CRTC_W", 0); >> + add_property(dev, p[i].plane_id, "CRTC_H", 0); >> + } >> +} >> + >> +static void clear_FB(struct device *dev, struct plane_arg *p, unsigned int count) >> +{ >> + unsigned int i; >> + >> + for (i = 0; i < count; i++) { >> + if (p[i].fb_id) { >> + drmModeRmFB(dev->fd, p[i].fb_id); >> + p[i].fb_id = 0; >> + } >> + if (p[i].old_fb_id) { >> + drmModeRmFB(dev->fd, p[i].old_fb_id); >> + p[i].old_fb_id = 0; >> + } >> + if (p[i].bo) { >> + bo_destroy(p[i].bo); >> + p[i].bo = NULL; >> + } >> + if (p[i].old_bo) { >> + bo_destroy(p[i].old_bo); >> + p[i].old_bo = NULL; >> + } >> + >> + } >> +} >> + >> +static void set_mode(struct device *dev, struct pipe_arg *pipes, unsigned int count) >> +{ >> + unsigned int i; >> + unsigned int j; >> + int ret; >> + >> + for (i = 0; i < count; i++) { >> + struct pipe_arg *pipe = &pipes[i]; >> + >> + ret = pipe_find_crtc_and_mode(dev, pipe); >> + if (ret < 0) >> + continue; >> + } >> + >> + for (i = 0; i < count; i++) { >> + struct pipe_arg *pipe = &pipes[i]; >> + uint32_t blob_id; >> + >> + if (pipe->mode == NULL) >> + continue; >> + >> + printf("setting mode %s-%dHz@%s on connectors ", >> + pipe->mode_str, pipe->mode->vrefresh, pipe->format_str); >> + for (j = 0; j < pipe->num_cons; ++j) { >> + printf("%s, ", pipe->cons[j]); >> + add_property(dev, pipe->con_ids[j], "CRTC_ID", pipe->crtc->crtc->crtc_id); >> + } >> + printf("crtc %d\n", pipe->crtc->crtc->crtc_id); >> + >> + drmModeCreatePropertyBlob(dev->fd, pipe->mode, sizeof(*pipe->mode), &blob_id); >> + add_property(dev, pipe->crtc->crtc->crtc_id, "MODE_ID", blob_id); >> + add_property(dev, pipe->crtc->crtc->crtc_id, "ACTIVE", 1); >> + } >> +} >> + >> +static void clear_mode(struct device *dev, struct pipe_arg *pipes, unsigned int count) >> +{ >> + unsigned int i; >> + unsigned int j; >> + >> + for (i = 0; i < count; i++) { >> + struct pipe_arg *pipe = &pipes[i]; >> + >> + if (pipe->mode == NULL) >> + continue; >> + >> + for (j = 0; j < pipe->num_cons; ++j) >> + add_property(dev, pipe->con_ids[j], "CRTC_ID",0); >> + >> + add_property(dev, pipe->crtc->crtc->crtc_id, "MODE_ID", 0); >> + add_property(dev, pipe->crtc->crtc->crtc_id, "ACTIVE", 0); >> + } >> + >> +} >> + >> +#define min(a, b) ((a) < (b) ? (a) : (b)) >> + >> +static int parse_connector(struct pipe_arg *pipe, const char *arg) >> +{ >> + unsigned int len; >> + unsigned int i; >> + const char *p; >> + char *endp; >> + >> + pipe->vrefresh = 0; >> + pipe->crtc_id = (uint32_t)-1; >> + strcpy(pipe->format_str, "XR24"); >> + >> + /* Count the number of connectors and allocate them. */ >> + pipe->num_cons = 1; >> + for (p = arg; *p && *p != ':' && *p != '@'; ++p) { >> + if (*p == ',') >> + pipe->num_cons++; >> + } >> + >> + pipe->con_ids = calloc(pipe->num_cons, sizeof(*pipe->con_ids)); >> + pipe->cons = calloc(pipe->num_cons, sizeof(*pipe->cons)); >> + if (pipe->con_ids == NULL || pipe->cons == NULL) >> + return -1; >> + >> + /* Parse the connectors. */ >> + for (i = 0, p = arg; i < pipe->num_cons; ++i, p = endp + 1) { >> + endp = strpbrk(p, ",@:"); >> + if (!endp) >> + break; >> + >> + pipe->cons[i] = strndup(p, endp - p); >> + >> + if (*endp != ',') >> + break; >> + } >> + >> + if (i != pipe->num_cons - 1) >> + return -1; >> + >> + /* Parse the remaining parameters. */ >> + if (*endp == '@') { >> + arg = endp + 1; >> + pipe->crtc_id = strtoul(arg, &endp, 10); >> + } >> + if (*endp != ':') >> + return -1; >> + >> + arg = endp + 1; >> + >> + /* Search for the vertical refresh or the format. */ >> + p = strpbrk(arg, "-@"); >> + if (p == NULL) >> + p = arg + strlen(arg); >> + len = min(sizeof pipe->mode_str - 1, (unsigned int)(p - arg)); >> + strncpy(pipe->mode_str, arg, len); >> + pipe->mode_str[len] = '\0'; >> + >> + if (*p == '-') { >> + pipe->vrefresh = strtoul(p + 1, &endp, 10); >> + p = endp; >> + } >> + >> + if (*p == '@') { >> + strncpy(pipe->format_str, p + 1, 4); >> + pipe->format_str[4] = '\0'; >> + } >> + >> + pipe->fourcc = util_format_fourcc(pipe->format_str); >> + if (pipe->fourcc == 0) { >> + fprintf(stderr, "unknown format %s\n", pipe->format_str); >> + return -1; >> + } >> + >> + return 0; >> +} >> + >> +static int parse_plane(struct plane_arg *plane, const char *p) >> +{ >> + char *end; >> + >> + plane->plane_id = strtoul(p, &end, 10); >> + if (*end != '@') >> + return -EINVAL; >> + >> + p = end + 1; >> + plane->crtc_id = strtoul(p, &end, 10); >> + if (*end != ':') >> + return -EINVAL; >> + >> + p = end + 1; >> + plane->w = strtoul(p, &end, 10); >> + if (*end != 'x') >> + return -EINVAL; >> + >> + p = end + 1; >> + plane->h = strtoul(p, &end, 10); >> + >> + if (*end == '+' || *end == '-') { >> + plane->x = strtol(end, &end, 10); >> + if (*end != '+' && *end != '-') >> + return -EINVAL; >> + plane->y = strtol(end, &end, 10); >> + >> + plane->has_position = true; >> + } >> + >> + if (*end == '*') { >> + p = end + 1; >> + plane->scale = strtod(p, &end); >> + if (plane->scale <= 0.0) >> + return -EINVAL; >> + } else { >> + plane->scale = 1.0; >> + } >> + >> + if (*end == '@') { >> + p = end + 1; >> + if (strlen(p) != 4) >> + return -EINVAL; >> + >> + strcpy(plane->format_str, p); >> + } else { >> + strcpy(plane->format_str, "XR24"); >> + } >> + >> + plane->fourcc = util_format_fourcc(plane->format_str); >> + if (plane->fourcc == 0) { >> + fprintf(stderr, "unknown format %s\n", plane->format_str); >> + return -EINVAL; >> + } >> + >> + return 0; >> +} >> + >> +static int parse_property(struct property_arg *p, const char *arg) >> +{ >> + if (sscanf(arg, "%d:%32[^:]:%" SCNu64, &p->obj_id, p->name, &p->value) != 3) >> + return -1; >> + >> + p->obj_type = 0; >> + p->name[DRM_PROP_NAME_LEN] = '\0'; >> + >> + return 0; >> +} >> + >> +static void usage(char *name) >> +{ >> + fprintf(stderr, "usage: %s [-cDdefMPpsCvw]\n", name); >> + >> + fprintf(stderr, "\n Query options:\n\n"); >> + fprintf(stderr, "\t-c\tlist connectors\n"); >> + fprintf(stderr, "\t-e\tlist encoders\n"); >> + fprintf(stderr, "\t-f\tlist framebuffers\n"); >> + fprintf(stderr, "\t-p\tlist CRTCs and planes (pipes)\n"); >> + >> + fprintf(stderr, "\n Test options:\n\n"); >> + fprintf(stderr, "\t-P <plane_id>@<crtc_id>:<w>x<h>[+<x>+<y>][*<scale>][@<format>]\tset a plane\n"); >> + fprintf(stderr, "\t-s <connector_id>[,<connector_id>][@<crtc_id>]:<mode>[-<vrefresh>][@<format>]\tset a mode\n"); >> + fprintf(stderr, "\t-v\ttest loop\n"); >> + fprintf(stderr, "\t-w <obj_id>:<prop_name>:<value>\tset property\n"); >> + >> + fprintf(stderr, "\n Generic options:\n\n"); >> + fprintf(stderr, "\t-d\tdrop master after mode set\n"); >> + fprintf(stderr, "\t-M module\tuse the given driver\n"); >> + fprintf(stderr, "\t-D device\tuse the given device\n"); >> + >> + fprintf(stderr, "\n\tDefault is to dump all info.\n"); >> + exit(0); >> +} >> + >> +static int pipe_resolve_connectors(struct device *dev, struct pipe_arg *pipe) >> +{ >> + drmModeConnector *connector; >> + unsigned int i; >> + uint32_t id; >> + char *endp; >> + >> + for (i = 0; i < pipe->num_cons; i++) { >> + id = strtoul(pipe->cons[i], &endp, 10); >> + if (endp == pipe->cons[i]) { >> + connector = get_connector_by_name(dev, pipe->cons[i]); >> + if (!connector) { >> + fprintf(stderr, "no connector named '%s'\n", >> + pipe->cons[i]); >> + return -ENODEV; >> + } >> + >> + id = connector->connector_id; >> + } >> + >> + pipe->con_ids[i] = id; >> + } >> + >> + return 0; >> +} >> + >> +static char optstr[] = "cdD:efM:P:ps:vw:"; >> + >> +int main(int argc, char **argv) >> +{ >> + struct device dev; >> + >> + int c; >> + int encoders = 0, connectors = 0, crtcs = 0, planes = 0, framebuffers = 0; >> + int drop_master = 0; >> + int test_loop = 0; >> + char *device = NULL; >> + char *module = NULL; >> + unsigned int i; >> + unsigned int count = 0, plane_count = 0; >> + unsigned int prop_count = 0; >> + struct pipe_arg *pipe_args = NULL; >> + struct plane_arg *plane_args = NULL; >> + struct property_arg *prop_args = NULL; >> + unsigned int args = 0; >> + int ret; >> + >> + memset(&dev, 0, sizeof dev); >> + >> + opterr = 0; >> + while ((c = getopt(argc, argv, optstr)) != -1) { >> + args++; >> + >> + switch (c) { >> + case 'c': >> + connectors = 1; >> + break; >> + case 'D': >> + device = optarg; >> + args--; >> + break; >> + case 'd': >> + drop_master = 1; >> + break; >> + case 'e': >> + encoders = 1; >> + break; >> + case 'f': >> + framebuffers = 1; >> + break; >> + case 'M': >> + module = optarg; >> + /* Preserve the default behaviour of dumping all information. */ >> + args--; >> + break; >> + case 'P': >> + plane_args = realloc(plane_args, >> + (plane_count + 1) * sizeof *plane_args); >> + if (plane_args == NULL) { >> + fprintf(stderr, "memory allocation failed\n"); >> + return 1; >> + } >> + memset(&plane_args[plane_count], 0, sizeof(*plane_args)); >> + >> + if (parse_plane(&plane_args[plane_count], optarg) < 0) >> + usage(argv[0]); >> + >> + plane_count++; >> + break; >> + case 'p': >> + crtcs = 1; >> + planes = 1; >> + break; >> + case 's': >> + pipe_args = realloc(pipe_args, >> + (count + 1) * sizeof *pipe_args); >> + if (pipe_args == NULL) { >> + fprintf(stderr, "memory allocation failed\n"); >> + return 1; >> + } >> + memset(&pipe_args[count], 0, sizeof(*pipe_args)); >> + >> + if (parse_connector(&pipe_args[count], optarg) < 0) >> + usage(argv[0]); >> + >> + count++; >> + break; >> + case 'v': >> + test_loop = 1; >> + break; >> + case 'w': >> + prop_args = realloc(prop_args, >> + (prop_count + 1) * sizeof *prop_args); >> + if (prop_args == NULL) { >> + fprintf(stderr, "memory allocation failed\n"); >> + return 1; >> + } >> + memset(&prop_args[prop_count], 0, sizeof(*prop_args)); >> + >> + if (parse_property(&prop_args[prop_count], optarg) < 0) >> + usage(argv[0]); >> + >> + prop_count++; >> + break; >> + default: >> + usage(argv[0]); >> + break; >> + } >> + } >> + >> + if (!args) >> + encoders = connectors = crtcs = planes = framebuffers = 1; >> + >> + dev.fd = util_open(device, module); >> + if (dev.fd < 0) >> + return -1; >> + >> + ret = drmSetClientCap(dev.fd, DRM_CLIENT_CAP_ATOMIC, 1); >> + if (ret) { >> + fprintf(stderr, "no atomic modesetting support: %s\n", strerror(errno)); >> + drmClose(dev.fd); >> + return -1; >> + } >> + >> + dev.resources = get_resources(&dev); >> + if (!dev.resources) { >> + drmClose(dev.fd); >> + return 1; >> + } >> + >> + for (i = 0; i < count; i++) { >> + if (pipe_resolve_connectors(&dev, &pipe_args[i]) < 0) { >> + free_resources(dev.resources); >> + drmClose(dev.fd); >> + return 1; >> + } >> + } >> + >> +#define dump_resource(dev, res) if (res) dump_##res(dev) >> + >> + dump_resource(&dev, encoders); >> + dump_resource(&dev, connectors); >> + dump_resource(&dev, crtcs); >> + dump_resource(&dev, planes); >> + dump_resource(&dev, framebuffers); >> + >> + dev.req = drmModeAtomicAlloc(); >> + >> + for (i = 0; i < prop_count; ++i) >> + set_property(&dev, &prop_args[i]); >> + >> + if (count && plane_count) { >> + uint64_t cap = 0; >> + >> + ret = drmGetCap(dev.fd, DRM_CAP_DUMB_BUFFER, &cap); >> + if (ret || cap == 0) { >> + fprintf(stderr, "driver doesn't support the dumb buffer API\n"); >> + drmModeAtomicFree(dev.req); >> + return 1; >> + } >> + >> + set_mode(&dev, pipe_args, count); >> + set_planes(&dev, plane_args, plane_count, false); >> + >> + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); >> + if (ret) { >> + fprintf(stderr, "Atomic Commit failed [1]\n"); >> + return 1; >> + } >> + >> + gettimeofday(&pipe_args->start, NULL); >> + pipe_args->swap_count = 0; >> + >> + while (test_loop) { >> + drmModeAtomicFree(dev.req); >> + dev.req = drmModeAtomicAlloc(); >> + set_planes(&dev, plane_args, plane_count, true); >> + >> + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); >> + if (ret) { >> + fprintf(stderr, "Atomic Commit failed [2]\n"); >> + return 1; >> + } >> + >> + pipe_args->swap_count++; >> + if (pipe_args->swap_count == 60) { >> + struct timeval end; >> + double t; >> + >> + gettimeofday(&end, NULL); >> + t = end.tv_sec + end.tv_usec * 1e-6 - >> + (pipe_args->start.tv_sec + pipe_args->start.tv_usec * 1e-6); >> + fprintf(stderr, "freq: %.02fHz\n", pipe_args->swap_count / t); >> + pipe_args->swap_count = 0; >> + pipe_args->start = end; >> + } >> + } >> + >> + if (drop_master) >> + drmDropMaster(dev.fd); >> + >> + getchar(); >> + >> + drmModeAtomicFree(dev.req); >> + dev.req = drmModeAtomicAlloc(); >> + >> + clear_mode(&dev, pipe_args, count); >> + clear_planes(&dev, plane_args, plane_count); >> + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); >> + if (ret) { >> + fprintf(stderr, "Atomic Commit failed\n"); >> + return 1; >> + } >> + >> + clear_FB(&dev, plane_args, plane_count); >> + } >> + >> + drmModeAtomicFree(dev.req); >> + free_resources(dev.resources); >> + >> + return 0; >> +} >> -- >> 2.15.0 >> >> _______________________________________________ >> dri-devel mailing list >> dri-devel@lists.freedesktop.org >> https://lists.freedesktop.org/mailman/listinfo/dri-devel
On 20 July 2018 at 12:33, Benjamin Gaignard <benjamin.gaignard@linaro.org> wrote: > This is a modetest like tool but using atomic API. > > With modetest_atomic it is mandatory to specify a mode ("-s") > and a plane ("-P") to display a pattern on screen. > > "-v" does a loop swapping between two framebuffers for each > active planes. > > modetest_atomic doesn't offer cursor support > > Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> > --- > > The code is based on modetest and keep most of it infrastructure > like arguments parsing, finding properties id from their name, > resources dumping or the general way of working. > It duplicates modetest code but adding compilation flags or > conditional tests everywhere in modetest would have made it > more complex and unreadable. > > Creating modetest_atomic could allow to test atomic API without > need to use "big" frameworks like weston, drm_hwcomposer or igt > with all their dependencies. > kmscube could also be used to test atomic API but it need EGL. > > It have been tested (only) on stm driver with one or two planes > actived. > > tests/modetest/Makefile.am | 13 +- > tests/modetest/Makefile.sources | 7 + > tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ Quick diff/grep/wc exercise shows that ~10% of modetest_atomic.c is new code. I agree that modetest.c is getting kind of big and messy. How about instead of duplicating it, we flesh out the legacy vs atomic codepaths into helpers in separate files? It will result in [c]leaner code, only one executable installed/packaged and less build system changes. What do you think? Emil
2018-07-25 12:29 GMT+02:00 Emil Velikov <emil.l.velikov@gmail.com>: > On 20 July 2018 at 12:33, Benjamin Gaignard > <benjamin.gaignard@linaro.org> wrote: >> This is a modetest like tool but using atomic API. >> >> With modetest_atomic it is mandatory to specify a mode ("-s") >> and a plane ("-P") to display a pattern on screen. >> >> "-v" does a loop swapping between two framebuffers for each >> active planes. >> >> modetest_atomic doesn't offer cursor support >> >> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> >> --- >> >> The code is based on modetest and keep most of it infrastructure >> like arguments parsing, finding properties id from their name, >> resources dumping or the general way of working. >> It duplicates modetest code but adding compilation flags or >> conditional tests everywhere in modetest would have made it >> more complex and unreadable. >> >> Creating modetest_atomic could allow to test atomic API without >> need to use "big" frameworks like weston, drm_hwcomposer or igt >> with all their dependencies. >> kmscube could also be used to test atomic API but it need EGL. >> >> It have been tested (only) on stm driver with one or two planes >> actived. >> >> tests/modetest/Makefile.am | 13 +- >> tests/modetest/Makefile.sources | 7 + >> tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ > > Quick diff/grep/wc exercise shows that ~10% of modetest_atomic.c is new code. > I agree that modetest.c is getting kind of big and messy. How about > instead of duplicating it, we flesh out the legacy vs atomic codepaths > into helpers in separate files? > > It will result in [c]leaner code, only one executable > installed/packaged and less build system changes. > > What do you think? > Emil It was my initial thought too: I have tried to add "-a" option to enable atomic way of working but their is small changes to do everywhere and that add lot of conditional tests. Make the changes for build system isn't that complicated (see in v2) Regards, Benjamin
On 25 July 2018 at 12:27, Benjamin Gaignard <benjamin.gaignard@linaro.org> wrote: > 2018-07-25 12:29 GMT+02:00 Emil Velikov <emil.l.velikov@gmail.com>: >> On 20 July 2018 at 12:33, Benjamin Gaignard >> <benjamin.gaignard@linaro.org> wrote: >>> This is a modetest like tool but using atomic API. >>> >>> With modetest_atomic it is mandatory to specify a mode ("-s") >>> and a plane ("-P") to display a pattern on screen. >>> >>> "-v" does a loop swapping between two framebuffers for each >>> active planes. >>> >>> modetest_atomic doesn't offer cursor support >>> >>> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> >>> --- >>> >>> The code is based on modetest and keep most of it infrastructure >>> like arguments parsing, finding properties id from their name, >>> resources dumping or the general way of working. >>> It duplicates modetest code but adding compilation flags or >>> conditional tests everywhere in modetest would have made it >>> more complex and unreadable. >>> >>> Creating modetest_atomic could allow to test atomic API without >>> need to use "big" frameworks like weston, drm_hwcomposer or igt >>> with all their dependencies. >>> kmscube could also be used to test atomic API but it need EGL. >>> >>> It have been tested (only) on stm driver with one or two planes >>> actived. >>> >>> tests/modetest/Makefile.am | 13 +- >>> tests/modetest/Makefile.sources | 7 + >>> tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ >> >> Quick diff/grep/wc exercise shows that ~10% of modetest_atomic.c is new code. >> I agree that modetest.c is getting kind of big and messy. How about >> instead of duplicating it, we flesh out the legacy vs atomic codepaths >> into helpers in separate files? >> >> It will result in [c]leaner code, only one executable >> installed/packaged and less build system changes. >> >> What do you think? >> Emil > > It was my initial thought too: I have tried to add "-a" option to > enable atomic way > of working but their is small changes to do everywhere and that add > lot of conditional tests. Can you please try beating something into shape? It's fairly hard to judge without anything to look at. Thanks Emil
2018-07-25 14:05 GMT+02:00 Emil Velikov <emil.l.velikov@gmail.com>: > On 25 July 2018 at 12:27, Benjamin Gaignard > <benjamin.gaignard@linaro.org> wrote: >> 2018-07-25 12:29 GMT+02:00 Emil Velikov <emil.l.velikov@gmail.com>: >>> On 20 July 2018 at 12:33, Benjamin Gaignard >>> <benjamin.gaignard@linaro.org> wrote: >>>> This is a modetest like tool but using atomic API. >>>> >>>> With modetest_atomic it is mandatory to specify a mode ("-s") >>>> and a plane ("-P") to display a pattern on screen. >>>> >>>> "-v" does a loop swapping between two framebuffers for each >>>> active planes. >>>> >>>> modetest_atomic doesn't offer cursor support >>>> >>>> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> >>>> --- >>>> >>>> The code is based on modetest and keep most of it infrastructure >>>> like arguments parsing, finding properties id from their name, >>>> resources dumping or the general way of working. >>>> It duplicates modetest code but adding compilation flags or >>>> conditional tests everywhere in modetest would have made it >>>> more complex and unreadable. >>>> >>>> Creating modetest_atomic could allow to test atomic API without >>>> need to use "big" frameworks like weston, drm_hwcomposer or igt >>>> with all their dependencies. >>>> kmscube could also be used to test atomic API but it need EGL. >>>> >>>> It have been tested (only) on stm driver with one or two planes >>>> actived. >>>> >>>> tests/modetest/Makefile.am | 13 +- >>>> tests/modetest/Makefile.sources | 7 + >>>> tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ >>> >>> Quick diff/grep/wc exercise shows that ~10% of modetest_atomic.c is new code. >>> I agree that modetest.c is getting kind of big and messy. How about >>> instead of duplicating it, we flesh out the legacy vs atomic codepaths >>> into helpers in separate files? >>> >>> It will result in [c]leaner code, only one executable >>> installed/packaged and less build system changes. >>> >>> What do you think? >>> Emil >> >> It was my initial thought too: I have tried to add "-a" option to >> enable atomic way >> of working but their is small changes to do everywhere and that add >> lot of conditional tests. > Can you please try beating something into shape? It's fairly hard to > judge without anything to look at. After dump_resource code in main function all the logic is different between the two modetest because, with atomic you to build a request and commit it while, with legacy API, you could set mode and after set a plane. In addition with atomic version you have set a mode and a plane to be able to display something. That explain why the end of main function is completely different. Another example in the set_mode function: in legacy API you have create a FB and reference/use it with calls to bo_create, drmModeAddFB2 and drmModeSetCrtc while for atomic you have to set properties on connectors, create and set a blob for the mode before active the crct. The same diff exist for set_plane. I can add big if/else blocks but I do think that will make the code more readable. > > Thanks > Emil
On 25 July 2018 at 13:37, Benjamin Gaignard <benjamin.gaignard@linaro.org> wrote: > 2018-07-25 14:05 GMT+02:00 Emil Velikov <emil.l.velikov@gmail.com>: >> On 25 July 2018 at 12:27, Benjamin Gaignard >> <benjamin.gaignard@linaro.org> wrote: >>> 2018-07-25 12:29 GMT+02:00 Emil Velikov <emil.l.velikov@gmail.com>: >>>> On 20 July 2018 at 12:33, Benjamin Gaignard >>>> <benjamin.gaignard@linaro.org> wrote: >>>>> This is a modetest like tool but using atomic API. >>>>> >>>>> With modetest_atomic it is mandatory to specify a mode ("-s") >>>>> and a plane ("-P") to display a pattern on screen. >>>>> >>>>> "-v" does a loop swapping between two framebuffers for each >>>>> active planes. >>>>> >>>>> modetest_atomic doesn't offer cursor support >>>>> >>>>> Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> >>>>> --- >>>>> >>>>> The code is based on modetest and keep most of it infrastructure >>>>> like arguments parsing, finding properties id from their name, >>>>> resources dumping or the general way of working. >>>>> It duplicates modetest code but adding compilation flags or >>>>> conditional tests everywhere in modetest would have made it >>>>> more complex and unreadable. >>>>> >>>>> Creating modetest_atomic could allow to test atomic API without >>>>> need to use "big" frameworks like weston, drm_hwcomposer or igt >>>>> with all their dependencies. >>>>> kmscube could also be used to test atomic API but it need EGL. >>>>> >>>>> It have been tested (only) on stm driver with one or two planes >>>>> actived. >>>>> >>>>> tests/modetest/Makefile.am | 13 +- >>>>> tests/modetest/Makefile.sources | 7 + >>>>> tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ >>>> >>>> Quick diff/grep/wc exercise shows that ~10% of modetest_atomic.c is new code. >>>> I agree that modetest.c is getting kind of big and messy. How about >>>> instead of duplicating it, we flesh out the legacy vs atomic codepaths >>>> into helpers in separate files? >>>> >>>> It will result in [c]leaner code, only one executable >>>> installed/packaged and less build system changes. >>>> >>>> What do you think? >>>> Emil >>> >>> It was my initial thought too: I have tried to add "-a" option to >>> enable atomic way >>> of working but their is small changes to do everywhere and that add >>> lot of conditional tests. >> Can you please try beating something into shape? It's fairly hard to >> judge without anything to look at. > > After dump_resource code in main function all the logic is different > between the two > modetest because, with atomic you to build a request and commit it > while, with legacy API, > you could set mode and after set a plane. > In addition with atomic version you have set a mode and a plane to be > able to display something. > That explain why the end of main function is completely different. > > Another example in the set_mode function: in legacy API you have > create a FB and reference/use it > with calls to bo_create, drmModeAddFB2 and drmModeSetCrtc while for > atomic you have to set > properties on connectors, create and set a blob for the mode before > active the crct. > The same diff exist for set_plane. > > I can add big if/else blocks but I do think that will make the code > more readable. Guessing you meant "...I do not think..." What is the blocker of moving the atomic specifics into a few functions? As said before legacy.c and atomic.c would be great. That will about 5(?) cases of if (atomic) atomic_foo(...) else legacy_foo(...) I can see that 5 (10 even) cases of if (atomic) atomic_foo(...) else legacy_foo(...) can be fiddly. Yet it fades in the context of 1.5kloc. Thanks Emil
diff --git a/tests/modetest/Makefile.am b/tests/modetest/Makefile.am index 4b296c83..8f697bb3 100644 --- a/tests/modetest/Makefile.am +++ b/tests/modetest/Makefile.am @@ -10,10 +10,12 @@ AM_CFLAGS += \ if HAVE_INSTALL_TESTS bin_PROGRAMS = \ - modetest + modetest \ + modetest_atomic else noinst_PROGRAMS = \ - modetest + modetest \ + modetest_atomic endif modetest_SOURCES = $(MODETEST_FILES) @@ -22,3 +24,10 @@ modetest_LDADD = \ $(top_builddir)/libdrm.la \ $(top_builddir)/tests/util/libutil.la \ $(CAIRO_LIBS) + +modetest_atomic_SOURCES = $(MODETEST_ATOMIC_FILES) + +modetest_atomic_LDADD = \ + $(top_builddir)/libdrm.la \ + $(top_builddir)/tests/util/libutil.la \ + $(CAIRO_LIBS) diff --git a/tests/modetest/Makefile.sources b/tests/modetest/Makefile.sources index 399af0df..0a1df4c0 100644 --- a/tests/modetest/Makefile.sources +++ b/tests/modetest/Makefile.sources @@ -4,3 +4,10 @@ MODETEST_FILES := \ cursor.c \ cursor.h \ modetest.c + +MODETEST_ATOMIC_FILES := \ + buffers.c \ + buffers.h \ + cursor.c \ + cursor.h \ + modetest_atomic.c diff --git a/tests/modetest/modetest_atomic.c b/tests/modetest/modetest_atomic.c new file mode 100644 index 00000000..8c877860 --- /dev/null +++ b/tests/modetest/modetest_atomic.c @@ -0,0 +1,1546 @@ +/* + * DRM based mode setting test program + * Copyright 2008 Tungsten Graphics + * Jakob Bornecrantz <jakob@tungstengraphics.com> + * Copyright 2008 Intel Corporation + * Jesse Barnes <jesse.barnes@intel.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* + * This fairly simple test program dumps output in a similar format to the + * "xrandr" tool everyone knows & loves. It's necessarily slightly different + * since the kernel separates outputs into encoder and connector structures, + * each with their own unique ID. The program also allows test testing of the + * memory management and mode setting APIs by allowing the user to specify a + * connector and mode to use for mode setting. If all works as expected, a + * blue background should be painted on the monitor attached to the specified + * connector after the selected mode is set. + * + * TODO: use cairo to write the mode info on the selected output once + * the mode has been programmed, along with possible test patterns. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <unistd.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <poll.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include "xf86drm.h" +#include "xf86drmMode.h" +#include "drm_fourcc.h" + +#include "util/common.h" +#include "util/format.h" +#include "util/kms.h" +#include "util/pattern.h" + +#include "buffers.h" + +struct crtc { + drmModeCrtc *crtc; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + drmModeModeInfo *mode; +}; + +struct encoder { + drmModeEncoder *encoder; +}; + +struct connector { + drmModeConnector *connector; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + char *name; +}; + +struct fb { + drmModeFB *fb; +}; + +struct plane { + drmModePlane *plane; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; +}; + +struct resources { + drmModeRes *res; + drmModePlaneRes *plane_res; + + struct crtc *crtcs; + struct encoder *encoders; + struct connector *connectors; + struct fb *fbs; + struct plane *planes; +}; + +struct device { + int fd; + + struct resources *resources; + drmModeAtomicReq *req; +}; + +static inline int64_t U642I64(uint64_t val) +{ + return (int64_t)*((int64_t *)&val); +} + +#define bit_name_fn(res) \ +const char * res##_str(int type) { \ + unsigned int i; \ + const char *sep = ""; \ + for (i = 0; i < ARRAY_SIZE(res##_names); i++) { \ + if (type & (1 << i)) { \ + printf("%s%s", sep, res##_names[i]); \ + sep = ", "; \ + } \ + } \ + return NULL; \ +} + +static const char *mode_type_names[] = { + "builtin", + "clock_c", + "crtc_c", + "preferred", + "default", + "userdef", + "driver", +}; + +static bit_name_fn(mode_type) + +static const char *mode_flag_names[] = { + "phsync", + "nhsync", + "pvsync", + "nvsync", + "interlace", + "dblscan", + "csync", + "pcsync", + "ncsync", + "hskew", + "bcast", + "pixmux", + "dblclk", + "clkdiv2" +}; + +static bit_name_fn(mode_flag) + +static void dump_fourcc(uint32_t fourcc) +{ + printf(" %c%c%c%c", + fourcc, + fourcc >> 8, + fourcc >> 16, + fourcc >> 24); +} + +static void dump_encoders(struct device *dev) +{ + drmModeEncoder *encoder; + int i; + + printf("Encoders:\n"); + printf("id\tcrtc\ttype\tpossible crtcs\tpossible clones\t\n"); + for (i = 0; i < dev->resources->res->count_encoders; i++) { + encoder = dev->resources->encoders[i].encoder; + if (!encoder) + continue; + + printf("%d\t%d\t%s\t0x%08x\t0x%08x\n", + encoder->encoder_id, + encoder->crtc_id, + util_lookup_encoder_type_name(encoder->encoder_type), + encoder->possible_crtcs, + encoder->possible_clones); + } + printf("\n"); +} + +static void dump_mode(drmModeModeInfo *mode) +{ + printf(" %s %d %d %d %d %d %d %d %d %d %d", + mode->name, + mode->vrefresh, + mode->hdisplay, + mode->hsync_start, + mode->hsync_end, + mode->htotal, + mode->vdisplay, + mode->vsync_start, + mode->vsync_end, + mode->vtotal, + mode->clock); + + printf(" flags: "); + mode_flag_str(mode->flags); + printf("; type: "); + mode_type_str(mode->type); + printf("\n"); +} + +static void dump_blob(struct device *dev, uint32_t blob_id) +{ + uint32_t i; + unsigned char *blob_data; + drmModePropertyBlobPtr blob; + + blob = drmModeGetPropertyBlob(dev->fd, blob_id); + if (!blob) { + printf("\n"); + return; + } + + blob_data = blob->data; + + for (i = 0; i < blob->length; i++) { + if (i % 16 == 0) + printf("\n\t\t\t"); + printf("%.2hhx", blob_data[i]); + } + printf("\n"); + + drmModeFreePropertyBlob(blob); +} + +static void dump_prop(struct device *dev, drmModePropertyPtr prop, + uint32_t prop_id, uint64_t value) +{ + int i; + printf("\t%d", prop_id); + if (!prop) { + printf("\n"); + return; + } + + printf(" %s:\n", prop->name); + + printf("\t\tflags:"); + if (prop->flags & DRM_MODE_PROP_PENDING) + printf(" pending"); + if (prop->flags & DRM_MODE_PROP_IMMUTABLE) + printf(" immutable"); + if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) + printf(" signed range"); + if (drm_property_type_is(prop, DRM_MODE_PROP_RANGE)) + printf(" range"); + if (drm_property_type_is(prop, DRM_MODE_PROP_ENUM)) + printf(" enum"); + if (drm_property_type_is(prop, DRM_MODE_PROP_BITMASK)) + printf(" bitmask"); + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) + printf(" blob"); + if (drm_property_type_is(prop, DRM_MODE_PROP_OBJECT)) + printf(" object"); + printf("\n"); + + if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) { + printf("\t\tvalues:"); + for (i = 0; i < prop->count_values; i++) + printf(" %"PRId64, U642I64(prop->values[i])); + printf("\n"); + } + + if (drm_property_type_is(prop, DRM_MODE_PROP_RANGE)) { + printf("\t\tvalues:"); + for (i = 0; i < prop->count_values; i++) + printf(" %"PRIu64, prop->values[i]); + printf("\n"); + } + + if (drm_property_type_is(prop, DRM_MODE_PROP_ENUM)) { + printf("\t\tenums:"); + for (i = 0; i < prop->count_enums; i++) + printf(" %s=%llu", prop->enums[i].name, + prop->enums[i].value); + printf("\n"); + } else if (drm_property_type_is(prop, DRM_MODE_PROP_BITMASK)) { + printf("\t\tvalues:"); + for (i = 0; i < prop->count_enums; i++) + printf(" %s=0x%llx", prop->enums[i].name, + (1LL << prop->enums[i].value)); + printf("\n"); + } else { + assert(prop->count_enums == 0); + } + + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) { + printf("\t\tblobs:\n"); + for (i = 0; i < prop->count_blobs; i++) + dump_blob(dev, prop->blob_ids[i]); + printf("\n"); + } else { + assert(prop->count_blobs == 0); + } + + printf("\t\tvalue:"); + if (drm_property_type_is(prop, DRM_MODE_PROP_BLOB)) + dump_blob(dev, value); + else if (drm_property_type_is(prop, DRM_MODE_PROP_SIGNED_RANGE)) + printf(" %"PRId64"\n", value); + else + printf(" %"PRIu64"\n", value); +} + +static void dump_connectors(struct device *dev) +{ + int i, j; + + printf("Connectors:\n"); + printf("id\tencoder\tstatus\t\tname\t\tsize (mm)\tmodes\tencoders\n"); + for (i = 0; i < dev->resources->res->count_connectors; i++) { + struct connector *_connector = &dev->resources->connectors[i]; + drmModeConnector *connector = _connector->connector; + if (!connector) + continue; + + printf("%d\t%d\t%s\t%-15s\t%dx%d\t\t%d\t", + connector->connector_id, + connector->encoder_id, + util_lookup_connector_status_name(connector->connection), + _connector->name, + connector->mmWidth, connector->mmHeight, + connector->count_modes); + + for (j = 0; j < connector->count_encoders; j++) + printf("%s%d", j > 0 ? ", " : "", connector->encoders[j]); + printf("\n"); + + if (connector->count_modes) { + printf(" modes:\n"); + printf("\tname refresh (Hz) hdisp hss hse htot vdisp " + "vss vse vtot)\n"); + for (j = 0; j < connector->count_modes; j++) + dump_mode(&connector->modes[j]); + } + + if (_connector->props) { + printf(" props:\n"); + for (j = 0; j < (int)_connector->props->count_props; j++) + dump_prop(dev, _connector->props_info[j], + _connector->props->props[j], + _connector->props->prop_values[j]); + } + } + printf("\n"); +} + +static void dump_crtcs(struct device *dev) +{ + int i; + uint32_t j; + + printf("CRTCs:\n"); + printf("id\tfb\tpos\tsize\n"); + for (i = 0; i < dev->resources->res->count_crtcs; i++) { + struct crtc *_crtc = &dev->resources->crtcs[i]; + drmModeCrtc *crtc = _crtc->crtc; + if (!crtc) + continue; + + printf("%d\t%d\t(%d,%d)\t(%dx%d)\n", + crtc->crtc_id, + crtc->buffer_id, + crtc->x, crtc->y, + crtc->width, crtc->height); + dump_mode(&crtc->mode); + + if (_crtc->props) { + printf(" props:\n"); + for (j = 0; j < _crtc->props->count_props; j++) + dump_prop(dev, _crtc->props_info[j], + _crtc->props->props[j], + _crtc->props->prop_values[j]); + } else { + printf(" no properties found\n"); + } + } + printf("\n"); +} + +static void dump_framebuffers(struct device *dev) +{ + drmModeFB *fb; + int i; + + printf("Frame buffers:\n"); + printf("id\tsize\tpitch\n"); + for (i = 0; i < dev->resources->res->count_fbs; i++) { + fb = dev->resources->fbs[i].fb; + if (!fb) + continue; + + printf("%u\t(%ux%u)\t%u\n", + fb->fb_id, + fb->width, fb->height, + fb->pitch); + } + printf("\n"); +} + +static void dump_planes(struct device *dev) +{ + unsigned int i, j; + + printf("Planes:\n"); + printf("id\tcrtc\tfb\tCRTC x,y\tx,y\tgamma size\tpossible crtcs\n"); + + if (!dev->resources->plane_res) + return; + + for (i = 0; i < dev->resources->plane_res->count_planes; i++) { + struct plane *plane = &dev->resources->planes[i]; + drmModePlane *ovr = plane->plane; + if (!ovr) + continue; + + printf("%d\t%d\t%d\t%d,%d\t\t%d,%d\t%-8d\t0x%08x\n", + ovr->plane_id, ovr->crtc_id, ovr->fb_id, + ovr->crtc_x, ovr->crtc_y, ovr->x, ovr->y, + ovr->gamma_size, ovr->possible_crtcs); + + if (!ovr->count_formats) + continue; + + printf(" formats:"); + for (j = 0; j < ovr->count_formats; j++) + dump_fourcc(ovr->formats[j]); + printf("\n"); + + if (plane->props) { + printf(" props:\n"); + for (j = 0; j < plane->props->count_props; j++) + dump_prop(dev, plane->props_info[j], + plane->props->props[j], + plane->props->prop_values[j]); + } else { + printf(" no properties found\n"); + } + } + printf("\n"); + + return; +} + +static void free_resources(struct resources *res) +{ + int i; + + if (!res) + return; + +#define free_resource(_res, __res, type, Type) \ + do { \ + if (!(_res)->type##s) \ + break; \ + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ + if (!(_res)->type##s[i].type) \ + break; \ + drmModeFree##Type((_res)->type##s[i].type); \ + } \ + free((_res)->type##s); \ + } while (0) + +#define free_properties(_res, __res, type) \ + do { \ + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ + drmModeFreeObjectProperties(res->type##s[i].props); \ + free(res->type##s[i].props_info); \ + } \ + } while (0) + + if (res->res) { + free_properties(res, res, crtc); + + free_resource(res, res, crtc, Crtc); + free_resource(res, res, encoder, Encoder); + + for (i = 0; i < res->res->count_connectors; i++) + free(res->connectors[i].name); + + free_resource(res, res, connector, Connector); + free_resource(res, res, fb, FB); + + drmModeFreeResources(res->res); + } + + if (res->plane_res) { + free_properties(res, plane_res, plane); + + free_resource(res, plane_res, plane, Plane); + + drmModeFreePlaneResources(res->plane_res); + } + + free(res); +} + +static struct resources *get_resources(struct device *dev) +{ + struct resources *res; + int i; + + res = calloc(1, sizeof(*res)); + if (res == 0) + return NULL; + + drmSetClientCap(dev->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + + res->res = drmModeGetResources(dev->fd); + if (!res->res) { + fprintf(stderr, "drmModeGetResources failed: %s\n", + strerror(errno)); + goto error; + } + + res->crtcs = calloc(res->res->count_crtcs, sizeof(*res->crtcs)); + res->encoders = calloc(res->res->count_encoders, sizeof(*res->encoders)); + res->connectors = calloc(res->res->count_connectors, sizeof(*res->connectors)); + res->fbs = calloc(res->res->count_fbs, sizeof(*res->fbs)); + + if (!res->crtcs || !res->encoders || !res->connectors || !res->fbs) + goto error; + +#define get_resource(_res, __res, type, Type) \ + do { \ + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ + (_res)->type##s[i].type = \ + drmModeGet##Type(dev->fd, (_res)->__res->type##s[i]); \ + if (!(_res)->type##s[i].type) \ + fprintf(stderr, "could not get %s %i: %s\n", \ + #type, (_res)->__res->type##s[i], \ + strerror(errno)); \ + } \ + } while (0) + + get_resource(res, res, crtc, Crtc); + get_resource(res, res, encoder, Encoder); + get_resource(res, res, connector, Connector); + get_resource(res, res, fb, FB); + + /* Set the name of all connectors based on the type name and the per-type ID. */ + for (i = 0; i < res->res->count_connectors; i++) { + struct connector *connector = &res->connectors[i]; + drmModeConnector *conn = connector->connector; + int num; + + num = asprintf(&connector->name, "%s-%u", + util_lookup_connector_type_name(conn->connector_type), + conn->connector_type_id); + if (num < 0) + goto error; + } + +#define get_properties(_res, __res, type, Type) \ + do { \ + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ + struct type *obj = &res->type##s[i]; \ + unsigned int j; \ + obj->props = \ + drmModeObjectGetProperties(dev->fd, obj->type->type##_id, \ + DRM_MODE_OBJECT_##Type); \ + if (!obj->props) { \ + fprintf(stderr, \ + "could not get %s %i properties: %s\n", \ + #type, obj->type->type##_id, \ + strerror(errno)); \ + continue; \ + } \ + obj->props_info = calloc(obj->props->count_props, \ + sizeof(*obj->props_info)); \ + if (!obj->props_info) \ + continue; \ + for (j = 0; j < obj->props->count_props; ++j) \ + obj->props_info[j] = \ + drmModeGetProperty(dev->fd, obj->props->props[j]); \ + } \ + } while (0) + + get_properties(res, res, crtc, CRTC); + get_properties(res, res, connector, CONNECTOR); + + for (i = 0; i < res->res->count_crtcs; ++i) + res->crtcs[i].mode = &res->crtcs[i].crtc->mode; + + res->plane_res = drmModeGetPlaneResources(dev->fd); + if (!res->plane_res) { + fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", + strerror(errno)); + return res; + } + + res->planes = calloc(res->plane_res->count_planes, sizeof(*res->planes)); + if (!res->planes) + goto error; + + get_resource(res, plane_res, plane, Plane); + get_properties(res, plane_res, plane, PLANE); + + return res; + +error: + free_resources(res); + return NULL; +} + +static int get_crtc_index(struct device *dev, uint32_t id) +{ + int i; + + for (i = 0; i < dev->resources->res->count_crtcs; ++i) { + drmModeCrtc *crtc = dev->resources->crtcs[i].crtc; + if (crtc && crtc->crtc_id == id) + return i; + } + + return -1; +} + +static drmModeConnector *get_connector_by_name(struct device *dev, const char *name) +{ + struct connector *connector; + int i; + + for (i = 0; i < dev->resources->res->count_connectors; i++) { + connector = &dev->resources->connectors[i]; + + if (strcmp(connector->name, name) == 0) + return connector->connector; + } + + return NULL; +} + +static drmModeConnector *get_connector_by_id(struct device *dev, uint32_t id) +{ + drmModeConnector *connector; + int i; + + for (i = 0; i < dev->resources->res->count_connectors; i++) { + connector = dev->resources->connectors[i].connector; + if (connector && connector->connector_id == id) + return connector; + } + + return NULL; +} + +static drmModeEncoder *get_encoder_by_id(struct device *dev, uint32_t id) +{ + drmModeEncoder *encoder; + int i; + + for (i = 0; i < dev->resources->res->count_encoders; i++) { + encoder = dev->resources->encoders[i].encoder; + if (encoder && encoder->encoder_id == id) + return encoder; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Pipes and planes + */ + +/* + * Mode setting with the kernel interfaces is a bit of a chore. + * First you have to find the connector in question and make sure the + * requested mode is available. + * Then you need to find the encoder attached to that connector so you + * can bind it with a free crtc. + */ +struct pipe_arg { + const char **cons; + uint32_t *con_ids; + unsigned int num_cons; + uint32_t crtc_id; + char mode_str[64]; + char format_str[5]; + unsigned int vrefresh; + unsigned int fourcc; + drmModeModeInfo *mode; + struct crtc *crtc; + unsigned int fb_id[2], current_fb_id; + struct timeval start; + + int swap_count; +}; + +struct plane_arg { + uint32_t plane_id; /* the id of plane to use */ + uint32_t crtc_id; /* the id of CRTC to bind to */ + bool has_position; + int32_t x, y; + uint32_t w, h; + double scale; + unsigned int fb_id; + unsigned int old_fb_id; + struct bo *bo; + struct bo *old_bo; + char format_str[5]; /* need to leave room for terminating \0 */ + unsigned int fourcc; +}; + +static drmModeModeInfo * +connector_find_mode(struct device *dev, uint32_t con_id, const char *mode_str, + const unsigned int vrefresh) +{ + drmModeConnector *connector; + drmModeModeInfo *mode; + int i; + + connector = get_connector_by_id(dev, con_id); + if (!connector || !connector->count_modes) + return NULL; + + for (i = 0; i < connector->count_modes; i++) { + mode = &connector->modes[i]; + if (!strcmp(mode->name, mode_str)) { + /* If the vertical refresh frequency is not specified then return the + * first mode that match with the name. Else, return the mode that match + * the name and the specified vertical refresh frequency. + */ + if (vrefresh == 0) + return mode; + else if (mode->vrefresh == vrefresh) + return mode; + } + } + + return NULL; +} + +static struct crtc *pipe_find_crtc(struct device *dev, struct pipe_arg *pipe) +{ + uint32_t possible_crtcs = ~0; + uint32_t active_crtcs = 0; + unsigned int crtc_idx; + unsigned int i; + int j; + + for (i = 0; i < pipe->num_cons; ++i) { + uint32_t crtcs_for_connector = 0; + drmModeConnector *connector; + drmModeEncoder *encoder; + int idx; + + connector = get_connector_by_id(dev, pipe->con_ids[i]); + if (!connector) + return NULL; + + for (j = 0; j < connector->count_encoders; ++j) { + encoder = get_encoder_by_id(dev, connector->encoders[j]); + if (!encoder) + continue; + + crtcs_for_connector |= encoder->possible_crtcs; + + idx = get_crtc_index(dev, encoder->crtc_id); + if (idx >= 0) + active_crtcs |= 1 << idx; + } + + possible_crtcs &= crtcs_for_connector; + } + + if (!possible_crtcs) + return NULL; + + /* Return the first possible and active CRTC if one exists, or the first + * possible CRTC otherwise. + */ + if (possible_crtcs & active_crtcs) + crtc_idx = ffs(possible_crtcs & active_crtcs); + else + crtc_idx = ffs(possible_crtcs); + + return &dev->resources->crtcs[crtc_idx - 1]; +} + +static int pipe_find_crtc_and_mode(struct device *dev, struct pipe_arg *pipe) +{ + drmModeModeInfo *mode = NULL; + int i; + + pipe->mode = NULL; + + for (i = 0; i < (int)pipe->num_cons; i++) { + mode = connector_find_mode(dev, pipe->con_ids[i], + pipe->mode_str, pipe->vrefresh); + if (mode == NULL) { + fprintf(stderr, + "failed to find mode \"%s\" for connector %s\n", + pipe->mode_str, pipe->cons[i]); + return -EINVAL; + } + } + + /* If the CRTC ID was specified, get the corresponding CRTC. Otherwise + * locate a CRTC that can be attached to all the connectors. + */ + if (pipe->crtc_id != (uint32_t)-1) { + for (i = 0; i < dev->resources->res->count_crtcs; i++) { + struct crtc *crtc = &dev->resources->crtcs[i]; + + if (pipe->crtc_id == crtc->crtc->crtc_id) { + pipe->crtc = crtc; + break; + } + } + } else { + pipe->crtc = pipe_find_crtc(dev, pipe); + } + + if (!pipe->crtc) { + fprintf(stderr, "failed to find CRTC for pipe\n"); + return -EINVAL; + } + + pipe->mode = mode; + pipe->crtc->mode = mode; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Properties + */ + +struct property_arg { + uint32_t obj_id; + uint32_t obj_type; + char name[DRM_PROP_NAME_LEN+1]; + uint32_t prop_id; + uint64_t value; +}; + +static void set_property(struct device *dev, struct property_arg *p) +{ + drmModeObjectProperties *props = NULL; + drmModePropertyRes **props_info = NULL; + const char *obj_type; + int ret; + int i; + + p->obj_type = 0; + p->prop_id = 0; + +#define find_object(_res, __res, type, Type) \ + do { \ + for (i = 0; i < (int)(_res)->__res->count_##type##s; ++i) { \ + struct type *obj = &(_res)->type##s[i]; \ + if (obj->type->type##_id != p->obj_id) \ + continue; \ + p->obj_type = DRM_MODE_OBJECT_##Type; \ + obj_type = #Type; \ + props = obj->props; \ + props_info = obj->props_info; \ + } \ + } while(0) \ + + find_object(dev->resources, res, crtc, CRTC); + if (p->obj_type == 0) + find_object(dev->resources, res, connector, CONNECTOR); + if (p->obj_type == 0) + find_object(dev->resources, plane_res, plane, PLANE); + if (p->obj_type == 0) { + fprintf(stderr, "Object %i not found, can't set property\n", + p->obj_id); + return; + } + + if (!props) { + fprintf(stderr, "%s %i has no properties\n", + obj_type, p->obj_id); + return; + } + + for (i = 0; i < (int)props->count_props; ++i) { + if (!props_info[i]) + continue; + if (strcmp(props_info[i]->name, p->name) == 0) + break; + } + + if (i == (int)props->count_props) { + fprintf(stderr, "%s %i has no %s property\n", + obj_type, p->obj_id, p->name); + return; + } + + p->prop_id = props->props[i]; + + ret = drmModeAtomicAddProperty(dev->req, p->obj_id, p->prop_id, p->value); + if (ret < 0) + fprintf(stderr, "failed to set %s %i property %s to %" PRIu64 ": %s\n", + obj_type, p->obj_id, p->name, p->value, strerror(errno)); +} + +/* -------------------------------------------------------------------------- */ + +/*static bool format_support(const drmModePlanePtr ovr, uint32_t fmt) +{ + unsigned int i; + + for (i = 0; i < ovr->count_formats; ++i) { + if (ovr->formats[i] == fmt) + return true; + } + + return false; +}*/ + +static void add_property(struct device *dev, uint32_t obj_id, + const char *name, uint64_t value) +{ + struct property_arg p; + + p.obj_id = obj_id; + strcpy(p.name, name); + p.value = value; + + set_property(dev, &p); +} + +static int set_plane(struct device *dev, struct plane_arg *p, + int pattern, bool update) +{ + uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; + struct bo *plane_bo; + int crtc_x, crtc_y, crtc_w, crtc_h; + struct crtc *crtc = NULL; + unsigned int i; + unsigned int old_fb_id; + + /* Find an unused plane which can be connected to our CRTC. Find the + * CRTC index first, then iterate over available planes. + */ + for (i = 0; i < (unsigned int)dev->resources->res->count_crtcs; i++) { + if (p->crtc_id == dev->resources->res->crtcs[i]) { + crtc = &dev->resources->crtcs[i]; + break; + } + } + + if (!crtc) { + fprintf(stderr, "CRTC %u not found\n", p->crtc_id); + return -1; + } + + if (!update) + fprintf(stderr, "testing %dx%d@%s on plane %u, crtc %u\n", + p->w, p->h, p->format_str, p->plane_id, p->crtc_id); + + plane_bo = p->old_bo; + p->old_bo = p->bo; + + if (!plane_bo) { + plane_bo = bo_create(dev->fd, p->fourcc, p->w, p->h, + handles, pitches, offsets, pattern); + + if (plane_bo == NULL) + return -1; + + if (drmModeAddFB2(dev->fd, p->w, p->h, p->fourcc, + handles, pitches, offsets, &p->fb_id, 0)) { + fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); + return -1; + } + } + + p->bo = plane_bo; + + old_fb_id = p->fb_id; + p->old_fb_id = old_fb_id; + + crtc_w = p->w * p->scale; + crtc_h = p->h * p->scale; + if (!p->has_position) { + /* Default to the middle of the screen */ + crtc_x = (crtc->mode->hdisplay - crtc_w) / 2; + crtc_y = (crtc->mode->vdisplay - crtc_h) / 2; + } else { + crtc_x = p->x; + crtc_y = p->y; + } + + add_property(dev, p->plane_id, "FB_ID", p->fb_id); + add_property(dev, p->plane_id, "CRTC_ID", p->crtc_id); + add_property(dev, p->plane_id, "SRC_X", 0); + add_property(dev, p->plane_id, "SRC_Y", 0); + add_property(dev, p->plane_id, "SRC_W", p->w << 16); + add_property(dev, p->plane_id, "SRC_H", p->h << 16); + add_property(dev, p->plane_id, "CRTC_X", crtc_x); + add_property(dev, p->plane_id, "CRTC_Y", crtc_y); + add_property(dev, p->plane_id, "CRTC_W", crtc_w); + add_property(dev, p->plane_id, "CRTC_H", crtc_h); + + return 0; +} + +static void set_planes(struct device *dev, struct plane_arg *p, + unsigned int count, bool update) +{ + unsigned int i, pattern = UTIL_PATTERN_SMPTE; + + /* set up planes */ + for (i = 0; i < count; i++) { + if (i > 0) + pattern = UTIL_PATTERN_TILES; + + if (set_plane(dev, &p[i], pattern, update)) + return; + } +} + +static void clear_planes(struct device *dev, struct plane_arg *p, unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) { + add_property(dev, p[i].plane_id, "FB_ID", 0); + add_property(dev, p[i].plane_id, "CRTC_ID", 0); + add_property(dev, p[i].plane_id, "SRC_X", 0); + add_property(dev, p[i].plane_id, "SRC_Y", 0); + add_property(dev, p[i].plane_id, "SRC_W", 0); + add_property(dev, p[i].plane_id, "SRC_H", 0); + add_property(dev, p[i].plane_id, "CRTC_X", 0); + add_property(dev, p[i].plane_id, "CRTC_Y", 0); + add_property(dev, p[i].plane_id, "CRTC_W", 0); + add_property(dev, p[i].plane_id, "CRTC_H", 0); + } +} + +static void clear_FB(struct device *dev, struct plane_arg *p, unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) { + if (p[i].fb_id) { + drmModeRmFB(dev->fd, p[i].fb_id); + p[i].fb_id = 0; + } + if (p[i].old_fb_id) { + drmModeRmFB(dev->fd, p[i].old_fb_id); + p[i].old_fb_id = 0; + } + if (p[i].bo) { + bo_destroy(p[i].bo); + p[i].bo = NULL; + } + if (p[i].old_bo) { + bo_destroy(p[i].old_bo); + p[i].old_bo = NULL; + } + + } +} + +static void set_mode(struct device *dev, struct pipe_arg *pipes, unsigned int count) +{ + unsigned int i; + unsigned int j; + int ret; + + for (i = 0; i < count; i++) { + struct pipe_arg *pipe = &pipes[i]; + + ret = pipe_find_crtc_and_mode(dev, pipe); + if (ret < 0) + continue; + } + + for (i = 0; i < count; i++) { + struct pipe_arg *pipe = &pipes[i]; + uint32_t blob_id; + + if (pipe->mode == NULL) + continue; + + printf("setting mode %s-%dHz@%s on connectors ", + pipe->mode_str, pipe->mode->vrefresh, pipe->format_str); + for (j = 0; j < pipe->num_cons; ++j) { + printf("%s, ", pipe->cons[j]); + add_property(dev, pipe->con_ids[j], "CRTC_ID", pipe->crtc->crtc->crtc_id); + } + printf("crtc %d\n", pipe->crtc->crtc->crtc_id); + + drmModeCreatePropertyBlob(dev->fd, pipe->mode, sizeof(*pipe->mode), &blob_id); + add_property(dev, pipe->crtc->crtc->crtc_id, "MODE_ID", blob_id); + add_property(dev, pipe->crtc->crtc->crtc_id, "ACTIVE", 1); + } +} + +static void clear_mode(struct device *dev, struct pipe_arg *pipes, unsigned int count) +{ + unsigned int i; + unsigned int j; + + for (i = 0; i < count; i++) { + struct pipe_arg *pipe = &pipes[i]; + + if (pipe->mode == NULL) + continue; + + for (j = 0; j < pipe->num_cons; ++j) + add_property(dev, pipe->con_ids[j], "CRTC_ID",0); + + add_property(dev, pipe->crtc->crtc->crtc_id, "MODE_ID", 0); + add_property(dev, pipe->crtc->crtc->crtc_id, "ACTIVE", 0); + } + +} + +#define min(a, b) ((a) < (b) ? (a) : (b)) + +static int parse_connector(struct pipe_arg *pipe, const char *arg) +{ + unsigned int len; + unsigned int i; + const char *p; + char *endp; + + pipe->vrefresh = 0; + pipe->crtc_id = (uint32_t)-1; + strcpy(pipe->format_str, "XR24"); + + /* Count the number of connectors and allocate them. */ + pipe->num_cons = 1; + for (p = arg; *p && *p != ':' && *p != '@'; ++p) { + if (*p == ',') + pipe->num_cons++; + } + + pipe->con_ids = calloc(pipe->num_cons, sizeof(*pipe->con_ids)); + pipe->cons = calloc(pipe->num_cons, sizeof(*pipe->cons)); + if (pipe->con_ids == NULL || pipe->cons == NULL) + return -1; + + /* Parse the connectors. */ + for (i = 0, p = arg; i < pipe->num_cons; ++i, p = endp + 1) { + endp = strpbrk(p, ",@:"); + if (!endp) + break; + + pipe->cons[i] = strndup(p, endp - p); + + if (*endp != ',') + break; + } + + if (i != pipe->num_cons - 1) + return -1; + + /* Parse the remaining parameters. */ + if (*endp == '@') { + arg = endp + 1; + pipe->crtc_id = strtoul(arg, &endp, 10); + } + if (*endp != ':') + return -1; + + arg = endp + 1; + + /* Search for the vertical refresh or the format. */ + p = strpbrk(arg, "-@"); + if (p == NULL) + p = arg + strlen(arg); + len = min(sizeof pipe->mode_str - 1, (unsigned int)(p - arg)); + strncpy(pipe->mode_str, arg, len); + pipe->mode_str[len] = '\0'; + + if (*p == '-') { + pipe->vrefresh = strtoul(p + 1, &endp, 10); + p = endp; + } + + if (*p == '@') { + strncpy(pipe->format_str, p + 1, 4); + pipe->format_str[4] = '\0'; + } + + pipe->fourcc = util_format_fourcc(pipe->format_str); + if (pipe->fourcc == 0) { + fprintf(stderr, "unknown format %s\n", pipe->format_str); + return -1; + } + + return 0; +} + +static int parse_plane(struct plane_arg *plane, const char *p) +{ + char *end; + + plane->plane_id = strtoul(p, &end, 10); + if (*end != '@') + return -EINVAL; + + p = end + 1; + plane->crtc_id = strtoul(p, &end, 10); + if (*end != ':') + return -EINVAL; + + p = end + 1; + plane->w = strtoul(p, &end, 10); + if (*end != 'x') + return -EINVAL; + + p = end + 1; + plane->h = strtoul(p, &end, 10); + + if (*end == '+' || *end == '-') { + plane->x = strtol(end, &end, 10); + if (*end != '+' && *end != '-') + return -EINVAL; + plane->y = strtol(end, &end, 10); + + plane->has_position = true; + } + + if (*end == '*') { + p = end + 1; + plane->scale = strtod(p, &end); + if (plane->scale <= 0.0) + return -EINVAL; + } else { + plane->scale = 1.0; + } + + if (*end == '@') { + p = end + 1; + if (strlen(p) != 4) + return -EINVAL; + + strcpy(plane->format_str, p); + } else { + strcpy(plane->format_str, "XR24"); + } + + plane->fourcc = util_format_fourcc(plane->format_str); + if (plane->fourcc == 0) { + fprintf(stderr, "unknown format %s\n", plane->format_str); + return -EINVAL; + } + + return 0; +} + +static int parse_property(struct property_arg *p, const char *arg) +{ + if (sscanf(arg, "%d:%32[^:]:%" SCNu64, &p->obj_id, p->name, &p->value) != 3) + return -1; + + p->obj_type = 0; + p->name[DRM_PROP_NAME_LEN] = '\0'; + + return 0; +} + +static void usage(char *name) +{ + fprintf(stderr, "usage: %s [-cDdefMPpsCvw]\n", name); + + fprintf(stderr, "\n Query options:\n\n"); + fprintf(stderr, "\t-c\tlist connectors\n"); + fprintf(stderr, "\t-e\tlist encoders\n"); + fprintf(stderr, "\t-f\tlist framebuffers\n"); + fprintf(stderr, "\t-p\tlist CRTCs and planes (pipes)\n"); + + fprintf(stderr, "\n Test options:\n\n"); + fprintf(stderr, "\t-P <plane_id>@<crtc_id>:<w>x<h>[+<x>+<y>][*<scale>][@<format>]\tset a plane\n"); + fprintf(stderr, "\t-s <connector_id>[,<connector_id>][@<crtc_id>]:<mode>[-<vrefresh>][@<format>]\tset a mode\n"); + fprintf(stderr, "\t-v\ttest loop\n"); + fprintf(stderr, "\t-w <obj_id>:<prop_name>:<value>\tset property\n"); + + fprintf(stderr, "\n Generic options:\n\n"); + fprintf(stderr, "\t-d\tdrop master after mode set\n"); + fprintf(stderr, "\t-M module\tuse the given driver\n"); + fprintf(stderr, "\t-D device\tuse the given device\n"); + + fprintf(stderr, "\n\tDefault is to dump all info.\n"); + exit(0); +} + +static int pipe_resolve_connectors(struct device *dev, struct pipe_arg *pipe) +{ + drmModeConnector *connector; + unsigned int i; + uint32_t id; + char *endp; + + for (i = 0; i < pipe->num_cons; i++) { + id = strtoul(pipe->cons[i], &endp, 10); + if (endp == pipe->cons[i]) { + connector = get_connector_by_name(dev, pipe->cons[i]); + if (!connector) { + fprintf(stderr, "no connector named '%s'\n", + pipe->cons[i]); + return -ENODEV; + } + + id = connector->connector_id; + } + + pipe->con_ids[i] = id; + } + + return 0; +} + +static char optstr[] = "cdD:efM:P:ps:vw:"; + +int main(int argc, char **argv) +{ + struct device dev; + + int c; + int encoders = 0, connectors = 0, crtcs = 0, planes = 0, framebuffers = 0; + int drop_master = 0; + int test_loop = 0; + char *device = NULL; + char *module = NULL; + unsigned int i; + unsigned int count = 0, plane_count = 0; + unsigned int prop_count = 0; + struct pipe_arg *pipe_args = NULL; + struct plane_arg *plane_args = NULL; + struct property_arg *prop_args = NULL; + unsigned int args = 0; + int ret; + + memset(&dev, 0, sizeof dev); + + opterr = 0; + while ((c = getopt(argc, argv, optstr)) != -1) { + args++; + + switch (c) { + case 'c': + connectors = 1; + break; + case 'D': + device = optarg; + args--; + break; + case 'd': + drop_master = 1; + break; + case 'e': + encoders = 1; + break; + case 'f': + framebuffers = 1; + break; + case 'M': + module = optarg; + /* Preserve the default behaviour of dumping all information. */ + args--; + break; + case 'P': + plane_args = realloc(plane_args, + (plane_count + 1) * sizeof *plane_args); + if (plane_args == NULL) { + fprintf(stderr, "memory allocation failed\n"); + return 1; + } + memset(&plane_args[plane_count], 0, sizeof(*plane_args)); + + if (parse_plane(&plane_args[plane_count], optarg) < 0) + usage(argv[0]); + + plane_count++; + break; + case 'p': + crtcs = 1; + planes = 1; + break; + case 's': + pipe_args = realloc(pipe_args, + (count + 1) * sizeof *pipe_args); + if (pipe_args == NULL) { + fprintf(stderr, "memory allocation failed\n"); + return 1; + } + memset(&pipe_args[count], 0, sizeof(*pipe_args)); + + if (parse_connector(&pipe_args[count], optarg) < 0) + usage(argv[0]); + + count++; + break; + case 'v': + test_loop = 1; + break; + case 'w': + prop_args = realloc(prop_args, + (prop_count + 1) * sizeof *prop_args); + if (prop_args == NULL) { + fprintf(stderr, "memory allocation failed\n"); + return 1; + } + memset(&prop_args[prop_count], 0, sizeof(*prop_args)); + + if (parse_property(&prop_args[prop_count], optarg) < 0) + usage(argv[0]); + + prop_count++; + break; + default: + usage(argv[0]); + break; + } + } + + if (!args) + encoders = connectors = crtcs = planes = framebuffers = 1; + + dev.fd = util_open(device, module); + if (dev.fd < 0) + return -1; + + ret = drmSetClientCap(dev.fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret) { + fprintf(stderr, "no atomic modesetting support: %s\n", strerror(errno)); + drmClose(dev.fd); + return -1; + } + + dev.resources = get_resources(&dev); + if (!dev.resources) { + drmClose(dev.fd); + return 1; + } + + for (i = 0; i < count; i++) { + if (pipe_resolve_connectors(&dev, &pipe_args[i]) < 0) { + free_resources(dev.resources); + drmClose(dev.fd); + return 1; + } + } + +#define dump_resource(dev, res) if (res) dump_##res(dev) + + dump_resource(&dev, encoders); + dump_resource(&dev, connectors); + dump_resource(&dev, crtcs); + dump_resource(&dev, planes); + dump_resource(&dev, framebuffers); + + dev.req = drmModeAtomicAlloc(); + + for (i = 0; i < prop_count; ++i) + set_property(&dev, &prop_args[i]); + + if (count && plane_count) { + uint64_t cap = 0; + + ret = drmGetCap(dev.fd, DRM_CAP_DUMB_BUFFER, &cap); + if (ret || cap == 0) { + fprintf(stderr, "driver doesn't support the dumb buffer API\n"); + drmModeAtomicFree(dev.req); + return 1; + } + + set_mode(&dev, pipe_args, count); + set_planes(&dev, plane_args, plane_count, false); + + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); + if (ret) { + fprintf(stderr, "Atomic Commit failed [1]\n"); + return 1; + } + + gettimeofday(&pipe_args->start, NULL); + pipe_args->swap_count = 0; + + while (test_loop) { + drmModeAtomicFree(dev.req); + dev.req = drmModeAtomicAlloc(); + set_planes(&dev, plane_args, plane_count, true); + + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); + if (ret) { + fprintf(stderr, "Atomic Commit failed [2]\n"); + return 1; + } + + pipe_args->swap_count++; + if (pipe_args->swap_count == 60) { + struct timeval end; + double t; + + gettimeofday(&end, NULL); + t = end.tv_sec + end.tv_usec * 1e-6 - + (pipe_args->start.tv_sec + pipe_args->start.tv_usec * 1e-6); + fprintf(stderr, "freq: %.02fHz\n", pipe_args->swap_count / t); + pipe_args->swap_count = 0; + pipe_args->start = end; + } + } + + if (drop_master) + drmDropMaster(dev.fd); + + getchar(); + + drmModeAtomicFree(dev.req); + dev.req = drmModeAtomicAlloc(); + + clear_mode(&dev, pipe_args, count); + clear_planes(&dev, plane_args, plane_count); + ret = drmModeAtomicCommit(dev.fd, dev.req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); + if (ret) { + fprintf(stderr, "Atomic Commit failed\n"); + return 1; + } + + clear_FB(&dev, plane_args, plane_count); + } + + drmModeAtomicFree(dev.req); + free_resources(dev.resources); + + return 0; +}
This is a modetest like tool but using atomic API. With modetest_atomic it is mandatory to specify a mode ("-s") and a plane ("-P") to display a pattern on screen. "-v" does a loop swapping between two framebuffers for each active planes. modetest_atomic doesn't offer cursor support Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> --- The code is based on modetest and keep most of it infrastructure like arguments parsing, finding properties id from their name, resources dumping or the general way of working. It duplicates modetest code but adding compilation flags or conditional tests everywhere in modetest would have made it more complex and unreadable. Creating modetest_atomic could allow to test atomic API without need to use "big" frameworks like weston, drm_hwcomposer or igt with all their dependencies. kmscube could also be used to test atomic API but it need EGL. It have been tested (only) on stm driver with one or two planes actived. tests/modetest/Makefile.am | 13 +- tests/modetest/Makefile.sources | 7 + tests/modetest/modetest_atomic.c | 1546 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1564 insertions(+), 2 deletions(-) create mode 100644 tests/modetest/modetest_atomic.c