/* AMD Family 17h and later BIOS compressor */ /* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #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", no_argument, 0, 'c' }, {"maxsize", required_argument, 0, 'm' }, {"uncompress", no_argument, 0, 'u' }, {"help", no_argument, 0, 'h' }, }; static void usage(void) { printf(": 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: -i in_file -o out_file -[c|u]\n"); printf("-i | --infile Input file\n"); printf("-o | --outfile Output file\n"); printf("-c | --compress Compress\n"); printf("-m | --maxsize 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; }