178 lines
5.2 KiB
C
178 lines
5.2 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_tlb.c
|
|
* @brief Implementation of TLB window ownership — ALLOCATE_TLB, the aperture
|
|
* mmap, and FREE_TLB. No window is ever aimed here; that is Phase 1b.
|
|
*
|
|
* @status Phase 1a, exercised on the p150a test card.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
/* POSIX.1-2008 is needed for mmap's PROT_* under -std=c99, which turns off the
|
|
* implicit feature-test macros glibc would otherwise define. */
|
|
#ifndef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
|
|
#include "ft_tt_tlb.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
struct ft_tt_tlb_window {
|
|
ft_tt_kmd_window *kmd_window;
|
|
uint32_t id;
|
|
size_t size;
|
|
void *aperture;
|
|
bool aimed;
|
|
uint64_t base_address;
|
|
};
|
|
|
|
/*
|
|
* Phase 1a maps the aperture PROT_NONE: the window is claimed but aimed
|
|
* nowhere, so a stray dereference must fault on the host rather than issue a
|
|
* NoC transaction to an undefined endpoint (spec §7.3 — that mistake costs a
|
|
* physical power cycle). Phase 1b flips this to a real mapping.
|
|
*/
|
|
static int aperture_protection(void)
|
|
{
|
|
#ifdef FT_TT_PHASE1B_NOC_ENABLED
|
|
return PROT_READ | PROT_WRITE;
|
|
#else
|
|
return PROT_NONE;
|
|
#endif
|
|
}
|
|
|
|
/* The kernel boundary speaks errno; the Foundry surface speaks FT_TT_*. */
|
|
static int status_from_errno(int negative_errno)
|
|
{
|
|
switch (-negative_errno) {
|
|
case EINVAL:
|
|
return FT_TT_EINVAL;
|
|
case ENOMEM:
|
|
return FT_TT_ENOMEM;
|
|
case EPERM:
|
|
return FT_TT_EPERM;
|
|
case ENODEV:
|
|
return FT_TT_ENODEV;
|
|
default:
|
|
return FT_TT_EIO;
|
|
}
|
|
}
|
|
|
|
int ft_tt_tlb_window_alloc(ft_tt_ctx *ctx, size_t size, enum ft_tt_kmd_cache_mode cache,
|
|
ft_tt_tlb_window **out_window)
|
|
{
|
|
ft_tt_tlb_window *window = NULL;
|
|
int rc;
|
|
|
|
if (!ctx || !out_window || !ft_tt_ctx_available(ctx))
|
|
return FT_TT_EINVAL;
|
|
|
|
window = calloc(1, sizeof(*window));
|
|
if (!window) {
|
|
ft_tt_log_error("out of memory allocating a TLB window handle");
|
|
return FT_TT_ENOMEM;
|
|
}
|
|
|
|
rc = ft_tt_kmd_window_alloc(ft_tt_ctx_kmd_device(ctx), size, cache, aperture_protection(),
|
|
&window->kmd_window);
|
|
if (rc != 0) {
|
|
/* -EINVAL here means the board has no window of this size at all, which
|
|
* is exactly what a 4 GiB request gets with no BAR4 (spec §2.3). */
|
|
ft_tt_log_error("ALLOCATE_TLB(%zu bytes) failed on device %d: %s", size,
|
|
ft_tt_ctx_device_index(ctx), strerror(-rc));
|
|
free(window);
|
|
return status_from_errno(rc);
|
|
}
|
|
|
|
if (ft_tt_kmd_window_id(window->kmd_window, &window->id) != 0 ||
|
|
ft_tt_kmd_window_size(window->kmd_window, &window->size) != 0 ||
|
|
ft_tt_kmd_window_aperture(window->kmd_window, &window->aperture) != 0) {
|
|
ft_tt_log_error("TLB window accessors failed after a successful allocation");
|
|
(void)ft_tt_kmd_window_free(ft_tt_ctx_kmd_device(ctx), window->kmd_window);
|
|
free(window);
|
|
return FT_TT_ERROR;
|
|
}
|
|
|
|
ft_tt_ctx_note_window(ctx, +1);
|
|
*out_window = window;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int ft_tt_tlb_window_free(ft_tt_ctx *ctx, ft_tt_tlb_window *window)
|
|
{
|
|
int rc;
|
|
|
|
if (!ctx || !window)
|
|
return FT_TT_EINVAL;
|
|
|
|
rc = ft_tt_kmd_window_free(ft_tt_ctx_kmd_device(ctx), window->kmd_window);
|
|
if (rc != 0)
|
|
ft_tt_log_error("FREE_TLB(%u) failed on device %d: %s", window->id,
|
|
ft_tt_ctx_device_index(ctx), strerror(-rc));
|
|
|
|
ft_tt_ctx_note_window(ctx, -1);
|
|
free(window);
|
|
|
|
return rc == 0 ? FT_TT_OK : status_from_errno(rc);
|
|
}
|
|
|
|
uint32_t ft_tt_tlb_window_id(const ft_tt_tlb_window *window)
|
|
{
|
|
return window ? window->id : UINT32_MAX;
|
|
}
|
|
|
|
size_t ft_tt_tlb_window_size(const ft_tt_tlb_window *window)
|
|
{
|
|
return window ? window->size : 0;
|
|
}
|
|
|
|
size_t ft_tt_tlb_window_register_offset(const ft_tt_tlb_window *window)
|
|
{
|
|
return window ? (size_t)window->id * FT_TT_TLB_REG_STRIDE : SIZE_MAX;
|
|
}
|
|
|
|
volatile void *ft_tt_tlb_window_aperture(const ft_tt_tlb_window *window)
|
|
{
|
|
return window ? window->aperture : NULL;
|
|
}
|
|
|
|
bool ft_tt_tlb_window_is_aimed(const ft_tt_tlb_window *window)
|
|
{
|
|
return window ? window->aimed : false;
|
|
}
|
|
|
|
int ft_tt_tlb_window_aim(ft_tt_ctx *ctx, ft_tt_tlb_window *window, uint16_t x, uint16_t y,
|
|
uint64_t addr)
|
|
{
|
|
int rc;
|
|
|
|
if (!ctx || !window)
|
|
return FT_TT_EINVAL;
|
|
|
|
/* Phase 1a: ft_tt_kmd_window_aim_unicast returns -EPERM without issuing the
|
|
* ioctl, so the device is never reached. Phase 1b builds with
|
|
* FT_TT_PHASE1B_NOC_ENABLED and this becomes a real CONFIGURE_WINDOW. */
|
|
rc = ft_tt_kmd_window_aim_unicast(ft_tt_ctx_kmd_device(ctx), window->kmd_window, x, y, addr);
|
|
if (rc != 0) {
|
|
ft_tt_log_error("aiming window %u at NoC0 (%u, %u) 0x%llx failed: %s", window->id,
|
|
(unsigned)x, (unsigned)y, (unsigned long long)addr, strerror(-rc));
|
|
return status_from_errno(rc);
|
|
}
|
|
|
|
window->aimed = true;
|
|
window->base_address = addr;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
uint64_t ft_tt_tlb_window_base_address(const ft_tt_tlb_window *window)
|
|
{
|
|
return window ? window->base_address : 0;
|
|
}
|