Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions emulation/rocjitsu/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,18 +139,18 @@ corresponding core model.
The firmware-free non-AQL compute ring follows the same boundary. MES
maps the queue and selects its `GpuVm` address space, then registers it with the
selected command processor through `GpuQueueRegistry`. The registry binding is
only a lifetime and notification adapter. The CP-owned `Pm4QueueController`
holds each queue's ring traversal, cursor publication, retry state, and concrete
`Pm4PacketProcessor`, which implements the exact NOP/SET_UCONFIG_REG subset. The
MMIO model supplies only the supported register-write sink. This is deliberately
not a general PM4 implementation.

The PM4 controller leases a queue under its management lock, then releases that
lock before VM access or register callbacks. Removal and reconfiguration wait for
an active lease to quiesce, so frontend callbacks cannot stall unrelated queue
registration or notification and queue state cannot be destroyed during service.
The CP invokes PM4 service outside its AQL queue lock, and each PM4 queue turn is
bounded so a large PM4 ring cannot monopolize the CP event.
only a lifetime and notification adapter. `CommandProcessor` owns AQL, native
PM4 and DRM compute queues in `ComputeQueueRecord`. Each record retains ring
configuration, shader registers, command streams, dispatches and cursor publication.
Native register writes require the MMIO model's supported register-write sink;
unsupported writes fault the queue. DRM submissions may ignore unused graphics state.

