Free old lease fd reference after transition
[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, excluding planes, to be included in each DRM lease.
38  * Each lease needs at least a CRTC and conector. */
39 #define DRM_LEASE_MIN_RES (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_lease_name(struct lm *lm, drmModeConnectorPtr connector)
94 {
95         uint32_t type = connector->connector_type;
96         uint32_t id = connector->connector_type_id;
97
98         if (type >= ARRAY_LENGTH(connector_type_names))
99                 type = DRM_MODE_CONNECTOR_Unknown;
100
101         /* If the type is "Unknown", use the connector_id as the identify to
102          * guarantee that the name will be unique. */
103         if (type == DRM_MODE_CONNECTOR_Unknown)
104                 id = connector->connector_id;
105
106         char *name;
107         if (asprintf(&name, "card%d-%s-%d", minor(lm->dev_id),
108                      connector_type_names[type], id) < 0)
109                 return NULL;
110
111         return name;
112 }
113
114 static int drm_get_encoder_crtc_index(struct lm *lm, drmModeEncoderPtr encoder)
115 {
116         uint32_t crtc_id = encoder->crtc_id;
117         if (!crtc_id)
118                 return -1;
119
120         // The CRTC index only makes sense if it is less than the number of
121         // bits in the encoder possible_crtcs bitmap, which is 32.
122         assert(lm->drm_resource->count_crtcs < 32);
123
124         for (int i = 0; i < lm->drm_resource->count_crtcs; i++) {
125                 if (lm->drm_resource->crtcs[i] == crtc_id)
126                         return i;
127         }
128         return -1;
129 }
130
131 static int drm_get_active_crtc_index(struct lm *lm,
132                                      drmModeConnectorPtr connector)
133 {
134         drmModeEncoder *encoder =
135             drmModeGetEncoder(lm->drm_fd, connector->encoder_id);
136         if (!encoder)
137                 return -1;
138
139         int crtc_idx = drm_get_encoder_crtc_index(lm, encoder);
140         drmModeFreeEncoder(encoder);
141         return crtc_idx;
142 }
143
144 static int drm_get_crtc_index(struct lm *lm, drmModeConnectorPtr connector)
145 {
146
147         // try the active CRTC first
148         int crtc_index = drm_get_active_crtc_index(lm, connector);
149         if (crtc_index != -1)
150                 return crtc_index;
151
152         // If not try the first available CRTC on the connector/encoder
153         for (int i = 0; i < connector->count_encoders; i++) {
154                 drmModeEncoder *encoder =
155                     drmModeGetEncoder(lm->drm_fd, connector->encoders[i]);
156
157                 assert(encoder);
158
159                 uint32_t usable_crtcs =
160                     lm->available_crtcs & encoder->possible_crtcs;
161                 int crtc = ffs(usable_crtcs);
162                 drmModeFreeEncoder(encoder);
163                 if (crtc == 0)
164                         continue;
165                 crtc_index = crtc - 1;
166                 lm->available_crtcs &= ~(1 << crtc_index);
167                 break;
168         }
169         return crtc_index;
170 }
171
172 static void drm_find_available_crtcs(struct lm *lm)
173 {
174         // Assume all CRTCS are available by default,
175         lm->available_crtcs = ~0;
176
177         // then remove any that are in use. */
178         for (int i = 0; i < lm->drm_resource->count_encoders; i++) {
179                 int enc_id = lm->drm_resource->encoders[i];
180                 drmModeEncoderPtr enc = drmModeGetEncoder(lm->drm_fd, enc_id);
181                 if (!enc)
182                         continue;
183
184                 int crtc_idx = drm_get_encoder_crtc_index(lm, enc);
185                 if (crtc_idx >= 0)
186                         lm->available_crtcs &= ~(1 << crtc_idx);
187
188                 drmModeFreeEncoder(enc);
189         }
190 }
191
192 static bool lease_add_planes(struct lm *lm, struct lease *lease, int crtc_index)
193 {
194         for (uint32_t i = 0; i < lm->drm_plane_resource->count_planes; i++) {
195                 uint32_t plane_id = lm->drm_plane_resource->planes[i];
196                 drmModePlanePtr plane = drmModeGetPlane(lm->drm_fd, plane_id);
197
198                 assert(plane);
199
200                 // Exclude planes that can be used with multiple CRTCs for now
201                 if (plane->possible_crtcs == (1u << crtc_index)) {
202                         lease->object_ids[lease->nobject_ids++] = plane_id;
203                 }
204                 drmModeFreePlane(plane);
205         }
206         return true;
207 }
208
209 /* Lease transition
210  * Wait for a client to update the DRM framebuffer on the CRTC managed by
211  * a lease.  Once the framebuffer has been updated, it is safe to close
212  * the fd associated with the previous lease client, freeing the previous
213  * framebuffer if there are no other references to it. */
214 static void wait_for_fb_update(struct lease *lease, uint32_t old_fb)
215 {
216         uint32_t current_fb = old_fb;
217
218         struct pollfd drm_poll = {
219             .fd = lease->lease_fd,
220             .events = POLLIN,
221         };
222
223         while (current_fb == old_fb) {
224                 drmModeCrtcPtr crtc;
225                 if (poll(&drm_poll, 1, -1) < 0) {
226                         if (errno == EINTR)
227                                 continue;
228                         break;
229                 }
230
231                 crtc = drmModeGetCrtc(lease->lease_fd, lease->crtc_id);
232                 current_fb = crtc->buffer_id;
233                 drmModeFreeCrtc(crtc);
234         }
235 }
236
237 struct transition_ctx {
238         struct lease *lease;
239         int close_fd;
240         uint32_t old_fb;
241 };
242
243 static void transition_done(void *arg)
244 {
245         struct transition_ctx *ctx = arg;
246         close(ctx->close_fd);
247         free(ctx);
248 }
249
250 static void *finish_transition_task(void *arg)
251 {
252         struct transition_ctx *ctx = arg;
253         pthread_cleanup_push(transition_done, ctx);
254         wait_for_fb_update(ctx->lease, ctx->old_fb);
255         pthread_cleanup_pop(true);
256         return NULL;
257 }
258
259 static void close_after_lease_transition(struct lease *lease, int close_fd)
260 {
261         struct transition_ctx *ctx = calloc(1, sizeof(*ctx));
262
263         assert(ctx);
264
265         drmModeCrtcPtr crtc = drmModeGetCrtc(lease->lease_fd, lease->crtc_id);
266
267         ctx->lease = lease;
268         ctx->close_fd = close_fd;
269         ctx->old_fb = crtc->buffer_id;
270
271         drmModeFreeCrtc(crtc);
272
273         int ret = pthread_create(&lease->transition_tid, NULL,
274                                  finish_transition_task, ctx);
275
276         lease->transition_running = (ret == 0);
277 }
278
279 static void cancel_lease_transition_thread(struct lease *lease)
280 {
281
282         if (lease->transition_running) {
283                 pthread_cancel(lease->transition_tid);
284                 pthread_join(lease->transition_tid, NULL);
285         }
286
287         lease->transition_running = false;
288 }
289
290 static void lease_free(struct lease *lease)
291 {
292         free(lease->base.name);
293         free(lease->object_ids);
294         free(lease);
295 }
296
297 static struct lease *lease_create(struct lm *lm, drmModeConnectorPtr connector)
298 {
299         struct lease *lease = calloc(1, sizeof(struct lease));
300         if (!lease) {
301                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
302                 return NULL;
303         }
304
305         lease->base.name = drm_create_lease_name(lm, connector);
306         if (!lease->base.name) {
307                 DEBUG_LOG("Can't create lease name: %s\n", strerror(errno));
308                 goto err;
309         }
310
311         int nobjects = lm->drm_plane_resource->count_planes + DRM_LEASE_MIN_RES;
312         lease->object_ids = calloc(nobjects, sizeof(uint32_t));
313         if (!lease->object_ids) {
314                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
315                 goto err;
316         }
317
318         int crtc_index = drm_get_crtc_index(lm, connector);
319         if (crtc_index < 0) {
320                 DEBUG_LOG("No crtc found for connector: %s\n",
321                           lease->base.name);
322                 goto err;
323         }
324
325         if (!lease_add_planes(lm, lease, crtc_index))
326                 goto err;
327
328         uint32_t crtc_id = lm->drm_resource->crtcs[crtc_index];
329         lease->crtc_id = crtc_id;
330         lease->object_ids[lease->nobject_ids++] = crtc_id;
331         lease->object_ids[lease->nobject_ids++] = connector->connector_id;
332
333         lease->is_granted = false;
334
335         return lease;
336
337 err:
338         lease_free(lease);
339         return NULL;
340 }
341
342 struct lm *lm_create(const char *device)
343 {
344         struct lm *lm = calloc(1, sizeof(struct lm));
345         if (!lm) {
346                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
347                 return NULL;
348         }
349         lm->drm_fd = open(device, O_RDWR);
350         if (lm->drm_fd < 0) {
351                 ERROR_LOG("Cannot open DRM device (%s): %s\n", device,
352                           strerror(errno));
353                 goto err;
354         }
355
356         lm->drm_resource = drmModeGetResources(lm->drm_fd);
357         if (!lm->drm_resource) {
358                 ERROR_LOG("Invalid DRM device(%s)\n", device);
359                 DEBUG_LOG("drmModeGetResources failed: %s\n", strerror(errno));
360                 goto err;
361         }
362
363         lm->drm_plane_resource = drmModeGetPlaneResources(lm->drm_fd);
364         if (!lm->drm_plane_resource) {
365                 DEBUG_LOG("drmModeGetPlaneResources failed: %s\n",
366                           strerror(errno));
367                 goto err;
368         }
369
370         struct stat st;
371         if (fstat(lm->drm_fd, &st) < 0 || !S_ISCHR(st.st_mode)) {
372                 DEBUG_LOG("%s is not a valid device file\n", device);
373                 goto err;
374         }
375
376         lm->dev_id = st.st_rdev;
377
378         int num_leases = lm->drm_resource->count_connectors;
379
380         lm->leases = calloc(num_leases, sizeof(struct lease *));
381         if (!lm->leases) {
382                 DEBUG_LOG("Memory allocation failed: %s\n", strerror(errno));
383                 goto err;
384         }
385
386         drm_find_available_crtcs(lm);
387
388         for (int i = 0; i < num_leases; i++) {
389                 uint32_t connector_id = lm->drm_resource->connectors[i];
390                 drmModeConnectorPtr connector =
391                     drmModeGetConnector(lm->drm_fd, connector_id);
392
393                 if (!connector)
394                         continue;
395
396                 struct lease *lease = lease_create(lm, connector);
397                 drmModeFreeConnector(connector);
398
399                 if (!lease)
400                         continue;
401
402                 lm->leases[lm->nleases] = lease;
403                 lm->nleases++;
404         }
405         if (lm->nleases == 0)
406                 goto err;
407
408         return lm;
409
410 err:
411         lm_destroy(lm);
412         return NULL;
413 }
414
415 void lm_destroy(struct lm *lm)
416 {
417         assert(lm);
418
419         for (int i = 0; i < lm->nleases; i++) {
420                 lm_lease_revoke(lm, (struct lease_handle *)lm->leases[i]);
421                 lease_free(lm->leases[i]);
422         }
423
424         free(lm->leases);
425         drmModeFreeResources(lm->drm_resource);
426         drmModeFreePlaneResources(lm->drm_plane_resource);
427         close(lm->drm_fd);
428         free(lm);
429 }
430
431 int lm_get_lease_handles(struct lm *lm, struct lease_handle ***handles)
432 {
433         assert(lm);
434         assert(handles);
435
436         *handles = (struct lease_handle **)lm->leases;
437         return lm->nleases;
438 }
439
440 int lm_lease_grant(struct lm *lm, struct lease_handle *handle)
441 {
442         assert(lm);
443         assert(handle);
444
445         struct lease *lease = (struct lease *)handle;
446         if (lease->is_granted) {
447                 /* Lease is already claimed */
448                 return -1;
449         }
450
451         lease->lease_fd =
452             drmModeCreateLease(lm->drm_fd, lease->object_ids,
453                                lease->nobject_ids, 0, &lease->lessee_id);
454         if (lease->lease_fd < 0) {
455                 ERROR_LOG("drmModeCreateLease failed on lease %s: %s\n",
456                           lease->base.name, strerror(errno));
457                 return -1;
458         }
459
460         lease->is_granted = true;
461         return lease->lease_fd;
462 }
463
464 int lm_lease_transfer(struct lm *lm, struct lease_handle *handle)
465 {
466         assert(lm);
467         assert(handle);
468
469         struct lease *lease = (struct lease *)handle;
470         if (!lease->is_granted)
471                 return -1;
472
473         int old_lease_fd = dup(lease->lease_fd);
474
475         lm_lease_revoke(lm, handle);
476         if (lm_lease_grant(lm, handle) < 0) {
477                 close(old_lease_fd);
478                 return -1;
479         }
480
481         close_after_lease_transition(lease, old_lease_fd);
482         return lease->lease_fd;
483 }
484
485 void lm_lease_revoke(struct lm *lm, struct lease_handle *handle)
486 {
487         assert(lm);
488         assert(handle);
489
490         struct lease *lease = (struct lease *)handle;
491
492         if (!lease->is_granted)
493                 return;
494
495         drmModeRevokeLease(lm->drm_fd, lease->lessee_id);
496         cancel_lease_transition_thread(lease);
497         close(lease->lease_fd);
498         lease->is_granted = false;
499 }