/* 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 #include #include #include #include #include #include #include #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(); }