#DRM #DRI #udev

DRM (Direct Rendering Manager) refers to a device driver for a graphics card supporting DRI (Direct Rendering Infrastructure). I think DRM and DRI should be sorted out next time. In this note, only the method of finding the DRM device node corresponding to the main graphic card is summarized.

In general, it is safe to say that /dev/dri/card0 is a device node corresponding to the main graphics card. However, unless the graphic card does not support DRI or multiple graphic cards are used. Therefore, there is a need for a way to identify the main graphics card in a more reliable way.

Method 1. Using udevadm

udevadm prints the device properties corresponding to the device node created by udev. The main graphics card has 1 as the value of the boot_vga attribute. Therefore, if the value of the boot_vga property is 1 among the properties of a specific device node output by udevadm, the corresponding node corresponds to the main graphic card. If you want to see the properties of the graphic card corresponding to “/dev/dri/card0”, enter the following command. “/dev/dri/card0” is a term related to udev and seems to be called DEVNAME.

	$ udevadm info --query=all --name=/dev/dri/card0
	
	P: /devices/pci0000:00/0000:00:0f.0/drm/card0
	N: dri/card0
	E: DEVNAME=/dev/dri/card0
	E: DEVPATH=/devices/pci0000:00/0000:00:0f.0/drm/card0
	E: DEVTYPE=drm_minor
	E: ID_FOR_SEAT=drm-pci-0000_00_0f_0
	E: ID_PATH=pci-0000:00:0f.0
	E: ID_PATH_TAG=pci-0000_00_0f_0
	E: MAJOR=226
	E: MINOR=0
	E: SUBSYSTEM=drm
	E: TAGS=:master-of-seat:uaccess💺
	E: USEC_INITIALIZED=8444405

udev creates device nodes under “/dev” for hotplug-connected devices. In fact, udev is said to configure “/dev” with device information provided by sysfs. Device information is provided hierarchically under “/sys”, and this path is called DEVPATH in terms of udev. From the above result, it can be seen that the DEVPATH corresponding to the DEVNAME “/dev/dri/card0” is “/devices/pci0000:00/0000:00:0f.0/drm/card0”. Even with DEVPATH, device information can be inquired as shown below.

	$ udevadm info -a -p /sys/devices/pci0000:00/0000:00:0f.0/drm/card0
	
	Udevadm info starts with the device specified by the devpath and then
	walks up the chain of parent devices. It prints for every device
	found, all possible attributes in the udev rules key format.
	A rule to match, can be composed by the attributes of the device
	and the attributes from one single parent device.
	
	  looking at device '/devices/pci0000:00/0000:00:0f.0/drm/card0':
	    KERNEL=="card0"
	    SUBSYSTEM=="drm"
	    DRIVER==""
	
	  looking at parent device '/devices/pci0000:00/0000:00:0f.0':
	    KERNELS=="0000:00:0f.0"
	    SUBSYSTEMS=="pci"
	    DRIVERS=="vmwgfx"
	    ATTRS{boot_vga}=="1"
	    ATTRS{broken_parity_status}=="0"
	    ATTRS{class}=="0x030000"
	    ATTRS{consistent_dma_mask_bits}=="32"
	    ATTRS{d3cold_allowed}=="0"
	    ATTRS{device}=="0x0405"
	    ATTRS{dma_mask_bits}=="32"
	    ATTRS{driver_override}=="(null)"
	    ATTRS{enable}=="1"
	    ATTRS{irq}=="16"
	    ATTRS{local_cpulist}=="0-1"
	    ATTRS{local_cpus}=="00000000,00000000,00000000,00000003"
	    ATTRS{msi_bus}=="1"
	    ATTRS{numa_node}=="-1"
	    ATTRS{subsystem_device}=="0x0405"
	    ATTRS{subsystem_vendor}=="0x15ad"
	    ATTRS{vendor}=="0x15ad"
	
	  looking at parent device '/devices/pci0000:00':
	    KERNELS=="pci0000:00"
	    SUBSYSTEMS==""
	    DRIVERS==""

Method 2. Method using libudev

In C code, you can use libudev to find the device node corresponding to the main graphic card. This is reflected in mutter, the compositor used by weston and GNOME. This section considers the case where more than one graphics card is installed. Before the patch, it was hard-coded in the form of “open("/dev/dri/card0")”. The patch finds the boot_vga property by traversing the device nodes matching “drm/card[0-9]*” using udev_enumerate_*() functions. In the end, it obtains a udev_device object (a structure provided by libudev) with the corresponding properties, that is, the main graphic card information. Details of the patch can be found at the link below.

find_primary_gpu(), a function added as a patch, is very intuitive from its name. Below is a part of the call stack that runs from the inside of weston to the corresponding function.

  • libweston/compositor-drm.c : find_primary_gpu - libweston/compositor-drm.c : drm_backend_create - libweston/compositor-drm.c : weston_backend_init(WL_EXPORT) - libweston/compositor.c : weston_load_module(WL_EXPORT)

Note, weston_load_module()

Two strings “name” and “entrypoint” are input. First, the library located in “.libs/[name]” is dynamically loaded using “name”. “name” refers to the file name of the library to be loaded. weston maps the file name of the library to be loaded in each backend by hard-coding. This is an array of strings called backend_map[] in “libweston/compositor.c”. “[drm|fbdev|headless|rdp|wayland|x11]-backend.so” exists in the array, and the index for reference to the array uses the enum type “weston_compositor_backend” of “libweston/compositor.h”. That is, “backend_map[WESTON_BACKEND_DRM]” is mapped to the string “drm-backend.so”. This string is passed as “name”, the first argument of the weston_load_mudle() function. The types of backends and strings mapped to each are as follows.

/// libweston/compositor.h
enum weston_compositor_backend {
	WESTON_BACKEND_DRM,
	WESTON_BACKEND_FBDEV,
	WESTON_BACKEND_HEADLESS,
	WESTON_BACKEND_RDP,
	WESTON_BACKEND_WAYLAND,
	WESTON_BACKEND_X11,
};
			
// libweston/compositor.c
static const char * const backend_map[] = {
	[WESTON_BACKEND_DRM] =		"drm-backend.so",
	[WESTON_BACKEND_FBDEV] =	"fbdev-backend.so",
	[WESTON_BACKEND_HEADLESS] =	"headless-backend.so",
	[WESTON_BACKEND_RDP] =		"rdp-backend.so",
	[WESTON_BACKEND_WAYLAND] =	"wayland-backend.so",
	[WESTON_BACKEND_X11] =		"x11-backend.so",
};

weston_load_module() loads the library located in “name”, the first string received as an argument, with the function dlopen(). The second string received as an argument, “entrypoint”, means the name of a function to be called in the loaded library. In other words, using dlsym, find the address of “entrypoint”, that is, the function weston_backend_init(), and call it. libdl helps you to load the library at runtime and access the symbols of the library. The call stack until reaching weston_load_module() is as follows.

  • libweston/compositor.c : weston_load_module(WL_EXPORT) - libweston/compositor.c : weston_compositor_load_backend(WL_EXPORT) - compositor/main.c : weston_compositor_load_backend() - compositor/main.c : load_backend() - compositor/main.c : main()