Files
TheRock/rocm-systems/experimental/perf-dkms/QUICKSTART.md
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1000 lines
49 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# perf-dkms Kernel Module - Developer Quickstart Guide
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Setup Flow](#setup-flow)
3. [Reading Flow](#reading-flow)
4. [Teardown Flow](#teardown-flow)
5. [Key Data Structures](#key-data-structures)
6. [Counter Sharing](#counter-sharing)
7. [Dimension-Specific Monitoring](#dimension-specific-monitoring)
---
## Architecture Overview
### Layer Stack
The perf-dkms kernel module provides GPU performance counter monitoring through the Linux perf subsystem. The architecture consists of multiple layers:
```
┌─────────────────────────────────────────────────────────────────┐
│ Userspace (perf tool) │
│ Example: perf stat -C 0 -e amdgpu_pmu/sq_waves/ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Linux Perf Subsystem (kernel/events/) │
│ PMU callbacks: event_init, add, del, start, stop, read │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PMU Integration Layer (pmu_main.c) │
│ - Registers PMU with perf subsystem │
│ - Routes event operations to AQL layer │
│ - Manages background polling timer │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ AQL Integration Layer (aql_pmu_integration.c) │
│ - Global session management │
│ - GPU selection and mapping │
│ - Workqueue for atomic context handling │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Measurement Management (aql_packet_ops.c, aql_perf.c) │
│ - Counter allocation & sharing │
│ - PM4 packet generation (START/READ/STOP) │
│ - Reference counting for safe cleanup │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PM4 Packet Generation (aql_c/packet_generation.c) │
│ - Generate START packets (configure counters) │
│ - Generate READ packets (read counter values) │
│ - Generate STOP packets (disable counters) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ KFD Submission Layer (kfd_ioctl_submit_ib_packet) │
│ - Submits PM4 command buffers to GPU │
│ - Synchronous execution (blocks until GPU completes) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ GPU Hardware │
│ - Executes PM4 commands │
│ - Configures performance counters │
│ - Writes counter values to memory │
└─────────────────────────────────────────────────────────────────┘
```
### File Organization
```
experimental/perf-dkms/
├── src/
│ ├── pmu_main.c # PMU driver entry point & callbacks
│ ├── pmu_events.c # Event validation & utilities
│ ├── pmu_dimension.h # Dimension parsing & validation
│ ├── aql_pmu_integration.c # Global session & GPU mapping
│ ├── aql_perf.c # Session & GPU discovery
│ ├── aql_packet_ops.c # Measurement & packet creation
│ ├── aql_error_recovery.c # Error handling & recovery
│ ├── aql_queue_manager.c/h # AQL queue lifecycle
│ ├── kfd_ioctl_bridge.c/h # KFD ioctl bridge (vm_mmap)
│ └── aql_c/
│ ├── packet_generation.c # PM4 packet builders
│ ├── pm4_packets.c # Low-level PM4 primitives
│ ├── counter_registry.c # Counter definitions
│ ├── arch_creator.c # Architecture factory
│ ├── gfx12_creator.c # GFX12-specific architecture
│ ├── gfx12_events.c # GFX12 event mappings
│ ├── gfx9_creator.c # GFX9-specific architecture
│ ├── gfx9_events.c # GFX9 event mappings
│ └── aql_queue.c/h # Ring buffer & IB pool
└── QUICKSTART.md # This file
```
### Component Relationships
```
┌──────────────────────────────────────────────────────────────────┐
│ amdgpu_pmu (singleton) │
│ - struct pmu (Linux perf PMU) │
│ - hrtimer for background polling │
│ - Statistics tracking │
└──────────────────────────────────────────────────────────────────┘
│ owns
▼
┌──────────────────────────────────────────────────────────────────┐
│ global_aql_session (singleton) │
│ - kfd_file, kfd_process (KFD integration) │
│ - gpu_ids[] (discovered GPUs) │
│ - archs[] (architecture per GPU) │
│ - active_measurements (list) │
│ - shared_counters (list for sharing) │
└──────────────────────────────────────────────────────────────────┘
│ contains
▼
┌──────────────────────────────────────────────────────────────────┐
│ aql_measurement (one per perf event) │
│ - perf_event* (back-reference) │
│ - allocated_counter* (hardware counter) │
│ - shared_ref* (for counter sharing) │
│ - state, cached_counter_value │
│ - kref (reference counting) │
└──────────────────────────────────────────────────────────────────┘
│ uses
▼
┌──────────────────────────────────────────────────────────────────┐
│ arch_t (architecture, one per GPU) │
│ - block_map (SQ, GRBM, TA, GL2C, TCC blocks) │
│ - control_regs (register offsets) │
│ - num_se, num_sa, num_wgp_per_sa │
└──────────────────────────────────────────────────────────────────┘
│ contains
▼
┌──────────────────────────────────────────────────────────────────┐
│ block_info_t (per hardware block) │
│ - counter_reg_info[] (array of counters) │
│ - dimension[] (SE/SA/WGP dimensions) │
│ - instance_count (for GL2C/TCC) │
└──────────────────────────────────────────────────────────────────┘
│ contains
▼
┌──────────────────────────────────────────────────────────────────┐
│ counter_reg_info_t (per hardware counter) │
│ - select_addr, control_addr (config registers) │
│ - register_addr_lo, register_addr_hi (result) │
│ - allocation (state, event_id, user_id) │
│ - command_buffer, data_buffer (GPU memory) │
└──────────────────────────────────────────────────────────────────┘
```
---
## Setup Flow
When a user runs `perf stat -e amdgpu_pmu/sq_waves/ -C 0`, the following sequence occurs:
### Step-by-Step Initialization
```
User Command
│
▼
perf_event_open() syscall
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. amdgpu_pmu_event_init() [pmu_main.c:379] │
│ - Validates event configuration │
│ - Extracts dimension parameters from config1 │
│ - Maps CPU to GPU ID │
│ - Calls aql_pmu_event_init() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. aql_pmu_event_init() [aql_pmu_integration.c:285] │
│ - Checks AQL availability │
│ - Selects target GPU │
│ - Creates aql_measurement structure │
│ - Stores measurement in event->hw.config_base │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. aql_perf_measurement_create() [aql_packet_ops.c:620] │
│ - Allocates measurement structure │
│ - Initializes state to MEASUREMENT_IDLE │
│ - Sets counter_id from event->attr.config │
│ - Initializes reference count (kref) to 1 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. amdgpu_pmu_add() [pmu_main.c:504] │
│ - Adds event to PMU (called by perf core) │
│ - If PERF_EF_START flag set, calls aql_pmu_event_start() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. aql_pmu_event_start() [aql_pmu_integration.c:391] │
│ - Queues START work on global workqueue │
│ - Returns immediately (atomic context safe) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. aql_work_handler() [aql_packet_ops.c:1289] │
│ - Dequeued from workqueue (process context) │
│ - Calls aql_perf_measurement_start() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 7. aql_perf_measurement_start() [aql_packet_ops.c:682] │
│ - Acquires session mutex │
│ - Adds to active_measurements list │
│ - Calls aql_perf_create_start_packet() │
│ - Submits PM4 packet via kfd_ioctl_submit_ib_packet() │
│ - Starts background timer │
│ - Reads baseline counter value │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 8. aql_perf_create_start_packet() [aql_packet_ops.c:174] │
│ - Checks for shared counter (find_and_install_shared_counter) │
│ - If not shared: atomically allocates counter │
│ - Builds counter_info_t and counter_collection_t │
│ - Calls generate_start_packet() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 9. generate_start_packet() [packet_generation.c:197] │
│ - CS partial flush (synchronization) │
│ - Set GRBM to broadcast mode │
│ - Disable perfmon │
│ - Configure counter SELECT & CONTROL registers │
│ - Enable compute perfcount │
│ - Enable perfmon (state_enable) │
│ - Final CS partial flush │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 10. GPU Execution │
│ - Processes PM4 commands synchronously │
│ - Configures hardware counter registers │
│ - Starts counting events │
└─────────────────────────────────────────────────────────────────┘
```
### Counter Allocation Details
Counter allocation uses atomic operations to prevent race conditions:
```c
// In aql_counter_try_allocate() [aql_perf.c:729]
for (i = 0; i < block->counter_count; i++) {
counter_reg_info_t *reg = &block->counter_reg_info[i];
// Atomic compare-and-swap: FREE -> ALLOCATED
old_state = atomic_cmpxchg(&reg->allocation.state,
COUNTER_STATE_FREE,
COUNTER_STATE_ALLOCATED);
if (old_state == COUNTER_STATE_FREE) {
// Successfully claimed!
reg->allocation.event_id = event_id;
reg->allocation.user_id = (uint32_t)(uintptr_t)perf_event;
return reg;
}
}
```
---
## Reading Flow
Counter values are read through two mechanisms:
1. **Background Timer Polling** - Periodic refresh of cached values
2. **On-Demand Reads** - Direct reads when perf requests data
### Background Timer Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Timer fires every 20ms [pmu_main.c:216] │
│ (configurable via timer_period_ms module parameter) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ amdgpu_pmu_timer_handler() │
│ - Iterates active_measurements list │
│ - For each ACTIVE measurement: │
│ Creates AQL_WORK_READ work item │
│ Queues on global workqueue │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_work_handler() - AQL_WORK_READ case │
│ - Calls aql_perf_measurement_read() │
│ - Updates cached_counter_value under cache_lock │
│ - Sets cache_valid = true │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_perf_measurement_read() [aql_packet_ops.c:1138] │
│ - Creates READ PM4 packet │
│ - Submits to GPU synchronously │
│ - Reads result buffer │
│ - Aggregates or filters by dimension │
│ - Computes delta from start_counter_value │
└─────────────────────────────────────────────────────────────────┘
```
### On-Demand Read Flow
When perf calls `read()` on the event file descriptor:
```
perf read() syscall
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ amdgpu_pmu_read() [pmu_main.c:656] │
│ - Checks if event uses AQL hardware │
│ - Calls aql_pmu_event_read() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_pmu_event_read() [aql_pmu_integration.c:447] │
│ - Calls aql_perf_measurement_read_atomic() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_perf_measurement_read_atomic() [aql_packet_ops.c:1475] │
│ - Returns cached_counter_value immediately │
│ - Schedules background refresh (non-blocking) │
└─────────────────────────────────────────────────────────────────┘
```
### PM4 READ Packet Generation
```
generate_read_packet() [packet_generation.c:320]
│
├─→ Enable perfmon with sampling (non-destructive read)
│
├─→ Set GRBM to broadcast mode
│
├─→ CS partial flush (synchronization)
│
└─→ For each counter:
│
├─→ SE-dependent blocks (SQ, TA):
│ For each (SE, SA, WGP) in GPU topology:
│ Set GRBM_GFX_INDEX to (SE, SA, WGP)
│ COPY_DATA: counter_reg -> GPU memory
│ current_addr += 8 bytes
│
└─→ Global blocks (GRBM, GL2C, TCC):
For each instance:
Set GRBM_GFX_INDEX to instance
COPY_DATA: counter_reg -> GPU memory
current_addr += 8 bytes
```
### Result Buffer Layout
For an SQ counter on GFX12 (4 SE × 2 SA × 4 WGP = 32 instances):
```
GPU Memory (data_buffer):
┌──────────┬──────────┬──────────┬──────────┬─────┬──────────┐
│ [0] │ [1] │ [2] │ [3] │ ... │ [31] │
│ SE0,SA0 │ SE0,SA0 │ SE0,SA0 │ SE0,SA0 │ │ SE3,SA1 │
│ WGP0 │ WGP1 │ WGP2 │ WGP3 │ │ WGP3 │
│ (uint64) │ (uint64) │ (uint64) │ (uint64) │ │ (uint64) │
└──────────┴──────────┴──────────┴──────────┴─────┴──────────┘
Flat index calculation:
flat_idx = (se * num_sa * num_wgp) + (sa * num_wgp) + wgp
Example: SE=1, SA=0, WGP=2
flat_idx = (1 * 2 * 4) + (0 * 4) + 2 = 10
```
---
## Teardown Flow
When the perf event is closed or perf exits:
### Normal Teardown (Process Context)
```
close() syscall on event fd
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ perf core calls pmu->del() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ amdgpu_pmu_del() [pmu_main.c:541] │
│ - Checks context: in_atomic() || irqs_disabled()? │
│ - If process context: synchronous cleanup │
│ - Calls aql_pmu_event_read_sync() for final count │
│ - Calls aql_pmu_event_stop() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_pmu_event_stop() [aql_pmu_integration.c:419] │
│ - Calls aql_perf_measurement_stop_atomic() │
│ - Queues STOP work on global workqueue │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_work_handler() - AQL_WORK_STOP │
│ - Calls aql_perf_measurement_stop() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_perf_measurement_stop() [aql_packet_ops.c:814] │
│ - If owns_counter: generate & submit STOP packet │
│ - Release allocated counter (atomic set to FREE) │
│ - Release shared counter reference (decrement refcount) │
│ - Remove from active_measurements list │
│ - Stop timer if no more active measurements │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ perf core calls event->destroy() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_pmu_event_destroy() [aql_pmu_integration.c:365] │
│ - Calls aql_perf_measurement_destroy() │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_perf_measurement_destroy() [aql_packet_ops.c:1207] │
│ - Ensures measurement stopped │
│ - Releases counter if still allocated │
│ - Releases shared reference │
│ - Calls aql_measurement_put() (drops creator reference) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ aql_measurement_release() [aql_packet_ops.c:547] │
│ - Called when refcount reaches 0 │
│ - Final cleanup: free measurement structure │
└─────────────────────────────────────────────────────────────────┘
```
### Atomic Context Teardown
If `del()` is called from atomic context (e.g., interrupt):
```
amdgpu_pmu_del() detects atomic context
│
├─→ Reads cached counter value (no blocking)
│
├─→ Marks event as STOPPED
│
└─→ Actual cleanup deferred to event->destroy() callback
```
### PM4 STOP Packet
```
generate_stop_packet() [packet_generation.c:655]
│
├─→ Set GRBM to broadcast mode
│
├─→ Set perfmon state to STOP
│
└─→ CS partial flush (synchronization)
```
---
## Key Data Structures
### struct amdgpu_pmu
Main PMU driver structure (singleton):
```c
struct amdgpu_pmu {
struct pmu pmu; // Linux perf PMU
struct hrtimer timer; // Background polling timer
ktime_t timer_period; // Default: 20ms
// Event management
spinlock_t lock;
struct amdgpu_pmu_event events[64];
DECLARE_BITMAP(used_mask, 64);
// Statistics
atomic64_t total_events;
atomic64_t hardware_events;
};
```
**Location**: `src/amdgpu_pmu.h`
### struct aql_perf_session
Global session managing KFD and GPU resources (singleton):
```c
struct aql_perf_session {
// KFD integration
struct file *kfd_file; // /dev/kfd handle
struct kfd_process *process; // KFD process context
// GPU management
uint32_t *gpu_ids; // Array of GPU device IDs
uint32_t num_gpus; // Number of GPUs discovered
arch_t **archs; // Architecture per GPU
// Measurement tracking
struct list_head active_measurements;
spinlock_t measurement_lock;
// Counter sharing
struct list_head shared_counters;
spinlock_t shared_lock;
// State
struct mutex session_mutex;
refcount_t ref_count;
enum session_state state;
uint64_t session_id;
};
```
**Location**: `src/aql_perf.h`
**Lifecycle**: Created in `aql_pmu_init()`, destroyed in `aql_pmu_cleanup()`
### struct aql_measurement
Per-event measurement tracking (one per perf event):
```c
struct aql_measurement {
struct list_head list; // Link in active_measurements
struct aql_perf_session *session; // Parent session
uint32_t gpu_id; // Target GPU device ID
uint32_t counter_id; // Counter ID (from registry)
struct perf_event *event; // Back-reference to perf event
// State tracking
enum measurement_state state; // IDLE, STARTING, ACTIVE, etc.
ktime_t start_time;
uint64_t start_counter_value; // Baseline for delta
uint64_t last_counter_value;
// Counter allocation
counter_reg_info_t *allocated_counter; // Hardware counter
bool owns_counter; // True if allocated by us
struct shared_counter_ref *shared_ref; // Sharing reference
// Dimension-specific monitoring
struct pmu_dimension_coords target_dims;
bool dimension_specific;
// Atomic context support
spinlock_t cache_lock;
uint64_t cached_counter_value;
bool cache_valid;
// Reference counting
struct kref refcount; // Prevents use-after-free
};
```
**Location**: `src/aql_perf.h`
**Lifecycle**:
- Created in `aql_perf_measurement_create()`
- Released when `kref` reaches 0 in `aql_measurement_release()`
### struct arch_t
GPU architecture definition (one per GPU):
```c
typedef struct {
const char *name; // e.g., "gfx12"
// Topology
uint32_t num_se; // Shader Engines (e.g., 4)
uint32_t num_sa; // Shader Arrays per SE (e.g., 2)
uint32_t num_wgp_per_sa; // WGPs per SA (e.g., 4)
uint32_t num_cu; // Compute Units
uint32_t num_xcc; // XCCs
// Hardware blocks
struct {
block_info_t *blocks[HW_IP_BLOCK_LAST];
uint32_t block_count;
} block_map;
// Control registers
struct {
uint32_t grbm_gfx_index;
uint32_t cp_perfmon_cntl;
uint32_t compute_perfcount_enable;
// ... more register offsets
} control_regs;
} arch_t;
```
**Location**: `src/aql_c/aql_structures.h`
**Creation**: `arch_create_by_name("gfx12")` in `aql_perf_session_initialize()`
### struct block_info_t
Hardware block definition (SQ, GRBM, TA, GL2C, TCC):
```c
typedef struct {
const char *name; // e.g., "SQ", "GRBM"
hw_ip_block_t block_id; // HW_IP_BLOCK_SQ, etc.
// Counter configuration
counter_reg_info_t *counter_reg_info; // Array of counters
uint32_t counter_count; // Number of counters
// Topology dimensions
dimension_t dimensions[MAX_DIMENSIONS];
uint32_t dimension_count;
// For global blocks (GL2C, TCC)
uint32_t instance_count; // Number of instances
// Event ID range
uint32_t event_id_max;
} block_info_t;
```
**Location**: `src/aql_c/aql_structures.h`
**Example**: GFX12 SQ block has 16 counters, dimensions [SE=4, SA=2, WGP=4]
### struct counter_reg_info_t
Individual hardware counter register layout:
```c
typedef struct {
// Configuration registers
uint32_t select_addr; // Event select register
uint32_t control_addr; // Control register (0 if unused)
// Result registers
uint32_t register_addr_lo; // Counter value low 32 bits
uint32_t register_addr_hi; // Counter value high 32 bits
// Allocation tracking
struct {
atomic_t state; // FREE, ALLOCATED
uint32_t event_id; // Event being counted
uint32_t user_id; // perf_event pointer
ktime_t allocation_time;
// GPU data buffer pointers (provided by AQL queue manager)
void *data_cpu_addr; // Kernel VA for reading counter results
uint64_t data_gpu_addr; // GPU VA for COPY_DATA destination
uint32_t data_size; // Size of available data buffer
} allocation;
} counter_reg_info_t;
```
**Location**: `src/aql_c/aql_structures.h`
**Allocation**: Buffers provided by the per-GPU AQL queue's shared data buffer during `aql_perf_setup_counter_buffers()`
---
## Counter Sharing
Multiple perf events monitoring the same counter type can share a single hardware counter allocation. This is essential for dimension-specific monitoring where different events target different SE/SA/WGP instances.
### Sharing Mechanism
```
Event 1: perf stat -e amdgpu_pmu/sq_waves,se=0/
Event 2: perf stat -e amdgpu_pmu/sq_waves,se=1/
Event 3: perf stat -e amdgpu_pmu/sq_waves,se=2/
│
│ All monitor counter_id = SQ_WAVES
▼
┌──────────────────────────────────────────────────────────────┐
│ shared_counter_ref (refcount = 3) │
│ - counter_id: SQ_WAVES │
│ - measurement: -> Event 1's measurement (owner) │
│ - ref_count: 3 │
└──────────────────────────────────────────────────────────────┘
│
│ Points to
▼
┌──────────────────────────────────────────────────────────────┐
│ allocated_counter (SQ block, counter 0) │
│ - allocation.state: ALLOCATED │
│ - allocation.event_id: 0x001 (SQ_WAVES) │
│ - data_buffer: GPU memory for all 32 instances │
└──────────────────────────────────────────────────────────────┘
```
### Sharing Flow
```c
// In aql_perf_create_start_packet() [aql_packet_ops.c:229]
// 1. Try to find existing shared counter
shared_ref = find_and_install_shared_counter(session, counter_id, measurement);
if (shared_ref) {
// Found! Reuse existing allocation
// - measurement->allocated_counter now points to shared counter
// - measurement->shared_ref holds reference (refcount++)
// - measurement->owns_counter = false
*out_pm4_buffer = NULL; // No START packet needed
return 0;
}
// 2. First event for this counter - allocate new counter
allocated_counter = aql_counter_try_allocate(block, event_id, perf_event);
// 3. Create new shared reference
shared_ref = create_shared_counter(session, counter_id, measurement);
measurement->shared_ref = shared_ref;
measurement->owns_counter = true; // We allocated it
// 4. Generate and submit START packet (only for first event)
```
### Reference Counting
```
Event 1 starts: refcount = 1, counter ALLOCATED, START packet sent
Event 2 starts: refcount = 2, counter shared, no START packet
Event 3 starts: refcount = 3, counter shared, no START packet
Event 2 stops: refcount = 2, counter still in use, no STOP packet
Event 3 stops: refcount = 1, counter still in use, no STOP packet
Event 1 stops: refcount = 0, counter FREED, STOP packet sent
```
### Why Sharing Works
All events share the **same hardware counter** which reads all GPU instances. The dimension filtering happens in software:
```c
// In aql_perf_measurement_read() [aql_packet_ops.c:1138]
// Counter reads ALL instances into result_buffer
uint64_t result_buffer[32]; // For GFX12: 4 SE × 2 SA × 4 WGP
if (measurement->dimension_specific) {
// Filter to specific instance
uint32_t flat_idx = encode_dimension_index(
measurement->target_dims.se,
measurement->target_dims.sa,
measurement->target_dims.wgp,
arch->num_sa,
arch->num_wgp_per_sa
);
counter_value = result_buffer[flat_idx];
} else {
// Aggregate all instances
counter_value = aql_aggregate_counter_instances(...);
}
```
---
## Dimension-Specific Monitoring
Users can monitor specific hardware instances using dimension parameters:
### Syntax
```bash
# Monitor specific SE
perf stat -C 0 -e amdgpu_pmu/sq_waves,se=0/
# Monitor specific SA within SE
perf stat -C 0 -e amdgpu_pmu/sq_waves,se=1,sa=0/
# Monitor specific WGP within SA
perf stat -C 0 -e amdgpu_pmu/sq_waves,se=2,sa=1,wgp=3/
# Aggregate all dimensions (default)
perf stat -C 0 -e amdgpu_pmu/sq_waves,aggregate=1/
```
### Dimension Encoding
Dimensions are encoded in `event->attr.config1`:
```
config1 bit layout (64 bits):
┌─────────┬─────────┬─────────┬─────────┬─────────┬──────────────┐
│ Bits │ Field │ Values │ Bits │ Field │ Values │
├─────────┼─────────┼─────────┼─────────┼─────────┼──────────────┤
│ 0-7 │ XCC │ 0-255 │ 24-31 │ WGP │ 0-255 │
│ 8-15 │ SE │ 0-255 │ 32-39 │ CU │ 0-255 │
│ 16-23 │ SA │ 0-255 │ 40 │ AGG │ 0=off, 1=on │
└─────────┴─────────┴─────────┴─────────┴─────────┴──────────────┘
```
### Dimension Extraction
```c
// In pmu_main.c:434
if (config1 != 0) {
pmu_extract_dimensions(config1, &dims);
// dims.se, dims.sa, dims.wgp extracted
// dims.aggregate flag
// Validate against GPU limits
get_gpu_dimension_limits(gpu_id, &gpu_limits);
pmu_validate_dimensions(&dims, &gpu_limits);
// Store in measurement
measurement->target_dims = dims;
measurement->dimension_specific = !dims.aggregate;
}
```
### Result Filtering
When reading counters, dimension-specific events filter the result:
```c
// GFX12 example: 4 SE × 2 SA × 4 WGP = 32 instances
// Result buffer layout after GPU read:
uint64_t result_buffer[32] = {
// [0] SE0,SA0,WGP0
// [1] SE0,SA0,WGP1
// [2] SE0,SA0,WGP2
// [3] SE0,SA0,WGP3
// [4] SE0,SA1,WGP0
// ...
// [31] SE3,SA1,WGP3
};
// Event requesting SE=1, SA=0, WGP=2
uint32_t flat_idx = encode_dimension_index(1, 0, 2, 2, 4);
// flat_idx = (1 * 2 * 4) + (0 * 4) + 2 = 10
counter_value = result_buffer[10]; // Return only this instance
```
### Dimension Validation
Each counter defines which dimensions it supports:
```c
// In counter_registry.c
counter_def_t sq_waves = {
.id = COUNTER_SQ_WAVES,
.name = "sq_waves",
.hw_block = HW_IP_BLOCK_SQ,
.supported_dimensions = DIM_SE | DIM_SA | DIM_WGP, // SE-dependent
};
counter_def_t grbm_count = {
.id = COUNTER_GRBM_COUNT,
.name = "grbm_count",
.hw_block = HW_IP_BLOCK_GRBM,
.supported_dimensions = 0, // Global block, no dimensions
};
```
Validation rejects incompatible dimension requests:
```c
// In pmu_dimension.c
int pmu_validate_counter_dimensions(const counter_def_t *counter,
const struct pmu_dimension_coords *dims)
{
if (dims->se != DIM_UNSPECIFIED &&
!(counter->supported_dimensions & DIM_SE)) {
return -EINVAL; // SE not supported for this counter
}
// ... similar for SA, WGP, CU
}
```
---
## Debugging Tips
### Enable Debug Logging
```bash
# Load module with debug enabled
modprobe amdgpu_pmu debug_enable=1
# Check dmesg for detailed logs
dmesg -w | grep -E "(AQL_PERF|PMU)"
```
### Monitor Active Measurements
```bash
# View session state
cat /sys/kernel/debug/amdgpu_pmu/session_state
# View active measurements
cat /sys/kernel/debug/amdgpu_pmu/measurements
```
### Trace Event Flow
Key log messages to watch for:
```
[PMU] aql_pmu_event_init: config=0x1 config1=0x0
[PMU] Mapped CPU 0 to GPU 7410
[PMU] aql_perf_measurement_create: session_id=1, gpu_id=7410
[PMU] generate_start_packet: Allocating counter from block=SQ
[PMU] Counter allocated successfully
[PMU] START packet submitted
[PMU] Timer: Scheduled read for GPU 7410
[PMU] READ_SYNC: GPU 7410, counter_value=12345
[PMU] aql_perf_measurement_stop: GPU 7410
```
### Common Issues
**Issue**: `event_init` fails with `-EINVAL`
- **Cause**: Invalid dimension values or counter doesn't support requested dimensions
- **Fix**: Check counter's `supported_dimensions` in counter registry
**Issue**: Counter reads return 0
- **Cause**: Counter not started or GPU idle
- **Fix**: Verify GPU workload is running, check START packet submission
**Issue**: Measurement cleanup warnings
- **Cause**: Reference counting imbalance or work items not flushed
- **Fix**: Ensure `aql_pmu_flush_all_measurements()` called before session cleanup
---
## Summary
The perf-dkms kernel module provides comprehensive GPU performance monitoring through:
1. **Linux perf integration** - Standard perf event interface
2. **PM4 packet submission** - Direct GPU command buffer generation
3. **Counter sharing** - Efficient resource usage for dimension-specific monitoring
4. **Atomic context safety** - Workqueue-based deferred operations
5. **Reference counting** - Safe cleanup with async work items
Key architectural principles:
- **One global session** - Shared by all events
- **Per-event measurements** - Isolated state tracking
- **Atomic counter allocation** - Lock-free race prevention
- **Software dimension filtering** - Hardware reads all instances, SW selects
- **Background polling** - Periodic cache refresh for low latency reads
For more details, consult the source code in `experimental/perf-dkms/src/`.