// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2017-2019 Mellanox Technologies. All rights reserved */

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) "mlxfw_mfa2: " fmt

#include <linux/kernel.h>
#ifdef HAVE_BASECODE_EXTRAS
#include <linux/printk.h>
#endif
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/vmalloc.h>
#include <linux/xz.h>
#include "mlxfw_mfa2.h"
#include "mlxfw_mfa2_file.h"
#include "mlxfw_mfa2_tlv.h"
#include "mlxfw_mfa2_format.h"
#include "mlxfw_mfa2_tlv_multi.h"

/*               MFA2 FILE
 *  +----------------------------------+
 *  |        MFA2 finger print         |
 *  +----------------------------------+
 *  |   package descriptor multi_tlv   |
 *  | +------------------------------+ |     +-----------------+
 *  | |    package descriptor tlv    +-----> |num_devices=n    |
 *  | +------------------------------+ |     |num_components=m |
 *  +----------------------------------+     |CB offset        |
 *  |    device descriptor multi_tlv   |     |...              |
 *  | +------------------------------+ |     |                 |
 *  | |           PSID tlv           | |     +-----------------+
 *  | +------------------------------+ |
 *  | |     component index tlv      | |
 *  | +------------------------------+ |
 *  +----------------------------------+
 *  |  component descriptor multi_tlv  |
 *  | +------------------------------+ |     +-----------------+
 *  | |  component descriptor tlv    +-----> |Among others:    |
 *  | +------------------------------+ |     |CB offset=o      |
 *  +----------------------------------+     |comp index=i     |
 *  |                                  |     |...              |
 *  |                                  |     |                 |
 *  |                                  |     +-----------------+
 *  |        COMPONENT BLOCK (CB)      |
 *  |                                  |
 *  |                                  |
 *  |                                  |
 *  +----------------------------------+
 *
 * On the top level, an MFA2 file contains:
 *  - Fingerprint
 *  - Several multi_tlvs (TLVs of type MLXFW_MFA2_TLV_MULTI, as defined in
 *    mlxfw_mfa2_format.h)
 *  - Compresses content block
 *
 * The first multi_tlv
 * -------------------
 * The first multi TLV is treated as package descriptor, and expected to have a
 * first TLV child of type MLXFW_MFA2_TLV_PACKAGE_DESCRIPTOR which contains all
 * the global information needed to parse the file. Among others, it contains
 * the number of device descriptors and component descriptor following this
 * multi TLV.
 *
 * The device descriptor multi_tlv
 * -------------------------------
 * The multi TLVs following the package descriptor are treated as device
 * descriptor, and are expected to have the following children:
 *  - PSID TLV child of type MLXFW_MFA2_TLV_PSID containing that device PSID.
 *  - Component index of type MLXFW_MFA2_TLV_COMPONENT_PTR that contains that
 *    device component index.
 *
 * The component descriptor multi_tlv
 * ----------------------------------
 * The multi TLVs following the device descriptor multi TLVs are treated as
 * component descriptor, and are expected to have a first child of type
 * MLXFW_MFA2_TLV_COMPONENT_DESCRIPTOR that contains mostly the component index,
 * needed for the flash process and the offset to the binary within the
 * component block.
 */

static const u8 mlxfw_mfa2_fingerprint[] = "MLNX.MFA2.XZ.00!";
static const int mlxfw_mfa2_fingerprint_len =
			sizeof(mlxfw_mfa2_fingerprint) - 1;

static const u8 mlxfw_mfa2_comp_magic[] = "#BIN.COMPONENT!#";
static const int mlxfw_mfa2_comp_magic_len = sizeof(mlxfw_mfa2_comp_magic) - 1;

bool mlxfw_mfa2_check(const struct firmware *fw)
{
	if (fw->size < sizeof(mlxfw_mfa2_fingerprint))
		return false;

	return memcmp(fw->data, mlxfw_mfa2_fingerprint,
		      mlxfw_mfa2_fingerprint_len) == 0;
}

static bool
mlxfw_mfa2_tlv_multi_validate(const struct mlxfw_mfa2_file *mfa2_file,
			      const struct mlxfw_mfa2_tlv_multi *multi)
{
	const struct mlxfw_mfa2_tlv *tlv;
	u16 idx;

	/* Check that all children are valid */
	mlxfw_mfa2_tlv_multi_foreach(mfa2_file, tlv, idx, multi) {
		if (!tlv) {
			pr_err("Multi has invalid child");
			return false;
		}
	}
	return true;
}

