/*
 * YAFFS: Yet Another Flash File System. A NAND-flash specific file system.
 *
 * Copyright (C) 2002-2018 Aleph One Ltd.
 *
 * Created by Charles Manning <charles@aleph1.co.uk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include "yaffs_packedtags2.h"
#include "yportenv.h"
#include "yaffs_trace.h"
#include "yaffs_endian.h"

/* This code packs a set of extended tags into a binary structure for
 * NAND storage
 */

/* Some of the information is "extra" struff which can be packed in to
 * speed scanning
 * This is defined by having the EXTRA_HEADER_INFO_FLAG set.
 */

/* Extra flags applied to chunk_id */

#define EXTRA_HEADER_INFO_FLAG	0x80000000
#define EXTRA_SHRINK_FLAG	0x40000000
#define EXTRA_SHADOWS_FLAG	0x20000000
#define EXTRA_SPARE_FLAGS	0x10000000

#define ALL_EXTRA_FLAGS		0xf0000000

/* Also, the top 4 bits of the object Id are set to the object type. */
#define EXTRA_OBJECT_TYPE_SHIFT (28)
#define EXTRA_OBJECT_TYPE_MASK  ((0x0f) << EXTRA_OBJECT_TYPE_SHIFT)

static void yaffs_dump_packed_tags2_tags_only(
				const struct yaffs_packed_tags2_tags_only *ptt)
{
	(void) ptt;

	yaffs_trace(YAFFS_TRACE_MTD,
		"packed tags obj %d chunk %d byte %d seq %d",
		ptt->obj_id, ptt->chunk_id, ptt->n_bytes, ptt->seq_number);
}

static void yaffs_dump_packed_tags2(const struct yaffs_packed_tags2 *pt)
{
	yaffs_dump_packed_tags2_tags_only(&pt->t);
}

static void yaffs_dump_tags2(const struct yaffs_ext_tags *t)
{

	(void) t;
	yaffs_trace(YAFFS_TRACE_MTD,
		"ext.tags eccres %d blkbad %d chused %d obj %d chunk%d byte %d del %d ser %d seq %d",
		t->ecc_result, t->block_bad, t->chunk_used, t->obj_id,
		t->chunk_id, t->n_bytes, t->is_deleted, t->serial_number,
		t->seq_number);

}

static int yaffs_check_tags_extra_packable(const struct yaffs_ext_tags *t)
{
	if (t->chunk_id != 0 || !t->extra_available)
		return 0;

	/* Check if the file size is too long to store */
	if (t->extra_obj_type == YAFFS_OBJECT_TYPE_FILE &&
	    (t->extra_file_size >> 31) != 0)
		return 0;
	return 1;
}

void yaffs_pack_tags2_tags_only(struct yaffs_dev *dev,
				struct yaffs_packed_tags2_tags_only *ptt,
				const struct yaffs_ext_tags *t)
{
	ptt->chunk_id = t->chunk_id;
	ptt->seq_number = t->seq_number;
	ptt->n_bytes = t->n_bytes;
	ptt->obj_id = t->obj_id;

	/* Only store extra tags for object headers.
	 * If it is a file then only store  if the file size is short\
	 * enough to fit.
	 */
	if (yaffs_check_tags_extra_packable(t)) {
		/* Store the extra header info instead */
		/* We save the parent object in the chunk_id */
		ptt->chunk_id = EXTRA_HEADER_INFO_FLAG | t->extra_parent_id;
		if (t->extra_is_shrink)
			ptt->chunk_id |= EXTRA_SHRINK_FLAG;
		if (t->extra_shadows)
			ptt->chunk_id |= EXTRA_SHADOWS_FLAG;

		ptt->obj_id &= ~EXTRA_OBJECT_TYPE_MASK;
		ptt->obj_id |= (t->extra_obj_type << EXTRA_OBJECT_TYPE_SHIFT);

		if (t->extra_obj_type == YAFFS_OBJECT_TYPE_HARDLINK)
			ptt->n_bytes = t->extra_equiv_id;
		else if (t->extra_obj_type == YAFFS_OBJECT_TYPE_FILE)
			ptt->n_bytes = (unsigned) t->extra_file_size;
		else
			ptt->n_bytes = 0;
	}

	yaffs_dump_packed_tags2_tags_only(ptt);
	yaffs_dump_tags2(t);
	yaffs_do_endian_packed_tags2(dev, ptt);
}

