10 KiB
perf-dkms Design (Current Implementation)
Last Updated: 2026-04-10
Source of truth: experimental/perf-dkms/src/* (implementation-first)
Scope: This document describes the design that is currently implemented, including known reliability risks.
1. Purpose and Scope
perf-dkms provides an AMD GPU PMU kernel module (amdgpu_pmu) that exposes GPU hardware counters through Linux perf.
Primary goals in current implementation:
- Integrate GPU counters with standard perf workflows (
perf stat,perf record) - Submit PM4 commands through KFD/AQL queue plumbing
- Support dimension-aware counter reads (SE/SA/WGP etc.)
- Operate safely in atomic perf callback contexts by deferring heavy work to a workqueue
This is an experimental design with known corner cases and lifecycle hazards (documented below).
2. High-Level Architecture
Userspace
perf_event_open / perf stat / perf record
|
Linux perf core
|
amdgpu_pmu (pmu_main.c)
- event_init/add/start/stop/read/del callbacks
- hrtimer polling for cache refresh
|
AQL PMU integration (aql_pmu_integration.c)
- one global RCU-protected session
- one global workqueue
|
AQL session + measurement management (aql_perf.c + aql_packet_ops.c)
- per-event measurement objects
- shared counter tracking
- PM4 START/READ/END packet creation/submission
|
AQL queue/KFD bridge (aql_queue_manager.c + kfd_ioctl_bridge.c)
- queue create/destroy, ring submit/wait
- kernel-side ioctl bridging to KFD user-copy API
|
GPU blocks (SQ/GL2C/TA/GRBM etc.)
3. Component Responsibilities
pmu_main.c
- Registers PMU with perf (
perf_pmu_register) - Builds dynamic
events/sysfs attributes fromcounter_registry - Parses perf
config1dimensions and validates limits - Implements PMU callbacks:
event_initadd/delstart/stopread
- Runs background hrtimer polling for measurement cache refresh
- Emits sampling tracepoints for
perf recordsupport
aql_pmu_integration.c
- Owns global singleton session pointer (
global_aql_session) under RCU - Owns global workqueue used for deferred START/STOP/READ operations
- Provides API for session get/put with refcount protection
- Coordinates flush/cleanup on unload
aql_perf.c
- Creates and initializes session
- Opens
/dev/kfd - Discovers GPU IDs via sysfs topology
- Creates per-GPU AQL queues
- Creates per-GPU architecture objects (
arch_t) - Binds architecture counter allocations to queue data buffers
- Owns session-level cleanup and final resource release
aql_packet_ops.c
- Creates measurement objects and manages their lifecycle
- Implements PM4 START/READ/END packet generation orchestration
- Allocates/releases physical counters
- Implements current shared-counter mechanism
- Implements async work-item execution model for atomic-safe perf callbacks
- Implements cached atomic read path and synchronous read path
aql_queue_manager.c
- Creates/destroys per-GPU queue resources
- Manages ring buffers and signal buffers
- Submits PM4 IB packets and waits for completion
- Uses
kthread_use_mm-style access to queue user VA memory mappings
kfd_ioctl_bridge.c
- Bridges kernel code to KFD ioctl handlers expecting user pointers
- Allocates temporary userspace pages, copies args in/out, invokes ioctl
aql_c/*
- Architecture creation and event mapping
- Counter registry
- PM4 builders and packet-generation logic
- Shared helpers for dimensions and topology indexing
amdgpu_pmu_trace_dev.c
- Registers miscdevice
/dev/amdgpu_pmu_trace - Allows userspace to emit trace events via ioctls
4. Core Runtime Objects and Lifetimes
Global objects
global_aql_session(singleton, RCU-protected)aql_global_workqueue(singleton)- Module-global timer in
amdgpu_pmu
Per-session objects
gpu_ids[]archs[](per GPU)queues[](per GPU)active_measurementslistshared_counterslist (current sharing model)
Per-event object
struct aql_measurement- stored in
event->hw.config_base - includes state, counter pointers, cache, refcount, dimension options
- refcounted because async work can outlive callback frames
- stored in
Current counter-sharing object
struct shared_counter_ref- keyed by
counter_idin current implementation - contains refcount and pointer to owner measurement
- keyed by
5. End-to-End Lifecycle Flows
5.1 Module init
- PMU event attrs initialized from
counter_registry - PMU registered with perf
- AQL integration initialized:
- global workqueue created
- global session created and initialized
- GPU architecture availability validated
- Trace miscdevice registration attempted
5.2 Event init/add/start
event_initvalidates event type and counter ID- Maps perf CPU affinity to a GPU ID
- Parses
config1dimensions, validates against target GPU topology and counter capabilities - Creates measurement and stores pointer in
event->hw.config_base add(PERF_EF_START)orstart()schedules async START work- Worker executes START path:
- enters session mutex
- puts measurement on active list
- creates START packet (or uses shared counter path)
- submits PM4 and waits
- marks measurement ACTIVE
- starts polling timer
- captures baseline counter value for delta math
5.3 Read path (counting mode)
- Perf callback
read()callsaql_pmu_event_read() aql_pmu_event_read()callsaql_perf_measurement_read_atomic()- Atomic read returns cached value immediately and attempts to schedule async READ refresh
- Timer also periodically schedules READ refresh for active measurements
Result: perf-facing reads are mostly cache-based and async-refreshed.
5.4 Read path (synchronous worker)
Worker READ does:
- Create READ PM4 packet
- Submit PM4 and wait for completion
- Read result buffer
- If dimension-specific: index into flattened instance array
- Else: aggregate all instances for that block
- Convert absolute value into delta from
start_counter_value - Update measurement cache
5.5 Stop/del/destroy
stop()schedules async STOPdel()reads cached final value and calls stop path (atomic-safe behavior considered)event_destroyinvokes measurement destroy- destroy attempts to ensure STOP via workqueue and then drops creator ref
- final free happens via measurement kref release once no async refs remain
5.6 Module exit
- Cancel timer
- Unregister PMU (forces perf-side event cleanup)
- Flush pending AQL work
- Release session and destroy workqueue
- Free PMU resources and event attrs
6. Counter and Dimension Model
Counter model
counter_registryprovides stable logical counter IDs- Architecture maps logical counter ID to hardware event ID
- Current counters include SQ/GL2C/TA/GRBM groups
Dimension model
- Perf
config1encodes xcc/se/sa/wgp/cu/aggregate bits - Validation checks both topology limits and counter-supported dimensions
- Read path supports:
- aggregate mode (sum instances)
- dimension-specific mode (flat index lookup)
Count semantics
- Returned values are deltas for a measurement interval, not raw hardware absolute counts
- Baseline established at measurement start
7. Concurrency and Context Model
Context constraints
- Perf callbacks can run in atomic/no-sleep contexts
- GPU/KFD operations can sleep and require deferred execution
Mechanisms used
- RCU for global session pointer access
- Session mutex for lifecycle-serialized measurement transitions
- Spinlocks for measurement list, shared-counter list, per-measurement cache
- Global workqueue for deferred START/STOP/READ
- Global submit mutex for serialized PM4 submit+wait sequence
Work item model
aql_create_work_itemtakes measurement ref- Worker executes operation, signals completion (if sync waiter), drops ref, frees item
8. Current Reliability Risks and Design Debt
The following are present in the current implementation:
- Shared counter scope mismatch:
- Sharing key is only
counter_id, not(gpu_id, counter_id). - Can incorrectly share across GPUs.
- Shared owner pointer lifetime risk:
- Shared entry stores owner measurement pointer and reuses its counter pointer.
- If owner is destroyed first, stale pointer/UAF class risk exists.
- Shared stop ownership bug:
- Owner stop can release physical counter while other sharers still active.
- Timer stop semantics mismatch:
stop_timer_if_idlecurrently cancels timer unconditionally.- One stop can halt polling while other measurements are active.
- READ work dedupe ineffective:
- Each READ queue request allocates a new
work_struct, so queue-level dedupe does not prevent pile-up.
- Destroy STOP timeout handling is weak:
- Wait result is not fully enforced; fallback can try direct STOP from user context.
- MM context handling fragility in queue manager:
- Several hot-path accesses switch mm context without consistent strict gating to kernel-thread-only callsites.
- Dimension encoding ambiguity for explicit zero:
config1 == 0means unspecified; explicit zero-index requests are ambiguous.
- Observability debt:
total_samplesis initialized/printed but not updated.- Debug logging defaults are very verbose.
- Trace device exposure:
/dev/amdgpu_pmu_tracemode is0666(world writable), allowing unprivileged trace injection.
9. Testing Status (Current Tree)
Current tree includes:
- Unit tests for PM4 and packet-generation helpers in
src/aql_c/tests - Integration scripts and test apps under
test/andtest_apps/
Coverage gaps for current design:
- Cross-process shared-counter lifetime tests
- Multi-GPU sharing correctness tests
- Stress tests for READ workqueue backpressure
- Destroy/unload race and timeout-path validation
10. Design Direction (High-Level)
To improve simplicity and reliability while preserving cross-program sharing, the key direction is:
- Replace owner-measurement-based sharing with standalone counter-instance ownership keyed by GPU+counter, with explicit refcount transitions controlling START/STOP at
0->1and1->0.
This document intentionally captures the current design first; detailed target design changes should be tracked in a separate design-update section or follow-on RFC.