xref: /rk3399_rockchip-uboot/drivers/misc/misc_decompress.c (revision 2433663c664fe89961507844c5545fc1fcd9307a)
1 // SPDX-License-Identifier:     GPL-2.0+
2 /*
3  * Copyright (C) 2020 Rockchip Electronics Co., Ltd
4  */
5 #include <common.h>
6 #include <dm.h>
7 #include <misc.h>
8 #include <dm/uclass.h>
9 #include <dm/uclass-internal.h>
10 
11 #define HEAD_CRC		2
12 #define EXTRA_FIELD		4
13 #define ORIG_NAME		8
14 #define COMMENT			0x10
15 #define RESERVED		0xe0
16 #define DEFLATED		8
17 
18 static u32 misc_decomp_async, misc_decomp_sync;
19 
20 static void decomp_set_flags(u32 *flags, u8 comp)
21 {
22 	if (comp == IH_COMP_GZIP)
23 		*flags |= DECOM_GZIP;
24 	else if (comp == IH_COMP_LZ4)
25 		*flags |= DECOM_LZ4;
26 }
27 
28 void misc_decompress_async(u8 comp)
29 {
30 	decomp_set_flags(&misc_decomp_async, comp);
31 }
32 
33 void misc_decompress_sync(u8 comp)
34 {
35 	decomp_set_flags(&misc_decomp_sync, comp);
36 }
37 
38 static int misc_gzip_parse_header(const unsigned char *src, unsigned long len)
39 {
40 	int i, flags;
41 
42 	/* skip header */
43 	i = 10;
44 	flags = src[3];
45 	if (src[2] != DEFLATED || (flags & RESERVED) != 0) {
46 		debug("Error: Bad gzipped data\n");
47 		return (-1);
48 	}
49 	if ((flags & EXTRA_FIELD) != 0)
50 		i = 12 + src[10] + (src[11] << 8);
51 	if ((flags & ORIG_NAME) != 0)
52 		while (src[i++] != 0)
53 			;
54 	if ((flags & COMMENT) != 0)
55 		while (src[i++] != 0)
56 			;
57 	if ((flags & HEAD_CRC) != 0)
58 		i += 2;
59 	if (i >= len) {
60 		puts("Error: gunzip out of data in header\n");
61 		return (-1);
62 	}
63 	return i;
64 }
65 
66 static int misc_lz4_header_is_valid(const unsigned char *h)
67 {
68 	const struct lz4_frame_header *hdr  = (const struct lz4_frame_header *)h;
69 	/* We assume there's always only a single, standard frame. */
70 	if (le32_to_cpu(hdr->magic) != LZ4F_MAGIC || hdr->version != 1)
71 		return 0;        /* unknown format */
72 	if (hdr->reserved0 || hdr->reserved1 || hdr->reserved2)
73 		return 0; /* reserved must be zero */
74 	if (!hdr->independent_blocks)
75 		return 0; /* we can't support this yet */
76 
77 	return 1;
78 }
79 
80 static u64 misc_get_data_size(unsigned long src, unsigned long len, u32 comp)
81 {
82 	u64 size = 0;
83 
84 	if (comp == DECOM_GZIP) {
85 		size = *(u32 *)(src + len - 4);
86 	} else if (comp == DECOM_LZ4) {
87 		const struct lz4_frame_header *hdr =
88 			(const struct lz4_frame_header *)src;
89 		/*
90 		 * Here is the way to add size information in image:
91 		 *
92 		 * 1. lz4 command use arg: --content-size.
93 		 * 2. append u32 size at the end of image as kernel does.
94 		 */
95 		size = hdr->has_content_size ?
96 			*(u64 *)(src + sizeof(*hdr)) : *(u32 *)(src + len - 4);
97 	}
98 
99 	return size;
100 }
101 
102 static void misc_setup_default_sync(u32 comp)
103 {
104 	if (comp == DECOM_GZIP)
105 		misc_decompress_sync(IH_COMP_GZIP);
106 	else if (comp == DECOM_LZ4)
107 		misc_decompress_sync(IH_COMP_LZ4);
108 }
109 
110 static struct udevice *misc_decompress_get_device(u32 comp)
111 {
112 	return misc_get_device_by_capability(comp);
113 }
114 
115 static int misc_decompress_start(struct udevice *dev, unsigned long dst,
116 				 unsigned long src, unsigned long src_len,
117 				 u32 flags)
118 {
119 	struct decom_param param;
120 
121 	param.addr_dst = dst;
122 	param.addr_src = src;
123 	param.flags = flags;
124 	if (misc_gzip_parse_header((unsigned char *)src, 0xffff) > 0) {
125 		param.mode = DECOM_GZIP;
126 	} else if (misc_lz4_header_is_valid((void *)src)) {
127 		param.mode = DECOM_LZ4;
128 	} else {
129 		printf("Unsupported decompression format.\n");
130 		return -EPERM;
131 	}
132 
133 	param.size_src = src_len;
134 	param.size_dst = misc_get_data_size(src, src_len, param.mode);
135 
136 	if (!param.size_src || !param.size_dst)
137 		return -EINVAL;
138 
139 	return misc_ioctl(dev, IOCTL_REQ_START, &param);
140 }
141 
142 static int misc_decompress_stop(struct udevice *dev)
143 {
144 	return misc_ioctl(dev, IOCTL_REQ_STOP, NULL);
145 }
146 
147 static bool misc_decompress_is_complete(struct udevice *dev)
148 {
149 	if (misc_ioctl(dev, IOCTL_REQ_POLL, NULL))
150 		return false;
151 	else
152 		return true;
153 }
154 
155 static int misc_decompress_data_size(struct udevice *dev, u64 *size, u32 comp)
156 {
157 	struct decom_param param;
158 	int ret;
159 
160 	param.mode = comp;
161 	param.size_dst = 0; /* clear */
162 
163 	ret = misc_ioctl(dev, IOCTL_REQ_DATA_SIZE, &param);
164 	if (!ret)
165 		*size = param.size_dst;
166 
167 	return ret;
168 }
169 
170 static int misc_decompress_finish(struct udevice *dev, u32 comp)
171 {
172 	int timeout = 200000;	/* 2s */
173 
174 	while (!misc_decompress_is_complete(dev)) {
175 		if (timeout < 0)
176 			return -ETIMEDOUT;
177 		timeout--;
178 		udelay(10);
179 	}
180 
181 	return misc_decompress_stop(dev);
182 }
183 
184 int misc_decompress_cleanup(void)
185 {
186 	const struct misc_ops *ops;
187 	struct udevice *dev;
188 	struct uclass *uc;
189 	int ret;
190 	u32 comp;
191 
192 	ret = uclass_get(UCLASS_MISC, &uc);
193 	if (ret)
194 		return 0;
195 
196 	/* use "find_" */
197 	for (uclass_find_first_device(UCLASS_MISC, &dev);
198 	     dev;
199 	     uclass_find_next_device(&dev)) {
200 		ops = device_get_ops(dev);
201 		if (!ops || !ops->ioctl)
202 			continue;
203 		else if (ops->ioctl(dev, IOCTL_REQ_CAPABILITY, &comp))
204 			continue;
205 		else if (misc_decomp_async & comp)
206 			continue;
207 
208 		if (misc_decomp_sync & comp) {
209 			ret = misc_decompress_finish(dev, comp);
210 			if (ret) {
211 				printf("Failed to stop decompress: %s, ret=%d\n",
212 				       dev->name, ret);
213 				return ret;
214 			}
215 		}
216 	}
217 
218 	return 0;
219 }
220 
221 int misc_decompress_process(unsigned long dst, unsigned long src,
222 			    unsigned long src_len, u32 comp, bool sync,
223 			    u64 *size, u32 flags)
224 {
225 	struct udevice *dev;
226 	ulong dst_org = dst;
227 	u64 dst_size = 0;
228 	int ret;
229 
230 	dev = misc_decompress_get_device(comp);
231 	if (!dev)
232 		return -ENODEV;
233 
234 	/* Wait last finish */
235 	ret = misc_decompress_finish(dev, comp);
236 	if (ret)
237 		return ret;
238 
239 	/*
240 	 * Check if ARCH_DMA_MINALIGN aligned, otherwise use sync action
241 	 * for output data memcpy.
242 	 */
243 	if (!IS_ALIGNED(dst, ARCH_DMA_MINALIGN)) {
244 		dst_org = dst;
245 		dst = ALIGN(dst, ARCH_DMA_MINALIGN);
246 		sync = true;
247 	}
248 
249 	ret = misc_decompress_start(dev, dst, src, src_len, flags);
250 	if (ret)
251 		return ret;
252 
253 	/*
254 	 * Wait this round finish ?
255 	 *
256 	 * If sync, return original data length after decompress done.
257 	 * otherwise return from compressed file information.
258 	 */
259 	if (sync) {
260 		ret = misc_decompress_finish(dev, comp);
261 		if (ret)
262 			return ret;
263 
264 		if (size || (dst != dst_org)) {
265 			ret = misc_decompress_data_size(dev, &dst_size, comp);
266 			if (size)
267 				*size = dst_size;
268 			if (dst != dst_org)
269 				memcpy((char *)dst_org,
270 				       (const char *)dst, dst_size);
271 		}
272 	} else {
273 		/*
274 		 * setup cleanup sync flags by default if this is a sync request,
275 		 * unless misc_decompress_async() is called by manual.
276 		 *
277 		 * This avoid caller to forget to setup cleanup sync flags when
278 		 * they use a async operation, otherwise cpu jump to kernel
279 		 * before decompress done.
280 		 */
281 		misc_setup_default_sync(comp);
282 
283 		if (size)
284 			*size = misc_get_data_size(src, src_len, comp);
285 	}
286 
287 	return ret;
288 }
289