536 lines
23 KiB
C
536 lines
23 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file test_udrv_arc_msg.c
|
|
* @brief Gate for ft_tt_udrv_arc_msg.c — message encoding, ring arithmetic,
|
|
* and the synchronous exchange, driven against a SIMULATED ARC.
|
|
*
|
|
* The fake firmware in this file implements the PEER side of the protocol
|
|
* from the driver's semantics — it maintains its own pointers with its own
|
|
* arithmetic and never calls a function under test. That is deliberate: a
|
|
* ring exerciser built out of the ring code proves the code is
|
|
* self-consistent and nothing else (DEV_NOTES §1, "never instrument a
|
|
* mechanism with itself").
|
|
*
|
|
* WHAT THIS CANNOT PROVE: that a CSM write lands, that the doorbell is at
|
|
* 0x800B0000, that the QCB pointer is in RESET_SCRATCH(11), or that real
|
|
* firmware answers any of these messages. Those need silicon. What it does
|
|
* prove is that every word this driver would put on the wire is the word the
|
|
* kernel would have put there, and that the ring bookkeeping survives wrap,
|
|
* fullness, staleness and a dead device.
|
|
*
|
|
* @status Off-hardware. No transport here performs I/O of any kind.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "ft_tt_test_report.h"
|
|
#include "ft_tt_udrv.h"
|
|
#include "ft_tt_udrv_arc_msg.h"
|
|
|
|
/** Where the fake queue lives inside the fake CSM. */
|
|
#define FAKE_QUEUE_BASE (FT_TT_UDRV_ARC_CSM_BASE + 0x1000u)
|
|
#define FAKE_ENTRIES 4u
|
|
|
|
/** Enough CSM to hold the header plus both rings, with room to spare. */
|
|
#define FAKE_CSM_WORDS 4096u
|
|
|
|
/**
|
|
* A simulated ARC.
|
|
*
|
|
* `memory` is word-addressed from FT_TT_UDRV_ARC_CSM_BASE. `respond` decides
|
|
* whether the doorbell produces a reply, and `status` is the header the reply
|
|
* carries — zero for success, which is the firmware's convention.
|
|
*/
|
|
typedef struct fake_arc {
|
|
uint32_t memory[FAKE_CSM_WORDS];
|
|
bool respond;
|
|
uint32_t status;
|
|
bool dead; /**< every read returns 0xFFFFFFFF */
|
|
uint32_t reads;
|
|
uint32_t writes;
|
|
uint32_t triggers;
|
|
uint32_t waits;
|
|
uint32_t responses_pushed;
|
|
ft_tt_udrv_arc_msg last_request; /**< what the firmware saw */
|
|
} fake_arc;
|
|
|
|
/** Word index of a CSM address, or FAKE_CSM_WORDS when out of range. */
|
|
static uint32_t fake_word_index(uint32_t address)
|
|
{
|
|
uint32_t offset;
|
|
|
|
if (address < FT_TT_UDRV_ARC_CSM_BASE || (address & 3u) != 0u)
|
|
return FAKE_CSM_WORDS;
|
|
|
|
offset = (address - FT_TT_UDRV_ARC_CSM_BASE) / 4u;
|
|
return (offset < FAKE_CSM_WORDS) ? offset : FAKE_CSM_WORDS;
|
|
}
|
|
|
|
static int fake_read32(void *context, uint32_t address, uint32_t *out_value)
|
|
{
|
|
fake_arc *arc = context;
|
|
uint32_t index = fake_word_index(address);
|
|
|
|
arc->reads++;
|
|
|
|
if (arc->dead) {
|
|
*out_value = 0xFFFFFFFFu;
|
|
return 0;
|
|
}
|
|
|
|
if (index >= FAKE_CSM_WORDS)
|
|
return -1;
|
|
|
|
*out_value = arc->memory[index];
|
|
return 0;
|
|
}
|
|
|
|
static int fake_write32(void *context, uint32_t address, uint32_t value)
|
|
{
|
|
fake_arc *arc = context;
|
|
uint32_t index = fake_word_index(address);
|
|
|
|
arc->writes++;
|
|
|
|
if (index >= FAKE_CSM_WORDS)
|
|
return -1;
|
|
|
|
arc->memory[index] = value;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* The doorbell: consume one request and produce one response.
|
|
*
|
|
* Written from the protocol description, not from the code under test — its
|
|
* pointers, slot arithmetic and modulus are all its own.
|
|
*/
|
|
static int fake_trigger(void *context)
|
|
{
|
|
fake_arc *arc = context;
|
|
uint32_t header_word = (FAKE_QUEUE_BASE - FT_TT_UDRV_ARC_CSM_BASE) / 4u;
|
|
uint32_t req_wptr = arc->memory[header_word + 0u];
|
|
uint32_t res_rptr = arc->memory[header_word + 1u];
|
|
uint32_t req_rptr = arc->memory[header_word + 4u];
|
|
uint32_t res_wptr = arc->memory[header_word + 5u];
|
|
uint32_t request_word_base;
|
|
uint32_t response_word_base;
|
|
uint32_t slot;
|
|
uint32_t i;
|
|
|
|
arc->triggers++;
|
|
|
|
if (!arc->respond)
|
|
return 0;
|
|
|
|
/* Nothing to do if the request ring is empty. */
|
|
if (req_wptr == req_rptr)
|
|
return 0;
|
|
|
|
request_word_base = header_word + (FT_TT_UDRV_ARC_QUEUE_HEADER_BYTES / 4u);
|
|
response_word_base = request_word_base + (FAKE_ENTRIES * (FT_TT_UDRV_ARC_MSG_BYTES / 4u));
|
|
|
|
slot = req_rptr % FAKE_ENTRIES;
|
|
arc->last_request.header = arc->memory[request_word_base + (slot * 8u)];
|
|
for (i = 0u; i < FT_TT_UDRV_ARC_PAYLOAD_WORDS; i++)
|
|
arc->last_request.payload[i] = arc->memory[request_word_base + (slot * 8u) + 1u + i];
|
|
|
|
req_rptr = (req_rptr + 1u) % (2u * FAKE_ENTRIES);
|
|
arc->memory[header_word + 4u] = req_rptr;
|
|
|
|
/* Echo the payload back with the firmware's status in the header word,
|
|
* which is what makes "the response is the request's answer" checkable. */
|
|
slot = res_wptr % FAKE_ENTRIES;
|
|
arc->memory[response_word_base + (slot * 8u)] = arc->status;
|
|
for (i = 0u; i < FT_TT_UDRV_ARC_PAYLOAD_WORDS; i++)
|
|
arc->memory[response_word_base + (slot * 8u) + 1u + i] =
|
|
arc->last_request.payload[i] ^ 0xA5A5A5A5u;
|
|
|
|
res_wptr = (res_wptr + 1u) % (2u * FAKE_ENTRIES);
|
|
arc->memory[header_word + 5u] = res_wptr;
|
|
arc->responses_pushed++;
|
|
|
|
(void)res_rptr;
|
|
return 0;
|
|
}
|
|
|
|
static void fake_wait(void *context)
|
|
{
|
|
fake_arc *arc = context;
|
|
arc->waits++;
|
|
}
|
|
|
|
static void fake_init(fake_arc *arc, ft_tt_udrv_arc_transport *transport)
|
|
{
|
|
memset(arc, 0, sizeof(*arc));
|
|
arc->respond = true;
|
|
arc->status = 0u;
|
|
|
|
transport->csm_read32 = fake_read32;
|
|
transport->csm_write32 = fake_write32;
|
|
transport->trigger = fake_trigger;
|
|
transport->wait = fake_wait;
|
|
transport->context = arc;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ *
|
|
* Stages
|
|
* ------------------------------------------------------------------------ */
|
|
|
|
static void stage_queue_location(void)
|
|
{
|
|
ft_tt_udrv_arc_queue queue;
|
|
|
|
ft_tt_test_step("Stage 1 — locating the queue, and the addresses that follow");
|
|
|
|
ft_tt_test_check_fmt("QCB pointer is RESET_SCRATCH(11) = 0x8003042C",
|
|
FT_TT_UDRV_ARC_MSG_QCB_PTR == 0x8003042Cu, "0x%08x",
|
|
FT_TT_UDRV_ARC_MSG_QCB_PTR);
|
|
ft_tt_test_check_fmt("boot status is RESET_SCRATCH(2) = 0x80030408",
|
|
FT_TT_UDRV_ARC_BOOT_STATUS == 0x80030408u, "0x%08x",
|
|
FT_TT_UDRV_ARC_BOOT_STATUS);
|
|
|
|
ft_tt_test_check("a queue with 4 entries at CSM+0x1000 is accepted",
|
|
ft_tt_udrv_arc_queue_init(FAKE_QUEUE_BASE, FAKE_ENTRIES, &queue) ==
|
|
FT_TT_UDRV_OK,
|
|
"");
|
|
ft_tt_test_check_fmt("entry count is the low byte of QCB word 1",
|
|
queue.num_entries == FAKE_ENTRIES, "%u", queue.num_entries);
|
|
ft_tt_test_check_fmt("requests start 32 bytes past the base",
|
|
queue.request_base == FAKE_QUEUE_BASE + 32u, "0x%08x",
|
|
queue.request_base);
|
|
ft_tt_test_check_fmt("responses start after all 4 requests",
|
|
queue.response_base == FAKE_QUEUE_BASE + 32u + (4u * 32u), "0x%08x",
|
|
queue.response_base);
|
|
ft_tt_test_check_fmt("the four pointer addresses are +0, +4, +0x10, +0x14",
|
|
queue.req_wptr_addr == FAKE_QUEUE_BASE + 0x00u &&
|
|
queue.res_rptr_addr == FAKE_QUEUE_BASE + 0x04u &&
|
|
queue.req_rptr_addr == FAKE_QUEUE_BASE + 0x10u &&
|
|
queue.res_wptr_addr == FAKE_QUEUE_BASE + 0x14u,
|
|
"%08x %08x %08x %08x", queue.req_wptr_addr, queue.res_rptr_addr,
|
|
queue.req_rptr_addr, queue.res_wptr_addr);
|
|
|
|
/* The low byte only: firmware puts other things in the upper bits. */
|
|
ft_tt_test_check_fmt("only the low byte of QCB word 1 is the entry count",
|
|
ft_tt_udrv_arc_queue_init(FAKE_QUEUE_BASE, 0xDEAD0004u, &queue) ==
|
|
FT_TT_UDRV_OK &&
|
|
queue.num_entries == 4u,
|
|
"%u", queue.num_entries);
|
|
|
|
ft_tt_test_check("a zero-entry queue is refused",
|
|
ft_tt_udrv_arc_queue_init(FAKE_QUEUE_BASE, 0u, &queue) == FT_TT_UDRV_EINVAL,
|
|
"would divide by zero");
|
|
|
|
ft_tt_test_check("a queue below the CSM is refused",
|
|
ft_tt_udrv_arc_queue_init(0x1000u, 4u, &queue) == FT_TT_UDRV_EINVAL,
|
|
"outside 0x10000000");
|
|
|
|
ft_tt_test_check("a queue running off the end of the CSM is refused",
|
|
ft_tt_udrv_arc_queue_init(FT_TT_UDRV_ARC_CSM_BASE +
|
|
FT_TT_UDRV_ARC_CSM_SIZE - 64u,
|
|
4u, &queue) == FT_TT_UDRV_EINVAL,
|
|
"the ring would not fit");
|
|
|
|
ft_tt_test_check("a 255-entry queue that does not fit is refused",
|
|
ft_tt_udrv_arc_queue_init(FT_TT_UDRV_ARC_CSM_BASE +
|
|
FT_TT_UDRV_ARC_CSM_SIZE - 0x2000u,
|
|
255u, &queue) == FT_TT_UDRV_EINVAL,
|
|
"255 * 2 * 32 = 16320 bytes");
|
|
}
|
|
|
|
static void stage_ring_arithmetic(void)
|
|
{
|
|
uint32_t occupancy = 0u;
|
|
uint32_t pointer = 0u;
|
|
|
|
ft_tt_test_step("Stage 2 — ring arithmetic, including a non-power-of-two ring");
|
|
|
|
ft_tt_test_check_fmt("empty ring", ft_tt_udrv_arc_ring_occupancy(0u, 0u, 4u, &occupancy) ==
|
|
FT_TT_UDRV_OK &&
|
|
occupancy == 0u,
|
|
"%u", occupancy);
|
|
ft_tt_test_check_fmt("three in flight",
|
|
ft_tt_udrv_arc_ring_occupancy(3u, 0u, 4u, &occupancy) == FT_TT_UDRV_OK &&
|
|
occupancy == 3u,
|
|
"%u", occupancy);
|
|
ft_tt_test_check_fmt("full ring (wptr has lapped into the second half)",
|
|
ft_tt_udrv_arc_ring_occupancy(4u, 0u, 4u, &occupancy) == FT_TT_UDRV_OK &&
|
|
occupancy == 4u,
|
|
"%u", occupancy);
|
|
ft_tt_test_check_fmt("wrapped: wptr 1, rptr 7, 4 entries",
|
|
ft_tt_udrv_arc_ring_occupancy(1u, 7u, 4u, &occupancy) == FT_TT_UDRV_OK &&
|
|
occupancy == 2u,
|
|
"%u", occupancy);
|
|
|
|
ft_tt_test_check("a pointer at or past 2N is refused",
|
|
ft_tt_udrv_arc_ring_occupancy(8u, 0u, 4u, &occupancy) == FT_TT_UDRV_EINVAL,
|
|
"a wild pointer must not read as a full ring");
|
|
|
|
/* N not a power of two: a single u32 modular subtraction would get this wrong. */
|
|
occupancy = 0xFFFFFFFFu;
|
|
ft_tt_test_check_fmt("wrapped with N=3 (wptr 1, rptr 5): occupancy 2",
|
|
ft_tt_udrv_arc_ring_occupancy(1u, 5u, 3u, &occupancy) == FT_TT_UDRV_OK &&
|
|
occupancy == 2u,
|
|
"%u", occupancy);
|
|
|
|
ft_tt_test_check_fmt("advance wraps at 2N",
|
|
ft_tt_udrv_arc_ring_advance(7u, 4u, &pointer) == FT_TT_UDRV_OK &&
|
|
pointer == 0u,
|
|
"%u", pointer);
|
|
ft_tt_test_check_fmt("advance is ordinary in the middle",
|
|
ft_tt_udrv_arc_ring_advance(3u, 4u, &pointer) == FT_TT_UDRV_OK &&
|
|
pointer == 4u,
|
|
"%u", pointer);
|
|
}
|
|
|
|
static void stage_slot_addresses(void)
|
|
{
|
|
ft_tt_udrv_arc_queue queue;
|
|
uint32_t address = 0u;
|
|
|
|
ft_tt_test_step("Stage 3 — slot addresses fold the pointer modulo N, not 2N");
|
|
|
|
(void)ft_tt_udrv_arc_queue_init(FAKE_QUEUE_BASE, FAKE_ENTRIES, &queue);
|
|
|
|
ft_tt_test_check_fmt("request slot 0 word 0",
|
|
ft_tt_udrv_arc_request_word_address(&queue, 0u, 0u, &address) ==
|
|
FT_TT_UDRV_OK &&
|
|
address == queue.request_base,
|
|
"0x%08x", address);
|
|
|
|
ft_tt_test_check_fmt("request slot 0 word 7 is 28 bytes in",
|
|
ft_tt_udrv_arc_request_word_address(&queue, 0u, 7u, &address) ==
|
|
FT_TT_UDRV_OK &&
|
|
address == queue.request_base + 28u,
|
|
"0x%08x", address);
|
|
|
|
/* wptr 4 and wptr 0 are the same SLOT — the extra bit only distinguishes
|
|
* "full" from "empty", which is the whole reason the pointers run to 2N. */
|
|
ft_tt_test_check_fmt("wptr 4 addresses the same slot as wptr 0",
|
|
ft_tt_udrv_arc_request_word_address(&queue, 4u, 0u, &address) ==
|
|
FT_TT_UDRV_OK &&
|
|
address == queue.request_base,
|
|
"0x%08x", address);
|
|
|
|
ft_tt_test_check_fmt("response slot 3 word 0",
|
|
ft_tt_udrv_arc_response_word_address(&queue, 3u, 0u, &address) ==
|
|
FT_TT_UDRV_OK &&
|
|
address == queue.response_base + (3u * 32u),
|
|
"0x%08x", address);
|
|
|
|
ft_tt_test_check("word 8 is refused (a message is 8 words)",
|
|
ft_tt_udrv_arc_request_word_address(&queue, 0u, 8u, &address) ==
|
|
FT_TT_UDRV_EINVAL,
|
|
"");
|
|
|
|
ft_tt_test_check("a pointer at 2N is refused",
|
|
ft_tt_udrv_arc_request_word_address(&queue, 8u, 0u, &address) ==
|
|
FT_TT_UDRV_EINVAL,
|
|
"");
|
|
|
|
ft_tt_test_check_fmt("request and response rings do not overlap",
|
|
queue.response_base >= queue.request_base + (FAKE_ENTRIES * 32u),
|
|
"0x%08x vs 0x%08x", queue.response_base,
|
|
queue.request_base + (FAKE_ENTRIES * 32u));
|
|
}
|
|
|
|
static void stage_message_encodings(void)
|
|
{
|
|
ft_tt_udrv_arc_msg msg;
|
|
uint16_t settings[14];
|
|
unsigned i;
|
|
|
|
ft_tt_test_step("Stage 4 — the exact words tt-kmd puts on the wire");
|
|
|
|
(void)ft_tt_udrv_arc_msg_asic_state0(&msg);
|
|
ft_tt_test_check_fmt("ASIC_STATE0 is header 0xA0 with an empty payload",
|
|
msg.header == 0xA0u && msg.payload[0] == 0u && msg.payload[6] == 0u,
|
|
"0x%08x", msg.header);
|
|
|
|
(void)ft_tt_udrv_arc_msg_asic_state3(&msg);
|
|
ft_tt_test_check_fmt("ASIC_STATE3 is header 0xA3", msg.header == 0xA3u, "0x%08x", msg.header);
|
|
|
|
(void)ft_tt_udrv_arc_msg_test(&msg);
|
|
ft_tt_test_check_fmt("TEST is header 0x90", msg.header == 0x90u, "0x%08x", msg.header);
|
|
|
|
(void)ft_tt_udrv_arc_msg_set_wdt_timeout(10000u, &msg);
|
|
ft_tt_test_check_fmt("SET_WDT_TIMEOUT is 0xC1 with the timeout in payload[0]",
|
|
msg.header == 0xC1u && msg.payload[0] == 10000u, "0x%08x / %u",
|
|
msg.header, msg.payload[0]);
|
|
ft_tt_test_check_fmt("...and 10000 ms is the driver's own default (10 s)",
|
|
msg.payload[0] == 10u * 1000u, "%u", msg.payload[0]);
|
|
|
|
(void)ft_tt_udrv_arc_msg_trigger_reset(3u, &msg);
|
|
ft_tt_test_check_fmt("TRIGGER_RESET is 0x56 with argument 3 (ASIC + M3)",
|
|
msg.header == 0x56u && msg.payload[0] == 3u, "0x%08x / %u", msg.header,
|
|
msg.payload[0]);
|
|
|
|
/* THE ONE THAT MATTERS MOST: this is what sets the clock at open. */
|
|
(void)ft_tt_udrv_arc_msg_power_setting_open_default(&msg);
|
|
ft_tt_test_check_fmt("POWER_SETTING at open is header 0x7FFE0F21", msg.header == 0x7FFE0F21u,
|
|
"0x%08x", msg.header);
|
|
ft_tt_test_check_fmt("...i.e. every feature ON except MAX_AI_CLK",
|
|
((msg.header >> 16) & 0xFFFFu) ==
|
|
(FT_TT_UDRV_ARC_POWER_FLAG_ALL &
|
|
~FT_TT_UDRV_ARC_POWER_FLAG_MAX_AI_CLK),
|
|
"flags=0x%04x", (msg.header >> 16) & 0xFFFFu);
|
|
ft_tt_test_check_fmt("...with validity 0x0F (15 flags, 0 settings)",
|
|
((msg.header >> 8) & 0xFFu) == 0x0Fu, "0x%02x",
|
|
(msg.header >> 8) & 0xFFu);
|
|
|
|
(void)ft_tt_udrv_arc_msg_power_setting_last_close(&msg);
|
|
ft_tt_test_check_fmt("POWER_SETTING at last close is header 0x00000F21 — everything off",
|
|
msg.header == 0x00000F21u, "0x%08x", msg.header);
|
|
|
|
for (i = 0u; i < 14u; i++)
|
|
settings[i] = (uint16_t)(0x1000u + i);
|
|
|
|
(void)ft_tt_udrv_arc_msg_power_setting(0x2Fu, 0x0005u, settings, &msg);
|
|
ft_tt_test_check_fmt("a settings payload packs two u16 per word, little-endian first",
|
|
msg.payload[0] == 0x10011000u && msg.payload[6] == 0x100D100Cu,
|
|
"%08x .. %08x", msg.payload[0], msg.payload[6]);
|
|
ft_tt_test_check_fmt("...and validity/flags land in bytes 1 and 2-3",
|
|
msg.header == (0x21u | (0x2Fu << 8) | (0x0005u << 16)), "0x%08x",
|
|
msg.header);
|
|
|
|
ft_tt_test_check("a NULL output is refused",
|
|
ft_tt_udrv_arc_msg_asic_state0(NULL) == FT_TT_UDRV_EINVAL, "");
|
|
}
|
|
|
|
static void stage_push_pop(void)
|
|
{
|
|
fake_arc arc;
|
|
ft_tt_udrv_arc_transport transport;
|
|
ft_tt_udrv_arc_queue queue;
|
|
ft_tt_udrv_arc_msg msg;
|
|
uint32_t index;
|
|
|
|
ft_tt_test_step("Stage 5 — push and pop against the simulated ARC");
|
|
|
|
fake_init(&arc, &transport);
|
|
(void)ft_tt_udrv_arc_queue_init(FAKE_QUEUE_BASE, FAKE_ENTRIES, &queue);
|
|
|
|
arc.respond = false; /* the doorbell does nothing, so the ring just fills */
|
|
|
|
(void)ft_tt_udrv_arc_msg_asic_state0(&msg);
|
|
ft_tt_test_check("the first push succeeds",
|
|
ft_tt_udrv_arc_try_push(&transport, &queue, &msg) == FT_TT_UDRV_OK, "");
|
|
|
|
ft_tt_test_check_fmt("the request landed in slot 0",
|
|
arc.memory[fake_word_index(queue.request_base)] == 0xA0u, "0x%08x",
|
|
arc.memory[fake_word_index(queue.request_base)]);
|
|
ft_tt_test_check_fmt("...and the write pointer advanced to 1",
|
|
arc.memory[fake_word_index(queue.req_wptr_addr)] == 1u, "%u",
|
|
arc.memory[fake_word_index(queue.req_wptr_addr)]);
|
|
|
|
ft_tt_test_check("popping an empty response ring is EAGAIN, not an error",
|
|
ft_tt_udrv_arc_try_pop(&transport, &queue, &msg) == FT_TT_UDRV_EAGAIN, "");
|
|
|
|
for (index = 1u; index < FAKE_ENTRIES; index++)
|
|
(void)ft_tt_udrv_arc_try_push(&transport, &queue, &msg);
|
|
|
|
ft_tt_test_check("a full request ring refuses with EAGAIN",
|
|
ft_tt_udrv_arc_try_push(&transport, &queue, &msg) == FT_TT_UDRV_EAGAIN,
|
|
"4 of 4 slots occupied");
|
|
|
|
/* A dead device must be diagnosed as one, not treated as a wild pointer. */
|
|
arc.dead = true;
|
|
ft_tt_test_check("an all-ones pointer read is EIO, not arithmetic",
|
|
ft_tt_udrv_arc_try_push(&transport, &queue, &msg) == FT_TT_UDRV_EIO,
|
|
"0xFFFFFFFF means the NoC is gone");
|
|
ft_tt_test_check("...and the same on the pop side",
|
|
ft_tt_udrv_arc_try_pop(&transport, &queue, &msg) == FT_TT_UDRV_EIO, "");
|
|
}
|
|
|
|
static void stage_send_sync(void)
|
|
{
|
|
fake_arc arc;
|
|
ft_tt_udrv_arc_transport transport;
|
|
ft_tt_udrv_arc_queue queue;
|
|
ft_tt_udrv_arc_msg msg;
|
|
uint32_t round;
|
|
|
|
ft_tt_test_step("Stage 6 — the synchronous exchange");
|
|
|
|
fake_init(&arc, &transport);
|
|
(void)ft_tt_udrv_arc_queue_init(FAKE_QUEUE_BASE, FAKE_ENTRIES, &queue);
|
|
|
|
(void)ft_tt_udrv_arc_msg_set_wdt_timeout(10000u, &msg);
|
|
ft_tt_test_check("a well-formed exchange succeeds",
|
|
ft_tt_udrv_arc_send_sync(&transport, &queue, &msg, 8u) == FT_TT_UDRV_OK, "");
|
|
ft_tt_test_check_fmt("the firmware saw the message we built",
|
|
arc.last_request.header == 0xC1u && arc.last_request.payload[0] == 10000u,
|
|
"0x%08x / %u", arc.last_request.header, arc.last_request.payload[0]);
|
|
ft_tt_test_check_fmt("the doorbell was rung exactly once", arc.triggers == 1u, "%u",
|
|
arc.triggers);
|
|
ft_tt_test_check_fmt("the response overwrote the request, as the driver does",
|
|
msg.header == 0u && msg.payload[0] == (10000u ^ 0xA5A5A5A5u),
|
|
"0x%08x / 0x%08x", msg.header, msg.payload[0]);
|
|
|
|
/* Round-trip the ring several times so a wrap bug cannot hide behind a
|
|
* single exchange. */
|
|
for (round = 0u; round < 11u; round++) {
|
|
(void)ft_tt_udrv_arc_msg_asic_state0(&msg);
|
|
if (ft_tt_udrv_arc_send_sync(&transport, &queue, &msg, 8u) != FT_TT_UDRV_OK)
|
|
break;
|
|
}
|
|
ft_tt_test_check_fmt("11 more exchanges wrap both rings without wedging", round == 11u,
|
|
"completed %u of 11", round);
|
|
|
|
/* A firmware error must be surfaced AND its status preserved. */
|
|
fake_init(&arc, &transport);
|
|
arc.status = 0xDEADu;
|
|
(void)ft_tt_udrv_arc_msg_asic_state0(&msg);
|
|
ft_tt_test_check("a nonzero response header is a failure",
|
|
ft_tt_udrv_arc_send_sync(&transport, &queue, &msg, 8u) == FT_TT_UDRV_EIO,
|
|
"");
|
|
ft_tt_test_check_fmt("...and the firmware's status is left where the caller can read it",
|
|
msg.header == 0xDEADu, "0x%08x", msg.header);
|
|
|
|
/* No response: bounded, and it says EAGAIN rather than hanging. */
|
|
fake_init(&arc, &transport);
|
|
arc.respond = false;
|
|
(void)ft_tt_udrv_arc_msg_asic_state0(&msg);
|
|
ft_tt_test_check("a silent firmware exhausts the poll budget",
|
|
ft_tt_udrv_arc_send_sync(&transport, &queue, &msg, 5u) == FT_TT_UDRV_EAGAIN,
|
|
"");
|
|
ft_tt_test_check_fmt("...having waited exactly the budget", arc.waits == 5u, "%u waits",
|
|
arc.waits);
|
|
|
|
/* A stale response from an abandoned exchange must be drained, not
|
|
* mistaken for this exchange's answer. */
|
|
fake_init(&arc, &transport);
|
|
arc.respond = false;
|
|
(void)ft_tt_udrv_arc_msg_asic_state0(&msg);
|
|
(void)ft_tt_udrv_arc_try_push(&transport, &queue, &msg);
|
|
arc.respond = true;
|
|
(void)fake_trigger(&arc); /* firmware answers the ABANDONED request */
|
|
arc.responses_pushed = 0u;
|
|
|
|
(void)ft_tt_udrv_arc_msg_set_wdt_timeout(1234u, &msg);
|
|
ft_tt_test_check("a stale response is drained before the new request",
|
|
ft_tt_udrv_arc_send_sync(&transport, &queue, &msg, 8u) == FT_TT_UDRV_OK, "");
|
|
ft_tt_test_check_fmt("...and the answer belongs to the NEW request",
|
|
msg.payload[0] == (1234u ^ 0xA5A5A5A5u), "0x%08x", msg.payload[0]);
|
|
|
|
ft_tt_test_check("a transport missing an operation is refused",
|
|
ft_tt_udrv_arc_send_sync(NULL, &queue, &msg, 1u) == FT_TT_UDRV_EINVAL, "");
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
stage_queue_location();
|
|
stage_ring_arithmetic();
|
|
stage_slot_addresses();
|
|
stage_message_encodings();
|
|
stage_push_pop();
|
|
stage_send_sync();
|
|
|
|
return ft_tt_test_finish();
|
|
}
|