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
New section 3: maps devd, devmatch, /dev/devctl, linker.hints, kld*, kldxref,
the (missing) quiescence layer, and in-kernel newbus matching to their Apple
equivalents (IOKit notifications over Mach, in-kernel IOService matching +
IOCatalogue, kextd/kernelmanagerd loader, busyState/waitQuiet) with a column for
where each lands in this plan. Plus a note on the matching split: FreeBSD does
not-yet-loaded PNP matching in userland (devmatch) while Apple matches in-kernel;
NextBSD keeps the .ko match userland in the ported loader for now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@@ -82,7 +83,24 @@ <h2 id="now">2. What NextBSD does today (and what <code>hwregd</code> really own
82
83
</ol>
83
84
<p>Constraints found in-tree: <strong>configd’s loop is a single <code>mach_msg</code> over a port set</strong> and <em>cannot</em> also watch a device-event fd without <code>EVFILT_MACHPORT</code> (the #168 blocker, documented verbatim in <code>KernelEventMonitor</code>). <strong>launchd has Mach on-demand activation</strong> (a job starts when its <code>MachService</code> is messaged) but the <code>HardwareMatch</code> device-event driver is unimplemented. <strong>mach.ko</strong> has the event-bell pset→fd bridge and now the busyState hook + <code>mach_wait_quiet</code> (#176), but <em>no kernel-native match-notification surface</em> — today the IOKit notifications are synthesized in userland by <code>hwregd</code>.</p>
84
85
85
-
<h2id="blocks">3. The building blocks (and which exist)</h2>
86
+
<h2id="map">3. FreeBSD → Apple → NextBSD: component map</h2>
87
+
<p>How FreeBSD’s <code>devd</code>/<code>devmatch</code> stack maps to Apple’s components, what each does, and where it lands under this plan.</p>
88
+
<table>
89
+
<thead><tr><th>FreeBSD</th><th>What it does</th><th>Apple equivalent & how Apple handles it</th><th>NextBSD target (this plan)</th></tr></thead>
90
+
<tbody>
91
+
<tr><td><code>devd</code></td><td>Userland daemon on <code>/dev/devctl</code>; runs <code>devd.conf</code> rules on attach/detach/nomatch (kldload, run scripts, notify).</td><td><strong>No single daemon.</strong> Match-lifecycle events are delivered as <strong>IOKit notifications over Mach</strong> (<code>IOServiceAddMatchingNotification</code> → <code>IONotificationPort</code>); configd/clients subscribe.</td><td>mach.ko emits match-notifications over Mach (Phase 2); configd-hosted observers subscribe. The <code>devd</code> socket+rules model goes away.</td></tr>
92
+
<tr><td><code>devmatch</code></td><td>Matches unmatched devices against <code>linker.hints</code> PNP tables and <code>kldload</code>s the right module (called by devd on nomatch).</td><td>Matching is <strong>in-kernel</strong> (<code>IOService::registerService</code> → probe/score/start vs <strong>IOCatalogue</strong> personalities); the binary load is a <strong>launchd-activated loader daemon</strong> (<code>kextd</code> → <code>kernelmanagerd</code>) the kernel pings on demand.</td><td>The PNP-match + load relocates from hwregd into the ported <strong><code>kextd</code>-derived loader</strong> (driving <code>kldload</code>), woken by Phase 2 notifications + <code>waitQuiet</code>. (<code>.ko</code> matching stays userland unless later pushed in-kernel.)</td></tr>
93
+
<tr><td><code>/dev/devctl</code></td><td>Kernel character device emitting device-event lines (<code>?</code>/<code>+</code>/<code>-</code>/<code>!</code>).</td><td><strong>Mach notification port</strong> — the kernel sends Mach messages; no char-device socket.</td><td>Replaced by mach.ko match-notifications over Mach (Phase 2).</td></tr>
94
+
<tr><td><code>linker.hints</code><br><spanclass="cite">(built by <code>kldxref</code>)</span></td><td>The PNP→module + dependency database the matcher/loader consult.</td><td><strong>IOCatalogue</strong> — in-kernel kext <em>personalities</em> (<code>IOKitPersonalities</code> matching dicts from each kext’s Info.plist).</td><td>Kept — it’s the FreeBSD analog of IOCatalogue; <code>kldxref</code> stays as build/install plumbing.</td></tr>
95
+
<tr><td><code>kldload</code> / <code>kldunload</code> / <code>kldstat</code></td><td>Load / unload / list kernel modules (syscalls + CLIs).</td><td><code>kextload</code>/<code>kextunload</code> CLIs + the <code>kextd</code>/<code>kernelmanagerd</code> daemon load via <code>OSKext</code>; <code>kextstat</code> lists.</td><td><code>kld*</code> retained as the load primitive (the loader daemon calls them). Apple-named <code>kext*</code> CLIs deferred (no kexts yet).</td></tr>
96
+
<tr><td><code>kldxref</code></td><td>Builds <code>linker.hints</code> from each module’s <code>MODULE_PNP_INFO</code>/dependency metadata.</td><td><code>kmutil</code>/<code>kextcache</code> build prelinked / kext <strong>collections</strong>; personalities load into IOCatalogue.</td><td>Kept as build/install plumbing; no collection builder needed (NextBSD has no collections).</td></tr>
97
+
<tr><td><em>(quiescence)</em><br><spanclass="cite">none today — hwregd 250 ms timer + <code>devctl_freeze</code>/<code>thaw</code></span></td><td>Decide “devices have settled” before/around loading.</td><td><strong><code>IOService</code> busyState</strong> (<code>_adjustBusy</code>, propagates to the root) + <strong><code>waitQuiet</code></strong> / <code>IOServiceWaitQuiet</code>.</td><td>#176: kernel <code>device_match_start/end</code> hook → mach.ko <code>bus_busy</code> + <code>mach_wait_quiet</code> (the busyState/<code>waitQuiet</code> analog).</td></tr>
98
+
<tr><td><em>newbus probe/attach</em><br><spanclass="cite">(in-kernel match of built-in/loaded drivers)</span></td><td>Probe/score/attach drivers already present in the kernel.</td><td><code>IOService</code> match engine — in-kernel (same role).</td><td>Unchanged — newbus stays the in-kernel matcher; we add the Mach notification + quiescence surfaces around it.</td></tr>
99
+
</tbody>
100
+
</table>
101
+
<pclass="cite">Note: FreeBSD splits matching across kernel (newbus, for already-present drivers) and userland (<code>devmatch</code>, to decide which <em>not-yet-loaded</em> module to <code>kldload</code>). Apple does all matching in-kernel (IOCatalogue) and only the <em>binary load</em> in userland. NextBSD keeps the <code>.ko</code> PNP match in userland (the ported loader) for now, matching FreeBSD’s split rather than Apple’s — pushing it fully in-kernel is a larger, optional later step.</p>
102
+
103
+
<h2id="blocks">4. The building blocks (and which exist)</h2>
<p>Net result: device appears → kernel matches → busyState/quiesce + match-notification over Mach → configd-hosted logic (or a launchd-activated loader) reacts and loads on demand — no devctl-socket poll daemon, no wall-clock settle. That is “better than FreeBSD” and faithful to Apple.</p>
116
134
</div>
117
135
118
-
<h2id="roadmap">6. Phased roadmap</h2>
136
+
<h2id="roadmap">7. Phased roadmap</h2>
119
137
<p>Each phase follows the established pattern (kernel hook first as a <code>nextbsd-kernel</code> patch → consumer PR in <code>nextbsd</code> → boot-test gate), and each lands value on its own.</p>
120
138
<ol>
121
139
<li><strong>Phase 0 — busyState/<code>waitQuiet</code> (#176).</strong> Kernel <code>device_match_start/end</code> hook is <strong>merged</strong> (nextbsd-kernel #8). The consumer PR (mach.ko <code>bus_busy</code> + <code>mach_wait_quiet</code> + hwregd quiescence-flip + libIOKit APIs) is staged. <em>Land it as the foundation</em> — every later phase’s loader uses <code>waitQuiet</code>.</li>
<li><strong>Phase 3 — retire standalone hwregd.</strong> Fold its matcher + registry + serving into configd (Option A, on <code>EVFILT_MACHPORT</code>); make autoload a launchd-Mach-activated loader (Option B, driven by Phase 2 notifications + <code>waitQuiet</code>). Migrate the libIOKit + DiskArbitration consumers to the new host. Remove the <code>hwregd</code> process.</li>
125
143
</ol>
126
144
127
-
<h2id="risks">7. Caveats</h2>
145
+
<h2id="risks">8. Caveats</h2>
128
146
<ul>
129
147
<li><strong>“No daemon” is not the goal (or Apple).</strong> Apple keeps <code>kextd</code>/<code>kernelmanagerd</code>. The win is event-driven + launchd-activated, not zero processes. Phase 3 may end with a launchd-activated loader job rather than literally nothing.</li>
130
148
<li><strong>hwregd owns more than loading.</strong> Its registry tree, PCI enrichment, and the <code>org.freebsd.hwregd</code> serving surface back <strong>libIOKit</strong> and <strong>DiskArbitration</strong> today — Phase 3 must re-home all of it (into configd) before the process can be deleted, or those consumers break.</li>
0 commit comments