void yaffs_pack_tags2(struct yaffs_dev *dev,
		      struct yaffs_packed_tags2 *pt,
		      const struct yaffs_ext_tags *t, int tags_ecc)
{
	yaffs_pack_tags2_tags_only(dev, &pt->t, t);

	if (tags_ecc)
		yaffs_ecc_calc_other((unsigned char *)&pt->t,
				    sizeof(struct yaffs_packed_tags2_tags_only),
				    &pt->ecc);
}

void yaffs_unpack_tags2_tags_only(struct yaffs_dev *dev,
				  struct yaffs_ext_tags *t,
				  struct yaffs_packed_tags2_tags_only *ptt_ptr)
{
	struct yaffs_packed_tags2_tags_only ptt_copy = *ptt_ptr;

	memset(t, 0, sizeof(struct yaffs_ext_tags));

	if (ptt_copy.seq_number == 0xffffffff)
		return;

	yaffs_do_endian_packed_tags2(dev, &ptt_copy);

	t->block_bad = 0;
	t->chunk_used = 1;
	t->obj_id = ptt_copy.obj_id;
	t->chunk_id = ptt_copy.chunk_id;
	t->n_bytes = ptt_copy.n_bytes;
	t->is_deleted = 0;
	t->serial_number = 0;
	t->seq_number = ptt_copy.seq_number;

	/* Do extra header info stuff */
	if (ptt_copy.chunk_id & EXTRA_HEADER_INFO_FLAG) {
		t->chunk_id = 0;
		t->n_bytes = 0;

		t->extra_available = 1;
		t->extra_parent_id = ptt_copy.chunk_id & (~(ALL_EXTRA_FLAGS));
		t->extra_is_shrink = ptt_copy.chunk_id & EXTRA_SHRINK_FLAG ? 1 : 0;
		t->extra_shadows = ptt_copy.chunk_id & EXTRA_SHADOWS_FLAG ? 1 : 0;
		t->extra_obj_type = ptt_copy.obj_id >> EXTRA_OBJECT_TYPE_SHIFT;
		t->obj_id &= ~EXTRA_OBJECT_TYPE_MASK;

		if (t->extra_obj_type == YAFFS_OBJECT_TYPE_HARDLINK)
			t->extra_equiv_id = ptt_copy.n_bytes;
		else
			t->extra_file_size = ptt_copy.n_bytes;
	}
	yaffs_dump_packed_tags2_tags_only(ptt_ptr);
	yaffs_dump_tags2(t);
}

void yaffs_unpack_tags2(struct yaffs_dev *dev,
			struct yaffs_ext_tags *t,
			struct yaffs_packed_tags2 *pt,
			int tags_ecc)
{
	enum yaffs_ecc_result ecc_result = YAFFS_ECC_RESULT_NO_ERROR;

	if (pt->t.seq_number != 0xffffffff && tags_ecc) {
		/* Chunk is in use and we need to do ECC */

		struct yaffs_ecc_other ecc;
		int result;
		yaffs_ecc_calc_other((unsigned char *)&pt->t,
				sizeof(struct yaffs_packed_tags2_tags_only),
				&ecc);
		result =
		    yaffs_ecc_correct_other((unsigned char *)&pt->t,
				sizeof(struct yaffs_packed_tags2_tags_only),
				&pt->ecc, &ecc);
		switch (result) {
		case 0:
			ecc_result = YAFFS_ECC_RESULT_NO_ERROR;
			break;
		case 1:
			ecc_result = YAFFS_ECC_RESULT_FIXED;
			break;
		case -1:
			ecc_result = YAFFS_ECC_RESULT_UNFIXED;
			break;
		default:
			ecc_result = YAFFS_ECC_RESULT_UNKNOWN;
		}
	}
	yaffs_unpack_tags2_tags_only(dev, t, &pt->t);

	t->ecc_result = ecc_result;

	yaffs_dump_packed_tags2(pt);
	yaffs_dump_tags2(t);
}
