Foundry/tt/ft_tt_udrv_arc_msg.h

287 lines
14 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_udrv_arc_msg.h
* @brief The ARC firmware message queue — the mechanism behind every hardware
* action tt-kmd performs that is not a TLB register write: the power-up
* handshake, the watchdog timeout, the clock/power state, and the ASIC+DMC
* reset.
*
* ---------------------------------------------------------------------------
* WHAT THIS IS
* ---------------------------------------------------------------------------
* A plain message ring in the ARC's CSM, reached by ordinary 32-bit NoC reads
* and writes to tile (8,0) — the same path `ft_tt_arc_telemetry.c` already
* uses to read telemetry. Messaging adds WRITES to a path Foundry already
* reaches, and one write to a doorbell FIFO. There is no ioctl in it anywhere:
* tt-kmd's `arc_msg_send_sync()` is `csm_read32`/`csm_write32` in a loop, and those are `noc_read32`/`noc_write32` through the
* driver's own private TLB window.
*
* locate RESET_SCRATCH(11) = 0x8003042C holds the QCB address.
* QCB word 0 = queue base (a CSM address), word 1 & 0xFF = entries.
* header 32 bytes at the queue base: req_wptr +0x00, res_rptr +0x04,
* req_rptr +0x10, res_wptr +0x14.
* request ring of `num_entries` 32-byte messages at base + 32.
* response ring of `num_entries` 32-byte messages after the requests.
* trigger write 0 to 0x800B0000 (ARC_MSI_FIFO) on the same tile.
* status a response whose header word is nonzero is a firmware error.
*
* ---------------------------------------------------------------------------
* WHAT IS PROVEN HERE AND WHAT IS NOT
* ---------------------------------------------------------------------------
* Message ENCODING and RING ARITHMETIC are pure functions and are gated
* off-hardware against an independently written simulated ARC (the test's fake
* implements the peer side of the protocol from the driver's semantics, not by
* calling these functions — instrumenting a mechanism with itself proves
* nothing). The SEND is not proven and cannot be: it needs a NoC transaction.
* That is why the transport is a vtable — `ft_tt_udrv_arc_transport` — with
* exactly three operations, so the untested surface is three functions and not
* the protocol.
*
* ---------------------------------------------------------------------------
* RING OCCUPANCY FOR ANY ENTRY COUNT
* ---------------------------------------------------------------------------
* Pointers run over [0, 2N). A single modular subtraction in u32 is only right
* when 2N divides 2^32 (N a power of two, as on this card's firmware). This
* module branches on `wptr >= rptr` instead, so it is right for any N, and it
* refuses pointers outside [0, 2N).
*
* @status Still pure — nothing here issues a NoC transaction — but the protocol
* above it is NO LONGER UNPROVEN. `ft_tt_udrv_arc_probe.[ch]` supplies a real
* transport over a TLB window we aim ourselves, and on 2026-08-17 this ring
* carried ASIC_STATE0, SET_WDT_TIMEOUT, POWER_SETTING and TEST to the p150a's
* firmware, each answered with status 0. The queue address
* (`RESET_SCRATCH(11)`) and the doorbell (0x800B0000) were both confirmed
* against tt-kmd on live silicon; see ft_tt_udrv_arc_probe.h.
* @issues
* - **TEST (0x90) returns a firmware-side COUNTER in `payload[1]`**, one per
* TEST message whichever path sent it. A caller comparing a whole response
* against another one will see it move and must not read that as a
* mismatch.
* @todo None
*/
#ifndef FT_TT_UDRV_ARC_MSG_H
#define FT_TT_UDRV_ARC_MSG_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* ------------------------------------------------------------------------ *
* Addresses and constants, all the kernel driver's documented Blackhole ARC values.
* ------------------------------------------------------------------------ */
/** NoC coordinates of the ARC tile. Never probed; this is the driver's own. */
#define FT_TT_UDRV_ARC_X 8u
#define FT_TT_UDRV_ARC_Y 0u
/** RESET_SCRATCH(N) — the ARC scratch register file. */
#define FT_TT_UDRV_ARC_RESET_SCRATCH(n) (0x80030400u + ((uint32_t)(n) * 4u))
/** Holds the Message Queue Control Block address. */
#define FT_TT_UDRV_ARC_MSG_QCB_PTR FT_TT_UDRV_ARC_RESET_SCRATCH(11)
/** Boot status; bit 0 means the ARC will accept queue operations. */
#define FT_TT_UDRV_ARC_BOOT_STATUS FT_TT_UDRV_ARC_RESET_SCRATCH(2)
#define FT_TT_UDRV_ARC_BOOT_STATUS_READY_FOR_MSG 0x1u
/** Write 0 here to make the ARC process the request ring. */
#define FT_TT_UDRV_ARC_MSI_FIFO 0x800B0000u
/** The ARC's CSM window. Every queue address must lie inside it. */
#define FT_TT_UDRV_ARC_CSM_BASE 0x10000000u
#define FT_TT_UDRV_ARC_CSM_SIZE (1u << 19)
/** Message types we need. */
#define FT_TT_UDRV_ARC_MSG_ASIC_STATE0 0xA0u /**< power-up handshake */
#define FT_TT_UDRV_ARC_MSG_ASIC_STATE3 0xA3u /**< power-down, at cleanup */
#define FT_TT_UDRV_ARC_MSG_POWER_SETTING 0x21u /**< clock / power features */
#define FT_TT_UDRV_ARC_MSG_SET_WDT_TIMEOUT 0xC1u /**< firmware watchdog */
#define FT_TT_UDRV_ARC_MSG_TRIGGER_RESET 0x56u /**< ASIC + DMC reset */
#define FT_TT_UDRV_ARC_MSG_TEST 0x90u /**< liveness probe */
/** Power feature bits, `power_flags`. Bit 15 is reserved by the FW spec. */
#define FT_TT_UDRV_ARC_POWER_FLAG_MAX_AI_CLK (1u << 0)
#define FT_TT_UDRV_ARC_POWER_FLAG_MRISC_PHY_WAKEUP (1u << 1)
#define FT_TT_UDRV_ARC_POWER_FLAG_TENSIX_ENABLE (1u << 2)
#define FT_TT_UDRV_ARC_POWER_FLAG_L2CPU_ENABLE (1u << 3)
#define FT_TT_UDRV_ARC_POWER_FLAG_ALL 0x7FFFu
/** Bytes per ring entry, and the queue header that precedes both rings. */
#define FT_TT_UDRV_ARC_MSG_BYTES 32u
#define FT_TT_UDRV_ARC_QUEUE_HEADER_BYTES 32u
/** Payload words per message. */
#define FT_TT_UDRV_ARC_PAYLOAD_WORDS 7u
/** A request or a response. Wire layout is the header word then the payload. */
typedef struct ft_tt_udrv_arc_msg {
uint32_t header;
uint32_t payload[FT_TT_UDRV_ARC_PAYLOAD_WORDS];
} ft_tt_udrv_arc_msg;
/** A located message queue. All addresses are CSM addresses. */
typedef struct ft_tt_udrv_arc_queue {
uint32_t base; /**< queue base, from QCB word 0 */
uint32_t num_entries; /**< QCB word 1 & 0xFF */
uint32_t request_base; /**< base + 32 */
uint32_t response_base; /**< request_base + num_entries * 32 */
uint32_t req_wptr_addr;
uint32_t req_rptr_addr;
uint32_t res_wptr_addr;
uint32_t res_rptr_addr;
} ft_tt_udrv_arc_queue;
/**
* The three device operations the protocol needs. Everything above this line
* is arithmetic; everything below it is a NoC transaction.
*
* `wait` is called between polls and may be NULL. It exists so the polling
* loop has no dependency on a clock, which is what makes `send_sync` testable
* with a deterministic fake.
*/
typedef struct ft_tt_udrv_arc_transport {
int (*csm_read32)(void *context, uint32_t address, uint32_t *out_value);
int (*csm_write32)(void *context, uint32_t address, uint32_t value);
int (*trigger)(void *context);
void (*wait)(void *context);
void *context;
} ft_tt_udrv_arc_transport;
/* ------------------------------------------------------------------------ *
* Pure arithmetic — provable off-hardware.
* ------------------------------------------------------------------------ */
/**
* Build a queue descriptor from the two QCB words, validating that the whole
* ring lies inside the CSM.
*
* @param qcb_word0 queue base
* @param qcb_word1 low byte is the entry count
* @return FT_TT_UDRV_OK; FT_TT_UDRV_EINVAL for a NULL argument, a
* zero entry count (which would divide by zero in the ring
* maths — the kernel driver carries the same guard), or
* a ring that does not fit in the CSM
*/
int ft_tt_udrv_arc_queue_init(uint32_t qcb_word0, uint32_t qcb_word1,
ft_tt_udrv_arc_queue *out_queue);
/**
* Correct ring occupancy: `(wptr - rptr) mod 2 * num_entries`, for any entry
* count, refusing pointers that are already out of range.
*
* @return FT_TT_UDRV_OK or FT_TT_UDRV_EINVAL
*/
int ft_tt_udrv_arc_ring_occupancy(uint32_t wptr, uint32_t rptr, uint32_t num_entries,
uint32_t *out_occupancy);
/** Address of request slot `wptr % num_entries`, word `word_index` of 8. */
int ft_tt_udrv_arc_request_word_address(const ft_tt_udrv_arc_queue *queue, uint32_t wptr,
uint32_t word_index, uint32_t *out_address);
/** Address of response slot `rptr % num_entries`, word `word_index` of 8. */
int ft_tt_udrv_arc_response_word_address(const ft_tt_udrv_arc_queue *queue, uint32_t rptr,
uint32_t word_index, uint32_t *out_address);
/** Advance a ring pointer: `(pointer + 1) mod 2 * num_entries`. */
int ft_tt_udrv_arc_ring_advance(uint32_t pointer, uint32_t num_entries, uint32_t *out_pointer);
/** Wire word `word_index` of a message: index 0 is the header. */
int ft_tt_udrv_arc_msg_word(const ft_tt_udrv_arc_msg *msg, uint32_t word_index,
uint32_t *out_value);
/* ------------------------------------------------------------------------ *
* Message builders — the four exchanges the userspace driver owes the card.
* ------------------------------------------------------------------------ */
/** ASIC_STATE0, no payload. The power-up handshake at probe. */
int ft_tt_udrv_arc_msg_asic_state0(ft_tt_udrv_arc_msg *out_msg);
/** ASIC_STATE3, no payload. What tt-kmd sends at cleanup_hardware. */
int ft_tt_udrv_arc_msg_asic_state3(ft_tt_udrv_arc_msg *out_msg);
/** SET_WDT_TIMEOUT. tt-kmd sends `auto_reset_timeout` seconds x 1000, default
* 10 s => 10000. Read ft_tt_udrv.h §3 before choosing a different value. */
int ft_tt_udrv_arc_msg_set_wdt_timeout(uint32_t milliseconds, ft_tt_udrv_arc_msg *out_msg);
/**
* POWER_SETTING. `header = 0x21 | validity << 8 | power_flags << 16`, and the
* fourteen 16-bit settings pack two per payload word, little-endian — which is
* a `memcpy` in the driver and is written out explicitly
* here so it does not depend on host endianness.
*
* @param validity FT_TT_KMD_POWER_VALIDITY: low nibble = number of valid
* flags, high nibble = number of valid settings
* @param power_flags FT_TT_UDRV_ARC_POWER_FLAG_*
* @param settings 14 values; may be NULL for all zero
*/
int ft_tt_udrv_arc_msg_power_setting(uint8_t validity, uint16_t power_flags,
const uint16_t *settings, ft_tt_udrv_arc_msg *out_msg);
/**
* The exact POWER_SETTING tt-kmd sends when a legacy client opens the char
* device and no other fd is open: validity 0x0F, flags
* `FT_TT_UDRV_ARC_POWER_FLAG_ALL & ~MAX_AI_CLK` = 0x7FFE, payload zero.
* Header 0x7FFE0F21.
*
* Note what that means: the driver's default deliberately requests the LOW AI
* clock ("legacy client: default to AICLK=Low" in the driver's open()). Every timing
* this project has recorded was taken with that request in force.
*/
int ft_tt_udrv_arc_msg_power_setting_open_default(ft_tt_udrv_arc_msg *out_msg);
/**
* The POWER_SETTING tt-kmd sends at LAST CLOSE: the aggregation runs over an
* empty fd list, so every feature is off. Validity 0x0F, flags 0, header
* 0x00000F21. `blackhole_class` does not set `defer_idle_powerdown`, so this
* lands synchronously — there is no grace window on this part.
*/
int ft_tt_udrv_arc_msg_power_setting_last_close(ft_tt_udrv_arc_msg *out_msg);
/** TEST, no payload. tt-kmd sends it before an ASIC+DMC reset to prove the
* queue is alive; a failure there is the "NOC is likely hung" diagnosis. */
int ft_tt_udrv_arc_msg_test(ft_tt_udrv_arc_msg *out_msg);
/** TRIGGER_RESET. `argument` is 3 for the ASIC + M3 reset tt-kmd performs. */
int ft_tt_udrv_arc_msg_trigger_reset(uint16_t argument, ft_tt_udrv_arc_msg *out_msg);
/* ------------------------------------------------------------------------ *
* The protocol — pure arithmetic over a caller-supplied transport.
* ------------------------------------------------------------------------ */
/** Push one request if the ring has room. FT_TT_UDRV_EAGAIN when full. */
int ft_tt_udrv_arc_try_push(const ft_tt_udrv_arc_transport *transport,
const ft_tt_udrv_arc_queue *queue, const ft_tt_udrv_arc_msg *msg);
/** Pop one response if there is one. FT_TT_UDRV_EAGAIN when there is not. */
int ft_tt_udrv_arc_try_pop(const ft_tt_udrv_arc_transport *transport,
const ft_tt_udrv_arc_queue *queue, ft_tt_udrv_arc_msg *out_msg);
/**
* Drain stale responses, push, trigger, and poll for the reply — tt-kmd's
* `arc_msg_send_sync()` without the kernel's mutex and with a poll budget
* instead of a jiffies deadline, so the loop is deterministic in a test.
*
* @param msg the request; on success it is OVERWRITTEN by the response,
* exactly as the driver does
* @param max_polls how many times to look for the response before giving up.
* 0 means one look
* @return FT_TT_UDRV_OK when a response arrived with header 0;
* FT_TT_UDRV_EIO when the firmware reported a nonzero status
* (the status is left in `msg->header`) or a transport call
* failed; FT_TT_UDRV_EAGAIN on poll exhaustion;
* FT_TT_UDRV_EINVAL on a bad argument
*
* Thread-safe: NO. The ring is one shared resource per card and this function
* does not serialise access to it — the caller must, exactly as the kernel's
* `arc_msg_mutex` does. With several jobs on one card that has to be a
* cross-process lock (`ft_tt_udrv_claim.h`), not a mutex.
* Reentrant: no Signal-safe: no
*/
int ft_tt_udrv_arc_send_sync(const ft_tt_udrv_arc_transport *transport,
const ft_tt_udrv_arc_queue *queue, ft_tt_udrv_arc_msg *msg,
uint32_t max_polls);
#endif /* FT_TT_UDRV_ARC_MSG_H */