275 lines
7.2 KiB
C
275 lines
7.2 KiB
C
/* 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);
|
|
}
|