Identifying DRM devices
Some Wayland protocols need to mention DRM devices to clients, such as wp_linux_dmabuf
or wp_dmabuf_lease_manager
. Being able to reliably talk about them across the protocol, or even in general, is surprisingly complicated. The discussions about this are spread over several places, so I'll compile my understanding of it here.
Possible methods
- Pass path to dev node as string, e.g. "/dev/dri/card0"
With sandboxed or otherwise chrooted clients, it may not seem like the best idea to pass the compositor's view of paths onto the client. However, a lot of things already assume that /dev/dri
is set up correctly, for example, Vulkan device enumeration. Flatpak already sets this up correctly.
Relying on /dev/dri
existing and sharing paths with the compositor seems like a reasonable assumption for any client that is expected to do any graphics.
Using this has a somewhat awkward consequence of having to refer to the "primary" (card*) node or "render" (renderD*) node, even if we want to talk about the device more abstractly. Not every DRM device has rendering capabilities, and may not have a render node, but every DRM device has a primary node, even if they are a render-only device.
A client isn't expected to have the permissions to open primary nodes, but should be able to open render nodes. Even if a client is given a path to a primary node, they should be able to find the correct path to the render node by looking at /sys/dev/char/$major:$minor/device/drm
(Note: libdrm can do this for you). See makedev(3) for a description of major and minor, which can be taken from the st_rdev
member of the stat(2) struct.
- Pass device minor as u32, e.g. 0
Essentially the same as the path, but we skip the stat step. The DRM major number is 226 on Linux, so we could even skip sending that.
- Pass a file descriptor
This could've been used to punch a hole into a sandbox to allow for clients to render, but with Vulkan's device enumeration forcing /dev/dri
to be set up correctly, there is no real utility in doing this any more. It also raises the question of whether we pass a primary node fd or render node fd to the client, and other similar questions. The compositor must also be very careful to not share file descriptions with clients, because GEM names and DRM master permissions are per-file, and you could accidentally give clients access to things they shouldn't have access to.
- Pass
ID_PATH_TAG
udev property as string, e.g.pci-0000_07_00_0
,pci-0000_02_00_0-usb-0_8_1_0
This is generated by udev and is used in several other things for persistent device naming. I'm not sure if there is any meaningful difference between ID_PATH
and ID_PATH_TAG
here, but Mesa already uses ID_PATH_TAG
, so we may as well standardize on that. This describes where a device is attached to the system, rather than what it actually is.
This has the advantage of being able to talk about a device without any particular reference to the primary or render node.
Because this is a complex format which involves walking the /sys tree, I'm sure people don't really want to implement this themselves. This would mean that we're basically making libudev a dependency of everyone who wants to use these protocols.
Persistence across reboots
While this isn't important for the Wayland protocol itself, having a name that is persistent across reboots would be good for configuration files. The order that devices get enumerated is not stable, so a system with multiple DRM devices could easily have card0 and card1 swapped after rebooting.
ID_PATH
/ID_PATH_TAG
is the only solution that has a stable name, assuming it remains plugged into the same port. This is why Mesa uses it to identify devices.
Hotplugging
Hardware with PCI hotplugging support isn't particularly common, and isn't known to work well, but with Thunderbolt and other DRM devices attached via USB, it does become something that needs to be taken into account.
When a device is hot-unplugged, the driver should unregister it from the DRM core, freeing up the minor number to be used for the next device that gets hot-plugged. This leads to a potential race:
- Compositor sends "/dev/dri/card1" to the client
- /dev/dri/card1 is unplugged.
- New device is plugged in; gets assigned /dev/dri/card1
- Client receives "/dev/dri/card1" and opens the wrong device
Note: Some drivers don't unregister their devices properly, so their minor number will not be reused. This is a driver bug.
Even file descriptors aren't entirely immune from this, because if you fstat(2)
the file descriptor of an unplugged device, you will still get the previous minor number, which may have already been reassigned.
ID_PATH_TAG
is immune from minor numbers being reassigned, but then also has its own issue of something different being plugged into the same port as the removed device and getting assigned the exact same ID_PATH_TAG
. This could be fixed by also sending more information (e.g. PCI vendor and product) too.
But I don't think any solution is immune from all theoretical races, as long as we have to open paths like /dev/dri/card*
which could be reassigned at some point. Perhaps the problem is niche enough to just ignore and say "it works well enough 99.999% of the time" and not worth adding significantly more complexity to fix.
Non-Linux compatibility
While DRM is a Linux interface, it has been adopted by several BSDs, so they should be taken into account.
/dev/dri
is not used across every OS, but libdrm does have a defines for it for each OS instead.
Some BSDs use major/minor numbers in a different way than Linux does. I'm not really sure how, though.
ID_PATH_TAG
is rather Linux specific, as it relies on /sys, and the BSDs generally seem to have issues with udev. FreeBSD seems to have its own Linux /sys compatiblity. I do not know if FreeBSD has its own native equivalent, or what OpenBSD, NetBSD, or any other BSD has.
libdrm has typically been the place where a lot of the abstractions over different OSs happen, so this would be the place which makes the most sense to have any kind of shared helpers. If libdrm generated ID_PATH_TAG
strings itself (either via libudev or walking /sys itself) and could also open a DRM device based on that string, it would allow a place for the BSDs to generate their own unique strings using whatever native mechanism. Users would be expected to treat the string as opaque.
I don't know if libdrm really wants to have libudev as a dependency. The same logic could be reimplemented, but maintaining this in two places does seem annoying. However, I don't think this part of the libudev API depends on the udev daemon to be running.