Foundry/tt/tests/test_kmd.c

820 lines
32 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/**
* @file test_kmd.c
* @brief Off-device unit test of the kernel-driver boundary (ft_tt_kmd.c): every entry point
* is driven against fake open/ioctl/mmap/munmap/close, and every request is compared byte
* for byte against an image built from the ABI's documented OFFSETS — never from the
* library's own structs, so a wrong field offset in ft_tt_kmd_abi.h is a failure here.
*
* Built twice by Makefile.kmd: once fenced (default build: aiming must be -EPERM and issue
* nothing) and once with FT_TT_PHASE1B_NOC_ENABLED (aiming issues CONFIGURE_WINDOW).
*
* @status Covers every entry point in ft_tt_kmd.h, success and failure paths.
* @issues None
* @todo None
*/
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include "ft_tt_kmd.h"
#include "ft_tt_test_report.h"
#include "ft_tt_kmd_fake_syscalls.h"
/* ------------------------------------------------------------------------- */
/* The ABI as the spec writes it: literal numbers, independent of the library */
/* ------------------------------------------------------------------------- */
#define ABI_REQUEST(n) (0xFA00ul | (unsigned long)(n))
#define REQ_DEVICE_INFO ABI_REQUEST(0)
#define REQ_QUERY_MAPPINGS ABI_REQUEST(2)
#define REQ_DRIVER_INFO ABI_REQUEST(5)
#define REQ_ALLOCATE ABI_REQUEST(11)
#define REQ_FREE ABI_REQUEST(12)
#define REQ_CONFIGURE ABI_REQUEST(13)
#define REQ_NOC_CLEANUP ABI_REQUEST(14)
#define REQ_POWER_STATE ABI_REQUEST(15)
#define FAKE_FD 7
#define MAX_CALLS 16
#define MAX_ARG_BYTES 256
#define MIB (1ull << 20)
#define GIB (1ull << 30)
/* Argument size of each request, from the spec. */
static size_t request_length(unsigned long request)
{
switch (request) {
case REQ_DEVICE_INFO: return 24;
case REQ_QUERY_MAPPINGS: return 8 + 6 * 24;
case REQ_DRIVER_INFO: return 16;
case REQ_ALLOCATE: return 48;
case REQ_FREE: return 8;
case REQ_CONFIGURE: return 48;
case REQ_NOC_CLEANUP: return 32;
case REQ_POWER_STATE: return 40;
default: return 0;
}
}
/* ------------------------------------------------------------------------- */
/* Fake syscalls */
/* ------------------------------------------------------------------------- */
typedef enum { CALL_OPEN, CALL_CLOSE, CALL_IOCTL, CALL_MMAP, CALL_MUNMAP } fake_call_kind;
typedef struct {
fake_call_kind kind;
int fd;
char path[64];
int flags;
unsigned long request;
unsigned char bytes[MAX_ARG_BYTES];
void *address;
size_t length;
int protection;
int map_flags;
off_t offset;
} fake_call;
/** What call N does: fail with fail_errno, or copy `reply` over the ioctl argument. */
typedef struct {
int fail_errno;
unsigned char reply[MAX_ARG_BYTES];
size_t reply_length;
} fake_step;
static fake_call calls[MAX_CALLS];
static fake_step script[MAX_CALLS];
static int call_count;
static unsigned char fake_aperture[64];
static void fake_reset(void)
{
memset(calls, 0, sizeof(calls));
memset(script, 0, sizeof(script));
call_count = 0;
}
/* Record the next call; NULL once the log is full (the test then fails on the count). */
static fake_call *fake_record(fake_call_kind kind)
{
fake_call *call;
if (call_count >= MAX_CALLS)
return NULL;
call = &calls[call_count];
call->kind = kind;
return call;
}
/* Apply the scripted outcome of the call just recorded. Returns false when it fails. */
static bool fake_outcome(void)
{
int fail = script[call_count].fail_errno;
call_count++;
if (fail) {
errno = fail;
return false;
}
return true;
}
int test_kmd_fake_open(const char *path, int flags)
{
fake_call *call = fake_record(CALL_OPEN);
if (!call)
return -1;
snprintf(call->path, sizeof(call->path), "%s", path);
call->flags = flags;
return fake_outcome() ? FAKE_FD : -1;
}
int test_kmd_fake_close(int fd)
{
fake_call *call = fake_record(CALL_CLOSE);
if (!call)
return -1;
call->fd = fd;
return fake_outcome() ? 0 : -1;
}
int test_kmd_fake_ioctl(int fd, unsigned long request, void *argument)
{
fake_call *call = fake_record(CALL_IOCTL);
const fake_step *step = &script[call_count];
size_t length = request_length(request);
if (!call)
return -1;
call->fd = fd;
call->request = request;
if (argument && length)
memcpy(call->bytes, argument, length);
if (!fake_outcome())
return -1;
if (argument && step->reply_length)
memcpy(argument, step->reply, step->reply_length);
return 0;
}
void *test_kmd_fake_mmap(void *address, size_t length, int protection, int flags, int fd,
off_t offset)
{
fake_call *call = fake_record(CALL_MMAP);
if (!call)
return MAP_FAILED;
call->address = address;
call->length = length;
call->protection = protection;
call->map_flags = flags;
call->fd = fd;
call->offset = offset;
return fake_outcome() ? (void *)fake_aperture : MAP_FAILED;
}
int test_kmd_fake_munmap(void *address, size_t length)
{
fake_call *call = fake_record(CALL_MUNMAP);
if (!call)
return -1;
call->address = address;
call->length = length;
return fake_outcome() ? 0 : -1;
}
/* ------------------------------------------------------------------------- */
/* Byte images */
/* ------------------------------------------------------------------------- */
static void put_le(unsigned char *image, size_t offset, unsigned width, uint64_t value)
{
unsigned index;
for (index = 0; index < width; index++)
image[offset + index] = (unsigned char)(value >> (8u * index));
}
/** Check call `index` is an ioctl of `request` on FAKE_FD whose argument equals `expected`. */
static void check_ioctl(int index, unsigned long request, const unsigned char *expected,
const char *what)
{
const fake_call *call = &calls[index];
size_t length = request_length(request);
size_t offset;
if (index >= call_count || call->kind != CALL_IOCTL) {
ft_tt_test_check(what, false, "no ioctl at this position in the call log");
return;
}
ft_tt_test_check_fmt(what, call->request == request && call->fd == FAKE_FD,
"request 0x%lx (want 0x%lx) fd %d", call->request, request, call->fd);
for (offset = 0; offset < length; offset++) {
if (call->bytes[offset] != expected[offset])
break;
}
ft_tt_test_check_fmt(" argument bytes match the ABI offsets", offset == length,
offset == length ? "%zu bytes" : "first mismatch at byte %zu",
offset == length ? length : offset);
}
static void check_call_count(int want, const char *what)
{
ft_tt_test_check_fmt(what, call_count == want, "%d syscalls (want %d)", call_count, want);
}
/* ------------------------------------------------------------------------- */
/* Fixtures */
/* ------------------------------------------------------------------------- */
/* A QUERY_MAPPINGS reply: six records at 8 + 24k, with an unknown id in slot 4. */
static void script_mappings(fake_step *step, uint64_t bar0_len, uint64_t bar4_len)
{
static const uint32_t ids[6] = { 1, 2, 3, 4, 9, 6 };
size_t record;
memset(step, 0, sizeof(*step));
step->reply_length = 8 + 6 * 24;
put_le(step->reply, 0, 4, 6);
for (record = 0; record < 6; record++) {
size_t base = 8 + record * 24;
put_le(step->reply, base + 0, 4, ids[record]);
put_le(step->reply, base + 8, 8, (uint64_t)(record + 1) << 36);
put_le(step->reply, base + 16, 8, 0x5555u); /* overwritten for UC ids below */
}
put_le(step->reply, 8 + 0 * 24 + 16, 8, bar0_len);
put_le(step->reply, 8 + 2 * 24 + 16, 8, 1 * MIB);
if (bar4_len) {
put_le(step->reply, 8 + 4 * 24 + 0, 4, 5); /* make slot 4 resource2 UC */
put_le(step->reply, 8 + 4 * 24 + 16, 8, bar4_len);
}
}
/* Open a device with the given BAR lengths; the call log is reset afterwards. */
static ft_tt_kmd_device *open_fixture(uint64_t bar0_len, uint64_t bar4_len)
{
ft_tt_kmd_device *device = NULL;
fake_reset();
script_mappings(&script[1], bar0_len, bar4_len);
if (ft_tt_kmd_open("/dev/tenstorrent/0", &device, 0) != 0)
ft_tt_test_check("fixture open", false, "open failed");
fake_reset();
return device;
}
static void close_fixture(ft_tt_kmd_device *device)
{
fake_reset();
(void)ft_tt_kmd_close(device);
fake_reset();
}
/* ------------------------------------------------------------------------- */
/* Open / close */
/* ------------------------------------------------------------------------- */
static void test_open_success(void)
{
ft_tt_kmd_device *device = NULL;
ft_tt_kmd_resources resources;
unsigned char expected[MAX_ARG_BYTES] = { 0 };
int rc;
ft_tt_test_step("open: one open, one QUERY_MAPPINGS asking for six records");
fake_reset();
script_mappings(&script[1], 512 * MIB, 0);
rc = ft_tt_kmd_open("/dev/tenstorrent/3", &device, O_APPEND);
ft_tt_test_check_fmt("open succeeds", rc == 0 && device != NULL, "rc %d", rc);
check_call_count(2, "exactly two syscalls");
ft_tt_test_check_fmt("open path and flags", calls[0].kind == CALL_OPEN &&
strcmp(calls[0].path, "/dev/tenstorrent/3") == 0 &&
calls[0].flags == (O_RDWR | O_CLOEXEC | O_APPEND),
"%s flags 0x%x", calls[0].path, (unsigned)calls[0].flags);
put_le(expected, 0, 4, 6);
check_ioctl(1, REQ_QUERY_MAPPINGS, expected, "QUERY_MAPPINGS request");
ft_tt_test_check_fmt("fd is the opened one", ft_tt_kmd_fd(device) == FAKE_FD, "%d",
ft_tt_kmd_fd(device));
memset(&resources, 0xAB, sizeof(resources));
rc = ft_tt_kmd_get_resources(device, &resources);
ft_tt_test_check("resources are readable", rc == 0, "");
ft_tt_test_check_fmt("BAR0 from ids 1/2", resources.bar0_uc_offset == (1ull << 36) &&
resources.bar0_wc_offset == (2ull << 36) &&
resources.bar0_len == 512 * MIB,
"uc 0x%llx wc 0x%llx len 0x%llx",
(unsigned long long)resources.bar0_uc_offset,
(unsigned long long)resources.bar0_wc_offset,
(unsigned long long)resources.bar0_len);
ft_tt_test_check_fmt("BAR2 from ids 3/4 (length from the UC record only)",
resources.bar2_uc_offset == (3ull << 36) &&
resources.bar2_wc_offset == (4ull << 36) && resources.bar2_len == MIB,
"len 0x%llx", (unsigned long long)resources.bar2_len);
ft_tt_test_check_fmt("unknown id ignored; BAR4 has only its WC offset",
resources.bar4_uc_offset == 0 && resources.bar4_len == 0 &&
resources.bar4_wc_offset == (6ull << 36),
"uc 0x%llx len 0x%llx", (unsigned long long)resources.bar4_uc_offset,
(unsigned long long)resources.bar4_len);
close_fixture(device);
}
static void test_open_failures(void)
{
ft_tt_kmd_device *device = NULL;
int rc;
ft_tt_test_step("open: argument checks and failure paths");
fake_reset();
rc = ft_tt_kmd_open(NULL, &device, 0);
ft_tt_test_check_fmt("NULL path is -EINVAL", rc == -EINVAL, "rc %d", rc);
rc = ft_tt_kmd_open("/dev/tenstorrent/0", NULL, 0);
ft_tt_test_check_fmt("NULL out is -EINVAL", rc == -EINVAL, "rc %d", rc);
check_call_count(0, "no syscalls for bad arguments");
fake_reset();
script[0].fail_errno = ENOENT;
rc = ft_tt_kmd_open("/dev/tenstorrent/0", &device, 0);
ft_tt_test_check_fmt("failed open is -errno", rc == -ENOENT && device == NULL, "rc %d", rc);
check_call_count(1, "nothing after a failed open");
fake_reset();
script[1].fail_errno = ENOTTY;
rc = ft_tt_kmd_open("/dev/tenstorrent/0", &device, 0);
ft_tt_test_check_fmt("failed QUERY_MAPPINGS is its -errno", rc == -ENOTTY && device == NULL,
"rc %d", rc);
check_call_count(3, "open, ioctl, close");
ft_tt_test_check("the fd is closed", calls[2].kind == CALL_CLOSE && calls[2].fd == FAKE_FD,
"");
}
static void test_close(void)
{
ft_tt_kmd_device *device;
int rc;
ft_tt_test_step("close");
fake_reset();
rc = ft_tt_kmd_close(NULL);
ft_tt_test_check_fmt("NULL is 0 and touches nothing", rc == 0 && call_count == 0, "rc %d",
rc);
device = open_fixture(512 * MIB, 0);
rc = ft_tt_kmd_close(device);
ft_tt_test_check_fmt("close closes the fd", rc == 0 && call_count == 1 &&
calls[0].kind == CALL_CLOSE && calls[0].fd == FAKE_FD, "rc %d", rc);
device = open_fixture(512 * MIB, 0);
script[0].fail_errno = EBADF;
rc = ft_tt_kmd_close(device);
ft_tt_test_check_fmt("a failing close is -errno", rc == -EBADF, "rc %d", rc);
ft_tt_test_check_fmt("ft_tt_kmd_fd(NULL) is -1", ft_tt_kmd_fd(NULL) == -1, "%d",
ft_tt_kmd_fd(NULL));
}
/* ------------------------------------------------------------------------- */
/* Attributes */
/* ------------------------------------------------------------------------- */
/* One GET_DEVICE_INFO against a device with the given BARs, replying `device_id`. */
static int attrs_for(uint64_t bar0_len, uint64_t bar4_len, uint16_t device_id,
ft_tt_kmd_device_attrs *out)
{
ft_tt_kmd_device *device = open_fixture(bar0_len, bar4_len);
int rc;
script[0].reply_length = 24;
put_le(script[0].reply, 4, 4, 20);
put_le(script[0].reply, 8, 2, 0x1E52);
put_le(script[0].reply, 10, 2, device_id);
put_le(script[0].reply, 12, 2, 0x1E53);
put_le(script[0].reply, 14, 2, 0x0040);
put_le(script[0].reply, 16, 2, (0x12u << 8) | (3u << 3) | 5u);
put_le(script[0].reply, 18, 2, 32);
put_le(script[0].reply, 20, 2, 0x0007);
memset(out, 0xEE, sizeof(*out));
rc = ft_tt_kmd_get_attrs(device, out);
{
unsigned char expected[MAX_ARG_BYTES] = { 0 };
put_le(expected, 0, 4, 20);
check_ioctl(0, REQ_DEVICE_INFO, expected, "GET_DEVICE_INFO request");
check_call_count(1, "one syscall");
}
close_fixture(device);
return rc;
}
static void test_attrs(void)
{
ft_tt_kmd_device_attrs attrs;
int rc;
ft_tt_test_step("attrs: one GET_DEVICE_INFO, decoded");
rc = attrs_for(512 * MIB, 0, 0xB140, &attrs);
ft_tt_test_check_fmt("ids and arch", rc == 0 && attrs.pci_vendor_id == 0x1E52 &&
attrs.pci_device_id == 0xB140 &&
attrs.pci_subsystem_vendor_id == 0x1E53 &&
attrs.pci_subsystem_id == 0x40 &&
attrs.chip_arch == FT_TT_KMD_ARCH_BLACKHOLE,
"rc %d arch %llu", rc, (unsigned long long)attrs.chip_arch);
ft_tt_test_check_fmt("bdf and domain decoded", attrs.pci_bus == 0x12 &&
attrs.pci_device == 3 && attrs.pci_function == 5 &&
attrs.pci_domain == 7, "%llx:%llx.%llx domain %llx",
(unsigned long long)attrs.pci_bus, (unsigned long long)attrs.pci_device,
(unsigned long long)attrs.pci_function,
(unsigned long long)attrs.pci_domain);
ft_tt_test_check_fmt("Blackhole, 512 MiB BAR0, no BAR4: 202 x 2M (capped), 0 x 4G",
attrs.window_count_2m == 202 && attrs.window_count_4g == 0 &&
attrs.window_count_1m == 0 && attrs.window_count_16m == 0,
"2m %llu 4g %llu", (unsigned long long)attrs.window_count_2m,
(unsigned long long)attrs.window_count_4g);
rc = attrs_for(100 * MIB, 8 * GIB, 0xB140, &attrs);
ft_tt_test_check_fmt("100 MiB BAR0 and 8 GiB BAR4: 50 x 2M, 2 x 4G",
rc == 0 && attrs.window_count_2m == 50 && attrs.window_count_4g == 2,
"2m %llu 4g %llu", (unsigned long long)attrs.window_count_2m,
(unsigned long long)attrs.window_count_4g);
rc = attrs_for(512 * MIB, 8 * GIB, 0x401E, &attrs);
ft_tt_test_check_fmt("Wormhole: arch 1, no window counts", rc == 0 &&
attrs.chip_arch == FT_TT_KMD_ARCH_WORMHOLE &&
attrs.window_count_2m == 0 && attrs.window_count_4g == 0,
"arch %llu", (unsigned long long)attrs.chip_arch);
rc = attrs_for(512 * MIB, 0, 0xFACA, &attrs);
ft_tt_test_check_fmt("anything else: arch 0", rc == 0 &&
attrs.chip_arch == FT_TT_KMD_ARCH_UNKNOWN && attrs.window_count_2m == 0,
"arch %llu", (unsigned long long)attrs.chip_arch);
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
script[0].fail_errno = EFAULT;
rc = ft_tt_kmd_get_attrs(device, &attrs);
ft_tt_test_check_fmt("a failing ioctl is -errno", rc == -EFAULT, "rc %d", rc);
ft_tt_test_check_fmt("NULL out is -EINVAL",
ft_tt_kmd_get_attrs(device, NULL) == -EINVAL &&
ft_tt_kmd_get_resources(device, NULL) == -EINVAL &&
ft_tt_kmd_get_attrs(NULL, &attrs) == -EINVAL, "%s", "");
close_fixture(device);
}
}
/* ------------------------------------------------------------------------- */
/* Power state and close-time NoC write */
/* ------------------------------------------------------------------------- */
static void test_power_state(void)
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
unsigned char expected[MAX_ARG_BYTES] = { 0 };
int rc;
ft_tt_test_step("power state");
put_le(expected, 0, 4, 40);
put_le(expected, 9, 1, 0x0F);
put_le(expected, 10, 2, 0x7FFF);
rc = ft_tt_kmd_request_max_ai_clock(device, true);
ft_tt_test_check_fmt("max succeeds", rc == 0, "rc %d", rc);
check_ioctl(0, REQ_POWER_STATE, expected, "SET_POWER_STATE, max: word 0x7FFF");
fake_reset();
put_le(expected, 10, 2, 0x7FFE);
rc = ft_tt_kmd_request_max_ai_clock(device, false);
check_ioctl(0, REQ_POWER_STATE, expected, "SET_POWER_STATE, min: word 0x7FFE");
fake_reset();
script[0].fail_errno = EINVAL;
rc = ft_tt_kmd_request_max_ai_clock(device, true);
ft_tt_test_check_fmt("a refusal is -errno", rc == -EINVAL && call_count == 1, "rc %d", rc);
ft_tt_test_check("NULL device is -EINVAL",
ft_tt_kmd_request_max_ai_clock(NULL, true) == -EINVAL, "");
close_fixture(device);
}
static void test_noc_cleanup(void)
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
unsigned char expected[MAX_ARG_BYTES] = { 0 };
int rc;
ft_tt_test_step("close-time NoC write");
put_le(expected, 0, 4, 32);
put_le(expected, 8, 1, 1);
put_le(expected, 9, 1, 7);
put_le(expected, 10, 1, 9);
put_le(expected, 11, 1, 1);
put_le(expected, 16, 8, 0x123456789Aull);
put_le(expected, 24, 8, 0xDEADBEEFCAFEull);
rc = ft_tt_kmd_set_noc_cleanup(device, 1, 7, 9, 0x123456789Aull, 0xDEADBEEFCAFEull);
ft_tt_test_check_fmt("set succeeds", rc == 0, "rc %d", rc);
check_ioctl(0, REQ_NOC_CLEANUP, expected, "NOC_CLEANUP, set");
fake_reset();
memset(expected, 0, sizeof(expected));
put_le(expected, 0, 4, 32);
rc = ft_tt_kmd_clear_noc_cleanup(device);
ft_tt_test_check_fmt("clear succeeds", rc == 0, "rc %d", rc);
check_ioctl(0, REQ_NOC_CLEANUP, expected, "NOC_CLEANUP, clear: enabled 0, all else 0");
fake_reset();
script[0].fail_errno = ENOTTY;
rc = ft_tt_kmd_set_noc_cleanup(device, 0, 1, 2, 3, 4);
ft_tt_test_check_fmt("a refusal is -errno", rc == -ENOTTY, "rc %d", rc);
ft_tt_test_check("NULL device is -EINVAL",
ft_tt_kmd_set_noc_cleanup(NULL, 0, 0, 0, 0, 0) == -EINVAL &&
ft_tt_kmd_clear_noc_cleanup(NULL) == -EINVAL, "");
close_fixture(device);
}
/* ------------------------------------------------------------------------- */
/* Driver attributes */
/* ------------------------------------------------------------------------- */
static void test_driver_attr(void)
{
ft_tt_kmd_device *device;
fake_step reply;
unsigned char expected[MAX_ARG_BYTES] = { 0 };
uint64_t value = 99;
int rc;
ft_tt_test_step("driver attributes");
fake_reset();
ft_tt_test_check("NULL out is -EINVAL",
ft_tt_kmd_driver_attr(NULL, FT_TT_KMD_DRIVER_API_VERSION, NULL) == -EINVAL,
"");
rc = ft_tt_kmd_driver_attr(NULL, FT_TT_KMD_DRIVER_API_VERSION, &value);
ft_tt_test_check_fmt("API version without a device is 2", rc == 0 && value == 2,
"rc %d value %llu", rc, (unsigned long long)value);
value = 99;
rc = ft_tt_kmd_driver_attr(NULL, FT_TT_KMD_DRIVER_VERSION_MINOR, &value);
ft_tt_test_check_fmt("semver without a device is -ENODEV, value 0",
rc == -ENODEV && value == 0, "rc %d value %llu", rc,
(unsigned long long)value);
check_call_count(0, "no syscalls without a device");
memset(&reply, 0, sizeof(reply));
reply.reply_length = 16;
put_le(reply.reply, 4, 4, 12);
put_le(reply.reply, 8, 4, 99);
put_le(reply.reply, 12, 1, 1);
put_le(reply.reply, 13, 1, 5);
put_le(reply.reply, 14, 1, 7);
device = open_fixture(512 * MIB, 0);
script[0] = reply;
script[1] = reply;
rc = ft_tt_kmd_driver_attr(device, FT_TT_KMD_DRIVER_VERSION_MINOR, &value);
put_le(expected, 0, 4, 12);
check_ioctl(0, REQ_DRIVER_INFO, expected, "GET_DRIVER_INFO request");
ft_tt_test_check_fmt("minor is the reply's byte 13", rc == 0 && value == 5, "value %llu",
(unsigned long long)value);
rc = ft_tt_kmd_driver_attr(device, FT_TT_KMD_DRIVER_API_VERSION, &value);
ft_tt_test_check_fmt("API version is the compiled-in 2, not the reply's 99",
rc == 0 && value == 2 && call_count == 2, "value %llu",
(unsigned long long)value);
fake_reset();
script[0] = reply;
script[1] = reply;
rc = ft_tt_kmd_driver_attr(device, FT_TT_KMD_DRIVER_VERSION_MAJOR, &value);
ft_tt_test_check_fmt("major is byte 12", rc == 0 && value == 1, "%llu",
(unsigned long long)value);
rc = ft_tt_kmd_driver_attr(device, FT_TT_KMD_DRIVER_VERSION_PATCH, &value);
ft_tt_test_check_fmt("patch is byte 14", rc == 0 && value == 7, "%llu",
(unsigned long long)value);
rc = ft_tt_kmd_driver_attr(device, (enum ft_tt_kmd_driver_attr)4, &value);
ft_tt_test_check_fmt("an unknown attribute is -EINVAL", rc == -EINVAL, "rc %d", rc);
fake_reset();
script[0].fail_errno = EIO;
rc = ft_tt_kmd_driver_attr(device, FT_TT_KMD_DRIVER_API_VERSION, &value);
ft_tt_test_check_fmt("a failing ioctl is -errno", rc == -EIO, "rc %d", rc);
close_fixture(device);
}
/* ------------------------------------------------------------------------- */
/* Windows */
/* ------------------------------------------------------------------------- */
/* Script an ALLOCATE_WINDOW reply at step `index`: id 42, distinct UC and WC offsets. */
static void script_allocate(int index)
{
script[index].reply_length = 48;
put_le(script[index].reply, 16, 4, 42);
put_le(script[index].reply, 24, 8, 0x1000000000ull);
put_le(script[index].reply, 32, 8, 0x2000000000ull);
}
static void test_window_alloc(void)
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
ft_tt_kmd_window *window = NULL;
unsigned char expected[MAX_ARG_BYTES] = { 0 };
void *aperture = NULL;
uint32_t id = 0;
size_t size = 0;
int rc;
ft_tt_test_step("window alloc: ALLOCATE_WINDOW then mmap");
script_allocate(0);
rc = ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_WC, PROT_READ, &window);
ft_tt_test_check_fmt("alloc succeeds", rc == 0 && window != NULL, "rc %d", rc);
put_le(expected, 0, 8, 2 * MIB);
check_ioctl(0, REQ_ALLOCATE, expected, "ALLOCATE_WINDOW request");
ft_tt_test_check_fmt("mmap of the WC offset", call_count == 2 && calls[1].kind == CALL_MMAP &&
calls[1].address == NULL && calls[1].length == 2 * MIB &&
calls[1].protection == PROT_READ && calls[1].map_flags == MAP_SHARED &&
calls[1].fd == FAKE_FD && calls[1].offset == (off_t)0x2000000000ll,
"offset 0x%llx", (unsigned long long)calls[1].offset);
ft_tt_test_check_fmt("accessors", ft_tt_kmd_window_id(window, &id) == 0 && id == 42 &&
ft_tt_kmd_window_size(window, &size) == 0 && size == 2 * MIB &&
ft_tt_kmd_window_aperture(window, &aperture) == 0 &&
aperture == (void *)fake_aperture, "id %u size %zu", id, size);
fake_reset();
rc = ft_tt_kmd_window_free(device, window);
memset(expected, 0, sizeof(expected));
put_le(expected, 0, 4, 42);
ft_tt_test_check_fmt("free: munmap FIRST", rc == 0 && call_count == 2 &&
calls[0].kind == CALL_MUNMAP && calls[0].address == fake_aperture &&
calls[0].length == 2 * MIB, "rc %d", rc);
check_ioctl(1, REQ_FREE, expected, "then FREE_WINDOW with the id");
fake_reset();
script_allocate(0);
rc = ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_UC, PROT_NONE, &window);
ft_tt_test_check_fmt("UC maps the UC offset", rc == 0 &&
calls[1].offset == (off_t)0x1000000000ll &&
calls[1].protection == PROT_NONE, "offset 0x%llx",
(unsigned long long)calls[1].offset);
fake_reset();
script[0].fail_errno = EFAULT;
script[1].fail_errno = EINVAL;
rc = ft_tt_kmd_window_free(device, window);
ft_tt_test_check_fmt("free continues past a failed munmap; last failure wins",
rc == -EINVAL && call_count == 2, "rc %d", rc);
close_fixture(device);
}
static void test_window_alloc_failures(void)
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
ft_tt_kmd_window *window = NULL;
unsigned char expected[MAX_ARG_BYTES] = { 0 };
int rc;
ft_tt_test_step("window alloc: failure paths");
ft_tt_test_check("NULL device or out is -EINVAL",
ft_tt_kmd_window_alloc(NULL, 2 * MIB, FT_TT_KMD_CACHE_UC, 0, &window) ==
-EINVAL && ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_UC, 0,
NULL) == -EINVAL && call_count == 0, "");
script[0].fail_errno = ENOMEM;
rc = ft_tt_kmd_window_alloc(device, 4 * GIB, FT_TT_KMD_CACHE_WC, 0, &window);
ft_tt_test_check_fmt("a refused ALLOCATE is -errno and maps nothing",
rc == -ENOMEM && call_count == 1, "rc %d", rc);
fake_reset();
script_allocate(0);
script[1].fail_errno = ENODEV;
rc = ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_WC, 0, &window);
put_le(expected, 0, 4, 42);
ft_tt_test_check_fmt("a failed mmap is its -errno", rc == -ENODEV && call_count == 3,
"rc %d calls %d", rc, call_count);
check_ioctl(2, REQ_FREE, expected, "and the claim is released");
fake_reset();
script_allocate(0);
script[1].fail_errno = ENODEV;
script[2].fail_errno = EBADF;
rc = ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_WC, 0, &window);
ft_tt_test_check_fmt("a failed release after a failed mmap reports the release",
rc == -EBADF, "rc %d", rc);
ft_tt_test_check("window accessors and free refuse NULL",
ft_tt_kmd_window_id(NULL, NULL) == -EINVAL &&
ft_tt_kmd_window_size(NULL, NULL) == -EINVAL &&
ft_tt_kmd_window_aperture(NULL, NULL) == -EINVAL &&
ft_tt_kmd_window_free(device, NULL) == -EINVAL, "");
close_fixture(device);
}
#ifdef FT_TT_PHASE1B_NOC_ENABLED
static void test_window_aim(void)
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
ft_tt_kmd_window *window = NULL;
ft_tt_kmd_window_target target;
unsigned char expected[MAX_ARG_BYTES] = { 0 };
int rc;
ft_tt_test_step("window aim (FT_TT_PHASE1B_NOC_ENABLED): CONFIGURE_WINDOW");
script_allocate(0);
(void)ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_UC, 0, &window);
fake_reset();
memset(&target, 0, sizeof(target));
target.addr = 6 * MIB;
target.x_end = 0x0102;
target.y_end = 0x0304;
target.x_start = 0x0506;
target.y_start = 0x0708;
target.noc = 1;
target.mcast = 1;
target.ordering = FT_TT_KMD_ORDERING_POSTED_STRICT;
target.static_vc = 1;
rc = ft_tt_kmd_window_aim(device, window, &target);
put_le(expected, 0, 4, 42);
put_le(expected, 8, 8, 6 * MIB);
put_le(expected, 16, 2, 0x0102);
put_le(expected, 18, 2, 0x0304);
put_le(expected, 20, 2, 0x0506);
put_le(expected, 22, 2, 0x0708);
put_le(expected, 24, 1, 1);
put_le(expected, 25, 1, 1);
put_le(expected, 26, 1, 3);
put_le(expected, 28, 1, 1);
ft_tt_test_check_fmt("aim succeeds", rc == 0, "rc %d", rc);
check_ioctl(0, REQ_CONFIGURE, expected, "CONFIGURE_WINDOW, every field");
fake_reset();
rc = ft_tt_kmd_window_aim_unicast(device, window, 9, 11, 2 * MIB);
memset(expected, 0, sizeof(expected));
put_le(expected, 0, 4, 42);
put_le(expected, 8, 8, 2 * MIB);
put_le(expected, 16, 2, 9);
put_le(expected, 18, 2, 11);
check_ioctl(0, REQ_CONFIGURE, expected, "CONFIGURE_WINDOW, unicast");
fake_reset();
rc = ft_tt_kmd_window_aim_unicast(device, window, 9, 11, 2 * MIB + 4);
ft_tt_test_check_fmt("a misaligned address is -EINVAL and issues nothing",
rc == -EINVAL && call_count == 0, "rc %d", rc);
ft_tt_test_check("NULL arguments are -EINVAL",
ft_tt_kmd_window_aim(NULL, window, &target) == -EINVAL &&
ft_tt_kmd_window_aim(device, NULL, &target) == -EINVAL &&
ft_tt_kmd_window_aim(device, window, NULL) == -EINVAL, "");
script[0].fail_errno = EPERM;
rc = ft_tt_kmd_window_aim(device, window, &target);
ft_tt_test_check_fmt("a refused CONFIGURE is -errno", rc == -EPERM && call_count == 1,
"rc %d", rc);
fake_reset();
(void)ft_tt_kmd_window_free(device, window);
close_fixture(device);
}
#else /* !FT_TT_PHASE1B_NOC_ENABLED */
static void test_window_aim(void)
{
ft_tt_kmd_device *device = open_fixture(512 * MIB, 0);
ft_tt_kmd_window *window = NULL;
ft_tt_kmd_window_target target;
int rc;
ft_tt_test_step("window aim (default build): fenced");
script_allocate(0);
(void)ft_tt_kmd_window_alloc(device, 2 * MIB, FT_TT_KMD_CACHE_UC, 0, &window);
fake_reset();
memset(&target, 0, sizeof(target));
rc = ft_tt_kmd_window_aim(device, window, &target);
ft_tt_test_check_fmt("aim is -EPERM", rc == -EPERM, "rc %d", rc);
rc = ft_tt_kmd_window_aim_unicast(device, window, 1, 2, 0);
ft_tt_test_check_fmt("unicast aim is -EPERM", rc == -EPERM, "rc %d", rc);
ft_tt_test_check_fmt("even NULL arguments get -EPERM",
ft_tt_kmd_window_aim(NULL, NULL, NULL) == -EPERM, "%s", "");
check_call_count(0, "no syscall at all");
(void)ft_tt_kmd_window_free(device, window);
close_fixture(device);
}
#endif /* FT_TT_PHASE1B_NOC_ENABLED */
int main(void)
{
test_open_success();
test_open_failures();
test_close();
test_attrs();
test_power_state();
test_noc_cleanup();
test_driver_attr();
test_window_alloc();
test_window_alloc_failures();
test_window_aim();
return ft_tt_test_finish();
}