458 lines
13 KiB
C
458 lines
13 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file ft_tt_kmd.c
|
|
* @brief Implementation of the kernel-driver boundary: one fd per device, one ioctl per
|
|
* request, the window aperture mmap, and the NoC fence on CONFIGURE_WINDOW.
|
|
*
|
|
* Every syscall goes through an ft_tt_kmd_sys_* name that defaults to the libc call. The
|
|
* off-device unit test (tests/test_kmd.c) force-includes a header that points them at fakes
|
|
* recording each request; nothing else should define them.
|
|
*
|
|
* @status Complete; unit-tested against fake syscalls.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
/* POSIX.1-2008 for O_CLOEXEC and mmap under -std=c99. */
|
|
#ifndef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
|
|
#include "ft_tt_kmd.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#ifndef ft_tt_kmd_sys_open
|
|
#define ft_tt_kmd_sys_open open
|
|
#endif
|
|
#ifndef ft_tt_kmd_sys_close
|
|
#define ft_tt_kmd_sys_close close
|
|
#endif
|
|
#ifndef ft_tt_kmd_sys_ioctl
|
|
#define ft_tt_kmd_sys_ioctl ioctl
|
|
#endif
|
|
#ifndef ft_tt_kmd_sys_mmap
|
|
#define ft_tt_kmd_sys_mmap mmap
|
|
#endif
|
|
#ifndef ft_tt_kmd_sys_munmap
|
|
#define ft_tt_kmd_sys_munmap munmap
|
|
#endif
|
|
|
|
struct ft_tt_kmd_device {
|
|
int fd;
|
|
ft_tt_kmd_resources resources;
|
|
};
|
|
|
|
struct ft_tt_kmd_window {
|
|
uint32_t id;
|
|
size_t size;
|
|
void *aperture;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Device */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/* Record one QUERY_MAPPINGS entry: a UC id carries the BAR's offset AND length, a WC id only
|
|
* its offset. Unknown ids are ignored. */
|
|
static void cache_mapping(ft_tt_kmd_resources *resources, const struct ft_tt_kmd_mapping *map)
|
|
{
|
|
switch (map->id) {
|
|
case FT_TT_KMD_MAPPING_RESOURCE0_UC:
|
|
resources->bar0_uc_offset = map->base;
|
|
resources->bar0_len = map->size;
|
|
break;
|
|
case FT_TT_KMD_MAPPING_RESOURCE0_WC:
|
|
resources->bar0_wc_offset = map->base;
|
|
break;
|
|
case FT_TT_KMD_MAPPING_RESOURCE1_UC:
|
|
resources->bar2_uc_offset = map->base;
|
|
resources->bar2_len = map->size;
|
|
break;
|
|
case FT_TT_KMD_MAPPING_RESOURCE1_WC:
|
|
resources->bar2_wc_offset = map->base;
|
|
break;
|
|
case FT_TT_KMD_MAPPING_RESOURCE2_UC:
|
|
resources->bar4_uc_offset = map->base;
|
|
resources->bar4_len = map->size;
|
|
break;
|
|
case FT_TT_KMD_MAPPING_RESOURCE2_WC:
|
|
resources->bar4_wc_offset = map->base;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ft_tt_kmd_open(const char *path, ft_tt_kmd_device **out_device, int extra_flags)
|
|
{
|
|
struct ft_tt_kmd_query_mappings_request query;
|
|
ft_tt_kmd_device *device;
|
|
size_t index;
|
|
|
|
if (!path || !out_device)
|
|
return -EINVAL;
|
|
|
|
device = calloc(1, sizeof(*device));
|
|
if (!device)
|
|
return -ENOMEM;
|
|
|
|
device->fd = ft_tt_kmd_sys_open(path, O_RDWR | O_CLOEXEC | extra_flags);
|
|
if (device->fd < 0) {
|
|
int rc = -errno;
|
|
|
|
free(device);
|
|
return rc;
|
|
}
|
|
|
|
memset(&query, 0, sizeof(query));
|
|
query.in.count = FT_TT_KMD_MAPPING_QUERY_COUNT;
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_QUERY_MAPPINGS, &query) != 0) {
|
|
int rc = -errno;
|
|
|
|
(void)ft_tt_kmd_sys_close(device->fd);
|
|
free(device);
|
|
return rc;
|
|
}
|
|
|
|
for (index = 0; index < FT_TT_KMD_MAPPING_QUERY_COUNT; index++)
|
|
cache_mapping(&device->resources, &query.mappings[index]);
|
|
|
|
*out_device = device;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_close(ft_tt_kmd_device *device)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!device)
|
|
return 0;
|
|
|
|
if (ft_tt_kmd_sys_close(device->fd) != 0)
|
|
rc = -errno;
|
|
free(device);
|
|
return rc;
|
|
}
|
|
|
|
/* Window counts follow from the BAR lengths, and only a Blackhole is counted. */
|
|
static void derive_window_counts(const ft_tt_kmd_device *device, ft_tt_kmd_device_attrs *attrs)
|
|
{
|
|
uint64_t count_2m;
|
|
|
|
if (attrs->chip_arch != (uint64_t)FT_TT_KMD_ARCH_BLACKHOLE)
|
|
return;
|
|
|
|
count_2m = device->resources.bar0_len / FT_TT_KMD_WINDOW_SIZE_2M;
|
|
if (count_2m > FT_TT_KMD_BLACKHOLE_2M_WINDOW_LIMIT)
|
|
count_2m = FT_TT_KMD_BLACKHOLE_2M_WINDOW_LIMIT;
|
|
attrs->window_count_2m = count_2m;
|
|
attrs->window_count_4g = device->resources.bar4_len / FT_TT_KMD_WINDOW_SIZE_4G;
|
|
}
|
|
|
|
int ft_tt_kmd_get_attrs(ft_tt_kmd_device *device, ft_tt_kmd_device_attrs *out_attrs)
|
|
{
|
|
struct ft_tt_kmd_device_info_request request;
|
|
ft_tt_kmd_device_attrs attrs;
|
|
|
|
if (!device || !out_attrs)
|
|
return -EINVAL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.in.out_size = (uint32_t)sizeof(request.out);
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_GET_DEVICE_INFO, &request) != 0)
|
|
return -errno;
|
|
|
|
memset(&attrs, 0, sizeof(attrs));
|
|
if (request.out.device_id == FT_TT_KMD_PCI_DEVICE_ID_BLACKHOLE)
|
|
attrs.chip_arch = FT_TT_KMD_ARCH_BLACKHOLE;
|
|
else if (request.out.device_id == FT_TT_KMD_PCI_DEVICE_ID_WORMHOLE)
|
|
attrs.chip_arch = FT_TT_KMD_ARCH_WORMHOLE;
|
|
else
|
|
attrs.chip_arch = FT_TT_KMD_ARCH_UNKNOWN;
|
|
|
|
attrs.pci_domain = request.out.pci_domain;
|
|
attrs.pci_bus = (uint64_t)((request.out.bdf >> 8) & 0xFFu);
|
|
attrs.pci_device = (uint64_t)((request.out.bdf >> 3) & 0x1Fu);
|
|
attrs.pci_function = (uint64_t)(request.out.bdf & 0x7u);
|
|
attrs.pci_vendor_id = request.out.vendor_id;
|
|
attrs.pci_device_id = request.out.device_id;
|
|
attrs.pci_subsystem_vendor_id = request.out.subsystem_vendor_id;
|
|
attrs.pci_subsystem_id = request.out.subsystem_id;
|
|
derive_window_counts(device, &attrs);
|
|
|
|
*out_attrs = attrs;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_get_resources(const ft_tt_kmd_device *device, ft_tt_kmd_resources *out_resources)
|
|
{
|
|
if (!device || !out_resources)
|
|
return -EINVAL;
|
|
|
|
*out_resources = device->resources;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_fd(const ft_tt_kmd_device *device)
|
|
{
|
|
return device ? device->fd : -1;
|
|
}
|
|
|
|
int ft_tt_kmd_request_max_ai_clock(ft_tt_kmd_device *device, bool want_max)
|
|
{
|
|
struct ft_tt_kmd_power_state_request request;
|
|
|
|
if (!device)
|
|
return -EINVAL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.argsz = (uint32_t)sizeof(request);
|
|
request.validity = (uint8_t)FT_TT_KMD_POWER_VALIDITY(15u, 0u);
|
|
request.flags_word = want_max
|
|
? (uint16_t)FT_TT_KMD_POWER_FLAGS_ALL
|
|
: (uint16_t)(FT_TT_KMD_POWER_FLAGS_ALL &
|
|
~FT_TT_KMD_POWER_FLAG_MAX_AI_CLOCK);
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_SET_POWER_STATE, &request) != 0)
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
/* One NOC_CLEANUP carrying `request`'s payload; argsz is stamped here. */
|
|
static int issue_noc_cleanup(ft_tt_kmd_device *device,
|
|
struct ft_tt_kmd_noc_cleanup_request *request)
|
|
{
|
|
request->argsz = (uint32_t)sizeof(*request);
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_NOC_CLEANUP, request) != 0)
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_set_noc_cleanup(ft_tt_kmd_device *device, uint8_t noc, uint8_t x, uint8_t y,
|
|
uint64_t addr, uint64_t value)
|
|
{
|
|
struct ft_tt_kmd_noc_cleanup_request request;
|
|
|
|
if (!device)
|
|
return -EINVAL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.enabled = 1u;
|
|
request.x = x;
|
|
request.y = y;
|
|
request.noc = noc;
|
|
request.addr = addr;
|
|
request.data = value;
|
|
return issue_noc_cleanup(device, &request);
|
|
}
|
|
|
|
int ft_tt_kmd_clear_noc_cleanup(ft_tt_kmd_device *device)
|
|
{
|
|
struct ft_tt_kmd_noc_cleanup_request request;
|
|
|
|
if (!device)
|
|
return -EINVAL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
return issue_noc_cleanup(device, &request);
|
|
}
|
|
|
|
int ft_tt_kmd_driver_attr(ft_tt_kmd_device *device, enum ft_tt_kmd_driver_attr attr,
|
|
uint64_t *out_value)
|
|
{
|
|
struct ft_tt_kmd_driver_info_request request;
|
|
|
|
if (!out_value)
|
|
return -EINVAL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
if (device) {
|
|
request.in.out_size = (uint32_t)sizeof(request.out);
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_GET_DRIVER_INFO, &request) != 0)
|
|
return -errno;
|
|
}
|
|
|
|
switch (attr) {
|
|
case FT_TT_KMD_DRIVER_API_VERSION:
|
|
*out_value = FT_TT_KMD_ABI_VERSION;
|
|
return 0;
|
|
case FT_TT_KMD_DRIVER_VERSION_MAJOR:
|
|
*out_value = request.out.major;
|
|
break;
|
|
case FT_TT_KMD_DRIVER_VERSION_MINOR:
|
|
*out_value = request.out.minor;
|
|
break;
|
|
case FT_TT_KMD_DRIVER_VERSION_PATCH:
|
|
*out_value = request.out.patch;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return device ? 0 : -ENODEV;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Windows */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/* One FREE_WINDOW for `id`. */
|
|
static int issue_free_window(ft_tt_kmd_device *device, uint32_t id)
|
|
{
|
|
struct ft_tt_kmd_free_window_request request;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.in.id = id;
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_FREE_WINDOW, &request) != 0)
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_window_alloc(ft_tt_kmd_device *device, size_t size,
|
|
enum ft_tt_kmd_cache_mode cache, int protection,
|
|
ft_tt_kmd_window **out_window)
|
|
{
|
|
struct ft_tt_kmd_allocate_window_request request;
|
|
ft_tt_kmd_window *window;
|
|
uint64_t offset;
|
|
void *aperture;
|
|
|
|
if (!device || !out_window)
|
|
return -EINVAL;
|
|
|
|
window = calloc(1, sizeof(*window));
|
|
if (!window)
|
|
return -ENOMEM;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.in.size = (uint64_t)size;
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_ALLOCATE_WINDOW, &request) != 0) {
|
|
int rc = -errno;
|
|
|
|
free(window);
|
|
return rc;
|
|
}
|
|
|
|
offset = cache == FT_TT_KMD_CACHE_UC ? request.out.mmap_offset_uc
|
|
: request.out.mmap_offset_wc;
|
|
aperture = ft_tt_kmd_sys_mmap(NULL, size, protection, MAP_SHARED, device->fd,
|
|
(off_t)offset);
|
|
if (aperture == MAP_FAILED) {
|
|
int rc = -errno;
|
|
int free_rc = issue_free_window(device, request.out.id);
|
|
|
|
if (free_rc != 0)
|
|
rc = free_rc;
|
|
free(window);
|
|
return rc;
|
|
}
|
|
|
|
window->id = request.out.id;
|
|
window->size = size;
|
|
window->aperture = aperture;
|
|
*out_window = window;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_window_free(ft_tt_kmd_device *device, ft_tt_kmd_window *window)
|
|
{
|
|
int rc = 0;
|
|
int free_rc;
|
|
|
|
if (!device || !window)
|
|
return -EINVAL;
|
|
|
|
/* munmap FIRST: the driver may hand the id to another client the moment it is freed. */
|
|
if (ft_tt_kmd_sys_munmap(window->aperture, window->size) != 0)
|
|
rc = -errno;
|
|
free_rc = issue_free_window(device, window->id);
|
|
if (free_rc != 0)
|
|
rc = free_rc;
|
|
free(window);
|
|
return rc;
|
|
}
|
|
|
|
int ft_tt_kmd_window_aperture(const ft_tt_kmd_window *window, void **out_aperture)
|
|
{
|
|
if (!window || !out_aperture)
|
|
return -EINVAL;
|
|
*out_aperture = window->aperture;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_window_id(const ft_tt_kmd_window *window, uint32_t *out_id)
|
|
{
|
|
if (!window || !out_id)
|
|
return -EINVAL;
|
|
*out_id = window->id;
|
|
return 0;
|
|
}
|
|
|
|
int ft_tt_kmd_window_size(const ft_tt_kmd_window *window, size_t *out_size)
|
|
{
|
|
if (!window || !out_size)
|
|
return -EINVAL;
|
|
*out_size = window->size;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef FT_TT_PHASE1B_NOC_ENABLED
|
|
|
|
int ft_tt_kmd_window_aim(ft_tt_kmd_device *device, ft_tt_kmd_window *window,
|
|
const ft_tt_kmd_window_target *target)
|
|
{
|
|
struct ft_tt_kmd_configure_window_request request;
|
|
|
|
if (!device || !window || !target)
|
|
return -EINVAL;
|
|
if (window->size == 0 || target->addr % (uint64_t)window->size != 0)
|
|
return -EINVAL;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.in.id = window->id;
|
|
request.in.config.addr = target->addr;
|
|
request.in.config.x_end = target->x_end;
|
|
request.in.config.y_end = target->y_end;
|
|
request.in.config.x_start = target->x_start;
|
|
request.in.config.y_start = target->y_start;
|
|
request.in.config.noc = target->noc;
|
|
request.in.config.mcast = target->mcast;
|
|
request.in.config.ordering = target->ordering;
|
|
request.in.config.static_vc = target->static_vc;
|
|
if (ft_tt_kmd_sys_ioctl(device->fd, FT_TT_KMD_IOCTL_CONFIGURE_WINDOW, &request) != 0)
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
#else /* !FT_TT_PHASE1B_NOC_ENABLED */
|
|
|
|
int ft_tt_kmd_window_aim(ft_tt_kmd_device *device, ft_tt_kmd_window *window,
|
|
const ft_tt_kmd_window_target *target)
|
|
{
|
|
(void)device;
|
|
(void)window;
|
|
(void)target;
|
|
return -EPERM;
|
|
}
|
|
|
|
#endif /* FT_TT_PHASE1B_NOC_ENABLED */
|
|
|
|
int ft_tt_kmd_window_aim_unicast(ft_tt_kmd_device *device, ft_tt_kmd_window *window,
|
|
uint16_t x, uint16_t y, uint64_t addr)
|
|
{
|
|
ft_tt_kmd_window_target target;
|
|
|
|
memset(&target, 0, sizeof(target));
|
|
target.addr = addr;
|
|
target.x_end = x;
|
|
target.y_end = y;
|
|
return ft_tt_kmd_window_aim(device, window, &target);
|
|
}
|