Foundry/tt/ft_tt_udrv_pci_sysfs.h

218 lines
10 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_udrv_pci_sysfs.h
* @brief Find the card and describe its BARs using nothing but sysfs — the
* replacement for tt-kmd's GET_DEVICE_INFO and QUERY_MAPPINGS ioctls.
*
* Everything those two ioctls report is already published by the PCI core:
* `vendor`, `device`, `subsystem_vendor`, `subsystem_device`, `revision`,
* `class` are one-line files, and `resource` is the BAR table
* `pci_resource_start/end/flags` verbatim. QUERY_MAPPINGS adds only the mmap
* COOKIES the driver invented for its own character device, which a driver
* that mmaps `resource0_wc` directly does not need.
*
* The parsing is the whole risk and it is fully testable off-hardware: this
* module takes the sysfs ROOT as a parameter, so a test can point it at a
* scratch directory containing the same files. `tests/test_udrv_pci_sysfs.c`
* does exactly that, including the cases a real card cannot produce on
* demand — an absent BAR, a non-prefetchable BAR, a truncated `resource`,
* a device with the wrong ids.
*
* WHAT A FIXTURE CANNOT TEST, stated so nobody assumes otherwise: that the
* numbers sysfs prints are the ones the hardware decodes; that
* `resource0_wc` can actually be mmap'd (it needs root or a udev rule, and
* it needs `iomem_is_exclusive()` to say no — it does here, because tt-kmd
* never calls `pci_request_regions` and `CONFIG_IO_STRICT_DEVMEM` is not
* set on this kernel); and that a write-combined store to that mapping
* reaches the card. `tests/test_udrv_card_tlb.c` checks all three ON THE
* CARD, in that order, and is the only thing here that opens a device.
*
* @status Discovery and parsing are gated against a synthetic sysfs tree.
* `ft_tt_udrv_pci_map_bar()` is implemented as of 2026-08-16 and has mapped
* BAR0 of the live p150a — read-only first, then writable — through
* `resource0` and `resource0_wc`. It needs root, or the udev rule in this
* file's notes.
* @issues
* - The BAR files are mode 0600 root:root, so every caller needs root or a
* udev rule. **THE RULE IS WRITTEN AND EXERCISED (2026-08-18):**
* `tools/60-foundry-blackhole.rules`, gated by
* `tests/run_udev_nonroot_gate.sh`. With it installed and `tenstorrent`
* unloaded, an ordinary user in the `tenstorrent` group mapped both BARs,
* aimed windows, booted firmware on a core and byte-compared a `vector_add`
* — no sudo anywhere. Red arm: the BARs back at 0600 make the same command
* exit 1 with this file's own message. A user outside the group is still
* refused.
* - **A udev rule cannot grant CONFIG SPACE**, and that is where the privilege
* line really sits: an unprivileged read of `config` is truncated by the
* kernel to the 64-byte standard header, so MRRS and the ASIC reset stay
* root-only. The rule performs MRRS itself with `setpci` at device-add time
* for exactly that reason.
* @todo None
*/
#ifndef FT_TT_UDRV_PCI_SYSFS_H
#define FT_TT_UDRV_PCI_SYSFS_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/** PCI ids. Same constants ft_tt_kmd_abi.h carries, restated here so this
* module does not include the layer it replaces. */
#define FT_TT_UDRV_PCI_VENDOR_TENSTORRENT 0x1e52u
#define FT_TT_UDRV_PCI_DEVICE_BLACKHOLE 0xb140u
/** BARs sysfs publishes in `resource`, before the ROM and bridge windows. */
#define FT_TT_UDRV_PCI_BAR_COUNT 6u
/** Longest sysfs path this module builds, including the terminator. */
#define FT_TT_UDRV_PCI_PATH_MAX 320u
/** Longest "0000:12:00.0"-shaped address, including the terminator. */
#define FT_TT_UDRV_PCI_ADDRESS_MAX 16u
/** Default place to look for devices. */
#define FT_TT_UDRV_PCI_DEFAULT_DEVICES_DIR "/sys/bus/pci/devices"
/**
* One BAR, exactly as `resource` reports it.
*
* `length` follows the kernel's own `pci_resource_len()`: zero when `end` is
* zero, otherwise `end - start + 1`. That is not the same as `end >= start`,
* and the difference is the absent-BAR case — this board has no BAR4 and its
* line reads all zeros.
*/
typedef struct ft_tt_udrv_pci_bar {
uint64_t start;
uint64_t end;
uint64_t flags;
uint64_t length; /**< 0 means the BAR is absent */
bool present; /**< length > 0 */
bool memory; /**< IORESOURCE_MEM */
bool io; /**< IORESOURCE_IO */
bool prefetchable; /**< IORESOURCE_PREFETCH — also decides whether sysfs
* publishes a `resourceN_wc` companion at all */
bool is_64bit; /**< IORESOURCE_MEM_64 */
} ft_tt_udrv_pci_bar;
/** A PCI device as sysfs describes it. */
typedef struct ft_tt_udrv_pci_device {
char sysfs_path[FT_TT_UDRV_PCI_PATH_MAX]; /**< the device directory */
char address[FT_TT_UDRV_PCI_ADDRESS_MAX]; /**< its basename, e.g. 0000:12:00.0 */
uint16_t vendor;
uint16_t device;
uint16_t subsystem_vendor;
uint16_t subsystem_device;
uint32_t class_code;
uint8_t revision;
ft_tt_udrv_pci_bar bar[FT_TT_UDRV_PCI_BAR_COUNT];
} ft_tt_udrv_pci_device;
/**
* Read one device directory.
*
* @param device_dir e.g. "/sys/bus/pci/devices/0000:12:00.0". The basename
* becomes `address`
* @param out_device receives the description
* @return FT_TT_UDRV_OK; FT_TT_UDRV_EINVAL on a NULL/oversized
* argument; FT_TT_UDRV_ENODEV when a required attribute is
* missing or unparseable
*
* Thread-safe: yes Reentrant: yes Signal-safe: no
*/
int ft_tt_udrv_pci_read_device(const char *device_dir, ft_tt_udrv_pci_device *out_device);
/**
* Enumerate every device under `devices_dir` matching `vendor`/`device`, in
* ascending address order so device 0 is stable across boots.
*
* @param devices_dir NULL for FT_TT_UDRV_PCI_DEFAULT_DEVICES_DIR
* @param vendor PCI vendor id to match
* @param device PCI device id to match, or 0xFFFF for "any"
* @param out_devices receives up to `capacity` matches
* @param capacity size of `out_devices`
* @param out_count receives how many matched. May exceed `capacity`, in
* which case only `capacity` were written and the return
* is still FT_TT_UDRV_OK — the caller decides whether that
* is an error
* @return FT_TT_UDRV_OK, FT_TT_UDRV_EINVAL, or FT_TT_UDRV_ENODEV
* when `devices_dir` cannot be read
*/
int ft_tt_udrv_pci_find_devices(const char *devices_dir, uint16_t vendor, uint16_t device,
ft_tt_udrv_pci_device *out_devices, size_t capacity,
size_t *out_count);
/**
* Build the sysfs filename for a BAR's mapping file: `resourceN`, or
* `resourceN_wc` for the write-combined companion.
*
* The `_wc` file only EXISTS when the BAR is prefetchable — the PCI core
* creates it conditionally. BAR0 on this board is 512 MiB prefetchable, which
* is why the write-combined upload path is reachable at all.
*
* CORRECTION, checked on the card 2026-08-16: BAR2 here is prefetchable TOO
* (`resource2_wc` exists on 0000:12:00.0), so the earlier note that it is
* uncached-only was wrong. It does not change any decision — registers want
* uncached and get it through `resourceN` — but the reason must be "we chose
* UC", not "the file does not exist".
*
* @return FT_TT_UDRV_OK, or FT_TT_UDRV_EINVAL (bad bar index / no room /
* `write_combine` asked for on a non-prefetchable BAR)
*/
int ft_tt_udrv_pci_resource_path(const ft_tt_udrv_pci_device *device, unsigned bar,
bool write_combine, char *out_path, size_t path_bytes);
/**
* Map `length` bytes of a BAR at `offset` into this process — the whole of the
* host-side work that replaces tt-kmd's mmap path.
*
* `open()` the path `ft_tt_udrv_pci_resource_path()` produced and `mmap()` it.
* For a TLB window pass `offset = ft_tt_udrv_tlb2m_aperture_offset(id)` and
* `length = FT_TT_UDRV_TLB_2M_WINDOW_SIZE` with `write_combine = true`: that
* is bit for bit what tt-kmd's `map_tlb_window()` does
* (`io_remap_pfn_range(bar_start + id * 2 MiB)` with `pgprot_writecombine`),
* with the ioctl removed. For the TLB REGISTER file pass
* `offset = FT_TT_UDRV_TLB_REGS_START`, `length = FT_TT_UDRV_TLB_REGS_LEN` and
* `write_combine = false` — registers are uncached, which is what the driver's
* own `pci_iomap_range()` gives it.
*
* `writable` is a SAFETY parameter, not a convenience. The read-only rung of
* bring-up must be mechanically incapable of writing a register, not merely
* careful about it: a read-only mapping makes a stray store a SIGSEGV on the
* host instead of a wrong aim on the card. Pass false unless the caller's
* whole purpose is to write.
*
* @param device the device whose BAR to map
* @param bar BAR index; must be present
* @param write_combine map through `resourceN_wc`; requires a prefetchable BAR
* @param writable PROT_READ|PROT_WRITE and O_RDWR when true, else
* PROT_READ and O_RDONLY
* @param offset byte offset into the BAR; must be page-aligned, and
* offset + length must fit the BAR
* @param length bytes to map; must be non-zero
* @param out_mapping receives the mapping; release with
* ft_tt_udrv_pci_unmap_bar(mapping, length)
* @return FT_TT_UDRV_OK; FT_TT_UDRV_EINVAL for a bad argument, a
* misaligned offset or a range past the end of the BAR;
* FT_TT_UDRV_EIO when open() or mmap() fails (errno is
* left set, and EACCES means "not root and no udev rule")
*
* Thread-safe: yes Reentrant: yes Signal-safe: no
*/
int ft_tt_udrv_pci_map_bar(const ft_tt_udrv_pci_device *device, unsigned bar, bool write_combine,
bool writable, uint64_t offset, size_t length, void **out_mapping);
/**
* Release a mapping from ft_tt_udrv_pci_map_bar().
*
* @param mapping what map_bar returned; NULL is a no-op success
* @param length the same length map_bar was given
* @return FT_TT_UDRV_OK, FT_TT_UDRV_EINVAL, or FT_TT_UDRV_EIO
*
* Thread-safe: yes Reentrant: yes Signal-safe: no
*/
int ft_tt_udrv_pci_unmap_bar(void *mapping, size_t length);
#endif /* FT_TT_UDRV_PCI_SYSFS_H */