Foundry/barswap/tests/k80_bar_resize.c

182 lines
5.3 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file k80_bar_resize.c
* @brief Shrink every K80/GK210 BAR1 16 GB -> 256 MB and re-enumerate (all 16).
*
* The one-command runtime automation (kepler_compute_driver_spec.md §15): for
* each GK210, write the BAR1-size strap (PSTRAPS BAR0+0x101000 [15:14]=2 +
* override bit 31) via its boot-assigned BAR0, then `remove`+`rescan` so the
* kernel allocates the small BAR1 (16 GB fits nowhere; 256 MB does).
*
* v2 — reach the "broken bridge" dies: when a die's 16 GB BAR1 failed at boot,
* the kernel disables decode up its bridge chain, so a plain BAR0 write never
* reaches the chip and the strap silently no-ops (this stranded 2/16). Fix:
* before strapping each die, walk its PCI ancestor chain and enable memory
* decode (command reg bit 1) on every bridge + the die, so BAR0 routes and the
* strap takes. Then all 16 advertise 256 MB and the rescan mounts them.
*
* Usage (root): k80_bar_resize
*
* @status Automation — runtime resize, bridge-chain decode-enable for all 16.
* @issues The truly clean every-boot version is a built-in pci_fixup_early
* (loadable modules load after PCI enum); this runtime tool is the practical
* equivalent on stock kernels.
* @todo None
*/
#define _GNU_SOURCE
#include "barswap/gpu_owner_util.h"
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#define PSTRAPS_OFFSET 0x101000u
#define PAGE_MASK (~0xfffULL)
#define BAR1_FIELD_SHIFT 14
#define BAR1_FIELD_MASK 0xfu
#define BAR1_VAL_256MB 2u
#define BAR1_256MB 0x10000000ULL
#define CFG_COMMAND 0x04
#define CFG_MEM_DECODE 0x02
static int cfg_rw32(const char *bdf, unsigned off, uint32_t *val, int write)
{
char path[256];
int fd, ok;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/config", bdf);
fd = open(path, write ? O_WRONLY : O_RDONLY);
if (fd < 0)
return -1;
ok = (write ? pwrite(fd, val, 4, off) : pread(fd, val, 4, off)) == 4 ? 0 : -1;
close(fd);
return ok;
}
static void enable_decode(const char *bdf)
{
uint32_t cmd = 0;
if (cfg_rw32(bdf, CFG_COMMAND, &cmd, 0) == 0 && !(cmd & CFG_MEM_DECODE)) {
cmd |= CFG_MEM_DECODE;
cfg_rw32(bdf, CFG_COMMAND, &cmd, 1);
}
}
static int is_bdf(const char *s)
{
return strlen(s) == 12 && s[4] == ':' && s[7] == ':' && s[10] == '.';
}
/* Walk the die's PCI ancestor chain (root port -> PLX bridges -> die) and enable
* memory decode on every node, so the die's BAR0 actually routes. */
static void enable_chain_decode(const char *die_bdf)
{
char path[256], real[PATH_MAX], *tok;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s", die_bdf);
if (!realpath(path, real))
return;
for (tok = strtok(real, "/"); tok; tok = strtok(NULL, "/"))
if (is_bdf(tok))
enable_decode(tok);
}
static uint64_t resource_size(const char *bdf, int idx)
{
char path[256], line[128];
uint64_t start = 0, end = 0, size = 0;
int i;
FILE *fp;
snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/resource", bdf);
fp = fopen(path, "r");
if (!fp)
return 0;
for (i = 0; i <= idx; i++) {
if (!fgets(line, sizeof(line), fp))
break;
if (i == idx && sscanf(line, "%llx %llx", (unsigned long long *)&start,
(unsigned long long *)&end) == 2 && end > start)
size = end - start + 1;
}
fclose(fp);
return size;
}
static int shrink_die(const char *bdf)
{
uint64_t bar0, target, page, off;
int fd, rc = -1;
volatile uint32_t *map;
uint32_t v;
enable_chain_decode(bdf); /* v2: route BAR0 even on broken bridges */
bar0 = gou_read_bar0(bdf);
if (!bar0)
return -1;
fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0)
return -1;
target = bar0 + PSTRAPS_OFFSET;
page = target & PAGE_MASK;
off = target - page;
map = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)page);
if (map != MAP_FAILED) {
v = map[off / 4];
/* sanity: a routed PSTRAPS has bit31 (override) set on these cards and a
* sane field; all-ones means the read didn't route. */
if (v != 0xffffffffu) {
map[off / 4] = (v & ~(BAR1_FIELD_MASK << BAR1_FIELD_SHIFT))
| (BAR1_VAL_256MB << BAR1_FIELD_SHIFT);
(void)map[off / 4];
rc = (map[off / 4] != 0xffffffffu) ? 0 : -1;
}
munmap((void *)map, 0x1000);
}
close(fd);
return rc;
}
int main(void)
{
int n, i, shrunk = 0, mounted = 0;
struct timespec ts = { .tv_sec = 3, .tv_nsec = 0 };
setvbuf(stdout, NULL, _IONBF, 0);
n = gou_discover_gk210s();
printf("k80_bar_resize: %d GK210 dies\n", n);
if (n <= 0)
return 1;
for (i = 0; i < n; i++) {
if (shrink_die(gou_gk210_bdf(i)) == 0)
shrunk++;
else
printf(" %s: strap write did not route (still unreachable)\n",
gou_gk210_bdf(i));
}
printf("strapped 256 MB (verified routed): %d / %d\n", shrunk, n);
for (i = 0; i < n; i++) {
char p[256];
snprintf(p, sizeof(p), "/sys/bus/pci/devices/%s/remove", gou_gk210_bdf(i));
gou_sysfs_write(p, "1\n");
}
gou_sysfs_write("/sys/bus/pci/rescan", "1\n");
nanosleep(&ts, NULL);
gou_discover_gk210s();
n = gou_num_gk210();
for (i = 0; i < n; i++)
if (resource_size(gou_gk210_bdf(i), 1) == BAR1_256MB)
mounted++;
printf(">>> mounted 256 MB BAR1: %d / %d <<<\n", mounted, n);
return 0;
}