mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2025-02-16 13:34:45 -06:00
523 lines
14 KiB
C
523 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2020 Martin Whitaker.
|
|
//
|
|
// Derived from memtest86+ main.c:
|
|
//
|
|
// MemTest86+ V5 Specific code (GPL V2.0)
|
|
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
|
|
// http://www.canardpc.com - http://www.memtest.org
|
|
// ------------------------------------------------
|
|
// main.c - MemTest-86 Version 3.5
|
|
//
|
|
// Released under version 2 of the Gnu Public License.
|
|
// By Chris Brady
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "boot.h"
|
|
|
|
#include "cache.h"
|
|
#include "cpuid.h"
|
|
#include "cpuinfo.h"
|
|
#include "hwctrl.h"
|
|
#include "io.h"
|
|
#include "keyboard.h"
|
|
#include "pmem.h"
|
|
#include "memsize.h"
|
|
#include "pci.h"
|
|
#include "screen.h"
|
|
#include "smp.h"
|
|
#include "temperature.h"
|
|
#include "vmem.h"
|
|
|
|
#include "badram.h"
|
|
#include "config.h"
|
|
#include "display.h"
|
|
#include "error.h"
|
|
#include "test.h"
|
|
|
|
#include "tests.h"
|
|
|
|
#include "tsc.h"
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Constants
|
|
//------------------------------------------------------------------------------
|
|
|
|
#ifndef TRACE_BARRIERS
|
|
#define TRACE_BARRIERS 0
|
|
#endif
|
|
|
|
#ifndef TEST_INTERRUPT
|
|
#define TEST_INTERRUPT 0
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Private Variables
|
|
//------------------------------------------------------------------------------
|
|
|
|
// The following variables are written by the current "master" CPU, but may
|
|
// be read by all active CPUs.
|
|
|
|
static volatile int init_state = 0;
|
|
|
|
static barrier_t *start_barrier = NULL;
|
|
static spinlock_t *start_mutex = NULL;
|
|
|
|
static int8_t pcpu_num_to_vcpu_num[MAX_PCPUS];
|
|
|
|
static volatile bool start_run = false;
|
|
static volatile bool start_pass = false;
|
|
static volatile bool start_test = false;
|
|
static volatile bool rerun_test = false;
|
|
|
|
static volatile bool dummy_run = false;
|
|
|
|
static volatile int window_num = 0;
|
|
static volatile uintptr_t window_start = 0;
|
|
static volatile uintptr_t window_end = 0;
|
|
|
|
static volatile int test_stage = 0;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Public Variables
|
|
//------------------------------------------------------------------------------
|
|
|
|
// These are exposed in test.h.
|
|
|
|
int num_vcpus = 1;
|
|
|
|
volatile int master_vcpu = 0;
|
|
|
|
barrier_t *run_barrier = NULL;
|
|
|
|
spinlock_t *error_mutex = NULL;
|
|
|
|
volatile vm_map_t vm_map[MAX_MEM_SEGMENTS];
|
|
volatile int vm_map_size = 0;
|
|
|
|
volatile int pass_num = 0;
|
|
volatile int test_num = 0;
|
|
|
|
volatile bool restart = false;
|
|
volatile bool bail = false;
|
|
|
|
volatile uintptr_t test_addr[MAX_VCPUS];
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Private Functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
#define BARRIER \
|
|
if (TRACE_BARRIERS && !dummy_run) { \
|
|
trace(my_pcpu, "Start barrier wait at %s line %i", __FILE__, __LINE__); \
|
|
} \
|
|
barrier_wait(start_barrier)
|
|
|
|
static void run_at(uintptr_t addr, int my_pcpu)
|
|
{
|
|
uintptr_t *new_start_addr = (uintptr_t *)(addr + startup - _start);
|
|
|
|
if (my_pcpu == 0) {
|
|
map_window(0x80000); // TODO: Why is this necessary?
|
|
memmove((void *)addr, &_start, _end - _start);
|
|
}
|
|
BARRIER;
|
|
|
|
// We use a lock to ensure that only one CPU at a time jumps to
|
|
// the new code. Some of the startup stuff is not thread safe!
|
|
spin_lock(start_mutex);
|
|
|
|
goto *new_start_addr;
|
|
}
|
|
|
|
static void global_init(void)
|
|
{
|
|
floppy_off();
|
|
|
|
cache_on();
|
|
|
|
cpuid_init();
|
|
|
|
screen_init();
|
|
|
|
cpuinfo_init();
|
|
|
|
pmem_init();
|
|
|
|
pci_init();
|
|
|
|
badram_init();
|
|
|
|
config_init();
|
|
|
|
display_init();
|
|
|
|
error_init();
|
|
|
|
smp_init();
|
|
|
|
initial_config();
|
|
|
|
clear_message_area();
|
|
|
|
display_active_cpus(1);
|
|
display_total_cpus(num_pcpus);
|
|
|
|
num_vcpus = 0;
|
|
for (int i = 0; i < num_pcpus; i++) {
|
|
if (enable_pcpu[i]) {
|
|
pcpu_num_to_vcpu_num[i] = num_vcpus;
|
|
display_cpu_num(num_vcpus);
|
|
display_spinner(num_vcpus, 'S');
|
|
num_vcpus++;
|
|
}
|
|
}
|
|
master_vcpu = 0;
|
|
|
|
if (enable_temperature) {
|
|
int temp = get_cpu_temperature();
|
|
if (temp > 0) {
|
|
display_temperature(temp);
|
|
} else {
|
|
enable_temperature = false;
|
|
no_temperature = true;
|
|
}
|
|
}
|
|
if (enable_trace) {
|
|
display_pinned_message(0, 0,"CPU Trace");
|
|
display_pinned_message(1, 0,"--- ----------------------------------------------------------------------------");
|
|
set_scroll_lock(true);
|
|
}
|
|
|
|
for (int i = 0; i < pm_map_size; i++) {
|
|
trace(0, "pm %0*x - %0*x", 2*sizeof(uintptr_t), pm_map[i].start, 2*sizeof(uintptr_t), pm_map[i].end);
|
|
}
|
|
|
|
start_barrier = smp_alloc_barrier(num_vcpus);
|
|
run_barrier = smp_alloc_barrier(num_vcpus);
|
|
|
|
start_mutex = smp_alloc_mutex();
|
|
error_mutex = smp_alloc_mutex();
|
|
|
|
if (smp_start(enable_pcpu) != SMP_ERR_NONE) {
|
|
display_notice("Failed to start other CPUs. Press any key to reboot...");
|
|
while (get_key() == 0) { }
|
|
reboot();
|
|
}
|
|
|
|
start_run = true;
|
|
dummy_run = true;
|
|
restart = false;
|
|
}
|
|
|
|
static void setup_vm_map(uintptr_t win_start, uintptr_t win_end)
|
|
{
|
|
vm_map_size = 0;
|
|
|
|
// Reduce the window to fit in the user-specified limits.
|
|
if (win_start < pm_limit_lower) {
|
|
win_start = pm_limit_lower;
|
|
}
|
|
if (win_end > pm_limit_upper) {
|
|
win_end = pm_limit_upper;
|
|
}
|
|
if (win_start >= win_end) {
|
|
return;
|
|
}
|
|
|
|
// Now initialise the virtual memory map with the intersection
|
|
// of the window and the physical memory segments.
|
|
for (int i = 0; i < pm_map_size; i++) {
|
|
uintptr_t seg_start = pm_map[i].start;
|
|
uintptr_t seg_end = pm_map[i].end;
|
|
if (seg_start <= win_start) {
|
|
seg_start = win_start;
|
|
}
|
|
if (seg_end >= win_end) {
|
|
seg_end = win_end;
|
|
}
|
|
if (seg_start < seg_end && seg_start < win_end && seg_end > win_start) {
|
|
vm_map[vm_map_size].pm_base_addr = seg_start;
|
|
vm_map[vm_map_size].start = first_word_mapping(seg_start);
|
|
vm_map[vm_map_size].end = last_word_mapping(seg_end - 1, sizeof(testword_t));
|
|
vm_map_size++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_all_windows(int my_pcpu, int my_vcpu)
|
|
{
|
|
int active_cpus = 1;
|
|
bool i_am_active = (my_vcpu == master_vcpu);
|
|
if (!dummy_run) {
|
|
if (cpu_mode == PAR && test_list[test_num].cpu_mode == PAR) {
|
|
active_cpus = num_vcpus;
|
|
i_am_active = true;
|
|
}
|
|
}
|
|
if (my_vcpu == master_vcpu) {
|
|
barrier_init(run_barrier, active_cpus);
|
|
if (!dummy_run) {
|
|
display_active_cpus(active_cpus);
|
|
}
|
|
}
|
|
|
|
int iterations = test_list[test_num].iterations;
|
|
if (pass_num == 0) {
|
|
// Reduce iterations for a faster first pass.
|
|
iterations /= 3;
|
|
}
|
|
|
|
// Loop through all possible windows.
|
|
do {
|
|
if (!dummy_run) {
|
|
display_spinner(my_vcpu, 'W');
|
|
}
|
|
BARRIER;
|
|
if (bail) {
|
|
break;
|
|
}
|
|
|
|
if (my_vcpu == master_vcpu) {
|
|
if (window_num == 0 && test_list[test_num].stages > 1) {
|
|
// A multi-stage test runs through all the windows at each stage.
|
|
// Relocation may disrupt the test.
|
|
window_num = 1;
|
|
}
|
|
if (window_num == 0 && pm_limit_lower >= HIGH_LOAD_ADDR) {
|
|
// Avoid unnecessary relocation.
|
|
window_num = 1;
|
|
}
|
|
}
|
|
BARRIER;
|
|
|
|
// Relocate if necessary.
|
|
if (window_num > 0) {
|
|
if (!dummy_run && (uintptr_t)&_start != LOW_LOAD_ADDR) {
|
|
run_at(LOW_LOAD_ADDR, my_pcpu);
|
|
}
|
|
} else {
|
|
if (!dummy_run && (uintptr_t)&_start != HIGH_LOAD_ADDR) {
|
|
run_at(HIGH_LOAD_ADDR, my_pcpu);
|
|
}
|
|
}
|
|
|
|
if (my_vcpu == master_vcpu) {
|
|
switch (window_num) {
|
|
case 0:
|
|
window_start = 0;
|
|
window_end = (HIGH_LOAD_ADDR >> PAGE_SHIFT);
|
|
break;
|
|
case 1:
|
|
window_start = (HIGH_LOAD_ADDR >> PAGE_SHIFT);
|
|
window_end = VM_WINDOW_SIZE;
|
|
break;
|
|
default:
|
|
window_start = window_end;
|
|
window_end += VM_WINDOW_SIZE;
|
|
}
|
|
setup_vm_map(window_start, window_end);
|
|
}
|
|
BARRIER;
|
|
|
|
if (!i_am_active) {
|
|
continue;
|
|
}
|
|
|
|
if (vm_map_size == 0) {
|
|
// No memory to test in this window.
|
|
if (my_vcpu == master_vcpu) {
|
|
window_num++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!dummy_run) {
|
|
display_spinner(my_vcpu, '-');
|
|
}
|
|
|
|
if (dummy_run) {
|
|
if (my_vcpu == master_vcpu) {
|
|
ticks_per_test[pass_num][test_num] += run_test(-1, test_num, test_stage, iterations);
|
|
}
|
|
} else {
|
|
if (!map_window(vm_map[0].pm_base_addr)) {
|
|
// Either there is no PAE or we are at the PAE limit.
|
|
break;
|
|
}
|
|
run_test(my_vcpu, test_num, test_stage, iterations);
|
|
}
|
|
|
|
if (my_vcpu == master_vcpu) {
|
|
window_num++;
|
|
}
|
|
} while (window_end < pm_map[pm_map_size - 1].end);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Public Functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
// The main entry point called from the startup code.
|
|
|
|
void main(void)
|
|
{
|
|
int my_pcpu;
|
|
if (init_state == 0) {
|
|
// If this is the first time here, we must be CPU 0, as the APs haven't been started yet.
|
|
my_pcpu = 0;
|
|
} else {
|
|
my_pcpu = smp_my_pcpu_num();
|
|
}
|
|
|
|
if (init_state < 2) {
|
|
// Global initialisation is done by the boot CPU.
|
|
if (my_pcpu == 0) {
|
|
init_state = 1;
|
|
global_init();
|
|
} else {
|
|
smp_set_ap_booted(my_pcpu);
|
|
}
|
|
} else {
|
|
// Release the lock taken in run_at().
|
|
spin_unlock(start_mutex);
|
|
}
|
|
BARRIER;
|
|
init_state = 2;
|
|
|
|
#if TEST_INTERRUPT
|
|
if (my_pcpu == 0) {
|
|
__asm__ __volatile__ ("int $1");
|
|
}
|
|
#endif
|
|
|
|
int my_vcpu = pcpu_num_to_vcpu_num[my_pcpu];
|
|
|
|
// Due to the need to relocate ourselves in the middle of tests, the following
|
|
// code cannot be written in the natural way as a set of nested loops. So we
|
|
// have a single loop and use global state variables to allow us to restart
|
|
// where we left off after each relocation.
|
|
|
|
while (1) {
|
|
BARRIER;
|
|
if (my_vcpu == 0) {
|
|
if (start_run) {
|
|
pass_num = 0;
|
|
start_pass = true;
|
|
if (!dummy_run) {
|
|
display_start_run();
|
|
badram_init();
|
|
error_init();
|
|
}
|
|
}
|
|
if (start_pass) {
|
|
test_num = 0;
|
|
start_test = true;
|
|
if (dummy_run) {
|
|
ticks_per_pass[pass_num] = 0;
|
|
} else {
|
|
display_start_pass();
|
|
}
|
|
}
|
|
if (start_test) {
|
|
// trace(my_vcpu, "start test %i", test_num);
|
|
test_stage = 0;
|
|
rerun_test = true;
|
|
if (dummy_run) {
|
|
ticks_per_test[pass_num][test_num] = 0;
|
|
} else if (test_list[test_num].enabled) {
|
|
display_start_test();
|
|
}
|
|
bail = false;
|
|
}
|
|
if (rerun_test) {
|
|
window_num = 0;
|
|
window_start = 0;
|
|
window_end = 0;
|
|
}
|
|
start_run = false;
|
|
start_pass = false;
|
|
start_test = false;
|
|
rerun_test = false;
|
|
}
|
|
BARRIER;
|
|
if (test_list[test_num].enabled) {
|
|
test_all_windows(my_pcpu, my_vcpu);
|
|
}
|
|
BARRIER;
|
|
if (my_vcpu != 0) {
|
|
continue;
|
|
}
|
|
|
|
check_input();
|
|
if (restart) {
|
|
// The configuration has been changed.
|
|
master_vcpu = 0;
|
|
start_run = true;
|
|
dummy_run = true;
|
|
restart = false;
|
|
continue;
|
|
}
|
|
error_update();
|
|
|
|
if (test_list[test_num].enabled) {
|
|
if (++test_stage < test_list[test_num].stages) {
|
|
rerun_test = true;
|
|
continue;
|
|
}
|
|
test_stage = 0;
|
|
|
|
switch (cpu_mode) {
|
|
case PAR:
|
|
if (test_list[test_num].cpu_mode == SEQ) {
|
|
master_vcpu = (master_vcpu + 1) % num_vcpus;
|
|
if (master_vcpu != 0) {
|
|
rerun_test = true;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
case ONE:
|
|
master_vcpu = (master_vcpu + 1) % num_vcpus;
|
|
break;
|
|
case SEQ:
|
|
master_vcpu = (master_vcpu + 1) % num_vcpus;
|
|
if (master_vcpu != 0) {
|
|
rerun_test = true;
|
|
continue;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dummy_run) {
|
|
ticks_per_pass[pass_num] += ticks_per_test[pass_num][test_num];
|
|
}
|
|
|
|
start_test = true;
|
|
test_num++;
|
|
if (test_num < NUM_TEST_PATTERNS) {
|
|
continue;
|
|
}
|
|
|
|
pass_num++;
|
|
if (dummy_run && pass_num == NUM_PASS_TYPES) {
|
|
start_run = true;
|
|
dummy_run = false;
|
|
continue;
|
|
}
|
|
|
|
start_pass = true;
|
|
if (!dummy_run) {
|
|
display_pass_count(pass_num);
|
|
if (error_count == 0) {
|
|
display_notice("** Pass completed, no errors **");
|
|
}
|
|
}
|
|
}
|
|
}
|