NextBSD · Driver delivery · build model
How NextBSD produces the compiled-in kernel, the loadable klds, and the kexts from one tree without ever patching a stock FreeBSD build file. buildkernel NO_MODULES makes tier 1, buildextensions makes tiers 2–3 from in-tree modules, buildgraphics makes the out-of-tree GPU/nvidia kexts, and buildworld is untouched.
| Target | Owner | Produces | Tier |
|---|---|---|---|
make buildkernel NO_MODULES=yes | stock (flag only) | kernel image, no modules | 1 · compiled-in |
make buildextensions | NextBSD | in-tree sys/modules → plain klds + firmware kexts | 2 + 3 |
make buildgraphics | NextBSD | out-of-tree drm-kmod + nvidia → custom-named kexts | 3 (GPU) |
make buildworld | stock (untouched) | userland | — |
Naming style tracks the target: buildextensions emits FreeBSD-name kexts (if_iwlwifi.kext, mechanical); buildgraphics emits the 12 custom names (IntelGraphics.kext, NVIDIAGraphics<NNN>). See the naming manifest — it's the KEXTS table these targets read.
buildkernel NO_MODULES — tier 1, stays stockStock make buildkernel's all target is kernel-all + modules-all (in sys/conf/kern.post.mk). Passing NO_MODULES=yes (a flag — not a Makefile edit) drops the modules half, leaving just the kernel image. installkernel NO_MODULES=yes installs only the kernel to /boot/kernel.
make buildkernel KERNCONF=NEXTBSD NO_MODULES=yes
make installkernel KERNCONF=NEXTBSD NO_MODULES=yes DESTDIR=${STAGE}
Crucially this leaves behind the configured kernel obj tree — the config stage's opt_*.h, the machine/ symlinks, and the kernel-toolchain — which the next target needs. Because it's just a flag on a stock target, buildkernel stays byte-for-byte upstream (keeps the monorepo's merge-clean rule intact).
buildkernel && installkernel no longer yields a bootable system on its own — your compiled-in tier must genuinely cover boot-to-mountroot (root HBA, root FS, console, core buses), since the loadable tier now comes from a separate target. Document this so nobody's surprised.buildextensions — in-tree klds + kexts (tiers 2–3)A NextBSD-owned target that builds sys/modules the way buildkernel used to, then wraps the kext-tier drivers. It emits a mix: the ~250 plain klds stay plain .ko, and the ~30 firmware drivers get wrapped into .kext bundles via a shared bsd.kext.mk.
# nextbsd/extensions/Makefile — invoked AFTER buildkernel (needs its obj tree)
KODIR?= ${OBJTOP}/${MACHINE}.${MACHINE_ARCH}/${SRCTOP}/${KERNCONF}/modules${SRCTOP}/sys/modules
.include "${SRCTOP}/nextbsd/share/mk/nextbsd.kexts.conf" # the KEXTS table = the naming manifest
buildextensions: .PHONY
cd ${SRCTOP}/sys/modules && ${MKMODULESENV} ${MAKE} all # build every .ko (tier 2)
.for k in ${KEXTS} # wrap only the firmware/multi-version ones (tier 3)
@${MAKE} -f ${SRCTOP}/nextbsd/share/mk/bsd.kext.mk extension \
KEXT_NAME=${${k}_NAME} KEXT_ID=${${k}_ID} KO=${KODIR}/${${k}_KO} \
KEXT_PERSGEN=${SRCTOP}/nextbsd/tools/gen-${${k}_GEN}-personalities.sh \
KEXT_FW="${${k}_FW}" KEXT_DEPS="${${k}_DEPS}"
.endfor
bsd.kext.mk is the formalized ko2kext.sh: it takes an already-built .ko, runs the personality generator, bundles firmware into Resources/, writes Info.plist + OSBundleLibraries, and stages <Name>.kext/Contents/MacOS/<ko>. Move ko2kext.sh + gen-*-personalities.sh from the separate nextbsd-kernel-modules repo into nextbsd/tools/.
buildgraphics — out-of-tree GPU/nvidia (tier 3)drm-kmod (i915kms/amdgpu/radeonkms + the drm/ttm/dmabuf cores) and nvidia are ports, not sys/modules, so buildextensions can't reach them. They already need bespoke recipes (fetch, patch, per-branch), which is exactly why they get their own target — and their custom names cost nothing extra.
nextbsd/vendor/drm-kmod/ (self-contained, but a second upstream-sync burden) or fetch at build.NVIDIAGraphics<NNN>.kext.bsd.kext.mk wrap and install to the same /System/Library/Extensions..kext bundles. So fetching here is squarely allowed.buildworld — unchangedUserland only; it never touched kernel modules. Completely stock, runs in any order relative to the kernel/extensions targets. Your custom userland tools ride in via the LOCAL_DIRS/LOCAL_LIB_DIRS hooks (see the monorepo plan §5), not here.
The build split above is compatible with both. What differs is where the loadable drivers install and who matches them — and it hinges on whether /boot/kernel holds loose klds.
| Mixed (sidebar recommendation) | Pure (clean-namespace) | |
|---|---|---|
/boot/kernel | kernel + ~250 plain klds | kernel image only |
/System/Library/Extensions | ~38 kexts | all ~343 as kexts |
| Matching for the ~250 | native devmatch (free, authoritative) | your in-kernel matcher (must scale to all HW) |
| Personalities to generate | ~38 | 343 |
/boot/firmware | avoided if kexts serve fw from Resources/ (D4) | avoided (same) |
| Cost | /boot/kernel has klds; least bespoke machinery | uniform bundles, no loose files; but no native devmatch, matcher owns everything |
kern.module_path defaults to /boot/kernel;/boot/modules, and kldxref builds linker.hints from those dirs — that's exactly where devmatch looks. Kexts in /System/Library/Extensions are bundle directories (the .ko is at Contents/MacOS/), which kldxref doesn't traverse — so they load via your loader/matcher, not devmatch. Keeping /boot/kernel empty of klds therefore forces everything through the kext path (Pure), which re-raises the 343-wide personality burden and makes your T420-verified matcher load-bearing for all hardware. Mixed keeps the matcher scoped to ~38 and lets devmatch carry the long tail. This is the decision to make; the build targets serve whichever you pick.Four ordered targets with flags is great for CI (selective builds need the granularity) but heavy for a developer who just wants "build me a working system." The fix isn't fewer capabilities — it's a friendlier front door. Three options, not mutually exclusive.
| Option | Local experience | Trade-off |
|---|---|---|
A · Umbrella + build.sh cheapest | keep the granular targets for CI; add a make nextbsd meta-target that chains kernel→extensions→graphics→world in order, and/or a build.sh (your existing idiom) with sane defaults. Local = one command. | ~none. Pure convenience layer. |
| B · Collapse graphics into extensions | 3 targets: buildkernel, buildextensions (in-tree and out-of-tree via internal sub-makes), buildworld. | slightly coarser CI gating for GPU (recoverable with internal ifs). |
| C · Local stays stock; kexts are release-only best local ergonomics | don't use NO_MODULES locally — plain make buildkernel buildworld builds a normal, bootable FreeBSD-shaped system (klds in /boot/kernel). The kext-wrap runs only in the release/image pipeline. Day-to-day kernel hacking is just stock FreeBSD. | the kext path is exercised at release/CI, not in every local build. Pairs with the Mixed model. |
NO_MODULES + the strict tier split exists to keep /boot/kernel empty — which is the Pure goal (§5). Mixed doesn't need NO_MODULES at all: stock buildkernel builds kernel + klds (self-sufficient, bootable at every step), and a single make extensions/buildgraphics wrap handles just the ~38 kexts on top. So if local ergonomics matter, Mixed + Option C gives the simplest possible local story — local building is plain FreeBSD — while CI still does the selective/kext work. Pure is what forces the four-target dance. Another entry in Mixed's column.Recommendation: granular targets for CI + a make nextbsd umbrella and build.sh for local (A); and if you adopt Mixed, Option C makes routine local builds just stock FreeBSD. For iterating on a single driver you don't need any of this — cd sys/modules/foo && make already works after one full build.
buildextensions/buildgraphics depend on buildkernel's configured obj tree (opt headers, toolchain) — enforce buildkernel → {extensions, graphics}. buildworld is independent..ko isn't relinked). Key each .kext off its .ko mtime (+ the personality generator + firmware set) so a bundle re-wraps only when its inputs move; wrapping is cheap, so rebuild-on-.ko-change is safe.sys/** + nextbsd/{extensions,tools,share/mk}/**; gate buildgraphics on the vendored/pinned drm-kmod + nvidia refs. A sys/-only change rebuilds kernel + affected klds/kexts, not GPU; a drm-kmod bump rebuilds only graphics.Everything that enters the tree is a NextBSD-owned new file; no stock build file is edited.
| Concern | Mechanism (no stock edit) |
|---|---|
| No modules from buildkernel | NO_MODULES=yes — a flag |
| NextBSD-original klds | LOCAL_MODULES / LOCAL_MODULES_DIR → nextbsd/modules/ |
| Per-driver one-shot kext (NextBSD drivers) | driver Makefile .include <bsd.kext.mk>; or the stock bsd.kmod.mk's existing .-include <local.kmod.mk> hook |
| The extensions/graphics targets | new top-level nextbsd/ dir (extensions/, graphics/, share/mk/bsd.kext.mk, tools/), invoked from the release/CI driver |
| Kernel config | new file sys/${ARCH}/conf/NEXTBSD that include GENERIC — never edit GENERIC |
Stock Makefile.inc1, kern.post.mk, kmod.mk, bsd.kmod.mk, sys/modules/Makefile, and GENERIC stay untouched — so upstream merges never conflict on the build system.
/boot/kernel contents) follows from it.Resources/ (needs a firmware(9) hook, avoids /boot/firmware) vs nest a firmware .ko (unchanged firmware_get()) vs raw /boot/firmware files (FreeBSD-15 path)..ko kext packaging — ko2kext.sh can't yet bundle nvidia's nvidia+modeset+drm under one CFBundleExecutable; needed before buildgraphics ships nvidia KMS.