9627f0dc89f6923f6c5467e2c9397a6136409bff
[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 <poll.h>
26 #include <pthread.h>
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <strings.h>
31 #include <sys/stat.h>
32 #include <sys/sysmacros.h>
33 #include <unistd.h>
34 #include <xf86drm.h>
35 #include <xf86drmMode.h>
36
37 /* Number of resources, to be included in a DRM lease for each connector.
38  * Each connector needs both a CRTC and conector object:. */
39 #define DRM_OBJECTS_PER_CONNECTOR (2)
40
41 #define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0]))
42
43 struct lease {
44         struct lease_handle base;
45
46         bool is_granted;
47         uint32_t lessee_id;
48         int lease_fd;
49
50         uint32_t *object_ids;
51         int nobject_ids;
52
53         /* for lease transfer completion */
54         uint32_t crtc_id;
55         pthread_t transition_tid;
56         bool transition_running;
57 };
58
59 struct lm {
60         int drm_fd;
61         dev_t dev_id;
62
63         drmModeResPtr drm_resource;
64         drmModePlaneResPtr drm_plane_resource;
65         uint32_t available_crtcs;
66
67         struct lease **leases;
68         int nleases;
69 };
70
71 static const char *const connector_type_names[] = {
72     [DRM_MODE_CONNECTOR_Unknown] = "Unknown",
73     [DRM_MODE_CONNECTOR_VGA] = "VGA",
74     [DRM_MODE_CONNECTOR_DVII] = "DVI-I",
75     [DRM_MODE_CONNECTOR_DVID] = "DVI-D",
76     [DRM_MODE_CONNECTOR_DVIA] = "DVI-A",
77     [DRM_MODE_CONNECTOR_Composite] = "Composite",
78     [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO",
79     [DRM_MODE_CONNECTOR_LVDS] = "LVDS",
80     [DRM_MODE_CONNECTOR_Component] = "Component",
81     [DRM_MODE_CONNECTOR_9PinDIN] = "DIN",
82     [DRM_MODE_CONNECTOR_DisplayPort] = "DP",
83     [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A",
84     [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B",
85     [DRM_MODE_CONNECTOR_TV] = "TV",
86     [DRM_MODE_CONNECTOR_eDP] = "eDP",
87     [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual",
88     [DRM_MODE_CONNECTOR_DSI] = "DSI",
89     [DRM_MODE_CONNECTOR_DPI] = "DPI",
90     [DRM_MODE_CONNECTOR_WRITEBACK] = "Writeback",
91 };
92
93 static char *drm_create_default_lease_name(struct lm *lm,
94                                            drmModeConnectorPtr connector)
95 {
96         uint32_t type = connector->connector_type;
97         uint32_t id = connector->connector_type_id;
98
99         if (type >= ARRAY_LENGTH(connector_type_names))
100                 type = DRM_MODE_CONNECTOR_Unknown;
101
102         /* If the type is "Unknown", use the connector_id as the identify to
103          * guarantee that the name will be unique. */
104         if (type == DRM_MODE_CONNECTOR_Unknown)
105                 id = connector->connector_id;
106
107         char *name;
108         if (asprintf(&name, "card%d-%s-%d", minor(lm->dev_id),
109                      connector_type_names[type], id) < 0)
110                 return NULL;
111
112         return name;
113 }
114
115 static int drm_get_encoder_crtc_index(struct lm *lm, drmModeEncoderPtr encoder)
116 {
117         uint32_t crtc_id = encoder->crtc_id;
118         if (!crtc_id)
119                 return -1;
120
121         // The CRTC index only makes sense if it is less than the number of
122         // bits in the encoder possible_crtcs bitmap, which is 32.
123         assert(lm->drm_resource->count_crtcs < 32);
124
125         for (int i = 0; i < lm->drm_resource->count_crtcs; i++) {
126                 if (lm->drm_resource->crtcs[i] == crtc_id)
127                         return i;
128         }
129         return -1;
130 }
131
132 static int drm_get_active_crtc_index(struct lm *lm,
133                                      drmModeConnectorPtr connector)
134 {
135         drmModeEncoder *encoder =
136             drmModeGetEncoder(lm->drm_fd, connector->encoder_id);
137         if (!encoder)
138                 return -1;
139
140         int crtc_idx = drm_get_encoder_crtc_index(lm, encoder);
141         drmModeFreeEncoder(encoder);
142         return crtc_idx;
143 }
144
145 static int drm_get_crtc_index(struct lm *lm, drmModeConnectorPtr connector)
146 {
147
148         // try the active CRTC first
149         int crtc_index = drm_get_active_crtc_index(lm, connector);
150         if (crtc_index != -1)
151                 return crtc_index;
152
153         // If not try the first available CRTC on the connector/encoder
154         for (int i = 0; i < connector->count_encoders; i++) {
155                 drmModeEncoder *encoder =
156                     drmModeGetEncoder(lm->drm_fd, connector->encoders[i]);
157
158                 assert(encoder);
159
160                 uint32_t usable_crtcs =
161                     lm->available_crtcs & encoder->possible_crtcs;
162                 int crtc = ffs(usable_crtcs);
163                 drmModeFreeEncoder(encoder);
164                 if (crtc == 0)
165                         continue;
166                 crtc_index = crtc - 1;
167                 lm->available_crtcs &= ~(1 << crtc_index);
168                 break;
169         }
170         return crtc_index;
171 }
172
173 static void drm_find_available_crtcs(struct lm *lm)
174 {
175         // Assume all CRTCS are available by default,
176         lm->available_crtcs = ~0;
177
178         // then remove any that are in use. */
179         for (int i = 0; i < lm->drm_resource->count_encoders; i++) {
180                 int enc_id = lm->drm_resource->encoders[i];
181                 drmModeEncoderPtr enc = drmModeGetEncoder(lm->drm_fd, enc_id);
182                 if (!enc)
183                         continue;
184
185                 int crtc_idx = drm_get_encoder_crtc_index(lm, enc);
186                 if (crtc_idx >= 0)
187                         lm->available_crtcs &= ~(1 << crtc_idx);
188
189                 drmModeFreeEncoder(enc);
190         }
191 }
192
193 static bool lease_add_planes(struct lm *lm, struct lease *lease, int crtc_index)
194 {
195         for (uint32_t i = 0; i < lm->drm_plane_resource->count_planes; i++) {
196                 uint32_t plane_id = lm->drm_plane_resource->planes[i];
197                 drmModePlanePtr plane = drmModeGetPlane(lm->drm_fd, plane_id);
198
199                 assert(plane);
200
201                 // Exclude planes that can be used with multiple CRTCs for now
202                 if (plane->possible_crtcs == (1u << crtc_index)) {
203                         lease->object_ids[lease->nobject_ids++] = plane_id;
204                 }
205                 drmModeFreePlane(plane);
206         }
207         return true;
208 }
209
210 /* Lease transition
211  * Wait for a client to update the DRM framebuffer on the CRTC managed by
212  * a lease.  Once the framebuffer has been updated, it is safe to close
213  * the fd associated with the previous lease client, freeing the previous
214  * framebuffer if there are no other references to it. */
215 static void wait_for_fb_update(struct lease *lease, uint32_t old_fb)
216 {
217         uint32_t current_fb = old_fb;
218
219         struct pollfd drm_poll = {
220             .fd = lease->lease_fd,
221             .events = POLLIN,
222         };
223
224         while (current_fb == old_fb) {
225                 drmModeCrtcPtr crtc;
226                 if (poll(&drm_poll, 1, -1) < 0) {
227                         if (errno == EINTR)
228                                 continue;
229                         break;
230                 }
231
232                 crtc = drmModeGetCrtc(lease->lease_fd, lease->crtc_id);
233                 current_fb = crtc->buffer_id;
234                 drmModeFreeCrtc(crtc);
235         }
236 }
237
238 struct transition_ctx {
239         struct lease *lease;
240         int close_fd;
241         uint32_t old_fb;
242 };
243
244 static void transition_done(void *arg)
245 {
246         struct transition_ctx *ctx = arg;
247         close(ctx->close_fd);
248         free(ctx);
249 }
250
251 static void *finish_transition_task(void *arg)
252 {
253         struct transition_ctx *ctx = arg;
254         pthread_cleanup_push(transition_done, ctx);
255         wait_for_fb_update(ctx->lease, ctx->old_fb);
256         pthread_cleanup_pop(true);
257         return NULL;
258 }
259
260 static void close_after_lease_transition(struct lease *lease, int close_fd)
261 {
262         struct transition_ctx *ctx = calloc(1, sizeof(*ctx));
263
264         assert(ctx);
265
266         drmModeCrtcPtr crtc = drmModeGetCrtc(lease->lease_fd, lease->crtc_id);
267
268         ctx->lease = lease;
269         ctx->close_fd = close_fd;
270         ctx->old_fb = crtc->buffer_id;
271
272         drmModeFreeCrtc(crtc);
273
274         int ret = pthread_create(&lease->transition_tid, NULL,
275                                  finish_transition_task, ctx);
276
277         lease->transition_running = (ret == 0);
278 }
279
280 static void cancel_lease_transition_thread(struct lease *lease)
281 {
282
283         if (lease->transition_running) {
284                 pthread_cancel(lease->transition_tid);
285                 pthread_join(lease->transition_tid, NULL);
286         }
287
288         lease->transition_running = false;
289 }
290
291 static void lease_free(struct lease *lease)
292 {
293         free(lease->base.name);
294         free(lease->object_ids);
295         free(lease);
296 }
297
298 static struct lease *lease_create(struct lm *lm,
299                                   const struct lease_config *config)
300 {
301         struct lease *lease;
302
303         if (!config->lease_name) {
304                 ERROR_LOG("Mising lease name\n");
305                 return NULL;
306         }
307
308         lease = calloc(1, sizeof(struct lease));
309         if (!lease) {
310                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
311                 return NULL;
312         }
313
314         lease->base.name = strdup(config->lease_name);
315         if (!lease->base.name) {
316                 DEBUG_LOG("Can't create lease name: %s\n", strerror(errno));
317                 goto err;
318         }
319
320         int nobjects = lm->drm_plane_resource->count_planes +
321                        config->ncids * DRM_OBJECTS_PER_CONNECTOR;
322
323         lease->object_ids = calloc(nobjects, sizeof(uint32_t));
324         if (!lease->object_ids) {
325                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
326                 goto err;
327         }
328
329         for (int i = 0; i < config->ncids; i++) {
330                 drmModeConnectorPtr connector =
331                     drmModeGetConnector(lm->drm_fd, config->connector_ids[i]);
332
333                 if (connector == NULL) {
334                         ERROR_LOG("Can't find connector id: %d\n",
335                                   config->connector_ids);
336                         goto err;
337                 }
338
339                 uint32_t connector_id = connector->connector_id;
340
341                 int crtc_index = drm_get_crtc_index(lm, connector);
342
343                 drmModeFreeConnector(connector);
344
345                 if (crtc_index < 0) {
346                         DEBUG_LOG("No crtc found for connector: %d, lease %s\n",
347                                   connector_id, lease->base.name);
348                         goto err;
349                 }
350
351                 if (!lease_add_planes(lm, lease, crtc_index))
352                         goto err;
353
354                 uint32_t crtc_id = lm->drm_resource->crtcs[crtc_index];
355                 lease->crtc_id = crtc_id;
356                 lease->object_ids[lease->nobject_ids++] = crtc_id;
357                 lease->object_ids[lease->nobject_ids++] = connector_id;
358         }
359         lease->is_granted = false;
360         lease->lease_fd = -1;
361
362         return lease;
363
364 err:
365         lease_free(lease);
366         return NULL;
367 }
368
369 static void destroy_default_lease_configs(int num_configs,
370                                           struct lease_config *configs)
371 {
372         for (int i = 0; i < num_configs; i++) {
373                 free(configs[i].connector_ids);
374                 free(configs[i].lease_name);
375         }
376
377         free(configs);
378 }
379
380 static int create_default_lease_configs(struct lm *lm,
381                                         struct lease_config **configs)
382 {
383         struct lease_config *def_configs;
384         int num_configs = lm->drm_resource->count_connectors;
385
386         if (num_configs < 0)
387                 return -1;
388
389         def_configs = calloc(num_configs, sizeof(*def_configs));
390         if (!def_configs) {
391                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
392                 return -1;
393         }
394
395         for (int i = 0; i < num_configs; i++) {
396                 uint32_t cid = lm->drm_resource->connectors[i];
397
398                 def_configs[i].connector_ids = malloc(sizeof(uint32_t));
399                 if (!def_configs[i].connector_ids) {
400                         DEBUG_LOG("Memory allocation failed: %s\n",
401                                   strerror(errno));
402                         goto err;
403                 }
404
405                 drmModeConnectorPtr connector;
406                 connector = drmModeGetConnector(lm->drm_fd, cid);
407                 def_configs[i].lease_name =
408                     drm_create_default_lease_name(lm, connector);
409
410                 if (!def_configs[i].lease_name) {
411                         DEBUG_LOG(
412                             "Can't create lease name for connector %d: %s\n",
413                             cid, strerror(errno));
414                         goto err;
415                 }
416
417                 drmModeFreeConnector(connector);
418
419                 def_configs[i].connector_ids[0] = cid;
420                 def_configs[i].ncids = 1;
421         }
422
423         *configs = def_configs;
424         return num_configs;
425
426 err:
427         destroy_default_lease_configs(num_configs, def_configs);
428         return -1;
429 }
430
431 static struct lm *drm_device_get_resources(const char *device)
432 {
433         struct lm *lm = calloc(1, sizeof(struct lm));
434         if (!lm) {
435                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
436                 return NULL;
437         }
438         lm->drm_fd = open(device, O_RDWR);
439         if (lm->drm_fd < 0) {
440                 ERROR_LOG("Cannot open DRM device (%s): %s\n", device,
441                           strerror(errno));
442                 goto err;
443         }
444
445         lm->drm_resource = drmModeGetResources(lm->drm_fd);
446         if (!lm->drm_resource) {
447                 ERROR_LOG("Invalid DRM device(%s)\n", device);
448                 DEBUG_LOG("drmModeGetResources failed: %s\n", strerror(errno));
449                 goto err;
450         }
451
452         lm->drm_plane_resource = drmModeGetPlaneResources(lm->drm_fd);
453         if (!lm->drm_plane_resource) {
454                 DEBUG_LOG("drmModeGetPlaneResources failed: %s\n",
455                           strerror(errno));
456                 goto err;
457         }
458
459         struct stat st;
460         if (fstat(lm->drm_fd, &st) < 0 || !S_ISCHR(st.st_mode)) {
461                 DEBUG_LOG("%s is not a valid device file\n", device);
462                 goto err;
463         }
464
465         lm->dev_id = st.st_rdev;
466
467         return lm;
468 err:
469         lm_destroy(lm);
470         return NULL;
471 }
472
473 static int lm_create_leases(struct lm *lm, int num_leases,
474                             const struct lease_config *configs)
475 {
476         lm->leases = calloc(num_leases, sizeof(struct lease *));
477         if (!lm->leases) {
478                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
479                 return -1;
480         }
481
482         drm_find_available_crtcs(lm);
483
484         for (int i = 0; i < num_leases; i++) {
485                 struct lease *lease = lease_create(lm, &configs[i]);
486                 if (!lease)
487                         continue;
488
489                 lm->leases[lm->nleases] = lease;
490                 lm->nleases++;
491         }
492         if (lm->nleases == 0)
493                 return -1;
494
495         return 0;
496 }
497
498 struct lm *lm_create_with_config(const char *device, int num_leases,
499                                  struct lease_config *configs)
500 {
501         struct lease_config *default_configs = NULL;
502         struct lm *lm = drm_device_get_resources(device);
503
504         if (!lm)
505                 return NULL;
506
507         if (configs == NULL) {
508                 num_leases = create_default_lease_configs(lm, &default_configs);
509                 if (num_leases < 0) {
510                         lm_destroy(lm);
511                         ERROR_LOG("DRM connector enumeration failed\n");
512                         return NULL;
513                 }
514                 configs = default_configs;
515         }
516
517         if (lm_create_leases(lm, num_leases, configs) < 0) {
518                 lm_destroy(lm);
519                 lm = NULL;
520         }
521
522         if (default_configs)
523                 destroy_default_lease_configs(num_leases, default_configs);
524         return lm;
525 }
526
527 struct lm *lm_create(const char *device)
528 {
529         return lm_create_with_config(device, 0, NULL);
530 }
531
532 void lm_destroy(struct lm *lm)
533 {
534         assert(lm);
535
536         for (int i = 0; i < lm->nleases; i++) {
537                 struct lease_handle *lease_handle = &lm->leases[i]->base;
538                 lm_lease_revoke(lm, lease_handle);
539                 lm_lease_close(lease_handle);
540                 lease_free(lm->leases[i]);
541         }
542
543         free(lm->leases);
544         drmModeFreeResources(lm->drm_resource);
545         drmModeFreePlaneResources(lm->drm_plane_resource);
546         close(lm->drm_fd);
547         free(lm);
548 }
549
550 int lm_get_lease_handles(struct lm *lm, struct lease_handle ***handles)
551 {
552         assert(lm);
553         assert(handles);
554
555         *handles = (struct lease_handle **)lm->leases;
556         return lm->nleases;
557 }
558
559 int lm_lease_grant(struct lm *lm, struct lease_handle *handle)
560 {
561         assert(lm);
562         assert(handle);
563
564         struct lease *lease = (struct lease *)handle;
565         if (lease->is_granted) {
566                 /* Lease is already claimed */
567                 return -1;
568         }
569
570         int lease_fd =
571             drmModeCreateLease(lm->drm_fd, lease->object_ids,
572                                lease->nobject_ids, 0, &lease->lessee_id);
573         if (lease_fd < 0) {
574                 ERROR_LOG("drmModeCreateLease failed on lease %s: %s\n",
575                           lease->base.name, strerror(errno));
576                 return -1;
577         }
578
579         lease->is_granted = true;
580
581         int old_lease_fd = lease->lease_fd;
582         lease->lease_fd = lease_fd;
583
584         if (old_lease_fd >= 0)
585                 close_after_lease_transition(lease, old_lease_fd);
586
587         return lease_fd;
588 }
589
590 int lm_lease_transfer(struct lm *lm, struct lease_handle *handle)
591 {
592         assert(lm);
593         assert(handle);
594
595         struct lease *lease = (struct lease *)handle;
596         if (!lease->is_granted)
597                 return -1;
598
599         lm_lease_revoke(lm, handle);
600         if (lm_lease_grant(lm, handle) < 0) {
601                 lm_lease_close(handle);
602                 return -1;
603         }
604
605         return lease->lease_fd;
606 }
607
608 void lm_lease_revoke(struct lm *lm, struct lease_handle *handle)
609 {
610         assert(lm);
611         assert(handle);
612
613         struct lease *lease = (struct lease *)handle;
614
615         if (!lease->is_granted)
616                 return;
617
618         drmModeRevokeLease(lm->drm_fd, lease->lessee_id);
619         cancel_lease_transition_thread(lease);
620         lease->is_granted = false;
621 }
622
623 void lm_lease_close(struct lease_handle *handle)
624 {
625         assert(handle);
626
627         struct lease *lease = (struct lease *)handle;
628         if (lease->lease_fd >= 0)
629                 close(lease->lease_fd);
630         lease->lease_fd = -1;
631 }