diff --git a/usr.sbin/bhyve/e820.h b/usr.sbin/bhyve/e820.h --- a/usr.sbin/bhyve/e820.h +++ b/usr.sbin/bhyve/e820.h @@ -18,6 +18,17 @@ E820_TYPE_NVS = 4 }; +enum e820_allocation_strategy { + /* allocate any address */ + E820_ALLOCATE_ANY, + /* allocate lowest address larger than address */ + E820_ALLOCATE_LOWEST, + /* allocate highest address lower than address */ + E820_ALLOCATE_HIGHEST, + /* allocate a specific address */ + E820_ALLOCATE_SPECIFIC +}; + #pragma pack(1) struct e820_entry { @@ -28,5 +39,10 @@ #pragma pack() +#define E820_ALIGNMENT_NONE 1 + +uint64_t e820_alloc(const uint64_t address, const uint64_t length, + const uint64_t alignment, const enum e820_memory_type type, + const enum e820_allocation_strategy strategy); struct qemu_fwcfg_item *e820_get_fwcfg_item(void); int e820_init(struct vmctx *const ctx); diff --git a/usr.sbin/bhyve/e820.c b/usr.sbin/bhyve/e820.c --- a/usr.sbin/bhyve/e820.c +++ b/usr.sbin/bhyve/e820.c @@ -20,6 +20,14 @@ #include "e820.h" #include "qemu_fwcfg.h" +/* + * E820 always uses 64 bit entries. Emulation code will use vm_paddr_t since it + * works on physical addresses. If vm_paddr_t is larger than uint64_t E820 can't + * hold all possible physical addresses and we can get into trouble. + */ +static_assert(sizeof(vm_paddr_t) <= sizeof(uint64_t), + "Unable to represent physical memory by E820 table"); + #define E820_FWCFG_FILE_NAME "etc/e820" #define KB (1024UL) @@ -275,6 +283,92 @@ return (0); } +static uint64_t +e820_alloc_highest(const uint64_t max_address, const uint64_t length, + const uint64_t alignment, const enum e820_memory_type type) +{ + struct e820_element *element; + + TAILQ_FOREACH_REVERSE(element, &e820_table, e820_table, chain) { + uint64_t address, base, end; + + end = MIN(max_address, element->end); + base = roundup2(element->base, alignment); + + if (element->type != E820_TYPE_MEMORY || end < base || + end - base < length || end - length == 0) { + continue; + } + + address = rounddown2(end - length, alignment); + + if (e820_add_entry(address, address + length, type) != 0) { + return (0); + } + + return (address); + } + + return (0); +} + +static uint64_t +e820_alloc_lowest(const uint64_t min_address, const uint64_t length, + const uint64_t alignment, const enum e820_memory_type type) +{ + struct e820_element *element; + + TAILQ_FOREACH(element, &e820_table, chain) { + uint64_t base, end; + + end = element->end; + base = MAX(min_address, roundup2(element->base, alignment)); + + if (element->type != E820_TYPE_MEMORY || end < base || + end - base < length || base == 0) { + continue; + } + + if (e820_add_entry(base, base + length, type) != 0) { + return (0); + } + + return (base); + } + + return (0); +} + +uint64_t +e820_alloc(const uint64_t address, const uint64_t length, + const uint64_t alignment, const enum e820_memory_type type, + const enum e820_allocation_strategy strategy) +{ + assert(powerof2(alignment)); + assert((address & (alignment - 1)) == 0); + + switch (strategy) { + case E820_ALLOCATE_ANY: + /* + * Allocate any address. Therefore, ignore the address parameter + * and reuse the code path for allocating the lowest address. + */ + return e820_alloc_lowest(0, length, alignment, type); + case E820_ALLOCATE_LOWEST: + return e820_alloc_lowest(address, length, alignment, type); + case E820_ALLOCATE_HIGHEST: + return e820_alloc_highest(address, length, alignment, type); + case E820_ALLOCATE_SPECIFIC: + if (e820_add_entry(address, address + length, type) != 0) { + return 0; + } + + return address; + } + + return 0; +} + int e820_init(struct vmctx *const ctx) {