Queue service, removal and reconfiguration serialize under `hw_queue_mutex_`.
VM and register callbacks execute under that lock, so callbacks must not acquire
frontend locks whose holders can enter the CP. Each PM4 service turn is bounded.
Graceful removal waits for command streams, shader dispatches and durable
publications; forced removal cancels them. VM-polled native PM4 rings are rejected
until they have an execution poller; explicit and host-polled rings are supported.
Each service transaction pins both its `GpuVmAccess` and producer boundary; work
announced after a root replacement is deferred to a new snapshot rather than
being appended to an older blocked transaction.
Expand Down
6 changes: 3 additions & 3 deletions emulation/rocjitsu/docs/vm-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ execution.
| `amdgpu/packet_processor.h` | Shared compile-time one-packet contract and validated protocol-independent result envelope |
| `amdgpu/aql_packet_processor.h/cpp` | AqlPacketProcessor: fixed-size AQL decode and durable CP admission |
| `amdgpu/pm4_packet_processor.h/cpp` | Pm4PacketProcessor: PM4 framing, validation, and supported packet effects |
| `amdgpu/pm4_ring_consumer.h/cpp` | `Pm4RingConsumer`: per-queue ring traversal, retry, and cursor publication owned by the CP |
| `amdgpu/pm4_queue_controller.h/cpp` | Pm4QueueController: CP-owned PM4 queue, VM snapshot, retry, and cursor-publication state |
| `amdgpu/pm4_ring_consumer.h/cpp` | `Pm4RingConsumer`: standalone ring traversal helper; production compute queues execute in CommandProcessor |
| `amdgpu/dispatch_entry.h` | ComputeQueueRecord: CP-owned AQL/PM4 queues, nested command streams, shader dispatches and cursor publication |
| `amdgpu/pm4_queue_binding_factory.h/cpp` | Thin PM4 lifetime/notification adapter from GpuQueueRegistry to the owning CP |
| `amdgpu/gpu_queue_registry.h/cpp` | Frontend-neutral queue admission, lifetime, routing, and generation-safe handles |
| `amdgpu/aql_queue_binding_factory.h/cpp` | Reusable binding adapter from GpuQueueRegistry to the AQL command processor |
| `amdgpu/compute_queue_binding_factory.h/cpp` | Reusable binding adapter from GpuQueueRegistry to the unified compute command processor |

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ class KfdProcess {
uint32_t gpu_ordinal;
uint32_t doorbell_offset;
amdgpu::QueueHandle queue_handle;
std::optional<uint32_t> pm4_target_xcc;
};
std::unordered_map<uint32_t, QueueDoorbellInfo> queue_doorbell_map_;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
#include "rocjitsu/kmd/linux/kfd_ioctl_utils.h"
#include "rocjitsu/kmd/linux/kfd_topology.h"
#include "rocjitsu/kmd/linux/libc_passthrough.h"
#include "rocjitsu/vm/amdgpu/aql/aql_queue_binding_factory.h"
#include "rocjitsu/vm/amdgpu/command_processor.h"
#include "rocjitsu/vm/amdgpu/compute_queue_binding_factory.h"
#include "rocjitsu/vm/amdgpu/compute_unit.h"
#include "rocjitsu/vm/amdgpu/gpu_memory.h"
#include "rocjitsu/vm/amdgpu/hwreg.h"
Expand Down Expand Up @@ -198,9 +198,8 @@ int SimulatedKfd::submit_pm4(uint32_t render_minor, uint64_t queue_key,
auto *cp = gpus_[ordinal].soc->assign_queue_owner_cp(0);
if (!cp)
return -ENODEV;
amdgpu::Pm4SubmitQueue queue;
amdgpu::ComputeQueueConfig queue;
queue.address_space = process->gpu(ordinal).address_space;
queue.pm4 = std::make_shared<amdgpu::Pm4QueueState>();
queue.process_id = local_process_id_;
queue.queue_id = next_pm4_queue_id_++;
const uint32_t queue_id = queue.queue_id;
Expand Down Expand Up @@ -2694,6 +2693,9 @@ int SimulatedKfd::unmap_memory_ioctl(KfdProcess &proc, void *arg) {

int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
auto *args = static_cast<kfd_ioctl_create_queue_args *>(arg);
const uint32_t scheduling_percentage = args->queue_percentage & 0xff;
if (scheduling_percentage > 100)
return -EINVAL;
auto *gpu = find_gpu(args->gpu_id);
if (!gpu || !gpu->soc)
return -EINVAL;
Expand All @@ -2703,9 +2705,7 @@ int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
const bool is_sdma = args->queue_type == KFD_IOC_QUEUE_TYPE_SDMA ||
args->queue_type == KFD_IOC_QUEUE_TYPE_SDMA_XGMI ||
args->queue_type == KFD_IOC_QUEUE_TYPE_SDMA_BY_ENG_ID;
if (is_pm4_compute)
return -ENOTSUP;
if (!is_aql_compute && !is_sdma)
if (!is_aql_compute && !is_pm4_compute && !is_sdma)
return -ENOTSUP;

const std::optional<uint32_t> ring_size = normalize_queue_ring_size(args->ring_size);
Expand All @@ -2717,12 +2717,18 @@ int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
// different processes therefore share XCD resources while each process still
// distributes additional queues across the device.
const uint32_t queue_ordinal = proc.next_queue_id_ - 1;
// KFD reserves percentage bits 8..15 for a native PM4 queue's target XCC.
// An AQL vendor IB inherits the AQL queue's replicas instead of this placement.
const uint32_t owner_ordinal =
is_pm4_compute ? (args->queue_percentage >> 8) & 0xff : queue_ordinal;
Comment thread
Groverkss marked this conversation as resolved.
Resolved
if (is_pm4_compute && owner_ordinal >= gpu->soc->num_xcds())
return -EINVAL;
amdgpu::CommandProcessor *target_cp =
is_sdma ? nullptr : gpu->soc->assign_queue_owner_cp(queue_ordinal);
is_sdma ? nullptr : gpu->soc->assign_queue_owner_cp(owner_ordinal);
if (!is_sdma && target_cp == nullptr)
return -EINVAL;
const uint32_t target_xcc_id =
is_sdma ? args->sdma_engine_id : gpu->soc->queue_xcd_id(queue_ordinal);
is_sdma ? args->sdma_engine_id : gpu->soc->queue_xcd_id(owner_ordinal);

// Build the HW queue and reserve all per-process state under alloc_mutex_, then
// register it with the CommandProcessor with the lock RELEASED. The CP thread
Expand Down Expand Up @@ -2796,8 +2802,9 @@ int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
.producer_pointer_address = args->write_pointer_address};
queue_request.binding_factory =
is_sdma ? amdgpu::make_sdma_queue_binding_factory(gpu->soc->sdma_queue_scheduler())
: amdgpu::make_aql_queue_binding_factory(*target_cp);
: amdgpu::make_compute_queue_binding_factory(*target_cp);
queue_request.engine_id = args->sdma_engine_id;
queue_request.scheduling_percentage = scheduling_percentage;
// doorbell_base is captured here under alloc_mutex_ but register_queue() runs
// after the lock is released. This is stable because ROCr maps the doorbell
// page before creating queues, and queue creation for a process is single-
Expand All @@ -2809,9 +2816,10 @@ int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
.host_base = gs.doorbell_monitor_page,
.last_value = ~uint64_t(0)};
queue_request.type = is_sdma ? amdgpu::QueueType::Sdma : amdgpu::QueueType::Compute;
queue_request.packet_format =
is_sdma ? amdgpu::QueuePacketFormat::Sdma : amdgpu::QueuePacketFormat::Aql;
queue_request.abi = is_sdma ? amdgpu::QueueAbi::Generic : amdgpu::QueueAbi::KfdAql;
queue_request.packet_format = is_sdma ? amdgpu::QueuePacketFormat::Sdma
: is_pm4_compute ? amdgpu::QueuePacketFormat::Pm4
: amdgpu::QueuePacketFormat::Aql;
queue_request.abi = is_aql_compute ? amdgpu::QueueAbi::KfdAql : amdgpu::QueueAbi::Generic;
// Queue creation initializes both SDMA pointers to zero below. Preserve that
// device-side cursor explicitly so execution does not depend on reading the
// writeback destination before the first packet can retire.
Expand All @@ -2828,7 +2836,7 @@ int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
// unsupported value silently acquires.
queue_request.xcd_fanout = is_aql_compute;
// amd_queue_t base: write_pointer_address points to write_dispatch_id.
if (!is_sdma)
if (is_aql_compute)
queue_request.queue_descriptor_address =
args->write_pointer_address - offsetof(amd_queue_t, write_dispatch_id);
if (!is_sdma && args->ctx_save_restore_address != 0) {
Expand Down Expand Up @@ -2896,6 +2904,7 @@ int SimulatedKfd::create_queue_ioctl(KfdProcess &proc, void *arg) {
.gpu_ordinal = gpu_ordinal(args->gpu_id),
.doorbell_offset = queue_request.doorbell.offset,
.queue_handle = queue_handle,
.pm4_target_xcc = is_pm4_compute ? std::optional(owner_ordinal) : std::nullopt,
};
proc.queue_snapshot_map_[queue_id] = {
.ring_base_address = args->ring_base_address,
Expand Down Expand Up @@ -3009,6 +3018,10 @@ int SimulatedKfd::set_cu_mask_ioctl(KfdProcess &proc, void *arg) {

int SimulatedKfd::update_queue_ioctl(KfdProcess &proc, void *arg) {
auto *args = static_cast<kfd_ioctl_update_queue_args *>(arg);
// Decode scheduling independently of the native PM4 placement field.
const uint32_t scheduling_percentage = args->queue_percentage & 0xff;
if (scheduling_percentage > 100)
return -EINVAL;
const std::optional<uint32_t> ring_size = normalize_queue_ring_size(args->ring_size);
if (!ring_size)
return -EINVAL;
Expand All @@ -3026,12 +3039,21 @@ int SimulatedKfd::update_queue_ioctl(KfdProcess &proc, void *arg) {
if (!queue || queue->gpu_ordinal >= gpus_.size() || !gpus_[queue->gpu_ordinal].soc)
return -EFAULT;

if (queue->pm4_target_xcc) {
const uint32_t target_xcc = (args->queue_percentage >> 8) & 0xff;
if (target_xcc >= gpus_[queue->gpu_ordinal].soc->num_xcds())
return -EINVAL;
// Moving a live CP record requires migrating its registers and pending streams.
if (target_xcc != *queue->pm4_target_xcc)
return -EOPNOTSUPP;
}

amdgpu::QueueReconfigureResult update;
try {
update = gpus_[queue->gpu_ordinal].soc->queue_registry().reconfigure_queue(
queue->queue_handle, {.ring_base_address = args->ring_base_address,
.ring_size_bytes = *ring_size,
.scheduling_percentage = args->queue_percentage});
.scheduling_percentage = scheduling_percentage});
} catch (const std::bad_alloc &) {
return -ENOMEM;
} catch (...) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

rj_add_object_library(rocjitsu_vm_amdgpu
aql/aql_packet_processor.cpp
aql/aql_queue_binding_factory.cpp
compute_queue_binding_factory.cpp
circular_ring_reader.cpp
buffer_format.cpp
cluster_lds_multicast.cpp
Expand All @@ -27,7 +27,6 @@ rj_add_object_library(rocjitsu_vm_amdgpu
memory_wait_scoreboard.cpp
pm4/pm4_packet_processor.cpp
pm4/pm4_queue_binding_factory.cpp
pm4/pm4_queue_controller.cpp
pm4/pm4_ring_consumer.cpp
sdma_packet_processor.cpp
sdma_queue_binding_factory.cpp
Expand Down
Loading
Loading