| /* |
| * 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. |
| */ |
| #include "config.h" |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <sys/poll.h> |
| #include <sys/time.h> |
| |
| #include "xf86drm.h" |
| #include "xf86drmMode.h" |
| #include "drm_fourcc.h" |
| #include "libkms.h" |
| |
| #ifdef HAVE_CAIRO |
| #include <math.h> |
| #include <cairo.h> |
| #endif |
| |
| 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) \ |
| char * res##_str(int type) { \ |
| unsigned 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) |
| |
| void dump_encoders(void) |
| { |
| drmModeEncoder *encoder; |
| int i; |
| |
| printf("Encoders:\n"); |
| printf("id\tcrtc\ttype\tpossible crtcs\tpossible clones\t\n"); |
| for (i = 0; i < resources->count_encoders; i++) { |
| encoder = drmModeGetEncoder(fd, resources->encoders[i]); |
| |
| if (!encoder) { |
| fprintf(stderr, "could not get encoder %i: %s\n", |
| resources->encoders[i], strerror(errno)); |
| continue; |
| } |
| printf("%d\t%d\t%s\t0x%08x\t0x%08x\n", |
| encoder->encoder_id, |
| encoder->crtc_id, |
| encoder_type_str(encoder->encoder_type), |
| encoder->possible_crtcs, |
| encoder->possible_clones); |
| drmModeFreeEncoder(encoder); |
| } |
| printf("\n"); |
| } |
| |
| void dump_mode(drmModeModeInfo *mode) |
| { |
| printf(" %s %d %d %d %d %d %d %d %d %d\n", |
| mode->name, |
| mode->vrefresh, |
| mode->hdisplay, |
| mode->hsync_start, |
| mode->hsync_end, |
| mode->htotal, |
| mode->vdisplay, |
| mode->vsync_start, |
| mode->vsync_end, |
| mode->vtotal); |
| } |
| |
| static void |
| dump_props(drmModeConnector *connector) |
| { |
| drmModePropertyPtr props; |
| int i; |
| |
| for (i = 0; i < connector->count_props; i++) { |
| props = drmModeGetProperty(fd, connector->props[i]); |
| printf("\t%s, flags %d\n", props->name, props->flags); |
| drmModeFreeProperty(props); |
| } |
| } |
| |
| void dump_connectors(void) |
| { |
| drmModeConnector *connector; |
| int i, j; |
| |
| printf("Connectors:\n"); |
| printf("id\tencoder\tstatus\t\ttype\tsize (mm)\tmodes\tencoders\n"); |
| for (i = 0; i < resources->count_connectors; i++) { |
| connector = drmModeGetConnector(fd, resources->connectors[i]); |
| |
| if (!connector) { |
| fprintf(stderr, "could not get connector %i: %s\n", |
| resources->connectors[i], strerror(errno)); |
| continue; |
| } |
| |
| printf("%d\t%d\t%s\t%s\t%dx%d\t\t%d\t", |
| connector->connector_id, |
| connector->encoder_id, |
| connector_status_str(connector->connection), |
| connector_type_str(connector->connector_type), |
| 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) |
| continue; |
| |
| printf(" modes:\n"); |
| printf(" name refresh (Hz) hdisp hss hse htot vdisp " |
| "vss vse vtot)\n"); |
| for (j = 0; j < connector->count_modes; j++) |
| dump_mode(&connector->modes[j]); |
| |
| printf(" props:\n"); |
| dump_props(connector); |
| |
| drmModeFreeConnector(connector); |
| } |
| printf("\n"); |
| } |
| |
| void dump_crtcs(void) |
| { |
| drmModeCrtc *crtc; |
| int i; |
| |
| printf("CRTCs:\n"); |
| printf("id\tfb\tpos\tsize\n"); |
| for (i = 0; i < resources->count_crtcs; i++) { |
| crtc = drmModeGetCrtc(fd, resources->crtcs[i]); |
| |
| if (!crtc) { |
| fprintf(stderr, "could not get crtc %i: %s\n", |
| resources->crtcs[i], strerror(errno)); |
| 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); |
| |
| drmModeFreeCrtc(crtc); |
| } |
| printf("\n"); |
| } |
| |
| void dump_framebuffers(void) |
| { |
| drmModeFB *fb; |
| int i; |
| |
| printf("Frame buffers:\n"); |
| printf("id\tsize\tpitch\n"); |
| for (i = 0; i < resources->count_fbs; i++) { |
| fb = drmModeGetFB(fd, resources->fbs[i]); |
| |
| if (!fb) { |
| fprintf(stderr, "could not get fb %i: %s\n", |
| resources->fbs[i], strerror(errno)); |
| continue; |
| } |
| printf("%u\t(%ux%u)\t%u\n", |
| fb->fb_id, |
| fb->width, fb->height, |
| fb->pitch); |
| |
| drmModeFreeFB(fb); |
| } |
| printf("\n"); |
| } |
| |
| static void dump_planes(void) |
| { |
| drmModePlaneRes *plane_resources; |
| drmModePlane *ovr; |
| unsigned int i, j; |
| |
| plane_resources = drmModeGetPlaneResources(fd); |
| if (!plane_resources) { |
| fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", |
| strerror(errno)); |
| return; |
| } |
| |
| printf("Planes:\n"); |
| printf("id\tcrtc\tfb\tCRTC x,y\tx,y\tgamma size\n"); |
| for (i = 0; i < plane_resources->count_planes; i++) { |
| ovr = drmModeGetPlane(fd, plane_resources->planes[i]); |
| if (!ovr) { |
| fprintf(stderr, "drmModeGetPlane failed: %s\n", |
| strerror(errno)); |
| continue; |
| } |
| |
| printf("%d\t%d\t%d\t%d,%d\t\t%d,%d\t%d\n", |
| ovr->plane_id, ovr->crtc_id, ovr->fb_id, |
| ovr->crtc_x, ovr->crtc_y, ovr->x, ovr->y, |
| ovr->gamma_size); |
| |
| if (!ovr->count_formats) |
| continue; |
| |
| printf(" formats:"); |
| for (j = 0; j < ovr->count_formats; j++) |
| printf(" %4.4s", (char *)&ovr->formats[j]); |
| printf("\n"); |
| |
| drmModeFreePlane(ovr); |
| } |
| printf("\n"); |
| |
| return; |
| } |
| |
| /* |
| * 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; |
| char mode_str[64]; |
| drmModeModeInfo *mode; |
| drmModeEncoder *encoder; |
| int crtc; |
| int pipe; |
| unsigned int fb_id[2], current_fb_id; |
| struct timeval start; |
| |
| int swap_count; |
| }; |
| |
| struct plane { |
| uint32_t con_id; /* the id of connector to bind to */ |
| uint32_t w, h; |
| unsigned int fb_id; |
| char format_str[5]; /* need to leave room for terminating \0 */ |
| }; |
| |
| static void |
| connector_find_mode(struct connector *c) |
| { |
| drmModeConnector *connector; |
| int i, j; |
| |
| /* First, find the connector & mode */ |
| c->mode = NULL; |
| for (i = 0; i < resources->count_connectors; i++) { |
| connector = drmModeGetConnector(fd, resources->connectors[i]); |
| |
| if (!connector) { |
| fprintf(stderr, "could not get connector %i: %s\n", |
| resources->connectors[i], strerror(errno)); |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| if (!connector->count_modes) { |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| if (connector->connector_id != c->id) { |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| for (j = 0; j < connector->count_modes; j++) { |
| c->mode = &connector->modes[j]; |
| if (!strcmp(c->mode->name, c->mode_str)) |
| break; |
| } |
| |
| /* Found it, break out */ |
| if (c->mode) |
| break; |
| |
| drmModeFreeConnector(connector); |
| } |
| |
| if (!c->mode) { |
| fprintf(stderr, "failed to find mode \"%s\"\n", c->mode_str); |
| return; |
| } |
| |
| /* Now get the encoder */ |
| for (i = 0; i < resources->count_encoders; i++) { |
| c->encoder = drmModeGetEncoder(fd, resources->encoders[i]); |
| |
| if (!c->encoder) { |
| fprintf(stderr, "could not get encoder %i: %s\n", |
| resources->encoders[i], strerror(errno)); |
| drmModeFreeEncoder(c->encoder); |
| continue; |
| } |
| |
| if (c->encoder->encoder_id == connector->encoder_id) |
| break; |
| |
| drmModeFreeEncoder(c->encoder); |
| } |
| |
| if (c->crtc == -1) |
| c->crtc = c->encoder->crtc_id; |
| |
| /* and figure out which crtc index it is: */ |
| for (i = 0; i < resources->count_crtcs; i++) { |
| if (c->crtc == resources->crtcs[i]) { |
| c->pipe = i; |
| break; |
| } |
| } |
| |
| } |
| |
| static struct kms_bo * |
| allocate_buffer(struct kms_driver *kms, |
| int width, int height, int *stride) |
| { |
| struct kms_bo *bo; |
| unsigned bo_attribs[] = { |
| KMS_WIDTH, 0, |
| KMS_HEIGHT, 0, |
| KMS_BO_TYPE, KMS_BO_TYPE_SCANOUT_X8R8G8B8, |
| KMS_TERMINATE_PROP_LIST |
| }; |
| int ret; |
| |
| bo_attribs[1] = width; |
| bo_attribs[3] = height; |
| |
| ret = kms_bo_create(kms, bo_attribs, &bo); |
| if (ret) { |
| fprintf(stderr, "failed to alloc buffer: %s\n", |
| strerror(-ret)); |
| return NULL; |
| } |
| |
| ret = kms_bo_get_prop(bo, KMS_PITCH, stride); |
| if (ret) { |
| fprintf(stderr, "failed to retreive buffer stride: %s\n", |
| strerror(-ret)); |
| kms_bo_destroy(&bo); |
| return NULL; |
| } |
| |
| return bo; |
| } |
| |
| static void |
| make_pwetty(void *data, int width, int height, int stride) |
| { |
| #ifdef HAVE_CAIRO |
| cairo_surface_t *surface; |
| cairo_t *cr; |
| int x, y; |
| |
| surface = cairo_image_surface_create_for_data(data, |
| CAIRO_FORMAT_ARGB32, |
| width, height, |
| stride); |
| cr = cairo_create(surface); |
| cairo_surface_destroy(surface); |
| |
| cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); |
| for (x = 0; x < width; x += 250) |
| for (y = 0; y < height; y += 250) { |
| char buf[64]; |
| |
| 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); |
| |
| snprintf(buf, sizeof buf, "%d, %d", x, y); |
| cairo_move_to(cr, x + 20, y + 20); |
| cairo_text_path(cr, buf); |
| cairo_set_source_rgb(cr, 0, 0, 0); |
| cairo_stroke_preserve(cr); |
| cairo_set_source_rgb(cr, 1, 1, 1); |
| cairo_fill(cr); |
| } |
| |
| cairo_destroy(cr); |
| #endif |
| } |
| |
| static int |
| create_test_buffer(struct kms_driver *kms, |
| int width, int height, int *stride_out, |
| struct kms_bo **bo_out) |
| { |
| struct kms_bo *bo; |
| int ret, i, j, stride; |
| void *virtual; |
| |
| bo = allocate_buffer(kms, width, height, &stride); |
| if (!bo) |
| return -1; |
| |
| ret = kms_bo_map(bo, &virtual); |
| if (ret) { |
| fprintf(stderr, "failed to map buffer: %s\n", |
| strerror(-ret)); |
| kms_bo_destroy(&bo); |
| return -1; |
| } |
| |
| /* paint the buffer with colored tiles */ |
| for (j = 0; j < height; j++) { |
| uint32_t *fb_ptr = (uint32_t*)((char*)virtual + j * stride); |
| for (i = 0; i < width; i++) { |
| div_t d = div(i, width); |
| fb_ptr[i] = |
| 0x00130502 * (d.quot >> 6) + |
| 0x000a1120 * (d.rem >> 6); |
| } |
| } |
| |
| make_pwetty(virtual, width, height, stride); |
| |
| kms_bo_unmap(bo); |
| |
| *bo_out = bo; |
| *stride_out = stride; |
| return 0; |
| } |
| |
| static int |
| create_grey_buffer(struct kms_driver *kms, |
| int width, int height, int *stride_out, |
| struct kms_bo **bo_out) |
| { |
| struct kms_bo *bo; |
| int size, ret, stride; |
| void *virtual; |
| |
| bo = allocate_buffer(kms, width, height, &stride); |
| if (!bo) |
| return -1; |
| |
| ret = kms_bo_map(bo, &virtual); |
| if (ret) { |
| fprintf(stderr, "failed to map buffer: %s\n", |
| strerror(-ret)); |
| kms_bo_destroy(&bo); |
| return -1; |
| } |
| |
| size = stride * height; |
| memset(virtual, 0x77, size); |
| kms_bo_unmap(bo); |
| |
| *bo_out = bo; |
| *stride_out = stride; |
| |
| return 0; |
| } |
| |
| void |
| page_flip_handler(int fd, unsigned int frame, |
| unsigned int sec, unsigned int usec, void *data) |
| { |
| struct connector *c; |
| unsigned int new_fb_id; |
| struct timeval end; |
| double t; |
| |
| c = data; |
| if (c->current_fb_id == c->fb_id[0]) |
| new_fb_id = c->fb_id[1]; |
| else |
| new_fb_id = c->fb_id[0]; |
| |
| drmModePageFlip(fd, c->crtc, new_fb_id, |
| DRM_MODE_PAGE_FLIP_EVENT, c); |
| c->current_fb_id = new_fb_id; |
| c->swap_count++; |
| if (c->swap_count == 60) { |
| gettimeofday(&end, NULL); |
| t = end.tv_sec + end.tv_usec * 1e-6 - |
| (c->start.tv_sec + c->start.tv_usec * 1e-6); |
| fprintf(stderr, "freq: %.02fHz\n", c->swap_count / t); |
| c->swap_count = 0; |
| c->start = end; |
| } |
| } |
| |
| /* swap these for big endian.. */ |
| #define RED 2 |
| #define GREEN 1 |
| #define BLUE 0 |
| |
| static void |
| fill420(unsigned char *y, unsigned char *u, unsigned char *v, |
| int cs /*chroma pixel stride */, |
| int n, int width, int height, int stride) |
| { |
| int i, j; |
| |
| /* paint the buffer with colored tiles, in blocks of 2x2 */ |
| for (j = 0; j < height; j+=2) { |
| unsigned char *y1p = y + j * stride; |
| unsigned char *y2p = y1p + stride; |
| unsigned char *up = u + (j/2) * stride * cs / 2; |
| unsigned char *vp = v + (j/2) * stride * cs / 2; |
| |
| for (i = 0; i < width; i+=2) { |
| div_t d = div(n+i+j, width); |
| uint32_t rgb = 0x00130502 * (d.quot >> 6) + 0x000a1120 * (d.rem >> 6); |
| unsigned char *rgbp = (unsigned char *)&rgb; |
| unsigned char y = (0.299 * rgbp[RED]) + (0.587 * rgbp[GREEN]) + (0.114 * rgbp[BLUE]); |
| |
| *(y2p++) = *(y1p++) = y; |
| *(y2p++) = *(y1p++) = y; |
| |
| *up = (rgbp[BLUE] - y) * 0.565 + 128; |
| *vp = (rgbp[RED] - y) * 0.713 + 128; |
| up += cs; |
| vp += cs; |
| } |
| } |
| } |
| |
| static void |
| fill422(unsigned char *virtual, int n, int width, int height, int stride) |
| { |
| int i, j; |
| /* paint the buffer with colored tiles */ |
| for (j = 0; j < height; j++) { |
| uint8_t *ptr = (uint8_t*)((char*)virtual + j * stride); |
| for (i = 0; i < width; i++) { |
| div_t d = div(n+i+j, width); |
| uint32_t rgb = 0x00130502 * (d.quot >> 6) + 0x000a1120 * (d.rem >> 6); |
| unsigned char *rgbp = (unsigned char *)&rgb; |
| unsigned char y = (0.299 * rgbp[RED]) + (0.587 * rgbp[GREEN]) + (0.114 * rgbp[BLUE]); |
| |
| *(ptr++) = y; |
| *(ptr++) = (rgbp[BLUE] - y) * 0.565 + 128; |
| *(ptr++) = y; |
| *(ptr++) = (rgbp[RED] - y) * 0.713 + 128; |
| } |
| } |
| } |
| |
| static void |
| fill1555(unsigned char *virtual, int n, int width, int height, int stride) |
| { |
| int i, j; |
| /* paint the buffer with colored tiles */ |
| for (j = 0; j < height; j++) { |
| uint16_t *ptr = (uint16_t*)((char*)virtual + j * stride); |
| for (i = 0; i < width; i++) { |
| div_t d = div(n+i+j, width); |
| uint32_t rgb = 0x00130502 * (d.quot >> 6) + 0x000a1120 * (d.rem >> 6); |
| unsigned char *rgbp = (unsigned char *)&rgb; |
| |
| *(ptr++) = 0x8000 | |
| (rgbp[RED] >> 3) << 10 | |
| (rgbp[GREEN] >> 3) << 5 | |
| (rgbp[BLUE] >> 3); |
| } |
| } |
| } |
| |
| static int |
| set_plane(struct kms_driver *kms, struct connector *c, struct plane *p) |
| { |
| drmModePlaneRes *plane_resources; |
| drmModePlane *ovr; |
| uint32_t handles[4], pitches[4], offsets[4] = {0}; /* we only use [0] */ |
| uint32_t plane_id = 0; |
| struct kms_bo *plane_bo; |
| uint32_t plane_flags = 0, format; |
| int ret, crtc_x, crtc_y, crtc_w, crtc_h; |
| unsigned int i; |
| |
| /* find an unused plane which can be connected to our crtc */ |
| plane_resources = drmModeGetPlaneResources(fd); |
| if (!plane_resources) { |
| fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| |
| for (i = 0; i < plane_resources->count_planes && !plane_id; i++) { |
| ovr = drmModeGetPlane(fd, plane_resources->planes[i]); |
| if (!ovr) { |
| fprintf(stderr, "drmModeGetPlane failed: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| |
| if ((ovr->possible_crtcs & (1 << c->pipe)) && !ovr->crtc_id) |
| plane_id = ovr->plane_id; |
| |
| drmModeFreePlane(ovr); |
| } |
| |
| fprintf(stderr, "testing %dx%d@%s overlay plane\n", |
| p->w, p->h, p->format_str); |
| |
| if (!plane_id) { |
| fprintf(stderr, "failed to find plane!\n"); |
| return -1; |
| } |
| |
| if (!strcmp(p->format_str, "XR24")) { |
| if (create_test_buffer(kms, p->w, p->h, &pitches[0], &plane_bo)) |
| return -1; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[0]); |
| format = DRM_FORMAT_XRGB8888; |
| } else { |
| void *virtual; |
| |
| /* TODO: this always allocates a buffer for 32bpp RGB.. but for |
| * YUV formats, we don't use all of it.. since 4bytes/pixel is |
| * worst case, so live with it for now and just don't use all |
| * the buffer: |
| */ |
| plane_bo = allocate_buffer(kms, p->w, p->h, &pitches[0]); |
| if (!plane_bo) |
| return -1; |
| |
| ret = kms_bo_map(plane_bo, &virtual); |
| if (ret) { |
| fprintf(stderr, "failed to map buffer: %s\n", |
| strerror(-ret)); |
| kms_bo_destroy(&plane_bo); |
| return -1; |
| } |
| |
| /* just testing a limited # of formats to test single |
| * and multi-planar path.. would be nice to add more.. |
| */ |
| if (!strcmp(p->format_str, "YUYV")) { |
| pitches[0] = p->w * 2; |
| offsets[0] = 0; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[0]); |
| |
| fill422(virtual, 0, p->w, p->h, pitches[0]); |
| |
| format = DRM_FORMAT_YUYV; |
| } else if (!strcmp(p->format_str, "NV12")) { |
| pitches[0] = p->w; |
| offsets[0] = 0; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[0]); |
| pitches[1] = p->w; |
| offsets[1] = p->w * p->h; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[1]); |
| |
| fill420(virtual, virtual+offsets[1], virtual+offsets[1]+1, |
| 2, 0, p->w, p->h, pitches[0]); |
| |
| format = DRM_FORMAT_NV12; |
| } else if (!strcmp(p->format_str, "YV12")) { |
| pitches[0] = p->w; |
| offsets[0] = 0; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[0]); |
| pitches[1] = p->w / 2; |
| offsets[1] = p->w * p->h; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[1]); |
| pitches[2] = p->w / 2; |
| offsets[2] = offsets[1] + (p->w * p->h) / 4; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[2]); |
| |
| fill420(virtual, virtual+offsets[1], virtual+offsets[2], |
| 1, 0, p->w, p->h, pitches[0]); |
| |
| format = DRM_FORMAT_YVU420; |
| } else if (!strcmp(p->format_str, "XR15")) { |
| pitches[0] = p->w * 2; |
| offsets[0] = 0; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[0]); |
| |
| fill1555(virtual, 0, p->w, p->h, pitches[0]); |
| |
| format = DRM_FORMAT_XRGB1555; |
| } else if (!strcmp(p->format_str, "AR15")) { |
| pitches[0] = p->w * 2; |
| offsets[0] = 0; |
| kms_bo_get_prop(plane_bo, KMS_HANDLE, &handles[0]); |
| |
| fill1555(virtual, 0, p->w, p->h, pitches[0]); |
| |
| format = DRM_FORMAT_ARGB1555; |
| } else { |
| fprintf(stderr, "Unknown format: %s\n", p->format_str); |
| return -1; |
| } |
| |
| kms_bo_unmap(plane_bo); |
| } |
| |
| /* just use single plane format for now.. */ |
| if (drmModeAddFB2(fd, p->w, p->h, format, |
| handles, pitches, offsets, &p->fb_id, plane_flags)) { |
| fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); |
| return -1; |
| } |
| |
| /* ok, boring.. but for now put in middle of screen: */ |
| crtc_x = c->mode->hdisplay / 3; |
| crtc_y = c->mode->vdisplay / 3; |
| crtc_w = crtc_x; |
| crtc_h = crtc_y; |
| |
| /* note src coords (last 4 args) are in Q16 format */ |
| if (drmModeSetPlane(fd, plane_id, c->crtc, p->fb_id, |
| plane_flags, crtc_x, crtc_y, crtc_w, crtc_h, |
| 0, 0, p->w << 16, p->h << 16)) { |
| fprintf(stderr, "failed to enable plane: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| set_mode(struct connector *c, int count, struct plane *p, int plane_count, |
| int page_flip) |
| { |
| struct kms_driver *kms; |
| struct kms_bo *bo, *other_bo; |
| unsigned int fb_id, other_fb_id; |
| int i, j, ret, width, height, x, stride; |
| unsigned handle; |
| drmEventContext evctx; |
| |
| width = 0; |
| height = 0; |
| for (i = 0; i < count; i++) { |
| connector_find_mode(&c[i]); |
| if (c[i].mode == NULL) |
| continue; |
| width += c[i].mode->hdisplay; |
| if (height < c[i].mode->vdisplay) |
| height = c[i].mode->vdisplay; |
| } |
| |
| ret = kms_create(fd, &kms); |
| if (ret) { |
| fprintf(stderr, "failed to create kms driver: %s\n", |
| strerror(-ret)); |
| return; |
| } |
| |
| if (create_test_buffer(kms, width, height, &stride, &bo)) |
| return; |
| |
| kms_bo_get_prop(bo, KMS_HANDLE, &handle); |
| ret = drmModeAddFB(fd, width, height, 24, 32, stride, handle, &fb_id); |
| if (ret) { |
| fprintf(stderr, "failed to add fb (%ux%u): %s\n", |
| width, height, strerror(errno)); |
| return; |
| } |
| |
| x = 0; |
| for (i = 0; i < count; i++) { |
| if (c[i].mode == NULL) |
| continue; |
| |
| printf("setting mode %s on connector %d, crtc %d\n", |
| c[i].mode_str, c[i].id, c[i].crtc); |
| |
| ret = drmModeSetCrtc(fd, c[i].crtc, fb_id, x, 0, |
| &c[i].id, 1, c[i].mode); |
| |
| /* XXX: Actually check if this is needed */ |
| drmModeDirtyFB(fd, fb_id, NULL, 0); |
| |
| x += c[i].mode->hdisplay; |
| |
| if (ret) { |
| fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); |
| return; |
| } |
| |
| /* if we have a plane/overlay to show, set that up now: */ |
| for (j = 0; j < plane_count; j++) |
| if (p[j].con_id == c[i].id) |
| if (set_plane(kms, &c[i], &p[j])) |
| return; |
| } |
| |
| if (!page_flip) |
| return; |
| |
| if (create_grey_buffer(kms, width, height, &stride, &other_bo)) |
| return; |
| |
| kms_bo_get_prop(other_bo, KMS_HANDLE, &handle); |
| ret = drmModeAddFB(fd, width, height, 32, 32, stride, handle, |
| &other_fb_id); |
| if (ret) { |
| fprintf(stderr, "failed to add fb: %s\n", strerror(errno)); |
| return; |
| } |
| |
| for (i = 0; i < count; i++) { |
| if (c[i].mode == NULL) |
| continue; |
| |
| ret = drmModePageFlip(fd, c[i].crtc, other_fb_id, |
| DRM_MODE_PAGE_FLIP_EVENT, &c[i]); |
| if (ret) { |
| fprintf(stderr, "failed to page flip: %s\n", strerror(errno)); |
| return; |
| } |
| gettimeofday(&c[i].start, NULL); |
| c[i].swap_count = 0; |
| c[i].fb_id[0] = fb_id; |
| c[i].fb_id[1] = other_fb_id; |
| c[i].current_fb_id = other_fb_id; |
| } |
| |
| memset(&evctx, 0, sizeof evctx); |
| evctx.version = DRM_EVENT_CONTEXT_VERSION; |
| evctx.vblank_handler = NULL; |
| evctx.page_flip_handler = page_flip_handler; |
| |
| while (1) { |
| #if 0 |
| struct pollfd pfd[2]; |
| |
| pfd[0].fd = 0; |
| pfd[0].events = POLLIN; |
| pfd[1].fd = fd; |
| pfd[1].events = POLLIN; |
| |
| if (poll(pfd, 2, -1) < 0) { |
| fprintf(stderr, "poll error\n"); |
| break; |
| } |
| |
| if (pfd[0].revents) |
| break; |
| #else |
| struct timeval timeout = { .tv_sec = 3, .tv_usec = 0 }; |
| fd_set fds; |
| int ret; |
| |
| FD_ZERO(&fds); |
| FD_SET(0, &fds); |
| FD_SET(fd, &fds); |
| ret = select(fd + 1, &fds, NULL, NULL, &timeout); |
| |
| if (ret <= 0) { |
| fprintf(stderr, "select timed out or error (ret %d)\n", |
| ret); |
| continue; |
| } else if (FD_ISSET(0, &fds)) { |
| break; |
| } |
| #endif |
| |
| drmHandleEvent(fd, &evctx); |
| } |
| |
| kms_bo_destroy(&bo); |
| kms_bo_destroy(&other_bo); |
| kms_destroy(&kms); |
| } |
| |
| extern char *optarg; |
| extern int optind, opterr, optopt; |
| static char optstr[] = "ecpmfs:P:v"; |
| |
| void usage(char *name) |
| { |
| fprintf(stderr, "usage: %s [-ecpmf]\n", name); |
| fprintf(stderr, "\t-e\tlist encoders\n"); |
| fprintf(stderr, "\t-c\tlist connectors\n"); |
| fprintf(stderr, "\t-p\tlist CRTCs and planes (pipes)\n"); |
| fprintf(stderr, "\t-m\tlist modes\n"); |
| fprintf(stderr, "\t-f\tlist framebuffers\n"); |
| fprintf(stderr, "\t-v\ttest vsynced page flipping\n"); |
| fprintf(stderr, "\t-s <connector_id>:<mode>\tset a mode\n"); |
| fprintf(stderr, "\t-s <connector_id>@<crtc_id>:<mode>\tset a mode\n"); |
| fprintf(stderr, "\t-P <connector_id>:<w>x<h>\tset a plane\n"); |
| fprintf(stderr, "\t-P <connector_id>:<w>x<h>@<format>\tset a plane\n"); |
| fprintf(stderr, "\n\tDefault is to dump all info.\n"); |
| exit(0); |
| } |
| |
| #define dump_resource(res) if (res) dump_##res() |
| |
| static int page_flipping_supported(void) |
| { |
| /*FIXME: generic ioctl needed? */ |
| return 1; |
| #if 0 |
| int ret, value; |
| struct drm_i915_getparam gp; |
| |
| gp.param = I915_PARAM_HAS_PAGEFLIPPING; |
| gp.value = &value; |
| |
| ret = drmCommandWriteRead(fd, DRM_I915_GETPARAM, &gp, sizeof(gp)); |
| if (ret) { |
| fprintf(stderr, "drm_i915_getparam: %m\n"); |
| return 0; |
| } |
| |
| return *gp.value; |
| #endif |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int c; |
| int encoders = 0, connectors = 0, crtcs = 0, planes = 0, framebuffers = 0; |
| int test_vsync = 0; |
| char *modules[] = { "i915", "radeon", "nouveau", "vmwgfx", "omapdrm" }; |
| unsigned int i; |
| int count = 0, plane_count = 0; |
| struct connector con_args[2]; |
| struct plane plane_args[2] = {0}; |
| |
| opterr = 0; |
| while ((c = getopt(argc, argv, optstr)) != -1) { |
| switch (c) { |
| case 'e': |
| encoders = 1; |
| break; |
| case 'c': |
| connectors = 1; |
| break; |
| case 'p': |
| crtcs = 1; |
| planes = 1; |
| break; |
| case 'm': |
| modes = 1; |
| break; |
| case 'f': |
| framebuffers = 1; |
| break; |
| case 'v': |
| test_vsync = 1; |
| break; |
| case 's': |
| con_args[count].crtc = -1; |
| if (sscanf(optarg, "%d:%64s", |
| &con_args[count].id, |
| con_args[count].mode_str) != 2 && |
| sscanf(optarg, "%d@%d:%64s", |
| &con_args[count].id, |
| &con_args[count].crtc, |
| con_args[count].mode_str) != 3) |
| usage(argv[0]); |
| count++; |
| break; |
| case 'P': |
| strcpy(plane_args[plane_count].format_str, "XR24"); |
| if (sscanf(optarg, "%d:%dx%d@%4s", |
| &plane_args[plane_count].con_id, |
| &plane_args[plane_count].w, |
| &plane_args[plane_count].h, |
| plane_args[plane_count].format_str) != 4 && |
| sscanf(optarg, "%d:%dx%d", |
| &plane_args[plane_count].con_id, |
| &plane_args[plane_count].w, |
| &plane_args[plane_count].h) != 3) |
| usage(argv[0]); |
| plane_count++; |
| break; |
| default: |
| usage(argv[0]); |
| break; |
| } |
| } |
| |
| if (argc == 1) |
| encoders = connectors = crtcs = planes = modes = framebuffers = 1; |
| |
| for (i = 0; i < ARRAY_SIZE(modules); i++) { |
| printf("trying to load module %s...", modules[i]); |
| fd = drmOpen(modules[i], NULL); |
| if (fd < 0) { |
| printf("failed.\n"); |
| } else { |
| printf("success.\n"); |
| break; |
| } |
| } |
| |
| if (test_vsync && !page_flipping_supported()) { |
| fprintf(stderr, "page flipping not supported by drm.\n"); |
| return -1; |
| } |
| |
| if (i == ARRAY_SIZE(modules)) { |
| fprintf(stderr, "failed to load any modules, aborting.\n"); |
| return -1; |
| } |
| |
| resources = drmModeGetResources(fd); |
| if (!resources) { |
| fprintf(stderr, "drmModeGetResources failed: %s\n", |
| strerror(errno)); |
| drmClose(fd); |
| return 1; |
| } |
| |
| dump_resource(encoders); |
| dump_resource(connectors); |
| dump_resource(crtcs); |
| dump_resource(planes); |
| dump_resource(framebuffers); |
| |
| if (count > 0) { |
| set_mode(con_args, count, plane_args, plane_count, test_vsync); |
| getchar(); |
| } |
| |
| drmModeFreeResources(resources); |
| |
| return 0; |
| } |