/* * Mesa 3-D graphics library * Version: 7.9 * * Copyright (C) 2010 LunarG Inc. * Copyright (C) 2011 VMware Inc. All rights reserved. * * 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. * * Authors: * Chia-I Wu * Thomas Hellstrom */ #include "util/u_memory.h" #include "util/u_inlines.h" #include "egllog.h" #include "native_drm.h" static boolean drm_surface_validate(struct native_surface *nsurf, uint attachment_mask, unsigned int *seq_num, struct pipe_resource **textures, int *width, int *height) { struct drm_surface *drmsurf = drm_surface(nsurf); if (!resource_surface_add_resources(drmsurf->rsurf, attachment_mask)) return FALSE; if (textures) resource_surface_get_resources(drmsurf->rsurf, textures, attachment_mask); if (seq_num) *seq_num = drmsurf->sequence_number; if (width) *width = drmsurf->width; if (height) *height = drmsurf->height; return TRUE; } /** * Add textures as DRM framebuffers. */ static boolean drm_surface_init_framebuffers(struct native_surface *nsurf, boolean need_back) { struct drm_surface *drmsurf = drm_surface(nsurf); struct drm_display *drmdpy = drmsurf->drmdpy; int num_framebuffers = (need_back) ? 2 : 1; int i, err; for (i = 0; i < num_framebuffers; i++) { struct drm_framebuffer *fb; enum native_attachment natt; struct winsys_handle whandle; uint block_bits; if (i == 0) { fb = &drmsurf->front_fb; natt = NATIVE_ATTACHMENT_FRONT_LEFT; } else { fb = &drmsurf->back_fb; natt = NATIVE_ATTACHMENT_BACK_LEFT; } if (!fb->texture) { /* make sure the texture has been allocated */ resource_surface_add_resources(drmsurf->rsurf, 1 << natt); fb->texture = resource_surface_get_single_resource(drmsurf->rsurf, natt); if (!fb->texture) return FALSE; } /* already initialized */ if (fb->buffer_id) continue; /* TODO detect the real value */ fb->is_passive = TRUE; memset(&whandle, 0, sizeof(whandle)); whandle.type = DRM_API_HANDLE_TYPE_KMS; if (!drmdpy->base.screen->resource_get_handle(drmdpy->base.screen, fb->texture, &whandle)) return FALSE; block_bits = util_format_get_blocksizebits(drmsurf->color_format); err = drmModeAddFB(drmdpy->fd, drmsurf->width, drmsurf->height, block_bits, block_bits, whandle.stride, whandle.handle, &fb->buffer_id); if (err) { fb->buffer_id = 0; return FALSE; } } return TRUE; } static boolean drm_surface_flush_frontbuffer(struct native_surface *nsurf) { #ifdef DRM_MODE_FEATURE_DIRTYFB struct drm_surface *drmsurf = drm_surface(nsurf); struct drm_display *drmdpy = drmsurf->drmdpy; if (drmsurf->front_fb.is_passive) drmModeDirtyFB(drmdpy->fd, drmsurf->front_fb.buffer_id, NULL, 0); #endif return TRUE; } static boolean drm_surface_copy_swap(struct native_surface *nsurf) { struct drm_surface *drmsurf = drm_surface(nsurf); struct drm_display *drmdpy = drmsurf->drmdpy; (void) resource_surface_throttle(drmsurf->rsurf); if (!resource_surface_copy_swap(drmsurf->rsurf, &drmdpy->base)) return FALSE; (void) resource_surface_flush(drmsurf->rsurf, &drmdpy->base); if (!drm_surface_flush_frontbuffer(nsurf)) return FALSE; drmsurf->sequence_number++; return TRUE; } static boolean drm_surface_swap_buffers(struct native_surface *nsurf) { struct drm_surface *drmsurf = drm_surface(nsurf); struct drm_crtc *drmcrtc = &drmsurf->current_crtc; struct drm_display *drmdpy = drmsurf->drmdpy; struct drm_framebuffer tmp_fb; int err; if (!drmsurf->have_pageflip) return drm_surface_copy_swap(nsurf); if (!drmsurf->back_fb.buffer_id) { if (!drm_surface_init_framebuffers(&drmsurf->base, TRUE)) return FALSE; } if (drmsurf->is_shown && drmcrtc->crtc) { err = drmModePageFlip(drmdpy->fd, drmcrtc->crtc->crtc_id, drmsurf->back_fb.buffer_id, 0, NULL); if (err) { drmsurf->have_pageflip = FALSE; return drm_surface_copy_swap(nsurf); } } /* swap the buffers */ tmp_fb = drmsurf->front_fb; drmsurf->front_fb = drmsurf->back_fb; drmsurf->back_fb = tmp_fb; resource_surface_swap_buffers(drmsurf->rsurf, NATIVE_ATTACHMENT_FRONT_LEFT, NATIVE_ATTACHMENT_BACK_LEFT, FALSE); /* the front/back textures are swapped */ drmsurf->sequence_number++; drmdpy->event_handler->invalid_surface(&drmdpy->base, &drmsurf->base, drmsurf->sequence_number); return TRUE; } static boolean drm_surface_present(struct native_surface *nsurf, enum native_attachment natt, boolean preserve, uint swap_interval) { boolean ret; if (swap_interval) return FALSE; switch (natt) { case NATIVE_ATTACHMENT_FRONT_LEFT: ret = drm_surface_flush_frontbuffer(nsurf); break; case NATIVE_ATTACHMENT_BACK_LEFT: if (preserve) ret = drm_surface_copy_swap(nsurf); else ret = drm_surface_swap_buffers(nsurf); break; default: ret = FALSE; break; } return ret; } static void drm_surface_wait(struct native_surface *nsurf) { struct drm_surface *drmsurf = drm_surface(nsurf); resource_surface_wait(drmsurf->rsurf); } static void drm_surface_destroy(struct native_surface *nsurf) { struct drm_surface *drmsurf = drm_surface(nsurf); resource_surface_wait(drmsurf->rsurf); if (drmsurf->current_crtc.crtc) drmModeFreeCrtc(drmsurf->current_crtc.crtc); if (drmsurf->front_fb.buffer_id) drmModeRmFB(drmsurf->drmdpy->fd, drmsurf->front_fb.buffer_id); pipe_resource_reference(&drmsurf->front_fb.texture, NULL); if (drmsurf->back_fb.buffer_id) drmModeRmFB(drmsurf->drmdpy->fd, drmsurf->back_fb.buffer_id); pipe_resource_reference(&drmsurf->back_fb.texture, NULL); resource_surface_destroy(drmsurf->rsurf); FREE(drmsurf); } static struct drm_surface * drm_display_create_surface(struct native_display *ndpy, const struct native_config *nconf, uint width, uint height) { struct drm_display *drmdpy = drm_display(ndpy); struct drm_config *drmconf = drm_config(nconf); struct drm_surface *drmsurf; drmsurf = CALLOC_STRUCT(drm_surface); if (!drmsurf) return NULL; drmsurf->drmdpy = drmdpy; drmsurf->color_format = drmconf->base.color_format; drmsurf->width = width; drmsurf->height = height; drmsurf->have_pageflip = TRUE; drmsurf->rsurf = resource_surface_create(drmdpy->base.screen, drmsurf->color_format, PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW | PIPE_BIND_DISPLAY_TARGET | PIPE_BIND_SCANOUT); if (!drmsurf->rsurf) { FREE(drmsurf); return NULL; } resource_surface_set_size(drmsurf->rsurf, drmsurf->width, drmsurf->height); drmsurf->base.destroy = drm_surface_destroy; drmsurf->base.present = drm_surface_present; drmsurf->base.validate = drm_surface_validate; drmsurf->base.wait = drm_surface_wait; return drmsurf; } /** * Choose a CRTC that supports all given connectors. */ static uint32_t drm_display_choose_crtc(struct native_display *ndpy, uint32_t *connectors, int num_connectors) { struct drm_display *drmdpy = drm_display(ndpy); int idx; for (idx = 0; idx < drmdpy->resources->count_crtcs; idx++) { boolean found_crtc = TRUE; int i, j; for (i = 0; i < num_connectors; i++) { drmModeConnectorPtr connector; int encoder_idx = -1; connector = drmModeGetConnector(drmdpy->fd, connectors[i]); if (!connector) { found_crtc = FALSE; break; } /* find an encoder the CRTC supports */ for (j = 0; j < connector->count_encoders; j++) { drmModeEncoderPtr encoder = drmModeGetEncoder(drmdpy->fd, connector->encoders[j]); if (encoder->possible_crtcs & (1 << idx)) { encoder_idx = j; break; } drmModeFreeEncoder(encoder); } drmModeFreeConnector(connector); if (encoder_idx < 0) { found_crtc = FALSE; break; } } if (found_crtc) break; } if (idx >= drmdpy->resources->count_crtcs) { _eglLog(_EGL_WARNING, "failed to find a CRTC that supports the given %d connectors", num_connectors); return 0; } return drmdpy->resources->crtcs[idx]; } /** * Remember the original CRTC status and set the CRTC */ static boolean drm_display_set_crtc(struct native_display *ndpy, int crtc_idx, uint32_t buffer_id, uint32_t x, uint32_t y, uint32_t *connectors, int num_connectors, drmModeModeInfoPtr mode) { struct drm_display *drmdpy = drm_display(ndpy); struct drm_crtc *drmcrtc = &drmdpy->saved_crtcs[crtc_idx]; uint32_t crtc_id; int err; if (drmcrtc->crtc) { crtc_id = drmcrtc->crtc->crtc_id; } else { int count = 0, i; /* * Choose the CRTC once. It could be more dynamic, but let's keep it * simple for now. */ crtc_id = drm_display_choose_crtc(&drmdpy->base, connectors, num_connectors); /* save the original CRTC status */ drmcrtc->crtc = drmModeGetCrtc(drmdpy->fd, crtc_id); if (!drmcrtc->crtc) return FALSE; for (i = 0; i < drmdpy->num_connectors; i++) { struct drm_connector *drmconn = &drmdpy->connectors[i]; drmModeConnectorPtr connector = drmconn->connector; drmModeEncoderPtr encoder; encoder = drmModeGetEncoder(drmdpy->fd, connector->encoder_id); if (encoder) { if (encoder->crtc_id == crtc_id) { drmcrtc->connectors[count++] = connector->connector_id; if (count >= Elements(drmcrtc->connectors)) break; } drmModeFreeEncoder(encoder); } } drmcrtc->num_connectors = count; } err = drmModeSetCrtc(drmdpy->fd, crtc_id, buffer_id, x, y, connectors, num_connectors, mode); if (err) { drmModeFreeCrtc(drmcrtc->crtc); drmcrtc->crtc = NULL; drmcrtc->num_connectors = 0; return FALSE; } return TRUE; } static boolean drm_display_program(struct native_display *ndpy, int crtc_idx, struct native_surface *nsurf, uint x, uint y, const struct native_connector **nconns, int num_nconns, const struct native_mode *nmode) { struct drm_display *drmdpy = drm_display(ndpy); struct drm_surface *drmsurf = drm_surface(nsurf); const struct drm_mode *drmmode = drm_mode(nmode); uint32_t connector_ids[32]; uint32_t buffer_id; drmModeModeInfo mode_tmp, *mode; int i; if (num_nconns > Elements(connector_ids)) { _eglLog(_EGL_WARNING, "too many connectors (%d)", num_nconns); num_nconns = Elements(connector_ids); } if (drmsurf) { if (!drm_surface_init_framebuffers(&drmsurf->base, FALSE)) return FALSE; buffer_id = drmsurf->front_fb.buffer_id; /* the mode argument of drmModeSetCrtc is not constified */ mode_tmp = drmmode->mode; mode = &mode_tmp; } else { /* disable the CRTC */ buffer_id = 0; mode = NULL; num_nconns = 0; } for (i = 0; i < num_nconns; i++) { struct drm_connector *drmconn = drm_connector(nconns[i]); connector_ids[i] = drmconn->connector->connector_id; } if (!drm_display_set_crtc(&drmdpy->base, crtc_idx, buffer_id, x, y, connector_ids, num_nconns, mode)) { _eglLog(_EGL_WARNING, "failed to set CRTC %d", crtc_idx); return FALSE; } if (drmdpy->shown_surfaces[crtc_idx]) drmdpy->shown_surfaces[crtc_idx]->is_shown = FALSE; drmdpy->shown_surfaces[crtc_idx] = drmsurf; /* remember the settings for buffer swapping */ if (drmsurf) { uint32_t crtc_id = drmdpy->saved_crtcs[crtc_idx].crtc->crtc_id; struct drm_crtc *drmcrtc = &drmsurf->current_crtc; if (drmcrtc->crtc) drmModeFreeCrtc(drmcrtc->crtc); drmcrtc->crtc = drmModeGetCrtc(drmdpy->fd, crtc_id); assert(num_nconns < Elements(drmcrtc->connectors)); memcpy(drmcrtc->connectors, connector_ids, sizeof(*connector_ids) * num_nconns); drmcrtc->num_connectors = num_nconns; drmsurf->is_shown = TRUE; } return TRUE; } static const struct native_mode ** drm_display_get_modes(struct native_display *ndpy, const struct native_connector *nconn, int *num_modes) { struct drm_display *drmdpy = drm_display(ndpy); struct drm_connector *drmconn = drm_connector(nconn); const struct native_mode **nmodes_return; int count, i; /* delete old data */ if (drmconn->connector) { drmModeFreeConnector(drmconn->connector); FREE(drmconn->drm_modes); drmconn->connector = NULL; drmconn->drm_modes = NULL; drmconn->num_modes = 0; } /* detect again */ drmconn->connector = drmModeGetConnector(drmdpy->fd, drmconn->connector_id); if (!drmconn->connector) return NULL; count = drmconn->connector->count_modes; drmconn->drm_modes = CALLOC(count, sizeof(*drmconn->drm_modes)); if (!drmconn->drm_modes) { drmModeFreeConnector(drmconn->connector); drmconn->connector = NULL; return NULL; } for (i = 0; i < count; i++) { struct drm_mode *drmmode = &drmconn->drm_modes[i]; drmModeModeInfoPtr mode = &drmconn->connector->modes[i]; drmmode->mode = *mode; drmmode->base.desc = drmmode->mode.name; drmmode->base.width = drmmode->mode.hdisplay; drmmode->base.height = drmmode->mode.vdisplay; drmmode->base.refresh_rate = drmmode->mode.vrefresh; /* not all kernels have vrefresh = refresh_rate * 1000 */ if (drmmode->base.refresh_rate < 1000) drmmode->base.refresh_rate *= 1000; } nmodes_return = MALLOC(count * sizeof(*nmodes_return)); if (nmodes_return) { for (i = 0; i < count; i++) nmodes_return[i] = &drmconn->drm_modes[i].base; if (num_modes) *num_modes = count; } return nmodes_return; } static const struct native_connector ** drm_display_get_connectors(struct native_display *ndpy, int *num_connectors, int *num_crtc) { struct drm_display *drmdpy = drm_display(ndpy); const struct native_connector **connectors; int i; if (!drmdpy->connectors) { drmdpy->connectors = CALLOC(drmdpy->resources->count_connectors, sizeof(*drmdpy->connectors)); if (!drmdpy->connectors) return NULL; for (i = 0; i < drmdpy->resources->count_connectors; i++) { struct drm_connector *drmconn = &drmdpy->connectors[i]; drmconn->connector_id = drmdpy->resources->connectors[i]; /* drmconn->connector is allocated when the modes are asked */ } drmdpy->num_connectors = drmdpy->resources->count_connectors; } connectors = MALLOC(drmdpy->num_connectors * sizeof(*connectors)); if (connectors) { for (i = 0; i < drmdpy->num_connectors; i++) connectors[i] = &drmdpy->connectors[i].base; if (num_connectors) *num_connectors = drmdpy->num_connectors; } if (num_crtc) *num_crtc = drmdpy->resources->count_crtcs; return connectors; } static struct native_surface * drm_display_create_scanout_surface(struct native_display *ndpy, const struct native_config *nconf, uint width, uint height) { struct drm_surface *drmsurf; drmsurf = drm_display_create_surface(ndpy, nconf, width, height); return &drmsurf->base; } static struct native_display_modeset drm_display_modeset = { .get_connectors = drm_display_get_connectors, .get_modes = drm_display_get_modes, .create_scanout_surface = drm_display_create_scanout_surface, .program = drm_display_program }; void drm_display_fini_modeset(struct native_display *ndpy) { struct drm_display *drmdpy = drm_display(ndpy); int i; if (drmdpy->connectors) { for (i = 0; i < drmdpy->num_connectors; i++) { struct drm_connector *drmconn = &drmdpy->connectors[i]; if (drmconn->connector) { drmModeFreeConnector(drmconn->connector); FREE(drmconn->drm_modes); } } FREE(drmdpy->connectors); } if (drmdpy->shown_surfaces) { FREE(drmdpy->shown_surfaces); drmdpy->shown_surfaces = NULL; } if (drmdpy->saved_crtcs) { for (i = 0; i < drmdpy->resources->count_crtcs; i++) { struct drm_crtc *drmcrtc = &drmdpy->saved_crtcs[i]; if (drmcrtc->crtc) { /* restore crtc */ drmModeSetCrtc(drmdpy->fd, drmcrtc->crtc->crtc_id, drmcrtc->crtc->buffer_id, drmcrtc->crtc->x, drmcrtc->crtc->y, drmcrtc->connectors, drmcrtc->num_connectors, &drmcrtc->crtc->mode); drmModeFreeCrtc(drmcrtc->crtc); } } FREE(drmdpy->saved_crtcs); } if (drmdpy->resources) { drmModeFreeResources(drmdpy->resources); drmdpy->resources = NULL; } drmdpy->base.modeset = NULL; } boolean drm_display_init_modeset(struct native_display *ndpy) { struct drm_display *drmdpy = drm_display(ndpy); /* resources are fixed, unlike crtc, connector, or encoder */ drmdpy->resources = drmModeGetResources(drmdpy->fd); if (!drmdpy->resources) { _eglLog(_EGL_DEBUG, "Failed to get KMS resources. Disable modeset."); return FALSE; } drmdpy->saved_crtcs = CALLOC(drmdpy->resources->count_crtcs, sizeof(*drmdpy->saved_crtcs)); if (!drmdpy->saved_crtcs) { drm_display_fini_modeset(&drmdpy->base); return FALSE; } drmdpy->shown_surfaces = CALLOC(drmdpy->resources->count_crtcs, sizeof(*drmdpy->shown_surfaces)); if (!drmdpy->shown_surfaces) { drm_display_fini_modeset(&drmdpy->base); return FALSE; } drmdpy->base.modeset = &drm_display_modeset; return TRUE; }