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
};