Skip to content
This repository was archived by the owner on May 31, 2026. It is now read-only.

Commit 4f1243e

Browse files
Paradoxdovclaude
andcommitted
v0.4.65: multi-platform address-map table for bad-stick attribution
Replace the single hardcoded Haswell DDR3 map with a table of published (channel-hash, DIMM-bit) pairs. The probe accepts the FIRST row whose functions actually validate against the live row-conflict timing groups, so a row that doesn't fit the silicon is simply skipped (never mis- attributes) and the tool falls back to SMBIOS Type-20 on unknown platforms. Rows: Intel DDR3 2ch/2DIMM (IvyBridge/Haswell, validated against ground truth) and Intel DDR3 2ch (SandyBridge). DDR4/DDR5/Skylake+/AMD fall back safely until validated on that hardware. linewatch: MemForge2.src.c +53 -39, README.md +2 -2, quantai.ini +1 -1 (vs v0.4.64) ✓ Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 65c6f66 commit 4f1243e

3 files changed

Lines changed: 56 additions & 42 deletions

File tree

MemForge2.src.c

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* MemForge2 v0.4.64 — UEFI memory tester written from scratch.
2+
* MemForge2 v0.4.65 — UEFI memory tester written from scratch.
33
*
44
* Latest release: https://github.com/Paradoxdov/memforge/releases
55
* For per-version changes see git log / GitHub Releases page.
@@ -891,7 +891,7 @@ static void init_splash(CHAR16 *stage) {
891891
cls();
892892
UINTN cy = g_h / 2;
893893
/* Title — large centered line. */
894-
CHAR16 *title = L"MEMFORGE v0.4.64";
894+
CHAR16 *title = L"MEMFORGE v0.4.65";
895895
UINTN tx = (g_w - StrLen(title) * g_char_w) / 2;
896896
gfx_draw_str_color(tx, cy - g_char_h * 2, title, COL_ACCENT_HI);
897897
/* Stage indicator — what we're doing right now. */
@@ -1295,9 +1295,9 @@ static void render_header(UINT64 elapsed_ms, UINTN done, UINTN total) {
12951295
UINTN cols = g_text_cols;
12961296
if (cols >= 110) {
12971297
SPrint(buf, sizeof(buf),
1298-
T(L" MEMFORGE v0.4.64 | %ld.%ld ГБ RAM | %s "
1298+
T(L" MEMFORGE v0.4.65 | %ld.%ld ГБ RAM | %s "
12991299
L"| %s | прошло %02d:%02d | осталось ~%02d:%02d | Тесты %d/%d",
1300-
L" MEMFORGE v0.4.64 | %ld.%ld GB RAM | %s "
1300+
L" MEMFORGE v0.4.65 | %ld.%ld GB RAM | %s "
13011301
L"| %s | elapsed %02d:%02d | ETA ~%02d:%02d | Tests %d/%d"),
13021302
ram_gb_x10 / 10, ram_gb_x10 % 10,
13031303
pass_tag,
@@ -1307,25 +1307,25 @@ static void render_header(UINT64 elapsed_ms, UINTN done, UINTN total) {
13071307
(UINT32)done, (UINT32)total);
13081308
} else if (cols >= 90) {
13091309
SPrint(buf, sizeof(buf),
1310-
T(L" MEMFORGE v0.4.64 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d | осталось ~%02d:%02d",
1311-
L" MEMFORGE v0.4.64 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d | ETA ~%02d:%02d"),
1310+
T(L" MEMFORGE v0.4.65 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d | осталось ~%02d:%02d",
1311+
L" MEMFORGE v0.4.65 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d | ETA ~%02d:%02d"),
13121312
ram_gb_x10 / 10, ram_gb_x10 % 10,
13131313
pass_tag,
13141314
err_tag,
13151315
secs / 60, secs % 60,
13161316
eta_secs / 60, eta_secs % 60);
13171317
} else if (cols >= 70) {
13181318
SPrint(buf, sizeof(buf),
1319-
T(L" MEMFORGE v0.4.64 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d",
1320-
L" MEMFORGE v0.4.64 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d"),
1319+
T(L" MEMFORGE v0.4.65 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d",
1320+
L" MEMFORGE v0.4.65 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d"),
13211321
ram_gb_x10 / 10, ram_gb_x10 % 10,
13221322
pass_tag,
13231323
err_tag,
13241324
secs / 60, secs % 60);
13251325
} else {
13261326
SPrint(buf, sizeof(buf),
1327-
T(L" MEMFORGE v0.4.64 | %s | %s | прошло %02d:%02d",
1328-
L" MEMFORGE v0.4.64 | %s | %s | elapsed %02d:%02d"),
1327+
T(L" MEMFORGE v0.4.65 | %s | %s | прошло %02d:%02d",
1328+
L" MEMFORGE v0.4.65 | %s | %s | elapsed %02d:%02d"),
13291329
pass_tag,
13301330
err_tag,
13311331
secs / 60, secs % 60);
@@ -1679,7 +1679,7 @@ static volatile UINT32 g_dimm_err_count[MAX_DIMMS] = {0};
16791679
NOT "clean" — the attribution classifier must not treat it as separable. */
16801680
static volatile UINT8 g_dimm_tested[MAX_DIMMS] = {0};
16811681

1682-
/* v0.4.64 — interleave-aware attribution. The Path-B timing probe recovers AND
1682+
/* v0.4.65 — interleave-aware attribution. The Path-B timing probe recovers AND
16831683
confirms the Haswell DDR3 2ch/2DIMM address map; when valid we attribute an
16841684
error to the EXACT (channel,DIMM) = SPD slot, not the BIOS Type-20 range
16851685
(which is wrong under channel interleave). Validated on OptiPlex 9020:
@@ -1721,7 +1721,7 @@ static void record_error(kernel_id_t test, UINT32 core,
17211721
}
17221722
}
17231723
}
1724-
/* v0.4.64 — interleave-aware tally (exact SPD slot), parallel to the
1724+
/* v0.4.65 — interleave-aware tally (exact SPD slot), parallel to the
17251725
Type-20 one above. Only when the probe confirmed the address map. */
17261726
if (g_intl_valid) {
17271727
int sl = addr_to_intl_slot(addr);
@@ -3109,15 +3109,15 @@ static void run_butterfly(ap_arg_t *a) {
31093109
variant runs 30 sec of FMA chain (~10× longer) WITH interleaved memory
31103110
writes, so the test hits VRM + IMC simultaneously. This is the closest
31113111
pre-OS analogue to Prime95 Small FFT thermal stress. */
3112-
/* v0.4.64 — DIAG: immediate post-fill check for the AVX2 Sustained kernel (the
3112+
/* v0.4.65 — DIAG: immediate post-fill check for the AVX2 Sustained kernel (the
31133113
one that actually reports the byte-1 errors — earlier diag was wrongly placed
31143114
in run_avx2). Localizes WHEN byte 1 goes wrong: at the AVX2 store itself, or
31153115
during the FMA/verify window after it. */
31163116
static volatile UINT64 g_avx_imm_mismatch = 0;
31173117
static volatile int g_avx_imm_have_sample = 0;
31183118
static UINT64 g_avx_imm_addr = 0, g_avx_imm_exp = 0, g_avx_imm_act = 0;
3119-
static UINT64 g_avx_imm_flush = 0; /* v0.4.64 — DRAM re-read of the first post-store mismatch */
3120-
static volatile UINT64 g_avx_scalar_mismatch = 0; /* v0.4.64 — scalar-fill control */
3119+
static UINT64 g_avx_imm_flush = 0; /* v0.4.65 — DRAM re-read of the first post-store mismatch */
3120+
static volatile UINT64 g_avx_scalar_mismatch = 0; /* v0.4.65 — scalar-fill control */
31213121

31223122
static void run_avx2_sustained(ap_arg_t *a) {
31233123
if (!g_has_avx2) {
@@ -3164,7 +3164,7 @@ static void run_avx2_sustained(ap_arg_t *a) {
31643164
: "ymm0", "memory", "cc");
31653165
a->bytes += (UINT64)n * 8;
31663166

3167-
/* v0.4.64 — DIAG: verify the fill IMMEDIATELY, before the FMA burst,
3167+
/* v0.4.65 — DIAG: verify the fill IMMEDIATELY, before the FMA burst,
31683168
on the first iteration only. Mismatch HERE = the AVX2 256-bit store
31693169
itself wrote wrong byte 1; clean here but dirty at the post-FMA
31703170
verify = it goes wrong during the FMA/verify window. */
@@ -3185,7 +3185,7 @@ static void run_avx2_sustained(ap_arg_t *a) {
31853185
g_avx_imm_have_sample = 1;
31863186
}
31873187
}
3188-
/* v0.4.64 — CONTROL: same buffer, PLAIN scalar 64-bit stores, then
3188+
/* v0.4.65 — CONTROL: same buffer, PLAIN scalar 64-bit stores, then
31893189
immediate re-verify. AVX2 dirty + scalar clean => the 256-bit
31903190
store path is at fault, not the DRAM cells. */
31913191
for (UINTN iv = 0; iv < n; iv++) a->base[iv] = pat[iv & 3];
@@ -5701,7 +5701,7 @@ static void timing_probe_calibrate(void) {
57015701
uefi_call_wrapper(BS->FreePages, 2, addr, pages);
57025702
}
57035703

5704-
/* v0.4.64 — Path B step-2: recover DRAM addressing functions from the row-
5704+
/* v0.4.65 — Path B step-2: recover DRAM addressing functions from the row-
57055705
conflict timing channel (DRAMA solver). Groups random addresses into
57065706
"same-bank" sets (mutual row conflict), then brute-forces linear XOR
57075707
functions of address bits that are CONSTANT within every set but VARY across
@@ -5710,7 +5710,7 @@ static void timing_probe_calibrate(void) {
57105710
(which fn = DIMM) come next, anchored by a7=channel + the physical layout. */
57115711
#define FN_POOL 512
57125712
#define FN_BIT_LO 6
5713-
#define FN_BIT_HI 34 /* v0.4.64 — up to a34: block/channel select on a 16 GB box is a32/a33 */
5713+
#define FN_BIT_HI 34 /* v0.4.65 — up to a34: block/channel select on a 16 GB box is a32/a33 */
57145714
static UINT64 g_fn_pool[FN_POOL];
57155715
static UINT8 g_fn_setid[FN_POOL];
57165716
static int g_fn_nsets = 0;
@@ -5732,7 +5732,7 @@ static int fn_is_addr_func(UINT64 m) {
57325732
static void timing_probe_recover(void) {
57335733
CHAR16 lb[200];
57345734
if (!g_has_clflush) { log_line(L"[FUNC] no CLFLUSH — skipped"); return; }
5735-
/* v0.4.64 — sample across ALL physical RAM (every 4 GB block), not one low
5735+
/* v0.4.65 — sample across ALL physical RAM (every 4 GB block), not one low
57365736
512 MB buffer, so the channel/DIMM-select functions (block boundaries
57375737
a32/a33 on a 16 GB box) become visible — not just intra-DIMM bank bits.
57385738
Read-only: clflush + load timing on free conventional memory. */
@@ -5815,7 +5815,7 @@ static void timing_probe_recover(void) {
58155815
SPrint(lb, sizeof(lb), L"[FUNC] fn: a%d ^ a%d", a, b); log_line(lb); found++;
58165816
}
58175817
if (!found) log_line(L"[FUNC] no constant XOR funcs (1-2 bit) — sets noisy / threshold off");
5818-
/* v0.4.64 — test DRAMA Table-2a Haswell DDR3 candidates + block hypotheses
5818+
/* v0.4.65 — test DRAMA Table-2a Haswell DDR3 candidates + block hypotheses
58195819
EXPLICITLY (the channel hash is a 7-bit XOR, invisible to 1-2 bit brute). */
58205820
struct { UINT64 m; const CHAR16 *nm; } cand[] = {
58215821
{ (1ULL<<7)|(1ULL<<8)|(1ULL<<9)|(1ULL<<12)|(1ULL<<13)|(1ULL<<18)|(1ULL<<19), L"channel(intl 7b)" },
@@ -5829,17 +5829,31 @@ static void timing_probe_recover(void) {
58295829
fn_is_addr_func(cand[ci].m) ? (CHAR8*)"YES" : (CHAR8*)"no");
58305830
log_line(lb);
58315831
}
5832-
/* v0.4.64 — if the published Haswell channel hash AND the a16 DIMM bit both
5833-
check out on THIS board, lock them in as the attribution map. */
5834-
UINT64 chmask = (1ULL<<7)|(1ULL<<8)|(1ULL<<9)|(1ULL<<12)|(1ULL<<13)|(1ULL<<18)|(1ULL<<19);
5835-
if (fn_is_addr_func(chmask) && fn_is_addr_func(1ULL<<16)) {
5836-
g_intl_chan_mask = chmask;
5837-
g_intl_dimm_bit = 16;
5838-
g_intl_valid = 1;
5839-
log_line(L"[FUNC] interleave map CONFIRMED -> attribution by (channel,DIMM)=SPD slot");
5840-
} else {
5841-
log_line(L"[FUNC] interleave map NOT confirmed -> falling back to Type-20 ranges");
5832+
/* v0.4.65 — multi-platform address-map table. Each row is a published
5833+
(channel-hash, DIMM-in-channel-bit) pair; we accept the FIRST row whose
5834+
BOTH functions actually check out on THIS board (fn_is_addr_func tests
5835+
them against the live timing groups). A row that doesn't fit the silicon
5836+
simply won't confirm — so wrong rows never mis-attribute, they're just
5837+
skipped. Add new rows (Skylake/DDR4, AMD, …) once validated on that HW. */
5838+
static const struct { const CHAR16 *nm; UINT64 ch; int dbit; } g_addr_maps[] = {
5839+
{ L"Intel DDR3 2ch/2DIMM (IvyBridge/Haswell)",
5840+
(1ULL<<7)|(1ULL<<8)|(1ULL<<9)|(1ULL<<12)|(1ULL<<13)|(1ULL<<18)|(1ULL<<19), 16 },
5841+
{ L"Intel DDR3 2ch (SandyBridge)", (1ULL<<6), 16 },
5842+
};
5843+
g_intl_valid = 0;
5844+
for (UINTN mi = 0; mi < sizeof(g_addr_maps)/sizeof(g_addr_maps[0]); mi++) {
5845+
if (fn_is_addr_func(g_addr_maps[mi].ch) &&
5846+
fn_is_addr_func(1ULL << g_addr_maps[mi].dbit)) {
5847+
g_intl_chan_mask = g_addr_maps[mi].ch;
5848+
g_intl_dimm_bit = g_addr_maps[mi].dbit;
5849+
g_intl_valid = 1;
5850+
SPrint(lb, sizeof(lb), L"[FUNC] address map CONFIRMED: %s -> attribution by SPD slot", g_addr_maps[mi].nm);
5851+
log_line(lb);
5852+
break;
5853+
}
58425854
}
5855+
if (!g_intl_valid)
5856+
log_line(L"[FUNC] no known address map fits this platform -> Type-20 fallback");
58435857
for (int s = 0; s < 4 && s < g_fn_nsets; s++) {
58445858
UINT64 amn = ~0ULL, amx = 0; int cnt = 0;
58455859
for (int k = 0; k < FN_POOL; k++) if (g_fn_setid[k] == s) {
@@ -9369,7 +9383,7 @@ static void render_simple_verdict(UINT64 total_ms) {
93699383
}
93709384
} else { /* VERDICT_FAIL */
93719385
int didx = dominant_dimm_idx();
9372-
/* v0.4.64 — confirmed interleave map is AUTHORITATIVE: override the
9386+
/* v0.4.65 — confirmed interleave map is AUTHORITATIVE: override the
93739387
Type-20 guess with the exact (channel,DIMM)=SPD-slot attribution. */
93749388
if (g_intl_valid && g_dimm_count >= 4) {
93759389
UINT32 bn = 0; int bs = -1;
@@ -9706,8 +9720,8 @@ static void render_summary(UINT64 total_ms) {
97069720
UINTN hrow = (g_hdr_h / 2 - g_char_h / 2) / g_char_h;
97079721
CHAR16 buf[200];
97089722
SPrint(buf, sizeof(buf),
9709-
T(L" MEMFORGE v0.4.64 ИТОГИ | %d сек | Ядра %d/%d",
9710-
L" MEMFORGE v0.4.64 SUMMARY | %d sec | Cores %d/%d"),
9723+
T(L" MEMFORGE v0.4.65 ИТОГИ | %d сек | Ядра %d/%d",
9724+
L" MEMFORGE v0.4.65 SUMMARY | %d sec | Cores %d/%d"),
97119725
(UINT32)(total_ms / 1000),
97129726
(UINT32)g_n_enabled, (UINT32)g_n_cores);
97139727
say_at_rc(0, hrow, buf);
@@ -10231,7 +10245,7 @@ static void build_run_basename(CHAR16 *out, UINTN cap_chars) {
1023110245
UINT32 run_seq = g_hist_prev_valid ? (g_hist_prev.run_seq + 1) : 1;
1023210246
int di = (g_run_total_errors > 0) ? dominant_dimm_idx() : -1;
1023310247
if (di < 0 && g_dimm_count == 1) di = 0;
10234-
/* v0.4.64 — prefer the confirmed interleave attribution (exact SPD slot),
10248+
/* v0.4.65 — prefer the confirmed interleave attribution (exact SPD slot),
1023510249
same as the on-screen verdict, so the filename names the RIGHT stick. */
1023610250
if (g_intl_valid && g_dimm_count >= 4 && g_run_total_errors > 0) {
1023710251
UINT32 bn = 0; int bs = -1;
@@ -11855,7 +11869,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1185511869
}
1185611870
}
1185711871

11858-
log_line(L"=== MemForge2 v0.4.64 init ===");
11872+
log_line(L"=== MemForge2 v0.4.65 init ===");
1185911873
log_line(L"[WATCHDOG] UEFI 5-min watchdog disabled at app entry");
1186011874
flush_early_log(); /* v0.4.52 — emit lines buffered before the log opened */
1186111875
/* Show splash IMMEDIATELY so the user sees the program is alive while
@@ -11982,7 +11996,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1198211996
address->slot decode; for now logs MAD topology + cross-checks SMBIOS. */
1198311997
imc_dump();
1198411998
timing_probe_calibrate(); /* v0.4.54 — Path B step-1: validate row-conflict timing channel */
11985-
timing_probe_recover(); /* v0.4.64 — Path B step-2: recover DRAM addressing functions */
11999+
timing_probe_recover(); /* v0.4.65 — Path B step-2: recover DRAM addressing functions */
1198612000
/* Once total RAM is known: scale buffer-chunk size for big-RAM systems
1198712001
so the pass count stays reasonable. Skipped if user pinned BufferMB
1198812002
in quantai.ini. */
@@ -12299,7 +12313,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1229912313
g_pass_durations_count = 0;
1230012314
g_core_stall_count = 0; /* v0.4.47 — clear stall tally for fresh run */
1230112315
g_err_count = 0; /* v0.4.47 — fresh error tally each run */
12302-
g_avx_imm_mismatch = 0; g_avx_imm_have_sample = 0; /* v0.4.64 diag */
12316+
g_avx_imm_mismatch = 0; g_avx_imm_have_sample = 0; /* v0.4.65 diag */
1230312317
for (UINTN d = 0; d < MAX_DIMMS; d++) g_dimm_err_count[d] = 0;
1230412318
for (UINTN d = 0; d < MAX_DIMMS; d++) g_dimm_tested[d] = 0; /* v0.4.47 — fresh per-run */
1230512319
for (UINTN i = 0; i < g_n_enabled; i++) {
@@ -12723,7 +12737,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1272312737
L"corruption appears AFTER the fill (FMA/verify window)");
1272412738
log_line(db);
1272512739
}
12726-
/* v0.4.64 — interleave-aware verdict: name the bad stick by SERIAL
12740+
/* v0.4.65 — interleave-aware verdict: name the bad stick by SERIAL
1272712741
via the confirmed (channel,DIMM) map, not the wrong Type-20 range. */
1272812742
if (g_intl_valid && g_dimm_count >= 4) {
1272912743
int bs = -1; UINT32 bn = 0;

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MemForge2
22

3-
**Latest release: [v0.4.64](https://github.com/Paradoxdov/memforge/releases/latest)** — download `MemForge2.efi`, copy to `EFI/BOOT/loader.efi` on a FAT32 USB.
3+
**Latest release: [v0.4.65](https://github.com/Paradoxdov/memforge/releases/latest)** — download `MemForge2.efi`, copy to `EFI/BOOT/loader.efi` on a FAT32 USB.
44

55
UEFI memory diagnostic tool for shop / repair use. Boots from USB before any
66
OS loads, runs 14 stress and pattern tests in parallel on every CPU core,
@@ -222,7 +222,7 @@ EnableAVX=1
222222
;WatchdogSeconds=120 ; auto-reboot if a core wedges mid-test; 0 = off
223223

224224
[Meta]
225-
Version=0.4.64
225+
Version=0.4.65
226226
Language=en ; "ru" or "en"
227227

228228
[Display]

quantai.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ EnableAVX=1
9999

100100
; Used by the UEFI binary for the menu language. Also used by the analyzer.
101101
[Meta]
102-
Version=0.4.64
102+
Version=0.4.65
103103
Debug=0
104104
Language=en
105105

0 commit comments

Comments
 (0)