The zImage* executable files on ppc64 use a special section called ".kernel:vmlinux.strip" which contains a compressed vmlinux executable file. This patch adds a service to detect and unzip such a section in a malloc'ed buffer. The buffer is then used in elf_ppc64_load() to load the new vmlinux. Signed-off-by: C?dric Le Goater <clg at fr.ibm.com> --- kexec/arch/ppc64/kexec-elf-ppc64.c | 15 ++++ kexec/arch/ppc64/kexec-ppc64.h | 2 + kexec/arch/ppc64/kexec-zImage-ppc64.c | 123 +++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/kexec/arch/ppc64/kexec-elf-ppc64.c b/kexec/arch/ppc64/kexec-elf-ppc64.c index ce1036762582..069d8ba6e690 100644 --- a/kexec/arch/ppc64/kexec-elf-ppc64.c +++ b/kexec/arch/ppc64/kexec-elf-ppc64.c @@ -116,6 +116,8 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len, uint64_t toc_addr; uint32_t my_run_at_load; unsigned int slave_code[256/sizeof (unsigned int)], master_entry; + void *vmlinux_addr; + int vmlinux_size; /* See options.h -- add any more there, too. */ static const struct option options[] = { @@ -184,6 +186,7 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len, modified_cmdline_len = strlen(modified_cmdline); } +retry: /* Parse the Elf file */ result = build_elf_exec_info(buf, len, &ehdr, 0); if (result < 0) { @@ -191,6 +194,18 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len, return result; } + /* If this is a zimage boot wrapper, rebuild the elf info on + * the new vmlinux which has been unzipped and go on with the + * kernel load. + */ + if (!zImage_ppc64_unzip(&ehdr, &vmlinux_addr, &vmlinux_size)) { + free_elf_info(&ehdr); + free((void *) buf); + buf = vmlinux_addr; + len = vmlinux_size; + goto retry; + } + /* Load the Elf data. Physical load addresses in elf64 header do not * show up correctly. Use user supplied address for now to patch the * elf header diff --git a/kexec/arch/ppc64/kexec-ppc64.h b/kexec/arch/ppc64/kexec-ppc64.h index 9a0aecff7b33..e546d4737bfa 100644 --- a/kexec/arch/ppc64/kexec-ppc64.h +++ b/kexec/arch/ppc64/kexec-ppc64.h @@ -37,4 +37,6 @@ typedef struct mem_rgns { extern mem_rgns_t usablemem_rgns; +int zImage_ppc64_unzip(struct mem_ehdr *ehdr, void **buf, int *len); + #endif /* KEXEC_PPC64_H */ diff --git a/kexec/arch/ppc64/kexec-zImage-ppc64.c b/kexec/arch/ppc64/kexec-zImage-ppc64.c index d084ee587be5..67d751f19d7d 100644 --- a/kexec/arch/ppc64/kexec-zImage-ppc64.c +++ b/kexec/arch/ppc64/kexec-zImage-ppc64.c @@ -30,6 +30,9 @@ #include <getopt.h> #include <linux/elf.h> #include "../../kexec.h" +#include "kexec-ppc64.h" + +#include <zlib.h> #define MAX_HEADERS 32 @@ -175,3 +178,123 @@ void zImage_ppc64_usage(void) { fprintf(stderr, "zImage support is still broken\n"); } + +#define HEAD_CRC 2 +#define EXTRA_FIELD 4 +#define ORIG_NAME 8 +#define COMMENT 0x10 +#define RESERVED 0xe0 + +static int get_header_len(const char *header) +{ + int len = 10; + int flags = header[3]; + + /* check for gzip header */ + if ((header[0] != 0x1f) || (header[1] != 0x8b) || + (header[2] != Z_DEFLATED) || (flags & RESERVED) != 0) { + fprintf(stderr, "bad gzip header\n"); + return -1; + } + + if ((flags & EXTRA_FIELD) != 0) + len = 12 + header[10] + (header[11] << 8); + + if ((flags & ORIG_NAME) != 0) + while (header[len++] != 0) + ; + if ((flags & COMMENT) != 0) + while (header[len++] != 0) + ; + if ((flags & HEAD_CRC) != 0) + len += 2; + + return len; +} + +static int gunzip(void *src, int srclen, void *dst, int dstlen) +{ + z_stream strm; + int hdrlen; + int len; + int ret; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + hdrlen = get_header_len(src); + if (hdrlen == -1) + return -1; + + if (hdrlen >= srclen) { + fprintf(stderr, "gzip header too large : %d\n", hdrlen); + return -1; + } + + ret = inflateInit2(&strm, -MAX_WBITS); + if (ret != Z_OK) { + fprintf(stderr, "inflateInit2 failed : %d\n", ret); + return -1; + } + + /* skip gzip header */ + strm.total_in = hdrlen; + strm.next_in = src + hdrlen; + strm.avail_in = srclen - hdrlen; + + strm.next_out = dst; + strm.avail_out = dstlen; + + ret = inflate(&strm, Z_FULL_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) { + fprintf(stderr, "inflate failed: %d %s\n", ret, strm.msg); + return -1; + } + + len = strm.next_out - (unsigned char *) dst; + + inflateEnd(&strm); + + return len; +} + +int zImage_ppc64_unzip(struct mem_ehdr *ehdr, void **buf, int *len) +{ + struct mem_shdr *shdr; + void *vmlinuz_addr; + unsigned long vmlinuz_size; + unsigned int *vmlinux_sizep; + + void *vmlinux_addr; + int vmlinux_size; + + shdr = elf_rel_find_section(ehdr, ".kernel:vmlinux.strip"); + if (!shdr) + return -1; + + vmlinuz_addr = (void *) shdr->sh_data; + vmlinuz_size = shdr->sh_size; + + /* The size of the uncompressed file is stored in the last 4 + * bytes. The vmlinux size should be less than 4G ... */ + vmlinux_sizep = (vmlinuz_addr + vmlinuz_size) - 4; + + fprintf(stderr, "Found vmlinuz at %p, unzipping %d bytes\n", + vmlinuz_addr, *vmlinux_sizep); + vmlinux_addr = xmalloc(*vmlinux_sizep); + + vmlinux_size = gunzip(vmlinuz_addr, vmlinuz_size, + vmlinux_addr, *vmlinux_sizep); + if (vmlinux_size != *vmlinux_sizep) { + fprintf(stderr, "gunzip failed : only got %d of %d bytes.\n", + vmlinux_size, *vmlinux_sizep); + return -1; + } + + *buf = vmlinux_addr; + *len = vmlinux_size; + return 0; +} -- 1.7.10.4