blob: 3aff4423ad016f82ed706e78bc61aa141e213e52 [file] [log] [blame]
/*
* Wrapper around the kernel's pre-boot decompression library.
*
* Copyright (C) IBM Corporation 2016.
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#include "elf.h"
#include "page.h"
#include "string.h"
#include "stdio.h"
#include "ops.h"
#include "reg.h"
#include "types.h"
/*
* The decompressor_*.c files play #ifdef games so they can be used in both
* pre-boot and regular kernel code. We need these definitions to make the
* includes work.
*/
#define STATIC static
#define INIT
#define __always_inline inline
/*
* The build process will copy the required zlib source files and headers
* out of lib/ and "fix" the includes so they do not pull in other kernel
* headers.
*/
#ifdef CONFIG_KERNEL_GZIP
# include "decompress_inflate.c"
#endif
#ifdef CONFIG_KERNEL_XZ
# include "xz_config.h"
# include "../../../lib/decompress_unxz.c"
#endif
/* globals for tracking the state of the decompression */
static unsigned long decompressed_bytes;
static unsigned long limit;
static unsigned long skip;
static char *output_buffer;
/*
* flush() is called by __decompress() when the decompressor's scratch buffer is
* full.
*/
static long flush(void *v, unsigned long buffer_size)
{
unsigned long end = decompressed_bytes + buffer_size;
unsigned long size = buffer_size;
unsigned long offset = 0;
char *in = v;
char *out;
/*
* if we hit our decompression limit, we need to fake an error to abort
* the in-progress decompression.
*/
if (decompressed_bytes >= limit)
return -1;
/* skip this entire block */
if (end <= skip) {
decompressed_bytes += buffer_size;
return buffer_size;
}
/* skip some data at the start, but keep the rest of the block */
if (decompressed_bytes < skip && end > skip) {
offset = skip - decompressed_bytes;
in += offset;
size -= offset;
decompressed_bytes += offset;
}
out = &output_buffer[decompressed_bytes - skip];
size = min(decompressed_bytes + size, limit) - decompressed_bytes;
memcpy(out, in, size);
decompressed_bytes += size;
return buffer_size;
}
static void print_err(char *s)
{
/* suppress the "error" when we terminate the decompressor */
if (decompressed_bytes >= limit)
return;
printf("Decompression error: '%s'\n\r", s);
}
/**
* partial_decompress - decompresses part or all of a compressed buffer
* @inbuf: input buffer
* @input_size: length of the input buffer
* @outbuf: input buffer
* @output_size: length of the input buffer
* @skip number of output bytes to ignore
*
* This function takes compressed data from inbuf, decompresses and write it to
* outbuf. Once output_size bytes are written to the output buffer, or the
* stream is exhausted the function will return the number of bytes that were
* decompressed. Otherwise it will return whatever error code the decompressor
* reported (NB: This is specific to each decompressor type).
*
* The skip functionality is mainly there so the program and discover
* the size of the compressed image so that it can ask firmware (if present)
* for an appropriately sized buffer.
*/
long partial_decompress(void *inbuf, unsigned long input_size,
void *outbuf, unsigned long output_size, unsigned long _skip)
{
int ret;
/*
* The skipped bytes needs to be included in the size of data we want
* to decompress.
*/
output_size += _skip;
decompressed_bytes = 0;
output_buffer = outbuf;
limit = output_size;
skip = _skip;
ret = __decompress(inbuf, input_size, NULL, flush, outbuf,
output_size, NULL, print_err);
/*
* If decompression was aborted due to an actual error rather than
* a fake error that we used to abort, then we should report it.
*/
if (decompressed_bytes < limit)
return ret;
return decompressed_bytes - skip;
}