#define LOG_TAG "GRALLOC-KMS"
+#include <cutils/properties.h>
#include <cutils/log.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
+#include <stdio.h>
+#include <poll.h>
#include "gralloc_drm.h"
#include "gralloc_drm_priv.h"
+#include <hardware_legacy/uevent.h>
+
+#include <drm_fourcc.h>
/*
* Return true if a bo needs fb.
bo->drm->swap_mode != DRM_SWAP_COPY);
}
+static unsigned int drm_format_from_hal(int hal_format)
+{
+ switch(hal_format) {
+ case HAL_PIXEL_FORMAT_RGB_888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ return DRM_FORMAT_XRGB8888;
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ return DRM_FORMAT_RGBA8888;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return DRM_FORMAT_RGB565;
+ case HAL_PIXEL_FORMAT_YV12:
+ return DRM_FORMAT_YUV420;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Modify pitches, offsets and handles according to
+ * the format and return corresponding drm format value
+ */
+static int resolve_drm_format(struct gralloc_drm_bo_t *bo,
+ uint32_t *pitches, uint32_t *offsets, uint32_t *handles)
+{
+ memset(pitches, 0, 4 * sizeof(uint32_t));
+ memset(offsets, 0, 4 * sizeof(uint32_t));
+ memset(handles, 0, 4 * sizeof(uint32_t));
+
+ pitches[0] = bo->handle->stride;
+ handles[0] = bo->fb_handle;
+
+ int format = drm_format_from_hal(bo->handle->format);
+
+ // handle 'special formats'
+ switch(bo->handle->format) {
+ case HAL_PIXEL_FORMAT_YV12:
+
+ // U and V stride are half of Y plane
+ pitches[2] = pitches[0]/2;
+ pitches[1] = pitches[0]/2;
+
+ // like I420 but U and V are in reverse order
+ offsets[2] = offsets[0] +
+ pitches[0] * bo->handle->height;
+ offsets[1] = offsets[2] +
+ pitches[2] * bo->handle->height/2;
+
+ handles[1] = handles[2] = handles[0];
+ }
+ return format;
+}
+
/*
* Add a fb object for a bo.
*/
int gralloc_drm_bo_add_fb(struct gralloc_drm_bo_t *bo)
{
- uint8_t bpp;
+ uint32_t pitches[4];
+ uint32_t offsets[4];
+ uint32_t handles[4];
if (bo->fb_id)
return 0;
- bpp = gralloc_drm_get_bpp(bo->handle->format) * 8;
+ int drm_format = resolve_drm_format(bo, pitches, offsets, handles);
- return drmModeAddFB(bo->drm->fd,
- bo->handle->width, bo->handle->height, bpp, bpp,
- bo->handle->stride, bo->fb_handle,
- (uint32_t *) &bo->fb_id);
+ if (drm_format == 0) {
+ ALOGE("error resolving drm format");
+ return -EINVAL;
+ }
+
+ return drmModeAddFB2(bo->drm->fd,
+ bo->handle->width, bo->handle->height,
+ drm_format, handles, pitches, offsets,
+ (uint32_t *) &bo->fb_id, 0);
}
/*
/*
* Program CRTC.
*/
-static int drm_kms_set_crtc(struct gralloc_drm_t *drm, int fb_id)
+static int drm_kms_set_crtc(struct gralloc_drm_t *drm,
+ struct gralloc_drm_output *output, int fb_id)
{
int ret;
- ret = drmModeSetCrtc(drm->fd, drm->crtc_id, fb_id,
- 0, 0, &drm->connector_id, 1, &drm->mode);
+ ret = drmModeSetCrtc(drm->fd, output->crtc_id, fb_id,
+ 0, 0, &output->connector_id, 1, &output->mode);
if (ret) {
- LOGE("failed to set crtc");
+ ALOGE("failed to set crtc (%s) (crtc_id %d, fb_id %d, conn %d, mode %dx%d)",
+ strerror(errno), output->crtc_id, fb_id, output->connector_id,
+ output->mode.hdisplay, output->mode.vdisplay);
return ret;
}
-#ifdef DRM_MODE_FEATURE_DIRTYFB
- if (drm->mode_dirty_fb)
+ if (drm->mode_quirk_vmwgfx)
ret = drmModeDirtyFB(drm->fd, fb_id, &drm->clip, 1);
-#endif
return ret;
}
drm->waiting_flip = 0;
if (drm->next_front) {
/* record an error and break */
- LOGE("drmHandleEvent returned without flipping");
+ ALOGE("drmHandleEvent returned without flipping");
drm->current_front = drm->next_front;
drm->next_front = NULL;
}
if (!bo)
return 0;
- ret = drmModePageFlip(drm->fd, drm->crtc_id, bo->fb_id,
+ pthread_mutex_lock(&drm->hdmi_mutex);
+ if (drm->hdmi.active && drm->hdmi_mode == HDMI_CLONED) {
+ ret = drmModePageFlip(drm->fd, drm->hdmi.crtc_id, bo->fb_id, 0, NULL);
+ if (ret && errno != EBUSY)
+ ALOGE("failed to perform page flip for hdmi (%s) (crtc %d fb %d))",
+ strerror(errno), drm->hdmi.crtc_id, bo->fb_id);
+ }
+ pthread_mutex_unlock(&drm->hdmi_mutex);
+
+ ret = drmModePageFlip(drm->fd, drm->primary.crtc_id, bo->fb_id,
DRM_MODE_PAGE_FLIP_EVENT, (void *) drm);
- if (ret)
- LOGE("failed to perform page flip");
+ if (ret) {
+ ALOGE("failed to perform page flip for primary (%s) (crtc %d fb %d))",
+ strerror(errno), drm->primary.crtc_id, bo->fb_id);
+ /* try to set mode for next frame */
+ drm->first_post = 1;
+ }
else
drm->next_front = bo;
drmVBlank vbl;
int ret;
+ if (drm->mode_quirk_vmwgfx)
+ return;
+
flip = !!flip;
memset(&vbl, 0, sizeof(vbl));
/* get the current vblank */
ret = drmWaitVBlank(drm->fd, &vbl);
if (ret) {
- LOGW("failed to get vblank");
+ ALOGW("failed to get vblank");
return;
}
ret = drmWaitVBlank(drm->fd, &vbl);
if (ret) {
- LOGW("failed to wait vblank");
+ ALOGW("failed to wait vblank");
return;
}
}
int ret;
if (!bo->fb_id && drm->swap_mode != DRM_SWAP_COPY) {
- LOGE("unable to post bo %p without fb", bo);
+ ALOGE("unable to post bo %p without fb", bo);
return -EINVAL;
}
bo = dst;
}
- drm_kms_wait_for_post(drm, 0);
- ret = drm_kms_set_crtc(drm, bo->fb_id);
+ ret = drm_kms_set_crtc(drm, &drm->primary, bo->fb_id);
if (!ret) {
drm->first_post = 0;
drm->current_front = bo;
drm->next_front = NULL;
}
+ pthread_mutex_lock(&drm->hdmi_mutex);
+ if (drm->hdmi.active && drm->hdmi_mode == HDMI_CLONED)
+ drm_kms_set_crtc(drm, &drm->hdmi, bo->fb_id);
+ pthread_mutex_unlock(&drm->hdmi_mutex);
+
return ret;
}
bo, 0, 0,
bo->handle->width,
bo->handle->height);
+ if (drm->mode_quirk_vmwgfx)
+ ret = drmModeDirtyFB(drm->fd, drm->current_front->fb_id, &drm->clip, 1);
ret = 0;
break;
case DRM_SWAP_SETCRTC:
drm_kms_wait_for_post(drm, 0);
- ret = drm_kms_set_crtc(drm, bo->fb_id);
+ ret = drm_kms_set_crtc(drm, &drm->primary, bo->fb_id);
+
+ pthread_mutex_lock(&drm->hdmi_mutex);
+ if (drm->hdmi.active && drm->hdmi_mode == HDMI_CLONED)
+ drm_kms_set_crtc(drm, &drm->hdmi, bo->fb_id);
+ pthread_mutex_unlock(&drm->hdmi_mutex);
+
drm->current_front = bo;
break;
default:
static void on_signal(int sig)
{
- struct sigaction act;
-
- /* wait the pending flip */
- if (drm_singleton && drm_singleton->next_front) {
- /* there is race, but this function is hacky enough to ignore that */
- if (drm_singleton->waiting_flip)
- usleep(100 * 1000); /* 100ms */
- else
- drm_kms_page_flip(drm_singleton, NULL);
- }
-
- exit(-1);
+ struct gralloc_drm_t *drm = drm_singleton;
+
+ /* wait the pending flip */
+ if (drm && drm->swap_mode == DRM_SWAP_FLIP && drm->next_front) {
+ /* there is race, but this function is hacky enough to ignore that */
+ if (drm_singleton->waiting_flip)
+ usleep(100 * 1000); /* 100ms */
+ else
+ drm_kms_page_flip(drm_singleton, NULL);
+ }
+
+ exit(-1);
}
static void drm_kms_init_features(struct gralloc_drm_t *drm)
/* create the real front buffer */
front = gralloc_drm_bo_create(drm,
- drm->mode.hdisplay,
- drm->mode.vdisplay,
- drm->format,
+ drm->primary.mode.hdisplay,
+ drm->primary.mode.vdisplay,
+ drm->primary.fb_format,
GRALLOC_USAGE_HW_FB);
if (front && gralloc_drm_bo_add_fb(front)) {
- gralloc_drm_bo_destroy(front);
+ gralloc_drm_bo_decref(front);
front = NULL;
}
break;
}
- LOGD("will use %s for fb posting", swap_mode);
+ ALOGD("will use %s for fb posting", swap_mode);
+}
+
+static drmModeModeInfoPtr find_mode(drmModeConnectorPtr connector, int *bpp)
+{
+ char value[PROPERTY_VALUE_MAX];
+ drmModeModeInfoPtr mode;
+ int dist, i;
+ int xres = 0, yres = 0;
+
+ if (property_get("debug.drm.mode", value, NULL)) {
+ char *p = value, *end;
+
+ /* parse <xres>x<yres>[@<bpp>] */
+ if (sscanf(value, "%dx%d@%d", &xres, &yres, bpp) != 3) {
+ *bpp = 0;
+ if (sscanf(value, "%dx%d", &xres, &yres) != 2)
+ xres = yres = 0;
+ }
+
+ if ((xres && yres) || *bpp) {
+ ALOGI("will find the closest match for %dx%d@%d",
+ xres, yres, *bpp);
+ }
+ }
+ else {
+ *bpp = 0;
+ }
+
+ mode = NULL;
+ dist = INT_MAX;
+ for (i = 0; i < connector->count_modes; i++) {
+ drmModeModeInfoPtr m = &connector->modes[i];
+ int tmp;
+
+ if (xres && yres) {
+ tmp = (m->hdisplay - xres) * (m->hdisplay - xres) +
+ (m->vdisplay - yres) * (m->vdisplay - yres);
+ }
+ else {
+ /* use the first preferred mode */
+ tmp = (m->type & DRM_MODE_TYPE_PREFERRED) ? 0 : dist;
+ }
+
+ if (tmp < dist) {
+ mode = m;
+ dist = tmp;
+ if (!dist)
+ break;
+ }
+ }
+
+ /* fallback to the first mode */
+ if (!mode)
+ mode = &connector->modes[0];
+
+ *bpp /= 8;
+
+ return mode;
}
/*
* Initialize KMS with a connector.
*/
static int drm_kms_init_with_connector(struct gralloc_drm_t *drm,
- drmModeConnectorPtr connector)
+ struct gralloc_drm_output *output, drmModeConnectorPtr connector)
{
drmModeEncoderPtr encoder;
drmModeModeInfoPtr mode;
- int i;
+ int bpp, i;
if (!connector->count_modes)
return -EINVAL;
if (!encoder)
return -EINVAL;
+ /* find first possible crtc which is not used yet */
for (i = 0; i < drm->resources->count_crtcs; i++) {
- if (encoder->possible_crtcs & (1 << i))
+ if (encoder->possible_crtcs & (1 << i) &&
+ i != drm->primary.crtc_id)
break;
}
drmModeFreeEncoder(encoder);
if (i == drm->resources->count_crtcs)
return -EINVAL;
- drm->crtc_id = drm->resources->crtcs[i];
- drm->connector_id = connector->connector_id;
+ output->crtc_id = drm->resources->crtcs[i];
+ output->connector_id = connector->connector_id;
- /* find the first preferred mode */
- mode = NULL;
- for (i = 0; i < connector->count_modes; i++) {
- drmModeModeInfoPtr m = &connector->modes[i];
- if (m->type & DRM_MODE_TYPE_PREFERRED) {
- mode = m;
- break;
- }
+ /* print connector info */
+ if (connector->count_modes > 1) {
+ ALOGI("there are %d modes on connector 0x%x",
+ connector->count_modes,
+ connector->connector_id);
+ for (i = 0; i < connector->count_modes; i++)
+ ALOGI(" %s", connector->modes[i].name);
}
- /* no preference; use the first */
- if (!mode)
- mode = &connector->modes[0];
+ else {
+ ALOGI("there is one mode on connector 0x%d: %s",
+ connector->connector_id,
+ connector->modes[0].name);
+ }
+
+ mode = find_mode(connector, &bpp);
- drm->mode = *mode;
+ ALOGI("the best mode is %s", mode->name);
+
+ output->mode = *mode;
+ switch (bpp) {
+ case 2:
+ output->fb_format = HAL_PIXEL_FORMAT_RGB_565;
+ break;
+ case 4:
+ default:
+ output->fb_format = HAL_PIXEL_FORMAT_BGRA_8888;
+ break;
+ }
if (connector->mmWidth && connector->mmHeight) {
- drm->xdpi = (drm->mode.hdisplay * 25.4 / connector->mmWidth);
- drm->ydpi = (drm->mode.vdisplay * 25.4 / connector->mmHeight);
+ output->xdpi = (output->mode.hdisplay * 25.4 / connector->mmWidth);
+ output->ydpi = (output->mode.vdisplay * 25.4 / connector->mmHeight);
}
else {
- drm->xdpi = 75;
- drm->ydpi = 75;
+ output->xdpi = 75;
+ output->ydpi = 75;
}
- /* select between 32/16 bits */
-#if 1
- drm->format = HAL_PIXEL_FORMAT_BGRA_8888;
-#else
- drm->format = HAL_PIXEL_FORMAT_RGB_565;
-#endif
-
#ifdef DRM_MODE_FEATURE_DIRTYFB
drm->clip.x1 = 0;
drm->clip.y1 = 0;
- drm->clip.x2 = drm->mode.hdisplay;
- drm->clip.y2 = drm->mode.vdisplay;
+ drm->clip.x2 = output->mode.hdisplay;
+ drm->clip.y2 = output->mode.vdisplay;
#endif
return 0;
}
+
+/*
+ * Fetch a connector of particular type
+ */
+static drmModeConnectorPtr fetch_connector(struct gralloc_drm_t *drm,
+ uint32_t type)
+{
+ int i;
+
+ if (!drm->resources)
+ return NULL;
+
+ for (i = 0; i < drm->resources->count_connectors; i++) {
+ drmModeConnectorPtr connector =
+ connector = drmModeGetConnector(drm->fd,
+ drm->resources->connectors[i]);
+ if (connector) {
+ if (connector->connector_type == type &&
+ connector->connection == DRM_MODE_CONNECTED)
+ return connector;
+ drmModeFreeConnector(connector);
+ }
+ }
+ return NULL;
+}
+
+
+/*
+ * Thread that listens to uevents and checks if hdmi state changes
+ */
+static void *hdmi_observer(void *data)
+{
+ static char uevent_desc[4096];
+ drmModeConnectorPtr hdmi;
+ struct gralloc_drm_t *drm =
+ (struct gralloc_drm_t *) data;
+
+ uevent_init();
+
+ memset(uevent_desc, 0, sizeof(uevent_desc));
+
+ while(1) {
+
+ /* this polls */
+ int len = uevent_next_event(uevent_desc, sizeof(uevent_desc) - 2);
+
+ if(len && strstr(uevent_desc, "devices/virtual/switch/hdmi")) {
+
+ /* check what changed */
+ const char *prop = uevent_desc + strlen(uevent_desc) + 1;
+
+ while (*prop) {
+
+ const char *state = strstr(prop, "SWITCH_STATE=");
+ if (state) {
+ unsigned int value = 0;
+ state += strlen("SWITCH_STATE=");
+ value = atoi(state);
+
+ pthread_mutex_lock(&drm->hdmi_mutex);
+
+ if (value) {
+ hdmi = fetch_connector(drm, DRM_MODE_CONNECTOR_HDMIA);
+ if (hdmi) {
+ drm_kms_init_with_connector(drm, &drm->hdmi, hdmi);
+ drmModeFreeConnector(hdmi);
+
+ /* will trigger modeset */
+ drm->first_post = 1;
+
+ /* HACK, assume same mode for now */
+ memcpy(&drm->hdmi.mode, &drm->primary.mode,
+ sizeof(drmModeModeInfo));
+
+ drm->hdmi_mode = HDMI_CLONED;
+ drm->hdmi.active = 1;
+ pthread_mutex_unlock(&drm->hdmi_mutex);
+ }
+ break;
+ } else {
+ drm->hdmi.active = 0;
+ pthread_mutex_unlock(&drm->hdmi_mutex);
+ break;
+ }
+
+ pthread_mutex_unlock(&drm->hdmi_mutex);
+ }
+
+ /* next property/value pair */
+ prop += strlen(prop) + 1;
+ if (prop - uevent_desc >= len)
+ break;
+ }
+ }
+ }
+
+ pthread_exit(NULL);
+ return 0;
+}
+
+
/*
* Initialize KMS.
*/
int gralloc_drm_init_kms(struct gralloc_drm_t *drm)
{
+ drmModeConnectorPtr lvds, hdmi;
int i, ret;
if (drm->resources)
drm->resources = drmModeGetResources(drm->fd);
if (!drm->resources) {
- LOGE("failed to get modeset resources");
+ ALOGE("failed to get modeset resources");
return -EINVAL;
}
+ drm->plane_resources = drmModeGetPlaneResources(drm->fd);
+ if (!drm->plane_resources) {
+ ALOGD("no planes found from drm resources");
+ } else {
+ ALOGD("supported drm planes and formats");
+ /* fill a helper structure for hwcomposer */
+ drm->planes = calloc(drm->plane_resources->count_planes,
+ sizeof(struct gralloc_drm_plane_t));
+
+ for (i = 0; i < drm->plane_resources->count_planes; i++) {
+
+ unsigned int j;
+
+ drm->planes[i].drm_plane = drmModeGetPlane(drm->fd,
+ drm->plane_resources->planes[i]);
+
+ ALOGD("plane id %d", drm->planes[i].drm_plane->plane_id);
+ for (j = 0; j < drm->planes[i].drm_plane->count_formats; j++)
+ ALOGD(" format %c%c%c%c",
+ (drm->planes[i].drm_plane->formats[j]),
+ (drm->planes[i].drm_plane->formats[j])>>8,
+ (drm->planes[i].drm_plane->formats[j])>>16,
+ (drm->planes[i].drm_plane->formats[j])>>24);
+ }
+ }
+
/* find the crtc/connector/mode to use */
- for (i = 0; i < drm->resources->count_connectors; i++) {
- drmModeConnectorPtr connector;
+ lvds = fetch_connector(drm, DRM_MODE_CONNECTOR_LVDS);
+ if (lvds) {
+ drm_kms_init_with_connector(drm, &drm->primary, lvds);
+ drmModeFreeConnector(lvds);
+ drm->primary.active = 1;
+ }
- connector = drmModeGetConnector(drm->fd,
- drm->resources->connectors[i]);
- if (connector) {
- if (connector->connection == DRM_MODE_CONNECTED) {
- if (!drm_kms_init_with_connector(drm,
- connector))
- break;
+ /* if still no connector, find first connected connector and try it */
+ if (!drm->primary.active) {
+
+ for (i = 0; i < drm->resources->count_connectors; i++) {
+ drmModeConnectorPtr connector;
+
+ connector = drmModeGetConnector(drm->fd,
+ drm->resources->connectors[i]);
+ if (connector) {
+ if (connector->connection == DRM_MODE_CONNECTED) {
+ if (!drm_kms_init_with_connector(drm,
+ &drm->primary, connector))
+ break;
+ }
+
+ drmModeFreeConnector(connector);
}
+ }
+ if (i == drm->resources->count_connectors) {
+ ALOGE("failed to find a valid crtc/connector/mode combination");
+ drmModeFreeResources(drm->resources);
+ drm->resources = NULL;
- drmModeFreeConnector(connector);
+ return -EINVAL;
}
}
- if (i == drm->resources->count_connectors) {
- LOGE("failed to find a valid crtc/connector/mode combination");
- drmModeFreeResources(drm->resources);
- drm->resources = NULL;
- return -EINVAL;
+ /* check if hdmi is connected already */
+ hdmi = fetch_connector(drm, DRM_MODE_CONNECTOR_HDMIA);
+ if (hdmi) {
+ drm_kms_init_with_connector(drm, &drm->hdmi, hdmi);
+ drmModeFreeConnector(hdmi);
+
+ /* HACK, assume same mode for now */
+ memcpy(&drm->hdmi.mode, &drm->primary.mode,
+ sizeof(drmModeModeInfo));
+
+ drm->hdmi_mode = HDMI_CLONED;
+ drm->hdmi.active = 1;
}
+ /* launch hdmi observer thread */
+ pthread_mutex_init(&drm->hdmi_mutex, NULL);
+ pthread_create(&drm->hdmi_hotplug_thread, NULL, hdmi_observer, drm);
+
drm_kms_init_features(drm);
drm->first_post = 1;
return 0;
}
+void gralloc_drm_fini_kms(struct gralloc_drm_t *drm)
+{
+ switch (drm->swap_mode) {
+ case DRM_SWAP_FLIP:
+ drm_kms_page_flip(drm, NULL);
+ break;
+ case DRM_SWAP_COPY:
+ {
+ struct gralloc_drm_bo_t **bo = (drm->current_front) ?
+ &drm->current_front : &drm->next_front;
+
+ if (*bo)
+ gralloc_drm_bo_decref(*bo);
+ *bo = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* restore crtc? */
+
+ if (drm->resources) {
+ drmModeFreeResources(drm->resources);
+ drm->resources = NULL;
+ }
+
+ if (drm->planes) {
+ unsigned int i;
+ for (i = 0; i < drm->plane_resources->count_planes; i++)
+ drmModeFreePlane(drm->planes[i].drm_plane);
+ free(drm->planes);
+ drm->planes = NULL;
+ }
+
+ if (drm->plane_resources) {
+ drmModeFreePlaneResources(drm->plane_resources);
+ drm->plane_resources = NULL;
+ }
+
+ drm_singleton = NULL;
+}
+
+int gralloc_drm_is_kms_initialized(struct gralloc_drm_t *drm)
+{
+ return (drm->resources != NULL);
+}
+
/*
* Initialize a framebuffer device with KMS info.
*/
struct framebuffer_device_t *fb)
{
*((uint32_t *) &fb->flags) = 0x0;
- *((uint32_t *) &fb->width) = drm->mode.hdisplay;
- *((uint32_t *) &fb->height) = drm->mode.vdisplay;
- *((int *) &fb->stride) = drm->mode.hdisplay;
- *((float *) &fb->fps) = drm->mode.vrefresh;
-
- *((int *) &fb->format) = drm->format;
- *((float *) &fb->xdpi) = drm->xdpi;
- *((float *) &fb->ydpi) = drm->ydpi;
+ *((uint32_t *) &fb->width) = drm->primary.mode.hdisplay;
+ *((uint32_t *) &fb->height) = drm->primary.mode.vdisplay;
+ *((int *) &fb->stride) = drm->primary.mode.hdisplay;
+ *((float *) &fb->fps) = drm->primary.mode.vrefresh;
+
+ *((int *) &fb->format) = drm->primary.fb_format;
+ *((float *) &fb->xdpi) = drm->primary.xdpi;
+ *((float *) &fb->ydpi) = drm->primary.ydpi;
*((int *) &fb->minSwapInterval) = drm->swap_interval;
*((int *) &fb->maxSwapInterval) = drm->swap_interval;
}