5cfc5decd9f4fca0b2b3cc31e32cd8ed779864ab
[src/drm-lease-manager.git] / drm-lease-manager / lease-manager.c
1 /* Copyright 2020-2021 IGEL Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #define _GNU_SOURCE
17 #include "lease-manager.h"
18
19 #include "drm-lease.h"
20 #include "log.h"
21
22 #include <assert.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <strings.h>
29 #include <sys/stat.h>
30 #include <sys/sysmacros.h>
31 #include <unistd.h>
32 #include <xf86drm.h>
33 #include <xf86drmMode.h>
34
35 /* Number of resources, excluding planes, to be included in each DRM lease.
36  * Each lease needs at least a CRTC and conector. */
37 #define DRM_LEASE_MIN_RES (2)
38
39 #define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0]))
40
41 struct lease {
42         struct lease_handle base;
43
44         bool is_granted;
45         uint32_t lessee_id;
46         int lease_fd;
47
48         uint32_t *object_ids;
49         int nobject_ids;
50 };
51
52 struct lm {
53         int drm_fd;
54         dev_t dev_id;
55
56         drmModeResPtr drm_resource;
57         drmModePlaneResPtr drm_plane_resource;
58         uint32_t available_crtcs;
59
60         struct lease **leases;
61         int nleases;
62 };
63
64 static const char *const connector_type_names[] = {
65     [DRM_MODE_CONNECTOR_Unknown] = "Unknown",
66     [DRM_MODE_CONNECTOR_VGA] = "VGA",
67     [DRM_MODE_CONNECTOR_DVII] = "DVI-I",
68     [DRM_MODE_CONNECTOR_DVID] = "DVI-D",
69     [DRM_MODE_CONNECTOR_DVIA] = "DVI-A",
70     [DRM_MODE_CONNECTOR_Composite] = "Composite",
71     [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO",
72     [DRM_MODE_CONNECTOR_LVDS] = "LVDS",
73     [DRM_MODE_CONNECTOR_Component] = "Component",
74     [DRM_MODE_CONNECTOR_9PinDIN] = "DIN",
75     [DRM_MODE_CONNECTOR_DisplayPort] = "DP",
76     [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A",
77     [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B",
78     [DRM_MODE_CONNECTOR_TV] = "TV",
79     [DRM_MODE_CONNECTOR_eDP] = "eDP",
80     [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual",
81     [DRM_MODE_CONNECTOR_DSI] = "DSI",
82     [DRM_MODE_CONNECTOR_DPI] = "DPI",
83     [DRM_MODE_CONNECTOR_WRITEBACK] = "Writeback",
84 };
85
86 static char *drm_create_lease_name(struct lm *lm, drmModeConnectorPtr connector)
87 {
88         uint32_t type = connector->connector_type;
89         uint32_t id = connector->connector_type_id;
90
91         if (type >= ARRAY_LENGTH(connector_type_names))
92                 type = DRM_MODE_CONNECTOR_Unknown;
93
94         /* If the type is "Unknown", use the connector_id as the identify to
95          * guarantee that the name will be unique. */
96         if (type == DRM_MODE_CONNECTOR_Unknown)
97                 id = connector->connector_id;
98
99         char *name;
100         if (asprintf(&name, "card%d-%s-%d", minor(lm->dev_id),
101                      connector_type_names[type], id) < 0)
102                 return NULL;
103
104         return name;
105 }
106
107 static int drm_get_encoder_crtc_index(struct lm *lm, drmModeEncoderPtr encoder)
108 {
109         uint32_t crtc_id = encoder->crtc_id;
110         if (!crtc_id)
111                 return -1;
112
113         // The CRTC index only makes sense if it is less than the number of
114         // bits in the encoder possible_crtcs bitmap, which is 32.
115         assert(lm->drm_resource->count_crtcs < 32);
116
117         for (int i = 0; i < lm->drm_resource->count_crtcs; i++) {
118                 if (lm->drm_resource->crtcs[i] == crtc_id)
119                         return i;
120         }
121         return -1;
122 }
123
124 static int drm_get_active_crtc_index(struct lm *lm,
125                                      drmModeConnectorPtr connector)
126 {
127         drmModeEncoder *encoder =
128             drmModeGetEncoder(lm->drm_fd, connector->encoder_id);
129         if (!encoder)
130                 return -1;
131
132         int crtc_idx = drm_get_encoder_crtc_index(lm, encoder);
133         drmModeFreeEncoder(encoder);
134         return crtc_idx;
135 }
136
137 static int drm_get_crtc_index(struct lm *lm, drmModeConnectorPtr connector)
138 {
139
140         // try the active CRTC first
141         int crtc_index = drm_get_active_crtc_index(lm, connector);
142         if (crtc_index != -1)
143                 return crtc_index;
144
145         // If not try the first available CRTC on the connector/encoder
146         for (int i = 0; i < connector->count_encoders; i++) {
147                 drmModeEncoder *encoder =
148                     drmModeGetEncoder(lm->drm_fd, connector->encoders[i]);
149
150                 assert(encoder);
151
152                 uint32_t usable_crtcs =
153                     lm->available_crtcs & encoder->possible_crtcs;
154                 int crtc = ffs(usable_crtcs);
155                 drmModeFreeEncoder(encoder);
156                 if (crtc == 0)
157                         continue;
158                 crtc_index = crtc - 1;
159                 lm->available_crtcs &= ~(1 << crtc_index);
160                 break;
161         }
162         return crtc_index;
163 }
164
165 static void drm_find_available_crtcs(struct lm *lm)
166 {
167         // Assume all CRTCS are available by default,
168         lm->available_crtcs = ~0;
169
170         // then remove any that are in use. */
171         for (int i = 0; i < lm->drm_resource->count_encoders; i++) {
172                 int enc_id = lm->drm_resource->encoders[i];
173                 drmModeEncoderPtr enc = drmModeGetEncoder(lm->drm_fd, enc_id);
174                 if (!enc)
175                         continue;
176
177                 int crtc_idx = drm_get_encoder_crtc_index(lm, enc);
178                 if (crtc_idx >= 0)
179                         lm->available_crtcs &= ~(1 << crtc_idx);
180
181                 drmModeFreeEncoder(enc);
182         }
183 }
184
185 static bool lease_add_planes(struct lm *lm, struct lease *lease, int crtc_index)
186 {
187         for (uint32_t i = 0; i < lm->drm_plane_resource->count_planes; i++) {
188                 uint32_t plane_id = lm->drm_plane_resource->planes[i];
189                 drmModePlanePtr plane = drmModeGetPlane(lm->drm_fd, plane_id);
190
191                 assert(plane);
192
193                 // Exclude planes that can be used with multiple CRTCs for now
194                 if (plane->possible_crtcs == (1u << crtc_index)) {
195                         lease->object_ids[lease->nobject_ids++] = plane_id;
196                 }
197                 drmModeFreePlane(plane);
198         }
199         return true;
200 }
201
202 static void lease_free(struct lease *lease)
203 {
204         free(lease->base.name);
205         free(lease->object_ids);
206         free(lease);
207 }
208
209 static struct lease *lease_create(struct lm *lm, drmModeConnectorPtr connector)
210 {
211         struct lease *lease = calloc(1, sizeof(struct lease));
212         if (!lease) {
213                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
214                 return NULL;
215         }
216
217         lease->base.name = drm_create_lease_name(lm, connector);
218         if (!lease->base.name) {
219                 DEBUG_LOG("Can't create lease name: %s\n", strerror(errno));
220                 goto err;
221         }
222
223         int nobjects = lm->drm_plane_resource->count_planes + DRM_LEASE_MIN_RES;
224         lease->object_ids = calloc(nobjects, sizeof(uint32_t));
225         if (!lease->object_ids) {
226                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
227                 goto err;
228         }
229
230         int crtc_index = drm_get_crtc_index(lm, connector);
231         if (crtc_index < 0) {
232                 DEBUG_LOG("No crtc found for connector: %s\n",
233                           lease->base.name);
234                 goto err;
235         }
236
237         if (!lease_add_planes(lm, lease, crtc_index))
238                 goto err;
239
240         uint32_t crtc_id = lm->drm_resource->crtcs[crtc_index];
241         lease->object_ids[lease->nobject_ids++] = crtc_id;
242         lease->object_ids[lease->nobject_ids++] = connector->connector_id;
243
244         lease->is_granted = false;
245
246         return lease;
247
248 err:
249         lease_free(lease);
250         return NULL;
251 }
252
253 struct lm *lm_create(const char *device)
254 {
255         struct lm *lm = calloc(1, sizeof(struct lm));
256         if (!lm) {
257                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
258                 return NULL;
259         }
260         lm->drm_fd = open(device, O_RDWR);
261         if (lm->drm_fd < 0) {
262                 ERROR_LOG("Cannot open DRM device (%s): %s\n", device,
263                           strerror(errno));
264                 goto err;
265         }
266
267         lm->drm_resource = drmModeGetResources(lm->drm_fd);
268         if (!lm->drm_resource) {
269                 ERROR_LOG("Invalid DRM device(%s)\n", device);
270                 DEBUG_LOG("drmModeGetResources failed: %s\n", strerror(errno));
271                 goto err;
272         }
273
274         lm->drm_plane_resource = drmModeGetPlaneResources(lm->drm_fd);
275         if (!lm->drm_plane_resource) {
276                 DEBUG_LOG("drmModeGetPlaneResources failed: %s\n",
277                           strerror(errno));
278                 goto err;
279         }
280
281         struct stat st;
282         if (fstat(lm->drm_fd, &st) < 0 || !S_ISCHR(st.st_mode)) {
283                 DEBUG_LOG("%s is not a valid device file\n", device);
284                 goto err;
285         }
286
287         lm->dev_id = st.st_rdev;
288
289         int num_leases = lm->drm_resource->count_connectors;
290
291         lm->leases = calloc(num_leases, sizeof(struct lease *));
292         if (!lm->leases) {
293                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
294                 goto err;
295         }
296
297         drm_find_available_crtcs(lm);
298
299         for (int i = 0; i < num_leases; i++) {
300                 uint32_t connector_id = lm->drm_resource->connectors[i];
301                 drmModeConnectorPtr connector =
302                     drmModeGetConnector(lm->drm_fd, connector_id);
303
304                 if (!connector)
305                         continue;
306
307                 struct lease *lease = lease_create(lm, connector);
308                 drmModeFreeConnector(connector);
309
310                 if (!lease)
311                         continue;
312
313                 lm->leases[lm->nleases] = lease;
314                 lm->nleases++;
315         }
316         if (lm->nleases == 0)
317                 goto err;
318
319         return lm;
320
321 err:
322         lm_destroy(lm);
323         return NULL;
324 }
325
326 void lm_destroy(struct lm *lm)
327 {
328         assert(lm);
329
330         for (int i = 0; i < lm->nleases; i++) {
331                 lm_lease_revoke(lm, (struct lease_handle *)lm->leases[i]);
332                 lease_free(lm->leases[i]);
333         }
334
335         free(lm->leases);
336         drmModeFreeResources(lm->drm_resource);
337         drmModeFreePlaneResources(lm->drm_plane_resource);
338         close(lm->drm_fd);
339         free(lm);
340 }
341
342 int lm_get_lease_handles(struct lm *lm, struct lease_handle ***handles)
343 {
344         assert(lm);
345         assert(handles);
346
347         *handles = (struct lease_handle **)lm->leases;
348         return lm->nleases;
349 }
350
351 int lm_lease_grant(struct lm *lm, struct lease_handle *handle)
352 {
353         assert(lm);
354         assert(handle);
355
356         struct lease *lease = (struct lease *)handle;
357         if (lease->is_granted)
358                 return lease->lease_fd;
359
360         lease->lease_fd =
361             drmModeCreateLease(lm->drm_fd, lease->object_ids,
362                                lease->nobject_ids, 0, &lease->lessee_id);
363         if (lease->lease_fd < 0) {
364                 ERROR_LOG("drmModeCreateLease failed on lease %s: %s\n",
365                           lease->base.name, strerror(errno));
366                 return -1;
367         }
368
369         lease->is_granted = true;
370         return lease->lease_fd;
371 }
372
373 void lm_lease_revoke(struct lm *lm, struct lease_handle *handle)
374 {
375         assert(lm);
376         assert(handle);
377
378         struct lease *lease = (struct lease *)handle;
379
380         if (!lease->is_granted)
381                 return;
382
383         drmModeRevokeLease(lm->drm_fd, lease->lessee_id);
384         close(lease->lease_fd);
385         lease->is_granted = false;
386 }