938b34da24
The over arching goal it so provide an API that pre-silicon models can latch into for software bring up.# Please enter the commit message for your changes. Lines starting
[ROCm/ROCR-Runtime commit: d4b85b6bf5]
823 lines
25 KiB
C
823 lines
25 KiB
C
/*
|
|
* Copyright © 2025 Advanced Micro Devices, Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy,
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including
|
|
* the next paragraph) shall be included in all copies or substantial
|
|
* portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "hsakmt/hsakmtmodel.h"
|
|
#include "libhsakmt.h"
|
|
#include "hsakmt/hsakmttypes.h"
|
|
#include "hsakmt/hsakmtmodeliface.h"
|
|
#define _GNU_SOURCE
|
|
#define __USE_GNU
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <dlfcn.h>
|
|
#include <sys/mman.h>
|
|
#include <fcntl.h>
|
|
|
|
bool hsakmt_use_model;
|
|
char *hsakmt_model_topology;
|
|
|
|
struct model_node
|
|
{
|
|
bool is_gpu;
|
|
void *aperture;
|
|
hsakmt_model_t *model;
|
|
uint64_t doorbell_offset;
|
|
uint64_t total_memory_size;
|
|
uint64_t allocated_memory_size;
|
|
};
|
|
|
|
struct model_event
|
|
{
|
|
uint32_t event_type;
|
|
uint32_t auto_reset;
|
|
uint64_t value;
|
|
};
|
|
|
|
struct model_mem_data
|
|
{
|
|
uint64_t va_addr;
|
|
uint64_t file_offset;
|
|
uint64_t size;
|
|
uint64_t mapped_nodes_bitmask;
|
|
uint32_t flags;
|
|
uint32_t node_id;
|
|
};
|
|
|
|
struct model_queue
|
|
{
|
|
hsakmt_model_queue_t *queue;
|
|
uint32_t node_id;
|
|
};
|
|
|
|
#define MAX_MODEL_QUEUES 128
|
|
// Use a 256GB aperture for the model.
|
|
#define MODEL_APERTURE_SIZE (1llu << 38)
|
|
static void *model_mmio_page;
|
|
static pthread_mutex_t model_ioctl_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static unsigned model_event_limit;
|
|
static uint64_t *model_event_bitmap;
|
|
static struct model_event *model_events;
|
|
static pthread_cond_t model_event_condvar;
|
|
static void *model_library;
|
|
static const struct hsakmt_model_functions *model_functions;
|
|
static uint64_t model_memfd_size;
|
|
static uint64_t model_num_nodes;
|
|
static struct model_node *model_nodes;
|
|
static struct model_queue model_queues[MAX_MODEL_QUEUES];
|
|
|
|
HSAKMT_STATUS HSAKMTAPI hsaKmtModelEnabled(bool* enable)
|
|
{
|
|
*enable = hsakmt_use_model;
|
|
return HSAKMT_STATUS_SUCCESS;
|
|
}
|
|
|
|
void model_init_env_vars()
|
|
{
|
|
/* Check whether to use a model instead of real hardware */
|
|
hsakmt_model_topology = getenv("HSA_MODEL_TOPOLOGY");
|
|
if (hsakmt_model_topology)
|
|
hsakmt_use_model = true;
|
|
if (hsakmt_use_model)
|
|
{
|
|
/* Backing memory file is used to stand in for the kfd_fd,
|
|
* which is needed early, so create it already.
|
|
*
|
|
* For old systems without memfd_create, or if the user prefers,
|
|
* we create a regular backing file. Prefer to use memfd_create
|
|
* by default where possible.
|
|
*/
|
|
int fd = -1;
|
|
const char *fname = getenv("HSA_MODEL_MEMFILE");
|
|
if (fname)
|
|
{
|
|
fprintf(stderr, "model: use memory backing file given in HSA_MODEL_MEMFILE: %s\n", fname);
|
|
|
|
fd = open(fname, O_CREAT | O_EXCL | O_CLOEXEC | O_RDWR, S_IRUSR | S_IWUSR);
|
|
if (fd < 0)
|
|
{
|
|
perror("model: failed to create backing file");
|
|
abort();
|
|
}
|
|
|
|
unlink(fname);
|
|
}
|
|
|
|
if (fd < 0)
|
|
{
|
|
#ifdef HAVE_MEMFD_CREATE
|
|
fd = memfd_create("hsakmt_model", MFD_CLOEXEC);
|
|
if (fd < 0)
|
|
{
|
|
fprintf(stderr, "model: Failed to create memfd\n");
|
|
abort();
|
|
}
|
|
#else
|
|
fprintf(stderr, "model: built without memfd support\n"
|
|
"model: set HSA_MODEL_MEMFILE to path of a backing file\n");
|
|
abort();
|
|
#endif
|
|
}
|
|
assert(hsakmt_kfd_fd < 0);
|
|
hsakmt_kfd_fd = fd;
|
|
pthread_condattr_t condattr;
|
|
pthread_condattr_init(&condattr);
|
|
pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
|
|
pthread_cond_init(&model_event_condvar, &condattr);
|
|
pthread_condattr_destroy(&condattr);
|
|
const char *libname = getenv("HSA_MODEL_LIB");
|
|
if (!libname)
|
|
{
|
|
fprintf(stderr, "model: HSA_MODEL_LIB environment variable must be set to FFM .so\n");
|
|
abort();
|
|
}
|
|
// model_library = dlmopen(LM_ID_NEWLM, libname, RTLD_NOW);
|
|
model_library = dlopen(libname, RTLD_NOW | RTLD_LOCAL);
|
|
if (!model_library)
|
|
{
|
|
fprintf(stderr, "model: failed to load %s: %s\n", libname, dlerror());
|
|
abort();
|
|
}
|
|
get_hsakmt_model_functions_t getter = dlsym(model_library, "get_hsakmt_model_functions");
|
|
if (!getter)
|
|
{
|
|
fprintf(stderr, "model: Failed to get hsakmt_model_functions\n");
|
|
abort();
|
|
}
|
|
model_functions = getter();
|
|
if (model_functions->version_major != HSAKMT_MODEL_INTERFACE_VERSION_MAJOR ||
|
|
model_functions->version_minor < HSAKMT_MODEL_INTERFACE_VERSION_MINOR)
|
|
{
|
|
fprintf(stderr, "model: Model has interface version %u.%u, need version %u.%u\n",
|
|
model_functions->version_major, model_functions->version_minor,
|
|
HSAKMT_MODEL_INTERFACE_VERSION_MAJOR, HSAKMT_MODEL_INTERFACE_VERSION_MINOR);
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint64_t allocate_from_memfd(uint64_t size, uint64_t align)
|
|
{
|
|
if (!align)
|
|
align = 4096;
|
|
assert(POWER_OF_2(align)); /* must be power of two */
|
|
assert(align >= 4096);
|
|
size = (size + 4095) & ~4095;
|
|
model_memfd_size = (model_memfd_size + align - 1) & ~(align - 1);
|
|
uint64_t offset = model_memfd_size;
|
|
model_memfd_size += size;
|
|
int ret = ftruncate(hsakmt_kfd_fd, model_memfd_size);
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "model: ftruncate on memfd failed\n");
|
|
abort();
|
|
}
|
|
return offset;
|
|
}
|
|
static uint64_t get_sysfs_mem_bank_size(unsigned node_id, unsigned mem_id)
|
|
{
|
|
char prop_name[256];
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "%s/nodes/%u/mem_banks/%u/properties",
|
|
hsakmt_model_topology, node_id, mem_id);
|
|
FILE *f = fopen(path, "r");
|
|
if (!f)
|
|
{
|
|
fprintf(stderr, "model: Failed to open %s\n", path);
|
|
abort();
|
|
}
|
|
uint64_t prop_val;
|
|
while (fscanf(f, "%s %" PRIu64 "\n", prop_name, &prop_val) == 2)
|
|
{
|
|
if (!strcmp(prop_name, "size_in_bytes"))
|
|
{
|
|
fclose(f);
|
|
return prop_val;
|
|
}
|
|
}
|
|
fprintf(stderr, "model: Missing size_in_bytes in %s\n", path);
|
|
abort();
|
|
}
|
|
|
|
static void model_set_event(void *data, unsigned event_id)
|
|
{
|
|
if (!event_id)
|
|
return;
|
|
|
|
if (event_id > model_event_limit)
|
|
{
|
|
fprintf(stderr, "model_set_event: event_id = %u out of bounds\n",
|
|
event_id);
|
|
abort();
|
|
}
|
|
|
|
unsigned slot = event_id - 1;
|
|
|
|
if (!((model_event_bitmap[slot / 64] >> (slot % 64)) & 1))
|
|
{
|
|
fprintf(stderr, "model_set_event: event_id = %u is not allocated\n",
|
|
event_id);
|
|
abort();
|
|
}
|
|
|
|
struct model_event *event = &model_events[slot];
|
|
if (event->event_type == HSA_EVENTTYPE_SIGNAL)
|
|
{
|
|
assert(model_events[slot].value <= 1);
|
|
model_events[slot].value = 1;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "model: Unimplemented event type\n");
|
|
abort();
|
|
}
|
|
|
|
pthread_cond_broadcast(&model_event_condvar);
|
|
}
|
|
|
|
void model_init()
|
|
{
|
|
if (!hsakmt_use_model)
|
|
return;
|
|
HSAKMT_STATUS result;
|
|
HsaSystemProperties props;
|
|
/* Read the topology to determine nodes. */
|
|
result = hsakmt_topology_sysfs_get_system_props(&props);
|
|
if (result != HSAKMT_STATUS_SUCCESS)
|
|
{
|
|
fprintf(stderr, "model: Failed to parse topology\n");
|
|
abort();
|
|
}
|
|
model_nodes = calloc(props.NumNodes, sizeof(*model_nodes));
|
|
if (!model_nodes)
|
|
abort();
|
|
model_num_nodes = props.NumNodes;
|
|
for (unsigned node_id = 0; node_id < props.NumNodes; node_id++)
|
|
{
|
|
HsaNodeProperties node_props;
|
|
result = hsakmt_topology_get_node_props(node_id, &node_props);
|
|
if (result != HSAKMT_STATUS_SUCCESS)
|
|
{
|
|
fprintf(stderr, "model: Failed to get node %u properties\n", node_id);
|
|
abort();
|
|
}
|
|
if (node_props.KFDGpuID == 0)
|
|
continue;
|
|
if (node_props.KFDGpuID != node_id + 1)
|
|
{
|
|
fprintf(stderr,
|
|
"model: Node %u has KFD GPU ID %u, but should be %u."
|
|
" Please change the gpu_id file.\n",
|
|
node_id, node_props.KFDGpuID, node_id + 1);
|
|
abort();
|
|
}
|
|
model_nodes[node_id].is_gpu = true;
|
|
/* Reserve the VA space for the aperture, but don't fill it with pages. */
|
|
model_nodes[node_id].aperture =
|
|
mmap(NULL, MODEL_APERTURE_SIZE, PROT_NONE,
|
|
MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS, -1, 0);
|
|
pr_debug("Modeling Creating Memory Aperture: %p\n", model_nodes[node_id].aperture);
|
|
if (model_nodes[node_id].aperture == MAP_FAILED)
|
|
{
|
|
fprintf(stderr, "model: Failed to reserve aperture via mmap\n");
|
|
abort();
|
|
}
|
|
/* Create the doorbell region */
|
|
model_nodes[node_id].doorbell_offset = allocate_from_memfd(8192, 8192);
|
|
for (unsigned mem_id = 0; mem_id < node_props.NumMemoryBanks; ++mem_id)
|
|
{
|
|
model_nodes[node_id].total_memory_size += get_sysfs_mem_bank_size(node_id, mem_id);
|
|
}
|
|
/* Create the model */
|
|
// TODO: Move this into a separate thread
|
|
model_nodes[node_id].model = model_functions->create();
|
|
if (!model_nodes[node_id].model)
|
|
{
|
|
fprintf(stderr, "model: Failed to create model\n");
|
|
abort();
|
|
}
|
|
model_functions->set_global_aperture(model_nodes[node_id].model,
|
|
model_nodes[node_id].aperture,
|
|
MODEL_APERTURE_SIZE);
|
|
|
|
model_functions->set_set_event(model_nodes[node_id].model, model_set_event, NULL);
|
|
}
|
|
}
|
|
void model_set_mmio_page(void *ptr)
|
|
{
|
|
assert(!model_mmio_page);
|
|
model_mmio_page = ptr;
|
|
}
|
|
void model_set_event_page(void *ptr, unsigned event_limit)
|
|
{
|
|
// TODO: Fully understand what's happening with this page and the event limit.
|
|
// ROCR-Runtime allocates a pool of 4096 events, but also a handful or so
|
|
// of additional events, which blows through the event_limit of 4096
|
|
// that is passed here. And it seems that not using the page at all
|
|
// is supported?
|
|
assert(!model_event_limit);
|
|
assert(event_limit % 64 == 0);
|
|
event_limit *= 2;
|
|
model_event_limit = event_limit;
|
|
model_event_bitmap = calloc(event_limit / 64, 8);
|
|
model_events = calloc(event_limit, sizeof(*model_events));
|
|
}
|
|
/* Model implementation of KFD ioctl. */
|
|
|
|
static int model_kfd_ioctl_locked(unsigned long request, void *arg)
|
|
{
|
|
assert(_IOC_TYPE(request) == AMDKFD_IOCTL_BASE);
|
|
if (_IOC_NR(request) == 0x20)
|
|
{
|
|
// This is AMDKFD_IOC_SVM. It is defined / used in an unusual way.
|
|
struct kfd_ioctl_svm_args *args = arg;
|
|
if (args->op == KFD_IOCTL_SVM_OP_SET_ATTR)
|
|
{
|
|
// todo?
|
|
return 0;
|
|
}
|
|
fprintf(stderr, "model: Unimplemented SVM op\n");
|
|
abort();
|
|
}
|
|
switch (request)
|
|
{
|
|
case AMDKFD_IOC_GET_VERSION:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_GET_VERSION\n");
|
|
struct kfd_ioctl_get_version_args *args = arg;
|
|
args->major_version = 1;
|
|
args->minor_version = 14;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_GET_PROCESS_APERTURES_NEW:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_GET_PROCESS_APERTURES_NEW\n");
|
|
struct kfd_ioctl_get_process_apertures_new_args *args = arg;
|
|
struct kfd_process_device_apertures *apertures =
|
|
(void *)args->kfd_process_device_apertures_ptr;
|
|
assert(args->num_of_nodes == model_num_nodes);
|
|
for (unsigned node_id = 0; node_id < args->num_of_nodes; ++node_id)
|
|
{
|
|
memset(&apertures[node_id], 0, sizeof(apertures[node_id]));
|
|
if (!model_nodes[node_id].is_gpu)
|
|
continue;
|
|
apertures[node_id].gpu_id = 1 + node_id;
|
|
apertures[node_id].gpuvm_base = 0x4000llu;
|
|
apertures[node_id].gpuvm_limit = MODEL_APERTURE_SIZE;
|
|
apertures[node_id].lds_base = 0x4000000000000000llu; // 0x1000000000000?
|
|
apertures[node_id].lds_limit = 0x40000000ffffffffllu;
|
|
apertures[node_id].scratch_base = 0x5000000000000000llu; // 0x2000000000000?
|
|
apertures[node_id].scratch_limit = 0x50000000ffffffffllu;
|
|
}
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_SET_XNACK_MODE:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_SET_XNACK_MODE\n");
|
|
// Don't support XNACK
|
|
struct kfd_ioctl_set_xnack_mode_args *args = arg;
|
|
if (args->xnack_enabled < 0)
|
|
{
|
|
args->xnack_enabled = 0;
|
|
return 0;
|
|
}
|
|
errno = EPERM;
|
|
return -1;
|
|
}
|
|
case AMDKFD_IOC_GET_CLOCK_COUNTERS:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_GET_CLOCK_COUNTERS\n");
|
|
struct kfd_ioctl_get_clock_counters_args *args = arg;
|
|
args->gpu_clock_counter = 0; // TODO
|
|
args->cpu_clock_counter = 0;
|
|
args->system_clock_counter = 0;
|
|
args->system_clock_freq = 0;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_ACQUIRE_VM:
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_ACQUIRE_VM\n");
|
|
return 0;
|
|
case AMDKFD_IOC_SET_MEMORY_POLICY:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_SET_MEMORY_POLICY\n");
|
|
// todo?
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_AVAILABLE_MEMORY:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_AVAILABLE_MEMORY\n");
|
|
static const uint64_t minimum_reported = 128 * 1024 * 1024;
|
|
struct kfd_ioctl_get_available_memory_args *args = arg;
|
|
unsigned node_id = args->gpu_id - 1;
|
|
struct model_node *node = &model_nodes[node_id];
|
|
assert(node_id < model_num_nodes);
|
|
if (node->allocated_memory_size + minimum_reported >= node->total_memory_size)
|
|
args->available = minimum_reported;
|
|
else
|
|
args->available = node->total_memory_size - node->allocated_memory_size;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_ALLOC_MEMORY_OF_GPU:
|
|
{
|
|
// Expect an SVM style allocation: The memory is allocated on the host
|
|
// side e.g. via mmap(), and this IOCTL "only" registers the memory
|
|
// with the GPU. This is a no-op for us because we aren't a GPU.
|
|
struct kfd_ioctl_alloc_memory_of_gpu_args *args = arg;
|
|
unsigned node_id = args->gpu_id - 1;
|
|
assert(node_id < model_num_nodes);
|
|
assert(model_nodes[node_id].is_gpu);
|
|
if (args->va_addr == 0)
|
|
{
|
|
fprintf(stderr, "model: Expect only SVM allocations?\n");
|
|
abort();
|
|
}
|
|
if (args->size % PAGE_SIZE != 0)
|
|
{
|
|
fprintf(stderr, "model: Allocation size not a multiple of page size\n");
|
|
abort();
|
|
}
|
|
if (args->flags & KFD_IOC_ALLOC_MEM_FLAGS_USERPTR)
|
|
{
|
|
fprintf(stderr, "model: userptr not supported\n");
|
|
abort();
|
|
}
|
|
struct model_mem_data *mem_data = calloc(1, sizeof(*mem_data));
|
|
if (!mem_data)
|
|
abort();
|
|
mem_data->va_addr = args->va_addr;
|
|
mem_data->size = args->size;
|
|
mem_data->flags = args->flags;
|
|
mem_data->node_id = node_id;
|
|
if (args->flags & KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL)
|
|
{
|
|
assert(args->size == 8192);
|
|
mem_data->file_offset = model_nodes[node_id].doorbell_offset;
|
|
}
|
|
else
|
|
{
|
|
mem_data->file_offset = allocate_from_memfd(args->size, 0);
|
|
}
|
|
args->handle = (__u64)mem_data;
|
|
args->mmap_offset = mem_data->file_offset;
|
|
model_nodes[node_id].allocated_memory_size += args->size;
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_ALLOC_MEMORY_OF_GPU: VA: %lx : Size: %lu, Flags: %x\n", mem_data->va_addr, mem_data->size, mem_data->flags);
|
|
model_functions->alloced_memory(model_nodes[node_id].model, (uint64_t *)mem_data->va_addr, mem_data->size, mem_data->flags);
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_FREE_MEMORY_OF_GPU:
|
|
{
|
|
struct kfd_ioctl_free_memory_of_gpu_args *args = arg;
|
|
struct model_mem_data *mem_data = (void *)args->handle;
|
|
assert(!mem_data->mapped_nodes_bitmask);
|
|
// Free the memory by punching a hole into the underlying memfd.
|
|
//
|
|
// Ideally, we'd also remember holes in the file and re-use them for
|
|
// allocations to avoid the file size from growing indefinitely. It's
|
|
// unclear whether the current implementation causes kernel data
|
|
// structures to grow. But in practice, it almost certainly never
|
|
// matters.
|
|
int ret = fallocate(hsakmt_kfd_fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
|
|
mem_data->file_offset, mem_data->size);
|
|
if (ret != 0)
|
|
{
|
|
perror("model: failed to punch hole in memfd");
|
|
abort();
|
|
}
|
|
model_nodes[mem_data->node_id].allocated_memory_size -= mem_data->size;
|
|
model_functions->freed_memory(model_nodes[mem_data->node_id].model, (uint64_t *)mem_data->va_addr, mem_data->size);
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_FREE_MEMORY_OF_GPU: VA: %lx : Size: %lu, Flags: %x\n", mem_data->va_addr, mem_data->size, mem_data->flags);
|
|
free(mem_data);
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_MAP_MEMORY_TO_GPU:
|
|
{
|
|
struct kfd_ioctl_map_memory_to_gpu_args *args = arg;
|
|
struct model_mem_data *mem_data = (void *)args->handle;
|
|
while (args->n_success < args->n_devices)
|
|
{
|
|
uint32_t gpu_id = ((uint32_t *)args->device_ids_array_ptr)[args->n_success];
|
|
uint32_t node_id = gpu_id - 1;
|
|
assert(node_id < model_num_nodes);
|
|
if (mem_data->mapped_nodes_bitmask & (1llu << node_id))
|
|
{
|
|
fprintf(stderr, "model: Already mapped\n");
|
|
abort();
|
|
}
|
|
assert(model_nodes[node_id].aperture);
|
|
unsigned prot = PROT_READ;
|
|
if (mem_data->flags & KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE)
|
|
prot |= PROT_WRITE;
|
|
// TODO: Mark *shader*-executable memory?
|
|
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_MAP_MEMORY_TO_GPU: VA: %lx : Size: %lu, Flags: %x\n", mem_data->va_addr, mem_data->size, mem_data->flags);
|
|
void *ret = mmap(VOID_PTR_ADD(model_nodes[node_id].aperture, mem_data->va_addr),
|
|
mem_data->size, prot,
|
|
MAP_SHARED | MAP_FIXED, hsakmt_kfd_fd, mem_data->file_offset);
|
|
if (ret == MAP_FAILED)
|
|
{
|
|
fprintf(stderr, "model: mmap failed\n");
|
|
abort();
|
|
}
|
|
mem_data->mapped_nodes_bitmask |= (1llu << node_id);
|
|
args->n_success++;
|
|
}
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_UNMAP_MEMORY_FROM_GPU:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_UNMAP_MEMORY_FROM_GPU\n");
|
|
struct kfd_ioctl_unmap_memory_from_gpu_args *args = arg;
|
|
struct model_mem_data *mem_data = (void *)args->handle;
|
|
while (args->n_success < args->n_devices)
|
|
{
|
|
uint32_t gpu_id = ((uint32_t *)args->device_ids_array_ptr)[args->n_success];
|
|
uint32_t node_id = gpu_id - 1;
|
|
assert(node_id < model_num_nodes);
|
|
if (!(mem_data->mapped_nodes_bitmask & (1llu << node_id)))
|
|
{
|
|
fprintf(stderr, "model: Not mapped\n");
|
|
abort();
|
|
}
|
|
assert(model_nodes[node_id].aperture);
|
|
/* Overwrite the mapping with an empty mapping to keep
|
|
* it reserved. */
|
|
void *ret = mmap(VOID_PTR_ADD(model_nodes[node_id].aperture, mem_data->va_addr),
|
|
mem_data->size, PROT_NONE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE, -1, 0);
|
|
if (ret == MAP_FAILED)
|
|
{
|
|
perror("model: unmap failed");
|
|
abort();
|
|
}
|
|
mem_data->mapped_nodes_bitmask &= ~(1llu << node_id);
|
|
args->n_success++;
|
|
}
|
|
args->n_success = args->n_devices;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_CREATE_EVENT:
|
|
{
|
|
struct kfd_ioctl_create_event_args *args = arg;
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_CREATE_EVENT: %u\n", args->event_type);
|
|
// Find a free slot
|
|
unsigned i;
|
|
for (i = 0; i < model_event_limit; i += 64)
|
|
{
|
|
uint64_t bitmap = model_event_bitmap[i / 64];
|
|
if (bitmap == ~(uint64_t)0)
|
|
continue;
|
|
i += ffsll(~bitmap) - 1;
|
|
break;
|
|
}
|
|
if (i >= model_event_limit)
|
|
{
|
|
fprintf(stderr, "model: Ran out of event slots. Should be an application error.\n");
|
|
abort();
|
|
}
|
|
// Allocate the signal
|
|
model_event_bitmap[i / 64] |= (uint64_t)1 << (i % 64);
|
|
model_events[i].event_type = args->event_type;
|
|
model_events[i].auto_reset = args->auto_reset;
|
|
model_events[i].value = 0;
|
|
args->event_trigger_data = 0xbadf001; // ???
|
|
args->event_id = 1 + i;
|
|
args->event_slot_index = ~0;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_WAIT_EVENTS:
|
|
{
|
|
struct kfd_ioctl_wait_events_args *args = arg;
|
|
struct kfd_event_data *events = (void *)args->events_ptr;
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_WAIT_EVENTS: %u\n", args->num_events);
|
|
bool have_timeout = args->timeout != 0xffffffffu;
|
|
bool hit_timeout = false;
|
|
struct timespec timeout;
|
|
if (have_timeout)
|
|
{
|
|
clock_gettime(CLOCK_MONOTONIC, &timeout);
|
|
timeout.tv_sec += args->timeout / 1000;
|
|
timeout.tv_nsec += (args->timeout % 1000) * 1000000;
|
|
if (timeout.tv_nsec > 1000000000)
|
|
{
|
|
timeout.tv_nsec -= 1000000000;
|
|
timeout.tv_sec++;
|
|
}
|
|
}
|
|
for (;;)
|
|
{
|
|
bool final_ready = args->wait_for_all;
|
|
for (unsigned i = 0; i < args->num_events; ++i)
|
|
{
|
|
unsigned slot = events[i].event_id - 1;
|
|
struct model_event *event = &model_events[slot];
|
|
bool this_ready;
|
|
if (event->event_type == HSA_EVENTTYPE_SIGNAL)
|
|
{
|
|
uint64_t current_age = event->value;
|
|
uint64_t target_age = events[i].signal_event_data.last_event_age;
|
|
this_ready = current_age >= target_age;
|
|
}
|
|
else if (event->event_type == HSA_EVENTTYPE_HW_EXCEPTION ||
|
|
event->event_type == HSA_EVENTTYPE_NODECHANGE ||
|
|
event->event_type == HSA_EVENTTYPE_DEVICESTATECHANGE ||
|
|
event->event_type == HSA_EVENTTYPE_HW_EXCEPTION ||
|
|
event->event_type == HSA_EVENTTYPE_DEBUG_EVENT ||
|
|
event->event_type == HSA_EVENTTYPE_PROFILE_EVENT ||
|
|
event->event_type == HSA_EVENTTYPE_MEMORY)
|
|
{
|
|
// These never happen in the model
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "model: Unimplemented event type\n");
|
|
abort();
|
|
}
|
|
if (final_ready != this_ready)
|
|
{
|
|
final_ready = this_ready;
|
|
break;
|
|
}
|
|
}
|
|
if (final_ready)
|
|
break;
|
|
if (have_timeout)
|
|
{
|
|
int ret = pthread_cond_timedwait(
|
|
&model_event_condvar, &model_ioctl_mutex, &timeout);
|
|
if (ret == ETIMEDOUT)
|
|
{
|
|
hit_timeout = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pthread_cond_wait(&model_event_condvar, &model_ioctl_mutex);
|
|
}
|
|
}
|
|
/* Record most recent event ages and perform auto reset. */
|
|
for (unsigned i = 0; i < args->num_events; ++i)
|
|
{
|
|
unsigned slot = events[i].event_id - 1;
|
|
struct model_event *event = &model_events[slot];
|
|
if (event->event_type == HSA_EVENTTYPE_SIGNAL)
|
|
{
|
|
uint64_t last_age = event->value;
|
|
if (event->auto_reset && last_age >= events[i].signal_event_data.last_event_age)
|
|
event->value = 0;
|
|
events[i].signal_event_data.last_event_age = last_age;
|
|
}
|
|
}
|
|
args->wait_result = hit_timeout ? KFD_IOC_WAIT_RESULT_TIMEOUT
|
|
: KFD_IOC_WAIT_RESULT_COMPLETE;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_SET_EVENT:
|
|
{
|
|
struct kfd_ioctl_set_event_args *args = arg;
|
|
model_set_event(NULL, args->event_id);
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_RESET_EVENT:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_RESET_EVENT\n");
|
|
struct kfd_ioctl_reset_event_args *args = arg;
|
|
unsigned slot = args->event_id - 1;
|
|
struct model_event *event = &model_events[slot];
|
|
if (event->event_type == HSA_EVENTTYPE_SIGNAL)
|
|
{
|
|
model_events[slot].value = 0;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "model: Unimplemented event type\n");
|
|
abort();
|
|
}
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_DESTROY_EVENT:
|
|
{
|
|
struct kfd_ioctl_destroy_event_args *args = arg;
|
|
unsigned i = args->event_id - 1;
|
|
if (i >= model_event_limit || !(model_event_bitmap[i / 64] & ((uint64_t)1 << (i % 64))))
|
|
{
|
|
fprintf(stderr, "model: trying to destroy an event that doesn't exist.\n");
|
|
abort();
|
|
}
|
|
memset(&model_events[i], 0, sizeof(model_events[i]));
|
|
model_event_bitmap[i / 64] &= ~((uint64_t)1 << (i % 64));
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_CREATE_QUEUE:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_CREATE_QUEUE\n");
|
|
struct kfd_ioctl_create_queue_args *args = arg;
|
|
unsigned node_id = args->gpu_id - 1;
|
|
assert(node_id < model_num_nodes);
|
|
assert(model_nodes[node_id].model);
|
|
const bool supported_queue_type = args->queue_type == KFD_IOC_QUEUE_TYPE_COMPUTE_AQL ||
|
|
args->queue_type == KFD_IOC_QUEUE_TYPE_SDMA;
|
|
if (!supported_queue_type)
|
|
{
|
|
fprintf(stderr, "model: Unsupported queue type\n");
|
|
abort();
|
|
}
|
|
unsigned queue_id = 0;
|
|
while (queue_id < MAX_MODEL_QUEUES && model_queues[queue_id].queue)
|
|
queue_id++;
|
|
if (queue_id >= MAX_MODEL_QUEUES)
|
|
{
|
|
fprintf(stderr, "model: too many queues\n");
|
|
abort();
|
|
}
|
|
struct hsakmt_model_queue_info info = {0};
|
|
info.ring_base_address = args->ring_base_address;
|
|
info.ring_size = args->ring_size;
|
|
info.write_pointer_address = args->write_pointer_address;
|
|
info.read_pointer_address = args->read_pointer_address;
|
|
info.queue_type = args->queue_type;
|
|
model_queues[queue_id].queue =
|
|
model_functions->register_queue(model_nodes[node_id].model, &info);
|
|
model_queues[queue_id].node_id = node_id;
|
|
args->queue_id = queue_id;
|
|
// Note that strictly speaking, this is the offset into the hsakmt_kfd_fd
|
|
// file, not the DRM fd (but they are the same in our case).
|
|
args->doorbell_offset = model_nodes[node_id].doorbell_offset + 8 * queue_id;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_DESTROY_QUEUE:
|
|
{
|
|
struct kfd_ioctl_destroy_queue_args *args = arg;
|
|
if (args->queue_id >= MAX_MODEL_QUEUES || !model_queues[args->queue_id].queue)
|
|
{
|
|
fprintf(stderr, "model: trying to destroy a queue that doesn't exist\n");
|
|
abort();
|
|
}
|
|
struct model_queue *queue = &model_queues[args->queue_id];
|
|
// Older model versions simply leak the queue.
|
|
if (model_functions->version_minor >= 3)
|
|
model_functions->destroy_queue(model_nodes[queue->node_id].model, queue->queue);
|
|
queue->queue = NULL;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_GET_TILE_CONFIG:
|
|
{
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_GET_TILE_CONFIG\n");
|
|
struct kfd_ioctl_get_tile_config_args *args = arg;
|
|
args->gb_addr_config = 0x10000444;
|
|
return 0;
|
|
}
|
|
case AMDKFD_IOC_SET_SCRATCH_BACKING_VA:
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_SET_SCRATCH_BACKING_VA\n");
|
|
// no-op -- scratch allocations are communicated via amd_queue_s
|
|
return 0;
|
|
case AMDKFD_IOC_RUNTIME_ENABLE:
|
|
pr_debug("MODEL IOCTL: AMDKFD_IOC_RUNTIME_ENABLE\n");
|
|
fprintf(stderr, "model: Debugger runtime not implemented\n");
|
|
fprintf(stderr, "Fix this by clearing bit 30 of the 'capability' field in $HSA_MODEL_TOPOLOGY/%%d/properties\n");
|
|
abort();
|
|
default:
|
|
fprintf(stderr, "model: Unimplemented KFD ioctl\n");
|
|
abort();
|
|
}
|
|
}
|
|
int model_kfd_ioctl(unsigned long request, void *arg)
|
|
{
|
|
/* Use a very simle locking strategy for correctness. IOCTLs should
|
|
* be rare anyway and not contended considering the cost of running
|
|
* the model itself.
|
|
*
|
|
* The bulk of model execution happens in a separate thread *without*
|
|
* holding the IOCTL mutex. */
|
|
pthread_mutex_lock(&model_ioctl_mutex);
|
|
int ret = model_kfd_ioctl_locked(request, arg);
|
|
pthread_mutex_unlock(&model_ioctl_mutex);
|
|
return ret;
|
|
} |