Foundry/tt/ft_tt_udrv_tlb_encode.c

297 lines
11 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_udrv_tlb_encode.c
* @brief Implementation of the Blackhole 2 MiB TLB window register encoder.
*
* @status Off-hardware only.
* @issues None
* @todo None
*/
#include "ft_tt_udrv_tlb_encode.h"
#include <stdint.h>
#include "ft_tt_udrv.h"
/* ------------------------------------------------------------------------ *
* The register layout.
*
* tt-kmd expresses this as a packed bitfield struct over a 12-byte union. Bitfields in a packed little-endian struct allocate
* from the least significant bit upward and straddle byte and word boundaries
* freely, so the layout below IS that struct, written as offsets.
*
* Widths are derived by accumulation so that inserting a field cannot leave a
* stale offset behind. The literal PINS immediately after are deliberately NOT
* derived: a derived pin re-derives whatever mistake the accumulation made and
* agrees with itself (DEV_NOTES §3, "a struct assert that proves
* SELF-CONSISTENCY does not prove the struct has not MOVED").
* ------------------------------------------------------------------------ */
#define W_ADDRESS 43u
#define W_X_END 6u
#define W_Y_END 6u
#define W_X_START 6u
#define W_Y_START 6u
#define W_NOC 2u
#define W_MULTICAST 1u
#define W_ORDERING 2u
#define W_LINKED 1u
#define W_USE_STATIC_VC 1u
#define W_STREAM_HEADER 1u
#define W_STATIC_VC 3u
#define B_ADDRESS 0u
#define B_X_END (B_ADDRESS + W_ADDRESS)
#define B_Y_END (B_X_END + W_X_END)
#define B_X_START (B_Y_END + W_Y_END)
#define B_Y_START (B_X_START + W_X_START)
#define B_NOC (B_Y_START + W_Y_START)
#define B_MULTICAST (B_NOC + W_NOC)
#define B_ORDERING (B_MULTICAST + W_MULTICAST)
#define B_LINKED (B_ORDERING + W_ORDERING)
#define B_USE_STATIC_VC (B_LINKED + W_LINKED)
#define B_STREAM_HEADER (B_USE_STATIC_VC + W_USE_STATIC_VC)
#define B_STATIC_VC (B_STREAM_HEADER + W_STREAM_HEADER)
/* Literal pins. Read these off the TLB field list in ft_tt_udrv_tlb_encode.h by hand
* and change them only when that list changes. */
FT_TT_UDRV_STATIC_ASSERT(B_ADDRESS == 0, address_at_bit_0);
FT_TT_UDRV_STATIC_ASSERT(B_X_END == 43, x_end_at_bit_43);
FT_TT_UDRV_STATIC_ASSERT(B_Y_END == 49, y_end_at_bit_49);
FT_TT_UDRV_STATIC_ASSERT(B_X_START == 55, x_start_at_bit_55);
FT_TT_UDRV_STATIC_ASSERT(B_Y_START == 61, y_start_at_bit_61);
FT_TT_UDRV_STATIC_ASSERT(B_NOC == 67, noc_at_bit_67);
FT_TT_UDRV_STATIC_ASSERT(B_MULTICAST == 69, multicast_at_bit_69);
FT_TT_UDRV_STATIC_ASSERT(B_ORDERING == 70, ordering_at_bit_70);
FT_TT_UDRV_STATIC_ASSERT(B_LINKED == 72, linked_at_bit_72);
FT_TT_UDRV_STATIC_ASSERT(B_USE_STATIC_VC == 73, use_static_vc_at_bit_73);
FT_TT_UDRV_STATIC_ASSERT(B_STREAM_HEADER == 74, stream_header_at_bit_74);
FT_TT_UDRV_STATIC_ASSERT(B_STATIC_VC == 75, static_vc_at_bit_75);
FT_TT_UDRV_STATIC_ASSERT(B_STATIC_VC + W_STATIC_VC == 78, eighteen_reserved_bits);
/* y_start MUST straddle the mid32/high32 boundary — that is the whole reason
* this file has a reference-vector gate rather than a hand-written table. */
FT_TT_UDRV_STATIC_ASSERT(B_Y_START < 64 && (B_Y_START + W_Y_START) > 64, y_start_straddles);
/** The 2 MiB alignment the window's address field cannot express. */
#define TLB_2M_WINDOW_MASK ((uint64_t)FT_TT_UDRV_TLB_2M_WINDOW_SIZE - 1u)
/**
* Deposit `value` at bit `bit` of a 96-bit register held as a low 64 bits and
* a high 32 bits, truncating to `width`.
*
* The straddling case is the whole point: a field that starts below bit 64 and
* ends above it contributes its low part to `lo` (where the shift discards the
* overflow for us) and its high part to `hi`.
*/
static void put_field(uint64_t *lo, uint64_t *hi, unsigned bit, unsigned width, uint64_t value)
{
uint64_t mask = (width >= 64u) ? UINT64_MAX : ((UINT64_C(1) << width) - 1u);
value &= mask;
if (bit >= 64u) {
*hi |= value << (bit - 64u);
return;
}
*lo |= value << bit;
if (bit + width > 64u)
*hi |= value >> (64u - bit);
}
/**
* Extract the `width`-bit field at bit `bit` from a 96-bit register held as a
* low 64 bits and a high 32 bits.
*
* Written as its own traversal rather than as put_field() run backwards: the
* whole value of the decoder is that it is a SECOND reading of the layout, so
* a shift that is wrong in one direction has a chance of being caught rather
* than cancelling out. The literal bit pins above are shared, deliberately —
* they are pinned against the public field table (HostToDeviceTLBs.md), not
* against this file.
*/
static uint64_t get_field(uint64_t lo, uint64_t hi, unsigned bit, unsigned width)
{
uint64_t mask = (width >= 64u) ? UINT64_MAX : ((UINT64_C(1) << width) - 1u);
uint64_t value;
if (bit >= 64u)
return (hi >> (bit - 64u)) & mask;
value = lo >> bit;
/* The straddle. `bit` cannot be 0 here — reaching this branch needs
* bit + width > 64 with width <= 64 — so the shift is always < 64. */
if (bit + width > 64u)
value |= hi << (64u - bit);
return value & mask;
}
/** True when `value` fits in `width` bits. */
static bool fits(uint64_t value, unsigned width)
{
uint64_t mask = (width >= 64u) ? UINT64_MAX : ((UINT64_C(1) << width) - 1u);
return (value & ~mask) == 0u;
}
int ft_tt_udrv_tlb2m_encode(const ft_tt_udrv_tlb2m_config *config, uint32_t out_words[3])
{
uint64_t lo = 0;
uint64_t hi = 0;
if (!config || !out_words)
return FT_TT_UDRV_EINVAL;
/* The kernel's own refusal: a 2 MiB window cannot start off a 2 MiB
* boundary, because the register holds addr >> 21. */
if ((config->addr & TLB_2M_WINDOW_MASK) != 0u)
return FT_TT_UDRV_EINVAL;
put_field(&lo, &hi, B_ADDRESS, W_ADDRESS, config->addr >> FT_TT_UDRV_TLB_2M_SHIFT);
put_field(&lo, &hi, B_X_END, W_X_END, config->x_end);
put_field(&lo, &hi, B_Y_END, W_Y_END, config->y_end);
put_field(&lo, &hi, B_X_START, W_X_START, config->x_start);
put_field(&lo, &hi, B_Y_START, W_Y_START, config->y_start);
put_field(&lo, &hi, B_NOC, W_NOC, config->noc);
put_field(&lo, &hi, B_MULTICAST, W_MULTICAST, config->multicast);
put_field(&lo, &hi, B_ORDERING, W_ORDERING, config->ordering);
put_field(&lo, &hi, B_LINKED, W_LINKED, config->linked);
put_field(&lo, &hi, B_USE_STATIC_VC, W_USE_STATIC_VC, config->use_static_vc);
put_field(&lo, &hi, B_STREAM_HEADER, W_STREAM_HEADER, config->stream_header);
put_field(&lo, &hi, B_STATIC_VC, W_STATIC_VC, config->static_vc_id);
out_words[0] = (uint32_t)(lo & 0xFFFFFFFFu);
out_words[1] = (uint32_t)(lo >> 32);
out_words[2] = (uint32_t)(hi & 0xFFFFFFFFu);
return FT_TT_UDRV_OK;
}
int ft_tt_udrv_tlb2m_decode(const uint32_t words[3], ft_tt_udrv_tlb2m_config *out_config)
{
uint64_t lo;
uint64_t hi;
if (!words || !out_config)
return FT_TT_UDRV_EINVAL;
lo = (uint64_t)words[0] | ((uint64_t)words[1] << 32);
hi = (uint64_t)words[2];
out_config->addr = get_field(lo, hi, B_ADDRESS, W_ADDRESS) << FT_TT_UDRV_TLB_2M_SHIFT;
out_config->x_end = (uint8_t)get_field(lo, hi, B_X_END, W_X_END);
out_config->y_end = (uint8_t)get_field(lo, hi, B_Y_END, W_Y_END);
out_config->x_start = (uint8_t)get_field(lo, hi, B_X_START, W_X_START);
out_config->y_start = (uint8_t)get_field(lo, hi, B_Y_START, W_Y_START);
out_config->noc = (uint8_t)get_field(lo, hi, B_NOC, W_NOC);
out_config->multicast = (uint8_t)get_field(lo, hi, B_MULTICAST, W_MULTICAST);
out_config->ordering = (uint8_t)get_field(lo, hi, B_ORDERING, W_ORDERING);
out_config->linked = (uint8_t)get_field(lo, hi, B_LINKED, W_LINKED);
out_config->use_static_vc = (uint8_t)get_field(lo, hi, B_USE_STATIC_VC, W_USE_STATIC_VC);
out_config->stream_header = (uint8_t)get_field(lo, hi, B_STREAM_HEADER, W_STREAM_HEADER);
out_config->static_vc_id = (uint8_t)get_field(lo, hi, B_STATIC_VC, W_STATIC_VC);
return FT_TT_UDRV_OK;
}
bool ft_tt_udrv_tlb2m_config_equal(const ft_tt_udrv_tlb2m_config *left,
const ft_tt_udrv_tlb2m_config *right)
{
if (!left || !right)
return false;
return left->addr == right->addr && left->x_end == right->x_end &&
left->y_end == right->y_end && left->x_start == right->x_start &&
left->y_start == right->y_start && left->noc == right->noc &&
left->multicast == right->multicast && left->ordering == right->ordering &&
left->linked == right->linked && left->use_static_vc == right->use_static_vc &&
left->stream_header == right->stream_header &&
left->static_vc_id == right->static_vc_id;
}
int ft_tt_udrv_tlb2m_config_check(const ft_tt_udrv_tlb2m_config *config)
{
if (!config)
return FT_TT_UDRV_EINVAL;
if ((config->addr & TLB_2M_WINDOW_MASK) != 0u)
return FT_TT_UDRV_EINVAL;
if (!fits(config->x_end, W_X_END) || !fits(config->y_end, W_Y_END))
return FT_TT_UDRV_EINVAL;
if (!fits(config->x_start, W_X_START) || !fits(config->y_start, W_Y_START))
return FT_TT_UDRV_EINVAL;
if (!fits(config->noc, W_NOC) || !fits(config->ordering, W_ORDERING))
return FT_TT_UDRV_EINVAL;
if (!fits(config->multicast, W_MULTICAST) || !fits(config->linked, W_LINKED))
return FT_TT_UDRV_EINVAL;
if (!fits(config->use_static_vc, W_USE_STATIC_VC) ||
!fits(config->stream_header, W_STREAM_HEADER))
return FT_TT_UDRV_EINVAL;
if (!fits(config->static_vc_id, W_STATIC_VC))
return FT_TT_UDRV_EINVAL;
return FT_TT_UDRV_OK;
}
int ft_tt_udrv_tlb2m_config_unicast(ft_tt_udrv_tlb2m_config *out_config, uint8_t x, uint8_t y,
uint64_t addr, uint8_t noc, uint8_t ordering)
{
ft_tt_udrv_tlb2m_config config = {0};
if (!out_config)
return FT_TT_UDRV_EINVAL;
config.addr = addr;
config.x_end = x;
config.y_end = y;
config.noc = noc;
config.ordering = ordering;
if (ft_tt_udrv_tlb2m_config_check(&config) != FT_TT_UDRV_OK)
return FT_TT_UDRV_EINVAL;
*out_config = config;
return FT_TT_UDRV_OK;
}
int ft_tt_udrv_tlb2m_register_offset(uint32_t id, uint32_t *out_offset)
{
if (!out_offset || id >= FT_TT_UDRV_TLB_2M_WINDOW_COUNT)
return FT_TT_UDRV_EINVAL;
*out_offset = FT_TT_UDRV_TLB_REGS_START + (id * FT_TT_UDRV_TLB_REG_SIZE);
return FT_TT_UDRV_OK;
}
int ft_tt_udrv_tlb2m_strided_register_offset(uint32_t id, uint32_t *out_offset)
{
if (!out_offset || id >= FT_TT_UDRV_TLB_2M_WINDOW_COUNT)
return FT_TT_UDRV_EINVAL;
if (id >= FT_TT_UDRV_TLB_STRIDED_COUNT)
return FT_TT_UDRV_ENOTSUP;
*out_offset = FT_TT_UDRV_TLB_REGS_START + FT_TT_UDRV_TLB_STRIDED_REGS_OFFSET +
(id * FT_TT_UDRV_TLB_STRIDED_REG_SIZE);
return FT_TT_UDRV_OK;
}
int ft_tt_udrv_tlb2m_aperture_offset(uint32_t id, uint64_t *out_offset)
{
if (!out_offset || id >= FT_TT_UDRV_TLB_2M_WINDOW_COUNT)
return FT_TT_UDRV_EINVAL;
*out_offset = (uint64_t)id * (uint64_t)FT_TT_UDRV_TLB_2M_WINDOW_SIZE;
return FT_TT_UDRV_OK;
}