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