268 lines
13 KiB
C
268 lines
13 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_udrv_arc_probe.h
|
|
* @brief The ARC probe sequence for the USERSPACE DRIVER — the two messages
|
|
* `blackhole_init_hardware()` sends at probe, plus the `POWER_SETTING` that
|
|
* `tt_cdev_open()` sends and that nothing of ours has ever sent.
|
|
*
|
|
* `ft_tt_udrv_arc_msg.[ch]` owns the wire format and the ring arithmetic and
|
|
* is pure. This file is the two things it could not be: a TRANSPORT bound to
|
|
* a real NoC path, and the ORDERED SEQUENCE that brings a card up.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* THE THREE ADDRESSES, AND WHICH OF THEM THE CSM BOUND APPLIES TO
|
|
* ---------------------------------------------------------------------------
|
|
* tt-kmd reaches the ARC through two different accessors and the difference
|
|
* matters, because copying the wrong one refuses a legal access or permits an
|
|
* illegal one:
|
|
*
|
|
* noc_read32/noc_write32 ANY tile-local address on (8, 0). Used for the
|
|
* boot status and QCB pointer in the reset scratch
|
|
* file at 0x8003xxxx, and for the doorbell FIFO at
|
|
* 0x800B0000. Neither is in the CSM.
|
|
* csm_read32/csm_write32 the same NoC access with a bound: the address must
|
|
* lie inside [0x10000000, 0x10080000). Used for the
|
|
* queue header and both message rings.
|
|
*
|
|
* So the transport vtable this file installs enforces the CSM bound — that is
|
|
* what `ft_tt_udrv_arc_msg` is entitled to assume — and the locate path and the
|
|
* doorbell use the raw accessors below, exactly as the driver does.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* WHAT MAKES A RESPONSE EVIDENCE OF FIRMWARE, AND NOT OF OUR OWN RING
|
|
* ---------------------------------------------------------------------------
|
|
* The response read pointer is ours; the response WRITE pointer is the ARC's.
|
|
* `ft_tt_udrv_arc_try_pop()` returns EAGAIN until `res_wptr` moves, and nothing
|
|
* on the host can move it. A completed exchange is therefore firmware action,
|
|
* not ring bookkeeping — which is the difference between proving the protocol
|
|
* and proving the card. The red arm that shows this is a transport whose
|
|
* `trigger` does nothing: the request is pushed identically and no response
|
|
* ever arrives.
|
|
*
|
|
* @status ON SILICON 2026-08-17 (p150a at 0000:12:00.0, tt-kmd loaded as the
|
|
* oracle) via `tests/test_udrv_card_arc.c`. ASIC_STATE0, SET_WDT_TIMEOUT and
|
|
* POWER_SETTING all answered by firmware; POWER_SETTING's effect confirmed on
|
|
* an instrument that is not this one (the AI clock, read through tt-kmd's
|
|
* telemetry and through our own NoC read of the ARC telemetry table).
|
|
* @issues
|
|
* - ASIC_STATE3 and TRIGGER_RESET are built by `ft_tt_udrv_arc_msg.h` and are
|
|
* deliberately NOT sequenced here. A power-down at the wrong moment and an
|
|
* ASIC+DMC reset are both card-wide, and this file is what a bring-up runs.
|
|
* @todo None
|
|
*/
|
|
|
|
#ifndef FT_TT_UDRV_ARC_PROBE_H
|
|
#define FT_TT_UDRV_ARC_PROBE_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "ft_tt_udrv_arc_msg.h"
|
|
#include "ft_tt_udrv_noc.h"
|
|
|
|
/** tt-kmd's own `auto_reset_timeout` default, 10 s, in the milliseconds the
|
|
* message carries (`blackhole_init_hardware()`). */
|
|
#define FT_TT_UDRV_ARC_WATCHDOG_DEFAULT_MS 10000u
|
|
|
|
/** How long to wait for `ARC_BOOT_STATUS` bit 0, matching ARC_MSG_READY_MS. */
|
|
#define FT_TT_UDRV_ARC_READY_TIMEOUT_MS 500u
|
|
|
|
/** Poll budget for one exchange. Each poll sleeps FT_TT_UDRV_ARC_POLL_WAIT_US,
|
|
* so this is the same order as tt-kmd's 1000 ms jiffies deadline while staying
|
|
* a COUNT — a deadline cannot be reproduced in a test and a count can. */
|
|
#define FT_TT_UDRV_ARC_DEFAULT_POLLS 8000u
|
|
|
|
/** Between polls, matching the kernel's usleep_range(100, 200). */
|
|
#define FT_TT_UDRV_ARC_POLL_WAIT_US 120u
|
|
|
|
/* ================================================================
|
|
* The transport
|
|
* ================================================================ */
|
|
|
|
/**
|
|
* A transport context: one NoC path this process owns, aimed wherever the ARC
|
|
* tile is, plus the access counters that let a caller show a red arm really did
|
|
* fewer of something.
|
|
*
|
|
* The path must be UNCACHED (`write_combine = false` at open). Every access
|
|
* here is a register or a ring pointer, and write combining is free to merge
|
|
* and reorder stores inside a buffer — which for a ring pointer published after
|
|
* its payload is not a performance question, it is a corrupt message.
|
|
*/
|
|
typedef struct ft_tt_udrv_arc_noc_context {
|
|
ft_tt_udrv_noc_path *path;
|
|
uint16_t x; /**< TRANSLATED ARC coordinate. Never probed, never defaulted */
|
|
uint16_t y;
|
|
|
|
uint64_t reads; /**< 32-bit reads issued through this context */
|
|
uint64_t writes; /**< 32-bit writes issued through this context */
|
|
uint64_t triggers; /**< doorbell writes issued through this context */
|
|
} ft_tt_udrv_arc_noc_context;
|
|
|
|
/**
|
|
* Bind a transport vtable to a NoC path.
|
|
*
|
|
* @param context caller-owned, zeroed and filled; must outlive the transport
|
|
* @param path an open, UNCACHED path on a window this process owns
|
|
* @param x, y TRANSLATED coordinate of the ARC tile, derived by the caller
|
|
* @param out_transport filled with csm_read32 / csm_write32 / trigger / wait
|
|
* @return FT_TT_UDRV_OK or FT_TT_UDRV_EINVAL
|
|
*
|
|
* Thread-safe: no (the path is stateful) Reentrant: no Signal-safe: no
|
|
*/
|
|
int ft_tt_udrv_arc_transport_init(ft_tt_udrv_arc_noc_context *context, ft_tt_udrv_noc_path *path,
|
|
uint16_t x, uint16_t y,
|
|
ft_tt_udrv_arc_transport *out_transport);
|
|
|
|
/**
|
|
* A 32-bit read of ANY tile-local address on the ARC tile, with no CSM bound.
|
|
* This is tt-kmd's `noc_read32`, and the only legitimate users of it are the
|
|
* reset scratch file and the doorbell — everything else must go through the
|
|
* bounded accessors in the transport.
|
|
*
|
|
* @return FT_TT_UDRV_OK, FT_TT_UDRV_EINVAL, or FT_TT_UDRV_EIO
|
|
*/
|
|
int ft_tt_udrv_arc_noc_read32(ft_tt_udrv_arc_noc_context *context, uint32_t address,
|
|
uint32_t *out_value);
|
|
|
|
/** Write half of ft_tt_udrv_arc_noc_read32(). */
|
|
int ft_tt_udrv_arc_noc_write32(ft_tt_udrv_arc_noc_context *context, uint32_t address,
|
|
uint32_t value);
|
|
|
|
/* ================================================================
|
|
* Locating the queue
|
|
* ================================================================ */
|
|
|
|
/** Everything the locate step read, kept so a caller can print the two
|
|
* addresses this round exists to confirm rather than assert them. */
|
|
typedef struct ft_tt_udrv_arc_locate_report {
|
|
uint32_t boot_status; /**< RESET_SCRATCH(2) once bit 0 was set */
|
|
uint32_t boot_status_polls;/**< how many reads it took; 0 means it was ready */
|
|
uint32_t qcb_pointer; /**< the value RESET_SCRATCH(11) holds */
|
|
uint32_t qcb_word0; /**< queue base */
|
|
uint32_t qcb_word1; /**< low byte is the entry count */
|
|
/** Which step did not answer, or NULL on success. Without it, a NoC read that
|
|
* FAILED and an ARC that never became ready produce the SAME report — both
|
|
* leave `boot_status` and `boot_status_polls` at 0, which reads as a sleeping
|
|
* ARC and sends the reader after the firmware. On a second card host's cold bring-up the
|
|
* cause was neither: memory decode was off, the window aim's own readback
|
|
* refused, and no read ever reached the card. */
|
|
const char *failure_stage;
|
|
} ft_tt_udrv_arc_locate_report;
|
|
|
|
/**
|
|
* tt-kmd's `blackhole_arc_msg_locate_queue()`: wait for the ARC to say it will
|
|
* accept queue operations, read the QCB address out of RESET_SCRATCH(11), read
|
|
* the two QCB words, and build a validated queue descriptor.
|
|
*
|
|
* Stricter than the driver in one place that matters: the QCB pointer itself is
|
|
* bounds-checked before it is dereferenced. tt-kmd passes it straight to
|
|
* `csm_read32`, which refuses out-of-range addresses but reports only -EIO, so
|
|
* "firmware has not published a queue yet" and "the NoC is hung" are the same
|
|
* error there and are distinguishable here.
|
|
*
|
|
* @param context an initialised transport context
|
|
* @param timeout_ms how long to wait for ARC_BOOT_STATUS bit 0
|
|
* @param out_report may be NULL; filled with every word read
|
|
* @param out_queue the validated queue
|
|
* @return FT_TT_UDRV_OK; FT_TT_UDRV_EIO when the NoC answered all
|
|
* ones or the ARC never became ready; FT_TT_UDRV_ENODEV when
|
|
* the QCB pointer is not a CSM address (no queue published);
|
|
* FT_TT_UDRV_EINVAL for a malformed queue or a bad argument
|
|
*
|
|
* Thread-safe: no Reentrant: no Signal-safe: no
|
|
*/
|
|
int ft_tt_udrv_arc_locate_queue(ft_tt_udrv_arc_noc_context *context, unsigned timeout_ms,
|
|
ft_tt_udrv_arc_locate_report *out_report,
|
|
ft_tt_udrv_arc_queue *out_queue);
|
|
|
|
/* ================================================================
|
|
* Exchanges
|
|
* ================================================================ */
|
|
|
|
/** One request and whatever came back, kept whole so a caller can print the
|
|
* firmware's own words instead of a verdict derived from them. */
|
|
typedef struct ft_tt_udrv_arc_exchange {
|
|
uint32_t request_header;
|
|
uint32_t request_payload[FT_TT_UDRV_ARC_PAYLOAD_WORDS];
|
|
uint32_t response_header;
|
|
uint32_t response_payload[FT_TT_UDRV_ARC_PAYLOAD_WORDS];
|
|
double elapsed_seconds;
|
|
int rc; /**< the FT_TT_UDRV_* result of the send */
|
|
} ft_tt_udrv_arc_exchange;
|
|
|
|
/**
|
|
* Send one request and record the response.
|
|
*
|
|
* A nonzero response header is the firmware's own status and is NOT rewritten:
|
|
* `rc` reports FT_TT_UDRV_EIO and `response_header` holds the status, which is
|
|
* the same split tt-kmd makes when it returns -EREMOTEIO.
|
|
*
|
|
* @param max_polls 0 selects FT_TT_UDRV_ARC_DEFAULT_POLLS
|
|
* @return the same value left in `out_exchange->rc`
|
|
*
|
|
* Thread-safe: no (the ring is one shared resource per card; the caller
|
|
* serialises, as the kernel's arc_msg_mutex does)
|
|
* Reentrant: no Signal-safe: no
|
|
*/
|
|
int ft_tt_udrv_arc_exchange_send(const ft_tt_udrv_arc_transport *transport,
|
|
const ft_tt_udrv_arc_queue *queue,
|
|
const ft_tt_udrv_arc_msg *request, uint32_t max_polls,
|
|
ft_tt_udrv_arc_exchange *out_exchange);
|
|
|
|
/**
|
|
* The POWER_SETTING a userspace driver must send, because nothing else will.
|
|
*
|
|
* tt-kmd's legacy open asks for `FT_TT_KMD_POWER_FLAGS_ALL & ~MAX_AI_CLOCK` — every power
|
|
* feature on and the AI clock at its MINIMUM ("legacy client: default to
|
|
* AICLK=Low"). That request is the whole reason this card ran at 800 MHz until
|
|
* 2026-08-15. Ours asks for the same feature set with MAX_AI_CLK set, which is
|
|
* what `FT_TT_AI_CLK` unset means everywhere else in this backend.
|
|
*
|
|
* @param max_ai_clock true for 0x7FFF, false for tt-kmd's own 0x7FFE
|
|
*/
|
|
int ft_tt_udrv_arc_power_request_build(bool max_ai_clock, ft_tt_udrv_arc_msg *out_msg);
|
|
|
|
/* ================================================================
|
|
* The sequence
|
|
* ================================================================ */
|
|
|
|
/** What a bring-up did, message by message. */
|
|
typedef struct ft_tt_udrv_arc_probe_report {
|
|
ft_tt_udrv_arc_locate_report locate;
|
|
ft_tt_udrv_arc_exchange asic_state0;
|
|
ft_tt_udrv_arc_exchange set_wdt_timeout;
|
|
ft_tt_udrv_arc_exchange power_setting;
|
|
} ft_tt_udrv_arc_probe_report;
|
|
|
|
/**
|
|
* The whole hardware footprint of `blackhole_init_hardware()` plus the open-time
|
|
* `POWER_SETTING`, in the driver's own order.
|
|
*
|
|
* MRRS is deliberately absent. It lives in the PCI Express capability's DEVCTL
|
|
* register, and `ft_tt_udrv_reset.[ch]`'s config snapshot and restore already
|
|
* rewrites it — a second writer would be a second place to get it wrong.
|
|
* `fw_log_setup()` is absent by decision (ft_tt_udrv.h §4).
|
|
*
|
|
* SET_WDT_TIMEOUT is sent EXPLICITLY rather than inherited, matching the driver,
|
|
* because a previous owner's value is not knowable and the failure mode of the
|
|
* wrong one is the card resetting underneath us.
|
|
*
|
|
* @param transport one from ft_tt_udrv_arc_transport_init(); the queue is
|
|
* located through its own context, so a caller cannot pair
|
|
* a transport with a queue read through a different path
|
|
* @param watchdog_ms 0 selects FT_TT_UDRV_ARC_WATCHDOG_DEFAULT_MS
|
|
* @param max_ai_clock see ft_tt_udrv_arc_power_request_build()
|
|
* @param out_report may be NULL; filled whether the sequence succeeded or not
|
|
* @return FT_TT_UDRV_OK when every message was answered with
|
|
* status 0; otherwise the first failure's code, with the
|
|
* report showing which message it was
|
|
*
|
|
* Thread-safe: no Reentrant: no Signal-safe: no
|
|
*/
|
|
int ft_tt_udrv_arc_probe_hardware(const ft_tt_udrv_arc_transport *transport, uint32_t watchdog_ms,
|
|
bool max_ai_clock, ft_tt_udrv_arc_probe_report *out_report);
|
|
|
|
#endif /* FT_TT_UDRV_ARC_PROBE_H */
|