Foundry/tt/ft_tt_udrv_arc_probe.c

358 lines
12 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_udrv_arc_probe.c
* @brief Implementation of the ARC probe sequence (see ft_tt_udrv_arc_probe.h).
*
* @status On silicon 2026-08-17.
* @issues None
* @todo None
*/
/* `-std=c99 -pedantic` hides clock_gettime and nanosleep behind a feature-test
* macro on glibc while macOS declares them unconditionally — the cross-machine
* trap DEV_NOTES §5 records. Declared before any include. */
#define _POSIX_C_SOURCE 200809L
#include "ft_tt_udrv_arc_probe.h"
#include <string.h>
#include <time.h>
#include "ft_tt_udrv.h"
/** True when `[address, address + 4)` lies inside the ARC's CSM. Byte for byte
* the bound tt-kmd's `is_range_within_csm()` applies. */
static bool address_is_within_csm(uint32_t address)
{
uint64_t limit = (uint64_t)FT_TT_UDRV_ARC_CSM_BASE + (uint64_t)FT_TT_UDRV_ARC_CSM_SIZE;
return ((uint64_t)address >= (uint64_t)FT_TT_UDRV_ARC_CSM_BASE) &&
(((uint64_t)address + 4u) <= limit);
}
static double seconds_now(void)
{
struct timespec now;
(void)clock_gettime(CLOCK_MONOTONIC, &now);
return (double)now.tv_sec + (double)now.tv_nsec / 1e9;
}
static void sleep_microseconds(unsigned microseconds)
{
struct timespec request;
request.tv_sec = (time_t)(microseconds / 1000000u);
request.tv_nsec = (long)(microseconds % 1000000u) * 1000L;
(void)nanosleep(&request, NULL);
}
/* ------------------------------------------------------------------------ *
* The transport
* ------------------------------------------------------------------------ */
int ft_tt_udrv_arc_noc_read32(ft_tt_udrv_arc_noc_context *context, uint32_t address,
uint32_t *out_value)
{
int rc;
if (!context || !context->path || !out_value)
return FT_TT_UDRV_EINVAL;
rc = ft_tt_udrv_noc_transfer(context->path, context->x, context->y, (uint64_t)address,
out_value, sizeof(*out_value), true, FT_TT_NOC_WIDTH_WORD32);
if (rc != FT_TT_UDRV_OK)
return rc;
context->reads++;
return FT_TT_UDRV_OK;
}
int ft_tt_udrv_arc_noc_write32(ft_tt_udrv_arc_noc_context *context, uint32_t address,
uint32_t value)
{
int rc;
if (!context || !context->path)
return FT_TT_UDRV_EINVAL;
rc = ft_tt_udrv_noc_transfer(context->path, context->x, context->y, (uint64_t)address, &value,
sizeof(value), false, FT_TT_NOC_WIDTH_WORD32);
if (rc != FT_TT_UDRV_OK)
return rc;
context->writes++;
return FT_TT_UDRV_OK;
}
/** Transport read: the raw accessor with the CSM bound the ring is entitled to. */
static int transport_csm_read32(void *opaque, uint32_t address, uint32_t *out_value)
{
ft_tt_udrv_arc_noc_context *context = (ft_tt_udrv_arc_noc_context *)opaque;
if (!address_is_within_csm(address))
return FT_TT_UDRV_EINVAL;
return ft_tt_udrv_arc_noc_read32(context, address, out_value);
}
static int transport_csm_write32(void *opaque, uint32_t address, uint32_t value)
{
ft_tt_udrv_arc_noc_context *context = (ft_tt_udrv_arc_noc_context *)opaque;
if (!address_is_within_csm(address))
return FT_TT_UDRV_EINVAL;
return ft_tt_udrv_arc_noc_write32(context, address, value);
}
/**
* The doorbell: write 0 to ARC_MSI_FIFO on the ARC tile. Outside the CSM, so it
* uses the raw accessor — the same split tt-kmd makes.
*/
static int transport_trigger(void *opaque)
{
ft_tt_udrv_arc_noc_context *context = (ft_tt_udrv_arc_noc_context *)opaque;
int rc;
if (!context)
return FT_TT_UDRV_EINVAL;
rc = ft_tt_udrv_arc_noc_write32(context, FT_TT_UDRV_ARC_MSI_FIFO, 0u);
if (rc != FT_TT_UDRV_OK)
return rc;
context->triggers++;
return FT_TT_UDRV_OK;
}
static void transport_wait(void *opaque)
{
(void)opaque;
sleep_microseconds(FT_TT_UDRV_ARC_POLL_WAIT_US);
}
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)
{
if (!context || !path || !out_transport)
return FT_TT_UDRV_EINVAL;
/* A write-combined path would let a ring-pointer store overtake the payload
* it publishes. That is not slow, it is a message the firmware reads half
* of. */
if (path->write_combined)
return FT_TT_UDRV_EINVAL;
memset(context, 0, sizeof(*context));
context->path = path;
context->x = x;
context->y = y;
memset(out_transport, 0, sizeof(*out_transport));
out_transport->csm_read32 = transport_csm_read32;
out_transport->csm_write32 = transport_csm_write32;
out_transport->trigger = transport_trigger;
out_transport->wait = transport_wait;
out_transport->context = context;
return FT_TT_UDRV_OK;
}
/* ------------------------------------------------------------------------ *
* Locating the queue
* ------------------------------------------------------------------------ */
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)
{
ft_tt_udrv_arc_locate_report report;
double deadline;
uint32_t boot_status = 0u;
uint32_t qcb_pointer = 0u;
int rc;
if (!context || !out_queue)
return FT_TT_UDRV_EINVAL;
memset(&report, 0, sizeof(report));
deadline = seconds_now() + ((double)timeout_ms / 1000.0);
for (;;) {
rc = ft_tt_udrv_arc_noc_read32(context, FT_TT_UDRV_ARC_BOOT_STATUS, &boot_status);
if (rc != FT_TT_UDRV_OK) {
report.failure_stage = "the NoC read of ARC_BOOT_STATUS did not complete — nothing "
"was read, so the boot status below is not a value the card "
"returned. Check PCI_COMMAND bit 1 (memory decode) first";
goto done;
}
/* Recorded on EVERY exit, not just the one that succeeds. It used to be
* assigned after the loop, so both failure paths reported 0x00000000 —
* a value nobody read, and one that reads as a plausible "the ARC is
* asleep". On a second card host the true value was 0xFFFFFFFF (memory decode off),
* which names the cause on sight; the zero cost a bisect. */
report.boot_status = boot_status;
/* All ones is what a hung fabric or a device that has gone away reads
* as, and it also happens to have the ready bit set. */
if (boot_status == 0xFFFFFFFFu) {
report.failure_stage = "ARC_BOOT_STATUS reads all ones — the card is not answering "
"at all (memory decode off, or the device has gone away)";
rc = FT_TT_UDRV_EIO;
goto done;
}
if (boot_status & FT_TT_UDRV_ARC_BOOT_STATUS_READY_FOR_MSG)
break;
if (seconds_now() >= deadline) {
report.failure_stage = "the ARC never set ARC_BOOT_STATUS bit 0 (ready for a "
"message) within the timeout";
rc = FT_TT_UDRV_EIO;
goto done;
}
report.boot_status_polls++;
sleep_microseconds(1000u);
}
rc = ft_tt_udrv_arc_noc_read32(context, FT_TT_UDRV_ARC_MSG_QCB_PTR, &qcb_pointer);
if (rc != FT_TT_UDRV_OK) {
report.failure_stage = "the NoC read of RESET_SCRATCH(11), the queue-control-block "
"pointer, did not complete";
goto done;
}
report.qcb_pointer = qcb_pointer;
/* Bounds-checked BEFORE it is dereferenced, so "no queue published yet"
* reports as ENODEV instead of arriving as an EIO from the accessor. */
if (!address_is_within_csm(qcb_pointer) || !address_is_within_csm(qcb_pointer + 4u)) {
report.failure_stage = "RESET_SCRATCH(11) does not point inside the ARC CSM — firmware "
"has not published a message queue";
rc = FT_TT_UDRV_ENODEV;
goto done;
}
rc = ft_tt_udrv_arc_noc_read32(context, qcb_pointer, &report.qcb_word0);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_noc_read32(context, qcb_pointer + 4u, &report.qcb_word1);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_queue_init(report.qcb_word0, report.qcb_word1, out_queue);
done:
if (out_report)
*out_report = report;
return rc;
}
/* ------------------------------------------------------------------------ *
* Exchanges
* ------------------------------------------------------------------------ */
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)
{
ft_tt_udrv_arc_msg message;
double started;
int rc;
if (!out_exchange)
return FT_TT_UDRV_EINVAL;
memset(out_exchange, 0, sizeof(*out_exchange));
if (!transport || !queue || !request) {
out_exchange->rc = FT_TT_UDRV_EINVAL;
return out_exchange->rc;
}
message = *request;
out_exchange->request_header = request->header;
memcpy(out_exchange->request_payload, request->payload, sizeof(out_exchange->request_payload));
started = seconds_now();
rc = ft_tt_udrv_arc_send_sync(transport, queue, &message,
(max_polls == 0u) ? FT_TT_UDRV_ARC_DEFAULT_POLLS : max_polls);
out_exchange->elapsed_seconds = seconds_now() - started;
/* The response overwrote `message` in place whether the status was good or
* bad, and the firmware's own words are what a caller needs to see. */
out_exchange->response_header = message.header;
memcpy(out_exchange->response_payload, message.payload,
sizeof(out_exchange->response_payload));
out_exchange->rc = rc;
return rc;
}
int ft_tt_udrv_arc_power_request_build(bool max_ai_clock, ft_tt_udrv_arc_msg *out_msg)
{
uint16_t flags = (uint16_t)FT_TT_UDRV_ARC_POWER_FLAG_ALL;
if (!max_ai_clock)
flags = (uint16_t)(flags & ~(uint16_t)FT_TT_UDRV_ARC_POWER_FLAG_MAX_AI_CLK);
/* Validity 0x0F is FT_TT_KMD_POWER_VALIDITY(15, 0): fifteen valid flags, no valid
* settings — exactly what tt_cdev_open() builds. */
return ft_tt_udrv_arc_msg_power_setting(0x0Fu, flags, NULL, out_msg);
}
/* ------------------------------------------------------------------------ *
* The sequence
* ------------------------------------------------------------------------ */
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)
{
ft_tt_udrv_arc_probe_report report;
ft_tt_udrv_arc_noc_context *context;
ft_tt_udrv_arc_queue queue;
ft_tt_udrv_arc_msg message;
int rc;
if (!transport || !transport->context)
return FT_TT_UDRV_EINVAL;
context = (ft_tt_udrv_arc_noc_context *)transport->context;
memset(&report, 0, sizeof(report));
rc = ft_tt_udrv_arc_locate_queue(context, FT_TT_UDRV_ARC_READY_TIMEOUT_MS, &report.locate,
&queue);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_msg_asic_state0(&message);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_exchange_send(transport, &queue, &message, 0u, &report.asic_state0);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_msg_set_wdt_timeout(
(watchdog_ms == 0u) ? FT_TT_UDRV_ARC_WATCHDOG_DEFAULT_MS : watchdog_ms, &message);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_exchange_send(transport, &queue, &message, 0u, &report.set_wdt_timeout);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_power_request_build(max_ai_clock, &message);
if (rc != FT_TT_UDRV_OK)
goto done;
rc = ft_tt_udrv_arc_exchange_send(transport, &queue, &message, 0u, &report.power_setting);
done:
if (out_report)
*out_report = report;
return rc;
}