mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2025-02-25 18:55:23 -06:00
Initial commit.
This commit is contained in:
49
.gitignore
vendored
49
.gitignore
vendored
@@ -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
473
README.md
@@ -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
218
app/badram.c
Normal 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
30
app/badram.h
Normal 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
614
app/config.c
Normal 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
48
app/config.h
Normal 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
288
app/display.c
Normal 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
186
app/display.h
Normal 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
373
app/error.c
Normal 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
46
app/error.h
Normal 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
166
app/interrupt.c
Normal 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
17
app/interrupt.h
Normal 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
518
app/main.c
Normal 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
104
app/test.h
Normal 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
77
boot/boot.h
Normal 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
368
boot/bootsect.S
Normal 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
383
boot/setup.S
Normal 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
515
boot/startup32.S
Normal 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
593
boot/startup64.S
Normal 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
111
build32/Makefile
Normal 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
110
build64/Makefile
Normal 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
55
lib/barrier.c
Normal 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
34
lib/barrier.h
Normal 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
27
lib/ctype.c
Normal 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
28
lib/ctype.h
Normal 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
13
lib/div64.c
Normal 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
272
lib/print.c
Normal 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
85
lib/print.h
Normal 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
129
lib/read.c
Normal 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
22
lib/read.h
Normal 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
57
lib/spinlock.h
Normal 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
107
lib/string.c
Normal 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
60
lib/string.h
Normal 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
40
lib/unistd.c
Normal 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
20
lib/unistd.h
Normal 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
64
system/cache.h
Normal 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
135
system/cpuid.c
Normal 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
200
system/cpuid.h
Normal 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
774
system/cpuinfo.c
Normal 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
53
system/cpuinfo.h
Normal 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
4617
system/font.c
Normal file
File diff suppressed because it is too large
Load Diff
24
system/font.h
Normal file
24
system/font.h
Normal 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
44
system/hwctrl.c
Normal 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
25
system/hwctrl.h
Normal 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
145
system/io.h
Normal 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
117
system/keyboard.c
Normal 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
26
system/keyboard.h
Normal 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
36
system/memsize.h
Normal 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
45
system/msr.h
Normal 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
193
system/pci.c
Normal 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
18
system/pci.h
Normal 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
289
system/pmem.c
Normal 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
27
system/pmem.h
Normal 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
172
system/reloc32.c
Normal 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
181
system/reloc64.c
Normal 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
369
system/screen.c
Normal 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
91
system/screen.h
Normal 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
778
system/smp.c
Normal 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
69
system/smp.h
Normal 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
63
system/temperature.c
Normal 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
16
system/temperature.h
Normal 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
37
system/tsc.h
Normal 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
136
system/vmem.c
Normal 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
29
system/vmem.h
Normal 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
91
tests/addr_walk1.c
Normal 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
176
tests/bit_fade.c
Normal 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
232
tests/block_move.c
Normal 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
143
tests/modulo_n.c
Normal 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
165
tests/mov_inv_fixed.c
Normal 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
121
tests/mov_inv_random.c
Normal 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
148
tests/mov_inv_walk1.c
Normal 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
148
tests/own_addr.c
Normal 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
31
tests/test_funcs.h
Normal 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
110
tests/test_helper.c
Normal 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
68
tests/test_helper.h
Normal 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
238
tests/tests.c
Normal 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
34
tests/tests.h
Normal 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
|
||||
Reference in New Issue
Block a user