Foundry/tt/ft_tt_core_status.c

140 lines
5.7 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_core_status.c
* @brief Implementation of the read-only per-Tensix status read and its verdict.
*
* MOVED OUT OF tt/tools/ 2026-08-24 and it is a LIBRARY file now, because a second consumer
* appeared: Pantheon's arming path has to ask "is this core ready to have firmware booted
* onto it" before it boots, and that is the question this verdict already answers. Copying
* the classification into the serve would have produced a SECOND, subtly different reading
* of the same registers — see ft_tt_core_readiness.h for what the arming path adds on top.
* The three printers stayed behind in tools/ (ft_tt_core_status_print.c): a library that
* reads a register has no business writing to stdout.
*
* @status Runs on the p150a test card.
* @issues None
* @todo None
*/
#include "ft_tt_core_status.h"
#include <string.h>
#include "ft_tt_memmap.h"
#include "ft_tt_op_l1_layout.h"
bool ft_tt_core_status_processor_is_held(uint32_t reset_register, unsigned processor)
{
return (reset_register & ft_tt_processor_reset_bit(processor)) != 0u;
}
/** Local spelling of the exported predicate, so the classifier below reads as it always did. */
#define processor_is_held ft_tt_core_status_processor_is_held
const char *ft_tt_core_verdict_string(ft_tt_core_verdict verdict)
{
switch (verdict) {
case FT_TT_CORE_VERDICT_NO_ANSWER: return "no answer";
case FT_TT_CORE_VERDICT_HELD: return "held in reset";
case FT_TT_CORE_VERDICT_FIRMWARE_IDLE: return "firmware idle";
case FT_TT_CORE_VERDICT_BOOT_INCOMPLETE: return "boot handshake never finished";
case FT_TT_CORE_VERDICT_KERNEL_RUNNING: return "kernel running";
case FT_TT_CORE_VERDICT_SYNC_BLOCKED: return "BRISC blocked in tensix_sync";
case FT_TT_CORE_VERDICT_MIXED: return "mixed reset state";
}
return "?";
}
ft_tt_core_verdict ft_tt_core_status_classify(const ft_tt_core_status *status)
{
const ft_tt_launch_state *state = &status->state;
unsigned held_count = 0;
unsigned subordinate;
unsigned processor;
bool brisc_held;
bool subordinates_all_held = true;
bool subordinate_sync_pending = false;
if (status->read_result != FT_TT_OK)
return FT_TT_CORE_VERDICT_NO_ANSWER;
for (processor = 0; processor < FT_TT_PROC_COUNT; processor++)
if (processor_is_held(state->reset_register, processor))
held_count++;
if (held_count == FT_TT_PROC_COUNT)
return FT_TT_CORE_VERDICT_HELD;
brisc_held = processor_is_held(state->reset_register, FT_TT_PROC_DM0);
for (processor = FT_TT_PROC_DM1; processor < FT_TT_PROC_COUNT; processor++)
if (!processor_is_held(state->reset_register, processor))
subordinates_all_held = false;
/* subordinate_sync is [NCRISC, TRISC0, TRISC1, TRISC2];
* 0x00 = done, 0x40 = init, 0x80 = go. Anything not DONE is a subordinate BRISC is
* still waiting on. */
for (subordinate = 0; subordinate < FT_TT_SUBORDINATE_MAP_SIZE; subordinate++)
if (state->subordinate_sync[subordinate] != FT_TT_RUN_SYNC_MSG_DONE)
subordinate_sync_pending = true;
if (!brisc_held && subordinates_all_held && subordinate_sync_pending)
return FT_TT_CORE_VERDICT_SYNC_BLOCKED;
if (state->go_messages[0].signal == FT_TT_RUN_MSG_GO)
return FT_TT_CORE_VERDICT_KERNEL_RUNNING;
/* RUN_MSG_INIT is what the host primes the handshake with; BRISC overwrites it with
* DONE once it and its four subordinates have initialised. Still INIT
* with BRISC out of reset means that never happened. */
if (!brisc_held && state->go_messages[0].signal == FT_TT_RUN_MSG_INIT)
return FT_TT_CORE_VERDICT_BOOT_INCOMPLETE;
if (!brisc_held && held_count == 0)
return FT_TT_CORE_VERDICT_FIRMWARE_IDLE;
return FT_TT_CORE_VERDICT_MIXED;
}
/** Read the resident-queue control block, if one of ours is there. */
static void read_queue_control(const ft_tt_noc_access *noc, ft_tt_core_status *status)
{
uint32_t again[FT_TT_QUEUE_FIELD_COUNT];
if (noc->read(noc->user, status->x, status->y, FT_TT_OP_QUEUE_BASE, status->queue_control,
FT_TT_QUEUE_CONTROL_BYTES) != FT_TT_OK)
return;
if (status->queue_control[FT_TT_QUEUE_FIELD_MAGIC] != FT_TT_QUEUE_MAGIC)
return;
status->queue_present = true;
/* Two samples: a heartbeat that moves is the one signal here that a processor is
* still executing rather than merely leaving plausible words behind. */
if (noc->read(noc->user, status->x, status->y, FT_TT_OP_QUEUE_BASE, again,
FT_TT_QUEUE_CONTROL_BYTES) != FT_TT_OK)
return;
status->queue_heartbeat_ticking =
again[FT_TT_QUEUE_FIELD_HEARTBEAT] != status->queue_control[FT_TT_QUEUE_FIELD_HEARTBEAT];
}
int ft_tt_core_status_read(const ft_tt_noc_access *noc, const ft_tt_grid_info *grid,
uint32_t index, ft_tt_core_status *out)
{
ft_tt_noc_coord core;
int rc;
if (!noc || !grid || !out)
return FT_TT_EINVAL;
memset(out, 0, sizeof(*out));
out->index = index;
/* The coordinate comes from live telemetry every time. A guessed endpoint does not
* fail a read, it hangs the chip (spec §7.3). */
rc = ft_tt_coord_tensix_by_index(grid, index, FT_TT_COORD_TRANSLATED, &core);
if (rc != FT_TT_OK)
return rc;
out->x = (uint16_t)core.x;
out->y = (uint16_t)core.y;
out->read_result = ft_tt_launch_read_state(noc, out->x, out->y, &out->state);
if (out->read_result == FT_TT_OK)
read_queue_control(noc, out);
out->verdict = ft_tt_core_status_classify(out);
return FT_TT_OK;
}