| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <commonlib/bsd/compression.h> |
| #include <commonlib/endian.h> |
| #include <console/console.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> |
| #include <types.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) |
| { |
| /* No bootmem to check in earlier stages, caller should not use |
| selfload_check(). */ |
| if (!ENV_RAMSTAGE) { |
| printk(BIOS_ERR, |
| "Callers not supposed to call selfload_check() in romstage"); |
| return 0; |
| } |
| |
| 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_ULZMA_START); |
| len = ulzman(src, len, dest, memsz); |
| timestamp_add_now(TS_ULZMA_END); |
| if (!len) /* Decompression Error. */ |
| return 0; |
| break; |
| } |
| case CBFS_COMPRESS_LZ4: { |
| printk(BIOS_DEBUG, "using LZ4\n"); |
| timestamp_add_now(TS_ULZ4F_START); |
| len = ulz4fn(src, len, dest, memsz); |
| timestamp_add_now(TS_ULZ4F_END); |
| 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, |
| enum bootmem_type dest_type) |
| { |
| uint8_t *dest; |
| size_t memsz; |
| struct cbfs_payload_segment *seg, segment; |
| |
| /* dest_type == INVALID means we're not supposed to check anything. */ |
| if (dest_type == BM_MEM_INVALID) |
| return 0; |
| |
| 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; |
| } |
| |
| bool selfload_mapped(struct prog *payload, void *mapping, |
| enum bootmem_type dest_type) |
| { |
| uintptr_t entry = 0; |
| struct cbfs_payload_segment *cbfssegs; |
| |
| cbfssegs = &((struct cbfs_payload *)mapping)->segments; |
| |
| if (check_payload_segments(cbfssegs, dest_type)) |
| return false; |
| |
| if (load_payload_segments(cbfssegs, &entry)) |
| return false; |
| |
| printk(BIOS_SPEW, "Loaded segments\n"); |
| |
| /* Pass cbtables to payload if architecture desires it. */ |
| prog_set_entry(payload, (void *)entry, cbmem_find(CBMEM_ID_CBTABLE)); |
| |
| return true; |
| } |
| |
| bool selfload_check(struct prog *payload, enum bootmem_type dest_type) |
| { |
| if (prog_locate_hook(payload)) |
| return false; |
| |
| payload->cbfs_type = CBFS_TYPE_SELF; |
| void *mapping = cbfs_type_map(prog_name(payload), NULL, &payload->cbfs_type); |
| if (!mapping) |
| return false; |
| |
| bool ret = selfload_mapped(payload, mapping, dest_type); |
| |
| cbfs_unmap(mapping); |
| return ret; |
| } |
| |
| bool selfload(struct prog *payload) |
| { |
| return selfload_check(payload, BM_MEM_INVALID); |
| } |