/* Copyright 2020-2021 IGEL Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include "lease-manager.h" #include "drm-lease.h" #include "log.h" #include #include #include #include #include #include #include #include #include #include #include #include /* Number of resources, excluding planes, to be included in each DRM lease. * Each lease needs at least a CRTC and conector. */ #define DRM_LEASE_MIN_RES (2) #define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0])) struct lease { struct lease_handle base; bool is_granted; uint32_t lessee_id; int lease_fd; uint32_t *object_ids; int nobject_ids; }; struct lm { int drm_fd; dev_t dev_id; drmModeResPtr drm_resource; drmModePlaneResPtr drm_plane_resource; uint32_t available_crtcs; struct lease **leases; int nleases; }; 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", [DRM_MODE_CONNECTOR_WRITEBACK] = "Writeback", }; static char *drm_create_lease_name(struct lm *lm, drmModeConnectorPtr connector) { uint32_t type = connector->connector_type; uint32_t id = connector->connector_type_id; if (type >= ARRAY_LENGTH(connector_type_names)) type = DRM_MODE_CONNECTOR_Unknown; /* If the type is "Unknown", use the connector_id as the identify to * guarantee that the name will be unique. */ if (type == DRM_MODE_CONNECTOR_Unknown) id = connector->connector_id; char *name; if (asprintf(&name, "card%d-%s-%d", minor(lm->dev_id), connector_type_names[type], id) < 0) return NULL; return name; } static int drm_get_encoder_crtc_index(struct lm *lm, drmModeEncoderPtr encoder) { uint32_t crtc_id = encoder->crtc_id; if (!crtc_id) return -1; // The CRTC index only makes sense if it is less than the number of // bits in the encoder possible_crtcs bitmap, which is 32. assert(lm->drm_resource->count_crtcs < 32); for (int i = 0; i < lm->drm_resource->count_crtcs; i++) { if (lm->drm_resource->crtcs[i] == crtc_id) return i; } return -1; } static int drm_get_active_crtc_index(struct lm *lm, drmModeConnectorPtr connector) { drmModeEncoder *encoder = drmModeGetEncoder(lm->drm_fd, connector->encoder_id); if (!encoder) return -1; int crtc_idx = drm_get_encoder_crtc_index(lm, encoder); drmModeFreeEncoder(encoder); return crtc_idx; } static int drm_get_crtc_index(struct lm *lm, drmModeConnectorPtr connector) { // try the active CRTC first int crtc_index = drm_get_active_crtc_index(lm, connector); if (crtc_index != -1) return crtc_index; // If not try the first available CRTC on the connector/encoder for (int i = 0; i < connector->count_encoders; i++) { drmModeEncoder *encoder = drmModeGetEncoder(lm->drm_fd, connector->encoders[i]); assert(encoder); uint32_t usable_crtcs = lm->available_crtcs & encoder->possible_crtcs; int crtc = ffs(usable_crtcs); drmModeFreeEncoder(encoder); if (crtc == 0) continue; crtc_index = crtc - 1; lm->available_crtcs &= ~(1 << crtc_index); break; } return crtc_index; } static void drm_find_available_crtcs(struct lm *lm) { // Assume all CRTCS are available by default, lm->available_crtcs = ~0; // then remove any that are in use. */ for (int i = 0; i < lm->drm_resource->count_encoders; i++) { int enc_id = lm->drm_resource->encoders[i]; drmModeEncoderPtr enc = drmModeGetEncoder(lm->drm_fd, enc_id); if (!enc) continue; int crtc_idx = drm_get_encoder_crtc_index(lm, enc); if (crtc_idx >= 0) lm->available_crtcs &= ~(1 << crtc_idx); drmModeFreeEncoder(enc); } } static bool lease_add_planes(struct lm *lm, struct lease *lease, int crtc_index) { for (uint32_t i = 0; i < lm->drm_plane_resource->count_planes; i++) { uint32_t plane_id = lm->drm_plane_resource->planes[i]; drmModePlanePtr plane = drmModeGetPlane(lm->drm_fd, plane_id); assert(plane); // Exclude planes that can be used with multiple CRTCs for now if (plane->possible_crtcs == (1u << crtc_index)) { lease->object_ids[lease->nobject_ids++] = plane_id; } drmModeFreePlane(plane); } return true; } static void lease_free(struct lease *lease) { free(lease->base.name); free(lease->object_ids); free(lease); } static struct lease *lease_create(struct lm *lm, drmModeConnectorPtr connector) { struct lease *lease = calloc(1, sizeof(struct lease)); if (!lease) { DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); return NULL; } lease->base.name = drm_create_lease_name(lm, connector); if (!lease->base.name) { DEBUG_LOG("Can't create lease name: %s\n", strerror(errno)); goto err; } int nobjects = lm->drm_plane_resource->count_planes + DRM_LEASE_MIN_RES; lease->object_ids = calloc(nobjects, sizeof(uint32_t)); if (!lease->object_ids) { DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); goto err; } int crtc_index = drm_get_crtc_index(lm, connector); if (crtc_index < 0) { DEBUG_LOG("No crtc found for connector: %s\n", lease->base.name); goto err; } if (!lease_add_planes(lm, lease, crtc_index)) goto err; uint32_t crtc_id = lm->drm_resource->crtcs[crtc_index]; lease->object_ids[lease->nobject_ids++] = crtc_id; lease->object_ids[lease->nobject_ids++] = connector->connector_id; lease->is_granted = false; return lease; err: lease_free(lease); return NULL; } struct lm *lm_create(const char *device) { struct lm *lm = calloc(1, sizeof(struct lm)); if (!lm) { DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); return NULL; } lm->drm_fd = open(device, O_RDWR); if (lm->drm_fd < 0) { ERROR_LOG("Cannot open DRM device (%s): %s\n", device, strerror(errno)); goto err; } lm->drm_resource = drmModeGetResources(lm->drm_fd); if (!lm->drm_resource) { ERROR_LOG("Invalid DRM device(%s)\n", device); DEBUG_LOG("drmModeGetResources failed: %s\n", strerror(errno)); goto err; } lm->drm_plane_resource = drmModeGetPlaneResources(lm->drm_fd); if (!lm->drm_plane_resource) { DEBUG_LOG("drmModeGetPlaneResources failed: %s\n", strerror(errno)); goto err; } struct stat st; if (fstat(lm->drm_fd, &st) < 0 || !S_ISCHR(st.st_mode)) { DEBUG_LOG("%s is not a valid device file\n", device); goto err; } lm->dev_id = st.st_rdev; int num_leases = lm->drm_resource->count_connectors; lm->leases = calloc(num_leases, sizeof(struct lease *)); if (!lm->leases) { DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno)); goto err; } drm_find_available_crtcs(lm); for (int i = 0; i < num_leases; i++) { uint32_t connector_id = lm->drm_resource->connectors[i]; drmModeConnectorPtr connector = drmModeGetConnector(lm->drm_fd, connector_id); if (!connector) continue; struct lease *lease = lease_create(lm, connector); drmModeFreeConnector(connector); if (!lease) continue; lm->leases[lm->nleases] = lease; lm->nleases++; } if (lm->nleases == 0) goto err; return lm; err: lm_destroy(lm); return NULL; } void lm_destroy(struct lm *lm) { assert(lm); for (int i = 0; i < lm->nleases; i++) { lm_lease_revoke(lm, (struct lease_handle *)lm->leases[i]); lease_free(lm->leases[i]); } free(lm->leases); drmModeFreeResources(lm->drm_resource); drmModeFreePlaneResources(lm->drm_plane_resource); close(lm->drm_fd); free(lm); } int lm_get_lease_handles(struct lm *lm, struct lease_handle ***handles) { assert(lm); assert(handles); *handles = (struct lease_handle **)lm->leases; return lm->nleases; } int lm_lease_grant(struct lm *lm, struct lease_handle *handle) { assert(lm); assert(handle); struct lease *lease = (struct lease *)handle; if (lease->is_granted) { /* Lease is already claimed */ return -1; } lease->lease_fd = drmModeCreateLease(lm->drm_fd, lease->object_ids, lease->nobject_ids, 0, &lease->lessee_id); if (lease->lease_fd < 0) { ERROR_LOG("drmModeCreateLease failed on lease %s: %s\n", lease->base.name, strerror(errno)); return -1; } lease->is_granted = true; return lease->lease_fd; } void lm_lease_revoke(struct lm *lm, struct lease_handle *handle) { assert(lm); assert(handle); struct lease *lease = (struct lease *)handle; if (!lease->is_granted) return; drmModeRevokeLease(lm->drm_fd, lease->lessee_id); close(lease->lease_fd); lease->is_granted = false; }