lkml.org 
[lkml]   [2010]   [Jan]   [30]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH] MTD: Add new SmartMedia/xD FTL
    Date
    Drat... forgot to compile....
    This is an updated version
    (This time compile and run tested with xd card)
    ---
    This implements new readwrite SmartMedia/xd FTL.
    mtd driver must have support proper ECC and badblock vertification
    based on oob parts for 512 bytes nand.

    Also mtd driver must define read_oob and write_oob, which are used
    to read and write both data and oob together.

    Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
    ---
    drivers/mtd/Kconfig | 21 +
    drivers/mtd/Makefile | 1 +
    drivers/mtd/sm_ftl.c | 1258 ++++++++++++++++++++++++++++++++++++++++++++++++++
    drivers/mtd/sm_ftl.h | 92 ++++
    4 files changed, 1372 insertions(+), 0 deletions(-)
    create mode 100644 drivers/mtd/sm_ftl.c
    create mode 100644 drivers/mtd/sm_ftl.h
    diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
    index ecf90f5..0105ae0 100644
    --- a/drivers/mtd/Kconfig
    +++ b/drivers/mtd/Kconfig
    @@ -304,6 +304,27 @@ config SSFDC
    This enables read only access to SmartMedia formatted NAND
    flash. You can mount it with FAT file system.

    +
    +config SM_FTL
    + tristate "SmartMedia/xD new translation layer"
    + depends on EXPERIMENTAL && BLOCK
    + select MTD_BLKDEVS
    + help
    + This enables new and very EXPERMENTAL support for SmartMedia/xD
    + FTL (Flash tanslation layer)
    + Write support isn't yet well tested, therefore this code IS likely to
    + eat your card, so please don't use it together with valuable data.
    + Use readonly driver (CONFIG_SSFDC) instead.
    +
    +config SM_FTL_MUSEUM
    + boolean "Additional Support for 1MB and 2MB SmartMedia cards"
    + depends on SM_FTL && MTD_NAND
    + select MTD_NAND_ECC_SMC
    + help
    + Very old SmartMedia cards need ECC to be caluculated in the FTL
    + Such cards are very rare, thus enabling this option is mostly useless
    + Also this support is completely UNTESTED.
    +
    config MTD_OOPS
    tristate "Log panic/oops to an MTD buffer"
    depends on MTD
    diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
    index 82d1e4d..d53357b 100644
    --- a/drivers/mtd/Makefile
    +++ b/drivers/mtd/Makefile
    @@ -24,6 +24,7 @@ obj-$(CONFIG_NFTL) += nftl.o
    obj-$(CONFIG_INFTL) += inftl.o
    obj-$(CONFIG_RFD_FTL) += rfd_ftl.o
    obj-$(CONFIG_SSFDC) += ssfdc.o
    +obj-$(CONFIG_SM_FTL) += sm_ftl.o
    obj-$(CONFIG_MTD_OOPS) += mtdoops.o

    nftl-objs := nftlcore.o nftlmount.o
    diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c
    new file mode 100644
    index 0000000..87cc604
    --- /dev/null
    +++ b/drivers/mtd/sm_ftl.c
    @@ -0,0 +1,1258 @@
    +/*
    + * Copyright (C) 2009 - Maxim Levitsky
    + * SmartMedia/xD translation layer
    + *
    + * 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 <linux/kernel.h>
    +#include <linux/module.h>
    +#include <linux/random.h>
    +#include <linux/hdreg.h>
    +#include <linux/kthread.h>
    +#include <linux/freezer.h>
    +#include <linux/sysfs.h>
    +#include <linux/bitops.h>
    +#include "nand/sm_common.h"
    +#include "sm_ftl.h"
    +
    +#ifdef CONFIG_SM_FTL_MUSEUM
    +#include <linux/mtd/nand_ecc.h>
    +#endif
    +
    +
    +struct workqueue_struct *cache_flush_workqueue;
    +
    +static int cache_timeout = 1000;
    +module_param(cache_timeout, bool, S_IRUGO);
    +MODULE_PARM_DESC(cache_timeout, "Timeout in ms for cache flush (1000 default");
    +
    +/* ------------------- sysfs attributtes ---------------------------------- */
    +struct sm_sysfs_attribute {
    + struct device_attribute dev_attr;
    + char *data;
    + int len;
    +};
    +
    +ssize_t sm_attr_show(struct device *dev, struct device_attribute *attr,
    + char *buf)
    +{
    + struct sm_sysfs_attribute *sm_attr =
    + container_of(attr, struct sm_sysfs_attribute, dev_attr);
    +
    + strncpy(buf, sm_attr->data, sm_attr->len);
    + return sm_attr->len;
    +}
    +
    +
    +#define NUM_ATTRIBUTES 1
    +#define SM_CIS_VENDOR_OFFSET 0x59
    +struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl)
    +{
    + struct attribute_group *attr_group;
    + struct attribute **attributes;
    + struct sm_sysfs_attribute *vendor_attribute;
    +
    + int vendor_len = strnlen(ftl->cis_buffer + SM_CIS_VENDOR_OFFSET,
    + SM_SMALL_PAGE - SM_CIS_VENDOR_OFFSET);
    +
    + char *vendor = kmalloc(vendor_len, GFP_KERNEL);
    + memcpy(vendor, ftl->cis_buffer + SM_CIS_VENDOR_OFFSET, vendor_len);
    + vendor[vendor_len] = 0;
    +
    + /* Initialize sysfs attributes */
    + vendor_attribute =
    + kzalloc(sizeof(struct sm_sysfs_attribute), GFP_KERNEL);
    +
    + vendor_attribute->data = vendor;
    + vendor_attribute->len = vendor_len;
    + vendor_attribute->dev_attr.attr.name = "vendor";
    + vendor_attribute->dev_attr.attr.mode = S_IRUGO;
    + vendor_attribute->dev_attr.show = sm_attr_show;
    +
    +
    + /* Create array of pointers to the attributes */
    + attributes = kzalloc(sizeof(struct attribute *) * (NUM_ATTRIBUTES + 1),
    + GFP_KERNEL);
    + attributes[0] = &vendor_attribute->dev_attr.attr;
    +
    + /* Finally create the attribute group */
    + attr_group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL);
    + attr_group->attrs = attributes;
    + return attr_group;
    +}
    +
    +void sm_delete_sysfs_attributes(struct sm_ftl *ftl)
    +{
    + struct attribute **attributes = ftl->trans->disk_attributes->attrs;
    + int i;
    +
    + for (i = 0; attributes[i] ; i++) {
    +
    + struct device_attribute *dev_attr = container_of(attributes[i],
    + struct device_attribute, attr);
    +
    + struct sm_sysfs_attribute *sm_attr =
    + container_of(dev_attr,
    + struct sm_sysfs_attribute, dev_attr);
    +
    + kfree(sm_attr->data);
    + kfree(sm_attr);
    + }
    +
    + kfree(ftl->trans->disk_attributes->attrs);
    + kfree(ftl->trans->disk_attributes);
    +}
    +
    +
    +/* ----------------------- oob helpers -------------------------------------- */
    +
    +static int sm_get_lba(u8 *lba)
    +{
    + /* check fixed bits */
    + if ((lba[0] & 0xF8) != 0x10)
    + return -2;
    +
    + /* check parity - endianess doesn't matter */
    + if (hweight16(*(u16 *)lba) & 1)
    + return -2;
    +
    + return (lba[1] >> 1) | ((lba[0] & 0x07) << 7);
    +}
    +
    +
    +/*
    + * Read LBA asscociated with block
    + * returns -1, if block is erased
    + * returns -2 if error happens
    + */
    +static int sm_read_lba(struct sm_oob *oob)
    +{
    + static const u32 erased_pattern[4] = {
    + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
    +
    + u16 lba_test;
    + int lba;
    +
    + /* First test for erased block */
    + if (!memcmp(oob, erased_pattern, SM_OOB_SIZE))
    + return -1;
    +
    + /* Now check is both copies of the LBA differ too much */
    + lba_test = *(u16 *)oob->lba_copy1 ^ *(u16*)oob->lba_copy2;
    + if (lba_test && !is_power_of_2(lba_test))
    + return -2;
    +
    + /* And read it */
    + lba = sm_get_lba(oob->lba_copy1);
    +
    + if (lba == -2)
    + lba = sm_get_lba(oob->lba_copy2);
    +
    + return lba;
    +}
    +
    +static void sm_write_lba(struct sm_oob *oob, u16 lba)
    +{
    + u8 tmp[2];
    +
    + WARN_ON(lba >= 1000);
    +
    + tmp[0] = 0x10 | ((lba >> 7) & 0x07);
    + tmp[1] = (lba << 1) & 0xFF;
    +
    + if (hweight16(*(u16 *)tmp) & 0x01)
    + tmp[1] |= 1;
    +
    + oob->lba_copy1[0] = oob->lba_copy2[0] = tmp[0];
    + oob->lba_copy1[1] = oob->lba_copy2[1] = tmp[1];
    +}
    +
    +
    +/* Make offset from parts */
    +static loff_t sm_mkoffset(struct sm_ftl *ftl, int zone, int block, int boffset)
    +{
    + WARN_ON(boffset & (SM_SECTOR_SIZE - 1));
    + WARN_ON(zone < 0 || zone >= ftl->zone_count);
    + WARN_ON(block >= ftl->zone_size);
    + WARN_ON(boffset >= ftl->block_size);
    +
    + if (block == -1)
    + return -1;
    +
    + return (zone * SM_MAX_ZONE_SIZE + block) * ftl->block_size + boffset;
    +}
    +
    +/* Breaks offset into parts */
    +static void sm_break_offset(struct sm_ftl *ftl, loff_t offset,
    + int *zone, int *block, int *boffset)
    +{
    + *boffset = offset % ftl->block_size;
    + offset /= ftl->block_size;
    + *block = offset % ftl->max_lba;
    + offset /= ftl->max_lba;
    + *zone = offset >= ftl->zone_count ? -1 : offset;
    +}
    +
    +/* ---------------------- low level IO ------------------------------------- */
    +
    +static int sm_correct_sector(u8 *buffer, struct sm_oob *oob)
    +{
    +#ifdef CONFIG_SM_FTL_MUSEUM
    + u8 ecc[3];
    +
    + __nand_calculate_ecc(buffer, SM_SMALL_PAGE, ecc);
    + if (__nand_correct_data(buffer, ecc, oob->ecc1, SM_SMALL_PAGE) < 0)
    + return -EIO;
    +
    + buffer += SM_SMALL_PAGE;
    +
    + __nand_calculate_ecc(buffer, SM_SMALL_PAGE, ecc);
    + if (__nand_correct_data(buffer, ecc, oob->ecc2, SM_SMALL_PAGE) < 0)
    + return -EIO;
    +#endif
    + return 0;
    +}
    +
    +/* Reads a sector + oob*/
    +static int sm_read_sector(struct sm_ftl *ftl,
    + int zone, int block, int boffset,
    + u8 *buffer, struct sm_oob *oob)
    +{
    + struct mtd_info *mtd = ftl->trans->mtd;
    + struct mtd_oob_ops ops;
    + struct sm_oob tmp_oob;
    + int ret;
    + int try = 0;
    +
    + /* FTL can contain -1 entries that are by default filled with bits */
    + if (block == -1) {
    + memset(buffer, 0xFF, SM_SECTOR_SIZE);
    + return 0;
    + }
    +
    + /* User might not need the oob, but we do for data vertification */
    + if (!oob)
    + oob = &tmp_oob;
    +
    + ops.mode = ftl->smallpagenand ? MTD_OOB_RAW : MTD_OOB_PLACE;
    + ops.ooboffs = 0;
    + ops.ooblen = SM_OOB_SIZE;
    + ops.oobbuf = (void *)oob;
    + ops.len = SM_SECTOR_SIZE;
    + ops.datbuf = buffer;
    +
    +again:
    + /* Retrial mode */
    + if (try++) {
    + /* Avoid infinite recursion on CIS reads, sm_recheck_media
    + won't help anyway */
    + if (zone == 0 && block == ftl->cis_block && boffset ==
    + ftl->cis_boffset)
    + return ret;
    +
    + /* Test if media is stable */
    + if (try == 3 || sm_recheck_media(ftl))
    + return ret;
    + }
    +
    +
    + /* Unfortunelly, oob read will _always_ succeed,
    + despite card removal..... */
    + ret = mtd->read_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops);
    +
    + /* Test for unknown errors */
    + if (ret != 0 && ret != -EUCLEAN && ret != -EBADMSG) {
    + dbg("read of block %d at zone %d, failed due to error (%d)",
    + block, zone, ret);
    + goto again;
    + }
    +
    + /* Do a basic test on the oob, to guard against returned garbage */
    + if (oob->reserved != 0xFFFFFFFF)
    + goto again;
    +
    + /* This should never happen, unless there is a bug in the mtd driver */
    + WARN_ON(ops.oobretlen != SM_OOB_SIZE);
    + WARN_ON(buffer && ops.retlen != SM_SECTOR_SIZE);
    +
    + if (!buffer)
    + return 0;
    +
    + /* Test if sector marked as bad */
    + if (!sm_sector_valid(oob)) {
    + dbg("read of block %d at zone %d, failed because it is marked"
    + " as bad" , block, zone);
    + goto again;
    + }
    +
    + /* Test ECC*/
    + if (ret == -EBADMSG ||
    + (ftl->smallpagenand && sm_correct_sector(buffer, oob))) {
    +
    + dbg("read of block %d at zone %d, failed due to ECC error",
    + block, zone);
    + goto again;
    + }
    +
    + return 0;
    +}
    +
    +/* Writes a sector to media */
    +static int sm_write_sector(struct sm_ftl *ftl,
    + int zone, int block, int boffset,
    + u8 *buffer, struct sm_oob *oob)
    +{
    + struct mtd_oob_ops ops;
    + struct mtd_info *mtd = ftl->trans->mtd;
    + int ret;
    +
    + BUG_ON(ftl->readonly);
    +
    + if (zone == 0 && (block == ftl->cis_block || block == 0)) {
    + dbg("attempted to write the CIS!");
    + return -EIO;
    + }
    +
    + if (ftl->unstable)
    + return -EIO;
    +
    + ops.mode = ftl->smallpagenand ? MTD_OOB_RAW : MTD_OOB_PLACE;
    + ops.len = SM_SECTOR_SIZE;
    + ops.datbuf = buffer;
    + ops.ooboffs = 0;
    + ops.ooblen = SM_OOB_SIZE;
    + ops.oobbuf = (void *)oob;
    +
    + ret = mtd->write_oob(mtd, sm_mkoffset(ftl, zone, block, boffset), &ops);
    +
    + /* Now we assume that hardware will catch write bitflip errors */
    + /* If you are paranoid, use CONFIG_MTD_NAND_VERIFY_WRITE */
    +
    + if (ret) {
    + dbg("write to block %d at zone %d, failed with error %d",
    + block, zone, ret);
    +
    + sm_recheck_media(ftl);
    + return ret;
    + }
    +
    + /* This should never happen, unless there is a bug in the driver */
    + WARN_ON(ops.oobretlen != SM_OOB_SIZE);
    + WARN_ON(buffer && ops.retlen != SM_SECTOR_SIZE);
    +
    + return 0;
    +}
    +
    +/* ------------------------ block IO ------------------------------------- */
    +
    +/* Write a block using data and lba, and invalid sector bitmap */
    +static int sm_write_block(struct sm_ftl *ftl, u8 *buf,
    + int zone, int block, int lba,
    + unsigned long invalid_bitmap)
    +{
    + struct sm_oob oob;
    + int boffset;
    + int retry = 0;
    +
    + /* Initialize the oob with requested values */
    + memset(&oob, 0xFF, SM_OOB_SIZE);
    + sm_write_lba(&oob, lba);
    +restart:
    + for (boffset = 0; boffset < ftl->block_size;
    + boffset += SM_SECTOR_SIZE) {
    +
    + oob.data_status = 0xFF;
    +
    + if (test_bit(boffset / SM_SECTOR_SIZE, &invalid_bitmap)) {
    +
    + sm_printk("sector %d of block at LBA %d of zone %d"
    + " coudn't be read, marking it as invalid",
    + boffset / SM_SECTOR_SIZE, lba, zone);
    +
    + oob.data_status = 0;
    + }
    +
    +#ifdef CONFIG_SM_FTL_MUSEUM
    + if (ftl->smallpagenand) {
    + __nand_calculate_ecc(buf + boffset,
    + SM_SMALL_PAGE, oob.ecc1);
    +
    + __nand_calculate_ecc(buf + boffset + SM_SMALL_PAGE,
    + SM_SMALL_PAGE, oob.ecc2);
    + }
    +#endif
    + if (!sm_write_sector(ftl, zone, block, boffset,
    + buf + boffset, &oob))
    + continue;
    +
    + if (!retry) {
    +
    + /* If write fails. try to erase the block */
    + /* This is safe, because we never write in blocks
    + that contain valuable data.
    + This is intended to repair block that are marked
    + as erased, but that isn't fully erased*/
    +
    + if (sm_erase_block(ftl, zone, block, 0))
    + return -EIO;
    +
    + retry = 1;
    + goto restart;
    + } else {
    + sm_mark_block_bad(ftl, zone, block);
    + return -EIO;
    + }
    + }
    + return 0;
    +}
    +
    +
    +/* Mark whole block at offset 'offs' as bad. */
    +static void sm_mark_block_bad(struct sm_ftl *ftl, int zone, int block)
    +{
    + struct sm_oob oob;
    + int boffset;
    +
    + memset(&oob, 0xFF, SM_OOB_SIZE);
    + oob.block_status = 0xF0;
    +
    + if (ftl->unstable)
    + return;
    +
    + if (sm_recheck_media(ftl))
    + return;
    +
    + sm_printk("marking block %d of zone %d as bad", block, zone);
    +
    + /* We aren't checking the return value, because we don't care */
    + /* This also fails on fake xD cards, but I guess these won't expose
    + any bad blocks till fail completly */
    + for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE)
    + sm_write_sector(ftl, zone, block, boffset, NULL, &oob);
    +}
    +
    +/*
    + * Erase a block within a zone
    + * If erase succedes, it updates free block fifo, otherwise marks block as bad
    + */
    +static int sm_erase_block(struct sm_ftl *ftl, int zone_num, u16 block,
    + int put_free)
    +{
    + struct ftl_zone *zone = &ftl->zones[zone_num];
    + struct mtd_info *mtd = ftl->trans->mtd;
    + struct erase_info erase;
    +
    + erase.mtd = mtd;
    + erase.callback = sm_erase_callback;
    + erase.addr = sm_mkoffset(ftl, zone_num, block, 0);
    + erase.len = ftl->block_size;
    + erase.priv = (u_long)ftl;
    +
    + if (ftl->unstable)
    + return -EIO;
    +
    + BUG_ON(ftl->readonly);
    +
    + if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
    + sm_printk("attempted to erase the CIS!");
    + return -EIO;
    + }
    +
    + if (mtd->erase(mtd, &erase)) {
    + sm_printk("erase of block %d in zone %d failed",
    + block, zone_num);
    + goto error;
    + }
    +
    + if (erase.state == MTD_ERASE_PENDING)
    + wait_for_completion(&ftl->erase_completion);
    +
    + if (erase.state != MTD_ERASE_DONE) {
    + sm_printk("erase of block %d in zone %d failed after wait",
    + block, zone_num);
    + goto error;
    + }
    +
    + if (put_free)
    + kfifo_in(&zone->free_sectors,
    + (const unsigned char *)&block, sizeof(block));
    +
    + return 0;
    +error:
    + sm_mark_block_bad(ftl, zone_num, block);
    + return -EIO;
    +}
    +
    +static void sm_erase_callback(struct erase_info *self)
    +{
    + struct sm_ftl *ftl = (struct sm_ftl *)self->priv;
    + complete(&ftl->erase_completion);
    +}
    +
    +/* Throughtly test that block is valid. */
    +static int sm_check_block(struct sm_ftl *ftl, int zone, int block)
    +{
    + int boffset;
    + struct sm_oob oob;
    + int lbas[] = { -3, 0, 0, 0 };
    + int i = 0;
    + int test_lba;
    +
    +
    + /* First just check that block doesn't look fishy */
    + /* Only blocks that are valid or are sliced in two parts, are
    + accepted */
    + for (boffset = 0; boffset < ftl->block_size;
    + boffset += SM_SECTOR_SIZE) {
    +
    + /* This shoudn't happen anyway */
    + if (sm_read_sector(ftl, zone, block, boffset, NULL, &oob))
    + return -2;
    +
    + test_lba = sm_read_lba(&oob);
    +
    + if (lbas[i] != test_lba)
    + lbas[++i] = test_lba;
    +
    + /* If we found three different LBAs, something is fishy */
    + if (i == 3)
    + return -EIO;
    + }
    +
    + /* If the block is sliced (partialy erased usually) erase it */
    + if (i == 2) {
    + sm_erase_block(ftl, zone, block, 1);
    + return 1;
    + }
    +
    + return 0;
    +}
    +
    +/* ----------------- media scanning --------------------------------- */
    +static const struct chs_entry chs_table[] = {
    + { 1, 125, 4, 4 },
    + { 2, 125, 4, 8 },
    + { 4, 250, 4, 8 },
    + { 8, 250, 4, 16 },
    + { 16, 500, 4, 16 },
    + { 32, 500, 8, 16 },
    + { 64, 500, 8, 32 },
    + { 128, 500, 16, 32 },
    + { 256, 1000, 16, 32 },
    + { 512, 1015, 32, 63 },
    + { 1024, 985, 33, 63 },
    + { 2048, 985, 33, 63 },
    + { 0 },
    +};
    +
    +
    +static const u8 cis_signature[] = {
    + 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
    +};
    +/* Find out media parameters.
    + * This ideally has to be based on nand id, but for now device size is enough */
    +int sm_get_media_info(struct sm_ftl *ftl, struct mtd_info *mtd)
    +{
    + int i;
    + int size_in_megs = mtd->size / (1024 * 1024);
    +
    + ftl->readonly = mtd->type == MTD_ROM;
    +
    + /* Manual settings for very old devices */
    + ftl->zone_count = 1;
    + ftl->smallpagenand = 0;
    +
    + switch (size_in_megs) {
    + case 1:
    + /* 1 Mb flash/rom SmartMedia card (256 byte pages)*/
    + ftl->zone_size = 256;
    + ftl->max_lba = 250;
    + ftl->block_size = 8 * SM_SECTOR_SIZE;
    + ftl->smallpagenand = 1;
    +
    + break;
    + case 2:
    + /* 2 Mb flash SmartMedia (256 byte pages)*/
    + if (mtd->writesize == SM_SMALL_PAGE) {
    + ftl->zone_size = 512;
    + ftl->max_lba = 500;
    + ftl->block_size = 8 * SM_SECTOR_SIZE;
    + ftl->smallpagenand = 1;
    + /* 2 Mb rom SmartMedia */
    + } else {
    +
    + if (!ftl->readonly)
    + return -ENODEV;
    +
    + ftl->zone_size = 256;
    + ftl->max_lba = 250;
    + ftl->block_size = 16 * SM_SECTOR_SIZE;
    + }
    + break;
    + case 4:
    + /* 4 Mb flash/rom SmartMedia device */
    + ftl->zone_size = 512;
    + ftl->max_lba = 500;
    + ftl->block_size = 16 * SM_SECTOR_SIZE;
    + break;
    + case 8:
    + /* 8 Mb flash/rom SmartMedia device */
    + ftl->zone_size = 1024;
    + ftl->max_lba = 1000;
    + ftl->block_size = 16 * SM_SECTOR_SIZE;
    + }
    +
    + /* Minimum xD size is 16M. Also, all xD cards have standard zone
    + sizes. SmartMedia cards exist up to 128 Mb and have same layout*/
    + if (size_in_megs >= 16) {
    + ftl->zone_count = size_in_megs / 16;
    + ftl->zone_size = 1024;
    + ftl->max_lba = 1000;
    + ftl->block_size = 32 * SM_SECTOR_SIZE;
    + }
    +
    + /* Test for proper write,erase and oob sizes */
    + if (mtd->erasesize > ftl->block_size)
    + return -ENODEV;
    +
    + if (mtd->writesize > SM_SECTOR_SIZE)
    + return -ENODEV;
    +
    + if (ftl->smallpagenand && mtd->oobsize < SM_SMALL_OOB_SIZE)
    + return -ENODEV;
    +
    + if (!ftl->smallpagenand && mtd->oobsize < SM_OOB_SIZE)
    + return -ENODEV;
    +
    + /* We use these functions for IO */
    + if (!mtd->read_oob || !mtd->write_oob)
    + return -ENODEV;
    +
    + /* Find geometry information */
    + for (i = 0 ; i < ARRAY_SIZE(chs_table) ; i++) {
    + if (chs_table[i].size == size_in_megs) {
    + ftl->cylinders = chs_table[i].cyl;
    + ftl->heads = chs_table[i].head;
    + ftl->sectors = chs_table[i].sec;
    + return 0;
    + }
    + }
    +
    + sm_printk("media has unknown size : %dMB", size_in_megs);
    + ftl->cylinders = 985;
    + ftl->heads = 33;
    + ftl->sectors = 63;
    + return 0;
    +}
    +
    +/* Validate the CIS */
    +static int sm_read_cis(struct sm_ftl *ftl)
    +{
    + struct sm_oob oob;
    +
    + if (sm_read_sector(ftl,
    + 0, ftl->cis_block, ftl->cis_boffset, ftl->cis_buffer, &oob))
    + return -EIO;
    +
    + if (!sm_sector_valid(&oob) || !sm_block_valid(&oob))
    + return -EIO;
    +
    + if (!memcmp(ftl->cis_buffer, cis_signature, sizeof(cis_signature)))
    + return 0;
    +
    + if (!memcmp(ftl->cis_buffer + SM_SMALL_PAGE, cis_signature,
    + sizeof(cis_signature)))
    + return 0;
    + return -EIO;
    +}
    +
    +/* Scan the media for the CIS */
    +static int sm_find_cis(struct sm_ftl *ftl)
    +{
    + struct sm_oob oob;
    + int block, boffset;
    + int block_found = 0;
    +
    + /* Search for first valid block */
    + for (block = 0 ; block < ftl->zone_size - ftl->max_lba ; block++) {
    +
    + if (sm_read_sector(ftl, 0, block, 0, NULL, &oob))
    + continue;
    +
    + if (!sm_block_valid(&oob))
    + continue;
    + block_found = 1;
    + break;
    + }
    +
    + if (!block_found)
    + return -EIO;
    +
    + /* Search for first valid sector in this block */
    + for (boffset = 0 ; boffset < ftl->block_size;
    + boffset += SM_SECTOR_SIZE) {
    +
    + if (sm_read_sector(ftl, 0, block, boffset, NULL, &oob))
    + continue;
    +
    + if (!sm_sector_valid(&oob))
    + continue;
    + break;
    + }
    +
    + if (boffset == ftl->block_size)
    + return -EIO;
    +
    + ftl->cis_block = block;
    + ftl->cis_boffset = boffset;
    +
    + if (!sm_read_cis(ftl)) {
    + dbg("CIS block found at offset %d",
    + block * ftl->block_size + boffset);
    + return 0;
    + }
    +
    + return -EIO;
    +}
    +
    +/* Basic test to determine if underlying mtd device if functional */
    +static int sm_recheck_media(struct sm_ftl *ftl)
    +{
    + if (sm_read_cis(ftl)) {
    +
    + if (!ftl->unstable) {
    + sm_printk("media unstable, not allowing writes");
    + ftl->unstable = 1;
    + }
    + return -EIO;
    + }
    + return 0;
    +}
    +
    +/* Initialize a FTL zone */
    +static int sm_init_zone(struct sm_ftl *ftl, int zone_num)
    +{
    + struct ftl_zone *zone = &ftl->zones[zone_num];
    + struct sm_oob oob;
    + u16 block;
    + int lba;
    + int i = 0;
    +
    + dbg("initializing zone %d", zone_num);
    +
    + /* Allocate memory for FTL table */
    + zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL);
    +
    + if (!zone->lba_to_phys_table)
    + return -ENOMEM;
    + memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2);
    +
    +
    + /* Allocate memory for free sectors FIFO */
    + if (kfifo_alloc(&zone->free_sectors, ftl->zone_size * 2, GFP_KERNEL)) {
    + kfree(zone->lba_to_phys_table);
    + return -ENOMEM;
    + }
    +
    + /* Now scan the zone */
    + for (block = 0 ; block < ftl->zone_size ; block++) {
    +
    + /* Skip blocks till the CIS (including) */
    + if (zone_num == 0 && block <= ftl->cis_block)
    + continue;
    +
    + /* Read the oob of first sector */
    + if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob))
    + return -EIO;
    +
    + /* Test to see if block is erased. It is enough to test
    + first sector, because erase happens in one shot */
    + if (sm_block_erased(&oob)) {
    + kfifo_in(&zone->free_sectors,
    + (unsigned char *)&block, 2);
    + continue;
    + }
    +
    + /* If block is marked as bad, skip it */
    + /* This assumes we can trust first sector*/
    + /* However the way the block valid status is defined, ensures
    + very low probability of failure here */
    + if (!sm_block_valid(&oob)) {
    + dbg("PH %04d <-> <marked bad>", block);
    + continue;
    + }
    +
    +
    + lba = sm_read_lba(&oob);
    +
    + /* Invalid LBA means that block is damaged. */
    + /* We can try to erase it, or mark it as bad, but
    + lets leave that to recovery application */
    + if (lba == -2 || lba >= ftl->max_lba) {
    + dbg("PH %04d <-> LBA %04d(bad)", block, lba);
    + continue;
    + }
    +
    +
    + /* If there is no collision,
    + just put the sector in the FTL table */
    + if (zone->lba_to_phys_table[lba] < 0) {
    + dbg("PH %04d <-> LBA %04d", block, lba);
    + zone->lba_to_phys_table[lba] = block;
    + continue;
    + }
    +
    + sm_printk("collision"
    + " of LBA %d between blocks %d and %d in zone %d",
    + lba, zone->lba_to_phys_table[lba], block, zone_num);
    +
    + /* Test that this block is valid*/
    + if (sm_check_block(ftl, zone_num, block))
    + continue;
    +
    + /* Test now the old block */
    + if (sm_check_block(ftl, zone_num,
    + zone->lba_to_phys_table[lba])) {
    + zone->lba_to_phys_table[lba] = block;
    + continue;
    + }
    +
    + /* If both blocks are valid and share same LBA, it means that
    + they hold different versions of same data. It not
    + known which is more recent, thus just erase one of them
    + */
    + sm_printk("both blocks are valid, erasing the later");
    + sm_erase_block(ftl, zone_num, block, 1);
    + }
    +
    + dbg("zone initialized");
    + zone->initialized = 1;
    +
    + /* No free sectors, means that the zone is heavily damaged, write won't
    + work, but it can still can be (partially) read */
    + if (!kfifo_len(&zone->free_sectors)) {
    + sm_printk("no free blocks in zone %d", zone_num);
    + return 0;
    + }
    +
    + /* Randomize first block we write to */
    + get_random_bytes(&i, 2);
    + i %= (kfifo_len(&zone->free_sectors) / 2);
    +
    + while (i--) {
    + kfifo_out(&zone->free_sectors, (unsigned char *)&block, 2);
    + kfifo_in(&zone->free_sectors, (const unsigned char *)&block, 2);
    + }
    + return 0;
    +}
    +
    +/* Get and automaticly initialize an FTL mapping for one zone */
    +struct ftl_zone *sm_get_zone(struct sm_ftl *ftl, int zone_num)
    +{
    + struct ftl_zone *zone;
    + int error;
    +
    + BUG_ON(zone_num >= ftl->zone_count);
    + zone = &ftl->zones[zone_num];
    +
    + if (!zone->initialized) {
    + error = sm_init_zone(ftl, zone_num);
    +
    + if (error)
    + return ERR_PTR(error);
    + }
    + return zone;
    +}
    +
    +
    +/* ----------------- cache handling ------------------------------------------*/
    +
    +/* Initialize the one block cache */
    +void sm_cache_init(struct sm_ftl *ftl)
    +{
    + ftl->cache_data_invalid_bitmap = 0xFFFFFFFF;
    + ftl->cache_clean = 1;
    + ftl->cache_zone = -1;
    + ftl->cache_block = -1;
    + /*memset(ftl->cache_data, 0xAA, ftl->block_size);*/
    +}
    +
    +/* Put sector in one block cache */
    +void sm_cache_put(struct sm_ftl *ftl, char *buffer, int boffset)
    +{
    + memcpy(ftl->cache_data + boffset, buffer, SM_SECTOR_SIZE);
    + clear_bit(boffset / SM_SECTOR_SIZE, &ftl->cache_data_invalid_bitmap);
    + ftl->cache_clean = 0;
    +}
    +
    +/* Read a sector from the cache */
    +int sm_cache_get(struct sm_ftl *ftl, char *buffer, int boffset)
    +{
    + if (test_bit(boffset / SM_SECTOR_SIZE,
    + &ftl->cache_data_invalid_bitmap))
    + return -1;
    +
    + memcpy(buffer, ftl->cache_data + boffset, SM_SECTOR_SIZE);
    + return 0;
    +}
    +
    +/* Write the cache to hardware */
    +int sm_cache_flush(struct sm_ftl *ftl)
    +{
    + struct ftl_zone *zone;
    +
    + int sector_num;
    + u16 write_sector;
    + int zone_num = ftl->cache_zone;
    + int block_num;
    +
    + if (ftl->cache_clean)
    + return 0;
    +
    + if (ftl->unstable)
    + return -EIO;
    +
    + BUG_ON(zone_num < 0);
    + zone = &ftl->zones[zone_num];
    + block_num = zone->lba_to_phys_table[ftl->cache_block];
    +
    +
    + /* Try to read all unread areas of the cache block*/
    + for_each_bit(sector_num, &ftl->cache_data_invalid_bitmap,
    + ftl->block_size / SM_SECTOR_SIZE) {
    +
    + if (!sm_read_sector(ftl,
    + zone_num, block_num, sector_num * SM_SECTOR_SIZE,
    + ftl->cache_data + sector_num * SM_SECTOR_SIZE, NULL))
    + clear_bit(sector_num,
    + &ftl->cache_data_invalid_bitmap);
    + }
    +restart:
    + /* No spare blocks */
    + /* We could still continue by erasing the current block,
    + but for such worn out media it doesn't worth the trouble,
    + and the dangers */
    +
    + if (!kfifo_len(&zone->free_sectors)) {
    + dbg("no free sectors for write!");
    + return -EIO;
    + }
    +
    + kfifo_out(&zone->free_sectors, (unsigned char *)&write_sector, 2);
    +
    + if (sm_write_block(ftl, ftl->cache_data, zone_num, write_sector,
    + ftl->cache_block, ftl->cache_data_invalid_bitmap))
    + goto restart;
    +
    + /* Update the FTL table */
    + zone->lba_to_phys_table[ftl->cache_block] = write_sector;
    +
    + /* Write succesfull, so erase and free the old block */
    + if (block_num > 0)
    + sm_erase_block(ftl, zone_num, block_num, 1);
    +
    + sm_cache_init(ftl);
    + return 0;
    +}
    +
    +
    +/* flush timer, runs a second after last write */
    +static void sm_cache_flush_timer(unsigned long data)
    +{
    + struct sm_ftl *ftl = (struct sm_ftl *)data;
    + queue_work(cache_flush_workqueue, &ftl->flush_work);
    +}
    +
    +/* cache flush work, kicked by timer */
    +static void sm_cache_flush_work(struct work_struct *work)
    +{
    + struct sm_ftl *ftl = container_of(work, struct sm_ftl, flush_work);
    + mutex_lock(&ftl->mutex);
    + sm_cache_flush(ftl);
    + mutex_unlock(&ftl->mutex);
    + return;
    +}
    +
    +/* ---------------- outside interface -------------------------------------- */
    +
    +/* outside interface: read a sector */
    +static int sm_read(struct mtd_blktrans_dev *dev,
    + unsigned long sect_no, char *buf)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + struct ftl_zone *zone;
    + int error = 0, in_cache = 0;
    + int zone_num, block, boffset;
    +
    + sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset);
    + mutex_lock(&ftl->mutex);
    +
    +
    + zone = sm_get_zone(ftl, zone_num);
    + if (IS_ERR(zone)) {
    + error = PTR_ERR(zone);
    + goto unlock;
    + }
    +
    + /* Have to look at cache first */
    + if (ftl->cache_zone == zone_num && ftl->cache_block == block) {
    + in_cache = 1;
    + if (!sm_cache_get(ftl, buf, boffset))
    + goto unlock;
    + }
    +
    + /* Translate the block and return if doesn't exist in the table */
    + block = zone->lba_to_phys_table[block];
    +
    + if (block == -1) {
    + memset(buf, 0xFF, SM_SECTOR_SIZE);
    + goto unlock;
    + }
    +
    + if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) {
    + error = -EIO;
    + goto unlock;
    + }
    +
    + if (in_cache)
    + sm_cache_put(ftl, buf, boffset);
    +unlock:
    + mutex_unlock(&ftl->mutex);
    + return error;
    +}
    +
    +/* outside interface: write a sector */
    +static int sm_write(struct mtd_blktrans_dev *dev,
    + unsigned long sec_no, char *buf)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + struct ftl_zone *zone;
    + int error, zone_num, block, boffset;
    +
    + BUG_ON(ftl->readonly);
    + sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
    +
    + /* No need in flush thread running now */
    + del_timer(&ftl->timer);
    + mutex_lock(&ftl->mutex);
    +
    + zone = sm_get_zone(ftl, zone_num);
    + if (IS_ERR(zone)) {
    + error = PTR_ERR(zone);
    + goto unlock;
    + }
    +
    + /* If entry is not in cache, flush it */
    + if (ftl->cache_block != block || ftl->cache_zone != zone_num) {
    +
    + error = sm_cache_flush(ftl);
    + if (error)
    + goto unlock;
    +
    + ftl->cache_block = block;
    + ftl->cache_zone = zone_num;
    + }
    +
    + sm_cache_put(ftl, buf, boffset);
    +unlock:
    + mod_timer(&ftl->timer, jiffies + msecs_to_jiffies(cache_timeout));
    + mutex_unlock(&ftl->mutex);
    + return error;
    +}
    +
    +/* outside interface: flush everything */
    +static int sm_flush(struct mtd_blktrans_dev *dev)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + int retval;
    +
    + mutex_lock(&ftl->mutex);
    + retval = sm_cache_flush(ftl);
    + mutex_unlock(&ftl->mutex);
    + return retval;
    +}
    +
    +/* outside interface: device is released */
    +static int sm_release(struct mtd_blktrans_dev *dev)
    +{
    + struct sm_ftl *ftl = dev->priv;
    +
    + mutex_lock(&ftl->mutex);
    + del_timer_sync(&ftl->timer);
    + cancel_work_sync(&ftl->flush_work);
    + sm_cache_flush(ftl);
    + mutex_unlock(&ftl->mutex);
    + return 0;
    +}
    +
    +/* outside interface: get geometry */
    +static int sm_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + geo->heads = ftl->heads;
    + geo->sectors = ftl->sectors;
    + geo->cylinders = ftl->cylinders;
    + return 0;
    +}
    +
    +/* external interface: main initialization function */
    +static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
    +{
    + struct mtd_blktrans_dev *trans;
    + struct sm_ftl *ftl;
    +
    + /* Allocate & initialize our private structure */
    + ftl = kzalloc(sizeof(struct sm_ftl), GFP_KERNEL);
    + if (!ftl)
    + goto error1;
    +
    + mutex_init(&ftl->mutex);
    + setup_timer(&ftl->timer, sm_cache_flush_timer, (unsigned long)ftl);
    + INIT_WORK(&ftl->flush_work, sm_cache_flush_work);
    + init_completion(&ftl->erase_completion);
    +
    +
    + /* Read media information */
    + if (sm_get_media_info(ftl, mtd))
    + goto error2;
    +
    + /* Allocate temporary CIS buffer for read retry support */
    + ftl->cis_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL);
    + if (!ftl->cis_buffer)
    + goto error2;
    +
    + /* Allocate zone array, it will be initialized on demand */
    + ftl->zones = kzalloc(sizeof(struct ftl_zone) * ftl->zone_count,
    + GFP_KERNEL);
    + if (!ftl->zones)
    + goto error3;
    +
    + /* Allocate the cache*/
    + ftl->cache_data = kzalloc(ftl->block_size, GFP_KERNEL);
    +
    + if (!ftl->cache_data)
    + goto error4;
    +
    + sm_cache_init(ftl);
    +
    + /* Allocate upper layer structure and initialize it */
    + trans = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
    + if (!trans)
    + goto error5;
    +
    + ftl->trans = trans;
    + trans->priv = ftl;
    +
    + trans->tr = tr;
    + trans->mtd = mtd;
    + trans->devnum = -1;
    + trans->size = (ftl->block_size * ftl->max_lba * ftl->zone_count) >> 9;
    + trans->readonly = ftl->readonly;
    +
    +
    + if (sm_find_cis(ftl))
    + goto error6;
    +
    + trans->disk_attributes = sm_create_sysfs_attributes(ftl);
    +
    + sm_printk("Found %d MiB xD/SmartMedia FTL on mtd%d",
    + (int)(mtd->size / (1024 * 1024)), mtd->index);
    +
    + dbg("FTL layout:");
    + dbg("%d zone(s), each consists of %d blocks (+%d spares)",
    + ftl->zone_count, ftl->max_lba,
    + ftl->zone_size - ftl->max_lba);
    + dbg("each block consists of %d bytes",
    + ftl->block_size);
    +
    + /* Register device*/
    + if (add_mtd_blktrans_dev(trans))
    + goto error6;
    +
    + return;
    +error6:
    + kfree(trans);
    +error5:
    + kfree(ftl->cache_data);
    +error4:
    + kfree(ftl->zones);
    +error3:
    + kfree(ftl->cis_buffer);
    +error2:
    + kfree(ftl);
    +error1:
    + return;
    +}
    +
    +/* main interface: device {surprise,} removal */
    +static void sm_remove_dev(struct mtd_blktrans_dev *dev)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + int i;
    +
    + del_mtd_blktrans_dev(dev);
    +
    + for (i = 0 ; i < ftl->zone_count; i++) {
    +
    + if (!ftl->zones[i].initialized)
    + continue;
    +
    + kfree(ftl->zones[i].lba_to_phys_table);
    + kfifo_free(&ftl->zones[i].free_sectors);
    + }
    +
    + sm_delete_sysfs_attributes(ftl);
    + kfree(ftl->zones);
    + kfree(ftl->cache_data);
    + kfree(ftl);
    +}
    +
    +static struct mtd_blktrans_ops sm_ftl_ops = {
    + .name = "smblk",
    + .major = -1,
    + .part_bits = SM_FTL_PARTN_BITS,
    + .blksize = SM_SECTOR_SIZE,
    + .getgeo = sm_getgeo,
    +
    + .add_mtd = sm_add_mtd,
    + .remove_dev = sm_remove_dev,
    +
    + .readsect = sm_read,
    + .writesect = sm_write,
    +
    + .flush = sm_flush,
    + .release = sm_release,
    +
    + .owner = THIS_MODULE,
    +};
    +
    +static __init int sm_module_init(void)
    +{
    + int error = 0;
    + cache_flush_workqueue = create_freezeable_workqueue("smflush");
    +
    + if (IS_ERR(cache_flush_workqueue))
    + return PTR_ERR(cache_flush_workqueue);
    +
    + error = register_mtd_blktrans(&sm_ftl_ops);
    + if (error)
    + destroy_workqueue(cache_flush_workqueue);
    + return error;
    +
    +}
    +
    +static void __exit sm_module_exit(void)
    +{
    + destroy_workqueue(cache_flush_workqueue);
    + deregister_mtd_blktrans(&sm_ftl_ops);
    +}
    +
    +module_init(sm_module_init);
    +module_exit(sm_module_exit);
    +
    +MODULE_LICENSE("GPL");
    +MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
    +MODULE_DESCRIPTION("Smartmedia/xD mtd translation layer");
    diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h
    new file mode 100644
    index 0000000..e3941f4
    --- /dev/null
    +++ b/drivers/mtd/sm_ftl.h
    @@ -0,0 +1,92 @@
    +/*
    + * Copyright (C) 2009 - Maxim Levitsky
    + * SmartMedia/xD translation layer
    + *
    + * Based loosly on ssfdc.c which is
    + * (c) 2005 Eptar srl
    + * Author: Claudio Lanconelli <lanconelli.claudio@eptar.com>
    + *
    + * 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 <linux/mtd/blktrans.h>
    +#include <linux/kfifo.h>
    +#include <linux/sched.h>
    +#include <linux/completion.h>
    +
    +
    +
    +struct ftl_zone {
    + int initialized;
    + s16 *lba_to_phys_table; /* LBA to physical table */
    + struct kfifo free_sectors; /* queue of free sectors */
    +};
    +
    +struct sm_ftl {
    + struct mtd_blktrans_dev *trans;
    +
    + struct mutex mutex; /* protects the structure */
    + struct ftl_zone *zones; /* FTL tables for each zone */
    +
    + /* Media information */
    + int block_size; /* block size in bytes */
    + int zone_size; /* zone size in blocks */
    + int zone_count; /* number of zones */
    + int max_lba; /* maximum lba in a zone */
    + int smallpagenand; /* 256 bytes/page nand */
    + int readonly; /* is FS readonly */
    + int unstable;
    + int cis_block; /* CIS block location */
    + int cis_boffset; /* CIS offset in the block */
    + void *cis_buffer; /* tmp buffer for cis reads */
    +
    + /* Cache */
    + int cache_block; /* block number of cached block */
    + int cache_zone; /* zone of cached block */
    + unsigned char *cache_data; /* cached block data */
    + long unsigned int cache_data_invalid_bitmap;
    + int cache_clean;
    + struct work_struct flush_work;
    + struct timer_list timer;
    +
    + /* Async erase stuff */
    + struct completion erase_completion;
    +
    + /* Geometry stuff */
    + int heads;
    + int sectors;
    + int cylinders;
    +};
    +
    +struct chs_entry {
    + unsigned long size;
    + unsigned short cyl;
    + unsigned char head;
    + unsigned char sec;
    +};
    +
    +
    +#define SM_FTL_PARTN_BITS 3
    +
    +#define sm_printk(format, ...) \
    + printk(KERN_WARNING "sm_ftl" ": " format "\n", ## __VA_ARGS__)
    +
    +
    +#ifdef __DEBUG
    +
    +#define dbg(format, ...) \
    + printk(KERN_DEBUG "sm_ftl" ": " format "\n", ## __VA_ARGS__)
    +#else
    +
    +#define dbg(format, ...)
    +
    +#endif
    +
    +static void sm_erase_callback(struct erase_info *self);
    +static int sm_erase_block(struct sm_ftl *ftl, int zone_num, u16 block,
    + int put_free);
    +static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block);
    +
    +static int sm_recheck_media(struct sm_ftl *ftl);
    --
    1.6.3.3


    \
     
     \ /
      Last update: 2010-01-30 16:27    [from the cache]
    ©2003-2011 Jasper Spaans