584 lines
37 KiB
C
584 lines
37 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_udrv.h
|
|
* @brief Umbrella header for the FOUNDRY USERSPACE DRIVER for Blackhole — the
|
|
* layer that replaces tt-kmd so a user can plug the card in and run Pantheon
|
|
* with no kernel module, no DKMS, no kernel headers and no module signing.
|
|
*
|
|
* This header owns two things and no code: the shared status codes for the
|
|
* `ft_tt_udrv_*` modules, and the written record of WHAT tt-kmd does that we
|
|
* must replicate, and WHAT we lose by not having it.
|
|
*
|
|
* @status **THE PRODUCT NOW RUNS ON THIS LAYER WITH NO VENDOR DRIVER
|
|
* (2026-08-17).** The seam is `ft_tt_runtime_open()` selecting a transport
|
|
* from `FT_TT_DRIVER` (default `kmd`); the branch is
|
|
* `ft_tt_runtime_udrv.[ch]`. A real `pantheon serve` with `tenstorrent`
|
|
* unloaded answered a chat with `[admit] path=gpu` and 6,760 matmuls on the
|
|
* card. `ft_tt_core status all --driver udrv` read 140 cores and
|
|
* `recover --rung 3 --driver udrv --write` did the whole ASIC reset — both
|
|
* with no chardev on the machine. See `ft_tt_runtime_udrv.h` @status for the
|
|
* table and for the measured price of the aim readback.
|
|
*
|
|
* THE TLB PATH IS ON SILICON (2026-08-16, p150a at 0000:12:00.0, with
|
|
* tt-kmd loaded as the oracle). `tests/test_udrv_card_tlb.c` runs four rungs
|
|
* and all of them are green:
|
|
* 1. sysfs discovery + `mmap` of BAR0's register file, read-only
|
|
* 2. window 201 — tt-kmd's own — decodes to ARC (8,0), NoC 0, strict,
|
|
* aimed at the CSM page: `0x00000080 0x00004000 0x00000040`
|
|
* 3. 64 configurations aimed through CONFIGURE_TLB on two windows, one
|
|
* below and one above the strided threshold, byte-identical to what
|
|
* our encoder produces; coordinate truncation matches the kernel's
|
|
* 4. our own register writes, read back through TT-KMD's BAR0 mapping,
|
|
* and a window we aimed ourselves returns the same bytes as one the
|
|
* driver aimed. `0x9D8` is confirmed on silicon and `0xA08` refuted.
|
|
* THE RESET PATH IS ALSO ON SILICON (2026-08-17). `ft_tt_udrv_reset.[ch]`
|
|
* recovered a deliberately wedged core with three PCI config writes, its own
|
|
* config snapshot restored in place, and a bridge link retrain — no vendor
|
|
* ioctl, no PCI remove, no rescan.
|
|
* AND SO IS THE NoC TRANSPORT (2026-08-17). `ft_tt_udrv_noc.[ch]` moves bytes
|
|
* through windows we aim, with no ioctl in the data path. The load-bearing
|
|
* new fact: **a WRITE-COMBINED STORE THROUGH `resource0_wc` REACHES THE
|
|
* CARD** — proven at register level (read back through tt-kmd's BAR0
|
|
* mapping) and through an aperture into a Tensix's L1 (read back by
|
|
* `ft_tt_read_l1()` over tt-kmd's own window), at 296.8 MB/s WC against
|
|
* 109.0 MB/s UC. `stream_header` and the 3-bit `static_vc`, which
|
|
* CONFIGURE_TLB cannot express, are swept: the hardware RETAINS all 32
|
|
* combinations.
|
|
*
|
|
* AND THE ARC PROBE SEQUENCE IS ON SILICON (2026-08-17), which was the last
|
|
* step. `ft_tt_udrv_arc_probe.[ch]` binds the message encoders to the NoC
|
|
* transport and sends real messages; `tests/test_udrv_card_arc.c` is the
|
|
* round: 62 checks green, 7 mutations all red. What it settled:
|
|
* - **BOTH UNPROVEN ADDRESSES ARE CONFIRMED.** `RESET_SCRATCH(11)`
|
|
* (0x8003042C) held 0x1002DD68 -> queue base 0x10032DF0, 4 entries — and
|
|
* it is TT-KMD'S OWN queue, proved by making the driver send a message
|
|
* through SMC_MSG and watching the request write pointer OF THE QUEUE WE
|
|
* LOCATED advance by exactly one while a header word that is not a
|
|
* pointer stayed put. The doorbell at `ARC_MSI_FIFO` 0x800B0000 is proved
|
|
* LOAD-BEARING by a transport whose trigger does nothing: same push, same
|
|
* poll loop, and no response in 1.5 s; ringing it then released the
|
|
* message that was already queued.
|
|
* - ASIC_STATE0, SET_WDT_TIMEOUT (10 s) and POWER_SETTING all answered with
|
|
* firmware status 0. A TEST message sent through OUR path and through
|
|
* tt-kmd's SMC_MSG returns the same seven fixed words, and the eighth —
|
|
* the firmware's own TEST counter — advances by exactly one between them,
|
|
* so both paths feed ONE queue.
|
|
* - **POWER_SETTING WORKS AND IT IS MEASURABLE ON AN INSTRUMENT THAT IS NOT
|
|
* THE RING.** With tt-kmd asking for the MINIMUM from two open fds, our
|
|
* 0x7FFF request took the AI clock 800 -> 1350 MHz, Vcore 740 -> 862 mV
|
|
* and board power 35 -> 69 W; the crossed control (our own message asking
|
|
* for tt-kmd's 0x7FFE) put all three back; repeating the max request
|
|
* raised them again. Under a real Tensix load — four cores, 1500
|
|
* byte-exact vector_add runs each, 18.4 s, 184 telemetry samples taken by
|
|
* a second process WHILE it ran — the clock held at 1350 with a 75 W /
|
|
* 88 A peak, against 800 MHz and 39 W for the same work in the min arm.
|
|
* - Our own NoC walk of the ARC telemetry table agrees with tt-kmd's sysfs
|
|
* on every row, so the instrument survives the module.
|
|
* THE REHEARSAL IS RUN (2026-08-17) AND THE CARD WORKS WITH tt-kmd REMOVED.
|
|
* `tests/test_udrv_rehearsal.c` + `Makefile.udrvrehearsal`: 43 checks
|
|
* green in the bring-up arm and 64 in the reset arm, 5 mutations all red, card released `probe all` 140/140 and 5 GT/s x1 both
|
|
* ends. Its Makefile links NO `ft_tt_kmd.c`, NO `ft_tt_tlb.c`, NO
|
|
* `ft_tt_noc.c` and NO `ft_tt_device.c`, and `nm -u` on the binary finds no
|
|
* `ioctl` symbol at all. What it settled, each against the module being gone:
|
|
* - **THE ARC ANSWERS IN A3, AND THAT WAS THE ROUND'S BIGGEST UNKNOWN.**
|
|
* First NoC read after `rmmod`: ARC_BOOT_STATUS = 0x00000005, ready bit
|
|
* already SET, 0.000 s. RESET_SCRATCH(11) still 0x1002DD68 -> queue
|
|
* 0x10032DF0, 4 entries. TEST, ASIC_STATE0, SET_WDT_TIMEOUT and
|
|
* POWER_SETTING all answered firmware status 0.
|
|
* - **POWER_SETTING MEASURED THROUGH A CROSSED CONTROL, not a bare "did it
|
|
* go up".** With no module nothing lowers the clock at process exit, so a
|
|
* second run starts at 1350 and a naive check reads 1350 -> 1350. The
|
|
* bring-up now asks for tt-kmd's own 0x7FFE first (clock falls to 800),
|
|
* then 0x7FFF (rises to 1350), read through our own NoC walk of the ARC
|
|
* telemetry table — the only telemetry reader left once the sysfs tree
|
|
* goes.
|
|
* - **RESET WITH NO MODULE WORKS, and it surfaced one new fact:** straight
|
|
* after an ASIC reset ARC_BOOT_STATUS reads 0x00000002 — ready bit CLEAR
|
|
* — and takes ~280 polls to set, where after `rmmod` it is set on the
|
|
* first read. `ft_tt_udrv_arc_locate_queue()`'s bounded poll loop is what
|
|
* covers that; a bring-up that read the bit once would fail on the reset
|
|
* path only. Marker cleared in 119-120 ms, config restored in place (23
|
|
* steps, 6 written, 0 mismatches), link retrained 2.5 -> 5 GT/s in 10 ms,
|
|
* MRRS back to 4096, heartbeat restarted (172040 -> 5).
|
|
* - **RECOVERY OF A WEDGED CORE WITH NO VENDOR DRIVER.** Core 100 wedged
|
|
* deliberately by the 1x5 multicast relaunch arm (soft reset 0x00047000,
|
|
* instrn_buf_status 0x66, invalid_instrn 1); read as 0x66 over udrv with
|
|
* the module gone, recovered by the udrv ASIC reset, read back 0x77, and
|
|
* confirmed by `ft_tt_core probe 100 --write` PASS 4/4 once tt-kmd was
|
|
* reloaded. **`instrn_buf_status` over a udrv NoC path is the whole
|
|
* health instrument on a board with no module** — `probe --write` needs
|
|
* the tt-kmd backend.
|
|
* - MRRS reads 4096 straight after `rmmod`: `pci_disable_device()` does not
|
|
* clear DEVCTL. PCI_COMMAND reads 0x0102 — MEMORY DECODE STILL ON — even
|
|
* though `enable` reads 0. Had it been off, every BAR read would have
|
|
* been all ones through a mapping that succeeded.
|
|
* - `resource0` maps with no driver bound; `vfio-pci` was never needed.
|
|
* All 1024 register words identical through `resource0` and
|
|
* `resource0_wc`. Window 201 aimed and read back byte-exact, then
|
|
* restored. Three windows claimed with the flock as the ONLY arbitrator,
|
|
* a forked rival refused the held one and granted a free one.
|
|
* - **600 s IDLE HOLD WITH THE MODULE REMOVED and no host access of any
|
|
* kind: heartbeat 781 -> 6789 continuous, clock still 1350, Vcore still
|
|
* 860 mV, ARC still ready.** 60 watchdog timeouts, on the exact condition
|
|
* the target board will be in. Still not an afternoon.
|
|
* @issues
|
|
* - ~~THE PRODUCT CANNOT USE THIS LAYER~~ — **THE SEAM IS WRITTEN,
|
|
* 2026-08-17.** `ft_tt_runtime_open()` selects a transport from
|
|
* `FT_TT_DRIVER` (default `kmd`) and the udrv branch lives in
|
|
* `ft_tt_runtime_udrv.[ch]`: sysfs discovery, BAR mapping, two flock'd
|
|
* windows, our own ARC bring-up, the shared telemetry walk, and the same
|
|
* three `ft_tt_noc_access` accessors the tt-kmd branch produces. Nothing
|
|
* above `ft_tt_runtime` changed, and nothing was duplicated — the copy
|
|
* shape, the memory-space bounds, the telemetry walk and the coordinates
|
|
* are all shared. A `FT_TT_DRIVER` that cannot be honoured REFUSES: a
|
|
* serve stops rather than answering on the CPU, because the completion
|
|
* text is byte-identical either way. `ft_tt_core recover --driver udrv`
|
|
* is fixed the same way and no longer opens the chardev to read the
|
|
* before-state.
|
|
* - RESET IS DONE AND EXERCISED (2026-08-17), and it was the load-bearing
|
|
* loss. `ft_tt_udrv_reset.[ch]` does the ASIC reset as three PCI config
|
|
* writes, snapshots and restores config space IN PLACE, and retrains the
|
|
* link. It recovered a DELIBERATELY WEDGED core — 101 at 0x00047000,
|
|
* `probe --write` FAIL before and PASS 4/4 after — with no PCI remove and
|
|
* no rescan. Entry point: `ft_tt_core recover --rung 3 --driver udrv
|
|
* --write`. §5(a) below is updated and §5(e)'s discriminating experiment
|
|
* is answered.
|
|
* - **`rmmod tenstorrent` LEAVES THE CARD IN A3 — AND THE QUEUE ANSWERS
|
|
* ANYWAY (MEASURED 2026-08-17). This entry is kept as the record of what
|
|
* was feared; it is no longer an open question.** The paragraph below is
|
|
* the reasoning that made it the round's first move, and it was right to
|
|
* be: the answer just went the good way.**
|
|
* `blackhole_cleanup_hardware()` sends ASIC_STATE3 and
|
|
* FW_LOG RELEASE on unload, and the last chardev close has already sent
|
|
* POWER_SETTING with every flag off. So the first card this driver ever
|
|
* meets alone is in a state nothing here has been measured in. The antidote
|
|
* is exactly `ft_tt_udrv_arc_probe_hardware()` and every message in it is
|
|
* proven — but only ever sent to a card already in A0. **Whether the message
|
|
* queue answers at all in A3 is the open question**, and the fallback is one
|
|
* `modprobe` away.
|
|
* - We do not send ASIC_STATE3 ourselves. `ft_tt_udrv_arc_msg_asic_state3()`
|
|
* exists and is deliberately not sequenced: a power-down at the wrong moment
|
|
* is card-wide, and this module is what a bring-up runs. A card we owned is
|
|
* therefore left in A0, which is an asymmetry with the driver, not a leak.
|
|
* - /dev/mem is NOT a usable second reader of the BAR on this box: a `pread`
|
|
* at the BAR's physical address returns EFAULT even as root. The second
|
|
* reader is tt-kmd's own BAR0 mapping, which will not exist on a machine
|
|
* with no tt-kmd — so after the move there is no independent readback
|
|
* path at all, and the register comparison becomes self-instrumenting.
|
|
* WHAT SURVIVES THE MOVE, and it is more than it sounds: a bad aim is still
|
|
* detectable WITHOUT a second reader, because the window's registers are
|
|
* mapped UNCACHED and reading them back is a real PCIe read of the card.
|
|
* `ft_tt_udrv_noc_window_aim()` does that on every aim and refuses the
|
|
* aperture on any disagreement. What is lost is only the ability to catch a
|
|
* mapping that consistently points at the WRONG PAGE — a failure that would
|
|
* agree with itself. That specific residual is what the on-card comparison
|
|
* against tt-kmd closes, and it has now been made (2026-08-17: all 606
|
|
* configuration words identical through both mappings, and our own aim
|
|
* confirmed through tt-kmd's before any aperture was dereferenced).
|
|
* @todo
|
|
* - [x] NoC transport. DONE 2026-08-17, `ft_tt_udrv_noc.[ch]`. The copy shape
|
|
* is SHARED with ft_tt_noc.c through ft_tt_noc_copy.h rather than
|
|
* ported, so there is one PIO copy in the backend and a measurement
|
|
* through either transport is a measurement of both.
|
|
* - [x] The probe sequence: ASIC_STATE0, SET_WDT_TIMEOUT, POWER_SETTING. DONE
|
|
* 2026-08-17, `ft_tt_udrv_arc_probe.[ch]`. MRRS stayed off the list: it
|
|
* lives in DEVCTL and the config restore rewrites it.
|
|
* - [x] Decide whether the ARC watchdog needs a host heartbeat. ANSWERED — see
|
|
* §3. No heartbeat. Armed at 10 s and held idle for 35 s with no host
|
|
* traffic of any kind; link unchanged at 5 GT/s, heartbeat advanced 350
|
|
* with no restart, the AI clock ONLY our message could have set was
|
|
* still in force, and the ARC still answered.
|
|
* - [x] Recovery, exercised on a deliberately wedged core. DONE 2026-08-17.
|
|
* - [x] Bring the card up after a reset with NO tt-kmd present. **DONE
|
|
* 2026-08-17 — the rehearsal ran exactly this and it works.** See the
|
|
* @status block; the one thing it taught is that the ARC is NOT ready
|
|
* for messages on the first read after a reset.
|
|
* - [x] **Give the product a udrv seam.** DONE 2026-08-17,
|
|
* `ft_tt_runtime_udrv.[ch]` + the `FT_TT_DRIVER` branch in
|
|
* `ft_tt_runtime_open()`.
|
|
* - [ ] Send `POWER_SETTING` 0x7FFE (or ASIC_STATE3) on release, so a card we
|
|
* owned is not left at 1350 MHz forever. Visible today: a second run of
|
|
* the rehearsal starts on a card the first one left at max.
|
|
* - [x] **Set MRRS ourselves at bring-up. DONE 2026-08-18**,
|
|
* `ft_tt_udrv_config_max_read_request_set()` called from
|
|
* `runtime_udrv_open_locked()` before the BAR is mapped — the order
|
|
* tt-kmd's own probe uses. **Proven on a COLD path, not a reset path:**
|
|
* `tests/run_udrv_mrrs_cold_gate.sh` seeds a WRONG MRRS from pid+clock
|
|
* (this bank always reads 4096, so an unseeded check is vacuous), opens
|
|
* over udrv with no reset anywhere in the path, and the card comes back
|
|
* at 4096 with the bring-up reporting `WRITTEN by us` rather than
|
|
* `already correct`. Discriminator: the same command over `kmd` leaves
|
|
* the seeded value alone, so 4096 is not something that happens anyway.
|
|
* Two mutations, both red — the DEVCTL write deleted (the readback
|
|
* refuses and says so), and the bring-up not calling it at all.
|
|
* **HONEST SCOPE: this is probe parity, not a throughput lever.** MRRS
|
|
* bounds reads the DEVICE issues upstream and our data path never has
|
|
* the card read host memory (§4).
|
|
* **AND IT NEEDS ROOT.** A config-space write is privileged, and an
|
|
* unprivileged READ of `config` is truncated by the kernel to the
|
|
* 64-byte standard header — so a non-root process cannot even walk to
|
|
* the PCI Express capability, let alone write DEVCTL. No udev rule
|
|
* changes that. The bring-up is therefore BEST EFFORT and says loudly
|
|
* what it could not do; `tools/60-foundry-blackhole.rules` carries a
|
|
* `setpci CAP_EXP+8.w=5000:7000` so udev does it once at device-add
|
|
* time and a non-root run inherits a correct card.
|
|
* - [x] **The udev rule, exercised at last (2026-08-18).**
|
|
* `tools/60-foundry-blackhole.rules` + `tests/run_udev_nonroot_gate.sh`.
|
|
* With the rule installed, `tenstorrent` UNLOADED and no sudo anywhere
|
|
* in the command, an ordinary user in the `tenstorrent` group ran
|
|
* `ft_tt_core status` over udrv, `probe 0 --write` (which BOOTS the five
|
|
* RISC-Vs and byte-compares a `vector_add`: PASS 4/4) and the whole
|
|
* `test_phase2_vector_add` suite (18 checks, 0 failed). Red arm: the
|
|
* BARs put back to 0600 make the same command exit 1 with "this needs
|
|
* root or a udev rule", and re-triggering udev makes it pass again. A
|
|
* user OUTSIDE the group is still refused — the rule grants, it does not
|
|
* open.
|
|
* - **THE /tmp OWNERSHIP TRAP IS CLOSED, and it had a second half nobody had
|
|
* named.** `mkdir(0777)` under root's umask 022 leaves a **0755 root-owned**
|
|
* `/tmp/ft_tt_udrv.d`, and every later non-root job then fails to claim.
|
|
* `ensure_directory()` now chmods unconditionally and the claim files are
|
|
* `fchmod`ed to 0666. The second half: the failure was REPORTED as "every
|
|
* one of the 202 windows is claimed by a live process" on a completely idle
|
|
* card, because the claim loop treated any non-OK as contention. It now
|
|
* names the real cause.
|
|
* - [ ] (superseded, kept for the record) Bring the card up after a reset with NO tt-kmd present. HALF CLOSED:
|
|
* we can now send ASIC_STATE0 and POWER_SETTING ourselves, which is what
|
|
* the chardev open was doing for us, and both were proved on a live
|
|
* card. What is still untested is that sequence run AFTER an actual
|
|
* reset with the module absent — i.e. that the ARC comes back ready for
|
|
* messages on its own. Nothing here is a NEW unknown; it is the same
|
|
* sequence at a moment nobody has stood in yet.
|
|
*
|
|
* ===========================================================================
|
|
* §1. WHAT THE SIX IOCTLS ACTUALLY DO
|
|
* ===========================================================================
|
|
* Foundry uses exactly six of tt-kmd's ioctls. Three of them never touch the
|
|
* card, two are pure arbitration, and one programs hardware.
|
|
*
|
|
* GET_DEVICE_INFO constants + PCI config values. All of it
|
|
* is in sysfs (vendor/device/subsystem_*).
|
|
* GET_DRIVER_INFO the driver's own version. Moot.
|
|
* QUERY_MAPPINGS pci_resource_len() on BARs 0/2/4 plus
|
|
* mmap cookies the driver invented. Derived
|
|
* from `/sys/.../resource` instead.
|
|
* ALLOCATE_TLB a bit in a per-device bitmap. NOTHING is
|
|
* FREE_TLB written to the card — but this is NOT
|
|
* therefore trivial. It is the arbitration
|
|
* that stops two processes claiming the
|
|
* same one of the 202 windows, and the card
|
|
* is meant to host several jobs at once.
|
|
* Replaced by `ft_tt_udrv_claim.[ch]`,
|
|
* which is the one module here that is
|
|
* finished, because it touches no hardware.
|
|
* CONFIGURE_TLB THE ONLY ONE THAT PROGRAMS HARDWARE:
|
|
* three iowrite32 to BAR0 + 0x1FC00000 +
|
|
* id*12, plus one zeroing write to the
|
|
* strided register for id < 32.
|
|
*
|
|
* ===========================================================================
|
|
* §2. WHAT PROBE DOES TO HARDWARE — the replacement obligation
|
|
* ===========================================================================
|
|
* `blackhole_init_hardware()` is four steps:
|
|
*
|
|
* 1. pcie_set_readrq(pdev, 4096) — MRRS. A PCIe capability config write
|
|
* (DEVCTL bits 14:12), reachable through `/sys/.../config`.
|
|
* 2. ARC message ASIC_STATE0 (0xA0), no payload — the power-up handshake.
|
|
* 3. ARC message SET_WDT_TIMEOUT (0xC1), payload[0] = auto_reset_timeout
|
|
* seconds * 1000 (module default 10 s => 10000).
|
|
* 4. fw_log_setup() — hands a coherent host DMA buffer to the ARC for
|
|
* firmware logging. See §4: we do not need it.
|
|
*
|
|
* And open()/close() are NOT neutral. `tt_cdev_open()` sets
|
|
* a legacy fd's power state to `FT_TT_KMD_POWER_FLAGS_ALL & ~FT_TT_KMD_POWER_FLAG_MAX_AI_CLOCK`
|
|
* with validity FT_TT_KMD_POWER_VALIDITY(15, 0), then aggregates over all open fds
|
|
* and sends ARC POWER_SETTING (0x21). With exactly one legacy fd open that is
|
|
*
|
|
* header = 0x21 | (0x0F << 8) | (0x7FFE << 16) = 0x7FFE0F21, payload 0
|
|
*
|
|
* and on LAST CLOSE the aggregation loop runs over an empty list, so
|
|
*
|
|
* header = 0x21 | (0x0F << 8) | (0x0000 << 16) = 0x00000F21, payload 0
|
|
*
|
|
* i.e. every power feature off and AICLK at minimum. `blackhole_class` does
|
|
* not set `defer_idle_powerdown`, so that lands SYNCHRONOUSLY at last close —
|
|
* there is no grace window on this part.
|
|
*
|
|
* Two things follow that are easy to miss. The tt-kmd default deliberately
|
|
* CLEARS MAX_AI_CLK ("legacy client: default to AICLK=Low"), so every number
|
|
* this project has ever measured was taken with the low-clock request in
|
|
* force. And the powerdown at last close is why a card that has been idle
|
|
* behaves differently from one that has not.
|
|
*
|
|
* **ALL FOUR OF THESE ARE NOW OURS TO SEND, AND THREE ARE PROVEN ON SILICON
|
|
* (2026-08-17, `ft_tt_udrv_arc_probe.[ch]`):** ASIC_STATE0, SET_WDT_TIMEOUT and
|
|
* POWER_SETTING were sent through our own transport and answered with firmware
|
|
* status 0, and POWER_SETTING's effect was measured on the AI clock. MRRS needs
|
|
* no message at all — it is DEVCTL, which the reset round's config restore
|
|
* rewrites. `fw_log_setup()` is not implemented by decision (§4).
|
|
*
|
|
* We ask for `FT_TT_KMD_POWER_FLAGS_ALL` (0x7FFF), i.e. tt-kmd's own legacy word plus
|
|
* bit 0, which is what every other part of this backend already asks tt-kmd for
|
|
* through SET_POWER_STATE. `ft_tt_udrv_arc_power_request_build(false, ...)`
|
|
* reproduces the driver's 0x7FFE exactly, and is what we send on release so the
|
|
* card and the driver's model of it agree again.
|
|
*
|
|
* ===========================================================================
|
|
* §3. THE ARC WATCHDOG — an open question, deliberately not assumed
|
|
* ===========================================================================
|
|
* SET_WDT_TIMEOUT is sent once at probe and NOTHING in tt-kmd ever pets it:
|
|
* `git grep` over the whole module finds ARC_MSG_TYPE_SET_WDT_TIMEOUT in
|
|
* exactly one place, blackhole_init_hardware(). There is no timer, no work
|
|
* item, and no ioctl that re-arms it. Two readings are consistent with that:
|
|
*
|
|
* (a) the timer is firmware-side and watches the FIRMWARE, not the host, so
|
|
* a host that never speaks again is fine; or
|
|
* (b) the timer watches host liveness and 10 s is simply longer than any
|
|
* gap tt-kmd's own traffic leaves.
|
|
*
|
|
* **ANSWERED ON SILICON, 2026-08-17: (a). THERE IS NO HOST HEARTBEAT
|
|
* OBLIGATION.** The timer is firmware-side and watches the FIRMWARE. The arm:
|
|
* SET_WDT_TIMEOUT sent explicitly at 10000 ms and answered with status 0 (so
|
|
* this firmware does implement it and did not ignore the message), then the
|
|
* device held open doing NOTHING for 35 s — 3.5x the timeout, no NoC access, no
|
|
* ioctl, no message. A reset in that window had three independent ways to show
|
|
* and none of them did:
|
|
*
|
|
* the LINK a card reset drops it to Gen1 (measured repeatedly, §5(a)).
|
|
* It read 5 GT/s x1 before and after.
|
|
* the HEARTBEAT `tt_heartbeat` is a firmware counter; a reboot restarts it.
|
|
* It advanced by 350 with no discontinuity.
|
|
* the AI CLOCK the hold was deliberately taken at 1350 MHz, a state NOTHING
|
|
* on that machine but our own POWER_SETTING produces — tt-kmd
|
|
* was asking for the minimum from two open fds. A reset loses
|
|
* the request. It was still 1350 afterwards.
|
|
*
|
|
* And the queue was still at the same address and still answered a TEST.
|
|
*
|
|
* The driver still sends SET_WDT_TIMEOUT EXPLICITLY rather than inheriting
|
|
* whatever the previous owner left, because a previous owner's value is not
|
|
* knowable and the failure mode of the wrong one is the card resetting
|
|
* underneath us.
|
|
*
|
|
* WHAT THIS ARM DOES NOT PROVE, stated because the rehearsal has to carry it:
|
|
* 35 s is 3.5 timeouts, not an afternoon. It rules out "the watchdog fires on
|
|
* host silence at the timeout"; it does not rule out a much longer firmware
|
|
* timer that nothing here would have tripped. The counter-evidence is that an
|
|
* idle card with tt-kmd loaded and no client open goes hours without ARC
|
|
* traffic and does not reset, which is the state this bank sits in every day.
|
|
*
|
|
* ===========================================================================
|
|
* §4. FIRMWARE LOGGING — we do not need it, and here is why
|
|
* ===========================================================================
|
|
* `fw_log_setup()` allocates a coherent host buffer and hands its DMA address
|
|
* to the ARC (FW_LOG 0xC7 / SETUP). It is the ONLY thing in our whole path
|
|
* that would require the card to read host memory, and it is the ONLY thing
|
|
* that would require an interrupt (fw_log_interrupt() is the MSI handler).
|
|
*
|
|
* Dropping it costs a diagnostic channel and nothing else: it is best-effort
|
|
* in the driver too ("Best-effort: start firmware log forwarding"), a failure
|
|
* is a dev_warn, and it feeds a debugfs/parser path Foundry has never read.
|
|
* Keeping it would drag in dma_alloc_coherent-equivalent host memory, a
|
|
* physical address, and MSI plumbing — i.e. it would single-handedly
|
|
* reintroduce every hard part of a userspace driver that this card otherwise
|
|
* does not have. DECISION: not implemented, not planned.
|
|
*
|
|
* ===========================================================================
|
|
* §5. WHAT WE LOSE — and whether it is replaceable
|
|
* ===========================================================================
|
|
* Measured against tt-kmd 2.11.1-pre as installed on the card host.
|
|
*
|
|
* (a) RESET_DEVICE. FOUR of the five destructive flags are replaceable and one
|
|
* is trivially so; this is the loss weighted highest and it turns out to be
|
|
* the smallest.
|
|
*
|
|
* RESET_PCIE_LINK (flag 1) is `pcie_hot_reset_and_restore_state()`, a
|
|
* secondary-bus reset on the upstream bridge — exactly what
|
|
* `/sys/bus/pci/devices/0000:12:00.0/reset` does, and that file's
|
|
* `reset_method` on this box reads `bus`. REPLACEABLE, already present.
|
|
*
|
|
* CONFIG_WRITE (flag 2) is `pcie_timer_interrupt()` = two
|
|
* PCI CONFIG dword writes: 0x934 <- 1, then 0x930 <- 0x11. Config space
|
|
* on this box is 4096 bytes, so both offsets are reachable through
|
|
* `pwrite()` on `/sys/.../config`. REPLACEABLE.
|
|
*
|
|
* ASIC_RESET (flag 4) — the rung 3 that this project's recovery ladder
|
|
* actually depends on — is `set_reset_marker()` + `pcie_timer_interrupt()`
|
|
* (in the driver), i.e. set PCI_COMMAND.PARITY (config 0x04 bit 6) and
|
|
* then the same two dword writes. THREE CONFIG WRITES. REPLACEABLE.
|
|
*
|
|
* ASIC_DMC_RESET (flag 5) is two ARC messages — TEST (0x90) to prove the
|
|
* queue is alive, then TRIGGER_RESET (0x56) with payload[0] = 3
|
|
* (in the driver). REPLACEABLE by ft_tt_udrv_arc_msg.
|
|
*
|
|
* What is NOT free is the bookkeeping tt-kmd does AROUND the reset:
|
|
* bump_reset_gen, vma_zap, reclaim TLBs, reclaim iATUs, and the
|
|
* save/restore of PCI state (`pci_save_state`/`pci_restore_state`).
|
|
* Config-space save/restore has no sysfs equivalent, so we take our own
|
|
* snapshot and write it back. **BUILT AND PROVEN ON SILICON 2026-08-17**
|
|
* in `ft_tt_udrv_reset.[ch]`: the ASIC reset recovered a deliberately
|
|
* wedged core in place, no PCI remove and no rescan.
|
|
*
|
|
* `remove` + `rescan` was the alternative and it is RULED OUT as the
|
|
* primary path, not merely unchosen. A rescan re-runs BAR ASSIGNMENT, and
|
|
* this card only re-enumerates at all because its flash set
|
|
* `pcie_bar4_size = 0`; with the stock 32 GiB BAR4 request a 36-bit host
|
|
* cannot place the BAR and the device never comes back
|
|
* (`docs/p150a-flashing.md`). An in-place restore writes back the
|
|
* addresses the PCI core already chose and cannot fail that way. `remove`
|
|
* is also unscopeable and needs the bus-wide `/sys/bus/pci/rescan`. It is
|
|
* kept as an explicit fallback (`--udrv-reenumerate`), not deleted.
|
|
*
|
|
* And rung 3's remove + rescan was never a hardware requirement: tt-kmd's
|
|
* POST_RESET clears `needs_hw_init` before deciding whether it can
|
|
* restore, so a POST_RESET issued before the card
|
|
* clears the reset marker loses the restore permanently. We wait for the
|
|
* marker first. RESIDUAL EXPOSURE: bringing the card up after a reset
|
|
* with NO tt-kmd loaded is still untested — the reset needs no ARC
|
|
* message, but nothing then sends ASIC_STATE0 or POWER_SETTING.
|
|
*
|
|
* (b) tenstorrent_vma_zap. Revokes every userspace BAR mapping
|
|
* across a reset so a stale pointer cannot touch a device that has moved
|
|
* underneath it. NOT REPLACEABLE as a mechanism — one process cannot
|
|
* unmap another's pages. It is also LARGELY UNNEEDED here: its purpose is
|
|
* to protect processes that did not ask for the reset, and in the
|
|
* userspace-driver design the process asking for the reset is the only
|
|
* process holding a mapping. The replacement is a discipline, not a
|
|
* mechanism: munmap our own apertures before resetting and re-map after,
|
|
* and hold the card lock (`tools/ft_tt_card_lock.c`) across the whole
|
|
* sequence. EXPOSURE: a second process that mapped the BAR behind our back
|
|
* keeps a live mapping across a reset. That is a real hole and it is the
|
|
* honest cost of leaving the kernel; it is bounded by the fact that the
|
|
* BAR files are root-owned 0600.
|
|
*
|
|
* (c) The sysfs/hwmon telemetry tree (`/sys/.../tenstorrent/`, hwmon
|
|
* asic_temp/vcore/current/power/fan_rpm). REPLACEABLE and already
|
|
* replaced: `ft_tt_arc_telemetry.c` reads the same telemetry table over
|
|
* the NoC from tile (8,0), which is where tt-kmd reads it from too.
|
|
* What is lost is the ability for a THIRD PARTY —
|
|
* tt-smi, a monitoring agent, `sensors` — to read temperature without
|
|
* going through Foundry. EXPOSURE: operational visibility, not
|
|
* correctness. Note tt-smi is already dead on this host (AVX SIGILL,
|
|
* DEV_NOTES §2), so nothing on this machine actually loses a reader.
|
|
*
|
|
* (d) SET_NOC_CLEANUP's crash-time write. Registers ONE
|
|
* 32-bit NoC write that the kernel performs when the fd closes for any
|
|
* reason, including SIGKILL. NOT REPLACEABLE — the whole value of it is
|
|
* that it survives the death of the process, which is precisely what a
|
|
* userspace driver cannot promise. EXPOSURE: this is the "an abort must
|
|
* DRAIN" hazard with the last safety net removed. Note carefully that
|
|
* Foundry does not use it today (no caller anywhere in `tt/`), so nothing
|
|
* REGRESSES — but the option to fix `SIGTERM does not retire card
|
|
* residencies` (plan Phase 7) by registering a stop-flag write with the
|
|
* kernel goes away, and that was the cheap fix. The remaining answers are
|
|
* a signal handler (which the plan already rejects as unsafe from a
|
|
* handler), the residency idle-spin bound (which already exists and is
|
|
* what makes an idle stop safe), or a supervisor process. RECOMMENDATION:
|
|
* treat the idle-spin bound as the actual guarantee and say so, rather
|
|
* than adopting the ioctl now and losing it later.
|
|
*
|
|
* (e) RECOVERY GRANULARITY — a design question, not just a replacement
|
|
* obligation. tt-kmd exposes reset as a fixed menu (link / config-write /
|
|
* ASIC / ASIC+DMC) and every entry on it resets the WHOLE CARD. On a
|
|
* 140-core part that is meant to host several jobs at once (see
|
|
* `ft_tt_udrv_claim.h`), the blast radius IS the problem: one job's wedged
|
|
* core costs every other job its work. Foundry's rungs 0-2 are the
|
|
* targeted, per-core attempts and they are recorded as FAILING on the
|
|
* `0x00047000` fingerprint, with only rung 3 — the card-wide ASIC reset —
|
|
* working.
|
|
*
|
|
* That record is evidence the targeted path does not work TODAY. It is not
|
|
* evidence it cannot. Three explanations are separable, and the tree
|
|
* already contains enough to rank them:
|
|
*
|
|
* (i) The stuck state is in the NoC FABRIC, so no per-core action can
|
|
* clear it. LARGELY REFUTED ALREADY: a core wedged in the
|
|
* `tensix_sync()` family "still answer[s] NoC reads"
|
|
* (DEV_NOTES §1), and the whole 15-core incident recovered core by
|
|
* core once the cause was removed. A fabric that had lost the core
|
|
* would not answer.
|
|
* (ii) The rungs are simply not doing the right thing and nobody dug,
|
|
* because rung 3 always worked.
|
|
* (iii) The rungs address the WRONG UNIT. This is the reading the code
|
|
* already states: rung 0 deasserts RISC-Vs, rung 1 reboots
|
|
* firmware, rung 2 writes the value a PROCESSOR is waiting for —
|
|
* all three assume something is executing that can observe a host
|
|
* write. The recorded wedges are in the Tensix BACKEND (the matrix
|
|
* engine), which executes nothing (`ft_tt_core_recover.h`, @status).
|
|
*
|
|
* MY READING: (iii), with (ii) as a subset — the rungs are not wrong so
|
|
* much as aimed at the processors when the stuck unit is the backend. And
|
|
* the uncomfortable corollary is that the ONE per-core register that would
|
|
* reach the backend is `0xFFB121B0`'s matrix-engine bits, which are on
|
|
* this project's permanent do-not-touch list: asserting them silently and
|
|
* permanently corrupts that core's compute and cost 14 cores once
|
|
* (DEV_NOTES §1). **So per-core recovery from this fingerprint is blocked
|
|
* by a hazard, not by unfinished work**, and a userspace driver does not
|
|
* change that — it only removes the ioctl wrapper around the card-wide
|
|
* reset that does work.
|
|
*
|
|
* THAT EXPERIMENT IS RUN AND ANSWERED, 2026-08-17. Core 101 was wedged
|
|
* deliberately at 0x00047000; it still answered NoC reads and all five of
|
|
* its neighbours probed PASS, so the fabric was intact and (i) is dead. An
|
|
* ASIC reset with NO PCI remove and NO rescan then recovered it — `probe
|
|
* --write` FAIL before, PASS 4/4 after. **So the stuck state is ASIC
|
|
* configuration cleared at reset, and re-enumeration was never doing the
|
|
* work.** That leaves (iii) and keeps a narrower per-core equivalent
|
|
* PLAUSIBLE — but the only per-core register that reaches the backend is
|
|
* still `0xFFB121B0`'s matrix-engine bits, permanently banned, so this is a
|
|
* direction for Quasar and not a plan for Blackhole.
|
|
*
|
|
* ===========================================================================
|
|
* §6. WHAT "NO KERNEL MODULE" DOES AND DOES NOT MEAN
|
|
* ===========================================================================
|
|
* It means no VENDOR module: no tt-kmd, no DKMS build, no kernel headers on
|
|
* the user's machine, no signing for Secure Boot, no version skew with the
|
|
* running kernel. It does NOT mean no kernel code — the PCI core still
|
|
* enumerates the device and sysfs still publishes it. The device must be
|
|
* unbound from any driver (or bound to vfio-pci), and the sysfs resource
|
|
* files are root-owned, so this needs root or a udev rule. Both are packaging,
|
|
* not engineering.
|
|
*
|
|
* **IT ALSO DOES NOT MEAN NO ROOT AT ALL, and the line is exactly where PCI
|
|
* CONFIG SPACE begins (measured 2026-08-18).** With
|
|
* `tools/60-foundry-blackhole.rules` installed, an ordinary user in the
|
|
* `tenstorrent` group does the whole DATA path with no privilege: map both BAR
|
|
* mappings, claim windows, aim them, move bytes, boot firmware on a Tensix, read
|
|
* the ARC telemetry table, take the per-core flock claims. What that user CANNOT
|
|
* do is anything in config space — MRRS and the ASIC reset (and therefore
|
|
* `ft_tt_core recover`) — because the kernel truncates an unprivileged read of
|
|
* `config` to the 64-byte standard header and refuses the writes outright. So on
|
|
* the target board: **serving is unprivileged, recovery is privileged**, and the
|
|
* udev rule's `setpci` line is what keeps the one privileged bring-up step off
|
|
* the run-time path.
|
|
*/
|
|
|
|
#ifndef FT_TT_UDRV_H
|
|
#define FT_TT_UDRV_H
|
|
|
|
/** C99 has no `_Static_assert`; this is the classic negative-array-size form,
|
|
* the same one ft_tt_kmd_abi.h uses. Restated rather than included so this
|
|
* module carries no dependency on the layer it replaces. */
|
|
#define FT_TT_UDRV_STATIC_ASSERT(cond, tag) \
|
|
typedef char ft_tt_udrv_static_assert_##tag[(cond) ? 1 : -1]
|
|
|
|
/**
|
|
* Status codes for the userspace-driver layer. The numeric values match
|
|
* `ft_tt_device.h`'s enum deliberately, so a caller that already maps those to
|
|
* messages needs no second table — but this module does NOT include that
|
|
* header, because the whole point is that it does not depend on the tt-kmd
|
|
* path it replaces.
|
|
*/
|
|
enum {
|
|
FT_TT_UDRV_OK = 0,
|
|
FT_TT_UDRV_ERROR = -1, /**< unclassified failure */
|
|
FT_TT_UDRV_ENOMEM = -2, /**< host allocation failed */
|
|
FT_TT_UDRV_EINVAL = -3, /**< bad argument */
|
|
FT_TT_UDRV_EIO = -4, /**< a read/write/mmap failed; errno was logged */
|
|
FT_TT_UDRV_ENODEV = -5, /**< no such device, or not the part we expect */
|
|
FT_TT_UDRV_EAGAIN = -6, /**< ring full, or no response ready yet */
|
|
FT_TT_UDRV_ENOTSUP = -7 /**< a declared seam that is not implemented yet */
|
|
};
|
|
|
|
#endif /* FT_TT_UDRV_H */
|