This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A fork of Mesa (upstream: https://gitlab.freedesktop.org/mesa/mesa) whose purpose is to add a
kbase backend to Panfrost/PanVK — i.e. run Mesa's Mali drivers on top of Arm's proprietary
kbase kernel driver (/dev/mali*, r32p0–r44p0) instead of the upstream panfrost/panthor
DRM drivers. The target device is the Google Pixel 7 (Mali-G710, Valhall/CSF, arch ≥ 10).
All custom work sits on top of upstream commit e24dc5bd1e7; everything after that commit is
fork-specific.
Mesa uses Meson. The canonical fork build configuration is in
.github/workflows/pixel7-kbase-debug.yml (CI cross-check: aarch64 debug build on Debian 13).
The key non-default option is:
meson setup build \
-Dgallium-drivers=panfrost \
-Dvulkan-drivers=panfrost \
-Dpanfrost-kmds=kbase,panthor \
-Dplatforms=wayland -Dglx=disabled
meson compile -C build-Dpanfrost-kmds(defined inmeson.options) selects which kernel-driver backends are compiled in: any ofpanfrost,panthor,kbase. Each enabled KMD definesHAVE_PAN_KMOD_<NAME>; all kbase-specific code must stay behind#if defined(HAVE_PAN_KMOD_KBASE)so builds without it stay clean. At least one KMD must be enabled.- Run a single test:
meson test -C build <test-name>; list tests withmeson test -C build --list. CI builds with-Dbuild-tests=false, so enable tests locally if needed. - GitHub Actions builds on every push and uploads the Mesa install tree as an artifact
(
mesa-pixel7-kbase-debug-aarch64.zip).
All Panfrost userspace (both the Gallium driver and PanVK) talks to the kernel through the
pan_kmod vtable (pan_kmod.h / pan_kmod_backend.h). Backends: panfrost_kmod.c,
panthor_kmod.c, and the fork-added kbase_kmod.c. pan_kmod.c dispatches by driver
name; pan_kmod_dev_create() uses drmGetVersion(), which fails on a kbase fd — callers that
may hold a kbase fd fall back to pan_kmod_dev_create_with_driver(fd, flags, "kbase", ...).
kbase is fundamentally different from the DRM backends (see the header comment in
kbase_kmod.c — read it before touching that file):
- The fd is not a DRM fd. The handshake is VERSION_CHECK → SET_FLAGS → mmap of the tracking page; all other ioctls return -EPERM before that. The two kbase flavours use different VERSION_CHECK ioctl numbers: CSF (arch ≥ 10, uAPI 1.x) is nr 52, JM (arch ≤ 9, uAPI 11.x) is nr 0 — probe CSF first, then JM.
- No GEM handles —
kbase_kmod_devmints its own u32 handles for the sharedhandle_to_bosparse array. - SAME_VA memory model: for 64-bit clients the kernel forces SAME_VA on non-executable
allocations.
MEM_ALLOCreturns a cookie, and mmap()-ing the fd at that cookie creates the mapping with CPU VA == GPU VA. The backend mmaps every BO at allocation time and keeps the mapping untilbo_free(munmap of a SAME_VA region destroys the GPU mapping — free-on-close). The optionalbo_mmap/bo_munmapops inpan_kmod_opsexist for this; callers must usepan_kmod_bo_munmap(), never rawos_munmap(), on BO mappings. - GPU-executable BOs come from the EXEC_VA zone (
MEM_EXEC_INITat device open) and return a real GPU VA that doubles as the mmap offset. drmPrimeFDToHandle()doesn't work →pan_kmod_bo_import()fails gracefully. dma-buf import / WSI is future work.PAN_KMOD_VM_OP_MODE_ASYNCand explicit-VAvm_bindare unsupported.- ioctl UAPI lives in
include/drm-uapi/mali_kbase_ioctl.h, assembled from the real Arm kbase uAPI headers (values cross-checked against panfork and shipping kernel sources — do not invent values; verify againstdrivers/gpu/arm/bifrostin a vendor kernel tree). GPU properties come from a key/size-encoded props blob thatkbase_kmod.cparses. - GL on kbase is gated off in
pan_device.c(no submission support yet); bypass withPAN_EXPERIMENTAL_KBASE_GL=1. Vulkan (panvk) enumeration works without the gate.
PAN_KBASE_MAX_NODES (= 8) in pan_kmod.h is the single shared constant for node enumeration.
- PanVK:
panvk_instance.cenumerates DRM devices first, then kbase nodes;panvk_physical_device_init_kbase()(panvk_physical_device.c) opens the node. A failed open returnsVK_ERROR_INCOMPATIBLE_DRIVERand enumeration continues (probe permission errors are expected and logged quietly). - Gallium/pipe-loader:
pipe_loader_drm.chaspipe_loader_kbase_probe()using a dedicatedkbase_driver_descriptor; wired throughdrm_helper.hand the panfrost winsys (panfrost_open_device_kbase()inpan_device.c). - Fd-based fallback: when DRM version detection fails on an fd handed to the panfrost Gallium driver, it retries with the kbase backend.
panvk submits work on kbase through userspace-owned CS ring buffers
(csf/panvk_vX_gpu_queue.c, kbase branches guarded by HAVE_PAN_KMOD_KBASE +
gpu_queue_uses_kbase()): one queue group per Vulkan queue with one 64K ring per panvk
subqueue, bound via CS_QUEUE_REGISTER/BIND, USER_IO pages mmapped from the bind cookie.
Every submission emits the same ring sequence the panthor kernel uses (FLUSH_CACHE2 gated on
LATEST_FLUSH → CALL → SYNC_ADD64 on a per-subqueue seqno cell, deferred on all scoreboard
slots), clobbering only the 4 FW-unpreserved registers, then publishes CS_INSERT and kicks.
All touched subqueues must be kicked before waiting on any (their streams cross-synchronize).
CSF-generic panvk code must use panvk_get_csif_props() / panvk_get_flush_id()
(panvk_device.h) — never the panthor_kmod_* variants directly.
Tiler heap on kbase (see docs/kbase-minecraft-fault-analysis.md): each Vulkan queue owns
a kbase tiler heap (1 MiB chunks, max 400) that is renewed wholesale (create new + retire old
ctx) every KBASE_TILER_HEAP_RENEW_INTERVAL (8) graphics submissions after draining;
PANVK_KBASE_HEAP_RENEW_INTERVAL overrides, 0 disables. The retired ctx is destroyed only at
a later renewal once both graphics subqueues re-armed HEAP_SET (destroying it immediately gave
firmware a dangling chunk list -> wild-pointer 0xc0 faults). On kbase the command streams emit
only VERTEX_TILER_STARTED — no VERTEX_TILER_COMPLETED / FRAGMENT_COMPLETED / FINISH_FRAGMENT,
and no tiler-OOM IR handler (cmdbuf_skips_gpu_heap_ops() in csf/panvk_vX_cmd_draw.c): the
kernel validates heap statistics on every chunk-grow request and kills the group when
frag_end > vt_end (cross-CSG counter mixing) or when nothing is in flight (all-zero
counters), so started-only is the only shape that always validates.
Synchronization is synchronous: submissions are CPU-waited by polling seqno cells,
semaphore waits/signals resolve on the CPU (kbase_cpu_sync_type spin-wait syncs in
panvk_physical_device.c). Async submission (kbase fences/KCPU queues), sparse binding
(BIND queue family is a stub), dma-buf import/WSI, and bo_wait remain future work.
- Follow upstream Mesa style (
.editorconfig,.clang-format); 3-space indent in C. - Commit messages use upstream Mesa prefixes:
panvk:,panfrost:,kbase:,kbase_kmod:,pipe-loader:etc. - Do not break the non-kbase build: changes shared with upstream code paths (pipe-loader,
pan_kmod core, panvk) must compile with
-Dpanfrost-kmds=panfrost,panthoralone.