BC-250 console image: custom GNOME recipe with remote admin
Build Bazzite BC-250 / Check Bazzite channel digests (push) Has been cancelled
Build Bazzite BC-250 / Build Custom Image (push) Has been cancelled
Build Bazzite BC-250 / Build Custom 40CU Image (push) Has been cancelled
Build Bazzite BC-250 / Save Bazzite channel digest cache (push) Has been cancelled
Build Bazzite BC-250 / Publish GitHub Release (push) Has been cancelled
Build Bazzite BC-250 / Check Bazzite channel digests (push) Has been cancelled
Build Bazzite BC-250 / Build Custom Image (push) Has been cancelled
Build Bazzite BC-250 / Build Custom 40CU Image (push) Has been cancelled
Build Bazzite BC-250 / Save Bazzite channel digest cache (push) Has been cancelled
Build Bazzite BC-250 / Publish GitHub Release (push) Has been cancelled
Based on 62fixolab/Latest-Bazzite-AMD-BC-250-Patched-Images @ 347fd4d. Adds on top of the fork: - recipes/bc250-console-gnome.yml: governor + gnome-remote-desktop + openssh-server, sshd enabled, hhd.service masked, no signing module (local build + ISO path) - files/console/usr/bin/bc250-remote-setup: one-time on-box SSH/RDP setup - files/console/usr/lib/bootc/kargs.d/50-bc250-ttm.toml: ttm memory kargs - BUILD-CONSOLE.md: build -> ISO -> validation instructions files/console/ is separate from files/system/ so the 40-CU unlock tooling stays out of this stable 24-CU image. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Vendored
Executable
+175
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-40cu-benchmark.sh — Reproducible A/B benchmark for 40 CU unlock
|
||||
#
|
||||
# Runs llama-bench at 24 CU and 40 CU with matched clocks and cooldown.
|
||||
# Requires: UMR, Vulkan llama-bench, a GGUF model, patched amdgpu (bc250_cc_write_mode=3)
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./bc250-40cu-benchmark.sh [model_path] [llama_bench_path]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MODEL="${1:-}"
|
||||
BENCH="${2:-}"
|
||||
UMR="${UMR:-}"
|
||||
COOL_TARGET=76000
|
||||
|
||||
info() { printf '\033[0;32m[+]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[0;33m[!]\033[0m %s\n' "$*"; }
|
||||
die() { printf '\033[0;31m[E]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
find_umr() {
|
||||
for p in /opt/umr/build/src/app/umr /usr/local/bin/umr /usr/bin/umr; do
|
||||
[ -x "$p" ] && UMR="$p" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
find_model() {
|
||||
for p in /opt/models/*.gguf /opt/models/*/*.gguf /root/models/*.gguf; do
|
||||
[ -f "$p" ] && echo "$p" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
find_bench() {
|
||||
for p in \
|
||||
/opt/llama.cpp/build-vulkan/bin/llama-bench \
|
||||
/opt/bc250/llama-vulkan-build/bin/llama-bench \
|
||||
/usr/local/bin/llama-bench; do
|
||||
[ -x "$p" ] && echo "$p" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
get_temp() {
|
||||
cat /sys/class/drm/card0/device/hwmon/hwmon*/temp1_input 2>/dev/null | head -1
|
||||
}
|
||||
|
||||
get_power() {
|
||||
cat /sys/kernel/debug/dri/0/amdgpu_pm_info 2>/dev/null | grep "current SoC" | awk '{print $1}'
|
||||
}
|
||||
|
||||
get_sclk() {
|
||||
cat /sys/kernel/debug/dri/0/amdgpu_pm_info 2>/dev/null | grep "SCLK" | head -1 | awk '{print $1}'
|
||||
}
|
||||
|
||||
get_vddgfx() {
|
||||
cat /sys/kernel/debug/dri/0/amdgpu_pm_info 2>/dev/null | grep "VDDGFX" | awk '{print $1}'
|
||||
}
|
||||
|
||||
wait_cool() {
|
||||
local t
|
||||
while true; do
|
||||
t=$(get_temp)
|
||||
[ "$t" -lt "$COOL_TARGET" ] 2>/dev/null && break
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
set_24cu() {
|
||||
"$UMR" -w cyan_skillfish.gfx1013.mmSPI_PG_ENABLE_STATIC_WGP_MASK 0x7 2>/dev/null
|
||||
"$UMR" -w cyan_skillfish.gfx1013.mmRLC_PG_ALWAYS_ON_WGP_MASK 0x3 2>/dev/null
|
||||
}
|
||||
|
||||
set_40cu() {
|
||||
"$UMR" -w cyan_skillfish.gfx1013.mmSPI_PG_ENABLE_STATIC_WGP_MASK 0x1f 2>/dev/null
|
||||
"$UMR" -w cyan_skillfish.gfx1013.mmRLC_PG_ALWAYS_ON_WGP_MASK 0x1f 2>/dev/null
|
||||
}
|
||||
|
||||
run_bench() {
|
||||
local label="$1" outfile="$2"
|
||||
local spi
|
||||
spi=$("$UMR" -r cyan_skillfish.gfx1013.mmSPI_PG_ENABLE_STATIC_WGP_MASK 2>&1 | grep "=>" | awk '{print $NF}')
|
||||
local t0
|
||||
t0=$(get_temp)
|
||||
|
||||
local benchlib
|
||||
benchlib=$(dirname "$BENCH")
|
||||
LD_LIBRARY_PATH="$benchlib" "$BENCH" \
|
||||
-m "$MODEL" -p 512 -n 0 -ngl 99 -r 1 > "$outfile" 2>&1 &
|
||||
local bpid=$!
|
||||
sleep 2
|
||||
local pwr clk vdd t1
|
||||
pwr=$(get_power)
|
||||
clk=$(get_sclk)
|
||||
vdd=$(get_vddgfx)
|
||||
t1=$(get_temp)
|
||||
wait $bpid
|
||||
local tps
|
||||
tps=$(grep "pp512" "$outfile" | sed 's/.*|[[:space:]]*//' | awk '{print $1}')
|
||||
|
||||
printf " %-35s %7s tok/s %5s MHz %5s mV %6s W %s->%sC\n" \
|
||||
"$label (SPI=$spi)" "$tps" "$clk" "$vdd" "$pwr" "$((t0/1000))" "$((t1/1000))"
|
||||
}
|
||||
|
||||
# --- auto-detect ---
|
||||
[ "$(id -u)" = "0" ] || die "Must run as root"
|
||||
|
||||
if [ -z "$UMR" ]; then
|
||||
find_umr || die "UMR not found. Install from: https://gitlab.freedesktop.org/tomstdenis/umr"
|
||||
fi
|
||||
if [ -z "$MODEL" ]; then
|
||||
MODEL=$(find_model) || die "No GGUF model found. Pass path as first argument."
|
||||
fi
|
||||
if [ -z "$BENCH" ]; then
|
||||
BENCH=$(find_bench) || die "No Vulkan llama-bench found. Pass path as second argument."
|
||||
fi
|
||||
|
||||
[ -f "$MODEL" ] || die "Model not found: $MODEL"
|
||||
[ -x "$BENCH" ] || die "llama-bench not found: $BENCH"
|
||||
|
||||
# verify patched amdgpu
|
||||
cc_mode=$(cat /sys/module/amdgpu/parameters/bc250_cc_write_mode 2>/dev/null || echo "N/A")
|
||||
if [ "$cc_mode" != "3" ]; then
|
||||
die "bc250_cc_write_mode=$cc_mode (need 3). Enable the patched amdgpu first."
|
||||
fi
|
||||
|
||||
cu_count=$(dmesg | grep -o 'active_cu_number [0-9]*' | tail -1 | awk '{print $2}')
|
||||
|
||||
echo "================================================================="
|
||||
echo " BC-250 40 CU A/B/A Benchmark"
|
||||
echo "================================================================="
|
||||
echo ""
|
||||
echo " Model: $(basename "$MODEL")"
|
||||
echo " Bench: $BENCH"
|
||||
echo " UMR: $UMR"
|
||||
echo " CU enum: $cu_count CC mode: $cc_mode"
|
||||
echo " Governor: $(cat /etc/cyan-skillfish-governor/config.toml 2>/dev/null | grep 'frequency' | tail -1 | tr -d ' ')"
|
||||
echo ""
|
||||
printf " %-35s %7s %5s %5s %6s %s\n" \
|
||||
"State" "tok/s" "SCLK" "VDDGFX" "Power" "Temp"
|
||||
printf " %-35s %7s %5s %5s %6s %s\n" \
|
||||
"---" "-----" "----" "------" "-----" "----"
|
||||
|
||||
# A: 24 CU
|
||||
set_24cu
|
||||
wait_cool
|
||||
run_bench "24 CU (SPI=0x7)" /tmp/bench_24cu.txt
|
||||
|
||||
# B: 40 CU
|
||||
set_40cu
|
||||
wait_cool
|
||||
run_bench "40 CU (SPI=0x1F)" /tmp/bench_40cu.txt
|
||||
|
||||
# A: 24 CU confirm
|
||||
set_24cu
|
||||
wait_cool
|
||||
run_bench "24 CU confirm (SPI=0x7)" /tmp/bench_24cu_confirm.txt
|
||||
|
||||
# Restore 40 CU
|
||||
set_40cu
|
||||
|
||||
echo ""
|
||||
|
||||
# Extract tok/s for ratio
|
||||
tps_24=$(grep "pp512" /tmp/bench_24cu.txt | sed 's/.*|[[:space:]]*//' | awk '{print $1}')
|
||||
tps_40=$(grep "pp512" /tmp/bench_40cu.txt | sed 's/.*|[[:space:]]*//' | awk '{print $1}')
|
||||
ratio=$(awk "BEGIN {printf \"%.2f\", $tps_40 / $tps_24}")
|
||||
|
||||
echo " Ratio: $tps_40 / $tps_24 = ${ratio}x"
|
||||
echo ""
|
||||
echo " Expected: ~1.5-1.67x for compute-bound PP"
|
||||
echo " If ~1.0x: SPI write may not have taken effect (check dmesg)"
|
||||
echo ""
|
||||
echo "================================================================="
|
||||
Vendored
Executable
+688
@@ -0,0 +1,688 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-compute-verify.sh - heavy Vulkan compute correctness test for BC-250.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ELEMENTS=16777216
|
||||
PASSES=3
|
||||
ITERS=64
|
||||
KEEP_TMP=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [--elements N] [--passes N] [--iters N] [--keep-tmp]
|
||||
|
||||
Runs a Vulkan compute correctness test with:
|
||||
- FP32 fma chains
|
||||
- integer multiply/add
|
||||
- bitwise rotate/xor/shift patterns
|
||||
- LDS shared-memory read/write
|
||||
- full per-element CPU golden comparison
|
||||
|
||||
ELEMENTS must be a multiple of 256. Default: $ELEMENTS
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--elements)
|
||||
ELEMENTS="${2:?missing value for --elements}"
|
||||
shift 2
|
||||
;;
|
||||
--passes)
|
||||
PASSES="${2:?missing value for --passes}"
|
||||
shift 2
|
||||
;;
|
||||
--iters)
|
||||
ITERS="${2:?missing value for --iters}"
|
||||
shift 2
|
||||
;;
|
||||
--keep-tmp)
|
||||
KEEP_TMP=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$ELEMENTS:$PASSES:$ITERS" in
|
||||
*[!0-9:]*|"")
|
||||
echo "ERROR: --elements, --passes, and --iters must be positive integers" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$ELEMENTS" -le 0 ] || [ "$PASSES" -le 0 ] || [ "$ITERS" -le 0 ]; then
|
||||
echo "ERROR: --elements, --passes, and --iters must be positive integers" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ $((ELEMENTS % 256)) -ne 0 ]; then
|
||||
echo "ERROR: --elements must be a multiple of 256" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
command -v glslangValidator >/dev/null 2>&1 || {
|
||||
echo "ERROR: glslangValidator not found" >&2
|
||||
exit 1
|
||||
}
|
||||
command -v gcc >/dev/null 2>&1 || {
|
||||
echo "ERROR: gcc not found" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
TMPDIR="$(mktemp -d)"
|
||||
if [ "$KEEP_TMP" -eq 0 ]; then
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
else
|
||||
echo "Keeping temporary files in $TMPDIR"
|
||||
fi
|
||||
|
||||
cat >"$TMPDIR/bc250_compute_verify.comp" <<'GLSL'
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
layout(std430, set = 0, binding = 0) readonly buffer InputA {
|
||||
uint a[];
|
||||
};
|
||||
|
||||
layout(std430, set = 0, binding = 1) readonly buffer InputB {
|
||||
uint b[];
|
||||
};
|
||||
|
||||
layout(std430, set = 0, binding = 2) writeonly buffer OutputInt {
|
||||
uint out_int[];
|
||||
};
|
||||
|
||||
layout(std430, set = 0, binding = 3) writeonly buffer OutputFp {
|
||||
uint out_fp[];
|
||||
};
|
||||
|
||||
layout(push_constant) uniform Params {
|
||||
uint n;
|
||||
uint seed;
|
||||
uint pass;
|
||||
uint iters;
|
||||
} pc;
|
||||
|
||||
shared uint lds[256];
|
||||
|
||||
uint rotl32(uint v, uint s)
|
||||
{
|
||||
s &= 31u;
|
||||
return s == 0u ? v : ((v << s) | (v >> (32u - s)));
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint idx = gl_GlobalInvocationID.x;
|
||||
uint lid = gl_LocalInvocationID.x;
|
||||
uint x = a[idx] ^ pc.seed ^ (pc.pass * 0x9e3779b9u);
|
||||
uint y = b[idx] + rotl32(idx ^ pc.seed, pc.pass + 7u);
|
||||
float f = uintBitsToFloat(0x3f800000u | (x & 0x007fffffu));
|
||||
|
||||
for (uint j = 0u; j < pc.iters; ++j) {
|
||||
x = x * 1664525u + 1013904223u + j + pc.pass;
|
||||
x ^= rotl32(y + j * 0x45d9f3bu, j + pc.pass);
|
||||
y += x ^ (j * 0x27d4eb2du) ^ (x >> ((j & 7u) + 1u));
|
||||
f = fma(f, 1.0009765625, float(int(y & 255u) - 128) * 0.00000011920928955078125);
|
||||
}
|
||||
|
||||
lds[lid] = x ^ y ^ pc.seed;
|
||||
barrier();
|
||||
|
||||
uint peer0 = lds[(lid * 17u + pc.pass) & 255u];
|
||||
uint peer1 = lds[(lid + 1u) & 255u];
|
||||
x ^= peer0 + rotl32(peer1, lid);
|
||||
y ^= rotl32(peer0 ^ peer1, pc.pass + 11u);
|
||||
|
||||
out_int[idx] = x ^ y ^ rotl32(idx + pc.seed, pc.pass);
|
||||
out_fp[idx] = floatBitsToUint(f);
|
||||
}
|
||||
GLSL
|
||||
|
||||
cat >"$TMPDIR/bc250_compute_verify.c" <<'C'
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOCAL_SIZE 256u
|
||||
|
||||
#define CHECK(call) do { \
|
||||
VkResult _res = (call); \
|
||||
if (_res != VK_SUCCESS) { \
|
||||
fprintf(stderr, "%s failed: %d at line %d\n", #call, _res, __LINE__); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct params {
|
||||
uint32_t n;
|
||||
uint32_t seed;
|
||||
uint32_t pass;
|
||||
uint32_t iters;
|
||||
};
|
||||
|
||||
static uint32_t rotl32(uint32_t v, uint32_t s)
|
||||
{
|
||||
s &= 31u;
|
||||
return s == 0u ? v : (uint32_t)((v << s) | (v >> (32u - s)));
|
||||
}
|
||||
|
||||
static uint32_t f32_bits(float f)
|
||||
{
|
||||
uint32_t u;
|
||||
memcpy(&u, &f, sizeof(u));
|
||||
return u;
|
||||
}
|
||||
|
||||
static float bits_f32(uint32_t u)
|
||||
{
|
||||
float f;
|
||||
memcpy(&f, &u, sizeof(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
static uint32_t fp32_ordered_bits(uint32_t bits)
|
||||
{
|
||||
if (bits & 0x80000000u)
|
||||
return 0x80000000u - (bits & 0x7fffffffu);
|
||||
return 0x80000000u + bits;
|
||||
}
|
||||
|
||||
static uint32_t fp32_ulp_distance(uint32_t a, uint32_t b)
|
||||
{
|
||||
uint32_t oa = fp32_ordered_bits(a);
|
||||
uint32_t ob = fp32_ordered_bits(b);
|
||||
|
||||
return oa > ob ? oa - ob : ob - oa;
|
||||
}
|
||||
|
||||
static void pre_lds_expected(uint32_t idx, const uint32_t *a, const uint32_t *b,
|
||||
const struct params *p, uint32_t *x_out,
|
||||
uint32_t *y_out, uint32_t *fp_out)
|
||||
{
|
||||
uint32_t x = a[idx] ^ p->seed ^ (p->pass * 0x9e3779b9u);
|
||||
uint32_t y = b[idx] + rotl32(idx ^ p->seed, p->pass + 7u);
|
||||
float f = bits_f32(0x3f800000u | (x & 0x007fffffu));
|
||||
|
||||
for (uint32_t j = 0; j < p->iters; ++j) {
|
||||
x = x * 1664525u + 1013904223u + j + p->pass;
|
||||
x ^= rotl32(y + j * 0x45d9f3bu, j + p->pass);
|
||||
y += x ^ (j * 0x27d4eb2du) ^ (x >> ((j & 7u) + 1u));
|
||||
f = fmaf(f, 1.0009765625f,
|
||||
(float)((int)(y & 255u) - 128) * 0.00000011920928955078125f);
|
||||
}
|
||||
|
||||
*x_out = x;
|
||||
*y_out = y;
|
||||
*fp_out = f32_bits(f);
|
||||
}
|
||||
|
||||
static void final_expected(uint32_t idx, const uint32_t *lds,
|
||||
uint32_t x, uint32_t y, const struct params *p,
|
||||
uint32_t *int_out)
|
||||
{
|
||||
uint32_t lid = idx & (LOCAL_SIZE - 1u);
|
||||
uint32_t peer0 = lds[(lid * 17u + p->pass) & 255u];
|
||||
uint32_t peer1 = lds[(lid + 1u) & 255u];
|
||||
|
||||
x ^= peer0 + rotl32(peer1, lid);
|
||||
y ^= rotl32(peer0 ^ peer1, p->pass + 11u);
|
||||
*int_out = x ^ y ^ rotl32(idx + p->seed, p->pass);
|
||||
}
|
||||
|
||||
static uint32_t find_memory_type(VkPhysicalDevice pd, uint32_t bits,
|
||||
VkMemoryPropertyFlags flags)
|
||||
{
|
||||
VkPhysicalDeviceMemoryProperties props;
|
||||
|
||||
vkGetPhysicalDeviceMemoryProperties(pd, &props);
|
||||
for (uint32_t i = 0; i < props.memoryTypeCount; ++i) {
|
||||
if ((bits & (1u << i)) &&
|
||||
(props.memoryTypes[i].propertyFlags & flags) == flags)
|
||||
return i;
|
||||
}
|
||||
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
static int read_file(const char *path, char **buf, size_t *size)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
long len;
|
||||
|
||||
if (!f)
|
||||
return 1;
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
len = ftell(f);
|
||||
if (len <= 0) {
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
rewind(f);
|
||||
*buf = malloc((size_t)len);
|
||||
if (!*buf) {
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
if (fread(*buf, 1, (size_t)len, f) != (size_t)len) {
|
||||
fclose(f);
|
||||
free(*buf);
|
||||
return 1;
|
||||
}
|
||||
fclose(f);
|
||||
*size = (size_t)len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double now_sec(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *spv_path;
|
||||
uint32_t n;
|
||||
uint32_t passes;
|
||||
uint32_t iters;
|
||||
const VkDeviceSize bytes_in = 0;
|
||||
VkApplicationInfo app = {
|
||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
.pApplicationName = "bc250-compute-verify",
|
||||
.apiVersion = VK_API_VERSION_1_1,
|
||||
};
|
||||
VkInstanceCreateInfo ici = {
|
||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
.pApplicationInfo = &app,
|
||||
};
|
||||
VkInstance instance;
|
||||
VkPhysicalDevice pds[16];
|
||||
uint32_t pd_count = 16;
|
||||
VkPhysicalDevice pd = VK_NULL_HANDLE;
|
||||
VkPhysicalDeviceProperties pd_props;
|
||||
uint32_t queue_family = UINT32_MAX;
|
||||
VkQueueFamilyProperties qprops[32];
|
||||
uint32_t qcount = 32;
|
||||
float priority = 1.0f;
|
||||
VkDeviceQueueCreateInfo qci = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &priority,
|
||||
};
|
||||
VkDeviceCreateInfo dci = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &qci,
|
||||
};
|
||||
VkDevice dev;
|
||||
VkQueue queue;
|
||||
VkBuffer buffers[4] = {0};
|
||||
VkDeviceMemory memories[4] = {0};
|
||||
void *maps[4] = {0};
|
||||
VkDescriptorSetLayoutBinding bindings[4];
|
||||
VkDescriptorSetLayoutCreateInfo dsli = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.bindingCount = 4,
|
||||
.pBindings = bindings,
|
||||
};
|
||||
VkDescriptorSetLayout dsl;
|
||||
VkPushConstantRange pcr = {
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.offset = 0,
|
||||
.size = sizeof(struct params),
|
||||
};
|
||||
VkPipelineLayoutCreateInfo plci = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.setLayoutCount = 1,
|
||||
.pSetLayouts = &dsl,
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &pcr,
|
||||
};
|
||||
VkPipelineLayout pipeline_layout;
|
||||
char *spv = NULL;
|
||||
size_t spv_size = 0;
|
||||
VkShaderModuleCreateInfo smci = {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
};
|
||||
VkShaderModule shader;
|
||||
VkComputePipelineCreateInfo cpci = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
};
|
||||
VkPipeline pipeline;
|
||||
VkDescriptorPoolSize pool_size = {
|
||||
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.descriptorCount = 4,
|
||||
};
|
||||
VkDescriptorPoolCreateInfo dpci = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.maxSets = 1,
|
||||
.poolSizeCount = 1,
|
||||
.pPoolSizes = &pool_size,
|
||||
};
|
||||
VkDescriptorPool pool;
|
||||
VkDescriptorSetAllocateInfo dsai = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.descriptorSetCount = 1,
|
||||
};
|
||||
VkDescriptorSet ds;
|
||||
VkCommandPoolCreateInfo cmdp_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
};
|
||||
VkCommandPool cmd_pool;
|
||||
VkFenceCreateInfo fci = {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
};
|
||||
VkFence fence;
|
||||
uint64_t total_errors = 0;
|
||||
uint64_t total_fp_errors = 0;
|
||||
uint64_t total_int_errors = 0;
|
||||
uint32_t first_error_pass = UINT32_MAX;
|
||||
|
||||
(void)bytes_in;
|
||||
if (argc != 5) {
|
||||
fprintf(stderr, "usage: %s shader.spv elements passes iters\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
spv_path = argv[1];
|
||||
n = (uint32_t)strtoul(argv[2], NULL, 0);
|
||||
passes = (uint32_t)strtoul(argv[3], NULL, 0);
|
||||
iters = (uint32_t)strtoul(argv[4], NULL, 0);
|
||||
if (!n || !passes || !iters || (n % LOCAL_SIZE) != 0) {
|
||||
fprintf(stderr, "invalid elements/passes/iters\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const VkDeviceSize bytes = (VkDeviceSize)n * sizeof(uint32_t);
|
||||
|
||||
CHECK(vkCreateInstance(&ici, NULL, &instance));
|
||||
CHECK(vkEnumeratePhysicalDevices(instance, &pd_count, pds));
|
||||
for (uint32_t i = 0; i < pd_count; ++i) {
|
||||
vkGetPhysicalDeviceProperties(pds[i], &pd_props);
|
||||
if (pd_props.vendorID == 0x1002 && strstr(pd_props.deviceName, "BC-250")) {
|
||||
pd = pds[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pd == VK_NULL_HANDLE) {
|
||||
for (uint32_t i = 0; i < pd_count; ++i) {
|
||||
vkGetPhysicalDeviceProperties(pds[i], &pd_props);
|
||||
if (pd_props.vendorID == 0x1002) {
|
||||
pd = pds[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pd == VK_NULL_HANDLE) {
|
||||
fprintf(stderr, "AMD Vulkan device not found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
vkGetPhysicalDeviceProperties(pd, &pd_props);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(pd, &qcount, qprops);
|
||||
for (uint32_t i = 0; i < qcount; ++i) {
|
||||
if (qprops[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
|
||||
queue_family = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (queue_family == UINT32_MAX) {
|
||||
fprintf(stderr, "compute queue not found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
qci.queueFamilyIndex = queue_family;
|
||||
CHECK(vkCreateDevice(pd, &dci, NULL, &dev));
|
||||
vkGetDeviceQueue(dev, queue_family, 0, &queue);
|
||||
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
VkBufferCreateInfo bci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = bytes,
|
||||
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
};
|
||||
VkMemoryRequirements req;
|
||||
VkMemoryAllocateInfo mai = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
};
|
||||
uint32_t mem_type;
|
||||
|
||||
CHECK(vkCreateBuffer(dev, &bci, NULL, &buffers[i]));
|
||||
vkGetBufferMemoryRequirements(dev, buffers[i], &req);
|
||||
mem_type = find_memory_type(pd, req.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
if (mem_type == UINT32_MAX) {
|
||||
fprintf(stderr, "host visible coherent memory not found\n");
|
||||
return 1;
|
||||
}
|
||||
mai.allocationSize = req.size;
|
||||
mai.memoryTypeIndex = mem_type;
|
||||
CHECK(vkAllocateMemory(dev, &mai, NULL, &memories[i]));
|
||||
CHECK(vkBindBufferMemory(dev, buffers[i], memories[i], 0));
|
||||
CHECK(vkMapMemory(dev, memories[i], 0, bytes, 0, &maps[i]));
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
((uint32_t *)maps[0])[i] = i * 17u + 3u;
|
||||
((uint32_t *)maps[1])[i] = rotl32(i ^ 0x9e3779b9u, i & 31u) + 0x85ebca6bu;
|
||||
((uint32_t *)maps[2])[i] = 0;
|
||||
((uint32_t *)maps[3])[i] = 0;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
bindings[i].binding = i;
|
||||
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
bindings[i].descriptorCount = 1;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
bindings[i].pImmutableSamplers = NULL;
|
||||
}
|
||||
CHECK(vkCreateDescriptorSetLayout(dev, &dsli, NULL, &dsl));
|
||||
CHECK(vkCreatePipelineLayout(dev, &plci, NULL, &pipeline_layout));
|
||||
if (read_file(spv_path, &spv, &spv_size)) {
|
||||
fprintf(stderr, "failed to read SPIR-V shader: %s\n", spv_path);
|
||||
return 1;
|
||||
}
|
||||
smci.codeSize = spv_size;
|
||||
smci.pCode = (const uint32_t *)spv;
|
||||
CHECK(vkCreateShaderModule(dev, &smci, NULL, &shader));
|
||||
cpci.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
cpci.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
cpci.stage.module = shader;
|
||||
cpci.stage.pName = "main";
|
||||
cpci.layout = pipeline_layout;
|
||||
CHECK(vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &cpci, NULL, &pipeline));
|
||||
|
||||
CHECK(vkCreateDescriptorPool(dev, &dpci, NULL, &pool));
|
||||
dsai.descriptorPool = pool;
|
||||
dsai.pSetLayouts = &dsl;
|
||||
CHECK(vkAllocateDescriptorSets(dev, &dsai, &ds));
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
VkDescriptorBufferInfo dbi = {
|
||||
.buffer = buffers[i],
|
||||
.offset = 0,
|
||||
.range = bytes,
|
||||
};
|
||||
VkWriteDescriptorSet wds = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = ds,
|
||||
.dstBinding = i,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.pBufferInfo = &dbi,
|
||||
};
|
||||
vkUpdateDescriptorSets(dev, 1, &wds, 0, NULL);
|
||||
}
|
||||
|
||||
cmdp_ci.queueFamilyIndex = queue_family;
|
||||
CHECK(vkCreateCommandPool(dev, &cmdp_ci, NULL, &cmd_pool));
|
||||
CHECK(vkCreateFence(dev, &fci, NULL, &fence));
|
||||
|
||||
printf("device=%s queue_family=%u elements=%u passes=%u iters=%u\n",
|
||||
pd_props.deviceName, queue_family, n, passes, iters);
|
||||
|
||||
for (uint32_t pass = 0; pass < passes; ++pass) {
|
||||
VkCommandBufferAllocateInfo cbai = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = cmd_pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
VkCommandBuffer cmd;
|
||||
VkCommandBufferBeginInfo cbbi = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
};
|
||||
struct params p = {
|
||||
.n = n,
|
||||
.seed = 0xa5a5a5a5u ^ pass * 0x12345u,
|
||||
.pass = pass,
|
||||
.iters = iters,
|
||||
};
|
||||
uint64_t pass_errors = 0;
|
||||
uint64_t pass_fp_errors = 0;
|
||||
uint64_t pass_int_errors = 0;
|
||||
double t0;
|
||||
double t1;
|
||||
|
||||
memset(maps[2], 0, (size_t)bytes);
|
||||
memset(maps[3], 0, (size_t)bytes);
|
||||
|
||||
CHECK(vkAllocateCommandBuffers(dev, &cbai, &cmd));
|
||||
CHECK(vkBeginCommandBuffer(cmd, &cbbi));
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout,
|
||||
0, 1, &ds, 0, NULL);
|
||||
vkCmdPushConstants(cmd, pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
0, sizeof(p), &p);
|
||||
vkCmdDispatch(cmd, n / LOCAL_SIZE, 1, 1);
|
||||
CHECK(vkEndCommandBuffer(cmd));
|
||||
|
||||
{
|
||||
VkSubmitInfo si = {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmd,
|
||||
};
|
||||
t0 = now_sec();
|
||||
CHECK(vkQueueSubmit(queue, 1, &si, fence));
|
||||
CHECK(vkWaitForFences(dev, 1, &fence, VK_TRUE, UINT64_MAX));
|
||||
t1 = now_sec();
|
||||
CHECK(vkResetFences(dev, 1, &fence));
|
||||
}
|
||||
|
||||
for (uint32_t base = 0; base < n; base += LOCAL_SIZE) {
|
||||
uint32_t x[LOCAL_SIZE];
|
||||
uint32_t y[LOCAL_SIZE];
|
||||
uint32_t fp[LOCAL_SIZE];
|
||||
uint32_t lds[LOCAL_SIZE];
|
||||
|
||||
for (uint32_t lane = 0; lane < LOCAL_SIZE; ++lane) {
|
||||
uint32_t idx = base + lane;
|
||||
pre_lds_expected(idx, maps[0], maps[1], &p,
|
||||
&x[lane], &y[lane], &fp[lane]);
|
||||
lds[lane] = x[lane] ^ y[lane] ^ p.seed;
|
||||
}
|
||||
|
||||
for (uint32_t lane = 0; lane < LOCAL_SIZE; ++lane) {
|
||||
uint32_t idx = base + lane;
|
||||
uint32_t want_int;
|
||||
uint32_t got_int = ((uint32_t *)maps[2])[idx];
|
||||
uint32_t got_fp = ((uint32_t *)maps[3])[idx];
|
||||
|
||||
final_expected(idx, lds, x[lane], y[lane], &p, &want_int);
|
||||
if (got_int != want_int) {
|
||||
if (pass_errors < 16) {
|
||||
fprintf(stderr,
|
||||
"int mismatch pass=%u idx=%u got=0x%08x want=0x%08x\n",
|
||||
pass, idx, got_int, want_int);
|
||||
}
|
||||
pass_errors++;
|
||||
pass_int_errors++;
|
||||
}
|
||||
{
|
||||
uint32_t ulp_diff = fp32_ulp_distance(got_fp, fp[lane]);
|
||||
|
||||
if (ulp_diff > (p.iters / 3 + 2)) {
|
||||
if (pass_errors < 16) {
|
||||
fprintf(stderr,
|
||||
"fp mismatch pass=%u idx=%u got=0x%08x want=0x%08x ulp=%" PRIu32 "\n",
|
||||
pass, idx, got_fp, fp[lane], ulp_diff);
|
||||
}
|
||||
pass_errors++;
|
||||
pass_fp_errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("pass=%u dispatch_sec=%.6f errors=%" PRIu64 " int_errors=%" PRIu64 " fp_errors=%" PRIu64 "\n",
|
||||
pass, t1 - t0, pass_errors, pass_int_errors, pass_fp_errors);
|
||||
|
||||
if (pass_errors && first_error_pass == UINT32_MAX)
|
||||
first_error_pass = pass;
|
||||
total_errors += pass_errors;
|
||||
total_int_errors += pass_int_errors;
|
||||
total_fp_errors += pass_fp_errors;
|
||||
vkFreeCommandBuffers(dev, cmd_pool, 1, &cmd);
|
||||
}
|
||||
|
||||
printf("summary elements=%u passes=%u total_checked=%" PRIu64 " errors=%" PRIu64 " int_errors=%" PRIu64 " fp_errors=%" PRIu64 "\n",
|
||||
n, passes, (uint64_t)n * passes * 2u, total_errors,
|
||||
total_int_errors, total_fp_errors);
|
||||
|
||||
if (first_error_pass != UINT32_MAX)
|
||||
printf("first_error_pass=%u\n", first_error_pass);
|
||||
|
||||
vkDestroyFence(dev, fence, NULL);
|
||||
vkDestroyCommandPool(dev, cmd_pool, NULL);
|
||||
vkDestroyDescriptorPool(dev, pool, NULL);
|
||||
vkDestroyPipeline(dev, pipeline, NULL);
|
||||
vkDestroyShaderModule(dev, shader, NULL);
|
||||
vkDestroyPipelineLayout(dev, pipeline_layout, NULL);
|
||||
vkDestroyDescriptorSetLayout(dev, dsl, NULL);
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
vkUnmapMemory(dev, memories[i]);
|
||||
vkFreeMemory(dev, memories[i], NULL);
|
||||
vkDestroyBuffer(dev, buffers[i], NULL);
|
||||
}
|
||||
vkDestroyDevice(dev, NULL);
|
||||
vkDestroyInstance(instance, NULL);
|
||||
free(spv);
|
||||
|
||||
return total_errors ? 2 : 0;
|
||||
}
|
||||
C
|
||||
|
||||
echo "Compiling compute verifier..."
|
||||
glslangValidator -V "$TMPDIR/bc250_compute_verify.comp" -o "$TMPDIR/bc250_compute_verify.spv" >/dev/null
|
||||
gcc -std=c11 -O2 -Wall -Wextra -o "$TMPDIR/bc250_compute_verify" \
|
||||
"$TMPDIR/bc250_compute_verify.c" -lvulkan -lm
|
||||
|
||||
echo "Running BC-250 compute verifier..."
|
||||
"$TMPDIR/bc250_compute_verify" "$TMPDIR/bc250_compute_verify.spv" "$ELEMENTS" "$PASSES" "$ITERS"
|
||||
Vendored
Executable
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-cu-health-test.sh - reboot-resuming per-WGP health test for BC-250.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STATEDIR="${BC250_CU_HEALTH_STATE:-/var/lib/bc250-cu-health-test}"
|
||||
CONF="${BC250_CU_HEALTH_CONF:-/etc/modprobe.d/bc250-cu-health-isolate.conf}"
|
||||
SERVICE="${BC250_CU_HEALTH_SERVICE:-bc250-cu-health-resume.service}"
|
||||
VERIFY="${BC250_CU_VERIFY:-$(dirname "$(readlink -f "$0")")/bc250-compute-verify.sh}"
|
||||
ELEMENTS="${BC250_CU_HEALTH_ELEMENTS:-16777216}"
|
||||
PASSES="${BC250_CU_HEALTH_PASSES:-2}"
|
||||
ITERS="${BC250_CU_HEALTH_ITERS:-64}"
|
||||
REBOOT_DELAY="${BC250_CU_HEALTH_REBOOT_DELAY:-5}"
|
||||
FINAL_REBOOT="${BC250_CU_HEALTH_FINAL_REBOOT:-1}"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 start|resume|quick|status|reset
|
||||
|
||||
Commands:
|
||||
start Install a systemd resume service and test all 20 WGPs across reboots.
|
||||
resume Internal command used by systemd after each reboot.
|
||||
quick Run the heavy verifier once in the current CU configuration.
|
||||
status Print saved progress/results.
|
||||
reset Remove state, resume service, and isolation config.
|
||||
|
||||
Environment:
|
||||
BC250_CU_HEALTH_ELEMENTS=$ELEMENTS
|
||||
BC250_CU_HEALTH_PASSES=$PASSES
|
||||
BC250_CU_HEALTH_ITERS=$ITERS
|
||||
BC250_CU_HEALTH_FINAL_REBOOT=$FINAL_REBOOT
|
||||
|
||||
Results:
|
||||
$STATEDIR/results.tsv
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
need_root() {
|
||||
[ "$(id -u)" = "0" ] || die "must run as root"
|
||||
}
|
||||
|
||||
target_to_tuple() {
|
||||
local idx="$1"
|
||||
local se sh wgp
|
||||
|
||||
se=$((idx / 10))
|
||||
sh=$(((idx / 5) % 2))
|
||||
wgp=$((idx % 5))
|
||||
echo "$se $sh $wgp"
|
||||
}
|
||||
|
||||
disable_cu_for_target() {
|
||||
local target="$1"
|
||||
local out=()
|
||||
local idx se sh wgp tse tsh twgp cu
|
||||
|
||||
read -r tse tsh twgp < <(target_to_tuple "$target")
|
||||
for idx in $(seq 0 19); do
|
||||
read -r se sh wgp < <(target_to_tuple "$idx")
|
||||
if [ "$se" -eq "$tse" ] && [ "$sh" -eq "$tsh" ] && [ "$wgp" -eq "$twgp" ]; then
|
||||
continue
|
||||
fi
|
||||
out+=("$se.$sh.$((wgp * 2))")
|
||||
out+=("$se.$sh.$((wgp * 2 + 1))")
|
||||
done
|
||||
IFS=,
|
||||
echo "${out[*]}"
|
||||
}
|
||||
|
||||
write_config_for_target() {
|
||||
local target="$1"
|
||||
local disable_csv
|
||||
local se sh wgp
|
||||
|
||||
read -r se sh wgp < <(target_to_tuple "$target")
|
||||
disable_csv="$(disable_cu_for_target "$target")"
|
||||
cat >"$CONF" <<EOF
|
||||
# BC-250 per-WGP health-test isolation config.
|
||||
# Target: index=$target SE$se SH$sh WGP$wgp CUs $((wgp * 2))-$((wgp * 2 + 1)).
|
||||
# Generated by $0 on $(date -Iseconds).
|
||||
options amdgpu bc250_cc_write_mode=3 disable_cu=$disable_csv
|
||||
EOF
|
||||
}
|
||||
|
||||
write_full_config() {
|
||||
cat >"$CONF" <<EOF
|
||||
# BC-250 full 40-CU config restored after health test.
|
||||
options amdgpu bc250_cc_write_mode=3
|
||||
EOF
|
||||
}
|
||||
|
||||
refresh_initramfs() {
|
||||
if command -v update-initramfs >/dev/null 2>&1; then
|
||||
update-initramfs -u -k "$(uname -r)" || true
|
||||
elif command -v dracut >/dev/null 2>&1; then
|
||||
dracut -f || true
|
||||
fi
|
||||
}
|
||||
|
||||
install_service() {
|
||||
local self
|
||||
|
||||
self="$(readlink -f "$0")"
|
||||
cat >/etc/systemd/system/"$SERVICE" <<EOF
|
||||
[Unit]
|
||||
Description=Resume BC-250 per-WGP CU health test
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash $self resume
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE"
|
||||
}
|
||||
|
||||
remove_service() {
|
||||
systemctl disable "$SERVICE" >/dev/null 2>&1 || true
|
||||
rm -f /etc/systemd/system/"$SERVICE"
|
||||
systemctl daemon-reload || true
|
||||
}
|
||||
|
||||
current_cu_count() {
|
||||
dmesg | grep -o 'active_cu_number [0-9]*' | tail -1 | awk '{print $2}'
|
||||
}
|
||||
|
||||
run_verify_for_target() {
|
||||
local target="$1"
|
||||
local se sh wgp log status rc cu_count started finished
|
||||
|
||||
read -r se sh wgp < <(target_to_tuple "$target")
|
||||
log="$STATEDIR/logs/wgp-${target}-se${se}-sh${sh}-wgp${wgp}.log"
|
||||
started="$(date -Iseconds)"
|
||||
cu_count="$(current_cu_count || true)"
|
||||
status="PASS"
|
||||
rc=0
|
||||
|
||||
echo "Testing target index=$target SE$se SH$sh WGP$wgp..."
|
||||
set +e
|
||||
"$VERIFY" --elements "$ELEMENTS" --passes "$PASSES" --iters "$ITERS" 2>&1 | tee "$log"
|
||||
rc=${PIPESTATUS[0]}
|
||||
set -e
|
||||
[ "$rc" -eq 0 ] || status="FAIL"
|
||||
finished="$(date -Iseconds)"
|
||||
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$target" "$se" "$sh" "$wgp" "$status" "$rc" \
|
||||
"${cu_count:-unknown}" "$started" "$finished" >>"$STATEDIR/results.tsv"
|
||||
}
|
||||
|
||||
next_reboot() {
|
||||
local target="$1"
|
||||
local se sh wgp
|
||||
|
||||
read -r se sh wgp < <(target_to_tuple "$target")
|
||||
write_config_for_target "$target"
|
||||
echo "$target" >"$STATEDIR/current_target"
|
||||
refresh_initramfs
|
||||
echo "Configured next isolated target: index=$target SE$se SH$sh WGP$wgp."
|
||||
echo "Rebooting in $REBOOT_DELAY seconds..."
|
||||
sleep "$REBOOT_DELAY"
|
||||
reboot
|
||||
}
|
||||
|
||||
finish_test() {
|
||||
write_full_config
|
||||
refresh_initramfs
|
||||
remove_service
|
||||
echo "done" >"$STATEDIR/phase"
|
||||
echo "Per-WGP health test complete."
|
||||
echo "Results: $STATEDIR/results.tsv"
|
||||
awk -F'\t' 'BEGIN{pass=0; fail=0} $5=="PASS"{pass++} $5=="FAIL"{fail++} END{printf "PASS WGPs: %d FAIL WGPs: %d usable CUs: %d/40\n", pass, fail, 40 - fail * 2}' "$STATEDIR/results.tsv"
|
||||
if [ "$FINAL_REBOOT" = "1" ]; then
|
||||
echo "Rebooting in $REBOOT_DELAY seconds to reload amdgpu with the restored full-40CU config..."
|
||||
sleep "$REBOOT_DELAY"
|
||||
reboot
|
||||
else
|
||||
echo "Full 40-CU config is written for the next boot; current boot remains on the last isolated WGP."
|
||||
fi
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
case "$cmd" in
|
||||
start)
|
||||
need_root
|
||||
[ -x "$VERIFY" ] || die "verifier not executable: $VERIFY"
|
||||
mkdir -p "$STATEDIR/logs"
|
||||
cat >"$STATEDIR/results.tsv" <<'EOF'
|
||||
#idx se sh wgp status rc active_cu started finished
|
||||
EOF
|
||||
echo "running" >"$STATEDIR/phase"
|
||||
install_service
|
||||
next_reboot 0
|
||||
;;
|
||||
resume)
|
||||
need_root
|
||||
[ -x "$VERIFY" ] || die "verifier not executable: $VERIFY"
|
||||
mkdir -p "$STATEDIR/logs"
|
||||
phase="$(cat "$STATEDIR/phase" 2>/dev/null || echo missing)"
|
||||
[ "$phase" = "running" ] || die "health test is not running (phase=$phase)"
|
||||
target="$(cat "$STATEDIR/current_target" 2>/dev/null || echo 0)"
|
||||
run_verify_for_target "$target"
|
||||
next=$((target + 1))
|
||||
if [ "$next" -lt 20 ]; then
|
||||
next_reboot "$next"
|
||||
else
|
||||
finish_test
|
||||
fi
|
||||
;;
|
||||
quick)
|
||||
[ -x "$VERIFY" ] || die "verifier not executable: $VERIFY"
|
||||
"$VERIFY" --elements "$ELEMENTS" --passes "$PASSES" --iters "$ITERS"
|
||||
;;
|
||||
status)
|
||||
if [ -f "$STATEDIR/results.tsv" ]; then
|
||||
cat "$STATEDIR/results.tsv"
|
||||
else
|
||||
echo "No health-test results in $STATEDIR"
|
||||
fi
|
||||
;;
|
||||
reset)
|
||||
need_root
|
||||
remove_service
|
||||
rm -rf "$STATEDIR"
|
||||
rm -f "$CONF"
|
||||
refresh_initramfs
|
||||
echo "Removed health-test state, service, and isolation config."
|
||||
;;
|
||||
-h|--help|"")
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
die "unknown command: $cmd"
|
||||
;;
|
||||
esac
|
||||
Vendored
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-cu-mask.sh - generate/install selective WGP masks for BC-250 40-CU unlock.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RESULTS="${BC250_CU_HEALTH_RESULTS:-/var/lib/bc250-cu-health-test/results.tsv}"
|
||||
CONF="${BC250_CU_MASK_CONF:-/etc/modprobe.d/bc250-40cu-selective-mask.conf}"
|
||||
INSTALL=0
|
||||
BAD_ITEMS=()
|
||||
TMP_CONF="$(mktemp)"
|
||||
trap 'rm -f "$TMP_CONF"' EXIT
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [--results FILE] [--bad SE.SH.WGP[,SE.SH.WGP...]] [--bad-cu SE.SH.CU[,SE.SH.CU...]] [--install]
|
||||
|
||||
Examples:
|
||||
$0 --bad 1.0.3
|
||||
$0 --results /var/lib/bc250-cu-health-test/results.tsv --install
|
||||
$0 --bad-cu 1.0.6,1.0.7 --install
|
||||
|
||||
Output modprobe config:
|
||||
options amdgpu bc250_cc_write_mode=3 disable_cu=SE.SH.CU,...
|
||||
|
||||
WGP is the hardware disable granularity. Each bad WGP expands to both CUs.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
split_csv() {
|
||||
local item
|
||||
IFS=',' read -ra _items <<<"$1"
|
||||
for item in "${_items[@]}"; do
|
||||
[ -n "$item" ] && BAD_ITEMS+=("$item")
|
||||
done
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--results)
|
||||
RESULTS="${2:?missing value for --results}"
|
||||
shift 2
|
||||
;;
|
||||
--bad)
|
||||
split_csv "${2:?missing value for --bad}"
|
||||
shift 2
|
||||
;;
|
||||
--bad-cu)
|
||||
IFS=',' read -ra _cus <<<"${2:?missing value for --bad-cu}"
|
||||
for cu_item in "${_cus[@]}"; do
|
||||
IFS='.' read -r se sh cu <<<"$cu_item"
|
||||
[[ "$se" =~ ^[0-1]$ && "$sh" =~ ^[0-1]$ && "$cu" =~ ^[0-9]$ ]] ||
|
||||
die "invalid CU entry: $cu_item"
|
||||
BAD_ITEMS+=("$se.$sh.$((cu / 2))")
|
||||
done
|
||||
shift 2
|
||||
;;
|
||||
--install)
|
||||
INSTALL=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown argument: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${#BAD_ITEMS[@]}" -eq 0 ] && [ -f "$RESULTS" ]; then
|
||||
while IFS=$'\t' read -r idx se sh wgp status rest; do
|
||||
case "$idx" in ""|\#*) continue ;; esac
|
||||
[ "$status" = "FAIL" ] || continue
|
||||
BAD_ITEMS+=("$se.$sh.$wgp")
|
||||
done <"$RESULTS"
|
||||
fi
|
||||
|
||||
declare -A seen_wgp=()
|
||||
declare -A seen_cu=()
|
||||
bad_wgps=()
|
||||
disable_cus=()
|
||||
|
||||
for item in "${BAD_ITEMS[@]}"; do
|
||||
IFS='.' read -r se sh wgp <<<"$item"
|
||||
[[ "$se" =~ ^[0-1]$ && "$sh" =~ ^[0-1]$ && "$wgp" =~ ^[0-4]$ ]] ||
|
||||
die "invalid WGP entry: $item"
|
||||
key="$se.$sh.$wgp"
|
||||
if [ -z "${seen_wgp[$key]+x}" ]; then
|
||||
seen_wgp[$key]=1
|
||||
bad_wgps+=("$key")
|
||||
fi
|
||||
for cu in $((wgp * 2)) $((wgp * 2 + 1)); do
|
||||
cu_key="$se.$sh.$cu"
|
||||
if [ -z "${seen_cu[$cu_key]+x}" ]; then
|
||||
seen_cu[$cu_key]=1
|
||||
disable_cus+=("$cu_key")
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
disable_csv="$(IFS=,; echo "${disable_cus[*]}")"
|
||||
|
||||
{
|
||||
echo "# BC-250 selective 40-CU mask."
|
||||
echo "# Generated by $0 on $(date -Iseconds)."
|
||||
echo "# Bad WGPs: ${bad_wgps[*]:-none}"
|
||||
echo "# Requires patched amdgpu with bc250_cc_write_mode support."
|
||||
if [ -n "$disable_csv" ]; then
|
||||
echo "options amdgpu bc250_cc_write_mode=3 disable_cu=$disable_csv"
|
||||
else
|
||||
echo "options amdgpu bc250_cc_write_mode=3"
|
||||
fi
|
||||
} >"$TMP_CONF"
|
||||
|
||||
if [ "$INSTALL" -eq 1 ]; then
|
||||
[ "$(id -u)" = "0" ] || die "--install requires root"
|
||||
install -m 0644 "$TMP_CONF" "$CONF"
|
||||
echo "Installed $CONF"
|
||||
if command -v update-initramfs >/dev/null 2>&1; then
|
||||
update-initramfs -u -k "$(uname -r)" || true
|
||||
elif command -v dracut >/dev/null 2>&1; then
|
||||
dracut -f || true
|
||||
fi
|
||||
else
|
||||
cat "$TMP_CONF"
|
||||
fi
|
||||
|
||||
usable=$((40 - ${#bad_wgps[@]} * 2))
|
||||
echo "# Usable after mask: $usable/40 CUs (${#bad_wgps[@]} bad WGPs masked)"
|
||||
Vendored
Executable
+386
@@ -0,0 +1,386 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-enable-40cu-arch.sh — Build and install a patched amdgpu for 40 CU on BC-250
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./bc250-enable-40cu-arch.sh build # patch + compile + install
|
||||
# sudo ./bc250-enable-40cu-arch.sh enable # set 40 CU mode and reboot
|
||||
# sudo ./bc250-enable-40cu-arch.sh disable # return to stock 24 CU and reboot
|
||||
# sudo ./bc250-enable-40cu-arch.sh status # show current CU state
|
||||
# sudo ./bc250-enable-40cu-arch.sh restore # restore original amdgpu module
|
||||
#
|
||||
# Requirements: linux-headers, gcc, make, zstd, curl. Must run as root on BC-250.
|
||||
# Tested on: Arch Linux
|
||||
#
|
||||
# Authors: duggasco, Claude | License: GPL-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
KVER="$(uname -r)"
|
||||
KVER_BASE="${KVER%%-*}" # e.g. 6.9.3 from 6.9.3-arch1-1
|
||||
MODDIR="/usr/lib/modules/${KVER}"
|
||||
MODPATH="${MODDIR}/kernel/drivers/gpu/drm/amd/amdgpu/amdgpu.ko"
|
||||
MODSRC=""
|
||||
BUILDDIR="/tmp/bc250-40cu-build"
|
||||
CONF40="/etc/modprobe.d/bc250-40cu.conf"
|
||||
BACKUP_SUFFIX=".bc250-backup-$(date +%Y%m%d)"
|
||||
BC250_PCI_ID="13fe"
|
||||
|
||||
info() { printf '\033[0;32m[+]\033[0m %s\n' "$*" >&2; }
|
||||
warn() { printf '\033[0;33m[!]\033[0m %s\n' "$*" >&2; }
|
||||
err() { printf '\033[0;31m[E]\033[0m %s\n' "$*" >&2; }
|
||||
die() { err "$@"; exit 1; }
|
||||
|
||||
write_param_patch() {
|
||||
cat > "$1" << 'ENDPARAM'
|
||||
|
||||
/* BC-250 40 CU unlock: clears harvest mask + enables SPI dispatch to all WGPs */
|
||||
static int bc250_cc_write_mode;
|
||||
module_param(bc250_cc_write_mode, int, 0444);
|
||||
MODULE_PARM_DESC(bc250_cc_write_mode,
|
||||
"BC-250: 0=off 1=probe-SE0SH0 2=clear-SE0SH0 3=clear-all-SAs 4=probe-all-SAs");
|
||||
#define BC250_PCI_DEVICE_ID 0x13FE
|
||||
|
||||
ENDPARAM
|
||||
}
|
||||
|
||||
write_cc_patch() {
|
||||
cat > "$1" << 'ENDCC'
|
||||
|
||||
/* BC-250: unlock harvested CUs — CC (enumeration) + SPI (dispatch) + RLC (power) */
|
||||
if (bc250_cc_write_mode > 0 && adev->pdev->device == BC250_PCI_DEVICE_ID) {
|
||||
int bc_se, bc_sh;
|
||||
for (bc_se = 0; bc_se < adev->gfx.config.max_shader_engines; bc_se++) {
|
||||
for (bc_sh = 0; bc_sh < adev->gfx.config.max_sh_per_se; bc_sh++) {
|
||||
u32 bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after;
|
||||
if (bc250_cc_write_mode == 2 && (bc_se > 0 || bc_sh > 0))
|
||||
continue;
|
||||
gfx_v10_0_select_se_sh(adev, bc_se, bc_sh, 0xffffffff, 0);
|
||||
bc_cc_orig = RREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG);
|
||||
WREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG, 0);
|
||||
bc_cc_after = RREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG);
|
||||
bc_spi_orig = RREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK);
|
||||
WREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK, 0x1f);
|
||||
bc_spi_after = RREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK);
|
||||
WREG32_SOC15(GC, 0, mmRLC_PG_ALWAYS_ON_WGP_MASK, 0x1f);
|
||||
if (bc250_cc_write_mode == 1 || bc250_cc_write_mode == 4) {
|
||||
WREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG, bc_cc_orig);
|
||||
WREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK, bc_spi_orig);
|
||||
dev_info(adev->dev,
|
||||
"bc250-40cu-probe: se=%d sh=%d CC=0x%08x->0x%08x SPI=0x%08x->0x%08x (restored)",
|
||||
bc_se, bc_sh, bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after);
|
||||
} else {
|
||||
dev_info(adev->dev,
|
||||
"bc250-40cu-enable: mode=%d se=%d sh=%d CC=0x%08x->0x%08x SPI=0x%08x->0x%08x",
|
||||
bc250_cc_write_mode, bc_se, bc_sh,
|
||||
bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after);
|
||||
}
|
||||
}
|
||||
}
|
||||
gfx_v10_0_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, 0);
|
||||
}
|
||||
|
||||
ENDCC
|
||||
}
|
||||
|
||||
check_bc250() {
|
||||
if ! lspci -nn 2>/dev/null | grep -qi "${BC250_PCI_ID}"; then
|
||||
warn "No BC-250 (PCI ID 13fe) detected. This patch is BC-250 specific."
|
||||
printf "Continue anyway? [y/N] "
|
||||
read -r ans
|
||||
case "$ans" in y|Y) ;; *) exit 1 ;; esac
|
||||
fi
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
local missing=""
|
||||
command -v gcc >/dev/null 2>&1 || missing="${missing} gcc"
|
||||
command -v make >/dev/null 2>&1 || missing="${missing} make"
|
||||
command -v zstd >/dev/null 2>&1 || missing="${missing} zstd"
|
||||
command -v curl >/dev/null 2>&1 || missing="${missing} curl"
|
||||
if [ ! -d "${MODDIR}/build" ]; then
|
||||
missing="${missing} linux-headers"
|
||||
fi
|
||||
if [ -n "$missing" ]; then
|
||||
err "Missing dependencies:${missing}"
|
||||
err "Install with: pacman -S base-devel zstd curl linux-headers"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
find_source() {
|
||||
local d
|
||||
# Check common source locations (including Arch /usr/src/linux)
|
||||
for d in \
|
||||
"/usr/src/linux-${KVER}" \
|
||||
"/usr/src/linux-${KVER_BASE}" \
|
||||
"/usr/src/linux" \
|
||||
"/usr/src/linux-source-${KVER_BASE}"; do
|
||||
if [ -f "$d/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="$d"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for a pre-extracted tarball left from a previous build
|
||||
if [ -f "${BUILDDIR}/src/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="${BUILDDIR}/src"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Download minimal amdgpu subtree directly from kernel.org
|
||||
local major="${KVER_BASE%%.*}"
|
||||
local url="https://cdn.kernel.org/pub/linux/kernel/v${major}.x/linux-${KVER_BASE}.tar.xz"
|
||||
info "Kernel source not found locally."
|
||||
info "Downloading amdgpu source from kernel.org (~120 MB)..."
|
||||
info " ${url}"
|
||||
|
||||
mkdir -p "${BUILDDIR}/src"
|
||||
if curl -fL --progress-bar "$url" | \
|
||||
tar xJ -C "${BUILDDIR}/src" --strip-components=1 \
|
||||
--wildcards \
|
||||
'*/drivers/gpu/drm/amd/' \
|
||||
'*/include/drm/' \
|
||||
'*/include/uapi/drm/' \
|
||||
2>/dev/null; then
|
||||
if [ -f "${BUILDDIR}/src/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="${BUILDDIR}/src"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
die "Cannot find kernel source for ${KVER_BASE}.
|
||||
Option 1: pacman -S asp && asp checkout linux (then makepkg -o)
|
||||
Option 2: place extracted source at /usr/src/linux-${KVER_BASE}"
|
||||
}
|
||||
|
||||
patch_source() {
|
||||
local gfx="${MODSRC}/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c"
|
||||
[ -f "$gfx" ] || die "gfx_v10_0.c not found at ${gfx}"
|
||||
|
||||
if grep -q 'bc250_cc_write_mode' "$gfx"; then
|
||||
info "Source already patched."
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Patching gfx_v10_0.c..."
|
||||
cp "$gfx" "${gfx}.orig"
|
||||
|
||||
# Step 1: insert module parameter before '#include "amdgpu.h"'
|
||||
if ! grep -q '#include "amdgpu.h"' "$gfx"; then
|
||||
die "Cannot find anchor: #include amdgpu.h"
|
||||
fi
|
||||
|
||||
local param_file
|
||||
param_file="$(mktemp)"
|
||||
write_param_patch "$param_file"
|
||||
sed -i "/#include \"amdgpu.h\"/r ${param_file}" "$gfx"
|
||||
rm -f "$param_file"
|
||||
|
||||
# Step 2: insert CC write block in gfx_v10_0_get_cu_info after mutex_lock
|
||||
local cc_file
|
||||
cc_file="$(mktemp)"
|
||||
write_cc_patch "$cc_file"
|
||||
|
||||
# Two-phase awk: avoid matching the forward declaration of gfx_v10_0_get_cu_info.
|
||||
# A forward declaration ends with "); " on a later line, while the actual body
|
||||
# starts with a standalone "{" on its own line right after the signature.
|
||||
awk -v insertfile="$cc_file" '
|
||||
/static.*gfx_v10_0_get_cu_info/ { maybe_func = 1 }
|
||||
maybe_func && /;/ { maybe_func = 0 }
|
||||
maybe_func && /^\{/ { in_cu_info = 1; maybe_func = 0 }
|
||||
in_cu_info && /mutex_lock/ && !inserted {
|
||||
print
|
||||
while ((getline line < insertfile) > 0) print line
|
||||
close(insertfile)
|
||||
inserted = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$gfx" > "${gfx}.new"
|
||||
|
||||
if grep -q 'bc250-40cu-enable' "${gfx}.new"; then
|
||||
mv "${gfx}.new" "$gfx"
|
||||
rm -f "$cc_file"
|
||||
info "Patch applied successfully."
|
||||
else
|
||||
rm -f "${gfx}.new" "$cc_file"
|
||||
mv "${gfx}.orig" "$gfx"
|
||||
die "Failed to insert CC write block. Kernel source layout may differ."
|
||||
fi
|
||||
}
|
||||
|
||||
build_module() {
|
||||
local amdgpu_dir="${MODSRC}/drivers/gpu/drm/amd/amdgpu"
|
||||
[ -d "$amdgpu_dir" ] || die "amdgpu source directory not found"
|
||||
|
||||
# define_trace.h (in the kernel headers) resolves the trace header as:
|
||||
# ../../drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h
|
||||
# relative to its own location, ending up at:
|
||||
# ${kbuild}/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h
|
||||
# That directory already exists in linux-headers (contains only Kconfig),
|
||||
# so we copy the trace header there temporarily for the build.
|
||||
local kbuild="${MODDIR}/build"
|
||||
local kbuild_amdgpu="${kbuild}/drivers/gpu/drm/amd/amdgpu"
|
||||
local trace_dst="${kbuild_amdgpu}/amdgpu_trace.h"
|
||||
local trace_copied=0
|
||||
if [ ! -f "$trace_dst" ]; then
|
||||
mkdir -p "$kbuild_amdgpu"
|
||||
cp "${amdgpu_dir}/amdgpu_trace.h" "$trace_dst"
|
||||
trace_copied=1
|
||||
fi
|
||||
|
||||
info "Building amdgpu module for kernel ${KVER} (2-5 min)..."
|
||||
make -C "$kbuild" M="$amdgpu_dir" -j"$(nproc)" modules 2>&1 | tail -10 >&2
|
||||
local make_rc=${PIPESTATUS[0]}
|
||||
|
||||
[ "$trace_copied" -eq 1 ] && rm -f "$trace_dst"
|
||||
|
||||
[ "$make_rc" -eq 0 ] || die "Build failed (make exited $make_rc)"
|
||||
|
||||
local built="${amdgpu_dir}/amdgpu.ko"
|
||||
[ -f "$built" ] || die "Build failed - amdgpu.ko not produced"
|
||||
|
||||
# Use grep -qa to avoid SIGPIPE/pipefail issue with `strings | grep -q` on large .ko files
|
||||
if ! grep -qa 'bc250_cc_write_mode' "$built"; then
|
||||
die "Built module missing bc250_cc_write_mode - patch failed"
|
||||
fi
|
||||
|
||||
info "Build successful: ${built} ($(du -h "$built" | cut -f1))"
|
||||
echo "$built"
|
||||
}
|
||||
|
||||
install_module() {
|
||||
local built="$1"
|
||||
local target="${MODPATH}"
|
||||
|
||||
if [ -f "${target}.zst" ]; then
|
||||
target="${target}.zst"
|
||||
elif [ ! -f "$target" ]; then
|
||||
target="${target}.zst"
|
||||
fi
|
||||
|
||||
if [ -f "$target" ] && [ ! -f "${target}${BACKUP_SUFFIX}" ]; then
|
||||
info "Backing up original to ${target}${BACKUP_SUFFIX}"
|
||||
cp "$target" "${target}${BACKUP_SUFFIX}"
|
||||
fi
|
||||
|
||||
if [ "${target%.zst}" != "$target" ]; then
|
||||
info "Compressing and installing module..."
|
||||
zstd -f "$built" -o "$target"
|
||||
else
|
||||
cp "$built" "$target"
|
||||
fi
|
||||
|
||||
depmod -a "$KVER"
|
||||
info "Module installed at ${target}"
|
||||
}
|
||||
|
||||
do_build() {
|
||||
check_bc250
|
||||
check_deps
|
||||
find_source
|
||||
patch_source
|
||||
local built
|
||||
built="$(build_module)"
|
||||
install_module "$built"
|
||||
echo ""
|
||||
info "Done! Patched amdgpu module installed."
|
||||
info "Next: sudo $0 enable"
|
||||
}
|
||||
|
||||
do_enable() {
|
||||
printf '# BC-250 40 CU re-enablement\noptions amdgpu bc250_cc_write_mode=3\n' > "$CONF40"
|
||||
info "40 CU mode configured in ${CONF40}"
|
||||
if ! ( set +o pipefail; modinfo amdgpu 2>/dev/null | grep -q 'bc250_cc_write_mode' ); then
|
||||
warn "Patched module not detected. Run: sudo $0 build"
|
||||
rm -f "$CONF40"
|
||||
exit 1
|
||||
fi
|
||||
info "Rebooting..."
|
||||
sleep 2
|
||||
reboot
|
||||
}
|
||||
|
||||
do_disable() {
|
||||
rm -f "$CONF40"
|
||||
info "40 CU config removed. Rebooting to stock 24 CU..."
|
||||
sleep 2
|
||||
reboot
|
||||
}
|
||||
|
||||
do_restore() {
|
||||
local target="${MODPATH}"
|
||||
if [ -f "${target}.zst" ]; then target="${target}.zst"; fi
|
||||
local backup
|
||||
backup="$(ls -1 "${target}.bc250-backup-"* 2>/dev/null | head -1)"
|
||||
[ -n "$backup" ] || die "No backup found"
|
||||
cp "$backup" "$target"
|
||||
rm -f "$CONF40"
|
||||
depmod -a "$KVER"
|
||||
info "Original module restored. Reboot to apply."
|
||||
}
|
||||
|
||||
do_status() {
|
||||
printf '\033[1m=== BC-250 CU Status ===\033[0m\n\n'
|
||||
|
||||
if lspci -nn 2>/dev/null | grep -qi "${BC250_PCI_ID}"; then
|
||||
printf ' PCI device: \033[0;32mBC-250 detected\033[0m\n'
|
||||
else
|
||||
printf ' PCI device: \033[0;31mBC-250 not found\033[0m\n'
|
||||
fi
|
||||
|
||||
if ( set +o pipefail; modinfo amdgpu 2>/dev/null | grep -q 'bc250_cc_write_mode' ); then
|
||||
printf ' amdgpu module: \033[0;32mpatched\033[0m\n'
|
||||
else
|
||||
printf ' amdgpu module: \033[0;33mstock (unpatched)\033[0m\n'
|
||||
fi
|
||||
|
||||
local mode
|
||||
mode="$(cat /sys/module/amdgpu/parameters/bc250_cc_write_mode 2>/dev/null || echo 'N/A')"
|
||||
printf ' write_mode: %s\n' "$mode"
|
||||
|
||||
local cu_line
|
||||
cu_line="$(dmesg 2>/dev/null | grep 'active_cu_number' | tail -1)"
|
||||
if [ -n "$cu_line" ]; then
|
||||
local cus
|
||||
cus="$(echo "$cu_line" | grep -o 'active_cu_number [0-9]*' | awk '{print $2}')"
|
||||
if [ "$cus" = "40" ]; then
|
||||
printf ' active CUs: \033[0;32m\033[1m40\033[0m (full die)\n'
|
||||
elif [ "$cus" = "24" ]; then
|
||||
printf ' active CUs: \033[0;33m24\033[0m (stock)\n'
|
||||
else
|
||||
printf ' active CUs: %s\n' "$cus"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$CONF40" ]; then
|
||||
printf ' modprobe conf: \033[0;32m%s (40 CU enabled)\033[0m\n' "$CONF40"
|
||||
else
|
||||
printf ' modprobe conf: (none - stock mode)\n'
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
build) do_build ;;
|
||||
enable) do_enable ;;
|
||||
disable) do_disable ;;
|
||||
restore) do_restore ;;
|
||||
status) do_status ;;
|
||||
*)
|
||||
echo "BC-250 40 CU Re-enablement Tool (Arch Linux)"
|
||||
echo ""
|
||||
echo "Usage: sudo $0 <command>"
|
||||
echo ""
|
||||
echo " build Patch, compile, install patched amdgpu (~5 min)"
|
||||
echo " enable Activate 40 CU mode and reboot"
|
||||
echo " disable Return to stock 24 CU and reboot"
|
||||
echo " status Show current CU state"
|
||||
echo " restore Restore original amdgpu module"
|
||||
echo ""
|
||||
echo "Quick start:"
|
||||
echo " sudo $0 build && sudo $0 enable"
|
||||
echo ""
|
||||
echo "Dependencies: pacman -S base-devel zstd curl linux-headers"
|
||||
;;
|
||||
esac
|
||||
Vendored
Executable
+387
@@ -0,0 +1,387 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-enable-40cu-fedora.sh — Build and install a patched amdgpu for 40 CU on BC-250
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./bc250-enable-40cu-fedora.sh build # patch + compile + install
|
||||
# sudo ./bc250-enable-40cu-fedora.sh enable # set 40 CU mode and reboot
|
||||
# sudo ./bc250-enable-40cu-fedora.sh disable # return to stock 24 CU and reboot
|
||||
# sudo ./bc250-enable-40cu-fedora.sh status # show current CU state
|
||||
# sudo ./bc250-enable-40cu-fedora.sh restore # restore original amdgpu module
|
||||
#
|
||||
# Requirements: kernel-devel, gcc, make, zstd, curl. Must run as root on BC-250.
|
||||
# Tested on: Fedora 43 with kernel 7.0.9-105.fc43.x86_64
|
||||
#
|
||||
# Authors: duggasco, Claude, Martin | License: GPL-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
KVER="$(uname -r)"
|
||||
KVER_BASE="${KVER%%-*}" # e.g. 6.9.3 from 6.9.3-arch1-1
|
||||
MODDIR="/usr/lib/modules/${KVER}"
|
||||
MODPATH="${MODDIR}/kernel/drivers/gpu/drm/amd/amdgpu/amdgpu.ko"
|
||||
MODSRC=""
|
||||
BUILDDIR="/tmp/bc250-40cu-build"
|
||||
CONF40="/etc/modprobe.d/bc250-40cu.conf"
|
||||
BACKUP_SUFFIX=".bc250-backup-$(date +%Y%m%d)"
|
||||
BC250_PCI_ID="13fe"
|
||||
|
||||
info() { printf '\033[0;32m[+]\033[0m %s\n' "$*" >&2; }
|
||||
warn() { printf '\033[0;33m[!]\033[0m %s\n' "$*" >&2; }
|
||||
err() { printf '\033[0;31m[E]\033[0m %s\n' "$*" >&2; }
|
||||
die() { err "$@"; exit 1; }
|
||||
|
||||
write_param_patch() {
|
||||
cat > "$1" << 'ENDPARAM'
|
||||
|
||||
/* BC-250 40 CU unlock: clears harvest mask + enables SPI dispatch to all WGPs */
|
||||
static int bc250_cc_write_mode;
|
||||
module_param(bc250_cc_write_mode, int, 0444);
|
||||
MODULE_PARM_DESC(bc250_cc_write_mode,
|
||||
"BC-250: 0=off 1=probe-SE0SH0 2=clear-SE0SH0 3=clear-all-SAs 4=probe-all-SAs");
|
||||
#define BC250_PCI_DEVICE_ID 0x13FE
|
||||
|
||||
ENDPARAM
|
||||
}
|
||||
|
||||
write_cc_patch() {
|
||||
cat > "$1" << 'ENDCC'
|
||||
|
||||
/* BC-250: unlock harvested CUs — CC (enumeration) + SPI (dispatch) + RLC (power) */
|
||||
if (bc250_cc_write_mode > 0 && adev->pdev->device == BC250_PCI_DEVICE_ID) {
|
||||
int bc_se, bc_sh;
|
||||
for (bc_se = 0; bc_se < adev->gfx.config.max_shader_engines; bc_se++) {
|
||||
for (bc_sh = 0; bc_sh < adev->gfx.config.max_sh_per_se; bc_sh++) {
|
||||
u32 bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after;
|
||||
if (bc250_cc_write_mode == 2 && (bc_se > 0 || bc_sh > 0))
|
||||
continue;
|
||||
gfx_v10_0_select_se_sh(adev, bc_se, bc_sh, 0xffffffff, 0);
|
||||
bc_cc_orig = RREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG);
|
||||
WREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG, 0);
|
||||
bc_cc_after = RREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG);
|
||||
bc_spi_orig = RREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK);
|
||||
WREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK, 0x1f);
|
||||
bc_spi_after = RREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK);
|
||||
WREG32_SOC15(GC, 0, mmRLC_PG_ALWAYS_ON_WGP_MASK, 0x1f);
|
||||
if (bc250_cc_write_mode == 1 || bc250_cc_write_mode == 4) {
|
||||
WREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG, bc_cc_orig);
|
||||
WREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK, bc_spi_orig);
|
||||
dev_info(adev->dev,
|
||||
"bc250-40cu-probe: se=%d sh=%d CC=0x%08x->0x%08x SPI=0x%08x->0x%08x (restored)",
|
||||
bc_se, bc_sh, bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after);
|
||||
} else {
|
||||
dev_info(adev->dev,
|
||||
"bc250-40cu-enable: mode=%d se=%d sh=%d CC=0x%08x->0x%08x SPI=0x%08x->0x%08x",
|
||||
bc250_cc_write_mode, bc_se, bc_sh,
|
||||
bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after);
|
||||
}
|
||||
}
|
||||
}
|
||||
gfx_v10_0_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, 0);
|
||||
}
|
||||
|
||||
ENDCC
|
||||
}
|
||||
|
||||
check_bc250() {
|
||||
if ! lspci -nn 2>/dev/null | grep -qi "${BC250_PCI_ID}"; then
|
||||
warn "No BC-250 (PCI ID 13fe) detected. This patch is BC-250 specific."
|
||||
printf "Continue anyway? [y/N] "
|
||||
read -r ans
|
||||
case "$ans" in y|Y) ;; *) exit 1 ;; esac
|
||||
fi
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
local missing=""
|
||||
command -v gcc >/dev/null 2>&1 || missing="${missing} gcc"
|
||||
command -v make >/dev/null 2>&1 || missing="${missing} make"
|
||||
command -v zstd >/dev/null 2>&1 || missing="${missing} zstd"
|
||||
command -v curl >/dev/null 2>&1 || missing="${missing} curl"
|
||||
if [ ! -d "${MODDIR}/build" ]; then
|
||||
missing="${missing} linux-headers"
|
||||
fi
|
||||
if [ -n "$missing" ]; then
|
||||
err "Missing dependencies:${missing}"
|
||||
err "Install with: dnf install kernel-devel-$(uname -r) gcc make zstd curl"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
find_source() {
|
||||
local d
|
||||
# Check common source locations
|
||||
for d in \
|
||||
"/usr/src/linux-${KVER}" \
|
||||
"/usr/src/linux-${KVER_BASE}" \
|
||||
"/usr/src/linux" \
|
||||
"/usr/src/kernels/${KVER}-full"; do
|
||||
if [ -f "$d/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="$d"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for a pre-extracted tarball left from a previous build
|
||||
if [ -f "${BUILDDIR}/src/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="${BUILDDIR}/src"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Download minimal amdgpu subtree directly from kernel.org
|
||||
local major="${KVER_BASE%%.*}"
|
||||
local url="https://cdn.kernel.org/pub/linux/kernel/v${major}.x/linux-${KVER_BASE}.tar.xz"
|
||||
info "Kernel source not found locally."
|
||||
info "Downloading amdgpu source from kernel.org (~120 MB)..."
|
||||
info " ${url}"
|
||||
|
||||
mkdir -p "${BUILDDIR}/src"
|
||||
if curl -fL --progress-bar "$url" | \
|
||||
tar xJ -C "${BUILDDIR}/src" --strip-components=1 \
|
||||
--wildcards \
|
||||
'*/drivers/gpu/drm/amd/' \
|
||||
'*/include/drm/' \
|
||||
'*/include/uapi/drm/' \
|
||||
2>/dev/null; then
|
||||
if [ -f "${BUILDDIR}/src/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="${BUILDDIR}/src"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
die "Cannot find kernel source for ${KVER_BASE}.
|
||||
Option 1: dnf download --source kernel && rpm2cpio kernel-*.src.rpm | cpio -idm
|
||||
then unpack linux-${KVER_BASE}.tar.xz into /usr/src/
|
||||
Option 2: place extracted source at /usr/src/linux-${KVER_BASE}"
|
||||
}
|
||||
|
||||
patch_source() {
|
||||
local gfx="${MODSRC}/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c"
|
||||
[ -f "$gfx" ] || die "gfx_v10_0.c not found at ${gfx}"
|
||||
|
||||
if grep -q 'bc250_cc_write_mode' "$gfx"; then
|
||||
info "Source already patched."
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Patching gfx_v10_0.c..."
|
||||
cp "$gfx" "${gfx}.orig"
|
||||
|
||||
# Step 1: insert module parameter before '#include "amdgpu.h"'
|
||||
if ! grep -q '#include "amdgpu.h"' "$gfx"; then
|
||||
die "Cannot find anchor: #include amdgpu.h"
|
||||
fi
|
||||
|
||||
local param_file
|
||||
param_file="$(mktemp)"
|
||||
write_param_patch "$param_file"
|
||||
sed -i "/#include \"amdgpu.h\"/r ${param_file}" "$gfx"
|
||||
rm -f "$param_file"
|
||||
|
||||
# Step 2: insert CC write block in gfx_v10_0_get_cu_info after mutex_lock
|
||||
local cc_file
|
||||
cc_file="$(mktemp)"
|
||||
write_cc_patch "$cc_file"
|
||||
|
||||
# Two-phase awk: avoid matching the forward declaration of gfx_v10_0_get_cu_info.
|
||||
# A forward declaration ends with "); " on a later line, while the actual body
|
||||
# starts with a standalone "{" on its own line right after the signature.
|
||||
awk -v insertfile="$cc_file" '
|
||||
/static.*gfx_v10_0_get_cu_info/ { maybe_func = 1 }
|
||||
maybe_func && /;/ { maybe_func = 0 }
|
||||
maybe_func && /^\{/ { in_cu_info = 1; maybe_func = 0 }
|
||||
in_cu_info && /mutex_lock/ && !inserted {
|
||||
print
|
||||
while ((getline line < insertfile) > 0) print line
|
||||
close(insertfile)
|
||||
inserted = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$gfx" > "${gfx}.new"
|
||||
|
||||
if grep -q 'bc250-40cu-enable' "${gfx}.new"; then
|
||||
mv "${gfx}.new" "$gfx"
|
||||
rm -f "$cc_file"
|
||||
info "Patch applied successfully."
|
||||
else
|
||||
rm -f "${gfx}.new" "$cc_file"
|
||||
mv "${gfx}.orig" "$gfx"
|
||||
die "Failed to insert CC write block. Kernel source layout may differ."
|
||||
fi
|
||||
}
|
||||
|
||||
build_module() {
|
||||
local amdgpu_dir="${MODSRC}/drivers/gpu/drm/amd/amdgpu"
|
||||
[ -d "$amdgpu_dir" ] || die "amdgpu source directory not found"
|
||||
|
||||
# define_trace.h (in the kernel headers) resolves the trace header as:
|
||||
# ../../drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h
|
||||
# relative to its own location, ending up at:
|
||||
# ${kbuild}/drivers/gpu/drm/amd/amdgpu/amdgpu_trace.h
|
||||
# That directory already exists in linux-headers (contains only Kconfig),
|
||||
# so we copy the trace header there temporarily for the build.
|
||||
local kbuild="${MODDIR}/build"
|
||||
local kbuild_amdgpu="${kbuild}/drivers/gpu/drm/amd/amdgpu"
|
||||
local trace_dst="${kbuild_amdgpu}/amdgpu_trace.h"
|
||||
local trace_copied=0
|
||||
if [ ! -f "$trace_dst" ]; then
|
||||
mkdir -p "$kbuild_amdgpu"
|
||||
cp "${amdgpu_dir}/amdgpu_trace.h" "$trace_dst"
|
||||
trace_copied=1
|
||||
fi
|
||||
|
||||
info "Building amdgpu module for kernel ${KVER} (2-5 min)..."
|
||||
make -C "$kbuild" M="$amdgpu_dir" -j"$(nproc)" modules 2>&1 | tail -10 >&2
|
||||
local make_rc=${PIPESTATUS[0]}
|
||||
|
||||
[ "$trace_copied" -eq 1 ] && rm -f "$trace_dst"
|
||||
|
||||
[ "$make_rc" -eq 0 ] || die "Build failed (make exited $make_rc)"
|
||||
|
||||
local built="${amdgpu_dir}/amdgpu.ko"
|
||||
[ -f "$built" ] || die "Build failed - amdgpu.ko not produced"
|
||||
|
||||
# Use grep -qa to avoid SIGPIPE/pipefail issue with `strings | grep -q` on large .ko files
|
||||
if ! grep -qa 'bc250_cc_write_mode' "$built"; then
|
||||
die "Built module missing bc250_cc_write_mode - patch failed"
|
||||
fi
|
||||
|
||||
info "Build successful: ${built} ($(du -h "$built" | cut -f1))"
|
||||
echo "$built"
|
||||
}
|
||||
|
||||
install_module() {
|
||||
local built="$1"
|
||||
local target="${MODPATH}"
|
||||
|
||||
if [ -f "${target}.zst" ]; then
|
||||
target="${target}.zst"
|
||||
elif [ ! -f "$target" ]; then
|
||||
target="${target}.zst"
|
||||
fi
|
||||
|
||||
if [ -f "$target" ] && [ ! -f "${target}${BACKUP_SUFFIX}" ]; then
|
||||
info "Backing up original to ${target}${BACKUP_SUFFIX}"
|
||||
cp "$target" "${target}${BACKUP_SUFFIX}"
|
||||
fi
|
||||
|
||||
if [ "${target%.zst}" != "$target" ]; then
|
||||
info "Compressing and installing module..."
|
||||
zstd -f "$built" -o "$target"
|
||||
else
|
||||
cp "$built" "$target"
|
||||
fi
|
||||
|
||||
depmod -a "$KVER"
|
||||
info "Module installed at ${target}"
|
||||
}
|
||||
|
||||
do_build() {
|
||||
check_bc250
|
||||
check_deps
|
||||
find_source
|
||||
patch_source
|
||||
local built
|
||||
built="$(build_module)"
|
||||
install_module "$built"
|
||||
echo ""
|
||||
info "Done! Patched amdgpu module installed."
|
||||
info "Next: sudo $0 enable"
|
||||
}
|
||||
|
||||
do_enable() {
|
||||
printf '# BC-250 40 CU re-enablement\noptions amdgpu bc250_cc_write_mode=3\n' > "$CONF40"
|
||||
info "40 CU mode configured in ${CONF40}"
|
||||
if ! ( set +o pipefail; modinfo amdgpu 2>/dev/null | grep -q 'bc250_cc_write_mode' ); then
|
||||
warn "Patched module not detected. Run: sudo $0 build"
|
||||
rm -f "$CONF40"
|
||||
exit 1
|
||||
fi
|
||||
info "Rebooting..."
|
||||
sleep 2
|
||||
reboot
|
||||
}
|
||||
|
||||
do_disable() {
|
||||
rm -f "$CONF40"
|
||||
info "40 CU config removed. Rebooting to stock 24 CU..."
|
||||
sleep 2
|
||||
reboot
|
||||
}
|
||||
|
||||
do_restore() {
|
||||
local target="${MODPATH}"
|
||||
if [ -f "${target}.zst" ]; then target="${target}.zst"; fi
|
||||
local backup
|
||||
backup="$(ls -1 "${target}.bc250-backup-"* 2>/dev/null | head -1)"
|
||||
[ -n "$backup" ] || die "No backup found"
|
||||
cp "$backup" "$target"
|
||||
rm -f "$CONF40"
|
||||
depmod -a "$KVER"
|
||||
info "Original module restored. Reboot to apply."
|
||||
}
|
||||
|
||||
do_status() {
|
||||
printf '\033[1m=== BC-250 CU Status ===\033[0m\n\n'
|
||||
|
||||
if lspci -nn 2>/dev/null | grep -qi "${BC250_PCI_ID}"; then
|
||||
printf ' PCI device: \033[0;32mBC-250 detected\033[0m\n'
|
||||
else
|
||||
printf ' PCI device: \033[0;31mBC-250 not found\033[0m\n'
|
||||
fi
|
||||
|
||||
if ( set +o pipefail; modinfo amdgpu 2>/dev/null | grep -q 'bc250_cc_write_mode' ); then
|
||||
printf ' amdgpu module: \033[0;32mpatched\033[0m\n'
|
||||
else
|
||||
printf ' amdgpu module: \033[0;33mstock (unpatched)\033[0m\n'
|
||||
fi
|
||||
|
||||
local mode
|
||||
mode="$(cat /sys/module/amdgpu/parameters/bc250_cc_write_mode 2>/dev/null || echo 'N/A')"
|
||||
printf ' write_mode: %s\n' "$mode"
|
||||
|
||||
local cu_line
|
||||
cu_line="$(dmesg 2>/dev/null | grep 'active_cu_number' | tail -1)"
|
||||
if [ -n "$cu_line" ]; then
|
||||
local cus
|
||||
cus="$(echo "$cu_line" | grep -o 'active_cu_number [0-9]*' | awk '{print $2}')"
|
||||
if [ "$cus" = "40" ]; then
|
||||
printf ' active CUs: \033[0;32m\033[1m40\033[0m (full die)\n'
|
||||
elif [ "$cus" = "24" ]; then
|
||||
printf ' active CUs: \033[0;33m24\033[0m (stock)\n'
|
||||
else
|
||||
printf ' active CUs: %s\n' "$cus"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$CONF40" ]; then
|
||||
printf ' modprobe conf: \033[0;32m%s (40 CU enabled)\033[0m\n' "$CONF40"
|
||||
else
|
||||
printf ' modprobe conf: (none - stock mode)\n'
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
build) do_build ;;
|
||||
enable) do_enable ;;
|
||||
disable) do_disable ;;
|
||||
restore) do_restore ;;
|
||||
status) do_status ;;
|
||||
*)
|
||||
echo "BC-250 40 CU Re-enablement Tool (Fedora)"
|
||||
echo ""
|
||||
echo "Usage: sudo $0 <command>"
|
||||
echo ""
|
||||
echo " build Patch, compile, install patched amdgpu (~5 min)"
|
||||
echo " enable Activate 40 CU mode and reboot"
|
||||
echo " disable Return to stock 24 CU and reboot"
|
||||
echo " status Show current CU state"
|
||||
echo " restore Restore original amdgpu module"
|
||||
echo ""
|
||||
echo "Quick start:"
|
||||
echo " sudo $0 build && sudo $0 enable"
|
||||
echo ""
|
||||
echo "Dependencies: dnf install kernel-devel-$(uname -r) gcc make zstd curl"
|
||||
;;
|
||||
esac
|
||||
Vendored
Executable
+360
@@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env bash
|
||||
# bc250-enable-40cu.sh — Build and install a patched amdgpu for 40 CU on BC-250
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./bc250-enable-40cu.sh build # patch + compile + install
|
||||
# sudo ./bc250-enable-40cu.sh enable # set 40 CU mode and reboot
|
||||
# sudo ./bc250-enable-40cu.sh disable # return to stock 24 CU and reboot
|
||||
# sudo ./bc250-enable-40cu.sh status # show current CU state
|
||||
# sudo ./bc250-enable-40cu.sh restore # restore original amdgpu module
|
||||
#
|
||||
# Requirements: kernel headers, gcc, make, zstd. Must run as root on BC-250.
|
||||
# Tested on: Debian Forky kernel 6.19.14+deb14-amd64
|
||||
#
|
||||
# Authors: duggasco, Claude | License: GPL-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
KVER="$(uname -r)"
|
||||
MODDIR="/lib/modules/${KVER}"
|
||||
MODPATH="${MODDIR}/kernel/drivers/gpu/drm/amd/amdgpu/amdgpu.ko"
|
||||
MODSRC=""
|
||||
BUILDDIR="/tmp/bc250-40cu-build"
|
||||
CONF40="/etc/modprobe.d/bc250-40cu.conf"
|
||||
BACKUP_SUFFIX=".bc250-backup-$(date +%Y%m%d)"
|
||||
BC250_PCI_ID="13fe"
|
||||
|
||||
info() { printf '\033[0;32m[+]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[0;33m[!]\033[0m %s\n' "$*"; }
|
||||
err() { printf '\033[0;31m[E]\033[0m %s\n' "$*" >&2; }
|
||||
die() { err "$@"; exit 1; }
|
||||
|
||||
write_param_patch() {
|
||||
cat > "$1" << 'ENDPARAM'
|
||||
|
||||
/* BC-250 40 CU unlock: clears harvest mask + enables SPI dispatch to all WGPs */
|
||||
static int bc250_cc_write_mode;
|
||||
module_param(bc250_cc_write_mode, int, 0444);
|
||||
MODULE_PARM_DESC(bc250_cc_write_mode,
|
||||
"BC-250: 0=off 1=probe-SE0SH0 2=clear-SE0SH0 3=clear-all-SAs 4=probe-all-SAs");
|
||||
#define BC250_PCI_DEVICE_ID 0x13FE
|
||||
|
||||
ENDPARAM
|
||||
}
|
||||
|
||||
write_cc_patch() {
|
||||
cat > "$1" << 'ENDCC'
|
||||
|
||||
/* BC-250: unlock harvested CUs — CC (enumeration) + SPI (dispatch) + RLC (power) */
|
||||
if (bc250_cc_write_mode > 0 && adev->pdev->device == BC250_PCI_DEVICE_ID) {
|
||||
int bc_se, bc_sh;
|
||||
for (bc_se = 0; bc_se < adev->gfx.config.max_shader_engines; bc_se++) {
|
||||
for (bc_sh = 0; bc_sh < adev->gfx.config.max_sh_per_se; bc_sh++) {
|
||||
u32 bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after;
|
||||
if (bc250_cc_write_mode == 2 && (bc_se > 0 || bc_sh > 0))
|
||||
continue;
|
||||
gfx_v10_0_select_se_sh(adev, bc_se, bc_sh, 0xffffffff, 0);
|
||||
bc_cc_orig = RREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG);
|
||||
WREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG, 0);
|
||||
bc_cc_after = RREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG);
|
||||
bc_spi_orig = RREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK);
|
||||
WREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK, 0x1f);
|
||||
bc_spi_after = RREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK);
|
||||
WREG32_SOC15(GC, 0, mmRLC_PG_ALWAYS_ON_WGP_MASK, 0x1f);
|
||||
if (bc250_cc_write_mode == 1 || bc250_cc_write_mode == 4) {
|
||||
WREG32_SOC15(GC, 0, mmCC_GC_SHADER_ARRAY_CONFIG, bc_cc_orig);
|
||||
WREG32_SOC15(GC, 0, mmSPI_PG_ENABLE_STATIC_WGP_MASK, bc_spi_orig);
|
||||
dev_info(adev->dev,
|
||||
"bc250-40cu-probe: se=%d sh=%d CC=0x%08x->0x%08x SPI=0x%08x->0x%08x (restored)",
|
||||
bc_se, bc_sh, bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after);
|
||||
} else {
|
||||
dev_info(adev->dev,
|
||||
"bc250-40cu-enable: mode=%d se=%d sh=%d CC=0x%08x->0x%08x SPI=0x%08x->0x%08x",
|
||||
bc250_cc_write_mode, bc_se, bc_sh,
|
||||
bc_cc_orig, bc_cc_after, bc_spi_orig, bc_spi_after);
|
||||
}
|
||||
}
|
||||
}
|
||||
gfx_v10_0_select_se_sh(adev, 0xffffffff, 0xffffffff, 0xffffffff, 0);
|
||||
}
|
||||
|
||||
ENDCC
|
||||
}
|
||||
|
||||
check_bc250() {
|
||||
if ! lspci -nn 2>/dev/null | grep -qi "${BC250_PCI_ID}"; then
|
||||
warn "No BC-250 (PCI ID 13fe) detected. This patch is BC-250 specific."
|
||||
printf "Continue anyway? [y/N] "
|
||||
read -r ans
|
||||
case "$ans" in y|Y) ;; *) exit 1 ;; esac
|
||||
fi
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
local missing=""
|
||||
command -v gcc >/dev/null 2>&1 || missing="${missing} gcc"
|
||||
command -v make >/dev/null 2>&1 || missing="${missing} make"
|
||||
command -v zstd >/dev/null 2>&1 || missing="${missing} zstd"
|
||||
if [ ! -d "${MODDIR}/build" ]; then
|
||||
missing="${missing} linux-headers-${KVER}"
|
||||
fi
|
||||
if [ -n "$missing" ]; then
|
||||
die "Missing dependencies:${missing}"
|
||||
fi
|
||||
}
|
||||
|
||||
find_source() {
|
||||
local d
|
||||
for d in \
|
||||
"/usr/src/linux-source-${KVER%%-*}" \
|
||||
"/usr/src/linux-source-${KVER%%+*}" \
|
||||
"/usr/src/linux-${KVER}" \
|
||||
"/usr/src/linux"; do
|
||||
if [ -f "$d/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="$d"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
local srcpkg=""
|
||||
local p
|
||||
for p in \
|
||||
"/usr/src/linux-source-${KVER%%-*}.tar.xz" \
|
||||
"/usr/src/linux-source-${KVER%%+*}.tar.xz"; do
|
||||
[ -f "$p" ] && srcpkg="$p" && break
|
||||
done
|
||||
if [ -z "$srcpkg" ]; then
|
||||
srcpkg="$(find /usr/src -maxdepth 4 -name 'linux-source-*.tar.xz' 2>/dev/null | head -1)"
|
||||
fi
|
||||
|
||||
if [ -n "$srcpkg" ]; then
|
||||
info "Extracting kernel source from ${srcpkg}..."
|
||||
mkdir -p "${BUILDDIR}/src"
|
||||
tar xf "$srcpkg" -C "${BUILDDIR}/src" --strip-components=1 \
|
||||
'*/drivers/gpu/drm/amd/amdgpu/' 2>/dev/null || true
|
||||
if [ -f "${BUILDDIR}/src/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="${BUILDDIR}/src"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Kernel source not found locally. Trying apt..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get install -y "linux-source-${KVER%%-*}" 2>/dev/null || true
|
||||
srcpkg="/usr/src/linux-source-${KVER%%-*}.tar.xz"
|
||||
if [ -f "$srcpkg" ]; then
|
||||
mkdir -p "${BUILDDIR}/src"
|
||||
tar xf "$srcpkg" -C "${BUILDDIR}/src" --strip-components=1 \
|
||||
'*/drivers/gpu/drm/amd/amdgpu/' 2>/dev/null || true
|
||||
if [ -f "${BUILDDIR}/src/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c" ]; then
|
||||
MODSRC="${BUILDDIR}/src"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
die "Cannot find kernel source for ${KVER}. Install: apt install linux-source-${KVER%%-*}"
|
||||
}
|
||||
|
||||
patch_source() {
|
||||
local gfx="${MODSRC}/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c"
|
||||
[ -f "$gfx" ] || die "gfx_v10_0.c not found at ${gfx}"
|
||||
|
||||
if grep -q 'bc250_cc_write_mode' "$gfx"; then
|
||||
info "Source already patched."
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Patching gfx_v10_0.c..."
|
||||
cp "$gfx" "${gfx}.orig"
|
||||
|
||||
# Step 1: insert module parameter before '#include "amdgpu.h"'
|
||||
if ! grep -q '#include "amdgpu.h"' "$gfx"; then
|
||||
die "Cannot find anchor: #include amdgpu.h"
|
||||
fi
|
||||
|
||||
local param_file
|
||||
param_file="$(mktemp)"
|
||||
write_param_patch "$param_file"
|
||||
sed -i "/#include \"amdgpu.h\"/r ${param_file}" "$gfx"
|
||||
rm -f "$param_file"
|
||||
|
||||
# Step 2: insert CC write block in gfx_v10_0_get_cu_info after mutex_lock
|
||||
local cc_file
|
||||
cc_file="$(mktemp)"
|
||||
write_cc_patch "$cc_file"
|
||||
|
||||
awk -v insertfile="$cc_file" '
|
||||
/static.*gfx_v10_0_get_cu_info/ { in_cu_info = 1 }
|
||||
in_cu_info && /mutex_lock/ && !inserted {
|
||||
print
|
||||
while ((getline line < insertfile) > 0) print line
|
||||
close(insertfile)
|
||||
inserted = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$gfx" > "${gfx}.new"
|
||||
|
||||
if grep -q 'bc250-cc-clear' "${gfx}.new"; then
|
||||
mv "${gfx}.new" "$gfx"
|
||||
rm -f "$cc_file"
|
||||
info "Patch applied successfully."
|
||||
else
|
||||
rm -f "${gfx}.new" "$cc_file"
|
||||
mv "${gfx}.orig" "$gfx"
|
||||
die "Failed to insert CC write block. Kernel source layout may differ."
|
||||
fi
|
||||
}
|
||||
|
||||
build_module() {
|
||||
local amdgpu_dir="${MODSRC}/drivers/gpu/drm/amd/amdgpu"
|
||||
[ -d "$amdgpu_dir" ] || die "amdgpu source directory not found"
|
||||
|
||||
info "Building amdgpu module for kernel ${KVER} (2-5 min)..."
|
||||
make -C "${MODDIR}/build" M="$amdgpu_dir" -j"$(nproc)" modules 2>&1 | tail -5
|
||||
|
||||
local built="${amdgpu_dir}/amdgpu.ko"
|
||||
[ -f "$built" ] || die "Build failed - amdgpu.ko not produced"
|
||||
|
||||
if ! strings "$built" | grep -q 'bc250_cc_write_mode'; then
|
||||
die "Built module missing bc250_cc_write_mode - patch failed"
|
||||
fi
|
||||
|
||||
info "Build successful: ${built} ($(du -h "$built" | cut -f1))"
|
||||
echo "$built"
|
||||
}
|
||||
|
||||
install_module() {
|
||||
local built="$1"
|
||||
local target="${MODPATH}"
|
||||
|
||||
if [ -f "${target}.zst" ]; then
|
||||
target="${target}.zst"
|
||||
elif [ ! -f "$target" ]; then
|
||||
target="${target}.zst"
|
||||
fi
|
||||
|
||||
if [ -f "$target" ] && [ ! -f "${target}${BACKUP_SUFFIX}" ]; then
|
||||
info "Backing up original to ${target}${BACKUP_SUFFIX}"
|
||||
cp "$target" "${target}${BACKUP_SUFFIX}"
|
||||
fi
|
||||
|
||||
if [ "${target%.zst}" != "$target" ]; then
|
||||
info "Compressing and installing module..."
|
||||
zstd -f "$built" -o "$target"
|
||||
else
|
||||
cp "$built" "$target"
|
||||
fi
|
||||
|
||||
depmod -a "$KVER"
|
||||
info "Module installed at ${target}"
|
||||
}
|
||||
|
||||
do_build() {
|
||||
check_bc250
|
||||
check_deps
|
||||
find_source
|
||||
patch_source
|
||||
local built
|
||||
built="$(build_module)"
|
||||
install_module "$built"
|
||||
echo ""
|
||||
info "Done! Patched amdgpu module installed."
|
||||
info "Next: sudo $0 enable"
|
||||
}
|
||||
|
||||
do_enable() {
|
||||
printf '# BC-250 40 CU re-enablement\noptions amdgpu bc250_cc_write_mode=3\n' > "$CONF40"
|
||||
info "40 CU mode configured in ${CONF40}"
|
||||
if ! modinfo amdgpu 2>/dev/null | grep -q 'bc250_cc_write_mode'; then
|
||||
warn "Patched module not detected. Run: sudo $0 build"
|
||||
rm -f "$CONF40"
|
||||
exit 1
|
||||
fi
|
||||
info "Rebooting..."
|
||||
sleep 2
|
||||
reboot
|
||||
}
|
||||
|
||||
do_disable() {
|
||||
rm -f "$CONF40"
|
||||
info "40 CU config removed. Rebooting to stock 24 CU..."
|
||||
sleep 2
|
||||
reboot
|
||||
}
|
||||
|
||||
do_restore() {
|
||||
local target="${MODPATH}"
|
||||
if [ -f "${target}.zst" ]; then target="${target}.zst"; fi
|
||||
local backup
|
||||
backup="$(ls -1 "${target}.bc250-backup-"* 2>/dev/null | head -1)"
|
||||
[ -n "$backup" ] || die "No backup found"
|
||||
cp "$backup" "$target"
|
||||
rm -f "$CONF40"
|
||||
depmod -a "$KVER"
|
||||
info "Original module restored. Reboot to apply."
|
||||
}
|
||||
|
||||
do_status() {
|
||||
printf '\033[1m=== BC-250 CU Status ===\033[0m\n\n'
|
||||
|
||||
if lspci -nn 2>/dev/null | grep -qi "${BC250_PCI_ID}"; then
|
||||
printf ' PCI device: \033[0;32mBC-250 detected\033[0m\n'
|
||||
else
|
||||
printf ' PCI device: \033[0;31mBC-250 not found\033[0m\n'
|
||||
fi
|
||||
|
||||
if modinfo amdgpu 2>/dev/null | grep -q 'bc250_cc_write_mode'; then
|
||||
printf ' amdgpu module: \033[0;32mpatched\033[0m\n'
|
||||
else
|
||||
printf ' amdgpu module: \033[0;33mstock (unpatched)\033[0m\n'
|
||||
fi
|
||||
|
||||
local mode
|
||||
mode="$(cat /sys/module/amdgpu/parameters/bc250_cc_write_mode 2>/dev/null || echo 'N/A')"
|
||||
printf ' write_mode: %s\n' "$mode"
|
||||
|
||||
local cu_line
|
||||
cu_line="$(dmesg 2>/dev/null | grep 'active_cu_number' | tail -1)"
|
||||
if [ -n "$cu_line" ]; then
|
||||
local cus
|
||||
cus="$(echo "$cu_line" | grep -o 'active_cu_number [0-9]*' | awk '{print $2}')"
|
||||
if [ "$cus" = "40" ]; then
|
||||
printf ' active CUs: \033[0;32m\033[1m40\033[0m (full die)\n'
|
||||
elif [ "$cus" = "24" ]; then
|
||||
printf ' active CUs: \033[0;33m24\033[0m (stock)\n'
|
||||
else
|
||||
printf ' active CUs: %s\n' "$cus"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$CONF40" ]; then
|
||||
printf ' modprobe conf: \033[0;32m%s (40 CU enabled)\033[0m\n' "$CONF40"
|
||||
else
|
||||
printf ' modprobe conf: (none - stock mode)\n'
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
build) do_build ;;
|
||||
enable) do_enable ;;
|
||||
disable) do_disable ;;
|
||||
restore) do_restore ;;
|
||||
status) do_status ;;
|
||||
*)
|
||||
echo "BC-250 40 CU Re-enablement Tool"
|
||||
echo ""
|
||||
echo "Usage: sudo $0 <command>"
|
||||
echo ""
|
||||
echo " build Patch, compile, install patched amdgpu (~5 min)"
|
||||
echo " enable Activate 40 CU mode and reboot"
|
||||
echo " disable Return to stock 24 CU and reboot"
|
||||
echo " status Show current CU state"
|
||||
echo " restore Restore original amdgpu module"
|
||||
echo ""
|
||||
echo "Quick start:"
|
||||
echo " sudo $0 build && sudo $0 enable"
|
||||
;;
|
||||
esac
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
# cu_map.sh — Read and display CU bitmap from DRM ioctl via libdrm.
|
||||
#
|
||||
# Optional health overlay:
|
||||
# ./cu_map.sh --health /var/lib/bc250-cu-health-test/results.tsv
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HEALTH="${BC250_CU_HEALTH_RESULTS:-/var/lib/bc250-cu-health-test/results.tsv}"
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--health)
|
||||
HEALTH="${2:?missing value for --health}"
|
||||
shift 2
|
||||
;;
|
||||
--no-health)
|
||||
HEALTH=""
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '1,10p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown argument: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
BC250_CU_HEALTH_RESULTS="$HEALTH" python3 << 'PYEOF'
|
||||
import ctypes, struct, os, sys
|
||||
|
||||
libdrm = ctypes.CDLL("libdrm_amdgpu.so.1")
|
||||
fd = os.open("/dev/dri/renderD128", os.O_RDWR)
|
||||
dev = ctypes.c_void_p()
|
||||
maj, min_ = ctypes.c_uint32(), ctypes.c_uint32()
|
||||
libdrm.amdgpu_device_initialize(fd, ctypes.byref(maj), ctypes.byref(min_), ctypes.byref(dev))
|
||||
|
||||
buf = (ctypes.c_uint8 * 1024)()
|
||||
libdrm.amdgpu_query_info(dev, 0x16, 1024, ctypes.byref(buf))
|
||||
raw = bytes(buf)
|
||||
|
||||
num_se = struct.unpack_from('<I', raw, 20)[0]
|
||||
num_sh = struct.unpack_from('<I', raw, 24)[0]
|
||||
cu_active = struct.unpack_from('<I', raw, 48)[0]
|
||||
|
||||
total = 0
|
||||
rows = []
|
||||
patterns = []
|
||||
for se in range(num_se):
|
||||
for sh in range(num_sh):
|
||||
bm = struct.unpack_from('<I', raw, 56 + (se * 4 + sh) * 4)[0]
|
||||
n = bin(bm).count('1')
|
||||
total += n
|
||||
bar = ''.join('■' if bm & (1 << i) else '□' for i in range(10))
|
||||
# check if disabled CUs are contiguous (all packed at one end)
|
||||
disabled = [i for i in range(10) if not (bm & (1 << i))]
|
||||
if len(disabled) == 0:
|
||||
pattern = "full"
|
||||
elif disabled == list(range(disabled[0], disabled[0] + len(disabled))):
|
||||
pattern = "contiguous"
|
||||
else:
|
||||
pattern = "scattered"
|
||||
rows.append(f"SE{se} SH{sh}: {bar}")
|
||||
patterns.append(pattern)
|
||||
|
||||
possible = num_se * num_sh * 10
|
||||
harvested = possible - total
|
||||
print()
|
||||
health_path = os.environ.get("BC250_CU_HEALTH_RESULTS", "")
|
||||
health = {}
|
||||
if health_path and os.path.exists(health_path):
|
||||
with open(health_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split("\t")
|
||||
if len(parts) < 5:
|
||||
continue
|
||||
_idx, se, sh, wgp, status = parts[:5]
|
||||
try:
|
||||
health[(int(se), int(sh), int(wgp))] = status
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
print("BC-250 CU Map" + (" + Health" if health else ""))
|
||||
for r in rows:
|
||||
print(r)
|
||||
print(f"{total}/{possible} CUs active, {harvested} harvested")
|
||||
|
||||
if health:
|
||||
usable = 0
|
||||
failed = 0
|
||||
print()
|
||||
print("BC-250 CU Map + Health")
|
||||
for se in range(num_se):
|
||||
for sh in range(num_sh):
|
||||
glyphs = []
|
||||
for cu in range(10):
|
||||
wgp = cu // 2
|
||||
status = health.get((se, sh, wgp))
|
||||
if status == "FAIL":
|
||||
glyphs.append("✗")
|
||||
elif cu < 6:
|
||||
glyphs.append("■")
|
||||
elif status == "PASS":
|
||||
glyphs.append("✓")
|
||||
else:
|
||||
glyphs.append("?")
|
||||
for wgp in range(5):
|
||||
status = health.get((se, sh, wgp))
|
||||
if status == "FAIL":
|
||||
failed += 2
|
||||
elif wgp < 3 or status == "PASS":
|
||||
usable += 2
|
||||
print(f"SE{se} SH{sh}: {''.join(glyphs)}")
|
||||
print(f"{usable}/{possible} CUs usable ({failed} defective, masked)")
|
||||
elif health_path:
|
||||
print()
|
||||
print(f"Health overlay: no results file found at {health_path}")
|
||||
|
||||
libdrm.amdgpu_device_deinitialize(dev)
|
||||
os.close(fd)
|
||||
PYEOF
|
||||
Reference in New Issue
Block a user