NextBSD: NVIDIA autoload & Linux binaries — LinuxKPI vs the Linuxulator
Why NVIDIAGraphics595.kext won’t autoload (and claude-code won’t install), what the two “Linux” subsystems actually are, and the split fix. Draft, July 2026.
1. The confusion: “we already load i915/drm/wifi that use Linux”
Right — but they use LinuxKPI, not the Linuxulator. Two unrelated subsystems with maddeningly similar names:
| What it is | In the NextBSD kernel? | Who depends on it | |
|---|---|---|---|
LinuxKPI (linuxkpi) | Linux driver-API shim — lets Linux GPU/Wi-Fi drivers compile & link against FreeBSD | baked in (COMPAT_LINUXKPI) | i915kms, amdgpu, drm, iwlwifi, and nvidia-drm |
Linuxulator (linux/linux_common/linux64) | Linux userland ABI — translates Linux syscalls so Linux binaries run | absent | Linux binaries, linux_base, and nvidia.ko’s optional compat |
So nvidia-drm.ko (the KMS bridge) is in the same boat as i915 — it only needs linuxkpi (present). Its verified deps: MODULE_DEPEND(nvidia_drm, linuxkpi), drmn, dmabuf, nvidia, nvidia_modeset — no Linuxulator.
The odd one is nvidia.ko (the core). The FreeBSD port’s default LINUX option defines NV_SUPPORT_LINUX_COMPAT, which adds:
src/nvidia/nvidia_pci.c: MODULE_DEPEND(nvidia, linux, 1,1,1);
MODULE_DEPEND(nvidia, linux_common, 1,1,1); /* #if X86_64 */
src/nvidia-modeset/...: MODULE_DEPEND(nvidia_modeset, linux, ...); /* #if NVKMS_SUPPORT_LINUX_COMPAT */
That dep exists only to register a Linux-ioctl handler on /dev/nvidia so Linux processes (under the Linuxulator) can talk to the GPU. It has nothing to do with driving the display. That is the one thing i915/drm/Wi-Fi don’t do — and the only reason NVIDIA trips.
2. What was observed (real hardware)
On a NextBSD box with an RTX 3060 Ti (Ampere GA104, PCI 0x2489):
- GPU detected;
0x2489is in the kext’s 293-id Turing+ match table; the three.kos are valid ELF (not truncated). So matching + packaging are correct. kextload NVIDIAGraphics595.kext→ fails at the deepest dep withdmesg:KLD NVIDIACore595: depends on linux — not available or version mismatch. The chain loads bottom-up (core → modeset → drm) and dies on the first link.pkg install claude-code(a Linux x64 binary; portUSES=linux:rl9) →linux_base-rl9pre-install aborts:Cannot install package: kernel missing 64-bit Linux support.
3. The crux: on amd64 the module names are inverted
Verified against freebsd-src releng/15.1. The version-record name a MODULE_DEPEND matches is not the directory you’d guess:
| Module name | Registered by | Role on amd64 | Feature sysctl |
|---|---|---|---|
linux | sys/amd64/linux32/linux32_sysvec.c — MODULE_VERSION(linux, 1) | the 32-bit (i386) Linux ABI | kern.features.linux |
linux64 | sys/amd64/linux/linux_sysvec.c — MODULE_VERSION(linux64, 1) | the 64-bit Linux ABI | kern.features.linux64 |
linux_common | sys/compat/linux/linux_common.c — MODULE_VERSION(linux_common, 1) | shared (arch-independent) | — |
Consequences, each independently confirmed:
- NVIDIA depends on the token
linux(grep of the 595.84 source:MODULE_DEPEND(nvidia, linux, 1,1,1)and(nvidia, linux_common, 1,1,1); it never referenceslinux64). On amd64 the namelinuxis registered only by the 32-bitlinux32module. So satisfying NVIDIA’s dep by bake-in would require baking the entire i386 Linux ABI.linux64does not satisfy it. - The
linux_base-rl9gate keys onkern.features.linux64(emulators/linux_base-rl9@preexectestssysctl -q kern.features.linux64). That feature is registered byFEATURE(linux64, …)inlinux_sysvec.c— i.e. thelinux64module. The portsUSES=linuxframework does no kernel probing; the check lives entirely in that@preexec, and it is correct. linux_commondepends onnetlink, which is already baked (options NETLINKin amd64 DEFAULTS) — nothing to do.
4. Recommended plan — split the fix
4a. NVIDIA autoload — drop the compat (don’t bake for this)
Build the kmod with NV_SUPPORT_LINUX_COMPAT off (the port’s !MLINUX edit), in nvidia-portpatch.sh:
s/define (NV_SUPPORT_LINUX_COMPAT)/undef \1/ → src/nvidia/nv-freebsd.h s/define (NVKMS_SUPPORT_LINUX_COMPAT)/undef \1/ → src/nvidia-modeset/nvidia-modeset-freebsd.c
The guarded MODULE_DEPENDs vanish: nvidia.ko keeps only mem+io; nvidia-modeset keeps only nvidia; nvidia-drm is unchanged. The whole NVIDIAGraphics595 chain then autoloads on the already-baked linuxkpi/drm stack — exactly like i915/amdgpu/radeon. This is a one-line rebuild of the existing nvidia-build job; no kernel change.
What dropping the compat actually removes — and what it means for Linux games / Steam / AppImages
NV_SUPPORT_LINUX_COMPAT gates the driver’s entire Linux-ioctl bridge on the NVIDIA device nodes: the handler registration in nvidia_linux.c (linux_ioctl_register_handler(&nvidia_handler)) plus every Linux-ioctl dispatch path in nvidia_ctl.c and nvidia_dev.c, plus the MODULE_DEPEND(nvidia, linux/linux_common). Turning it off removes precisely that — the ability for a process running under the Linuxulator to ioctl /dev/nvidia, /dev/nvidiactl, /dev/nvidiaN. Native FreeBSD/NextBSD ioctls to those nodes are untouched.
So in reality, once NextBSD does have the Linuxulator and you run a Linux binary that wants the NVIDIA GPU:
| Scenario | With the compat | Without it (this change) |
|---|---|---|
A Linux game / Steam title / AppImage that renders on the NVIDIA GPU (its Linux libGLX_nvidia/Vulkan ICD ioctls /dev/nvidia) | GPU-accelerated (if the matching NVIDIA Linux userland is installed under /compat/linux) | No NVIDIA acceleration — the Linux GL/Vulkan libs can’t reach the driver. The app still runs, but falls back to software rendering (llvmpipe) or fails to init the GPU. |
A Linux CLI/TUI/server tool (claude-code, most AppImages that don’t touch the GPU) | works | works — unaffected |
A Linux game on an Intel/AMD GPU (Mesa/DRI via /dev/dri) | works | works — this is an NVIDIA-only change; Mesa/DRI never used the NVIDIA compat |
Native NextBSD graphics — Gershwin desktop, native Wayland/X, native GL/Vulkan, /dev/dri KMS on the NVIDIA card | works | works — fully hardware-accelerated; never touched the Linux path |
4b. Linux binaries (claude-code) — bake linux_common + linux64
This is the genuine kernel work, and it’s the clean pair (no shared-TU collision). Mirror the existing linuxkpi_video / compat_mach bake precedent already in nextbsd-kernel. A statically compiled-in module runs its DECLARE_MODULE/MODULE_VERSION/FEATURE linker-set SYSINITs identically to a .ko (proven today: baked-in COMPAT_LINUXKPI already satisfies nvidia-drm’s linuxkpi dep), so a later kldload resolves against the kernel image.
src-overlay/conf/files.linuxulator— two gated blocks,optional compat_linux, fromsys/modules/{linux_common,linux64}/MakefileSRCS (resolved to real dirs:compat/linux/*.c,amd64/linux/*.c,x86/linux/*.c,amd64/linux/linux_support.S, thelinux_vdso_inc.S). The committed generated syscall tables (linux_sysent.c/linux_syscalls.c/linux_systrace_args.c) already exist in-tree — no regen.build.yml— a “Wire in Linuxulator” step, KERNEL leg only, mirroring thecompat_machstep: define the option inconf/optionsif absent,catthe fragment ontosys/conf/files, appendoptions COMPAT_LINUXto the generatedNEXTBSDconfig. Correction (review):COMPAT_LINUX/COMPAT_LINUX32are already declared insys/conf/options.amd64(asopt_dontuse.hno-ops) — so grepoptions.amd64, not the genericoptions, or you append a duplicate/conflicting definition. And there are zero Linuxulator entries infiles.amd64, sooptions COMPAT_LINUXalone bakes nothing until the fragment iscat’d.kern.pre.mkadditions — the real work (nolinuxkpi_videoprecedent), ported fromsys/modules/linux64/Makefile: (a) thelinux_assym.hgenassym rule (solinux_sysvec.c/linux_support.Scan include it); (b) the vDSO pipeline —linux_locore.o(-mcmodel=small -msoft-float) +linux_vdso_gtod.o→ld --shared -T linux_vdso.lds.s→linux_vdso.so.o, embedded vialinux_vdso_inc.S. Prototype this in isolation first — it’s the main CI risk.
Substrate already present via the config: COMPAT_FREEBSD32 (GENERIC), COMPAT_LINUXKPI (baked), NETLINK (DEFAULTS). Arch: linux64/linux_common apply to amd64 (and arm64 has its own linux64); the i386 linux32 is not part of this recommended path.
5. Runtime: the kernel bake is necessary, not sufficient
To actually run Linux binaries after the kernel supports them:
/compat/linuxpopulated = thelinux_base-rl9userland (Linuxld-linux+ glibc). The Linuxulator only translates syscalls;claude-codestill loads its interpreter/compat/linux/lib64/ld-linux-x86-64.so.2.linux_base-rl9installs there — but only after its@preexecpasses, which is why the kernel bake (kern.features.linux64) must land first.- Pseudo-filesystems: many Linux apps expect
linprocfson/compat/linux/procandlinsysfson/compat/linux/sys(+tmpfsfor/dev/shm, a devfs at/compat/linux/devfor/dev/full,/dev/shm). These are separate module-only subsystems — bakeLINPROCFS/LINSYSFSthe same way, or accept they’re absent. A node/Electron-class binary likeclaude-codecommonly probes/proc. - The mount/brand actor: stock FreeBSD does this in
rc.d/linux. NextBSD ships no rc.d (doctrine: launchd-native) — so add a launchdLaunchDaemon(upstream namespace, notorg.nextbsd.*) that mountslinprocfs/linsysfs/tmpfs+ the Linux devfs ruleset into/compat/linuxat boot. Not an rc.d script. Explicitbrandelfis unnecessary (brand-note matching handles modern binaries).
6. Build & deploy
Kernel-only change; the NVIDIA kexts are not rebuilt for §4b (they resolve at kldload against the new kernel’s baked names). §4a does rebuild the nvidia-build job (the portpatch edit). Flow, per the existing pipeline:
buildkernelcompiles the Linuxulator into the kernel image viafiles.linuxulator(kernel leg only;NO_MODULES=yesunchanged). Keep it off the shared modules build — that leg readspatches/seriesbut not the overlay/build.ymlwiring, and would otherwise double-buildlinux*.ko(same discipline aslinuxkpi_video/compat_mach).- PR: the amd64
disk.imgboot smoke-test gates that the enlarged kernel (Linuxulator + vDSO) still boots to login — a real check the vDSO/assym machinery works. - main push:
publishrefreshescontinuouswith the newnextbsd-kernel-amd64.tar.gz+kernel-obj; the ISO builder ingests the new kernel. Land as a PR to main; merge only after the green boot test. (No direct-push to main.)
7. Verification (on the box)
sysctl kern.features.linux64 # 1 (the exact bit linux_base @preexec tests; from baked linux64) sysctl compat.linux.osrelease # non-empty (linux_common/linux_mib registered) kldstat -v 1 | grep -iE 'linux_common|linux64|linuxkpi' # names registered as part of kernel image (id 1) dmesg | grep -i 'depends on linux' # EMPTY (was the NVIDIACore595 failure) [after 4a rebuild] kldstat | grep -i nvidia # nvidia + nvidia-modeset + nvidia-drm autoloaded on the RTX 3060 Ti ls -l /dev/dri # card0 + renderD128 (nvidia-drm KMS up; needs hw.nvidiadrm.modeset=1) nvidia-smi # driver talks to the GPU pkg install -y claude-code # no 'kernel missing 64-bit Linux support' abort /compat/linux/bin/bash -c 'uname -sm' # 'Linux x86_64' — a Linux ELF runs under the baked linux64 brand claude --version # the claude-code Linux binary runs end-to-end
Note: /dev/dri only appears when nvidia-drm modesetting is enabled — the hw.nvidiadrm.modeset="1" tunable (already set) is required.
8. Risks
- vDSO +
linux_assym.hmachinery has nolinuxkpi_videoprecedent — the genassym rule + theld/soft-float vDSO pipeline must be hand-ported intokern.pre.mk. Prototype in isolation; the boot smoke-test is the gate. - Shared-TU collision is avoided by the recommended
linux_common+linux64pair (no overlapping basenames). It only bites if you addlinux32. - Config-file gotcha:
COMPAT_LINUX/COMPAT_LINUX32already exist inoptions.amd64— grep the right file or you emit a conflicting duplicate. - Static-init ordering:
netlink(DEFAULTS, beforelinux_commonatSI_SUB_EXEC) is fine; confirmlinux_common_modeventruns before the ELF-brand modevents. - Permanent kernel-image size bump (the extra ABI + vDSO). Small next to the drm/nvidia footprint.
- Not a conflict: the Linuxulator is ABI-isolated — it installs its own
sysentvecselected per-process at exec; it does not alter the native FreeBSD/mach syscall path. Baking it does not endanger the Darwin userland.
9. Recommendation
Do §4a now (disable NV_SUPPORT_LINUX_COMPAT) — a one-line rebuild that makes NVIDIAGraphics595.kext autoload on the RTX 3060 Ti today, matching how i915/amdgpu/radeon already work. Do §4b next (bake linux_common+linux64 into nextbsd-kernel) as the proper, separately-justified feature that unlocks Linux binaries (claude-code, linux_base) — the real, honest reason to add the Linuxulator. Skip linux32 unless 32-bit Linux binaries become a goal. Use claude-code-legacy as a stopgap until §4b lands.