// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2020 Martin Whitaker. // // Derived from memtest86+ config.c: // // MemTest86+ V5.00 Specific code (GPL V2.0) // By Samuel DEMEULEMEESTER, sdemeule@memtest.org // http://www.x86-secret.com - http://www.memtest.org // ---------------------------------------------------- // config.c - MemTest-86 Version 3.4 // // Released under version 2 of the Gnu Public License. // By Chris Brady #include #include #include "cpuinfo.h" #include "hwctrl.h" #include "keyboard.h" #include "memsize.h" #include "pmem.h" #include "screen.h" #include "smp.h" #include "read.h" #include "print.h" #include "unistd.h" #include "display.h" #include "test.h" #include "tests.h" #include "config.h" //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ // Origin and size of the pop-up window. #define POP_R 4 #define POP_C 22 #define POP_W 36 #define POP_H 16 #define POP_REGION POP_R, POP_C, POP_R + POP_H - 1, POP_C + POP_W - 1 static const char *cpu_mode_str[] = { "PAR", "SEQ", "RR " }; //------------------------------------------------------------------------------ // Private Variables //------------------------------------------------------------------------------ static uint16_t popup_save_buffer[POP_W * POP_H]; //------------------------------------------------------------------------------ // Public Variables //------------------------------------------------------------------------------ uintptr_t pm_limit_lower = 0; uintptr_t pm_limit_upper = 0; uintptr_t num_pages_to_test = 0; cpu_mode_t cpu_mode = PAR; error_mode_t error_mode = ERROR_MODE_NONE; bool enable_pcpu[MAX_PCPUS]; bool enable_temperature = false; bool enable_trace = false; //------------------------------------------------------------------------------ // Private Functions //------------------------------------------------------------------------------ static void update_num_pages_to_test(void) { num_pages_to_test = 0; for (int i = 0; i < pm_map_size; i++) { if (pm_map[i].start >= pm_limit_lower && pm_map[i].end <= pm_limit_upper) { num_pages_to_test += pm_map[i].end - pm_map[i].start; continue; } if (pm_map[i].start < pm_limit_lower) { if (pm_map[i].end < pm_limit_lower) { continue; } if (pm_map[i].end > pm_limit_upper) { num_pages_to_test += pm_limit_upper - pm_limit_lower; } else { num_pages_to_test += pm_map[i].end - pm_limit_lower; } continue; } if (pm_map[i].end > pm_limit_upper) { if (pm_map[i].start > pm_limit_upper) { continue; } num_pages_to_test += pm_limit_upper - pm_map[i].start; } } } static void clear_popup_row(int row) { clear_screen_region(row, POP_C, row, POP_C + POP_W - 1); } static void display_input_message(int row, const char *message) { clear_popup_row(row); prints(row, POP_C+2, message); } static void display_error_message(int row, const char *message) { clear_popup_row(row); set_foreground_colour(YELLOW); prints(row, POP_C+2, message); set_foreground_colour(WHITE); } static void display_selection_header(int row, int max_num) { prints(row+0, POP_C+2, "Current selection:"); printc(row+1, POP_C+2, '0'); for (int i = 1; i < max_num; i++) { printc(row+1, POP_C+2+i, i % 10 ? 0xc4 : 0xc3); } printi(row+1, POP_C+2+max_num, max_num, 2, false, true); } static void display_enabled(int row, int n, bool enabled) { printc(row, POP_C+2+n, enabled ? '*' : '.'); } static bool set_all_tests(bool enabled) { clear_popup_row(POP_R+14); for (int i = 0; i < NUM_TEST_PATTERNS; i++) { test_list[i].enabled = enabled; display_enabled(POP_R+12, i, enabled); } return true; } static bool add_or_remove_test(bool add) { display_input_message(POP_R+14, "Enter test #"); int n = read_value(POP_R+14, POP_C+2+12, 2, 0); if (n < 0 || n >= NUM_TEST_PATTERNS) { display_error_message(POP_R+14, "Invalid test number"); return false; } test_list[n].enabled = add; display_enabled(POP_R+12, n, add); clear_popup_row(POP_R+14); return true; } static bool add_test_range() { display_input_message(POP_R+14, "Enter first test #"); int n1 = read_value(POP_R+14, POP_C+2+18, 2, 0); if (n1 < 0 || n1 >= NUM_TEST_PATTERNS) { display_error_message(POP_R+14, "Invalid test number"); return false; } display_input_message(POP_R+14, "Enter last test #"); int n2 = read_value(POP_R+14, POP_C+2+17, 2, 0); if (n2 < n1 || n2 >= NUM_TEST_PATTERNS) { display_error_message(POP_R+14, "Invalid test range"); return false; } for (int i = n1; i <= n2; i++) { test_list[i].enabled = true; display_enabled(POP_R+12, i, true); } clear_popup_row(POP_R+14); return true; } static void test_selection_menu(void) { clear_screen_region(POP_REGION); prints(POP_R+1, POP_C+2, "Test Selection:"); prints(POP_R+3, POP_C+4, " Clear selection"); prints(POP_R+4, POP_C+4, " Remove one test"); prints(POP_R+5, POP_C+4, " Add one test"); prints(POP_R+6, POP_C+4, " Add test range"); prints(POP_R+7, POP_C+4, " Add all tests"); prints(POP_R+8, POP_C+4, " Exit menu"); display_selection_header(POP_R+10, NUM_TEST_PATTERNS - 1); for (int i = 0; i < NUM_TEST_PATTERNS; i++) { display_enabled(POP_R+12, i, test_list[i].enabled); } bool exit_menu = false; while (!exit_menu) { bool changed = false; switch (get_key()) { case '1': changed = set_all_tests(false); break; case '2': changed = add_or_remove_test(false); break; case '3': changed = add_or_remove_test(true); break; case '4': changed = add_test_range(); break; case '5': changed = set_all_tests(true); break; case ESC: { clear_popup_row(POP_R+14); int num_selected = 0; for (int i = 0; i < NUM_TEST_PATTERNS; i++) { if (test_list[i].enabled) { num_selected++; } } if (num_selected > 0) { exit_menu = true; } else { display_error_message(POP_R+14, "You must select at least one test"); } } break; default: usleep(1000); break; } if (changed) { restart = true; changed = false; } } clear_screen_region(POP_REGION); } static void address_range_menu(void) { clear_screen_region(POP_REGION); prints(POP_R+1, POP_C+2, "Address Range:"); prints(POP_R+3, POP_C+4, " Set lower limit"); prints(POP_R+4, POP_C+4, " Set upper limit"); prints(POP_R+5, POP_C+4, " Test all memory"); prints(POP_R+6, POP_C+4, " Exit menu"); printf(POP_R+8, POP_C+2, "Current range: %kB - %kB", pm_limit_lower << 2, pm_limit_upper << 2); bool exit_menu = false; while (!exit_menu) { bool changed = false; switch (get_key()) { case '1': { display_input_message(POP_R+10, "Enter lower limit: "); uintptr_t page = read_value(POP_R+10, POP_C+2+19, 15, -PAGE_SHIFT); if (page < pm_limit_upper) { clear_popup_row(POP_R+10); pm_limit_lower = page; changed = true; } else { display_error_message(POP_R+10, "Lower must be less than upper"); } } break; case '2': { display_input_message(POP_R+10, "Enter upper limit: "); uintptr_t page = read_value(POP_R+10, POP_C+2+19, 15, -PAGE_SHIFT); if (page > pm_limit_lower) { clear_popup_row(POP_R+10); pm_limit_upper = page; changed = true; } else { display_error_message(POP_R+10, "Upper must be greater than lower"); } } break; case '3': clear_popup_row(POP_R+10); pm_limit_lower = 0; pm_limit_upper = pm_map[pm_map_size - 1].end; changed = true; break; case ESC: exit_menu = true; break; default: usleep(1000); break; } if (changed) { clear_popup_row(POP_R+8); printf(POP_R+8, POP_C+2, "Current range: %kB - %kB", pm_limit_lower << 2, pm_limit_upper << 2); update_num_pages_to_test(); restart = true; changed = false; } } clear_screen_region(POP_REGION); } static void cpu_mode_menu(void) { clear_screen_region(POP_REGION); prints(POP_R+1, POP_C+2, "CPU Sequencing Mode:"); prints(POP_R+3, POP_C+4, " Parallel (All)"); prints(POP_R+4, POP_C+4, " Sequential (Seq)"); prints(POP_R+5, POP_C+4, " Round robin (RR)"); prints(POP_R+6, POP_C+4, " Exit menu"); printc(POP_R+3+cpu_mode, POP_C+2, '*'); bool exit_menu = false; while (!exit_menu) { int ch = get_key(); switch (ch) { case '1': case '2': case '3': printc(POP_R+3+cpu_mode, POP_C+2, ' '); cpu_mode = ch - '1'; printc(POP_R+3+cpu_mode, POP_C+2, '*'); break; case ESC: exit_menu = true; break; default: usleep(1000); break; } } clear_screen_region(POP_REGION); } static void error_mode_menu(void) { clear_screen_region(POP_REGION); prints(POP_R+1, POP_C+2, "Error Reporting Mode:"); prints(POP_R+3, POP_C+4, " Error counts only"); prints(POP_R+4, POP_C+4, " Error summary"); prints(POP_R+5, POP_C+4, " Individual errors"); prints(POP_R+6, POP_C+4, " BadRAM patterns"); prints(POP_R+7, POP_C+4, " Exit menu"); printc(POP_R+3+error_mode, POP_C+2, '*'); bool exit_menu = false; while (!exit_menu) { int ch = get_key(); switch (ch) { case '1': case '2': case '3': case '4': printc(POP_R+3+error_mode, POP_C+2, ' '); error_mode = ch - '1'; printc(POP_R+3+error_mode, POP_C+2, '*'); break; case ESC: exit_menu = true; break; default: usleep(1000); break; } } clear_screen_region(POP_REGION); } static bool set_all_cpus(bool enabled) { clear_popup_row(POP_R+14); for (int i = 1; i < num_pcpus; i++) { enable_pcpu[i] = enabled; display_enabled(POP_R+12, i, enabled); } return true; } static bool add_or_remove_cpu(bool add) { display_input_message(POP_R+14, "Enter CPU #"); int n = read_value(POP_R+14, POP_C+2+11, 2, 0); if (n < 1 || n >= num_pcpus) { display_error_message(POP_R+14, "Invalid CPU number"); return false; } enable_pcpu[n] = add; display_enabled(POP_R+12, n, add); clear_popup_row(POP_R+14); return true; } static bool add_cpu_range() { display_input_message(POP_R+14, "Enter first CPU #"); int n1 = read_value(POP_R+14, POP_C+2+17, 2, 0); if (n1 < 1 || n1 >= num_pcpus) { display_error_message(POP_R+14, "Invalid CPU number"); return false; } display_input_message(POP_R+14, "Enter last CPU #"); int n2 = read_value(POP_R+14, POP_C+2+16, 2, 0); if (n2 < n1 || n2 >= num_pcpus) { display_error_message(POP_R+14, "Invalid CPU range"); return false; } for (int i = n1; i <= n2; i++) { enable_pcpu[i] = true; display_enabled(POP_R+12, i, true); } clear_popup_row(POP_R+14); return true; } static void cpu_selection_menu(void) { clear_screen_region(POP_REGION); prints(POP_R+1, POP_C+2, "CPU Selection:"); prints(POP_R+3, POP_C+4, " Clear selection"); prints(POP_R+4, POP_C+4, " Remove one CPU"); prints(POP_R+5, POP_C+4, " Add one CPU"); prints(POP_R+6, POP_C+4, " Add CPU range"); prints(POP_R+7, POP_C+4, " Add all CPUs"); prints(POP_R+8, POP_C+4, " Exit menu"); display_selection_header(POP_R+10, num_pcpus - 1); printc(POP_R+12, POP_C+2, 'B'); for (int i = 1; i < num_pcpus; i++) { display_enabled(POP_R+12, i, enable_pcpu[i]); } bool exit_menu = false; while (!exit_menu) { bool changed = false; switch (get_key()) { case '1': changed = set_all_cpus(false); break; case '2': changed = add_or_remove_cpu(false); break; case '3': changed = add_or_remove_cpu(true); break; case '4': changed = add_cpu_range(); break; case '5': changed = set_all_cpus(true); break; case ESC: clear_popup_row(POP_R+14); exit_menu = true; break; default: usleep(1000); break; } if (changed) { restart = true; changed = false; } } clear_screen_region(POP_REGION); } //------------------------------------------------------------------------------ // Public Functions //------------------------------------------------------------------------------ void config_init(void) { pm_limit_lower = 0; pm_limit_upper = pm_map[pm_map_size - 1].end; update_num_pages_to_test(); cpu_mode = PAR; error_mode = ERROR_MODE_ADDRESS; for (int i = 0; i < MAX_PCPUS; i++) { enable_pcpu[i] = true; } enable_temperature = !no_temperature; enable_trace = false; } void config_menu(bool initial) { save_screen_region(POP_REGION, popup_save_buffer); set_background_colour(BLACK); clear_screen_region(POP_REGION); cpu_mode_t old_cpu_mode = cpu_mode; bool exit_menu = false; while (!exit_menu) { prints(POP_R+1, POP_C+2, "Settings:"); prints(POP_R+3, POP_C+4, " Test selection"); prints(POP_R+4, POP_C+4, " Address range"); prints(POP_R+5, POP_C+4, " CPU sequencing mode"); prints(POP_R+6, POP_C+4, " Error reporting mode"); if (initial) { if (num_pcpus < 2) set_foreground_colour(BOLD+BLACK); prints(POP_R+7, POP_C+4, " CPU selection"); if (num_pcpus < 2) set_foreground_colour(WHITE); if (no_temperature) set_foreground_colour(BOLD+BLACK); printf(POP_R+8, POP_C+4, " Temperature %s", enable_temperature ? "disable" : "enable "); if (no_temperature) set_foreground_colour(WHITE); printf(POP_R+9, POP_C+4, " Boot trace %s", enable_trace ? "disable" : "enable "); prints(POP_R+10, POP_C+4, " Exit menu"); } else { prints(POP_R+7, POP_C+4, " Skip current test"); prints(POP_R+8 , POP_C+4, " Exit menu"); } switch (get_key()) { case '1': test_selection_menu(); break; case '2': address_range_menu(); break; case '3': cpu_mode_menu(); break; case '4': error_mode_menu(); break; case '5': if (initial) { if (num_pcpus > 1) { cpu_selection_menu(); } } else { bail = true; } break; case '6': if (initial) { if (!no_temperature) { enable_temperature = !enable_temperature; } } break; case '7': if (initial) { enable_trace = !enable_trace; } break; case ESC: exit_menu = true; break; default: usleep(1000); break; } } restore_screen_region(POP_REGION, popup_save_buffer); set_background_colour(BLUE); if (cpu_mode != old_cpu_mode) { display_cpu_mode(cpu_mode_str[cpu_mode]); restart = true; } if (restart) { bail = true; } } void initial_config(void) { display_notice("Press to configure, to enable SMP, to start testing "); bool got_key = false; bool smp_enabled = false; bool smp_init_done = false; for (int i = 0; i < 5000 && !got_key; i++) { usleep(1000); switch (get_key()) { case ESC: clear_message_area(); display_notice("Rebooting..."); reboot(); break; case '1': smp_init(smp_enabled); smp_init_done = true; config_menu(true); got_key = true; break; case '2': smp_enabled = !smp_enabled; if (smp_enabled) { display_notice("Press to configure, to disable SMP, to start testing"); } else { display_notice("Press to configure, to enable SMP, to start testing "); } i = 0; break; case ' ': toggle_scroll_lock(); break; case '\n': got_key = true; break; default: break; } } if (!smp_init_done) { smp_init(smp_enabled); } }