Foundry/tt/tools/ft_tt_core_recover.c

188 lines
6.9 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_core_recover.c
* @brief Implementation of the three core-level recovery rungs.
*
* @status Runs on the p150a test card.
* @issues None
* @todo None
*/
#include "ft_tt_core_recover.h"
#include <stdio.h>
#include <string.h>
#include "ft_tt_memmap.h"
#include "ft_tt_op_l1_layout.h"
/** Pages granted to a blocked consumer by rung 2's counter poke. Above every page count
* this backend configures, so one write satisfies any outstanding wait. */
#define POKE_PAGE_GRANT 64u
const char *ft_tt_core_recovery_rung_string(ft_tt_core_recovery_rung rung)
{
switch (rung) {
case FT_TT_RECOVERY_RUNG_DEASSERT_RISCS: return "rung 0 — deassert the RISC-Vs";
case FT_TT_RECOVERY_RUNG_RELOAD_FIRMWARE: return "rung 1 — reload firmware and boot";
case FT_TT_RECOVERY_RUNG_POKE_WAIT: return "rung 2 — poke the wait";
}
return "?";
}
/** The four sync bytes as one word, NCRISC first, for a compact before/after record. */
static uint32_t pack_subordinate_sync(const uint8_t sync[FT_TT_SUBORDINATE_MAP_SIZE])
{
return (uint32_t)sync[0] | ((uint32_t)sync[1] << 8) | ((uint32_t)sync[2] << 16) |
((uint32_t)sync[3] << 24);
}
/** Fill the common half of a report and record the before-state. */
static void begin_report(const ft_tt_core_status *status, ft_tt_core_recovery_rung rung,
ft_tt_core_recovery_report *out)
{
memset(out, 0, sizeof(*out));
out->index = status->index;
out->x = status->x;
out->y = status->y;
out->rung = rung;
out->reset_register_before = status->state.reset_register;
out->subordinate_sync_before = pack_subordinate_sync(status->state.subordinate_sync);
}
/** Re-read what the rung changed, so the report's "after" is measured, not assumed. */
static void end_report(const ft_tt_noc_access *noc, ft_tt_core_recovery_report *out)
{
ft_tt_launch_state state;
if (ft_tt_launch_read_state(noc, out->x, out->y, &state) != FT_TT_OK)
return;
out->reset_register_after = state.reset_register;
out->subordinate_sync_after = pack_subordinate_sync(state.subordinate_sync);
}
int ft_tt_core_recover_deassert_riscs(const ft_tt_noc_access *noc,
const ft_tt_core_status *status,
ft_tt_core_recovery_report *out)
{
if (!noc || !status || !out)
return FT_TT_EINVAL;
begin_report(status, FT_TT_RECOVERY_RUNG_DEASSERT_RISCS, out);
/* ALL_RISCS and nothing else. The backend bits are not in this mask and must never be
* written (ft_tt_launch.h). `staggered` is meaningless for a single core. */
out->result = ft_tt_launch_deassert_reset(noc, out->x, out->y, FT_TT_SOFT_RESET_ALL_RISCS,
false);
end_report(noc, out);
return FT_TT_OK;
}
int ft_tt_core_recover_reload_firmware(const ft_tt_noc_access *noc,
const ft_tt_core_status *status,
const ft_tt_firmware_set *firmware, bool force,
ft_tt_core_recovery_report *out)
{
if (!noc || !status || !firmware || !out)
return FT_TT_EINVAL;
begin_report(status, FT_TT_RECOVERY_RUNG_RELOAD_FIRMWARE, out);
if (!force && status->verdict == FT_TT_CORE_VERDICT_KERNEL_RUNNING) {
out->result = FT_TT_EPERM;
return FT_TT_EPERM;
}
out->result = ft_tt_core_boot_firmware(noc, out->x, out->y, firmware, NULL);
end_report(noc, out);
return FT_TT_OK;
}
/** Poke 1 — ask a resident kernel to retire, if one left a control block behind. */
static int request_queue_stop(const ft_tt_noc_access *noc, const ft_tt_core_status *status,
ft_tt_core_recovery_report *out)
{
uint32_t stop = 1u;
int rc;
if (!status->queue_present)
return FT_TT_OK;
rc = noc->write(noc->user, out->x, out->y,
FT_TT_OP_QUEUE_BASE + FT_TT_QUEUE_FIELD_STOP_REQUEST * 4u, &stop,
sizeof(stop));
if (rc == FT_TT_OK)
out->queue_stop_requested = true;
return rc;
}
/** Poke 2 — tell a BRISC in `tensix_sync()` that its subordinates are done. */
static int release_subordinate_sync(const ft_tt_noc_access *noc, ft_tt_core_recovery_report *out)
{
uint32_t all_done = FT_TT_RUN_SYNC_MSG_ALL_SUBORDINATES_DONE;
return noc->write(noc->user, out->x, out->y, FT_TT_MB_SUBORDINATE_SYNC_ADDR, &all_done,
sizeof(all_done));
}
/** Poke 3 — grant pages on every circular buffer, releasing any blocked consumer. */
static int grant_circular_buffer_pages(const ft_tt_noc_access *noc,
ft_tt_core_recovery_report *out)
{
unsigned cb_index;
for (cb_index = 0; cb_index < FT_TT_NUM_CIRCULAR_BUFFERS; cb_index++) {
uint32_t acked = 0;
uint32_t granted;
int rc = noc->read(noc->user, out->x, out->y, FT_TT_CB_TILES_ACKED_ADDR(cb_index), &acked,
sizeof(acked));
if (rc != FT_TT_OK)
return rc;
/* The wait is `received - acked >= n` in 16-bit arithmetic, so the grant is
* relative to what the consumer has already freed. */
granted = (acked + POKE_PAGE_GRANT) & 0xFFFFu;
rc = noc->write(noc->user, out->x, out->y, FT_TT_CB_TILES_RECEIVED_ADDR(cb_index),
&granted, sizeof(granted));
if (rc != FT_TT_OK)
return rc;
out->circular_buffers_poked++;
}
return FT_TT_OK;
}
int ft_tt_core_recover_poke_wait(const ft_tt_noc_access *noc, const ft_tt_core_status *status,
ft_tt_core_recovery_report *out)
{
int rc;
if (!noc || !status || !out)
return FT_TT_EINVAL;
begin_report(status, FT_TT_RECOVERY_RUNG_POKE_WAIT, out);
rc = request_queue_stop(noc, status, out);
if (rc == FT_TT_OK)
rc = release_subordinate_sync(noc, out);
if (rc == FT_TT_OK)
rc = grant_circular_buffer_pages(noc, out);
out->result = rc;
end_report(noc, out);
return FT_TT_OK;
}
void ft_tt_core_recovery_print(const ft_tt_core_recovery_report *report)
{
if (!report)
return;
printf("Tensix %u (%u, %u) — %s\n", report->index, report->x, report->y,
ft_tt_core_recovery_rung_string(report->rung));
printf(" writes returned rc=%d\n", report->result);
if (report->queue_stop_requested)
printf(" resident-queue stop flag set\n");
if (report->circular_buffers_poked != 0u)
printf(" granted %u pages on each of %u circular buffers\n", POKE_PAGE_GRANT,
report->circular_buffers_poked);
printf(" soft reset 0x%08x -> 0x%08x\n", report->reset_register_before,
report->reset_register_after);
printf(" subordinate sync 0x%08x -> 0x%08x\n", report->subordinate_sync_before,
report->subordinate_sync_after);
}