123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- /*
- * sharpslpart.c - MTD partition parser for NAND flash using the SHARP FTL
- * for logical addressing, as used on the PXA models of the SHARP SL Series.
- *
- * Copyright (C) 2017 Andrea Adami <andrea.adami@gmail.com>
- *
- * Based on SHARP GPL 2.4 sources:
- * http://support.ezaurus.com/developer/source/source_dl.asp
- * drivers/mtd/nand/sharp_sl_logical.c
- * linux/include/asm-arm/sharp_nand_logical.h
- *
- * Copyright (C) 2002 SHARP
- *
- * 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.
- *
- */
- #include <linux/kernel.h>
- #include <linux/slab.h>
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/bitops.h>
- #include <linux/sizes.h>
- #include <linux/mtd/mtd.h>
- #include <linux/mtd/partitions.h>
- /* oob structure */
- #define NAND_NOOB_LOGADDR_00 8
- #define NAND_NOOB_LOGADDR_01 9
- #define NAND_NOOB_LOGADDR_10 10
- #define NAND_NOOB_LOGADDR_11 11
- #define NAND_NOOB_LOGADDR_20 12
- #define NAND_NOOB_LOGADDR_21 13
- #define BLOCK_IS_RESERVED 0xffff
- #define BLOCK_UNMASK_COMPLEMENT 1
- /* factory defaults */
- #define SHARPSL_NAND_PARTS 3
- #define SHARPSL_FTL_PART_SIZE (7 * SZ_1M)
- #define SHARPSL_PARTINFO1_LADDR 0x00060000
- #define SHARPSL_PARTINFO2_LADDR 0x00064000
- #define BOOT_MAGIC 0x424f4f54
- #define FSRO_MAGIC 0x4653524f
- #define FSRW_MAGIC 0x46535257
- /**
- * struct sharpsl_ftl - Sharp FTL Logical Table
- * @logmax: number of logical blocks
- * @log2phy: the logical-to-physical table
- *
- * Structure containing the logical-to-physical translation table
- * used by the SHARP SL FTL.
- */
- struct sharpsl_ftl {
- unsigned int logmax;
- unsigned int *log2phy;
- };
- /* verify that the OOB bytes 8 to 15 are free and available for the FTL */
- static int sharpsl_nand_check_ooblayout(struct mtd_info *mtd)
- {
- u8 freebytes = 0;
- int section = 0;
- while (true) {
- struct mtd_oob_region oobfree = { };
- int ret, i;
- ret = mtd_ooblayout_free(mtd, section++, &oobfree);
- if (ret)
- break;
- if (!oobfree.length || oobfree.offset > 15 ||
- (oobfree.offset + oobfree.length) < 8)
- continue;
- i = oobfree.offset >= 8 ? oobfree.offset : 8;
- for (; i < oobfree.offset + oobfree.length && i < 16; i++)
- freebytes |= BIT(i - 8);
- if (freebytes == 0xff)
- return 0;
- }
- return -ENOTSUPP;
- }
- static int sharpsl_nand_read_oob(struct mtd_info *mtd, loff_t offs, u8 *buf)
- {
- struct mtd_oob_ops ops = { };
- int ret;
- ops.mode = MTD_OPS_PLACE_OOB;
- ops.ooblen = mtd->oobsize;
- ops.oobbuf = buf;
- ret = mtd_read_oob(mtd, offs, &ops);
- if (ret != 0 || mtd->oobsize != ops.oobretlen)
- return -1;
- return 0;
- }
- /*
- * The logical block number assigned to a physical block is stored in the OOB
- * of the first page, in 3 16-bit copies with the following layout:
- *
- * 01234567 89abcdef
- * -------- --------
- * ECC BB xyxyxy
- *
- * When reading we check that the first two copies agree.
- * In case of error, matching is tried using the following pairs.
- * Reserved values 0xffff mean the block is kept for wear leveling.
- *
- * 01234567 89abcdef
- * -------- --------
- * ECC BB xyxy oob[8]==oob[10] && oob[9]==oob[11] -> byte0=8 byte1=9
- * ECC BB xyxy oob[10]==oob[12] && oob[11]==oob[13] -> byte0=10 byte1=11
- * ECC BB xy xy oob[12]==oob[8] && oob[13]==oob[9] -> byte0=12 byte1=13
- */
- static int sharpsl_nand_get_logical_num(u8 *oob)
- {
- u16 us;
- int good0, good1;
- if (oob[NAND_NOOB_LOGADDR_00] == oob[NAND_NOOB_LOGADDR_10] &&
- oob[NAND_NOOB_LOGADDR_01] == oob[NAND_NOOB_LOGADDR_11]) {
- good0 = NAND_NOOB_LOGADDR_00;
- good1 = NAND_NOOB_LOGADDR_01;
- } else if (oob[NAND_NOOB_LOGADDR_10] == oob[NAND_NOOB_LOGADDR_20] &&
- oob[NAND_NOOB_LOGADDR_11] == oob[NAND_NOOB_LOGADDR_21]) {
- good0 = NAND_NOOB_LOGADDR_10;
- good1 = NAND_NOOB_LOGADDR_11;
- } else if (oob[NAND_NOOB_LOGADDR_20] == oob[NAND_NOOB_LOGADDR_00] &&
- oob[NAND_NOOB_LOGADDR_21] == oob[NAND_NOOB_LOGADDR_01]) {
- good0 = NAND_NOOB_LOGADDR_20;
- good1 = NAND_NOOB_LOGADDR_21;
- } else {
- return -EINVAL;
- }
- us = oob[good0] | oob[good1] << 8;
- /* parity check */
- if (hweight16(us) & BLOCK_UNMASK_COMPLEMENT)
- return -EINVAL;
- /* reserved */
- if (us == BLOCK_IS_RESERVED)
- return BLOCK_IS_RESERVED;
- return (us >> 1) & GENMASK(9, 0);
- }
- static int sharpsl_nand_init_ftl(struct mtd_info *mtd, struct sharpsl_ftl *ftl)
- {
- unsigned int block_num, phymax;
- int i, ret, log_num;
- loff_t block_adr;
- u8 *oob;
- oob = kzalloc(mtd->oobsize, GFP_KERNEL);
- if (!oob)
- return -ENOMEM;
- phymax = mtd_div_by_eb(SHARPSL_FTL_PART_SIZE, mtd);
- /* FTL reserves 5% of the blocks + 1 spare */
- ftl->logmax = ((phymax * 95) / 100) - 1;
- ftl->log2phy = kmalloc_array(ftl->logmax, sizeof(*ftl->log2phy),
- GFP_KERNEL);
- if (!ftl->log2phy) {
- ret = -ENOMEM;
- goto exit;
- }
- /* initialize ftl->log2phy */
- for (i = 0; i < ftl->logmax; i++)
- ftl->log2phy[i] = UINT_MAX;
- /* create physical-logical table */
- for (block_num = 0; block_num < phymax; block_num++) {
- block_adr = (loff_t)block_num * mtd->erasesize;
- if (mtd_block_isbad(mtd, block_adr))
- continue;
- if (sharpsl_nand_read_oob(mtd, block_adr, oob))
- continue;
- /* get logical block */
- log_num = sharpsl_nand_get_logical_num(oob);
- /* cut-off errors and skip the out-of-range values */
- if (log_num > 0 && log_num < ftl->logmax) {
- if (ftl->log2phy[log_num] == UINT_MAX)
- ftl->log2phy[log_num] = block_num;
- }
- }
- pr_info("Sharp SL FTL: %d blocks used (%d logical, %d reserved)\n",
- phymax, ftl->logmax, phymax - ftl->logmax);
- ret = 0;
- exit:
- kfree(oob);
- return ret;
- }
- static void sharpsl_nand_cleanup_ftl(struct sharpsl_ftl *ftl)
- {
- kfree(ftl->log2phy);
- }
- static int sharpsl_nand_read_laddr(struct mtd_info *mtd,
- loff_t from,
- size_t len,
- void *buf,
- struct sharpsl_ftl *ftl)
- {
- unsigned int log_num, final_log_num;
- unsigned int block_num;
- loff_t block_adr;
- loff_t block_ofs;
- size_t retlen;
- int err;
- log_num = mtd_div_by_eb((u32)from, mtd);
- final_log_num = mtd_div_by_eb(((u32)from + len - 1), mtd);
- if (len <= 0 || log_num >= ftl->logmax || final_log_num > log_num)
- return -EINVAL;
- block_num = ftl->log2phy[log_num];
- block_adr = (loff_t)block_num * mtd->erasesize;
- block_ofs = mtd_mod_by_eb((u32)from, mtd);
- err = mtd_read(mtd, block_adr + block_ofs, len, &retlen, buf);
- /* Ignore corrected ECC errors */
- if (mtd_is_bitflip(err))
- err = 0;
- if (!err && retlen != len)
- err = -EIO;
- if (err)
- pr_err("sharpslpart: error, read failed at %#llx\n",
- block_adr + block_ofs);
- return err;
- }
- /*
- * MTD Partition Parser
- *
- * Sample values read from SL-C860
- *
- * # cat /proc/mtd
- * dev: size erasesize name
- * mtd0: 006d0000 00020000 "Filesystem"
- * mtd1: 00700000 00004000 "smf"
- * mtd2: 03500000 00004000 "root"
- * mtd3: 04400000 00004000 "home"
- *
- * PARTITIONINFO1
- * 0x00060000: 00 00 00 00 00 00 70 00 42 4f 4f 54 00 00 00 00 ......p.BOOT....
- * 0x00060010: 00 00 70 00 00 00 c0 03 46 53 52 4f 00 00 00 00 ..p.....FSRO....
- * 0x00060020: 00 00 c0 03 00 00 00 04 46 53 52 57 00 00 00 00 ........FSRW....
- */
- struct sharpsl_nand_partinfo {
- __le32 start;
- __le32 end;
- __be32 magic;
- u32 reserved;
- };
- static int sharpsl_nand_read_partinfo(struct mtd_info *master,
- loff_t from,
- size_t len,
- struct sharpsl_nand_partinfo *buf,
- struct sharpsl_ftl *ftl)
- {
- int ret;
- ret = sharpsl_nand_read_laddr(master, from, len, buf, ftl);
- if (ret)
- return ret;
- /* check for magics */
- if (be32_to_cpu(buf[0].magic) != BOOT_MAGIC ||
- be32_to_cpu(buf[1].magic) != FSRO_MAGIC ||
- be32_to_cpu(buf[2].magic) != FSRW_MAGIC) {
- pr_err("sharpslpart: magic values mismatch\n");
- return -EINVAL;
- }
- /* fixup for hardcoded value 64 MiB (for older models) */
- buf[2].end = cpu_to_le32(master->size);
- /* extra sanity check */
- if (le32_to_cpu(buf[0].end) <= le32_to_cpu(buf[0].start) ||
- le32_to_cpu(buf[1].start) < le32_to_cpu(buf[0].end) ||
- le32_to_cpu(buf[1].end) <= le32_to_cpu(buf[1].start) ||
- le32_to_cpu(buf[2].start) < le32_to_cpu(buf[1].end) ||
- le32_to_cpu(buf[2].end) <= le32_to_cpu(buf[2].start)) {
- pr_err("sharpslpart: partition sizes mismatch\n");
- return -EINVAL;
- }
- return 0;
- }
- static int sharpsl_parse_mtd_partitions(struct mtd_info *master,
- const struct mtd_partition **pparts,
- struct mtd_part_parser_data *data)
- {
- struct sharpsl_ftl ftl;
- struct sharpsl_nand_partinfo buf[SHARPSL_NAND_PARTS];
- struct mtd_partition *sharpsl_nand_parts;
- int err;
- /* check that OOB bytes 8 to 15 used by the FTL are actually free */
- err = sharpsl_nand_check_ooblayout(master);
- if (err)
- return err;
- /* init logical mgmt (FTL) */
- err = sharpsl_nand_init_ftl(master, &ftl);
- if (err)
- return err;
- /* read and validate first partition table */
- pr_info("sharpslpart: try reading first partition table\n");
- err = sharpsl_nand_read_partinfo(master,
- SHARPSL_PARTINFO1_LADDR,
- sizeof(buf), buf, &ftl);
- if (err) {
- /* fallback: read second partition table */
- pr_warn("sharpslpart: first partition table is invalid, retry using the second\n");
- err = sharpsl_nand_read_partinfo(master,
- SHARPSL_PARTINFO2_LADDR,
- sizeof(buf), buf, &ftl);
- }
- /* cleanup logical mgmt (FTL) */
- sharpsl_nand_cleanup_ftl(&ftl);
- if (err) {
- pr_err("sharpslpart: both partition tables are invalid\n");
- return err;
- }
- sharpsl_nand_parts = kcalloc(SHARPSL_NAND_PARTS,
- sizeof(*sharpsl_nand_parts),
- GFP_KERNEL);
- if (!sharpsl_nand_parts)
- return -ENOMEM;
- /* original names */
- sharpsl_nand_parts[0].name = "smf";
- sharpsl_nand_parts[0].offset = le32_to_cpu(buf[0].start);
- sharpsl_nand_parts[0].size = le32_to_cpu(buf[0].end) -
- le32_to_cpu(buf[0].start);
- sharpsl_nand_parts[1].name = "root";
- sharpsl_nand_parts[1].offset = le32_to_cpu(buf[1].start);
- sharpsl_nand_parts[1].size = le32_to_cpu(buf[1].end) -
- le32_to_cpu(buf[1].start);
- sharpsl_nand_parts[2].name = "home";
- sharpsl_nand_parts[2].offset = le32_to_cpu(buf[2].start);
- sharpsl_nand_parts[2].size = le32_to_cpu(buf[2].end) -
- le32_to_cpu(buf[2].start);
- *pparts = sharpsl_nand_parts;
- return SHARPSL_NAND_PARTS;
- }
- static struct mtd_part_parser sharpsl_mtd_parser = {
- .parse_fn = sharpsl_parse_mtd_partitions,
- .name = "sharpslpart",
- };
- module_mtd_part_parser(sharpsl_mtd_parser);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Andrea Adami <andrea.adami@gmail.com>");
- MODULE_DESCRIPTION("MTD partitioning for NAND flash on Sharp SL Series");
|