Initial commit.

This commit is contained in:
Martin Whitaker
2020-05-24 21:30:55 +01:00
parent 4d7ba9de49
commit fbd3376668
77 changed files with 16667 additions and 47 deletions

49
.gitignore vendored
View File

@@ -3,50 +3,9 @@
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Linker scripts
ldscript*
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# Binaries
memtest*

473
README.md
View File

@@ -1,2 +1,471 @@
# pcmemtest
A memory tester for Intel/AMD x86 systems (rewrite of memtest86+)
# PCMemTest
PCMemTest is a thorough, stand-alone memory test for Intel/AMD x86 architecture
systems. BIOS based memory tests are only a quick check and often miss failures
that are detected by PCMemTest.
PCMemTest can be loaded and run either directly by a legacy PC BIOS or via an
intermediate bootloader that supports the Linux 16-bit, 32-bit, or 64-bit boot
protocol. It should work on any Pentium class or later CPU.
## Table of Contents
* [Origins]
* [Licensing]
* [Build and Installation]
* [Operation]
* [Error Display]
* [Trouble-shooting Memory Errors]
* [Execution Time]
* [Memory Testing Philosophy]
* [PCMemTest Test Algorithms]
* [Individual Test Descriptions]
* [Known Limitations and Bugs]
* [Acknowledgments]
## Origins
PCMemTest is a fork and rewrite of Memtest86+, which in turn was a fork of
Memtest86. The purpose of the rewrite was to:
* make the code more readable and easier to maintain
* make the code 64-bit clean and support UEFI boot
* fix failures seen when building with newer versions of GCC
In the process, a number of features of Memtest86+ that are not required for
the main purpose of PCMemTest (testing the system memory) have been dropped.
In particular, no attempt is made to measure the cache and main memory speed,
or to identify and report the DRAM type. This should allow PCMemTest to work
without modification on future hardware.
PCMemTest was based on the last public release of Memtest86+, v5.01.
## Licensing
PCMemTest is released under the terms of the GNU General Public License version
2 (GPLv2). Other than the provisions of the GPL there are no restrictions for
use, private or commercial. See the LICENSE file for details.
## Build and Installation
Build is only tested on a Linux system, but should be possible on any system
using the GNU toolchain and the ELF file format. The tools required are:
* GCC
* binutils
* make
* genisoimage (optional)
To build a 32-bit image that can be booted by an intermediate bootloader,
change directory into the `build32` directory and type `make`. The result
is a `memtest.bin` binary image file which can be booted using either the
16-bit or 32-bit Linux boot protocols.
To build a 64-bit image that can be booted by an intermediate bootloader,
change directory into the `build64` directory and type `make`. The result
is a `memtest.bin` binary image file which can be booted using any of the
16-bit, 32-bit, or 64-bit Linux boot protocols.
In either case, to build an ISO image that can be used to create a bootable
CD, DVD, or USB Flash drive, type `make iso`, The result is a `memtest.iso`
ISO image file. This can then be written directly to a blank CD or DVD, or
to a USB Flash drive, which can then be booted directly by a legacy PC BIOS.
Note that when writing to a USB Flash drive, the ISO image must be written
directly ('dumped') to the raw device, either by using the `dd` command or
by using a utility that provides the same functionality.
When using an intermediate bootloader, the memtest.bin file should be stored
in a disk partition the bootloader can access, and the bootloader configuration
should be updated to boot from that file as if it were a Linux kernel with no
initial RAM disk. Any boot command line options are ignored. If using the
16-bit boot protocol, PCMemTest will use the display in text mode (640x400).
If using the 32-bit or 64-bit boot protocols, PCMemTest will use the display
in either text mode or graphics mode, as specified in the boot_params struct
passed to it by the bootloader. If in graphics mode, the supplied framebuffer
must be at least 640x400 pixels; if larger, the display will be centred. If
the system was booted in UEFI mode, graphics mode must be used.
## Operation
Once booted, PCMemTest will initialise its display, then pause for a few
seconds to allow the user to configure its operation. If no key is pressed,
it will automatically start running all tests using all available CPU cores,
continuing indefinitely until the user reboots or halts the machine.
At startup, and when running tests, PCMemTest responds to the following keys:
* F1
* enters the configuration menu
* Space
* toggles scroll lock (stops/starts error message scrolling)
* Enter
* single message scroll (only when scroll lock enabled)
* Escape
* exits the test and reboots the machine
Note that testing is stalled when scroll lock is enabled and the scroll region
is full.
The configuration menu allows the user to:
* select which tests are run (default: all tests)
* limit the address range over which tests are performed (default: all memory)
* select the CPU sequencing mode (default: parallel)
* parallel
* each CPU core works in parallel on a subset of the memory region being
tested
* sequential
* each CPU core works in turn on the full memory region being tested
* round robin
* a single CPU core works on the full memory region being tested, with a
new CPU core being selected (in round-robin fashion) for each test
* select the error reporting mode (default: individual errors)
* error counts only
* error summary
* individual errors
* BadRAM patterns
* select which of the available CPU cores are used (at startup only)
* a maximum of 32 CPU cores can be selected, due to display limits
* the bootstrap processor (BSP) cannot be deselected
* enable or disable the temperature display (at startup only)
* enable or disable boot tracing for debug (at startup only)
* skip to the next test (when running tests)
## Error Reporting
The error reporting mode may be changed at any time without disrupting the
current test sequence. Error statistics are collected regardless of the
current error reporting mode (so switching to error summary mode will show
the accumulated statistics since the current test sequence started). BadRAM
patterns are only accumulated when in BadRAM mode.
Any change to the selected tests, address range, or CPU sequencing mode will
start a new test sequence and reset the error statistics.
### Error Counts Only
The error counts only mode just displays the total number of errors found since
the current test sequence started.
### Error Summary
The error summary mode displays the following information:
* Lowest Error Address
* the lowest address that where an error has been reported
* Highest Error Address
* the highest address that where an error has been reported
* Bits in Error Mask
* a hexadecimal mask of all bits that have been in error
* Bits in Error
* total bits in error for all error instances and the min, max and average
number of bits in error across each error instance
* Max Contiguous Errors
* the maximum of contiguous addresses with errors
* Test Errors
* the total number of errors for each individual test
### Individual Errors
The individual error mode displays the following information for each error
instance:
* pCPU
* the physical CPU core number that detected the error
* Pass
* the test pass number where the error occurred (a test pass is a single
run over all the currently selected tests)
* Test
* the individual test number where the error occurred
* Failing Address
* the memory address where the error occurred
* Expected
* the hexadecimal data pattern expected to be found
* Found
* the hexadecimal data pattern read from the failing address
* Err Bits (only in 32-bit builds)
* a hexadecimal mask showing the bits in error
### BadRAM Patterns
The BadRAM patterns mode accumulates and displays error patterns for use with
the [Linux BadRAM feature](http://rick.vanrein.org/linux/badram/). Lines are
printed in the form `badram=F1,M1,F2,M2...` In each `F,M` pair, the `F`
represents a fault address and the `M` is a bitmask for that address. These
patterns state that faults have occurred in addresses that equal F on all `1`
bits in M. Such a pattern may capture more errors that actually exist, but
at least all the errors are captured. These patterns have been designed to
capture regular patterns of errors caused by the hardware structure in a terse
syntax.
The BadRAM patterns are grown incrementally rather than calculated from an
overview of all errors. The number of pairs is constrained to five for a
number of practical reasons. As a result, handcrafting patterns from the
output in address printing mode may, in exceptional cases, yield better
results.
## Trouble-shooting Memory Errors
Please be aware that not all errors reported by PCMemTest are due to bad
memory. The test implicitly tests the CPU, caches, and motherboard. It is
impossible for the test to determine what causes the failure to occur. Most
failures will be due to a problem with memory. When it is not, the only option
is to replace parts until the failure is corrected.
Once a memory error has been detected, determining the failing module is not a
clear cut procedure. With the large number of motherboard vendors and possible
combinations of memory slots it would be difficult if not impossible to assemble
complete information about how a particular error would map to a failing memory
module. However, there are steps that may be taken to determine the failing
module. Here are three techniques that you may wish to use:
1) Removing modules
This is the simplest method for isolating a failing modules, but may only
be employed when one or more modules can be removed from the system. By
selectively removing modules from the system and then running the test
you will be able to find the bad module(s). Be sure to note exactly which
modules are in the system when the test passes and when the test fails.
2) Rotating modules
When none of the modules can be removed then you may wish to rotate modules
to find the failing one. This technique can only be used if there are three
or more modules in the system. Change the location of two modules at a time.
For example put the module from slot 1 into slot 2 and put the module from
slot 2 in slot 1. Run the test and if either the failing bit or address
changes then you know that the failing module is one of the ones just moved.
By using several combinations of module movement you should be able to
determine which module is failing.
3) Replacing modules
If you are unable to use either of the previous techniques then you are
left to selective replacement of modules to find the failure.
Sometimes memory errors show up due to component incompatibility. A memory
module may work fine in one system and not in another. This is not uncommon
and is a source of confusion. The components are not necessarily bad but
certain combinations may need to be avoided.
In the vast majority of cases errors reported by PCMemTest are valid. There
are some systems that cause PCMemTest to be confused about the size of memory
and it will try to test non-existent memory. This will cause a large number of
consecutive addresses to be reported as bad and generally there will be many
bits in error. If you have a relatively small number of failing addresses and
only one or two bits in error you can be certain that the errors are valid.
Also intermittent errors are always valid.
All valid memory errors should be corrected. It is possible that a particular
error will never show up in normal operation. However, operating with marginal
memory is risky and can result in data loss and even disk corruption.
PCMemTest can not diagnose many types of PC failures. For example a faulty CPU
that causes your OS to crash will most likely just cause PCMemTest to crash in
the same way.
## Execution Time
The time required for a complete pass of PCMemTest will vary greatly depending
on CPU speed, memory speed, and memory size. PCMemTest executes indefinitely.
The pass counter increments each time that all of the selected tests have been
run. Generally a single pass is sufficient to catch all but the most obscure
errors. However, for complete confidence when intermittent errors are suspected
testing for a longer period is advised.
## Memory Testing Philosophy
There are many good approaches for testing memory. However, many tests simply
throw some patterns at memory without much thought or knowledge of memory
architecture or how errors can best be detected. This works fine for hard
memory failures but does little to find intermittent errors. BIOS based memory
tests are useless for finding intermittent memory errors.
Memory chips consist of a large array of tightly packed memory cells, one for
each bit of data. The vast majority of the intermittent failures are a result
of interaction between these memory cells. Often writing a memory cell can
cause one of the adjacent cells to be written with the same data. An effective
memory test attempts to test for this condition. Therefore, an ideal strategy
for testing memory would be the following:
1. write a cell with a zero
2. write all of the adjacent cells with a one, one or more times
3. check that the first cell still has a zero
It should be obvious that this strategy requires an exact knowledge of how the
memory cells are laid out on the chip. In addition there is a never ending
number of possible chip layouts for different chip types and manufacturers
making this strategy impractical. However, there are testing algorithms that
can approximate this ideal strategy.
## PCMemTest Test Algorithms
PCMemTest uses two algorithms that provide a reasonable approximation of the
ideal test strategy above. The first of these strategies is called moving
inversions. The moving inversion tests work as follows:
1. Fill memory with a pattern
2. Starting at the lowest address
1. check that the pattern has not changed
2. write the pattern's complement
3. increment the address
4. repeat 2.1 to 2.3
3. Starting at the highest address
1. check that the pattern has not changed
2. write the pattern's complement
3. decrement the address
4. repeat 3.1 - 3.3
This algorithm is a good approximation of an ideal memory test but there are
some limitations. Most high density chips today store data 4 to 16 bits wide.
With chips that are more than one bit wide it is impossible to selectively
read or write just one bit. This means that we cannot guarantee that all
adjacent cells have been tested for interaction. In this case the best we can
do is to use some patterns to ensure that all adjacent cells have at least
been written with all possible one and zero combinations.
It can also be seen that caching, buffering, and out of order execution will
interfere with the moving inversions algorithm and make it less effective.
It is possible to turn off caching but the memory buffering in new high
performance chips cannot be disabled. To address this limitation a new
algorithm called Modulo-20 was created. This algorithm is not affected by
caching or buffering. The algorithm works as follows:
1. For starting offsets of 0 - 19 do
1. write every 20th location with a pattern
2. write all other locations with the pattern's complement
3. repeat 1.2 one or more times
4. check every 20th location for the pattern
This algorithm accomplishes nearly the same level of adjacency testing as
moving inversions but is not affected by caching or buffering. Since separate
write passes (1.1, 1.2) and the read pass (1.4) are done for all of memory we
can be assured that all of the buffers and cache have been flushed between
passes. The selection of 20 as the stride size was somewhat arbitrary. Larger
strides may be more effective but would take longer to execute. The choice of
20 seemed to be a reasonable compromise between speed and thoroughness.
## Individual Test Descriptions
PCMemTest executes a series of numbered tests to check for errors. These tests
consist of a combination of test algorithm, data pattern and caching. The
execution order for these tests were arranged so that errors will be detected
as rapidly as possible. A description of each test follows.
To allow testing of more than 4GB of memory on 32-bit CPUs, the physical
address range is split into 1GB windows which are be mapped one at a time
into a virtual memory window. Each 1GB window may contain one or more
contiguous memory regions. For most tests, the test is performed on each
memory region in turn. Caching is enabled for all but the first test.
### Test 0 : Address test, walking ones, no cache
In each memory region in turn, tests all address bits by using a walking
ones address pattern. Errors from this test are not used to calculate BadRAM
patterns.
### Test 1 : Address test, own address in window
In each memory region in turn, each address is written with its own address
and then each address is checked for consistency. This test is performed
sequentially with each available CPU, regardless of the CPU sequencing mode
selected by the user.
### Test 2 : Address test, own address + window
Across all memory regions, each address is written with its own address plus
the window number and then each address is checked for consistency. This
catches any errors in the high order address bits that would be missed when
testing each window in turn. This test is performed sequentially with each
available CPU, regardless of the CPU sequencing mode selected by the user.
### Test 3 : Moving inversions, ones & zeros
In each memory region in turn, and for each pattern in turn, uses the moving
inversions algorithm with patterns of all ones and all zeros.
### Test 4 : Moving inversions, 8 bit pattern
In each memory region in turn, and for each pattern in turn, uses the moving
inversions algorithm with patterns of 8-bit wide walking ones and walking zeros.
### Test 5 : Moving inversions, random pattern
In each memory region in turn, and for each pattern in turn, uses the moving
inversions algorithm with patterns of a random number and its complement. The
random number is different on each test pass so multiple passes increase
effectiveness.
### Test 6 : Moving inversions, 32/64 bit pattern
In each memory region in turn, and for each pattern in turn, uses the moving
inversions algorithm with patterns of 32-bit wide (on 32-bit builds) or 64-bit
wide (on 64-bit builds) walking ones and walking zeros. Unlike previous tests,
the pattern is rotated 1 bit on each successive address.
### Test 7 : Block move, 64 moves
This test stresses memory by using block move (movs) instructions and is based
on Robert Redelmeier's burnBX test.
In each memory region in turn, memory is initialized with shifting patterns
that are inverted every 8 bytes. Then blocks of memory are moved around using
the movs instruction. After the moves are completed the data patterns are
checked. Because the data is checked only after the memory moves are completed
it is not possible to know where the error occurred. The addresses reported
are only for where the bad pattern was found. In consequence, errors from this
test are not used to calculate BadRAM patterns.
### Test 8 : Random number sequence
In each memory region in turn, each address is written with a random number,
then each address is checked for consistency and written with the complement
of the original data, then each address is again checked for consistency.
### Test 9 : Modulo 20, random pattern
In each memory region in turn, and for each pattern in turn, uses the
Modulo-20 algorithm with patterns of a random number and its complement.
The random number is different on each test pass so multiple passes increase
effectiveness.
### Test 10 : Bit fade test, 2 patterns
Across all memory regions, and for each pattern in turn, initialises each
memory location with a pattern, sleeps for a period of time, then checks
each memory location for consistency. The test is performed with patterns
of all zeros and all ones.
## Known Limitations and Bugs
* When booted on a UEFI system, keyboard input will only be seem if the
CSM is enabled in the BIOS. Without this, the test will run, but you
will be unable to alter the configuration.
* Temperature reporting is currently only supported for Intel CPUs.
## Acknowledgments
PCMemTest was based on Memtest86+, developed by Samuel Demeulemeester, which
in turn was based on Memtest86, developed by Chris Brady with the resources
and assistance listed below:
* The initial versions of the source files bootsect.S, setup.S, head.S and
build.c are from the Linux 1.2.1 kernel and have been heavily modified.
* Doug Sisk provided code to support a console connected via a serial port.
(not used by PCMemTest)
* Code to create BadRAM patterns was provided by Rick van Rein.
* The block move test is based on Robert Redelmeier's burnBX test.
* Screen buffer code was provided by Jani Averbach.
(not used by PCMemTest)
* Eric Biederman provided all of the feature content for version 3.0
plus many bugfixes and significant code cleanup.
* Major enhancements to hardware detection and reporting in version 3.2, 3.3
and 3.4 provided by Samuel Demeulemeester (from Memtest86+ v1.11, v1.60
and v1.70).

218
app/badram.c Normal file
View File

@@ -0,0 +1,218 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ patn.c:
//
// MemTest86+ V1.60 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.x86-secret.com - http://www.memtest.org
// ----------------------------------------------------
// Pattern extension for memtest86
//
// Generates patterns for the Linux kernel's BadRAM extension that avoids
// allocation of faulty pages.
//
// Released under version 2 of the Gnu Public License.
//
// By Rick van Rein, vanrein@zonnet.nl
//
// What it does:
// - Keep track of a number of BadRAM patterns in an array;
// - Combine new faulty addresses with it whenever possible;
// - Keep masks as selective as possible by minimising resulting faults;
// - Print a new pattern only when the pattern array is changed.
#include <stdbool.h>
#include <stdint.h>
#include "display.h"
#include "badram.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define MAX_PATTERNS 10
// DEFAULT_MASK covers a uintptr_t, since that is the testing granularity.
#ifdef __x86_64__
#define DEFAULT_MASK (UINTPTR_MAX << 3)
#else
#define DEFAULT_MASK (UINTPTR_MAX << 2)
#endif
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef struct {
uintptr_t addr;
uintptr_t mask;
} pattern_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static pattern_t pattern[MAX_PATTERNS];
static int num_patterns = 0;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
#define COMBINE_MASK(a,b,c,d) ((a & b & c & d) | (~a & b & ~c & d))
/*
* Combine two addr/mask pairs to one addr/mask pair.
*/
static void combine(uintptr_t addr1, uintptr_t mask1, uintptr_t addr2, uintptr_t mask2, uintptr_t *addr, uintptr_t *mask)
{
*mask = COMBINE_MASK(addr1, mask1, addr2, mask2);
*addr = addr1 | addr2;
*addr &= *mask; // Normalise, no fundamental need for this
}
/*
* Count the number of addresses covered with a mask.
*/
static uintptr_t addresses(uintptr_t mask)
{
uintptr_t ctr = 1;
int i = 8*sizeof(uintptr_t);
while (i-- > 0) {
if (! (mask & 1)) {
ctr += ctr;
}
mask >>= 1;
}
return ctr;
}
/*
* Count how many more addresses would be covered by addr1/mask1 when combined
* with addr2/mask2.
*/
static uintptr_t combi_cost(uintptr_t addr1, uintptr_t mask1, uintptr_t addr2, uintptr_t mask2)
{
uintptr_t cost1 = addresses(mask1);
uintptr_t tmp, mask;
combine(addr1, mask1, addr2, mask2, &tmp, &mask);
return addresses(mask) - cost1;
}
/*
* Find the cheapest array index to extend with the given addr/mask pair.
* Return -1 if nothing below the given minimum cost can be found.
*/
static int cheap_index(uintptr_t addr1, uintptr_t mask1, uintptr_t min_cost)
{
int i = num_patterns;
int idx = -1;
while (i-- > 0) {
uintptr_t tmp_cost = combi_cost(pattern[i].addr, pattern[i].mask, addr1, mask1);
if (tmp_cost < min_cost) {
min_cost = tmp_cost;
idx = i;
}
}
return idx;
}
/*
* Try to find a relocation index for idx if it costs nothing.
* Return -1 if no such index exists.
*/
static int relocate_index(int idx)
{
uintptr_t addr = pattern[idx].addr;
uintptr_t mask = pattern[idx].mask;
pattern[idx].addr = ~pattern[idx].addr; // Never select idx
int new = cheap_index(addr, mask, 1 + addresses(mask));
pattern[idx].addr = addr;
return new;
}
/*
* Relocate the given index idx only if free of charge.
* This is useful to combine to `neighbouring' sections to integrate.
* Inspired on the Buddy memalloc principle in the Linux kernel.
*/
static void relocate_if_free(int idx)
{
int newidx = relocate_index(idx);
if (newidx >= 0) {
uintptr_t caddr, cmask;
combine(pattern[newidx].addr, pattern[newidx].mask,
pattern[ idx].addr, pattern[ idx].mask,
&caddr, &cmask);
pattern[newidx].addr = caddr;
pattern[newidx].mask = cmask;
if (idx < --num_patterns) {
pattern[idx].addr = pattern[num_patterns].addr;
pattern[idx].mask = pattern[num_patterns].mask;
}
relocate_if_free (newidx);
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void badram_init(void)
{
num_patterns = 0;
}
bool badram_insert(uintptr_t addr)
{
if (cheap_index(addr, DEFAULT_MASK, 1) != -1) {
return false;
}
if (num_patterns < MAX_PATTERNS) {
pattern[num_patterns].addr = addr;
pattern[num_patterns].mask = DEFAULT_MASK;
num_patterns++;
relocate_if_free(num_patterns - 1);
} else {
int idx = cheap_index(addr, DEFAULT_MASK, UINTPTR_MAX);
uintptr_t caddr, cmask;
combine(pattern[idx].addr, pattern[idx].mask, addr, DEFAULT_MASK, &caddr, &cmask);
pattern[idx].addr = caddr;
pattern[idx].mask = cmask;
relocate_if_free(idx);
}
return true;
}
void badram_display(void)
{
if (num_patterns == 0) {
return;
}
check_input();
scroll();
display_scrolled_message(0, "badram=");
int col = 7;
for (int i = 0; i < num_patterns; i++) {
if (i > 0) {
display_scrolled_message(col, ",");
col++;
}
int text_width = 2 * (TESTWORD_DIGITS + 3);
if (col > (SCREEN_WIDTH - text_width)) {
scroll();
col = 7;
}
display_scrolled_message(col, "0x0*x,0x0*x",
TESTWORD_DIGITS, pattern[i].addr,
TESTWORD_DIGITS, pattern[i].mask);
col += text_width;
}
}

30
app/badram.h Normal file
View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef BADRAM_H
#define BADRAM_H
/*
* Support for generating patterns for the Linux kernel BadRAM extension.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdbool.h>
#include <stdint.h>
/*
* Initialises the pattern array.
*/
void badram_init(void);
/*
* Inserts a single faulty address into the pattern array. Returns
* true iff the array was changed.
*/
bool badram_insert(uintptr_t addr);
/*
* Displays the pattern array in the scrollable display region in the
* format used by the Linux kernel.
*/
void badram_display(void);
#endif // BADRAM_H

614
app/config.c Normal file
View File

@@ -0,0 +1,614 @@
// 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 <stdbool.h>
#include <stdint.h>
#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);
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);
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);
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, "<F1> Clear selection");
prints(POP_R+4, POP_C+4, "<F2> Remove one test");
prints(POP_R+5, POP_C+4, "<F3> Add one test");
prints(POP_R+6, POP_C+4, "<F4> Add test range");
prints(POP_R+7, POP_C+4, "<F5> Add all tests");
prints(POP_R+8, POP_C+4, "<ESC> 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, "<F1> Set lower limit");
prints(POP_R+4, POP_C+4, "<F2> Set upper limit");
prints(POP_R+5, POP_C+4, "<F3> Test all memory");
prints(POP_R+6, POP_C+4, "<ESC> 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, "<F1> Parallel (All)");
prints(POP_R+4, POP_C+4, "<F2> Sequential (Seq)");
prints(POP_R+5, POP_C+4, "<F3> Round robin (RR)");
prints(POP_R+6, POP_C+4, "<ESC> 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, "<F1> Error counts only");
prints(POP_R+4, POP_C+4, "<F2> Error summary");
prints(POP_R+5, POP_C+4, "<F3> Individual errors");
prints(POP_R+6, POP_C+4, "<F4> BadRAM patterns");
prints(POP_R+7, POP_C+4, "<ESC> 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);
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);
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);
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, "<F1> Clear selection");
prints(POP_R+4, POP_C+4, "<F2> Remove one CPU");
prints(POP_R+5, POP_C+4, "<F3> Add one CPU");
prints(POP_R+6, POP_C+4, "<F4> Add CPU range");
prints(POP_R+7, POP_C+4, "<F5> Add all CPUs");
prints(POP_R+8, POP_C+4, "<ESC> 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, "<F1> Test selection");
prints(POP_R+4, POP_C+4, "<F2> Address range");
prints(POP_R+5, POP_C+4, "<F3> CPU sequencing mode");
prints(POP_R+6, POP_C+4, "<F4> Error reporting mode");
if (initial) {
if (num_pcpus < 2) set_foreground_colour(BOLD+BLACK);
prints(POP_R+7, POP_C+4, "<F5> 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, "<F6> Temperature %s", enable_temperature ? "disable" : "enable ");
if (no_temperature) set_foreground_colour(WHITE);
printf(POP_R+9, POP_C+4, "<F7> Boot trace %s", enable_trace ? "disable" : "enable ");
prints(POP_R+10, POP_C+4, "<ESC> Exit menu");
} else {
prints(POP_R+7, POP_C+4, "<F5> Skip current test");
prints(POP_R+8 , POP_C+4, "<ESC> 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 <F1> to configure, <Enter> to start testing");
bool got_key = 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':
config_menu(true);
got_key = true;
break;
case ' ':
toggle_scroll_lock();
break;
case '\n':
got_key = true;
break;
default:
break;
}
}
}

48
app/config.h Normal file
View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef CONFIG_H
#define CONFIG_H
/*
* Provides the configuration settings and pop-up menu.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdbool.h>
#include <stdint.h>
#include "smp.h"
typedef enum {
PAR,
SEQ,
ONE
} cpu_mode_t;
typedef enum {
ERROR_MODE_NONE,
ERROR_MODE_SUMMARY,
ERROR_MODE_ADDRESS,
ERROR_MODE_BADRAM
} error_mode_t;
extern uintptr_t pm_limit_lower;
extern uintptr_t pm_limit_upper;
extern uintptr_t num_pages_to_test;
extern cpu_mode_t cpu_mode;
extern error_mode_t error_mode;
extern bool enable_pcpu[MAX_PCPUS];
extern bool enable_temperature;
extern bool enable_trace;
void config_init(void);
void config_menu(bool initial);
void initial_config(void);
#endif // CONFIG_H

288
app/display.c Normal file
View File

@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
#include <stdbool.h>
#include <stdint.h>
#include "cpuid.h"
#include "cpuinfo.h"
#include "hwctrl.h"
#include "io.h"
#include "keyboard.h"
#include "pmem.h"
#include "temperature.h"
#include "tsc.h"
#include "barrier.h"
#include "spinlock.h"
#include "config.h"
#include "error.h"
#include "tests.h"
#include "display.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define NUM_SPIN_STATES 4
static const char spin_state[NUM_SPIN_STATES] = { '|', '/', '-', '\\' };
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static bool scroll_lock = false;
static bool scroll_wait = false;
static int spin_idx[MAX_VCPUS]; // current spinner position
static int pass_ticks = 0; // current value (ticks_per_pass is final value)
static int test_ticks = 0; // current value (ticks_per_test is final value)
static int pass_bar_length = 0; // currently displayed length
static int test_bar_length = 0; // currently displayed length
static uint64_t run_start_time = 0; // TSC time stamp
//------------------------------------------------------------------------------
// Variables
//------------------------------------------------------------------------------
int scroll_message_row;
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void display_init(void)
{
cursor_off();
clear_screen();
set_foreground_colour(RED);
set_background_colour(WHITE);
clear_screen_region(0, 0, 0, 27);
#if TESTWORD_WIDTH > 32
prints( 0, 0, " PCMemTest-64 v1.0 ");
#else
prints( 0, 0, " PCMemTest-32 v1.0 ");
#endif
set_foreground_colour(WHITE);
set_background_colour(BLUE);
prints( 0,28, "| ");
prints( 1, 0, "CPU : N/A | Pass % ");
prints( 2, 0, "L1 Cache: N/A | Test % ");
prints( 3, 0, "L2 Cache: N/A | Test # ");
prints( 4, 0, "L3 Cache: N/A | Testing: ");
prints( 5, 0, "Memory : N/A | Pattern: ");
prints( 6, 0, "-------------------------------------------------------------------------------");
prints( 7, 0, "vCPU#: | Time: Temperature: N/A ");
prints( 8, 0, "State: | ");
prints( 9, 0, "Cores: Active / Total (Run: All) | Pass: Errors: ");
prints(10, 0, "-------------------------------------------------------------------------------");
// Redraw lines using box drawing characters.
for (int i = 0; i < 80; i++) {
print_char( 6, i, 0xc4);
print_char(10, i, 0xc4);
}
for (int i = 0; i < 6; i++) {
print_char(i, 28, 0xb3);
}
for (int i = 7; i < 10; i++) {
print_char(i, 39, 0xb3);
}
print_char( 6, 28, 0xc1);
print_char( 6, 39, 0xc2);
print_char(10, 39, 0xc1);
set_foreground_colour(BLUE);
set_background_colour(WHITE);
clear_screen_region(ROW_FOOTER, 0, ROW_FOOTER, SCREEN_WIDTH - 1);
prints(ROW_FOOTER, 0, " <ESC> exit <F1> configuration <Space> scroll lock");
set_foreground_colour(WHITE);
set_background_colour(BLUE);
if (cpu_model) {
display_cpu_model(cpu_model);
}
if (clks_per_msec) {
display_cpu_clk((int)(clks_per_msec / 1000));
}
if (cpuid_info.flags.lm) {
display_cpu_addr_mode("(x64)");
} else if (cpuid_info.flags.pae) {
display_cpu_addr_mode("(PAE)");
}
if (l1_cache) {
display_l1_cache_size(l1_cache);
}
if (l2_cache) {
display_l2_cache_size(l2_cache);
}
if (l3_cache) {
display_l3_cache_size(l3_cache);
}
if (num_pm_pages) {
// Round to nearest MB.
display_memory_size(1024 * ((num_pm_pages + 128) / 256));
}
scroll_message_row = ROW_SCROLL_T;
}
void display_start_run(void)
{
clear_message_area();
clear_screen_region(7, 47, 9, 57); // run time
clear_screen_region(9, 47, 9, 57); // pass number
clear_screen_region(9, 66, 9, SCREEN_WIDTH - 1); // error count
display_pass_count(0);
display_error_count(0);
run_start_time = get_tsc();
}
void display_start_pass(void)
{
clear_screen_region(1, 39, 1, SCREEN_WIDTH - 1); // progress bar
display_pass_percentage(0);
pass_bar_length = 0;
pass_ticks = 0;
}
void display_start_test(void)
{
clear_screen_region(2, 39, 5, SCREEN_WIDTH - 1); // progress bar, test details
clear_screen_region(3, 36, 3, 37); // test number
display_test_percentage(0);
display_test_number(test_num);
display_test_description(test_list[test_num].description);
test_bar_length = 0;
test_ticks = 0;
}
void check_input(void)
{
switch (get_key()) {
case ESC:
clear_message_area();
display_notice("Rebooting...");
reboot();
break;
case '1':
config_menu(false);
break;
case ' ':
set_scroll_lock(!scroll_lock);
break;
case '\n':
scroll_wait = false;
break;
default:
break;
}
}
void set_scroll_lock(bool enabled)
{
scroll_lock = enabled;
set_foreground_colour(BLUE);
prints(ROW_FOOTER, 48, scroll_lock ? "unlock" : "lock ");
set_foreground_colour(WHITE);
}
void toggle_scroll_lock(void)
{
set_scroll_lock(!scroll_lock);
}
void scroll(void)
{
if (scroll_message_row < ROW_SCROLL_B) {
scroll_message_row++;
} else {
if (scroll_lock) {
display_footer_message("<Enter> Single step");
}
scroll_wait = true;
do {
check_input();
} while (scroll_wait && scroll_lock);
scroll_wait = false;
clear_footer_message();
scroll_screen_region(ROW_SCROLL_T, 0, ROW_SCROLL_B, SCREEN_WIDTH - 1);
}
}
void do_tick(int my_vcpu)
{
spin_idx[my_vcpu] = (spin_idx[my_vcpu] + 1) % NUM_SPIN_STATES;
display_spinner(my_vcpu, spin_state[spin_idx[my_vcpu]]);
barrier_wait(run_barrier);
if (master_vcpu == my_vcpu) {
check_input();
error_update();
}
barrier_wait(run_barrier);
// Only the master CPU does the update.
if (master_vcpu != my_vcpu) {
return;
}
test_ticks++;
pass_ticks++;
pass_type_t pass_type = (pass_num == 0) ? FAST_PASS : FULL_PASS;
int pct = 0;
if (ticks_per_test[pass_type][test_num] > 0) {
pct = 100 * test_ticks / ticks_per_test[pass_type][test_num];
if (pct > 100) {
pct = 100;
}
}
display_test_percentage(pct);
display_test_bar((BAR_LENGTH * pct) / 100);
pct = 0;
if (ticks_per_pass[pass_type] > 0) {
pct = 100 * pass_ticks / ticks_per_pass[pass_type];
if (pct > 100) {
pct = 100;
}
}
display_pass_percentage(pct);
display_pass_bar((BAR_LENGTH * pct) / 100);
if (cpuid_info.flags.rdtsc) {
int secs = (get_tsc() - run_start_time) / (1000 * clks_per_msec);
int mins = secs / 60; secs %= 60;
int hours = mins / 60; mins %= 60;
display_run_time(hours, mins, secs);
}
if (enable_temperature) {
display_temperature(get_cpu_temperature());
}
}
void do_trace(int my_cpu, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
spin_lock(error_mutex);
scroll();
printi(scroll_message_row, 0, my_cpu, 2, false, false);
vprintf(scroll_message_row, 4, fmt, args);
spin_unlock(error_mutex);
va_end(args);
}

186
app/display.h Normal file
View File

@@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef DISPLAY_H
#define DISPLAY_H
/*
* Provides (macro) functions that implement the UI display.
* All knowledge about the display layout is encapsulated here.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include "screen.h"
#include "print.h"
#include "string.h"
#include "test.h"
#define ROW_MESSAGE_T 11
#define ROW_MESSAGE_B (SCREEN_HEIGHT - 2)
#define ROW_SCROLL_T (ROW_MESSAGE_T + 2)
#define ROW_SCROLL_B (SCREEN_HEIGHT - 2)
#define ROW_FOOTER (SCREEN_HEIGHT - 1)
#define BAR_LENGTH 40
#define ERROR_LIMIT UINT64_C(999999999999)
#define display_cpu_model(str) \
prints(0, 30, str)
#define display_cpu_clk(freq) \
printf(1, 10, "%iMHz", freq)
#define display_cpu_addr_mode(str) \
prints(1, 20, str)
#define display_l1_cache_size(size) \
printf(2, 10, "%kB", (uintptr_t)(size))
#define display_l2_cache_size(size) \
printf(3, 10, "%kB", (uintptr_t)(size))
#define display_l3_cache_size(size) \
printf(4, 10, "%kB", (uintptr_t)(size))
#define display_memory_size(size) \
printf(5, 10, "%kB", (uintptr_t)(size))
#define display_cpu_num(me) \
printc(7, 7 + (me), '0' + ((me) % 10))
#define display_spinner(me, spin_state) \
printc(8, 7 + (me), spin_state)
#define display_active_cpus(count) \
printi(9, 7, count, 2, false, false)
#define display_total_cpus(count) \
printi(9, 19, count, 2, false, false)
#define display_cpu_mode(str) \
prints(9, 34, str)
#define display_pass_percentage(pct) \
printi(1, 34, pct, 3, false, false)
#define display_pass_bar(length) \
while (length > pass_bar_length) { \
printc(1, 39 + pass_bar_length, '#'); \
pass_bar_length++; \
}
#define display_test_percentage(pct) \
printi(2, 34, pct, 3, false, false)
#define display_test_bar(length) \
while (length > test_bar_length) { \
printc(2, 39 + test_bar_length, '#'); \
test_bar_length++; \
}
#define display_test_number(number) \
printi(3, 36, number, 2, false, true)
#define display_test_description(str) \
prints(3, 39, str)
#define display_test_addresses(pb, pe, total) \
{ \
clear_screen_region(4, 39, 4, SCREEN_WIDTH - 1); \
printf(4, 39, "%kB - %kB %kB of %kB", pb, pe, (pe) - (pb), total); \
}
#define display_test_stage_description(...) \
{ \
clear_screen_region(4, 39, 4, SCREEN_WIDTH - 1); \
printf(4, 39, __VA_ARGS__); \
}
#define display_test_pattern_name(str) \
{ \
clear_screen_region(5, 39, 5, SCREEN_WIDTH - 1); \
prints(5, 39, str); \
}
#define display_test_pattern_value(pattern) \
{ \
clear_screen_region(5, 39, 5, SCREEN_WIDTH - 1); \
printf(5, 39, "0x%0*x", TESTWORD_DIGITS, pattern); \
}
#define display_test_pattern_values(pattern, offset) \
{ \
clear_screen_region(5, 39, 5, SCREEN_WIDTH - 1); \
printf(5, 39, "0x%0*x - %i", TESTWORD_DIGITS, pattern, offset); \
}
#define display_run_time(hours, mins, secs) \
printf(7, 47, "%i:%02i:%02i", hours, mins, secs)
#define display_temperature(temp) \
printf(7, 71, "%2i%cC ", temp, 0xf8)
#define display_pass_count(count) \
printi(9, 47, count, 0, false, true)
#define display_error_count(count) \
printi(9, 66, count, 0, false, true)
#define clear_message_area() \
{ \
clear_screen_region(ROW_MESSAGE_T, 0, ROW_MESSAGE_B, SCREEN_WIDTH - 1); \
scroll_message_row = ROW_SCROLL_T - 1; \
}
#define display_pinned_message(row, col, ...) \
printf(ROW_MESSAGE_T + row, col, __VA_ARGS__)
#define display_scrolled_message(col, ...) \
printf(scroll_message_row, col, __VA_ARGS__)
#define display_notice(str) \
prints(ROW_MESSAGE_T + 6, (SCREEN_WIDTH - strlen(str)) / 2, str)
#define clear_footer_message() \
{ \
set_background_colour(WHITE); \
clear_screen_region(ROW_FOOTER, 56, ROW_FOOTER, SCREEN_WIDTH - 1); \
set_background_colour(BLUE); \
}
#define display_footer_message(str) \
{ \
set_foreground_colour(BLUE); \
prints(ROW_FOOTER, 56, str); \
set_foreground_colour(WHITE); \
}
#define trace(my_cpu, ...) \
if (enable_trace) do_trace(my_cpu, __VA_ARGS__)
extern int scroll_message_row;
void display_init(void);
void display_start_run(void);
void display_start_pass(void);
void display_start_test(void);
void check_input(void);
void set_scroll_lock(bool enabled);
void toggle_scroll_lock(void);
void scroll(void);
void do_tick(int my_vcpu);
void do_trace(int my_vcpu, const char *fmt, ...);
#endif // DISPLAY_H

373
app/error.c Normal file
View File

@@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ error.c
//
// error.c - MemTest-86 Version 4.1
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#include "smp.h"
#include "vmem.h"
#include "badram.h"
#include "config.h"
#include "display.h"
#include "test.h"
#include "tests.h"
#include "error.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#ifndef USB_WORKAROUND
#define USB_WORKAROUND 1
#endif
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef enum { ADDR_ERROR, DATA_ERROR, PARITY_ERROR, NEW_MODE } error_type_t;
typedef struct {
uintptr_t page;
uintptr_t offset;
} page_offs_t;
typedef struct {
page_offs_t min_addr;
page_offs_t max_addr;
testword_t bad_bits;
int min_bits;
int max_bits;
uint64_t total_bits;
uintptr_t run_length;
uintptr_t max_run;
uintptr_t last_addr;
testword_t last_xor;
} error_info_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static error_mode_t last_error_mode = ERROR_MODE_NONE;
static error_info_t error_info;
//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------
uint64_t error_count = 0;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static bool update_error_info(uintptr_t addr, testword_t xor)
{
bool update_stats = false;
// Update address range.
testword_t page = page_of((void *)addr);
testword_t offset = addr & 0xFFF;
if (error_info.min_addr.page > page) {
error_info.min_addr.page = page;
error_info.min_addr.offset = offset;
update_stats = true;
} else if (error_info.min_addr.page == page && error_info.min_addr.offset > offset) {
error_info.min_addr.offset = offset;
update_stats = true;
}
if (error_info.max_addr.page < page) {
error_info.max_addr.page = page;
error_info.max_addr.offset = offset;
update_stats = true;
} else if (error_info.max_addr.page == page && error_info.max_addr.offset < offset) {
error_info.max_addr.offset = offset;
update_stats = true;
}
// Update bits in error.
int bits = 0;
for (int i = 0; i < TESTWORD_WIDTH; i++) {
if ((xor >> i) & 1) {
bits++;
}
}
if (bits > 0 && error_count < ERROR_LIMIT) {
error_info.total_bits += bits;
}
if (bits > error_info.max_bits) {
error_info.max_bits = bits;
update_stats = true;
}
if (bits < error_info.min_bits) {
error_info.min_bits = bits;
update_stats = true;
}
if (error_info.bad_bits ^ xor) {
update_stats = true;
}
error_info.bad_bits |= xor;
// Update max contiguous range.
if (error_info.max_run > 0) {
if (addr == error_info.last_addr + sizeof(testword_t)
|| addr == error_info.last_addr - sizeof(testword_t)) {
error_info.run_length++;
} else {
error_info.run_length = 1;
}
} else {
error_info.run_length = 1;
}
if (error_info.run_length > error_info.max_run) {
error_info.max_run = error_info.run_length;
update_stats = true;
}
return update_stats;
}
static void common_err(error_type_t type, uintptr_t addr, testword_t good, testword_t bad, bool use_for_badram)
{
spin_lock(error_mutex);
bool new_header = (error_count == 0) || (error_mode != last_error_mode);
if (new_header) {
clear_message_area();
}
last_error_mode = error_mode;
testword_t xor = good ^ bad;
bool new_stats = false;
switch (type) {
case ADDR_ERROR:
new_stats = update_error_info(addr, 0);
break;
case DATA_ERROR:
new_stats = update_error_info(addr, xor);
break;
case NEW_MODE:
new_stats = (error_count > 0);
default:
break;
}
bool new_address = (type != NEW_MODE);
bool new_badram = false;
if (error_mode == ERROR_MODE_BADRAM && use_for_badram) {
new_badram = badram_insert(addr);
}
if (new_address) {
if (error_count < ERROR_LIMIT) {
error_count++;
}
if (test_list[test_num].errors < INT_MAX) {
test_list[test_num].errors++;
}
}
switch (error_mode) {
case ERROR_MODE_SUMMARY:
if (type == PARITY_ERROR) {
break;
}
if (new_header) {
display_pinned_message(0, 1, " Lowest Error Address:");
display_pinned_message(1, 1, " Highest Error Address:");
display_pinned_message(2, 1, " Bits in Error Mask:");
display_pinned_message(3, 1, " Bits in Error - Total:");
display_pinned_message(4, 1, " Max Contiguous Errors:");
display_pinned_message(0, 64, "Test Errors");
for (int i = 0; i < NUM_TEST_PATTERNS; i++) {
display_pinned_message(1 + i, 65, "%2i:", i);
}
}
if (new_stats) {
int bits = 0;
for (int i = 0; i < TESTWORD_WIDTH; i++) {
if (error_info.bad_bits >> i & 1) {
bits++;
}
}
display_pinned_message(0, 25, "%09x%03x (%kB)",
error_info.min_addr.page,
error_info.min_addr.offset,
error_info.min_addr.page << 2);
display_pinned_message(1, 25, "%09x%03x (%kB)",
error_info.max_addr.page,
error_info.max_addr.offset,
error_info.max_addr.page << 2);
display_pinned_message(2, 25, "%0*x", TESTWORD_DIGITS,
error_info.bad_bits);
display_pinned_message(3, 25, " %2i Min: %2i Max: %2i Avg: %2i",
bits,
error_info.min_bits,
error_info.max_bits,
(int)(error_info.total_bits / error_count));
display_pinned_message(4, 25, "%u",
error_info.max_run);
for (int i = 0; i < NUM_TEST_PATTERNS; i++) {
display_pinned_message(1 + i, 69, "%c%i",
test_list[i].errors == INT_MAX ? '>' : ' ',
test_list[i].errors);
}
display_error_count(error_count);
}
break;
case ERROR_MODE_ADDRESS:
// Skip duplicates.
if (!new_header && addr == error_info.last_addr && xor == error_info.last_xor) {
break;
}
if (new_header) {
#if TESTWORD_WIDTH > 32
// columns: 0---------1---------2---------3---------4---------5---------6---------7---------
display_pinned_message(0, 0, "pCPU Pass Test Failing Address Expected Found ");
display_pinned_message(1, 0, "---- ---- ---- --------------------- ---------------- ----------------");
// fields: NN NNNN NN PPPPPPPPPOOO (N.NN?B) XXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX
#else
// columns: 0---------1---------2---------3---------4---------5---------6---------7---------
display_pinned_message(0, 0, "pCPU Pass Test Failing Address Expected Found Err Bits");
display_pinned_message(1, 0, "---- ---- ---- --------------------- -------- -------- --------");
// fields: NN NNNN NN PPPPPPPPPOOO (N.NN?B) XXXXXXXX XXXXXXXX XXXXXXXX
#endif
}
if (new_address) {
check_input();
scroll();
uintptr_t page = page_of((void *)addr);
uintptr_t offset = addr & 0xFFF;
set_foreground_colour(YELLOW);
display_scrolled_message(0, " %2i %4i %2i %09x%03x (%kB)",
smp_my_pcpu_num(), pass_num, test_num, page, offset, page << 2);
if (type == PARITY_ERROR) {
display_scrolled_message(41, "%s", "Parity error detected near this address");
} else {
#if TESTWORD_WIDTH > 32
display_scrolled_message(41, "%016x %016x", good, bad);
#else
display_scrolled_message(41, "%08x %08x %08x %i", good, bad, xor, error_count);
#endif
}
set_foreground_colour(WHITE);
display_error_count(error_count);
}
break;
case ERROR_MODE_BADRAM:
if (new_header) {
display_pinned_message(0, 0, "BadRAM Patterns");
display_pinned_message(0, 1, "---------------");
badram_init();
}
if (new_badram) {
badram_display();
}
break;
default:
break;
}
if (type != PARITY_ERROR) {
error_info.last_addr = addr;
error_info.last_xor = xor;
}
spin_unlock(error_mutex);
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void error_init(void)
{
error_info.min_addr.page = UINTPTR_MAX;
error_info.min_addr.offset = 0xfff;
error_info.max_addr.page = 0;
error_info.max_addr.offset = 0;
error_info.bad_bits = 0;
error_info.min_bits = 255;
error_info.max_bits = 0;
error_info.total_bits = 0;
error_info.run_length = 0;
error_info.max_run = 0;
error_info.last_addr = 0;
error_info.last_xor = 0;
error_count = 0;
}
void addr_error(volatile testword_t *addr1, volatile testword_t *addr2, testword_t good, testword_t bad)
{
common_err(ADDR_ERROR, (uintptr_t)addr1, good, bad, false); (void)addr2;
}
void data_error(volatile testword_t *addr, testword_t good, testword_t bad, bool use_for_badram)
{
#if USB_WORKAROUND
/* Skip any errrors that appear to be due to the BIOS using location
* 0x4e0 for USB keyboard support. This often happens with Intel
* 810, 815 and 820 chipsets. It is possible that we will skip
* a real error but the odds are very low.
*/
if ((uintptr_t)addr == 0x4e0 || (uintptr_t)addr == 0x410) {
return;
}
#endif
common_err(DATA_ERROR, (uintptr_t)addr, good, bad, use_for_badram);
}
#if REPORT_PARITY_ERRORS
void parity_error(void)
{
// We don't know the real address that caused the parity error,
// so use the last recorded test address.
common_err(PARITY_ERROR, test_addr[my_vcpu_num()], 0, 0, false);
}
#endif
void error_update(void)
{
if (error_count > 0) {
if (error_mode != last_error_mode) {
common_err(NEW_MODE, 0, 0, 0, false);
}
if (error_mode == ERROR_MODE_SUMMARY && test_list[test_num].errors > 0) {
display_pinned_message(1 + test_num, 69, "%c%i",
test_list[test_num].errors == INT_MAX ? '>' : ' ',
test_list[test_num].errors);
}
display_error_count(error_count);
}
}

