xref: /OK3568_Linux_fs/u-boot/drivers/misc/misc_decompress.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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 
decomp_set_flags(u32 * flags,u8 comp)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 
misc_decompress_async(u8 comp)28 void misc_decompress_async(u8 comp)
29 {
30 	decomp_set_flags(&misc_decomp_async, comp);
31 }
32 
misc_decompress_sync(u8 comp)33 void misc_decompress_sync(u8 comp)
34 {
35 	decomp_set_flags(&misc_decomp_sync, comp);
36 }
37 
misc_gzip_parse_header(const unsigned char * src,unsigned long len)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 
misc_lz4_header_is_valid(const unsigned char * h)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 
misc_get_data_size(unsigned long src,unsigned long len,u32 comp)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 
misc_setup_default_sync(u32 comp)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 
misc_decompress_get_device(u32 comp)110 static struct udevice *misc_decompress_get_device(u32 comp)
111 {
112 	return misc_get_device_by_capability(comp);
113 }
114 
misc_decompress_start(struct udevice * dev,unsigned long dst,unsigned long src,unsigned long src_len,u32 flags)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 
misc_decompress_stop(struct udevice * dev)142 static int misc_decompress_stop(struct udevice *dev)
143 {
144 	return misc_ioctl(dev, IOCTL_REQ_STOP, NULL);
145 }
146 
misc_decompress_is_complete(struct udevice * dev)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 
misc_decompress_data_size(struct udevice * dev,u64 * size,u32 comp)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 
misc_decompress_finish(struct udevice * dev,u32 comp)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 
misc_decompress_cleanup(void)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 		if (!device_active(dev))
201 			continue;
202 		ops = device_get_ops(dev);
203 		if (!ops || !ops->ioctl)
204 			continue;
205 		else if (ops->ioctl(dev, IOCTL_REQ_CAPABILITY, &comp))
206 			continue;
207 		else if (misc_decomp_async & comp)
208 			continue;
209 
210 		if (misc_decomp_sync & comp) {
211 			ret = misc_decompress_finish(dev, comp);
212 			if (ret) {
213 				printf("Failed to stop decompress: %s, ret=%d\n",
214 				       dev->name, ret);
215 				return ret;
216 			}
217 		}
218 	}
219 
220 	return 0;
221 }
222 
misc_decompress_process(unsigned long dst,unsigned long src,unsigned long src_len,u32 comp,bool sync,u64 * size,u32 flags)223 int misc_decompress_process(unsigned long dst, unsigned long src,
224 			    unsigned long src_len, u32 comp, bool sync,
225 			    u64 *size, u32 flags)
226 {
227 	struct udevice *dev;
228 	ulong dst_org = dst;
229 	u64 dst_size = 0;
230 	int ret;
231 
232 	dev = misc_decompress_get_device(comp);
233 	if (!dev)
234 		return -ENODEV;
235 
236 	/* Wait last finish */
237 	ret = misc_decompress_finish(dev, comp);
238 	if (ret)
239 		return ret;
240 
241 	/*
242 	 * Check if ARCH_DMA_MINALIGN aligned, otherwise use sync action
243 	 * for output data memcpy.
244 	 */
245 	if (!IS_ALIGNED(dst, ARCH_DMA_MINALIGN)) {
246 		dst_org = dst;
247 		dst = ALIGN(dst, ARCH_DMA_MINALIGN);
248 		sync = true;
249 	}
250 
251 	ret = misc_decompress_start(dev, dst, src, src_len, flags);
252 	if (ret)
253 		return ret;
254 
255 	/*
256 	 * Wait this round finish ?
257 	 *
258 	 * If sync, return original data length after decompress done.
259 	 * otherwise return from compressed file information.
260 	 */
261 	if (sync) {
262 		ret = misc_decompress_finish(dev, comp);
263 		if (ret)
264 			return ret;
265 
266 		if (size || (dst != dst_org)) {
267 			ret = misc_decompress_data_size(dev, &dst_size, comp);
268 			if (size)
269 				*size = dst_size;
270 			if (dst != dst_org)
271 				memcpy((char *)dst_org,
272 				       (const char *)dst, dst_size);
273 		}
274 	} else {
275 		/*
276 		 * setup cleanup sync flags by default if this is a sync request,
277 		 * unless misc_decompress_async() is called by manual.
278 		 *
279 		 * This avoid caller to forget to setup cleanup sync flags when
280 		 * they use a async operation, otherwise cpu jump to kernel
281 		 * before decompress done.
282 		 */
283 		misc_setup_default_sync(comp);
284 
285 		if (size)
286 			*size = misc_get_data_size(src, src_len, comp);
287 	}
288 
289 	return ret;
290 }
291