diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/bootmem.c | 9 | ||||
-rw-r--r-- | src/lib/selfboot.c | 537 |
2 files changed, 115 insertions, 431 deletions
diff --git a/src/lib/bootmem.c b/src/lib/bootmem.c index 38fdb1b937..2f9b5dc0b1 100644 --- a/src/lib/bootmem.c +++ b/src/lib/bootmem.c @@ -228,15 +228,6 @@ int bootmem_region_targets_usable_ram(uint64_t start, uint64_t size) return bootmem_region_targets_ram(start, start + size, &bootmem); } -/* Special testcase to use when loading payload segments when bounce-buffer is - * supported. Memory ranges tagged with >BM_MEM_OS_CUTOFF may be overwritten at - * the time we jump to payload. - */ -int bootmem_region_usable_with_bounce(uint64_t start, uint64_t size) -{ - return bootmem_region_targets_ram(start, start + size, &bootmem_os); -} - void *bootmem_allocate_buffer(size_t size) { const struct range_entry *r; diff --git a/src/lib/selfboot.c b/src/lib/selfboot.c index fbd1cc4320..c9c8beefd8 100644 --- a/src/lib/selfboot.c +++ b/src/lib/selfboot.c @@ -30,200 +30,6 @@ #include <timestamp.h> #include <cbmem.h> -static const unsigned long lb_start = (unsigned long)&_program; -static const unsigned long lb_end = (unsigned long)&_eprogram; - -struct segment { - struct segment *next; - struct segment *prev; - unsigned long s_dstaddr; - unsigned long s_srcaddr; - unsigned long s_memsz; - unsigned long s_filesz; - int compression; -}; - -static void segment_insert_before(struct segment *seg, struct segment *new) -{ - new->next = seg; - new->prev = seg->prev; - seg->prev->next = new; - seg->prev = new; -} - -static void segment_insert_after(struct segment *seg, struct segment *new) -{ - new->next = seg->next; - new->prev = seg; - seg->next->prev = new; - seg->next = new; -} - -/* The problem: - * Static executables all want to share the same addresses - * in memory because only a few addresses are reliably present on - * a machine, and implementing general relocation is hard. - * - * The solution: - * - Allocate a buffer the size of the coreboot image plus additional - * required space. - * - Anything that would overwrite coreboot copy into the lower part of - * the buffer. - * - After loading an ELF image copy coreboot to the top of the buffer. - * - Then jump to the loaded image. - * - * Benefits: - * - Nearly arbitrary standalone executables can be loaded. - * - coreboot is preserved, so it can be returned to. - * - The implementation is still relatively simple, - * and much simpler than the general case implemented in kexec. - */ - -static unsigned long bounce_size, bounce_buffer; - -static void get_bounce_buffer(unsigned long req_size) -{ - unsigned long lb_size; - void *buffer; - - /* When the ramstage is relocatable there is no need for a bounce - * buffer. All payloads should not overlap the ramstage. - */ - if (IS_ENABLED(CONFIG_RELOCATABLE_RAMSTAGE) || - !arch_supports_bounce_buffer()) { - bounce_buffer = ~0UL; - bounce_size = 0; - return; - } - - lb_size = lb_end - lb_start; - /* Plus coreboot size so I have somewhere - * to place a copy to return to. - */ - lb_size = req_size + lb_size; - - buffer = bootmem_allocate_buffer(lb_size); - - printk(BIOS_SPEW, "Bounce Buffer at %p, %lu bytes\n", buffer, lb_size); - - bounce_buffer = (uintptr_t)buffer; - bounce_size = req_size; -} - -static int overlaps_coreboot(struct segment *seg) -{ - unsigned long start, end; - start = seg->s_dstaddr; - end = start + seg->s_memsz; - return !((end <= lb_start) || (start >= lb_end)); -} - -static int relocate_segment(unsigned long buffer, struct segment *seg) -{ - /* Modify all segments that want to load onto coreboot - * to load onto the bounce buffer instead. - */ - /* ret: 1 : A new segment is inserted before the seg. - * 0 : A new segment is inserted after the seg, or no new one. - */ - unsigned long start, middle, end, ret = 0; - - printk(BIOS_SPEW, "lb: [0x%016lx, 0x%016lx)\n", - lb_start, lb_end); - - /* I don't conflict with coreboot so get out of here */ - if (!overlaps_coreboot(seg)) - return 0; - - if (!arch_supports_bounce_buffer()) - die("bounce buffer not supported"); - - start = seg->s_dstaddr; - middle = start + seg->s_filesz; - end = start + seg->s_memsz; - - printk(BIOS_SPEW, "segment: [0x%016lx, 0x%016lx, 0x%016lx)\n", - start, middle, end); - - if (seg->compression == CBFS_COMPRESS_NONE) { - /* Slice off a piece at the beginning - * that doesn't conflict with coreboot. - */ - if (start < lb_start) { - struct segment *new; - unsigned long len = lb_start - start; - new = malloc(sizeof(*new)); - *new = *seg; - new->s_memsz = len; - seg->s_memsz -= len; - seg->s_dstaddr += len; - seg->s_srcaddr += len; - if (seg->s_filesz > len) { - new->s_filesz = len; - seg->s_filesz -= len; - } else { - seg->s_filesz = 0; - } - - /* Order by stream offset */ - segment_insert_before(seg, new); - - /* compute the new value of start */ - start = seg->s_dstaddr; - - printk(BIOS_SPEW, - " early: [0x%016lx, 0x%016lx, 0x%016lx)\n", - new->s_dstaddr, - new->s_dstaddr + new->s_filesz, - new->s_dstaddr + new->s_memsz); - - ret = 1; - } - - /* Slice off a piece at the end - * that doesn't conflict with coreboot - */ - if (end > lb_end) { - unsigned long len = lb_end - start; - struct segment *new; - new = malloc(sizeof(*new)); - *new = *seg; - seg->s_memsz = len; - new->s_memsz -= len; - new->s_dstaddr += len; - new->s_srcaddr += len; - if (seg->s_filesz > len) { - seg->s_filesz = len; - new->s_filesz -= len; - } else { - new->s_filesz = 0; - } - /* Order by stream offset */ - segment_insert_after(seg, new); - - printk(BIOS_SPEW, - " late: [0x%016lx, 0x%016lx, 0x%016lx)\n", - new->s_dstaddr, - new->s_dstaddr + new->s_filesz, - new->s_dstaddr + new->s_memsz); - } - } - - /* Now retarget this segment onto the bounce buffer */ - /* sort of explanation: the buffer is a 1:1 mapping to coreboot. - * so you will make the dstaddr be this buffer, and it will get copied - * later to where coreboot lives. - */ - seg->s_dstaddr = buffer + (seg->s_dstaddr - lb_start); - - printk(BIOS_SPEW, " bounce: [0x%016lx, 0x%016lx, 0x%016lx)\n", - seg->s_dstaddr, - seg->s_dstaddr + seg->s_filesz, - seg->s_dstaddr + seg->s_memsz); - - return ret; -} - /* Decode a serialized cbfs payload segment * from memory into native endianness. */ @@ -238,200 +44,36 @@ static void cbfs_decode_payload_segment(struct cbfs_payload_segment *segment, segment->mem_len = read_be32(&src->mem_len); } -static int build_self_segment_list( - struct segment *head, - struct cbfs_payload *cbfs_payload, uintptr_t *entry) +static int segment_targets_usable_ram(void *dest, unsigned long memsz) { - struct segment *new; - struct cbfs_payload_segment *current_segment, *first_segment, segment; - - memset(head, 0, sizeof(*head)); - head->next = head->prev = head; - - first_segment = &cbfs_payload->segments; - - for (current_segment = first_segment;; ++current_segment) { - printk(BIOS_DEBUG, - "Loading segment from ROM address 0x%p\n", - current_segment); - - cbfs_decode_payload_segment(&segment, current_segment); + uintptr_t d = (uintptr_t) dest; + if (bootmem_region_targets_usable_ram(d, memsz)) + return 1; - switch (segment.type) { - case PAYLOAD_SEGMENT_PARAMS: - printk(BIOS_DEBUG, " parameter section (skipped)\n"); - continue; - - case PAYLOAD_SEGMENT_CODE: - case PAYLOAD_SEGMENT_DATA: - printk(BIOS_DEBUG, " %s (compression=%x)\n", - segment.type == PAYLOAD_SEGMENT_CODE - ? "code" : "data", segment.compression); - - new = malloc(sizeof(*new)); - new->s_dstaddr = segment.load_addr; - new->s_memsz = segment.mem_len; - new->compression = segment.compression; - new->s_srcaddr = (uintptr_t) - ((unsigned char *)first_segment) - + segment.offset; - new->s_filesz = segment.len; - - printk(BIOS_DEBUG, - " New segment dstaddr 0x%lx memsize 0x%lx srcaddr 0x%lx filesize 0x%lx\n", - new->s_dstaddr, new->s_memsz, new->s_srcaddr, - new->s_filesz); - - /* Clean up the values */ - if (new->s_filesz > new->s_memsz) { - new->s_filesz = new->s_memsz; - printk(BIOS_DEBUG, - " cleaned up filesize 0x%lx\n", - new->s_filesz); - } - break; - - case PAYLOAD_SEGMENT_BSS: - printk(BIOS_DEBUG, " BSS 0x%p (%d byte)\n", (void *) - (intptr_t)segment.load_addr, segment.mem_len); - - new = malloc(sizeof(*new)); - new->s_filesz = 0; - new->s_srcaddr = (uintptr_t) - ((unsigned char *)first_segment) - + segment.offset; - new->s_dstaddr = segment.load_addr; - new->s_memsz = segment.mem_len; - new->compression = CBFS_COMPRESS_NONE; - break; - - case PAYLOAD_SEGMENT_ENTRY: - printk(BIOS_DEBUG, " Entry Point 0x%p\n", (void *) - (intptr_t)segment.load_addr); - - *entry = segment.load_addr; - /* Per definition, a payload always has the entry point - * as last segment. Thus, we use the occurrence of the - * entry point as break condition for the loop. - * Can we actually just look at the number of section? - */ - return 1; - - default: - /* We found something that we don't know about. Throw - * hands into the sky and run away! - */ - printk(BIOS_EMERG, "Bad segment type %x\n", - segment.type); - return -1; - } + if (payload_arch_usable_ram_quirk(d, memsz)) + return 1; - /* We have found another CODE, DATA or BSS segment */ - /* Insert new segment at the end of the list */ - segment_insert_before(head, new); - } - - return 1; -} - -__weak int payload_arch_usable_ram_quirk(uint64_t start, uint64_t size) -{ + printk(BIOS_ERR, "SELF segment doesn't target RAM: 0x%p, %lu bytes\n", dest, memsz); + bootmem_dump_ranges(); return 0; } -static int payload_targets_usable_ram(struct segment *head) -{ - struct segment *ptr; - - for (ptr = head->next; ptr != head; ptr = ptr->next) { - if (bootmem_region_targets_usable_ram(ptr->s_dstaddr, - ptr->s_memsz)) - continue; - - if (payload_arch_usable_ram_quirk(ptr->s_dstaddr, ptr->s_memsz)) - continue; - - if (arch_supports_bounce_buffer() && - bootmem_region_usable_with_bounce(ptr->s_dstaddr, - ptr->s_memsz)) { - printk(BIOS_DEBUG, - "Payload is loaded over non-relocatable " - "ramstage. Will use bounce-buffer.\n"); - return 1; - } - - /* Payload segment not targeting RAM. */ - printk(BIOS_ERR, "SELF Payload doesn't target RAM:\n"); - printk(BIOS_ERR, "Failed Segment: 0x%lx, %lu bytes\n", - ptr->s_dstaddr, ptr->s_memsz); - bootmem_dump_ranges(); - return 0; - } - - return 1; -} - -static int load_self_segments(struct segment *head, struct prog *payload, - bool check_regions) +static int load_one_segment(uint8_t *dest, + uint8_t *src, + size_t len, + size_t memsz, + uint32_t compression, + int flags) { - struct segment *ptr; - unsigned long bounce_high = lb_end; - - if (check_regions) { - if (!payload_targets_usable_ram(head)) - return 0; - } - - for (ptr = head->next; ptr != head; ptr = ptr->next) { - /* - * Add segments to bootmem memory map before a bounce buffer is - * allocated so that there aren't conflicts with the actual - * payload. - */ - if (check_regions) { - bootmem_add_range(ptr->s_dstaddr, ptr->s_memsz, - BM_MEM_PAYLOAD); - } - - if (!overlaps_coreboot(ptr)) - continue; - if (ptr->s_dstaddr + ptr->s_memsz > bounce_high) - bounce_high = ptr->s_dstaddr + ptr->s_memsz; - } - get_bounce_buffer(bounce_high - lb_start); - if (!bounce_buffer) { - printk(BIOS_ERR, "Could not find a bounce buffer...\n"); - return 0; - } - - for (ptr = head->next; ptr != head; ptr = ptr->next) { - unsigned char *dest, *src, *middle, *end; - size_t len, memsz; - printk(BIOS_DEBUG, - "Loading Segment: addr: 0x%016lx memsz: 0x%016lx filesz: 0x%016lx\n", - ptr->s_dstaddr, ptr->s_memsz, ptr->s_filesz); - - /* Modify the segment to load onto the bounce_buffer if - * necessary. - */ - if (relocate_segment(bounce_buffer, ptr)) { - ptr = (ptr->prev)->prev; - continue; - } - - printk(BIOS_DEBUG, - "Post relocation: addr: 0x%016lx memsz: 0x%016lx filesz: 0x%016lx\n", - ptr->s_dstaddr, ptr->s_memsz, ptr->s_filesz); + unsigned char *middle, *end; + printk(BIOS_DEBUG, "Loading Segment: addr: 0x%p memsz: 0x%016zx filesz: 0x%016zx\n", + dest, memsz, len); /* Compute the boundaries of the segment */ - dest = (unsigned char *)(ptr->s_dstaddr); - src = (unsigned char *)(ptr->s_srcaddr); - len = ptr->s_filesz; - memsz = ptr->s_memsz; end = dest + memsz; /* Copy data from the initial buffer */ - switch (ptr->compression) { + switch (compression) { case CBFS_COMPRESS_LZMA: { printk(BIOS_DEBUG, "using LZMA\n"); timestamp_add_now(TS_START_ULZMA); @@ -456,9 +98,8 @@ static int load_self_segments(struct segment *head, struct prog *payload, break; } default: - printk(BIOS_INFO, "CBFS: Unknown compression type %d\n", - ptr->compression); - return -1; + printk(BIOS_INFO, "CBFS: Unknown compression type %d\n", compression); + return 0; } /* Calculate middle after any changes to len. */ middle = dest + len; @@ -479,52 +120,111 @@ static int load_self_segments(struct segment *head, struct prog *payload, memset(middle, 0, end - middle); } - /* Copy the data that's outside the area that shadows ramstage - */ - printk(BIOS_DEBUG, "dest %p, end %p, bouncebuffer %lx\n", dest, - end, bounce_buffer); - if ((unsigned long)end > bounce_buffer) { - if ((unsigned long)dest < bounce_buffer) { - unsigned char *from = dest; - unsigned char *to = (unsigned char *) - (lb_start - (bounce_buffer - - (unsigned long)dest)); - unsigned long amount = bounce_buffer - - (unsigned long)dest; - printk(BIOS_DEBUG, - "move prefix around: from %p, to %p, amount: %lx\n", - from, to, amount); - memcpy(to, from, amount); - } - if ((unsigned long)end > bounce_buffer + (lb_end - - lb_start)) { - unsigned long from = bounce_buffer + (lb_end - - lb_start); - unsigned long to = lb_end; - unsigned long amount = - (unsigned long)end - from; - printk(BIOS_DEBUG, - "move suffix around: from %lx, to %lx, amount: %lx\n", - from, to, amount); - memcpy((char *)to, (char *)from, amount); - } - } - /* * Each architecture can perform additional operations * on the loaded segment */ - prog_segment_loaded((uintptr_t)dest, ptr->s_memsz, - ptr->next == head ? SEG_FINAL : 0); + prog_segment_loaded((uintptr_t)dest, memsz, flags); + + + return 1; +} + +/* Note: this function is a bit dangerous so is not exported. + * It assumes you're smart enough not to call it with the very + * last segment, since it uses seg + 1 */ +static int last_loadable_segment(struct cbfs_payload_segment *seg) +{ + return read_be32(&(seg + 1)->type) == PAYLOAD_SEGMENT_ENTRY; +} + +static int load_payload_segments( + struct cbfs_payload_segment *cbfssegs, + int check_regions, + uintptr_t *entry) +{ + uint8_t *dest, *src; + size_t filesz, memsz; + uint32_t compression; + struct cbfs_payload_segment *first_segment, *seg, segment; + int flags = 0; + + for (first_segment = seg = cbfssegs;; ++seg) { + printk(BIOS_DEBUG, "Loading segment from ROM address 0x%p\n", seg); + + cbfs_decode_payload_segment(&segment, seg); + dest = (uint8_t *)(uintptr_t)segment.load_addr; + memsz = segment.mem_len; + compression = segment.compression; + filesz = segment.len; + + switch (segment.type) { + case PAYLOAD_SEGMENT_CODE: + case PAYLOAD_SEGMENT_DATA: + printk(BIOS_DEBUG, " %s (compression=%x)\n", + segment.type == PAYLOAD_SEGMENT_CODE + ? "code" : "data", segment.compression); + src = ((uint8_t *)first_segment) + segment.offset; + printk(BIOS_DEBUG, + " New segment dstaddr 0x%p memsize 0x%zx srcaddr 0x%p filesize 0x%zx\n", + dest, memsz, src, filesz); + + /* Clean up the values */ + if (filesz > memsz) { + filesz = memsz; + printk(BIOS_DEBUG, " cleaned up filesize 0x%zx\n", filesz); + } + break; + + case PAYLOAD_SEGMENT_BSS: + printk(BIOS_DEBUG, " BSS 0x%p (%d byte)\n", (void *) + (intptr_t)segment.load_addr, segment.mem_len); + filesz = 0; + src = ((uint8_t *)first_segment) + segment.offset; + compression = CBFS_COMPRESS_NONE; + break; + + case PAYLOAD_SEGMENT_ENTRY: + printk(BIOS_DEBUG, " Entry Point 0x%p\n", (void *) + (intptr_t)segment.load_addr); + + *entry = segment.load_addr; + /* Per definition, a payload always has the entry point + * as last segment. Thus, we use the occurrence of the + * entry point as break condition for the loop. + */ + return 0; + + default: + /* We found something that we don't know about. Throw + * hands into the sky and run away! + */ + printk(BIOS_EMERG, "Bad segment type %x\n", segment.type); + return -1; + } + if (check_regions && !segment_targets_usable_ram(dest, memsz)) + return -1; + /* Note that the 'seg + 1' is safe as we only call this + * function on "not the last" * items, since entry + * is always last. */ + if (last_loadable_segment(seg)) + flags = SEG_FINAL; + if (!load_one_segment(dest, src, filesz, memsz, compression, flags)) + return -1; } return 1; } +__weak int payload_arch_usable_ram_quirk(uint64_t start, uint64_t size) +{ + return 0; +} + bool selfload(struct prog *payload, bool check_regions) { uintptr_t entry = 0; - struct segment head; + struct cbfs_payload_segment *cbfssegs; void *data; data = rdev_mmap_full(prog_rdev(payload)); @@ -532,21 +232,14 @@ bool selfload(struct prog *payload, bool check_regions) if (data == NULL) return false; - /* Preprocess the self segments */ - if (!build_self_segment_list(&head, data, &entry)) - goto out; - - /* Load the segments */ - if (!load_self_segments(&head, payload, check_regions)) + cbfssegs = &((struct cbfs_payload *)data)->segments; + if (load_payload_segments(cbfssegs, check_regions, &entry)) goto out; printk(BIOS_SPEW, "Loaded segments\n"); rdev_munmap(prog_rdev(payload), data); - /* Update the payload's area with the bounce buffer information. */ - prog_set_area(payload, (void *)(uintptr_t)bounce_buffer, bounce_size); - /* Pass cbtables to payload if architecture desires it. */ prog_set_entry(payload, (void *)entry, cbmem_find(CBMEM_ID_CBTABLE)); |