386 lines
16 KiB
C
386 lines
16 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file ft_tt_arch.c
|
|
* @brief Architecture selection by PCI id, family naming, and the two table
|
|
* self-checks (structural, and agreement with the compiled headers).
|
|
*
|
|
* Selection is deliberately narrow: exactly one family has a table, and any other
|
|
* Tenstorrent device id is named and refused. The table the selector hands out
|
|
* has already passed both checks, so a caller holding a non-NULL table can index
|
|
* its arrays by its own counts without re-validating.
|
|
*
|
|
* @status Complete; covered by tests/test_arch.c.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#include "ft_tt_arch.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include "ft_tt_device.h"
|
|
#include "ft_tt_launch.h"
|
|
#include "ft_tt_mailbox.h"
|
|
#include "ft_tt_memmap.h"
|
|
|
|
/** Tenstorrent's PCI vendor id (public docs: ethdump.c sample). */
|
|
#define TENSTORRENT_PCI_VENDOR_ID 0x1E52u
|
|
|
|
/** Device ids of the families we can name. Blackhole is in the docs (ethdump.c
|
|
* sample); Wormhole B0 and Grayskull are TEST (tests/test_arch.c). */
|
|
#define BLACKHOLE_PCI_DEVICE_ID 0xB140u
|
|
#define WORMHOLE_PCI_DEVICE_ID 0x401Eu
|
|
#define GRAYSKULL_PCI_DEVICE_ID 0xFACAu
|
|
|
|
/** Telemetry masks are 32-bit words built as (1u << count) - 1, so every
|
|
* per-bit count must stay below 32. */
|
|
#define MAX_TELEMETRY_MASK_BITS 31u
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Naming */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
ft_tt_arch_id ft_tt_arch_id_for_pci_device_id(uint16_t pci_device_id)
|
|
{
|
|
switch (pci_device_id) {
|
|
case BLACKHOLE_PCI_DEVICE_ID:
|
|
return FT_TT_ARCH_BLACKHOLE;
|
|
case WORMHOLE_PCI_DEVICE_ID:
|
|
return FT_TT_ARCH_WORMHOLE_B0;
|
|
case GRAYSKULL_PCI_DEVICE_ID:
|
|
return FT_TT_ARCH_GRAYSKULL;
|
|
default:
|
|
return FT_TT_ARCH_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
const char *ft_tt_arch_id_string(ft_tt_arch_id id)
|
|
{
|
|
switch (id) {
|
|
case FT_TT_ARCH_GRAYSKULL:
|
|
return "Grayskull";
|
|
case FT_TT_ARCH_WORMHOLE_B0:
|
|
return "Wormhole B0";
|
|
case FT_TT_ARCH_BLACKHOLE:
|
|
return "Blackhole";
|
|
case FT_TT_ARCH_UNKNOWN:
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Selection */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/** The supported table for a family, or NULL when the family has none. */
|
|
static const ft_tt_arch *supported_table_for_family(ft_tt_arch_id id)
|
|
{
|
|
if (id == FT_TT_ARCH_BLACKHOLE)
|
|
return ft_tt_arch_blackhole();
|
|
return NULL;
|
|
}
|
|
|
|
const ft_tt_arch *ft_tt_arch_select_by_pci_ids(uint16_t pci_vendor_id, uint16_t pci_device_id)
|
|
{
|
|
ft_tt_arch_id family;
|
|
const ft_tt_arch *table;
|
|
|
|
if (pci_vendor_id != TENSTORRENT_PCI_VENDOR_ID) {
|
|
ft_tt_log_error("unsupported architecture: PCI vendor 0x%04x is not Tenstorrent "
|
|
"(device 0x%04x)",
|
|
(unsigned)pci_vendor_id, (unsigned)pci_device_id);
|
|
return NULL;
|
|
}
|
|
|
|
family = ft_tt_arch_id_for_pci_device_id(pci_device_id);
|
|
table = supported_table_for_family(family);
|
|
if (!table) {
|
|
ft_tt_log_error("unsupported architecture: %s (PCI 0x%04x:0x%04x) — no verified "
|
|
"table exists for it",
|
|
ft_tt_arch_id_string(family), (unsigned)pci_vendor_id,
|
|
(unsigned)pci_device_id);
|
|
return NULL;
|
|
}
|
|
|
|
if (ft_tt_arch_check(table) != FT_TT_OK || ft_tt_arch_check_headers(table) != FT_TT_OK) {
|
|
ft_tt_log_error("unsupported architecture: %s table failed its self-check",
|
|
table->name);
|
|
return NULL;
|
|
}
|
|
return table;
|
|
}
|
|
|
|
const ft_tt_arch *ft_tt_arch_select(uint16_t pci_vendor_id, uint16_t pci_device_id,
|
|
uint64_t kmd_chip_arch)
|
|
{
|
|
const ft_tt_arch *table = ft_tt_arch_select_by_pci_ids(pci_vendor_id, pci_device_id);
|
|
|
|
if (!table)
|
|
return NULL;
|
|
|
|
/* The driver's view is a cross-check. Two sources that disagree mean one of
|
|
* them is wrong, and we cannot tell which. */
|
|
if (table->kmd_chip_arch != kmd_chip_arch) {
|
|
ft_tt_log_error("unsupported architecture: device id says %s but the kernel driver "
|
|
"reports chip_arch %llu (expected %llu)",
|
|
table->name, (unsigned long long)kmd_chip_arch,
|
|
(unsigned long long)table->kmd_chip_arch);
|
|
return NULL;
|
|
}
|
|
return table;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Structural check */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/** True when `bits[0..count)` is a permutation of 0..count-1. */
|
|
static bool is_bit_permutation(const uint8_t *bits, uint32_t count)
|
|
{
|
|
uint32_t seen = 0u;
|
|
uint32_t index;
|
|
|
|
for (index = 0; index < count; index++) {
|
|
if (bits[index] >= count || (seen & (1u << bits[index])))
|
|
return false;
|
|
seen |= 1u << bits[index];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** True when every entry of a coordinate list lies inside the NoC grid. */
|
|
static bool xy_list_inside_grid(const ft_tt_arch *arch, const ft_tt_arch_xy *list, uint32_t count)
|
|
{
|
|
uint32_t index;
|
|
|
|
for (index = 0; index < count; index++) {
|
|
if (list[index].x >= arch->noc.size_x || list[index].y >= arch->noc.size_y)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool xy_inside_grid(const ft_tt_arch *arch, ft_tt_arch_xy point)
|
|
{
|
|
return point.x < arch->noc.size_x && point.y < arch->noc.size_y;
|
|
}
|
|
|
|
static int refuse_table(const ft_tt_arch *arch, const char *reason)
|
|
{
|
|
ft_tt_log_error("arch table '%s' refused: %s", arch->name ? arch->name : "(unnamed)", reason);
|
|
return FT_TT_EIO;
|
|
}
|
|
|
|
static int check_identity(const ft_tt_arch *arch)
|
|
{
|
|
if (!arch->name || arch->pci_vendor_id == 0u || !arch->pci_device_ids ||
|
|
arch->pci_device_id_count == 0u)
|
|
return refuse_table(arch, "no identity (name / PCI ids)");
|
|
if (arch->noc.size_x == 0u || arch->noc.size_y == 0u)
|
|
return refuse_table(arch, "empty NoC grid");
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
static int check_tensix(const ft_tt_arch *arch)
|
|
{
|
|
const ft_tt_arch_tensix *tensix = &arch->tensix;
|
|
uint32_t index;
|
|
|
|
if (tensix->harvest_axis != FT_TT_HARVEST_AXIS_COLUMN)
|
|
return refuse_table(arch, "harvest axis is not COLUMN (the only one ft_tt_coord maps)");
|
|
if (tensix->column_count == 0u || tensix->row_count == 0u)
|
|
return refuse_table(arch, "empty Tensix grid");
|
|
if (tensix->column_count > MAX_TELEMETRY_MASK_BITS)
|
|
return refuse_table(arch, "Tensix columns do not fit a 32-bit telemetry mask");
|
|
if (!tensix->noc_x_by_column || !tensix->noc_y_by_row || !tensix->telemetry_bit_by_column)
|
|
return refuse_table(arch, "missing Tensix column / row / harvesting-bit table");
|
|
if (!is_bit_permutation(tensix->telemetry_bit_by_column, tensix->column_count))
|
|
return refuse_table(arch, "Tensix harvesting bit order is not a permutation");
|
|
|
|
for (index = 0; index < tensix->column_count; index++) {
|
|
if (tensix->noc_x_by_column[index] >= arch->noc.size_x)
|
|
return refuse_table(arch, "a Tensix column lies outside the NoC grid");
|
|
}
|
|
for (index = 0; index < tensix->row_count; index++) {
|
|
if (tensix->noc_y_by_row[index] >= arch->noc.size_y)
|
|
return refuse_table(arch, "a Tensix row lies outside the NoC grid");
|
|
}
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
static int check_dram(const ft_tt_arch *arch)
|
|
{
|
|
const ft_tt_arch_dram *dram = &arch->dram;
|
|
|
|
if (dram->bank_count > MAX_TELEMETRY_MASK_BITS)
|
|
return refuse_table(arch, "DRAM banks do not fit a 32-bit telemetry mask");
|
|
if (dram->bank_count > FT_TT_MAX_DRAM_BANKS)
|
|
return refuse_table(arch, "more DRAM banks than FT_TT_MAX_DRAM_BANKS");
|
|
if (dram->bank_count == 0u)
|
|
return FT_TT_OK;
|
|
if (!dram->noc0 || dram->ports_per_bank == 0u)
|
|
return refuse_table(arch, "DRAM banks declared with no port table");
|
|
if (dram->translated_column_count == 0u ||
|
|
dram->bank_count % dram->translated_column_count != 0u)
|
|
return refuse_table(arch, "DRAM banks do not divide into the translated alias columns");
|
|
if (dram->max_harvested_banks >= dram->bank_count)
|
|
return refuse_table(arch, "every DRAM bank may be harvested");
|
|
if (!xy_list_inside_grid(arch, dram->noc0, dram->bank_count * dram->ports_per_bank))
|
|
return refuse_table(arch, "a DRAM port lies outside the NoC grid");
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
static int check_eth(const ft_tt_arch *arch)
|
|
{
|
|
if (arch->eth.channel_count > MAX_TELEMETRY_MASK_BITS)
|
|
return refuse_table(arch, "ETH channels do not fit a 32-bit telemetry mask");
|
|
if (arch->eth.channel_count == 0u)
|
|
return FT_TT_OK;
|
|
if (!arch->eth.noc0)
|
|
return refuse_table(arch, "ETH channels declared with no table");
|
|
if (!xy_list_inside_grid(arch, arch->eth.noc0, arch->eth.channel_count))
|
|
return refuse_table(arch, "an ETH channel lies outside the NoC grid");
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
static int check_fixed_blocks(const ft_tt_arch *arch)
|
|
{
|
|
const ft_tt_arch_fixed_blocks *fixed = &arch->fixed;
|
|
|
|
if (!xy_inside_grid(arch, fixed->arc))
|
|
return refuse_table(arch, "ARC lies outside the NoC grid");
|
|
if (fixed->security_count > 1u ||
|
|
(fixed->security_count == 1u && !xy_inside_grid(arch, fixed->security)))
|
|
return refuse_table(arch, "bad security tile description");
|
|
if (fixed->pcie_count > 0u &&
|
|
(!fixed->pcie_noc0 || !xy_list_inside_grid(arch, fixed->pcie_noc0, fixed->pcie_count)))
|
|
return refuse_table(arch, "bad PCIe tile table");
|
|
if (fixed->router_count > 0u &&
|
|
(!fixed->router_noc0 ||
|
|
!xy_list_inside_grid(arch, fixed->router_noc0, fixed->router_count)))
|
|
return refuse_table(arch, "bad router node table");
|
|
if (fixed->l2cpu_count == 0u)
|
|
return FT_TT_OK;
|
|
if (fixed->l2cpu_count > MAX_TELEMETRY_MASK_BITS)
|
|
return refuse_table(arch, "L2CPU tiles do not fit a 32-bit telemetry mask");
|
|
if (!fixed->l2cpu_noc0 || !fixed->l2cpu_telemetry_bit_by_tile)
|
|
return refuse_table(arch, "L2CPU tiles declared with no table / telemetry bit order");
|
|
if (!is_bit_permutation(fixed->l2cpu_telemetry_bit_by_tile, fixed->l2cpu_count))
|
|
return refuse_table(arch, "L2CPU telemetry bit order is not a permutation");
|
|
if (!xy_list_inside_grid(arch, fixed->l2cpu_noc0, fixed->l2cpu_count))
|
|
return refuse_table(arch, "an L2CPU tile lies outside the NoC grid");
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
static int check_tlb_and_core(const ft_tt_arch *arch)
|
|
{
|
|
if (arch->tlb.window_2m_count == 0u || arch->tlb.register_stride_bytes == 0u ||
|
|
arch->tlb.kernel_window_index >= arch->tlb.window_2m_count ||
|
|
arch->tlb.strided_window_count > arch->tlb.window_2m_count)
|
|
return refuse_table(arch, "empty or inconsistent TLB description");
|
|
if (arch->core.processor_count == 0u || arch->core.l1_size == 0u ||
|
|
arch->core.soft_reset_all_riscs_mask == 0u)
|
|
return refuse_table(arch, "per-Tensix description missing processors / L1 / reset mask");
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int ft_tt_arch_check(const ft_tt_arch *arch)
|
|
{
|
|
int rc;
|
|
|
|
if (!arch)
|
|
return FT_TT_EINVAL;
|
|
|
|
rc = check_identity(arch);
|
|
if (rc == FT_TT_OK)
|
|
rc = check_tensix(arch);
|
|
if (rc == FT_TT_OK)
|
|
rc = check_dram(arch);
|
|
if (rc == FT_TT_OK)
|
|
rc = check_eth(arch);
|
|
if (rc == FT_TT_OK)
|
|
rc = check_fixed_blocks(arch);
|
|
if (rc == FT_TT_OK)
|
|
rc = check_tlb_and_core(arch);
|
|
return rc;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Header agreement */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/** One scalar the compiled headers assume, paired with the table's value. */
|
|
typedef struct header_expectation {
|
|
const char *what;
|
|
uint64_t table_value;
|
|
uint64_t header_value;
|
|
} header_expectation;
|
|
|
|
int ft_tt_arch_check_headers(const ft_tt_arch *arch)
|
|
{
|
|
size_t index;
|
|
|
|
if (!arch)
|
|
return FT_TT_EINVAL;
|
|
|
|
{
|
|
const ft_tt_arch_tensix_core *core = &arch->core;
|
|
const header_expectation expectations[] = {
|
|
{ "L1 size", core->l1_size, FT_TT_MEM_L1_SIZE },
|
|
{ "processor count", core->processor_count, FT_TT_PROC_COUNT },
|
|
{ "processors per core type", core->processor_count,
|
|
FT_TT_MAX_PROCESSORS_PER_CORE_TYPE },
|
|
{ "mailbox base", core->mailbox_base, FT_TT_MEM_MAILBOX_BASE },
|
|
{ "programmable core types", core->programmable_core_type_count,
|
|
FT_TT_PROGRAMMABLE_CORE_TYPE_COUNT },
|
|
{ "max kernels", core->max_num_kernels, FT_TT_MAX_NUM_KERNELS },
|
|
{ "subordinate map size", core->subordinate_map_size, FT_TT_SUBORDINATE_MAP_SIZE },
|
|
{ "threading-variable processors", core->processors_for_threading_variables,
|
|
FT_TT_MAX_PROCESSORS_FOR_THREADING_VARIABLES },
|
|
{ "circular buffers", core->circular_buffer_count, FT_TT_NUM_CIRCULAR_BUFFERS },
|
|
{ "soft-reset address", core->soft_reset_address, FT_TT_TENSIX_SOFT_RESET_ADDR },
|
|
{ "all-RISC reset mask", core->soft_reset_all_riscs_mask, FT_TT_SOFT_RESET_ALL_RISCS },
|
|
{ "DRAM bytes per bank", core->dram_bytes_per_bank, FT_TT_MEM_DRAM_SIZE },
|
|
{ "L1 alignment", core->l1_alignment, FT_TT_L1_ALIGNMENT },
|
|
{ "DRAM alignment", core->dram_alignment, FT_TT_DRAM_ALIGNMENT },
|
|
{ "NoC overlay base", core->noc_overlay_base, FT_TT_NOC_OVERLAY_BASE },
|
|
{ "NoC stream register span", core->noc_stream_register_span,
|
|
FT_TT_NOC_STREAM_REG_SPACE_SIZE },
|
|
{ "Tensix rows", arch->tensix.row_count, FT_TT_TENSIX_ROWS_PER_COLUMN },
|
|
{ "harvest axis", (uint64_t)arch->tensix.harvest_axis, FT_TT_TENSIX_HARVEST_AXIS },
|
|
{ "NoC size x", arch->noc.size_x, FT_TT_NOC_SIZE_X },
|
|
{ "NoC size y", arch->noc.size_y, FT_TT_NOC_SIZE_Y },
|
|
{ "ARC x", arch->fixed.arc.x, FT_TT_ARC_NOC0_X },
|
|
{ "ARC y", arch->fixed.arc.y, FT_TT_ARC_NOC0_Y },
|
|
{ "2 MiB TLB windows", arch->tlb.window_2m_count, FT_TT_TLB_2M_WINDOW_COUNT },
|
|
{ "4 GiB TLB windows", arch->tlb.window_4g_count, FT_TT_TLB_4G_WINDOW_COUNT },
|
|
{ "kernel TLB window", arch->tlb.kernel_window_index, FT_TT_TLB_KERNEL_WINDOW_INDEX },
|
|
{ "TLB register stride", arch->tlb.register_stride_bytes, FT_TT_TLB_REG_STRIDE },
|
|
{ "TLB register offset", arch->tlb.registers_offset, FT_TT_TLB_REGS_OFFSET },
|
|
{ "strided TLB windows", arch->tlb.strided_window_count,
|
|
FT_TT_TLB_STRIDED_WINDOW_COUNT },
|
|
};
|
|
|
|
for (index = 0; index < sizeof(expectations) / sizeof(expectations[0]); index++) {
|
|
if (expectations[index].table_value != expectations[index].header_value) {
|
|
ft_tt_log_error("arch table '%s' disagrees with the compiled headers: %s is "
|
|
"%llu in the table, %llu in the headers",
|
|
arch->name ? arch->name : "(unnamed)", expectations[index].what,
|
|
(unsigned long long)expectations[index].table_value,
|
|
(unsigned long long)expectations[index].header_value);
|
|
return FT_TT_EIO;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The register file the headers map must cover every TLB register. */
|
|
if (arch->tlb.registers_length > FT_TT_TLB_REGS_LEN)
|
|
return refuse_table(arch, "TLB registers extend past the mapped register window");
|
|
/* A presence flag, not a number: the loader has no path for NCRISC IRAM. */
|
|
if (arch->core.has_ncrisc_iram)
|
|
return refuse_table(arch, "part has NCRISC IRAM, which the loader cannot stage");
|
|
return FT_TT_OK;
|
|
}
|