/* 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 */