671 lines
25 KiB
C
671 lines
25 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_device_reset.c
|
|
* @brief Implementation of the device-level (`RESET_DEVICE`) and bus-level (PCIe
|
|
* Secondary Bus Reset) recovery rungs.
|
|
*
|
|
* @status Runs on the p150a test card.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "ft_tt_device_reset.h"
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "ft_tt_device.h"
|
|
#include "ft_tt_kmd_abi.h"
|
|
|
|
/** PCI configuration header: the command register, and its memory-decode bit. */
|
|
#define PCI_COMMAND_OFFSET 0x04u
|
|
#define PCI_COMMAND_MEMORY 0x0002u
|
|
|
|
/** PCI-to-PCI bridge configuration header, offset of the Bridge Control register. */
|
|
#define PCI_BRIDGE_CONTROL_OFFSET 0x3Eu
|
|
|
|
/** Bridge Control bit 6: assert reset on the secondary bus (PCIe PERST# to its children). */
|
|
#define PCI_BRIDGE_CONTROL_SECONDARY_BUS_RESET 0x0040u
|
|
|
|
/** How long reset is held asserted. The PCIe base spec demands at least 1 ms; 100 ms is
|
|
* what every in-tree implementation of this uses and there is nothing to win by cutting
|
|
* it on a recovery path. */
|
|
#define SBR_ASSERT_MILLISECONDS 100u
|
|
|
|
/** How long the card is given to re-train and boot its ARC from SPI before the rescan. */
|
|
#define SBR_SETTLE_MILLISECONDS 2000u
|
|
|
|
/** How long the device is given to reappear in sysfs after the rescan. */
|
|
#define SBR_REAPPEAR_MILLISECONDS 10000u
|
|
|
|
#define PCI_CONFIG_SAVE_BYTES 4096u
|
|
|
|
/** How many times POST_RESET is retried, and how long between attempts. */
|
|
#define POST_RESET_ATTEMPTS 10u
|
|
#define POST_RESET_INTERVAL_MILLISECONDS 1000u
|
|
|
|
#define SYSFS_PCI_DEVICES "/sys/bus/pci/devices"
|
|
|
|
/** Tenstorrent's PCI vendor id. The card must be this or the SBR is refused. */
|
|
#define FT_TT_PCI_VENDOR_ID 0x1e52u
|
|
|
|
/** tt-kmd's `struct tenstorrent_reset_device`. Declared here rather than
|
|
* in the vendored ioctl header on purpose: that header carries payload structs only for
|
|
* the ioctls Foundry issues, so that nothing else CAN be issued by accident. This module
|
|
* is the one place allowed to issue this one. */
|
|
struct ft_tt_reset_device_ioctl {
|
|
struct {
|
|
uint32_t output_size_bytes;
|
|
uint32_t flags;
|
|
} in;
|
|
struct {
|
|
uint32_t output_size_bytes;
|
|
uint32_t result;
|
|
} out;
|
|
};
|
|
|
|
/** Record which sysfs step failed and hand back FT_TT_EIO. */
|
|
static int sysfs_step_failed(const char **out_stage, int *out_errno, const char *stage)
|
|
{
|
|
*out_stage = stage;
|
|
*out_errno = errno;
|
|
return FT_TT_EIO;
|
|
}
|
|
|
|
static void sleep_milliseconds(unsigned milliseconds)
|
|
{
|
|
struct timespec request;
|
|
|
|
request.tv_sec = (time_t)(milliseconds / 1000u);
|
|
request.tv_nsec = (long)(milliseconds % 1000u) * 1000000L;
|
|
while (nanosleep(&request, &request) != 0 && errno == EINTR)
|
|
;
|
|
}
|
|
|
|
/* ================================================================
|
|
* RESET_DEVICE — rungs 3 and 4
|
|
* ================================================================ */
|
|
|
|
/** Issue one flag on an already-open fd, recording the driver's own result word. */
|
|
static int issue_reset_flag(int fd, ft_tt_device_reset_flag flag, uint32_t *out_result,
|
|
int *out_errno)
|
|
{
|
|
struct ft_tt_reset_device_ioctl request;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.in.output_size_bytes = (uint32_t)sizeof(request.out);
|
|
request.in.flags = (uint32_t)flag;
|
|
|
|
*out_errno = 0;
|
|
if (ioctl(fd, FT_TT_KMD_IOCTL_RESET_DEVICE, &request) != 0) {
|
|
*out_errno = errno;
|
|
*out_result = 0;
|
|
return FT_TT_EIO;
|
|
}
|
|
*out_result = request.out.result;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int ft_tt_device_reset_issue(const char *chardev_path, ft_tt_device_reset_flag flag,
|
|
ft_tt_device_reset_report *out)
|
|
{
|
|
struct stat node;
|
|
int fd;
|
|
|
|
if (!chardev_path || !out)
|
|
return FT_TT_EINVAL;
|
|
memset(out, 0, sizeof(*out));
|
|
out->flag = flag;
|
|
|
|
fd = open(chardev_path, O_RDWR | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
ft_tt_log_error("reset: cannot open %s: %s", chardev_path, strerror(errno));
|
|
return FT_TT_EIO;
|
|
}
|
|
|
|
(void)issue_reset_flag(fd, flag, &out->driver_result, &out->ioctl_errno);
|
|
|
|
/* POST_RESET is the completion half — it restores PCI state and re-runs init_hardware. It has to be RETRIED: its first check is that the reset marker
|
|
* (the PCI parity-error-response bit) has been cleared BY the reset, and a card still
|
|
* in reset answers 0xffff to that read, which looks like the marker is still set.
|
|
* Issued with no delay it returns FAILED on a reset that worked (measured). */
|
|
out->post_reset_issued = true;
|
|
for (out->post_reset_attempts = 1u; out->post_reset_attempts <= POST_RESET_ATTEMPTS;
|
|
out->post_reset_attempts++) {
|
|
sleep_milliseconds(POST_RESET_INTERVAL_MILLISECONDS);
|
|
(void)issue_reset_flag(fd, FT_TT_RESET_DEVICE_POST_RESET, &out->post_reset_driver_result,
|
|
&out->post_reset_errno);
|
|
if (out->post_reset_errno == 0 && out->post_reset_driver_result == 0u)
|
|
break;
|
|
}
|
|
close(fd);
|
|
|
|
out->chardev_present_after = stat(chardev_path, &node) == 0;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
void ft_tt_device_reset_print(const ft_tt_device_reset_report *report)
|
|
{
|
|
if (!report)
|
|
return;
|
|
printf("RESET_DEVICE flag %u: ", (unsigned)report->flag);
|
|
if (report->ioctl_errno != 0)
|
|
printf("ioctl failed, errno %d (%s)\n", report->ioctl_errno,
|
|
strerror(report->ioctl_errno));
|
|
else
|
|
printf("driver result=%u (%s)\n", report->driver_result,
|
|
report->driver_result == 0u ? "success" : "FAILED");
|
|
if (report->post_reset_issued) {
|
|
printf(" POST_RESET (%u attempt%s): ", report->post_reset_attempts,
|
|
report->post_reset_attempts == 1u ? "" : "s");
|
|
if (report->post_reset_errno != 0)
|
|
printf("ioctl failed, errno %d (%s)\n", report->post_reset_errno,
|
|
strerror(report->post_reset_errno));
|
|
else
|
|
printf("driver result=%u (%s)\n", report->post_reset_driver_result,
|
|
report->post_reset_driver_result == 0u ? "success" : "FAILED");
|
|
}
|
|
printf(" chardev present afterwards: %s\n", report->chardev_present_after ? "yes" : "no");
|
|
}
|
|
|
|
/* ================================================================
|
|
* sysfs helpers
|
|
* ================================================================ */
|
|
|
|
/** Read a whole small sysfs file into `buffer` as a NUL-terminated string. */
|
|
static int read_sysfs_text(const char *path, char *buffer, size_t bytes)
|
|
{
|
|
ssize_t got;
|
|
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
|
|
|
if (fd < 0)
|
|
return FT_TT_EIO;
|
|
got = read(fd, buffer, bytes - 1u);
|
|
close(fd);
|
|
if (got < 0)
|
|
return FT_TT_EIO;
|
|
buffer[got] = '\0';
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/** Write one byte of text to a sysfs control file ("1" to remove, rescan, ...). */
|
|
static int write_sysfs_text(const char *path, const char *value)
|
|
{
|
|
ssize_t written;
|
|
int fd = open(path, O_WRONLY | O_CLOEXEC);
|
|
|
|
if (fd < 0)
|
|
return FT_TT_EIO;
|
|
written = write(fd, value, strlen(value));
|
|
close(fd);
|
|
return written == (ssize_t)strlen(value) ? FT_TT_OK : FT_TT_EIO;
|
|
}
|
|
|
|
/** A 16-bit read of one device's PCI configuration space through sysfs. */
|
|
static int read_config_word(const char *bdf, unsigned offset, uint16_t *out_value)
|
|
{
|
|
char path[256];
|
|
uint8_t bytes[2];
|
|
ssize_t got;
|
|
int fd;
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s/config", bdf) < 0)
|
|
return FT_TT_EIO;
|
|
fd = open(path, O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0)
|
|
return FT_TT_EIO;
|
|
got = pread(fd, bytes, sizeof(bytes), (off_t)offset);
|
|
close(fd);
|
|
if (got != (ssize_t)sizeof(bytes))
|
|
return FT_TT_EIO;
|
|
*out_value = (uint16_t)((uint16_t)bytes[0] | ((uint16_t)bytes[1] << 8));
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/** A 16-bit write into one device's PCI configuration space through sysfs. Root only. */
|
|
static int write_config_word(const char *bdf, unsigned offset, uint16_t value)
|
|
{
|
|
char path[256];
|
|
uint8_t bytes[2];
|
|
ssize_t written;
|
|
int fd;
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s/config", bdf) < 0)
|
|
return FT_TT_EIO;
|
|
bytes[0] = (uint8_t)(value & 0xFFu);
|
|
bytes[1] = (uint8_t)((value >> 8) & 0xFFu);
|
|
fd = open(path, O_WRONLY | O_CLOEXEC);
|
|
if (fd < 0)
|
|
return FT_TT_EIO;
|
|
written = pwrite(fd, bytes, sizeof(bytes), (off_t)offset);
|
|
close(fd);
|
|
return written == (ssize_t)sizeof(bytes) ? FT_TT_OK : FT_TT_EIO;
|
|
}
|
|
|
|
/** Copy a device's configuration space to a file, for the evidence a reset would erase. */
|
|
static int save_config_space(const char *bdf, const char *directory, const char *label)
|
|
{
|
|
char source_path[256];
|
|
char destination_path[512];
|
|
uint8_t buffer[PCI_CONFIG_SAVE_BYTES];
|
|
ssize_t got;
|
|
ssize_t written;
|
|
int source;
|
|
int destination;
|
|
|
|
if (snprintf(source_path, sizeof(source_path), SYSFS_PCI_DEVICES "/%s/config", bdf) < 0)
|
|
return FT_TT_EIO;
|
|
if (snprintf(destination_path, sizeof(destination_path), "%s/%s_%s_config.bin", directory,
|
|
label, bdf) < 0)
|
|
return FT_TT_EIO;
|
|
|
|
source = open(source_path, O_RDONLY | O_CLOEXEC);
|
|
if (source < 0)
|
|
return FT_TT_EIO;
|
|
got = read(source, buffer, sizeof(buffer));
|
|
close(source);
|
|
if (got <= 0)
|
|
return FT_TT_EIO;
|
|
|
|
destination = open(destination_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
|
|
if (destination < 0)
|
|
return FT_TT_EIO;
|
|
written = write(destination, buffer, (size_t)got);
|
|
close(destination);
|
|
return written == got ? FT_TT_OK : FT_TT_EIO;
|
|
}
|
|
|
|
/** Length of BAR0 as sysfs reports it, or 0 if it is unassigned/absent. */
|
|
static uint64_t read_bar0_length(const char *bdf)
|
|
{
|
|
char path[256];
|
|
char text[512];
|
|
unsigned long long start = 0;
|
|
unsigned long long end = 0;
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s/resource", bdf) < 0)
|
|
return 0;
|
|
if (read_sysfs_text(path, text, sizeof(text)) != FT_TT_OK)
|
|
return 0;
|
|
if (sscanf(text, "%llx %llx", &start, &end) != 2)
|
|
return 0;
|
|
return end > start ? (uint64_t)(end - start + 1ull) : 0u;
|
|
}
|
|
|
|
/** Characters in "dddd:bb:dd.f", without the terminator. */
|
|
#define PCI_ADDRESS_LENGTH 12u
|
|
|
|
/** True when `name` looks like "dddd:bb:dd.f". */
|
|
static bool looks_like_pci_address(const char *name)
|
|
{
|
|
return strlen(name) == PCI_ADDRESS_LENGTH && name[4] == ':' && name[7] == ':' &&
|
|
name[10] == '.';
|
|
}
|
|
|
|
/** Copy an address the caller has already checked with looks_like_pci_address(). */
|
|
static void copy_pci_address(char destination[FT_TT_PCI_ADDRESS_MAX], const char *name)
|
|
{
|
|
memcpy(destination, name, PCI_ADDRESS_LENGTH);
|
|
destination[PCI_ADDRESS_LENGTH] = '\0';
|
|
}
|
|
|
|
/* Defined with the rung-5 code below; used by both halves. */
|
|
static bool wait_for_device(const char *bdf, unsigned timeout_milliseconds);
|
|
static bool driver_is_bound(const char *bdf);
|
|
|
|
/** Is the card's memory decode enabled? A reset clears the command register, and a card
|
|
* with `Mem-` answers every BAR access as if it were not there. */
|
|
static bool memory_decode_enabled(const char *bdf)
|
|
{
|
|
uint16_t command = 0;
|
|
|
|
if (read_config_word(bdf, PCI_COMMAND_OFFSET, &command) != FT_TT_OK)
|
|
return false;
|
|
return (command & PCI_COMMAND_MEMORY) != 0u;
|
|
}
|
|
|
|
int ft_tt_pcie_reenumerate_is_permitted(const char *card_bdf)
|
|
{
|
|
char control_path[256];
|
|
|
|
if (!card_bdf)
|
|
return FT_TT_EINVAL;
|
|
/* W_OK on the sysfs control node, which is root-only. access() rather than geteuid()
|
|
* so a capability or an ACL that grants it without uid 0 is honoured, and so the
|
|
* answer is about the file we will actually write. */
|
|
if (snprintf(control_path, sizeof(control_path), SYSFS_PCI_DEVICES "/%s/remove", card_bdf) < 0)
|
|
return FT_TT_EIO;
|
|
if (access(control_path, W_OK) != 0)
|
|
return FT_TT_EPERM;
|
|
if (access("/sys/bus/pci/rescan", W_OK) != 0)
|
|
return FT_TT_EPERM;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int ft_tt_pcie_reenumerate(const char *card_bdf, ft_tt_pcie_reenumeration_report *out)
|
|
{
|
|
ft_tt_pcie_topology topology;
|
|
char control_path[256];
|
|
struct stat node;
|
|
|
|
if (!card_bdf || !out)
|
|
return FT_TT_EINVAL;
|
|
memset(out, 0, sizeof(*out));
|
|
|
|
/* The bridge is derived from the card, never named: a rescan is harmless, but the
|
|
* path it is written to should still come from the device we are holding. */
|
|
if (ft_tt_pcie_topology_resolve(card_bdf, &topology) != FT_TT_OK || topology.bridge[0] == 0)
|
|
return sysfs_step_failed(&out->failure_stage, &out->failure_errno,
|
|
"resolving the card's bridge");
|
|
|
|
if (snprintf(control_path, sizeof(control_path), SYSFS_PCI_DEVICES "/%s/remove", card_bdf) < 0)
|
|
return FT_TT_EIO;
|
|
if (write_sysfs_text(control_path, "1") != FT_TT_OK)
|
|
return sysfs_step_failed(&out->failure_stage, &out->failure_errno,
|
|
"removing the card from the PCI bus");
|
|
out->removed = true;
|
|
sleep_milliseconds(2000u);
|
|
|
|
if (snprintf(control_path, sizeof(control_path), SYSFS_PCI_DEVICES "/%s/rescan",
|
|
topology.bridge) < 0)
|
|
return FT_TT_EIO;
|
|
if (write_sysfs_text(control_path, "1") != FT_TT_OK)
|
|
return sysfs_step_failed(&out->failure_stage, &out->failure_errno,
|
|
"rescanning the bridge");
|
|
out->rescanned = true;
|
|
|
|
out->device_returned = wait_for_device(card_bdf, SBR_REAPPEAR_MILLISECONDS);
|
|
if (!out->device_returned)
|
|
return sysfs_step_failed(&out->failure_stage, &out->failure_errno,
|
|
"the card did not reappear after the rescan");
|
|
sleep_milliseconds(2000u);
|
|
|
|
out->bar0_length_after = read_bar0_length(card_bdf);
|
|
out->memory_decode_enabled = memory_decode_enabled(card_bdf);
|
|
out->driver_bound = driver_is_bound(card_bdf);
|
|
out->chardev_present = stat("/dev/tenstorrent/0", &node) == 0;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
void ft_tt_pcie_reenumeration_print(const ft_tt_pcie_reenumeration_report *report)
|
|
{
|
|
if (!report)
|
|
return;
|
|
printf("re-enumeration: removed=%s rescanned=%s returned=%s\n",
|
|
report->removed ? "yes" : "no", report->rescanned ? "yes" : "no",
|
|
report->device_returned ? "yes" : "no");
|
|
printf(" BAR0 %llu bytes, memory decode %s, tt-kmd %s, /dev/tenstorrent/0 %s\n",
|
|
(unsigned long long)report->bar0_length_after,
|
|
report->memory_decode_enabled ? "ENABLED" : "DISABLED",
|
|
report->driver_bound ? "bound" : "NOT BOUND",
|
|
report->chardev_present ? "present" : "MISSING");
|
|
if (report->failure_stage)
|
|
printf(" FAILED at: %s (errno %d)\n", report->failure_stage, report->failure_errno);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Topology — the refusal
|
|
* ================================================================ */
|
|
|
|
int ft_tt_pcie_topology_resolve(const char *card_bdf, ft_tt_pcie_topology *out)
|
|
{
|
|
char device_link[256];
|
|
char resolved[PATH_MAX];
|
|
char *last_separator;
|
|
char vendor_text[32];
|
|
char path[256];
|
|
DIR *bridge_directory;
|
|
struct dirent *entry;
|
|
unsigned long parsed;
|
|
|
|
if (!card_bdf || !out)
|
|
return FT_TT_EINVAL;
|
|
memset(out, 0, sizeof(*out));
|
|
if (!looks_like_pci_address(card_bdf))
|
|
return FT_TT_EINVAL;
|
|
copy_pci_address(out->card, card_bdf);
|
|
|
|
/* The bridge is DERIVED from the card's own device path. It is never named from
|
|
* outside this module, so a typo cannot aim this at a bridge with K80s under it. */
|
|
if (snprintf(device_link, sizeof(device_link), SYSFS_PCI_DEVICES "/%s", card_bdf) < 0)
|
|
return FT_TT_EIO;
|
|
if (!realpath(device_link, resolved)) {
|
|
out->refusal = "the card has no sysfs device path";
|
|
return FT_TT_EIO;
|
|
}
|
|
last_separator = strrchr(resolved, '/');
|
|
if (!last_separator) {
|
|
out->refusal = "the card's device path has no parent";
|
|
return FT_TT_EIO;
|
|
}
|
|
*last_separator = '\0';
|
|
last_separator = strrchr(resolved, '/');
|
|
if (!last_separator || !looks_like_pci_address(last_separator + 1)) {
|
|
out->refusal = "the card's parent is not a PCI bridge (it is a host bridge or root)";
|
|
return FT_TT_OK;
|
|
}
|
|
copy_pci_address(out->bridge, last_separator + 1);
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s/vendor", card_bdf) < 0)
|
|
return FT_TT_EIO;
|
|
if (read_sysfs_text(path, vendor_text, sizeof(vendor_text)) == FT_TT_OK) {
|
|
parsed = strtoul(vendor_text, NULL, 0);
|
|
out->card_vendor_id = (uint16_t)parsed;
|
|
}
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s/device", card_bdf) < 0)
|
|
return FT_TT_EIO;
|
|
if (read_sysfs_text(path, vendor_text, sizeof(vendor_text)) == FT_TT_OK) {
|
|
parsed = strtoul(vendor_text, NULL, 0);
|
|
out->card_device_id = (uint16_t)parsed;
|
|
}
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s", out->bridge) < 0)
|
|
return FT_TT_EIO;
|
|
bridge_directory = opendir(path);
|
|
if (!bridge_directory) {
|
|
out->refusal = "the bridge has no sysfs directory";
|
|
return FT_TT_EIO;
|
|
}
|
|
while ((entry = readdir(bridge_directory)) != NULL) {
|
|
if (!looks_like_pci_address(entry->d_name))
|
|
continue;
|
|
out->child_count++;
|
|
if (strcmp(entry->d_name, card_bdf) != 0 && out->unexpected_child[0] == '\0')
|
|
copy_pci_address(out->unexpected_child, entry->d_name);
|
|
}
|
|
closedir(bridge_directory);
|
|
|
|
if (out->child_count != 1u)
|
|
out->refusal = "the bridge does not have exactly one child device";
|
|
else if (out->unexpected_child[0] != '\0')
|
|
out->refusal = "the bridge's only child is not the card";
|
|
else if (out->card_vendor_id != FT_TT_PCI_VENDOR_ID)
|
|
out->refusal = "the card's PCI vendor id is not Tenstorrent's";
|
|
else
|
|
out->safe = true;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
void ft_tt_pcie_topology_print(const ft_tt_pcie_topology *topology)
|
|
{
|
|
if (!topology)
|
|
return;
|
|
printf("PCIe topology: card %s [%04x:%04x] behind bridge %s, %u child device%s\n",
|
|
topology->card, topology->card_vendor_id, topology->card_device_id,
|
|
topology->bridge[0] ? topology->bridge : "(none)", topology->child_count,
|
|
topology->child_count == 1u ? "" : "s");
|
|
if (topology->unexpected_child[0] != '\0')
|
|
printf(" also behind that bridge: %s\n", topology->unexpected_child);
|
|
if (topology->safe)
|
|
printf(" a secondary bus reset here touches the card and nothing else\n");
|
|
else
|
|
printf(" REFUSED: %s\n", topology->refusal ? topology->refusal : "unknown");
|
|
}
|
|
|
|
/* ================================================================
|
|
* Rung 5 — the secondary bus reset
|
|
* ================================================================ */
|
|
|
|
/** Wait for the device directory to reappear after a rescan. */
|
|
static bool wait_for_device(const char *bdf, unsigned timeout_milliseconds)
|
|
{
|
|
char path[256];
|
|
unsigned waited = 0;
|
|
struct stat node;
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s", bdf) < 0)
|
|
return false;
|
|
while (waited < timeout_milliseconds) {
|
|
if (stat(path, &node) == 0)
|
|
return true;
|
|
sleep_milliseconds(100u);
|
|
waited += 100u;
|
|
}
|
|
return stat(path, &node) == 0;
|
|
}
|
|
|
|
/** Is a driver bound to the device now? */
|
|
static bool driver_is_bound(const char *bdf)
|
|
{
|
|
char path[256];
|
|
char target[256];
|
|
ssize_t length;
|
|
|
|
if (snprintf(path, sizeof(path), SYSFS_PCI_DEVICES "/%s/driver", bdf) < 0)
|
|
return false;
|
|
length = readlink(path, target, sizeof(target) - 1u);
|
|
if (length <= 0)
|
|
return false;
|
|
target[length] = '\0';
|
|
return strstr(target, "tenstorrent") != NULL;
|
|
}
|
|
|
|
/** Identity words (vendor, device) survive re-enumeration; BAR assignment need not. */
|
|
static bool identity_matches(const char *bdf, const ft_tt_pcie_topology *expected)
|
|
{
|
|
ft_tt_pcie_topology now;
|
|
|
|
if (ft_tt_pcie_topology_resolve(bdf, &now) != FT_TT_OK)
|
|
return false;
|
|
return now.card_vendor_id == expected->card_vendor_id &&
|
|
now.card_device_id == expected->card_device_id;
|
|
}
|
|
|
|
static int sbr_fail(ft_tt_pcie_sbr_report *out, const char *stage, int error_number)
|
|
{
|
|
out->failure_stage = stage;
|
|
out->failure_errno = error_number;
|
|
return FT_TT_EIO;
|
|
}
|
|
|
|
int ft_tt_pcie_secondary_bus_reset(const char *card_bdf, const char *save_directory,
|
|
ft_tt_pcie_sbr_report *out)
|
|
{
|
|
char control_path[256];
|
|
struct stat node;
|
|
uint16_t control;
|
|
int rc;
|
|
|
|
if (!card_bdf || !out)
|
|
return FT_TT_EINVAL;
|
|
memset(out, 0, sizeof(*out));
|
|
|
|
rc = ft_tt_pcie_topology_resolve(card_bdf, &out->topology);
|
|
if (rc != FT_TT_OK)
|
|
return sbr_fail(out, "reading the PCIe topology", 0);
|
|
if (!out->topology.safe) {
|
|
out->failure_stage = "topology check refused the reset";
|
|
return FT_TT_EPERM;
|
|
}
|
|
|
|
out->bar0_length_before = read_bar0_length(card_bdf);
|
|
|
|
if (save_directory) {
|
|
if (save_config_space(card_bdf, save_directory, "card") != FT_TT_OK)
|
|
return sbr_fail(out, "saving the card's config space", errno);
|
|
if (save_config_space(out->topology.bridge, save_directory, "bridge") != FT_TT_OK)
|
|
return sbr_fail(out, "saving the bridge's config space", errno);
|
|
out->config_saved = true;
|
|
}
|
|
|
|
if (read_config_word(out->topology.bridge, PCI_BRIDGE_CONTROL_OFFSET,
|
|
&out->bridge_control_before) != FT_TT_OK)
|
|
return sbr_fail(out, "reading the bridge control register", errno);
|
|
|
|
/* Remove the card first: the reset is about to invalidate everything tt-kmd knows
|
|
* about it, and a driver still bound through that is a use-after-reset. */
|
|
if (snprintf(control_path, sizeof(control_path), SYSFS_PCI_DEVICES "/%s/remove", card_bdf) < 0)
|
|
return sbr_fail(out, "building the remove path", 0);
|
|
if (write_sysfs_text(control_path, "1") != FT_TT_OK)
|
|
return sbr_fail(out, "removing the card from the PCI bus", errno);
|
|
out->device_removed = true;
|
|
sleep_milliseconds(200u);
|
|
|
|
control = (uint16_t)(out->bridge_control_before | PCI_BRIDGE_CONTROL_SECONDARY_BUS_RESET);
|
|
if (write_config_word(out->topology.bridge, PCI_BRIDGE_CONTROL_OFFSET, control) != FT_TT_OK)
|
|
return sbr_fail(out, "asserting secondary bus reset", errno);
|
|
(void)read_config_word(out->topology.bridge, PCI_BRIDGE_CONTROL_OFFSET,
|
|
&out->bridge_control_asserted);
|
|
sleep_milliseconds(SBR_ASSERT_MILLISECONDS);
|
|
|
|
/* Restore the register exactly as it was — this is the one register we modified. */
|
|
if (write_config_word(out->topology.bridge, PCI_BRIDGE_CONTROL_OFFSET,
|
|
out->bridge_control_before) != FT_TT_OK)
|
|
return sbr_fail(out, "deasserting secondary bus reset", errno);
|
|
(void)read_config_word(out->topology.bridge, PCI_BRIDGE_CONTROL_OFFSET,
|
|
&out->bridge_control_after);
|
|
sleep_milliseconds(SBR_SETTLE_MILLISECONDS);
|
|
|
|
if (snprintf(control_path, sizeof(control_path), SYSFS_PCI_DEVICES "/%s/rescan",
|
|
out->topology.bridge) < 0)
|
|
return sbr_fail(out, "building the rescan path", 0);
|
|
if (write_sysfs_text(control_path, "1") != FT_TT_OK)
|
|
return sbr_fail(out, "rescanning the bridge", errno);
|
|
out->rescanned = true;
|
|
|
|
out->device_returned = wait_for_device(card_bdf, SBR_REAPPEAR_MILLISECONDS);
|
|
if (!out->device_returned)
|
|
return sbr_fail(out, "the card did not reappear after the rescan", 0);
|
|
|
|
out->bar0_length_after = read_bar0_length(card_bdf);
|
|
out->driver_bound = driver_is_bound(card_bdf);
|
|
out->config_matches_saved = identity_matches(card_bdf, &out->topology);
|
|
out->chardev_present = stat("/dev/tenstorrent/0", &node) == 0;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
void ft_tt_pcie_sbr_print(const ft_tt_pcie_sbr_report *report)
|
|
{
|
|
if (!report)
|
|
return;
|
|
|
|
ft_tt_pcie_topology_print(&report->topology);
|
|
printf(" config space saved: %s\n", report->config_saved ? "yes" : "no");
|
|
printf(" card removed: %s\n", report->device_removed ? "yes" : "no");
|
|
printf(" bridge control: 0x%04x -> 0x%04x (asserted) -> 0x%04x\n",
|
|
report->bridge_control_before, report->bridge_control_asserted,
|
|
report->bridge_control_after);
|
|
printf(" bridge rescanned: %s\n", report->rescanned ? "yes" : "no");
|
|
printf(" card returned: %s\n", report->device_returned ? "yes" : "no");
|
|
printf(" BAR0 length: %llu -> %llu bytes\n",
|
|
(unsigned long long)report->bar0_length_before,
|
|
(unsigned long long)report->bar0_length_after);
|
|
printf(" identity unchanged: %s\n", report->config_matches_saved ? "yes" : "no");
|
|
printf(" tt-kmd bound: %s\n", report->driver_bound ? "yes" : "no");
|
|
printf(" /dev/tenstorrent/0: %s\n", report->chardev_present ? "present" : "MISSING");
|
|
if (report->failure_stage)
|
|
printf(" FAILED at: %s (errno %d)\n", report->failure_stage, report->failure_errno);
|
|
}
|