nativeos/arch/i386/kernel/mem/pmm.c

236 lines
6.4 KiB
C

/*
* This file is part of NativeOS
* Copyright (C) 2015-2020 The NativeOS contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* \file pmm.c
* \brief Physical Memory Manager
*
* The physical memory manager allocates pages. A page is an aligned 4 KB
* memory region (in other words, a memory block that is always 4096 bytes
* long, whose first byte has 0x000 as their 12 least significant bits).
*
* The virtual memory manager depends on the physical memory manager in order
* to allocate physical memory pages that will be put in the frames in use by
* the virtual memory address range.
*/
#include <kernel/mem/heap.h>
#include <kernel/mem/pmm.h>
#include <machine/multiboot.h>
/**
* \brief Frame index.
*
* This abstraction provides a little more context to variables that hold a
* frame number, to make them stand out among other kind of unsigned integer
* variables that we hold in this module.
*/
typedef unsigned int frame_idx_t;
/**
* \brief Frame allocation bitmap
*
* Each bit of this bitmap represents one of the 4 KB frames the memory is
* split by. Bit i is set to 1 when frame i, spanning memory addresses
* (i * 0x4000) up to (i * 0x4000 + 0x3FFF) is already taken.
*/
static unsigned int * frames_map;
/**
* \brief Number of allocatable frames
*
* This is the length of the bitmap phys_frame_bitmap. So, this variable will
* hold the total amount of frames that exist in the memory of this machine.
* Up to 2^20 frames can exist on a machine fully loaded with 4 GB of RAM.
* For a system with less than 4 GB of RAM, less frames will exist.
*/
static frame_idx_t frames_count;
#define FRAME_NUMBER(memaddr) (memaddr >> 12) /* divide by 4096 */
#define PHYSICAL_ADDR(idx) (idx << 12) /* multiply by 4096 */
#define BIT_INDEX(frame_idx) (frame_idx >> 5) /* divide by 32 */
#define BIT_OFFSET(frame_idx) (frame_idx & 0x1F) /* modulus 32 */
#define OFFSET_MASK(bit) (((unsigned int) 0x1) << bit)
static inline void
frame_set (frame_idx_t idx)
{
frames_map[BIT_INDEX(idx)] |= OFFSET_MASK(BIT_OFFSET(idx));
}
static inline unsigned int
frame_test (frame_idx_t idx)
{
return frames_map[BIT_INDEX(idx)] & OFFSET_MASK(BIT_OFFSET(idx));
}
static inline void
frame_clear (frame_idx_t idx)
{
frames_map[BIT_INDEX(idx)] &= ~OFFSET_MASK(BIT_OFFSET(idx));
}
/**
* \brief Return the index of a free frame.
*
* Note that this implementation can be improved, because it will take more
* time to allocate frames as the system grows the in use memory. However,
* this is a simple implementation that will make things kick in the meantime.
*
* \return either -1 if no free frames are found, or the frame index otherwise.
*/
static frame_idx_t
frame_find_free ()
{
frame_idx_t idx, offset;
unsigned int map;
for (idx = 0; idx < frames_count; idx += 32) {
if (frames_map[BIT_INDEX(idx)] != (unsigned int) -1) {
map = frames_map[BIT_INDEX(idx)];
for (offset = 0; offset < 32; offset++) {
if (!(map & OFFSET_MASK(BIT_OFFSET(offset)))) {
return idx | offset;
}
}
}
}
return -1;
}
static void
allocate_frames ()
{
/* mem_upper contains the size of the extended memory area. Note that
* because multiboot respects the original IBM PC memory map, the
* extended memory starts at 1 MB, so I have to add 1024 kB to whatever
* I have here. Also note that this variable is expressed in kBs. */
unsigned int mapsize, memsize = (multiboot_info->mem_upper + 1) << 10;
frames_count = FRAME_NUMBER(memsize);
mapsize = BIT_INDEX(frames_count);
if (BIT_OFFSET(frames_count) != 0) {
mapsize++;
}
frames_map = (unsigned int *) heap_alloc(mapsize);
}
/**
* \brief Mark frames in use for a specific memory block.
* \param mblock a memory region declared by multiboot as in use.
*/
static void
reserve_system_block (multiboot_mmap_t * mblock)
{
physaddr_t page;
unsigned int f, frame_offset = FRAME_NUMBER(mblock->base_addr);
for (f = frame_offset, page = 0;
f < frames_count && page < mblock->length;
f++, page += 0x1000) {
frame_set(f);
}
}
/**
* \brief Mark frames in use by the system as in use.
*
* Multiboot may report that some memory areas are reserved (hardware mappings,
* ACPI tables, or damaged memory regions). In such cases, it may be desired
* to mark such frames as permanently in use so that no further pages are
* allocated into these frames.
*/
static void
reserve_system ()
{
multiboot_mmap_t * mblock;
physaddr_t mmap_end;
if (multiboot_info->flags & 0x40) {
mblock = (multiboot_mmap_t *) multiboot_info->mmap_addr;
mmap_end = (physaddr_t) mblock + multiboot_info->mmap_length;
while ((physaddr_t) mblock < mmap_end) {
/* If the block is reserved, mark the pages. */
if (mblock->type != 1) {
reserve_system_block(mblock);
}
/* Skips to the next block (forgive weird math). */
mblock = (multiboot_mmap_t *) ((unsigned int) mblock +
mblock->size + sizeof(mblock->size));
}
}
}
/**
* \brief Mark the memory pages in use by the kernel as in use.
*
* This subroutine will scan the memory pages in use by the kernel binary image
* as in use, so that the physical memory manager never tries to allocate pages
* inside the kernel image.
*/
static void
reserve_kernel ()
{
extern char kernel_start, kernel_after;
physaddr_t addr;
frame_idx_t frame;
/* Start counting at the start of the page. */
addr = ((physaddr_t) &kernel_start) & 0xFFFFF000;
/* Mark pages in use by the kernel. */
for (frame = FRAME_NUMBER(addr);
addr < (physaddr_t) &kernel_after;
addr += 4096, frame++) {
frame_set(frame);
}
}
void
pmm_init ()
{
allocate_frames();
reserve_system();
reserve_kernel();
// TODO: reserve_modules();
}
physaddr_t
pmm_alloc_page ()
{
frame_idx_t frame = frame_find_free();
if (frame == (unsigned int) -1) {
return 0;
} else {
frame_set(frame);
return PHYSICAL_ADDR(frame);
}
}
void
pmm_free_page (physaddr_t page)
{
frame_clear(FRAME_NUMBER(page));
}