diff --git a/stand/kboot/kboot.h b/stand/kboot/kboot.h index dcf1487ce404..4a8fe497339e 100644 --- a/stand/kboot/kboot.h +++ b/stand/kboot/kboot.h @@ -1,52 +1,53 @@ /*- * Copyright (c) 2022, Netflix, Inc. * * SPDX-License-Identifier: BSD-2-Clause */ #ifndef KBOOT_H #define KBOOT_H #define DEVT_HOSTDISK 1234 struct memory_segments { uint64_t start; uint64_t end; uint64_t type; /* MD defined */ }; bool enumerate_memory_arch(void); bool has_acpi(void); vm_offset_t acpi_rsdp(void); void do_init(void); /* Per-platform fdt fixup */ void fdt_arch_fixups(void *fdtp); uint64_t kboot_get_phys_load_segment(void); uint8_t kboot_get_kernel_machine_bits(void); /* hostdisk.c */ extern const char *hostfs_root; const char *hostdisk_gen_probe(void); void hostdisk_zfs_probe(void); bool hostdisk_zfs_find_default(void); /* seg.c */ #define SYSTEM_RAM 1 void init_avail(void); void need_avail(int n); void add_avail(uint64_t start, uint64_t end, uint64_t type); void remove_avail(uint64_t start, uint64_t end, uint64_t type); uint64_t first_avail(uint64_t align, uint64_t min_size, uint64_t type); void print_avail(void); bool populate_avail_from_iomem(void); +uint64_t space_avail(uint64_t start); /* util.c */ bool file2str(const char *fn, char *buffer, size_t buflen); bool file2u64(const char *fn, uint64_t *val); #endif /* KBOOT_H */ diff --git a/stand/kboot/seg.c b/stand/kboot/seg.c index 8cf3b833c9d1..947aa0a2a7a3 100644 --- a/stand/kboot/seg.c +++ b/stand/kboot/seg.c @@ -1,346 +1,365 @@ /*- * Copyright (c) 2023, Netflix, Inc. * * SPDX-License-Identifier: BSD-2-Clause */ #include "stand.h" #include "kboot.h" #include static struct memory_segments *segs; static int nr_seg = 0; static int segalloc = 0; void init_avail(void) { if (segs) free(segs); nr_seg = 0; segalloc = 16; segs = malloc(sizeof(*segs) * segalloc); if (segs == NULL) panic("not enough memory to get memory map\n"); } /* * Make sure at least n items can be accessed in the segs array. Note the * realloc here will invalidate cached pointers (potentially), so addresses * into the segs array must be recomputed after this call. */ void need_avail(int n) { if (n <= segalloc) return; while (n > segalloc) segalloc *= 2; segs = realloc(segs, segalloc * sizeof(*segs)); if (segs == NULL) panic("not enough memory to get memory map\n"); } /* * Always called for a new range, so always just append a range, * unless it's continuous with the prior range. */ void add_avail(uint64_t start, uint64_t end, uint64_t type) { /* * This range is contiguous with the previous range, and is * the same type: we can collapse the two. */ if (nr_seg >= 1 && segs[nr_seg - 1].end + 1 == start && segs[nr_seg - 1].type == type) { segs[nr_seg - 1].end = end; return; } /* * Otherwise we need to add a new range at the end, but don't need to * adjust the current end. */ need_avail(nr_seg + 1); segs[nr_seg].start = start; segs[nr_seg].end = end; segs[nr_seg].type = type; nr_seg++; } /* * All or part of a prior entry needs to be modified. Given the structure of the * code, we know that it will always be modifying the last time and/or extending * the one before it if its contiguous. */ void remove_avail(uint64_t start, uint64_t end, uint64_t type) { struct memory_segments *s; /* * simple case: we are extending a previously removed item. */ if (nr_seg >= 2) { s = &segs[nr_seg - 2]; if (s->end + 1 == start && s->type == type) { s->end = end; /* Now adjust the ending element */ s++; if (s->end == end) { /* we've used up the 'free' space */ nr_seg--; return; } /* Otherwise adjust the 'free' space */ s->start = end + 1; return; } } /* * OK, we have four cases: * (1) The new chunk is at the start of the free space, but didn't catch the above * folding for whatever reason (different type, start of space). In this case, * we allocate 1 additional item. The current end is copied to the new end. The * current end is set to and the new end's start is set to end + 1. * (2) The new chunk is in the middle of the free space. In this case we allocate 2 * additional items. We copy the current end to the new end, set the new end's start * to end + 1, the old end's end to start - 1 and the new item is * (3) The new chunk is at the end of the current end. In this case we allocate 1 more * and adjust the current end's end to start - 1 and set the new end to . * (4) The new chunk is exactly the current end, except for type. In this case, we just adjust * the type. * We can assume we always have at least one chunk since that's created with new_avail() above * necessarily before we are called to subset it. */ s = &segs[nr_seg - 1]; if (s->start == start) { if (s->end == end) { /* (4) */ s->type = type; return; } /* chunk at start of old chunk -> (1) */ need_avail(nr_seg + 1); s = &segs[nr_seg - 1]; /* Realloc may change pointers */ s[1] = s[0]; s->start = start; s->end = end; s->type = type; s[1].start = end + 1; nr_seg++; return; } if (s->end == end) { /* At end of old chunk (3) */ need_avail(nr_seg + 1); s = &segs[nr_seg - 1]; /* Realloc may change pointers */ s[1] = s[0]; s->end = start - 1; s[1].start = start; s[1].type = type; nr_seg++; return; } /* In the middle, need to split things up (2) */ need_avail(nr_seg + 2); s = &segs[nr_seg - 1]; /* Realloc may change pointers */ s[2] = s[1] = s[0]; s->end = start - 1; s[1].start = start; s[1].end = end; s[1].type = type; s[2].start = end + 1; nr_seg += 2; } void print_avail(void) { printf("Found %d RAM segments:\n", nr_seg); for (int i = 0; i < nr_seg; i++) { printf("%#jx-%#jx type %lu\n", (uintmax_t)segs[i].start, (uintmax_t)segs[i].end, (u_long)segs[i].type); } } uint64_t first_avail(uint64_t align, uint64_t min_size, uint64_t memtype) { uint64_t s, len; for (int i = 0; i < nr_seg; i++) { if (segs[i].type != memtype) /* Not candidate */ continue; s = roundup(segs[i].start, align); if (s >= segs[i].end) /* roundup past end */ continue; len = segs[i].end - s + 1; if (len >= min_size) { printf("Found a big enough hole at in seg %d at %#jx (%#jx-%#jx)\n", i, (uintmax_t)s, (uintmax_t)segs[i].start, (uintmax_t)segs[i].end); return (s); } } return (0); } enum types { system_ram = SYSTEM_RAM, firmware_reserved, linux_code, linux_data, linux_bss, unknown, }; static struct kv { uint64_t type; char * name; int flags; #define KV_KEEPER 1 } str2type_kv[] = { { linux_code, "Kernel code", KV_KEEPER }, { linux_data, "Kernel data", KV_KEEPER }, { linux_bss, "Kernel bss", KV_KEEPER }, { firmware_reserved, "reserved" }, { 0, NULL }, }; static const char * parse_line(const char *line, uint64_t *startp, uint64_t *endp) { const char *walker; char *next; uint64_t start, end; /* * Each line is a range followed by a descriptoin of the form: * * Bail if we have any parsing errors. */ walker = line; start = strtoull(walker, &next, 16); if (start == ULLONG_MAX || walker == next) return (NULL); walker = next; if (*walker != '-') return (NULL); walker++; end = strtoull(walker, &next, 16); if (end == ULLONG_MAX || walker == next) return (NULL); walker = next; /* Now eat the ' : ' in front of the string we want to return */ if (strncmp(walker, " : ", 3) != 0) return (NULL); *startp = start; *endp = end; return (walker + 3); } static struct kv * kvlookup(const char *str, struct kv *kvs, size_t nkv) { for (int i = 0; i < nkv; i++) if (strcmp(kvs[i].name, str) == 0) return (&kvs[i]); return (NULL); } /* Trim trailing whitespace */ static void chop(char *line) { char *ep = line + strlen(line) - 1; while (ep >= line && isspace(*ep)) *ep-- = '\0'; } #define SYSTEM_RAM_STR "System RAM" #define RESERVED "reserved" bool populate_avail_from_iomem(void) { int fd; char buf[128]; const char *str; uint64_t start, end; struct kv *kv; fd = open("host:/proc/iomem", O_RDONLY); if (fd == -1) { printf("Can't get memory map\n"); return false; } if (fgetstr(buf, sizeof(buf), fd) < 0) goto out; /* Nothing to do ???? */ init_avail(); chop(buf); while (true) { /* * Look for top level items we understand. Skip anything that's * a continuation, since we don't care here. If we care, we'll * consume them all when we recognize that top level item. */ if (buf[0] == ' ') /* Continuation lines? Ignore */ goto next_line; str = parse_line(buf, &start, &end); if (str == NULL) /* Malformed -> ignore */ goto next_line; /* * All we care about is System RAM */ if (strncmp(str, SYSTEM_RAM_STR, sizeof(SYSTEM_RAM_STR) - 1) == 0) add_avail(start, end, system_ram); else if (strncmp(str, RESERVED, sizeof(RESERVED) - 1) == 0) add_avail(start, end, firmware_reserved); else goto next_line; /* Ignore hardware */ while (fgetstr(buf, sizeof(buf), fd) >= 0 && buf[0] == ' ') { chop(buf); str = parse_line(buf, &start, &end); if (str == NULL) break; kv = kvlookup(str, str2type_kv, nitems(str2type_kv)); if (kv == NULL) /* failsafe for new types: igonre */ remove_avail(start, end, unknown); else if ((kv->flags & KV_KEEPER) == 0) remove_avail(start, end, kv->type); /* Else no need to adjust since it's a keeper */ } /* * if buf[0] == ' ' then we know that the fgetstr failed and we * should break. Otherwise fgetstr succeeded and we have a * buffer we need to examine for being a top level item. */ if (buf[0] == ' ') break; chop(buf); continue; /* buf has next top level line to parse */ next_line: if (fgetstr(buf, sizeof(buf), fd) < 0) break; } out: close(fd); return true; } + +/* + * Return the amount of space available in the segment that @start@ lives in, + * from @start@ to the end of the segment. + */ +uint64_t +space_avail(uint64_t start) +{ + for (int i = 0; i < nr_seg; i++) { + if (start >= segs[i].start && start <= segs[i].end) + return segs[i].end - start; + } + + /* + * Properly used, we should never get here. Unsure if this should be a + * panic or not. + */ + return 0; +}