PATCH 3/7] ubi: logging feature for ubi
Brijesh Singh
brijesh.s.singh at gmail.com
Mon Apr 12 04:36:11 EDT 2010
Note: commit for logging feature
Signed-off-by: Rohit Dongre <rohit.dongre at samsung.com>
---
--- ubi_old/drivers/mtd/ubi/commit.c 1970-01-01 05:30:00.000000000 +0530
+++ ubi_new/drivers/mtd/ubi/commit.c 2010-04-09 21:54:02.635580892 +0530
@@ -0,0 +1,1376 @@
+/*
+ * Copyright © 2009 Samsung Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Rohit Dongre
+ * Brijesh Singh
+ */
+
+/*
+ * The UBIL commit sub-system.
+ *
+ * Commit module perform checkpointing of valid eba mappings. Whenever el calls
+ * commit, checkpointing for valid blocks is done and new el is created for
+ * further el logging.
+ *
+ * Commit blocks is mirrored to have two copies for valid eba mappings. At
+ * attach time commit blocks are read to get last valid eba mappings.
+ */
+
+
+#include <linux/slab.h>
+#include <linux/crc32.h>
+#include <linux/err.h>
+#ifdef CONFIG_MTD_UBIL_COMPR
+#include <linux/crypto.h>
+#endif
+#include "ubi.h"
+
+#ifdef CONFIG_MTD_UBI_LOGGED
+
+#define COMMIT_HDR_SIZE sizeof(struct cmt_bud_hdr)
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+static struct ubi_compressor lzo_compr = {
+ .compr_type = 1,
+ .name = "lzo",
+ .capi_name = "lzo",
+};
+#endif
+
+/**
+ * ubi_schedule_cmt - set flag to perform commit
+ * @ubi_device: UBI device description object
+ *
+ * This function set ubi->schedule_cmt flag if not set and take el lock.
+ *
+ * In order to perform commit operation, el should be locked to avoid
+ * inconsistencies in commit and el.
+ */
+
+void ubi_schedule_cmt(struct ubi_device *ubi)
+{
+ if (!ubi->schedule_cmt) {
+ /* Take el mutex */
+ mutex_lock(&ubi->el_mutex);
+ /* Set flag to do commit operation*/
+ ubi->schedule_cmt = 1;
+ }
+}
+
+/**
+ * ubi_ensure_cmt - perform commit operation if required
+ * @ubi_device: UBI device description object
+ *
+ * This function check ubi->schedule_cmt flag, if set commit operation is
+ * called. Otherwise function do nothing and return success.
+ *
+ * During ubi attach operation if error occures in commit read or el scan
+ * operation, ubi->schedule_cmt is set. If flag is set, commit operation is
+ * performed. After performing commit flag is reset and el lock is released.
+ *
+ * If commit operation fails negative error code is returned,
otherwise function
+ * return %0 on success.
+ */
+
+int ubi_ensure_cmt(struct ubi_device *ubi)
+{
+ int err = 0;
+
+ if (!ubi->schedule_cmt)
+ return 0;
+
+ ubi_msg("Recovering ubi");
+ err = ubi_cmt(ubi);
+ /*
+ * If commit operation fails, then ubi is switched to read-only mode.
+ * So if ubi_cmt() return failure, ubi is already in read-only mode,
+ * same error is returned and we release el mutex.
+ */
+ ubi->schedule_cmt = 0;
+ mutex_unlock(&ubi->el_mutex);
+ return err;
+}
+
+/**
+ * paranoid_check_reservd_status - check status of active reserved blocks
+ * @ubi_device: UBI device description object
+ *
+ * This function check status of active reserved blocks. All reserved blocks
+ * must have UBIL_PEB_USED_SP status. If active reserved block is found with
+ * wrong status, function return %-1, otherwise return %0.
+ */
+
+int paranoid_check_reservd_status(struct ubi_device *ubi)
+{
+ int i, pnum;
+
+ dbg_cmt("paranoid check cmt rsvd blck status ");
+
+ for (i = 0; i < be32_to_cpu(ubi->sb_node->cmt_next_buds); i++) {
+ pnum = be32_to_cpu(ubi->sb_node->buds[i]);
+ if (ubi->peb_lookup[pnum].status != UBIL_PEB_USED_SP)
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * paranoid_check_special - verify bud is reserved bud
+ * @ubi_device: UBI device description object
+ * @pnum: physical eraseblock to verify
+ *
+ * This function check whether @pnum is reserved bud. If @pnum is a reserved
+ * function return 1. If @in_pnum is not a reserved bud then return %0.
+ */
+
+int paranoid_check_special(struct ubi_device *ubi, int pnum)
+{
+ int resvd_pnum, act_bud, bud;
+ struct ubi_sb *sb ;
+
+ sb = ubi->sb_node;
+ act_bud = be32_to_cpu(sb->cmt_next_buds);
+
+ for (bud = 0; bud < act_bud; bud++) {
+ resvd_pnum = be32_to_cpu(sb->buds[bud]);
+ if (pnum == resvd_pnum)
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * ubi_cmt_progress - check commit status
+ * @ubi_device: UBI device description object
+ *
+ * This function returns status of commit operation. Commit opeation consist of
+ * different sub-operations. Depending on status, function return any one of
+ * the following status
+ * C_FREE - no commit operation
+ * C_STARTED - started commit operation
+ * C_WRITING - writing commit to device
+ * C_FINISHED - finished write operation
+ * C_FAILED - commit operation failed
+ */
+
+int ubi_cmt_progress(struct ubi_device *ubi)
+{
+ int status = 0;
+ spin_lock(&ubi->cmt_lock);
+ status = ubi->c_status;
+ spin_unlock(&ubi->cmt_lock);
+ return status;
+}
+
+
+/**
+ * ubi_cmt_init - initialize commit sub-system
+ * @ubi_device: UBI device description object
+ *
+ * This function initialize commit sub-system. If memory allocation fails
+ * function return negative error code and zero in case of success.
+ */
+int ubi_cmt_init(struct ubi_device *ubi)
+{
+ dbg_cmt("initializing commit");
+
+ ubi->c_buds = kmalloc(ubi->c_reservd_buds * UBIL_CMT_COPIES
+ * sizeof(int), GFP_KERNEL);
+ if (!ubi->c_buds) {
+ ubi_err("could not allocate memory");
+ return -ENOMEM;
+ }
+ /* Calculate maximum count of rec in one commit bud */
+ ubi->c_max_no_of_rec = ubi->c_max_data_size / UBIL_EL_REC_SIZE ;
+
+ /* Set commit status */
+ ubi->c_status = C_FREE;
+ ubi->c_dirty = C_CLEAN;
+ ubi->c_previous_status = UBIL_CMT_WRITE_SUCCESS;
+ /* Initialize commit lock */
+ spin_lock_init(&ubi->cmt_lock);
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+ ubi->compr = &lzo_compr;
+ ubi->compr->cc = crypto_alloc_comp(ubi->compr->capi_name, 0, 0);
+#endif
+ ubi_msg("commit max data size: %d bytes", ubi->c_max_data_size);
+ ubi_msg("commit max no of records: %d", ubi->c_max_no_of_rec);
+
+ return 0;
+}
+
+
+/**
+ * cmt_start - gather reserved buds for commit operation
+ * @ubi_device: UBI device description object
+ *
+ * This function gather required count of reserved buds for commit
+ * operation. Status of each new bud is set to UBIL_PEB_USED_SP. If
+ * required reserved buds are collected, sb is updated to have
+ * information about new reserved buds.
+ *
+ * Funtion returs zero on success and negative error code in failures.
+ */
+static int cmt_start(struct ubi_device *ubi)
+{
+ int err, pnum, bud_index, peb_count = 0;
+ struct ubi_sb *sb;
+
+ dbg_cmt("commit starting ");
+ /* Get sb */
+ sb = ubi_sb_get_node(ubi);
+
+ peb_count = UBIL_CMT_COPIES * ubi->c_reservd_buds;
+ peb_count += ubi->el_reservd_buds ;
+
+ /* Index for storing new reserved bud info in sb->buds[]*/
+ bud_index = be32_to_cpu(sb->cmt_next_buds);
+
+ dbg_cmt("commit start getting pebs from wl");
+
+ while (peb_count > 0) {
+ /* Get reserved bud */
+ pnum = ubi_wl_get_peb(ubi, UBIL_RESVD);
+ if (pnum < 0) {
+ ubi_err("unable get peb for commit");
+ err = -pnum;
+ goto unlock_sb;
+ }
+ /* Mark reserved bud as special */
+ ubi->peb_lookup[pnum].status = UBIL_PEB_USED_SP;
+ /* update sb */
+ sb->buds[bud_index++] = cpu_to_be32(pnum);
+ peb_count--;
+ dbg_cmt("\t%d", pnum);
+ }
+ dbg_cmt("\n");
+
+ /* Set commit status as invalide in sb */
+ sb->cmt_status = cpu_to_be32(UBIL_CMT_INVALID);
+ /* sync sb to store reserved bud information */
+ err = ubi_sb_sync_node(ubi);
+ if (err) {
+ ubi_err("sb sync failed");
+ goto unlock_sb;
+ }
+
+unlock_sb:
+ ubi_sb_put_node(ubi);
+ return err;
+}
+
+/**
+ * cmt_end - finish commit write process
+ * @ubi_device: UBI device description object
+ *
+ * This function complete commmit operation, by writing new sb to device. After
+ * successfull sb write operation, status of new reserved buds is changed in
+ * RAM. After successfully updation, old reserved bud are put to wear-leaveling
+ * sub-system.
+ *
+ * If wl_put_peb operation fails, we go to read-only mode. In case of
+ * success function returns %0 otherwise return negative error code.
+ */
+static int cmt_end(struct ubi_device *ubi)
+{
+
+ int copy, cmt_bud, el_bud, pnum, err ;
+ int act_bud, bud;
+ int *old_pebs, index;
+
+ struct ubi_sb *sb ;
+ /* Get sb */
+ sb = ubi_sb_get_node(ubi);
+
+ old_pebs = kmalloc(sizeof(int) * be32_to_cpu(sb->cmt_next_buds),
+ GFP_KERNEL);
+ if (!old_pebs) {
+ ubi_err("error allocating memory");
+ ubi_sb_put_node(ubi);
+ return -ENOMEM;
+ }
+
+ act_bud = be32_to_cpu(sb->cmt_next_buds);
+ /* Update new reserved bud information in sb */
+ for (bud = 0; bud < act_bud; bud++) {
+ pnum = be32_to_cpu(sb->buds[act_bud + bud]);
+ old_pebs[bud] = be32_to_cpu(sb->buds[bud]);
+ sb->buds[bud] = cpu_to_be32(pnum);
+ /* Flush buffer reserved bud information */
+ sb->buds[act_bud + bud] = cpu_to_be32(-1);
+ }
+ /* Set commit status */
+ sb->cmt_status = cpu_to_be32(UBIL_CMT_WRITE_SUCCESS);
+ /* write sb to flash */
+ err = ubi_sb_sync_node(ubi);
+ if (err) {
+ ubi_err("sb sync error ");
+ ubi_sb_put_node(ubi);
+ goto out_free;
+ }
+ /* Release sb */
+ ubi_sb_put_node(ubi);
+
+ /* Update el and commit information in memory*/
+ bud = 0;
+ dbg_cmt("commit end marking el SP ");
+ for (el_bud = 0; el_bud < ubi->el_reservd_buds; el_bud++) {
+ /* Update el bud list */
+ ubi->el_buds[el_bud] = be32_to_cpu(sb->buds[bud]);
+ /* Update el bud status */
+ ubi->peb_lookup[be32_to_cpu(sb->buds[bud])].status
+ = UBIL_PEB_USED_SP;
+ dbg_cmt(" %d ", be32_to_cpu(sb->buds[bud]));
+ bud++;
+ }
+ dbg_cmt("\n");
+
+ dbg_cmt("commit end marking commit SP ");
+ for (copy = 0; copy < UBIL_CMT_COPIES; copy++)
+ for (cmt_bud = 0; cmt_bud < ubi->c_reservd_buds;
+ cmt_bud++) {
+ index = copy * ubi->c_reservd_buds + cmt_bud;
+ /* Update commit bud list */
+ ubi->c_buds[index] = be32_to_cpu(sb->buds[bud]);
+ /* Update commit bud status */
+ ubi->peb_lookup[be32_to_cpu(sb->buds[bud])].status
+ = UBIL_PEB_USED_SP;
+
+ dbg_cmt(" %d ", be32_to_cpu(sb->buds[bud]));
+ bud++;
+ }
+ dbg_cmt("\n");
+
+ dbg_cmt("commit end putting pebs");
+ /* Put old reserved buds to wl */
+ while (--bud >= 0) {
+ dbg_cmt("\t%d", old_pebs[bud]);
+ err = ubi_wl_put_peb(ubi, old_pebs[bud], 0);
+ if (err) {
+ /* If ubi_wl_put_peb fails, then commit is
+ * consistent, but old special buds may
+ * not be recovered. At present we continue
+ * working in read-only mode.
+ */
+ ubi_err("error while putting reserved PEBs");
+ ubi_ro_mode(ubi);
+ err = 0;
+ goto out_free;
+ }
+ }
+ dbg_cmt("\n");
+
+ err = paronoid_check_reservd_status(ubi);
+ if (err < 0) {
+ ubi_err("reserved peb status wrong after commit");
+ kfree(old_pebs);
+ return err;
+ }
+out_free:
+ kfree(old_pebs);
+ return err;
+}
+
+/**
+ * cmt_write_bud_header - write commit header
+ * @ubi_device: UBI device description object
+ * @pnum: physical eraseblock
+ * @bud_hdr: commit header to be written
+ *
+ * Returns zero in case of success and negative error code in case
+ * of failure.
+ */
+
+static int cmt_write_bud_header(struct ubi_device *ubi, int pnum,
+ struct cmt_bud_hdr *bud_hdr)
+{
+
+ int err, crc, data_size ;
+
+ bud_hdr->MAGIC = cpu_to_be32(UBIL_CMT_BUD_HDR_MAGIC);
+ bud_hdr->cmt_version = cpu_to_be32(UBIL_CMT_VERSION);
+ bud_hdr->lh.node_type = cpu_to_be32(UBIL_CMT_NODE_T);
+ data_size = sizeof(struct cmt_bud_hdr) -
+ sizeof(struct node_t);
+ bud_hdr->lh.data_size = cpu_to_be32(data_size);
+ crc = crc32(UBI_CRC32_INIT, bud_hdr->lh.data, data_size);
+
+ bud_hdr->lh.data_crc = cpu_to_be32(crc);
+ err = ubi_write_node(ubi, node2lnode(bud_hdr), pnum, 0,
+ ubi->node_size);
+ if (err)
+ ubi_err("commit header write failed");
+
+ return err ;
+}
+
+/**
+ * cmt_write_data - write data to commit bud
+ * @ubi_device: UBI device description object
+ * @pnum: physical eraseblock to write data
+ * @buf: buffer with data to write
+ * @offset: offset within the physical eraseblock where to write
+ * @len: how many bytes to write
+ *
+ * This function writes data on physical eraseblock @pnum to offset @offset.
+ * In case of success returns zero, otherwise return negative erro code.
+ */
+
+static int cmt_write_data(struct ubi_device *ubi, int pnum,
+ void *buf, int offset, int len)
+{
+
+ int data_size = ALIGN(len, ubi->node_size);
+ ubi_assert(offset >= 0);
+ return ubi_io_write(ubi, buf, pnum, offset, data_size);
+}
+
+/**
+ * update_peb_status - change peb status in write buffer
+ * @ubi_device: UBI device description object
+ * @buf: buffer with data
+ * @bud: commit bud
+ *
+ * This function change status of reserved pebs in buffer @buf.
+ *
+ * In commit operation, present peb status from ubi->peb_lookup are
+ * written to device. If status of peb is changed in memory before
+ * writing commit to device, in case of fail write operations, peb_lookup
+ * will not be consistent.
+ *
+ * So updating peb status is deffered till writing to device is not done. In
+ * write operation, peb status is updated only in write buffer. In this
+ * function, write buffer is updated to contain latest peb status.
+ */
+
+static void update_peb_status(struct ubi_device *ubi, void * buf, int bud)
+{
+ int peb_count, index = 0, start_peb, end_peb, offset;
+ struct ubi_sb *sb = ubi->sb_node;
+ struct peb_info *el_rec;
+
+ el_rec = (struct peb_info *)buf;
+
+ start_peb = bud * ubi->c_max_no_of_rec;
+ end_peb = start_peb + ubi->c_max_no_of_rec;
+
+ if (end_peb > ubi->peb_count)
+ end_peb = ubi->peb_count;
+
+ peb_count = UBIL_CMT_COPIES * ubi->c_reservd_buds;
+ peb_count += ubi->el_reservd_buds;
+
+ /* Update old reserved peb status to UBIL_PEB_ERASE_PENDING */
+ dbg_cmt("commit start updating status Pending ");
+ while (--peb_count >= 0) {
+ if (be32_to_cpu(sb->buds[index]) < end_peb &&
+ be32_to_cpu(sb->buds[index]) >= start_peb) {
+ dbg_cmt("pnum %d",
+ be32_to_cpu(sb->buds[index]));
+ offset = be32_to_cpu(sb->buds[index])
+ % ubi->c_max_no_of_rec;
+ (el_rec + offset)->status
+ = UBIL_PEB_ERASE_PENDING;
+ index++;
+ }
+ }
+ dbg_cmt("\n");
+
+ peb_count = UBIL_CMT_COPIES * ubi->c_reservd_buds;
+ peb_count += ubi->el_reservd_buds;
+
+ /* Update new reserved peb status to UBIL_PEB_USED_SP */
+ dbg_cmt("\n commit start updating status SPECL ");
+ while (--peb_count >= 0) {
+ if (be32_to_cpu(sb->buds[index]) < end_peb)
+ if (be32_to_cpu(sb->buds[index]) >= start_peb) {
+ dbg_cmt("pnum %d",
+ be32_to_cpu(sb->buds[index]));
+ offset = be32_to_cpu(sb->buds[index])
+ % ubi->c_max_no_of_rec;
+ (el_rec + offset)->status
+ = UBIL_PEB_USED_SP;
+ index++;
+ }
+ }
+ dbg_cmt("\n");
+}
+
+/**
+ * write_record - write one peb_info record to buffer
+ * @ubi_device: UBI device description object
+ * @buf: buffer for storing peb information
+ * @pnum: physical eraseblock whose information is written to buffer
+ *
+ * This function write physical eraseblock @pnum information from
+ * ubi->peb_lookup to buffer @buf.
+ */
+
+static int write_record(struct ubi_device *ubi, void *buf, int pnum)
+{
+ struct peb_info *peb_rec;
+
+ peb_rec = &(ubi->peb_lookup[pnum]);
+ memcpy(buf, peb_rec, sizeof(struct peb_info));
+
+ return 0;
+}
+
+/**
+ * prepare_write_buffer - prepare commit write buffer
+ * @ubi_device: UBI device description object
+ * @buf: buffer for storing peb information
+ * @bud: commit bud
+ *
+ * This function populate buffer for writing commit bud @bud. Each commit bud
+ * can contain peb information for fixed set of pebs. So peb information of
+ * specific peb will always present in unique commit bud.
+ *
+ * peb information for pebs present in commit bud @bud is written to @buf from
+ * ubi->peb_lookup. Function return size of data written to buffer on succes.
+ * In case of failure function return negative error code
+ */
+
+static int prepare_write_buffer(struct ubi_device *ubi, void *buf, int bud)
+{
+ unsigned int data_size = 0, peb_count, start_peb, err;
+
+ start_peb = bud * ubi->c_max_no_of_rec;
+ peb_count = ubi->c_max_no_of_rec;
+
+ memset(buf, 0xFF, ubi->c_max_data_size);
+ while (peb_count-- && start_peb < ubi->peb_count) {
+ /* Write one record to buffer */
+ err = write_record(ubi, buf, start_peb);
+ if (err) {
+ ubi_err("error in copying record");
+ return err;
+ }
+ /* Update data size */
+ data_size = data_size + UBIL_EL_REC_SIZE;
+ start_peb++;
+ /* Go to next record location in buffer */
+ buf = buf + UBIL_EL_REC_SIZE;
+ }
+ return data_size;
+}
+
+/**
+ * cmt_write - write commit buds
+ * @ubi_device: UBI device description object
+ *
+ * This function write peb information to commit bud/s and create new el.
+ *
+ * In commit operation, peb information is written to commit buds. As letest
+ * peb information is written to device, present el is redundant. Thus, in
+ * commit write, new el is created.
+ *
+ * Each commit bud contain information for fixed set of pebs. In commit write
+ * each commit bud is written one at a time. Since commit is maintained in two
+ * copies, bud write operation is done for each commit copy.
+ *
+ * On success return %0, else return negative error code.
+ */
+
+static int cmt_write(struct ubi_device *ubi)
+{
+ int copy, cmt_bud = 0, el_bud;
+ int count, pnum, data_size, index;
+ int err = 0, crc;
+ void *buf;
+ struct ubi_sb *sb ;
+ struct cmt_bud_hdr bud_hdr;
+#ifdef CONFIG_MTD_UBIL_COMPR
+ void *compr_buf;
+ int compr_data_size = ubi->c_max_data_size;
+#endif
+
+ sb = ubi_sb_get_node(ubi);
+ /* Write buffer */
+ buf = vmalloc(ubi->c_max_data_size);
+ if (!buf) {
+ ubi_err("memmory allocation failed");
+ goto no_buf_memory;
+ }
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+ /* Write buffer */
+ compr_buf = vmalloc(ubi->c_max_data_size);
+ if (!compr_buf) {
+ ubi_err("memmory allocation failed");
+ vfree(buf);
+ return -ENOMEM;
+ }
+#endif
+ count = be32_to_cpu(sb->cmt_next_buds);
+ /* Create new el */
+ for (el_bud = 0; el_bud < ubi->el_reservd_buds; el_bud++) {
+ err = ubi_el_create_dflt(ubi, el_bud,
+ be32_to_cpu(sb->buds[count]));
+ if (err) {
+ ubi_err("error in creating default el");
+ goto write_error;
+ }
+ count++;
+ }
+ /* Reset el offset */
+ ubi->el_active_bud = 0;
+ ubi->el_offset = 0;
+
+ /* Write each commit bud */
+ while (cmt_bud < ubi->c_reservd_buds) {
+
+ data_size = prepare_write_buffer(ubi, buf, cmt_bud);
+ if (data_size < 0) {
+ ubi_err("error in filling write buffer");
+ err = -EINVAL;
+ goto write_error;
+ }
+
+ update_peb_status(ubi, buf, cmt_bud);
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+ err = crypto_comp_compress(ubi->compr->cc, buf, data_size,
+ compr_buf, (unsigned int *)&compr_data_size);
+ dbg_cmt("commit data of %d size, compressed to size %d",
+ data_size, compr_data_size);
+ if (err) {
+ ubi_err("error compressing data");
+ goto write_error;
+ }
+#endif
+ /* For each commit copy */
+ for (copy = 0; copy < UBIL_CMT_COPIES; copy++) {
+
+ index = count + copy * ubi->c_reservd_buds;
+ index += cmt_bud;
+
+ pnum = be32_to_cpu(sb->buds[index]);
+ ubi_assert(pnum >= 0);
+
+#ifndef CONFIG_MTD_UBIL_COMPR
+ err = cmt_write_data(ubi , pnum, buf,
+ ubi->node_size, data_size);
+#else
+ err = cmt_write_data(ubi , pnum, compr_buf,
+ ubi->node_size, compr_data_size);
+#endif
+ if (err) {
+ ubi_err("commit write data error ");
+ goto write_error;
+ }
+
+ /* Calculate data crc */
+#ifndef CONFIG_MTD_UBIL_COMPR
+ crc = crc32(UBI_CRC32_INIT, buf, data_size);
+ bud_hdr.data_crc = cpu_to_be32(crc);
+ bud_hdr.data_size = cpu_to_be32(data_size);
+#else
+ crc = crc32(UBI_CRC32_INIT, compr_buf, compr_data_size);
+ bud_hdr.data_crc = cpu_to_be32(crc);
+ bud_hdr.data_size = cpu_to_be32(compr_data_size);
+#endif
+
+ /* Write commit header */
+ err = cmt_write_bud_header(ubi, pnum, &bud_hdr) ;
+ if (err) {
+ ubi_err("commit write header error ");
+ goto write_error ;
+ }
+ }
+ cmt_bud++;
+ }
+write_error:
+ ubi_sb_put_node(ubi);
+#ifdef CONFIG_MTD_UBIL_COMPR
+ vfree(compr_buf);
+#endif
+ vfree(buf);
+ return err ;
+no_buf_memory:
+ ubi_sb_put_node(ubi);
+ return -ENOMEM ;
+}
+
+
+/**
+ * cmt_write_no_wl - write commit at ubinize time
+ * @ubi_device: UBI device description object
+ *
+ * This function is used for creating default commit at ubinize time.
+ *
+ * During ubinize time wl is not initialized. It is not possible to gather pebs
+ * from wl. Pebs are gathered explicitely and are used for writing commit buds.
+ *
+ * This funtion return zero in case of success and negative error code
+ * otherwise.
+ */
+
+static int cmt_write_no_wl(struct ubi_device *ubi)
+{
+
+ int copy, pnum, cmt_bud ;
+ int crc, data_size, err = 0, index;
+#ifdef CONFIG_MTD_UBIL_COMPR
+ void *compr_buf;
+ int compr_data_size = ubi->c_max_data_size;
+#endif
+ struct cmt_bud_hdr bud_hdr ;
+ void *buf;
+
+
+ /* Write buffer */
+ buf = vmalloc(ubi->c_max_data_size);
+ if (!buf) {
+ ubi_err("memmory allocation failed");
+ return -ENOMEM;
+ }
+#ifdef CONFIG_MTD_UBIL_COMPR
+ /* Write buffer */
+ compr_buf = vmalloc(ubi->c_max_data_size);
+ if (!compr_buf) {
+ ubi_err("memmory allocation failed");
+ vfree(buf);
+ return -ENOMEM;
+ }
+#endif
+
+ cmt_bud = 0;
+ /* For each commit bud */
+ while (cmt_bud < ubi->c_reservd_buds) {
+ /* Populate write buffer */
+ data_size = prepare_write_buffer(ubi, buf, cmt_bud);
+ if (data_size <= 0) {
+ ubi_err("error in fillind write buffer");
+ goto write_error;
+ }
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+ err = crypto_comp_compress(ubi->compr->cc, buf, data_size,
+ compr_buf, (unsigned int *)&compr_data_size);
+ dbg_cmt("commit data of %d size, compressed to size %d",
+ data_size, compr_data_size);
+ if (err) {
+ ubi_err("error compressing data");
+ goto write_error;
+ }
+#endif
+
+ for (copy = 0; copy < UBIL_CMT_COPIES; copy++) {
+ /* Get physical eraseblock number for write */
+ index = copy * ubi->c_reservd_buds + cmt_bud;
+ pnum = ubi->c_buds[index];
+
+ /* Write data to commit bud */
+#ifndef CONFIG_MTD_UBIL_COMPR
+ err = cmt_write_data(ubi , pnum, buf,
+ ubi->node_size, data_size);
+#else
+ err = cmt_write_data(ubi , pnum, compr_buf,
+ ubi->node_size, compr_data_size);
+#endif
+ if (err) {
+ ubi_err("commit write data error ");
+ goto write_error ;
+ }
+
+ /* Calculate data crc */
+#ifndef CONFIG_MTD_UBIL_COMPR
+ crc = crc32(UBI_CRC32_INIT, buf, data_size);
+ bud_hdr.data_crc = cpu_to_be32(crc);
+ bud_hdr.data_size = cpu_to_be32(data_size);
+#else
+ crc = crc32(UBI_CRC32_INIT, compr_buf, compr_data_size);
+ bud_hdr.data_crc = cpu_to_be32(crc);
+ bud_hdr.data_size = cpu_to_be32(compr_data_size);
+#endif
+ /* Write commit header */
+ err = cmt_write_bud_header(ubi, pnum, &bud_hdr) ;
+ if (err) {
+ ubi_err("commit write header error ");
+ goto write_error ;
+ }
+ }
+ cmt_bud++;
+ }
+
+write_error:
+#ifdef CONFIG_MTD_UBIL_COMPR
+ vfree(compr_buf);
+#endif
+ vfree(buf);
+ return err ;
+}
+
+/**
+ * cmt_update_sb - Update sb
+ * @ubi_device: UBI device description object
+ *
+ * This function update sb with reserved peb information.
+ */
+static void cmt_update_sb(struct ubi_device *ubi)
+{
+ int pnum, copy, peb_count;
+ int bud = 0, data_size = 0, index;
+
+ struct ubi_sb *sb = ubi->sb_node;
+
+ sb->cmt_resrvd_buds = cpu_to_be32(ubi->c_reservd_buds);
+
+ /* Update el bud list*/
+ for (pnum = 0; pnum < ubi->el_reservd_buds; pnum++) {
+ sb->buds[bud++] = cpu_to_be32(ubi->el_buds[pnum]);
+ data_size += sizeof(int);
+ }
+
+ /* Update commit bud list */
+ for (copy = 0; copy < UBIL_CMT_COPIES; copy++) {
+ for (pnum = 0; pnum < ubi->c_reservd_buds; pnum++) {
+ index = copy * ubi->c_reservd_buds + pnum;
+ sb->buds[bud++] = cpu_to_be32(ubi->c_buds[index]);
+ data_size += sizeof(int);
+ }
+ }
+
+ peb_count = UBIL_CMT_COPIES * ubi->c_reservd_buds ;
+ peb_count += ubi->el_reservd_buds;
+
+ /* Set starting offset of buffer reserved buds */
+ sb->cmt_next_buds = cpu_to_be32(peb_count);
+
+ /* Flush buffer reserved buds information*/
+ for (pnum = 0; pnum < peb_count; pnum++) {
+ sb->buds[bud++] = cpu_to_be32(-1);
+ data_size += sizeof(int);
+ }
+ /* Set comit status */
+ sb->cmt_status = cpu_to_be32(UBIL_CMT_WRITE_SUCCESS) ;
+}
+
+/**
+ * ubi_cmt_ubinize_write - create default commit
+ * @ubi_device: UBI device description object
+ *
+ * This function creates default commit. On successfull write of default
+ * commit, accordingly sb is also updated. So, when next time sb is writtne
+ * updated commit information is also written.
+ * Function returns zero on success and negative error code in case of failure.
+ */
+
+int ubi_cmt_ubinize_write(struct ubi_device *ubi)
+{
+ int err;
+ dbg_cmt("Starting ubinize commit write");
+ err = cmt_write_no_wl(ubi);
+ if (err) {
+ ubi_err("error writing commit copy to flash");
+ return err;
+ }
+ /* update sb */
+ cmt_update_sb(ubi);
+ return err;
+}
+
+/**
+ * read_record - read one peb_info record from buffer
+ * @ubi_device: UBI device description object
+ * @buf: buffer for reading peb information
+ * @pnum: physical eraseblock whose information is read from buffer
+ *
+ * This function read physical eraseblock @pnum information from buffer @buf to
+ * ubi->peb_lookup table. Functions returns zero on success and negative error
+ * code is returned on failure.
+ */
+static int read_record(struct ubi_device *ubi, void *buf, int pnum)
+{
+
+ struct peb_info *peb_rec;
+
+ peb_rec = &(ubi->peb_lookup[pnum]);
+ memcpy(peb_rec, buf, sizeof(struct peb_info));
+
+ return 0;
+}
+
+/**
+ * prepare_read_buffer - read one el record from buffer
+ * @ubi_device: UBI device description object
+ * @buf: buffer for reading peb information
+ * @bud: commit bud
+ *
+ * This function read information about pebs present in commit bud @bud from
+ * buffer @buf. Read information is used to populates ubi->peb_lookup table.
+ * On success function return zero else return error code.
+ */
+
+static int prepare_read_buffer(struct ubi_device *ubi, void *buf, int bud)
+{
+ int peb_count, err = 0, start_peb;
+
+ start_peb = bud * ubi->c_max_no_of_rec ;
+ peb_count = ubi->c_max_no_of_rec ;
+
+ while (peb_count-- && start_peb < ubi->peb_count) {
+ /* Read one record from buffer */
+ err = read_record(ubi, buf, start_peb);
+ if (err) {
+ ubi_err("error in reading commit record");
+ return err;
+ }
+ start_peb++;
+ /* Go to next record in buffer */
+ buf = buf + UBIL_EL_REC_SIZE;
+ }
+ return err;
+}
+
+/**
+ * cmt_read_bud_header - read commit bud header
+ * @ubi_device: UBI device description object
+ * @pnum: physical eraeblock
+ * @bud_hdr: commit header
+ *
+ * This function reads commit header for physical eraseblock @pnum to @bud_hdr.
+ * On successful read operations function return zero else return negative
+ * error code.
+ */
+
+static int cmt_read_bud_header(struct ubi_device *ubi, int pnum,
+ struct cmt_bud_hdr *bud_hdr)
+{
+ int err, crc, data_size;
+
+ err = ubi_read_node(ubi, node2lnode(bud_hdr), pnum, 0, ubi->node_size);
+ if (err) {
+ if (err != -EBADMSG || err != UBI_IO_BITFLIPS)
+ return err;
+ }
+
+ data_size = be32_to_cpu(bud_hdr->lh.data_size);
+
+ crc = crc32(UBI_CRC32_INIT, bud_hdr->lh.data, data_size);
+ if (crc != be32_to_cpu(bud_hdr->lh.data_crc)) {
+ ubi_err("CRC Mismatch");
+ return -EBADMSG;
+ }
+
+ if (be32_to_cpu(bud_hdr->MAGIC) != UBIL_CMT_BUD_HDR_MAGIC) {
+ ubi_err("commit block header magic incorrect");
+ return -EINVAL ;
+ }
+
+ if (be32_to_cpu(bud_hdr->cmt_version) != UBIL_CMT_VERSION) {
+ ubi_err("commit block incorrect log version");
+ return -EINVAL ;
+ }
+ if (be32_to_cpu(bud_hdr->lh.node_type) != UBIL_CMT_NODE_T) {
+ ubi_err("commit block incorrect log type");
+ return -EINVAL;
+ }
+
+ return err ;
+}
+
+/**
+ * cmt_read_data - read commit bud data
+ * @ubi_device: UBI device description object
+ * @pnum: physical eraseblock for reading
+ * @buf: buffer to read data
+ * @offset: offset within the physical eraseblock where to read
+ * @len: how many bytes to read
+ *
+ * This function reads data from physical eraseblock @pnum to buffer
+ * @buf. In case of success returns zero, otherwise return negative erro code.
+ */
+
+static int cmt_read_data(struct ubi_device *ubi, int pnum,
+ void *buf, int offset, int len)
+{
+ int data_size = ALIGN(len, ubi->node_size);
+ ubi_assert(offset >= 0);
+ return ubi_io_read(ubi, buf, pnum, offset, data_size);
+}
+
+/**
+ * commit_read_bud - read commit bud
+ * @ubi_device: UBI device description object
+ * @buf: buffer to read bud
+ * @bud: commit bud to read
+ * @active_copy: active commit copy
+ *
+ * Commit is maintained in two copies. This function read commit bud
+ * @bud from copy @active_copy to buffer @buf. If read operation is
+ * successfull function return zero.
+ *
+ * If error is BITFLIPS or EBADMSG then ubi->schedule_cmt flag is set
+ * to recover from erronious cndition and read continue furhter processing.
+ * But in case of any other error read operation do not proceed and same
+ * error code is returned.
+ */
+
+static int cmt_read_bud(struct ubi_device *ubi,
+ void *buf, int cmt_bud, int active_copy)
+{
+ int err, crc, data_size, index;
+ struct cmt_bud_hdr *bud_hdr;
+#ifdef CONFIG_MTD_UBIL_COMPR
+ void *decompr_buf;
+ int decompr_data_size = ubi->c_max_data_size;
+#endif
+
+ bud_hdr = alloc_nodes(ubi, 1);
+ if (!bud_hdr) {
+ ubi_err("out of memory");
+ return -ENOMEM;
+ }
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+ /* Write buffer */
+ decompr_buf = vmalloc(ubi->c_max_data_size);
+ if (!decompr_buf) {
+ ubi_err("memmory allocation failed");
+ free_nodes(bud_hdr);
+ return -ENOMEM;
+ }
+#endif
+
+ /* Default active copy is first commit copy */
+ index = active_copy * ubi->c_reservd_buds + cmt_bud;
+ /* Read commit bud header */
+ err = cmt_read_bud_header(ubi, ubi->c_buds[index], bud_hdr);
+ if (err == UBI_IO_BITFLIPS || err == -EBADMSG) {
+ ubi_err("commit read header bitflip occured");
+ /* Recoverable error occured. Set commit recovery flag */
+ ubi_schedule_cmt(ubi);
+ } else if (err) {
+ ubi_err("commit read header error ");
+ err = -EINVAL;
+ goto free_node;
+ }
+
+ data_size = be32_to_cpu(bud_hdr->data_size);
+
+ /* Read commit bud data */
+ err = cmt_read_data(ubi, ubi->c_buds[index], buf,
+ ubi->node_size, data_size);
+
+ if (err == UBI_IO_BITFLIPS || err == -EBADMSG) {
+ /*
+ * bitflip or EBADMSG detected set commit recovery and continue
+ * operation.
+ */
+ ubi_err("commit read data bitflip occured");
+ ubi_schedule_cmt(ubi);
+ } else if (err) {
+ ubi_err("commit read data error ");
+ err = -EINVAL;
+ goto free_node;
+ }
+ /* Calculate data crc */
+ crc = crc32(UBI_CRC32_INIT, buf, data_size);
+ if (crc != be32_to_cpu(bud_hdr->data_crc)) {
+ ubi_err("commit block bad data crc");
+ err = -EINVAL;
+ goto free_node;
+ }
+
+#ifdef CONFIG_MTD_UBIL_COMPR
+ /* decompress data */
+ err = crypto_comp_decompress(ubi->compr->cc, buf, data_size,
+ decompr_buf, (unsigned int *)&decompr_data_size);
+
+ dbg_cmt("commit data of %d size, decompressed to size %d",
+ data_size, decompr_data_size);
+
+ /* populate ubi->peb_lookup from commit bud data */
+ err = prepare_read_buffer(ubi, decompr_buf, cmt_bud);
+#else
+ /* Populate ubi->peb_lookup from commit bud data */
+ err = prepare_read_buffer(ubi, buf, cmt_bud);
+
+#endif
+ if (err) {
+ ubi_err("fill read buffer error");
+ err = -EINVAL;
+ goto free_node;
+ }
+
+free_node:
+#ifdef CONFIG_MTD_UBIL_COMPR
+ vfree(decompr_buf);
+#endif
+ free_nodes(bud_hdr);
+ return err;
+}
+
+/**
+ * ubi_cmt_read - read commit
+ * @ubi_device: UBI device description object
+ *
+ * This function reads the peb_info stored in commit buds.
+ * Each commit bud is read and peb_lookup table is populated. Commit is
+ * maintained in two copies. If read operation from one copy fails, second copy
+ * become active copy and read operation is performed from second copy. Once
+ * copy is switched, each commit bud is read again independent of failure point
+ * is first read operation.
+ *
+ * On successful read of commit from either copy function return zero.
+ * If read operation from second copy also fails, commit read operation is
+ * aborted and EINVAL is returned. In case of other failure function return
+ * negative error code.
+ */
+
+int ubi_cmt_read(struct ubi_device *ubi)
+{
+ int cmt_bud, err, active_copy = 0;
+ char *buf = NULL;
+
+ dbg_cmt("reading commit data from flash");
+
+ /* Buffer for reading data */
+ buf = vmalloc(ubi->c_max_data_size);
+ if (!buf) {
+ ubi_err("buf memory allocation failed");
+ return -ENOMEM;
+ }
+ memset(buf, 0xFF, ubi->c_max_data_size);
+retry:
+ cmt_bud = 0;
+ while (cmt_bud < ubi->c_reservd_buds) {
+ /* Read one commit bud */
+ err = cmt_read_bud(ubi, buf, cmt_bud, active_copy);
+ if (err) {
+
+ ubi_err("process bud read error");
+ goto read_error;
+ }
+ cmt_bud++;
+ }
+ vfree(buf);
+ return 0;
+
+read_error:
+ /* Error occured while reading commit bud.
+ * If error occure while reading first copy,
+ * read second commit copy. If reading second
+ * copy also fails, abort read process.
+ */
+ if (active_copy == 0) {
+ active_copy = 1 ;
+ /* Set commit recovery flag and read second copy */
+ ubi_schedule_cmt(ubi);
+ ubi_msg("switching commit copy");
+ goto retry ;
+ }
+ vfree(buf);
+ return -EINVAL;
+}
+
+/**
+ * ubi_comit_sb_init - initialize commit sub-system from sb
+ * @ubi_device: UBI device description object
+ *
+ * This function reads commit information stored in sb and initialize
+ * commit sub-system.
+ *
+ * This function returns zero on success.
+ */
+
+int ubi_cmt_sb_init(struct ubi_device *ubi)
+{
+
+ int cmt_bud, copy, index, *cmt_peb;
+ struct ubi_sb *sb;
+
+ dbg_cmt("initializing commit from sb data");
+
+ sb = ubi->sb_node ;
+ ubi->c_reservd_buds = be32_to_cpu(sb->cmt_resrvd_buds);
+
+ ubi_assert(sb);
+ ubi_assert(sb->buds);
+
+ ubi->c_previous_status = be32_to_cpu(sb->cmt_status);
+ ubi_msg("previous commit status %s",
+ ubi->c_previous_status ? "Success" : "Failed");
+ /* Initialize peb list of commit */
+ cmt_peb = sb->buds + be32_to_cpu(sb->el_resrvd_buds);
+ for (copy = 0; copy < UBIL_CMT_COPIES; copy++) {
+ for (cmt_bud = 0; cmt_bud < ubi->c_reservd_buds;
+ cmt_bud++) {
+ index = copy * ubi->c_reservd_buds + cmt_bud;
+ ubi->c_buds[index] = be32_to_cpu(*cmt_peb) ;
+ cmt_peb = cmt_peb + 1 ;
+ }
+ }
+ return 0 ;
+}
+
+/**
+ * ubi_cmt_close - close commit sub-system
+ * @ubi_device: UBI device description object
+ *
+ */
+
+int ubi_cmt_close(struct ubi_device *ubi)
+{
+ dbg_cmt("freeing commit data structures");
+#ifdef CONFIG_MTD_UBIL_COMPR
+ crypto_free_comp(ubi->compr->cc);
+#endif
+ kfree(ubi->c_buds);
+ return 0;
+}
+
+/* ubi_cmt - perform commit operation
+ * @ubi_device: UBI device description object
+ *
+ * This function write peb_lookup table to commit buds.
+ * Commit is done in following steps
+ * 1) Start commit
+ * - Get new pebs for special blocks
+ * 2) Write commit buds
+ * - Create default el and write commit buds
+ * 3) End commit
+ * - Write updated sb to flash
+ * - Do changes in RAM structures.
+ * - Put old special buds
+ *
+ * Before starting commit, ubi->c_dirty flag is checked. If write operation
+ * is not performed since ubi is initialized, then no need to do commit write
+ * operation. This flag is set when first write operation is performed.
+ *
+ * On success this function return zero. If any write operations fails
+ * then we go to read-only mode and return negative error code.
+ */
+
+int ubi_cmt(struct ubi_device *ubi)
+{
+ int err ;
+
+ dbg_cmt("closing commit");
+ spin_lock(&ubi->cmt_lock);
+
+ /* Check commit progress status */
+ if (ubi->c_status != C_FREE) {
+ spin_unlock(&ubi->cmt_lock);
+ return -EBUSY;
+ }
+
+ /* Set commit running status to C_STARTED */
+ ubi->c_status = C_STARTED;
+ spin_unlock(&ubi->cmt_lock);
+
+ err = cmt_start(ubi);
+ if (err) {
+ ubi_err("commit start failed");
+ goto ro_mode;
+ }
+ spin_lock(&ubi->cmt_lock);
+ ubi->c_status = C_WRITING;
+ spin_unlock(&ubi->cmt_lock);
+
+ err = cmt_write(ubi);
+ if (err) {
+ ubi_err("ubi commit write error ");
+ goto ro_mode;
+ }
+
+ spin_lock(&ubi->cmt_lock);
+ ubi->c_status = C_FINISHED;
+ spin_unlock(&ubi->cmt_lock);
+
+ /* Close commit write operation */
+ err = cmt_end(ubi);
+ if (err) {
+ ubi_err("ubi commit end error");
+ goto ro_mode;
+ }
+
+ spin_lock(&ubi->cmt_lock);
+ ubi->c_status = C_FREE;
+ ubi->c_dirty = C_CLEAN;
+ spin_unlock(&ubi->cmt_lock);
+ return err;
+ro_mode:
+ spin_lock(&ubi->cmt_lock);
+ ubi->c_status = C_FAILED;
+ spin_unlock(&ubi->cmt_lock);
+ ubi_err("commit failed");
+ ubi_ro_mode(ubi);
+ return err;
+}
+
+/**
+ * cmt_put_resvd_peb - Put buffer pebs in case of failures
+ * @ubi_device: UBI device description object
+ *
+ * In case of partial commit write, we read commit from old commit copy.
+ * But in partial commit, buffer pebs gathered for special blocks must be put
+ * back. During attach process, we check previous commit status and if it is
+ * invalid using this function we mark buffer pebs as erase pending. In scan
+ * process, these erase pending blocks gets handled accordingly.
+ *
+ * In this function, we check sb for buffer pebs. If it contain valid
+ * number then er mark these pebs as erase pending. After marking them as
+ * erase pending commit status is set as valid and do sb sync operation.
+ * This function return zero in success and negative error code in case
+ * of failures.
+ */
+
+int ubi_cmt_put_resvd_peb(struct ubi_device *ubi)
+{
+ int err, *buffer_peb, pnum, peb_count;
+ struct ubi_sb *sb;
+
+ dbg_cmt("putting buffer pebs");
+
+ /* Get sb */
+ sb = ubi_sb_get_node(ubi);
+ buffer_peb = sb->buds + be32_to_cpu(sb->cmt_next_buds);
+
+ peb_count = ubi->c_reservd_buds * UBIL_CMT_COPIES;
+ peb_count += ubi->el_reservd_buds;;
+
+ while (peb_count > 0) {
+ pnum = be32_to_cpu(*buffer_peb);
+ /* Check pnum is not equal to -1 and greater than 0
+ * We have sb on 0 so pnum should be greater than 0.
+ */
+ if (pnum != -1 && pnum > 0)
+ ubi->peb_lookup[pnum].status = UBIL_PEB_ERASE_PENDING;
+
+ *buffer_peb = cpu_to_be32(-1);
+ buffer_peb = buffer_peb + 1;
+ peb_count--;
+ }
+ /* Set commit status */
+ sb->cmt_status = cpu_to_be32(UBIL_CMT_WRITE_SUCCESS) ;
+ /* Write sb */
+ err = ubi_sb_sync_node(ubi);
+ if (err) {
+ ubi_err("sb sync error");
+ ubi_sb_put_node(ubi);
+ return err;
+ }
+ /* Release sb */
+ ubi_sb_put_node(ubi);
+ return 0;
+}
+
+#endif
More information about the linux-mtd
mailing list