Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / u-boot / fs / yaffs2 / yaffs_tagscompat.c
diff --git a/qemu/roms/u-boot/fs/yaffs2/yaffs_tagscompat.c b/qemu/roms/u-boot/fs/yaffs2/yaffs_tagscompat.c
new file mode 100644 (file)
index 0000000..9ac5896
--- /dev/null
@@ -0,0 +1,407 @@
+/*
+ * YAFFS: Yet Another Flash File System. A NAND-flash specific file system.
+ *
+ * Copyright (C) 2002-2011 Aleph One Ltd.
+ *   for Toby Churchill Ltd and Brightstar Engineering
+ *
+ * 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_guts.h"
+#include "yaffs_tagscompat.h"
+#include "yaffs_ecc.h"
+#include "yaffs_getblockinfo.h"
+#include "yaffs_trace.h"
+
+static void yaffs_handle_rd_data_error(struct yaffs_dev *dev, int nand_chunk);
+
+
+/********** Tags ECC calculations  *********/
+
+void yaffs_calc_ecc(const u8 *data, struct yaffs_spare *spare)
+{
+       yaffs_ecc_calc(data, spare->ecc1);
+       yaffs_ecc_calc(&data[256], spare->ecc2);
+}
+
+void yaffs_calc_tags_ecc(struct yaffs_tags *tags)
+{
+       /* Calculate an ecc */
+       unsigned char *b = ((union yaffs_tags_union *)tags)->as_bytes;
+       unsigned i, j;
+       unsigned ecc = 0;
+       unsigned bit = 0;
+
+       tags->ecc = 0;
+
+       for (i = 0; i < 8; i++) {
+               for (j = 1; j & 0xff; j <<= 1) {
+                       bit++;
+                       if (b[i] & j)
+                               ecc ^= bit;
+               }
+       }
+       tags->ecc = ecc;
+}
+
+int yaffs_check_tags_ecc(struct yaffs_tags *tags)
+{
+       unsigned ecc = tags->ecc;
+
+       yaffs_calc_tags_ecc(tags);
+
+       ecc ^= tags->ecc;
+
+       if (ecc && ecc <= 64) {
+               /* TODO: Handle the failure better. Retire? */
+               unsigned char *b = ((union yaffs_tags_union *)tags)->as_bytes;
+
+               ecc--;
+
+               b[ecc / 8] ^= (1 << (ecc & 7));
+
+               /* Now recvalc the ecc */
+               yaffs_calc_tags_ecc(tags);
+
+               return 1;       /* recovered error */
+       } else if (ecc) {
+               /* Wierd ecc failure value */
+               /* TODO Need to do somethiong here */
+               return -1;      /* unrecovered error */
+       }
+       return 0;
+}
+
+/********** Tags **********/
+
+static void yaffs_load_tags_to_spare(struct yaffs_spare *spare_ptr,
+                                    struct yaffs_tags *tags_ptr)
+{
+       union yaffs_tags_union *tu = (union yaffs_tags_union *)tags_ptr;
+
+       yaffs_calc_tags_ecc(tags_ptr);
+
+       spare_ptr->tb0 = tu->as_bytes[0];
+       spare_ptr->tb1 = tu->as_bytes[1];
+       spare_ptr->tb2 = tu->as_bytes[2];
+       spare_ptr->tb3 = tu->as_bytes[3];
+       spare_ptr->tb4 = tu->as_bytes[4];
+       spare_ptr->tb5 = tu->as_bytes[5];
+       spare_ptr->tb6 = tu->as_bytes[6];
+       spare_ptr->tb7 = tu->as_bytes[7];
+}
+
+static void yaffs_get_tags_from_spare(struct yaffs_dev *dev,
+                                     struct yaffs_spare *spare_ptr,
+                                     struct yaffs_tags *tags_ptr)
+{
+       union yaffs_tags_union *tu = (union yaffs_tags_union *)tags_ptr;
+       int result;
+
+       tu->as_bytes[0] = spare_ptr->tb0;
+       tu->as_bytes[1] = spare_ptr->tb1;
+       tu->as_bytes[2] = spare_ptr->tb2;
+       tu->as_bytes[3] = spare_ptr->tb3;
+       tu->as_bytes[4] = spare_ptr->tb4;
+       tu->as_bytes[5] = spare_ptr->tb5;
+       tu->as_bytes[6] = spare_ptr->tb6;
+       tu->as_bytes[7] = spare_ptr->tb7;
+
+       result = yaffs_check_tags_ecc(tags_ptr);
+       if (result > 0)
+               dev->n_tags_ecc_fixed++;
+       else if (result < 0)
+               dev->n_tags_ecc_unfixed++;
+}
+
+static void yaffs_spare_init(struct yaffs_spare *spare)
+{
+       memset(spare, 0xff, sizeof(struct yaffs_spare));
+}
+
+static int yaffs_wr_nand(struct yaffs_dev *dev,
+                        int nand_chunk, const u8 *data,
+                        struct yaffs_spare *spare)
+{
+       if (nand_chunk < dev->param.start_block * dev->param.chunks_per_block) {
+               yaffs_trace(YAFFS_TRACE_ERROR,
+                       "**>> yaffs chunk %d is not valid",
+                       nand_chunk);
+               return YAFFS_FAIL;
+       }
+
+       return dev->param.write_chunk_fn(dev, nand_chunk, data, spare);
+}
+
+static int yaffs_rd_chunk_nand(struct yaffs_dev *dev,
+                              int nand_chunk,
+                              u8 *data,
+                              struct yaffs_spare *spare,
+                              enum yaffs_ecc_result *ecc_result,
+                              int correct_errors)
+{
+       int ret_val;
+       struct yaffs_spare local_spare;
+
+       if (!spare) {
+               /* If we don't have a real spare, then we use a local one. */
+               /* Need this for the calculation of the ecc */
+               spare = &local_spare;
+       }
+
+       if (!dev->param.use_nand_ecc) {
+               ret_val =
+                   dev->param.read_chunk_fn(dev, nand_chunk, data, spare);
+               if (data && correct_errors) {
+                       /* Do ECC correction */
+                       /* Todo handle any errors */
+                       int ecc_result1, ecc_result2;
+                       u8 calc_ecc[3];
+
+                       yaffs_ecc_calc(data, calc_ecc);
+                       ecc_result1 =
+                           yaffs_ecc_correct(data, spare->ecc1, calc_ecc);
+                       yaffs_ecc_calc(&data[256], calc_ecc);
+                       ecc_result2 =
+                           yaffs_ecc_correct(&data[256], spare->ecc2,
+                                             calc_ecc);
+
+                       if (ecc_result1 > 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>yaffs ecc error fix performed on chunk %d:0",
+                                       nand_chunk);
+                               dev->n_ecc_fixed++;
+                       } else if (ecc_result1 < 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>yaffs ecc error unfixed on chunk %d:0",
+                                       nand_chunk);
+                               dev->n_ecc_unfixed++;
+                       }
+
+                       if (ecc_result2 > 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>yaffs ecc error fix performed on chunk %d:1",
+                                       nand_chunk);
+                               dev->n_ecc_fixed++;
+                       } else if (ecc_result2 < 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>yaffs ecc error unfixed on chunk %d:1",
+                                       nand_chunk);
+                               dev->n_ecc_unfixed++;
+                       }
+
+                       if (ecc_result1 || ecc_result2) {
+                               /* We had a data problem on this page */
+                               yaffs_handle_rd_data_error(dev, nand_chunk);
+                       }
+
+                       if (ecc_result1 < 0 || ecc_result2 < 0)
+                               *ecc_result = YAFFS_ECC_RESULT_UNFIXED;
+                       else if (ecc_result1 > 0 || ecc_result2 > 0)
+                               *ecc_result = YAFFS_ECC_RESULT_FIXED;
+                       else
+                               *ecc_result = YAFFS_ECC_RESULT_NO_ERROR;
+               }
+       } else {
+               /* Must allocate enough memory for spare+2*sizeof(int) */
+               /* for ecc results from device. */
+               struct yaffs_nand_spare nspare;
+
+               memset(&nspare, 0, sizeof(nspare));
+
+               ret_val = dev->param.read_chunk_fn(dev, nand_chunk, data,
+                                                  (struct yaffs_spare *)
+                                                  &nspare);
+               memcpy(spare, &nspare, sizeof(struct yaffs_spare));
+               if (data && correct_errors) {
+                       if (nspare.eccres1 > 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>mtd ecc error fix performed on chunk %d:0",
+                                       nand_chunk);
+                       } else if (nspare.eccres1 < 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>mtd ecc error unfixed on chunk %d:0",
+                                       nand_chunk);
+                       }
+
+                       if (nspare.eccres2 > 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>mtd ecc error fix performed on chunk %d:1",
+                                       nand_chunk);
+                       } else if (nspare.eccres2 < 0) {
+                               yaffs_trace(YAFFS_TRACE_ERROR,
+                                       "**>>mtd ecc error unfixed on chunk %d:1",
+                                       nand_chunk);
+                       }
+
+                       if (nspare.eccres1 || nspare.eccres2) {
+                               /* We had a data problem on this page */
+                               yaffs_handle_rd_data_error(dev, nand_chunk);
+                       }
+
+                       if (nspare.eccres1 < 0 || nspare.eccres2 < 0)
+                               *ecc_result = YAFFS_ECC_RESULT_UNFIXED;
+                       else if (nspare.eccres1 > 0 || nspare.eccres2 > 0)
+                               *ecc_result = YAFFS_ECC_RESULT_FIXED;
+                       else
+                               *ecc_result = YAFFS_ECC_RESULT_NO_ERROR;
+
+               }
+       }
+       return ret_val;
+}
+
+/*
+ * Functions for robustisizing
+ */
+
+static void yaffs_handle_rd_data_error(struct yaffs_dev *dev, int nand_chunk)
+{
+       int flash_block = nand_chunk / dev->param.chunks_per_block;
+
+       /* Mark the block for retirement */
+       yaffs_get_block_info(dev, flash_block + dev->block_offset)->
+               needs_retiring = 1;
+       yaffs_trace(YAFFS_TRACE_ERROR | YAFFS_TRACE_BAD_BLOCKS,
+               "**>>Block %d marked for retirement",
+               flash_block);
+
+       /* TODO:
+        * Just do a garbage collection on the affected block
+        * then retire the block
+        * NB recursion
+        */
+}
+
+int yaffs_tags_compat_wr(struct yaffs_dev *dev,
+                        int nand_chunk,
+                        const u8 *data, const struct yaffs_ext_tags *ext_tags)
+{
+       struct yaffs_spare spare;
+       struct yaffs_tags tags;
+
+       yaffs_spare_init(&spare);
+
+       if (ext_tags->is_deleted)
+               spare.page_status = 0;
+       else {
+               tags.obj_id = ext_tags->obj_id;
+               tags.chunk_id = ext_tags->chunk_id;
+
+               tags.n_bytes_lsb = ext_tags->n_bytes & (1024 - 1);
+
+               if (dev->data_bytes_per_chunk >= 1024)
+                       tags.n_bytes_msb = (ext_tags->n_bytes >> 10) & 3;
+               else
+                       tags.n_bytes_msb = 3;
+
+               tags.serial_number = ext_tags->serial_number;
+
+               if (!dev->param.use_nand_ecc && data)
+                       yaffs_calc_ecc(data, &spare);
+
+               yaffs_load_tags_to_spare(&spare, &tags);
+       }
+       return yaffs_wr_nand(dev, nand_chunk, data, &spare);
+}
+
+int yaffs_tags_compat_rd(struct yaffs_dev *dev,
+                        int nand_chunk,
+                        u8 *data, struct yaffs_ext_tags *ext_tags)
+{
+       struct yaffs_spare spare;
+       struct yaffs_tags tags;
+       enum yaffs_ecc_result ecc_result = YAFFS_ECC_RESULT_UNKNOWN;
+       static struct yaffs_spare spare_ff;
+       static int init;
+       int deleted;
+
+       if (!init) {
+               memset(&spare_ff, 0xff, sizeof(spare_ff));
+               init = 1;
+       }
+
+       if (!yaffs_rd_chunk_nand(dev, nand_chunk,
+                                       data, &spare, &ecc_result, 1))
+               return YAFFS_FAIL;
+
+       /* ext_tags may be NULL */
+       if (!ext_tags)
+               return YAFFS_OK;
+
+       deleted = (hweight8(spare.page_status) < 7) ? 1 : 0;
+
+       ext_tags->is_deleted = deleted;
+       ext_tags->ecc_result = ecc_result;
+       ext_tags->block_bad = 0;        /* We're reading it */
+       /* therefore it is not a bad block */
+       ext_tags->chunk_used =
+               memcmp(&spare_ff, &spare, sizeof(spare_ff)) ? 1 : 0;
+
+       if (ext_tags->chunk_used) {
+               yaffs_get_tags_from_spare(dev, &spare, &tags);
+               ext_tags->obj_id = tags.obj_id;
+               ext_tags->chunk_id = tags.chunk_id;
+               ext_tags->n_bytes = tags.n_bytes_lsb;
+
+               if (dev->data_bytes_per_chunk >= 1024)
+                       ext_tags->n_bytes |=
+                               (((unsigned)tags.n_bytes_msb) << 10);
+
+               ext_tags->serial_number = tags.serial_number;
+       }
+
+       return YAFFS_OK;
+}
+
+int yaffs_tags_compat_mark_bad(struct yaffs_dev *dev, int flash_block)
+{
+       struct yaffs_spare spare;
+
+       memset(&spare, 0xff, sizeof(struct yaffs_spare));
+
+       spare.block_status = 'Y';
+
+       yaffs_wr_nand(dev, flash_block * dev->param.chunks_per_block, NULL,
+                     &spare);
+       yaffs_wr_nand(dev, flash_block * dev->param.chunks_per_block + 1,
+                     NULL, &spare);
+
+       return YAFFS_OK;
+}
+
+int yaffs_tags_compat_query_block(struct yaffs_dev *dev,
+                                 int block_no,
+                                 enum yaffs_block_state *state,
+                                 u32 *seq_number)
+{
+       struct yaffs_spare spare0, spare1;
+       static struct yaffs_spare spare_ff;
+       static int init;
+       enum yaffs_ecc_result dummy;
+
+       if (!init) {
+               memset(&spare_ff, 0xff, sizeof(spare_ff));
+               init = 1;
+       }
+
+       *seq_number = 0;
+
+       yaffs_rd_chunk_nand(dev, block_no * dev->param.chunks_per_block, NULL,
+                           &spare0, &dummy, 1);
+       yaffs_rd_chunk_nand(dev, block_no * dev->param.chunks_per_block + 1,
+                           NULL, &spare1, &dummy, 1);
+
+       if (hweight8(spare0.block_status & spare1.block_status) < 7)
+               *state = YAFFS_BLOCK_STATE_DEAD;
+       else if (memcmp(&spare_ff, &spare0, sizeof(spare_ff)) == 0)
+               *state = YAFFS_BLOCK_STATE_EMPTY;
+       else
+               *state = YAFFS_BLOCK_STATE_NEEDS_SCAN;
+
+       return YAFFS_OK;
+}