113546Sjulian// SPDX-License-Identifier: GPL-2.0-only
235509Sjb/*
313546Sjulian * Wrapper for decompressing LZ4-compressed kernel, initramfs, and initrd
413546Sjulian *
513546Sjulian * Copyright (C) 2013, LG Electronics, Kyungsik Lee <kyungsik.lee@lge.com>
613546Sjulian */
713546Sjulian
813546Sjulian#ifdef STATIC
913546Sjulian#define PREBOOT
1013546Sjulian#include "lz4/lz4_decompress.c"
1113546Sjulian#else
1213546Sjulian#include <linux/decompress/unlz4.h>
13165967Simp#endif
1413546Sjulian#include <linux/types.h>
1513546Sjulian#include <linux/lz4.h>
1613546Sjulian#include <linux/decompress/mm.h>
1713546Sjulian#include <linux/compiler.h>
1813546Sjulian
1913546Sjulian#include <asm/unaligned.h>
2049439Sdeischen
2113546Sjulian/*
2213546Sjulian * Note: Uncompressed chunk size is used in the compressor side
2313546Sjulian * (userspace side for compression).
2413546Sjulian * It is hardcoded because there is not proper way to extract it
2513546Sjulian * from the binary stream which is generated by the preliminary
2613546Sjulian * version of LZ4 tool so far.
2713546Sjulian */
2813546Sjulian#define LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE (8 << 20)
2950476Speter#define ARCHIVE_MAGICNUMBER 0x184C2102
3025402Sjb
3113546SjulianSTATIC inline int INIT unlz4(u8 *input, long in_len,
32174112Sdeischen				long (*fill)(void *, unsigned long),
33174112Sdeischen				long (*flush)(void *, unsigned long),
3413546Sjulian				u8 *output, long *posp,
3517706Sjulian				void (*error) (char *x))
3613546Sjulian{
3713546Sjulian	int ret = -1;
3825402Sjb	size_t chunksize = 0;
3913546Sjulian	size_t uncomp_chunksize = LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE;
40174112Sdeischen	u8 *inp;
41103388Smini	u8 *inp_start;
4213546Sjulian	u8 *outp;
43174112Sdeischen	long size = in_len;
44174112Sdeischen#ifdef PREBOOT
4575658Sdeischen	size_t out_len = get_unaligned_le32(input + in_len);
4671581Sdeischen#endif
4713546Sjulian	size_t dest_len;
4871581Sdeischen
4956698Sjasone
50113658Sdeischen	if (output) {
5156698Sjasone		outp = output;
5256698Sjasone	} else if (!flush) {
5356698Sjasone		error("NULL output pointer and no flush function provided");
5456698Sjasone		goto exit_0;
55123312Sdavidxu	} else {
5656698Sjasone		outp = large_malloc(uncomp_chunksize);
5756698Sjasone		if (!outp) {
5856698Sjasone			error("Could not allocate output buffer");
5956698Sjasone			goto exit_0;
6056698Sjasone		}
6156698Sjasone	}
6256698Sjasone
6356698Sjasone	if (input && fill) {
6456698Sjasone		error("Both input pointer and fill function provided,");
65103419Smini		goto exit_1;
66123312Sdavidxu	} else if (input) {
67123312Sdavidxu		inp = input;
68123312Sdavidxu	} else if (!fill) {
69123312Sdavidxu		error("NULL input pointer and missing fill function");
70123312Sdavidxu		goto exit_1;
7156698Sjasone	} else {
7256698Sjasone		inp = large_malloc(LZ4_compressBound(uncomp_chunksize));
7356698Sjasone		if (!inp) {
74			error("Could not allocate input buffer");
75			goto exit_1;
76		}
77	}
78	inp_start = inp;
79
80	if (posp)
81		*posp = 0;
82
83	if (fill) {
84		size = fill(inp, 4);
85		if (size < 4) {
86			error("data corrupted");
87			goto exit_2;
88		}
89	}
90
91	chunksize = get_unaligned_le32(inp);
92	if (chunksize == ARCHIVE_MAGICNUMBER) {
93		if (!fill) {
94			inp += 4;
95			size -= 4;
96		}
97	} else {
98		error("invalid header");
99		goto exit_2;
100	}
101
102	if (posp)
103		*posp += 4;
104
105	for (;;) {
106
107		if (fill) {
108			size = fill(inp, 4);
109			if (size == 0)
110				break;
111			if (size < 4) {
112				error("data corrupted");
113				goto exit_2;
114			}
115		} else if (size < 4) {
116			/* empty or end-of-file */
117			goto exit_3;
118		}
119
120		chunksize = get_unaligned_le32(inp);
121		if (chunksize == ARCHIVE_MAGICNUMBER) {
122			if (!fill) {
123				inp += 4;
124				size -= 4;
125			}
126			if (posp)
127				*posp += 4;
128			continue;
129		}
130
131		if (!fill && chunksize == 0) {
132			/* empty or end-of-file */
133			goto exit_3;
134		}
135
136		if (posp)
137			*posp += 4;
138
139		if (!fill) {
140			inp += 4;
141			size -= 4;
142		} else {
143			if (chunksize > LZ4_compressBound(uncomp_chunksize)) {
144				error("chunk length is longer than allocated");
145				goto exit_2;
146			}
147			size = fill(inp, chunksize);
148			if (size < chunksize) {
149				error("data corrupted");
150				goto exit_2;
151			}
152		}
153#ifdef PREBOOT
154		if (out_len >= uncomp_chunksize) {
155			dest_len = uncomp_chunksize;
156			out_len -= dest_len;
157		} else
158			dest_len = out_len;
159
160		ret = LZ4_decompress_fast(inp, outp, dest_len);
161		chunksize = ret;
162#else
163		dest_len = uncomp_chunksize;
164
165		ret = LZ4_decompress_safe(inp, outp, chunksize, dest_len);
166		dest_len = ret;
167#endif
168		if (ret < 0) {
169			error("Decoding failed");
170			goto exit_2;
171		}
172
173		ret = -1;
174		if (flush && flush(outp, dest_len) != dest_len)
175			goto exit_2;
176		if (output)
177			outp += dest_len;
178		if (posp)
179			*posp += chunksize;
180
181		if (!fill) {
182			size -= chunksize;
183
184			if (size == 0)
185				break;
186			else if (size < 0) {
187				error("data corrupted");
188				goto exit_2;
189			}
190			inp += chunksize;
191		}
192	}
193
194exit_3:
195	ret = 0;
196exit_2:
197	if (!input)
198		large_free(inp_start);
199exit_1:
200	if (!output)
201		large_free(outp);
202exit_0:
203	return ret;
204}
205
206#ifdef PREBOOT
207STATIC int INIT __decompress(unsigned char *buf, long in_len,
208			      long (*fill)(void*, unsigned long),
209			      long (*flush)(void*, unsigned long),
210			      unsigned char *output, long out_len,
211			      long *posp,
212			      void (*error)(char *x)
213	)
214{
215	return unlz4(buf, in_len - 4, fill, flush, output, posp, error);
216}
217#endif
218