main shrub/neuswc / libswc / drm.c
  1/* swc: drm.c
  2 *
  3 * Copyright (c) 2013-2020 Michael Forney
  4 *
  5 * Permission is hereby granted, free of charge, to any person obtaining a copy
  6 * of this software and associated documentation files (the "Software"), to deal
  7 * in the Software without restriction, including without limitation the rights
  8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 * copies of the Software, and to permit persons to whom the Software is
 10 * furnished to do so, subject to the following conditions:
 11 *
 12 * The above copyright notice and this permission notice shall be included in
 13 * all copies or substantial portions of the Software.
 14 *
 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 21 * SOFTWARE.
 22 */
 23
 24#include "drm.h"
 25#include "dmabuf.h"
 26#include "event.h"
 27#include "internal.h"
 28#include "launch.h"
 29#include "output.h"
 30#include "plane.h"
 31#include "screen.h"
 32#include "util.h"
 33#include "wayland_buffer.h"
 34
 35#include "wayland-drm-server-protocol.h"
 36#include <dirent.h>
 37#include <drm.h>
 38#include <errno.h>
 39#include <fcntl.h>
 40#include <limits.h>
 41#include <stdio.h>
 42#include <stdlib.h>
 43#include <string.h>
 44#include <strings.h>
 45#include <unistd.h>
 46#include <wayland-server.h>
 47#include <wld/drm.h>
 48#include <wld/wld.h>
 49#include <xf86drm.h>
 50
 51struct swc_drm swc_drm;
 52
 53static struct {
 54	char *path;
 55
 56	struct wl_global *global;
 57	struct wl_global *dmabuf;
 58	struct wl_event_source *event_source;
 59} drm;
 60
 61static void
 62authenticate(struct wl_client *client, struct wl_resource *resource,
 63             uint32_t magic)
 64{
 65	wl_drm_send_authenticated(resource);
 66}
 67
 68static void
 69create_buffer(struct wl_client *client, struct wl_resource *resource,
 70              uint32_t id, uint32_t name, int32_t width, int32_t height,
 71              uint32_t stride, uint32_t format)
 72{
 73	wl_resource_post_error(
 74	    resource, WL_DRM_ERROR_INVALID_NAME,
 75	    "GEM names are not supported, use a PRIME fd instead");
 76}
 77
 78static void
 79create_planar_buffer(struct wl_client *client, struct wl_resource *resource,
 80                     uint32_t id, uint32_t name, int32_t width, int32_t height,
 81                     uint32_t format, int32_t offset0, int32_t stride0,
 82                     int32_t offset1, int32_t stride1, int32_t offset2,
 83                     int32_t stride2)
 84{
 85	wl_resource_post_error(resource, WL_DRM_ERROR_INVALID_FORMAT,
 86	                       "planar buffers are not supported\n");
 87}
 88
 89static void
 90create_prime_buffer(struct wl_client *client, struct wl_resource *resource,
 91                    uint32_t id, int32_t fd, int32_t width, int32_t height,
 92                    uint32_t format, int32_t offset0, int32_t stride0,
 93                    int32_t offset1, int32_t stride1, int32_t offset2,
 94                    int32_t stride2)
 95{
 96	struct wld_buffer *buffer;
 97	struct wl_resource *buffer_resource;
 98	union wld_object object = {.i = fd};
 99
100	buffer = wld_import_buffer(swc.drm->context, WLD_DRM_OBJECT_PRIME_FD,
101	                           object, width, height, format, stride0);
102	close(fd);
103
104	if (!buffer) {
105		goto error0;
106	}
107
108	buffer_resource = wayland_buffer_create_resource(
109	    client, wl_resource_get_version(resource), id, buffer);
110
111	if (!buffer_resource) {
112		goto error1;
113	}
114
115	return;
116
117error1:
118	wld_buffer_unreference(buffer);
119error0:
120	wl_resource_post_no_memory(resource);
121}
122
123static const struct wl_drm_interface drm_impl = {
124    .authenticate = authenticate,
125    .create_buffer = create_buffer,
126    .create_planar_buffer = create_planar_buffer,
127    .create_prime_buffer = create_prime_buffer,
128};
129
130static int
131select_card(const struct dirent *entry)
132{
133	unsigned num;
134	return sscanf(entry->d_name, "card%u", &num) == 1;
135}
136
137static bool
138find_primary_drm_device(char *path, size_t size)
139{
140	struct dirent **cards, *card = NULL;
141	int num_cards, ret;
142	unsigned index;
143	FILE *file;
144	unsigned char boot_vga;
145
146	num_cards = scandir("/dev/dri", &cards, &select_card, &alphasort);
147
148	if (num_cards == -1) {
149		return false;
150	}
151
152	for (index = 0; index < num_cards; ++index) {
153		snprintf(path, size, "/sys/class/drm/%s/device/boot_vga",
154		         cards[index]->d_name);
155
156		if ((file = fopen(path, "r"))) {
157			ret = fscanf(file, "%hhu", &boot_vga);
158			fclose(file);
159
160			if (ret == 1 && boot_vga) {
161				free(card);
162				card = cards[index];
163				DEBUG("/dev/dri/%s is the primary GPU\n", card->d_name);
164				break;
165			}
166		}
167
168		if (!card) {
169			card = cards[index];
170		} else {
171			free(cards[index]);
172		}
173	}
174
175	free(cards);
176
177	if (!card) {
178		return false;
179	}
180
181	if (snprintf(path, size, "/dev/dri/%s", card->d_name) >= size) {
182		return false;
183	}
184
185	free(card);
186	return true;
187}
188
189static bool
190find_available_crtc(drmModeRes *resources, drmModeConnector *connector,
191                    uint32_t taken_crtcs, int *crtc_index)
192{
193	int i, j;
194	uint32_t possible_crtcs;
195	drmModeEncoder *encoder;
196
197	for (i = 0; i < connector->count_encoders; ++i) {
198		encoder = drmModeGetEncoder(swc.drm->fd, connector->encoders[i]);
199		if (!encoder) {
200			continue;
201		}
202		possible_crtcs = encoder->possible_crtcs;
203		drmModeFreeEncoder(encoder);
204
205		for (j = 0; j < resources->count_crtcs; ++j) {
206			if ((possible_crtcs & (1 << j)) && !(taken_crtcs & (1 << j))) {
207				*crtc_index = j;
208				return true;
209			}
210		}
211	}
212
213	return false;
214}
215
216static void
217handle_vblank(int fd, unsigned int sequence, unsigned int sec,
218              unsigned int usec, void *data)
219{
220}
221
222static void
223handle_page_flip(int fd, unsigned int sequence, unsigned int sec,
224                 unsigned int usec, unsigned int crtc_id, void *data)
225{
226	struct drm_handler *handler = data;
227
228	handler->page_flip(handler, sec * 1000 + usec / 1000);
229}
230
231static drmEventContext event_context = {
232    .version = DRM_EVENT_CONTEXT_VERSION,
233    .vblank_handler = handle_vblank,
234    .page_flip_handler2 = handle_page_flip,
235};
236
237static int
238handle_data(int fd, uint32_t mask, void *data)
239{
240	drmHandleEvent(fd, &event_context);
241	return 1;
242}
243
244static void
245bind_drm(struct wl_client *client, void *data, uint32_t version, uint32_t id)
246{
247	struct wl_resource *resource;
248
249	resource = wl_resource_create(client, &wl_drm_interface, version, id);
250	if (!resource) {
251		wl_client_post_no_memory(client);
252		return;
253	}
254	wl_resource_set_implementation(resource, &drm_impl, NULL, NULL);
255
256	if (version >= 2) {
257		wl_drm_send_capabilities(resource, WL_DRM_CAPABILITY_PRIME);
258	}
259
260	wl_drm_send_device(resource, drm.path);
261	wl_drm_send_format(resource, WL_DRM_FORMAT_XRGB8888);
262	wl_drm_send_format(resource, WL_DRM_FORMAT_ARGB8888);
263}
264
265bool
266drm_initialize(void)
267{
268	uint64_t val;
269	char primary[PATH_MAX];
270
271	if (!find_primary_drm_device(primary, sizeof(primary))) {
272		ERROR("Could not find DRM device\n");
273		goto error0;
274	}
275
276	swc.drm->fd = launch_open_device(primary, O_RDWR | O_CLOEXEC);
277	if (swc.drm->fd == -1) {
278		ERROR("Could not open DRM device at %s\n", primary);
279		goto error0;
280	}
281	if (drmSetClientCap(swc.drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) < 0) {
282		ERROR("Could not enable DRM universal planes\n");
283		goto error1;
284	}
285	if (drmGetCap(swc.drm->fd, DRM_CAP_CURSOR_WIDTH, &val) < 0) {
286		val = 64;
287	}
288	swc.drm->cursor_w = val;
289	if (drmGetCap(swc.drm->fd, DRM_CAP_CURSOR_HEIGHT, &val) < 0) {
290		val = 64;
291	}
292	swc.drm->cursor_h = val;
293	swc.backend = &swc.drm->backend;
294	swc.backend->cursor_width = swc.drm->cursor_w;
295	swc.backend->cursor_height = swc.drm->cursor_h;
296
297	drm.path = drmGetRenderDeviceNameFromFd(swc.drm->fd);
298	if (!drm.path) {
299		ERROR("Could not determine render node path\n");
300		goto error1;
301	}
302
303	if (!(swc.drm->context = wld_drm_create_context(swc.drm->fd))) {
304		ERROR("Could not create WLD DRM context\n");
305		goto error1;
306	}
307
308	if (!(swc.drm->renderer = wld_create_renderer(swc.drm->context))) {
309		ERROR("Could not create WLD DRM renderer\n");
310		goto error2;
311	}
312	swc.backend->context = swc.drm->context;
313	swc.backend->renderer = swc.drm->renderer;
314
315	drm.event_source = wl_event_loop_add_fd(
316	    swc.event_loop, swc.drm->fd, WL_EVENT_READABLE, &handle_data, NULL);
317
318	if (!drm.event_source) {
319		ERROR("Could not create DRM event source\n");
320		goto error3;
321	}
322
323	if (!wld_drm_is_dumb(swc.drm->context)) {
324		drm.global = wl_global_create(swc.display, &wl_drm_interface, 2, NULL,
325		                              &bind_drm);
326		if (!drm.global) {
327			ERROR("Could not create wl_drm global\n");
328			goto error4;
329		}
330
331		drm.dmabuf = swc_dmabuf_create(swc.display);
332		if (!drm.dmabuf) {
333			WARNING("Could not create wp_linux_dmabuf global\n");
334		}
335	}
336
337	return true;
338
339error4:
340	wl_event_source_remove(drm.event_source);
341error3:
342	wld_destroy_renderer(swc.drm->renderer);
343error2:
344	wld_destroy_context(swc.drm->context);
345error1:
346	close(swc.drm->fd);
347error0:
348	return false;
349}
350
351void
352drm_finalize(void)
353{
354	if (drm.global) {
355		wl_global_destroy(drm.global);
356	}
357	wl_event_source_remove(drm.event_source);
358	wld_destroy_renderer(swc.drm->renderer);
359	wld_destroy_context(swc.drm->context);
360	free(drm.path);
361	close(swc.drm->fd);
362}
363
364bool
365drm_create_screens(struct wl_list *screens)
366{
367	drmModePlaneRes *plane_ids;
368	drmModeRes *resources;
369	drmModeConnector *connector;
370	struct plane *plane, *cursor_plane;
371	struct output *output;
372	uint32_t i, taken_crtcs = 0;
373	struct wl_list planes;
374
375	plane_ids = drmModeGetPlaneResources(swc.drm->fd);
376	if (!plane_ids) {
377		ERROR("Could not get DRM plane resources\n");
378		return false;
379	}
380	wl_list_init(&planes);
381	for (i = 0; i < plane_ids->count_planes; ++i) {
382		plane = plane_new(plane_ids->planes[i]);
383		if (plane) {
384			wl_list_insert(&planes, &plane->link);
385		}
386	}
387	drmModeFreePlaneResources(plane_ids);
388
389	resources = drmModeGetResources(swc.drm->fd);
390	if (!resources) {
391		struct plane *p, *ptmp;
392
393		wl_list_for_each_safe(p, ptmp, &planes, link)
394			plane_destroy(p);
395		ERROR("Could not get DRM resources\n");
396		return false;
397	}
398	for (i = 0; i < resources->count_connectors;
399	     ++i, drmModeFreeConnector(connector)) {
400		connector = drmModeGetConnector(swc.drm->fd, resources->connectors[i]);
401
402		if (connector->connection == DRM_MODE_CONNECTED) {
403			int crtc_index;
404
405			if (!find_available_crtc(resources, connector, taken_crtcs,
406			                         &crtc_index)) {
407				WARNING("Could not find CRTC for connector %d\n", i);
408				continue;
409			}
410
411			cursor_plane = NULL;
412			wl_list_for_each(plane, &planes, link)
413			{
414				if (plane->type == DRM_PLANE_TYPE_CURSOR &&
415				    plane->possible_crtcs & 1 << crtc_index) {
416					wl_list_remove(&plane->link);
417					cursor_plane = plane;
418					break;
419				}
420			}
421			if (!cursor_plane) {
422				WARNING("Could not find cursor plane for CRTC %d\n",
423				        crtc_index);
424			}
425
426			if (!(output = output_new(connector))) {
427				continue;
428			}
429
430			output->screen =
431			    screen_new(resources->crtcs[crtc_index], output, cursor_plane);
432			output->screen->id = crtc_index;
433			taken_crtcs |= 1 << crtc_index;
434
435			wl_list_insert(screens, &output->screen->link);
436		}
437	}
438	drmModeFreeResources(resources);
439
440	return true;
441}
442
443enum { WLD_USER_OBJECT_FRAMEBUFFER = WLD_USER_ID };
444
445struct framebuffer {
446	struct wld_exporter exporter;
447	struct wld_destructor destructor;
448	uint32_t id;
449};
450
451static bool
452framebuffer_export(struct wld_exporter *exporter, struct wld_buffer *buffer,
453                   uint32_t type, union wld_object *object)
454{
455	struct framebuffer *framebuffer =
456	    wl_container_of(exporter, framebuffer, exporter);
457
458	switch (type) {
459	case WLD_USER_OBJECT_FRAMEBUFFER:
460		object->u32 = framebuffer->id;
461		break;
462	default:
463		return false;
464	}
465
466	return true;
467}
468
469static void
470framebuffer_destroy(struct wld_destructor *destructor)
471{
472	struct framebuffer *framebuffer =
473	    wl_container_of(destructor, framebuffer, destructor);
474
475	drmModeRmFB(swc.drm->fd, framebuffer->id);
476	free(framebuffer);
477}
478
479uint32_t
480drm_get_framebuffer(struct wld_buffer *buffer)
481{
482	struct framebuffer *framebuffer;
483	union wld_object object;
484	int ret;
485
486	if (!buffer) {
487		return 0;
488	}
489
490	if (wld_export(buffer, WLD_USER_OBJECT_FRAMEBUFFER, &object)) {
491		return object.u32;
492	}
493
494	if (!wld_export(buffer, WLD_DRM_OBJECT_HANDLE, &object)) {
495		ERROR("Could not get buffer handle\n");
496		return 0;
497	}
498
499	if (!(framebuffer = malloc(sizeof(*framebuffer)))) {
500		return 0;
501	}
502
503	ret = drmModeAddFB2(swc.drm->fd, buffer->width, buffer->height,
504	                    buffer->format, (uint32_t[4]){object.u32},
505	                    (uint32_t[4]){buffer->pitch}, (uint32_t[4]){0},
506	                    &framebuffer->id, 0);
507	if (ret < 0) {
508		free(framebuffer);
509		return 0;
510	}
511
512	framebuffer->exporter.export = &framebuffer_export;
513	wld_buffer_add_exporter(buffer, &framebuffer->exporter);
514	framebuffer->destructor.destroy = &framebuffer_destroy;
515	wld_buffer_add_destructor(buffer, &framebuffer->destructor);
516
517	return framebuffer->id;
518}