Skip to main content

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

danger

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:

note

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 as NULL to use the current.
  • virt: The target virtual address
  • flags: Mainly either MEM_DEFAULT or MEM_CREATE. If MEM_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.

caution

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 page
  • MEM_PAGE_READONLY: Read only page
  • MEM_PAGE_WRITETHROUGH: Enable writethrough on the page
  • MEM_PAGE_NOT_CACHEABLE: UC
  • MEM_PAGE_NOT_PRESENT: The page is no longer present in memory
  • MEM_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 this
  • MEM_PAGE_FREE: Free the page. Basically just passes your page to mem_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);
caution

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, &params, 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
};