/* * Copyright © 2013 Intel Corporation * * 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 (including the next * paragraph) 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. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include "drm_fourcc.h" #include "drmtest.h" #include "igt_kms.h" /* helpers to create nice-looking framebuffers */ static int create_bo_for_fb(int fd, int width, int height, int bpp, bool tiled, uint32_t *gem_handle_ret, unsigned *size_ret, unsigned *stride_ret) { uint32_t gem_handle; int size; unsigned stride; if (tiled) { int v; /* Round the tiling up to the next power-of-two and the * region up to the next pot fence size so that this works * on all generations. * * This can still fail if the framebuffer is too large to * be tiled. But then that failure is expected. */ v = width * bpp / 8; for (stride = 512; stride < v; stride *= 2) ; v = stride * height; for (size = 1024*1024; size < v; size *= 2) ; } else { /* Scan-out has a 64 byte alignment restriction */ stride = (width * (bpp / 8) + 63) & ~63; size = stride * height; } gem_handle = gem_create(fd, size); if (tiled) gem_set_tiling(fd, gem_handle, I915_TILING_X, stride); *stride_ret = stride; *size_ret = size; *gem_handle_ret = gem_handle; return 0; } void kmstest_paint_color(cairo_t *cr, int x, int y, int w, int h, double r, double g, double b) { cairo_rectangle(cr, x, y, w, h); cairo_set_source_rgb(cr, r, g, b); cairo_fill(cr); } void kmstest_paint_color_alpha(cairo_t *cr, int x, int y, int w, int h, double r, double g, double b, double a) { cairo_rectangle(cr, x, y, w, h); cairo_set_source_rgba(cr, r, g, b, a); cairo_fill(cr); } void kmstest_paint_color_gradient(cairo_t *cr, int x, int y, int w, int h, int r, int g, int b) { cairo_pattern_t *pat; pat = cairo_pattern_create_linear(x, y, x + w, y + h); cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 1); cairo_pattern_add_color_stop_rgba(pat, 0, r, g, b, 1); cairo_rectangle(cr, x, y, w, h); cairo_set_source(cr, pat); cairo_fill(cr); cairo_pattern_destroy(pat); } static void paint_test_patterns(cairo_t *cr, int width, int height) { double gr_height, gr_width; int x, y; y = height * 0.10; gr_width = width * 0.75; gr_height = height * 0.08; x = (width / 2) - (gr_width / 2); kmstest_paint_color_gradient(cr, x, y, gr_width, gr_height, 1, 0, 0); y += gr_height; kmstest_paint_color_gradient(cr, x, y, gr_width, gr_height, 0, 1, 0); y += gr_height; kmstest_paint_color_gradient(cr, x, y, gr_width, gr_height, 0, 0, 1); y += gr_height; kmstest_paint_color_gradient(cr, x, y, gr_width, gr_height, 1, 1, 1); } int kmstest_cairo_printf_line(cairo_t *cr, enum kmstest_text_align align, double yspacing, const char *fmt, ...) { double x, y, xofs, yofs; cairo_text_extents_t extents; char *text; va_list ap; int ret; va_start(ap, fmt); ret = vasprintf(&text, fmt, ap); assert(ret >= 0); va_end(ap); cairo_text_extents(cr, text, &extents); xofs = yofs = 0; if (align & align_right) xofs = -extents.width; else if (align & align_hcenter) xofs = -extents.width / 2; if (align & align_top) yofs = extents.height; else if (align & align_vcenter) yofs = extents.height / 2; cairo_get_current_point(cr, &x, &y); if (xofs || yofs) cairo_rel_move_to(cr, xofs, yofs); cairo_text_path(cr, text); cairo_set_source_rgb(cr, 0, 0, 0); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 1, 1, 1); cairo_fill(cr); cairo_move_to(cr, x, y + extents.height + yspacing); free(text); return extents.width; } static void paint_marker(cairo_t *cr, int x, int y) { enum kmstest_text_align align; int xoff, yoff; 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); xoff = x ? -20 : 20; align = x ? align_right : align_left; yoff = y ? -20 : 20; align |= y ? align_bottom : align_top; cairo_move_to(cr, x + xoff, y + yoff); cairo_set_font_size(cr, 18); kmstest_cairo_printf_line(cr, align, 0, "(%d, %d)", x, y); } void kmstest_paint_test_pattern(cairo_t *cr, int width, int height) { paint_test_patterns(cr, width, height); cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); /* Paint corner markers */ paint_marker(cr, 0, 0); paint_marker(cr, width, 0); paint_marker(cr, 0, height); paint_marker(cr, width, height); assert(!cairo_status(cr)); } void kmstest_paint_image(cairo_t *cr, const char *filename, int dst_x, int dst_y, int dst_width, int dst_height) { cairo_surface_t *image; int img_width, img_height; double scale_x, scale_y; image = cairo_image_surface_create_from_png(filename); assert(cairo_surface_status(image) == CAIRO_STATUS_SUCCESS); img_width = cairo_image_surface_get_width(image); img_height = cairo_image_surface_get_height(image); scale_x = (double)dst_width / img_width; scale_y = (double)dst_height / img_height; cairo_save(cr); cairo_translate(cr, dst_x, dst_y); cairo_scale(cr, scale_x, scale_y); cairo_set_source_surface(cr, image, 0, 0); cairo_paint(cr); cairo_surface_destroy(image); cairo_restore(cr); } #define DF(did, cid, _bpp, _depth) \ { DRM_FORMAT_##did, CAIRO_FORMAT_##cid, # did, _bpp, _depth } static struct format_desc_struct { uint32_t drm_id; cairo_format_t cairo_id; const char *name; int bpp; int depth; } format_desc[] = { DF(RGB565, RGB16_565, 16, 16), DF(RGB888, INVALID, 24, 24), DF(XRGB8888, RGB24, 32, 24), DF(XRGB2101010, RGB30, 32, 30), DF(ARGB8888, ARGB32, 32, 32), }; #undef DF #define for_each_format(f) \ for (f = format_desc; f - format_desc < ARRAY_SIZE(format_desc); f++) static uint32_t bpp_depth_to_drm_format(int bpp, int depth) { struct format_desc_struct *f; for_each_format(f) if (f->bpp == bpp && f->depth == depth) return f->drm_id; abort(); } /* Return fb_id on success, 0 on error */ unsigned int kmstest_create_fb(int fd, int width, int height, int bpp, int depth, bool tiled, struct kmstest_fb *fb) { memset(fb, 0, sizeof(*fb)); if (create_bo_for_fb(fd, width, height, bpp, tiled, &fb->gem_handle, &fb->size, &fb->stride) < 0) return 0; if (drmModeAddFB(fd, width, height, depth, bpp, fb->stride, fb->gem_handle, &fb->fb_id) < 0) { gem_close(fd, fb->gem_handle); return 0; } fb->width = width; fb->height = height; fb->tiling = tiled; fb->drm_format = bpp_depth_to_drm_format(bpp, depth); return fb->fb_id; } uint32_t drm_format_to_bpp(uint32_t drm_format) { struct format_desc_struct *f; for_each_format(f) if (f->drm_id == drm_format) return f->bpp; abort(); } unsigned int kmstest_create_fb2(int fd, int width, int height, uint32_t format, bool tiled, struct kmstest_fb *fb) { uint32_t handles[4]; uint32_t pitches[4]; uint32_t offsets[4]; uint32_t fb_id; int bpp; int ret; memset(fb, 0, sizeof(*fb)); bpp = drm_format_to_bpp(format); ret = create_bo_for_fb(fd, width, height, bpp, tiled, &fb->gem_handle, &fb->size, &fb->stride); if (ret < 0) return ret; memset(handles, 0, sizeof(handles)); handles[0] = fb->gem_handle; memset(pitches, 0, sizeof(pitches)); pitches[0] = fb->stride; memset(offsets, 0, sizeof(offsets)); if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &fb_id, 0) < 0) { gem_close(fd, fb->gem_handle); return 0; } fb->width = width; fb->height = height; fb->tiling = tiled; fb->drm_format = format; fb->fb_id = fb_id; return fb_id; } static cairo_format_t drm_format_to_cairo(uint32_t drm_format) { struct format_desc_struct *f; for_each_format(f) if (f->drm_id == drm_format) return f->cairo_id; abort(); } static cairo_surface_t *create_image_surface(int fd, struct kmstest_fb *fb) { cairo_surface_t *surface; cairo_format_t cformat; void *fb_ptr; cformat = drm_format_to_cairo(fb->drm_format); fb_ptr = gem_mmap(fd, fb->gem_handle, fb->size, PROT_READ | PROT_WRITE); surface = cairo_image_surface_create_for_data((unsigned char *)fb_ptr, cformat, fb->width, fb->height, fb->stride); assert(surface); return surface; } static cairo_t *create_cairo_ctx(int fd, struct kmstest_fb *fb) { cairo_t *cr; cairo_surface_t *surface; surface = create_image_surface(fd, fb); cr = cairo_create(surface); cairo_surface_destroy(surface); return cr; } void kmstest_write_fb(int fd, struct kmstest_fb *fb, const char *filename) { cairo_surface_t *surface; cairo_status_t status; surface = create_image_surface(fd, fb); status = cairo_surface_write_to_png(surface, filename); assert(status == CAIRO_STATUS_SUCCESS); cairo_surface_destroy(surface); } cairo_t *kmstest_get_cairo_ctx(int fd, struct kmstest_fb *fb) { if (!fb->cairo_ctx) fb->cairo_ctx = create_cairo_ctx(fd, fb); gem_set_domain(fd, fb->gem_handle, I915_GEM_DOMAIN_CPU, I915_GEM_DOMAIN_CPU); return fb->cairo_ctx; } void kmstest_remove_fb(int fd, struct kmstest_fb *fb) { if (fb->cairo_ctx) cairo_destroy(fb->cairo_ctx); do_or_die(drmModeRmFB(fd, fb->fb_id)); gem_close(fd, fb->gem_handle); } const char *kmstest_format_str(uint32_t drm_format) { struct format_desc_struct *f; for_each_format(f) if (f->drm_id == drm_format) return f->name; return "invalid"; } const char *kmstest_pipe_str(int pipe) { const char *str[] = { "A", "B", "C" }; if (pipe > 2) return "invalid"; return str[pipe]; } void kmstest_get_all_formats(const uint32_t **formats, int *format_count) { static uint32_t *drm_formats; if (!drm_formats) { struct format_desc_struct *f; uint32_t *format; drm_formats = calloc(ARRAY_SIZE(format_desc), sizeof(*drm_formats)); format = &drm_formats[0]; for_each_format(f) *format++ = f->drm_id; } *formats = drm_formats; *format_count = ARRAY_SIZE(format_desc); } struct type_name { int type; const char *name; }; #define type_name_fn(res) \ const char * kmstest_##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, "DP" }, { DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" }, { DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" }, { DRM_MODE_CONNECTOR_TV, "TV" }, { DRM_MODE_CONNECTOR_eDP, "eDP" }, }; type_name_fn(connector_type) static const char *mode_stereo_name(const drmModeModeInfo *mode) { switch (mode->flags & DRM_MODE_FLAG_3D_MASK) { case DRM_MODE_FLAG_3D_FRAME_PACKING: return "FP"; case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE: return "FA"; case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE: return "LA"; case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL: return "SBSF"; case DRM_MODE_FLAG_3D_L_DEPTH: return "LD"; case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH: return "LDGFX"; case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM: return "TB"; case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF: return "SBSH"; default: return NULL; } } void kmstest_dump_mode(drmModeModeInfo *mode) { const char *stereo = mode_stereo_name(mode); printf(" %s %d %d %d %d %d %d %d %d %d 0x%x 0x%x %d%s%s%s\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, mode->flags, mode->type, mode->clock, stereo ? " (3D:" : "", stereo ? stereo : "", stereo ? ")" : ""); fflush(stdout); } int kmstest_get_pipe_from_crtc_id(int fd, int crtc_id) { struct drm_i915_get_pipe_from_crtc_id pfci; int ret; memset(&pfci, 0, sizeof(pfci)); pfci.crtc_id = crtc_id; ret = drmIoctl(fd, DRM_IOCTL_I915_GET_PIPE_FROM_CRTC_ID, &pfci); igt_assert(ret == 0); return pfci.pipe; } static signed long set_vt_mode(unsigned long mode) { int fd; unsigned long prev_mode; fd = open("/dev/tty0", O_RDONLY); if (fd < 0) return -errno; prev_mode = 0; if (drmIoctl(fd, KDGETMODE, &prev_mode)) goto err; if (drmIoctl(fd, KDSETMODE, (void *)mode)) goto err; close(fd); return prev_mode; err: close(fd); return -errno; } static unsigned long orig_vt_mode = -1UL; static void restore_vt_mode_at_exit(int sig) { if (orig_vt_mode != -1UL) set_vt_mode(orig_vt_mode); } /* * Set the VT to graphics mode and install an exit handler to restore the * original mode. */ void igt_set_vt_graphics_mode(void) { igt_install_exit_handler(restore_vt_mode_at_exit); igt_disable_exit_handler(); orig_vt_mode = set_vt_mode(KD_GRAPHICS); if (orig_vt_mode < 0) orig_vt_mode = -1UL; igt_enable_exit_handler(); igt_assert(orig_vt_mode >= 0); } int kmstest_get_connector_default_mode(int drm_fd, drmModeConnector *connector, drmModeModeInfo *mode) { drmModeRes *resources; int i; resources = drmModeGetResources(drm_fd); if (!resources) { perror("drmModeGetResources failed"); return -1; } if (!connector->count_modes) { fprintf(stderr, "no modes for connector %d\n", connector->connector_id); drmModeFreeResources(resources); return -1; } for (i = 0; i < connector->count_modes; i++) { if (i == 0 || connector->modes[i].type & DRM_MODE_TYPE_PREFERRED) { *mode = connector->modes[i]; if (mode->type & DRM_MODE_TYPE_PREFERRED) break; } } drmModeFreeResources(resources); return 0; } int kmstest_get_connector_config(int drm_fd, uint32_t connector_id, unsigned long crtc_idx_mask, struct kmstest_connector_config *config) { drmModeRes *resources; drmModeConnector *connector; drmModeEncoder *encoder; int i, j; resources = drmModeGetResources(drm_fd); if (!resources) { perror("drmModeGetResources failed"); goto err1; } /* First, find the connector & mode */ connector = drmModeGetConnector(drm_fd, connector_id); if (!connector) goto err2; if (connector->connection != DRM_MODE_CONNECTED) goto err3; if (!connector->count_modes) { fprintf(stderr, "connector %d has no modes\n", connector_id); goto err3; } if (connector->connector_id != connector_id) { fprintf(stderr, "connector id doesn't match (%d != %d)\n", connector->connector_id, connector_id); goto err3; } /* * Find given CRTC if crtc_id != 0 or else the first CRTC not in use. * In both cases find the first compatible encoder and skip the CRTC * if there is non such. */ encoder = NULL; /* suppress GCC warning */ for (i = 0; i < resources->count_crtcs; i++) { if (!resources->crtcs[i] || !(crtc_idx_mask & (1 << i))) continue; /* Now get a compatible encoder */ for (j = 0; j < connector->count_encoders; j++) { encoder = drmModeGetEncoder(drm_fd, connector->encoders[j]); if (!encoder) { fprintf(stderr, "could not get encoder %d: %s\n", resources->encoders[j], strerror(errno)); continue; } if (encoder->possible_crtcs & (1 << i)) goto found; drmModeFreeEncoder(encoder); } } goto err3; found: if (kmstest_get_connector_default_mode(drm_fd, connector, &config->default_mode) < 0) goto err4; config->connector = connector; config->encoder = encoder; config->crtc = drmModeGetCrtc(drm_fd, resources->crtcs[i]); config->crtc_idx = i; config->pipe = kmstest_get_pipe_from_crtc_id(drm_fd, config->crtc->crtc_id); drmModeFreeResources(resources); return 0; err4: drmModeFreeEncoder(encoder); err3: drmModeFreeConnector(connector); err2: drmModeFreeResources(resources); err1: return -1; } void kmstest_free_connector_config(struct kmstest_connector_config *config) { drmModeFreeCrtc(config->crtc); drmModeFreeEncoder(config->encoder); drmModeFreeConnector(config->connector); }