Foundry/tt/tests/test_udrv_pci_sysfs.c

566 lines
24 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file test_udrv_pci_sysfs.c
* @brief Gate for ft_tt_udrv_pci_sysfs.c, driven against a SYNTHETIC sysfs
* tree built in a scratch directory.
*
* A fixture is what makes this testable at all: the cases that matter are
* the ones a real card cannot produce on demand — an absent BAR, a
* non-prefetchable BAR, a one-byte BAR, a truncated `resource` file, a
* device whose ids do not parse. The real card contributes exactly one of
* them (BAR4 absent) and only by accident of this board's 36-bit host.
*
* The reference numbers in stage 1 are the REAL card's, read from
* /sys/bus/pci/devices/0000:12:00.0 on the card host, so the parser is held to
* what the machine actually prints and not to what this file imagines.
*
* WHAT THIS CANNOT PROVE, stated so it is never assumed: that the numbers
* describe hardware, that a BAR's sysfs file can be opened as root or under
* a udev rule, that the PCI core's mmap path behaves like a regular file's,
* or that a write-combined store reaches the card. Stage 7 maps a REAL file
* through map_bar, which covers the path building, the offset arithmetic and
* the protection — everything except the fact that the far end is silicon.
* `tests/test_udrv_card_tlb.c` is where that is settled.
*
* @status Off-hardware. Touches nothing but a scratch directory.
* @issues None
* @todo None
*/
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ft_tt_test_report.h"
#include "ft_tt_udrv.h"
#include "ft_tt_udrv_pci_sysfs.h"
/** Size of the fixture's real, mappable `resource0`. Sixteen pages. */
#define FIXTURE_RESOURCE_BYTES 65536u
/** The real card's BAR table, copied from `cat resource` on the card host. */
#define REAL_BAR0_LINE "0x00000005e0000000 0x00000005ffffffff 0x000000000014220c\n"
#define REAL_BAR2_LINE "0x0000000600000000 0x00000006000fffff 0x000000000014220c\n"
#define ABSENT_LINE "0x0000000000000000 0x0000000000000000 0x0000000000000000\n"
/** BAR2 as a NON-prefetchable memory BAR: the same line without bit 13. */
#define NONPREFETCH_BAR2_LINE "0x0000000600000000 0x00000006000fffff 0x000000000014020c\n"
/** A one-byte memory BAR: end == start != 0. Length must be 1, not 0. */
#define ONE_BYTE_LINE "0x0000000700000000 0x0000000700000000 0x0000000000000200\n"
static char scratch_root[256];
/** mkdir, aborting the run if it fails — a broken fixture is not a result. */
static void make_directory(const char *path)
{
if (mkdir(path, 0755) != 0) {
(void)fprintf(stderr, "fixture: mkdir %s failed\n", path);
exit(2);
}
}
/** Write one small file into a fixture device directory. */
static void write_file(const char *device_dir, const char *name, const char *contents)
{
char path[512];
FILE *file;
(void)snprintf(path, sizeof(path), "%s/%s", device_dir, name);
file = fopen(path, "w");
if (!file) {
(void)fprintf(stderr, "fixture: fopen %s failed\n", path);
exit(2);
}
(void)fputs(contents, file);
(void)fclose(file);
}
/** Build one fixture device. `resource_body` may be NULL to omit the file. */
static void make_device(const char *address, const char *vendor, const char *device,
const char *resource_body)
{
char dir[512];
(void)snprintf(dir, sizeof(dir), "%s/%s", scratch_root, address);
make_directory(dir);
write_file(dir, "vendor", vendor);
write_file(dir, "device", device);
write_file(dir, "subsystem_vendor", "0x1e52\n");
write_file(dir, "subsystem_device", "0x0040\n");
write_file(dir, "class", "0x120000\n");
write_file(dir, "revision", "0x00\n");
if (resource_body)
write_file(dir, "resource", resource_body);
}
/**
* Give a fixture device a real 64 KiB `resource0` full of a pattern whose
* value depends on the byte's OFFSET — so an implementation that maps the
* wrong page reads different bytes rather than plausible ones.
*
* The `>> 8` term is not decoration and it is not arbitrary. The first
* version of this was `(i * 7 + 11) & 0xFF`, which repeats every 256 bytes;
* every page therefore held identical bytes, and a mutation that mapped
* offset 0 instead of the offset it was given PASSED. That is precisely the
* failure mode this whole phase is about — a wrong aim is a wrong ADDRESS,
* not an error — reproduced in a fixture. The pattern must be aperiodic
* across pages or the offset is not being tested at all.
*/
static void make_mappable_resource(const char *address)
{
unsigned char page[FIXTURE_RESOURCE_BYTES];
char path[512];
size_t index;
FILE *file;
for (index = 0u; index < sizeof(page); index++)
page[index] = (unsigned char)((index * 7u + 11u + (index >> 8)) & 0xFFu);
(void)snprintf(path, sizeof(path), "%s/%s/resource0", scratch_root, address);
file = fopen(path, "wb");
if (!file) {
(void)fprintf(stderr, "fixture: fopen %s failed\n", path);
exit(2);
}
if (fwrite(page, 1u, sizeof(page), file) != sizeof(page)) {
(void)fprintf(stderr, "fixture: short write to %s\n", path);
exit(2);
}
(void)fclose(file);
}
static void build_fixture(void)
{
(void)snprintf(scratch_root, sizeof(scratch_root), "/tmp/ft_tt_udrv_pci_fixture.%ld",
(long)getpid());
make_directory(scratch_root);
/* The real card: prefetchable 512 MiB BAR0, 1 MiB BAR2, no BAR4. */
make_device("0000:12:00.0", "0x1e52\n", "0xb140\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE ABSENT_LINE ABSENT_LINE);
/* A second Tenstorrent card at a LOWER address, to prove ordering. */
make_device("0000:03:00.0", "0x1e52\n", "0xb140\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE ABSENT_LINE ABSENT_LINE);
/* Right vendor, wrong part — must not be handed to a Blackhole caller. */
make_device("0000:04:00.0", "0x1e52\n", "0x401e\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE ABSENT_LINE ABSENT_LINE);
/* Somebody else's device entirely. */
make_device("0000:05:00.0", "0x10de\n", "0x102d\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE ABSENT_LINE ABSENT_LINE);
/* Non-prefetchable BAR2 and a one-byte BAR3, neither of which the real
* card can be made to produce. */
make_device("0000:06:00.0", "0x1e52\n", "0xb140\n",
REAL_BAR0_LINE ABSENT_LINE NONPREFETCH_BAR2_LINE ONE_BYTE_LINE ABSENT_LINE
ABSENT_LINE);
/* Malformed: only four BAR lines. */
make_device("0000:07:00.0", "0x1e52\n", "0xb140\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE);
/* Malformed: `resource` missing entirely. */
make_device("0000:08:00.0", "0x1e52\n", "0xb140\n", NULL);
/* Malformed: vendor is not a number. */
make_device("0000:09:00.0", "banana\n", "0xb140\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE ABSENT_LINE ABSENT_LINE);
/* A device with a REAL, mappable `resource0`, so stage 7 can exercise
* map_bar's plumbing. Its vendor is deliberately NOT Tenstorrent's — every
* enumeration count in stage 5 would otherwise move, and a fixture that
* makes an unrelated stage change is a fixture people stop trusting. */
make_device("0000:0a:00.0", "0x1e53\n", "0xb140\n",
REAL_BAR0_LINE ABSENT_LINE REAL_BAR2_LINE ABSENT_LINE ABSENT_LINE ABSENT_LINE);
make_mappable_resource("0000:0a:00.0");
}
static void remove_fixture(void)
{
char command[512];
/* The fixture is ours, freshly made under a pid-qualified name. */
(void)snprintf(command, sizeof(command), "rm -rf '%s'", scratch_root);
if (system(command) != 0)
(void)fprintf(stderr, "fixture: cleanup of %s failed\n", scratch_root);
}
static void device_path(const char *address, char *out_path, size_t path_bytes)
{
(void)snprintf(out_path, path_bytes, "%s/%s", scratch_root, address);
}
static void stage_the_real_card(void)
{
ft_tt_udrv_pci_device device;
char path[512];
ft_tt_test_step("Stage 1 — the real card's own sysfs numbers parse correctly");
device_path("0000:12:00.0", path, sizeof(path));
ft_tt_test_check("0000:12:00.0 reads",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_OK, "");
ft_tt_test_check_fmt("vendor is Tenstorrent",
device.vendor == FT_TT_UDRV_PCI_VENDOR_TENSTORRENT, "0x%04x",
device.vendor);
ft_tt_test_check_fmt("device is Blackhole", device.device == FT_TT_UDRV_PCI_DEVICE_BLACKHOLE,
"0x%04x", device.device);
ft_tt_test_check_fmt("address is the basename", strcmp(device.address, "0000:12:00.0") == 0,
"%s", device.address);
ft_tt_test_check_fmt("BAR0 is 512 MiB", device.bar[0].length == (512ull << 20), "%llu bytes",
(unsigned long long)device.bar[0].length);
ft_tt_test_check_fmt("BAR0 starts at 0x5e0000000", device.bar[0].start == 0x5e0000000ull,
"0x%llx", (unsigned long long)device.bar[0].start);
ft_tt_test_check("BAR0 is prefetchable — this is what makes resource0_wc exist",
device.bar[0].prefetchable, "flags bit 13");
ft_tt_test_check("BAR0 is a 64-bit memory BAR",
device.bar[0].memory && device.bar[0].is_64bit, "flags bits 9 and 20");
ft_tt_test_check_fmt("BAR1 is absent (the upper half of the 64-bit BAR0)",
!device.bar[1].present && device.bar[1].length == 0u, "len=%llu",
(unsigned long long)device.bar[1].length);
ft_tt_test_check_fmt("BAR2 is 1 MiB", device.bar[2].length == (1ull << 20), "%llu bytes",
(unsigned long long)device.bar[2].length);
ft_tt_test_check("BAR4 is absent — this board has no 4 GiB TLB windows",
!device.bar[4].present, "36-bit host, pcie_bar4_size 0");
}
static void stage_length_arithmetic(void)
{
ft_tt_udrv_pci_device device;
char path[512];
ft_tt_test_step("Stage 2 — length follows the KERNEL's rule, not a plausible one");
device_path("0000:06:00.0", path, sizeof(path));
ft_tt_test_check("0000:06:00.0 reads",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_OK, "");
/* pci_resource_len() is "0 when end is 0", NOT "0 when end <= start". A
* one-byte BAR has end == start != 0 and a length of 1; a rule written as
* `end > start` calls it absent and silently loses the aperture. */
ft_tt_test_check_fmt("a one-byte BAR has length 1, not 0", device.bar[3].length == 1u,
"%llu", (unsigned long long)device.bar[3].length);
ft_tt_test_check("a one-byte BAR is present", device.bar[3].present, "len 1");
ft_tt_test_check("a non-prefetchable BAR2 is reported as such",
!device.bar[2].prefetchable, "flags bit 13 clear");
ft_tt_test_check_fmt("...and still has its full length", device.bar[2].length == (1ull << 20),
"%llu", (unsigned long long)device.bar[2].length);
}
static void stage_resource_paths(void)
{
ft_tt_udrv_pci_device real;
ft_tt_udrv_pci_device odd;
char path[512];
char built[FT_TT_UDRV_PCI_PATH_MAX];
char expected[FT_TT_UDRV_PCI_PATH_MAX];
ft_tt_test_step("Stage 3 — resource paths, and the _wc file that only sometimes exists");
device_path("0000:12:00.0", path, sizeof(path));
(void)ft_tt_udrv_pci_read_device(path, &real);
device_path("0000:06:00.0", path, sizeof(path));
(void)ft_tt_udrv_pci_read_device(path, &odd);
(void)snprintf(expected, sizeof(expected), "%s/0000:12:00.0/resource0_wc", scratch_root);
ft_tt_test_check_fmt("BAR0 write-combined path",
ft_tt_udrv_pci_resource_path(&real, 0u, true, built, sizeof(built)) ==
FT_TT_UDRV_OK &&
strcmp(built, expected) == 0,
"%s", built);
(void)snprintf(expected, sizeof(expected), "%s/0000:12:00.0/resource2", scratch_root);
ft_tt_test_check_fmt("BAR2 uncached path",
ft_tt_udrv_pci_resource_path(&real, 2u, false, built, sizeof(built)) ==
FT_TT_UDRV_OK &&
strcmp(built, expected) == 0,
"%s", built);
ft_tt_test_check("a _wc path on a NON-prefetchable BAR is refused",
ft_tt_udrv_pci_resource_path(&odd, 2u, true, built, sizeof(built)) ==
FT_TT_UDRV_EINVAL,
"the PCI core never created that file");
ft_tt_test_check("a path for an ABSENT BAR is refused",
ft_tt_udrv_pci_resource_path(&real, 4u, false, built, sizeof(built)) ==
FT_TT_UDRV_EINVAL,
"BAR4 does not exist here");
ft_tt_test_check("a path for BAR 6 is refused",
ft_tt_udrv_pci_resource_path(&real, 6u, false, built, sizeof(built)) ==
FT_TT_UDRV_EINVAL,
"only 6 BARs");
ft_tt_test_check("a too-small output buffer is refused",
ft_tt_udrv_pci_resource_path(&real, 0u, true, built, 8u) == FT_TT_UDRV_EINVAL,
"would truncate");
}
static void stage_malformed_inputs(void)
{
ft_tt_udrv_pci_device device;
char path[512];
ft_tt_test_step("Stage 4 — malformed sysfs is refused, not silently zeroed");
device_path("0000:07:00.0", path, sizeof(path));
ft_tt_test_check("a `resource` with only 4 BAR lines is refused",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_ENODEV,
"a short file would read as two absent BARs");
device_path("0000:08:00.0", path, sizeof(path));
ft_tt_test_check("a missing `resource` file is refused",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_ENODEV, "");
device_path("0000:09:00.0", path, sizeof(path));
ft_tt_test_check("an unparseable `vendor` is refused",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_ENODEV,
"\"banana\"");
device_path("0000:99:00.0", path, sizeof(path));
ft_tt_test_check("a device directory that does not exist is refused",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_ENODEV, "");
ft_tt_test_check("a NULL directory is refused",
ft_tt_udrv_pci_read_device(NULL, &device) == FT_TT_UDRV_EINVAL, "");
ft_tt_test_check("a NULL output is refused",
ft_tt_udrv_pci_read_device("/tmp", NULL) == FT_TT_UDRV_EINVAL, "");
}
static void stage_enumeration(void)
{
ft_tt_udrv_pci_device found[4];
size_t count = 0u;
ft_tt_test_step("Stage 5 — enumeration matches on ids and orders by address");
ft_tt_test_check("scanning the fixture succeeds",
ft_tt_udrv_pci_find_devices(scratch_root, FT_TT_UDRV_PCI_VENDOR_TENSTORRENT,
FT_TT_UDRV_PCI_DEVICE_BLACKHOLE, found, 4u,
&count) == FT_TT_UDRV_OK,
"");
/* Three well-formed Blackholes: 03, 06, 12. The Wormhole at 04, the NVIDIA
* device at 05, and the three malformed ones must all be skipped. */
ft_tt_test_check_fmt("exactly 3 Blackholes matched", count == 3u, "%zu", count);
if (count >= 3u) {
ft_tt_test_check_fmt("device 0 is the lowest address",
strcmp(found[0].address, "0000:03:00.0") == 0, "%s",
found[0].address);
ft_tt_test_check_fmt("device 1 is next", strcmp(found[1].address, "0000:06:00.0") == 0,
"%s", found[1].address);
ft_tt_test_check_fmt("device 2 is the highest",
strcmp(found[2].address, "0000:12:00.0") == 0, "%s",
found[2].address);
}
count = 0u;
(void)ft_tt_udrv_pci_find_devices(scratch_root, FT_TT_UDRV_PCI_VENDOR_TENSTORRENT, 0xFFFFu,
found, 4u, &count);
ft_tt_test_check_fmt("\"any device id\" also matches the Wormhole", count == 4u, "%zu",
count);
count = 0u;
(void)ft_tt_udrv_pci_find_devices(scratch_root, 0x1234u, 0xFFFFu, found, 4u, &count);
ft_tt_test_check_fmt("an unknown vendor matches nothing", count == 0u, "%zu", count);
count = 99u;
ft_tt_test_check("a directory that does not exist is refused",
ft_tt_udrv_pci_find_devices("/tmp/ft_tt_udrv_no_such_dir", 0x1e52u, 0xFFFFu,
found, 4u, &count) == FT_TT_UDRV_ENODEV,
"");
count = 0u;
(void)ft_tt_udrv_pci_find_devices(scratch_root, FT_TT_UDRV_PCI_VENDOR_TENSTORRENT,
FT_TT_UDRV_PCI_DEVICE_BLACKHOLE, found, 1u, &count);
ft_tt_test_check_fmt("an undersized buffer still reports the true count", count == 3u, "%zu",
count);
}
static void stage_map_refusals(void)
{
ft_tt_udrv_pci_device real;
ft_tt_udrv_pci_device odd;
void *mapping = (void *)0x1;
char path[512];
long page = sysconf(_SC_PAGESIZE);
ft_tt_test_step("Stage 6 — map_bar refuses every bad range BEFORE it opens anything");
device_path("0000:12:00.0", path, sizeof(path));
(void)ft_tt_udrv_pci_read_device(path, &real);
device_path("0000:06:00.0", path, sizeof(path));
(void)ft_tt_udrv_pci_read_device(path, &odd);
ft_tt_test_check("an absent BAR is refused",
ft_tt_udrv_pci_map_bar(&real, 4u, false, false, 0u, 4096u, &mapping) ==
FT_TT_UDRV_EINVAL,
"BAR4 does not exist here");
ft_tt_test_check("...and the caller's pointer is cleared, not left stale", mapping == NULL,
"NULL");
ft_tt_test_check("BAR 6 is refused",
ft_tt_udrv_pci_map_bar(&real, 6u, false, false, 0u, 4096u, &mapping) ==
FT_TT_UDRV_EINVAL,
"only 6 BARs");
ft_tt_test_check("a zero length is refused",
ft_tt_udrv_pci_map_bar(&real, 0u, false, false, 0u, 0u, &mapping) ==
FT_TT_UDRV_EINVAL,
"mmap would fail anyway; refuse where the reason is visible");
ft_tt_test_check("a NULL output is refused",
ft_tt_udrv_pci_map_bar(&real, 0u, false, false, 0u, 4096u, NULL) ==
FT_TT_UDRV_EINVAL,
"");
/* A wrong aim is a wrong ADDRESS, not an error — so the range arithmetic
* is checked against the length the KERNEL published, and it is checked
* here rather than trusted. */
ft_tt_test_check("an offset past the end of the BAR is refused",
ft_tt_udrv_pci_map_bar(&real, 0u, false, false, 512ull << 20,
(size_t)page, &mapping) == FT_TT_UDRV_EINVAL,
"BAR0 is exactly 512 MiB, so 512 MiB is one past the end");
ft_tt_test_check("a length that runs off the end of the BAR is refused",
ft_tt_udrv_pci_map_bar(&real, 0u, false, false,
(512ull << 20) - (uint64_t)page, (size_t)page * 2u,
&mapping) == FT_TT_UDRV_EINVAL,
"last page + one more");
ft_tt_test_check("a misaligned offset is refused",
ft_tt_udrv_pci_map_bar(&real, 0u, false, false, 0x1FC00004ull, 4096u,
&mapping) == FT_TT_UDRV_EINVAL,
"mmap needs a page-aligned offset; the PCI core would map a whole page");
ft_tt_test_check("a write-combined map of a NON-prefetchable BAR is refused",
ft_tt_udrv_pci_map_bar(&odd, 2u, true, false, 0u, 4096u, &mapping) ==
FT_TT_UDRV_EINVAL,
"the PCI core never created resource2_wc for it");
/* The range check must not be what refuses a LEGAL range. This one is
* legal and fails at open(), which is the fixture having no resource0 —
* i.e. the two refusals are distinguishable. */
ft_tt_test_check("a legal range on a BAR with no resource file fails at open, not at the "
"range check",
ft_tt_udrv_pci_map_bar(&real, 2u, false, false, 0u, (size_t)page,
&mapping) == FT_TT_UDRV_EIO,
"EIO, not EINVAL");
ft_tt_test_check("unmapping NULL is a no-op success",
ft_tt_udrv_pci_unmap_bar(NULL, 4096u) == FT_TT_UDRV_OK, "");
ft_tt_test_check("unmapping with a zero length is refused",
ft_tt_udrv_pci_unmap_bar((void *)&real, 0u) == FT_TT_UDRV_EINVAL, "");
/* The alignment rule is the HOST's page size, which is 4096 on the card host
* and 16384 on the Mac this is written on. Nothing here may assume 4096. */
ft_tt_test_check_fmt("the host page size is a power of two", page > 0 && (page & (page - 1)) == 0,
"%ld", page);
}
/**
* Stage 7 — map a REAL file through map_bar.
*
* The fixture device at 0000:0a:00.0 has a genuine 64 KiB `resource0` full of
* a known pattern. mmap does not care that it is a regular file rather than a
* BAR, so this exercises the whole of map_bar's plumbing — path building,
* open, offset arithmetic, prot — off-hardware, and it is the ONLY part of
* that plumbing a fixture can reach.
*
* The child-process arm is the point of the `writable` parameter: the
* read-only rung's whole safety claim is that a stray store CANNOT reach a
* register, and "we were careful" is not that claim. A fork that stores into
* a PROT_READ mapping must die on a signal.
*/
static void stage_map_a_real_file(void)
{
ft_tt_udrv_pci_device device;
volatile unsigned char *bytes;
void *mapping = NULL;
char path[512];
size_t page = (size_t)sysconf(_SC_PAGESIZE);
pid_t child;
int status = 0;
ft_tt_test_step("Stage 7 — map_bar really maps, and a read-only map really is read-only");
device_path("0000:0a:00.0", path, sizeof(path));
ft_tt_test_check("the file-backed fixture device reads",
ft_tt_udrv_pci_read_device(path, &device) == FT_TT_UDRV_OK, "");
ft_tt_test_check("map_bar maps page 1 of it read-only",
ft_tt_udrv_pci_map_bar(&device, 0u, false, false, (uint64_t)page, page,
&mapping) == FT_TT_UDRV_OK &&
mapping != NULL,
"");
if (!mapping)
return;
bytes = mapping;
/* The fixture writes byte i of the file as (i * 7 + 11) & 0xFF, so an
* offset the implementation got wrong lands on a different value rather
* than on a plausible one. */
ft_tt_test_check_fmt("the first mapped byte is page 1's, not page 0's",
bytes[0] == (unsigned char)((page * 7u + 11u + (page >> 8)) & 0xFFu), "0x%02x",
bytes[0]);
ft_tt_test_check_fmt("the last mapped byte is page 1's last",
bytes[page - 1u] ==
(unsigned char)(((2u * page - 1u) * 7u + 11u + ((2u * page - 1u) >> 8)) & 0xFFu),
"0x%02x", bytes[page - 1u]);
child = fork();
if (child == 0) {
bytes[0] = 0xFFu;
_exit(0);
}
(void)waitpid(child, &status, 0);
ft_tt_test_check_fmt("a store into a read-only mapping kills the process",
WIFSIGNALED(status) && (WTERMSIG(status) == SIGSEGV ||
WTERMSIG(status) == SIGBUS),
"signalled=%d signal=%d", WIFSIGNALED(status),
WIFSIGNALED(status) ? WTERMSIG(status) : 0);
ft_tt_test_check("unmapping succeeds", ft_tt_udrv_pci_unmap_bar(mapping, page) ==
FT_TT_UDRV_OK,
"");
}
int main(void)
{
build_fixture();
stage_the_real_card();
stage_length_arithmetic();
stage_resource_paths();
stage_malformed_inputs();
stage_enumeration();
stage_map_refusals();
stage_map_a_real_file();
remove_fixture();
return ft_tt_test_finish();
}