static bool
mlxfw_mfa2_file_dev_validate(const struct mlxfw_mfa2_file *mfa2_file,
			     const struct mlxfw_mfa2_tlv *dev_tlv,
			     u16 dev_idx)
{
	const struct mlxfw_mfa2_tlv_component_ptr *cptr;
	const struct mlxfw_mfa2_tlv_multi *multi;
	const struct mlxfw_mfa2_tlv_psid *psid;
	const struct mlxfw_mfa2_tlv *tlv;
	u16 cptr_count;
	u16 cptr_idx;
	int err;

	pr_debug("Device %d\n", dev_idx);

	multi = mlxfw_mfa2_tlv_multi_get(mfa2_file, dev_tlv);
	if (!multi) {
		pr_err("Device %d is not a valid TLV error\n", dev_idx);
		return false;
	}

	if (!mlxfw_mfa2_tlv_multi_validate(mfa2_file, multi))
		return false;

	/* Validate the device has PSID tlv */
	tlv = mlxfw_mfa2_tlv_multi_child_find(mfa2_file, multi,
					      MLXFW_MFA2_TLV_PSID, 0);
	if (!tlv) {
		pr_err("Device %d does not have PSID\n", dev_idx);
		return false;
	}

	psid = mlxfw_mfa2_tlv_psid_get(mfa2_file, tlv);
	if (!psid) {
		pr_err("Device %d PSID TLV is not valid\n", dev_idx);
		return false;
	}

	print_hex_dump_debug("  -- Device PSID ", DUMP_PREFIX_NONE, 16, 16,
			     psid->psid, be16_to_cpu(tlv->len), true);

	/* Validate the device has COMPONENT_PTR */
	err = mlxfw_mfa2_tlv_multi_child_count(mfa2_file, multi,
					       MLXFW_MFA2_TLV_COMPONENT_PTR,
					       &cptr_count);
	if (err)
		return false;

	if (cptr_count == 0) {
		pr_err("Device %d has no components\n", dev_idx);
		return false;
	}

	for (cptr_idx = 0; cptr_idx < cptr_count; cptr_idx++) {
		tlv = mlxfw_mfa2_tlv_multi_child_find(mfa2_file, multi,
						      MLXFW_MFA2_TLV_COMPONENT_PTR,
						      cptr_idx);
		if (!tlv)
			return false;

		cptr = mlxfw_mfa2_tlv_component_ptr_get(mfa2_file, tlv);
		if (!cptr) {
			pr_err("Device %d COMPONENT_PTR TLV is not valid\n",
			       dev_idx);
			return false;
		}

		pr_debug("  -- Component index %d\n",
			 be16_to_cpu(cptr->component_index));
	}
	return true;
}

static bool
mlxfw_mfa2_file_comp_validate(const struct mlxfw_mfa2_file *mfa2_file,
			      const struct mlxfw_mfa2_tlv *comp_tlv,
			      u16 comp_idx)
{
	const struct mlxfw_mfa2_tlv_component_descriptor *cdesc;
	const struct mlxfw_mfa2_tlv_multi *multi;
	const struct mlxfw_mfa2_tlv *tlv;

	pr_debug("Component %d\n", comp_idx);

	multi = mlxfw_mfa2_tlv_multi_get(mfa2_file, comp_tlv);
	if (!multi) {
		pr_err("Component %d is not a valid TLV error\n", comp_idx);
		return false;
	}

	if (!mlxfw_mfa2_tlv_multi_validate(mfa2_file, multi))
		return false;

	/* Check that component have COMPONENT_DESCRIPTOR as first child */
	tlv = mlxfw_mfa2_tlv_multi_child(mfa2_file, multi);
	if (!tlv) {
		pr_err("Component descriptor %d multi TLV error\n", comp_idx);
		return false;
	}

	cdesc = mlxfw_mfa2_tlv_component_descriptor_get(mfa2_file, tlv);
	if (!cdesc) {
		pr_err("Component %d does not have a valid descriptor\n",
		       comp_idx);
		return false;
	}
	pr_debug("  -- Component type %d\n", be16_to_cpu(cdesc->identifier));
	pr_debug("  -- Offset 0x%llx and size %d\n",
		 ((u64) be32_to_cpu(cdesc->cb_offset_h) << 32)
		 | be32_to_cpu(cdesc->cb_offset_l), be32_to_cpu(cdesc->size));

	return true;
}

