You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 888555f
Browse filesBrowse the repository at this point in the historyBrowse files
The original kmodloader plan was modeled on kextd + IOKit personality
matching; Phase 1a-c shipped a literal port. Adding macOS to the
comparison closes the loop on "why didn't you just do what Apple
does?" and grounds the project's design heritage.
What's added:
- macOS column in the matrix (11 axes); explicit "n/a" for axes that
don't translate (scfb/vesa fallback, NVIDIA legacy version
selection)
- New axis: pre-link/boot speedup — only macOS has it
(kextcache fuses kernel + matched kexts into a single boot image)
- New §7 explaining the kextd loop, IOPCIMatch + IOProbeScore (the
design we shipped and removed), kextcache prelinking, DriverKit
(modern userspace drivers), and three reasons we don't do exactly
this on FreeBSD
- TL;DR bullet acknowledging the heritage
- macOS / IOKit references section with apple-oss-distributions
pointers
Subsequent sections renumbered (§8 too-new, §9 scorecard, §10 xlibre
takeaway, §11 recommendations, §12 references). Navigation updated.
Copy file name to clipboardExpand all lines: freebsd-gpu-detection-comparison.html
+89-5Lines changed: 89 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -83,6 +83,7 @@
83
83
<ahref="#initgfx">NomadBSD initgfx</a>
84
84
<ahref="#xlibre">xlibre</a>
85
85
<ahref="#linux">Linux udev</a>
86
+
<ahref="#macos">macOS kextd</a>
86
87
<ahref="#toonew">"Too-new" GPU case</a>
87
88
<ahref="#scorecard">Scorecard</a>
88
89
<ahref="#refs">References</a>
@@ -107,6 +108,7 @@ <h3>TL;DR</h3>
107
108
<li><strong>"Too new" failure mode is benign on FreeBSD.</strong> If <code>i915kms</code> loads but the chip isn't in its supported table, the LinuxKPI shim returns <code>ENXIO</code> from <code>pci_probe</code>. The kmod stays resident (~10MB RAM), no <code>drmn</code> child attaches, <code>vgapci0</code> keeps owning the slot, <code>vt_efifb</code> keeps owning the console, and Xorg falls back to <code>scfb</code> (UEFI) or <code>vesa</code> (BIOS). No crash, no console disruption.</li>
108
109
<li><strong>xlibre (and Xorg generally) doesn't help us pick kmods.</strong> Its detection runs <em>after</em> the DRM kmod is loaded; the vendor-switch table maps to userspace X driver names (<code>intel</code>/<code>modesetting</code>/<code>amdgpu</code>), not kmod names. Doesn't tell us which kmods to load or skip.</li>
109
110
<li><strong>Linux's udev+modprobe is the right shape</strong> — and FreeBSD has the equivalent (devmatch + linker.hints). The only reason kmodloader needs a GPU PCI scan at all is that drm-kmod doesn't declare <code>MODULE_PNP_INFO</code>. Upstream PR to drm-kmod adding it would let our GPU pass collapse into devmatch.</li>
111
+
<li><strong>macOS kextd + IOKit was our original reference architecture.</strong> Phase 1a-c shipped a literal port (.kext bundles, IOPCIMatch arrays, IOProbeScore). We ripped it out in Phase 1d because hand-curated personality plists don't scale to FreeBSD's hardware breadth — Apple's full-time staff curates IOPCIMatch tables for the SoCs Apple ships; we'd be doing the same work for every PC ever made. Apple's <code>kextcache</code> prelink optimization remains an interesting Phase 4+ idea.</li>
<td><spanclass="yesno no">No</span> — load on demand at boot</td>
234
+
<td><spanclass="yesno no">No</span></td>
235
+
<td><spanclass="yesno no">No</span></td>
236
+
<td>n/a</td>
237
+
<td><spanclass="yesno no">No</span> at the module-load layer (initramfs is a separate concept)</td>
238
+
<td><spanclass="yesno yes">Yes</span> — <code>kextcache</code> fuses kernel + matched kexts into a single boot image</td>
216
239
</tr>
217
240
</tbody>
218
241
</table>
@@ -358,7 +381,59 @@ <h3>6.2 The single difference that matters</h3>
358
381
<h3>6.3 Linux "too new" handling</h3>
359
382
<p>If the .ko loads but the live device ID isn't in the module's <code>pci_ids</code>: the driver's <code>.probe()</code> is never called, the device sits unbound. Modern kernels expose <code>i915.force_probe=</code>, <code>amdgpu.force_probe=</code> etc. for users with too-new hardware. There is no userspace "fall back to a different kernel module" mechanism — udev hands MODALIAS to libkmod, gets a hit or miss, and that's it.</p>
360
383
361
-
<h2id="toonew">7. The "too-new GPU" failure case</h2>
384
+
<h2id="macos">7. macOS: kextd + IOKit (the reference architecture)</h2>
385
+
386
+
<p>The original kmodloader plan was modeled directly on Apple's <code>kextd</code> + IOKit personality matching. Phase 1a-c shipped a literal port of that idea (<code>.kext</code> bundles in <code>/System/Library/Extensions/</code>, <code>IOKitPersonalities</code> dicts, integer probe scores, highest-scoring kext wins). Phase 1d ripped it out in favor of <code>devmatch</code> for FreeBSD-specific reasons. Worth showing the reference architecture both for design provenance and to answer "why don't you just do what Apple does?"</p>
387
+
388
+
<h3>7.1 The kextd loop</h3>
389
+
<p><code>kextd</code> is a launchd-supervised daemon (<code>com.apple.kextd.plist</code>). On startup it:</p>
390
+
<ol>
391
+
<li>Walks <code>/System/Library/Extensions/*.kext/</code> and <code>/Library/Extensions/*.kext/</code>.</li>
392
+
<li>Reads each bundle's <code>Contents/Info.plist</code>, extracts the <code>IOKitPersonalities</code> dict.</li>
393
+
<li>Hands all personalities to the kernel via <code>kextload(8)</code>-equivalent.</li>
394
+
<li>Listens on a Mach port for <code>kAppleEventLoopMessage</code>-style notifications: when a new device appears in IOKit's IORegistry, kextd is told and re-runs matching for that device.</li>
395
+
</ol>
396
+
<p>The kernel keeps a personality registry indexed by match category (<code>"PCI"</code>, <code>"USB"</code>, <code>"USBClass"</code>, etc.). When IOKit sees a new device, it walks the registry, calls <code>IOService::probe</code> on every personality whose match dict could apply, and binds the device to the highest-scoring service.</p>
397
+
398
+
<h3>7.2 IOPCIMatch + IOProbeScore (the design our Phase 1a-c shipped)</h3>
399
+
<p>An IOKit personality looks like this (Apple's actual i915 driver, simplified):</p>
<p>The probe call returns either <code>0</code> (don't match, skip) or a positive score. Higher score wins; ties broken by personality registration order. Non-matching personalities never get a probe call — the match dict is the gate.</p>
416
+
417
+
<p><strong>This is what kmodloader Phase 1a-c implemented</strong> in <code>kmodloader/personalities/*.kext/Contents/Info.plist</code> with our own probe-score algorithm. Worked correctly for hardware we hand-curated; failed scaling because FreeBSD's drm-kmod doesn't ship Info.plists with IOPCIMatch arrays, so we'd be hand-curating ~2000 device IDs ourselves — without Apple's full-time staff to maintain them.</p>
418
+
419
+
<h3>7.3 kextcache prelinking</h3>
420
+
<p>Apple's optimization for boot speed: <code>kextcache</code> fuses the kernel binary plus every kext that matched at last-boot into a single signed boot image (<code>/System/Library/PrelinkedKernels/prelinkedkernel</code>). At boot the loader maps the prelinked kernel directly — no per-boot scan of <code>/System/Library/Extensions/</code>, no per-kext load decisions, no probe loop. The matching work happened once at install time.</p>
421
+
<p>Plan §11 Phase 4 flagged this as a future optimization for kmodloader; it remains a Phase-4 idea. The Apple-shape parallel is roughly: at install time, run kmodloader's three-path scan, write the result to a manifest, have <code>/boot/loader</code> consume the manifest and pre-load on next boot. Defers all the "vendor scan + devmatch + vm_guest" work from boot to install.</p>
422
+
423
+
<h3>7.4 DriverKit (modern Apple userspace drivers)</h3>
424
+
<p>Since macOS 10.15 / iOS 14, Apple has been moving drivers out of the kernel and into sandboxed userspace processes called <code>.dext</code> bundles (DriverExtension). Each dext is a launchd-launched signed binary that talks to a small in-kernel "personality" stub via IOKit's <code>DriverKit</code> framework. Match logic stays the same (<code>IOPCIMatch</code> in the dext's Info.plist); the actual driver code runs in userspace.</p>
425
+
<p>Genuinely interesting as a long-term direction but not on kmodloader's roadmap. FreeBSD has no equivalent to DriverKit's userspace-driver kernel infrastructure today, and porting one is a project comparable to launchd itself.</p>
426
+
427
+
<h3>7.5 Why we don't do exactly this on FreeBSD</h3>
428
+
<p>We started here (Phase 1a-c) and reached a different place (Phase 1d-1g) for three reasons:</p>
429
+
<ol>
430
+
<li><strong>Apple ships finite hardware.</strong> A handful of staff curate the IOPCIMatch tables for the SoCs/GPUs Apple ships. FreeBSD targets every PC ever made; hand-curated personality plists don't scale to that hardware breadth.</li>
431
+
<li><strong>FreeBSD already has the data, just in a different format.</strong><code>linker.hints</code> built from <code>MODULE_PNP_INFO</code> macros gives us the same (vendor:device → kld) map for every in-tree driver. <code>devmatch(8)</code> is the canonical reader. We don't need to reinvent the registry; we need to use the one FreeBSD already has.</li>
432
+
<li><strong>The narrow gap (drm-kmod) is closeable with a 3-vendor map plus one frozen 699-ID list.</strong> No need for Apple-style full personality plists when the actual missing data is "which of three vendor IDs do I have."</li>
433
+
</ol>
434
+
<p>The Apple-shape design we kept: launchd supervision, ObjC implementation, plist-based config, the conceptual model of "a daemon that owns kernel-extension loading." The Apple-shape design we dropped: the per-driver IOPCIMatch personality plist as the load-decision data structure.</p>
435
+
436
+
<h2id="toonew">8. The "too-new GPU" failure case</h2>
362
437
363
438
<p>The user's specific concern: <em>what happens in our case when the device is too new for the latest drm-kmods</em>? Detailed answer with FreeBSD source citations.</p>
<strong>Loading drm-kmod / nvidia kmods speculatively is safe.</strong> Probe rejection is the designed-in path. The only cost is ~10MB resident kernel memory per loaded-but-unbound kmod. No panic, no console disruption, no Xorg breakage. The <code>vt_efifb</code> framebuffer is the universal fallback and stays in place exactly because no DRM driver attached to take it over.
405
480
</div>
406
481
407
-
<h2id="scorecard">8. Scorecard — wins and gaps</h2>
482
+
<h2id="scorecard">9. Scorecard — wins and gaps</h2>
408
483
409
484
<h3>8.1 Where kmodloader wins</h3>
410
485
@@ -450,7 +525,7 @@ <h3>8.2 Where kmodloader has gaps</h3>
450
525
<strong>Indirect efifb awareness only.</strong> When DRM kmod fails to bind, <code>vt_efifb</code> keeps the console on UEFI systems — works correctly without us doing anything. But we don't <em>verify</em> this; we don't generate an Xorg fallback config. Acceptable today (Xorg autoconfig handles scfb fallback); becomes a policy question if we ship an X server with strict driver requirements.
451
526
</div>
452
527
453
-
<h2id="xlibre-takeaway">9. Specific takeaway: xlibre devmatch detection</h2>
528
+
<h2id="xlibre-takeaway">10. Specific takeaway: xlibre devmatch detection</h2>
454
529
455
530
<p>The user's prompt asked specifically about xlibre's devmatch detection so kmodloader doesn't load too aggressively. Conclusion after deep-reading xlibre source:</p>
<p>The right framing is: <strong>don't expect xlibre to filter our kmod-load decisions</strong>. Filtering belongs to the kmod-load layer (us). What xlibre does provide is a clean fallback through its <code>listPossibleVideoDrivers()</code> chain (modesetting → fbdev/scfb → vesa) that handles "no DRM kmod attached" without panic — confirming that loading drm-kmod kmods speculatively is safe.</p>
<li>FreeBSD Handbook x11 chapter: official Xorg fallback statement (UEFI→scfb, BIOS→vesa)</li>
522
597
</ul>
523
598
599
+
<h3>macOS / IOKit</h3>
600
+
<ul>
601
+
<li>Apple <code>kextd</code> source: <ahref="https://github.com/apple-oss-distributions/IOKitTools">github.com/apple-oss-distributions/IOKitTools</a> (referenced for design — not vendored)</li>
0 commit comments