libdrm and xf86drm.c Re: Upstreaming Proposal

David Shao davshao at
Sun Mar 12 05:32:09 UTC 2017

TL;DR Stop trying to use libdevq and just copy from it a couple
of functions.

This is what needs to be done to upstream to libdrm FreeBSD
changes to xf86drm.c.  Working code implementing these
changes exists and has been published.

Given a file descriptor fd, in the case of FreeBSD to a
device such as /dev/dri/card0 or /dev/dri/controlD64,
and assuming this fd is for some PCI graphics device,
libdrm's most complicated query is to fill in the fields
of a struct listing the numerical values for the devices's
domain, bus, device (slot), function, vendor id, device id,
subvendor id, subdevice id, and revision id.
The kernel knows this information.

The cleanest implementation is OpenBSD's which simply
defines an ioctl and a struct whose fields the kernel simply
fills in.

Especially for kernel drm, unused ioctl numbers seems scarce,
so this approach has not been taken in general for all OSes.

If one is not going to ioctl an file descriptor, what about
fstat-ing it?  From fstat one can obtain at least three pieces
of information for the underlying device: major number, minor
number, and the device name.  The only reason major and
minor manipulations occur in xf86drm.c is for Linux, and
that is because these numbers are used to lookup entries
under /sys.  The BSDs should basically IGNORE all of the
major / minor code and concentrate on the device name.

For FreeBSD the code should parse the device name taking
care to extract the device name number at the end, for
example the 0 from /dev/dri/card0.  Unfortunately the PCI
device information is split into two separate branches
of the sysctl tree, part under hw.dri and part under
dev.vgapci.  The device name
number (0) should be used to look up under hw.dri.0 the
PCI device's domain, bus, slot, and function.  Then this
information should be dragged over to the dev.vgapci branch
to search for the matching entry with the same values, after
which one reads off the vendor, device, subvendor, and
subdevice ids (and guesses the revision id).  The code to implement
this is not that difficult, just a series of snprintf,
sscanf, and sysctl calls.

Unfortunately FreeBSD ports keeps insisting that what is the
content of only two fairly trivial functions should be obtained
only by linking with the libdevq project.  This is what makes it
impossible for the patches to be upstreamed, first to mesa3d and now to libdrm.

The current drmBSDDeviceNameHack() misses the point
because of this need to hammer the code to use libdevq.  There
are hardcoded constants and strings that already are defined
in more general form in libdrm.  (And now that drm-next may
have introduced render nodes, how does "/dev/dri/renderD128"
compare as a string to "/dev/dri/controlD64"?)   The FreeBSD
ports patch ignores the perfectly fine string comparison code
already in libdrm to determine the type of node in

static int drmGetNodeType(const char *name)
    if (strncmp(name, DRM_PRIMARY_MINOR_NAME,
        sizeof(DRM_PRIMARY_MINOR_NAME) - 1) == 0)
        return DRM_NODE_PRIMARY;

    if (strncmp(name, DRM_CONTROL_MINOR_NAME,
        sizeof(DRM_CONTROL_MINOR_NAME ) - 1) == 0)
        return DRM_NODE_CONTROL;

    if (strncmp(name, DRM_RENDER_MINOR_NAME,
        sizeof(DRM_RENDER_MINOR_NAME) - 1) == 0)
        return DRM_NODE_RENDER;

    return -EINVAL;

But what is most disheartening about the present
drmBSDDeviceNameHack() is that it fails to return separately
for its caller the one piece of information that actually
matters for FreeBSD: the device name number, probably 0.
Instead it just jams this number onto the hacked path name
returned to the caller expecting the caller to parse this name
yet again for the number.

Given that libdrm already has code to determine the node type, maybe
try something like this:

static int
drmBSDDeviceNameHack(const char *path, char *hacked_path,
    int length, int node_type)
    int start, number, base;
    const char *errstr;

    base = drmGetMinorBase(node_type);
    if (node_type == DRM_NODE_RENDER) {
        start = sizeof(DRM_RENDER_MINOR_NAME) - 1;
    } else if (node_type == DRM_NODE_CONTROL) {
        start = sizeof(DRM_CONTROL_MINOR_NAME) - 1;
    } else {
        start = sizeof(DRM_PRIMARY_MINOR_NAME) - 1;
    number = strtonum(&(path[start]), 0, 256, &errstr) - base;
    snprintf(hacked_path, length, "card%i", number);

    return number;

Or perhaps change the libdrm node type parsing code to do all
the parsing in one shot.

Having obtained this device name number 0, we change to:

#if defined (__FreeBSD__) || defined(__DragonFly__)
    ret = drmProcessPciDevice(&d, node, node_type, maj, number,
        true, flags);

instead of using the useless minor number min.

Now because the domain, bus, slot, and function numbers
need to be dragged over to search dev.vgapci, we need an
internal API change of the form:

#if defined(__FreeBSD__)
static int drmParsePciDeviceInfoBSD(int maj, int min,
                                 drmPciDeviceInfoPtr device,
                                 drmPciBusInfoPtr info,
                                 uint32_t flags)
static int drmParsePciDeviceInfo(int maj, int min,
                                 drmPciDeviceInfoPtr device,
                                 uint32_t flags)

Working patches tested on an Intel IvyBridge integrated
graphics machine can be found in the pkgsrc-wip project
libdrm-dfbsd, patch name patch-ab.  This patch is bloated
up by a tremendous number of drmMsg debugging lines
I added and also by code to deal with DragonFly not having
a well-defined idea of a major number.

More information about the freebsd-x11 mailing list