Allocating Memory
Ethereal provides many memory allocation routines, some good and some bad.
Heap Memory
Heap memory is the most common type of memory that a driver would need to acquire.
To acquire heap memory, Ethereal provides common kernel replacement functions for standard C ones.
void *kmalloc(size_t sz);
void kfree(void *p);
void *krealloc(void *p, size_t sz);
void *kcalloc(size_t n, size_t size);
These functions work exactly the same as malloc
, free
, etc. and provide general-purpose heap functions.
DMA/MMIO
DMA and MMIO are special subsets of memory that require specific page permissions.
Ethereal provides APIs to automatically allocate and free DMA/MMIO memory.
DMA can be allocated and freed with the mem_allocateDMA
/mem_freeDMA
functions, which are defined as:
uintptr_t mem_allocateDMA(size_t size);
void mem_freeDMA(uintptr_t base, uintptr_t size);
DMA is mapped as non-cacheable kernel mode memory.
MMIO can be allocated and freed with the mem_mapMMIO
/mem_unmapMMIO
functions, which are defined as:
uintptr_t mem_mapMMIO(uintptr_t phys, size_t size);
void mem_unmapMMIO(uintptr_t virt, uintptr_t size);
MMIO is mapped as non-cacheable kernel mode memory.
Managing your own memory
Ethereal's memory API may make you violently rage. Send all threats for me to GitHub issues.
In all seriousness, the memory API is okay except for a few functions. Below are for very specific cases and in most times aren't needed.
This is where the meat comes into play. Let's look at VMM functions.
Pages
The page_t
structure varies from architecture to architecture, so all architectures are required to define the following macros:
For example implementations, see hexahedron/include/arch/x86_64/mem.h
#define MEM_ALIGN_PAGE(addr) ... // Graciously align an address to the nearest page
#define MEM_ALIGN_PAGE_DESTRUCTIVE(addr) ... // Align an address to the nearest page, discarding any bits
/* The following are GETTERS. They are one way */
#define PAGE_IS_PRESENT(pg) ...
#define PAGE_IS_WRITABLE(pg) ...
#define PAGE_IS_USERMODE(pg) ...
#define PAGE_IS_COW(pg) ...
#define PAGE_IS_DIRTY(pg) ...
/* The following are SETTERS. You can set them, but getting them is not recommended. */
#define PAGE_PRESENT(pg) ...
#define PAGE_COW(pg) ...
#define PAGE_FRAME_RAW(pg) ...
#define MEM_SET_FRAME(pg, frame) ... // Set the frame of a page
#define MEM_GET_FRAME(pg) ... // Get the frame of a page
Getting pages
To get a page, use the mem_getPage
function.
page_t *mem_getPage(page_t *dir, uintptr_t virt, int flags);
dir
: Specifies the page directory to get the page in. Leave asNULL
to use the current.virt
: The target virtual addressflags
: Mainly eitherMEM_DEFAULT
orMEM_CREATE
. IfMEM_CREATE
is specified, the page is created and returned.
Returns a pointer to a page object or NULL depending on flags.
Allocating frames for pages
Hexahedron provides a dedicated API call for this:
void mem_allocatePage(page_t *page, uintptr_t flags);
Pass your page and set your flags to customize the page's protection bits + frame.
Normally this will also allocate a new frame for your page and set it (use MEM_PAGE_NOALLOC
to stop this).
Available flags:
MEM_DEFAULT
: Default protection is URWX.MEM_PAGE_KERNEL
: Kernel mode only pageMEM_PAGE_READONLY
: Read only pageMEM_PAGE_WRITETHROUGH
: Enable writethrough on the pageMEM_PAGE_NOT_CACHEABLE
: UCMEM_PAGE_NOT_PRESENT
: The page is no longer present in memoryMEM_PAGE_NOALLOC
: Use this when you just want to change a page's protection flags. This means that a new frame won't be allocated when you call thisMEM_PAGE_FREE
: Free the page. Basically just passes your page tomem_freePage
MEM_PAGE_NO_EXECUTE
: Only on supported architectures. Enables NX.MEM_PAGE_WRITE_COMBINE
: Only on supported architectures. Enables WC.
You can free any page with the mem_freePage
function:
void mem_freePage(page_t *page);
Physical memory manipulation
You can map physical to virtual memory with mem_mapAddress
:
void mem_mapAddress(page_t *dir, uintptr_t phys, uintptr_t virt, int flags);
Alternatively, for short physical memory accesses try HHDM:
uintptr_t mem_remapPhys(uintptr_t frame_address, uintptr_t size);
void mem_unmapPhys(uintptr_t frame_address, uintptr_t size);
Upon finishing with your remapped physical memory, call mem_unmapPhys
.
This is because of the i386 memory pool system.
Putting it all together
Let's map our BAR into memory.
/**
* @brief PCI example
*/
#include <kernel/loader/driver.h>
#include <kernel/debug.h>
#include <kernel/drivers/pci.h>
#include <kernel/mem/mem.h>
#include <string.h>
int driver_scanCallback(pci_device_t *device, void *context) {
dprintf(INFO, "Found matching device at bus %d slot %d func %d\n", device->bus, device->slot, device->function);
// Get BAR0
pci_bar_t *b = pci_getBAR(device->bus, device->slot, device->function, 0);
assert(b);
// Map it as MMIO
uintptr_t m = mem_mapMMIO(b->address, b->size);
dprintf(DEBUG, "Mapped memory to 0x%016llX\n", m);
memset(m, 0, b->size);
mem_unmapMMIO(m, b->size);
return 0;
}
int driver_init(int argc, char *argv[]) {
dprintf(DEBUG, "Scanning for example PCI device with VID 1234 and PID 1111...\n");
pci_id_mapping_t id_list[] = {
{ .vid = 0x1234, .pid = { 0x1111, PCI_NONE }},
PCI_ID_MAPPING_END
};
pci_scan_parameters_t params = {
.class_code = 0, // No class code
.subclass_code = 0, // No class code
.id_list = id_list
};
return pci_scan(driver_scanCallback, ¶ms, NULL);
}
int driver_deinit() { return DRIVER_STATUS_SUCCESS; }
struct driver_metadata driver_metadata = {
.name = "Example Driver",
.author = "Your Name Here",
.init = driver_init,
.deinit = driver_deinit
};