1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Wrapper for decompressing LZ4-compressed kernel, initramfs, and initrd
4 *
5 * Copyright (C) 2013, LG Electronics, Kyungsik Lee <kyungsik.lee@lge.com>
6 */
7
8#ifdef STATIC
9#define PREBOOT
10#include "lz4/lz4_decompress.c"
11#else
12#include <linux/decompress/unlz4.h>
13#endif
14#include <linux/types.h>
15#include <linux/lz4.h>
16#include <linux/decompress/mm.h>
17#include <linux/compiler.h>
18
19#include <asm/unaligned.h>
20
21/*
22 * Note: Uncompressed chunk size is used in the compressor side
23 * (userspace side for compression).
24 * It is hardcoded because there is not proper way to extract it
25 * from the binary stream which is generated by the preliminary
26 * version of LZ4 tool so far.
27 */
28#define LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE (8 << 20)
29#define ARCHIVE_MAGICNUMBER 0x184C2102
30
31STATIC inline int INIT unlz4(u8 *input, long in_len,
32				long (*fill)(void *, unsigned long),
33				long (*flush)(void *, unsigned long),
34				u8 *output, long *posp,
35				void (*error) (char *x))
36{
37	int ret = -1;
38	size_t chunksize = 0;
39	size_t uncomp_chunksize = LZ4_DEFAULT_UNCOMPRESSED_CHUNK_SIZE;
40	u8 *inp;
41	u8 *inp_start;
42	u8 *outp;
43	long size = in_len;
44#ifdef PREBOOT
45	size_t out_len = get_unaligned_le32(input + in_len);
46#endif
47	size_t dest_len;
48
49
50	if (output) {
51		outp = output;
52	} else if (!flush) {
53		error("NULL output pointer and no flush function provided");
54		goto exit_0;
55	} else {
56		outp = large_malloc(uncomp_chunksize);
57		if (!outp) {
58			error("Could not allocate output buffer");
59			goto exit_0;
60		}
61	}
62
63	if (input && fill) {
64		error("Both input pointer and fill function provided,");
65		goto exit_1;
66	} else if (input) {
67		inp = input;
68	} else if (!fill) {
69		error("NULL input pointer and missing fill function");
70		goto exit_1;
71	} else {
72		inp = large_malloc(LZ4_compressBound(uncomp_chunksize));
73		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