From 027b04dc44b82447ca6c2760e67320831fba726a Mon Sep 17 00:00:00 2001 From: Jeffy Chen Date: Wed, 28 Feb 2024 18:05:41 +0800 Subject: [PATCH 11/11] Support multiple monitors Tested on RK3588 EVB with: 1/ FRECON_OUTPUT_CONFIG=fit:270:1280x800,DSI-1=:90: frecon 2/ Connect HDMI Signed-off-by: Jeffy Chen --- drm.c | 703 +++++++++++++++++++++++++++++++++------------------------ drm.h | 66 ++++-- fb.c | 416 ++++++++++++++++++---------------- fb.h | 79 +++---- main.c | 3 +- 5 files changed, 726 insertions(+), 541 deletions(-) diff --git a/drm.c b/drm.c index 471210e..1ba6fd9 100644 --- a/drm.c +++ b/drm.c @@ -16,71 +16,51 @@ #include #include "drm.h" -#include "input.h" #include "util.h" static drm_t* g_drm = NULL; - -static int32_t crtc_planes_num(drm_t* drm, int32_t crtc_index) +drm_t *drm_get() { - drmModePlanePtr plane; - int32_t planes_num = 0; - drmModePlaneResPtr plane_resources = drmModeGetPlaneResources(drm->fd); - - if (!plane_resources) - return 1; /* Just pretend there is one plane. */ - - for (uint32_t p = 0; p < plane_resources->count_planes; p++) { - plane = drmModeGetPlane(drm->fd, plane_resources->planes[p]); - - if (plane->possible_crtcs & (1 << crtc_index)) - planes_num++; - - drmModeFreePlane(plane); - } - drmModeFreePlaneResources(plane_resources); - return planes_num; + return g_drm; } -static bool get_connector_path(drm_t* drm, uint32_t connector_id, uint32_t* ret_encoder_id, uint32_t* ret_crtc_id) +/* Get current CRTC for given connector_id. */ +static bool get_crtc_for_connector(drm_t* drm, uint32_t connector_id, uint32_t* ret_crtc_id) { drmModeConnector* connector = drmModeGetConnector(drm->fd, connector_id); drmModeEncoder* encoder; + uint32_t crtc_id = 0; if (!connector) return false; /* Error. */ - if (ret_encoder_id) - *ret_encoder_id = connector->encoder_id; if (!connector->encoder_id) { drmModeFreeConnector(connector); - if (ret_crtc_id) - *ret_crtc_id = 0; - return true; /* Not connected. */ + goto out; /* Not connected. */ } encoder = drmModeGetEncoder(drm->fd, connector->encoder_id); if (!encoder) { - if (ret_crtc_id) - *ret_crtc_id = 0; drmModeFreeConnector(connector); - return false; /* Error. */ + goto out; /* Error. */ } - if (ret_crtc_id) - *ret_crtc_id = encoder->crtc_id; + crtc_id = encoder->crtc_id; drmModeFreeEncoder(encoder); drmModeFreeConnector(connector); - return true; /* Connected. */ +out: + if (ret_crtc_id) + *ret_crtc_id = crtc_id; + + return !!crtc_id; /* Connected. */ } -/* Find CRTC with most planes for given connector_id. */ +/* Find CRTC for given connector_id. */ static bool find_crtc_for_connector(drm_t* drm, uint32_t connector_id, uint32_t* ret_crtc_id) { int enc; int32_t crtc_id = -1; - int32_t max_crtc_planes = -1; drmModeConnector* connector = drmModeGetConnector(drm->fd, connector_id); if (!connector) @@ -92,16 +72,10 @@ static bool find_crtc_for_connector(drm_t* drm, uint32_t connector_id, uint32_t* if (encoder) { for (crtc = 0; crtc < drm->resources->count_crtcs; crtc++) { - int32_t crtc_planes; - if (!(encoder->possible_crtcs & (1 << crtc))) continue; - crtc_planes = crtc_planes_num(drm, crtc); - if (max_crtc_planes < crtc_planes) { - crtc_id = drm->resources->crtcs[crtc]; - max_crtc_planes = crtc_planes; - } + crtc_id = drm->resources->crtcs[crtc]; } drmModeFreeEncoder(encoder); @@ -150,36 +124,6 @@ static int drm_is_primary_plane(drm_t* drm, uint32_t plane_id) return ret; } -/* Disable all planes except for primary on crtc we use. */ -static void drm_disable_non_primary_planes(drm_t* drm, uint32_t console_crtc_id) -{ - int ret; - - if (!drm->plane_resources) - return; - - for (uint32_t p = 0; p < drm->plane_resources->count_planes; p++) { - drmModePlanePtr plane; - plane = drmModeGetPlane(drm->fd, - drm->plane_resources->planes[p]); - if (plane) { - int primary = drm_is_primary_plane(drm, plane->plane_id); - if (!(plane->crtc_id == console_crtc_id && primary != 0)) { - ret = drmModeSetPlane(drm->fd, plane->plane_id, plane->crtc_id, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0); - if (ret) { - LOG(WARNING, "Unable to disable plane:%d %m", plane->plane_id); - } - } - drmModeFreePlane(plane); - } - } -} - static bool drm_is_internal(unsigned type) { unsigned t; @@ -216,114 +160,330 @@ static drmModeConnector* find_first_connected_connector(drm_t* drm, bool interna return NULL; } -static bool find_main_monitor(drm_t* drm) +static uint32_t find_main_monitor(drm_t* drm) { - int modes; - uint32_t console_crtc_id = 0; - int lid_state = input_check_lid_state(); - drmModeConnector* main_monitor_connector = NULL; - - drm->console_connector_id = 0; + drmModeConnector* connector; + uint32_t connector_id = 0; /* * Find the LVDS/eDP/DSI connectors. Those are the main screens. */ - if (lid_state <= 0) - main_monitor_connector = find_first_connected_connector(drm, true, false); + connector = find_first_connected_connector(drm, true, false); /* * Now try external connectors. */ - if (!main_monitor_connector) - main_monitor_connector = - find_first_connected_connector(drm, false, true); + if (!connector) + connector = find_first_connected_connector(drm, false, true); /* * If we still didn't find a connector, give up and return. */ - if (!main_monitor_connector) + if (!connector) + return 0; + + if (connector->count_modes) + connector_id = connector->connector_id; + + drmModeFreeConnector(connector); + return connector_id; +} + +static bool drm_is_connected(drm_t* drm, uint32_t connector_id) +{ + drmModeConnector* connector = drmModeGetConnector(drm->fd, connector_id); + if (!connector) return false; - if (!main_monitor_connector->count_modes) + if (connector->connection == DRM_MODE_CONNECTED) { + drmModeFreeConnector(connector); + return true; + } + drmModeFreeConnector(connector); + return false; +} + +/* based on weston-13: libweston/backend-drm/drm.c */ +static const char *const 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] = "SVIDEO", + [DRM_MODE_CONNECTOR_LVDS] = "LVDS", + [DRM_MODE_CONNECTOR_Component] = "Component", + [DRM_MODE_CONNECTOR_9PinDIN] = "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", + [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", + [DRM_MODE_CONNECTOR_DSI] = "DSI", + [DRM_MODE_CONNECTOR_DPI] = "DPI", +}; + +static char *drm_make_name(drm_t *drm, uint32_t connector_id) +{ + drmModeConnector *connector; + char *name; + const char *type_name = NULL; + int ret; + + connector = drmModeGetConnector(drm->fd, connector_id); + if (!connector) + return NULL; + + if (connector->connector_type < ARRAY_SIZE(connector_type_names)) + type_name = connector_type_names[connector->connector_type]; + + if (!type_name) + type_name = "UNNAMED"; + + ret = asprintf(&name, "%s-%d", type_name, connector->connector_type_id); + if (ret < 0) + name = NULL; + + drmModeFreeConnector(connector); + return name; +} + +static bool drm_get_mode(drm_t* drm, drm_output_t *output) +{ + drmModeConnector *connector; + drmModeModeInfo *preferred = NULL, *mode; + int i; + + connector = drmModeGetConnector(drm->fd, output->connector_id); + if (!connector) return false; - drm->console_connector_id = main_monitor_connector->connector_id; + if (connector->connection != DRM_MODE_CONNECTED || + !connector->count_modes) + goto err; - for (modes = 0; modes < main_monitor_connector->count_modes; modes++) { - if (main_monitor_connector->modes[modes].type & - DRM_MODE_TYPE_PREFERRED) { - drm->console_mode_info = main_monitor_connector->modes[modes]; + for (i = 0; i < connector->count_modes; i++) { + mode = &connector->modes[i]; + + if (!preferred && mode->type & DRM_MODE_TYPE_PREFERRED) + preferred = mode; + + if (mode->hdisplay == output->preferred_width && + mode->vdisplay == output->preferred_height) { + preferred = mode; break; } } - /* If there was no preferred mode use first one. */ - if (modes == main_monitor_connector->count_modes) - drm->console_mode_info = main_monitor_connector->modes[0]; - - drmModeFreeConnector(main_monitor_connector); - get_connector_path(drm, drm->console_connector_id, NULL, &console_crtc_id); + if (preferred) + output->mode = *preferred; + else + output->mode = connector->modes[connector->count_modes - 1]; - if (!console_crtc_id) - /* No existing path, find one. */ - find_crtc_for_connector(drm, drm->console_connector_id, &console_crtc_id); + drmModeFreeConnector(connector); + return true; +err: + drmModeFreeConnector(connector); + return false; +} - if (!console_crtc_id) - /* Cannot find CRTC for connector. We will not be able to use it. */ +static bool drm_remove_output(drm_t* drm, uint32_t connector_id) +{ + if (!connector_id) return false; - return true; + drm_for_each_output(drm, output) { + if (output->connector_id != connector_id) + continue; + + LOG(INFO, "Removing output(%s)", output->name); + + drmModeSetCrtc(drm->fd, output->crtc_id, -1, 0, 0, + NULL, -1, NULL); + + free(output->name); + + /* Hacky way to remove entry */ + do { + memcpy(output, output + 1, sizeof(*output)); + output ++; + } while (output->connector_id); + + drm->count_outputs --; + return true; + } + + return false; } -static void drm_clear_rmfb(drm_t* drm) +static bool drm_add_output(drm_t* drm, uint32_t connector_id) { - if (drm->delayed_rmfb_fb_id) { - drmModeRmFB(drm->fd, drm->delayed_rmfb_fb_id); - drm->delayed_rmfb_fb_id = 0; + drmModePlaneResPtr pres; + drm_output_t *output; + uint32_t crtc_pipe = 0, count_primaries = 0; + const char *env; + int i; + + if (!connector_id) + return false; + + drm_remove_output(drm, connector_id); + + if (drm->count_outputs >= MAX_OUTPUTS) + return false; + + if (!drm_is_connected(drm, connector_id)) + return false; + + output = &drm->outputs[drm->count_outputs]; + output->connector_id = connector_id; + + output->name = drm_make_name(drm, connector_id); + if (!output->name) + goto err; + + if (!drm_get_mode(drm, output)) + goto err; + + if (!get_crtc_for_connector(drm, connector_id, &output->crtc_id) && + !find_crtc_for_connector(drm, connector_id, &output->crtc_id)) + goto err; + + for (i = 0; i < drm->resources->count_crtcs; i++) { + if (drm->resources->crtcs[i] == output->crtc_id) { + crtc_pipe = i; + break; + } } + + pres = drmModeGetPlaneResources(drm->fd); + if (!pres) + goto err; + + for (i = 0; i < (int)pres->count_planes; i++) { + drmModePlanePtr plane; + + if (!drm_is_primary_plane(drm, pres->planes[i])) + continue; + + count_primaries ++; + + if ((count_primaries - 1) != crtc_pipe) + continue; + + plane = drmModeGetPlane(drm->fd, pres->planes[i]); + if (plane) { + if (plane->possible_crtcs & 1 << crtc_pipe) + output->plane_id = plane->plane_id; + drmModeFreePlane(plane); + } + break; + } + + drmModeFreePlaneResources(pres); + + if (!output->plane_id) + goto err; + + /* For example: FRECON_OUTPUT_CONFIG="fit:0,DSI-1=stretch:90:1920x1080" */ + if ((env = getenv("FRECON_OUTPUT_CONFIG"))) { + char fill[64] = "\0"; + int rotation = 0; + + if (strstr(env, output->name)) + env = strstr(env, output->name) + + strlen(output->name) + 1; + + output->preferred_width = output->preferred_height = 0; + sscanf(env, "%[a-z]", fill); + sscanf(env, "%*[^0-9,]%d", &rotation); + sscanf(env, "%*[^0-9,]%*d:%dx%d", + &output->preferred_width, &output->preferred_height); + + if (!strcmp(fill, "fit")) + output->fill = DRM_FILL_FIT; + else if (!strcmp(fill, "stretch")) + output->fill = DRM_FILL_STRETCH; + else if (!strcmp(fill, "none")) + output->fill = DRM_FILL_NONE; + + if (rotation == 90) + output->rotation = DRM_ROTATION_90; + else if (rotation == 180) + output->rotation = DRM_ROTATION_180; + else if (rotation == 270) + output->rotation = DRM_ROTATION_270; + else + output->rotation = DRM_ROTATION_0; + } else if ((env = getenv("FRECON_FB_ROTATE"))) { + int rotation = atoi(env); + + if (rotation == 90) + output->rotation = DRM_ROTATION_90; + else if (rotation == 180) + output->rotation = DRM_ROTATION_180; + else if (rotation == 270) + output->rotation = DRM_ROTATION_270; + else + output->rotation = DRM_ROTATION_0; + } + + drm->count_outputs ++; + + LOG(INFO, "Added output(%s): fill=%s, rotate=%d, %dx%d", output->name, + (output->fill == DRM_FILL_FIT) ? "fit" : + (output->fill == DRM_FILL_STRETCH ? "stretch" : "none"), + output->rotation * 90, + output->preferred_width, output->preferred_height); + + return true; +err: + if (output->name) + free(output->name); + + memset(output, 0, sizeof(*output)); + return false; } static void drm_fini(drm_t* drm) { + drm_output_t *output; + if (!drm) return; - if (drm->fd >= 0) { - drm_clear_rmfb(drm); + if (drm->fd < 0) { + free(drm); + return; + } - if (drm->plane_resources) { - drmModeFreePlaneResources(drm->plane_resources); - drm->plane_resources = NULL; - } + output = &drm->outputs[0]; + while (output->connector_id) + drm_remove_output(drm, output->connector_id); - if (drm->resources) { - drmModeFreeResources(drm->resources); - drm->resources = NULL; - } + if (drm->resources) + drmModeFreeResources(drm->resources); - drmClose(drm->fd); - drm->fd = -1; - } + drmClose(drm->fd); free(drm); } -static bool drm_equal(drm_t* l, drm_t* r) +void drm_close(void) { - if (!l && !r) - return true; - if ((!l && r) || (l && !r)) - return false; + if (!g_drm) + return; - if (l->console_connector_id != r->console_connector_id) - return false; - return true; + drm_fini(g_drm); + g_drm = NULL; } /* * Find DRM object to display frecon on. */ -drm_t* drm_scan(void) +static drm_t* drm_scan(void) { int ret; uint64_t atomic = 0; @@ -364,15 +524,6 @@ drm_t* drm_scan(void) return NULL; } - drm->plane_resources = drmModeGetPlaneResources(drm->fd); - - if (!find_main_monitor(drm)) { - drm_fini(drm); - return NULL; - } - - drm->refcount = 1; - version = drmGetVersion(drm->fd); if (version) { LOG(INFO, @@ -388,198 +539,166 @@ drm_t* drm_scan(void) return drm; } -void drm_set(drm_t* drm_) +bool drm_init(void) { - if (g_drm) { - drm_delref(g_drm); - g_drm = NULL; - } - g_drm = drm_; -} + drm_t *drm; + uint32_t connector_id; + const char *env; + int i; -void drm_close(void) -{ - if (g_drm) { - drm_delref(g_drm); - g_drm = NULL; - } -} + drm_close(); -void drm_delref(drm_t* drm) -{ + drm = drm_scan(); if (!drm) - return; - if (drm->refcount) { - drm->refcount--; - } else { - LOG(ERROR, "Imbalanced drm_close()"); - } - if (drm->refcount) { - return; - } - - drm_fini(drm); -} + return false; -drm_t* drm_addref(void) -{ - if (g_drm) { - g_drm->refcount++; - return g_drm; + env = getenv("FRECON_SINGLE_HEAD"); + if (env && !strcmp(env, "1")) { + LOG(INFO, "Single head mode."); + drm->single_head = true; } - return NULL; -} - -/* - * Returns true if connector/crtc/driver have changed and framebuffer object have to be re-created. - */ -bool drm_rescan(void) -{ - drm_t* ndrm; - - ndrm = drm_scan(); - if (ndrm) { - if (drm_equal(ndrm, g_drm)) { - drm_fini(ndrm); - } else { - drm_delref(g_drm); - g_drm = ndrm; - return true; - } + if (drm->single_head) { + connector_id = find_main_monitor(drm); + drm_add_output(drm, connector_id); } else { - if (g_drm) { - drm_delref(g_drm); /* No usable monitor/drm object. */ - g_drm = NULL; - return true; + for (i = 0; i < drm->resources->count_connectors; i++) { + connector_id = drm->resources->connectors[i]; + drm_add_output(drm, connector_id); } } - return false; -} -bool drm_valid(drm_t* drm) { - return drm && drm->fd >= 0 && drm->resources && drm->console_connector_id; -} + LOG(INFO, "Inited with %d outputs", drm->count_outputs); -static int remove_gamma_properties(drm_t* drm, uint32_t crtc_id) { - drmModeObjectPropertiesPtr crtc_props = NULL; + g_drm = drm; + return true; +} - crtc_props = drmModeObjectGetProperties(drm->fd, - crtc_id, - DRM_MODE_OBJECT_CRTC); - if (!crtc_props) { - LOG(ERROR, "Could not query properties for crtc %d %m.", crtc_id); - return -ENOENT; - } +/* Return true if we already has it and will keep it */ +static bool drm_got_output(drm_t *drm, uint32_t connector_id) +{ + drm_for_each_output(drm, output) { + drmModeModeInfo mode = output->mode; - for (uint32_t i = 0; i < crtc_props->count_props; i++) { - drmModePropertyPtr prop; - prop = drmModeGetProperty(drm->fd, crtc_props->props[i]); - if (!prop) + if (output->connector_id != connector_id) continue; - // Remove the GAMMA_LUT and DEGAMMA_LUT properties. - if (!strcmp(prop->name, "GAMMA_LUT") || - !strcmp(prop->name, "DEGAMMA_LUT")) { - // Ignore the return in case it is not supported. - if (drmModeObjectSetProperty(drm->fd, crtc_id, - DRM_MODE_OBJECT_CRTC, - crtc_props->props[i], - 0)) { - LOG(ERROR, "Unable to remove %s from crtc:%d %m", prop->name, crtc_id); - } + /* Check connected and the preferred mode is unchanged */ + if (drm_is_connected(drm, connector_id)) { + if (drm_get_mode(drm, output) && + !strcmp(mode.name, output->mode.name)) + return true; } - drmModeFreeProperty(prop); + + output->mode = mode; + break; } - drmModeFreeObjectProperties(crtc_props); - return 0; -} + return false; +} -int32_t drm_setmode(drm_t* drm, uint32_t fb_id) +/* + * Returns true if connector/crtc/driver have changed and framebuffer object have to be re-created. + */ +bool drm_rescan(void) { - int conn; - int32_t ret = 0; - uint32_t existing_console_crtc_id = 0; + drm_t* drm = g_drm; + uint32_t connector_id; + bool changed; + int i; - get_connector_path(drm, drm->console_connector_id, NULL, &existing_console_crtc_id); + drmModeFreeResources(drm->resources); - /* Loop through all the connectors, disable ones that are configured and set video mode on console connector. */ - for (conn = 0; conn < drm->resources->count_connectors; conn++) { - uint32_t connector_id = drm->resources->connectors[conn]; + drm->resources = drmModeGetResources(drm->fd); + if (!drm->resources) + goto err; - if (connector_id == drm->console_connector_id) { - uint32_t console_crtc_id = 0; + if (drm->single_head) { + connector_id = find_main_monitor(drm); + if (drm_got_output(drm, connector_id)) + goto out; - if (existing_console_crtc_id) - console_crtc_id = existing_console_crtc_id; - else { - find_crtc_for_connector(drm, connector_id, &console_crtc_id); + if (drm_remove_output(drm, drm->outputs[0].connector_id)) + changed = true; - if (!console_crtc_id) { - LOG(ERROR, "Could not get console crtc for connector:%d in modeset.\n", drm->console_connector_id); - return -ENOENT; - } - } + if (drm_add_output(drm, connector_id)) + changed = true; - ret = drmModeSetCrtc(drm->fd, console_crtc_id, - fb_id, - 0, 0, // x,y - &drm->console_connector_id, - 1, // connector_count - &drm->console_mode_info); // mode + goto out; + } - if (ret) { - LOG(ERROR, "Unable to set crtc:%d connector:%d %m", console_crtc_id, drm->console_connector_id); - return ret; - } + for (i = 0; i < drm->resources->count_connectors; i++) { + connector_id = drm->resources->connectors[i]; + + if (!drm_is_connected(drm, connector_id)) { + if (drm_remove_output(drm, connector_id)) + changed = true; + continue; + } + + if (drm_got_output(drm, connector_id)) + continue; - ret = drmModeSetCursor(drm->fd, console_crtc_id, - 0, 0, 0); + drm_add_output(drm, connector_id); + changed = true; + } +out: + if (changed) + LOG(INFO, "Updated with %d outputs", drm->count_outputs); - if (ret) - LOG(ERROR, "Unable to hide cursor on crtc:%d %m.", console_crtc_id); + return changed; +err: + drm_fini(drm); + g_drm = NULL; + return true; +} - ret = remove_gamma_properties(drm, console_crtc_id); - if (ret) - LOG(ERROR, "Unable to remove gamma LUT properties from crtc:%d %m.", console_crtc_id); +int32_t drm_setmode(drm_t *drm, drm_output_t *output, uint32_t fb_id, + uint32_t src_w, uint32_t src_h) +{ + uint32_t dst_x, dst_y, dst_w, dst_h; + int32_t ret; - drm_disable_non_primary_planes(drm, console_crtc_id); + if (!output) + return 0; - } else { - uint32_t crtc_id = 0; + /* Make sure that all planes and cursor are disabled on the CRTC */ + drmModeSetCrtc(drm->fd, output->crtc_id, 0, 0, 0, NULL, 0, NULL); - get_connector_path(drm, connector_id, NULL, &crtc_id); - if (!crtc_id) - /* This connector is not configured, skip. */ - continue; + if (!fb_id) + return 0; - if (existing_console_crtc_id && existing_console_crtc_id == crtc_id) - /* This connector is mirroring from the same CRTC as console. It will be turned off when console is set. */ - continue; + LOG(INFO, "Displaying on output(%s)", output->name); - ret = drmModeSetCrtc(drm->fd, crtc_id, 0, // buffer_id - 0, 0, // x,y - NULL, // connectors - 0, // connector_count - NULL); // mode - if (ret) - LOG(ERROR, "Unable to disable crtc %d: %m", crtc_id); - } + ret = drmModeSetCrtc(drm->fd, output->crtc_id, fb_id, 0, 0, + &output->connector_id, 1, &output->mode); + if (ret) { + LOG(ERROR, "Unable to set crtc:%d connector:%d %m", + output->crtc_id, output->connector_id); + return ret; } - drm_clear_rmfb(drm); - /* LOG(INFO, "TIMING: Console switch modeset finished."); */ - return ret; -} + dst_x = dst_y = 0; + dst_w = output->mode.hdisplay; + dst_h = output->mode.vdisplay; + + switch(output->fill) { + case DRM_FILL_NONE: + dst_w = src_w; + dst_h = src_h; + break; + case DRM_FILL_FIT: + if (dst_w * src_h > src_w * dst_h) + dst_w = src_w * dst_h / src_h; + else + dst_h = dst_w * src_h / src_w; + break; + case DRM_FILL_STRETCH: + default: + break; + } -/* - * Delayed rmfb(). We want to keep fb at least till after next modeset - * so our transitions are cleaner (e.g. when recreating term after exitin - * shell). Also it keeps fb around till Chrome starts. - */ -void drm_rmfb(drm_t* drm, uint32_t fb_id) -{ - drm_clear_rmfb(drm); - drm->delayed_rmfb_fb_id = fb_id; + return drmModeSetPlane(drm->fd, output->plane_id, output->crtc_id, + fb_id, 0, dst_x, dst_y, dst_w, dst_h, + 0, 0, src_w << 16, src_h << 16); } diff --git a/drm.h b/drm.h index ff42efc..4afa343 100644 --- a/drm.h +++ b/drm.h @@ -7,30 +7,70 @@ #ifndef DRM_H #define DRM_H - #include #include #include #include +#define MAX_OUTPUTS 16 + +typedef enum { + DRM_FILL_NONE, + DRM_FILL_FIT, + DRM_FILL_STRETCH, +} drm_fill_t; + +typedef enum { + DRM_ROTATION_0, + DRM_ROTATION_90, + DRM_ROTATION_180, + DRM_ROTATION_270, + DRM_ROTATION_MAX, +} drm_rotation_t; + +typedef struct _output_t { + char *name; + uint32_t connector_id; + + uint32_t crtc_id; + uint32_t plane_id; + drmModeModeInfo mode; + + drm_fill_t fill; + drm_rotation_t rotation; + + uint32_t preferred_width; + uint32_t preferred_height; +} drm_output_t; + typedef struct _drm_t { - int refcount; int fd; drmModeRes* resources; - drmModePlaneResPtr plane_resources; - uint32_t console_connector_id; - drmModeModeInfo console_mode_info; - uint32_t delayed_rmfb_fb_id; + + bool single_head; + + drm_output_t outputs[MAX_OUTPUTS]; + uint32_t count_outputs; } drm_t; -drm_t* drm_scan(void); -void drm_set(drm_t* drm); +#define drm_output_width(output) \ + (((output)->rotation == DRM_ROTATION_0 || \ + (output)->rotation == DRM_ROTATION_180) ? \ + (output)->mode.hdisplay : (output)->mode.vdisplay) + +#define drm_output_height(output) \ + (((output)->rotation == DRM_ROTATION_0 || \ + (output)->rotation == DRM_ROTATION_180) ? \ + (output)->mode.vdisplay : (output)->mode.hdisplay) + +#define drm_for_each_output(drm, output) \ + for (drm_output_t *output = &(drm)->outputs[0]; output->name; output++) + +bool drm_init(void); void drm_close(void); -drm_t* drm_addref(void); -void drm_delref(drm_t* drm); +drm_t* drm_get(void); bool drm_rescan(void); -bool drm_valid(drm_t* drm); -int32_t drm_setmode(drm_t* drm, uint32_t fb_id); -void drm_rmfb(drm_t* drm, uint32_t fb_id); +int32_t drm_setmode(drm_t *drm, drm_output_t *output, uint32_t fb_id, + uint32_t src_w, uint32_t src_h); #endif diff --git a/fb.c b/fb.c index 71de475..5d40795 100644 --- a/fb.c +++ b/fb.c @@ -19,18 +19,73 @@ #include "util.h" #include "fb.h" -static int fb_buffer_create(fb_t* fb, - int* pitch) +static bool fb_buf_lock(fb_t *fb, fb_buf_t *buf) +{ + if (buf->lock.count == 0 && buf->handle > 0) { + buf->lock.map = + mmap(0, buf->size, PROT_READ | PROT_WRITE, + MAP_SHARED, fb->drm->fd, buf->lock.map_offset); + if (buf->lock.map == MAP_FAILED) { + LOG(ERROR, "mmap failed"); + return false; + } + } + + if (buf->lock.map) + buf->lock.count++; + + return true; +} + +static void fb_buf_unlock(fb_t *fb, fb_buf_t *buf) +{ + if (buf->lock.count > 0) + buf->lock.count--; + else + LOG(ERROR, "buf locking unbalanced"); + + if (buf->lock.count == 0 && buf->handle > 0) { + int32_t ret; + struct drm_clip_rect clip_rect = { + 0, 0, buf->width, buf->height + }; + munmap(buf->lock.map, buf->size); + ret = drmModeDirtyFB(fb->drm->fd, buf->fb_id, &clip_rect, 1); + if (ret) { + int loglevel = ERROR; + /* Do not print "normal" errors by default. */ + if (errno == ENOSYS || errno == EACCES) + loglevel = DEBUG; + LOG(loglevel, "drmModeDirtyFB failed: %d %m", errno); + } + } +} + +static int fb_buffer_ensure(fb_t *fb, drm_output_t *output) { struct drm_mode_create_dumb create_dumb; struct drm_mode_destroy_dumb destroy_dumb; - uint32_t* fb_buffer; + fb_buf_t *buf; + uint32_t fb_id; int ret; + fb_for_each_buf(fb, tmp) + if (tmp->rotation == output->rotation) + return 0; + + buf = &fb->bufs[fb->count_bufs]; + memset(&create_dumb, 0, sizeof (create_dumb)); create_dumb.bpp = 32; - create_dumb.width = fb->drm->console_mode_info.hdisplay; - create_dumb.height = fb->drm->console_mode_info.vdisplay; + + drm_for_each_output(fb->drm, tmp) { + if (tmp->rotation != output->rotation) + continue; + + create_dumb.width = MAX(create_dumb.width, tmp->mode.hdisplay); + create_dumb.height = MAX(create_dumb.height, + tmp->mode.vdisplay); + } ret = drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); if (ret) { @@ -38,10 +93,7 @@ static int fb_buffer_create(fb_t* fb, return ret; } - fb->buffer_properties.size = create_dumb.size; - fb->buffer_handle = create_dumb.handle; - - struct drm_mode_map_dumb map_dumb; + struct drm_mode_map_dumb map_dumb = {0}; map_dumb.handle = create_dumb.handle; ret = drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); if (ret) { @@ -49,30 +101,91 @@ static int fb_buffer_create(fb_t* fb, goto destroy_buffer; } - fb->lock.map_offset = map_dumb.offset; - uint32_t handles[4] = {0}; uint32_t pitches[4] = {0}; uint32_t offsets[4] = {0}; handles[0] = create_dumb.handle; pitches[0] = create_dumb.pitch; - ret = drmModeAddFB2(fb->drm->fd, fb->drm->console_mode_info.hdisplay, fb->drm->console_mode_info.vdisplay, + ret = drmModeAddFB2(fb->drm->fd, create_dumb.width, create_dumb.height, DRM_FORMAT_XRGB8888, handles, - pitches, offsets, &fb->fb_id, 0); + pitches, offsets, &fb_id, 0); if (ret) { LOG(ERROR, "drmModeAddFB2 failed"); goto destroy_buffer; } - *pitch = create_dumb.pitch; + buf->rotation = output->rotation; + buf->width = create_dumb.width; + buf->height = create_dumb.height; + buf->pitch = create_dumb.pitch; + buf->size = create_dumb.size; + buf->handle = create_dumb.handle; + buf->fb_id = fb_id; + buf->lock.map_offset = map_dumb.offset; + + switch (buf->rotation) { + case DRM_ROTATION_90: + buf->src_width = fb->height; + buf->src_height = fb->width; + + buf->m[0][0] = 0; + buf->m[0][1] = -1; + buf->m[0][2] = buf->src_width - 1; + + buf->m[1][0] = 1; + buf->m[1][1] = 0; + buf->m[1][2] = 0; + break; + case DRM_ROTATION_270: + buf->src_width = fb->height; + buf->src_height = fb->width; + + buf->m[0][0] = 0; + buf->m[0][1] = 1; + buf->m[0][2] = 0; + + buf->m[1][0] = -1; + buf->m[1][1] = 0; + buf->m[1][2] = buf->src_height - 1; + break; + case DRM_ROTATION_180: + buf->src_width = fb->width; + buf->src_height = fb->height; + + buf->m[0][0] = -1; + buf->m[0][1] = 0; + buf->m[0][2] = buf->src_width - 1; + + buf->m[1][0] = 0; + buf->m[1][1] = -1; + buf->m[1][2] = buf->src_height - 1; + break; + case DRM_ROTATION_0: + default: + buf->src_width = fb->width; + buf->src_height = fb->height; + + buf->m[0][0] = 1; + buf->m[0][1] = 0; + buf->m[0][2] = 0; - fb_buffer = fb_lock(fb); - if (fb_buffer) { - memset(fb_buffer, 0, fb->buffer_properties.size); - fb_unlock(fb); + buf->m[1][0] = 0; + buf->m[1][1] = 1; + buf->m[1][2] = 0; + break; } + fb->count_bufs ++; + + LOG(INFO, "Created FB buf: %dx%d(%dx%d) for rotate=%d", + buf->width, buf->height, buf->src_width, buf->src_height, + buf->rotation * 90); + + if (fb_buf_lock(fb, buf)) { + memset(buf->lock.map, 0, buf->size); + fb_buf_unlock(fb, buf); + } return 0; destroy_buffer: @@ -87,84 +200,59 @@ void fb_buffer_destroy(fb_t* fb) { struct drm_mode_destroy_dumb destroy_dumb; - if (fb->buffer_handle <= 0) - goto unref_drm; + fb_for_each_buf(fb, buf) { + if (buf->lock.count) { + munmap(buf->lock.map, buf->size); + buf->lock.map = NULL; + buf->lock.count = 0; + } - drm_rmfb(fb->drm, fb->fb_id); - fb->fb_id = 0; - destroy_dumb.handle = fb->buffer_handle; - drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); - fb->buffer_handle = 0; - fb->lock.map = NULL; - fb->lock.count = 0; -unref_drm: - if (fb->drm) { - drm_delref(fb->drm); - fb->drm = NULL; + LOG(INFO, "Destroying FB buf: %dx%d for rotate=%d", + buf->width, buf->height, buf->rotation * 90); + + drmModeRmFB(fb->drm->fd, buf->fb_id); + buf->fb_id = 0; + + destroy_dumb.handle = buf->handle; + drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, + &destroy_dumb); + buf->handle = 0; } + + fb->count_bufs = 0; } int fb_buffer_init(fb_t* fb) { - int32_t width, height, pitch = 0; const char *buf; int r; - /* reuse the buffer_properties if it was set before */ - if (!fb->buffer_properties.width || !fb->buffer_properties.height || - !fb->buffer_properties.pitch || !fb->buffer_properties.scaling || - !fb->buffer_properties.rotation) { - /* some reasonable defaults */ - fb->buffer_properties.width = 640; - fb->buffer_properties.height = 480; - fb->buffer_properties.pitch = 640 * 4; - fb->buffer_properties.scaling = 1; - fb->buffer_properties.rotation = DRM_MODE_ROTATE_0; - } - - fb->drm = drm_addref(); - - if (!fb->drm) { - LOG(WARNING, "No monitor available, running headless!"); + fb->drm = drm_get(); + if (!fb->drm) return -ENODEV; - } - - width = fb->drm->console_mode_info.hdisplay; - height = fb->drm->console_mode_info.vdisplay; - r = fb_buffer_create(fb, &pitch); - if (r < 0) { - LOG(ERROR, "fb_buffer_create failed"); - return r; + fb->width = fb->height = INT32_MAX; + drm_for_each_output(fb->drm, output) { + fb->width = MIN(fb->width, drm_output_width(output)); + fb->height = MIN(fb->height, drm_output_height(output)); } - fb->buffer_properties.width = width; - fb->buffer_properties.height = height; - fb->buffer_properties.pitch = pitch; + LOG(INFO, "Initing FB: %dx%d", fb->width, fb->height); - buf = getenv("FRECON_FB_ROTATE"); - if (buf) { - switch (atoi(buf)) { - case 90: - fb->buffer_properties.rotation = DRM_MODE_ROTATE_90; - break; - case 180: - fb->buffer_properties.rotation = DRM_MODE_ROTATE_180; - break; - case 270: - fb->buffer_properties.rotation = DRM_MODE_ROTATE_270; - break; - default: - fb->buffer_properties.rotation = DRM_MODE_ROTATE_0; - break; + drm_for_each_output(fb->drm, output) { + r = fb_buffer_ensure(fb, output); + if (r < 0) { + LOG(ERROR, "failed to create buffer for %s", + output->name); + return r; } } buf = getenv("FRECON_FB_SCALE"); if (buf) { - fb->buffer_properties.scaling = atoi(buf); - if (fb->buffer_properties.scaling < 1) - fb->buffer_properties.scaling = 1; + fb->scaling = atoi(buf); + if (fb->scaling < 1) + fb->scaling = 1; } return 0; @@ -195,90 +283,63 @@ void fb_close(fb_t* fb) int32_t fb_setmode(fb_t* fb) { - /* headless mode */ - if (!drm_valid(fb->drm)) + if (!fb->drm) return 0; - return drm_setmode(fb->drm, fb->fb_id); -} - -uint32_t* fb_lock(fb_t* fb) -{ - if (fb->lock.count == 0 && fb->buffer_handle > 0) { - fb->lock.map = - mmap(0, fb->buffer_properties.size, PROT_READ | PROT_WRITE, - MAP_SHARED, fb->drm->fd, fb->lock.map_offset); - if (fb->lock.map == MAP_FAILED) { - LOG(ERROR, "mmap failed"); - return NULL; + drm_for_each_output(fb->drm, output) { + fb_for_each_buf(fb, buf) { + if (buf->rotation == output->rotation) { + int32_t r = drm_setmode(fb->drm, output, + buf->fb_id, + buf->src_width, + buf->src_height); + if (r < 0) + LOG(ERROR, "Failed to set mode for %s", + output->name); + break; + } } } - if (fb->lock.map) - fb->lock.count++; - - return fb->lock.map; + return 0; } -void fb_unlock(fb_t* fb) -{ - if (fb->lock.count > 0) - fb->lock.count--; - else - LOG(ERROR, "fb locking unbalanced"); +bool fb_lock(fb_t* fb) { + fb_for_each_buf(fb, buf) { + if (fb_buf_lock(fb, buf)) + continue; - if (fb->lock.count == 0 && fb->buffer_handle > 0) { - int32_t ret; - struct drm_clip_rect clip_rect = { - 0, 0, fb->buffer_properties.width, fb->buffer_properties.height - }; - munmap(fb->lock.map, fb->buffer_properties.size); - ret = drmModeDirtyFB(fb->drm->fd, fb->fb_id, &clip_rect, 1); - if (ret) { - int loglevel = ERROR; - /* Do not print "normal" errors by default. */ - if (errno == ENOSYS || errno == EACCES) - loglevel = DEBUG; - LOG(loglevel, "drmModeDirtyFB failed: %d %m", errno); + fb_for_each_buf(fb, tmp) { + if (tmp == buf) + break; + fb_buf_unlock(fb, tmp); } + return false; } + return true; +} + +void fb_unlock(fb_t* fb) { + fb_for_each_buf(fb, buf) + fb_buf_unlock(fb, buf); } int32_t fb_getwidth(fb_t* fb) { - switch (fb->buffer_properties.rotation) { - case DRM_MODE_ROTATE_90: - case DRM_MODE_ROTATE_270: - return fb->buffer_properties.height; - break; - case DRM_MODE_ROTATE_0: - case DRM_MODE_ROTATE_180: - default: - return fb->buffer_properties.width; - } + return fb->width; } int32_t fb_getheight(fb_t* fb) { - switch (fb->buffer_properties.rotation) { - case DRM_MODE_ROTATE_90: - case DRM_MODE_ROTATE_270: - return fb->buffer_properties.width; - break; - case DRM_MODE_ROTATE_0: - case DRM_MODE_ROTATE_180: - default: - return fb->buffer_properties.height; - } + return fb->height; } int32_t fb_getscaling(fb_t* fb) { - return fb->buffer_properties.scaling; + return fb->scaling; } -bool -fb_stepper_init(fb_stepper_t *s, fb_t *fb, int32_t x, int32_t y, uint32_t width, uint32_t height) +bool fb_stepper_init(fb_stepper_t *s, fb_t *fb, int32_t x, int32_t y, uint32_t width, uint32_t height) { s->fb = fb; s->start_x = x; @@ -287,67 +348,44 @@ fb_stepper_init(fb_stepper_t *s, fb_t *fb, int32_t x, int32_t y, uint32_t width, s->h = height; s->x = 0; s->y = 0; - s->pitch_div_4 = s->fb->buffer_properties.pitch >> 2; /* quick check if whole rect is outside fb */ if (x + width <= 0 || y + height <= 0) return false; - switch (s->fb->buffer_properties.rotation) { - case DRM_MODE_ROTATE_90: - case DRM_MODE_ROTATE_270: - s->max_x = s->fb->buffer_properties.height; - s->max_y = s->fb->buffer_properties.width; - break; - case DRM_MODE_ROTATE_180: - case DRM_MODE_ROTATE_0: - default: - s->max_x = s->fb->buffer_properties.width; - s->max_y = s->fb->buffer_properties.height; - } - - if (x >= s->max_x - || y >= s->max_y) + s->max_x = s->fb->width; + s->max_y = s->fb->height; + if (x >= s->max_x || y >= s->max_y) return false; - switch (s->fb->buffer_properties.rotation) { - case DRM_MODE_ROTATE_90: - s->m[0][0] = 0; - s->m[0][1] = -1; - s->m[0][2] = s->fb->buffer_properties.width - 1; - - s->m[1][0] = 1; - s->m[1][1] = 0; - s->m[1][2] = 0; - break; - case DRM_MODE_ROTATE_270: - s->m[0][0] = 0; - s->m[0][1] = 1; - s->m[0][2] = 0; - - s->m[1][0] = -1; - s->m[1][1] = 0; - s->m[1][2] = s->fb->buffer_properties.height - 1; - break; - case DRM_MODE_ROTATE_180: - s->m[0][0] = -1; - s->m[0][1] = 0; - s->m[0][2] = s->fb->buffer_properties.width - 1; - - s->m[1][0] = 0; - s->m[1][1] = -1; - s->m[1][2] = s->fb->buffer_properties.height - 1; - break; - case DRM_MODE_ROTATE_0: - default: - s->m[0][0] = 1; - s->m[0][1] = 0; - s->m[0][2] = 0; + return true; +} - s->m[1][0] = 0; - s->m[1][1] = 1; - s->m[1][2] = 0; +bool fb_stepper_step_x(fb_stepper_t *s, uint32_t rgba) +{ + int32_t x = s->start_x + s->x; + int32_t y = s->start_y + s->y; + int32_t p; + + if (x >= 0 && x < s->max_x && y >= 0 && y < s->max_y) { + fb_for_each_buf(s->fb, buf) { + p = (x * buf->m[0][0] + y * buf->m[0][1] + buf->m[0][2]) + + (x * buf->m[1][0] + y * buf->m[1][1] + + buf->m[1][2]) * buf->pitch / 4; + buf->lock.map[p] = rgba; + } } + s->x++; + if (s->x >= s->w) { + s->x = 0; + return false; + } return true; } + +bool fb_stepper_step_y(fb_stepper_t *s) +{ + s->y++; + return s->y < s->h; +} diff --git a/fb.h b/fb.h index 8c67e0f..8596e87 100644 --- a/fb.h +++ b/fb.h @@ -9,15 +9,6 @@ #include "drm.h" -typedef struct { - int32_t width; - int32_t height; - int32_t pitch; - int32_t scaling; - int32_t size; - int32_t rotation; // DRM_MODE_ROTATE_* -} buffer_properties_t; - typedef struct { int32_t count; uint64_t map_offset; @@ -25,60 +16,58 @@ typedef struct { } fb_lock_t; typedef struct { - drm_t *drm; - buffer_properties_t buffer_properties; - fb_lock_t lock; - uint32_t buffer_handle; + uint32_t handle; + + uint32_t width; + uint32_t height; + uint32_t pitch; + uint32_t size; + + uint32_t src_width; + uint32_t src_height; + uint32_t fb_id; + + int32_t m[2][3]; + + drm_rotation_t rotation; + fb_lock_t lock; +} fb_buf_t; + +typedef struct { + drm_t *drm; + + int32_t width; + int32_t height; + + int32_t scaling; + + fb_buf_t bufs[DRM_ROTATION_MAX]; + uint32_t count_bufs; } fb_t; typedef struct { fb_t *fb; int32_t start_x, start_y; - uint32_t w, h; - uint32_t x, y; + int32_t x, y, w, h; int32_t max_x, max_y; - uint32_t pitch_div_4; - int32_t m[2][3]; } fb_stepper_t; +#define fb_for_each_buf(fb, buf) \ + for (fb_buf_t *buf = &(fb)->bufs[0]; buf->handle; buf++) + fb_t* fb_init(void); void fb_close(fb_t* fb); int32_t fb_setmode(fb_t* fb); int fb_buffer_init(fb_t* fb); void fb_buffer_destroy(fb_t* fb); -uint32_t* fb_lock(fb_t* fb); +bool fb_lock(fb_t* fb); void fb_unlock(fb_t* fb); int32_t fb_getwidth(fb_t* fb); int32_t fb_getheight(fb_t* fb); int32_t fb_getscaling(fb_t* fb); bool fb_stepper_init(fb_stepper_t *s, fb_t *fb, int32_t x, int32_t y, uint32_t width, uint32_t height); - -bool static inline fb_stepper_step_x(fb_stepper_t *s, uint32_t rgba) -{ - int32_t x = s->start_x + s->x; - int32_t y = s->start_y + s->y; - int32_t p; - - if (x >= 0 && x < s->max_x - && y >= 0 && y < s->max_y) { - p = (x * s->m[0][0] + y * s->m[0][1] + s->m[0][2]) - + (x * s->m[1][0] + y * s->m[1][1] + s->m[1][2]) * s->pitch_div_4; - s->fb->lock.map[p] = rgba; - } - - s->x++; - if (s->x >= s->w) { - s->x = 0; - return false; - } - return true; -} - -bool inline fb_stepper_step_y(fb_stepper_t *s) -{ - s->y++; - return s->y < s->h; -} +bool fb_stepper_step_x(fb_stepper_t *s, uint32_t rgba); +bool fb_stepper_step_y(fb_stepper_t *s); #endif diff --git a/main.c b/main.c index 5187531..e67bb87 100644 --- a/main.c +++ b/main.c @@ -181,7 +181,6 @@ int main(int argc, char* argv[]) int ret; int c; unsigned vt; - drm_t* drm; if (getenv("FRECON_VTS")) command_flags.enable_vts = true; @@ -271,7 +270,7 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - drm_set(drm = drm_scan()); + drm_init(); if (command_flags.pre_create_vts) { for (unsigned vt = 1; -- 2.20.1