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, ¶m);
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, ¶m);
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