Add initial version
[src/drm-lease-manager.git] / drm-lease-manager / lease-manager.c
diff --git a/drm-lease-manager/lease-manager.c b/drm-lease-manager/lease-manager.c
new file mode 100644 (file)
index 0000000..5cfc5de
--- /dev/null
@@ -0,0 +1,386 @@
+/* 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+/* 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)
+               return lease->lease_fd;
+
+       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;
+}