memtest86plus/system/pmem.c

290 lines
9.8 KiB
C
Raw Normal View History

2020-05-24 15:30:55 -05:00
// 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();
}