diff options
author | Jesse Barnes <jbarnes@virtuousgeek.org> | 2010-12-21 09:38:23 -0800 |
---|---|---|
committer | Jesse Barnes <jbarnes@virtuousgeek.org> | 2010-12-21 09:39:07 -0800 |
commit | 5406c633a006eb76a81b73d27cc3be1754b0718f (patch) | |
tree | a0525a5a11df30894d7c71f80d89f1a4b572e236 | |
parent | 253acc34af6fb9976a8d54991560dec5920af20b (diff) |
tests: add display testing program
Just a simple program to light up all detected outputs at their native
mode and respond to hotplug events.
-rw-r--r-- | configure.ac | 18 | ||||
-rw-r--r-- | tests/Makefile.am | 10 | ||||
-rw-r--r-- | tests/testdisplay.c | 698 |
3 files changed, 726 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index 3ab2a8dc..e1afb7f3 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,24 @@ AM_CONDITIONAL(HAVE_DRM, test "x$have_drm" = "xyes") PKG_CHECK_MODULES(PCIACCESS, [pciaccess >= 0.10]) +PKG_CHECK_MODULES(CAIRO, cairo, [HAVE_CAIRO=yes], [HAVE_CAIRO=no]) +if test "x$HAVE_CAIRO" = xyes; then + AC_DEFINE(HAVE_CAIRO, 1, [Have cairo support]) +fi +AM_CONDITIONAL(HAVE_CAIRO, [test "x$HAVE_CAIRO" = xyes]) + +PKG_CHECK_MODULES(LIBUDEV, libudev, [HAVE_LIBUDEV=yes], [HAVE_LIBUDEV=no]) +if test "x$HAVE_LIBUDEV" = xyes; then + AC_DEFINE(HAVE_LIBUDEV, 1, [Have libudev support]) +fi +AM_CONDITIONAL(HAVE_LIBUDEV, [test "x$HAVE_LIBUDEV" = xyes]) + +PKG_CHECK_MODULES(GLIB, glib-2.0, [HAVE_GLIB=yes], [HAVE_GLIB=no]) +if test "x$HAVE_GLIB" = xyes; then + AC_DEFINE(HAVE_GLIB, 1, [Have glib support]) +fi +AM_CONDITIONAL(HAVE_GLIB, [test "x$HAVE_GLIB" = xyes]) + dnl Use lots of warning flags with GCC WARN_CFLAGS="" diff --git a/tests/Makefile.am b/tests/Makefile.am index e9f56310..2bdc3dfd 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -24,5 +24,15 @@ AM_CFLAGS = $(DRM_CFLAGS) $(WARN_CFLAGS) \ -I$(srcdir)/../lib LDADD = ../lib/libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) +if HAVE_CAIRO +if HAVE_LIBUDEV +if HAVE_GLIB +TESTS += testdisplay +LDADD += $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(GLIB_LIBS) +AM_CFLAGS += $(CAIRO_CFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS) +endif +endif +endif + gem_fence_thrash_CFLAGS = $(AM_CFLAGS) -pthread gem_fence_thrash_LDADD = $(LDADD) -lpthread diff --git a/tests/testdisplay.c b/tests/testdisplay.c new file mode 100644 index 00000000..fe9ef36a --- /dev/null +++ b/tests/testdisplay.c @@ -0,0 +1,698 @@ +/* + * Copyright 2010 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 program is intended for testing of display functionality. It should + * allow for testing of + * - hotplug + * - mode setting + * - clone & twin modes + * - panel fitting + * - test patterns & pixel generators + * Additional programs can test the detected outputs against VBT provided + * device lists (both docked & undocked). + * + * TODO: + * - pixel generator in transcoder + * - test pattern reg in pipe + * - test patterns on outputs (e.g. TV) + * - handle hotplug (leaks crtcs, can't handle clones) + * - allow mode force + * - expose output specific controls + * - e.g. DDC-CI brightness + * - HDMI controls + * - panel brightness + * - DP commands (e.g. poweroff) + * - verify outputs against VBT/physical connectors + */ +#include "config.h" + +#include <assert.h> +#include <cairo.h> +#include <errno.h> +#include <glib.h> +#include <libudev.h> +#include <math.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/poll.h> +#include <sys/time.h> + +#include "xf86drm.h" +#include "xf86drmMode.h" +#include "intel_bufmgr.h" +#include "i915_drm.h" + +struct udev_monitor *uevent_monitor; +drmModeRes *resources; +int fd, modes; + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +struct type_name { + int type; + char *name; +}; + +#define type_name_fn(res) \ +static char * res##_str(int type) { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(res##_names); i++) { \ + if (res##_names[i].type == type) \ + return res##_names[i].name; \ + } \ + return "(invalid)"; \ +} + +struct type_name encoder_type_names[] = { + { DRM_MODE_ENCODER_NONE, "none" }, + { DRM_MODE_ENCODER_DAC, "DAC" }, + { DRM_MODE_ENCODER_TMDS, "TMDS" }, + { DRM_MODE_ENCODER_LVDS, "LVDS" }, + { DRM_MODE_ENCODER_TVDAC, "TVDAC" }, +}; + +type_name_fn(encoder_type) + +struct type_name connector_status_names[] = { + { DRM_MODE_CONNECTED, "connected" }, + { DRM_MODE_DISCONNECTED, "disconnected" }, + { DRM_MODE_UNKNOWNCONNECTION, "unknown" }, +}; + +type_name_fn(connector_status) + +struct type_name connector_type_names[] = { + { DRM_MODE_CONNECTOR_Unknown, "unknown" }, + { DRM_MODE_CONNECTOR_VGA, "VGA" }, + { DRM_MODE_CONNECTOR_DVII, "DVI-I" }, + { DRM_MODE_CONNECTOR_DVID, "DVI-D" }, + { DRM_MODE_CONNECTOR_DVIA, "DVI-A" }, + { DRM_MODE_CONNECTOR_Composite, "composite" }, + { DRM_MODE_CONNECTOR_SVIDEO, "s-video" }, + { DRM_MODE_CONNECTOR_LVDS, "LVDS" }, + { DRM_MODE_CONNECTOR_Component, "component" }, + { DRM_MODE_CONNECTOR_9PinDIN, "9-pin DIN" }, + { DRM_MODE_CONNECTOR_DisplayPort, "DisplayPort" }, + { DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" }, + { DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" }, + { DRM_MODE_CONNECTOR_TV, "TV" }, + { DRM_MODE_CONNECTOR_eDP, "Embedded DisplayPort" }, +}; + +type_name_fn(connector_type) + +/* + * 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 connector { + uint32_t id; + int mode_valid; + drmModeModeInfo mode; + drmModeEncoder *encoder; + drmModeConnector *connector; + int crtc; +}; + +static void connector_find_preferred_mode(struct connector *c) +{ + drmModeConnector *connector; + drmModeEncoder *encoder = NULL; + int i, j; + + /* First, find the connector & mode */ + c->mode_valid = 0; + connector = drmModeGetConnector(fd, c->id); + if (!connector) { + fprintf(stderr, "could not get connector %d: %s\n", + c->id, strerror(errno)); + drmModeFreeConnector(connector); + return; + } + + if (connector->connection != DRM_MODE_CONNECTED) { + drmModeFreeConnector(connector); + return; + } + + if (!connector->count_modes) { + fprintf(stderr, "connector %d has no modes\n", c->id); + drmModeFreeConnector(connector); + return; + } + + if (connector->connector_id != c->id) { + fprintf(stderr, "connector id doesn't match (%d != %d)\n", + connector->connector_id, c->id); + drmModeFreeConnector(connector); + return; + } + + for (j = 0; j < connector->count_modes; j++) { + c->mode = connector->modes[j]; + if (c->mode.flags & DRM_MODE_TYPE_PREFERRED) { + c->mode_valid = 1; + break; + } + } + + if (!c->mode_valid) { + fprintf(stderr, "failed to find any modes on connector %d\n", + c->id); + return; + } + + /* Now get the encoder */ + for (i = 0; i < connector->count_encoders; i++) { + encoder = drmModeGetEncoder(fd, connector->encoders[i]); + + if (!encoder) { + fprintf(stderr, "could not get encoder %i: %s\n", + resources->encoders[i], strerror(errno)); + drmModeFreeEncoder(encoder); + continue; + } + + break; + } + + c->encoder = encoder; + + if (i == resources->count_encoders) { + fprintf(stderr, "failed to find encoder\n"); + c->mode_valid = 0; + return; + } + + /* Find first CRTC not in use */ + for (i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i]) + break; + } + c->crtc = resources->crtcs[i]; + resources->crtcs[i] = 0; + c->connector = connector; +} + +static drm_intel_bo * +allocate_buffer(drm_intel_bufmgr *bufmgr, + int width, int height, int *stride) +{ + int size; + + /* Scan-out has a 64 byte alignment restriction */ + width *= 4; /* 32bpp */ + size = (width + 63) & -64; + *stride = size; + size *= height; + + return drm_intel_bo_alloc(bufmgr, "frontbuffer", size, 0); +} + +enum corner { + topleft, + topright, + bottomleft, + bottomright, +}; + +static void +paint_marker(cairo_t *cr, int x, int y, char *str, enum corner text_location) +{ + cairo_text_extents_t extents; + int xoff, yoff; + + cairo_set_font_size(cr, 18); + cairo_text_extents(cr, str, &extents); + + switch (text_location) { + case topleft: + xoff = -20; + xoff -= extents.width; + yoff = -20; + break; + case topright: + xoff = 20; + yoff = -20; + break; + case bottomleft: + xoff = -20; + xoff -= extents.width; + yoff = 20; + break; + case bottomright: + xoff = 20; + yoff = 20; + break; + default: + xoff = 0; + yoff = 0; + } + + cairo_move_to(cr, x, y - 20); + cairo_line_to(cr, x, y + 20); + cairo_move_to(cr, x - 20, y); + cairo_line_to(cr, x + 20, y); + cairo_new_sub_path(cr); + cairo_arc(cr, x, y, 10, 0, M_PI * 2); + cairo_set_line_width(cr, 4); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_set_line_width(cr, 2); + cairo_stroke(cr); + + cairo_move_to(cr, x + xoff, y + yoff); + cairo_text_path(cr, str); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); +} + +static void +paint_output_info(cairo_t *cr, struct connector *c, int width, int height) +{ + cairo_text_extents_t name_extents, mode_extents; + char name_buf[128], mode_buf[128]; + int i, x, y, modes_x, modes_y; + + /* Get text extents for each string */ + snprintf(name_buf, sizeof name_buf, "%s", + connector_type_str(c->connector->connector_type)); + cairo_set_font_size(cr, 48); + cairo_select_font_face(cr, "Helvetica", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_text_extents(cr, name_buf, &name_extents); + + snprintf(mode_buf, sizeof mode_buf, "%s @ %dHz on %s encoder", + c->mode.name, c->mode.vrefresh, + encoder_type_str(c->encoder->encoder_type)); + cairo_set_font_size(cr, 36); + cairo_text_extents(cr, mode_buf, &mode_extents); + + /* Paint output name */ + x = width / 2; + x -= name_extents.width / 2; + y = height / 2; + y -= (name_extents.height / 2) - (mode_extents.height / 2) - 10; + cairo_set_font_size(cr, 48); + cairo_move_to(cr, x, y); + cairo_text_path(cr, name_buf); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); + + /* Paint mode name */ + x = width / 2; + x -= mode_extents.width / 2; + modes_x = x; + y = height / 2; + y += (mode_extents.height / 2) + (name_extents.height / 2) + 10; + cairo_set_font_size(cr, 36); + cairo_move_to(cr, x, y); + cairo_text_path(cr, mode_buf); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); + + /* List available modes */ + snprintf(mode_buf, sizeof mode_buf, "Available modes:"); + cairo_set_font_size(cr, 18); + cairo_text_extents(cr, mode_buf, &mode_extents); + x = modes_x; + modes_x = x + mode_extents.width; + y += mode_extents.height + 10; + modes_y = y; + cairo_move_to(cr, x, y); + cairo_text_path(cr, mode_buf); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); + + for (i = 0; i < c->connector->count_modes; i++) { + snprintf(mode_buf, sizeof mode_buf, "%s @ %dHz", + c->connector->modes[i].name, + c->connector->modes[i].vrefresh); + cairo_set_font_size(cr, 18); + cairo_text_extents(cr, mode_buf, &mode_extents); + x = modes_x - mode_extents.width; /* right justify modes */ + y += mode_extents.height + 10; + if (y + mode_extents.height >= height) { + y = modes_y + mode_extents.height + 10; + modes_x += mode_extents.width + 10; + x = modes_x - mode_extents.width; + } + cairo_move_to(cr, x, y); + cairo_text_path(cr, mode_buf); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); + } +} + +static int +create_test_buffer(drm_intel_bufmgr *bufmgr, int width, int height, + int *stride_out, drm_intel_bo **bo_out) +{ + drm_intel_bo *bo; + int ret, stride; + + bo = allocate_buffer(bufmgr, width, height, &stride); + if (!bo) { + fprintf(stderr, "failed to alloc buffer: %s\n", + + strerror(errno)); + return -1; + } + + ret = drm_intel_gem_bo_map_gtt(bo); + if (ret) { + fprintf(stderr, "failed to GTT map buffer: %s\n", + strerror(errno)); + return -1; + } + + *bo_out = bo; + *stride_out = stride; + return 0; +} + +static void +set_mode(struct connector *c) +{ + drm_intel_bufmgr *bufmgr; + drm_intel_bo *bo; + unsigned int fb_id; + int ret, width, height, stride; + cairo_surface_t *surface; + cairo_t *cr; + char buf[128]; + + width = 0; + height = 0; + connector_find_preferred_mode(c); + if (!c->mode_valid) + return; + + width += c->mode.hdisplay; + if (height < c->mode.vdisplay) + height = c->mode.vdisplay; + + bufmgr = drm_intel_bufmgr_gem_init(fd, 2<<20); + if (!bufmgr) { + fprintf(stderr, "failed to init bufmgr: %s\n", strerror(errno)); + return; + } + + if (create_test_buffer(bufmgr, width, height, &stride, &bo)) + return; + + surface = cairo_image_surface_create_for_data(bo->virtual, + CAIRO_FORMAT_ARGB32, + width, height, + stride); + cr = cairo_create(surface); + cairo_surface_destroy(surface); + + cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); + + /* Paint corner markers */ + snprintf(buf, sizeof buf, "(%d, %d)", 0, 0); + paint_marker(cr, 0, 0, buf, bottomright); + snprintf(buf, sizeof buf, "(%d, %d)", width, 0); + paint_marker(cr, width, 0, buf, bottomleft); + snprintf(buf, sizeof buf, "(%d, %d)", 0, height); + paint_marker(cr, 0, height, buf, topright); + snprintf(buf, sizeof buf, "(%d, %d)", width, height); + paint_marker(cr, width, height, buf, topleft); + + /* Paint output info */ + paint_output_info(cr, c, width, height); + + cairo_destroy(cr); + drm_intel_gem_bo_unmap_gtt(bo); + + ret = drmModeAddFB(fd, width, height, 32, 32, stride, bo->handle, + &fb_id); + if (ret) { + fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); + return; + } + + if (!c->mode_valid) + return; + + ret = drmModeSetCrtc(fd, c->crtc, fb_id, 0, 0, + &c->id, 1, &c->mode); + + if (ret) { + fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); + return; + } + + drmModeFreeEncoder(c->encoder); + drmModeFreeConnector(c->connector); +} + +/* + * Re-probe outputs and light up as many as possible. + * + * On Intel, we have two CRTCs that we can drive independently with + * different timings and scanout buffers. + * + * Each connector has a corresponding encoder, except in the SDVO case + * where an encoder may have multiple connectors. + */ +static void update_display(void) +{ + struct connector *connectors; + int c; + + resources = drmModeGetResources(fd); + if (!resources) { + fprintf(stderr, "drmModeGetResources failed: %s\n", + strerror(errno)); + return; + } + + connectors = calloc(resources->count_connectors, + sizeof(struct connector)); + if (!connectors) + return; + + /* Find any connected displays */ + for (c = 0; c < resources->count_connectors; c++) { + connectors[c].id = resources->connectors[c]; + set_mode(&connectors[c]); + } + drmModeFreeResources(resources); +} + +extern char *optarg; +extern int optind, opterr, optopt; +static char optstr[] = "h"; + +static void usage(char *name) +{ + fprintf(stderr, "usage: %s [-h]\n", name); + exit(0); +} + +static gboolean hotplug_event(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + gchar buf[256]; + gsize count; + struct udev_device *dev; + dev_t udev_devnum; + struct stat s; + const char *hotplug; + + g_io_channel_read(source, buf, 256, &count); + + dev = udev_monitor_receive_device(uevent_monitor); + if (!dev) + goto out; + + udev_devnum = udev_device_get_devnum(dev); + fstat(fd, &s); + + hotplug = udev_device_get_property_value(dev, "HOTPLUG"); + + if (memcmp(&s.st_rdev, &udev_devnum, sizeof(dev_t)) == 0 && + hotplug && atoi(hotplug) == 1) + update_display(); + + udev_device_unref(dev); +out: + return TRUE; +} + +static gboolean input_event(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + gchar buf[256]; + gsize count; + + g_io_channel_read(source, buf, 255, &count); + buf[count] = '\0'; + + if (buf[0] == 'q' && (count == 1 || buf[1] == '\n')) + exit(0); + + return TRUE; +} + +int main(int argc, char **argv) +{ + int c; + int encoders = 0, connectors = 0, crtcs = 0, framebuffers = 0; + char *modules[] = { "i915" }; + int i; + struct udev *u; + int ret = 0; + GIOChannel *udevchannel, *stdinchannel; + GMainLoop *mainloop; + + opterr = 0; + while ((c = getopt(argc, argv, optstr)) != -1) { + switch (c) { + default: + fprintf(stderr, "unknown option %c\n", c); + /* fall through */ + case 'h': + usage(argv[0]); + break; + } + } + + if (argc == 1) + encoders = connectors = crtcs = modes = framebuffers = 1; + + for (i = 0; i < ARRAY_SIZE(modules); i++) { + fd = drmOpen(modules[i], NULL); + if (fd < 0) + printf("failed to load %s driver.\n", modules[i]); + else + break; + } + + if (i == ARRAY_SIZE(modules)) { + fprintf(stderr, "failed to load any modules, aborting.\n"); + ret = -1; + goto out; + } + + u = udev_new(); + if (!u) { + fprintf(stderr, "failed to create udev object\n"); + ret = -1; + goto out_close; + } + + uevent_monitor = udev_monitor_new_from_netlink(u, "udev"); + if (!uevent_monitor) { + fprintf(stderr, "failed to create udev event monitor\n"); + ret = -1; + goto out_udev_unref; + } + + ret = udev_monitor_filter_add_match_subsystem_devtype(uevent_monitor, + "drm", + "drm_minor"); + if (ret < 0) { + fprintf(stderr, "failed to filter for drm events\n"); + goto out_udev_mon_unref; + } + + ret = udev_monitor_enable_receiving(uevent_monitor); + if (ret < 0) { + fprintf(stderr, "failed to enable udev event reception\n"); + goto out_udev_mon_unref; + } + + mainloop = g_main_loop_new(NULL, FALSE); + if (!mainloop) { + fprintf(stderr, "failed to create glib mainloop\n"); + ret = -1; + goto out_mainloop_unref; + } + + udevchannel = + g_io_channel_unix_new(udev_monitor_get_fd(uevent_monitor)); + if (!udevchannel) { + fprintf(stderr, "failed to create udev GIO channel\n"); + goto out_mainloop_unref; + } + + ret = g_io_add_watch(udevchannel, G_IO_IN | G_IO_ERR, hotplug_event, + u); + if (ret < 0) { + fprintf(stderr, "failed to add watch on udev GIO channel\n"); + goto out_udev_off; + } + + stdinchannel = g_io_channel_unix_new(0); + if (!stdinchannel) { + fprintf(stderr, "failed to create stdin GIO channel\n"); + goto out_udev_off; + } + + ret = g_io_add_watch(stdinchannel, G_IO_IN | G_IO_ERR, input_event, + NULL); + if (ret < 0) { + fprintf(stderr, "failed to add watch on stdin GIO channel\n"); + goto out_stdio_off; + } + + update_display(); + g_main_loop_run(mainloop); + +out_stdio_off: + g_io_channel_shutdown(stdinchannel, TRUE, NULL); +out_udev_off: + g_io_channel_shutdown(udevchannel, TRUE, NULL); +out_mainloop_unref: + g_main_loop_unref(mainloop); +out_udev_mon_unref: + udev_monitor_unref(uevent_monitor); +out_udev_unref: + udev_unref(u); +out_close: + drmClose(fd); +out: + return ret; +} |