static bool mlxfw_mfa2_file_validate(const struct mlxfw_mfa2_file *mfa2_file)
{
	const struct mlxfw_mfa2_tlv *tlv;
	u16 idx;

	pr_debug("Validating file\n");

	/* check that all the devices exist */
	mlxfw_mfa2_tlv_foreach(mfa2_file, tlv, idx, mfa2_file->first_dev,
			       mfa2_file->dev_count) {
		if (!tlv) {
			pr_err("Device TLV error\n");
			return false;
		}

		/* Check each device */
		if (!mlxfw_mfa2_file_dev_validate(mfa2_file, tlv, idx))
			return false;
	}

	/* check that all the components exist */
	mlxfw_mfa2_tlv_foreach(mfa2_file, tlv, idx, mfa2_file->first_component,
			       mfa2_file->component_count) {
		if (!tlv) {
			pr_err("Device TLV error\n");
			return false;
		}

		/* Check each component */
		if (!mlxfw_mfa2_file_comp_validate(mfa2_file, tlv, idx))
			return false;
	}
	return true;
}

struct mlxfw_mfa2_file *mlxfw_mfa2_file_init(const struct firmware *fw)
{
	const struct mlxfw_mfa2_tlv_package_descriptor *pd;
	const struct mlxfw_mfa2_tlv_multi *multi;
	const struct mlxfw_mfa2_tlv *multi_child;
	const struct mlxfw_mfa2_tlv *first_tlv;
	struct mlxfw_mfa2_file *mfa2_file;
	const void *first_tlv_ptr;
	const void *cb_top_ptr;

	mfa2_file = kzalloc(sizeof(*mfa2_file), GFP_KERNEL);
	if (!mfa2_file)
		return ERR_PTR(-ENOMEM);

	mfa2_file->fw = fw;
	first_tlv_ptr = fw->data + NLA_ALIGN(mlxfw_mfa2_fingerprint_len);
	first_tlv = mlxfw_mfa2_tlv_get(mfa2_file, first_tlv_ptr);
	if (!first_tlv) {
		pr_err("Could not parse package descriptor TLV\n");
		goto err_out;
	}

	multi = mlxfw_mfa2_tlv_multi_get(mfa2_file, first_tlv);
	if (!multi) {
		pr_err("First TLV is not of valid multi type\n");
		goto err_out;
	}

	multi_child = mlxfw_mfa2_tlv_multi_child(mfa2_file, multi);
	if (!multi_child)
		goto err_out;

	pd = mlxfw_mfa2_tlv_package_descriptor_get(mfa2_file, multi_child);
	if (!pd) {
		pr_err("Could not parse package descriptor TLV\n");
		goto err_out;
	}

	mfa2_file->first_dev = mlxfw_mfa2_tlv_next(mfa2_file, first_tlv);
	if (!mfa2_file->first_dev) {
		pr_err("First device TLV is not valid\n");
		goto err_out;
	}

	mfa2_file->dev_count = be16_to_cpu(pd->num_devices);
	mfa2_file->first_component = mlxfw_mfa2_tlv_advance(mfa2_file,
							    mfa2_file->first_dev,
							    mfa2_file->dev_count);
	mfa2_file->component_count = be16_to_cpu(pd->num_components);
	mfa2_file->cb = fw->data + NLA_ALIGN(be32_to_cpu(pd->cb_offset));
	if (!mlxfw_mfa2_valid_ptr(mfa2_file, mfa2_file->cb)) {
		pr_err("Component block is out side the file\n");
		goto err_out;
	}
	mfa2_file->cb_archive_size = be32_to_cpu(pd->cb_archive_size);
	cb_top_ptr = mfa2_file->cb + mfa2_file->cb_archive_size - 1;
	if (!mlxfw_mfa2_valid_ptr(mfa2_file, cb_top_ptr)) {
		pr_err("Component block size is too big\n");
		goto err_out;
	}

	if (!mlxfw_mfa2_file_validate(mfa2_file))
		goto err_out;
	return mfa2_file;
err_out:
	kfree(mfa2_file);
	return ERR_PTR(-EINVAL);
}

