Appendix · reference

libdrm platform enumeration

Why the PinePhone Pro needs a patched libdrm until FreeBSD platform DRM discovery grows upstream support.

The Failure

FreeBSD’s stock graphics/libdrm path treats FreeBSD DRM devices like PCI devices. That is good enough for a desktop GPU, but the PinePhone Pro’s two DRM devices are FDT/platform devices:

hw.dri.0.busid: platform:rk_drm0
hw.dri.0.name: rockchip 0x7a
hw.dri.1.busid: platform:panfrost0
hw.dri.1.name: panfrost 0x79

Mesa and wlroots ask libdrm for the device list before selecting a renderer. With an unpatched libdrm, drmGetDevices2() returns zero useful devices, Mesa’s loader reports failed device information, and Sway either falls back to the wrong path or fails before renderer creation. The visible symptom is glxinfo -B reporting llvmpipe or Sway not reaching the Panfrost renderer at all.

Kernel Side

Our DRM overlay creates the metadata that userland needs. drm_sysctl_init() adds hw.dri.N.name, hw.dri.N.busid, and hw.dri.N.modesetting; both the Rockchip display driver and Panfrost call it after registering their DRM devices. The busid helper emits platform:<device> for non-PCI parents.

src/sys/dev/drm/freebsd/drm_sysctl.c c · L188-225
188 drm_add_busid_modesetting(struct drm_device *dev, struct sysctl_ctx_list *ctx,
189 struct sysctl_oid *top)
190 {
191 struct sysctl_oid *oid;
192 device_t bsddev;
193 int domain, bus, slot, func;
194
195 bsddev = dev->dev;
196
197 printf("drm_busid: bsddev=%s parent=%s\n", device_get_nameunit(bsddev), device_get_name(device_get_parent(bsddev)));
198
199 /*
200 * Check if this is a platform (FDT) device or PCI device.
201 * Platform devices use "platform:name" busid format.
202 */
203 if (device_get_parent(bsddev) != NULL &&
204 strcmp(device_get_name(device_get_parent(bsddev)), "pci") != 0) {
205 /* Platform device - use driver name and unit */
206 snprintf(dev->busid_str, sizeof(dev->busid_str),
207 "platform:%s", device_get_nameunit(bsddev));
208 } else {
209 /* PCI device */
210 domain = pci_get_domain(bsddev);
211 bus = pci_get_bus(bsddev);
212 slot = pci_get_slot(bsddev);
213 func = pci_get_function(bsddev);
214 snprintf(dev->busid_str, sizeof(dev->busid_str),
215 "pci:%04x:%02x:%02x.%d", domain, bus, slot, func);
216 }
217 printf("drm_busid: result=%s\n", dev->busid_str);
218 oid = SYSCTL_ADD_STRING(ctx, SYSCTL_CHILDREN(top), OID_AUTO, "busid",
219 CTLFLAG_RD, dev->busid_str, 0, NULL);
220 if (oid == NULL)
221 return (-ENOMEM);
222 dev->modesetting = (dev->driver->driver_features & DRIVER_MODESET) != 0;
223 oid = SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(top), OID_AUTO,
224 "modesetting", CTLFLAG_RD, &dev->modesetting, 0, NULL);
225 if (oid == NULL)

That kernel-side bridge landed in b3c2366 DRM sysctl + libdrm platform device support , and Panfrost’s attach path calls it in 1f895ec panfrost: call drm_sysctl_init so libdrm can enumerate the device .

Userland Patch

The libdrm port overlay lives in the repo at ports/graphics/libdrm/files/patch-xf86drm.c. It has three jobs:

