Files
netris-nestri/build/kernel/nestri.fragment
T
2026-09-25 12:17:00 +03:00

209 lines
11 KiB
Plaintext

# Kconfig fragment for the guest kernel.
#
# Merged onto the tree's config by scripts/kernel-build.sh with the kernel's own
# merge_config.sh, then resolved with `make olddefconfig`, then checked: every
# entry below must hold in the final .config or the build stops. A fragment
# rather than a saved full config: a full .config pins thousands of symbols and
# rots the moment the tree is bumped, while this says only what the guest
# actually requires and lets the version's own defaults handle the rest.
#
# Every entry here is load-bearing or a deliberate performance choice. Nothing
# is speculative.
# ── Gaming needs ─────────────────────────────────────────
# NTSYNC is a great improvement over fsync and esync approaches.
CONFIG_NTSYNC=y
# ── Timers ───────────────────────────────────────────────
# The one that cost a day of silent audio. The guest has no sound hardware, so
# PipeWire drives its whole graph off a timerfd at a 2.67ms cycle, which a
# HZ_PERIODIC/250 kernel cannot express. Symptom: everything reports healthy,
# neswire sends a steady ~3 kbps of Opus, and every sample is zero.
CONFIG_HIGH_RES_TIMERS=y
CONFIG_NO_HZ_IDLE=y
CONFIG_HZ_1000=y
# ── Shared memory ────────────────────────────────────────
# PipeWire moves audio through memfd, not through its socket. Wine uses POSIX
# shm and semaphores directly. /dev/shm itself is mounted by nesinit --
# devtmpfs does not provide it.
CONFIG_SHMEM=y
CONFIG_MEMFD_CREATE=y
CONFIG_TMPFS=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
# ── What PipeWire schedules on ───────────────────────────
# FUTEX_PI is also what Proton's fsync uses.
CONFIG_TIMERFD=y
CONFIG_EVENTFD=y
CONFIG_EPOLL=y
CONFIG_SIGNALFD=y
CONFIG_FUTEX=y
CONFIG_FUTEX_PI=y
# ── Clock sources ────────────────────────────────────────
# Listed because they were correct throughout the silent-audio episode. A good
# clock *source* says nothing about whether the kernel can schedule a timer
# *event* at the resolution asked for; only the block above governs that.
CONFIG_KVM_GUEST=y
CONFIG_PARAVIRT_CLOCK=y
CONFIG_X86_TSC=y
# ── Steal time ───────────────────────────────────────────
# Time the host spends running something else on a vCPU is subtracted from
# whatever task was on it, rather than charged to it. vCPUs share a set of host
# cores instead of owning one each, so steal happens, and without this the
# scheduler reads it as the task having run -- a frame thread preempted by the
# host looks, to the fair class, like one that used its slice.
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
# ── Idle: poll before halting ────────────────────────────
# An idle vCPU that halts exits to the host, and waking it costs an IPI and a
# VM entry. The frame pipeline sleeps and wakes every few milliseconds, so that
# is paid constantly. haltpoll spins for a bounded, adaptive window first
# (guest_halt_poll_ns, 200us), so a wakeup that comes quickly never leaves the
# guest. When the host offers it, the guest also switches host-side polling
# off, so the two never poll at once.
#
# Built in but inert: it loads only if the host sets KVM_HINTS_REALTIME or the
# command line forces it. The hint is the wrong tool -- it also promises the
# vCPUs are never preempted, and the guest drops paravirt spinlocks on the
# strength of that, which a shared set of host cores cannot promise. Forcing
# the idle driver gets the polling and nothing else. Built into the command
# line, which is prepended to what the host passes, so no caller has to know.
# The cost is host CPU spent spinning on an idle vCPU.
#
# Prepended, not replacing: with OVERRIDE off the host's arguments come after
# this and the last value wins, so `cpuidle_haltpoll.force=0` from the host
# still turns it off.
CONFIG_HALTPOLL_CPUIDLE=y
CONFIG_CPU_IDLE_GOV_HALTPOLL=y
CONFIG_CMDLINE_BOOL=y
CONFIG_CMDLINE="cpuidle_haltpoll.force=1 swiotlb=noforce"
# CONFIG_CMDLINE_OVERRIDE is not set
# ── No bounce buffer ─────────────────────────────────────
# With more than about 3 GiB of RAM the guest has memory above 4 GiB, and x86
# then sets aside a 64 MiB SWIOTLB bounce buffer and zeroes it at boot, which
# makes the host back all of it. Nothing here ever bounces: the virtio devices
# do not offer ACCESS_PLATFORM, so virtio skips the DMA API entirely, and there
# is no other DMA-capable device. `swiotlb=noforce` in the command line above
# skips the allocation. A device that genuinely needs a 32-bit DMA mask would
# fail to map rather than bounce; there is none.
# ── microVM transport ────────────────────────────────────
CONFIG_VIRTIO=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_MMIO=y
CONFIG_VIRTIO_BLK=y # root on /dev/vda
CONFIG_VIRTIO_CONSOLE=y # hvc0, the only way into a guest that will not boot
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_FS=y # the shares the boot descriptor names
CONFIG_FUSE_FS=y # virtiofs needs it
CONFIG_VSOCKETS=y # neshub's link to the host
CONFIG_VIRTIO_VSOCKETS=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_DRM=y
CONFIG_DRM_VIRTIO_GPU=y
CONFIG_VIRTIO_GPU_NV=y # NVIDIA hosts: the ioctl forwarder, from virtio-nvgpu
# ── Filesystems ──────────────────────────────────────────
CONFIG_EXT4_FS=y # output/rootfs.ext4, and a box's writable install layer
CONFIG_MISC_FILESYSTEMS=y # the menu EROFS lives under; off in base.config, which drops it silently
CONFIG_EROFS_FS=y # a game's build image, the install overlay's lower layer
CONFIG_OVERLAY_FS=y # the session overlay over a read-only install
# ── Memory typing ────────────────────────────────────────
# Page Attribute Table, and the MTRR support it depends on.
#
# Both are `def_bool y` upstream, promptable only under EXPERT -- so they were
# switched off deliberately at some point, and the guest booted logging
# "PAT support disabled because CONFIG_X86_PAT is disabled in the kernel".
#
# Without PAT there are no write-combining mappings. This guest exists to push
# frames through virtio-gpu, and WC on the GPU aperture is precisely what PAT
# provides; every frame would go through uncached or writeback mappings
# instead. Not a correctness problem, which is why it went unnoticed.
CONFIG_MTRR=y
CONFIG_X86_PAT=y
# ── POSIX ACLs ───────────────────────────────────────────
# Nothing depends on the ACLs themselves; without them, anything that sets one
# on a device node or a file logs "Operation not supported" at every boot, and
# a boot log full of failures that do not matter hides the ones that do.
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_TMPFS_POSIX_ACL=y
# ── Performance ──────────────────────────────────────────
# Chosen, not required. The mainline subset of what CachyOS enables, minus
# anything needing patches. PREEMPT_LAZY is the tree's own default and is left
# alone; PREEMPT_DYNAMIC on top allows `preempt=full` from the command line
# without a rebuild.
CONFIG_PREEMPT_DYNAMIC=y
CONFIG_LRU_GEN=y
CONFIG_LRU_GEN_ENABLED=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
CONFIG_PSI=y
# ── Undoing the minimal seed ─────────────────────────────
# base.config descends from a size-minimised config with EXPERT on, and EXPERT
# is what makes each of these promptable. Every one is `default y` (or the
# default choice) upstream, and every one was off in the built kernel. They
# are pinned here because nothing else would stop a reseed from bringing them
# back.
#
# SLUB_TINY drops the per-CPU slab caches, so every kmalloc and kfree takes the
# locked slow path. Its own help text says it is not for performance. This
# guest allocates constantly: network buffers, GPU ioctls, io_uring, futexes.
# CONFIG_SLUB_TINY is not set
#
# Without ADVISE_SYSCALLS, madvise() and fadvise() return ENOSYS. THP above is
# madvise-only, so no user mapping could ever get a huge page, and allocators
# could never hand memory back with MADV_DONTNEED or MADV_FREE.
CONFIG_ADVISE_SYSCALLS=y
#
# Without MEMBARRIER, Wine and the .NET runtime fall back to an mprotect() trick
# to flush other threads' write buffers, which forces a TLB shootdown across
# every vCPU -- an IPI storm, and each IPI is a VM exit. RSEQ gives glibc and
# per-CPU allocators their fast paths.
CONFIG_MEMBARRIER=y
CONFIG_RSEQ=y
#
# -O2, not -Os: the whole kernel was being compiled for size, and a guest that
# runs Wine spends much of its time in syscalls.
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
#
# Without jump labels, every static key is a memory load and a branch instead
# of a patched no-op, on paths such as steal-time accounting, the scheduler and
# cgroup checks.
CONFIG_JUMP_LABEL=y
# Autogroup is deliberately OFF, which is a departure from CachyOS.
#
# It groups tasks by session id, which is a desktop tuning: it keeps a
# terminal's `make -j` from starving the browser. This guest is a
# single-application appliance with no interactive sessions to balance
# against each other, so there is nothing for it to do but add overhead.
# CONFIG_SCHED_AUTOGROUP is not set
# ── Deliberately absent ──────────────────────────────────
# No CONFIG_SND of any kind. The guest has no sound hardware and wants none:
# neswire is a virtual PipeWire sink, and an ALSA stack would only add a second
# thing that could claim to be the default output.
# Speculation mitigations are off, as a decision rather than an oversight.
#
# `lscpu` in the guest reports Spectre v1/v2, SSB, SRSO and TSA as Vulnerable,
# and that is intended. This is a single-tenant sandbox: one player's session,
# torn down at the end of it, with the VM boundary as the isolation. The
# attacks these defend against are cross-tenant side channels, and the cost is
# paid on every syscall and context switch in a workload that is latency
# bound.
#
# What would change this: running two players' sessions on one box at the same
# time, or anything of value living inside the guest. Neither is true today.
# CONFIG_CPU_MITIGATIONS is not set