628 lines
28 KiB
C
628 lines
28 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_runtime_udrv.c
|
|
* @brief Implementation of the userspace-driver half of the runtime (see
|
|
* ft_tt_runtime_udrv.h).
|
|
*
|
|
* @status ON SILICON 2026-08-17 with `tenstorrent` unloaded: a real
|
|
* `pantheon serve` answered a chat on the card over this path. Numbers and
|
|
* the priced 6.6% are in ft_tt_runtime_udrv.h's @status.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#ifndef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
|
|
#include "ft_tt_runtime_udrv.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "ft_tt_arc_telemetry.h"
|
|
#include "ft_tt_device.h"
|
|
#include "ft_tt_noc.h"
|
|
#include "ft_tt_udrv.h"
|
|
#include "ft_tt_udrv_arc_probe.h"
|
|
#include "ft_tt_udrv_claim.h"
|
|
#include "ft_tt_udrv_noc.h"
|
|
#include "ft_tt_udrv_noc_bank.h"
|
|
#include "ft_tt_udrv_pci_sysfs.h"
|
|
#include "ft_tt_udrv_reset.h"
|
|
|
|
/** How many Tenstorrent devices this will enumerate before giving up looking
|
|
* for `device_index`. A host with more cards than this is a refusal, not a
|
|
* silently truncated scan. */
|
|
#define RUNTIME_UDRV_MAX_DEVICES 8u
|
|
|
|
/** Two KINDS of window: one UC (run control + the ARC) and one WC (bulk) — the
|
|
* same split the tt-kmd path allocates, for the same two reasons
|
|
* (ft_tt_runtime.h's "WHY TWO WINDOWS").
|
|
*
|
|
* EACH KIND IS A BANK OF WINDOWS NOW, NOT ONE. A single window is a one-entry
|
|
* cache over an access stream that rotates across the resident cores, so it
|
|
* missed on nearly every descriptor and each miss cost an 8.92 us aim —
|
|
* 4,333 a token, 25.1% of the decode wall. `FT_TT_UDRV_WINDOWS` sizes both
|
|
* banks; 1 reproduces the single-window transport exactly. */
|
|
struct ft_tt_runtime_udrv {
|
|
ft_tt_udrv_pci_device pci;
|
|
const ft_tt_arch *arch;
|
|
|
|
ft_tt_udrv_noc_device device;
|
|
bool device_open;
|
|
|
|
ft_tt_udrv_claim_namespace windows;
|
|
|
|
ft_tt_udrv_noc_bank control; /**< UC — registers, mailbox, images, ARC */
|
|
bool control_open;
|
|
ft_tt_udrv_noc_bank bulk; /**< WC — DRAM and L1 bulk data */
|
|
bool bulk_open;
|
|
/** WC — a THIRD bank, owned by ONE other thread (`ft_tt_runtime_udrv_reader_access()`):
|
|
* PHASE W, the prefill's alpha/beta reads off the issuing thread. Windows are per-bank
|
|
* and the device's register mapping is read-only after open, so two threads each on
|
|
* their own bank never touch the same mutable state. */
|
|
ft_tt_udrv_noc_bank reader[FT_TT_UDRV_READER_BANKS_MAX];
|
|
uint32_t reader_banks;
|
|
|
|
ft_tt_udrv_arc_noc_context arc;
|
|
ft_tt_udrv_arc_transport arc_transport;
|
|
};
|
|
|
|
/* ================================================================
|
|
* Status translation
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* Map an FT_TT_UDRV_* code onto the FT_TT_* code the backend above the seam
|
|
* speaks.
|
|
*
|
|
* The two enums share their first six values by construction (ft_tt_udrv.h),
|
|
* but they DIVERGE at -6 and -7: FT_TT_EPERM/FT_TT_EUNAVAIL against
|
|
* FT_TT_UDRV_EAGAIN/FT_TT_UDRV_ENOTSUP. A memcpy of the number would therefore
|
|
* report "blocked by the Phase 1a NoC fence" for "the ring is full", which is
|
|
* the sort of wrong error message that costs an afternoon. Written out.
|
|
*/
|
|
static int udrv_status(int udrv_rc)
|
|
{
|
|
switch (udrv_rc) {
|
|
case FT_TT_UDRV_OK:
|
|
return FT_TT_OK;
|
|
case FT_TT_UDRV_ENOMEM:
|
|
return FT_TT_ENOMEM;
|
|
case FT_TT_UDRV_EINVAL:
|
|
return FT_TT_EINVAL;
|
|
case FT_TT_UDRV_EIO:
|
|
return FT_TT_EIO;
|
|
case FT_TT_UDRV_ENODEV:
|
|
return FT_TT_ENODEV;
|
|
case FT_TT_UDRV_EAGAIN:
|
|
case FT_TT_UDRV_ENOTSUP:
|
|
case FT_TT_UDRV_ERROR:
|
|
default:
|
|
return FT_TT_ERROR;
|
|
}
|
|
}
|
|
|
|
/* ================================================================
|
|
* The three accessors
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* The run-control vtable. `ft_tt_udrv_noc_access_write/read` already carry the
|
|
* `ft_tt_noc_access` signature at WORD32 width, so the whole of run control —
|
|
* soft reset, mailbox, go messages, ELF images — runs over them unchanged.
|
|
* Their `user` is the UC path.
|
|
*
|
|
* Nothing is wrapped here on purpose: a wrapper would be a place for the two
|
|
* transports to drift.
|
|
*/
|
|
|
|
/** BLOCK16 over the WC path, with the memory space's own bounds and alignment.
|
|
*
|
|
* The check is `ft_tt_noc_check_transfer()` — the SAME function ft_tt_noc.c's
|
|
* ft_tt_write_dram() calls — so a register address is refused here for exactly
|
|
* the reason and with exactly the message it is refused on the tt-kmd path.
|
|
* That structural separation (ft_tt_runtime.h) is a correctness property, not
|
|
* a convention, and it must not be re-derived per transport. */
|
|
static int bulk_transfer(void *user, ft_tt_memory_space space, bool is_read, uint16_t x,
|
|
uint16_t y, uint64_t address, void *host, size_t bytes)
|
|
{
|
|
ft_tt_udrv_noc_bank *bank = (ft_tt_udrv_noc_bank *)user;
|
|
int rc;
|
|
|
|
if (!bank || !host)
|
|
return FT_TT_EINVAL;
|
|
rc = ft_tt_noc_check_transfer(space, is_read, address, bytes);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
/* The store fence after the last slice lives inside ft_tt_udrv_noc_transfer()
|
|
* — a WC store may sit in the write-combining buffer, and correctness must
|
|
* not depend on an unrelated module's later UC access draining it. */
|
|
return udrv_status(ft_tt_udrv_noc_bank_transfer(bank, x, y, address, host, bytes, is_read,
|
|
FT_TT_NOC_WIDTH_BLOCK16));
|
|
}
|
|
|
|
static int bulk_dram_write(void *user, uint16_t x, uint16_t y, uint64_t addr, const void *src,
|
|
size_t bytes)
|
|
{
|
|
/* Const is dropped so one walk serves both directions; with is_read false
|
|
* nothing is ever written through this pointer. */
|
|
return bulk_transfer(user, FT_TT_MEM_SPACE_DRAM, false, x, y, addr,
|
|
(void *)(uintptr_t)src, bytes);
|
|
}
|
|
|
|
static int bulk_dram_read(void *user, uint16_t x, uint16_t y, uint64_t addr, void *dst,
|
|
size_t bytes)
|
|
{
|
|
return bulk_transfer(user, FT_TT_MEM_SPACE_DRAM, true, x, y, addr, dst, bytes);
|
|
}
|
|
|
|
static int bulk_l1_write(void *user, uint16_t x, uint16_t y, uint64_t addr, const void *src,
|
|
size_t bytes)
|
|
{
|
|
return bulk_transfer(user, FT_TT_MEM_SPACE_L1, false, x, y, addr, (void *)(uintptr_t)src,
|
|
bytes);
|
|
}
|
|
|
|
static int bulk_l1_read(void *user, uint16_t x, uint16_t y, uint64_t addr, void *dst, size_t bytes)
|
|
{
|
|
return bulk_transfer(user, FT_TT_MEM_SPACE_L1, true, x, y, addr, dst, bytes);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Discovery
|
|
* ================================================================ */
|
|
|
|
/** Find the `device_index`-th Tenstorrent card in sysfs and select its
|
|
* architecture table from the PCI ids alone. Any Tenstorrent device id is
|
|
* enumerated and the arch table decides — matching one hardcoded id here would
|
|
* hide a part we can name but do not support behind "no device". */
|
|
static int find_card(int device_index, ft_tt_runtime_udrv *udrv)
|
|
{
|
|
ft_tt_udrv_pci_device found[RUNTIME_UDRV_MAX_DEVICES];
|
|
size_t count = 0;
|
|
int rc;
|
|
|
|
if (device_index < 0) {
|
|
ft_tt_log_error("tt runtime udrv: device index %d is negative", device_index);
|
|
return FT_TT_EINVAL;
|
|
}
|
|
rc = ft_tt_udrv_pci_find_devices(NULL, FT_TT_UDRV_PCI_VENDOR_TENSTORRENT, 0xFFFFu, found,
|
|
RUNTIME_UDRV_MAX_DEVICES, &count);
|
|
if (rc != FT_TT_UDRV_OK) {
|
|
ft_tt_log_error("tt runtime udrv: cannot read %s", FT_TT_UDRV_PCI_DEFAULT_DEVICES_DIR);
|
|
return udrv_status(rc);
|
|
}
|
|
if (count == 0u) {
|
|
ft_tt_log_error("tt runtime udrv: no Tenstorrent device in %s — REFUSING. This path "
|
|
"does not fall back to anything",
|
|
FT_TT_UDRV_PCI_DEFAULT_DEVICES_DIR);
|
|
return FT_TT_ENODEV;
|
|
}
|
|
if ((size_t)device_index >= count || (size_t)device_index >= RUNTIME_UDRV_MAX_DEVICES) {
|
|
ft_tt_log_error("tt runtime udrv: asked for device %d and sysfs publishes %zu",
|
|
device_index, count);
|
|
return FT_TT_ENODEV;
|
|
}
|
|
|
|
udrv->pci = found[device_index];
|
|
udrv->arch = ft_tt_arch_select_by_pci_ids(udrv->pci.vendor, udrv->pci.device);
|
|
if (!udrv->arch)
|
|
return FT_TT_ENODEV;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/**
|
|
* Open one bank of windows, preferring HIGH indices.
|
|
*
|
|
* `ALLOCATE_TLB` allocates from the bottom of the range and our flock cannot
|
|
* see its bitmap, so while tt-kmd is loaded a bottom-up claim here and a
|
|
* bottom-up allocation there converge on the same window and neither party is
|
|
* told. Walking down from the top does not make that safe — nothing can, short
|
|
* of the module being gone — but it removes the collision that would otherwise
|
|
* happen on the FIRST window every time. The window the driver keeps for itself
|
|
* (`arch->tlb.kernel_window_index`, 201) is skipped outright: claiming it out
|
|
* from under a loaded tt-kmd corrupts the driver's own telemetry reads.
|
|
*/
|
|
static int open_window_bank(ft_tt_runtime_udrv *udrv, bool write_combine, const char *name,
|
|
uint32_t windows, ft_tt_udrv_noc_bank *out_bank)
|
|
{
|
|
int rc = ft_tt_udrv_noc_bank_open(&udrv->device, &udrv->windows,
|
|
udrv->arch->tlb.kernel_window_index, write_combine,
|
|
windows, name, out_bank);
|
|
|
|
if (rc == FT_TT_UDRV_OK)
|
|
return FT_TT_OK;
|
|
/* EAGAIN is contention — somebody holds every window, which is the normal
|
|
* reason to fail. ANYTHING ELSE is the claim FILE not being usable, and
|
|
* reporting that as contention is how a permission problem in /tmp reads as
|
|
* "the card is busy". It cost this gate a round: a root run had left the
|
|
* tree 0755 root-owned and every non-root job then printed "all 202 windows
|
|
* are claimed by a live process" on a completely idle card. */
|
|
if (rc == FT_TT_UDRV_EAGAIN)
|
|
ft_tt_log_error("tt runtime udrv: every one of the %u windows is claimed by a live "
|
|
"process",
|
|
udrv->windows.count);
|
|
else
|
|
ft_tt_log_error("tt runtime udrv: the %s window bank could not be opened (rc=%d) — this "
|
|
"is the flock file under /tmp, not the card. Check the ownership of "
|
|
"/tmp/ft_tt_udrv.d: a root run that created it under a umask leaves it "
|
|
"unusable by every later non-root job",
|
|
name, rc);
|
|
return FT_TT_ERROR;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Bring-up
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* MRRS — step 1 of `blackhole_init_hardware()`, and the only step of it that is
|
|
* not an ARC message.
|
|
*
|
|
* It reads 4096 on this host whether or not the module is loaded, because tt-kmd
|
|
* once set it and `pci_disable_device()` does not clear DEVCTL. **THE TARGET
|
|
* BOARD HAS NOBODY TO SET IT.** So the bring-up sets it, exactly as the vendor
|
|
* probe does.
|
|
*
|
|
* BEST EFFORT ON PURPOSE, and this is a decision rather than laziness: a
|
|
* config-space write is privileged, so a NON-ROOT run — which is the whole point
|
|
* of the udev rule that lets an ordinary user drive the BARs — cannot do it.
|
|
* Refusing the card over it would make the packaging goal and the probe-parity
|
|
* goal contradict each other. It is loud instead: the failure names itself, and
|
|
* the value it is leaving behind is one that was READ, not assumed.
|
|
*/
|
|
static void set_max_read_request_size(ft_tt_runtime_udrv *udrv)
|
|
{
|
|
ft_tt_udrv_max_read_request_report report;
|
|
int rc = ft_tt_udrv_config_max_read_request_set(udrv->pci.sysfs_path,
|
|
FT_TT_UDRV_MRRS_BYTES_DEFAULT, &report);
|
|
|
|
if (rc != FT_TT_UDRV_OK) {
|
|
/* `bytes_after` is only meaningful once DEVCTL has been read; on the
|
|
* capability-walk failure it is still zero and printing it would state a
|
|
* value nobody observed. */
|
|
if (report.capability_offset != 0u)
|
|
ft_tt_log_error("tt runtime udrv: MRRS NOT SET — %s (errno %d). The card keeps %u "
|
|
"bytes (DEVCTL code %u). A config-space write is privileged; on a "
|
|
"box where tt-kmd never ran nothing else sets this",
|
|
report.failure_stage ? report.failure_stage : "unknown",
|
|
report.failure_errno, report.bytes_after, report.code_after);
|
|
else
|
|
ft_tt_log_error("tt runtime udrv: MRRS NOT SET and its CURRENT VALUE IS UNKNOWN — "
|
|
"%s (errno %d). Non-root cannot read or write it; a udev rule with "
|
|
"`setpci CAP_EXP+8.w=5000:7000` is the root-side answer",
|
|
report.failure_stage ? report.failure_stage : "unknown",
|
|
report.failure_errno);
|
|
return;
|
|
}
|
|
ft_tt_log_error("tt runtime udrv: MRRS %u -> %u bytes (%s)", report.bytes_before,
|
|
report.bytes_after,
|
|
report.written ? "WRITTEN by us" : "already correct, nothing written");
|
|
}
|
|
|
|
/**
|
|
* Memory decode — `pci_enable_device()`'s job, and nothing on a cold host does it.
|
|
*
|
|
* MEASURED ON a second card host, 2026-08-18, on a board that has never had tt-kmd:
|
|
* PCI_COMMAND reads 0x0100, the BAR0 mmap SUCCEEDS, and all 1024 words of it
|
|
* read 0xFFFFFFFF. The ARC bring-up then failed at its first NoC read. On the
|
|
* p150a this never showed, because `pci_disable_device()` leaves the bit set and
|
|
* every reading there was taken after tt-kmd had run at least once.
|
|
*
|
|
* BEST EFFORT, exactly as MRRS is and for the same reason — the write is
|
|
* privileged and the udev rule is the non-root answer. It is loud rather than
|
|
* fatal because the failure it would cause is caught one step later with a
|
|
* better message: BAR0 answering all-ones is what `ft_tt_udrv_arc_locate_queue()`
|
|
* rejects by name.
|
|
*/
|
|
static void enable_memory_decode(ft_tt_runtime_udrv *udrv)
|
|
{
|
|
ft_tt_udrv_memory_decode_report report;
|
|
int rc = ft_tt_udrv_config_memory_decode_enable(udrv->pci.sysfs_path, &report);
|
|
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_log_error("tt runtime udrv: MEMORY DECODE NOT ENABLED — %s (errno %d). "
|
|
"PCI_COMMAND reads 0x%04x. With bit 1 clear the BAR still maps and "
|
|
"every word of it reads 0xFFFFFFFF; a config-space write is "
|
|
"privileged, so this needs root or the udev rule's "
|
|
"`setpci COMMAND=0002:0002`",
|
|
report.failure_stage ? report.failure_stage : "unknown",
|
|
report.failure_errno, report.command_after);
|
|
return;
|
|
}
|
|
ft_tt_log_error("tt runtime udrv: memory decode PCI_COMMAND 0x%04x -> 0x%04x (%s)",
|
|
report.command_before, report.command_after,
|
|
report.written ? "WRITTEN by us" : "already enabled, nothing written");
|
|
}
|
|
|
|
/** The ARC read32 the shared telemetry walk runs on. `ft_tt_udrv_arc_noc_read32`
|
|
* is tt-kmd's own `noc_read32`: any tile-local address on the ARC tile, which
|
|
* is what the reset-scratch pointers and the CSM both need. */
|
|
static int arc_read32(void *user, uint32_t address, uint32_t *out_value)
|
|
{
|
|
ft_tt_runtime_udrv *udrv = (ft_tt_runtime_udrv *)user;
|
|
|
|
return udrv_status(ft_tt_udrv_arc_noc_read32(&udrv->arc, address, out_value));
|
|
}
|
|
|
|
int ft_tt_runtime_udrv_read_telemetry(ft_tt_runtime_udrv *udrv, const uint16_t *tags,
|
|
unsigned count, uint32_t *out_values, bool *out_found)
|
|
{
|
|
if (!udrv || !udrv->arch)
|
|
return FT_TT_EINVAL;
|
|
return ft_tt_arc_telemetry_read_tags_via(udrv->arch, arc_read32, udrv, tags, count,
|
|
out_values, out_found);
|
|
}
|
|
|
|
/** ASIC_STATE0, SET_WDT_TIMEOUT and POWER_SETTING — `blackhole_init_hardware()`
|
|
* plus the open-time power request, which is what tt_cdev_open() was doing for
|
|
* us and what nothing else on the machine will do. */
|
|
static int bring_the_card_up(ft_tt_runtime_udrv *udrv)
|
|
{
|
|
ft_tt_udrv_arc_probe_report report;
|
|
ft_tt_noc_coord arc_coord;
|
|
int rc = ft_tt_coord_arc(udrv->arch, FT_TT_COORD_TRANSLATED, &arc_coord);
|
|
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
|
|
/* THE ARC SHARES THE CONTROL BANK'S FIRST WINDOW rather than claiming a
|
|
* third. It aims that path through the same function the bank's own
|
|
* transfers do, so the bank's lookup — which reads the paths' OWN aim state
|
|
* and keeps no second copy of it — sees the ARC's aim immediately and never
|
|
* routes a transfer at a window the ARC has moved. */
|
|
rc = ft_tt_udrv_arc_transport_init(&udrv->arc, &udrv->control.path[0], (uint16_t)arc_coord.x,
|
|
(uint16_t)arc_coord.y, &udrv->arc_transport);
|
|
if (rc != FT_TT_UDRV_OK)
|
|
return udrv_status(rc);
|
|
|
|
memset(&report, 0, sizeof(report));
|
|
rc = ft_tt_udrv_arc_probe_hardware(&udrv->arc_transport, FT_TT_UDRV_ARC_WATCHDOG_DEFAULT_MS,
|
|
ft_tt_ai_clock_wants_maximum(), &report);
|
|
if (rc != FT_TT_UDRV_OK) {
|
|
/* The STAGE leads, because the numbers after it are only meaningful once
|
|
* something was actually read. This message used to open with
|
|
* "boot status 0x00000000 after 0 polls" on a card whose BARs were not
|
|
* even decoding, which reads as a sleeping ARC and sends the reader after
|
|
* the firmware instead of after PCI_COMMAND. */
|
|
ft_tt_log_error("tt runtime udrv: ARC bring-up failed — %s (locate boot status 0x%08x "
|
|
"after %u polls; ASIC_STATE0 rc=%d, SET_WDT_TIMEOUT rc=%d, "
|
|
"POWER_SETTING rc=%d)",
|
|
report.locate.failure_stage ? report.locate.failure_stage
|
|
: "no stage reported",
|
|
report.locate.boot_status, report.locate.boot_status_polls,
|
|
report.asic_state0.rc, report.set_wdt_timeout.rc, report.power_setting.rc);
|
|
return udrv_status(rc);
|
|
}
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Open and close
|
|
* ================================================================ */
|
|
|
|
/** The body of the open, so every failure lands on one cleanup path. */
|
|
static int runtime_udrv_open_locked(int device_index, ft_tt_runtime_udrv *udrv,
|
|
ft_tt_grid_info *out_grid)
|
|
{
|
|
ft_tt_grid_info grid;
|
|
int rc = find_card(device_index, udrv);
|
|
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
|
|
/* Both before the BAR is mapped, which is the order tt-kmd's probe uses.
|
|
* Memory decode goes FIRST: MRRS is probe parity, but without decode the
|
|
* mapping still succeeds and every register reads 0xFFFFFFFF. */
|
|
enable_memory_decode(udrv);
|
|
set_max_read_request_size(udrv);
|
|
|
|
rc = udrv_status(ft_tt_udrv_noc_device_open(&udrv->pci, true, &udrv->device));
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_log_error("tt runtime udrv: cannot map BAR0 of %s — the resource files are "
|
|
"root-owned 0600, so this needs root or a udev rule",
|
|
udrv->pci.address);
|
|
return rc;
|
|
}
|
|
udrv->device_open = true;
|
|
|
|
rc = udrv_status(ft_tt_udrv_claim_namespace_init(&udrv->windows, NULL, udrv->pci.address,
|
|
"tlb2m", FT_TT_UDRV_TLB_2M_WINDOW_COUNT));
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
|
|
rc = open_window_bank(udrv, false, "register", ft_tt_udrv_noc_bank_windows_selected(),
|
|
&udrv->control);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
udrv->control_open = true;
|
|
|
|
/* The bring-up runs on the UC path BEFORE the grid is read, because the
|
|
* telemetry table is what the bring-up's own effect is measured on and
|
|
* because nothing else on this machine sends POWER_SETTING. */
|
|
rc = bring_the_card_up(udrv);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
|
|
rc = ft_tt_arc_telemetry_read_grid_via(udrv->arch, arc_read32, udrv, &grid);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_log_error("tt runtime udrv: telemetry read failed: %s", ft_tt_error_string(rc));
|
|
return rc;
|
|
}
|
|
/* A topology we cannot map is a topology we must not probe: a wrong NoC
|
|
* endpoint hangs the chip (spec §7.3). Refuse before any bulk window. */
|
|
rc = ft_tt_coord_check_grid(&grid);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_log_error("tt runtime udrv: grid is not a topology we can map; refusing the card");
|
|
return rc;
|
|
}
|
|
|
|
rc = open_window_bank(udrv, true, "bulk", ft_tt_udrv_noc_bank_windows_selected(),
|
|
&udrv->bulk);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
udrv->bulk_open = true;
|
|
/* The reader banks are OPTIONAL: a process that cannot claim one runs its reads on the
|
|
* bulk bank, as before. `FT_TT_UDRV_READER_WINDOWS=0` skips them; `FT_TT_UDRV_READER_BANKS`
|
|
* (default 2) is how many threads may each own one. */
|
|
{
|
|
const char *env = getenv("FT_TT_UDRV_READER_WINDOWS");
|
|
const char *banks_env = getenv("FT_TT_UDRV_READER_BANKS");
|
|
const long want = env && *env ? strtol(env, NULL, 10) : 2L;
|
|
long banks = banks_env && *banks_env ? strtol(banks_env, NULL, 10) : 2L;
|
|
|
|
if (banks > (long)FT_TT_UDRV_READER_BANKS_MAX)
|
|
banks = (long)FT_TT_UDRV_READER_BANKS_MAX;
|
|
while (want > 0 && want <= (long)FT_TT_UDRV_NOC_BANK_MAX_WINDOWS &&
|
|
(long)udrv->reader_banks < banks &&
|
|
open_window_bank(udrv, true, "reader", (uint32_t)want,
|
|
&udrv->reader[udrv->reader_banks]) == FT_TT_OK)
|
|
udrv->reader_banks++;
|
|
}
|
|
/* THE COUNTS ARE ECHOED, NOT ASSUMED. `FT_TT_UDRV_WINDOWS` is the knob the
|
|
* aim ladder is scored on, and a knob that reads as armed and is not is
|
|
* this project's most expensive instrument defect — so what was ASKED FOR
|
|
* and what was ACHIEVED are both on the line, and the first window index of
|
|
* each bank names itself the way the single-window line used to. */
|
|
ft_tt_log_error("tt runtime udrv: %s, NO VENDOR DRIVER — %u register windows from %u (UC) "
|
|
"and %u bulk windows from %u (WC), %u asked for, index %u reserved for the "
|
|
"vendor driver, %u Tensix in %u columns",
|
|
udrv->pci.address, udrv->control.window_count,
|
|
udrv->control.path[0].window_id, udrv->bulk.window_count,
|
|
udrv->bulk.path[0].window_id, ft_tt_udrv_noc_bank_windows_selected(),
|
|
udrv->arch->tlb.kernel_window_index, grid.tensix_core_count,
|
|
grid.tensix_column_count);
|
|
*out_grid = grid;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int ft_tt_runtime_udrv_open(int device_index, ft_tt_runtime_udrv **out_udrv,
|
|
ft_tt_grid_info *out_grid, ft_tt_noc_access *out_noc,
|
|
ft_tt_noc_access *out_bulk_dram, ft_tt_noc_access *out_bulk_l1)
|
|
{
|
|
ft_tt_runtime_udrv *udrv = NULL;
|
|
int rc;
|
|
|
|
if (!out_udrv || !out_grid || !out_noc || !out_bulk_dram || !out_bulk_l1)
|
|
return FT_TT_EINVAL;
|
|
|
|
udrv = calloc(1, sizeof(*udrv));
|
|
if (!udrv)
|
|
return FT_TT_ENOMEM;
|
|
|
|
rc = runtime_udrv_open_locked(device_index, udrv, out_grid);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_runtime_udrv_close(udrv);
|
|
return rc;
|
|
}
|
|
|
|
/* Every `user` points into the HEAP handle, not into any caller's struct,
|
|
* so the accessors survive the caller copying its runtime by value. That is
|
|
* the one thing the tt-kmd path has to re-bind by hand after the copy. */
|
|
out_noc->write = ft_tt_udrv_noc_bank_access_write;
|
|
out_noc->read = ft_tt_udrv_noc_bank_access_read;
|
|
out_noc->user = &udrv->control;
|
|
out_bulk_dram->write = bulk_dram_write;
|
|
out_bulk_dram->read = bulk_dram_read;
|
|
out_bulk_dram->user = &udrv->bulk;
|
|
out_bulk_l1->write = bulk_l1_write;
|
|
out_bulk_l1->read = bulk_l1_read;
|
|
out_bulk_l1->user = &udrv->bulk;
|
|
|
|
*out_udrv = udrv;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
void ft_tt_runtime_udrv_close(ft_tt_runtime_udrv *udrv)
|
|
{
|
|
if (!udrv)
|
|
return;
|
|
/* The histogram goes out BEFORE the banks are torn down, because closing a
|
|
* bank zeroes it. It is a no-op unless FT_TT_UDRV_AIM_HISTOGRAM was set. */
|
|
ft_tt_udrv_noc_bank_histogram_log(&udrv->control);
|
|
ft_tt_udrv_noc_bank_histogram_log(&udrv->bulk);
|
|
/* Same rule for the reuse ladder, and a no-op unless FT_TT_UDRV_AIM_REUSE
|
|
* was set. It is what sizes the bank, so it prints its own self-check. */
|
|
ft_tt_udrv_noc_bank_reuse_log(&udrv->control);
|
|
ft_tt_udrv_noc_bank_reuse_log(&udrv->bulk);
|
|
while (udrv->reader_banks > 0u)
|
|
(void)ft_tt_udrv_noc_bank_close(&udrv->reader[--udrv->reader_banks]);
|
|
if (udrv->bulk_open)
|
|
(void)ft_tt_udrv_noc_bank_close(&udrv->bulk);
|
|
if (udrv->control_open)
|
|
(void)ft_tt_udrv_noc_bank_close(&udrv->control);
|
|
if (udrv->device_open)
|
|
(void)ft_tt_udrv_noc_device_close(&udrv->device);
|
|
free(udrv);
|
|
}
|
|
|
|
int ft_tt_runtime_udrv_reader_access(ft_tt_runtime_udrv *udrv, uint32_t index,
|
|
ft_tt_noc_access *out_dram)
|
|
{
|
|
if (!udrv || !out_dram)
|
|
return FT_TT_EINVAL;
|
|
if (index >= udrv->reader_banks)
|
|
return FT_TT_EUNAVAIL;
|
|
out_dram->write = bulk_dram_write;
|
|
out_dram->read = bulk_dram_read;
|
|
out_dram->user = &udrv->reader[index];
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int ft_tt_runtime_udrv_reader_l1_access(ft_tt_runtime_udrv *udrv, uint32_t index,
|
|
ft_tt_noc_access *out_l1)
|
|
{
|
|
if (!udrv || !out_l1)
|
|
return FT_TT_EINVAL;
|
|
if (index >= udrv->reader_banks)
|
|
return FT_TT_EUNAVAIL;
|
|
out_l1->write = bulk_l1_write;
|
|
out_l1->read = bulk_l1_read;
|
|
out_l1->user = &udrv->reader[index];
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
const char *ft_tt_runtime_udrv_pci_address(const ft_tt_runtime_udrv *udrv)
|
|
{
|
|
return udrv ? udrv->pci.address : "";
|
|
}
|
|
|
|
int ft_tt_runtime_udrv_window_stats_read(const ft_tt_runtime_udrv *udrv,
|
|
ft_tt_runtime_window_stats *out)
|
|
{
|
|
if (!out)
|
|
return FT_TT_EINVAL;
|
|
memset(out, 0, sizeof(*out));
|
|
if (!udrv)
|
|
return FT_TT_EINVAL;
|
|
|
|
out->register_read = ft_tt_udrv_noc_bank_bytes_read(&udrv->control);
|
|
out->register_written = ft_tt_udrv_noc_bank_bytes_written(&udrv->control);
|
|
out->register_aims = ft_tt_udrv_noc_bank_aim_count(&udrv->control);
|
|
out->bulk_read = ft_tt_udrv_noc_bank_bytes_read(&udrv->bulk);
|
|
out->bulk_written = ft_tt_udrv_noc_bank_bytes_written(&udrv->bulk);
|
|
out->bulk_aims = ft_tt_udrv_noc_bank_aim_count(&udrv->bulk);
|
|
out->aim_failures = ft_tt_udrv_noc_bank_aim_failures(&udrv->control) +
|
|
ft_tt_udrv_noc_bank_aim_failures(&udrv->bulk);
|
|
out->aim_seconds = ft_tt_udrv_noc_bank_aim_seconds(&udrv->control) +
|
|
ft_tt_udrv_noc_bank_aim_seconds(&udrv->bulk);
|
|
out->aim_verify_seconds = ft_tt_udrv_noc_bank_aim_verify_seconds(&udrv->control) +
|
|
ft_tt_udrv_noc_bank_aim_verify_seconds(&udrv->bulk);
|
|
out->register_windows = udrv->control.window_count;
|
|
out->bulk_windows = udrv->bulk.window_count;
|
|
out->register_hits = udrv->control.hits;
|
|
out->register_misses = udrv->control.misses;
|
|
out->register_evictions = udrv->control.evictions;
|
|
out->bulk_hits = udrv->bulk.hits;
|
|
out->bulk_misses = udrv->bulk.misses;
|
|
out->bulk_evictions = udrv->bulk.evictions;
|
|
out->misaims_injected = udrv->control.misaims_injected + udrv->bulk.misaims_injected;
|
|
out->audit_failures = udrv->control.audit_failures + udrv->bulk.audit_failures;
|
|
out->register_endpoints = ft_tt_udrv_noc_bank_endpoint_count(&udrv->control);
|
|
out->bulk_endpoints = ft_tt_udrv_noc_bank_endpoint_count(&udrv->bulk);
|
|
return FT_TT_OK;
|
|
}
|