/* * YAFFS: Yet another FFS. A NAND-flash specific file system. * * Copyright (C) 2002 Aleph One Ltd. * for Toby Churchill Ltd and Brightstar Engineering * * Created by Charles Manning * * 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. * * * This version hacked for emulating 2kpage NAND for YAFFS2 testing. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EM_SIZE_IN_MEG 4 #define PAGE_DATA_SIZE 2048 #define PAGE_SPARE_SIZE 64 #define PAGES_PER_BLOCK 64 #define NAND_SHIFT 11 /* Shifter for 2k */ #define EM_SIZE_IN_BYTES (EM_SIZE_IN_MEG << 20) #define PAGE_TOTAL_SIZE (PAGE_DATA_SIZE + PAGE_SPARE_SIZE) #define BLOCK_TOTAL_SIZE (PAGES_PER_BLOCK * PAGE_TOTAL_SIZE) #define BLOCKS_PER_MEG ((1<<20) / (PAGES_PER_BLOCK * PAGE_DATA_SIZE)) static struct mtd_info nandemul2k_mtd; typedef struct { __u8 data[PAGE_TOTAL_SIZE]; // Data + spare int empty; // is this empty? } nandemul_Page; typedef struct { nandemul_Page *page[PAGES_PER_BLOCK]; int damaged; } nandemul_Block; typedef struct { nandemul_Block **block; int nBlocks; } nandemul_Device; static nandemul_Device ned; static int sizeInMB = EM_SIZE_IN_MEG; static void nandemul_yield(int n) { #ifdef __KERNEL__ if (n > 0) schedule_timeout(n); #endif } static void nandemul2k_Read(void *buffer, int page, int start, int nBytes) { int pg = page % PAGES_PER_BLOCK; int blk = page / PAGES_PER_BLOCK; if (buffer && nBytes > 0) memcpy(buffer,&ned.block[blk]->page[pg]->data[start],nBytes); } static void nandemul2k_Program(const void *buffer, int page, int start, int nBytes) { int blk = page / PAGES_PER_BLOCK; int pg = page % PAGES_PER_BLOCK; __u8 *p; __u8 *b = (__u8 *)buffer; p = &ned.block[blk]->page[pg]->data[start]; while (buffer && nBytes > 0) { *p = *p & *b; p++; b++; nBytes--; } } static void nandemul2k_DoErase(int blockNumber) { int i; nandemul_Block *blk; if (blockNumber < 0 || blockNumber >= ned.nBlocks) return; blk = ned.block[blockNumber]; for(i = 0; i < PAGES_PER_BLOCK; i++) { memset(blk->page[i],0xff,sizeof(nandemul_Page)); blk->page[i]->empty = 1; } nandemul_yield(2); } static int nandemul2k_CalcNBlocks(void) { return EM_SIZE_IN_MEG * BLOCKS_PER_MEG; } static int CheckInit(void) { static int initialised = 0; int i, j; int fail = 0; int nBlocks; int nAllocated = 0; if (initialised) return 0; ned.nBlocks = nBlocks = nandemul2k_CalcNBlocks(); ned.block = kmalloc(sizeof(nandemul_Block*) * nBlocks, GFP_KERNEL); if (!ned.block) return ENOMEM; for (i=fail=0; i page[j] = kmalloc(sizeof(nandemul_Page), GFP_KERNEL)) == 0) fail = 1; } nandemul2k_DoErase(i); ned.block[i]->damaged = 0; nAllocated++; } } if (fail) { //Todo thump pages for(i = 0; i < nAllocated; i++) kfree(ned.block[i]); kfree(ned.block); return ENOMEM; } ned.nBlocks = nBlocks; initialised = 1; return 1; } static void nandemul2k_CleanUp(void) { int i,j; for(i = 0; i < ned.nBlocks; i++) { for(j = 0; j < PAGES_PER_BLOCK; j++) { kfree(ned.block[i]->page[j]); } kfree(ned.block[i]); } kfree(ned.block); ned.block = 0; } #ifdef CONFIG_MTD_NAND_ECC #include #endif /* * NAND low-level MTD interface functions */ static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *oob_buf, struct nand_oobinfo *dummy); static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, const u_char *oob_buf, struct nand_oobinfo *dummy); static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); static int nand_writev (struct mtd_info *mtd, const struct iovec *vecs, unsigned long count, loff_t to, size_t *retlen); static int nand_erase (struct mtd_info *mtd, struct erase_info *instr); static void nand_sync (struct mtd_info *mtd); /* * NAND read */ static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { return nand_read_ecc(mtd, from, len, retlen, buf, NULL,NULL); } /* * NAND read with ECC */ static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *oob_buf,struct nand_oobinfo *oobsel) { int start, page; int n = len; int nToCopy; /* Do not allow reads past end of device */ if ((from + len) > mtd->size) { *retlen = 0; return -EINVAL; } /* Initialize return value */ *retlen = 0; while (n > 0) { /* First we calculate the starting page */ page = from >> NAND_SHIFT; /* Get raw starting column */ start = from & (mtd->oobblock-1); // OK now check for the curveball where the start and end are in // the same page if((start + n) < mtd->oobblock) nToCopy = n; else nToCopy = mtd->oobblock - start; nandemul2k_Read(buf, page, start, nToCopy); nandemul2k_Read(oob_buf,page,PAGE_DATA_SIZE,PAGE_SPARE_SIZE); n -= nToCopy; from += nToCopy; buf += nToCopy; if(oob_buf) oob_buf += PAGE_SPARE_SIZE; *retlen += nToCopy; } return 0; } /* * NAND read out-of-band */ static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { int col, page; printk("nand_read_oob: from = 0x%08x, buf = 0x%08x, len = %i\n", (unsigned int) from, (unsigned int) buf, (int) len); /* Shift to get page */ page = ((int) from) >> NAND_SHIFT; /* Mask to get column */ col = from & 0x0f; /* Initialize return length value */ *retlen = 0; /* Do not allow reads past end of device */ if ((from + len) > mtd->size) { printk("nand_read_oob: Attempt read beyond end of device\n"); *retlen = 0; return -EINVAL; } nandemul2k_Read(buf,page,PAGE_DATA_SIZE + col,len); /* Return happy */ *retlen = len; return 0; } static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { return nand_write_ecc (mtd, to, len, retlen, buf, NULL,NULL); } static int nand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, const u_char *oob_buf, struct nand_oobinfo *dummy) { int start, page; int n = len; int nToCopy; /* Do not allow reads past end of device */ if ((to + len) > mtd->size) { *retlen = 0; return -EINVAL; } /* Initialize return value */ *retlen = 0; while (n > 0) { /* First we calculate the starting page */ page = to >> NAND_SHIFT; /* Get raw starting column */ start = to & (mtd->oobblock - 1); // OK now check for the curveball where the start and end are in // the same page if((start + n) < mtd->oobblock) nToCopy = n; else nToCopy = mtd->oobblock - start; nandemul2k_Program(buf, page, start, nToCopy); nandemul2k_Program(oob_buf, page, PAGE_DATA_SIZE, PAGE_SPARE_SIZE); n -= nToCopy; to += nToCopy; buf += nToCopy; if(oob_buf) oob_buf += PAGE_SPARE_SIZE; *retlen += nToCopy; } return 0; } static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { int col, page; printk("nand_read_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int)len); /* Shift to get page */ page = ((int) to) >> NAND_SHIFT; /* Mask to get column */ col = to & 0x0f; /* Initialize return length value */ *retlen = 0; /* Do not allow reads past end of device */ if ((to + len) > mtd->size) { printk("nand_read_oob: Attempt read beyond end of device\n"); *retlen = 0; return -EINVAL; } nandemul2k_Program(buf, page, 512 + col, len); /* Return happy */ *retlen = len; return 0; } /* * NAND write with iovec */ static int nand_writev (struct mtd_info *mtd, const struct iovec *vecs, unsigned long count, loff_t to, size_t *retlen) { return -EINVAL; } /* * NAND erase a block */ static int nand_erase (struct mtd_info *mtd, struct erase_info *instr) { int i, nBlocks,block; printk("nand_erase: start = 0x%08x, len = %i\n", (unsigned int) instr->addr, (unsigned int) instr->len); /* Start address must align on block boundary */ if (instr->addr & (mtd->erasesize - 1)) { printk("nand_erase: Unaligned address\n"); return -EINVAL; } /* Length must align on block boundary */ if (instr->len & (mtd->erasesize - 1)) { printk("nand_erase: Length not block aligned\n"); return -EINVAL; } /* Do not allow erase past end of device */ if ((instr->len + instr->addr) > mtd->size) { printk("nand_erase: Erase past end of device\n"); return -EINVAL; } nBlocks = instr->len >> (NAND_SHIFT + 5); block = instr->addr >> (NAND_SHIFT + 5); for (i = 0; i < nBlocks; i++) { nandemul2k_DoErase(block); block++; } return 0; } int nand_block_isbad(struct mtd_info *mtd,int blockNo) { return 0; } int nand_block_markbad(struct mtd_info *mtd, int blockNo) { return 0; } /* * NAND sync */ static void nand_sync (struct mtd_info *mtd) { printk("nand_sync: called\n"); } /* * Scan for the NAND device */ static int nand_scan(struct mtd_info *mtd, int nchips) { mtd->oobblock = PAGE_DATA_SIZE; mtd->oobsize = PAGE_SPARE_SIZE; mtd->erasesize = PAGE_DATA_SIZE * PAGES_PER_BLOCK; mtd->size = sizeInMB << 20; /* Fill in remaining MTD driver data */ mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; mtd->owner = THIS_MODULE; mtd->ecctype = MTD_ECC_NONE; mtd->erase = nand_erase; mtd->point = NULL; mtd->unpoint = NULL; mtd->read = nand_read; mtd->write = nand_write; mtd->read_ecc = nand_read_ecc; mtd->write_ecc = nand_write_ecc; mtd->read_oob = nand_read_oob; mtd->write_oob = nand_write_oob; mtd->block_isbad = nand_block_isbad; mtd->block_markbad = nand_block_markbad; mtd->readv = NULL; mtd->writev = nand_writev; mtd->sync = nand_sync; mtd->lock = NULL; mtd->unlock = NULL; mtd->suspend = NULL; mtd->resume = NULL; /* Return happy */ return 0; } #if 0 #ifdef MODULE MODULE_PARM(sizeInMB, "i"); __setup("sizeInMB=",sizeInMB); #endif #endif /* * Define partitions for flash devices */ static struct mtd_partition nandemul2k_partition[] = { { name: "NANDemul partition 1", offset: 0, size: 0 }, }; static int nPartitions = sizeof(nandemul2k_partition)/sizeof(nandemul2k_partition[0]); static int __init nandemul2k_init (void) { /* Do the nand init */ CheckInit(); nand_scan(&nandemul2k_mtd, 1); /* Build the partition table */ nandemul2k_partition[0].size = sizeInMB * 1024 * 1024; /* Register the partition */ add_mtd_partitions(&nandemul2k_mtd, nandemul2k_partition, nPartitions); return 0; } static void __exit nandemul2k_cleanup(void) { nandemul2k_CleanUp(); del_mtd_partitions(&nandemul2k_mtd); /* Unregister partitions */ del_mtd_device(&nandemul2k_mtd); /* Unregister the device */ } module_init(nandemul2k_init); module_exit(nandemul2k_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Charles Manning "); MODULE_AUTHOR("Coywolf Qi Hunt "); MODULE_DESCRIPTION("2k Page/128k Block NAND emulated in RAM");