xref: /rk3399_rockchip-uboot/drivers/misc/misc_decompress.c (revision 975d7ad9e7d98f6e33776aeaf33d3be17d0721a9)
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 u32 misc_get_data_size(unsigned long src, unsigned long len, u32 comp)
67 {
68 	if (comp == DECOM_GZIP)
69 		return *(u32 *)(src + len - 4);
70 
71 	return 0;
72 }
73 
74 static void misc_setup_default_sync(u32 comp)
75 {
76 	if (comp == DECOM_GZIP)
77 		misc_decompress_sync(IH_COMP_GZIP);
78 	else if (comp == DECOM_LZ4)
79 		misc_decompress_sync(IH_COMP_LZ4);
80 }
81 
82 static struct udevice *misc_decompress_get_device(u32 comp)
83 {
84 	return misc_get_device_by_capability(comp);
85 }
86 
87 static int misc_decompress_start(struct udevice *dev, unsigned long dst,
88 				 unsigned long src, unsigned long src_len,
89 				 u32 flags)
90 {
91 	struct decom_param param;
92 
93 	param.addr_dst = dst;
94 	param.addr_src = src;
95 	param.flags = flags;
96 	if (misc_gzip_parse_header((unsigned char *)src, 0xffff) > 0) {
97 		param.mode = DECOM_GZIP;
98 	} else {
99 		printf("Unsupported decompression format.\n");
100 		return -EPERM;
101 	}
102 
103 	param.size_src = src_len;
104 	param.size_dst = misc_get_data_size(src, src_len, param.mode);
105 
106 	if (!param.size_src || !param.size_dst)
107 		return -EINVAL;
108 
109 	return misc_ioctl(dev, IOCTL_REQ_START, &param);
110 }
111 
112 static int misc_decompress_stop(struct udevice *dev)
113 {
114 	return misc_ioctl(dev, IOCTL_REQ_STOP, NULL);
115 }
116 
117 static bool misc_decompress_is_complete(struct udevice *dev)
118 {
119 	if (misc_ioctl(dev, IOCTL_REQ_POLL, NULL))
120 		return false;
121 	else
122 		return true;
123 }
124 
125 static int misc_decompress_data_size(struct udevice *dev, u64 *size, u32 comp)
126 {
127 	struct decom_param param;
128 	int ret;
129 
130 	param.mode = comp;
131 	param.size_dst = 0; /* clear */
132 
133 	ret = misc_ioctl(dev, IOCTL_REQ_DATA_SIZE, &param);
134 	if (!ret)
135 		*size = param.size_dst;
136 
137 	return ret;
138 }
139 
140 static int misc_decompress_finish(struct udevice *dev, u32 comp)
141 {
142 	int timeout = 20000;
143 
144 	while (!misc_decompress_is_complete(dev)) {
145 		if (timeout < 0)
146 			return -EIO;
147 		timeout--;
148 		udelay(10);
149 	}
150 
151 	return misc_decompress_stop(dev);
152 }
153 
154 int misc_decompress_cleanup(void)
155 {
156 	const struct misc_ops *ops;
157 	struct udevice *dev;
158 	struct uclass *uc;
159 	int ret;
160 	u32 comp;
161 
162 	ret = uclass_get(UCLASS_MISC, &uc);
163 	if (ret)
164 		return 0;
165 
166 	/* use "find_" */
167 	for (uclass_find_first_device(UCLASS_MISC, &dev);
168 	     dev;
169 	     uclass_find_next_device(&dev)) {
170 		ops = device_get_ops(dev);
171 		if (!ops || !ops->ioctl)
172 			continue;
173 		else if (ops->ioctl(dev, IOCTL_REQ_CAPABILITY, &comp))
174 			continue;
175 		else if (misc_decomp_async & comp)
176 			continue;
177 
178 		if (misc_decomp_sync & comp) {
179 			ret = misc_decompress_finish(dev, comp);
180 			if (ret) {
181 				printf("Failed to stop decompress: %s, ret=%d\n",
182 				       dev->name, ret);
183 				return ret;
184 			}
185 		}
186 	}
187 
188 	return 0;
189 }
190 
191 int misc_decompress_process(unsigned long dst, unsigned long src,
192 			    unsigned long src_len, u32 comp, bool sync,
193 			    u64 *size, u32 flags)
194 {
195 	struct udevice *dev;
196 	ulong dst_org = dst;
197 	u64 dst_size = 0;
198 	int ret;
199 
200 	dev = misc_decompress_get_device(comp);
201 	if (!dev)
202 		return -ENODEV;
203 
204 	/* Wait last finish */
205 	ret = misc_decompress_finish(dev, comp);
206 	if (ret)
207 		return ret;
208 
209 	/*
210 	 * Check if ARCH_DMA_MINALIGN aligned, otherwise use sync action
211 	 * for output data memcpy.
212 	 */
213 	if (!IS_ALIGNED(dst, ARCH_DMA_MINALIGN)) {
214 		dst_org = dst;
215 		dst = ALIGN(dst, ARCH_DMA_MINALIGN);
216 		sync = true;
217 	}
218 
219 	ret = misc_decompress_start(dev, dst, src, src_len, flags);
220 	if (ret)
221 		return ret;
222 
223 	/*
224 	 * Wait this round finish ?
225 	 *
226 	 * If sync, return original data length after decompress done.
227 	 * otherwise return from compressed file information.
228 	 */
229 	if (sync) {
230 		ret = misc_decompress_finish(dev, comp);
231 		if (ret)
232 			return ret;
233 
234 		if (size || (dst != dst_org)) {
235 			ret = misc_decompress_data_size(dev, &dst_size, comp);
236 			if (size)
237 				*size = dst_size;
238 			if (dst != dst_org)
239 				memcpy((char *)dst_org,
240 				       (const char *)dst, dst_size);
241 		}
242 	} else {
243 		/*
244 		 * setup cleanup sync flags by default if this is a sync request,
245 		 * unless misc_decompress_async() is called by manual.
246 		 *
247 		 * This avoid caller to forget to setup cleanup sync flags when
248 		 * they use a async operation, otherwise cpu jump to kernel
249 		 * before decompress done.
250 		 */
251 		misc_setup_default_sync(comp);
252 
253 		if (size)
254 			*size = misc_get_data_size(src, src_len, comp);
255 	}
256 
257 	return ret;
258 }
259