// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2020 Rockchip Electronics Co., Ltd */ #include <common.h> #include <dm.h> #include <misc.h> #include <dm/uclass.h> #include <dm/uclass-internal.h> #define HEAD_CRC 2 #define EXTRA_FIELD 4 #define ORIG_NAME 8 #define COMMENT 0x10 #define RESERVED 0xe0 #define DEFLATED 8 static u32 misc_decomp_async, misc_decomp_sync; static void decomp_set_flags(u32 *flags, u8 comp) { if (comp == IH_COMP_GZIP) *flags |= DECOM_GZIP; else if (comp == IH_COMP_LZ4) *flags |= DECOM_LZ4; } void misc_decompress_async(u8 comp) { decomp_set_flags(&misc_decomp_async, comp); } void misc_decompress_sync(u8 comp) { decomp_set_flags(&misc_decomp_sync, comp); } static int misc_gzip_parse_header(const unsigned char *src, unsigned long len) { int i, flags; /* skip header */ i = 10; flags = src[3]; if (src[2] != DEFLATED || (flags & RESERVED) != 0) { debug("Error: Bad gzipped data\n"); return (-1); } if ((flags & EXTRA_FIELD) != 0) i = 12 + src[10] + (src[11] << 8); if ((flags & ORIG_NAME) != 0) while (src[i++] != 0) ; if ((flags & COMMENT) != 0) while (src[i++] != 0) ; if ((flags & HEAD_CRC) != 0) i += 2; if (i >= len) { puts("Error: gunzip out of data in header\n"); return (-1); } return i; } static int misc_lz4_header_is_valid(const unsigned char *h) { const struct lz4_frame_header *hdr = (const struct lz4_frame_header *)h; /* We assume there's always only a single, standard frame. */ if (le32_to_cpu(hdr->magic) != LZ4F_MAGIC || hdr->version != 1) return 0; /* unknown format */ if (hdr->reserved0 || hdr->reserved1 || hdr->reserved2) return 0; /* reserved must be zero */ if (!hdr->independent_blocks) return 0; /* we can't support this yet */ return 1; } static u64 misc_get_data_size(unsigned long src, unsigned long len, u32 comp) { u64 size = 0; if (comp == DECOM_GZIP) { size = *(u32 *)(src + len - 4); } else if (comp == DECOM_LZ4) { const struct lz4_frame_header *hdr = (const struct lz4_frame_header *)src; /* * Here is the way to add size information in image: * * 1. lz4 command use arg: --content-size. * 2. append u32 size at the end of image as kernel does. */ size = hdr->has_content_size ? *(u64 *)(src + sizeof(*hdr)) : *(u32 *)(src + len - 4); } return size; } static void misc_setup_default_sync(u32 comp) { if (comp == DECOM_GZIP) misc_decompress_sync(IH_COMP_GZIP); else if (comp == DECOM_LZ4) misc_decompress_sync(IH_COMP_LZ4); } static struct udevice *misc_decompress_get_device(u32 comp) { return misc_get_device_by_capability(comp); } static int misc_decompress_start(struct udevice *dev, unsigned long dst, unsigned long src, unsigned long src_len, u32 flags) { struct decom_param param; param.addr_dst = dst; param.addr_src = src; param.flags = flags; if (misc_gzip_parse_header((unsigned char *)src, 0xffff) > 0) { param.mode = DECOM_GZIP; } else if (misc_lz4_header_is_valid((void *)src)) { param.mode = DECOM_LZ4; } else { printf("Unsupported decompression format.\n"); return -EPERM; } param.size_src = src_len; param.size_dst = misc_get_data_size(src, src_len, param.mode); if (!param.size_src || !param.size_dst) return -EINVAL; return misc_ioctl(dev, IOCTL_REQ_START, ¶m); } static int misc_decompress_stop(struct udevice *dev) { return misc_ioctl(dev, IOCTL_REQ_STOP, NULL); } static bool misc_decompress_is_complete(struct udevice *dev) { if (misc_ioctl(dev, IOCTL_REQ_POLL, NULL)) return false; else return true; } static int misc_decompress_data_size(struct udevice *dev, u64 *size, u32 comp) { struct decom_param param; int ret; param.mode = comp; param.size_dst = 0; /* clear */ ret = misc_ioctl(dev, IOCTL_REQ_DATA_SIZE, ¶m); if (!ret) *size = param.size_dst; return ret; } static int misc_decompress_finish(struct udevice *dev, u32 comp) { int timeout = 200000; /* 2s */ while (!misc_decompress_is_complete(dev)) { if (timeout < 0) return -ETIMEDOUT; timeout--; udelay(10); } return misc_decompress_stop(dev); } int misc_decompress_cleanup(void) { const struct misc_ops *ops; struct udevice *dev; struct uclass *uc; int ret; u32 comp; ret = uclass_get(UCLASS_MISC, &uc); if (ret) return 0; /* use "find_" */ for (uclass_find_first_device(UCLASS_MISC, &dev); dev; uclass_find_next_device(&dev)) { if (!device_active(dev)) continue; ops = device_get_ops(dev); if (!ops || !ops->ioctl) continue; else if (ops->ioctl(dev, IOCTL_REQ_CAPABILITY, &comp)) continue; else if (misc_decomp_async & comp) continue; if (misc_decomp_sync & comp) { ret = misc_decompress_finish(dev, comp); if (ret) { printf("Failed to stop decompress: %s, ret=%d\n", dev->name, ret); return ret; } } } return 0; } int misc_decompress_process(unsigned long dst, unsigned long src, unsigned long src_len, u32 comp, bool sync, u64 *size, u32 flags) { struct udevice *dev; ulong dst_org = dst; u64 dst_size = 0; int ret; dev = misc_decompress_get_device(comp); if (!dev) return -ENODEV; /* Wait last finish */ ret = misc_decompress_finish(dev, comp); if (ret) return ret; /* * Check if ARCH_DMA_MINALIGN aligned, otherwise use sync action * for output data memcpy. */ if (!IS_ALIGNED(dst, ARCH_DMA_MINALIGN)) { dst_org = dst; dst = ALIGN(dst, ARCH_DMA_MINALIGN); sync = true; } ret = misc_decompress_start(dev, dst, src, src_len, flags); if (ret) return ret; /* * Wait this round finish ? * * If sync, return original data length after decompress done. * otherwise return from compressed file information. */ if (sync) { ret = misc_decompress_finish(dev, comp); if (ret) return ret; if (size || (dst != dst_org)) { ret = misc_decompress_data_size(dev, &dst_size, comp); if (size) *size = dst_size; if (dst != dst_org) memcpy((char *)dst_org, (const char *)dst, dst_size); } } else { /* * setup cleanup sync flags by default if this is a sync request, * unless misc_decompress_async() is called by manual. * * This avoid caller to forget to setup cleanup sync flags when * they use a async operation, otherwise cpu jump to kernel * before decompress done. */ misc_setup_default_sync(comp); if (size) *size = misc_get_data_size(src, src_len, comp); } return ret; }