46
app/error.h Normal file
View File

@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef ERROR_H
#define ERROR_H
/*
* Provides functions that can be called by the memory tests to report errors.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
#include "test.h"
/*
* The number of errors recorded during the current run.
*/
extern uint64_t error_count;
/*
* Initialises the error records.
*/
void error_init(void);
/*
* Adds an address error to the error reports.
*/
void addr_error(volatile testword_t *addr1, volatile testword_t *addr2, testword_t good, testword_t bad);
/*
* Adds a data error to the error reports.
*/
void data_error(volatile testword_t *addr, testword_t good, testword_t bad, bool use_for_badram);
#if REPORT_PARITY_ERRORS
/*
* Adds a parity error to the error reports.
*/
void parity_error(void);
#endif
/*
* Refreshes the error display after the error mode is changed.
*/
void error_update(void);
#endif // ERROR_H

166
app/interrupt.c Normal file
View File

@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from extract of memtest86+ lib.c:
//
// lib.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdint.h>
#include "hwctrl.h"
#include "keyboard.h"
#include "screen.h"
#include "smp.h"
#include "error.h"
#include "display.h"
#include "interrupt.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#ifdef __x86_64__
#define REG_PREFIX 'r'
#define REG_DIGITS 16
#define ADR_DIGITS 12
#else
#define REG_PREFIX 'e'
#define REG_DIGITS 8
#define ADR_DIGITS 8
#endif
static const char *codes[] = {
"Divide by 0",
"Debug",
"NMI",
"Breakpoint",
"Overflow",
"Bounds",
"Invalid Op",
"No FPU",
"Double fault",
"Seg overrun",
"Invalid TSS",
"Seg fault",
"Stack fault",
"Gen prot.",
"Page fault",
"Reserved",
"FPU error",
"Alignment",
"Machine chk",
"SIMD FPE"
};
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
#ifdef __x86_64__
typedef uint64_t reg_t;
#else
typedef uint32_t reg_t;
#endif
struct trap_regs {
reg_t ss;
reg_t es;
reg_t ds;
reg_t sp;
reg_t bp;
reg_t si;
reg_t di;
reg_t dx;
reg_t cx;
reg_t bx;
reg_t ax;
reg_t vect;
reg_t code;
reg_t ip;
reg_t cs;
reg_t flags;
};
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void interrupt(struct trap_regs *trap_regs)
{
// Get the page fault address.
uintptr_t address = 0;
if (trap_regs->vect == 14) {
#ifdef __x86_64__
__asm__(
"movq %%cr2, %0"
:"=r" (address)
);
#else
__asm__(
"movl %%cr2, %0"
:"=r" (address)
);
#endif
}
#if REPORT_PARITY_ERRORS
if (trap_regs->vect == 2) {
parity_error();
return;
}
#endif
spin_lock(error_mutex);
clear_message_area();
display_pinned_message(0, 0, "Unexpected interrupt on CPU %i", smp_my_pcpu_num());
if (trap_regs->vect <= 19) {
display_pinned_message(2, 0, "Type: %s", codes[trap_regs->vect]);
} else {
display_pinned_message(2, 0, "Type: %i", trap_regs->vect);
}
display_pinned_message(3, 0, " PC: %0*x", REG_DIGITS, (uintptr_t)trap_regs->ip);
display_pinned_message(4, 0, " CS: %0*x", REG_DIGITS, (uintptr_t)trap_regs->cs);
display_pinned_message(5, 0, "Flag: %0*x", REG_DIGITS, (uintptr_t)trap_regs->flags);
display_pinned_message(6, 0, "Code: %0*x", REG_DIGITS, (uintptr_t)trap_regs->code);
display_pinned_message(7, 0, " DS: %0*x", REG_DIGITS, (uintptr_t)trap_regs->ds);
display_pinned_message(8, 0, " ES: %0*x", REG_DIGITS, (uintptr_t)trap_regs->es);
display_pinned_message(9, 0, " SS: %0*x", REG_DIGITS, (uintptr_t)trap_regs->ss);
if (trap_regs->vect == 14) {
display_pinned_message(9, 0, " Addr: %0*x", REG_DIGITS, address);
}
display_pinned_message(2, 25, "%cax: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->ax);
display_pinned_message(3, 25, "%cbx: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->bx);
display_pinned_message(4, 25, "%ccx: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->cx);
display_pinned_message(5, 25, "%cdx: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->dx);
display_pinned_message(6, 25, "%cdi: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->di);
display_pinned_message(7, 25, "%csi: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->si);
display_pinned_message(8, 25, "%cbp: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->bp);
display_pinned_message(9, 25, "%csp: %0*x", REG_PREFIX, REG_DIGITS, (uintptr_t)trap_regs->sp);
display_pinned_message(0, 50, "Stack:");
for (int i = 0; i < 12; i++) {
uintptr_t addr = trap_regs->sp + sizeof(reg_t)*(11 - i);
reg_t data = *(reg_t *)addr;
display_pinned_message(1 + i, 50, "%0*x %0*x", ADR_DIGITS, addr, REG_DIGITS, (uintptr_t)data);
}
display_pinned_message(11, 0, "CS:PC:");
uint8_t *pp = (uint8_t *)((uintptr_t)trap_regs->ip);
for (int i = 0; i < 12; i++) {
display_pinned_message(11, 7 + 3*i, "%02x", (uintptr_t)pp[i]);
}
clear_screen_region(ROW_FOOTER, 0, ROW_FOOTER, SCREEN_WIDTH - 1);
prints(ROW_FOOTER, 0, "Press any key to reboot...");
while (get_key() == 0) { }
reboot();
}

17
app/interrupt.h Normal file
View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef INTERRUPT_H
#define INTERRUPT_H
/*
* Provides the interrupt handler.
*
* Copyright (C) 2020 Martin Whitaker.
*/
struct trap_regs;
/*
* Handles an interrupt.
*/
void interrupt(struct trap_regs *trap_regs);
#endif // INTERRUPT_H

518
app/main.c Normal file
View File

@@ -0,0 +1,518 @@
// 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)
{
// 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.
vm_map_size = 0;
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.
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 **");
}
}
}
}

104
app/test.h Normal file
View File

@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TEST_H
#define TEST_H
/*
* Provides types and variables used when performing the memory tests.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
#include "pmem.h"
#include "barrier.h"
#include "spinlock.h"
/*
* The maximum number of virtual CPUs supported. Note that the display can
* only show the state of a maximum of 32 vCPUs.
*/
#define MAX_VCPUS 32
/*
* The number of activated virtual CPUs.
*/
extern int num_vcpus;
/*
* The current master virtual CPU.
*/
extern volatile int master_vcpu;
/*
* A barrier used when running tests.
*/
extern barrier_t *run_barrier;
/*
* A mutex used when reporting errors or printing trace information.
*/
extern spinlock_t *error_mutex;
/*
* The word width (in bits) used for memory testing.
*/
#ifdef __x86_64__
#define TESTWORD_WIDTH 64
#else
#define TESTWORD_WIDTH 32
#endif
/*
* The number of hex digits needed to display a memory test word.
*/
#define TESTWORD_DIGITS (TESTWORD_WIDTH / 4)
/*
* The word type used for memory testing.
*/
typedef uintptr_t testword_t;
/*
* A virtual memory segment descriptor.
*/
typedef struct {
uintptr_t pm_base_addr;
testword_t *start;
testword_t *end;
} vm_map_t;
/*
* The list of memory segments currently mapped into virtual memory.
*/
extern volatile vm_map_t vm_map[MAX_MEM_SEGMENTS];
/*
* The number of memory segments currently mapped into virtual memory.
*/
extern volatile int vm_map_size;
/*
* The number of completed test passes.
*/
extern volatile int pass_num;
/*
* The current test number.
*/
extern volatile int test_num;
/*
* A flag indicating that testing should be restarted due to a configuration
* change.
*/
extern volatile bool restart;
/*
* A flag indicating that the current test should be aborted.
*/
extern volatile bool bail;
/*
* The base address of the block of memory currently being tested.
*/
extern volatile uintptr_t test_addr[MAX_VCPUS];
#endif // TEST_H

77
boot/boot.h Normal file
View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef BOOT_H
#define BOOT_H
/*
* Definitions used in the boot code. Also defines exported symbols needed
* in the main code.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#define MAX_APS 64 /* Maximum number of active APs. This
only affects memory footprint, so
can be increased if needed */
#define BSP_STACK_SIZE 4096 /* Stack size for the BSP */
#define AP_STACK_SIZE 2048 /* Stack size for each AP */
#define LOW_LOAD_ADDR 0x00010000 /* The low load address for the main program */
#define HIGH_LOAD_ADDR 0x00100000 /* The high load address for the main program */
#define SETUP_SECS 2 /* Size of the 16-bit setup code in sectors */
#define BOOT_SEG 0x07c0 /* Segment address for the 16-bit boot code */
#define SETUP_SEG 0x07e0 /* Segment address for the 16-bit setup code */
#define MAIN_SEG 0x1000 /* Segment address for the main program code
when loaded by the 16-bit bootloader */
#define KERNEL_CS 0x10 /* 32-bit segment address for code */
#define KERNEL_DS 0x18 /* 32-bit segment address for data */
/* The following addresses are offsets from BOOT_SEG. */
#define BOOT_STACK ((1 + SETUP_SECS) * 512)
#define BOOT_STACK_TOP ((MAIN_SEG - BOOT_SEG) << 4)
/* The following definitions must match the Linux boot_params struct. */
#define E820_ENTRIES 0x1e8 /* offsetof(boot_params.e820_entries) */
#define E820_MAP 0x2d0 /* offsetof(boot_params.e820_table) */
#define E820_MAP_SIZE 128 /* max. number of entries in E820_MAP */
/* The following definitions must match the Linux e820_entry struct. */
#define E820_ADDR 0 /* offsetof(e820_entry.addr) */
#define E820_SIZE 8 /* offsetof(e820_entry.size) */
#define E820_TYPE 16 /* offsetof(e820_entry.type) */
#define E820_ENTRY_SIZE 20 /* sizeof(e820_entry) */
#ifndef __ASSEMBLY__
extern uint8_t _start[];
extern uint8_t startup[];
extern uint64_t pml4[];
extern uint64_t pdp[];
extern uint64_t pd0[];
extern uint64_t pd1[];
extern uint64_t pd2[];
extern uint64_t pd3[];
extern uintptr_t boot_params_addr;
extern uint8_t ap_trampoline[];
extern uint32_t ap_startup_addr;
extern uint8_t ap_trampoline_end[];
extern uint8_t _end[];
#endif /* ! __ASSEMBLY__ */
#endif /* BOOT_H */

368
boot/bootsect.S Normal file
View File

@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0
//
// bootsect.S supports booting directly from the BIOS or via an intermediate
// bootloader that supports the Linux boot protocol. If booted directly from
// the BIOS, it is loaded at address 0x7c00. It then loads setup.S immediately
// after itself (address 0x7e00) and the main program code at segment MAIN_SEG,
// using BIOS interrupts to read the data from disk. When using an intermediate
// bootloader, it provides the first few bytes of the Linux boot header (at the
// end of the boot sector), with the remainder of the header being provided by
// setup.S.
//
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ bootsect.S:
//
// bootsect.s Copyright (C) 1991, 1992 Linus Torvalds
//
// 1-Jan-96 Modified by Chris Brady for use as a boot loader for MemTest-86.
#define __ASSEMBLY__
#include "boot.h"
.section ".bootsect", "ax", @progbits
.code16
# The BIOS boot entry point. This will be located at 0x7c00.
.globl boot
boot:
# Initialise the segment registers and the stack.
ljmp $BOOT_SEG, $(init - boot)
init:
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $BOOT_STACK_TOP, %ax
movw %ax, %sp
# Many BIOS's default disk parameter tables will not recognize
# multi-sector reads beyond the maximum sector number specified
# in the default diskette parameter tables - this may mean 7
# sectors in some cases.
#
# Since single sector reads are slow and out of the question,
# we must take care of this by creating new parameter tables
# (for the first disk) in RAM. We will set the maximum sector
# count to 18 - the most we will encounter on an HD 1.44.
#
# High doesn't hurt. Low does.
#
# Segments are as follows:
# ds=es=ss=cs = BOOT_SEG,
# fs = 0, gs = parameter table segment
pushw $0
popw %fs
movw $0x78, %bx # fs:bx is parameter table address
lgs %fs:(%bx),%si # gs:si is source
movw %dx, %di # es:di is destination
movw $6, %cx # copy 12 bytes
cld
rep movsw %gs:(%si), (%di)
movw %dx, %di
movb $18, 4(%di) # patch sector count
movw %di, %fs:(%bx)
movw %es, %fs:2(%bx)
movw %cs, %ax
movw %ax, %fs
movw %ax, %gs
xorb %ah, %ah # reset FDC
xorb %dl, %dl
int $0x13
# Load the setup sectors directly after the boot block.
# Note that 'es' is already set up.
load_setup:
xorw %dx, %dx # drive 0, head 0
movw $0x0002, %cx # sector 2, track 0
movw $0x0200, %bx # address = 512, in BOOT_SEG
movw $(0x0200 + SETUP_SECS), %ax # service 2, nr of sectors
# (assume all on head 0, track 0)
int $0x13 # read it
jnc load_setup_done # ok - continue
pushw %ax # dump error code
call print_nl
movw %sp, %bp
call print_hex
popw %ax
xorb %dl, %dl # reset FDC
xorb %ah, %ah
int $0x13
jmp load_setup
load_setup_done:
# Get disk drive parameters, specifically number of sectors/track.
# It seems that there is no BIOS call to get the number of sectors.
# Guess 18 sectors if sector 18 can be read, 15 if sector 15 can be
# read. Otherwise guess 9.
xorw %dx, %dx # drive 0, head 0
movw $0x0012, %cx # sector 18, track 0
movw $BOOT_STACK, %bx # use the bottom of the stack (es = cs)
movw $0x0201, %ax # service 2, 1 sector
int $0x13
jnc got_sectors
movb $0x0f, %cl # sector 15
movw $0x0201, %ax # service 2, 1 sector
int $0x13
jnc got_sectors
movb $0x09, %cl
got_sectors:
movw %cx, %cs:sectors - boot
movw $BOOT_SEG, %ax
movw %ax, %es
# Print a message.
movb $0x03, %ah # read cursor pos
xorb %bh, %bh
int $0x10
leaw boot_msg, %bp
movw $(boot_msg_end - boot_msg), %cx
movw $0x0007, %bx # page 0, attribute 7 (normal)
movw $0x1301, %ax # write string, move cursor
int $0x10
# Load the main test program.
movw $MAIN_SEG, %ax
movw %ax, %es
call read_it
call kill_motor
call turn_off_cursor
call print_nl
# Fix up the Linux boot header to indicate we've loaded into low memory.
leaw code32_start - boot, %di
movl $LOW_LOAD_ADDR, (%di)
# After that (everything loaded), we jump to the setup code loaded
# directly after the boot block.
ljmp $SETUP_SEG, $0
# This subroutine loads the system at address 0x10000, making sure no 64KB
# boundaries are crossed. We try to load it as fast as possible, loading
# whole tracks whenever we can.
#
# in: es - starting address segment (normally 0x1000)
#
sread: .word 1 + SETUP_SECS # sectors read of current track
head: .word 0 # current head
track: .word 0 # current track
read_it:
movw %es, %ax
testw $0x0fff, %ax
die:
jne die # es must be at 64kB boundary
xorw %bx,%bx # bx is starting address within segment
rp_read:
movw %es, %ax
subw $MAIN_SEG, %ax # have we loaded all yet?
cmpw sys_size - boot, %ax
jbe ok1_read
ret
ok1_read:
movw %cs:sectors - boot, %ax
subw sread - boot, %ax
movw %ax, %cx
shlw $9, %cx
addw %bx, %cx
jnc ok2_read
je ok2_read
xorw %ax, %ax
subw %bx, %ax
shrw $9, %ax
ok2_read:
call read_track
movw %ax, %cx
add sread - boot, %ax
cmpw %cs:sectors - boot, %ax
jne ok3_read
movw $1, %ax
subw head - boot, %ax
jne ok4_read
incw track - boot
ok4_read:
movw %ax, head - boot
xorw %ax, %ax
ok3_read:
movw %ax, sread - boot
shlw $9, %cx
addw %cx, %bx
jnc rp_read
movw %es, %ax
addb $0x10, %ah
movw %ax, %es
xorw %bx, %bx
jmp rp_read
read_track:
pusha
pusha
movw $0xe2e, %ax # loading... message 2e = .
movw $7, %bx
int $0x10
popa
movw track - boot, %dx
movw sread - boot, %cx
incw %cx
movb %dl, %ch
movw head - boot, %dx
movb %dl, %dh
andw $0x0100, %dx
movb $2, %ah
pushw %dx # save for error dump
pushw %cx
pushw %bx
pushw %ax
int $0x13
jc bad_rt
addw $8, %sp
popa
ret
bad_rt:
pushw %ax # save error code
call print_all # ah = error, al = read
xorb %ah, %ah
xorb %dl, %dl
int $0x13
addw $10, %sp
popa
jmp read_track
# This subroutine is for debugging purposes. It will print out all of the
# registers. The assumption is that this is called from a routine, with a
# stack frame like:
# dx
# cx
# bx
# ax
# err
# ret <- sp
print_all:
movw $5, %cx # error code + 4 registers
movw %sp, %bp
print_loop:
pushw %cx # save count left
call print_nl # nl for readability
cmpb 5, %cl # see if register name is needed
jae no_reg
movw $(0xe05 + 'A' - 1), %ax
subb %cl, %al
int $0x10
movb $'X', %al
int $0x10
movb $':', %al
int $0x10
no_reg:
addw $2, %bp # next register
call print_hex # print it
popw %cx
loop print_loop
ret
print_nl:
movw $0xe0d, %ax # CR
int $0x10
movb $0x0a, %al # LF
int $0x10
ret
# This subroutine is for debugging purposes, and prints the word pointed to
# by ss:bp in hexadecimal.
print_hex:
movw $4, %cx # 4 hex digits
movw (%bp), %dx # load word into dx
print_digit:
rolw $4, %dx # rotate so that lowest 4 bits are used
movb $0xe, %ah
movb %dl, %al # mask off so we have only next nibble
andb $0xf, %al
addb $'0', %al # convert to 0-based digit
cmpb $'9', %al # check for overflow
jbe good_digit
addb $('A' - '0' - 10), %al
good_digit:
int $0x10
loop print_digit
ret
# This subroutine turns off the floppy drive motor, so that we enter the
# kernel in a known state, and don't have to worry about it later.
kill_motor:
pushw %dx
movw $0x3f2, %dx
xorb %al, %al
outb %al, %dx
popw %dx
ret
# This subroutine turns off the text display cursor.
turn_off_cursor:
movb $0x01, %ah
movb $0x00, %bh
movw $0x2000, %cx
int $0x10
ret
# Local variables.
sectors:
.word 0
boot_msg:
.ascii "Loading PCMemTest"
boot_msg_end:
# Emulate the Linux boot header, to allow loading by intermediate boot loaders.
.org 497
setup_sects:
.byte SETUP_SECS
root_flags:
.word 0
sys_size:
.long _syssize
ram_size:
.word 0
vid_mode:
.word 0
root_dev:
.word 0
boot_flag:
.word 0xAA55

383
boot/setup.S Normal file
View File

@@ -0,0 +1,383 @@
// SPDX-License-Identifier: GPL-2.0
//
// setup.S collects the memory map information from the BIOS, disables APM,
// enables A20, and performs the switch from real mode to protected mode
// before jumping to the main program entry point.
//
// The memory map information is stored in the 4KB block of memory immediately
// following the setup code. The layout of the information matches the Linux
// boot_params struct. A pointer to this block is passed to the main program,
// for compatiblity with the Linux 32-bit boot protocol.
//
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ setup.S and head.S:
//
// 1-Jan-96 Modified by Chris Brady for use as a boot/loader for memtest-86.
#define __ASSEMBLY__
#include "boot.h"
#define BOOT_PARAMS_START (SETUP_SECS * 512)
#define BOOT_PARAMS_END (BOOT_PARAMS_START + 4096)
.section ".setup", "ax", @progbits
.code16
# Emulate the Linux boot header, to allow loading by other boot loaders.
# Indicate that the main program code should be loaded in high memory.
# bootsect.S will fix up the values if we are booted directly from the BIOS.
.globl setup
setup:
jmp do_setup
header:
.ascii "HdrS"
version:
.word 0x020c
realmode_swtch:
.long 0
start_sys_seg:
.word 0x1000
kernel_version:
.word 0
type_of_loader:
.byte 0
loadflags:
.byte 0x1 # LOADED_HIGH
setup_move_size:
.word 0
.globl code32_start
code32_start:
.long HIGH_LOAD_ADDR
ramdisk_image:
.long 0
ramdisk_size:
.long 0
bootsect_kludge:
.long 0
heap_end_ptr:
.word 0
ext_loader_ver:
.byte 0
ext_loader_type:
.byte 0
cmd_line_ptr:
.long 0
initrd_addr_max:
.long 0xffffffff
kernel_alignment:
.long 0
relocatable_kernel:
.byte 0
min_alignment:
.byte 0
xload_flags:
#ifdef __X86_64__
.word 1 # XLF_KERNEL_64
#else
.word 0
#endif
cmd_line_size:
.long 255
hardware_subarch:
.long 0
hardware_subarch_data:
.quad 0
payload_offset:
.long 0
payload_length:
.long 0
setup_data:
.quad 0
pref_address:
.quad HIGH_LOAD_ADDR
init_size:
.long _initsize
handover_offset:
.long 0
do_setup:
# Reload the segment registers, except for the stack.
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
# Get the memory map and disable APM.
call get_mem_info
call disable_apm
# Disable interrupts.
cli
movb $0x80, %al # disable NMI
outb %al, $0x70
# Enable A20.
# Try to switch using the fast A20 gate.
movw $0x92, %dx
inb %dx, %al
# Skip if it's unimplemented (read returns 0xff).
cmpb $0xff, %al
jz 0f
orb $0x02, %al # set the ALT_A20_GATE bit
andb $0xfe, %al # clear the INIT_NOW bit
outb %al, %dx
0:
# Use the keyboard controller method anyway.
call empty_8042
movb $0xd1, %al # send write command
outb %al, $0x64
call empty_8042
movb $0xdf, %al # A20 on
outb %al, $0x60
call empty_8042
# Set up a minimal GDT and IDT.
xorl %eax, %eax
movw %cs, %ax
shll $4, %eax
addl %eax, gdt_descr - setup + 2
lgdt gdt_descr - setup
lidt idt_descr - setup
# Load a pointer to the boot_params block into ESI.
xorl %esi, %esi
movw %cs, %si
shll $4, %esi
addl $BOOT_PARAMS_START, %esi
# Fix up the jump address.
movl (code32_start - setup), %eax
movl %eax, (jump - setup + 2)
# Copy code32_start to the boot_params struct.
movl %eax, (BOOT_PARAMS_START + 0x214)
# Switch to protected mode.
movl %cr0, %eax
orl $1, %eax
movl %eax, %cr0
jmp flush
flush:
# Reload the segment registers and jump to the main test program.
movw $KERNEL_DS, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw %ax, %fs
movw %ax, %gs
jump:
data32 ljmp $KERNEL_CS, $0
# This subroutine queries the BIOS to determine the system memory map
# and stores the results in the boot_params structure that we pass to
# the startup code.
#define SMAP 0x534d4150
get_mem_info:
push %ds
push %es
# Set DS and ES to point to the start of the boot_params structure.
movw %ds, %ax
addw $(BOOT_PARAMS_START >> 4), %ax
movw %ax, %ds
movw %ax, %es
# Zero the entire boot_params structure.
movw $0x0000, %di
movw $0x0400, %cx
xorl %eax, %eax
cld
rep stosl
# First try method E820. E820 returns memory classified into a whole
# bunch of different types, and allows memory holes and everything.
mem_e820:
movw $E820_MAP, %di # destination pointer
xorl %ebx, %ebx # continuation counter
loop_e820:
movl $0x0000e820, %eax # e820, upper word zeroed
movl $SMAP, %edx # ASCII 'SMAP'
movl $20, %ecx # size of the e820 record
int $0x15 # make the call
jc done_e820 # bail out if it fails
cmpl $SMAP, %eax # check the return is 'SMAP'
jne done_e820 # bail out if it fails
incb (E820_ENTRIES)
addw $E820_ENTRY_SIZE, %di
movb (E820_ENTRIES), %al # check for table full
cmpb $E820_MAP_SIZE, %al
je done_e820
cmpl $0, %ebx # any more entries?
jne loop_e820
done_e820:
cmpb $0, (E820_ENTRIES)
jnz get_mem_done
# Next try method E801.
mem_e801:
stc # Fix to work around buggy BIOSs
xorw %cx,%cx # which don't clear/set carry on
xorw %dx,%dx # pass/error of e801h memory size
# call or merely pass cx,dx through
# without changing them.
movw $0xe801, %ax
int $0x15
jc mem_88
cmpw $0x0, %cx # Kludge to handle BIOSes which
jne 0f # report their extended memory in
cmpw $0x0, %dx # AX/BX rather than CX/DX. The spec
jne 0f # I have read seems to indicate that
movw %ax, %cx # AX/BX are more reasonable anyway.
movw %bx, %dx
0:
jmp fake_e820
# Finally try method 88.
mem_88:
movb $0x88, %ah
int $0x15
movw %ax, %cx
movw $0, %dx
fake_e820:
# Write entry for memory below 1MB.
movl $0x0, E820_ADDR(%di)
movl $0xa0000, E820_SIZE(%di)
movl $1, E820_TYPE(%di)
incb (E820_ENTRIES)
addw $E820_ENTRY_SIZE, %di
# Write entry for memory between 1MB and 16MB.
andl $0xffff, %ecx # convert to 32-bits
jz 0f
shll $10, %ecx # convert to bytes
movl $0x100000, E820_ADDR(%di)
movl %ecx, E820_SIZE(%di)
movl $1, E820_TYPE(%di)
incb (E820_ENTRIES)
addw $E820_ENTRY_SIZE, %di
0:
# Write entry for memory above 16MB.
andl $0xffff, %edx # convert to 32-bits
jz 1f
shll $16, %edx # convert to bytes
movl $0x1000000, E820_ADDR(%di)
movl %edx, E820_SIZE(%di)
movl $1, E820_TYPE(%di)
incb (E820_ENTRIES)
addw $E820_ENTRY_SIZE, %di
1:
get_mem_done:
pop %es
pop %ds
ret
# This subroutine disables APM if it is present.
disable_apm:
movw $0x5300, %ax # APM BIOS installation check
xorw %bx, %bx
int $0x15
jc disable_apm_done # error -> no APM BIOS
cmpw $0x504d, %bx # check for "PM" signature
jne disable_apm_done # no signature -> no APM BIOS
movw $0x5304, %ax # Disconnect first just in case
xorw %bx, %bx
int $0x15 # ignore return code
movw $0x5301, %ax # Real Mode connect
xorw %bx, %bx
int $0x15
jc disable_apm_done # error
movw $0x5308, %ax # Disable APM
mov $0xffff, %bx
xorw %cx, %cx
int $0x15
disable_apm_done:
ret
# This subroutine checks that the keyboard command queue is empty (after
# emptying the output buffers). No timeout is used - if this hangs there
# is something wrong with the machine, and we probably couldn't proceed
# anyway.
empty_8042:
call delay
inb $0x64, %al # 8042 status port
cmpb $0xff, %al # skip if not implemented
jz empty_8042_ret
testb $1, %al # anything in the output buffer?
jz no_output
call delay
inb $0x60, %al # read it
jmp empty_8042
no_output:
testb $2, %al # is input buffer full?
jnz empty_8042 # yes - loop
empty_8042_ret:
ret
# This subroutine provides a short delay.
delay:
.word 0x00eb # jmp $+2
ret
# A minimal GDT and IDT.
.align 4
gdt:
.quad 0x0000000000000000 # NULL descriptor
.quad 0x0000000000000000 # not used
.quad 0x00c09a0000007fff # 128MB 32-bit code at 0x000000
.quad 0x00c0920000007fff # 128MB 32-bit code at 0x000000
gdt_end:
.word 0 # for alignment
gdt_descr:
.word gdt_end - gdt - 1 # gdt limit
.long gdt - setup # gdt base - relocated at run time
.word 0 # for alignment
idt_descr:
.word 0 # idt limit=0
.long 0 # idt base=0
# Pad to the declared size.
.org (SETUP_SECS*512)

515
boot/startup32.S Normal file
View File

@@ -0,0 +1,515 @@
// SPDX-License-Identifier: GPL-2.0
//
// startup32.S contains the 32-bit startup code for both the BSP and APs.
// It initialises stacks, memory management, and exception handling, clears
// the BSS, completes relocation, and finally calls the main application.
// It supports the 32-bit Linux boot protocol for the first boot of the BSP.
//
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ head.S:
//
// linux/boot/head.S
// Copyright (C) 1991, 1992 Linus Torvalds
// 1-Jan-96 Modified by Chris Brady for use as a boot/loader for MemTest-86.
// Set up the memory management for flat non-paged linear addressing.
// 17 May 2004 : Added X86_PWRCAP for AMD64 (Memtest86+ - Samuel D.)
#define __ASSEMBLY__
#include "boot.h"
#define NUM_INT_VEC 20
.text
.code32
# The Linux 32-bit boot entry point.
.globl startup32
startup32:
cld
cli
# The 32-bit entry point for AP boot and for restart after relocation.
.globl startup
startup:
# Use a temporary stack until we pick the correct one. We can
# safely use the high address, even if we are loaded low.
movl $(HIGH_LOAD_ADDR + startup_stack_top - startup), %esp
# Load the GOT pointer.
call 0f
0: popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.-0b], %ebx
# Save the boot params pointer (if first boot).
cmpl $1, first_boot@GOTOFF(%ebx)
jnz 1f
movl %esi, boot_params_addr@GOTOFF(%ebx)
1:
# Pick the correct stack.
call smp_my_pcpu_num
movl $AP_STACK_SIZE, %edx
mul %edx
leal bsp_stack_top@GOTOFF(%ebx), %esp
addl %eax, %esp
# Initialise the GDT descriptor.
leal gdt@GOTOFF(%ebx), %eax
movl %eax, 2 + gdt_descr@GOTOFF(%ebx)
# Load the GDT and the segment registers.
lgdt gdt_descr@GOTOFF(%ebx)
leal flush@GOTOFF(%ebx), %eax
movw $KERNEL_CS, -2(%esp)
movl %eax, -6(%esp)
ljmp *-6(%esp)
flush: movw $KERNEL_DS, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
# Initialise the IDT.
leal idt@GOTOFF(%ebx), %edi
leal vec0@GOTOFF(%ebx), %esi
movw $NUM_INT_VEC, %cx
0: movl %esi, %edx
movl $(KERNEL_CS << 16), %eax
movw %dx, %ax # selector = 0x0010 = cs
movw $0x8E00, %dx # interrupt gate - dpl=0, present
movl %eax, (%edi)
movl %edx, 4(%edi)
addl $(vec1-vec0), %esi
addl $8, %edi
dec %cx
jnz 0b
# Initialise the IDT descriptor.
leal idt@GOTOFF(%ebx), %eax
movl %eax, 2 + idt_descr@GOTOFF(%ebx)
# Load the IDT.
lidt idt_descr@GOTOFF(%ebx)
# Zero the BSS (if first boot).
cmpl $1, first_boot@GOTOFF(%ebx)
jnz 1f
xorl %eax, %eax
leal _bss@GOTOFF(%ebx), %edi
leal _end@GOTOFF(%ebx), %ecx
subl %edi, %ecx
0: movl %eax, (%edi)
addl $4, %edi
subl $4, %ecx
jnz 0b
movl $0, first_boot@GOTOFF(%ebx)
1:
# Initialise the FPU.
finit
# Call the dynamic linker to fix up the addresses in the GOT.
call reloc
# Enable PAE if supported.
movl %ebx, %edi # ebx is overwritten by cpuid
movl $0x00000001, %eax # test the PAE flag
cpuid
andl $0x00000040, %edx
jz 1f # bail if not supported
movl %cr4, %eax # enable PAE
orl $0x00000020, %eax
movl %eax, %cr4
leal pdp@GOTOFF(%edi), %eax # set the page directory base address
movl %eax, %cr3
# Enable long mode if supported.
movl $0x80000000, %eax # check if function 0x80000001 is available
cpuid
cmpl $0x80000001, %eax
jb 0f # bail if not supported
mov $0x80000001, %eax # test the LM flag
cpuid
andl $0x20000000, %edx
jz 0f # bail if not supported
movl $0xc0000080, %ecx # enable long mode.
rdmsr
orl $0x00000100, %eax
wrmsr
leal pml4@GOTOFF(%edi), %eax # set the page directory base address
movl %eax, %cr3
# Enable paging.
0: movl %cr0, %eax
orl $0x80000000, %eax
movl %eax, %cr0
1: movl %edi, %ebx
# Run the application.
call main
# In case we return, simulate an exception.
pushfl
pushl %cs
call 0f
0: pushl $0 # error code
pushl $257 # vector
jmp int_handler
# Individual interrupt vector handlers. These need to be spaced equally, to
# allow the IDT initialisation loop above to work, so we use noops to pad out
# where required.
vec0:
pushl $0 # error code
pushl $0 # vector
jmp int_handler
vec1:
pushl $0 # error code
pushl $1 # vector
jmp int_handler
vec2:
pushl $0 # error code
pushl $2 # vector
jmp int_handler
vec3:
pushl $0 # error code
pushl $3 # vector
jmp int_handler
vec4:
pushl $0 # error code
pushl $4 # vector
jmp int_handler
vec5:
pushl $0 # error code
pushl $5 # vector
jmp int_handler
vec6:
pushl $0 # error code
pushl $6 # vector
jmp int_handler
vec7:
pushl $0 # error code
pushl $7 # vector
jmp int_handler
vec8:
nop;nop # error code already provided
pushl $8 # vector
jmp int_handler
vec9:
pushl $0 # error code
pushl $9 # vector
jmp int_handler
vec10:
nop;nop # error code already provided
pushl $10 # vector
jmp int_handler
vec11:
nop;nop # error code already provided
pushl $11 # vector
jmp int_handler
vec12:
nop;nop # error code already provided
pushl $12 # vector
jmp int_handler
vec13:
nop;nop # error code already provided
pushl $13 # vector
jmp int_handler
vec14:
nop;nop # error code already provided
pushl $14 # vector
jmp int_handler
vec15:
pushl $0 # error code
pushl $15 # vector
jmp int_handler
vec16:
pushl $0 # error code
pushl $16 # vector
jmp int_handler
vec17:
nop;nop # error code
pushl $17 # vector
jmp int_handler
vec18:
pushl $0 # error code
pushl $18 # vector
jmp int_handler
vec19:
pushl $0 # error code
pushl $19 # vector
jmp int_handler
# The common interrupt handler code. Pass the register state to the
# application interrupt handler.
int_handler:
pushl %eax
pushl %ebx
pushl %ecx
pushl %edx
pushl %edi
pushl %esi
pushl %ebp
# original stack pointer
leal 48(%esp), %eax
pushl %eax
pushl %ds
pushl %es
pushl %ss
pushl %esp # pointer to trap regs struct on the stack
call interrupt
addl $20, %esp
popl %ebp
popl %esi
popl %edi
popl %edx
popl %ecx
popl %ebx
popl %eax
addl $8, %esp
iret
# The interrupt descriptor table.
.align 4
.word 0 # for alignment
idt_descr:
.word idt_end - idt - 1 # size
.long 0 # addr: filled in at run time
idt:
.fill NUM_INT_VEC, 8, 0 # filled in at run time
idt_end:
# The global descriptor table.
.word 0 # for alignment
gdt_descr:
.word gdt_end - gdt - 1 # size
.long 0 # addr: filled in at run time
.align 4
.globl gdt
gdt:
.quad 0x0000000000000000 # NULL descriptor
.quad 0x0000000000000000 # not used
.quad 0x00cf9b000000ffff # 0x10 main 4gb code at 0x000000
.quad 0x00cf93000000ffff # 0x18 main 4gb data at 0x000000
.globl gdt_end
gdt_end:
.data
.macro ptes64 start, count=64
.quad \start + 0x0000000 + 0x83
.quad \start + 0x0200000 + 0x83
.quad \start + 0x0400000 + 0x83
.quad \start + 0x0600000 + 0x83
.quad \start + 0x0800000 + 0x83
.quad \start + 0x0A00000 + 0x83
.quad \start + 0x0C00000 + 0x83
.quad \start + 0x0E00000 + 0x83
.if \count-1
ptes64 "(\start+0x01000000)",\count-1
.endif
.endm
.macro maxdepth depth=1
.if \depth-1
maxdepth \depth-1
.endif
.endm
maxdepth
# The long mode level 4 page map table.
.align 4096
.globl pml4
pml4:
.long pdp + 0x3 # relocated at run time
.long 0
# Page Directory Pointer Table:
# 4 Entries, pointing to the Page Directory Tables.
.align 4096
.globl pdp
pdp:
.long pd0 + 0x1 # relocated at run time
.long 0
.long pd1 + 0x1 # relocated at run time
.long 0
.long pd2 + 0x1 # relocated at run time
.long 0
.long pd3 + 0x1 # relocated at run time
.long 0
# Page Directory Tables:
# There are 4 tables. The first two map the first 2 GB of memory. The third
# is used with PAE to map the rest of memory in 1 GB segments. The fourth is
# reserved for mapping the video frame buffer. We use 2 MB pages so only the
# Page Directory Table is used (no page tables).
.align 4096
.globl pd0
pd0:
ptes64 0x0000000000000000
.align 4096
.globl pd1
pd1:
ptes64 0x0000000040000000
.align 4096
.globl pd2
pd2:
ptes64 0x0000000080000000
.align 4096
.globl pd3
pd3:
ptes64 0x00000000C0000000
.previous
# ap_trampoline is the entry point for CPUs other than the bootstrap
# CPU (BSP). It gets copied to a page in low memory, to enable the APs
# to boot when the main program has been loaded in high memory.
.code16
.align 4
.globl ap_trampoline
ap_trampoline:
movw %cs, %ax
movw %ax, %ds
# Patch the jump address.
movl (ap_startup_addr - ap_trampoline), %ebx
movl %ebx, (ap_jump - ap_trampoline + 2)
# Patch and load the GDT descriptor. It should point to the main
# GDT descriptor, which has already been initialised by the BSP.
movl %ebx, %eax
addl $(gdt - startup), %eax
movl %eax, (ap_gdt_descr - ap_trampoline + 2)
lgdt ap_gdt_descr - ap_trampoline
# Switch to protected mode and reload the segment registers.
movl %cr0, %eax
orl $1, %eax
movl %eax, %cr0
jmp ap_flush
ap_flush:
movw $KERNEL_DS, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
# Jump to the main entry point.
ap_jump:
data32 ljmp $KERNEL_CS, $0
.align 4
.word 0 # for alignment
ap_gdt_descr:
.word gdt_end - gdt - 1 # gdt limit
.long 0 # gdt base - filled in at run time
.globl ap_startup_addr
ap_startup_addr:
.long 0 # filled in at run time
.globl ap_trampoline_end
ap_trampoline_end:
.previous
# Variables.
.data
.globl boot_params_addr
boot_params_addr:
.long 0
first_boot:
.long 1
.previous
# Stacks.
.bss
.align 16
bsp_stack_base:
. = . + BSP_STACK_SIZE
bsp_stack_top:
ap_stacks_base:
. = . + (AP_STACK_SIZE * MAX_APS)
ap_stacks_top:
startup_stack_base:
. = . + 64
startup_stack_top:
.previous

593
boot/startup64.S Normal file
View File

@@ -0,0 +1,593 @@
// SPDX-License-Identifier: GPL-2.0
//
// startup64.S contains the 64-bit startup code for both the BSP and APs.
// It initialises stacks, memory management, and exception handling, clears
// the BSS, completes relocation, and finally calls the main application.
// It supports both the 32-bit and 64-bit Linux boot protocols for the first
// boot of the BSP.
//
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ head.S:
//
// linux/boot/head.S
// Copyright (C) 1991, 1992 Linus Torvalds
// 1-Jan-96 Modified by Chris Brady for use as a boot/loader for MemTest-86.
// Set up the memory management for flat non-paged linear addressing.
// 17 May 2004 : Added X86_PWRCAP for AMD64 (Memtest86+ - Samuel D.)
#define __ASSEMBLY__
#include "boot.h"
#define NUM_INT_VEC 20
.text
.code32
# The Linux 32-bit boot entry point.
.globl startup32
startup32:
cld
cli
# Use a temporary stack until we pick the correct one. We can
# safely use the high address, even if we are loaded low.
movl $(HIGH_LOAD_ADDR + startup_stack_top - startup), %esp
# Get the load address.
movl 0x214(%esi), %ebx
# Save the boot params pointer.
movl %esi, (boot_params_addr - startup32)(%ebx)
# Initialise the pml4 and pdp tables.
leal (pml4 - startup32)(%ebx), %ecx
leal (pdp - startup32)(%ebx), %edx
movl %edx, %eax
addl $0x3, %eax
movl %eax, 0(%ecx)
leal (pd0 - startup32)(%ebx), %eax
addl $0x3, %eax
movl %eax, 0(%edx)
leal (pd1 - startup32)(%ebx), %eax
addl $0x3, %eax
movl %eax, 8(%edx)
leal (pd2 - startup32)(%ebx), %eax
addl $0x3, %eax
movl %eax, 16(%edx)
leal (pd3 - startup32)(%ebx), %eax
addl $0x3, %eax
movl %eax, 24(%edx)
# Set the page directory base address.
movl %ecx, %cr3
# Enable PAE.
movl %cr4, %eax
orl $0x20, %eax
movl %eax, %cr4
# Enable long mode.
movl $0xc0000080, %ecx
rdmsr
orl $0x00000100, %eax
wrmsr
# Enable paging and protection.
movl %cr0, %eax
orl $0x80000001, %eax
movl %eax, %cr0
# Initialise the 64-bit GDT descriptor.
leal (gdt - startup32)(%ebx), %eax
movl %eax, 2 + (gdt_descr - startup32)(%ebx)
# Load the GDT and enter long mode.
lgdt (gdt_descr - startup32)(%ebx)
leal (startup - startup32)(%ebx), %eax
movw $KERNEL_CS, -2(%esp)
movl %eax, -6(%esp)
ljmp *-6(%esp)
.org 0x200
.code64
# The Linux 64-bit boot entry point.
.globl startup64
startup64:
cld
cli
# Save the boot params pointer.
movq %rsi, boot_params_addr(%rip)
# The 64-bit entry point for AP boot and for restart after relocation.
.globl startup
startup:
# Use a temporary stack until we pick the correct one.
leaq startup_stack_top(%rip), %rsp
# Pick the correct stack.
xorq %rax, %rax
call smp_my_pcpu_num
movl $AP_STACK_SIZE, %edx
mul %edx
leaq bsp_stack_top(%rip), %rsp
addq %rax, %rsp
# Initialise the pml4 and pdp tables.
leaq pml4(%rip), %rcx
leaq pdp(%rip), %rdx
movq %rdx, %rax
addq $0x3, %rax
movq %rax, 0(%rcx)
leaq pd0(%rip), %rax
addq $0x3, %rax
movq %rax, 0(%rdx)
leaq pd1(%rip), %rax
addq $0x3, %rax
movq %rax, 8(%rdx)
leaq pd2(%rip), %rax
addq $0x3, %rax
movq %rax, 16(%rdx)
leaq pd3(%rip), %rax
addq $0x3, %rax
movq %rax, 24(%rdx)
# Set the page directory base address.
movq %rcx, %cr3
# Initialise the GDT descriptor.
leaq gdt(%rip), %rax
movq %rax, 2 + gdt_descr(%rip)
# Load the GDT and the segment registers.
lgdt gdt_descr(%rip)
leaq flush(%rip), %rax
movw $KERNEL_CS, -2(%rsp)
movl %eax, -6(%rsp)
ljmp *-6(%rsp)
flush: movw $KERNEL_DS, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
# Initialise the IDT.
leaq idt(%rip), %rdi
leaq vec0(%rip), %rsi
movw $NUM_INT_VEC, %cx
0: movq %rsi, %rdx
movl $(KERNEL_CS << 16), %eax
movw %dx, %ax # selector = 0x0010 = cs
movw $0x8E00, %dx # interrupt gate - dpl=0, present
movl %eax, (%edi)
movl %edx, 4(%edi)
shrq $32, %rdx
movl %edx, 8(%edi)
movl $0, 12(%edi)
addq $(vec1-vec0), %rsi
addq $16, %rdi
dec %cx
jnz 0b
# Initialise the IDT descriptor.
leaq idt(%rip), %rax
movq %rax, 2 + idt_descr(%rip)
# Load the IDT.
lidt idt_descr(%rip)
# Zero the BSS (if first boot).
cmpl $1, first_boot(%rip)
jnz 1f
xorq %rax, %rax
leaq _bss(%rip), %rdi
leaq _end(%rip), %rcx
subq %rdi, %rcx
0: movq %rax, (%rdi)
addq $8, %rdi
subq $8, %rcx
jnz 0b
movl $0, first_boot(%rip)
1:
# Initialise the FPU.
finit
#if 0
# Enable SSE.
movq %cr0, %rax
andw $0xfffb, %ax # clear coprocessor emulation bit
orw $0x0002, %ax # set coprocessor monitoring bit
mov %rax, %cr0
movq %cr4, %rax
orw $0x0600, %ax # set OSFXSR and OSXMMEXCPT
movq %rax, %cr4
#endif
# Call the dynamic linker to fix up the addresses in the GOT.
call reloc
# Run the application.
call main
# In case we return, simulate an exception.
pushfq
xorq %rax, %rax
movw %cs, %ax
pushq %rax
call 0f
0: pushq $0 # error code
pushq $257 # vector
jmp int_handler
# Individual interrupt vector handlers. These need to be spaced equally, to
# allow the IDT initialisation loop above to work, so we use noops to pad out
# where required.
vec0:
pushq $0 # error code
pushq $0 # vector
jmp int_handler
vec1:
pushq $0 # error code
pushq $1 # vector
jmp int_handler
vec2:
pushq $0 # error code
pushq $2 # vector
jmp int_handler
vec3:
pushq $0 # error code
pushq $3 # vector
jmp int_handler
vec4:
pushq $0 # error code
pushq $4 # vector
jmp int_handler
vec5:
pushq $0 # error code
pushq $5 # vector
jmp int_handler
vec6:
pushq $0 # error code
pushq $6 # vector
jmp int_handler
vec7:
pushq $0 # error code
pushq $7 # vector
jmp int_handler
vec8:
nop;nop # error code already provided
pushq $8 # vector
jmp int_handler
vec9:
pushq $0 # error code
pushq $9 # vector
jmp int_handler
vec10:
nop;nop # error code already provided
pushq $10 # vector
jmp int_handler
vec11:
nop;nop # error code already provided
pushq $11 # vector
jmp int_handler
vec12:
nop;nop # error code already provided
pushq $12 # vector
jmp int_handler
vec13:
nop;nop # error code already provided
pushq $13 # vector
jmp int_handler
vec14:
nop;nop # error code already provided
pushq $14 # vector
jmp int_handler
vec15:
pushq $0 # error code
pushq $15 # vector
jmp int_handler
vec16:
pushq $0 # error code
pushq $16 # vector
jmp int_handler
vec17:
nop;nop # error code
pushq $17 # vector
jmp int_handler
vec18:
pushq $0 # error code
pushq $18 # vector
jmp int_handler
vec19:
pushq $0 # error code
pushq $19 # vector
jmp int_handler
# The common interrupt handler code. Pass the register state to the
# application interrupt handler.
int_handler:
pushq %rax
pushq %rbx
pushq %rcx
pushq %rdx
pushq %rdi
pushq %rsi
pushq %rbp
# original stack pointer
leaq 96(%rsp), %rax
pushq %rax
xorq %rax, %rax
movw %ds, %ax
pushq %rax
movw %es, %ax
pushq %rax
movw %ss, %ax
pushq %rax
movq %rsp, %rdi # pointer to trap regs struct on the stack
call interrupt
addq $32, %rsp
popq %rbp
popq %rsi
popq %rdi
popq %rdx
popq %rcx
popq %rbx
popq %rax
addq $16, %rsp
iretq
# The interrupt descriptor table.
.align 4
.word 0 # for alignment
idt_descr:
.word idt_end - idt - 1 # size
.quad 0 # addr: filled in at run time
idt:
.fill NUM_INT_VEC*2, 8, 0 # filled in at run time
idt_end:
# The global descriptor table.
.word 0 # for alignment
gdt_descr:
.word gdt_end - gdt - 1 # size
.quad 0 # addr: filled in at run time
.align 4
.globl gdt
gdt:
.quad 0x0000000000000000 # NULL descriptor
.quad 0x0000000000000000 # not used
.quad 0x00209a0000000000 # 0x10 64-bit code at 0x000000
.quad 0x0000920000000000 # 0x18 64-bit data at 0x000000
.globl gdt_end
gdt_end:
.data
.macro ptes64 start, count=64
.quad \start + 0x0000000 + 0x83
.quad \start + 0x0200000 + 0x83
.quad \start + 0x0400000 + 0x83
.quad \start + 0x0600000 + 0x83
.quad \start + 0x0800000 + 0x83
.quad \start + 0x0A00000 + 0x83
.quad \start + 0x0C00000 + 0x83
.quad \start + 0x0E00000 + 0x83
.if \count-1
ptes64 "(\start+0x01000000)",\count-1
.endif
.endm
.macro maxdepth depth=1
.if \depth-1
maxdepth \depth-1
.endif
.endm
maxdepth
# The level 4 page map table.
.align 4096
.globl pml4
pml4:
.quad 0 # filled in at run time
# Page Directory Pointer Table:
# 4 Entries, pointing to the Page Directory Tables.
.align 4096
.globl pdp
pdp:
.quad 0 # filled in at run time
.quad 0 # filled in at run time
.quad 0 # filled in at run time
.quad 0 # filled in at run time
# Page Directory Tables:
# There are 4 tables. The first two map the first 2 GB of memory. The third
# is used with PAE to map the rest of memory in 1 GB segments. The fourth is
# reserved for mapping the video frame buffer. We use 2 MB pages so only the
# Page Directory Table is used (no page tables).
.align 4096
.globl pd0
pd0:
ptes64 0x0000000000000000
.align 4096
.globl pd1
pd1:
ptes64 0x0000000040000000
.align 4096
.globl pd2
pd2:
ptes64 0x0000000080000000
.align 4096
.globl pd3
pd3:
ptes64 0x00000000C0000000
.previous
# ap_trampoline is the entry point for CPUs other than the bootstrap
# CPU (BSP). It gets copied to a page in low memory, to enable the APs
# to boot when the main program has been loaded in high memory.
.code16
.align 4
.globl ap_trampoline
ap_trampoline:
movw %cs, %ax
movw %ax, %ds
# Patch the jump address.
movl (ap_startup_addr - ap_trampoline), %ebx
movl %ebx, (ap_jump - ap_trampoline + 2)
# Patch and load the GDT descriptor. It should point to the main
# GDT descriptor, which has already been initialised by the BSP.
movl %ebx, %eax
addl $(gdt - startup), %eax
movl %eax, (ap_gdt_descr - ap_trampoline + 2)
lgdt ap_gdt_descr - ap_trampoline
# Set the page directory base address.
movl %ebx, %eax
addl $(pml4 - startup), %eax
movl %eax, %cr3
# Enable PAE.
movl %cr4, %eax
orl $0x20, %eax
movl %eax, %cr4
# Enable long mode.
movl $0xc0000080, %ecx
rdmsr
orl $0x00000100, %eax
wrmsr
# Enable paging and protection.
movl %cr0, %eax
orl $0x80000001, %eax
movl %eax, %cr0
# Jump to the 64-bit entry point.
ap_jump:
data32 ljmp $KERNEL_CS, $0
.align 4
.word 0 # for alignment
ap_gdt_descr:
.word gdt_end - gdt - 1 # gdt limit
.long 0 # gdt base - filled in at run time
.globl ap_startup_addr
ap_startup_addr:
.long 0 # filled in at run time
.globl ap_trampoline_end
ap_trampoline_end:
.previous
# Variables.
.data
.globl boot_params_addr
boot_params_addr:
.quad 0
first_boot:
.long 1
.previous
# Stacks.
.bss
.align 16
bsp_stack_base:
. = . + BSP_STACK_SIZE
bsp_stack_top:
ap_stacks_base:
. = . + (AP_STACK_SIZE * MAX_APS)
ap_stacks_top:
startup_stack_base:
. = . + 64
startup_stack_top:
.previous

111
build32/Makefile Normal file
View File

@@ -0,0 +1,111 @@
AS = as -32
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -Wshadow -m32 -march=i586 -fpic -fno-builtin \
-ffreestanding -fomit-frame-pointer -fno-stack-protector
INC_DIRS = -I../boot -I../system -I../lib -I../tests -I../app
SYS_OBJS = system/cpuid.o \
system/cpuinfo.o \
system/font.o \
system/hwctrl.o \
system/keyboard.o \
system/pci.o \
system/pmem.o \
system/reloc.o \
system/screen.o \
system/smp.o \
system/temperature.o \
system/vmem.o
LIB_OBJS = lib/barrier.o \
lib/ctype.o \
lib/div64.o \
lib/print.o \
lib/read.o \
lib/string.o \
lib/unistd.o
TST_OBJS = tests/addr_walk1.o \
tests/bit_fade.o \
tests/block_move.o \
tests/modulo_n.o \
tests/mov_inv_fixed.o \
tests/mov_inv_random.o \
tests/mov_inv_walk1.o \
tests/own_addr.o \
tests/test_helper.o \
tests/tests.o
APP_OBJS = app/badram.o \
app/config.o \
app/display.o \
app/error.o \
app/interrupt.o \
app/main.o
OBJS = boot/startup.o $(SYS_OBJS) $(LIB_OBJS) $(TST_OBJS) $(APP_OBJS)
all: memtest.bin
-include $(subst .o,.d,$(SYS_OBJS))
-include $(subst .o,.d,$(LIB_OBJS))
-include $(subst .o,.d,$(TST_OBJS))
-include $(subst .o,.d,$(APP_OBJS))
boot/%.o: boot/%.s
$(AS) $< -o $@
boot/startup.s: ../boot/startup32.S ../boot/boot.h
@mkdir -p boot
$(CC) -m32 -E -traditional -I../boot -o $@ $<
boot/%.s: ../boot/%.S ../boot/boot.h
@mkdir -p boot
$(CC) -m32 -E -traditional -I../boot -o $@ $<
system/reloc.o: ../system/reloc32.c
@mkdir -p system
$(CC) -c $(CFLAGS) -fno-strict-aliasing -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
system/%.o: ../system/%.c
@mkdir -p system
$(CC) -c $(CFLAGS) -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
lib/%.o: ../lib/%.c
@mkdir -p lib
$(CC) -c $(CFLAGS) -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
tests/%.o: ../tests/%.c
@mkdir -p tests
$(CC) -c $(CFLAGS) -O3 $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
app/%.o: ../app/%.c
@mkdir -p app
$(CC) -c $(CFLAGS) -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
# Link it statically once so I know I don't have undefined symbols and
# then link it dynamically so I have full relocation information.
memtest_shared: $(OBJS) ldscripts/memtest_shared.lds Makefile
$(LD) --warn-constructors --warn-common -static -T ldscripts/memtest_shared.lds -o $@ $(OBJS) && \
$(LD) -shared -Bsymbolic -T ldscripts/memtest_shared.lds -o $@ $(OBJS)
memtest_shared.bin: memtest_shared
objcopy -O binary $< memtest_shared.bin
memtest.bin: memtest_shared.bin boot/bootsect.o boot/setup.o ldscripts/memtest_bin.lds
$(LD) -T ldscripts/memtest_bin.lds boot/bootsect.o boot/setup.o -b binary memtest_shared.bin -o memtest.bin
memtest.img: memtest.bin
dd if=/dev/zero of=memtest.img bs=1474560 count=1
dd if=memtest.bin of=memtest.img bs=1474560 conv=notrunc
iso: memtest.img
@mkdir -p iso/boot
genisoimage -b memtest.img -c boot/boot.catalog -V "PCMemTest-32" -o memtest.iso iso memtest.img
@rm -rf iso
clean:
rm -rf boot system lib tests app *.iso memtest* iso

110
build64/Makefile Normal file
View File

@@ -0,0 +1,110 @@
AS = as -64
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -Wshadow -m64 -march=x86-64 -mno-mmx -mno-sse -mno-sse2 \
-fpic -fno-builtin -ffreestanding -fomit-frame-pointer -fno-stack-protector
INC_DIRS = -I../boot -I../system -I../lib -I../tests -I../app
SYS_OBJS = system/cpuid.o \
system/cpuinfo.o \
system/font.o \
system/hwctrl.o \
system/keyboard.o \
system/pci.o \
system/pmem.o \
system/reloc.o \
system/screen.o \
system/smp.o \
system/temperature.o \
system/vmem.o
LIB_OBJS = lib/barrier.o \
lib/ctype.o \
lib/print.o \
lib/read.o \
lib/string.o \
lib/unistd.o
TST_OBJS = tests/addr_walk1.o \
tests/bit_fade.o \
tests/block_move.o \
tests/modulo_n.o \
tests/mov_inv_fixed.o \
tests/mov_inv_random.o \
tests/mov_inv_walk1.o \
tests/own_addr.o \
tests/test_helper.o \
tests/tests.o
APP_OBJS = app/badram.o \
app/config.o \
app/display.o \
app/error.o \
app/interrupt.o \
app/main.o
OBJS = boot/startup.o $(SYS_OBJS) $(LIB_OBJS) $(TST_OBJS) $(APP_OBJS)
all: memtest.bin
-include $(subst .o,.d,$(SYS_OBJS))
-include $(subst .o,.d,$(LIB_OBJS))
-include $(subst .o,.d,$(TST_OBJS))
-include $(subst .o,.d,$(APP_OBJS))
boot/%.o: boot/%.s
$(AS) $< -o $@
boot/startup.s: ../boot/startup64.S ../boot/boot.h
@mkdir -p boot
$(CC) -E -traditional -I../boot -o $@ $<
boot/%.s: ../boot/%.S ../boot/boot.h
@mkdir -p boot
$(CC) -E -traditional -I../boot -o $@ $<
system/reloc.o: ../system/reloc64.c
@mkdir -p system
$(CC) -c $(CFLAGS) -fno-strict-aliasing -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
system/%.o: ../system/%.c
@mkdir -p system
$(CC) -c $(CFLAGS) -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
lib/%.o: ../lib/%.c
@mkdir -p lib
$(CC) -c $(CFLAGS) -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
tests/%.o: ../tests/%.c
@mkdir -p tests
$(CC) -c $(CFLAGS) -O3 $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
app/%.o: ../app/%.c
@mkdir -p app
$(CC) -c $(CFLAGS) -Os $(INC_DIRS) -o $@ $< -MMD -MP -MT $@ -MF $(@:.o=.d)
# Link it statically once so I know I don't have undefined symbols and
# then link it dynamically so I have full relocation information.
memtest_shared: $(OBJS) ldscripts/memtest_shared.lds Makefile
$(LD) --warn-constructors --warn-common -static -T ldscripts/memtest_shared.lds -o $@ $(OBJS) && \
$(LD) -shared -Bsymbolic -T ldscripts/memtest_shared.lds -o $@ $(OBJS)
memtest_shared.bin: memtest_shared
objcopy -O binary $< memtest_shared.bin
memtest.bin: memtest_shared.bin boot/bootsect.o boot/setup.o ldscripts/memtest_bin.lds
$(LD) -T ldscripts/memtest_bin.lds boot/bootsect.o boot/setup.o -b binary memtest_shared.bin -o memtest.bin
memtest.img: memtest.bin
dd if=/dev/zero of=memtest.img bs=1474560 count=1
dd if=memtest.bin of=memtest.img bs=1474560 conv=notrunc
iso: memtest.img
@mkdir -p iso/boot
genisoimage -b memtest.img -c boot/boot.catalog -V "PCMemTest-64" -o memtest.iso iso memtest.img
@rm -rf iso
clean:
rm -rf boot system lib tests app *.iso memtest* iso

55
lib/barrier.c Normal file
View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ smp.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// ------------------------------------------------
// smp.c - MemTest-86 Version 3.5
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stddef.h>
#include "barrier.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void barrier_init(barrier_t *barrier, int num_threads)
{
barrier->num_threads = num_threads;
barrier->count = num_threads;
spin_unlock(&barrier->lock);
spin_unlock(&barrier->st1);
spin_unlock(&barrier->st2);
spin_lock(&barrier->st2);
}
void barrier_wait(barrier_t *barrier)
{
if (barrier == NULL || barrier->num_threads < 2) {
return;
}
spin_wait(&barrier->st1); // Wait if the barrier is active.
spin_lock(&barrier->lock); // Get lock for barrier struct.
if (--barrier->count == 0) { // Last process?
spin_lock(&barrier->st1); // Hold up any processes re-entering.
spin_unlock(&barrier->st2); // Release the other processes.
barrier->count++;
spin_unlock(&barrier->lock);
} else {
spin_unlock(&barrier->lock);
spin_wait(&barrier->st2); // Wait for peers to arrive.
spin_lock(&barrier->lock);
if (++barrier->count == barrier->num_threads) {
spin_unlock(&barrier->st1);
spin_lock(&barrier->st2);
}
spin_unlock(&barrier->lock);
}
}

34
lib/barrier.h Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef BARRIER_H
#define BARRIER_H
/*
* Provides a barrier synchronisation primitive.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include "spinlock.h"
/*
* A barrier object.
*/
typedef struct
{
int num_threads;
volatile int count;
spinlock_t lock;
spinlock_t st1;
spinlock_t st2;
} barrier_t;
/*
* Initialises the barrier to block the specified number of threads.
*/
void barrier_init(barrier_t *barrier, int num_threads);
/*
* Waits for all threads to arrive at the barrier.
*/
void barrier_wait(barrier_t *barrier);
#endif // BARRIER_H

27
lib/ctype.c Normal file
View File

@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
#include "ctype.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int toupper(int c)
{
if (c >= 'a' && c <= 'z') {
return c + 'A' -'a';
} else {
return c;
}
}
int isdigit(int c)
{
return c >= '0' && c <= '9';
}
int isxdigit(int c)
{
return isdigit(c) || (toupper(c) >= 'A' && toupper(c) <= 'F');
}

28
lib/ctype.h Normal file
View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef CTYPE_H
#define CTYPE_H
/*
* Provides a subset of the functions normally provided by <ctype.h>.
*
* Copyright (C) 2020 Martin Whitaker.
*/
/*
* If c is a lower-case letter, returns its upper-case equivalent, otherwise
* returns c. Assumes c is an ASCII character.
*/
int toupper(int c);
/*
* Returns 1 if c is a decimal digit, otherwise returns 0. Assumes c is an
* ASCII character.
*/
int isdigit(int c);
/*
* Returns 1 if c is a hexadecimal digit, otherwise returns 0. Assumes c is an
* ASCII character.
*/
int isxdigit(int c);
#endif // CTYPE_H

13
lib/div64.c Normal file
View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
#include <stdint.h>
// Implement the 64-bit division primitive. This is only needed for 32-bit
// builds. We don't use this for anything critical, so a floating-point
// approximation is good enough.
uint64_t __udivdi3(uint64_t num, uint64_t den)
{
return (uint64_t)((double)num / (double)den);
}

272
lib/print.c Normal file
View File

@@ -0,0 +1,272 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
#include <stdint.h>
#include "screen.h"
#include "string.h"
#include "print.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define BUFFER_SIZE 64
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static int int_to_dec_str(char buffer[], int value, int min_length, int max_length)
{
bool negative = (value < 0);
if (negative) {
value = -value;
if (min_length > 1) {
min_length--;
}
max_length--;
}
int length = 0;
while (length < min_length || (value > 0 && length < max_length)) {
buffer[length++] = '0' + (value % 10);
value /= 10;
}
if (negative) {
buffer[length++] = '-';
}
return length;
}
static int uint_to_dec_str(char buffer[], uintptr_t value, int min_length, int max_length)
{
int length = 0;
while (length < min_length || (value > 0 && length < max_length)) {
buffer[length++] = '0' + (value % 10);
value /= 10;
}
return length;
}
static int uint_to_hex_str(char buffer[], uintptr_t value, int min_length, int max_length)
{
int length = 0;
while (length < min_length || (value > 0 && length < max_length) ){
int digit = value % 16;
if (digit < 10) {
buffer[length++] = '0' + digit;
} else {
buffer[length++] = 'a' + digit - 10;
}
value /= 16;
}
return length;
}
static int min_str_length(int field_length, bool pad)
{
return (field_length > 0 && pad) ? field_length : 1;
}
static int print_in_field(int row, int col, const char buffer[], int buffer_length, int field_length, bool left)
{
bool reversed = false;
if (buffer_length < 0) {
buffer_length = -buffer_length;
reversed = true;
}
if (!left) {
while (field_length > buffer_length) {
print_char(row, col++, ' ');
field_length--;
}
}
if (reversed) {
for (int i = buffer_length - 1; i >= 0; i--) {
print_char(row, col++, buffer[i]);
}
} else {
for (int i = 0; i < buffer_length; i++) {
print_char(row, col++, buffer[i]);
}
}
if (left) {
while (field_length > buffer_length) {
print_char(row, col++, ' ');
field_length--;
}
}
return col;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int printc(int row, int col, const char c)
{
print_char(row, col++, c);
return col;
}
int prints(int row, int col, const char *str)
{
while (*str) {
print_char(row, col++, *str++);
}
return col;
}
int printi(int row, int col, int value, int field_length, bool pad, bool left)
{
char buffer[BUFFER_SIZE];
int length = int_to_dec_str(buffer, value, min_str_length(field_length, pad), BUFFER_SIZE);
return print_in_field(row, col, buffer, -length, field_length, left);
}
int printu(int row, int col, uintptr_t value, int field_length, bool pad, bool left)
{
char buffer[BUFFER_SIZE];
int length = uint_to_dec_str(buffer, value, min_str_length(field_length, pad), BUFFER_SIZE);
return print_in_field(row, col, buffer, -length, field_length, left);
}
int printx(int row, int col, uintptr_t value, int field_length, bool pad, bool left)
{
char buffer[BUFFER_SIZE];
int length = uint_to_hex_str(buffer, value, min_str_length(field_length, pad), BUFFER_SIZE);
return print_in_field(row, col, buffer, -length, field_length, left);
}
int printk(int row, int col, uintptr_t value, int field_length, bool pad, bool left)
{
static const char suffix[4] = { 'K', 'M', 'G', 'T' };
int scale = 0;
int fract = 0;
while (value >= 1024 && scale < (int)(sizeof(suffix) - 1)) {
fract = value % 1024;
value /= 1024;
scale++;
}
int whole_length = field_length > 1 ? field_length - 1 : 0;
int fract_length = 0;
if (fract > 0) {
if (value < 10) {
whole_length = field_length > 4 ? field_length - 4 : 0;
fract = (100 * fract) / 1024;
if (fract > 0) {
if (fract % 10) {
fract_length = 2;
} else {
fract_length = 1;
fract /= 10;
}
}
} else if (value < 100) {
whole_length = field_length > 3 ? field_length - 3 : 0;
fract = (100 * fract) / (10 * 1024);
if (fract > 0) {
fract_length = 1;
}
}
}
char buffer[BUFFER_SIZE];
int length = 0;
buffer[length++] = suffix[scale];
if (fract_length > 0) {
length += int_to_dec_str(&buffer[length], fract, fract_length, fract_length);
buffer[length++] = '.';
}
length += uint_to_dec_str(&buffer[length], value, min_str_length(whole_length, pad), BUFFER_SIZE - length);
return print_in_field(row, col, buffer, -length, field_length, left);
}
int printf(int row, int col, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int end_col = vprintf(row, col, fmt, args);
va_end(args);
return end_col;
}
int vprintf(int row, int col, const char *fmt, va_list args)
{
while (*fmt) {
if (*fmt != '%') {
print_char(row, col++, *fmt++);
continue;
}
fmt++;
if (*fmt == '%') {
print_char(row, col++, *fmt++);
continue;
}
bool pad = false;
bool left = false;
int length = 0;
if (*fmt == '-') {
left = true;
fmt++;
}
if (*fmt == '0') {
pad = !left;
fmt++;
}
if (*fmt == '*') {
length = va_arg(args, int);
if (length < 0) {
length = -length;
left = true;
}
fmt++;
} else {
while (*fmt >= '0' && *fmt <= '9') {
length = 10 * length + *fmt - '0';
fmt++;
}
}
switch (*fmt) {
case 'c': {
char buffer[1];
buffer[0] = va_arg(args, int);
col = print_in_field(row, col, buffer, 1, length, left);
} break;
case 's': {
const char *str = va_arg(args, char *);
col = print_in_field(row, col, str, strlen(str), length, left);
} break;
case 'i':
col = printi(row, col, va_arg(args, int), length, pad, left);
break;
case 'u':
col = printu(row, col, va_arg(args, uintptr_t), length, pad, left);
break;
case 'x':
col = printx(row, col, va_arg(args, uintptr_t), length, pad, left);
break;
case 'k':
col = printk(row, col, va_arg(args, uintptr_t), length, pad, left);
break;
}
fmt++;
}
return col;
}

85
lib/print.h Normal file
View File

@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef PRINT_H
#define PRINT_H
/*
* Provides functions to print strings and formatted values to the screen.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
/*
* Prints a single character on screen at location (row,col) and returns col+1.
*/
int printc(int row, int col, char c);
/*
* Prints a string on screen starting at location (row,col) and returns the
* next column after the string.
*/
int prints(int row, int col, const char *str);
/*
* Prints a signed decimal number on screen starting at location (row,col) in
* a field of at least length characters, optionally padding the number with
* leading zeros, and optionally left-justifying instead of right-justifying
* in the field. Returns the next column after the formatted number.
*/
int printi(int row, int col, int value, int length, bool pad, bool left);
/*
* Prints an unsigned decimal number on screen starting at location (row,col)
* in a field of at least length characters, optionally padding the number with
* leading zeros, and optionally left-justifying instead of right-justifying in
* the field. Returns the next column after the formatted number.
*/
int printu(int row, int col, uintptr_t value, int length, bool pad, bool left);
/*
* Prints an unsigned hexadecimal number on screen starting at location (row,col)
* in a field of at least length characters, optionally padding the number with
* leading zeros, and optionally left-justifying instead of right-justifying in
* the field. Returns the next column after the formatted number.
*/
int printx(int row, int col, uintptr_t value, int length, bool pad, bool left);
/*
* Prints a K<unit> value on screen starting at location (row,col) in a field of
* at least length characters, optionally padding the number with leading zeros,
* and optionally left-justifying instead of right-justifying in the field. The
* value is shown to 3 significant figures in the nearest K/M/G/T units. Returns
* the next column after the formatted value.
*/
int printk(int row, int col, uintptr_t value, int length, bool pad, bool left);
/*
* Emulates the standard printf function. Printing starts at location (row,col).
*
* The conversion flags supported are:
* - left justify
* 0 pad with leading zeros
*
* The conversion specifiers supported are:
* c character (int type)
* s string (char* type)
* i signed decimal integer (int type)
* u unsigned decimal integer (uintptr_t type)
* x unsigned hexadecimal integer (uintptr_t type)
* k K<unit> value (scaled to K/M/G/T) (uintptr_t type)
*
* The only other conversion option supported is the minimum field width. This
* may be either a literal value or '*'.
*
* Returns the next column after the formatted string.
*/
int printf(int row, int col, const char *fmt, ...);
/*
* The alternate form of printf.
*/
int vprintf(int row, int col, const char *fmt, va_list args);
#endif // PRINT_H

129
lib/read.c Normal file
View File

@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ lib.c:
//
// lib.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "ctype.h"
#include "keyboard.h"
#include "print.h"
#include "unistd.h"
#include "read.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
uintptr_t read_value(int row, int col, int field_width)
{
char buffer[1 + field_width];
for (int i = 0; i <= field_width; i++ ) {
buffer[i] = ' ';
}
int n = 0;
int base = 10;
bool done = false;
bool got_suffix = false;
while (!done) {
char c = get_key();
switch (c) {
case '\n':
if (n > 0) {
done = true;
}
break;
case '\b':
if (n > 0) {
got_suffix = false;
buffer[--n] = ' ';
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (n < field_width && base >= 10 && !got_suffix) {
buffer[n] = c;
}
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
if (n < field_width && base >= 16 && !got_suffix) {
buffer[n] = c;
}
break;
case 'k':
case 'p':
case 'm':
case 'g':
case 't':
if (n > 0 && n < field_width && buffer[n-1] != 'x') {
got_suffix = true;
buffer[n] = toupper(c);
}
break;
case 'x':
/* Only allow 'x' after an initial 0 */
if (n == 1 && n < field_width && buffer[0] == '0') {
buffer[n] = 'x';
}
break;
default:
usleep(1000);
break;
}
if (buffer[n] != ' ') {
n++;
}
prints(row, col, buffer);
if (buffer[0] == '0' && buffer[1] == 'x') {
base = 16;
} else {
base = 10;
}
}
int shift = 0;
if (got_suffix) {
switch (buffer[n-1]) {
case 'T': /* tera */ shift = 40; n--; break;
case 'G': /* gig */ shift = 30; n--; break;
case 'M': /* meg */ shift = 20; n--; break;
case 'P': /* page */ shift = 12; n--; break;
case 'K': /* kilo */ shift = 10; n--; break;
}
}
uintptr_t value = 0;
for (int i = (base == 16) ? 2 : 0; i < n; i++) {
value *= base;
if (buffer[i] >= 'a') {
value += buffer[i] - 'a';
} else {
value += buffer[i] - '0';
}
}
return value << shift;
}

22
lib/read.h Normal file
View File

@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef READ_H
#define READ_H
/*
* Provides a function to read a numeric value.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
/*
* Returns an unsigned numeric value entered on the keyboard. Echoes the
* input to the display field located at (row,col), limiting it to field_width
* characters. If the entered value is prefixed by "0x", assumes base 16,
* otherwise assumes base 10. If the value is suffixed by 'K', 'P', 'M', or
* 'G', the returned value will be scaled by 10^10, 10^12, 10^20, or 10^30
* accordingly.
*/
uintptr_t read_value(int x, int y, int field_width);
#endif // READ_H

57
lib/spinlock.h Normal file
View File

@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef SPINLOCK_H
#define SPINLOCK_H
/*
* Provides a lightweight mutex synchronisation primitive.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdbool.h>
/*
* A mutex object. Use spin_unlock() to initialise prior to first use.
*/
typedef volatile bool spinlock_t;
/*
* Spins until the mutex is unlocked.
*/
static inline void spin_wait(spinlock_t *lock)
{
if (lock) {
while (*lock) {
__builtin_ia32_pause();
for (volatile int i = 0; i < 100; i++) { } // this reduces power consumption
}
}
}
/*
* Spins until the mutex is unlocked, then locks the mutex.
*/
static inline void spin_lock(spinlock_t *lock)
{
if (lock) {
while (!__sync_bool_compare_and_swap(lock, false, true)) {
do {
__builtin_ia32_pause();
for (volatile int i = 0; i < 100; i++) { } // this reduces power consumption
} while (*lock);
}
__sync_synchronize();
}
}
/*
* Unlocks the mutex.
*/
static inline void spin_unlock(spinlock_t *lock)
{
if (lock) {
__sync_synchronize();
*lock = false;
}
}
#endif // SPINLOCK_H

107
lib/string.c Normal file
View File

@@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ lib.c:
//
// lib.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stddef.h>
#include <string.h>
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int memcmp(const void *s1, const void *s2, size_t n)
{
const unsigned char *src1 = s1, *src2 = s2;
for (size_t i = 0; i < n; i++) {
if (src1[i] != src2[i]) {
return (int)src1[i] - (int)src2[i];
}
}
return 0;
}
void *memcpy(void *dest, const void *src, size_t n)
{
char *d = (char *)dest, *s = (char *)src;
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
return dest;
}
void *memmove(void *dest, const void *src, size_t n)
{
char *d = (char *)dest, *s = (char *)src;
if (n > 0) {
if (dest < src) {
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
}
if (dest > src) {
size_t i = n;
do {
i--;
d[i] = s[i];
} while (i > 0);
}
}
return dest;
}
void *memset(void *s, int c, size_t n)
{
char *d = (char *)s;
for (size_t i = 0; i < n; i++) {
d[i] = c;
}
return s;
}
size_t strlen(const char *s)
{
size_t len = 0;
while (*s++) {
len++;
}
return len;
}
int strncmp(const char *s1, const char *s2, size_t n)
{
for (size_t i = 0; i < n; i++) {
if (s1[i] != s2[i]) {
return (int)s1[i] - (int)s2[i];
}
if (s1[i] == '\0') {
return 0;
}
}
return 0;
}
char *strstr(const char *haystack, const char *needle)
{
size_t haystack_len = strlen(haystack);
size_t needle_len = strlen(needle);
size_t max_idx = haystack_len - needle_len;
for (size_t idx = 0; idx <= max_idx; idx++) {
if (memcmp(haystack + idx, needle, needle_len) == 0) {
return (char *)haystack + idx;
}
}
return NULL;
}

60
lib/string.h Normal file
View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef STRING_H
#define STRING_H
/*
* Provides a subset of the functions normally provided by <string.h>.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stddef.h>
/*
* Compares the first n bytes of the memory areas pointed to by s1 and s2
* and returns 0 if all bytes are the same or the numerical difference
* between the first mismatching byte in s1 (interpreted as an unsigned
* value) and the corresponding byte in s2.
*/
int memcmp(const void *s1, const void *s2, size_t n);
/*
* Copies n bytes from the memory area pointed to by src to the memory area
* pointed to by dest and returns a pointer to dest. The memory areas must
* not overlap.
*/
void *memcpy(void *dst, const void *src, size_t n);
/*
* Copies n bytes from the memory area pointed to by src to the memory area
* pointed to by dest and returns a pointer to dest. The memory areas may
* overlap.
*/
void *memmove(void *dest, const void *src, size_t n);
/*
* Fills the first n bytes of the memory area pointed to by s with the byte
* value c.
*/
void *memset(void *s, int c, size_t n);
/*
* Returns the string length, excluding the terminating null character.
*/
size_t strlen(const char *s);
/*
* Compares at most the first n characters in the strings s1 and s2 and
* returns 0 if all characters are the same or the numerical difference
* between the first mismatching character in s1 (interpreted as a signed
* value) and the corresponding character in s2.
*/
int strncmp(const char *s1, const char *s2, size_t n);
/*
* Finds the first occurrence of the substring needle in the string haystack
* and returns a pointer to the beginning of the located substring, or NULL
* if the substring is not found.
*/
char *strstr(const char *haystack, const char *needle);
#endif // STRING_H

40
lib/unistd.c Normal file
View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
#include <stdint.h>
#include "cpuinfo.h"
#include "tsc.h"
#include "unistd.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void usleep(unsigned int usec)
{
if (clks_per_msec > 0) {
// If we've measured the CPU speed, we know the TSC is available.
uint64_t cycles = ((uint64_t)usec * clks_per_msec) / 1000;
uint64_t t0 = get_tsc();
do {
__builtin_ia32_pause();
for (volatile int i = 0; i < 100; i++) { } // this reduces power consumption
} while ((get_tsc() - t0) < cycles);
} else {
// This will be highly inaccurate, but should give at least the requested delay.
volatile uint64_t count = (uint64_t)usec * 1000;
while (count > 0) {
count--;
}
}
}
void sleep(unsigned int sec)
{
while (sec > 0) {
usleep(1000000);
sec--;
}
}

20
lib/unistd.h Normal file
View File

@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef UNISTD_H
#define UNISTD_H
/*
* Provides a subset of the functions normally provided by <unistd.h>.
*
* Copyright (C) 2020 Martin Whitaker.
*/
/*
* Sleeps for at least usec microseconds.
*/
void usleep(unsigned int usec);
/*
* Sleeps for at least sec seconds.
*/
void sleep(unsigned int sec);
#endif // UNISTD_H

64
system/cache.h Normal file
View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef CACHE_H
#define CACHE_H
/*
* Provides functions to enable/disable the CPU caches.
*
* Copyright (C) 2020 Martin Whitaker.
*/
/*
* Disable the CPU caches.
*/
static inline void cache_off(void)
{
#ifdef __x86_64__
__asm__ __volatile__ ("\t"
"movq %%cr0, %%rax \n\t"
"orl $0x40000000, %%eax \n\t" /* Set CD */
"movq %%rax, %%cr0 \n\t"
"wbinvd \n"
: /* no outputs */
: /* no inputs */
: "rax"
);
#else
__asm__ __volatile__ ("\t"
"movl %%cr0, %%eax \n\t"
"orl $0x40000000, %%eax \n\t" /* Set CD */
"movl %%eax, %%cr0 \n\t"
"wbinvd \n"
: /* no outputs */
: /* no inputs */
: "eax"
);
#endif
}
/*
* Enable the CPU caches.
*/
static inline void cache_on(void)
{
#ifdef __x86_64__
__asm__ __volatile__ ("\t"
"movq %%cr0, %%rax \n\t"
"andl $0x9fffffff, %%eax \n\t" /* Clear CD and NW */
"movq %%rax, %%cr0 \n"
: /* no outputs */
: /* no inputs */
: "rax"
);
#else
__asm__ __volatile__ ("\t"
"movl %%cr0, %%eax \n\t"
"andl $0x9fffffff, %%eax \n\t" /* Clear CD and NW */
"movl %%eax, %%cr0 \n"
: /* no outputs */
: /* no inputs */
: "eax"
);
#endif
}
#endif // CACHE_H

135
system/cpuid.c Normal file
View File

@@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ cpuid.h
// (original contained no copyright statement)
#include <stdint.h>
#include "cpuid.h"
//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------
cpuid_info_t cpuid_info;
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void cpuid_init(void)
{
uint32_t dummy[3];
char *p, *q;
// Get the max standard cpuid & vendor ID.
cpuid(0x0, 0,
&cpuid_info.max_vcpuid,
&cpuid_info.vendor_id.raw[0],
&cpuid_info.vendor_id.raw[2],
&cpuid_info.vendor_id.raw[1]
);
cpuid_info.vendor_id.str[CPUID_VENDOR_STR_LENGTH - 1] = '\0';
// Get the processor family information & feature flags.
if (cpuid_info.max_vcpuid >= 1) {
cpuid(0x1, 0,
&cpuid_info.version.raw,
&cpuid_info.proc_info.raw,
&cpuid_info.flags.raw[1],
&cpuid_info.flags.raw[0]
);
}
// Get the digital thermal sensor & power management status bits.
if (cpuid_info.max_vcpuid >= 6) {
cpuid(0x6, 0,
&cpuid_info.dts_pmp,
&dummy[0],
&dummy[1],
&dummy[2]
);
}
// Get the max extended cpuid.
cpuid(0x80000000, 0,
&cpuid_info.max_xcpuid,
&dummy[0],
&dummy[1],
&dummy[2]
);
// Get extended feature flags, only save EDX.
if (cpuid_info.max_xcpuid >= 0x80000001) {
cpuid(0x80000001, 0,
&dummy[0],
&dummy[1],
&dummy[2],
&cpuid_info.flags.raw[2]
);
}
// Get the brand ID.
if (cpuid_info.max_xcpuid >= 0x80000004) {
cpuid(0x80000002, 0,
&cpuid_info.brand_id.raw[0],
&cpuid_info.brand_id.raw[1],
&cpuid_info.brand_id.raw[2],
&cpuid_info.brand_id.raw[3]
);
cpuid(0x80000003, 0,
&cpuid_info.brand_id.raw[4],
&cpuid_info.brand_id.raw[5],
&cpuid_info.brand_id.raw[6],
&cpuid_info.brand_id.raw[7]
);
cpuid(0x80000004, 0,
&cpuid_info.brand_id.raw[8],
&cpuid_info.brand_id.raw[9],
&cpuid_info.brand_id.raw[10],
&cpuid_info.brand_id.raw[11]
);
cpuid_info.brand_id.str[CPUID_BRAND_STR_LENGTH - 1] = '\0';
}
// Intel chips right-justify this string for some reason - undo that.
p = q = &cpuid_info.brand_id.str[0];
while (*p == ' ') {
p++;
}
if (p != q) {
while (*p) {
*q++ = *p++;
}
while (q <= &cpuid_info.brand_id.str[CPUID_BRAND_STR_LENGTH]) {
*q++ = '\0';
}
}
// Get cache information.
switch (cpuid_info.vendor_id.str[0]) {
case 'A':
// AMD Processors
if (cpuid_info.max_xcpuid >= 0x80000005) {
cpuid(0x80000005, 0,
&dummy[0],
&dummy[1],
&cpuid_info.cache_info.raw[0],
&cpuid_info.cache_info.raw[1]
);
}
if (cpuid_info.max_xcpuid >= 0x80000006) {
cpuid(0x80000006, 0,
&dummy[0],
&dummy[1],
&cpuid_info.cache_info.raw[2],
&cpuid_info.cache_info.raw[3]
);
}
break;
case 'G':
// Intel Processors
// No cpuid info to read.
break;
}
}

200
system/cpuid.h Normal file
View File

@@ -0,0 +1,200 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef CPUID_H
#define CPUID_H
/*
* Provides access to the CPUID information.
*
* Copyright (C) 2020 Martin Whitaker.
*
* Derived from memtest86+ cpuid.h
* (original contained no copyright statement)
*/
#include <stdint.h>
/*
* Structures that hold the collected CPUID information.
*/
typedef union {
uint32_t raw;
struct {
uint32_t stepping : 4;
uint32_t model : 4;
uint32_t family : 4;
uint32_t processorType : 2;
uint32_t : 2;
uint32_t extendedModel : 4;
uint32_t extendedFamily : 8;
uint32_t : 4;
};
} cpuid_version_t;
typedef union {
uint32_t raw;
struct {
uint32_t brandIndex : 8;
uint32_t cflushLineSize : 8;
uint32_t logicalProcessorCount : 8;
uint32_t apicID : 8;
};
} cpuid_proc_info_t;
typedef union {
uint32_t raw[3];
struct {
uint32_t fpu : 1; // EDX feature flags, bit 0 */
uint32_t vme : 1;
uint32_t de : 1;
uint32_t pse : 1;
uint32_t rdtsc : 1;
uint32_t msr : 1;
uint32_t pae : 1;
uint32_t mce : 1;
uint32_t cx8 : 1;
uint32_t apic : 1;
uint32_t : 1;
uint32_t sep : 1;
uint32_t mtrr : 1;
uint32_t pge : 1;
uint32_t mca : 1;
uint32_t cmov : 1;
uint32_t pat : 1;
uint32_t pse36 : 1;
uint32_t psn : 1;
uint32_t cflush : 1;
uint32_t : 1;
uint32_t ds : 1;
uint32_t acpi : 1;
uint32_t mmx : 1;
uint32_t fxsr : 1;
uint32_t sse : 1;
uint32_t sse2 : 1;
uint32_t ss : 1;
uint32_t htt : 1;
uint32_t tm : 1;
uint32_t bit30 : 1;
uint32_t pbe : 1; // EDX feature flags, bit 31
uint32_t sse3 : 1; // ECX feature flags, bit 0
uint32_t mulq : 1;
uint32_t bit2 : 1;
uint32_t mon : 1;
uint32_t dscpl : 1;
uint32_t vmx : 1;
uint32_t smx : 1;
uint32_t eist : 1;
uint32_t tm2 : 1;
uint32_t : 23; // ECX feature flags, bit 31
uint32_t : 29; // EDX extended feature flags, bit 0
uint32_t lm : 1;
uint32_t : 2; // EDX extended feature flags, bit 31
};
} cpuid_feature_flags_t;
#define CPUID_VENDOR_LENGTH 3
#define CPUID_VENDOR_STR_LENGTH (CPUID_VENDOR_LENGTH * sizeof(uint32_t) + 1) // includes space for null terminator
typedef union {
uint32_t raw[CPUID_VENDOR_LENGTH];
char str[CPUID_VENDOR_STR_LENGTH];
} cpuid_vendor_string_t;
#define CPUID_BRAND_LENGTH 12
#define CPUID_BRAND_STR_LENGTH (CPUID_BRAND_LENGTH * sizeof(uint32_t) + 1) // includes space for null terminator
typedef union {
uint32_t raw[CPUID_BRAND_LENGTH];
char str[CPUID_BRAND_STR_LENGTH];
} cpuid_brand_string_t;
typedef union {
uint32_t raw[12];
struct {
uint32_t : 24;
uint32_t l1_i_size : 8;
uint32_t : 24;
uint32_t l1_d_size : 8;
uint32_t : 16;
uint32_t l2_size : 16;
uint32_t : 18;
uint32_t l3_size : 14;
};
} cpuid_cache_info_t;
typedef union {
uint32_t raw;
struct {
uint32_t : 1;
};
} cpuid_custom_features;
typedef struct {
uint32_t max_vcpuid;
uint32_t max_xcpuid;
uint32_t dts_pmp;
cpuid_version_t version;
cpuid_proc_info_t proc_info;
cpuid_feature_flags_t flags;
cpuid_vendor_string_t vendor_id;
cpuid_brand_string_t brand_id;
cpuid_cache_info_t cache_info;
cpuid_custom_features custom;
} cpuid_info_t;
typedef union {
uint32_t raw;
struct {
uint32_t ctype : 5;
uint32_t level : 3;
uint32_t is_self_initializing : 1;
uint32_t is_fully_associative : 1;
uint32_t reserved : 4;
uint32_t num_threads_sharing : 12;
uint32_t num_cores_on_die : 6;
};
} cpuid4_eax_t;
typedef union {
uint32_t raw;
struct {
uint32_t coherency_line_size : 12;
uint32_t physical_line_partition : 10;
uint32_t ways_of_associativity : 10;
};
} cpuid4_ebx_t;
typedef union {
uint32_t raw;
struct {
uint32_t number_of_sets : 32;
};
} cpuid4_ecx_t;
/*
* The CPUID information collected by cpuid_init();
*/
extern cpuid_info_t cpuid_info;
/*
* Reads the CPUID information and stores it in cpuid_info.
*/
void cpuid_init(void);
/*
* Executes the cpuid instruction.
*/
static inline void cpuid(uint32_t op, uint32_t count, uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx)
{
*eax = op;
*ecx = count;
__asm__ __volatile__ ("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax),
"2" (*ecx)
);
}
#endif // CPUID_H

774
system/cpuinfo.c Normal file
View File

@@ -0,0 +1,774 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ init.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// ------------------------------------------------
// init.c - MemTest-86 Version 3.6
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "cpuid.h"
#include "io.h"
#include "tsc.h"
#include "cpuinfo.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define PIT_TICKS_50mS 59659 // PIT clock is 1.193182MHz
//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------
const char *cpu_model = NULL;
uint32_t imc_type = 0;
int l1_cache = 0;
int l2_cache = 0;
int l3_cache = 0;
bool no_temperature = false;
uint32_t clks_per_msec = 0;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static void determine_cache_size()
{
switch (cpuid_info.vendor_id.str[0]) {
case 'A':
// AMD Processors (easy!)
l1_cache = cpuid_info.cache_info.l1_d_size;
l2_cache = cpuid_info.cache_info.l2_size;
l3_cache = cpuid_info.cache_info.l3_size;
l3_cache *= 512;
break;
case 'G':
// Intel Processors
l1_cache = 0;
l2_cache = 0;
l3_cache = 0;
// Use CPUID(4) if it is available.
if (cpuid_info.max_vcpuid > 3) {
cpuid4_eax_t eax;
cpuid4_ebx_t ebx;
cpuid4_ecx_t ecx;
uint32_t dummy;
// Loop through the cache leaves.
int i = 0;
do {
cpuid(4, i, &eax.raw, &ebx.raw, &ecx.raw, &dummy);
// Check for a valid cache type...
if (eax.ctype == 1 || eax.ctype == 3) {
// Compute the cache size
int size = (ecx.number_of_sets + 1)
* (ebx.coherency_line_size + 1)
* (ebx.physical_line_partition + 1)
* (ebx.ways_of_associativity + 1);
size /= 1024;
switch (eax.level) {
case 1:
l1_cache += size;
break;
case 2:
l2_cache += size;
break;
case 3:
l3_cache += size;
break;
default:
break;
}
}
i++;
} while (eax.ctype != 0);
return;
}
// No CPUID(4) so we use the older CPUID(2) method.
uint32_t v[4];
uint8_t *dp = (uint8_t *)v;
int i = 0;
do {
cpuid(2, 0, &v[0], &v[1], &v[2], &v[3]);
// If bit 31 is set, this is an unknown format.
for (int j = 0; j < 4; j++) {
if (v[j] & (1 << 31)) {
v[j] = 0;
}
}
// Byte 0 is level count, not a descriptor.
for (int j = 1; j < 16; j++) {
switch (dp[j]) {
case 0x6:
case 0xa:
case 0x66:
l1_cache += 8;
break;
case 0x8:
case 0xc:
case 0xd:
case 0x60:
case 0x67:
l1_cache += 16;
break;
case 0xe:
l1_cache += 24;
break;
case 0x9:
case 0x2c:
case 0x30:
case 0x68:
l1_cache += 32;
break;
case 0x39:
case 0x3b:
case 0x41:
case 0x79:
l2_cache += 128;
break;
case 0x3a:
l2_cache += 192;
break;
case 0x21:
case 0x3c:
case 0x3f:
case 0x42:
case 0x7a:
case 0x82:
l2_cache += 256;
break;
case 0x3d:
l2_cache += 384;
break;
case 0x3e:
case 0x43:
case 0x7b:
case 0x7f:
case 0x80:
case 0x83:
case 0x86:
l2_cache += 512;
break;
case 0x44:
case 0x78:
case 0x7c:
case 0x84:
case 0x87:
l2_cache += 1024;
break;
case 0x45:
case 0x7d:
case 0x85:
l2_cache += 2048;
break;
case 0x48:
l2_cache += 3072;
break;
case 0x4e:
l2_cache += 6144;
break;
case 0x23:
case 0xd0:
l3_cache += 512;
break;
case 0xd1:
case 0xd6:
l3_cache += 1024;
break;
case 0x25:
case 0xd2:
case 0xd7:
case 0xdc:
case 0xe2:
l3_cache += 2048;
break;
case 0x29:
case 0x46:
case 0x49:
case 0xd8:
case 0xdd:
case 0xe3:
l3_cache += 4096;
break;
case 0x4a:
l3_cache += 6144;
break;
case 0x47:
case 0x4b:
case 0xde:
case 0xe4:
l3_cache += 8192;
break;
case 0x4c:
case 0xea:
l3_cache += 12288;
break;
case 0x4d:
l3_cache += 16384;
break;
case 0xeb:
l3_cache += 18432;
break;
case 0xec:
l3_cache += 24576;
break;
default:
break;
}
}
} while (++i < dp[0]);
break;
default:
break;
}
}
static void determine_imc(void)
{
// Check AMD IMC
if (cpuid_info.vendor_id.str[0] == 'A' && cpuid_info.version.family == 0xF)
{
switch (cpuid_info.version.extendedFamily)
{
case 0x0:
imc_type = 0x0100; // Old K8
break;
case 0x1:
case 0x2:
imc_type = 0x0101; // K10 (Family 10h & 11h)
break;
case 0x3:
imc_type = 0x0102; // A-Series APU (Family 12h)
break;
case 0x5:
imc_type = 0x0103; // C- / E- / Z- Series APU (Family 14h)
break;
case 0x6:
imc_type = 0x0104; // FX Series (Family 15h)
break;
case 0x7:
imc_type = 0x0105; // Kabini & related (Family 16h)
break;
default:
break;
}
return;
}
// Check Intel IMC
if (cpuid_info.vendor_id.str[0] == 'G' && cpuid_info.version.family == 6 && cpuid_info.version.extendedModel)
{
switch (cpuid_info.version.model) {
case 0x5:
switch (cpuid_info.version.extendedModel) {
case 2:
imc_type = 0x0003; // Core i3/i5 1st Gen 45 nm (NHM)
break;
case 3:
no_temperature = true; // Atom Clover Trail
break;
case 4:
imc_type = 0x0007; // HSW-ULT
break;
default:
break;
}
break;
case 0x6:
if (cpuid_info.version.extendedModel == 3) {
imc_type = 0x0009; // Atom Cedar Trail
no_temperature = true;
}
break;
case 0x7:
if (cpuid_info.version.extendedModel == 3) {
imc_type = 0x000A; // Atom Bay Trail
}
break;
case 0xA:
switch (cpuid_info.version.extendedModel) {
case 0x1:
imc_type = 0x0001; // Core i7 1st Gen 45 nm (NHME)
break;
case 0x2:
imc_type = 0x0004; // Core 2nd Gen (SNB)
break;
case 0x3:
imc_type = 0x0006; // Core 3nd Gen (IVB)
break;
default:
break;
}
break;
case 0xC:
switch (cpuid_info.version.extendedModel) {
case 0x1:
if (cpuid_info.version.stepping > 9) {
imc_type = 0x0008; // Atom PineView
}
no_temperature = true;
break;
case 0x2:
imc_type = 0x0002; // Core i7 1st Gen 32 nm (WMR)
break;
case 0x3:
imc_type = 0x0007; // Core 4nd Gen (HSW)
break;
default:
break;
}
break;
case 0xD:
imc_type = 0x0005; // SNB-E
break;
case 0xE:
imc_type = 0x0001; // Core i7 1st Gen 45 nm (NHM)
break;
default:
break;
}
return;
}
}
static void determine_cpu_model(void)
{
// If we can get a brand string use it, and we are done.
if (cpuid_info.max_xcpuid >= 0x80000004) {
cpu_model = cpuid_info.brand_id.str;
determine_imc();
return;
}
// The brand string is not available so we need to figure out CPU what we have.
switch (cpuid_info.vendor_id.str[0]) {
case 'A':
// AMD Processors
switch (cpuid_info.version.family) {
case 4:
switch (cpuid_info.version.model) {
case 3:
cpu_model = "AMD 486DX2";
break;
case 7:
cpu_model = "AMD 486DX2-WB";
break;
case 8:
cpu_model = "AMD 486DX4";
break;
case 9:
cpu_model = "AMD 486DX4-WB";
break;
case 14:
cpu_model = "AMD 5x86-WT";
break;
case 15:
cpu_model = "AMD 5x86-WB";
break;
default:
break;
}
break;
case 5:
switch (cpuid_info.version.model) {
case 0:
case 1:
case 2:
case 3:
cpu_model = "AMD K5";
l1_cache = 8;
break;
case 6:
case 7:
cpu_model = "AMD K6";
break;
case 8:
cpu_model = "AMD K6-2";
break;
case 9:
cpu_model = "AMD K6-III";
break;
case 13:
cpu_model = "AMD K6-III+";
break;
default:
break;
}
break;
case 6:
switch (cpuid_info.version.model) {
case 1:
cpu_model = "AMD Athlon (0.25)";
break;
case 2:
case 4:
cpu_model = "AMD Athlon (0.18)";
break;
case 6:
if (l2_cache == 64) {
cpu_model = "AMD Duron (0.18)";
} else {
cpu_model = "Athlon XP (0.18)";
}
break;
case 8:
case 10:
if (l2_cache == 64) {
cpu_model = "AMD Duron (0.13)";
} else {
cpu_model = "Athlon XP (0.13)";
}
break;
case 3:
case 7:
cpu_model = "AMD Duron";
// Duron stepping 0 CPUID for L2 is broken (AMD errata T13)
if (cpuid_info.version.stepping == 0) {
// Hard code the right L2 size.
l2_cache = 64;
}
break;
default:
break;
}
break;
default:
// All AMD family values >= 10 have the Brand ID feature so we don't need to find the CPU type.
break;
}
break;
case 'G':
// Transmeta Processors - vendor_id starts with "GenuineTMx86"
if (cpuid_info.vendor_id.str[7] == 'T' ) {
if (cpuid_info.version.family == 5) {
cpu_model = "TM 5x00";
} else if (cpuid_info.version.family == 15) {
cpu_model = "TM 8x00";
}
l1_cache = cpuid_info.cache_info.l1_i_size + cpuid_info.cache_info.l1_d_size;
l2_cache = cpuid_info.cache_info.l2_size;
break;
}
// Intel Processors - vendor_id starts with "GenuineIntel"
switch (cpuid_info.version.family) {
case 4:
switch (cpuid_info.version.model) {
case 0:
case 1:
cpu_model = "Intel 486DX";
break;
case 2:
cpu_model = "Intel 486SX";
break;
case 3:
cpu_model = "Intel 486DX2";
break;
case 4:
cpu_model = "Intel 486SL";
break;
case 5:
cpu_model = "Intel 486SX2";
break;
case 7:
cpu_model = "Intel 486DX2-WB";
break;
case 8:
cpu_model = "Intel 486DX4";
break;
case 9:
cpu_model = "Intel 486DX4-WB";
break;
default:
break;
}
break;
case 5:
switch (cpuid_info.version.model) {
case 0:
case 1:
case 2:
case 3:
case 7:
cpu_model = "Pentium";
if (l1_cache == 0) {
l1_cache = 8;
}
break;
case 4:
case 8:
cpu_model = "Pentium-MMX";
if (l1_cache == 0) {
l1_cache = 16;
}
break;
default:
break;
}
break;
case 6:
switch (cpuid_info.version.model) {
case 0:
case 1:
cpu_model = "Pentium Pro";
break;
case 3:
case 4:
cpu_model = "Pentium II";
break;
case 5:
if (l2_cache == 0) {
cpu_model = "Celeron";
} else {
cpu_model = "Pentium II";
}
break;
case 6:
if (l2_cache == 128) {
cpu_model = "Celeron";
} else {
cpu_model = "Pentium II";
}
break;
case 7:
case 8:
case 11:
if (l2_cache == 128) {
cpu_model = "Celeron";
} else {
cpu_model = "Pentium III";
}
break;
case 9:
if (l2_cache == 512) {
cpu_model = "Celeron M (0.13)";
} else {
cpu_model = "Pentium M (0.13)";
}
break;
case 10:
cpu_model = "Pentium III Xeon";
break;
case 12:
l1_cache = 24;
cpu_model = "Atom (0.045)";
break;
case 13:
if (l2_cache == 1024) {
cpu_model = "Celeron M (0.09)";
} else {
cpu_model = "Pentium M (0.09)";
}
break;
case 14:
cpu_model = "Intel Core";
break;
case 15:
if (l2_cache == 1024) {
cpu_model = "Pentium E";
} else {
cpu_model = "Intel Core 2";
}
break;
default:
break;
}
break;
case 15:
switch (cpuid_info.version.model) {
case 0:
case 1:
case 2:
if (l2_cache == 128) {
cpu_model = "Celeron";
} else {
cpu_model = "Pentium 4";
}
break;
case 3:
case 4:
if (l2_cache == 256) {
cpu_model = "Celeron (0.09)";
} else {
cpu_model = "Pentium 4 (0.09)";
}
break;
case 6:
cpu_model = "Pentium D (65nm)";
break;
default:
cpu_model = "Unknown Intel";
break;
}
break;
default:
break;
}
break;
case 'C':
// VIA/Cyrix/Centaur Processors with CPUID
if (cpuid_info.vendor_id.str[1] == 'e' ) {
// CentaurHauls
l1_cache = cpuid_info.cache_info.l1_i_size + cpuid_info.cache_info.l1_d_size;
l2_cache = cpuid_info.cache_info.l2_size >> 8;
switch (cpuid_info.version.family) {
case 5:
cpu_model = "Centaur 5x86";
break;
case 6: // VIA C3
switch (cpuid_info.version.model) {
case 10:
cpu_model = "VIA C7 (C5J)";
l1_cache = 64;
l2_cache = 128;
break;
case 13:
cpu_model = "VIA C7 (C5R)";
l1_cache = 64;
l2_cache = 128;
break;
case 15:
cpu_model = "VIA Isaiah (CN)";
l1_cache = 64;
l2_cache = 128;
break;
default:
if (cpuid_info.version.stepping < 8) {
cpu_model = "VIA C3 Samuel2";
} else {
cpu_model = "VIA C3 Eden";
}
break;
}
default:
break;
}
} else { /* CyrixInstead */
switch (cpuid_info.version.family) {
case 5:
switch (cpuid_info.version.model) {
case 0:
cpu_model = "Cyrix 6x86MX/MII";
break;
case 4:
cpu_model = "Cyrix GXm";
break;
default:
break;
}
break;
case 6: // VIA C3
switch (cpuid_info.version.model) {
case 6:
cpu_model = "Cyrix III";
break;
case 7:
if (cpuid_info.version.stepping < 8) {
cpu_model = "VIA C3 Samuel2";
} else {
cpu_model = "VIA C3 Ezra-T";
}
break;
case 8:
cpu_model = "VIA C3 Ezra-T";
break;
case 9:
cpu_model = "VIA C3 Nehemiah";
break;
default:
break;
}
// L1 = L2 = 64 KB from Cyrix III to Nehemiah
l1_cache = 64;
l2_cache = 64;
break;
default:
break;
}
}
break;
default:
// Unknown processor - make a guess at the family.
switch (cpuid_info.version.family) {
case 5:
cpu_model = "586";
break;
case 6:
cpu_model = "686";
break;
default:
cpu_model = "Unidentified Processor";
break;
}
break;
}
}
static void measure_cpu_speed(void)
{
if (cpuid_info.flags.rdtsc == 0) {
return;
}
// Set up timer
outb((inb(0x61) & ~0x02) | 0x01, 0x61);
outb(0xb0, 0x43);
outb(PIT_TICKS_50mS & 0xff, 0x42);
outb(PIT_TICKS_50mS >> 8, 0x42);
uint32_t start_time;
rdtscl(start_time);
int loops = 0;
do {
loops++;
} while ((inb(0x61) & 0x20) == 0);
uint32_t end_time;
rdtscl(end_time);
uint32_t run_time = end_time - start_time;
// Make sure we have a credible result
if (loops >= 4 && run_time >= 50000) {
clks_per_msec = run_time / 50;
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void cpuinfo_init(void)
{
// Get cache sizes for most AMD and Intel CPUs. Exceptions for old
// CPUs are handled in determine_cpu_model().
determine_cache_size();
determine_cpu_model();
measure_cpu_speed();
}

53
system/cpuinfo.h Normal file
View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef CPUINFO_H
#define CPUINFO_H
/*
* Provides information about the CPU type, clock speed and cache sizes.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdbool.h>
#include <stdint.h>
/*
* A string identifying the CPU make and model.
*/
extern const char *cpu_model;
/*
* A number identifying the integrated memory controller type.
*/
extern uint32_t imc_type;
/*
* The size of the L1 cache in KB.
*/
extern int l1_cache;
/*
* The size of the L2 cache in KB.
*/
extern int l2_cache;
/*
* The size of the L3 cache in KB.
*/
extern int l3_cache;
/*
* A flag indicating that we can't read the core temperature on this CPU.
*/
extern bool no_temperature;
/*
* The TSC clock speed in kHz. Assumed to be the nominal CPU clock speed.
*/
extern uint32_t clks_per_msec;
/*
* Determines the CPU info and stores it in the exported variables.
*/
void cpuinfo_init(void);
#endif // CPUINFO_H

4617
system/font.c Normal file

File diff suppressed because it is too large Load Diff

24
system/font.h Normal file
View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef FONT_H
#define FONT_H
/*
* Provides the font used for framebuffer display.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
/*
* Font size definitions.
*/
#define FONT_WIDTH 8
#define FONT_HEIGHT 16
#define FONT_CHARS 256
/*
* The font data.
*/
extern const uint8_t font_data[FONT_CHARS][FONT_HEIGHT];
#endif // FONT_H

44
system/hwctrl.c Normal file
View File

@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ lib.c:
//
// lib.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdint.h>
#include "io.h"
#include "hwctrl.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void reboot(void)
{
// Tell the BIOS to do a warm reboot.
*((uint16_t *)0x472) = 0x1234;
// Pulse the system reset signal.
outb(0xfe, 0x64);
}
void floppy_off()
{
// Stop the floppy motor.
outb(0x8, 0x3f2);
}
void cursor_off()
{
// Set HW cursor off screen.
outb(0x0f, 0x3d4);
outb(0xff, 0x3d5);
outb(0x0e, 0x3d4);
outb(0xff, 0x3d5);
}

25
system/hwctrl.h Normal file
View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef HWCTRL_H
#define HWCTRL_H
/*
* Provides miscellaneous hardware control functions.
*
* Copyright (C) 2020 Martin Whitaker.
*/
/*
* Reboots the machine.
*/
void reboot(void);
/*
* Turns off the floppy motor.
*/
void floppy_off();
/*
* Disables the screen cursor.
*/
void cursor_off();
#endif // HWCTRL_H

145
system/io.h Normal file
View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef IO_H
#define IO_H
/*
* Provides macro definitions for the x86 IO instructions
* inb/inw/inl/outb/outw/outl and the "string versions" of the same
* (insb/insw/insl/outsb/outsw/outsl). You can also use "pausing"
* versions of the single-IO instructions (inb_p/inw_p/..).
*
* This file is not meant to be obfuscating: it's just complicated
* to (a) handle it all in a way that makes gcc able to optimize it
* as well as possible and (b) trying to avoid writing the same thing
* over and over again with slight variations and possibly making a
* mistake somewhere.
*
* Derived from memtest86+ io.h.
* (original contained no copyright statement)
*/
#ifdef SLOW_IO_BY_JUMPING
#define __SLOW_DOWN_IO __asm__ __volatile__("jmp 1f\n1:\tjmp 1f\n1:")
#else
#define __SLOW_DOWN_IO __asm__ __volatile__("outb %al,$0x80")
#endif
#ifdef REALLY_SLOW_IO
#define SLOW_DOWN_IO { __SLOW_DOWN_IO; __SLOW_DOWN_IO; __SLOW_DOWN_IO; __SLOW_DOWN_IO; }
#else
#define SLOW_DOWN_IO __SLOW_DOWN_IO
#endif
#define __OUT1(s,x) \
static inline void __out##s(unsigned x value, unsigned short port) {
#define __OUT2(s,s1,s2) \
__asm__ __volatile__ ("out" #s " %" s1 "0,%" s2 "1"
#define __OUT(s,s1,x) \
__OUT1(s,x) __OUT2(s,s1,"w") : : "a" (value), "d" (port)); } \
__OUT1(s##c,x) __OUT2(s,s1,"") : : "a" (value), "id" (port)); } \
__OUT1(s##_p,x) __OUT2(s,s1,"w") : : "a" (value), "d" (port)); SLOW_DOWN_IO; } \
__OUT1(s##c_p,x) __OUT2(s,s1,"") : : "a" (value), "id" (port)); SLOW_DOWN_IO; }
#define __IN1(s) \
static inline RETURN_TYPE __in##s(unsigned short port) { RETURN_TYPE _v;
#define __IN2(s,s1,s2) \
__asm__ __volatile__ ("in" #s " %" s2 "1,%" s1 "0"
#define __IN(s,s1,i...) \
__IN1(s) __IN2(s,s1,"w") : "=a" (_v) : "d" (port) ,##i ); return _v; } \
__IN1(s##c) __IN2(s,s1,"") : "=a" (_v) : "id" (port) ,##i ); return _v; } \
__IN1(s##_p) __IN2(s,s1,"w") : "=a" (_v) : "d" (port) ,##i ); SLOW_DOWN_IO; return _v; } \
__IN1(s##c_p) __IN2(s,s1,"") : "=a" (_v) : "id" (port) ,##i ); SLOW_DOWN_IO; return _v; }
#define __OUTS(s) \
static inline void outs##s(unsigned short port, const void * addr, unsigned long count) \
{ __asm__ __volatile__ ("cld ; rep ; outs" #s \
: "=S" (addr), "=c" (count) : "d" (port),"0" (addr),"1" (count)); }
#define RETURN_TYPE unsigned char
/* __IN(b,"b","0" (0)) */
__IN(b,"")
#undef RETURN_TYPE
#define RETURN_TYPE unsigned short
/* __IN(w,"w","0" (0)) */
__IN(w,"")
#undef RETURN_TYPE
#define RETURN_TYPE unsigned int
__IN(l,"")
#undef RETURN_TYPE
__OUT(b,"b",char)
__OUT(w,"w",short)
__OUT(l,,int)
__OUTS(b)
__OUTS(w)
__OUTS(l)
/*
* Note that due to the way __builtin_constant_p() works, you
* - can't use it inside a inline function (it will never be true)
* - you don't have to worry about side effects within the __builtin..
*/
#define outb(val,port) \
((__builtin_constant_p((port)) && (port) < 256) ? \
__outbc((val),(port)) : \
__outb((val),(port)))
#define inb(port) \
((__builtin_constant_p((port)) && (port) < 256) ? \
__inbc(port) : \
__inb(port))
#define outw(val,port) \
((__builtin_constant_p((port)) && (port) < 256) ? \
__outwc((val),(port)) : \
__outw((val),(port)))
#define inw(port) \
((__builtin_constant_p((port)) && (port) < 256) ? \
__inwc(port) : \
__inw(port))
#define outl(val,port) \
((__builtin_constant_p((port)) && (port) < 256) ? \
__outlc((val),(port)) : \
__outl((val),(port)))
#define inl(port) \
((__builtin_constant_p((port)) && (port) < 256) ? \
__inlc(port) : \
__inl(port))
static __inline unsigned char
inb_p (unsigned short int __port)
{
unsigned char _v;
__asm__ __volatile__ ("\t"
"inb %w1,%0 \n\t"
"outb %%al,$0x80 \n"
: "=a" (_v)
: "Nd" (__port)
);
return _v;
}
static __inline void
outb_p (unsigned char __value, unsigned short int __port)
{
__asm__ __volatile__ ("\t"
"outb %b0,%w1 \n\t"
"outb %%al,$0x80 \n"
: /* no outputs */
: "a" (__value),
"Nd" (__port)
);
}
#endif // IO_H

117
system/keyboard.c Normal file
View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
#include <stdint.h>
#include "io.h"
#include "keyboard.h"
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
// Convert set 1 scancodes to characters.
static const char keymap[] = {
/* 0x00 */ 0,
/* 0x01 */ ESC,
/* 0x02 */ '1',
/* 0x03 */ '2',
/* 0x04 */ '3',
/* 0x05 */ '4',
/* 0x06 */ '5',
/* 0x07 */ '6',
/* 0x08 */ '7',
/* 0x09 */ '8',
/* 0x0a */ '9',
/* 0x0b */ '0',
/* 0x0c */ '-',
/* 0x0d */ '+',
/* 0x0e */ '\b',
/* 0x0f */ '\t',
/* 0x10 */ 'q',
/* 0x11 */ 'w',
/* 0x12 */ 'e',
/* 0x13 */ 'r',
/* 0x14 */ 't',
/* 0x15 */ 'y',
/* 0x16 */ 'u',
/* 0x17 */ 'i',
/* 0x18 */ 'o',
/* 0x19 */ 'p',
/* 0x1a */ '[',
/* 0x1b */ ']',
/* 0x1c */ '\n',
/* 0x1d */ 0,
/* 0x1e */ 'a',
/* 0x1f */ 's',
/* 0x20 */ 'd',
/* 0x21 */ 'f',
/* 0x22 */ 'g',
/* 0x23 */ 'h',
/* 0x24 */ 'j',
/* 0x25 */ 'k',
/* 0x26 */ 'l',
/* 0x27 */ ';',
/* 0x28 */ '\'',
/* 0x29 */ '`',
/* 0x2a */ 0,
/* 0x2b */ '\\',
/* 0x2c */ 'z',
/* 0x2d */ 'x',
/* 0x2e */ 'c',
/* 0x2f */ 'v',
/* 0x30 */ 'b',
/* 0x31 */ 'n',
/* 0x32 */ 'm',
/* 0x33 */ ',',
/* 0x34 */ '.',
/* 0x35 */ '/',
/* 0x36 */ 0,
/* 0x37 */ '*', // keypad
/* 0x38 */ 0,
/* 0x39 */ ' ',
/* 0x3a */ 0,
/* 0x3b */ '1',
/* 0x3c */ '2',
/* 0x3d */ '3',
/* 0x3e */ '4',
/* 0x3f */ '5',
/* 0x40 */ '6',
/* 0x41 */ '7',
/* 0x42 */ '8',
/* 0x43 */ '9',
/* 0x44 */ '0',
/* 0x45 */ 0,
/* 0x46 */ 0,
/* 0x47 */ '7', // keypad
/* 0x48 */ '8', // keypad
/* 0x49 */ '9', // keypad
/* 0x4a */ '-', // keypad
/* 0x4b */ '4', // keypad
/* 0x4c */ '5', // keypad
/* 0x4d */ '6', // keypad
/* 0x4e */ '+', // keypad
/* 0x4f */ '1', // keypad
/* 0x50 */ '2', // keypad
/* 0x51 */ '3', // keypad
/* 0x52 */ '0', // keypad
/* 0x53 */ '.', // keypad
};
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
char get_key(void)
{
uint8_t c = inb(0x64);
if (c & 0x01) {
c = inb(0x60);
if (c < sizeof(keymap)) {
return keymap[c];
}
// Ignore keys we don't recognise and key up codes
}
return '\0';
}

26
system/keyboard.h Normal file
View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef KEYBOARD_H
#define KEYBOARD_H
/*
* Provides the keyboard interface. It converts incoming key codes to
* ASCII characters.
*
* Copyright (C) 2020 Martin Whitaker.
*/
/*
* The Escape character.
*/
#define ESC 27
/*
* Checks if a key has been pressed and returns the primary ASCII character
* corresponding to that key if so, otherwise returns the null character.
* F1 to F10 are mapped to the corresponding decimal digit (F10 -> 0). All
* other keys that don't have a corresponding ASCII character are ignored.
* Characters are only returned for key presses, not key releases. A US
* keyboard layout is assumed (because we can't easily do anything else).
*/
char get_key(void);
#endif // KEYBOARD_H

36
system/memsize.h Normal file
View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef MEMSIZE_H
#define MEMSIZE_H
/*
* Provides some convenient constants and constant constructors.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stddef.h>
#include <stdint.h>
#define KB 10
#define MB 20
#define GB 30
#define TB 40
#define VM_PAGE_SHIFT 21
#define VM_PAGE_SIZE (1 << VM_PAGE_SHIFT)
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define VM_PAGE_C(size, units) \
(units < VM_PAGE_SHIFT ? (uintptr_t)(size) << (VM_PAGE_SHIFT - units) : (uintptr_t)(size) << (units - VM_PAGE_SHIFT))
#define PAGE_C(size, units) \
(units < PAGE_SHIFT ? (uintptr_t)(size) << (PAGE_SHIFT - units) : (uintptr_t)(size) << (units - PAGE_SHIFT))
#define ADDR_C(size, units) \
((uintptr_t)(size) << units)
#define SIZE_C(size, units) \
((size_t)(size) << units)
#endif // MEMSIZE_H

45
system/msr.h Normal file
View File

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef MSR_H
#define MSR_H
/*
* Provides access to the CPU machine-specific registers.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#define MSR_PLATFORM_INFO 0xce
#define MSR_EBC_FREQUENCY_ID 0x2c
#define MSR_IA32_PLATFORM_ID 0x17
#define MSR_IA32_EBL_CR_POWERON 0x2a
#define MSR_IA32_MCG_CTL 0x17b
#define MSR_IA32_PERF_STATUS 0x198
#define MSR_IA32_THERM_STATUS 0x19c
#define MSR_IA32_TEMPERATURE_TARGET 0x1a2
#define MSR_EFER 0xc0000080
#define MSR_K7_HWCR 0xc0010015
#define MSR_K7_VID_STATUS 0xc0010042
#define MSR_AMD64_NB_CFG 0xc001001f
#define MSR_AMD64_COFVID_STATUS 0xc0010071
#define rdmsr(msr, value1, value2) \
__asm__ __volatile__("rdmsr" \
: "=a" (value1), \
"=d" (value2) \
: "c" (msr) \
: "edi" \
)
#define wrmsr(msr, value1, value2) \
__asm__ __volatile__("wrmsr" \
: /* no outputs */ \
: "c" (msr), \
"a" (value1), \
"d" (value2) \
)
#endif // MSR_H

193
system/pci.c Normal file
View File

@@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ pci.c:
//
// MemTest86+ V5.00 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.x86-secret.com - http://www.memtest.org
// ----------------------------------------------------
// pci.c - MemTest-86 Version 3.2
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdint.h>
#include "cpuid.h"
#include "io.h"
#include "pci.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define PCI_CONF_TYPE_NONE 0
#define PCI_CONF_TYPE_1 1
#define PCI_CONF_TYPE_2 2
#define PCI_CLASS_DEVICE 0x0a
#define PCI_CLASS_BRIDGE_HOST 0x0600
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static unsigned char pci_conf_type = PCI_CONF_TYPE_NONE;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
#define PCI_CONF1_ADDRESS(bus, dev, fn, reg) \
(0x80000000 | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))
#define PCI_CONF2_ADDRESS(dev, reg) (unsigned short)(0xC000 | (dev << 8) | reg)
#define PCI_CONF3_ADDRESS(bus, dev, fn, reg) \
(0x80000000 | (((reg >> 8) & 0xF) << 24) | (bus << 16) | ((dev & 0x1F) << 11) | (fn << 8) | (reg & 0xFF))
static int pci_sanity_check(void)
{
/* Do a trivial check to make certain we can see a host bridge.
* There are reportedly some buggy chipsets from intel and
* compaq where this test does not work, I will worry about
* that when we support them.
*/
uint32_t value;
int result = pci_conf_read(0, 0, 0, PCI_CLASS_DEVICE, 2, &value);
if (result == 0) {
result = -1;
if (value == PCI_CLASS_BRIDGE_HOST) {
result = 0;
}
}
return result;
}
static int pci_check_direct(void)
{
unsigned char tmpCFB;
unsigned int tmpCF8;
if (cpuid_info.vendor_id.str[0] == 'A' && cpuid_info.version.family == 0xF) {
pci_conf_type = PCI_CONF_TYPE_1;
return 0;
} else {
/* Check if configuration type 1 works. */
pci_conf_type = PCI_CONF_TYPE_1;
tmpCFB = inb(0xCFB);
outb(0x01, 0xCFB);
tmpCF8 = inl(0xCF8);
outl(0x80000000, 0xCF8);
if ((inl(0xCF8) == 0x80000000) && (pci_sanity_check() == 0)) {
outl(tmpCF8, 0xCF8);
outb(tmpCFB, 0xCFB);
return 0;
}
outl(tmpCF8, 0xCF8);
/* Check if configuration type 2 works. */
pci_conf_type = PCI_CONF_TYPE_2;
outb(0x00, 0xCFB);
outb(0x00, 0xCF8);
outb(0x00, 0xCFA);
if (inb(0xCF8) == 0x00 && inb(0xCFA) == 0x00 && (pci_sanity_check() == 0)) {
outb(tmpCFB, 0xCFB);
return 0;
}
outb(tmpCFB, 0xCFB);
/* Nothing worked return an error */
pci_conf_type = PCI_CONF_TYPE_NONE;
return -1;
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int pci_init(void)
{
// For now just make certain we can directly use the pci functions.
return pci_check_direct();
}
int pci_conf_read(unsigned bus, unsigned dev, unsigned fn, unsigned reg, unsigned len, uint32_t *value)
{
int result;
if (!value || (bus > 255) || (dev > 31) || (fn > 7) || (reg > 255 && pci_conf_type != PCI_CONF_TYPE_1))
return -1;
result = -1;
switch (pci_conf_type) {
case PCI_CONF_TYPE_1:
if (reg < 256) {
outl(PCI_CONF1_ADDRESS(bus, dev, fn, reg), 0xCF8);
} else {
outl(PCI_CONF3_ADDRESS(bus, dev, fn, reg), 0xCF8);
}
switch (len) {
case 1: *value = inb(0xCFC + (reg & 3)); result = 0; break;
case 2: *value = inw(0xCFC + (reg & 2)); result = 0; break;
case 4: *value = inl(0xCFC); result = 0; break;
}
break;
case PCI_CONF_TYPE_2:
outb(0xF0 | (fn << 1), 0xCF8);
outb(bus, 0xCFA);
switch (len) {
case 1: *value = inb(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
case 2: *value = inw(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
case 4: *value = inl(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
}
outb(0, 0xCF8);
break;
}
return result;
}
int pci_conf_write(unsigned bus, unsigned dev, unsigned fn, unsigned reg, unsigned len, uint32_t value)
{
int result;
if (!value || (bus > 255) || (dev > 31) || (fn > 7) || (reg > 255 && pci_conf_type != PCI_CONF_TYPE_1))
return -1;
result = -1;
switch (pci_conf_type)
{
case PCI_CONF_TYPE_1:
if (reg < 256) {
outl(PCI_CONF1_ADDRESS(bus, dev, fn, reg), 0xCF8);
} else {
outl(PCI_CONF3_ADDRESS(bus, dev, fn, reg), 0xCF8);
}
switch (len) {
case 1: outb(value, 0xCFC + (reg & 3)); result = 0; break;
case 2: outw(value, 0xCFC + (reg & 2)); result = 0; break;
case 4: outl(value, 0xCFC); result = 0; break;
}
break;
case PCI_CONF_TYPE_2:
outb(0xF0 | (fn << 1), 0xCF8);
outb(bus, 0xCFA);
switch (len) {
case 1: outb(value, PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
case 2: outw(value, PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
case 4: outl(value, PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
}
outb(0, 0xCF8);
break;
}
return result;
}

18
system/pci.h Normal file
View File

@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef PCI_H
#define PCI_H
/*
* Provides functions to perform PCI config space reads and writes.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
int pci_init(void);
int pci_conf_read(unsigned bus, unsigned dev, unsigned fn, unsigned reg, unsigned len, uint32_t *value);
int pci_conf_write(unsigned bus, unsigned dev, unsigned fn, unsigned reg, unsigned len, uint32_t value);
#endif // PCI_H

289
system/pmem.c Normal file
View File

@@ -0,0 +1,289 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ memsize.c
//
// memsize.c - MemTest-86 Version 3.3
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "boot.h"
#include "memsize.h"
#include "string.h"
#include "pmem.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// The reserved memory starting at 640KB.
#define RES_START 0x0a0000
#define RES_END 0x100000
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
// The following definition must match the Linux e820_type enum.
typedef enum {
E820_NONE = 0,
E820_RAM = 1,
E820_RESERVED = 2,
E820_ACPI = 3, // usable as RAM once ACPI tables have been read
E820_NVS = 4
} e820_type_t;
// The following definition must match the Linux e820_entry struct.
typedef struct {
uint64_t addr;
uint64_t size;
uint32_t type;
} __attribute__((packed)) e820_entry_t;
//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------
pm_map_t pm_map[MAX_MEM_SEGMENTS];
int pm_map_size = 0;
size_t num_pm_pages = 0;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
// Some PC BIOS e820 responses include overlapping entries.
// Here we create a new map with the overlaps removed.
static int sanitize_e820_map(e820_entry_t new_map[], const e820_entry_t orig_map[], int orig_entries)
{
struct change_member {
const e820_entry_t *entry; // pointer to original bios entry
bool start; // true = start addr, false = end addr
uint64_t addr; // address for this change point
};
// Make these arrays static to keep the stack use small.
// This function does not need to be reentrant.
static struct change_member change_point_list[2*E820_MAP_SIZE];
static struct change_member *change_point[2*E820_MAP_SIZE];
struct change_member *change_tmp;
static const e820_entry_t *overlap_list[E820_MAP_SIZE];
/*
Visually we're performing the following (1,2,3,4 = memory types)...
Sample memory map (w/overlaps):
____22__________________
______________________4_
____1111________________
_44_____________________
11111111________________
____________________33__
___________44___________
__________33333_________
______________22________
___________________2222_
_________111111111______
_____________________11_
_________________4______
Sanitized equivalent (no overlap):
1_______________________
_44_____________________
___1____________________
____22__________________
______11________________
_________1______________
__________3_____________
___________44___________
_____________33_________
_______________2________
________________1_______
_________________4______
___________________2____
____________________33__
______________________4_
*/
// Bail out if we find any unreasonable addresses in the original map.
for (int i = 0; i < orig_entries; i++) {
if (orig_map[i].addr + orig_map[i].size < orig_map[i].addr) {
return 0;
}
}
// Create pointers for initial change-point information (for sorting).
for (int i = 0; i < 2*orig_entries; i++) {
change_point[i] = &change_point_list[i];
}
// Record all known change-points (starting and ending addresses).
int chg_idx = 0;
for (int i = 0; i < orig_entries; i++) {
change_point[chg_idx]->addr = orig_map[i].addr;
change_point[chg_idx]->start = true;
change_point[chg_idx]->entry = &orig_map[i];
chg_idx++;
change_point[chg_idx]->addr = orig_map[i].addr + orig_map[i].size;
change_point[chg_idx]->start = false;
change_point[chg_idx]->entry = &orig_map[i];
chg_idx++;
}
// Sort change-point list by memory addresses (low -> high).
bool still_changing = true;
while (still_changing) {
still_changing = false;
for (int i = 1; i < 2*orig_entries; i++) {
// If current_addr > last_addr or if current_addr = last_addr
// and current is a start addr and last is an end addr, swap.
if ((change_point[i]->addr < change_point[i-1]->addr)
|| ((change_point[i]->addr == change_point[i-1]->addr) && change_point[i]->start && !change_point[i-1]->start)) {
change_tmp = change_point[i];
change_point[i] = change_point[i-1];
change_point[i-1] = change_tmp;
still_changing = true;
}
}
}
// Create a new bios memory map, removing overlaps.
int overlap_entries = 0;
int new_map_entries = 0;
uint64_t last_addr = 0;
uint32_t last_type = E820_NONE;
// Loop through change-points, determining effect on the new map.
for (chg_idx = 0; chg_idx < 2*orig_entries; chg_idx++) {
// Keep track of all overlapping entries.
if (change_point[chg_idx]->start) {
// Add map entry to overlap list (> 1 entry implies an overlap)
overlap_list[overlap_entries++] = change_point[chg_idx]->entry;
} else {
// Remove entry from list (order independent, so swap with last)
for (int i = 0; i < overlap_entries; i++) {
if (overlap_list[i] == change_point[chg_idx]->entry) {
overlap_list[i] = overlap_list[overlap_entries-1];
}
}
overlap_entries--;
}
// If there are overlapping entries, decide which "type" to use
// (larger value takes precedence -- 1=usable, 2,3,4,4+=unusable)
uint32_t current_type = E820_NONE;
for (int i = 0; i < overlap_entries; i++) {
if (overlap_list[i]->type > current_type) {
current_type = overlap_list[i]->type;
}
}
// Continue building up new map based on this information.
if (current_type != last_type) {
if (last_type != E820_NONE) {
new_map[new_map_entries].size = change_point[chg_idx]->addr - last_addr;
// Move forward only if the new size was non-zero
if (new_map[new_map_entries].size != 0) {
if (++new_map_entries >= E820_MAP_SIZE) {
break;
}
}
}
if (current_type != E820_NONE) {
new_map[new_map_entries].addr = change_point[chg_idx]->addr;
new_map[new_map_entries].type = current_type;
last_addr = change_point[chg_idx]->addr;
}
last_type = current_type;
}
}
return new_map_entries;
}
static void init_pm_map(const e820_entry_t e820_map[], int e820_entries)
{
pm_map_size = 0;
for (int i = 0; i < e820_entries; i++) {
if (e820_map[i].type == E820_RAM || e820_map[i].type == E820_ACPI) {
uint64_t start = e820_map[i].addr;
uint64_t end = start + e820_map[i].size;
// Don't ever use memory between 640KB and 1024KB
if (start > RES_START && start < RES_END) {
if (end < RES_END) {
continue;
}
start = RES_END;
}
if (end > RES_START && end < RES_END) {
end = RES_START;
}
pm_map[pm_map_size].start = (start + PAGE_SIZE - 1) >> PAGE_SHIFT;
pm_map[pm_map_size].end = end >> PAGE_SHIFT;
num_pm_pages += pm_map[pm_map_size].end - pm_map[pm_map_size].start;
if ((pm_map_size > 0) && (pm_map[pm_map_size].start == pm_map[pm_map_size - 1].end)) {
pm_map[pm_map_size - 1].end = pm_map[pm_map_size].end;
} else {
pm_map_size++;
}
}
}
}
static void sort_pm_map(void)
{
// Do an insertion sort on the pm_map. On an already sorted list this should be a O(1) algorithm.
for (int i = 0; i < pm_map_size; i++) {
// Find where to insert the current element.
int j = i - 1;
while (j >= 0) {
if (pm_map[i].start > pm_map[j].start) {
j++;
break;
}
j--;
}
// Insert the current element.
if (i != j) {
pm_map_t temp;
temp = pm_map[i];
memmove(&pm_map[j], &pm_map[j+1], (i - j) * sizeof(temp));
pm_map[j] = temp;
}
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void pmem_init(void)
{
e820_entry_t sanitized_map[E820_MAP_SIZE];
num_pm_pages = 0;
const e820_entry_t *e820_map = (e820_entry_t *)(boot_params_addr + E820_MAP);
int e820_entries = *(uint8_t *)(boot_params_addr + E820_ENTRIES);
int sanitized_entries = sanitize_e820_map(sanitized_map, e820_map, e820_entries);
init_pm_map(sanitized_map, sanitized_entries);
sort_pm_map();
}

27
system/pmem.h Normal file
View File

@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef PMEM_H
#define PMEM_H
/*
* Provides a description of the system physical memory map.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stddef.h>
#include <stdint.h>
#define MAX_MEM_SEGMENTS 127
typedef struct {
uintptr_t start;
uintptr_t end;
} pm_map_t;
extern pm_map_t pm_map[MAX_MEM_SEGMENTS];
extern int pm_map_size;
extern size_t num_pm_pages;
void pmem_init(void);
#endif /* PMEM_H */

172
system/reloc32.c Normal file
View File

@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ reloc.c:
//
// reloc.c - MemTest-86 Version 3.3
//
// Released under version 2 of the Gnu Public License.
// By Eric Biederman
#include <stddef.h>
#include <stdint.h>
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// Dynamic section tag values
#define DT_NULL 0 // End of dynamic section
#define DT_PLTRELSZ 2 // Size in bytes of PLT relocs
#define DT_REL 17 // Address of Rel relocs
#define DT_RELSZ 18 // Total size of Rel relocs
#define DT_RELENT 19
#define DT_PLTREL 20 // Type of reloc in PLT
#define DT_JMPREL 23 // Address of PLT relocs
#define DT_NUM 34 // Number of tag values
// Relocation types
#define R_386_NONE 0
#define R_386_RELATIVE 8
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef uint32_t Elf32_Addr;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf32_Word;
typedef struct
{
Elf32_Sword d_tag;
union
{
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
typedef struct
{
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
#define ELF32_R_TYPE(r_info) ((r_info) & 0xff)
static inline void assert(int expr)
{
if (!expr) {
__asm__ __volatile__ ("int $3");
}
}
/*
* Return the run-time load address of the shared object. This must be inlined
* in a function which uses global data.
*/
static inline Elf32_Addr __attribute__ ((unused)) get_load_address(void)
{
Elf32_Addr addr;
__asm__ __volatile__ (
"leal _start@GOTOFF(%%ebx), %0"
: "=r" (addr)
:
: "cc"
);
return addr;
}
/*
* Return the link-time address of _DYNAMIC. Conveniently, this is the first
* element of the GOT. This must be inlined in a function which uses global
* data.
*/
static inline Elf32_Addr __attribute__ ((unused)) get_dynamic_section_offset(void)
{
register Elf32_Addr *got __asm__ ("%ebx");
return *got;
}
static void get_dynamic_info(Elf32_Dyn *dyn_section, Elf32_Addr load_offs, Elf32_Dyn *dyn_info[DT_NUM])
{
Elf32_Dyn *dyn = dyn_section;
while (dyn->d_tag != DT_NULL) {
if (dyn->d_tag < DT_NUM) {
dyn_info[dyn->d_tag] = dyn;
}
dyn++;
}
if (dyn_info[DT_REL] != NULL) {
assert(dyn_info[DT_RELENT]->d_un.d_val == sizeof(Elf32_Rel));
dyn_info[DT_REL]->d_un.d_ptr += load_offs;
}
if (dyn_info[DT_PLTREL] != NULL) {
assert(dyn_info[DT_PLTREL]->d_un.d_val == DT_REL);
}
if (dyn_info[DT_JMPREL] != NULL) {
dyn_info[DT_JMPREL]->d_un.d_ptr += load_offs;
}
}
static void do_relocation(Elf32_Addr load_addr, Elf32_Addr load_offs, const Elf32_Rel *rel)
{
Elf32_Addr *target_addr = (Elf32_Addr *)(load_addr + rel->r_offset);
if (ELF32_R_TYPE(rel->r_info) == R_386_RELATIVE) {
*target_addr += load_offs;
return;
}
if (ELF32_R_TYPE(rel->r_info) == R_386_NONE) {
return;
}
assert(! "unexpected dynamic reloc type");
}
static void do_relocations(Elf32_Addr load_addr, Elf32_Addr load_offs, Elf32_Addr rel_addr, Elf32_Addr rel_size)
{
const Elf32_Rel *rel_start = (const Elf32_Rel *)(rel_addr);
const Elf32_Rel *rel_end = (const Elf32_Rel *)(rel_addr + rel_size);
for (const Elf32_Rel *rel = rel_start; rel < rel_end; rel++) {
do_relocation(load_addr, load_offs, rel);
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void reloc(void)
{
static volatile Elf32_Addr last_load_addr = 0;
Elf32_Dyn *dyn_info[DT_NUM];
for (int i = 0; i < DT_NUM; i++) {
dyn_info[i] = NULL;
}
Elf32_Addr load_addr = get_load_address();
Elf32_Addr load_offs = load_addr - last_load_addr;
if (load_addr == last_load_addr) {
return;
}
last_load_addr = load_addr;
Elf32_Dyn *dyn_section = (Elf32_Dyn *)(load_addr + get_dynamic_section_offset());
get_dynamic_info(dyn_section, load_offs, dyn_info);
do_relocations(load_addr, load_offs, dyn_info[DT_REL]->d_un.d_ptr, dyn_info[DT_RELSZ]->d_un.d_val);
if (dyn_info[DT_PLTREL]->d_un.d_val == DT_REL) {
do_relocations(load_addr, load_offs, dyn_info[DT_JMPREL]->d_un.d_ptr, dyn_info[DT_PLTRELSZ]->d_un.d_val);
}
}

181
system/reloc64.c Normal file
View File

@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ reloc.c:
//
// reloc.c - MemTest-86 Version 3.3
//
// Released under version 2 of the Gnu Public License.
// By Eric Biederman
#include <stddef.h>
#include <stdint.h>
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// Dynamic section tag values
#define DT_NULL 0 // End of dynamic section
#define DT_PLTRELSZ 2 // Size in bytes of PLT relocs
#define DT_RELA 7 // Address of Rel relocs
#define DT_RELASZ 8 // Total size of Rel relocs
#define DT_RELAENT 9
#define DT_PLTREL 20 // Type of reloc in PLT
#define DT_JMPREL 23 // Address of PLT relocs
#define DT_NUM 34 // Number of tag values
// Relocation types
#define R_X86_64_NONE 0
#define R_X86_64_RELATIVE 8
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef uint64_t Elf64_Addr;
typedef int64_t Elf64_Sxword;
typedef uint64_t Elf64_Xword;
typedef struct
{
Elf64_Sxword d_tag;
union
{
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
typedef struct
{
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
} Elf64_Rela;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
#define ELF64_R_TYPE(r_info) ((r_info) & 0xffffffff)
static inline void assert(int expr)
{
if (!expr) {
__asm__ __volatile__ ("int $3");
}
}
/*
* Return the run-time load address of the shared object.
*/
static inline Elf64_Addr __attribute__ ((unused)) get_load_address(void)
{
Elf64_Addr addr;
__asm__ __volatile__ (
"leaq _start(%%rip), %0"
: "=r" (addr)
:
: "cc"
);
return addr;
}
/*
* Return the link-time address of _DYNAMIC. Conveniently, this is the first
* element of the GOT.
*/
static inline Elf64_Addr __attribute__ ((unused)) get_dynamic_section_offset(void)
{
Elf64_Addr offs;
__asm__ __volatile__ (
"movq _GLOBAL_OFFSET_TABLE_(%%rip), %0"
: "=r" (offs)
:
: "cc"
);
return offs;
}
static void get_dynamic_info(Elf64_Dyn *dyn_section, Elf64_Addr load_offs, Elf64_Dyn *dyn_info[DT_NUM])
{
Elf64_Dyn *dyn = dyn_section;
while (dyn->d_tag != DT_NULL) {
if (dyn->d_tag < DT_NUM) {
dyn_info[dyn->d_tag] = dyn;
}
dyn++;
}
if (dyn_info[DT_RELA] != NULL) {
assert(dyn_info[DT_RELAENT]->d_un.d_val == sizeof(Elf64_Rela));
dyn_info[DT_RELA]->d_un.d_ptr += load_offs;
}
if (dyn_info[DT_PLTREL] != NULL) {
assert(dyn_info[DT_PLTREL]->d_un.d_val == DT_RELA);
}
if (dyn_info[DT_JMPREL] != NULL) {
dyn_info[DT_JMPREL]->d_un.d_ptr += load_offs;
}
}
static void do_relocation(Elf64_Addr load_addr, Elf64_Addr load_offs, const Elf64_Rela *rel)
{
Elf64_Addr *target_addr = (Elf64_Addr *)(load_addr + rel->r_offset);
if (ELF64_R_TYPE(rel->r_info) == R_X86_64_RELATIVE) {
if (load_offs == load_addr) {
*target_addr = load_addr + rel->r_addend;
} else {
*target_addr += load_offs;
}
return;
}
if (ELF64_R_TYPE(rel->r_info) == R_X86_64_NONE) {
return;
}
assert(! "unexpected dynamic reloc type");
}
static void do_relocations(Elf64_Addr load_addr, Elf64_Addr load_offs, Elf64_Addr rel_addr, Elf64_Addr rel_size)
{
const Elf64_Rela *rel_start = (const Elf64_Rela *)(rel_addr);
const Elf64_Rela *rel_end = (const Elf64_Rela *)(rel_addr + rel_size);
for (const Elf64_Rela *rel = rel_start; rel < rel_end; rel++) {
do_relocation(load_addr, load_offs, rel);
}
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void reloc(void)
{
static volatile Elf64_Addr last_load_addr = 0;
Elf64_Dyn *dyn_info[DT_NUM];
for (int i = 0; i < DT_NUM; i++) {
dyn_info[i] = NULL;
}
Elf64_Addr load_addr = get_load_address();
Elf64_Addr load_offs = load_addr - last_load_addr;
if (load_addr == last_load_addr) {
return;
}
last_load_addr = load_addr;
Elf64_Dyn *dyn_section = (Elf64_Dyn *)(load_addr + get_dynamic_section_offset());
get_dynamic_info(dyn_section, load_offs, dyn_info);
do_relocations(load_addr, load_offs, dyn_info[DT_RELA]->d_un.d_ptr, dyn_info[DT_RELASZ]->d_un.d_val);
if (dyn_info[DT_PLTREL]->d_un.d_val == DT_RELA) {
do_relocations(load_addr, load_offs, dyn_info[DT_JMPREL]->d_un.d_ptr, dyn_info[DT_PLTRELSZ]->d_un.d_val);
}
}

369
system/screen.c Normal file
View File

@@ -0,0 +1,369 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker
#include <stdbool.h>
#include <stdint.h>
#include "boot.h"
#include "font.h"
#include "vmem.h"
#include "screen.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// screen_info.orig_video_isVGA values.
#define VIDEO_TYPE_VLFB 0x23 // VESA VGA in graphic mode
#define VIDEO_TYPE_EFI 0x70 // EFI graphic mode
// screen_info.capabilities values.
#define LFB_CAPABILITY_64BIT_BASE (1 << 1)
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
// The following definition must match the Linux screen_info struct.
typedef struct {
uint8_t orig_x;
uint8_t orig_y;
uint16_t ext_mem_k;
uint16_t orig_video_page;
uint8_t orig_video_mode;
uint8_t orig_video_cols;
uint8_t flags;
uint8_t unused2;
uint16_t orig_video_ega_bx;
uint16_t unused3;
uint8_t orig_video_lines;
uint8_t orig_video_isVGA;
uint16_t orig_video_points;
uint16_t lfb_width;
uint16_t lfb_height;
uint16_t lfb_depth;
uint32_t lfb_base;
uint32_t lfb_size;
uint16_t cl_magic, cl_offset;
uint16_t lfb_linelength;
uint8_t red_size;
uint8_t red_pos;
uint8_t green_size;
uint8_t green_pos;
uint8_t blue_size;
uint8_t blue_pos;
uint8_t rsvd_size;
uint8_t rsvd_pos;
uint16_t vesapm_seg;
uint16_t vesapm_off;
uint16_t pages;
uint16_t vesa_attributes;
uint32_t capabilities;
uint32_t ext_lfb_base;
uint8_t _reserved[2];
} __attribute__((packed)) screen_info_t;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} __attribute__((packed)) rgb_value_t;
typedef union {
struct {
uint8_t ch;
uint8_t attr;
};
struct {
uint16_t value;
};
} vga_char_t;
typedef vga_char_t vga_buffer_t[SCREEN_HEIGHT][SCREEN_WIDTH];
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static const rgb_value_t vga_pallete[16] = {
// R G B
{ 0, 0, 0 }, // BLACK
{ 0, 0, 170 }, // BLUE
{ 0, 170, 0 }, // GREEN
{ 0, 170, 170 }, // CYAN
{ 170, 0, 0 }, // RED
{ 170, 0, 170 }, // MAUVE
{ 170, 85, 0 }, // YELLOW (brown really)
{ 170, 170, 170 }, // WHITE
{ 85, 85, 85 }, // BOLD+BLACK
{ 85, 85, 255 }, // BOLD+BLUE
{ 85, 255, 85 }, // BOLD+GREEN
{ 85, 255, 255 }, // BOLD+CYAN
{ 255, 85, 85 }, // BOLD+RED
{ 255, 85, 255 }, // BOLD+MAUVE
{ 255, 255, 85 }, // BOLD+YELLOW
{ 255, 255, 255 } // BOLD+WHITE
};
static vga_buffer_t *vga_buffer = (vga_buffer_t *)(0xb8000);
static vga_buffer_t shadow_buffer;
static int lfb_bytes_per_pixel = 0;
static uintptr_t lfb_base;
static uintptr_t lfb_stride;
static uint32_t lfb_pallete[16];
static uint8_t current_attr = WHITE | BLUE << 4;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static void vga_put_char(int row, int col, uint8_t ch, uint8_t attr)
{
shadow_buffer[row][col].ch = ch;
shadow_buffer[row][col].attr = attr;
(*vga_buffer)[row][col].value = shadow_buffer[row][col].value;
}
static void lfb8_put_char(int row, int col, uint8_t ch, uint8_t attr)
{
shadow_buffer[row][col].ch = ch;
shadow_buffer[row][col].attr = attr;
uint8_t fg_colour = attr % 16;
uint8_t bg_colour = attr / 16;
uint8_t *pixel_row = (uint8_t *)lfb_base + row * FONT_HEIGHT * lfb_stride + col * FONT_WIDTH;
for (int y = 0; y < FONT_HEIGHT; y++) {
uint8_t font_row = font_data[ch][y];
for (int x = 0; x < FONT_WIDTH; x++) {
pixel_row[x] = font_row & 0x80 ? fg_colour : bg_colour;
font_row <<= 1;
}
pixel_row += lfb_stride;
}
}
static void lfb16_put_char(int row, int col, uint8_t ch, uint8_t attr)
{
shadow_buffer[row][col].ch = ch;
shadow_buffer[row][col].attr = attr;
uint16_t fg_colour = lfb_pallete[attr % 16];
uint16_t bg_colour = lfb_pallete[attr / 16];
uint16_t *pixel_row = (uint16_t *)lfb_base + row * FONT_HEIGHT * lfb_stride + col * FONT_WIDTH;
for (int y = 0; y < FONT_HEIGHT; y++) {
uint8_t font_row = font_data[ch][y];
for (int x = 0; x < FONT_WIDTH; x++) {
pixel_row[x] = font_row & 0x80 ? fg_colour : bg_colour;
font_row <<= 1;
}
pixel_row += lfb_stride;
}
}
static void lfb32_put_char(int row, int col, uint8_t ch, uint8_t attr)
{
shadow_buffer[row][col].ch = ch;
shadow_buffer[row][col].attr = attr;
uint32_t fg_colour = lfb_pallete[attr % 16];
uint32_t bg_colour = lfb_pallete[attr / 16];
uint32_t *pixel_row = (uint32_t *)lfb_base + row * FONT_HEIGHT * lfb_stride + col * FONT_WIDTH;
for (int y = 0; y < FONT_HEIGHT; y++) {
uint8_t font_row = font_data[ch][y];
for (int x = 0; x < FONT_WIDTH; x++) {
pixel_row[x] = font_row & 0x80 ? fg_colour : bg_colour;
font_row <<= 1;
}
pixel_row += lfb_stride;
}
}
static void (*put_char)(int, int, uint8_t, uint8_t) = vga_put_char;
static void put_value(int row, int col, uint16_t value)
{
put_char(row, col, value % 256, value / 256);
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void screen_init(void)
{
const screen_info_t *screen_info = (screen_info_t *)boot_params_addr;
bool use_lfb = screen_info->orig_video_isVGA == VIDEO_TYPE_VLFB
|| screen_info->orig_video_isVGA == VIDEO_TYPE_EFI;
if (use_lfb) {
int lfb_width = screen_info->lfb_width;
int lfb_height = screen_info->lfb_height;
int lfb_depth = screen_info->lfb_depth;
if (lfb_depth <= 8) {
lfb_bytes_per_pixel = 1;
put_char = lfb8_put_char;
} else if (lfb_depth <= 16) {
lfb_bytes_per_pixel = 2;
put_char = lfb16_put_char;
} else {
lfb_bytes_per_pixel = 4;
put_char = lfb32_put_char;
}
lfb_base = screen_info->lfb_base;
#ifdef __x86_64__
if (LFB_CAPABILITY_64BIT_BASE & screen_info->capabilities) {
lfb_base |= (uintptr_t)screen_info->ext_lfb_base << 32;
}
#endif
lfb_stride = screen_info->lfb_linelength;
lfb_base = map_framebuffer(lfb_base, lfb_height * lfb_width * lfb_bytes_per_pixel);
// Blank the whole framebuffer.
int pixels_per_word = sizeof(uint32_t) / lfb_bytes_per_pixel;
uint32_t *line = (uint32_t *)lfb_base;
for (int y = 0; y < lfb_height; y++) {
for (int x = 0; x < (lfb_width / pixels_per_word); x++) {
line[x] = 0;
}
line += lfb_stride / sizeof(uint32_t);
}
int excess_width = lfb_width - (SCREEN_WIDTH * FONT_WIDTH);
if (excess_width > 0) {
lfb_base += (excess_width / 2) * lfb_bytes_per_pixel;
}
int excess_height = lfb_height - (SCREEN_HEIGHT * FONT_HEIGHT);
if (excess_height > 0) {
lfb_base += (excess_height / 2) * lfb_stride;
}
lfb_stride /= lfb_bytes_per_pixel;
// Initialise the pallete.
uint32_t r_max = (1 << screen_info->red_size ) - 1;
uint32_t g_max = (1 << screen_info->green_size) - 1;
uint32_t b_max = (1 << screen_info->blue_size ) - 1;
for (int i = 0; i < 16; i++) {
uint32_t r = ((vga_pallete[i].r * r_max) / 255) << screen_info->red_pos;
uint32_t g = ((vga_pallete[i].g * g_max) / 255) << screen_info->green_pos;
uint32_t b = ((vga_pallete[i].b * b_max) / 255) << screen_info->blue_pos;
lfb_pallete[i] = r | g | b;
}
}
}
void set_foreground_colour(screen_colour_t colour)
{
current_attr = (current_attr & 0xf0) | (colour & 0x0f);
}
void set_background_colour(screen_colour_t colour)
{
current_attr = (current_attr & 0x8f) | ((colour << 4) & 0x70);
}
void clear_screen(void)
{
for (int row = 0; row < SCREEN_HEIGHT; row++) {
for (int col = 0; col < SCREEN_WIDTH; col++) {
put_char(row, col, ' ', current_attr);
}
}
}
void clear_screen_region(int start_row, int start_col, int end_row, int end_col)
{
if (start_row < 0) start_row = 0;
if (start_col < 0) start_col = 0;
if (end_row >= SCREEN_HEIGHT) end_row = SCREEN_HEIGHT - 1;
if (end_col >= SCREEN_WIDTH) end_col = SCREEN_WIDTH - 1;
if (start_row > end_row) return;
if (start_col > end_col) return;
for (int row = start_row; row <= end_row; row++) {
for (int col = start_col; col <= end_col; col++) {
put_char(row, col, ' ', current_attr);
}
}
}
void scroll_screen_region(int start_row, int start_col, int end_row, int end_col)
{
if (start_row < 0) start_row = 0;
if (start_col < 0) start_col = 0;
if (end_row >= SCREEN_HEIGHT) end_row = SCREEN_HEIGHT - 1;
if (end_col >= SCREEN_WIDTH) end_col = SCREEN_WIDTH - 1;
if (start_row > end_row) return;
if (start_col > end_col) return;
for (int row = start_row; row <= end_row; row++) {
for (int col = start_col; col <= end_col; col++) {
if (row < end_row) {
put_value(row, col, shadow_buffer[row + 1][col].value);
} else {
put_char(row, col, ' ', current_attr);
}
}
}
}
void save_screen_region(int start_row, int start_col, int end_row, int end_col, uint16_t buffer[])
{
if (start_row < 0) start_row = 0;
if (start_col < 0) start_col = 0;
uint16_t *dst = &buffer[0];
for (int row = start_row; row <= end_row; row++) {
if (row >= SCREEN_HEIGHT) break;
for (int col = start_col; col <= end_col; col++) {
if (col >= SCREEN_WIDTH) break;
*dst++ = shadow_buffer[row][col].value;
}
}
}
void restore_screen_region(int start_row, int start_col, int end_row, int end_col, const uint16_t buffer[])
{
if (start_row < 0) start_row = 0;
if (start_col < 0) start_col = 0;
const uint16_t *src = &buffer[0];
for (int row = start_row; row <= end_row; row++) {
if (row >= SCREEN_HEIGHT) break;
for (int col = start_col; col <= end_col; col++) {
if (col >= SCREEN_WIDTH) break;
put_value(row, col, *src++);
}
}
}
void print_char(int row, int col, char ch)
{
if (row < 0 || row >= SCREEN_HEIGHT) return;
if (col < 0 || col >= SCREEN_WIDTH) return;
put_char(row, col, ch, (current_attr & 0x0f) | (shadow_buffer[row][col].attr & 0xf0));
}

91
system/screen.h Normal file
View File

@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef SCREEN_H
#define SCREEN_H
/*
* Provides the display interface. It provides an 80x25 VGA-compatible text
* display.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
/*
* Screen size definitions. The screen size cannot be changed.
*/
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
/*
* Colours that can be used for the foreground or background.
*/
typedef enum {
BLACK = 0,
BLUE = 1,
GREEN = 2,
CYAN = 3,
RED = 4,
MAUVE = 5,
YELLOW = 6,
WHITE = 7
} screen_colour_t;
/*
* Modifier that can be added to any foreground colour.
* Has no effect on background colours.
*/
#define BOLD 8
/*
* Initialise the display interface.
*/
void screen_init(void);
/*
* Set the foreground colour used for subsequent drawing operations.
*/
void set_foreground_colour(screen_colour_t colour);
/*
* Set the background colour used for subsequent drawing operations.
*/
void set_background_colour(screen_colour_t colour);
/*
* Clear the whole screen, using the current background colour.
*/
void clear_screen(void);
/*
* Clear the specified region of the screen, using the current background
* colour.
*/
void clear_screen_region(int start_row, int start_col, int end_row, int end_col);
/*
* Move the contents of the specified region of the screen up one row,
* discarding the top row, and clearing the bottom row, using the current
* background colour.
*/
void scroll_screen_region(int start_row, int start_col, int end_row, int end_col);
/*
* Copy the contents of the specified region of the screen into the supplied
* buffer.
*/
void save_screen_region(int start_row, int start_col, int end_row, int end_col, uint16_t buffer[]);
/*
* Restore the specified region of the screen from the supplied buffer.
* This restores both text and colours.
*/
void restore_screen_region(int start_row, int start_col, int end_row, int end_col, const uint16_t buffer[]);
/*
* Write the supplied character to the specified screen location, using the
* current foreground colour. Has no effect if the location is outside the
* screen.
*/
void print_char(int row, int col, char ch);
#endif // SCREEN_H

778
system/smp.c Normal file
View File

@@ -0,0 +1,778 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ smp.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// ------------------------------------------------
// smp.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 "memsize.h"
#include "pmem.h"
#include "string.h"
#include "unistd.h"
#include "smp.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#define MAX_APIC_IDS 256
// APIC registers
#define APICR_ID 0x02
#define APICR_ESR 0x28
#define APICR_ICRLO 0x30
#define APICR_ICRHI 0x31
// APIC destination shorthands
#define APIC_DEST_DEST 0
#define APIC_DEST_LOCAL 1
#define APIC_DEST_ALL_INC 2
#define APIC_DEST_ALL_EXC 3
// APIC IPI Command Register format
#define APIC_ICRHI_RESERVED 0x00ffffff
#define APIC_ICRHI_DEST_MASK 0xff000000
#define APIC_ICRHI_DEST_OFFSET 24
#define APIC_ICRLO_RESERVED 0xfff32000
#define APIC_ICRLO_DEST_MASK 0x000c0000
#define APIC_ICRLO_DEST_OFFSET 18
#define APIC_ICRLO_TRIGGER_MASK 0x00008000
#define APIC_ICRLO_TRIGGER_OFFSET 15
#define APIC_ICRLO_LEVEL_MASK 0x00004000
#define APIC_ICRLO_LEVEL_OFFSET 14
#define APIC_ICRLO_STATUS_MASK 0x00001000
#define APIC_ICRLO_STATUS_OFFSET 12
#define APIC_ICRLO_DESTMODE_MASK 0x00000800
#define APIC_ICRLO_DESTMODE_OFFSET 11
#define APIC_ICRLO_DELMODE_MASK 0x00000700
#define APIC_ICRLO_DELMODE_OFFSET 8
#define APIC_ICRLO_VECTOR_MASK 0x000000ff
#define APIC_ICRLO_VECTOR_OFFSET 0
// APIC trigger types
#define APIC_TRIGGER_EDGE 0
#define APIC_TRIGGER_LEVEL 1
// APIC delivery modes
#define APIC_DELMODE_FIXED 0
#define APIC_DELMODE_LOWEST 1
#define APIC_DELMODE_SMI 2
#define APIC_DELMODE_NMI 4
#define APIC_DELMODE_INIT 5
#define APIC_DELMODE_STARTUP 6
#define APIC_DELMODE_EXTINT 7
// Table signatures
#define FPSignature ('_' | ('M' << 8) | ('P' << 16) | ('_' << 24))
#define MPCSignature ('P' | ('C' << 8) | ('M' << 16) | ('P' << 24))
#define RSDPSignature ('R' | ('S' << 8) | ('D' << 16) | (' ' << 24))
#define RSDTSignature ('R' | ('S' << 8) | ('D' << 16) | ('T' << 24))
#define XSDTSignature ('X' | ('S' << 8) | ('D' << 16) | ('T' << 24))
#define MADTSignature ('A' | ('P' << 8) | ('I' << 16) | ('C' << 24))
// MP config table entry types
#define MP_PROCESSOR 0
#define MP_BUS 1
#define MP_IOAPIC 2
#define MP_INTSRC 3
#define MP_LINTSRC 4
// MP processor cpu_flag values
#define CPU_ENABLED 1
#define CPU_BOOTPROCESSOR 2
// MADT processor flag values
#define MADT_PF_ENABLED 0x1
#define MADT_PF_ONLINE_CAPABLE 0x2
// Linux EFI loader signatures
#define EL32Signature ('E' | ('L' << 8) | ('3' << 16) | ('2' << 24))
#define EL64Signature ('E' | ('L' << 8) | ('6' << 16) | ('4' << 24))
// Private memory heap used for AP trampoline and synchronisation objects
#define HEAP_BASE_ADDR (smp_page << PAGE_SHIFT)
#define AP_TRAMPOLINE_PAGE (smp_page)
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
typedef uint32_t apic_register_t[4];
typedef struct {
uint32_t signature; // "_MP_"
uint32_t phys_addr;
uint8_t length;
uint8_t spec_rev;
uint8_t checksum;
uint8_t feature[5];
} floating_pointer_struct_t;
typedef struct {
uint32_t signature; // "PCMP"
uint16_t length;
uint8_t spec_rev;
uint8_t checksum;
char oem[8];
char product_id[12];
uint32_t oem_ptr;
uint16_t oem_size;
uint16_t oem_count;
uint32_t lapic_addr;
uint32_t reserved;
} mp_config_table_header_t;
typedef struct {
uint8_t type; // MP_PROCESSOR
uint8_t apic_id;
uint8_t apic_ver;
uint8_t cpu_flag;
uint32_t cpu_signature;
uint32_t feature_flag;
uint32_t reserved[2];
} mp_processor_entry_t;
typedef struct {
uint8_t type; // MP_BUS
uint8_t bus_id;
char bus_type[6];
} mp_bus_entry_t;
typedef struct {
uint8_t type; // MP_IOAPIC
uint8_t apic_id;
uint8_t apic_ver;
uint8_t flags;
uint32_t apic_addr;
} mp_io_apic_entry_t;
typedef struct {
uint8_t type;
uint8_t irq_type;
uint16_t irq_flag;
uint8_t src_bus_id;
uint8_t src_bus_irq;
uint8_t dst_apic;
uint8_t dst_irq;
} mp_interrupt_entry_t;
typedef struct {
uint8_t type;
uint8_t irq_type;
uint16_t irq_flag;
uint8_t src_bus_id;
uint8_t src_bus_irq;
uint8_t dst_apic;
uint8_t dst_apic_lint;
} mp_local_interrupt_entry_t;
typedef struct {
char signature[8]; // "RSD PTR "
uint8_t checksum;
char oem_id[6];
uint8_t revision;
uint32_t rsdt_addr;
uint32_t length;
uint64_t xsdt_addr;
uint8_t xchecksum;
uint8_t reserved[3];
} rsdp_t;
typedef struct {
char signature[4]; // "RSDT"
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oem_id[6];
char oem_table_id[8];
char oem_revision[4];
char creator_id[4];
char creator_revision[4];
} rsdt_t;
typedef struct {
uint8_t type;
uint8_t length;
uint8_t acpi_id;
uint8_t apic_id;
uint32_t flags;
} madt_processor_entry_t;
typedef struct {
uint32_t a;
uint16_t b;
uint16_t c;
uint8_t d[8];
} efi_guid_t;
typedef struct {
uint64_t signature;
uint32_t revision;
uint32_t header_size;
uint32_t crc32;
uint32_t reserved;
} efi_table_header_t;
typedef struct {
efi_table_header_t header;
uint32_t fw_vendor;
uint32_t fw_revision;
uint32_t con_in_handle;
uint32_t con_in;
uint32_t con_out_handle;
uint32_t con_out;
uint32_t std_err_handle;
uint32_t std_err;
uint32_t runtime_services;
uint32_t boot_services;
uint32_t num_config_tables;
uint32_t config_tables;
} efi32_system_table_t;
typedef struct {
efi_table_header_t header;
uint64_t fw_vendor;
uint32_t fw_revision;
uint32_t unused1;
uint64_t con_in_handle;
uint64_t con_in;
uint64_t con_out_handle;
uint64_t con_out;
uint64_t std_err_handle;
uint64_t std_err;
uint64_t runtime_services;
uint64_t boot_services;
uint32_t num_config_tables;
uint32_t unused2;
uint64_t config_tables;
} efi64_system_table_t;
typedef struct {
efi_guid_t guid;
uint32_t table;
} efi32_config_table_t;
typedef struct {
efi_guid_t guid;
uint64_t table;
} efi64_config_table_t;
// The following definition must match the Linux efi_info struct.
typedef struct {
uint32_t loader_signature;
uint32_t sys_tab;
uint32_t mem_desc_size;
uint32_t mem_desc_version;
uint32_t mem_map;
uint32_t mem_map_size;
uint32_t sys_tab_hi;
uint32_t mem_map_hi;
} efi_info_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static const efi_guid_t EFI_ACPI_1_RDSP_GUID = { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d} };
static const efi_guid_t EFI_ACPI_2_RDSP_GUID = { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81} };
static volatile apic_register_t *apic = NULL;
static int8_t apic_id_to_pcpu_num[MAX_APIC_IDS];
static uint8_t pcpu_num_to_apic_id[MAX_PCPUS];
static volatile bool cpu_started[MAX_PCPUS];
static uintptr_t smp_page = 0;
static uintptr_t alloc_addr = 0;
//------------------------------------------------------------------------------
// Variables
//------------------------------------------------------------------------------
int num_pcpus = 1; // There is always at least one CPU, the BSP
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static int my_apic_id(void)
{
return (apic[APICR_ID][0]) >> 24;
}
static void apic_write(unsigned reg, uint32_t val)
{
apic[reg][0] = val;
}
static uint32_t apic_read(unsigned reg)
{
return apic[reg][0];
}
static void send_ipi(unsigned apic_id, unsigned trigger, unsigned level, unsigned mode, uint8_t vector)
{
uint32_t v;
v = apic_read(APICR_ICRHI) & 0x00ffffff;
apic_write(APICR_ICRHI, v | (apic_id << 24));
v = apic_read(APICR_ICRLO) & ~0xcdfff;
v |= APIC_DEST_DEST << APIC_ICRLO_DEST_OFFSET;
v |= trigger << APIC_ICRLO_TRIGGER_OFFSET;
v |= level << APIC_ICRLO_LEVEL_OFFSET;
v |= mode << APIC_ICRLO_DELMODE_OFFSET;
v |= vector;
apic_write(APICR_ICRLO, v);
}
static int checksum(const void *data, int length)
{
uint8_t sum = 0;
uint8_t *ptr = (uint8_t *)data;
while (length--) {
sum += *ptr++;
}
return sum;
}
static floating_pointer_struct_t *scan_for_floating_ptr_struct(uintptr_t addr, int length)
{
uint32_t *ptr = (uint32_t *)addr;
uint32_t *end = ptr + length / sizeof(uint32_t);
while (ptr < end) {
if (*ptr == FPSignature && checksum(ptr, 16) == 0) {
floating_pointer_struct_t *fp = (floating_pointer_struct_t *)ptr;
if (fp->length == 1 && (fp->spec_rev == 1 || fp->spec_rev == 4)) {
return fp;
}
}
ptr++;
}
return NULL;
}
static bool read_mp_config_table(uintptr_t addr)
{
mp_config_table_header_t *mpc = (mp_config_table_header_t *)addr;
if (mpc->signature != MPCSignature || checksum(mpc, mpc->length) != 0) {
return false;
}
apic = (volatile apic_register_t *)((uintptr_t)mpc->lapic_addr);
uint8_t *tab_entry_ptr = (uint8_t *)mpc + sizeof(mp_config_table_header_t);
uint8_t *mpc_table_end = (uint8_t *)mpc + mpc->length;
while (tab_entry_ptr < mpc_table_end) {
switch (*tab_entry_ptr) {
case MP_PROCESSOR: {
mp_processor_entry_t *entry = (mp_processor_entry_t *)tab_entry_ptr;
if (entry->cpu_flag & CPU_BOOTPROCESSOR) {
// BSP is CPU 0
pcpu_num_to_apic_id[0] = entry->apic_id;
} else if (num_pcpus < MAX_PCPUS) {
pcpu_num_to_apic_id[num_pcpus] = entry->apic_id;
num_pcpus++;
}
// we cannot handle non-local 82489DX apics
if ((entry->apic_ver & 0xf0) != 0x10) {
num_pcpus = 1; // reset to initial value
return false;
}
tab_entry_ptr += sizeof(mp_processor_entry_t);
break;
}
case MP_BUS: {
tab_entry_ptr += sizeof(mp_bus_entry_t);
break;
}
case MP_IOAPIC: {
tab_entry_ptr += sizeof(mp_io_apic_entry_t);
break;
}
case MP_INTSRC:
tab_entry_ptr += sizeof(mp_interrupt_entry_t);
break;
case MP_LINTSRC:
tab_entry_ptr += sizeof(mp_local_interrupt_entry_t);
break;
default:
num_pcpus = 1; // reset to initial value
return false;
}
}
return true;
}
static bool find_cpus_in_floating_mp_struct(void)
{
// Search for the Floating MP structure pointer.
floating_pointer_struct_t *fp = scan_for_floating_ptr_struct(0x0, 0x400);
if (fp == NULL) {
fp = scan_for_floating_ptr_struct(639*0x400, 0x400);
}
if (fp == NULL) {
fp = scan_for_floating_ptr_struct(0xf0000, 0x10000);
}
if (fp == NULL) {
// Search the BIOS ESDS area.
uintptr_t address = *(uint16_t *)0x40E << 4;
if (address) {
fp = scan_for_floating_ptr_struct(address, 0x400);
}
}
if (fp == NULL) {
// Floating MP structure pointer not found - give up.
return false;
}
if (fp->feature[0] > 0 && fp->feature[0] <= 7) {
// This is a default config, so plug in the numbers.
apic = (volatile apic_register_t *)0xFEE00000;
pcpu_num_to_apic_id[0] = 0;
pcpu_num_to_apic_id[1] = 1;
num_pcpus = 2;
return true;
}
// Do we have a pointer to a MP configuration table?
if (fp->phys_addr != 0) {
if (read_mp_config_table(fp->phys_addr)) {
// Found a good MP table, done.
return true;
}
}
return false;
}
static rsdp_t *scan_for_rsdp(uintptr_t addr, int length)
{
uint32_t *ptr = (uint32_t *)addr;
uint32_t *end = ptr + length / sizeof(uint32_t);
while (ptr < end) {
rsdp_t *rp = (rsdp_t *)ptr;
if (*ptr == RSDPSignature && checksum(ptr, rp->length) == 0) {
return rp;
}
ptr++;
}
return NULL;
}
static bool parse_madt(void *addr)
{
mp_config_table_header_t *mpc = (mp_config_table_header_t *)addr;
if (checksum(mpc, mpc->length) != 0) {
return false;
}
apic = (volatile apic_register_t *)((uintptr_t)mpc->lapic_addr);
int found_cpus = 0;
uint8_t *tab_entry_ptr = (uint8_t *)mpc + sizeof(mp_config_table_header_t);
uint8_t *mpc_table_end = (uint8_t *)mpc + mpc->length;
while (tab_entry_ptr < mpc_table_end) {
madt_processor_entry_t *entry = (madt_processor_entry_t *)tab_entry_ptr;
if (entry->type == MP_PROCESSOR) {
if (entry->flags & (MADT_PF_ENABLED|MADT_PF_ONLINE_CAPABLE)) {
if (num_pcpus < MAX_PCPUS) {
pcpu_num_to_apic_id[found_cpus] = entry->apic_id;
// The first CPU is the BSP, don't increment.
if (found_cpus > 0) {
num_pcpus++;
}
}
found_cpus++;
}
}
tab_entry_ptr += entry->length;
}
return true;
}
static rsdp_t *find_rsdp_in_efi32_system_table(efi32_system_table_t *system_table)
{
efi32_config_table_t *config_tables = (efi32_config_table_t *)((uintptr_t)system_table->config_tables);
uintptr_t table_addr = 0;
for (uint32_t i = 0; i < system_table->num_config_tables; i++) {
if (memcmp(&config_tables[i].guid, &EFI_ACPI_2_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
break;
}
if (memcmp(&config_tables[i].guid, &EFI_ACPI_1_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
}
}
return (rsdp_t *)table_addr;
}
#ifdef __x86_64__
static rsdp_t *find_rsdp_in_efi64_system_table(efi64_system_table_t *system_table)
{
efi64_config_table_t *config_tables = (efi64_config_table_t *)((uintptr_t)system_table->config_tables);
uintptr_t table_addr = 0;
for (uint32_t i = 0; i < system_table->num_config_tables; i++) {
if (memcmp(&config_tables[i].guid, &EFI_ACPI_2_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
break;
}
if (memcmp(&config_tables[i].guid, &EFI_ACPI_1_RDSP_GUID, sizeof(efi_guid_t)) == 0) {
table_addr = config_tables[i].table;
}
}
return (rsdp_t *)table_addr;
}
#endif
static bool find_cpus_in_rsdp(void)
{
efi_info_t *efi_info = (efi_info_t *)(boot_params_addr + 0x1c0);
// Search for the RSDP
rsdp_t *rp = NULL;
if (efi_info->loader_signature == EL32Signature) {
uintptr_t system_table_addr = (uintptr_t)efi_info->sys_tab;
rp = find_rsdp_in_efi32_system_table((efi32_system_table_t *)system_table_addr);
}
#ifdef __x86_64__
if (efi_info->loader_signature == EL64Signature) {
uintptr_t system_table_addr = (uintptr_t)efi_info->sys_tab_hi << 32 | (uintptr_t)efi_info->sys_tab;
rp = find_rsdp_in_efi64_system_table((efi64_system_table_t *)system_table_addr);
}
#endif
if (rp == NULL) {
// Search the BIOS reserved area.
rp = scan_for_rsdp(0xE0000, 0x20000);
}
if (rp == NULL) {
// Search the BIOS ESDS area.
uintptr_t address = *(uint16_t *)0x40E << 4;
if (address) {
rp = scan_for_rsdp(address, 0x400);
}
}
if (rp == NULL) {
// RSDP not found, give up.
return false;
}
// Found the RSDP, now get either the RSDT or XSDT.
rsdt_t *rt;
if (rp->revision >= 2) {
rt = (rsdt_t *)((uintptr_t)rp->xsdt_addr);
if (rt == 0) {
return false;
}
// Validate the XSDT.
if (*(uint32_t *)rt != XSDTSignature) {
return false;
}
if (checksum((uint8_t *)rt, rt->length) != 0) {
return false;
}
} else {
rt = (rsdt_t *)((uintptr_t)rp->rsdt_addr);
if (rt == 0) {
return false;
}
// Validate the RSDT.
if (*(uint32_t *)rt != RSDTSignature) {
return false;
}
if (checksum((uint8_t *)rt, rt->length) != 0) {
return false;
}
}
// Scan the RSDT or XSDT for a pointer to the MADT.
uint32_t *tab_ptr = (uint32_t *)(rt + 1); // immediately follows the RSDT/XSDT
uint32_t *tab_end = tab_ptr + (rt->length / sizeof(uint32_t));
while (tab_ptr < tab_end) {
uint32_t *ptr = (uint32_t *)((uintptr_t)(*tab_ptr++)); // read the next table entry
if (ptr && *ptr == MADTSignature) {
if (parse_madt(ptr)) {
return true;
}
}
}
return false;
}
static smp_error_t start_cpu(int pcpu_num)
{
int apic_id = pcpu_num_to_apic_id[pcpu_num];
// Clear the APIC ESR register.
apic_write(APICR_ESR, 0);
apic_read(APICR_ESR);
// Pulse the INIT IPI.
send_ipi(apic_id, APIC_TRIGGER_LEVEL, 1, APIC_DELMODE_INIT, 0);
usleep(100000);
send_ipi(apic_id, APIC_TRIGGER_LEVEL, 0, APIC_DELMODE_INIT, 0);
for (int num_sipi = 0; num_sipi < 2; num_sipi++) {
apic_write(APICR_ESR, 0);
send_ipi(apic_id, 0, 0, APIC_DELMODE_STARTUP, AP_TRAMPOLINE_PAGE);
bool send_pending;
int timeout = 0;
do {
usleep(10);
timeout++;
send_pending = (apic_read(APICR_ICRLO) & APIC_ICRLO_STATUS_MASK) != 0;
} while (send_pending && timeout < 1000);
if (send_pending) {
return SMP_ERR_STARTUP_IPI_NOT_SENT;
}
usleep(100000);
uint32_t error = apic_read(APICR_ESR) & 0xef;
if (error) {
return SMP_ERR_STARTUP_IPI_ERROR + error;
}
}
int timeout = 0;
do {
usleep(10);
timeout++;
} while (!cpu_started[pcpu_num] && timeout < 100000);
if (!cpu_started[pcpu_num]) {
return SMP_ERR_BOOT_TIMEOUT;
}
return SMP_ERR_NONE;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void smp_init(void)
{
for (int i = 0; i < MAX_APIC_IDS; i++) {
apic_id_to_pcpu_num[i] = 0;
}
for (int i = 0; i < MAX_PCPUS; i++) {
pcpu_num_to_apic_id[i] = 0;
cpu_started[i] = false;
}
num_pcpus = 1;
(void)(find_cpus_in_rsdp() || find_cpus_in_floating_mp_struct());
for (int i = 0; i < num_pcpus; i++) {
apic_id_to_pcpu_num[pcpu_num_to_apic_id[i]] = i;
}
// Reserve last page of first segment for AP trampoline and sync objects.
// These need to remain pinned in place during relocation.
smp_page = --pm_map[0].end;
ap_startup_addr = (uintptr_t)startup;
size_t ap_trampoline_size = ap_trampoline_end - ap_trampoline;
memcpy((uint8_t *)HEAP_BASE_ADDR, ap_trampoline, ap_trampoline_size);
alloc_addr = HEAP_BASE_ADDR + ap_trampoline_size;
}
smp_error_t smp_start(bool enable_pcpu[MAX_PCPUS])
{
enable_pcpu[0] = true; // we don't support disabling the boot CPU
for (int i = 1; i < num_pcpus; i++) {
if (enable_pcpu[i]) {
smp_error_t error = start_cpu(i);
if (error != SMP_ERR_NONE) {
return error;
}
}
}
return SMP_ERR_NONE;
}
void smp_set_ap_booted(int pcpu_num)
{
cpu_started[pcpu_num] = true;
}
int smp_my_pcpu_num(void)
{
return num_pcpus > 1 ? apic_id_to_pcpu_num[my_apic_id()] : 0;
}
barrier_t *smp_alloc_barrier(int num_threads)
{
barrier_t *barrier = (barrier_t *)(alloc_addr);
alloc_addr += sizeof(barrier_t);
barrier_init(barrier, num_threads);
return barrier;
}
spinlock_t *smp_alloc_mutex()
{
spinlock_t *mutex = (spinlock_t *)(alloc_addr);
alloc_addr += sizeof(spinlock_t);
spin_unlock(mutex);
return mutex;
}

69
system/smp.h Normal file
View File

@@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef _SMP_H_
#define _SMP_H_
/*
* Provides support for multi-threaded operation.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
#include "boot.h"
#include "barrier.h"
#include "spinlock.h"
/*
* The maximum number of active physical CPUs. This only affects memory
* footprint, so can be increased if needed.
*/
#define MAX_PCPUS (1 + MAX_APS)
/*
* An error code returned by smp_start().
*/
typedef enum {
SMP_ERR_NONE = 0,
SMP_ERR_BOOT_TIMEOUT = 1,
SMP_ERR_STARTUP_IPI_NOT_SENT = 2,
SMP_ERR_STARTUP_IPI_ERROR = 0x100 // error code will be added to this
} smp_error_t;
/*
* The number of available physical CPUs. Initially this is 1, but may
* increase after calling smp_init().
*/
extern int num_pcpus;
/*
* Initialises the SMP state and detects the number of physical CPUs.
*/
void smp_init(void);
/*
* Starts the selected APs.
*/
smp_error_t smp_start(bool enable_pcpu[MAX_PCPUS]);
/*
* Signals that an AP has booted.
*/
void smp_set_ap_booted(int pcpu_num);
/*
* Returns the ordinal number of the calling PCPU.
*/
int smp_my_pcpu_num(void);
/*
* Allocates and initialises a barrier object in pinned memory.
*/
barrier_t *smp_alloc_barrier(int num_threads);
/*
* Allocates and initialises a spinlock object in pinned memory.
*/
spinlock_t *smp_alloc_mutex();
#endif /* _SMP_H_ */

63
system/temperature.c Normal file
View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ init.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// ------------------------------------------------
// init.c - MemTest-86 Version 3.6
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdint.h>
#include "cpuid.h"
#include "cpuinfo.h"
#include "msr.h"
#include "pci.h"
#include "temperature.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int get_cpu_temperature(void)
{
if (imc_type == 0) {
return 0;
}
// Intel CPU
if (cpuid_info.vendor_id.str[0] == 'G' && cpuid_info.max_vcpuid >= 6) {
if (cpuid_info.dts_pmp & 1) {
uint32_t msrl, msrh;
rdmsr(MSR_IA32_THERM_STATUS, msrl, msrh);
int Tabs = (msrl >> 16) & 0x7F;
rdmsr(MSR_IA32_TEMPERATURE_TARGET, msrl, msrh);
int Tjunc = (msrl >> 16) & 0x7F;
if (Tjunc < 50 || Tjunc > 125) {
Tjunc = 90;
}
return Tjunc - Tabs;
}
}
#if 0 // TODO: This doesn't give accurate results.
// AMD CPU
if (cpuid_info.vendor_id.str[0] == 'A' && cpuid_info.version.extendedFamily > 0) {
uint32_t rtcr;
pci_conf_read(0, 24, 3, 0xA4, 4, &rtcr);
int raw_temp = (rtcr >> 21) & 0x7FF;
return raw_temp / 8;
}
#endif
return 0;
}

16
system/temperature.h Normal file
View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TEMPERATURE_H
#define TEMPERATURE_H
/*
* Provides a function to read the CPU core temperature.
*
* Copyright (C) 2020 Martin Whitaker.
*/
/*
* Returns the current temperature of the CPU. Returns 0 if
* the temperature cannot be read.
*/
int get_cpu_temperature(void);
#endif // TEMPERATURE_H

37
system/tsc.h Normal file
View File

@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TSC_H
#define TSC_H
/*
* Provides access to the CPU timestamp counter.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdint.h>
#define rdtsc(low, high) \
__asm__ __volatile__("rdtsc" \
: "=a" (low), \
"=d" (high) \
)
#define rdtscl(low) \
__asm__ __volatile__("rdtsc" \
: "=a" (low) \
: /* no inputs */ \
: "edx" \
)
/*
* Reads and returns the timestamp counter value.
*/
static inline uint64_t get_tsc(void)
{
uint32_t tl;
uint32_t th;
rdtsc(tl, th);
return (uint64_t)th << 32 | (uint64_t)tl;
}
#endif // TSC_H

136
system/vmem.c Normal file
View File

@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from memtest86+ vmem.c
//
// vmem.c - MemTest-86
//
// Virtual memory handling (PAE)
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "boot.h"
#include "cpuid.h"
#include "vmem.h"
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static uintptr_t mapped_window = 2;
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static void load_pdbr()
{
void *page_table;
if (cpuid_info.flags.lm == 1) {
page_table = pml4;
} else {
page_table = pdp;
}
__asm__ __volatile__(
#ifdef __x86_64__
"movq %0, %%cr3\n\t"
#else
"movl %0, %%cr3\n\t"
#endif
:
: "r" (page_table)
: "rax"
);
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
uintptr_t map_framebuffer(uintptr_t base_addr, size_t size)
{
uintptr_t first_page = base_addr >> VM_PAGE_SHIFT;
uintptr_t last_page = (base_addr + size - 1) >> VM_PAGE_SHIFT;
if (first_page >= VM_PAGE_C(3,GB) && last_page < VM_PAGE_C(4,GB)) {
// No mapping required.
return base_addr;
}
// Compute the page table entries.
uintptr_t page = first_page;
for (int i = 0; i < 512; i++) {
pd3[i] = (page << VM_PAGE_SHIFT) + 0x83;
if (++page > last_page) break;
}
// Reload the PDBR to flush any remnants of the old mapping.
load_pdbr();
// Return the mapped address.
return ADDR_C(3,GB) + base_addr % VM_PAGE_SIZE;
}
bool map_window(uintptr_t start_page)
{
uintptr_t window = start_page >> (30 - PAGE_SHIFT);
if (window < 2) {
// Less than 2 GB so no mapping is required.
return true;
}
if (cpuid_info.flags.pae == 0) {
// No PAE, so we can only access 4GB.
if (window < 4) {
mapped_window = window;
return true;
}
return false;
}
if (cpuid_info.flags.lm == 0 && (start_page >= PAGE_C(64,GB))) {
// Fail, we want an address that is out of bounds
// for PAE and no long mode (ie. 32 bit CPU).
return false;
}
// Compute the page table entries.
for (uintptr_t i = 0; i < 512; i++) {
pd2[i] = ((uint64_t)window << 30) + (i << VM_PAGE_SHIFT) + 0x83;
}
// Reload the PDBR to flush any remnants of the old mapping.
load_pdbr();
mapped_window = window;
return true;
}
void *first_word_mapping(uintptr_t page)
{
void *result;
if (page < PAGE_C(2,GB)) {
// If the address is less than 2GB, it is directly mapped.
result = (void *)(page << PAGE_SHIFT);
} else {
// Otherwise it is mapped to the third GB.
uintptr_t alias = PAGE_C(2,GB) + page % PAGE_C(1,GB);
result = (void *)(alias << PAGE_SHIFT);
}
return result;
}
void *last_word_mapping(uintptr_t page, size_t word_size)
{
return (uint8_t *)first_word_mapping(page) + (PAGE_SIZE - word_size);
}
uintptr_t page_of(void *addr)
{
uintptr_t page = (uintptr_t)addr >> PAGE_SHIFT;
if (page >= PAGE_C(2,GB)) {
page = page % PAGE_C(1,GB);
page += mapped_window << (30 - PAGE_SHIFT);
}
return page;
}

29
system/vmem.h Normal file
View File

@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef VMEM_H
#define VMEM_H
/*
* Provides functions to handle physical memory page mapping into virtual
* memory.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "memsize.h"
#define VM_WINDOW_SIZE PAGE_C(1,GB)
uintptr_t map_framebuffer(uintptr_t base_addr, size_t size);
bool map_window(uintptr_t start_page);
void *first_word_mapping(uintptr_t page);
void *last_word_mapping(uintptr_t page, size_t word_size);
uintptr_t page_of(void *addr);
#endif // VMEM_H

91
tests/addr_walk1.c Normal file
View File

@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdint.h>
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_addr_walk1(int my_vcpu)
{
int ticks = 0;
// There isn't a meaningful address for this test.
test_addr[my_vcpu] = 0;
testword_t invert = 0;
for (int i = 0; i < 2; i++) {
if (my_vcpu == master_vcpu) {
display_test_pattern_value(invert);
}
ticks++;
if (my_vcpu < 0) {
continue;
}
for (int j = 0; j < vm_map_size; j++) {
uintptr_t pb = (uintptr_t)vm_map[j].start;
uintptr_t pe = (uintptr_t)vm_map[j].end;
// Walking one on our first address.
uintptr_t mask1 = sizeof(testword_t);
do {
volatile testword_t *p1 = (testword_t *)(pb | mask1);
mask1 <<= 1;
if (p1 > (testword_t *)pe) {
break;
}
testword_t expect = invert ^ (testword_t)p1;
*p1 = expect;
// Walking one on our second address.
uintptr_t mask2 = sizeof(testword_t);
do {
volatile testword_t *p2 = (testword_t *)(pb | mask2);
mask2 <<= 1;
if (p2 == p1) {
continue;
}
if (p2 > (testword_t *)pe) {
break;
}
*p2 = ~invert ^ (testword_t)p2;
testword_t actual = *p1;
if (unlikely(actual != expect)) {
addr_error(p1, p2, expect, actual);
*p1 = expect; // recover from error
}
} while (mask2);
} while (mask1);
}
invert = ~invert;
do_tick(my_vcpu);
BAILOUT;
}
return ticks;
}

176
tests/bit_fade.c Normal file
View File

@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "unistd.h"
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static int pattern_fill(int my_vcpu, testword_t pattern)
{
int ticks = 0;
if (my_vcpu == master_vcpu) {
display_test_pattern_value(pattern);
}
for (int i = 0; i < vm_map_size; i++) {
testword_t *start = vm_map[i].start;
testword_t *end = vm_map[i].end;
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
*p = pattern;
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
return ticks;
}
static int pattern_check(int my_vcpu, testword_t pattern)
{
int ticks = 0;
for (int i = 0; i < vm_map_size; i++) {
testword_t *start = vm_map[i].start;
testword_t *end = vm_map[i].end;
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t actual = *p;
if (unlikely(actual != pattern)) {
data_error(p, pattern, actual, true);
}
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
return ticks;
}
static int fade_delay(int my_vcpu, int sleep_secs)
{
int ticks = 0;
if (my_vcpu == master_vcpu) {
display_test_stage_description("fade over %i seconds", sleep_secs);
}
while (sleep_secs > 0) {
sleep_secs--;
ticks++;
if (my_vcpu < 0) {
continue;
}
sleep(1);
do_tick(my_vcpu);
BAILOUT;
}
return ticks;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_bit_fade(int my_vcpu, int stage, int sleep_secs)
{
const testword_t all_zero = 0;
const testword_t all_ones = ~all_zero;
static int last_stage = -1;
int ticks = 0;
switch (stage) {
case 0:
ticks = pattern_fill(my_vcpu, all_zero);
break;
case 1:
// Only sleep once.
if (stage != last_stage) {
ticks = fade_delay(my_vcpu, sleep_secs);
}
break;
case 2:
ticks = pattern_check(my_vcpu, all_zero);
break;
case 3:
ticks = pattern_fill(my_vcpu, all_ones);
break;
case 4:
// Only sleep once.
if (stage != last_stage) {
ticks = fade_delay(my_vcpu, sleep_secs);
}
break;
case 5:
ticks = pattern_check(my_vcpu, all_ones);
break;
default:
break;
}
last_stage = stage;
return ticks;
}

232
tests/block_move.c Normal file
View File

@@ -0,0 +1,232 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_block_move(int my_vcpu, int iterations)
{
int ticks = 0;
if (my_vcpu == master_vcpu) {
display_test_pattern_name("block move");
}
// Initialize memory with the initial pattern.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, 16 * sizeof(testword_t));
testword_t *p = start;
testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
testword_t pattern1 = 1;
do {
testword_t pattern2 = ~pattern1;
p[ 0] = pattern1;
p[ 1] = pattern1;
p[ 2] = pattern1;
p[ 3] = pattern1;
p[ 4] = pattern2;
p[ 5] = pattern2;
p[ 6] = pattern1;
p[ 7] = pattern1;
p[ 8] = pattern1;
p[ 9] = pattern1;
p[10] = pattern2;
p[11] = pattern2;
p[12] = pattern1;
p[13] = pattern1;
p[14] = pattern2;
p[15] = pattern2;
pattern1 = pattern1 << 1 | pattern1 >> (TESTWORD_WIDTH - 1); // rotate left
} while (p <= (pe - 16) && (p += 16)); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
barrier_wait(run_barrier);
// Now move the data around. First move the data up half of the segment size
// we are testing. Then move the data to the original location + 32 bytes.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, 16 * sizeof(testword_t));
testword_t *p = start;
testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
size_t half_length = (pe - p + 1) / 2;
testword_t *pm = p + half_length;
for (int j = 0; j < iterations; j++) {
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
#ifdef __x86_64__
__asm__ __volatile__ (
"cld\n"
"jmp L110\n\t"
".p2align 4,,7\n\t"
"L110:\n\t"
// At the end of all this
// - the second half equals the initial value of the first half
// - the first half is right shifted 64-bytes (with wrapping)
// Move first half to second half
"movq %1,%%rdi\n\t" // Destination, pm (mid point)
"movq %0,%%rsi\n\t" // Source, p (start point)
"movq %2,%%rcx\n\t" // Length, half_length (size of a half in DWORDS)
"rep\n\t"
"movsq\n\t"
// Move the second half, less the last 64 bytes, to the first half, offset plus 64 bytes
"movq %0,%%rdi\n\t"
"addq $64,%%rdi\n\t" // Destination, p (start-point) plus 32 bytes
"movq %1,%%rsi\n\t" // Source, pm (mid-point)
"movq %2,%%rcx\n\t"
"subq $8,%%rcx\n\t" // Length, half_length (size of a half in QWORDS) minus 8 QWORDS (64 bytes)
"rep\n\t"
"movsq\n\t"
// Move last 8 QWORDS (64 bytes) of the second half to the start of the first half
"movq %0,%%rdi\n\t" // Destination, p(start-point)
// Source, 8 QWORDS from the end of the second half, left over by the last rep/movsl
"movq $8,%%rcx\n\t" // Length, 8 QWORDS (64 bytes)
"rep\n\t"
"movsq\n\t"
:: "g" (p), "g" (pm), "g" (half_length)
: "rdi", "rsi", "rcx"
);
#else
__asm__ __volatile__ (
"cld\n"
"jmp L110\n\t"
".p2align 4,,7\n\t"
"L110:\n\t"
// At the end of all this
// - the second half equals the initial value of the first half
// - the first half is right shifted 32 bytes (with wrapping)
// Move first half to second half
"movl %1,%%edi\n\t" // Destination, pm (mid point)
"movl %0,%%esi\n\t" // Source, p (start point)
"movl %2,%%ecx\n\t" // Length, half_length (size of a half in DWORDS)
"rep\n\t"
"movsl\n\t"
// Move the second half, less the last 32 bytes, to the first half, offset plus 32 bytes
"movl %0,%%edi\n\t"
"addl $32,%%edi\n\t" // Destination, p (start-point) plus 32 bytes
"movl %1,%%esi\n\t" // Source, pm (mid-point)
"movl %2,%%ecx\n\t"
"subl $8,%%ecx\n\t" // Length, half_length (size of a half in DWORDS) minus 8 DWORDS (32 bytes)
"rep\n\t"
"movsl\n\t"
// Move last 8 DWORDS (32 bytes) of the second half to the start of the first half
"movl %0,%%edi\n\t" // Destination, p(start-point)
// Source, 8 DWORDS from the end of the second half, left over by the last rep/movsl
"movl $8,%%ecx\n\t" // Length, 8 DWORDS (32 bytes)
"rep\n\t"
"movsl\n\t"
:: "g" (p), "g" (pm), "g" (half_length)
: "edi", "esi", "ecx"
);
#endif
do_tick(my_vcpu);
BAILOUT;
}
} while (!at_end && ++pe); // advance pe to next start point
}
barrier_wait(run_barrier);
// Now check the data. The error checking is rather crude. We just check that the
// adjacent words are the same.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, 16 * sizeof(testword_t));
testword_t *p = start;
testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
if (unlikely(p[0] != p[1])) {
data_error(p, p[0], p[1], false);
}
} while (p <= (pe - 2) && (p += 2)); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
return ticks;
}

143
tests/modulo_n.c Normal file
View File

@@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_modulo_n(int my_vcpu, int iterations, testword_t pattern1, testword_t pattern2, int n, int offset)
{
int ticks = 0;
if (my_vcpu == master_vcpu) {
display_test_pattern_values(pattern1, offset);
}
// Write every nth location with pattern1.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, sizeof(testword_t));
end -= n; // avoids pointer overflow when incrementing p
testword_t *p = start + offset; // we assume each chunk has at least 'n' words, so this won't overflow
testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
*p = pattern1;
} while (p <= (pe - n) && (p += n)); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
// Write the rest of memory "iteration" times with pattern2.
for (int i = 0; i < iterations; i++) {
for (int j = 0; j < vm_map_size; j++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, j, sizeof(testword_t));
int k = 0;
testword_t *p = start;
testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
if (k != offset) {
*p = pattern2;
}
k++;
if (k == n) {
k = 0;
}
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
}
// Now check every nth location.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, sizeof(testword_t));
end -= n; // avoids pointer overflow when incrementing p
testword_t *p = start + offset; // we assume each chunk has at least 'offset' words, so this won't overflow
testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t actual = *p;
if (unlikely(actual != pattern1)) {
data_error(p, pattern1, actual, true);
}
} while (p <= (pe - n) && (p += n)); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
return ticks;
}

165
tests/mov_inv_fixed.c Normal file
View File

@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
#define HAND_OPTIMISED 1 // Use hand-optimised assembler code for performance.
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_mov_inv_fixed(int my_vcpu, int iterations, testword_t pattern1, testword_t pattern2)
{
int ticks = 0;
if (my_vcpu == master_vcpu) {
display_test_pattern_value(pattern1);
}
// Initialize memory with the initial pattern.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, sizeof(testword_t));
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
#if HAND_OPTIMISED
#ifdef __x86_64__
uint64_t length = pe - p + 1;
__asm__ __volatile__ ("\t"
"rep \n\t"
"stosq \n\t"
:
: "c" (length), "D" (p), "a" (pattern1)
:
);
p = pe;
#else
uint32_t length = pe - p + 1;
__asm__ __volatile__ ("\t"
"rep \n\t"
"stosl \n\t"
:
: "c" (length), "D" (p), "a" (pattern1)
:
);
p = pe;
#endif
#else
do {
*p = pattern1;
} while (p++ < pe); // test before increment in case pointer overflows
#endif
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
// Check for the current pattern and then write the alternate pattern for
// each memory location. Test from the bottom up and then from the top down.
for (int i = 0; i < iterations; i++) {
for (int j = 0; j < vm_map_size; j++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, j, sizeof(testword_t));
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t actual = *p;
if (unlikely(actual != pattern1)) {
data_error(p, pattern1, actual, true);
}
*p = pattern2;
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
for (int j = vm_map_size - 1; j >= 0; j--) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, j, sizeof(testword_t));
volatile testword_t *p = end;
volatile testword_t *ps = end;
bool at_start = false;
do {
// take care to avoid pointer underflow
if ((ps - start) >= SPIN_SIZE) {
ps -= SPIN_SIZE - 1;
} else {
at_start = true;
ps = start;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t actual = *p;
if (unlikely(actual != pattern2)) {
data_error(p, pattern2, actual, true);
}
*p = pattern1;
} while (p-- > ps); // test before decrement in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_start && --ps); // advance ps to next start point
}
}
return ticks;
}

121
tests/mov_inv_random.c Normal file
View File

@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "cpuid.h"
#include "tsc.h"
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_mov_inv_random(int my_vcpu)
{
int ticks = 0;
uint64_t seed;
if (cpuid_info.flags.rdtsc) {
seed = get_tsc();
} else {
seed = UINT64_C(0x12345678) * (1 + pass_num);
}
if (my_vcpu == master_vcpu) {
display_test_pattern_value(seed);
}
// Initialize memory with the initial pattern.
random_seed(my_vcpu, seed);
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, sizeof(testword_t));
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
*p = random(my_vcpu);
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
// Check for initial pattern and then write the inverse pattern for each
// memory location. Repeat.
testword_t invert = 0;
for (int i = 0; i < 2; i++) {
random_seed(my_vcpu, seed);
for (int j = 0; j < vm_map_size; j++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, j, sizeof(testword_t));
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t expect = random(my_vcpu) ^ invert;
testword_t actual = *p;
if (unlikely(actual != expect)) {
data_error(p, expect, actual, true);
}
*p = ~expect;
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
invert = ~invert;
}
return ticks;
}

148
tests/mov_inv_walk1.c Normal file
View File

@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_mov_inv_walk1(int my_vcpu, int iterations, int offset, bool inverse)
{
int ticks = 0;
testword_t pattern = (testword_t)1 << offset;
if (my_vcpu == master_vcpu) {
display_test_pattern_value(inverse ? ~pattern : pattern);
}
// Initialize memory with the initial pattern.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, i, sizeof(testword_t));
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
*p = inverse ? ~pattern : pattern;
pattern = pattern << 1 | pattern >> (TESTWORD_WIDTH - 1); // rotate left
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
// Check for initial pattern and then write the complement for each memory location.
// Test from bottom up and then from the top down.
for (int i = 0; i < iterations; i++) {
pattern = (testword_t)1 << offset;
for (int j = 0; j < vm_map_size; j++) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, j, sizeof(testword_t));
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t expect = inverse ? ~pattern : pattern;
testword_t actual = *p;
if (unlikely(actual != expect)) {
data_error(p, expect, actual, true);
}
*p = ~expect;
pattern = pattern << 1 | pattern >> (TESTWORD_WIDTH - 1); // rotate left
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
for (int j = vm_map_size - 1; j >= 0; j--) {
testword_t *start, *end;
calculate_chunk(&start, &end, my_vcpu, j, sizeof(testword_t));
volatile testword_t *p = end;
volatile testword_t *ps = end;
bool at_start = false;
do {
// take care to avoid pointer underflow
if ((ps - start) >= SPIN_SIZE) {
ps -= SPIN_SIZE - 1;
} else {
at_start = true;
ps = start;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)ps;
do {
pattern = pattern >> 1 | pattern << (TESTWORD_WIDTH - 1); // rotate right
testword_t expect = inverse ? pattern : ~pattern;
testword_t actual = *p;
if (unlikely(actual != expect)) {
data_error(p, expect, actual, true);
}
*p = ~expect;
} while (p-- > ps); // test before decrement in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_start && --ps); // advance ps to next start point
}
}
return ticks;
}

148
tests/own_addr.c Normal file
View File

@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdbool.h>
#include <stdint.h>
#include "display.h"
#include "error.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static int pattern_fill(int my_vcpu, testword_t offset)
{
int ticks = 0;
if (my_vcpu == master_vcpu) {
display_test_pattern_name("own address");
}
// Write each address with it's own address.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start = vm_map[i].start;
testword_t *end = vm_map[i].end;
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
*p = (testword_t)p + offset;
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
return ticks;
}
static int pattern_check(int my_vcpu, testword_t offset)
{
int ticks = 0;
// Check each address has its own address.
for (int i = 0; i < vm_map_size; i++) {
testword_t *start = vm_map[i].start;
testword_t *end = vm_map[i].end;
volatile testword_t *p = start;
volatile testword_t *pe = start;
bool at_end = false;
do {
// take care to avoid pointer overflow
if ((end - pe) >= SPIN_SIZE) {
pe += SPIN_SIZE - 1;
} else {
at_end = true;
pe = end;
}
ticks++;
if (my_vcpu < 0) {
continue;
}
test_addr[my_vcpu] = (uintptr_t)p;
do {
testword_t expect = (testword_t)p + offset;
testword_t actual = *p;
if (unlikely(actual != expect)) {
data_error(p, expect, actual, true);
}
} while (p++ < pe); // test before increment in case pointer overflows
do_tick(my_vcpu);
BAILOUT;
} while (!at_end && ++pe); // advance pe to next start point
}
return ticks;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
int test_own_addr1(int my_vcpu)
{
int ticks = 0;
ticks += pattern_fill(my_vcpu, 0);
ticks += pattern_check(my_vcpu, 0);
return ticks;
}
int test_own_addr2(int my_vcpu, int stage)
{
static testword_t offset = 0;
static int last_stage = -1;
int ticks = 0;
offset = (stage == last_stage) ? offset + 1 : 1;
switch (stage) {
case 0:
ticks = pattern_fill(my_vcpu, offset);
break;
case 1:
ticks = pattern_check(my_vcpu, offset);
break;
default:
break;
}
last_stage = stage;
return ticks;
}

31
tests/test_funcs.h Normal file
View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TEST_FUNCS_H
#define TEST_FUNCS_H
/*
* Provides the prototypes for the basic test functions used to implement
* the tests.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include "test.h"
int test_addr_walk1(int my_vcpu);
int test_own_addr1(int my_vcpu);
int test_own_addr2(int my_vcpu, int stage);
int test_mov_inv_fixed(int my_vcpu, int iterations, testword_t pattern1, testword_t pattern2);
int test_mov_inv_walk1(int my_vcpu, int iterations, int offset, bool inverse);
int test_mov_inv_random(int my_vcpu);
int test_modulo_n(int my_vcpu, int iterations, testword_t pattern1, testword_t pattern2, int n, int offset);
int test_block_move(int my_vcpu, int iterations);
int test_bit_fade(int my_vcpu, int stage, int sleep_secs);
#endif // TEST_FUNCS_H

110
tests/test_helper.c Normal file
View File

@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Partly derived from an extract of memtest86+ test.c:
//
// MemTest86+ V5 Specific code (GPL V2.0)
// By Samuel DEMEULEMEESTER, sdemeule@memtest.org
// http://www.canardpc.com - http://www.memtest.org
// Thanks to Passmark for calculate_chunk() and various comments !
// ----------------------------------------------------
// test.c - MemTest-86 Version 3.4
//
// Released under version 2 of the Gnu Public License.
// By Chris Brady
#include <stdint.h>
#include "config.h"
#include "display.h"
#include "test_helper.h"
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
// We keep a separate LFSR for each CPU. Space them out by at least a cache line,
// otherwise performance suffers.
typedef struct {
uint64_t lfsr;
uint64_t pad[7];
} prsg_state_t;
//------------------------------------------------------------------------------
// Private Variables
//------------------------------------------------------------------------------
static prsg_state_t prsg_state[MAX_VCPUS];
//------------------------------------------------------------------------------
// Private Functions
//------------------------------------------------------------------------------
static inline uint32_t prsg(int my_vcpu)
{
// This implements a 64 bit linear feedback shift register with XNOR
// feedback from taps 64, 63, 61, 60. It generates 32 new bits each
// time the function is called. Because the feedback taps are all in
// the upper 32 bits, we can generate the new bits in parallel.
uint64_t lfsr = prsg_state[my_vcpu].lfsr;
uint32_t feedback = ~((lfsr >> 32) ^ (lfsr >> 31) ^ (lfsr >> 29) ^ (lfsr >> 28));
prsg_state[my_vcpu].lfsr = (lfsr << 32) | feedback;
return feedback;
}
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
void random_seed(int my_vcpu, uint64_t seed)
{
if (my_vcpu < 0) {
return;
}
// Avoid the PRSG illegal state.
if (~seed == 0) {
seed = 0;
}
prsg_state[my_vcpu].lfsr = seed;
}
testword_t random(int my_vcpu)
{
if (my_vcpu < 0) {
return 0;
}
testword_t value = prsg(my_vcpu);
#if TESTWORD_WIDTH > 32
value = value << 32 | prsg(my_vcpu);
#endif
return value;
}
void calculate_chunk(testword_t **start, testword_t **end, int my_vcpu, int segment, size_t chunk_align)
{
if (my_vcpu < 0) {
my_vcpu = 0;
}
// If we are only running 1 CPU then test the whole segment.
if (num_vcpus == 1) {
*start = vm_map[segment].start;
*end = vm_map[segment].end;
} else {
uintptr_t segment_size = (vm_map[segment].end - vm_map[segment].start + 1) * sizeof(testword_t);
uintptr_t chunk_size = round_down(segment_size / num_vcpus, chunk_align);
// Calculate chunk boundaries.
*start = (testword_t *)((uintptr_t)vm_map[segment].start + chunk_size * my_vcpu);
*end = (testword_t *)((uintptr_t)(*start) + chunk_size) - 1;
if (*end > vm_map[segment].end) {
*end = vm_map[segment].end;
}
}
}

68
tests/test_helper.h Normal file
View File

@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TEST_HELPER_H
#define TEST_HELPER_H
/*
* Provides some common definitions and helper functions for the memory
* tests.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stddef.h>
#include <stdint.h>
#include "test.h"
/*
* A wrapper for guiding branch prediction.
*/
#define unlikely(x) __builtin_expect(!!(x), 0)
/*
* The block size processed between each update of the progress bars and
* spinners. This also affects how quickly the program will respond to the
* keyboard.
*/
#define SPIN_SIZE (1 << 27) // in testwords
/*
* A macro to perform test bailout when requested.
*/
#define BAILOUT if (bail) return ticks
/*
* Returns value rounded down to the nearest multiple of align_size.
*/
static inline uintptr_t round_down(uintptr_t value, size_t align_size)
{
return value & ~(align_size - 1);
}
/*
* Returns value rounded up to the nearest multiple of align_size.
*/
static inline uintptr_t round_up(uintptr_t value, size_t align_size)
{
return (value + (align_size - 1)) & ~(align_size - 1);
}
/*
* Seeds the psuedo-random number generator for my_vcpu.
*/
void random_seed(int my_vcpu, uint64_t seed);
/*
* Returns a psuedo-random number for my_vcpu. The sequence of numbers returned
* is repeatable for a given starting seed. The sequence repeats after 2^64 - 1
* numbers. Within that period, no number is repeated.
*/
testword_t random(int my_vcpu);
/*
* Calculates the start and end word address for the chunk of segment that is
* to be tested by my_vcpu. The chunk start will be aligned to a multiple of
* chunk_align.
*/
void calculate_chunk(testword_t **start, testword_t **end, int my_vcpu, int segment, size_t chunk_align);
#endif // TEST_HELPER_H

238
tests/tests.c Normal file
View File

@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker.
//
// Derived from an extract of 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 "tsc.h"
#include "vmem.h"
#include "barrier.h"
#include "config.h"
#include "display.h"
#include "test.h"
#include "test_funcs.h"
#include "test_helper.h"
#include "tests.h"
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
#ifndef TRACE_BARRIERS
#define TRACE_BARRIERS 0
#endif
#define MODULO_N 20
//------------------------------------------------------------------------------
// Public Variables
//------------------------------------------------------------------------------
test_pattern_t test_list[NUM_TEST_PATTERNS] = {
// ena, cpu, stgs, itrs, errs, description
{ true, SEQ, 1, 6, 0, "[Address test, walking ones, no cache] "},
{ true, SEQ, 1, 6, 0, "[Address test, own address in window] "},
{ true, SEQ, 2, 6, 0, "[Address test, own address + window] "},
{ true, PAR, 1, 6, 0, "[Moving inversions, 1s & 0s] "},
{ true, PAR, 1, 3, 0, "[Moving inversions, 8 bit pattern] "},
{ true, PAR, 1, 30, 0, "[Moving inversions, random pattern] "},
#if TESTWORD_WIDTH > 32
{ true, PAR, 1, 3, 0, "[Moving inversions, 64 bit pattern] "},
#else
{ true, PAR, 1, 3, 0, "[Moving inversions, 32 bit pattern] "},
#endif
{ true, PAR, 1, 81, 0, "[Block move] "},
{ true, PAR, 1, 48, 0, "[Random number sequence] "},
{ true, PAR, 1, 6, 0, "[Modulo 20, random pattern] "},
{ true, ONE, 6, 240, 0, "[Bit fade test, 2 patterns] "},
};
int ticks_per_pass[NUM_PASS_TYPES];
int ticks_per_test[NUM_PASS_TYPES][NUM_TEST_PATTERNS];
//------------------------------------------------------------------------------
// Public Functions
//------------------------------------------------------------------------------
#define BARRIER \
if (my_vcpu >= 0) { \
if (TRACE_BARRIERS) { \
trace(my_vcpu, "Run barrier wait at %s line %i", __FILE__, __LINE__); \
} \
barrier_wait(run_barrier); \
}
int run_test(int my_vcpu, int test, int stage, int iterations)
{
if (my_vcpu == master_vcpu) {
if ((uintptr_t)&_start > LOW_LOAD_ADDR) {
// Relocated so we need to test all selected lower memory.
vm_map[0].start = first_word_mapping(pm_limit_lower);
// For USB_WORKAROUND.
if (vm_map[0].start < (uintptr_t *)0x500) {
vm_map[0].start = (uintptr_t *)0x500;
}
}
/* Update display of memory segments being tested */
uintptr_t pb = page_of(vm_map[0].start);
uintptr_t pe = page_of(vm_map[vm_map_size - 1].end) + 1;
display_test_addresses(pb << 2, pe << 2, num_pages_to_test << 2);
}
BARRIER;
int ticks = 0;
switch (test) {
// Address test, walking ones.
case 0:
cache_off();
ticks += test_addr_walk1(my_vcpu);
cache_on();
BAILOUT;
break;
// Address test, own address in window.
case 1:
ticks += test_own_addr1(my_vcpu);
BAILOUT;
break;
// Address test, own address + window.
case 2:
ticks += test_own_addr2(my_vcpu, stage);
BAILOUT;
break;
// Moving inversions, all ones and zeros.
case 3: {
testword_t pattern1 = 0;
testword_t pattern2 = ~pattern1;
BARRIER;
ticks += test_mov_inv_fixed(my_vcpu, iterations, pattern1, pattern2);
BAILOUT;
BARRIER;
ticks += test_mov_inv_fixed(my_vcpu, iterations, pattern2, pattern1);
BAILOUT;
} break;
// Moving inversions, 8 bit walking ones and zeros.
case 4: {
#if TESTWORD_WIDTH > 32
testword_t pattern1 = UINT64_C(0x8080808080808080);
#else
testword_t pattern1 = 0x80808080;
#endif
for (int i = 0; i < 8; i++) {
testword_t pattern2 = ~pattern1;
BARRIER;
ticks += test_mov_inv_fixed(my_vcpu, iterations, pattern1, pattern2);
BAILOUT;
BARRIER;
ticks += test_mov_inv_fixed(my_vcpu, iterations, pattern2, pattern1);
BAILOUT;
pattern1 >>= 1;
}
} break;
// Moving inversions, fixed random pattern.
case 5:
if (cpuid_info.flags.rdtsc) {
random_seed(my_vcpu, get_tsc());
} else {
random_seed(my_vcpu, UINT64_C(0x12345678) * (1 + pass_num));
}
for (int i = 0; i < iterations; i++) {
testword_t pattern1 = random(my_vcpu);
testword_t pattern2 = ~pattern1;
BARRIER;
ticks += test_mov_inv_fixed(my_vcpu, 2, pattern1, pattern2);
BAILOUT;
}
break;
// Moving inversions, 32/64 bit shifting pattern.
case 6:
for (int offset = 0; offset < TESTWORD_WIDTH; offset++) {
BARRIER;
ticks += test_mov_inv_walk1(my_vcpu, iterations, offset, false);
BAILOUT;
BARRIER;
ticks += test_mov_inv_walk1(my_vcpu, iterations, offset, true);
BAILOUT;
}
break;
// Block move.
case 7:
ticks += test_block_move(my_vcpu, iterations);
BAILOUT;
break;
// Moving inversions, fully random patterns.
case 8:
for (int i = 0; i < iterations; i++) {
BARRIER;
ticks += test_mov_inv_random(my_vcpu);
BAILOUT;
}
break;
// Modulo 20 check, fixed random pattern.
case 9:
if (cpuid_info.flags.rdtsc) {
random_seed(my_vcpu, get_tsc());
} else {
random_seed(my_vcpu, UINT64_C(0x12345678) * (1 + pass_num));
}
for (int i = 0; i < iterations; i++) {
for (int offset = 0; offset < MODULO_N; offset++) {
testword_t pattern1 = random(my_vcpu);
testword_t pattern2 = ~pattern1;
BARRIER;
ticks += test_modulo_n(my_vcpu, 2, pattern1, pattern2, MODULO_N, offset);
BAILOUT;
BARRIER;
ticks += test_modulo_n(my_vcpu, 2, pattern2, pattern1, MODULO_N, offset);
BAILOUT;
}
}
break;
// Bit fade test.
case 10:
ticks += test_bit_fade(my_vcpu, stage, iterations);
BAILOUT;
break;
}
return ticks;
}

34
tests/tests.h Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef TESTS_H
#define TESTS_H
/*
* Provides support for identifying and running the memory tests.
*
* Copyright (C) 2020 Martin Whitaker.
*/
#include <stdbool.h>
#include "config.h"
#define NUM_TEST_PATTERNS 11
typedef struct {
bool enabled;
cpu_mode_t cpu_mode;
int stages;
int iterations;
int errors;
char *description;
} test_pattern_t;
extern test_pattern_t test_list[NUM_TEST_PATTERNS];
typedef enum { FAST_PASS, FULL_PASS, NUM_PASS_TYPES } pass_type_t;
extern int ticks_per_pass[NUM_PASS_TYPES];
extern int ticks_per_test[NUM_PASS_TYPES][NUM_TEST_PATTERNS];
int run_test(int my_vcpu, int test, int stage, int iterations);
#endif // TESTS_H