static const struct mlxfw_mfa2_tlv_multi *
mlxfw_mfa2_tlv_dev_get(const struct mlxfw_mfa2_file *mfa2_file,
		       const char *psid, u16 psid_size)
{
	const struct mlxfw_mfa2_tlv_psid *tlv_psid;
	const struct mlxfw_mfa2_tlv_multi *dev_multi;
	const struct mlxfw_mfa2_tlv *dev_tlv;
	const struct mlxfw_mfa2_tlv *tlv;
	u32 idx;

	/* for each device tlv */
	mlxfw_mfa2_tlv_foreach(mfa2_file, dev_tlv, idx, mfa2_file->first_dev,
			       mfa2_file->dev_count) {
		if (!dev_tlv)
			return NULL;

		dev_multi = mlxfw_mfa2_tlv_multi_get(mfa2_file, dev_tlv);
		if (!dev_multi)
			return NULL;

		/* find psid child and compare */
		tlv = mlxfw_mfa2_tlv_multi_child_find(mfa2_file, dev_multi,
						      MLXFW_MFA2_TLV_PSID, 0);
		if (!tlv)
			return NULL;
		if (be16_to_cpu(tlv->len) != psid_size)
			continue;

		tlv_psid = mlxfw_mfa2_tlv_psid_get(mfa2_file, tlv);
		if (!tlv_psid)
			return NULL;

		if (memcmp(psid, tlv_psid->psid, psid_size) == 0)
			return dev_multi;
	}

	return NULL;
}

int mlxfw_mfa2_file_component_count(const struct mlxfw_mfa2_file *mfa2_file,
				    const char *psid, u32 psid_size,
				    u32 *p_count)
{
	const struct mlxfw_mfa2_tlv_multi *dev_multi;
	u16 count;
	int err;

	dev_multi = mlxfw_mfa2_tlv_dev_get(mfa2_file, psid, psid_size);
	if (!dev_multi)
		return -EINVAL;

	err = mlxfw_mfa2_tlv_multi_child_count(mfa2_file, dev_multi,
					       MLXFW_MFA2_TLV_COMPONENT_PTR,
					       &count);
	if (err)
		return err;

	*p_count = count;
	return 0;
}

static int mlxfw_mfa2_xz_dec_run(struct xz_dec *xz_dec, struct xz_buf *xz_buf,
				 bool *finished)
{
	enum xz_ret xz_ret;

	xz_ret = xz_dec_run(xz_dec, xz_buf);

	switch (xz_ret) {
	case XZ_STREAM_END:
		*finished = true;
		return 0;
	case XZ_OK:
		*finished = false;
		return 0;
	case XZ_MEM_ERROR:
		pr_err("xz no memory\n");
		return -ENOMEM;
	case XZ_DATA_ERROR:
		pr_err("xz file corrupted\n");
		return -EINVAL;
	case XZ_FORMAT_ERROR:
		pr_err("xz format not found\n");
		return -EINVAL;
	case XZ_OPTIONS_ERROR:
		pr_err("unsupported xz option\n");
		return -EINVAL;
	case XZ_MEMLIMIT_ERROR:
		pr_err("xz dictionary too small\n");
		return -EINVAL;
	default:
		pr_err("xz error %d\n", xz_ret);
		return -EINVAL;
	}
}

static int mlxfw_mfa2_file_cb_offset_xz(const struct mlxfw_mfa2_file *mfa2_file,
					off_t off, size_t size, u8 *buf)
{
	struct xz_dec *xz_dec;
	struct xz_buf dec_buf;
	off_t curr_off = 0;
	bool finished;
	int err;

	xz_dec = xz_dec_init(XZ_DYNALLOC, (u32) -1);
	if (!xz_dec)
		return -EINVAL;

	dec_buf.in_size = mfa2_file->cb_archive_size;
	dec_buf.in = mfa2_file->cb;
	dec_buf.in_pos = 0;
	dec_buf.out = buf;

	/* decode up to the offset */
	do {
		dec_buf.out_pos = 0;
		dec_buf.out_size = min_t(size_t, size, off - curr_off);
		if (dec_buf.out_size == 0)
			break;

		err = mlxfw_mfa2_xz_dec_run(xz_dec, &dec_buf, &finished);
		if (err)
			goto out;
		if (finished) {
			pr_err("xz section too short\n");
			err = -EINVAL;
			goto out;
		}
		curr_off += dec_buf.out_pos;
	} while (curr_off != off);

	/* decode the needed section */
	dec_buf.out_pos = 0;
	dec_buf.out_size = size;
	err = mlxfw_mfa2_xz_dec_run(xz_dec, &dec_buf, &finished);
out:
	xz_dec_end(xz_dec);
	return err;
}

