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
1000 lines
49 KiB
Markdown
1000 lines
49 KiB
Markdown
# 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(®->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/`.
|