rumpk/hal/ram_blk.zig

266 lines
7.5 KiB
Zig

// SPDX-License-Identifier: LCL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
//
// This file is part of the Nexus Commonwealth.
// See legal/LICENSE_COMMONWEALTH.md for license terms.
//! Rumpk HAL: Reed-Solomon RAM Block Device (SPEC-023)
//!
//! Provides ECC-protected RAM storage using Reed-Solomon GF(2^8).
//! This is the "Cortex" - Space-Grade resilient memory.
const std = @import("std");
// =========================================================
// Galois Field GF(2^8) Arithmetic
// =========================================================
// Primitive polynomial: x^8 + x^4 + x^3 + x^2 + 1 (0x11D)
const GF_PRIM: u16 = 0x11D;
// Precomputed log and antilog tables for GF(2^8)
var gf_log: [256]u8 = undefined;
var gf_exp: [512]u8 = undefined;
var gf_initialized: bool = false;
fn gf_init() void {
if (gf_initialized) return;
var x: u16 = 1;
for (0..255) |i| {
gf_exp[i] = @truncate(x);
gf_log[@as(usize, @as(u8, @truncate(x)))] = @truncate(i);
x <<= 1;
if (x & 0x100 != 0) {
x ^= GF_PRIM;
}
}
// Extend exp table for easier modulo
for (255..512) |i| {
gf_exp[i] = gf_exp[i - 255];
}
gf_log[0] = 0; // Undefined, but avoid issues
gf_initialized = true;
}
fn gf_mul(a: u8, b: u8) u8 {
if (a == 0 or b == 0) return 0;
const log_a = gf_log[a];
const log_b = gf_log[b];
return gf_exp[@as(u16, log_a) + @as(u16, log_b)];
}
fn gf_div(a: u8, b: u8) u8 {
if (b == 0) return 0; // Error: division by zero
if (a == 0) return 0;
const log_a = gf_log[a];
const log_b = gf_log[b];
return gf_exp[@as(u16, log_a) + 255 - @as(u16, log_b)];
}
fn gf_pow(a: u8, n: u8) u8 {
if (n == 0) return 1;
if (a == 0) return 0;
const log_a: u16 = gf_log[a];
return gf_exp[(log_a * @as(u16, n)) % 255];
}
fn gf_inv(a: u8) u8 {
return gf_exp[255 - @as(u16, gf_log[a])];
}
// =========================================================
// Reed-Solomon Codec
// =========================================================
pub const SECTOR_SIZE: usize = 512;
pub const PARITY_SIZE: usize = 32; // Can correct 16 byte errors
pub const TOTAL_SIZE: usize = SECTOR_SIZE + PARITY_SIZE;
// Generator polynomial coefficients (precomputed for t=16)
var rs_generator: [PARITY_SIZE + 1]u8 = undefined;
var rs_generator_initialized: bool = false;
fn rs_init_generator() void {
if (rs_generator_initialized) return;
gf_init();
// Build generator polynomial: prod(x - alpha^i) for i = 0..PARITY_SIZE-1
rs_generator[0] = 1;
for (1..PARITY_SIZE + 1) |i| {
rs_generator[i] = 0;
}
for (0..PARITY_SIZE) |i| {
const alpha_i = gf_pow(2, @truncate(i));
// Multiply by (x - alpha^i)
var j: usize = PARITY_SIZE;
while (j > 0) : (j -= 1) {
rs_generator[j] = rs_generator[j - 1] ^ gf_mul(rs_generator[j], alpha_i);
}
rs_generator[0] = gf_mul(rs_generator[0], alpha_i);
}
rs_generator_initialized = true;
}
/// Encode data with Reed-Solomon parity
pub fn rs_encode(data: []const u8, parity: *[PARITY_SIZE]u8) void {
rs_init_generator();
// Initialize parity to zero
for (parity) |*p| p.* = 0;
// Polynomial division
for (data) |byte| {
const feedback = byte ^ parity[0];
// Shift parity register
for (0..PARITY_SIZE - 1) |i| {
parity[i] = parity[i + 1] ^ gf_mul(rs_generator[PARITY_SIZE - 1 - i], feedback);
}
parity[PARITY_SIZE - 1] = gf_mul(rs_generator[0], feedback);
}
}
/// Calculate syndromes for error detection
fn rs_syndromes(data: []const u8, parity: []const u8) [PARITY_SIZE]u8 {
var syndromes: [PARITY_SIZE]u8 = undefined;
for (0..PARITY_SIZE) |i| {
const alpha_i = gf_pow(2, @truncate(i));
var s: u8 = 0;
// Evaluate polynomial at alpha^i
for (data) |byte| {
s = gf_mul(s, alpha_i) ^ byte;
}
for (parity) |byte| {
s = gf_mul(s, alpha_i) ^ byte;
}
syndromes[i] = s;
}
return syndromes;
}
/// Check if all syndromes are zero (no errors)
fn rs_check_syndromes(syndromes: []const u8) bool {
for (syndromes) |s| {
if (s != 0) return false;
}
return true;
}
/// Decode and repair data using Reed-Solomon
/// Returns true if data is valid (or was repaired), false if uncorrectable
pub fn rs_decode(data: []u8, parity: []u8) bool {
rs_init_generator();
const syndromes = rs_syndromes(data, parity);
// No errors
if (rs_check_syndromes(&syndromes)) return true;
// TODO: Implement Berlekamp-Massey algorithm for error location
// TODO: Implement Forney algorithm for error correction
// For MVP, we detect but don't yet repair multi-byte errors
// Simple single-byte error correction (simplified)
// This is a placeholder - full RS decoding is complex
return false; // Could not repair
}
// =========================================================
// RAM Block Device Interface
// =========================================================
var ram_base: [*]u8 = undefined;
var ram_size: usize = 0;
var ram_initialized: bool = false;
/// Initialize the RAM block device with a memory region
export fn ram_blk_init(base: [*]u8, size: usize) void {
gf_init();
rs_init_generator();
ram_base = base;
ram_size = size;
ram_initialized = true;
}
/// Get the number of sectors available
export fn ram_blk_sector_count() u32 {
if (!ram_initialized) return 0;
return @truncate(ram_size / TOTAL_SIZE);
}
/// Write a sector with ECC protection
export fn ram_blk_write(sector: u32, data: [*]const u8) i32 {
if (!ram_initialized) return -1;
const offset = @as(usize, sector) * TOTAL_SIZE;
if (offset + TOTAL_SIZE > ram_size) return -1;
// Calculate parity
var parity: [PARITY_SIZE]u8 = undefined;
rs_encode(data[0..SECTOR_SIZE], &parity);
// Write data + parity
const dest = ram_base + offset;
@memcpy(dest[0..SECTOR_SIZE], data[0..SECTOR_SIZE]);
@memcpy(dest[SECTOR_SIZE..TOTAL_SIZE], &parity);
return SECTOR_SIZE;
}
/// Read a sector with ECC verification/repair
export fn ram_blk_read(sector: u32, data: [*]u8) i32 {
if (!ram_initialized) return -1;
const offset = @as(usize, sector) * TOTAL_SIZE;
if (offset + TOTAL_SIZE > ram_size) return -1;
const src = ram_base + offset;
// Read data + parity
@memcpy(data[0..SECTOR_SIZE], src[0..SECTOR_SIZE]);
var parity: [PARITY_SIZE]u8 = undefined;
@memcpy(&parity, src[SECTOR_SIZE..TOTAL_SIZE]);
// Verify and attempt repair
var data_slice: [SECTOR_SIZE]u8 = undefined;
@memcpy(&data_slice, data[0..SECTOR_SIZE]);
if (!rs_decode(&data_slice, &parity)) {
// TODO: Log the uncorrectable error
return -2; // ECC_ERROR
}
// Copy repaired data back
@memcpy(data[0..SECTOR_SIZE], &data_slice);
return SECTOR_SIZE;
}
/// Scrub a sector (read and verify, repair if needed)
export fn ram_blk_scrub(sector: u32) i32 {
var temp: [SECTOR_SIZE]u8 = undefined;
const result = ram_blk_read(sector, &temp);
if (result == SECTOR_SIZE) {
// Re-write to fix any corrected errors
return ram_blk_write(sector, &temp);
}
return result;
}
/// Scrub all sectors (patrol scrubbing)
export fn ram_blk_scrub_all() u32 {
var errors: u32 = 0;
const count = ram_blk_sector_count();
for (0..count) |i| {
if (ram_blk_scrub(@truncate(i)) < 0) {
errors += 1;
}
}
return errors;
}