lkml.org 
[lkml]   [2010]   [Jan]   [8]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Subject[PATCH 9/9] mtd: Add new SmartMedia/xD FTL
    From
    Date
    >From 743f723b6e7134cf9d99a158f3ac920c180d406a Mon Sep 17 00:00:00 2001
    From: Maxim Levitsky <maximlevitsky@gmail.com>
    Date: Fri, 8 Jan 2010 16:44:07 +0200
    Subject: [PATCH 9/9] mtd: Add new SmartMedia/xD FTL

    This implements new readwrite SmartMedia/xd FTL.

    It depends on nand driver to define proper oob layout that excludes
    all ecc areas and nothing more.

    Support for very old 256 byte/page devices is not yet enabled/complete.
    For these devices, all ecc handling will be done inside this FTL
    due to wierd oob layout.

    Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
    ---
    drivers/mtd/Kconfig | 12 +
    drivers/mtd/Makefile | 1 +
    drivers/mtd/sm_ftl.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
    drivers/mtd/sm_ftl.h | 75 ++++
    4 files changed, 1131 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 ebeabd6..e13bf41 100644
    --- a/drivers/mtd/Kconfig
    +++ b/drivers/mtd/Kconfig
    @@ -313,6 +313,18 @@ 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
    + select MTD_SM_COMMON
    + 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 MTD_OOPS
    tristate "Log panic/oops to an MTD buffer"
    depends on MTD
    diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
    index 02c5b17..02f6375 100644
    --- a/drivers/mtd/Makefile
    +++ b/drivers/mtd/Makefile
    @@ -25,6 +25,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..b72c30b
    --- /dev/null
    +++ b/drivers/mtd/sm_ftl.c
    @@ -0,0 +1,1043 @@
    +/*
    + * 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/bitops.h>
    +#include "sm_common.h"
    +#include "sm_ftl.h"
    +
    +static u8 tmp_buffer[SM_SECTOR_SIZE];
    +static int cache_size = 5;
    +
    +module_param(cache_size, int, S_IRUGO);
    +MODULE_PARM_DESC(cache_size,
    + "Number of blocks to hold in the cache (5 default)");
    +
    +
    +static void sm_erase_callback(struct erase_info *self);
    +static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
    + int put_free);
    +static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block);
    +static int sm_cache_flush_thread(void *data);
    +
    +
    +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 },
    +};
    +
    +/* 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 flas/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 {
    + 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, and thus 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 and erase sizes */
    + if (mtd->erasesize > ftl->block_size)
    + return -ENODEV;
    +
    + if (mtd->writesize > SM_SECTOR_SIZE)
    + return -ENODEV;
    +
    + if (mtd->oobavail < sizeof(struct sm_oob))
    + return -ENODEV;
    +
    + /* For now, don't support small page nand */
    + if (ftl->smallpagenand)
    + return -ENODEV;
    +
    + /* This shouldn't happen */
    + if (ftl->zone_count * ftl->zone_size * ftl->block_size != mtd->size)
    + 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;
    + }
    + }
    +
    + ftl->cylinders = 985;
    + ftl->heads = 33;
    + ftl->sectors = 63;
    + return 0;
    +}
    +
    +/* 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;
    +
    + if (offset >= ftl->zone_count)
    + dbg("sm_break_offset: try to access a zone %lx",
    + (long unsigned int)offset);
    +
    + *zone = offset >= ftl->zone_count ? -1 : offset;
    +}
    +
    +/* 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_oob_ops ops;
    + struct sm_oob tmp_oob;
    + struct mtd_info *mtd = ftl->trans->mtd;
    + int ret;
    + loff_t offset;
    +
    + ops.len = SM_SECTOR_SIZE;
    + ops.datbuf = buffer;
    +
    + if (!oob)
    + oob = &tmp_oob;
    +
    + ops.mode = MTD_OOB_AUTO;
    + ops.ooboffs = 0;
    + ops.ooblen = sizeof(struct sm_oob);
    + ops.oobbuf = (void *)oob;
    +
    + /* FTL can contain -1 entries that are by default filled with bits */
    + if (block == -1) {
    +
    + if (buffer)
    + memset(buffer, 0xFF, SM_SECTOR_SIZE);
    + memset(oob, 0xFF, sizeof(struct sm_oob));
    + return 0;
    + }
    +
    + offset = sm_mkoffset(ftl, zone, block, boffset);
    + ret = mtd->read_oob(mtd, offset, &ops);
    +
    + if (ret) {
    + return -EIO;
    + dbg("read of sector at 0x%lx failed with error %d",
    + (long unsigned int)offset, ret);
    + }
    +
    + if (ops.oobretlen != sizeof(struct sm_oob)) {
    + dbg("read of sector at 0x%lx failed with wrong oob len %d",
    + (long unsigned int)offset, (int)ops.oobretlen);
    + return -EIO;
    + }
    +
    + if (buffer && sm_sector_valid(oob)) {
    + dbg("read of sector at 0x%lxfailed because "
    + "it is marked as invalid",
    + (long unsigned int)offset);
    + return -EIO;
    + }
    +
    + return 0;
    +}
    +
    +/* Write a block using data and lba */
    +static int sm_write_block(struct sm_ftl *ftl, u8 *buf,
    + int zone_num, int block, int lba)
    +{
    + struct mtd_oob_ops ops;
    + struct mtd_info *mtd = ftl->trans->mtd;
    + int boffset;
    + loff_t offset;
    + int retry;
    +
    + struct sm_oob oob;
    + memset(&oob, 0xFF, sizeof(oob));
    + sm_write_lba(&oob, lba);
    +
    + if (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
    + dbg("attempted to write the CIS!");
    + return -EIO;
    + }
    +
    +
    + ops.len = SM_SECTOR_SIZE;
    +
    + ops.mode = MTD_OOB_AUTO;
    + ops.ooboffs = 0;
    + ops.ooblen = sizeof(struct sm_oob);
    + ops.oobbuf = (void *)&oob;
    +
    + /* Use write_oob here because some xD cards only accept writes that
    + contain both page and oob write. These cards most likely
    + do their own ftl */
    +
    + offset = sm_mkoffset(ftl, zone_num, block, 0);
    +
    +restart:
    + for (boffset = 0; boffset < ftl->block_size;
    + boffset += SM_SECTOR_SIZE) {
    +
    + ops.datbuf = buf + boffset;
    +
    + if (!ftl->trans->mtd->write_oob(ftl->trans->mtd,
    + offset + boffset, &ops))
    + continue;
    +
    + if (!retry) {
    + dbg("write of block %d in zone %d failed, erasing it",
    + block, zone_num);
    +
    + /* If write fails. try to erase the block */
    + sm_erase_block(ftl, zone_num, block, 0);
    + retry = 1;
    + goto restart;
    + } else {
    + dbg("write of block %d in zone %d failed again"
    + ", marking as bad", block, zone_num);
    +
    + sm_mark_block_bad(ftl, zone_num, block);
    + return -EIO;
    + }
    + }
    + return 0;
    +}
    +
    +/*
    + * Erase a block within a zone
    + * If erase succedes, it updates free block fifo
    + */
    +static void sm_erase_block(struct sm_ftl *ftl, int zone_num, s16 block,
    + int put_free)
    +{
    + struct ftl_zone *zone = &ftl->zones[zone_num];
    + struct erase_info erase;
    + struct mtd_info *mtd = ftl->trans->mtd;
    +
    + erase.mtd = ftl->trans->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 (zone_num == 0 && (block == ftl->cis_block || block == 0)) {
    + dbg("attempted to erase the CIS!");
    + return;
    + }
    +
    + if (ftl->trans->mtd->erase(ftl->trans->mtd, &erase)) {
    + dbg("erase of block %d in zone %d failed in mtd->erase call",
    + block, zone_num);
    + goto error;
    + }
    +
    + wait_for_completion(&ftl->erase_completion);
    +
    + if (ftl->erase_error) {
    + dbg("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, 2);
    + return;
    +
    +error:
    + sm_mark_block_bad(ftl, zone_num, block);
    + return;
    +}
    +
    +static void sm_erase_callback(struct erase_info *self)
    +{
    + struct sm_ftl *ftl = (struct sm_ftl *)self->priv;
    + ftl->erase_error = (self->state == MTD_ERASE_FAILED);
    + complete(&ftl->erase_completion);
    +}
    +
    +
    +/*
    + * Throughtly test that block is valid and belongs
    + * to specified LBA. Tries to erase it if not
    + */
    +static int sm_check_block_lba(struct sm_ftl *ftl, int zone, int block, int lba)
    +{
    + int boffset;
    + struct sm_oob oob;
    + int tmp;
    +
    + for (boffset = 0; boffset < ftl->block_size;
    + boffset += SM_SECTOR_SIZE) {
    +
    + if (sm_read_sector(ftl, zone, block, boffset, tmp_buffer,
    + &oob)) {
    + dbg("block check: fail in sector %d in zone %d",
    + block, zone);
    + goto erase;
    + }
    +
    + if (sm_block_valid(&oob) || sm_sector_valid(&oob)) {
    + dbg("block check: block/sector status invalid"
    + " for sector %d in zone %d",
    + block, zone);
    + goto erase;
    + }
    +
    + tmp = sm_read_lba(&oob);
    +
    + if (tmp != lba) {
    + dbg("block check: block offset %d, we get "
    + "different LBA (%d), should get %d",
    + boffset, tmp, lba);
    + goto erase;
    + }
    + }
    + return 0;
    +erase:
    + sm_erase_block(ftl, zone, block, 1);
    + return -EIO;
    +}
    +
    +/* Mark whole block at offset 'offs' as bad.
    + */
    +static void sm_mark_block_bad(struct sm_ftl *ftl, int zone_num, int block)
    +{
    + struct mtd_oob_ops ops;
    + struct sm_oob oob;
    + int boffset;
    + int offset = sm_mkoffset(ftl, zone_num, block, 0);
    + struct mtd_info *mtd = ftl->trans->mtd;
    +
    + dbg("marking block %d of zone %d as bad", block, zone_num);
    + memset(&oob, 0xFF, sizeof(oob));
    + oob.block_status = 0xF0;
    +
    + ops.mode = MTD_OOB_AUTO;
    + ops.ooboffs = 0;
    + ops.ooblen = sizeof(struct sm_oob);
    + ops.oobbuf = (void *)&oob;
    + ops.datbuf = NULL;
    +
    + /* We aren't checking the return value, because we don't care */
    + for (boffset = 0; boffset < ftl->block_size; boffset += SM_SECTOR_SIZE)
    + mtd->write_oob(mtd, offset + boffset, &ops);
    +}
    +
    +/* Initialize FTL mapping for one zone */
    +struct ftl_zone *sm_initialize_zone(struct sm_ftl *ftl, int zone_num)
    +{
    + struct sm_oob oob;
    + struct ftl_zone *zone;
    + u16 block;
    + int lba;
    + int i = 0;
    +
    + BUG_ON(zone_num >= ftl->zone_count);
    + zone = &ftl->zones[zone_num];
    + if (zone->initialized)
    + return zone;
    +
    + dbg("initializing zone %d", zone_num);
    +
    + zone->lba_to_phys_table = kmalloc(ftl->max_lba * 2, GFP_KERNEL);
    +
    + if (!zone->lba_to_phys_table)
    + return ERR_PTR(-ENOMEM);
    +
    + if (kfifo_alloc(&zone->free_sectors, ftl->zone_size * 2, GFP_KERNEL)) {
    + kfree(zone->lba_to_phys_table);
    + return ERR_PTR(-ENOMEM);
    + }
    +
    + memset(zone->lba_to_phys_table, -1, ftl->max_lba * 2);
    +
    + for (block = 0 ; block < ftl->zone_size ; block++) {
    +
    + /* Skip blocks till the CIS (including) */
    + if (zone_num == 0 && block <= ftl->cis_block) {
    +
    + if (block != ftl->cis_block)
    + dbg("skipping block %d because"
    + " it is before the CIS (%d)",
    + block, ftl->cis_block);
    + continue;
    + }
    +
    + /* Not much that we can do with blocks without
    + * even readable oob... - skip*/
    + /* Shouldn't happen though */
    + if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) {
    + dbg("skipping block %d because it's "
    + "oob was unreadable", block);
    + continue;
    + }
    +
    + /* Blocks with 0xFFs in the oob area are assumed free -
    + add to free table*/
    + lba = sm_read_lba(&oob);
    + if (lba == -1) {
    + kfifo_in(&zone->free_sectors,
    + (unsigned char *)&block, 2);
    + continue;
    + }
    +
    + /* Blocks that are marked as invalid aren't for sure usable */
    + /* If such block has correct LBA and no other block has it,
    + return errors on read */
    + if (sm_block_valid(&oob)) {
    + if (lba >= 0 && lba < ftl->max_lba)
    + zone->lba_to_phys_table[lba] = -2;
    + dbg("skipping block %d because it was marked bad",
    + block);
    + continue;
    + }
    +
    + /* Try to erase blocks that have invalid LBA,
    + but marked as valid */
    + if (lba >= ftl->max_lba || lba == -2) {
    + dbg("erasing block %d because it has invalid LBA (%d)",
    + block, lba);
    +
    + sm_erase_block(ftl, zone_num, block, 1);
    + continue;
    + }
    +
    + /* If there is no collision,
    + just put the sector in the FTL table */
    + if (zone->lba_to_phys_table[lba] < 0) {
    + /*dbg("LBA %04d -> PH %04d", lba, block);*/
    + zone->lba_to_phys_table[lba] = block;
    + continue;
    + }
    +
    + dbg("collision of LBA %d between blocks %d and %d in zone %d",
    + lba, zone->lba_to_phys_table[lba], block, zone_num);
    +
    + /* Otherwise, carefully see if one of them is invalid*/
    + if (sm_check_block_lba(ftl, zone_num, block, lba))
    + continue;
    +
    + if (sm_check_block_lba(ftl, zone_num,
    + zone->lba_to_phys_table[lba], lba))
    + continue;
    +
    + /* Now both blocks are valid and share same LBA...
    + I guess only solution is to throw a dice.... */
    + dbg("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)) {
    + dbg("no free blocks in zone %d", zone_num);
    + return zone;
    + }
    +
    + return zone;
    +
    + /* 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 zone;
    +}
    +
    +/* Write one cached block to hardware */
    +int sm_cache_block_write(struct sm_ftl *ftl, struct cached_block *cache_entry)
    +{
    + struct ftl_zone *zone;
    +
    + int sector_num;
    + u16 write_sector;
    + int zone_num = cache_entry->zone;
    + int block_num;
    +
    + BUG_ON(cache_entry->zone < 0);
    + zone = &ftl->zones[cache_entry->zone];
    + block_num = zone->lba_to_phys_table[cache_entry->lba];
    +
    +
    + /* Read all unread areas of the cache block*/
    + for_each_bit(sector_num, &cache_entry->data_invalid_bitmap,
    + ftl->block_size / SM_SECTOR_SIZE) {
    +
    +
    + if (sm_read_sector(ftl,
    + zone_num, block_num, sector_num * SM_SECTOR_SIZE,
    + cache_entry->data + sector_num * SM_SECTOR_SIZE, NULL))
    + return -EIO;
    + }
    +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, cache_entry->data, zone_num, write_sector,
    + cache_entry->lba))
    + goto restart;
    +
    + /* Update the FTL table */
    + zone->lba_to_phys_table[cache_entry->lba] = write_sector;
    +
    + /* Write succesfull, so erase and free the old block */
    + if (block_num > 0)
    + sm_erase_block(ftl, zone_num, block_num, 1);
    + return 0;
    +}
    +
    +/* Initialize new/used cache entry */
    +static int sm_cache_block_init(struct sm_ftl *ftl,
    + struct cached_block *cache_entry)
    +{
    + if (!cache_entry->data)
    + cache_entry->data = kmalloc(ftl->block_size, GFP_KERNEL);
    +
    + if (!cache_entry->data)
    + return -ENOMEM;
    +
    + cache_entry->data_invalid_bitmap = 0xFFFFFFFF;
    + cache_entry->lba = -1;
    + cache_entry->zone = -1;
    + return 0;
    +}
    +
    +/* Flushes write cache, have to be run with ->cache_lock held */
    +static int __sm_cache_flush(struct sm_ftl *ftl)
    +{
    + struct cached_block *cache_entry = NULL, *tmp_entry;
    + struct mtd_info *mtd = ftl->trans->mtd;
    + int error;
    +
    + if (ftl->readonly)
    + return -EROFS;
    +
    + if (list_empty(&ftl->cache))
    + return 0;
    +
    + list_for_each_entry_safe(cache_entry, tmp_entry, &ftl->cache,
    + list_member) {
    + /* Write should never fail, unless media is worn out */
    + if (sm_cache_block_write(ftl, cache_entry)) {
    + dbg("sm_ftl: failed to write block %d at zone %d",
    + (int)cache_entry->lba, cache_entry->zone);
    + ftl->readonly = 1;
    + return -EIO;
    + }
    +
    + list_del(&cache_entry->list_member);
    + list_add(&cache_entry->list_member, &ftl->free_cache);
    +
    + error = sm_cache_block_init(ftl, cache_entry);
    + if (error)
    + return error;
    + }
    + return 0;
    +}
    +
    +
    +/* Flushes the write cache */
    +static int sm_cache_flush(struct sm_ftl *ftl)
    +{
    + int retval;
    + mutex_lock(&ftl->cache_mutex);
    + retval = __sm_cache_flush(ftl);
    + mutex_unlock(&ftl->cache_mutex);
    + return retval;
    +}
    +
    +/* Frees the write cache */
    +static void sm_free_cache(struct sm_ftl *ftl)
    +{
    + struct cached_block *cache_entry;
    +
    + mutex_lock(&ftl->cache_mutex);
    + while (!list_empty(&ftl->free_cache)) {
    + cache_entry = list_first_entry(&ftl->free_cache,
    + struct cached_block, list_member);
    +
    + kfree(cache_entry->data);
    + list_del(&cache_entry->list_member);
    + kfree(cache_entry);
    + }
    + mutex_unlock(&ftl->cache_mutex);
    +}
    +
    +
    +/* outside interface: open the device */
    +static int sm_open(struct mtd_blktrans_dev *dev)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + ftl->flush_thread = kthread_run(sm_cache_flush_thread,
    + ftl, "smflush%d", dev->mtd->index);
    +
    + if (IS_ERR(ftl->flush_thread))
    + return PTR_ERR(ftl->flush_thread);
    + return 0;
    +}
    +
    +/* 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;
    + struct cached_block *cache_entry = NULL;
    + int error = 0;
    + int cache_found = 0;
    +
    + int zone_num, block, boffset;
    +
    + sm_break_offset(ftl, sect_no << 9, &zone_num, &block, &boffset);
    +
    + zone = sm_initialize_zone(ftl, zone_num);
    + if (IS_ERR(zone))
    + return PTR_ERR(zone);
    +
    + mutex_lock(&ftl->cache_mutex);
    +
    + /* Have to look at cache first */
    + list_for_each_entry(cache_entry, &ftl->cache, list_member)
    + if (cache_entry->zone == zone_num &&
    + cache_entry->lba == block &&
    +
    + !test_bit(boffset / SM_SECTOR_SIZE,
    + &cache_entry->data_invalid_bitmap)) {
    +
    + memcpy(buf, cache_entry->data + boffset,
    + SM_SECTOR_SIZE);
    + 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 (block == -2) {
    + dbg("read block %d of zone %d marked invalid in the ftl",
    + block, zone_num);
    + error = -EIO;
    + goto unlock;
    + }
    +
    + /* Do the read. The below relies on proper ftl setup and underlying
    + driver to check at least the ecc
    + */
    + if (sm_read_sector(ftl, zone_num, block, boffset, buf, NULL)) {
    + error = -EIO;
    + goto unlock;
    + }
    +
    + /* If we already have the cache entry, then add the data there, because
    + we will need it anyway..*/
    + if (cache_found) {
    + memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
    + clear_bit(boffset / SM_SECTOR_SIZE,
    + &cache_entry->data_invalid_bitmap);
    + }
    +unlock:
    + mutex_unlock(&ftl->cache_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;
    + struct cached_block *cache_entry = NULL;
    + int error;
    + int zone_num, block, boffset;
    + int cache_found = 0;
    +
    + if (ftl->readonly)
    + return -EROFS;
    +
    + /* Try to write the cache if possible */
    + mutex_lock(&ftl->cache_mutex);
    +
    + sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
    +
    + zone = sm_initialize_zone(ftl, zone_num);
    + if (IS_ERR(zone))
    + return PTR_ERR(zone);
    +
    +
    + /* Try to find existing cache entry */
    + list_for_each_entry(cache_entry, &ftl->cache, list_member)
    + if (cache_entry->zone == zone_num &&
    + cache_entry->lba == block) {
    + cache_found = 1;
    + break;
    + }
    +
    + /* Entry not in the cache, create new cache entry */
    + if (!cache_found) {
    +
    + /* Flush the cache if full */
    + if (list_empty(&ftl->free_cache)) {
    +
    + error = __sm_cache_flush(ftl);
    +
    + if (error)
    + goto unlock;
    + }
    +
    + BUG_ON(list_empty(&ftl->free_cache));
    +
    + cache_entry = list_first_entry(&ftl->free_cache,
    + struct cached_block, list_member);
    +
    + cache_entry->lba = block;
    + cache_entry->zone = zone_num;
    +
    + list_del(&cache_entry->list_member);
    + list_add(&cache_entry->list_member, &ftl->cache);
    + }
    +
    + /* And finally put data there */
    + memcpy(cache_entry->data + boffset, buf, SM_SECTOR_SIZE);
    + clear_bit(boffset / SM_SECTOR_SIZE, &cache_entry->data_invalid_bitmap);
    +unlock:
    + mutex_unlock(&ftl->cache_mutex);
    + return error;
    +}
    +
    +/* outside interface: flush everything */
    +static int sm_flush(struct mtd_blktrans_dev *dev)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + return sm_cache_flush(ftl);
    +}
    +
    +/* outside interface: last user has quit using the device,
    + also called on removal */
    +static int sm_release(struct mtd_blktrans_dev *dev)
    +{
    + struct sm_ftl *ftl = dev->priv;
    + sm_cache_flush(ftl);
    + kthread_stop(ftl->flush_thread);
    + 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;
    +}
    +
    +
    +/* Periodic cache flush thread */
    +static int sm_cache_flush_thread(void *data)
    +{
    + struct sm_ftl *ftl = (struct sm_ftl *)data;
    +
    + set_freezable();
    + while (!kthread_should_stop()) {
    +
    + try_to_freeze();
    + set_current_state(TASK_INTERRUPTIBLE);
    + schedule_timeout(msecs_to_jiffies(500));
    + sm_cache_flush(ftl);
    + }
    +
    + return 0;
    +}
    +
    +static const u8 cis_signature[] = {
    + 0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, 0xDF, 0x01, 0x20
    +};
    +
    +/* Locate the CIS */
    +static int sm_find_cis(struct sm_ftl *ftl)
    +{
    + int block, boffset;
    + struct sm_oob oob;
    + int block_found = 0;
    +
    +
    + /* Scan 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;
    +
    + /* Block might be still partially damaged, so scan for first valid
    + sector */
    + for (boffset = 0 ; boffset < ftl->block_size;
    + boffset += SM_SECTOR_SIZE) {
    +
    + if (sm_read_sector(ftl, 0, block, boffset, tmp_buffer, &oob))
    + continue;
    +
    + if (!memcmp(tmp_buffer, cis_signature, sizeof(cis_signature)))
    + goto found;
    +
    + if (!memcmp(tmp_buffer + SM_SECTOR_SIZE / 2, cis_signature,
    + sizeof(cis_signature)))
    + goto found;
    + return -EIO;
    + }
    +found:
    + ftl->cis_block = block;
    + dbg("CIS block found at offset %d", block * ftl->block_size + boffset);
    + 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;
    + int i;
    + struct cached_block *cache_entry;
    +
    +
    + /* Allocate & initialize our private structure */
    + ftl = kzalloc(sizeof(struct sm_ftl), GFP_KERNEL);
    + if (!ftl)
    + goto error1;
    +
    + INIT_LIST_HEAD(&ftl->cache);
    + INIT_LIST_HEAD(&ftl->free_cache);
    + mutex_init(&ftl->cache_mutex);
    + init_completion(&ftl->erase_completion);
    +
    + /* Read media information */
    + if (sm_get_media_info(ftl, mtd))
    + 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 error2;
    +
    + /* Allocate write cache */
    + INIT_LIST_HEAD(&ftl->cache);
    + INIT_LIST_HEAD(&ftl->free_cache);
    +
    + for (i = 0 ; i < cache_size ; i++) {
    + cache_entry = kzalloc(sizeof(struct cached_block),
    + GFP_KERNEL);
    + if (!cache_entry)
    + break;
    +
    + if (sm_cache_block_init(ftl, cache_entry)) {
    + kfree(cache_entry);
    + break;
    + }
    + list_add(&cache_entry->list_member, &ftl->free_cache);
    + }
    +
    + if (list_empty(&ftl->free_cache))
    + goto error3;
    +
    + /* Allocate upper layer structure and initialize it */
    + trans = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
    + if (!trans)
    + goto error4;
    +
    + 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 error4;
    +
    + /* Register device*/
    + if (add_mtd_blktrans_dev(trans))
    + goto error5;
    +
    + dbg("Found %d MiB SmartMedia/xD card on %s",
    + (int)(mtd->size / (1024 * 1024)), mtd->name);
    +
    + dbg("FTL layout:");
    + dbg("%d zones, 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);
    +
    + return;
    +error5:
    + kfree(trans);
    +error4:
    + sm_free_cache(ftl);
    +error3:
    + kfree(ftl->zones);
    +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;
    + dbg("removing the ftl device");
    + del_mtd_blktrans_dev(dev);
    + kfree(ftl->zones);
    + sm_free_cache(ftl);
    + kfree(ftl); /* WE free that here, but the ->release can still
    + be called after ..... fuck */
    +}
    +
    +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,
    + .readsect = sm_read,
    + .writesect = sm_write,
    + .add_mtd = sm_add_mtd,
    + .remove_dev = sm_remove_dev,
    + .open = sm_open,
    + .release = sm_release,
    + .flush = sm_flush,
    + .owner = THIS_MODULE,
    +};
    +
    +static __init int sm_module_init(void)
    +{
    + return register_mtd_blktrans(&sm_ftl_ops);
    +}
    +
    +static void __exit sm_module_exit(void)
    +{
    + 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..d86d00e
    --- /dev/null
    +++ b/drivers/mtd/sm_ftl.h
    @@ -0,0 +1,75 @@
    +/*
    + * 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/nand.h>
    +#include <linux/mtd/blktrans.h>
    +#include <linux/list.h>
    +#include <linux/kfifo.h>
    +#include <linux/sched.h>
    +#include <linux/completion.h>
    +#include <linux/spinlock.h>
    +
    +
    +struct ftl_zone {
    + int initialized;
    + s16 *lba_to_phys_table; /* LBA to physical table */
    + struct kfifo free_sectors; /* queue of free sectors */
    +};
    +
    +struct cached_block {
    + int zone;
    + unsigned long lba;
    + unsigned char *data;
    + long unsigned int data_invalid_bitmap;
    + struct list_head list_member;
    +};
    +
    +struct sm_ftl {
    + struct mtd_blktrans_dev *trans;
    + struct ftl_zone *zones;
    + struct list_head cache;
    + struct list_head free_cache;
    + struct mutex cache_mutex;
    + struct completion erase_completion;
    + struct task_struct *flush_thread;
    + int erase_error;
    +
    + 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;
    +
    + /* geometry stuff */
    + int heads;
    + int sectors;
    + int cylinders;
    +
    + /*Misc */
    + int cis_block;
    +};
    +
    +struct chs_entry {
    + unsigned long size;
    + unsigned short cyl;
    + unsigned char head;
    + unsigned char sec;
    +};
    +
    +
    +#define SM_FTL_PARTN_BITS 3
    +
    +#define dbg(format, ...) \
    + printk(KERN_ERR "sm_ftl" ": " format "\n", ## __VA_ARGS__)
    --
    1.6.3.3




    \
     
     \ /
      Last update: 2010-01-08 16:15    [W:0.077 / U:0.776 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site