Foundry/tt/ft_tt_buffer.c

271 lines
10 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_buffer.c
* @brief Implementation of the L1/DRAM bump allocator: arena setup from live
* telemetry, aligned carve-off, and a LIFO free that refuses rather than
* corrupts.
*
* @status Written on the Mac and unit-tested off-hardware (tests/test_noc.c).
* @issues None
* @todo None
*/
#include "ft_tt_buffer.h"
#include <string.h>
/* ------------------------------------------------------------------------- */
/* Arena helpers */
/* ------------------------------------------------------------------------- */
/** Round `value` up to a multiple of `alignment`, which is a power of two.
* Returns 0 on overflow so the caller can refuse. */
static uint64_t align_up(uint64_t value, uint32_t alignment)
{
uint64_t mask = (uint64_t)alignment - 1u;
if (value > UINT64_MAX - mask)
return 0u;
return (value + mask) & ~mask;
}
/** The arena for (space, bank), or NULL if the pair names no arena. */
static ft_tt_arena *arena_for(ft_tt_allocator *allocator, ft_tt_memory_space space,
uint32_t logical_bank)
{
if (!allocator || !allocator->initialized)
return NULL;
switch (space) {
case FT_TT_MEM_SPACE_L1:
return logical_bank == 0u ? &allocator->l1 : NULL;
case FT_TT_MEM_SPACE_DRAM:
return logical_bank < allocator->dram_bank_count ? &allocator->dram[logical_bank] : NULL;
default:
return NULL;
}
}
static const ft_tt_arena *arena_for_const(const ft_tt_allocator *allocator,
ft_tt_memory_space space, uint32_t logical_bank)
{
/* One implementation, and the const cast never escapes this function. */
return arena_for((ft_tt_allocator *)(uintptr_t)allocator, space, logical_bank);
}
/* ------------------------------------------------------------------------- */
/* Lifecycle */
/* ------------------------------------------------------------------------- */
int ft_tt_allocator_init(ft_tt_allocator *allocator, ft_tt_ctx *ctx, const ft_tt_grid_info *grid,
uint64_t l1_floor)
{
uint64_t floor;
uint32_t bank;
if (!allocator || !grid)
return FT_TT_EINVAL;
/* The bank count is only knowable from ARC telemetry (spec §3.4). Refuse
* rather than fall back to the nominal 8. */
if (!grid->valid) {
ft_tt_log_error("allocator: the grid has not been read from ARC telemetry");
return FT_TT_EUNAVAIL;
}
/* Every bound below is the LIVE one or the selected part's; the only constant
* involved is this backend's own array capacity. ft_tt_buffer.h's header note
* says why those are kept as three separate numbers. */
if (!grid->arch) {
ft_tt_log_error("allocator: the grid names no architecture, so no DRAM geometry is "
"known — refusing rather than assuming a part");
return FT_TT_EINVAL;
}
if (grid->dram_bank_count == 0u || grid->dram_bank_count > FT_TT_MAX_DRAM_BANKS) {
ft_tt_log_error("allocator: %u DRAM banks is outside this backend's capacity of %u arenas",
grid->dram_bank_count, FT_TT_MAX_DRAM_BANKS);
return FT_TT_EINVAL;
}
if (grid->dram_bank_count > grid->arch->dram.bank_count) {
/* Telemetry claiming more banks than the part has means the mask was
* misread. Allocating in a bank that is not there is a NoC write to
* nowhere, which is not a failure this would notice later. */
ft_tt_log_error("allocator: telemetry reports %u DRAM banks but %s has %u — refusing",
grid->dram_bank_count, grid->arch->name, grid->arch->dram.bank_count);
return FT_TT_EINVAL;
}
floor = (l1_floor == 0u) ? (uint64_t)FT_TT_L1_USABLE_BASE : l1_floor;
floor = align_up(floor, FT_TT_L1_ALIGNMENT);
if (floor < (uint64_t)FT_TT_L1_USABLE_BASE || floor >= (uint64_t)FT_TT_MEM_L1_SIZE) {
/* Below FT_TT_L1_USABLE_BASE is firmware, mailboxes and routing tables.
* Handing that out does not fail a test, it wedges a core. */
ft_tt_log_error("allocator: L1 floor 0x%llx is outside [0x%x, 0x%x)",
(unsigned long long)floor, FT_TT_L1_USABLE_BASE, FT_TT_MEM_L1_SIZE);
return FT_TT_EINVAL;
}
memset(allocator, 0, sizeof(*allocator));
allocator->ctx = ctx;
allocator->dram_bank_count = grid->dram_bank_count;
allocator->l1.floor = floor;
/* FT_TT_MEM_L1_SIZE, never FT_TT_MEM_MAX_KERNEL_SIZE — the latter is 896
* bytes larger than the L1 above MEM_MAP_END (ft_tt_memmap.h @issues). */
allocator->l1.ceiling = (uint64_t)FT_TT_MEM_L1_SIZE;
allocator->l1.cursor = floor;
/* Bank size from the SELECTED part, not from the compiled-in memory map. The
* two agree by construction — ft_tt_arch_check_headers() refuses a part where
* they do not — so this is the same number today and one fewer Blackhole
* constant on the path. */
for (bank = 0; bank < allocator->dram_bank_count; bank++) {
allocator->dram[bank].floor = 0u;
allocator->dram[bank].ceiling = grid->arch->core.dram_bytes_per_bank;
allocator->dram[bank].cursor = 0u;
}
allocator->initialized = true;
return FT_TT_OK;
}
int ft_tt_allocator_reset(ft_tt_allocator *allocator)
{
uint32_t bank;
if (!allocator || !allocator->initialized)
return FT_TT_EINVAL;
if (allocator->ctx) {
int64_t released = 0;
for (bank = 0; bank < allocator->dram_bank_count; bank++)
released += (int64_t)(allocator->dram[bank].cursor - allocator->dram[bank].floor);
ft_tt_ctx_note_device_alloc(allocator->ctx, -released);
}
allocator->l1.cursor = allocator->l1.floor;
allocator->l1.live_count = 0u;
for (bank = 0; bank < allocator->dram_bank_count; bank++) {
allocator->dram[bank].cursor = allocator->dram[bank].floor;
allocator->dram[bank].live_count = 0u;
}
return FT_TT_OK;
}
/* ------------------------------------------------------------------------- */
/* Allocation */
/* ------------------------------------------------------------------------- */
int ft_tt_buffer_alloc(ft_tt_allocator *allocator, ft_tt_memory_space space, uint32_t logical_bank,
size_t bytes, ft_tt_buffer *out_buffer)
{
ft_tt_arena *arena;
uint32_t alignment;
uint64_t reserved;
uint64_t address;
if (!allocator || !out_buffer || bytes == 0u)
return FT_TT_EINVAL;
arena = arena_for(allocator, space, logical_bank);
if (!arena) {
ft_tt_log_error("allocator: no %s arena for bank %u", ft_tt_memory_space_string(space),
logical_bank);
return FT_TT_EINVAL;
}
/* Align to the stricter of the two directions so the buffer is legal for
* both ft_tt_write_* and ft_tt_read_* (64 B in DRAM, 16 B in L1). */
alignment = ft_tt_noc_alignment(space, true);
if (alignment == 0u)
return FT_TT_EINVAL;
reserved = align_up((uint64_t)bytes, alignment);
if (reserved == 0u)
return FT_TT_EINVAL;
address = arena->cursor;
if (reserved > arena->ceiling - address) {
ft_tt_log_error("allocator: %s bank %u cannot fit %zu bytes (%llu free of %llu)",
ft_tt_memory_space_string(space), logical_bank, bytes,
(unsigned long long)(arena->ceiling - address),
(unsigned long long)(arena->ceiling - arena->floor));
return FT_TT_ENOMEM;
}
arena->cursor = address + reserved;
arena->live_count++;
if (space == FT_TT_MEM_SPACE_DRAM && allocator->ctx)
ft_tt_ctx_note_device_alloc(allocator->ctx, (int64_t)reserved);
out_buffer->space = space;
out_buffer->logical_bank = (space == FT_TT_MEM_SPACE_DRAM) ? logical_bank : 0u;
out_buffer->address = address;
out_buffer->bytes = bytes;
out_buffer->reserved_bytes = (size_t)reserved;
return FT_TT_OK;
}
int ft_tt_buffer_free(ft_tt_allocator *allocator, const ft_tt_buffer *buffer)
{
ft_tt_arena *arena;
if (!allocator || !buffer)
return FT_TT_EINVAL;
arena = arena_for(allocator, buffer->space, buffer->logical_bank);
if (!arena) {
ft_tt_log_error("allocator: freeing a buffer in no arena (%s bank %u)",
ft_tt_memory_space_string(buffer->space), buffer->logical_bank);
return FT_TT_EINVAL;
}
if (arena->live_count == 0u) {
ft_tt_log_error("allocator: freeing from an empty %s arena",
ft_tt_memory_space_string(buffer->space));
return FT_TT_EINVAL;
}
if (buffer->address + (uint64_t)buffer->reserved_bytes != arena->cursor) {
ft_tt_log_error(
"allocator: out-of-order free of %s 0x%llx+%zu; the cursor is at 0x%llx (bump "
"allocators only retract their last step)",
ft_tt_memory_space_string(buffer->space), (unsigned long long)buffer->address,
buffer->reserved_bytes, (unsigned long long)arena->cursor);
return FT_TT_EINVAL;
}
arena->cursor = buffer->address;
arena->live_count--;
if (buffer->space == FT_TT_MEM_SPACE_DRAM && allocator->ctx)
ft_tt_ctx_note_device_alloc(allocator->ctx, -(int64_t)buffer->reserved_bytes);
return FT_TT_OK;
}
/* ------------------------------------------------------------------------- */
/* Queries */
/* ------------------------------------------------------------------------- */
size_t ft_tt_allocator_free_bytes(const ft_tt_allocator *allocator, ft_tt_memory_space space,
uint32_t logical_bank)
{
const ft_tt_arena *arena = arena_for_const(allocator, space, logical_bank);
return arena ? (size_t)(arena->ceiling - arena->cursor) : 0u;
}
size_t ft_tt_allocator_used_bytes(const ft_tt_allocator *allocator, ft_tt_memory_space space,
uint32_t logical_bank)
{
const ft_tt_arena *arena = arena_for_const(allocator, space, logical_bank);
return arena ? (size_t)(arena->cursor - arena->floor) : 0u;
}
uint32_t ft_tt_allocator_live_count(const ft_tt_allocator *allocator, ft_tt_memory_space space,
uint32_t logical_bank)
{
const ft_tt_arena *arena = arena_for_const(allocator, space, logical_bank);
return arena ? arena->live_count : 0u;
}