18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Wrapper for decompressing LZ4-compressed kernel, initramfs, and initrd
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2013, LG Electronics, Kyungsik Lee <kyungsik.lee@lge.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#ifdef STATIC
98c2ecf20Sopenharmony_ci#define PREBOOT
108c2ecf20Sopenharmony_ci#include "lz4/lz4_decompress.c"
118c2ecf20Sopenharmony_ci#else
128c2ecf20Sopenharmony_ci#include <linux/decompress/unlz4.h>
138c2ecf20Sopenharmony_ci#endif
148c2ecf20Sopenharmony_ci#include <linux/types.h>
158c2ecf20Sopenharmony_ci#include <linux/lz4.h>
168c2ecf20Sopenharmony_ci#include <linux/decompress/mm.h>
178c2ecf20Sopenharmony_ci#include <linux/compiler.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <asm/unaligned.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/*
228c2ecf20Sopenharmony_ci * Note: Uncompressed chunk size is used in the compressor side
238c2ecf20Sopenharmony_ci * (userspace side for compression).
248c2ecf20Sopenharmony_ci * It is hardcoded because there is not proper way to extract it
258c2ecf20Sopenharmony_ci * from the binary stream which is generated by the preliminary
268c2ecf20Sopenharmony_ci * version of LZ4 tool so far.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci#define LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE (8 << 20)
298c2ecf20Sopenharmony_ci#define ARCHIVE_MAGICNUMBER 0x184C2102
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ciSTATIC inline int INIT unlz4(u8 *input, long in_len,
328c2ecf20Sopenharmony_ci				long (*fill)(void *, unsigned long),
338c2ecf20Sopenharmony_ci				long (*flush)(void *, unsigned long),
348c2ecf20Sopenharmony_ci				u8 *output, long *posp,
358c2ecf20Sopenharmony_ci				void (*error) (char *x))
368c2ecf20Sopenharmony_ci{
378c2ecf20Sopenharmony_ci	int ret = -1;
388c2ecf20Sopenharmony_ci	size_t chunksize = 0;
398c2ecf20Sopenharmony_ci	size_t uncomp_chunksize = LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE;
408c2ecf20Sopenharmony_ci	u8 *inp;
418c2ecf20Sopenharmony_ci	u8 *inp_start;
428c2ecf20Sopenharmony_ci	u8 *outp;
438c2ecf20Sopenharmony_ci	long size = in_len;
448c2ecf20Sopenharmony_ci#ifdef PREBOOT
458c2ecf20Sopenharmony_ci	size_t out_len = get_unaligned_le32(input + in_len);
468c2ecf20Sopenharmony_ci#endif
478c2ecf20Sopenharmony_ci	size_t dest_len;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	if (output) {
518c2ecf20Sopenharmony_ci		outp = output;
528c2ecf20Sopenharmony_ci	} else if (!flush) {
538c2ecf20Sopenharmony_ci		error("NULL output pointer and no flush function provided");
548c2ecf20Sopenharmony_ci		goto exit_0;
558c2ecf20Sopenharmony_ci	} else {
568c2ecf20Sopenharmony_ci		outp = large_malloc(uncomp_chunksize);
578c2ecf20Sopenharmony_ci		if (!outp) {
588c2ecf20Sopenharmony_ci			error("Could not allocate output buffer");
598c2ecf20Sopenharmony_ci			goto exit_0;
608c2ecf20Sopenharmony_ci		}
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	if (input && fill) {
648c2ecf20Sopenharmony_ci		error("Both input pointer and fill function provided,");
658c2ecf20Sopenharmony_ci		goto exit_1;
668c2ecf20Sopenharmony_ci	} else if (input) {
678c2ecf20Sopenharmony_ci		inp = input;
688c2ecf20Sopenharmony_ci	} else if (!fill) {
698c2ecf20Sopenharmony_ci		error("NULL input pointer and missing fill function");
708c2ecf20Sopenharmony_ci		goto exit_1;
718c2ecf20Sopenharmony_ci	} else {
728c2ecf20Sopenharmony_ci		inp = large_malloc(LZ4_compressBound(uncomp_chunksize));
738c2ecf20Sopenharmony_ci		if (!inp) {
748c2ecf20Sopenharmony_ci			error("Could not allocate input buffer");
758c2ecf20Sopenharmony_ci			goto exit_1;
768c2ecf20Sopenharmony_ci		}
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci	inp_start = inp;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	if (posp)
818c2ecf20Sopenharmony_ci		*posp = 0;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	if (fill) {
848c2ecf20Sopenharmony_ci		size = fill(inp, 4);
858c2ecf20Sopenharmony_ci		if (size < 4) {
868c2ecf20Sopenharmony_ci			error("data corrupted");
878c2ecf20Sopenharmony_ci			goto exit_2;
888c2ecf20Sopenharmony_ci		}
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	chunksize = get_unaligned_le32(inp);
928c2ecf20Sopenharmony_ci	if (chunksize == ARCHIVE_MAGICNUMBER) {
938c2ecf20Sopenharmony_ci		if (!fill) {
948c2ecf20Sopenharmony_ci			inp += 4;
958c2ecf20Sopenharmony_ci			size -= 4;
968c2ecf20Sopenharmony_ci		}
978c2ecf20Sopenharmony_ci	} else {
988c2ecf20Sopenharmony_ci		error("invalid header");
998c2ecf20Sopenharmony_ci		goto exit_2;
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	if (posp)
1038c2ecf20Sopenharmony_ci		*posp += 4;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	for (;;) {
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci		if (fill) {
1088c2ecf20Sopenharmony_ci			size = fill(inp, 4);
1098c2ecf20Sopenharmony_ci			if (size == 0)
1108c2ecf20Sopenharmony_ci				break;
1118c2ecf20Sopenharmony_ci			if (size < 4) {
1128c2ecf20Sopenharmony_ci				error("data corrupted");
1138c2ecf20Sopenharmony_ci				goto exit_2;
1148c2ecf20Sopenharmony_ci			}
1158c2ecf20Sopenharmony_ci		} else if (size < 4) {
1168c2ecf20Sopenharmony_ci			/* empty or end-of-file */
1178c2ecf20Sopenharmony_ci			goto exit_3;
1188c2ecf20Sopenharmony_ci		}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci		chunksize = get_unaligned_le32(inp);
1218c2ecf20Sopenharmony_ci		if (chunksize == ARCHIVE_MAGICNUMBER) {
1228c2ecf20Sopenharmony_ci			if (!fill) {
1238c2ecf20Sopenharmony_ci				inp += 4;
1248c2ecf20Sopenharmony_ci				size -= 4;
1258c2ecf20Sopenharmony_ci			}
1268c2ecf20Sopenharmony_ci			if (posp)
1278c2ecf20Sopenharmony_ci				*posp += 4;
1288c2ecf20Sopenharmony_ci			continue;
1298c2ecf20Sopenharmony_ci		}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		if (!fill && chunksize == 0) {
1328c2ecf20Sopenharmony_ci			/* empty or end-of-file */
1338c2ecf20Sopenharmony_ci			goto exit_3;
1348c2ecf20Sopenharmony_ci		}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci		if (posp)
1378c2ecf20Sopenharmony_ci			*posp += 4;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		if (!fill) {
1408c2ecf20Sopenharmony_ci			inp += 4;
1418c2ecf20Sopenharmony_ci			size -= 4;
1428c2ecf20Sopenharmony_ci		} else {
1438c2ecf20Sopenharmony_ci			if (chunksize > LZ4_compressBound(uncomp_chunksize)) {
1448c2ecf20Sopenharmony_ci				error("chunk length is longer than allocated");
1458c2ecf20Sopenharmony_ci				goto exit_2;
1468c2ecf20Sopenharmony_ci			}
1478c2ecf20Sopenharmony_ci			size = fill(inp, chunksize);
1488c2ecf20Sopenharmony_ci			if (size < chunksize) {
1498c2ecf20Sopenharmony_ci				error("data corrupted");
1508c2ecf20Sopenharmony_ci				goto exit_2;
1518c2ecf20Sopenharmony_ci			}
1528c2ecf20Sopenharmony_ci		}
1538c2ecf20Sopenharmony_ci#ifdef PREBOOT
1548c2ecf20Sopenharmony_ci		if (out_len >= uncomp_chunksize) {
1558c2ecf20Sopenharmony_ci			dest_len = uncomp_chunksize;
1568c2ecf20Sopenharmony_ci			out_len -= dest_len;
1578c2ecf20Sopenharmony_ci		} else
1588c2ecf20Sopenharmony_ci			dest_len = out_len;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci		ret = LZ4_decompress_fast(inp, outp, dest_len);
1618c2ecf20Sopenharmony_ci		chunksize = ret;
1628c2ecf20Sopenharmony_ci#else
1638c2ecf20Sopenharmony_ci		dest_len = uncomp_chunksize;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		ret = LZ4_decompress_safe(inp, outp, chunksize, dest_len);
1668c2ecf20Sopenharmony_ci		dest_len = ret;
1678c2ecf20Sopenharmony_ci#endif
1688c2ecf20Sopenharmony_ci		if (ret < 0) {
1698c2ecf20Sopenharmony_ci			error("Decoding failed");
1708c2ecf20Sopenharmony_ci			goto exit_2;
1718c2ecf20Sopenharmony_ci		}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci		ret = -1;
1748c2ecf20Sopenharmony_ci		if (flush && flush(outp, dest_len) != dest_len)
1758c2ecf20Sopenharmony_ci			goto exit_2;
1768c2ecf20Sopenharmony_ci		if (output)
1778c2ecf20Sopenharmony_ci			outp += dest_len;
1788c2ecf20Sopenharmony_ci		if (posp)
1798c2ecf20Sopenharmony_ci			*posp += chunksize;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci		if (!fill) {
1828c2ecf20Sopenharmony_ci			size -= chunksize;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci			if (size == 0)
1858c2ecf20Sopenharmony_ci				break;
1868c2ecf20Sopenharmony_ci			else if (size < 0) {
1878c2ecf20Sopenharmony_ci				error("data corrupted");
1888c2ecf20Sopenharmony_ci				goto exit_2;
1898c2ecf20Sopenharmony_ci			}
1908c2ecf20Sopenharmony_ci			inp += chunksize;
1918c2ecf20Sopenharmony_ci		}
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ciexit_3:
1958c2ecf20Sopenharmony_ci	ret = 0;
1968c2ecf20Sopenharmony_ciexit_2:
1978c2ecf20Sopenharmony_ci	if (!input)
1988c2ecf20Sopenharmony_ci		large_free(inp_start);
1998c2ecf20Sopenharmony_ciexit_1:
2008c2ecf20Sopenharmony_ci	if (!output)
2018c2ecf20Sopenharmony_ci		large_free(outp);
2028c2ecf20Sopenharmony_ciexit_0:
2038c2ecf20Sopenharmony_ci	return ret;
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci#ifdef PREBOOT
2078c2ecf20Sopenharmony_ciSTATIC int INIT __decompress(unsigned char *buf, long in_len,
2088c2ecf20Sopenharmony_ci			      long (*fill)(void*, unsigned long),
2098c2ecf20Sopenharmony_ci			      long (*flush)(void*, unsigned long),
2108c2ecf20Sopenharmony_ci			      unsigned char *output, long out_len,
2118c2ecf20Sopenharmony_ci			      long *posp,
2128c2ecf20Sopenharmony_ci			      void (*error)(char *x)
2138c2ecf20Sopenharmony_ci	)
2148c2ecf20Sopenharmony_ci{
2158c2ecf20Sopenharmony_ci	return unlz4(buf, in_len - 4, fill, flush, output, posp, error);
2168c2ecf20Sopenharmony_ci}
2178c2ecf20Sopenharmony_ci#endif
218