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 isIn the NextBSD kernel?Who depends on it
LinuxKPI (linuxkpi)Linux driver-API shim — lets Linux GPU/Wi-Fi drivers compile & link against FreeBSDbaked in (COMPAT_LINUXKPI)i915kms, amdgpu, drm, iwlwifi, and nvidia-drm
Linuxulator (linux/linux_common/linux64)Linux userland ABI — translates Linux syscalls so Linux binaries runabsentLinux 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_modesetno 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):

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 nameRegistered byRole on amd64Feature sysctl
linuxsys/amd64/linux32/linux32_sysvec.cMODULE_VERSION(linux, 1)the 32-bit (i386) Linux ABIkern.features.linux
linux64sys/amd64/linux/linux_sysvec.cMODULE_VERSION(linux64, 1)the 64-bit Linux ABIkern.features.linux64
linux_commonsys/compat/linux/linux_common.cMODULE_VERSION(linux_common, 1)shared (arch-independent)

Consequences, each independently confirmed:

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:

ScenarioWith the compatWithout 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)worksworks — unaffected
A Linux game on an Intel/AMD GPU (Mesa/DRI via /dev/dri)worksworks — 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 cardworksworks — 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.

  1. src-overlay/conf/files.linuxulator — two gated blocks, optional compat_linux, from sys/modules/{linux_common,linux64}/Makefile SRCS (resolved to real dirs: compat/linux/*.c, amd64/linux/*.c, x86/linux/*.c, amd64/linux/linux_support.S, the linux_vdso_inc.S). The committed generated syscall tables (linux_sysent.c/linux_syscalls.c/linux_systrace_args.c) already exist in-tree — no regen.
  2. build.yml — a “Wire in Linuxulator” step, KERNEL leg only, mirroring the compat_mach step: define the option in conf/options if absent, cat the fragment onto sys/conf/files, append options COMPAT_LINUX to the generated NEXTBSD config. Correction (review): COMPAT_LINUX/COMPAT_LINUX32 are already declared in sys/conf/options.amd64 (as opt_dontuse.h no-ops) — so grep options.amd64, not the generic options, or you append a duplicate/conflicting definition. And there are zero Linuxulator entries in files.amd64, so options COMPAT_LINUX alone bakes nothing until the fragment is cat’d.
  3. kern.pre.mk additions — the real work (no linuxkpi_video precedent), ported from sys/modules/linux64/Makefile: (a) the linux_assym.h genassym rule (so linux_sysvec.c/linux_support.S can include it); (b) the vDSO pipeline — linux_locore.o (-mcmodel=small -msoft-float) + linux_vdso_gtod.old --shared -T linux_vdso.lds.slinux_vdso.so.o, embedded via linux_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:

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:

  1. buildkernel compiles the Linuxulator into the kernel image via files.linuxulator (kernel leg only; NO_MODULES=yes unchanged). Keep it off the shared modules build — that leg reads patches/series but not the overlay/build.yml wiring, and would otherwise double-build linux*.ko (same discipline as linuxkpi_video/compat_mach).
  2. PR: the amd64 disk.img boot smoke-test gates that the enlarged kernel (Linuxulator + vDSO) still boots to login — a real check the vDSO/assym machinery works.
  3. main push: publish refreshes continuous with the new nextbsd-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

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.