/* SPDX-License-Identifier: GPL-2.0-only */ #include <commonlib/bsd/compression.h> #include <commonlib/endian.h> #include <console/console.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <symbols.h> #include <cbfs.h> #include <lib.h> #include <bootmem.h> #include <program_loading.h> #include <timestamp.h> #include <cbmem.h> /* The type syntax for C is essentially unparsable. -- Rob Pike */ typedef int (*checker_t)(struct cbfs_payload_segment *cbfssegs, void *args); /* Decode a serialized cbfs payload segment * from memory into native endianness. */ static void cbfs_decode_payload_segment(struct cbfs_payload_segment *segment, const struct cbfs_payload_segment *src) { segment->type = read_be32(&src->type); segment->compression = read_be32(&src->compression); segment->offset = read_be32(&src->offset); segment->load_addr = read_be64(&src->load_addr); segment->len = read_be32(&src->len); segment->mem_len = read_be32(&src->mem_len); } static int segment_targets_type(void *dest, unsigned long memsz, enum bootmem_type dest_type) { uintptr_t d = (uintptr_t) dest; if (bootmem_region_targets_type(d, memsz, dest_type)) return 1; if (payload_arch_usable_ram_quirk(d, memsz)) return 1; printk(BIOS_ERR, "SELF segment doesn't target RAM: %p, %lu bytes\n", dest, memsz); bootmem_dump_ranges(); return 0; } static int load_one_segment(uint8_t *dest, uint8_t *src, size_t len, size_t memsz, uint32_t compression, int flags) { unsigned char *middle, *end; printk(BIOS_DEBUG, "Loading Segment: addr: %p memsz: 0x%016zx filesz: 0x%016zx\n", dest, memsz, len); /* Compute the boundaries of the segment */ end = dest + memsz; /* Copy data from the initial buffer */ switch (compression) { case CBFS_COMPRESS_LZMA: { printk(BIOS_DEBUG, "using LZMA\n"); timestamp_add_now(TS_START_ULZMA); len = ulzman(src, len, dest, memsz); timestamp_add_now(TS_END_ULZMA); if (!len) /* Decompression Error. */ return 0; break; } case CBFS_COMPRESS_LZ4: { printk(BIOS_DEBUG, "using LZ4\n"); timestamp_add_now(TS_START_ULZ4F); len = ulz4fn(src, len, dest, memsz); timestamp_add_now(TS_END_ULZ4F); if (!len) /* Decompression Error. */ return 0; break; } case CBFS_COMPRESS_NONE: { printk(BIOS_DEBUG, "it's not compressed!\n"); memcpy(dest, src, len); break; } default: printk(BIOS_INFO, "CBFS: Unknown compression type %d\n", compression); return 0; } /* Calculate middle after any changes to len. */ middle = dest + len; printk(BIOS_SPEW, "[ 0x%08lx, %08lx, 0x%08lx) <- %08lx\n", (unsigned long)dest, (unsigned long)middle, (unsigned long)end, (unsigned long)src); /* Zero the extra bytes between middle & end */ if (middle < end) { printk(BIOS_DEBUG, "Clearing Segment: addr: 0x%016lx memsz: 0x%016lx\n", (unsigned long)middle, (unsigned long)(end - middle)); /* Zero the extra bytes */ memset(middle, 0, end - middle); } /* * Each architecture can perform additional operations * on the loaded segment */ 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 check_payload_segments(struct cbfs_payload_segment *cbfssegs, void *args) { uint8_t *dest; size_t memsz; struct cbfs_payload_segment *seg, segment; enum bootmem_type dest_type = *(enum bootmem_type *)args; for (seg = cbfssegs;; ++seg) { printk(BIOS_DEBUG, "Checking segment from ROM address %p\n", seg); cbfs_decode_payload_segment(&segment, seg); dest = (uint8_t *)(uintptr_t)segment.load_addr; memsz = segment.mem_len; if (segment.type == PAYLOAD_SEGMENT_ENTRY) break; if (!segment_targets_type(dest, memsz, dest_type)) return -1; } return 0; } static int load_payload_segments(struct cbfs_payload_segment *cbfssegs, 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 %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 %p memsize 0x%zx srcaddr %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 %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 %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; } /* 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; } static void *selfprepare(struct prog *payload) { void *data; data = rdev_mmap_full(prog_rdev(payload)); return data; } static bool _selfload(struct prog *payload, checker_t f, void *args) { uintptr_t entry = 0; struct cbfs_payload_segment *cbfssegs; void *data; data = selfprepare(payload); if (data == NULL) return false; cbfssegs = &((struct cbfs_payload *)data)->segments; if (f && f(cbfssegs, args)) goto out; if (load_payload_segments(cbfssegs, &entry)) goto out; printk(BIOS_SPEW, "Loaded segments\n"); rdev_munmap(prog_rdev(payload), data); /* Pass cbtables to payload if architecture desires it. */ prog_set_entry(payload, (void *)entry, cbmem_find(CBMEM_ID_CBTABLE)); return true; out: rdev_munmap(prog_rdev(payload), data); return false; } bool selfload_check(struct prog *payload, enum bootmem_type dest_type) { return _selfload(payload, check_payload_segments, &dest_type); } bool selfload(struct prog *payload) { return _selfload(payload, NULL, 0); }