DRM is the bridge between the Linux kernel and actual software running atop Linux, for most operations which involve a graphics card. For example, X11 and Wayland actually send drawing commands to the graphics card using DRM.
In this post I’ll be talking about how to write software which communicates with the graphics card directly, completely bypassing X. Do mind that I am also learning while I write, so some information might be inaccurate and also I’m jumping through unnecessary hoops, from the perspective of just getting things done.
I’ll be using C++.
libdrm
The kernel DRM API is file based, which means you can open a file representing
the graphics card (usually /dev/dri/card0
) and use that file to send actual
commands to the card, using the ioctl()
syscall with the opened file
representing the card.
Fortunately, there is also a library conveniently named libdrm that
simplifies all that. Keep in mind that all that it does is what we mentioned
above: opening a file and sending commands through ioctl()
syscalls.
In C++ a simple program that interacts with the video card through DRM might look like this at the top level:
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
// The file which represents the card
static const char gpu_dev[] = "/dev/dri/card0";
int open_gpu() {
return open(gpu_dev, O_RDWR);
}
int close_gpu(int gpu) {
return close(gpu);
}
int main(int arg_count, char** arg_vector) {
const int gpu = open_gpu();
assert(gpu >= 0);
// some ioctl() magic here...
assert(close_gpu(gpu) >= 0);
return 0;
}
And as a sanity check we can try to run it in a Linux machine:
❯ clang++ drm-part-1.cpp -o drm-part-1
❯ ./drm-part-1
❯
Surely enough, it runs! But it doesn’t do anything yet. It needs that ioctl()
magic, but at least no assertions failed and we know we can open the video card
file now!
Lets include another library which has some very handy structs and also
definitions of parameters for ioctl()
: libdrm/drm.h
. While we are at it,
lets also include sys/ioctl.h
and iostream
for syscalls and printing.
#include <libdrm/drm.h>
#include <sys/ioctl.h>
#include <iostream>
Driver Version
The first interaction that we are going to have with the card is not going to be
with the card itself but just with the driver (bummer, I know). Lets try to
retrieve the driver version using a ioctl()
call.
There is a definition for a struct which holds exactly this data in
libdrm/drm.h
, drm_version_t
:
struct drm_version {
int version_major; /**< Major version */
int version_minor; /**< Minor version */
int version_patchlevel; /**< Patch level */
__kernel_size_t name_len; /**< Length of name buffer */
char *name; /**< Name of driver */
__kernel_size_t date_len; /**< Length of date buffer */
char *date; /**< User-space buffer to hold date */
__kernel_size_t desc_len; /**< Length of desc buffer */
char *desc; /**< User-space buffer to hold desc */
};
To get the version from the driver we need to do three things:
- Initialize a
drm_version_t
, notice there are some char * fields for which we need to allocate memory. - Call
ioctl()
with the appropriate parameters and a pointer to ourdrm_version_t
. - Print out the information we get and free everything.
Seems easy enough right? Lets do it!
drm_version_t create_version() {
drm_version_t version;
version.name = new char[256];
version.date = new char[256];
version.desc = new char[256];
return version;
}
And:
void delete_version(const drm_version_t &version) {
delete version.name;
delete version.date;
delete version.desc;
}
To print it out:
void print_version(const drm_version_t &version) {
std::cout << std::string(version.name, version.name_len) << std::endl
<< std::string(version.desc, version.desc_len) << std::endl
<< version.version_major << "."
<< version.version_minor << "."
<< version.version_patchlevel << std::endl;
}
And finally, we can call all that in our main()
, notice the ioctl()
call
using the DRM_IOCTL_VERSION
value, which is defined in libdrm
so we don’t
have to worry about the specific values passed:
int main(int arg_count, char** arg_vector) {
const int gpu = open_gpu();
assert(gpu >= 0);
drm_version_t version = create_version();
assert(ioctl(gpu, DRM_IOCTL_VERSION, &version) == 0);
print_version(version);
delete_version(version);
assert(close_gpu(gpu) >= 0);
return 0;
}
After a little compiling, behold the power!
❯ ./drm-part-1
i915
Intel Graphics
1.6.0
We are talking to the DRM driver of my Intel graphics card! No X11 involved, this runs even directly in the tty!
Keep in mind that libdrm does a lot more than just provide the definitions and
we do not need to be calling ioctl()
directly, but for the purposes of
learning I found important know exactly how the syscalls are made to the
graphics driver.