diff options
author | Marshall Dawson <marshalldawson3rd@gmail.com> | 2019-03-19 14:48:33 -0600 |
---|---|---|
committer | Martin Roth <martinroth@google.com> | 2019-07-03 21:28:43 +0000 |
commit | 30cf1551683810504f7823e42d4cb6515459cff8 (patch) | |
tree | 9ee60318d62c7428453cf2f8a0ee73f50c65f173 /util/cbfstool/amdcompress.c | |
parent | 5e4a26a76e5ec342e10a5c8ce2ff26eb03bb5bc2 (diff) | |
download | coreboot-30cf1551683810504f7823e42d4cb6515459cff8.tar.xz |
util/cbfstool: Add AMD BIOS compression tool for PSP
Add a utility to generate a compressed BIOS image for AMD Family 17h.
If the input is an elf file, the utility extracts the program portion
for compression. Otherwise the file is compressed as-is.
In modern AMD systems, the PSP brings up DRAM then uncompresses the
BIOS image into memory prior to x86 beginning execution. The PSP
supports a zlib engine, and interprets the first 256 bytes as a
header, where offset 0x14 containing the uncompressed size. For
further details, see AMD Platform Security Processor BIOS Architecture
Design Guide for AMD Family 17h Processors (NDA only, #55758).
BUG=b:127766506
TEST=Use with WIP Picasso
Change-Id: Id1c54e0a6dae9e4a0362c6635fe8b8aa48a369d8
Signed-off-by: Marshall Dawson <marshalldawson3rd@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/33401
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Raul Rangel <rrangel@chromium.org>
Reviewed-by: Edward O'Callaghan <quasisec@chromium.org>
Diffstat (limited to 'util/cbfstool/amdcompress.c')
-rw-r--r-- | util/cbfstool/amdcompress.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/util/cbfstool/amdcompress.c b/util/cbfstool/amdcompress.c new file mode 100644 index 0000000000..641e1ea6c0 --- /dev/null +++ b/util/cbfstool/amdcompress.c @@ -0,0 +1,364 @@ +/* + * AMD Family 17h and later BIOS compressor + * + * Copyright (C) 2019 Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <getopt.h> +#include <elfparsing.h> +#include "zlib.h" + +#define DEBUG_FILE 0 + +#define HDR_SIZE 256 +#define UNCOMP_MAX 0x300000 + +#define DIR_UNDEF 0 +#define DIR_COMP 1 +#define DIR_UNCOMP 2 + +typedef struct _header { + uint32_t rsvd1[5]; + uint32_t size; + uint32_t rsvd2[58]; +} __attribute__((packed)) header; + +static const char *optstring = "i:o:cm:uh"; + +static struct option long_options[] = { + {"infile", required_argument, 0, 'i' }, + {"outfile", required_argument, 0, 'o' }, + {"compress", required_argument, 0, 'c' }, + {"maxsize", required_argument, 0, 'h' }, + {"uncompress", required_argument, 0, 'u' }, + {"help", no_argument, 0, 'h' }, +}; + +static void usage(void) +{ + printf("<name>: Extract or create a zlib compressed BIOS binary\n"); + printf(" image. A compressed image contains a 256 byte\n"); + printf(" header with a 32-bit size at 0x14.\n"); + printf("Usage: <name> -i in_file -o out_file -[c|u]\n"); + printf("-i | --infile <FILE> Input file\n"); + printf("-o | --outfile <FILE> Output file\n"); + printf("-c | --compress Compress\n"); + printf("-m | --maxsize <HEX_VAL> Maximum uncompressed size (optional)\n"); + printf(" * On compress: verify uncompressed size\n"); + printf(" will be less than or equal maxsize\n"); + printf(" * On uncompress: override default buffer size\n"); + printf(" allocation of 0x%x bytes\n", UNCOMP_MAX); + printf("-u | --uncompress Uncompress\n"); + printf("-h | --help Display this message\n"); + + exit(1); +} + +static int do_file(char *name, size_t *size, int oflag) +{ + struct stat fd_stat; + int fd; + + fd = open(name, oflag, 0666); + if (fd < 0) + return -1; + + if (fstat(fd, &fd_stat)) { + close(fd); + return -1; + } + + if (size) + *size = fd_stat.st_size; + return fd; +} + +static int parse_elf_to_xip_ram(const struct buffer *input, + struct buffer *output) +{ + struct parsed_elf pelf; + + if (parse_elf(input, &pelf, ELF_PARSE_ALL)) + return 1; + if (buffer_create(output, pelf.phdr->p_filesz, "") != 0) + return 1; + + memcpy(output->data, input->data + pelf.phdr->p_offset, output->size); + + return 0; +} + +static int convert_elf(struct buffer *buf) +{ + struct buffer out; + + if (parse_elf_to_xip_ram(buf, &out)) { + printf("\tError parsing ELF file\n"); + return -1; + } + + /* Discard the elf file in buf and replace with the progbits */ + free(buf->data); + buf->data = out.data; + buf->size = out.size; + + return 0; +} + +static int iself(const void *input) +{ + const Elf32_Ehdr *ehdr = input; + return !memcmp(ehdr->e_ident, ELFMAG, 4); +} + +/* todo: Consider using deflate() and inflate() instead of compress() and + * decompress(), especially if memory allocation somehow becomes a problem. + * Those two functions can operate on streams and process chunks of data. + */ + +/* Build the required header and follow it with the compressed image. Detect + * whether the input is an elf image, and if so, compress only the progbits. + * + * header + * 0 +------+-------+-------+-------+ + * | | | | | + * +----------------------+-------+ + * | | size | | | + * +----------------------+-------+ + * | | | | | + * | | | ... | + * 256 +------------------------------+ + * |compressed image | + * | ... | + * | ... | + * | ... | + * n +------------------------------+ + */ +static void do_compress(char *outf, char *inf, size_t max_size) +{ + int out_fd, in_fd; + struct buffer inbf, outbf; + int err; + + in_fd = do_file(inf, &inbf.size, O_RDONLY); + if (in_fd < 0) { + printf("\tError opening input file %s\n", inf); + err = 1; + goto out; + } + + out_fd = do_file(outf, 0, O_CREAT | O_WRONLY); + if (out_fd < 0) { + printf("\tError opening output file %s\n", outf); + err = 1; + goto out_close_in; + } + + inbf.data = calloc(inbf.size, 1); + if (!inbf.data) { + printf("\tError allocating 0x%zx bytes for input buffer\n", inbf.size); + err = 1; + goto out_close_out; + } + + if (read(in_fd, inbf.data, inbf.size) != (ssize_t)inbf.size) { + printf("\tError reading input file %s\n", inf); + err = 1; + goto out_free_in; + } + + if (iself(inbf.data)) { + if (convert_elf(&inbf)) { + err = 1; + goto out_free_in; + } + } + + if (max_size && inbf.size > max_size) { + printf("\tError - size (%zx) exceeds specified max_size (%zx)\n", + inbf.size, max_size); + err = 1; + goto out_free_in; + } + + outbf.size = inbf.size; /* todo: tbd worst case? */ + outbf.size += sizeof(header); + outbf.data = calloc(outbf.size, 1); + if (!outbf.size) { + printf("\tError allocating 0x%zx bytes for output buffer\n", outbf.size); + err = 1; + goto out_free_in; + } + + err = compress((Bytef *)(outbf.data + sizeof(header)), &outbf.size, + (Bytef *)inbf.data, inbf.size); + if (err != Z_OK) { + printf("\tzlib compression error %d\n", err); + err = 1; + goto out_free_out; + } + + if (DEBUG_FILE) + printf("\tCompressed 0x%zx bytes into 0x%zx\n", inbf.size, + outbf.size - sizeof(header)); + + ((header *)outbf.data)->size = outbf.size; + + if (write(out_fd, outbf.data, outbf.size + sizeof(header)) + != (ssize_t)(outbf.size + sizeof(header))) { + printf("\tError writing to %s\n", outf); + err = 1; + /* fall through to out_free_out */ + } + +out_free_out: + free(outbf.data); +out_free_in: + free(inbf.data); +out_close_out: + close(out_fd); +out_close_in: + close(in_fd); +out: + if (err) + exit(err); +} + +static void do_uncompress(char *outf, char *inf, size_t max_size) +{ + int out_fd, in_fd; + char *in_buf, *out_buf; + size_t size_unc, size_comp; + size_t bytes; + int err; + + in_fd = do_file(inf, &size_comp, O_RDONLY); + if (in_fd < 0) { + printf("\tError opening input file %s\n", inf); + err = 1; + goto out; + } + + out_fd = do_file(outf, 0, O_CREAT | O_WRONLY); + if (out_fd < 0) { + printf("\tError opening output file %s\n", outf); + err = 1; + goto out_close_in; + } + + in_buf = calloc(size_comp, 1); + if (!in_buf) { + printf("\tError allocating 0x%zx bytes for input buffer\n", size_comp); + err = 1; + goto out_close_out; + } + + bytes = read(in_fd, in_buf, size_comp); + if (bytes != size_comp) { + printf("\tError reading input file %s\n", inf); + err = 1; + goto out_free_in; + } + + size_comp = ((header *)in_buf)->size; + + size_unc = max_size ? max_size : UNCOMP_MAX; + out_buf = calloc(size_unc, 1); + if (!out_buf) { + printf("\tError allocating 0x%zx bytes for output buffer\n", size_unc); + err = 1; + goto out_free_in; + } + + err = uncompress((Bytef *)out_buf, &size_unc, + (Bytef *)in_buf + sizeof(header), size_comp); + if (err != Z_OK) { + printf("\tzlib uncompression error %d\n", err); + err = 1; + goto out_free_out; + } + + if (DEBUG_FILE) + printf("Uncompressed 0x%zx bytes into 0x%zx\n", size_comp, size_unc); + + bytes = write(out_fd, out_buf, size_unc); + if (bytes != size_unc) { + printf("\tError writing to %s\n", outf); + err = 1; + /* fall through to out_free_out */ + } + +out_free_out: + free(out_buf); +out_free_in: + free(in_buf); +out_close_out: + close(out_fd); +out_close_in: + close(in_fd); +out: + if (err) + exit(err); +} + +int main(int argc, char *argv[]) +{ + int c; + char *inf = 0, *outf = 0, *scratch; + int direction = DIR_UNDEF; + size_t max_size = 0; + + while (1) { + int optindex = 0; + + c = getopt_long(argc, argv, optstring, long_options, &optindex); + if (c == -1) + break; + + switch (c) { + case 'i': + inf = optarg; + break; + case 'o': + outf = optarg; + break; + case 'c': + if (direction != DIR_UNDEF) + usage(); + direction = DIR_COMP; + break; + case 'u': + if (direction != DIR_UNDEF) + usage(); + direction = DIR_UNCOMP; + break; + case 'm': + max_size = strtoull(optarg, &scratch, 16); + break; + case 'h': + usage(); + } + } + if (!inf || !outf || direction == DIR_UNDEF) + usage(); + + if (direction == DIR_COMP) + do_compress(outf, inf, max_size); + else + do_uncompress(outf, inf, max_size); + + return 0; +} |