ports/graphics/libdrm/files/patch-xf86drm.c diff · L1-105
1 --- xf86drm.c.orig 2026-04-06 16:08:04.207080000 -0500
2 +++ xf86drm.c 2026-04-06 16:10:00.969430000 -0500
3 @@ -3636,8 +3636,35 @@
4 return DRM_BUS_VIRTIO;
5 }
6 return subsystem_type;
7 -#elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__)
8 +#elif defined(__OpenBSD__) || defined(__DragonFly__)
9 return DRM_BUS_PCI;
10 +#elif defined(__FreeBSD__)
11 + {
12 + char dname[SPECNAMELEN];
13 + char sysctl_name[64];
14 + char sysctl_val[256];
15 + size_t sysctl_len;
16 + int id;
17 +
18 + if (!devname_r(makedev(maj, min), S_IFCHR, dname, sizeof(dname)))
19 + return DRM_BUS_PCI;
20 +
21 + /* Extract card number from dri/cardN or dri/renderDN */
22 + if (sscanf(dname, "dri/card%d", &id) != 1) {
23 + if (sscanf(dname, "dri/renderD%d", &id) == 1)
24 + id -= 128;
25 + else
26 + return DRM_BUS_PCI;
27 + }
28 +
29 + snprintf(sysctl_name, sizeof(sysctl_name), "hw.dri.%d.busid", id);
30 + sysctl_len = sizeof(sysctl_val);
31 + if (sysctlbyname(sysctl_name, sysctl_val, &sysctl_len, NULL, 0) == 0) {
32 + if (strncmp(sysctl_val, "platform:", 9) == 0)
33 + return DRM_BUS_PLATFORM;
34 + }
35 + return DRM_BUS_PCI;
36 + }
37 #else
38 #warning "Missing implementation of drmParseSubsystemType"
39 return -EINVAL;
40 @@ -4316,6 +4343,34 @@
41 free(name);
42
43 return 0;
44 +#elif defined(__FreeBSD__)
45 + {
46 + char dname[SPECNAMELEN];
47 + char sysctl_name[64];
48 + char sysctl_val[256];
49 + size_t sysctl_len;
50 + int id;
51 +
52 + if (!devname_r(makedev(maj, min), S_IFCHR, dname, sizeof(dname)))
53 + return -EINVAL;
54 + if (sscanf(dname, "dri/card%d", &id) != 1) {
55 + if (sscanf(dname, "dri/renderD%d", &id) == 1)
56 + id -= 128;
57 + else
58 + return -EINVAL;
59 + }
60 + snprintf(sysctl_name, sizeof(sysctl_name), "hw.dri.%d.busid", id);
61 + sysctl_len = sizeof(sysctl_val);
62 + if (sysctlbyname(sysctl_name, sysctl_val, &sysctl_len, NULL, 0) != 0)
63 + return -EINVAL;
64 + /* busid is "platform:NAME", extract NAME as fullname */
65 + if (strncmp(sysctl_val, "platform:", 9) == 0)
66 + strncpy(fullname, sysctl_val + 9, DRM_PLATFORM_DEVICE_NAME_LEN - 1);
67 + else
68 + strncpy(fullname, sysctl_val, DRM_PLATFORM_DEVICE_NAME_LEN - 1);
69 + fullname[DRM_PLATFORM_DEVICE_NAME_LEN - 1] = '\0';
70 + return 0;
71 + }
72 #else
73 #warning "Missing implementation of drmParseOFBusInfo"
74 return -EINVAL;
75 @@ -4376,6 +4431,36 @@
76
77 free(*compatible);
78 return err;
79 +#elif defined(__FreeBSD__)
80 + {
81 + char dname[SPECNAMELEN];
82 + char sysctl_name[64];
83 + char sysctl_val[256];
84 + size_t sysctl_len;
85 + int id;
86 +
87 + if (!devname_r(makedev(maj, min), S_IFCHR, dname, sizeof(dname)))
88 + return -EINVAL;
89 + if (sscanf(dname, "dri/card%d", &id) != 1) {
90 + if (sscanf(dname, "dri/renderD%d", &id) == 1)
91 + id -= 128;
92 + else
93 + return -EINVAL;
94 + }
95 + snprintf(sysctl_name, sizeof(sysctl_name), "hw.dri.%d.name", id);
96 + sysctl_len = sizeof(sysctl_val);
97 + if (sysctlbyname(sysctl_name, sysctl_val, &sysctl_len, NULL, 0) != 0)
98 + return -EINVAL;
99 + /* Use driver name as compatible string */
100 + /* Strip trailing hex after space: "rockchip 0x64" -> "rockchip" */
101 + char *sp = strchr(sysctl_val, ' ');
102 + if (sp) *sp = '\0';
103 + *compatible = calloc(2, sizeof(char*));
104 + if (!*compatible) return -ENOMEM;
105 + (*compatible)[0] = strdup(sysctl_val);

The patch was pinned as a ports overlay in 3c6af28 ports: pin libdrm patch for FreeBSD platform DRM enumeration . The ports/graphics/libdrm/README.md file is the build and verification recipe.

Verification

The patched binary should contain all of these strings:

strings /usr/local/lib/libdrm.so.2 | grep -E 'hw\.dri|platform:'
# hw.dri.%d.busid
# platform:
# hw.dri.%d.name
# hw.dri.%d.modesetting

The live behavior check is:

glxinfo -B
# OpenGL renderer string: Mali-T860 (Panfrost)

If a package install replaces libdrm, reinstall the patched aarch64 package and lock it again. That exact regression happened during the 2026-05-05 GPU stress pass when mesa-demos and glmark2 pulled the stock package back in.

◐ partial This is still a local patch, not an upstreamable final design. One known rough edge remains: render-node minor mapping uses renderD128 -> hw.dri.0, which is enough for Sway’s current path but is not a complete FreeBSD platform-device enumeration model.