From bc600ecc3bdf0f189861dfb840f70c2339a7a853 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Fri, 24 May 2024 13:24:27 +0300 Subject: rename common to src + I keep starting to type src and wondering why autocomplete won't work, I guess src is just uncounciously a better name --- src/pmem.c | 587 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 587 insertions(+) create mode 100644 src/pmem.c (limited to 'src/pmem.c') diff --git a/src/pmem.c b/src/pmem.c new file mode 100644 index 0000000..4da3f4c --- /dev/null +++ b/src/pmem.c @@ -0,0 +1,587 @@ +/* SPDX-License-Identifier: copyleft-next-0.3.1 */ +/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ + +/** + * @file pmem.c + * Physical memory subsystem. Allocates physical memory pages, with support for + * different ordered pages, depending on the underlying architecture. + * + * Effectively, each page order (4096, 2M, 1G...) has a free list of bitmaps for + * each page size. When an order runs out of free nodes, it just 'allocates' a + * node from a higher order list, and gives out those maps. This turned out to + * be around 50% faster than the previous method, with about half the necessary + * code. Could still probably be cleaned up a little bit, in particular I don't + * really care for probe_pmap() vs populate_pmap() but I suppose it's fine for + * now. + */ + +#include +#include +#include +#include +#include +#include /* memset */ +#include /* is_nset etc */ +#include + +/* \todo add memory page counting? + * To make sure memory is not overcommited at clone, for example. + */ + +/** + * Loop over orders in reverse, starting with highest, giving the iterator the + * name \p iter. + * + * @param iter Name of iterator. + */ +#define reverse_foreach_order(iter) \ + for (enum mm_order iter = max_order(); iter != MM_MIN; --iter) + +/** Page bitmap node */ +struct mm_bmap { + /** How many bits this node has. This is generally the same as \c bits in + * mm_bucket, but could be used for trailing nodes with some irregular + * number of bits. */ + size_t size; + + /** How many pages are currently used */ + size_t used; + + /** Next node in freelist */ + struct mm_bmap *next; + + /** Previous node in freelist */ + struct mm_bmap *prev; + + /** Actual bitmap */ + uint8_t bits[]; +}; + +/** Bucket of bitmaps for some order of pages */ +struct mm_bucket { + /** How many bits per (regular) bitmap, see \c size in mm_bmap */ + size_t bits; + + /** Size in bytes of a page of this order */ + size_t page_size; + + /** Current head of freelist */ + struct mm_bmap *head; + + /** Bitmaps in contiguous array, to make populating easier. */ + struct mm_bmap bmap[]; +}; + +/** Physical map. */ +struct mm_pmap { + /** Base address of our map. Note that this should be the virtual base + * address of the physical ram. */ + pm_t base; + /** Buckets, one per order up to maximum order. */ + struct mm_bucket *buckets[MM_NUM]; +}; + +/** Static physical map address. \note If I support NUMA, this should probably not be + * static, rather one physical map per NUMA region. */ +static struct mm_pmap *pmap = 0; + +/** + * Zero out memory if \p populate is true. + * Helper for populate_pmap(), makes it a bit more easy to follow when we're + * just calculating the size of out physical map versus actually building it. + * + * @param populate Whether to write anything. + * @param cont Where to write. + * @param size How many bytes to write. + * @return Address following last written byte. + */ +static pm_t __zero_if(bool populate, pm_t cont, size_t size) +{ + if (populate) + memset((void *)cont, 0, size); + + return cont + size; +} + +/** + * Get size in bytes of (regular) bitmap node in this bucket. + * + * @param bucket Bucket. + * @return Size in bytes of (regular) bitmap node. + */ +static size_t __get_set_size(struct mm_bucket *bucket) +{ + return sizeof(bucket->bmap[0]) + bucket->bits / 8; +} + +/** + * Get pointer to bitmap node at index \p set. + * + * @param bucket Bucket. + * @param set Index of bitmap node to get. + * @return Pointer to bitmap node. + */ +static struct mm_bmap *__get_set(struct mm_bucket *bucket, size_t set) +{ + uintptr_t bmap = (uintptr_t)bucket->bmap; + return (struct mm_bmap *)(bmap + __get_set_size(bucket) * set); +} + +/** + * Get index from pointer to \p bmap within \p bucket. + * + * @param bucket Bucket. + * @param bmap Bitmap node. + * @return Index of \p bmap within \p bucket. + */ +static size_t __get_set_index(struct mm_bucket *bucket, struct mm_bmap *bmap) +{ + size_t s = (uintptr_t)bmap - (uintptr_t)bucket->bmap; + return s / __get_set_size(bucket); +} + +/** + * Attach bitmap \p bmap to freelist within \p bucket. + * + * @param bucket Bucket. + * @param bmap Bitmap node. + */ +static void __attach_set(struct mm_bucket *bucket, struct mm_bmap *bmap) +{ + /* already attached */ + if (bmap->next) + return; + + bmap->next = bucket->head; + bucket->head = bmap; + if (bmap->next) + bmap->next->prev = bmap; +} + +/** + * Remove bitmap \p bmap from freelist within \p bucket. + * + * @param bucket Bucket. + * @param bmap Bitmap node. + */ +static void __detach_set(struct mm_bucket *bucket, struct mm_bmap *bmap) +{ + if (bucket->head == bmap) + bucket->head = bmap->next; + + if (bmap->next) + bmap->next->prev = bmap->prev; + + if (bmap->prev) + bmap->prev->next = bmap->next; +} + +/** + * Calculate address of page within bucket. + * + * @param bucket Bucket. + * @param s Index of bitmap node. + * @param b Bit within bitmap. + * @return Address of corresponding page. + */ +static pm_t __page_addr(struct mm_bucket *bucket, size_t s, size_t b) +{ + return pmap->base + + s * bucket->page_size * bucket->bits + + b * bucket->page_size; +} + +/** + * Calculate which bitmap node index and bit within bitmap an address + * corresponds to. + * + * @param bucket Bucket. + * @param a Address. + * @param s Corresponding bitmap node index. + * @param b Bitmap node bit index. + */ +static void __get_bit(struct mm_bucket *bucket, pm_t a, size_t *s, size_t *b) +{ + a -= pmap->base; + size_t p = a / bucket->page_size; + *s = p / bucket->bits; + *b = p % bucket->bits; +} + +void free_page(enum mm_order order, pm_t addr) +{ + struct mm_bucket *bucket = pmap->buckets[order]; + if (!bucket) + return; + + size_t set = 0, bit = 0; + __get_bit(bucket, addr, &set, &bit); + + struct mm_bmap *bmap = __get_set(bucket, set); + bmap->used--; + + bitmap_clear(bmap->bits, bit); + __attach_set(bucket, bmap); + + if (bmap->used == 0) { + __detach_set(bucket, bmap); + + if (bmap->size == order_width(order + 1)) + free_page(order + 1, __page_addr(bucket, set, bit)); + } +} + +pm_t alloc_page(enum mm_order order) +{ + struct mm_bucket *bucket = pmap->buckets[order]; + if (!bucket) + return 0; + + struct mm_bmap *bmap = bucket->head; + if (!bmap) { + pm_t a = alloc_page(order + 1); + if (!a) + return 0; + + size_t set = 0, bit = 0; + __get_bit(bucket, a, &set, &bit); + + bmap = __get_set(bucket, set); + bmap->used = 0; + bitmap_clear_all(bmap->bits, bmap->size); + __attach_set(bucket, bmap); + return alloc_page(order); + } + + bmap->used++; + + size_t set = __get_set_index(bucket, bmap); + size_t bit = bitmap_find_first_unset(bmap->bits, bmap->size); + bitmap_set(bmap->bits, bit); + + if (bmap->used == bmap->size) + __detach_set(bucket, bmap); + + return __page_addr(bucket, set, bit); +} + +void mark_used(enum mm_order order, pm_t addr) +{ + struct mm_bucket *bucket = pmap->buckets[order]; + if (!bucket) + return; + + size_t set = 0, bit = 0; + __get_bit(bucket, addr, &set, &bit); + + struct mm_bmap *bmap = __get_set(bucket, set); + if (bmap->used == 0) { + bitmap_clear_all(bmap->bits, bmap->size); + __attach_set(bucket, bmap); + mark_used(order + 1, addr); + } + + /* a page already in use can just be left alone. This MIGHT hide some + * bugs in case two separate things overlap in memory during + * initialization, but that scenario should probably be handled outside + * of this function anyway. */ + if (bitmap_is_set(bmap->bits, bit)) + return; + + bmap->used++; + bitmap_set(bmap->bits, bit); + + if (bmap->used == bmap->size) + __detach_set(bucket, bmap); +} + +/** + * Helper for probing/populating a bucket. + * + * @param n How many pages in total to account for. + * @todo currently may cut off some pages if \p n is larger than but not a multiple of order + * width. + * @param cont Where to place bucket. + * @param order Order of bucket to populate. + * @param first First bucket being populated. Top bucket, owns all pages to + * start with. + * @param populate Whether to actually write bucket to memory. + * @return Address right after where last byte of bycket would be. + */ +static pm_t __maybe_populate_bucket(size_t n, pm_t cont, enum mm_order order, + bool first, bool populate) +{ + struct mm_bucket *bucket = (struct mm_bucket *)cont; + /* todo max order? */ + size_t bits = order_width(order + 1); + if (bits == 0) + bits = n; + + if (populate) { + bucket->bits = bits; + bucket->page_size = order_size(order); + bucket->head = NULL; + } + + size_t set_size = sizeof(struct mm_bmap) + bits / 8; + + cont += sizeof(struct mm_bucket); + + size_t sets = n / bits; + for (size_t i = 0; i < sets; ++i) { + struct mm_bmap *bmap = (struct mm_bmap *)cont; + if (populate) { + memset(bmap, 0, set_size); + bmap->size = bits; + } + + if (first && populate) + __attach_set(bucket, bmap); + + n -= bits; + cont += set_size; + } + + if (n) { + struct mm_bmap *bmap = (struct mm_bmap *)cont; + if (populate) + bmap->size = n; + + if (first && populate) + __attach_set(bucket, bmap); + + cont += set_size; + } + + return cont; +} + +/** + * Probe how many bytes the physical map would take up, optionally populate + * empty physical map if \p populate is given. + * + * I realize it sounds like two + * different functions, and that it might be a good idea to split in twine, + * but my thinking was that using the same algorithm with a + * flag to enable writing to memory would decrease chances that I would + * calculate the size differently from what is actually needed. We need an + * accurate estimate of the pmap size to know whether we can place it somewhere + * and not overwrite something else. + * + * @param ram_base Address in kernel space where the physical RAM starts. + * @param ram_size Size of RAM in bytes. + * @param start Where to start building pmap. + * @param populate Whether to actually write anything out to memory. + * @return Size of pmap in bytes. + */ +static pm_t __maybe_populate_pmap(pm_t ram_base, size_t ram_size, pm_t start, + bool populate) +{ + pm_t cont = start; + + pmap = (struct mm_pmap *)start; + cont = __zero_if(populate, cont, sizeof(*pmap)); + if (populate) + pmap->base = ram_base; + + bool first = true; + reverse_foreach_order(iter) { + size_t num = ram_size / order_size(iter); + if (num == 0) + continue; + + if (populate) + pmap->buckets[iter] = (struct mm_bucket *)cont; + + cont = __maybe_populate_bucket(num, cont, iter, first, + populate); + first = false; + } + + return cont - start; +} + +pm_t populate_pmap(pm_t ram_base, size_t ram_size, pm_t start) +{ + return __maybe_populate_pmap(ram_base, ram_size, start, true); +} + +pm_t probe_pmap(pm_t ram_base, size_t ram_size, pm_t start) +{ + return __maybe_populate_pmap(ram_base, ram_size, start, false); +} + +/** + * Helper function for marking area used. + * + * @param base Base address of area. + * @param top Top address of top. + */ +static void __mark_area_used(pm_t base, pm_t top) +{ + if (top < base) { + bug("top < base: %lx < %lx\n", top, base); + return; + } + + size_t area_left = top - base; + pm_t runner = base; + while (area_left >= BASE_PAGE_SIZE) { + mark_used(BASE_PAGE, runner); + runner += BASE_PAGE_SIZE; + area_left -= BASE_PAGE_SIZE; + } + + if (area_left != 0) + mark_used(BASE_PAGE, runner); +} + +/** + * Mark reserved memory region used, to avoid it getting accidentally allocated. + * + * @param fdt Global FDT pointer. + */ +static void __mark_reserved_mem(void *fdt) +{ + int rmem_offset = fdt_path_offset(fdt, "/reserved-memory"); + struct cell_info ci = get_cellinfo(fdt, rmem_offset); + + int node = 0; + fdt_for_each_subnode(node, fdt, rmem_offset) { + uint8_t *rmem_reg = + (uint8_t *)fdt_getprop(fdt, node, "reg", NULL); + + pm_t base = (pm_t)fdt_load_reg_addr(ci, rmem_reg, 0); + + /** @todo make sure the top of a reserved memory area doesn't go + * against our assumptions in FW_MAX_SIZE? */ + pm_t top = (pm_t)fdt_load_reg_size(ci, rmem_reg, 0) + base; + __mark_area_used((pm_t)__va(base), (pm_t)__va(top)); + info("marked [%lx - %lx] reserved\n", + (pm_t)__va(base), (pm_t)__va(top)); + } +} + +/** + * Read top of RAM from FDT. + * + * @param fdt Global FDT pointer. + * @return Physical address of top of RAM. + */ +static pm_t __get_ramtop(void *fdt) +{ + int mem_offset = fdt_path_offset(fdt, "/memory"); + const void *mem_reg = fdt_getprop(fdt, mem_offset, "reg", NULL); + + /* here we actually want the root offset because /memory itself doesn't + * have children, I guess? */ + struct cell_info ci = get_cellinfo(fdt, fdt_path_offset(fdt, "/")); + pm_t base = (pm_t)fdt_load_reg_addr(ci, mem_reg, 0); + return (pm_t)fdt_load_reg_size(ci, mem_reg, 0) + base; +} + +/** + * Read top of FDT. + * + * @param fdt Global FDT pointer. + * @return Physical address of top of FDT. + */ +static pm_t __get_fdttop(void *fdt) +{ + const char *b = (const char *)fdt; + return (pm_t)(b + fdt_totalsize(fdt)); +} + +/** + * Return base of FDT. + * + * Technically pretty useless, but here mainly for cohesion. + * + * @param fdt Global FDT pointer. + * @return \c fdt. + */ +static pm_t __get_fdtbase(void *fdt) +{ + /* lol */ + return (pm_t)fdt; +} + +void init_pmem(void *fdt) +{ + /** @todo should I keep the info outputs? I suppose it's nice to see + * if any assumption is being broken in the serial log, but in that case + * I should really try adding more of them to other parts of the + * codebase as well, the pmem subsystem isn't really especially complex. + */ + info("initializing pmem\n"); + + size_t max_order = 0; + size_t base_bits = 0; + size_t bits[NUM_ORDERS] = { 0 }; + stat_pmem_conf(fdt, &max_order, &base_bits, bits); + init_mem(max_order, bits, base_bits); + + pm_t ram_size = __get_ramtop(fdt) - get_ram_base(); + pm_t ram_base = (pm_t)__va(get_ram_base()); + + info("using ram range [%lx - %lx]\n", + ram_base, ram_base + ram_size); + + /** @todo could probably improve error messages on failing to get fdt + * values */ + pm_t initrd_base = get_initrdbase(fdt); + pm_t initrd_top = get_initrdtop(fdt); + info("found initrd at [%lx - %lx]\n", initrd_base, initrd_top); + + pm_t fdt_top = __get_fdttop(fdt); + pm_t fdt_base = __get_fdtbase(fdt); + info("found fdt at [%lx - %lx]\n", fdt_base, fdt_top); + + /* find probably most suitable contiguous region of ram for our physical + * ram map */ + /** @todo this really should check that there's enough space in RAM + * instead of just forcing the pmap to be populated */ + pm_t pmap_base = align_up(MAX(initrd_top, fdt_top), BASE_PAGE_SIZE); + info("choosing to place pmem map at %lx\n", pmap_base); + + size_t probe_size = probe_pmap(ram_base, ram_size, pmap_base); + info("pmem map probe size returned %lu\n", probe_size); + + size_t actual_size = populate_pmap(ram_base, ram_size, pmap_base); + info("pmem map actual size %lu\n", actual_size); + + if (probe_size != actual_size) { + bug("probe_size (%#lx) != actual_size (%#lx)\n", probe_size, + actual_size); + } + + /* mark init stack, this should be unmapped once we get to executing + * processes */ + __mark_area_used(VM_STACK_BASE, VM_STACK_TOP); + info("marked stack [%lx - %lx] used\n", VM_STACK_BASE, VM_STACK_TOP); + + /* mark kernel */ + /* this could be made more explicit, I suppose. */ + __mark_area_used(VM_KERN, VM_KERN + PM_KERN_SIZE); + info("marked kernel [%lx - %lx] used\n", VM_KERN, + VM_KERN + PM_KERN_SIZE); + + /* mark fdt and initrd */ + __mark_area_used(initrd_base, initrd_top); + info("marked initrd [%lx - %lx] used\n", initrd_base, initrd_top); + + __mark_area_used(fdt_base, fdt_top); + info("marked fdt [%lx - %lx] used\n", fdt_base, fdt_top); + + /* mark pmap */ + __mark_area_used(pmap_base, pmap_base + actual_size); + info("marked pmap [%lx - %lx] used\n", pmap_base, + pmap_base + actual_size); + + /* mark reserved mem */ + __mark_reserved_mem(fdt); + + init_mem_nodes(); + + init_devmem((pm_t)__pa(ram_base), (pm_t)__pa(ram_base + ram_size)); +} -- cgit v1.3