162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Wrapper for decompressing LZ4-compressed kernel, initramfs, and initrd
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2013, LG Electronics, Kyungsik Lee <kyungsik.lee@lge.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#ifdef STATIC
962306a36Sopenharmony_ci#define PREBOOT
1062306a36Sopenharmony_ci#include "lz4/lz4_decompress.c"
1162306a36Sopenharmony_ci#else
1262306a36Sopenharmony_ci#include <linux/decompress/unlz4.h>
1362306a36Sopenharmony_ci#endif
1462306a36Sopenharmony_ci#include <linux/types.h>
1562306a36Sopenharmony_ci#include <linux/lz4.h>
1662306a36Sopenharmony_ci#include <linux/decompress/mm.h>
1762306a36Sopenharmony_ci#include <linux/compiler.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <asm/unaligned.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * Note: Uncompressed chunk size is used in the compressor side
2362306a36Sopenharmony_ci * (userspace side for compression).
2462306a36Sopenharmony_ci * It is hardcoded because there is not proper way to extract it
2562306a36Sopenharmony_ci * from the binary stream which is generated by the preliminary
2662306a36Sopenharmony_ci * version of LZ4 tool so far.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci#define LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE (8 << 20)
2962306a36Sopenharmony_ci#define ARCHIVE_MAGICNUMBER 0x184C2102
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ciSTATIC inline int INIT unlz4(u8 *input, long in_len,
3262306a36Sopenharmony_ci				long (*fill)(void *, unsigned long),
3362306a36Sopenharmony_ci				long (*flush)(void *, unsigned long),
3462306a36Sopenharmony_ci				u8 *output, long *posp,
3562306a36Sopenharmony_ci				void (*error) (char *x))
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	int ret = -1;
3862306a36Sopenharmony_ci	size_t chunksize = 0;
3962306a36Sopenharmony_ci	size_t uncomp_chunksize = LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE;
4062306a36Sopenharmony_ci	u8 *inp;
4162306a36Sopenharmony_ci	u8 *inp_start;
4262306a36Sopenharmony_ci	u8 *outp;
4362306a36Sopenharmony_ci	long size = in_len;
4462306a36Sopenharmony_ci#ifdef PREBOOT
4562306a36Sopenharmony_ci	size_t out_len = get_unaligned_le32(input + in_len);
4662306a36Sopenharmony_ci#endif
4762306a36Sopenharmony_ci	size_t dest_len;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	if (output) {
5162306a36Sopenharmony_ci		outp = output;
5262306a36Sopenharmony_ci	} else if (!flush) {
5362306a36Sopenharmony_ci		error("NULL output pointer and no flush function provided");
5462306a36Sopenharmony_ci		goto exit_0;
5562306a36Sopenharmony_ci	} else {
5662306a36Sopenharmony_ci		outp = large_malloc(uncomp_chunksize);
5762306a36Sopenharmony_ci		if (!outp) {
5862306a36Sopenharmony_ci			error("Could not allocate output buffer");
5962306a36Sopenharmony_ci			goto exit_0;
6062306a36Sopenharmony_ci		}
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (input && fill) {
6462306a36Sopenharmony_ci		error("Both input pointer and fill function provided,");
6562306a36Sopenharmony_ci		goto exit_1;
6662306a36Sopenharmony_ci	} else if (input) {
6762306a36Sopenharmony_ci		inp = input;
6862306a36Sopenharmony_ci	} else if (!fill) {
6962306a36Sopenharmony_ci		error("NULL input pointer and missing fill function");
7062306a36Sopenharmony_ci		goto exit_1;
7162306a36Sopenharmony_ci	} else {
7262306a36Sopenharmony_ci		inp = large_malloc(LZ4_compressBound(uncomp_chunksize));
7362306a36Sopenharmony_ci		if (!inp) {
7462306a36Sopenharmony_ci			error("Could not allocate input buffer");
7562306a36Sopenharmony_ci			goto exit_1;
7662306a36Sopenharmony_ci		}
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci	inp_start = inp;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (posp)
8162306a36Sopenharmony_ci		*posp = 0;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (fill) {
8462306a36Sopenharmony_ci		size = fill(inp, 4);
8562306a36Sopenharmony_ci		if (size < 4) {
8662306a36Sopenharmony_ci			error("data corrupted");
8762306a36Sopenharmony_ci			goto exit_2;
8862306a36Sopenharmony_ci		}
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	chunksize = get_unaligned_le32(inp);
9262306a36Sopenharmony_ci	if (chunksize == ARCHIVE_MAGICNUMBER) {
9362306a36Sopenharmony_ci		if (!fill) {
9462306a36Sopenharmony_ci			inp += 4;
9562306a36Sopenharmony_ci			size -= 4;
9662306a36Sopenharmony_ci		}
9762306a36Sopenharmony_ci	} else {
9862306a36Sopenharmony_ci		error("invalid header");
9962306a36Sopenharmony_ci		goto exit_2;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (posp)
10362306a36Sopenharmony_ci		*posp += 4;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	for (;;) {
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		if (fill) {
10862306a36Sopenharmony_ci			size = fill(inp, 4);
10962306a36Sopenharmony_ci			if (size == 0)
11062306a36Sopenharmony_ci				break;
11162306a36Sopenharmony_ci			if (size < 4) {
11262306a36Sopenharmony_ci				error("data corrupted");
11362306a36Sopenharmony_ci				goto exit_2;
11462306a36Sopenharmony_ci			}
11562306a36Sopenharmony_ci		} else if (size < 4) {
11662306a36Sopenharmony_ci			/* empty or end-of-file */
11762306a36Sopenharmony_ci			goto exit_3;
11862306a36Sopenharmony_ci		}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci		chunksize = get_unaligned_le32(inp);
12162306a36Sopenharmony_ci		if (chunksize == ARCHIVE_MAGICNUMBER) {
12262306a36Sopenharmony_ci			if (!fill) {
12362306a36Sopenharmony_ci				inp += 4;
12462306a36Sopenharmony_ci				size -= 4;
12562306a36Sopenharmony_ci			}
12662306a36Sopenharmony_ci			if (posp)
12762306a36Sopenharmony_ci				*posp += 4;
12862306a36Sopenharmony_ci			continue;
12962306a36Sopenharmony_ci		}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci		if (!fill && chunksize == 0) {
13262306a36Sopenharmony_ci			/* empty or end-of-file */
13362306a36Sopenharmony_ci			goto exit_3;
13462306a36Sopenharmony_ci		}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		if (posp)
13762306a36Sopenharmony_ci			*posp += 4;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci		if (!fill) {
14062306a36Sopenharmony_ci			inp += 4;
14162306a36Sopenharmony_ci			size -= 4;
14262306a36Sopenharmony_ci		} else {
14362306a36Sopenharmony_ci			if (chunksize > LZ4_compressBound(uncomp_chunksize)) {
14462306a36Sopenharmony_ci				error("chunk length is longer than allocated");
14562306a36Sopenharmony_ci				goto exit_2;
14662306a36Sopenharmony_ci			}
14762306a36Sopenharmony_ci			size = fill(inp, chunksize);
14862306a36Sopenharmony_ci			if (size < chunksize) {
14962306a36Sopenharmony_ci				error("data corrupted");
15062306a36Sopenharmony_ci				goto exit_2;
15162306a36Sopenharmony_ci			}
15262306a36Sopenharmony_ci		}
15362306a36Sopenharmony_ci#ifdef PREBOOT
15462306a36Sopenharmony_ci		if (out_len >= uncomp_chunksize) {
15562306a36Sopenharmony_ci			dest_len = uncomp_chunksize;
15662306a36Sopenharmony_ci			out_len -= dest_len;
15762306a36Sopenharmony_ci		} else
15862306a36Sopenharmony_ci			dest_len = out_len;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		ret = LZ4_decompress_fast(inp, outp, dest_len);
16162306a36Sopenharmony_ci		chunksize = ret;
16262306a36Sopenharmony_ci#else
16362306a36Sopenharmony_ci		dest_len = uncomp_chunksize;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		ret = LZ4_decompress_safe(inp, outp, chunksize, dest_len);
16662306a36Sopenharmony_ci		dest_len = ret;
16762306a36Sopenharmony_ci#endif
16862306a36Sopenharmony_ci		if (ret < 0) {
16962306a36Sopenharmony_ci			error("Decoding failed");
17062306a36Sopenharmony_ci			goto exit_2;
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		ret = -1;
17462306a36Sopenharmony_ci		if (flush && flush(outp, dest_len) != dest_len)
17562306a36Sopenharmony_ci			goto exit_2;
17662306a36Sopenharmony_ci		if (output)
17762306a36Sopenharmony_ci			outp += dest_len;
17862306a36Sopenharmony_ci		if (posp)
17962306a36Sopenharmony_ci			*posp += chunksize;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		if (!fill) {
18262306a36Sopenharmony_ci			size -= chunksize;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci			if (size == 0)
18562306a36Sopenharmony_ci				break;
18662306a36Sopenharmony_ci			else if (size < 0) {
18762306a36Sopenharmony_ci				error("data corrupted");
18862306a36Sopenharmony_ci				goto exit_2;
18962306a36Sopenharmony_ci			}
19062306a36Sopenharmony_ci			inp += chunksize;
19162306a36Sopenharmony_ci		}
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ciexit_3:
19562306a36Sopenharmony_ci	ret = 0;
19662306a36Sopenharmony_ciexit_2:
19762306a36Sopenharmony_ci	if (!input)
19862306a36Sopenharmony_ci		large_free(inp_start);
19962306a36Sopenharmony_ciexit_1:
20062306a36Sopenharmony_ci	if (!output)
20162306a36Sopenharmony_ci		large_free(outp);
20262306a36Sopenharmony_ciexit_0:
20362306a36Sopenharmony_ci	return ret;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci#ifdef PREBOOT
20762306a36Sopenharmony_ciSTATIC int INIT __decompress(unsigned char *buf, long in_len,
20862306a36Sopenharmony_ci			      long (*fill)(void*, unsigned long),
20962306a36Sopenharmony_ci			      long (*flush)(void*, unsigned long),
21062306a36Sopenharmony_ci			      unsigned char *output, long out_len,
21162306a36Sopenharmony_ci			      long *posp,
21262306a36Sopenharmony_ci			      void (*error)(char *x)
21362306a36Sopenharmony_ci	)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	return unlz4(buf, in_len - 4, fill, flush, output, posp, error);
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci#endif
218