346 lines
15 KiB
C
346 lines
15 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_device.h
|
|
* @brief Foundry's Tenstorrent Blackhole device surface: open/close a card,
|
|
* report what it is, expose the TLB register file, and answer capacity
|
|
* questions. Mirrors the shape of `foundry/cuda/fc_cuda.h` so a Foundry
|
|
* caller sees the same lifecycle on either backend.
|
|
*
|
|
* Module prefix: ft_tt_ (foundry tenstorrent). Pure C99, libc only — no C++,
|
|
* no tt-metal, no tt-umd, nothing to dlopen. The kernel boundary is the
|
|
* `ft_tt_kmd.h`; everything here is Foundry code (spec §3.4).
|
|
*
|
|
* @status Phase 1a. Device open/close, enumeration, driver version, BAR
|
|
* enumeration and the TLB register-file mapping are implemented and have run
|
|
* against the p150a at 12:00.0. NO NoC access of any kind exists in this
|
|
* phase: nothing configures a TLB window and nothing reads or writes device
|
|
* memory. `ft_tt_mem_info` therefore reports FT_TT_EUNAVAIL until Phase 1b
|
|
* fills the grid from ARC telemetry.
|
|
* @issues None
|
|
* @todo None
|
|
*
|
|
* Sources for the constants below (TLB facts: BlackholeA0/PCIExpressTile/HostToDeviceTLBs.md;
|
|
* the rest as noted per constant):
|
|
* SRC5 = foundry_blackhole_backend_spec.md
|
|
*/
|
|
|
|
#ifndef FT_TT_DEVICE_H
|
|
#define FT_TT_DEVICE_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include "ft_tt_arch.h"
|
|
#include "ft_tt_kmd.h"
|
|
|
|
/* ================================================================
|
|
* Status codes
|
|
* ================================================================ */
|
|
|
|
enum {
|
|
FT_TT_OK = 0,
|
|
FT_TT_ERROR = -1, /**< unclassified failure */
|
|
FT_TT_ENOMEM = -2, /**< host allocation failed */
|
|
FT_TT_EINVAL = -3, /**< bad argument */
|
|
FT_TT_EIO = -4, /**< ioctl or mmap failed; errno was logged */
|
|
FT_TT_ENODEV = -5, /**< no such device, or not a Blackhole */
|
|
FT_TT_EPERM = -6, /**< blocked by the Phase 1a NoC fence */
|
|
FT_TT_EUNAVAIL = -7, /**< the answer is not known yet in this phase */
|
|
FT_TT_ENOTFOUND = -8 /**< the named thing is not here — a program name the embedded
|
|
* blob does not carry. Distinct from ENODEV on purpose: the
|
|
* card is fine, the ARTIFACT does not contain what was asked
|
|
* for, and the two want different answers from a caller */
|
|
};
|
|
|
|
/** Human-readable name for an FT_TT_* code. Never NULL. */
|
|
const char *ft_tt_error_string(int code);
|
|
|
|
/** Log to stderr with the `[ft_tt]` tag. Used across the backend so a failure
|
|
* can be reconstructed from the log alone (no debugger on the card host). */
|
|
void ft_tt_log_error(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
|
|
|
/**
|
|
* Whether this process wants the MAXIMUM AI clock — true unless
|
|
* `FT_TT_AI_CLK=min`.
|
|
*
|
|
* ONE definition of what that variable means, because there are now two ways to
|
|
* ask the firmware for it: `SET_POWER_STATE` through tt-kmd, and our own
|
|
* `POWER_SETTING` message on the userspace-driver path. A second `getenv` in
|
|
* the second place is how two arms of an A/B end up at different clocks with
|
|
* nothing in the log saying so — and every figure recorded before 2026-08-15
|
|
* was taken at 800 MHz for exactly that class of reason.
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: no (getenv)
|
|
*/
|
|
bool ft_tt_ai_clock_wants_maximum(void);
|
|
|
|
/* ================================================================
|
|
* Host-side device map — the parts of BAR0 Foundry uses
|
|
* ================================================================ */
|
|
|
|
/** TLB configuration register file, at this offset into BAR0 (BlackholeA0/PCIExpressTile/HostToDeviceTLBs.md).
|
|
* Writing three 32-bit words here re-aims a window with no ioctl — the
|
|
* load-bearing performance fact of the whole backend (docs/tt_driver_notes.md
|
|
* §4). Phase 1a only READS it. */
|
|
#define FT_TT_TLB_REGS_OFFSET 0x1FC00000u
|
|
#define FT_TT_TLB_REGS_LEN 0x00001000u /* covers every TLB register */
|
|
|
|
/** Bytes of register per window, same for 2 MiB and 4 GiB kinds. */
|
|
#define FT_TT_TLB_REG_STRIDE 12u
|
|
|
|
/** Architectural window counts. The *usable* counts come from the device at
|
|
* runtime (ft_tt_tlb_counts) — these are for cross-checking, not for sizing. */
|
|
#define FT_TT_TLB_2M_WINDOW_COUNT 202u /* per HostToDeviceTLBs.md */
|
|
#define FT_TT_TLB_4G_WINDOW_COUNT 8u /* none exist without BAR4 */
|
|
#define FT_TT_TLB_TOTAL_WINDOW_COUNT (FT_TT_TLB_2M_WINDOW_COUNT + FT_TT_TLB_4G_WINDOW_COUNT)
|
|
|
|
/** The driver claims the topmost 2 MiB window for its own NoC access at init
|
|
* (the driver's `KERNEL_TLB_INDEX`, set at driver init), leaving 201
|
|
* for userspace. It is never handed out by ALLOCATE_TLB. */
|
|
#define FT_TT_TLB_KERNEL_WINDOW_INDEX (FT_TT_TLB_2M_WINDOW_COUNT - 1u)
|
|
|
|
/** The first 32 windows also carry a strided-multicast register, in a second
|
|
* bank after all the per-window registers. */
|
|
#define FT_TT_TLB_STRIDED_WINDOW_COUNT 32u
|
|
#define FT_TT_TLB_STRIDED_REGS_OFFSET (FT_TT_TLB_TOTAL_WINDOW_COUNT * FT_TT_TLB_REG_STRIDE)
|
|
|
|
/** Tensix rows per column, from tt-kmd's view of the part (SRC5 §2.1).
|
|
*
|
|
* **This is a CROSS-CHECK, not a source.** The live row count comes from the
|
|
* selected architecture table (`ft_tt_coord_tensix_row_count()`), and
|
|
* `ft_tt_arch_check_headers()` refuses to run if this macro disagrees with it.
|
|
* Never size a group or a loop from this — see the derive-from-live rule in
|
|
* the plan's Phase 5. */
|
|
#define FT_TT_TENSIX_ROWS_PER_COLUMN 10u
|
|
|
|
/** ARC tile, NoC0 coordinates. The kernel driver and the vendor host library both place the ARC tile at (8, 0). NOT a probed value — the kernel reads
|
|
* telemetry from exactly this endpoint at every probe.
|
|
*
|
|
* Deliberately kept as a SECOND copy of the arch table's `fixed.arc`, sourced
|
|
* from tt-kmd rather than tt-umd. `ft_tt_arch_check_headers()` compares the
|
|
* two, so the duplication is an enforced agreement between two independent
|
|
* upstream files rather than a drift hazard. */
|
|
#define FT_TT_ARC_NOC0_X 8u
|
|
#define FT_TT_ARC_NOC0_Y 0u
|
|
|
|
/* ================================================================
|
|
* Types
|
|
* ================================================================ */
|
|
|
|
/** Per-card context: one open `/dev/tenstorrent/N`, its cached attributes, and
|
|
* the mapped TLB register file. Opaque — see ft_tt_device.c. One instance per
|
|
* card, mirroring one fc_cuda_ctx per K80 die. */
|
|
typedef struct ft_tt_ctx ft_tt_ctx;
|
|
|
|
/** What the card is. Filled from GET_DEVICE_INFO + GET_DRIVER_INFO. */
|
|
typedef struct ft_tt_device_info {
|
|
uint64_t chip_arch; /**< enum ft_tt_kmd_arch */
|
|
uint16_t pci_vendor_id;
|
|
uint16_t pci_device_id;
|
|
uint16_t pci_subsystem_vendor_id;
|
|
uint16_t pci_subsystem_id;
|
|
uint16_t pci_domain;
|
|
uint8_t pci_bus;
|
|
uint8_t pci_device;
|
|
uint8_t pci_function;
|
|
uint8_t driver_version_major;
|
|
uint8_t driver_version_minor;
|
|
uint8_t driver_version_patch;
|
|
uint32_t driver_api_version;
|
|
} ft_tt_device_info;
|
|
|
|
/** TLB windows this card actually has, derived from live BAR lengths. */
|
|
typedef struct ft_tt_tlb_counts {
|
|
uint32_t window_2m_total; /**< as the driver counts them */
|
|
uint32_t window_2m_available; /**< minus the driver's own window */
|
|
uint32_t window_4g_total; /**< 0 on a board with no BAR4 */
|
|
} ft_tt_tlb_counts;
|
|
|
|
/**
|
|
* Grid topology, the only safe source of which is ARC telemetry (spec §3.4;
|
|
* GET_HARVESTING is a no-op and probing hangs the chip). `valid` is false
|
|
* until ft_tt_arc_telemetry_read_grid() has succeeded, which cannot happen in
|
|
* Phase 1a — see ft_tt_arc_telemetry.h.
|
|
*
|
|
* `arch` is what makes the telemetry masks MEAN anything: the same
|
|
* `tensix_enabled_col` word denotes different columns on different parts, and
|
|
* on a row-harvesting part it denotes rows. Carrying it in the grid rather
|
|
* than passing it beside every call is what keeps `ft_tt_coord`'s signatures
|
|
* unchanged and makes "a grid with no architecture" a refusal instead of a
|
|
* default. It is installed by ft_tt_grid_set(); a NULL is refused by
|
|
* ft_tt_coord_check_grid().
|
|
*/
|
|
typedef struct ft_tt_grid_info {
|
|
bool valid;
|
|
const ft_tt_arch *arch;
|
|
uint32_t tensix_enabled_col; /**< bitmask, one bit per Tensix column */
|
|
uint32_t enabled_eth;
|
|
uint32_t enabled_gddr;
|
|
uint32_t enabled_l2cpu;
|
|
uint32_t noc_translation_enabled;
|
|
uint32_t tensix_column_count; /**< popcount(tensix_enabled_col) */
|
|
uint32_t tensix_core_count; /**< columns x FT_TT_TENSIX_ROWS_PER_COLUMN */
|
|
uint32_t dram_bank_count; /**< popcount(enabled_gddr) */
|
|
} ft_tt_grid_info;
|
|
|
|
/* ================================================================
|
|
* Lifecycle
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* Open `/dev/tenstorrent/<device_index>`, enumerate it, and map the TLB
|
|
* register file. Refuses anything that is not a Blackhole.
|
|
*
|
|
* Nothing on the device is written and no NoC transaction is issued. The BAR0
|
|
* NoC window aperture is deliberately NOT mapped — see ft_tt_tlb_registers().
|
|
*
|
|
* @param device_index 0 for the first card
|
|
* @return owned context, or NULL (reason logged). Free with
|
|
* ft_tt_ctx_free().
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: no
|
|
*/
|
|
ft_tt_ctx *ft_tt_init_device(int device_index);
|
|
|
|
/**
|
|
* Unmap the register file and close the device. Safe on NULL. Any TLB windows
|
|
* still allocated through this context are released by the driver when the fd
|
|
* closes, but callers should free them explicitly (ft_tt_tlb_window_free).
|
|
*
|
|
* Thread-safe: no (frees `ctx`) Reentrant: no Signal-safe: no
|
|
*/
|
|
void ft_tt_ctx_free(ft_tt_ctx *ctx);
|
|
|
|
/** True if `ctx` is non-NULL and fully initialized. */
|
|
bool ft_tt_ctx_available(const ft_tt_ctx *ctx);
|
|
|
|
/** The `/dev/tenstorrent/N` index this context is bound to, or -1. */
|
|
int ft_tt_ctx_device_index(const ft_tt_ctx *ctx);
|
|
|
|
/** The device file descriptor, owned by `ctx`. -1 if unavailable. For the TLB
|
|
* layer's mmap calls; not for general use. */
|
|
int ft_tt_ctx_fd(const ft_tt_ctx *ctx);
|
|
|
|
/** The kernel-boundary handle, owned by `ctx`. NULL if unavailable. */
|
|
ft_tt_kmd_device *ft_tt_ctx_kmd_device(const ft_tt_ctx *ctx);
|
|
|
|
/**
|
|
* The architecture table selected for this card at open time.
|
|
*
|
|
* Never NULL on an open context: `ft_tt_init_device()` refuses a card whose
|
|
* architecture has no verified table, so a context that exists has one. This is
|
|
* the handle every layer above should ask for geometry instead of naming a
|
|
* Blackhole constant.
|
|
*
|
|
* @return the table, or NULL if `ctx` is NULL.
|
|
*/
|
|
const ft_tt_arch *ft_tt_ctx_arch(const ft_tt_ctx *ctx);
|
|
|
|
/* ================================================================
|
|
* Enumeration
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* Copy the cached device identity. No ioctl is issued.
|
|
* @return FT_TT_OK, or FT_TT_EINVAL.
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: yes
|
|
*/
|
|
int ft_tt_device_get_info(const ft_tt_ctx *ctx, ft_tt_device_info *out_info);
|
|
|
|
/**
|
|
* Copy the cached BAR resources (mmap offsets and lengths). A length of 0 means
|
|
* the BAR is absent, which is the expected state of BAR4 here (spec §2.3).
|
|
* @return FT_TT_OK, or FT_TT_EINVAL.
|
|
*/
|
|
int ft_tt_device_get_resources(const ft_tt_ctx *ctx, ft_tt_kmd_resources *out_resources);
|
|
|
|
/**
|
|
* Copy the TLB window counts derived from those BAR lengths.
|
|
* @return FT_TT_OK, or FT_TT_EINVAL.
|
|
*/
|
|
int ft_tt_tlb_counts_get(const ft_tt_ctx *ctx, ft_tt_tlb_counts *out_counts);
|
|
|
|
/* ================================================================
|
|
* TLB register file
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* Pointer to the mapped TLB configuration register file (BAR0 + 0x1FC00000),
|
|
* the mechanism Phase 1b uses to slide a window with three stores instead of an
|
|
* ioctl. Window `id`'s registers start at word index `id * 12 / 4`.
|
|
*
|
|
* These are NoC2AXI configuration registers in BAR0, not a NoC aperture:
|
|
* reading them issues no NoC transaction. In Phase 1a the mapping is READ-ONLY
|
|
* (PROT_READ); building with FT_TT_PHASE1B_NOC_ENABLED makes it writable.
|
|
*
|
|
* The rest of BAR0 is intentionally left unmapped. Its low 202 x 2 MiB is the
|
|
* TLB window aperture, and a stray store there is a NoC transaction to whatever
|
|
* that window happens to be aimed at — the failure mode that costs a power
|
|
* cycle. Phase 1b maps each window individually via ALLOCATE_TLB, which is all
|
|
* it needs, so the whole-BAR mapping in docs/tt_driver_notes.md §7 step 3 is
|
|
* not required and is not done.
|
|
*
|
|
* @return the mapping, or NULL if unavailable. Valid until ft_tt_ctx_free().
|
|
*/
|
|
const volatile uint32_t *ft_tt_tlb_registers(const ft_tt_ctx *ctx);
|
|
|
|
/** Bytes mapped at ft_tt_tlb_registers(), or 0. */
|
|
size_t ft_tt_tlb_registers_size(const ft_tt_ctx *ctx);
|
|
|
|
/* ================================================================
|
|
* Capacity
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* Copy the grid topology. `valid` is false until ARC telemetry has been read.
|
|
* @return FT_TT_OK, or FT_TT_EINVAL.
|
|
*/
|
|
int ft_tt_grid_get(const ft_tt_ctx *ctx, ft_tt_grid_info *out_grid);
|
|
|
|
/** Install a grid read by the telemetry module. Recomputes the derived counts.
|
|
* @return FT_TT_OK, or FT_TT_EINVAL. */
|
|
int ft_tt_grid_set(ft_tt_ctx *ctx, const ft_tt_grid_info *grid);
|
|
|
|
/**
|
|
* Device DRAM free/total in bytes — the TT analogue of fc_mem_info_ctx().
|
|
* Total is `dram_bank_count x FT_TT_MEM_DRAM_SIZE`; free is total minus what
|
|
* this context has allocated and not freed.
|
|
*
|
|
* Bank count is only knowable from ARC telemetry, so in Phase 1a this returns
|
|
* **FT_TT_EUNAVAIL and writes nothing**. It does not guess, and it does not
|
|
* report the nominal 8 banks as if it had measured them.
|
|
*
|
|
* @param free_bytes may be NULL
|
|
* @param total_bytes may be NULL
|
|
* @return FT_TT_OK, FT_TT_EINVAL, or FT_TT_EUNAVAIL
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: yes
|
|
*/
|
|
int ft_tt_mem_info(const ft_tt_ctx *ctx, size_t *free_bytes, size_t *total_bytes);
|
|
|
|
/** Bytes of device memory this context has allocated and not yet freed. Moved
|
|
* by ft_tt_ctx_note_device_alloc(), which the buffer allocator calls. */
|
|
size_t ft_tt_ctx_alloc_bytes(const ft_tt_ctx *ctx);
|
|
|
|
/**
|
|
* Adjust the device-memory tally that ft_tt_mem_info() subtracts from total.
|
|
* Called by ft_tt_buffer.c on every DRAM allocation and free; not for general
|
|
* use. A negative delta larger than the tally is clamped to zero and logged —
|
|
* an underflow means a double free, which is worth seeing.
|
|
*
|
|
* @param delta_bytes positive on allocate, negative on free
|
|
*/
|
|
void ft_tt_ctx_note_device_alloc(ft_tt_ctx *ctx, int64_t delta_bytes);
|
|
|
|
/** Live TLB windows currently held by this context. */
|
|
uint32_t ft_tt_ctx_live_window_count(const ft_tt_ctx *ctx);
|
|
|
|
/** Adjust the live-window tally. Called by the TLB layer; not for general use.
|
|
* @param delta +1 on allocate, -1 on free */
|
|
void ft_tt_ctx_note_window(ft_tt_ctx *ctx, int delta);
|
|
|
|
#endif /* FT_TT_DEVICE_H */
|