lists.openwall.net | lists / announce owl-users owl-dev john-users john-dev passwdqc-users yescrypt popa3d-users / oss-security kernel-hardening musl sabotage tlsify passwords / crypt-dev xvendor / Bugtraq Full-Disclosure linux-kernel linux-netdev linux-ext4 linux-hardening linux-cve-announce PHC | |
Open Source and information security mailing list archives
| ||
|
Date: Tue, 2 Feb 2016 02:30:51 +0000 From: Bean Huo <jackyard88@...il.com> To: richard@....at, dedekind1@...il.com, adrian.hunter@...el.com, computersforpeace@...il.com, boris.brezillon@...e-electrons.com Cc: beanhuo@...ron.com, linux-mtd@...ts.infradead.org, linux-kernel@...r.kernel.org, zszubbocsev@...ron.com, peterpandong@...ron.com Subject: [PATCH v2 16/17] driver:mtd:ubi:add new bakvol module in ubi layer From: Bean Huo <beanhuo@...ron.com> Bakvol(bakckup volume) module is for MLC NAND paired page power loss issue. This patch file is to add new file in mtd/ubi folder. Signed-off-by: BeanHuo <beanhuo@...ron.com> --- drivers/mtd/ubi/bakvol.c | 1296 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1296 insertions(+) create mode 100644 drivers/mtd/ubi/bakvol.c diff --git a/drivers/mtd/ubi/bakvol.c b/drivers/mtd/ubi/bakvol.c new file mode 100644 index 0000000..a6b1a31 --- /dev/null +++ b/drivers/mtd/ubi/bakvol.c @@ -0,0 +1,1296 @@ +/* This version ported to the Linux-UBI system by Micron + * + * Based on: Micorn APPARATUSES AND METHODS FOR MEMORY MANAGEMENT + * + */ + +/*=========================================== + A UBI solution for MLC NAND powerloss + + This driver implements backup lower page to an UBI internal bakvol volume. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); You may obtain a copy of + the License at http://www.mozilla.org/MPL/.But you can use this + file, no matter in compliance with the License or not. + + The initial developer of the original code is Beanhuo + <beanhuo@...orn.com>. Portions created by Micorn. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. +===========================================*/ +#include <asm/div64.h> + +#include <linux/crc32.h> +#include <linux/err.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> + +#include "ubi.h" + +#ifdef CONFIG_MTD_UBI_MLC_NAND_BAKVOL + +/* Global point for dual plane programming operation */ +struct mtd_oob_ops *oob_ops_bak, *oob_ops_src; +struct mtd_oob_ops *oob_ops; /* Global read/write ops point */ + +int Recovery_done; /* 1: bakvol recovery already done. 0: need to do recovery */ + +/* Temporary variables used during scanning PEB */ +struct ubi_ec_hdr *ech; +struct ubi_vid_hdr *vidh; + +/* Which generation of Micron NAND device will be used */ +#define MICRON_NAND_VERSION_L7X_CONFIG 0 +#define MICRON_NAND_VERSION_L8X_CONFIG 1 + +/** + * is_lowerpage - check page belongs to lower or uppoer page. + * For the detail algorithm, please refer to Micron + * product datasheet "Shared Pages". + * @page_num: physical page number within PEB + * + * if paeg_num is lower page, return 1 + * if page_num is upper page, return 0 + * if page_num is SLC page, return -1 + */ +static int is_lowerpage(int page_num) +{ + int ret; +#if MICRON_NAND_VERSION_L8X_CONFIG + /* used for Micron L8x series parallel nand */ + switch (page_num) { + case 2: + case 3: + case 248: + case 249: + case 252: + case 253: + case 254: + case 255: + ret = -1;/* This page belongs to SLC page. */ + break; + default: + if (page_num % 4 < 2) { + /* If remainder is 2,3 ,this page belongs to lower page, + * else if 0,1, it belongs to upper page */ + ret = 1; /* Lower page */ + } else { + ret = 0; + } + break; + } +#elif MICRON_NAND_VERSION_L7X_CONFIG + /* Used for Micron L7X series parallel NAND */ + switch (page_num) { + case 0: + case 1: + ret = 1; + break; + case 4: + case 5: + case 254: + case 255: /* 4,5,254, 255 is upper page */ + ret = 0; + break; + default: + if (page_num % 4 > 1) + /* 2 ,3 is lower page, 0,1 is upper page */ + ret = 1; /* Lower page */ + else + ret = 0; + break; + } +#endif + return ret; +} + +/** + * get_next_lowerpage - return next lower page number. + * @page: physical page number within PEB + * + * This function returns corresponding next lower page nubmer, + * if exist lower page; return -1, if this page is already last + * page or no lower page after this page. + * Note: For the detail algorithm, please refer to Micron product + * datasheet "Shared Pages". + */ +static int get_next_lowerpage(int page) +{ + int ret = -1; +#if MICRON_NAND_VERSION_L8X_CONFIG + if (page == 254) + ret = -1; + if (page >= 255) + return -1; + /* Used for L8x Micron series NAND */ + if (page % 4 < 2) { + if (page % 2 == 0) + ret = page + 1; + else { + if ((page == 1) || (page == 3)) + ret = page + 1; + else + ret = page + 3; + } + + } else { + if (page % 2 == 0) + ret = page + 2; + else + ret = page + 1; + } + + /* Skip SLC page */ + switch (ret) { + case 2: + case 3: + ret = 4; + break; + case 248: + case 249: + case 252: + case 253: + case 254: + case 255: + ret = -1; + break; + default: + break; + } +#elif MICRON_NAND_VERSION_L7X_CONFIG + /* Used for Micron L7X series NAND */ + switch (page) { + case 0: + case 1: + case 2: + ret = page + 1; + break; + case 4: + case 5: + ret = page + 2; + break; + case 254: + case 255: /* 254, 255 is upper page */ + ret = -1; + break; + default: + if (page % 4 > 1) { + if (page % 2 == 0) + ret = page + 1; + else + ret = page + 3; + } else { + if (page % 2 == 0) + ret = page + 2; + else + ret = page + 1; + + } + break; + } +#endif + return ret; +} + +/** + * get_oppo_plane_seq - return opposite plane number according to PEB number + * + * + */ +static int get_oppo_plane_seq(struct ubi_device *ubi, loff_t addr) +{ + int peb; + + peb = (int)(addr >> ubi->mtd->erasesize_shift); + + if (peb % 2) + return 0; /* plane 0 */ + else + return 1; /* plane 1 */ +} + +/** + * check_original_data - check original data whether being corrupted. + * @ubi: UBI device description object + * @bak_addr: address of backup page + * if source block not erased, user oob data + * should equal to bak_addr. + * @src_addr: source page address + * @datbuf: point of backup data + */ +static int check_original_data(struct ubi_device *ubi, loff_t bak_addr, + loff_t src_addr, uint8_t *datbuf) +{ + int i; + loff_t read_addr; + struct bakvol_oob_info *oob_info; + void *buf = datbuf; + int ret = -1; + + struct mtd_oob_ops *ops = kmalloc(sizeof(struct mtd_oob_ops), + GFP_KERNEL); + + if (!ops) { + ubi_err(ubi, "[%d]Error,kmalloc error!\n", __LINE__); + goto out; + } + + ops->datbuf = kmalloc(ubi->min_io_size, GFP_KERNEL); + ops->oobbuf = kmalloc(ubi->mtd->oobsize, GFP_KERNEL); + if (!ops->datbuf || !ops->oobbuf) { + ubi_err(ubi, "[%d]Error,kmalloc error!\n", __LINE__); + goto free; + } + + ops->mode = MTD_OPS_AUTO_OOB; + ops->ooblen = UBI_BAKVOL_OOB_SIZE; + ops->len = ubi->min_io_size; + ops->ooboffs = 0; + ops->retlen = 0; + ops->oobretlen = 0; + + dbg_gen("%s:Source page addr = 0x%llx\n", __func__, src_addr); + + ret = ubi->mtd->_read_oob(ubi->mtd, src_addr, ops); + + if (ret < 0) { + /* + * Read error, means this page being corrupted, + * need to recover. + */ + ret = 1; + goto free; + } + + oob_info = (struct bakvol_oob_info *)ops->oobbuf; + read_addr = be64_to_cpu(oob_info->addr); + if (read_addr != bak_addr) { + /* + * source page oobbuf != bak_addr, means this PEB already + * being erased or re-programmed. what's more, exsit bitflips + * in user oob area, anyway, current backup page data + * already is not corresponding source page data. + */ + dbg_gen("[%d] backup page address not match.\n", __LINE__); + ret = 0; + goto free; + } + + for (i = 0; i < ubi->min_io_size; i += 4) { + if (*(u32 *)ops->datbuf != *(u32 *)buf) { + ret = 1; + goto free; + } + } + ret = 0; + dbg_gen("Original data not being corrupted.\n"); + +free: + kfree(ops->oobbuf); + kfree(ops->datbuf); + kfree(ops); +out: + return ret; + +} +/** + * find_last_programmed_page - find last page being programmed. + * @ubi: UBI device description object + * @pnum: PEB number + * @ret_page: return last programmed page number + */ +static int find_last_programmed_page(struct ubi_device *ubi, + int pnum, int *ret_page) +{ + struct mtd_info *mtd = ubi->mtd; + int page, start_page, err; + + oob_ops->mode = MTD_OPS_AUTO_OOB; + oob_ops->ooblen = UBI_BAKVOL_OOB_SIZE; + oob_ops->len = ubi->min_io_size; + oob_ops->ooboffs = 0; + oob_ops->retlen = 0; + oob_ops->oobretlen = 0; + + page = (mtd->erasesize - 1) >> mtd->writesize_shift; + start_page = ubi->leb_start >> mtd->writesize_shift; + for (; page >= start_page; page--) { + err = ubi->mtd->_read_oob(ubi->mtd, + (pnum << mtd->erasesize_shift) | + (page << mtd->writesize_shift), oob_ops); + + if (err < 0 && err != -EUCLEAN) { + *ret_page = page; + return -1; + } + + if (!ubi_check_pattern(oob_ops->oobbuf, 0xFF, + oob_ops->ooblen)) { + *ret_page = page; + return 1; + } + } + return 0; +} + +/** + * get_peb_from_bakvol - get a PEB that already being opened by bakvol. + * @ubi: UBI device description object + * @plane: indicates this PEB should belong to which plane + * @page_num: indicates which page should not be programmed + */ +static struct ubi_bkblk_info *get_peb_from_bakvol(struct ubi_device *ubi, + char plane, int page_num) +{ + struct ubi_bkblk_tbl *backup_info = ubi->bkblk_tbl; + struct list_head *head = &backup_info->head; + struct ubi_bkblk_info *bbi, *tempbbi = NULL; + int page; + + if (list_empty(head)) { + dbg_gen("%s:bakvol no already opened PEB.\n", __func__); + return NULL; + } + + list_for_each_entry(bbi, head, node) { + if (bbi->plane == plane) { + page = get_next_lowerpage(bbi->pgnum); + if (page == -1) + continue; + if (page == page_num) { + /* + * Perfecct match + */ + tempbbi = bbi; + break; + } else if (page < page_num) { + /* + * Make sure this page not be programmed. + */ + if (tempbbi == NULL) + tempbbi = bbi; + else { + if (tempbbi->pgnum > bbi->pgnum) + /* + * As far as possible choose + * the PEB with smallest page + * number. + */ + tempbbi = bbi; + } + } + } + } + + if (tempbbi) + dbg_gen("Get bakvol PEB %d:%d for original lower page %d.\n", + tempbbi->peb, tempbbi->pgnum, page_num); + else + dbg_gen("Cannot get free bakvol PEB for plane %d:page %d.\n", + plane, page_num); + + return tempbbi; +} + +static void prepare_bakvol_oob_info(loff_t *addr, + struct bakvol_oob_info *bakvol_oob) +{ + uint32_t crc; + + ubi_assert(addr != NULL && bakvol_oob != NULL); + + bakvol_oob->addr = cpu_to_be64(*(loff_t *)addr); + crc = crc32(UBI_CRC32_INIT, bakvol_oob, UBI_BAKVOL_OOB_SIZE_CRC); + bakvol_oob->crc = cpu_to_be32(crc); +} + +/** + * validate_bakvol_oob_info- validate bakvol oob user info. + * @ubi: UBI device description object + * @oob_info: bakvol oob info + * + * This function returns zero if bakvol oob user info is valid, + * and return 1 if not. + */ +static int validate_bakvol_oob_info(struct ubi_device *ubi, + const struct bakvol_oob_info *oob_info) +{ + uint32_t crc, read_crc; + int ret = 0; + + crc = crc32(UBI_CRC32_INIT, oob_info, UBI_BAKVOL_OOB_SIZE_CRC); + read_crc = be32_to_cpu(oob_info->crc); + if (read_crc != crc) { + ubi_err(ubi, "OOB info CRC error,calculate 0x%x, read 0x%0x", + crc, read_crc); + ret = 1; + } + + return ret; +} + +static struct +ubi_bkblk_info *find_min_free_space_peb(struct ubi_bkblk_tbl *backup_info) +{ + struct list_head *head = &backup_info->head; + struct ubi_bkblk_info *bbi, *retbbi; + + retbbi = list_first_entry(head, struct ubi_bkblk_info, node); + + list_for_each_entry(bbi, head, node) { + if (bbi->pgnum > retbbi->pgnum) + retbbi = bbi; + } + + return retbbi; +} + +static struct +ubi_bkblk_info *allo_new_block_for_bakvol(struct ubi_device *ubi, u8 plane) +{ + + struct ubi_vid_hdr *vid_hdr; + struct ubi_volume *vol; + struct ubi_bkblk_tbl *backup_info = ubi->bkblk_tbl; + struct ubi_bkblk_info *bbin, *newbbin; + int bc_plane0, bc_plane1; + int pnum; + int err; + int tries = 1; + + vol = ubi->volumes[vol_id2idx(ubi, UBI_BACKUP_VOLUME_ID)]; + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return NULL; + + newbbin = kmalloc(sizeof(*newbbin), GFP_ATOMIC); + if (!newbbin) { + err = -ENOMEM; + goto out; + } + + vid_hdr->vol_type = UBI_VID_DYNAMIC; + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + vid_hdr->vol_id = cpu_to_be32(UBI_BACKUP_VOLUME_ID); + vid_hdr->compat = UBI_BACKUP_VOLUME_COMPAT; + vid_hdr->data_pad = cpu_to_be32(0); + vid_hdr->data_size = vid_hdr->used_ebs = vid_hdr->data_pad; + +retry: + pnum = ubi_wl_get_plane_peb(ubi, plane); + + if (pnum < 0) { + err = -ENOMEM; + goto free_mem; + } + + if (pnum%2 != plane) { + err = pnum; + goto free_mem; + } + + bc_plane0 = backup_info->bcount_of_plane[0]; + bc_plane1 = backup_info->bcount_of_plane[1]; + + if (bc_plane0 + bc_plane1 >= vol->reserved_pebs) { + /** + * we make sure bakvol opened PEBs cannot overflow + * bakvol reserved PEB. + */ + dbg_gen("bakvol opened PEB over reserved PEB.\n"); + dbg_gen("Will put PEB with minimum free space.\n"); + bbin = find_min_free_space_peb(backup_info); + + if (vol->eba_tbl[bbin->leb] >= 0) { + dbg_gen("bakvol put PEB %d.\n", bbin->peb); + err = ubi_wl_put_peb(ubi, vol->vol_id, bbin->leb, + bbin->peb, 0); + if (err) + goto free_mem; + } + + vol->eba_tbl[bbin->leb] = UBI_LEB_UNMAPPED; + backup_info->bcount_of_plane[bbin->plane]--; + newbbin->leb = bbin->leb; + + list_del(&bbin->node); + + } else { + int i = 0; + + for (i = 0; i < vol->reserved_pebs; i++) + if (vol->eba_tbl[i] == UBI_LEB_UNMAPPED) { + newbbin->leb = i; + break; + } + } + + vid_hdr->lnum = cpu_to_be32(newbbin->leb); + if ((newbbin->leb > vol->reserved_pebs) || (newbbin->leb < 0)) { + ubi_err(ubi, "BUG: logic block number [%d] error.\n", newbbin->leb); + panic("BUG!"); + } + + err = ubi_io_write_vid_hdr(ubi, pnum, vid_hdr); + if (err) { + ubi_err(ubi, "Failed to write VID header to PEB %d.\n", pnum); + goto write_error; + } + + vol->eba_tbl[newbbin->leb] = pnum; + + newbbin->peb = pnum; + newbbin->plane = plane; + newbbin->pgnum = get_next_lowerpage(1); /* Skip page0 and page1 */ + + /* Update bakvol variable */ + backup_info->bcount_of_plane[plane]++; + list_add(&newbbin->node, &backup_info->head); + +out: + ubi_free_vid_hdr(ubi, vid_hdr); + return newbbin; + +write_error: + /* + * Bad luck? This physical eraseblock is bad too? Let's try to + * get another one. + */ + ubi_err(ubi, "Failed to write to PEB %d", pnum); + ubi_wl_put_peb(ubi, vol->vol_id, newbbin->leb, newbbin->peb, 1); + if (++tries > UBI_IO_RETRIES) + goto free_mem; + + ubi_err(ubi, "Try again"); + goto retry; + +free_mem: + ubi_free_vid_hdr(ubi, vid_hdr); + kfree(newbbin); + ubi_err(ubi, "%s:%d %d times failed to alloc a new block!", + __func__, __LINE__, tries); + return NULL; +} + +/** + * is_backup_need -check if this page date need to be backup. + * @ubi: UBI device description object + * @addr: page address + * + * return 0, this page data shouldn't backup + * return 1, this page data should backup + */ +int is_backup_need(struct ubi_device *ubi, loff_t addr) +{ + int page_num, ret; + + page_num = (addr & ubi->mtd->erasesize_mask) >> + ubi->mtd->writesize_shift; + if (page_num == 0 || page_num == 1) + /* Currently,we don't backup EC head and VID head */ + return 0; + + ret = is_lowerpage(page_num); + if (ret != 1) { + dbg_gen("Page %d is %s page.\n", page_num, + (ret == -1 ? "SLC" : "upper")); + ret = 0; + } else + dbg_gen("Page %d is lower page.\n", page_num); + + return ret; +} + +/** + * ubi_check_bakvol_module - check if bakvol has been buildup + * @ubi: UBI device description object + * + * returns 1 in case of backup volume have been built, + * 0 in case of don't find + */ +int ubi_check_bakvol_module(struct ubi_device *ubi) +{ + return (ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_ENABLE); +} + +/** + * ubi_duplicate_data_to_bakvol - dual plane program data into bakvol area + * and main area respectively. + * @ubi: UBI device description object + * @addr: address will be programmed + * @len: date length + * @retlen: already programmed data length + * @buf: data buffer, points to data that should be programmed + */ +int ubi_duplicate_data_to_bakvol(struct ubi_device *ubi, loff_t addr, + size_t len, size_t *retlen, const void *buf) +{ + struct ubi_bkblk_tbl *backup_info = ubi->bkblk_tbl; + struct mtd_info *mtd = ubi->mtd; + int err = 0, nobak = 0; + int pnum; + u8 oppe_plane; + loff_t lpage_addr; /* Lower page byte address */ + struct ubi_bkblk_info *pbk; + int page_num; + struct bakvol_oob_info *oob_bak = NULL, *oob_src = NULL; + + if (len > ubi->min_io_size) { + ubi_err(ubi, "%d: Write data len overflow [%d]\n", + __LINE__, len); + return -EROFS; + } + + if (!buf) { + ubi_err(ubi, "%d: Write buf is NULL!\n", __LINE__); + return -EROFS; + + } + oppe_plane = get_oppo_plane_seq(ubi, addr); + + page_num = (int)(addr & ubi->mtd->erasesize_mask) >> + ubi->mtd->writesize_shift; + pnum = (int)(addr >> ubi->mtd->erasesize_shift); + + oob_bak = kzalloc(UBI_BAKVOL_OOB_SIZE, GFP_KERNEL); + if (!oob_bak) { + err = -ENOMEM; + ubi_err(ubi, "%s:%d: kzalloc error.\n", + __func__, __LINE__); + goto free; + } + + oob_src = kzalloc(UBI_BAKVOL_OOB_SIZE, GFP_KERNEL); + if (!oob_src) { + err = -ENOMEM; + ubi_err(ubi, "%s:%d: kzalloc error.\n", + __func__, __LINE__); + goto free; + } + + if (backup_info->bcount_of_plane[oppe_plane] == 0) + pbk = NULL; + else + pbk = get_peb_from_bakvol(ubi, oppe_plane, page_num); + + if (pbk == NULL) { + dbg_gen("Allocate new PEB for Bakvol.\n"); + pbk = allo_new_block_for_bakvol(ubi, oppe_plane); + if (!pbk) { + ubi_err(ubi, "Allocate new PEB failed.\n"); + nobak = 1; + goto Only_source; + } + } + + /* + * Here original page address stores in user oob area of bakvol page, + * and corresponding its backup page address stores in user oob area of + * main data area page. + */ + lpage_addr = (pbk->peb << mtd->erasesize_shift) | + (page_num << mtd->writesize_shift); + + /* + * organize bakvol page ops + */ + oob_ops_bak->datbuf = (uint8_t *)buf; + oob_ops_bak->mode = MTD_OPS_AUTO_OOB; + oob_ops_bak->ooblen = UBI_BAKVOL_OOB_SIZE; + prepare_bakvol_oob_info(&addr, oob_bak); + oob_ops_bak->oobbuf = (uint8_t *)oob_bak; /* Original page addr */ + oob_ops_bak->len = len; + oob_ops_bak->ooboffs = 0; + oob_ops_bak->retlen = 0; + oob_ops_bak->oobretlen = 0; + +Only_source: + /* + * Organize main data area page ops + */ + oob_ops_src->datbuf = (uint8_t *)buf; + oob_ops_src->mode = MTD_OPS_AUTO_OOB; + oob_ops_src->ooblen = UBI_BAKVOL_OOB_SIZE; + prepare_bakvol_oob_info(&lpage_addr, oob_src); + oob_ops_src->oobbuf = (uint8_t *)oob_src; /* backup page addr */ + oob_ops_src->len = len; + oob_ops_src->ooboffs = 0; + oob_ops_src->retlen = 0; + oob_ops_src->oobretlen = 0; + + if (!nobak) + err = mtd_write_dual_plane_oob(ubi->mtd, lpage_addr, + oob_ops_bak, addr, oob_ops_src); + + if ((err == -EOPNOTSUPP) || (nobak == 1)) { + /* + * If dual plane program function not enable, or get + * bakvol peb failed, we only program original data to + * main data area. + */ + ubi_err(ubi, "Only program source data.\n"); + err = mtd_write_oob(ubi->mtd, addr, oob_ops_src); + goto out; + } + + pbk->pgnum = page_num; /* updata bakvol PEB programmed page info */ + +out: + if (err) + *retlen = 0; + else + *retlen = len; +free: + kfree(oob_bak); + kfree(oob_src); + return err; +} + + +int ubi_bakvol_module_init(struct ubi_device *ubi) +{ + int step; + + if ((ubi->mtd->type != MTD_MLCNANDFLASH) || + (ubi->mtd->oobavail < UBI_BAKVOL_OOB_SIZE)) { + ubi_err(ubi, "NAND cann't meet Bakvol module requirement.\n"); + step = 0; + goto out_init; + } + + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); + if (!ech) { + step = 1; + goto out_init; + } + + vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vidh) { + step = 2; + goto out_ech; + } + + ubi->bkblk_tbl = kzalloc(sizeof(struct ubi_bkblk_tbl), GFP_KERNEL); + if (!ubi->bkblk_tbl) { + step = 3; + goto out_vidh; + } + ubi->bkblk_tbl->bcount_of_plane[0] = 0; + ubi->bkblk_tbl->bcount_of_plane[1] = 0; + INIT_LIST_HEAD(&ubi->bkblk_tbl->head); + + oob_ops_bak = kzalloc(sizeof(struct mtd_oob_ops), GFP_KERNEL); + if (!oob_ops_bak) { + step = 4; + goto out_backup_info; + } + + oob_ops_bak->oobbuf = kzalloc(ubi->mtd->oobsize, GFP_KERNEL); + if (!oob_ops_bak->oobbuf) { + step = 5; + goto out_back_oob_ops; + } + + oob_ops_src = kzalloc(sizeof(struct mtd_oob_ops), GFP_KERNEL); + if (!oob_ops_src) { + step = 6; + goto out_obs_src; + } + + oob_ops_src->oobbuf = kzalloc(ubi->mtd->oobsize, GFP_KERNEL); + if (!oob_ops_src->oobbuf) { + step = 7; + goto out_oob_ops; + } + + oob_ops = kmalloc(sizeof(struct mtd_oob_ops), GFP_KERNEL); + if (!oob_ops) { + step = 8; + goto out0; + } + + oob_ops->datbuf = kmalloc(ubi->min_io_size, GFP_KERNEL); + if (!oob_ops->datbuf) { + step = 9; + goto out1; + } + + oob_ops->oobbuf = kmalloc(ubi->mtd->oobsize, GFP_KERNEL); + if (!oob_ops->oobbuf) { + step = 10; + goto out2; + } + + ubi_free_vid_hdr(ubi, vidh); + kfree(ech); + + ubi->bkblk_tbl->bakvol_flag = UBI_BAKVOL_INIT_START; + return 0; + +out2: + kfree(oob_ops->datbuf); +out1: + kfree(oob_ops); +out0: + kfree(oob_ops_src->oobbuf); +out_oob_ops: + kfree(oob_ops_src); +out_obs_src: + kfree(oob_ops_bak->oobbuf); +out_back_oob_ops: + kfree(oob_ops_bak); +out_backup_info: + kfree(ubi->bkblk_tbl); +out_vidh: + ubi_free_vid_hdr(ubi, vidh); +out_ech: + kfree(ech); +out_init: + ubi_err(ubi, "bakvol module init error,init step [%d].\n", step); + ubi->bkblk_tbl->bakvol_flag = UBI_BAKVOL_REJECT; + return -1; +} + +/** + * ubi_bakvol_peb_scan - check VID header,see if this PEB + * belongs to bakvol + * + * This function returns 1 if this PEB not belongs to bakvol + * return -1 in case of allocate resource error + * return 0 if scan complete, and it belongs to bakvol + */ +int ubi_bakvol_peb_scan(struct ubi_device *ubi, struct ubi_vid_hdr *vidh, + int pnum) +{ + int page; + int vol_id; + struct ubi_bkblk_info *bbi; + int lnum; + int ret; + + page = 0; + + vol_id = be32_to_cpu(vidh->vol_id); + if (vol_id == UBI_BACKUP_VOLUME_ID) { + /* Found bakvol PEB */ + lnum = be32_to_cpu(vidh->lnum); + dbg_gen("%s:Found backup PEB,pnum is [%d],lnum is[%d].\n", + __func__, pnum, lnum); + + /* Unsupported internal volume */ + if (vidh->compat != UBI_COMPAT_REJECT) { + ubi_err(ubi, "Backup volume compat != UBI_COMPAT_REJECT\n"); + return -1; + } + } else { + return 1; + } + + ret = find_last_programmed_page(ubi, pnum, &page); + dbg_gen("bakvol PEB %d last programmed page [%d]\n", pnum, page); + + if (ret == 1) { + bbi = kmalloc(sizeof(*bbi), GFP_ATOMIC); + if (!bbi) + goto out; + + bbi->peb = pnum; + bbi->leb = lnum; + bbi->pgnum = page; + bbi->plane = pnum % 2; + bbi->plane ? (ubi->bkblk_tbl->bcount_of_plane[1]++) : + (ubi->bkblk_tbl->bcount_of_plane[0]++); + list_add(&bbi->node, &ubi->bkblk_tbl->head); + } else if (ret == 0) { + /* This backup PEB has not been programmed. */ + bbi = kmalloc(sizeof(*bbi), GFP_ATOMIC); + if (!bbi) + goto out; + bbi->peb = pnum; + bbi->leb = lnum; + bbi->pgnum = get_next_lowerpage(1); + bbi->plane = pnum % 2; + bbi->plane ? (ubi->bkblk_tbl->bcount_of_plane[1]++) : + (ubi->bkblk_tbl->bcount_of_plane[0]++); + list_add(&bbi->node, &ubi->bkblk_tbl->head); + } else { + /* Backup block is corrupted,maybe power cut happened during + * programming lower page. so this backup block with corrupted + * page can be removed from bakvol volume. + */ + dbg_gen("PEB %d will be removed from backup volume later.\n", + pnum); + + bbi = kmalloc(sizeof(*bbi), GFP_ATOMIC); + if (!bbi) + goto out; + bbi->peb = pnum; + bbi->leb = lnum; + bbi->pgnum = page; + bbi->plane = pnum % 2; + bbi->plane ? (ubi->bkblk_tbl->bcount_of_plane[1]++) : + (ubi->bkblk_tbl->bcount_of_plane[0]++); + list_add(&bbi->node, &ubi->bkblk_tbl->head); + } + + return 0; +out: + return -1; + +} + +/** + * ubi_bakvol_module_init_tail - init backup volume. + * @ubi: UBI device description object + * @si: scanning information + * + * This function init the backup volume + * Returns zero in case of success and a negative + * error code in case of failure + */ +int ubi_bakvol_module_init_tail(struct ubi_device *ubi, + struct ubi_attach_info *si) +{ + int reserved_pebs = 0; + struct ubi_volume *vol; + struct ubi_bkblk_info *bbi; + + if (ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_REJECT) + return 0; + + if (!(ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_INIT_DONE)) { + + /* And add the layout volume */ + vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL); + if (!vol) + return -ENOMEM; + + vol->reserved_pebs = UBI_BACKUP_VOLUME_EBS; + vol->alignment = 1; + vol->vol_type = UBI_DYNAMIC_VOLUME; + vol->name_len = sizeof(UBI_BACKUP_VOLUME_NAME) - 1; + vol->data_pad = 0; + memcpy(vol->name, UBI_BACKUP_VOLUME_NAME, vol->name_len + 1); + + vol->usable_leb_size = ubi->leb_size; + vol->used_ebs = vol->reserved_pebs; + vol->last_eb_bytes = vol->reserved_pebs; + vol->used_bytes = + (long long)vol->used_ebs * (ubi->leb_size - vol->data_pad); + vol->vol_id = UBI_BACKUP_VOLUME_ID; + vol->ref_count = UBI_BACKUP_VOLUME_EBS; + ubi->volumes[vol_id2idx(ubi, vol->vol_id)] = vol; + reserved_pebs += vol->reserved_pebs; + ubi->vol_count += 1; + vol->ubi = ubi; + if (reserved_pebs > ubi->avail_pebs) { + ubi_err(ubi, "No enough PEBs, required %d, available %d", + reserved_pebs, ubi->avail_pebs); + return -1; + } + ubi->rsvd_pebs += reserved_pebs; + ubi->avail_pebs -= reserved_pebs; + + ubi->bkblk_tbl->bakvol_flag = UBI_BAKVOL_INIT_DONE; + + ubi_msg(ubi, "bakvol module opened PEB list:"); + list_for_each_entry(bbi, &(ubi->bkblk_tbl->head), node) { + ubi_msg(ubi, "peb %d,pgnum %d,plane %d,;leb %d.", + bbi->peb, bbi->pgnum, bbi->plane, bbi->leb); + } + } + return 0; +} + +int ubi_corrupted_data_recovery(struct ubi_volume_desc *desc) +{ + u32 base_offset, buff_offset, pnum, offset, page; + u32 correct, uncorrect_l, uncorrect_h; + int ret, err; + struct ubi_device *ubi = desc->vol->ubi; + struct mtd_info *mtd = ubi->mtd; + struct ubi_bkblk_info *bbi; + unsigned char *buf = ubi->peb_buf; + struct ubi_volume_desc volume_desc; + int i, move; + loff_t bakpage_addr; + struct bakvol_oob_info *oob_info; + off_t src_addr; + + move = 0; + correct = 0; + uncorrect_h = 0; + uncorrect_l = 0; + + if ((ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_RECOVERY) || + !(ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_INIT_DONE)) + return 0; + + vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); + if (!vidh) + return -1; + + oob_ops->mode = MTD_OPS_AUTO_OOB; + oob_ops->ooblen = UBI_BAKVOL_OOB_SIZE; + oob_ops->len = ubi->min_io_size; + + list_for_each_entry(bbi, &(ubi->bkblk_tbl->head), node) { + dbg_gen("Processing bakvol PEB %d,pgnum %d,plane %d.\n", + bbi->peb, bbi->pgnum, bbi->plane); + i = get_next_lowerpage(1); + + for ( ; (i <= bbi->pgnum) && (i != -1); + i = get_next_lowerpage(i)) { + /* + * Read backup data from bakvol PEB with OOB. + */ + oob_ops->ooboffs = 0; + oob_ops->retlen = 0; + oob_ops->oobretlen = 0; + + err = ubi->mtd->_read_oob(ubi->mtd, + (bbi->peb << mtd->erasesize_shift) | + (i << mtd->writesize_shift), oob_ops); + + if (err < 0 && err != -EUCLEAN) { + dbg_gen("Read bakvol PEB %d:%d error %d.\n", + bbi->peb, i, err); + move = 1; + continue; + } + + if (ubi_check_pattern(oob_ops->oobbuf, 0xFF, + oob_ops->ooblen)) { + dbg_gen("Bakvol PEB %d ever skip lower page %d.\n", + bbi->peb, i); + continue; + } + + oob_info = (struct bakvol_oob_info *)oob_ops->oobbuf; + + if (validate_bakvol_oob_info(ubi, oob_info)) { + dbg_gen("Bakvol PEB %d page %d user oob area exist bitflips.\n", + bbi->peb, i); + continue; + } + + src_addr = be64_to_cpu(oob_info->addr); + + /* + * Calculate backup page address, used for check if source block + * already being erased or re-mapped. + */ + bakpage_addr = (bbi->peb << mtd->erasesize_shift) | + (i << mtd->writesize_shift); + + ret = check_original_data(ubi, bakpage_addr, + src_addr, oob_ops->datbuf); + if (ret == 1) { + /* The original data isn't correct anymore, + * already being damaged, and oobbuf store original page + * address. + */ + pnum = src_addr >> mtd->erasesize_shift; + offset = src_addr & mtd->erasesize_mask; + buff_offset = 0; + + /** + * Recover operation + */ + + for (base_offset = ubi->leb_start; + base_offset < ubi->peb_size; + base_offset += mtd->writesize) { + + ret = ubi_io_read(ubi, buf + buff_offset, pnum, + base_offset, mtd->writesize); + + if (ret == -EBADMSG || ret == -EIO) { + if (base_offset == offset) { + dbg_gen("Bakvol recover offset 0x%x.....\n", + base_offset); + memcpy(buf + buff_offset, oob_ops->datbuf, + mtd->writesize); + correct++; + } else { + page = base_offset >> mtd->writesize_shift; + if (is_lowerpage(page)) { + /* More low-page also + * be corrupted */ + uncorrect_l++; + ubi_err(ubi, "PEB %d unrecovery lower page %d\n", + pnum, page); + uncorrect_l++; + } else { + /** + * Unbackup upper page also + * be corruptted */ + ubi_err(ubi, "PEB %d unrecovery upper page %d.\n", + pnum, page); + uncorrect_h++; + } + } + } + + ret = ubi_check_pattern(buf + buff_offset, 0xFF, + mtd->writesize); + if (ret) + /* This page is empty, so the last of pages + * also are empty, no need to read.*/ + break; + + buff_offset += mtd->writesize; + } + + if ((correct == 0) || (uncorrect_h > 1) || (uncorrect_l > 0)) { + /* Could not recover. We only accept two corrupted + * pages, they are paired-page. If there are two + * corrupted lower pages within one PEB, also give + * up recovery. + */ + dbg_gen("Can not be recoverred.\n"); + uncorrect_l = 0; + uncorrect_h = 0; + continue; + } + + ret = ubi_io_read_vid_hdr(ubi, pnum, vidh, 0); + + volume_desc.vol = ubi->volumes[be32_to_cpu(vidh->vol_id)]; + volume_desc.mode = UBI_READWRITE; + ret = ubi_leb_change(&volume_desc, be32_to_cpu(vidh->lnum), + buf, buff_offset); + correct = 0; + uncorrect_h = 0; + + if (ret) { + ubi_err(ubi, "Changing %d bytes in LEB %d failed", + buff_offset, vidh->lnum); + ubi_err(ubi, "Ret error:%d.", ret); + dump_stack(); + } else + dbg_gen(".....Done\n"); + + } + + } + + if (move == 1) { + bbi->pgnum = (ubi->mtd->erasesize - 1) >> + ubi->mtd->writesize_shift; + move = 0; + } + } + ubi->bkblk_tbl->bakvol_flag |= UBI_BAKVOL_RECOVERY; + ubi_free_vid_hdr(ubi, vidh); + return 0; +} +EXPORT_SYMBOL_GPL(ubi_corrupted_data_recovery); + +void clear_bakvol(struct ubi_device *ubi) +{ + kfree(oob_ops->datbuf); + kfree(oob_ops); + kfree(oob_ops_src->oobbuf); + kfree(oob_ops_src); + kfree(oob_ops_bak->oobbuf); + kfree(oob_ops_bak); + kfree(ubi->bkblk_tbl); + ubi->bkblk_tbl->bakvol_flag = UBI_BAKVOL_INIT_START; +} + +void init_bakvol(struct ubi_volume_desc *desc, uint8_t choice) +{ + struct ubi_volume *vol = desc->vol; + struct ubi_device *ubi = vol->ubi; + + if (choice) { + if (ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_INIT_DONE || + ubi->bkblk_tbl->bakvol_flag & UBI_BAKVOL_DISABLE) { + dbg_gen("[%s][%d] Enable bakvol module successfully!\n", + __func__, __LINE__); + ubi->bkblk_tbl->bakvol_flag &= ~UBI_BAKVOL_DISABLE; + ubi->bkblk_tbl->bakvol_flag |= UBI_BAKVOL_ENABLE; + + } else + dbg_gen("[%s][%d] Enable bakvol module failed!\n", + __func__, __LINE__); + + } else { + ubi->bkblk_tbl->bakvol_flag &= ~UBI_BAKVOL_ENABLE; + ubi->bkblk_tbl->bakvol_flag |= UBI_BAKVOL_DISABLE; + dbg_gen("[%s][%d] Disable bakvol module!\n", + __func__, __LINE__); + } +} +EXPORT_SYMBOL_GPL(init_bakvol); +#else +int is_backup_need(struct ubi_device *ubi, loff_t addr) +{ + return 0; +} + +int ubi_check_bakvol_module(struct ubi_device *ubi) +{ + + return 0; +} + +int ubi_duplicate_data_to_bakvol(struct ubi_device *ubi, loff_t addr, + size_t len, size_t *retlen, const void *buf) +{ + return 0; +} +int ubi_bakvol_module_init(struct ubi_device *ubi) +{ + return 0; + +} + +int ubi_bakvol_peb_scan(struct ubi_device *ubi, struct ubi_vid_hdr *vidh, + int pnum) +{ + return 0; +} + +int ubi_bakvol_module_init_tail(struct ubi_device *ubi, + struct ubi_attach_info *si) +{ + return 0; +} + +int ubi_corrupted_data_recovery(struct ubi_volume_desc *desc) +{ + return 0; + +} +EXPORT_SYMBOL_GPL(ubi_corrupted_data_recovery); + + +void clear_bakvol(struct ubi_device *ubi) +{ + +} + +void init_bakvol(struct ubi_volume_desc *desc, uint8_t choice) +{ + +} +EXPORT_SYMBOL_GPL(init_bakvol); +#endif + +MODULE_LICENSE("Dual MPL/GPL"); +MODULE_AUTHOR("Bean Huo <beanhuo@...ron.com>"); +MODULE_DESCRIPTION("Support code for MLC NAND pair page powerloss protection"); -- 1.9.1
Powered by blists - more mailing lists