static const struct mlxfw_mfa2_tlv_component_descriptor *
mlxfw_mfa2_file_component_tlv_get(const struct mlxfw_mfa2_file *mfa2_file,
				  u16 comp_index)
{
	const struct mlxfw_mfa2_tlv_multi *multi;
	const struct mlxfw_mfa2_tlv *multi_child;
	const struct mlxfw_mfa2_tlv *comp_tlv;

	if (comp_index > mfa2_file->component_count)
		return NULL;

	comp_tlv = mlxfw_mfa2_tlv_advance(mfa2_file, mfa2_file->first_component,
					  comp_index);
	if (!comp_tlv)
		return NULL;

	multi = mlxfw_mfa2_tlv_multi_get(mfa2_file, comp_tlv);
	if (!multi)
		return NULL;

	multi_child = mlxfw_mfa2_tlv_multi_child(mfa2_file, multi);
	if (!multi_child)
		return NULL;

	return mlxfw_mfa2_tlv_component_descriptor_get(mfa2_file, multi_child);
}

struct mlxfw_mfa2_comp_data {
	struct mlxfw_mfa2_component comp;
	u8 buff[];
};

static const struct mlxfw_mfa2_tlv_component_descriptor *
mlxfw_mfa2_file_component_find(const struct mlxfw_mfa2_file *mfa2_file,
			       const char *psid, int psid_size,
			       int component_index)
{
	const struct mlxfw_mfa2_tlv_component_ptr *cptr;
	const struct mlxfw_mfa2_tlv_multi *dev_multi;
	const struct mlxfw_mfa2_tlv *cptr_tlv;
	u16 comp_idx;

	dev_multi = mlxfw_mfa2_tlv_dev_get(mfa2_file, psid, psid_size);
	if (!dev_multi)
		return NULL;

	cptr_tlv = mlxfw_mfa2_tlv_multi_child_find(mfa2_file, dev_multi,
						   MLXFW_MFA2_TLV_COMPONENT_PTR,
						   component_index);
	if (!cptr_tlv)
		return NULL;

	cptr = mlxfw_mfa2_tlv_component_ptr_get(mfa2_file, cptr_tlv);
	if (!cptr)
		return NULL;

	comp_idx = be16_to_cpu(cptr->component_index);
	return mlxfw_mfa2_file_component_tlv_get(mfa2_file, comp_idx);
}

struct mlxfw_mfa2_component *
mlxfw_mfa2_file_component_get(const struct mlxfw_mfa2_file *mfa2_file,
			      const char *psid, int psid_size,
			      int component_index)
{
	const struct mlxfw_mfa2_tlv_component_descriptor *comp;
	struct mlxfw_mfa2_comp_data *comp_data;
	u32 comp_buf_size;
	off_t cb_offset;
	u32 comp_size;
	int err;

	comp = mlxfw_mfa2_file_component_find(mfa2_file, psid, psid_size,
					      component_index);
	if (!comp)
		return ERR_PTR(-EINVAL);

	cb_offset = (u64) be32_to_cpu(comp->cb_offset_h) << 32 |
		    be32_to_cpu(comp->cb_offset_l);
	comp_size = be32_to_cpu(comp->size);
	comp_buf_size = comp_size + mlxfw_mfa2_comp_magic_len;

	comp_data = vzalloc(sizeof(*comp_data) + comp_buf_size);
	if (!comp_data)
		return ERR_PTR(-ENOMEM);
	comp_data->comp.data_size = comp_size;
	comp_data->comp.index = be16_to_cpu(comp->identifier);
	err = mlxfw_mfa2_file_cb_offset_xz(mfa2_file, cb_offset, comp_buf_size,
					   comp_data->buff);
	if (err) {
		pr_err("Component could not be reached in CB\n");
		goto err_out;
	}

	if (memcmp(comp_data->buff, mlxfw_mfa2_comp_magic,
		   mlxfw_mfa2_comp_magic_len) != 0) {
		pr_err("Component has wrong magic\n");
		err = -EINVAL;
		goto err_out;
	}

	comp_data->comp.data = comp_data->buff + mlxfw_mfa2_comp_magic_len;
	return &comp_data->comp;
err_out:
	vfree(comp_data);
	return ERR_PTR(err);
}

void mlxfw_mfa2_file_component_put(struct mlxfw_mfa2_component *comp)
{
	const struct mlxfw_mfa2_comp_data *comp_data;

	comp_data = container_of(comp, struct mlxfw_mfa2_comp_data, comp);
	vfree(comp_data);
}

void mlxfw_mfa2_file_fini(struct mlxfw_mfa2_file *mfa2_file)
{
	kfree(mfa2_file);
}
