You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
6.3 KiB
290 lines
6.3 KiB
// 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;
|
|
}
|
|
|