diff --git a/stand/kboot/kboot.h b/stand/kboot/kboot.h index 2d89933b51aa..dcf1487ce404 100644 --- a/stand/kboot/kboot.h +++ b/stand/kboot/kboot.h @@ -1,50 +1,52 @@ /*- * 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); /* 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 41ded9b4083c..8cf3b833c9d1 100644 --- a/stand/kboot/seg.c +++ b/stand/kboot/seg.c @@ -1,197 +1,346 @@ /*- * 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; +}