diff options
Diffstat (limited to 'linux-2.4.x/drivers/mtd/devices')
19 files changed, 5027 insertions, 1962 deletions
diff --git a/linux-2.4.x/drivers/mtd/devices/Config.in b/linux-2.4.x/drivers/mtd/devices/Config.in index 92316b4..717e67a 100644 --- a/linux-2.4.x/drivers/mtd/devices/Config.in +++ b/linux-2.4.x/drivers/mtd/devices/Config.in @@ -1,6 +1,6 @@ -# drivers/mtd/maps/Config.in +# drivers/mtd/devices/Config.in -# $Id: Config.in,v 1.5 2001/09/23 15:33:10 dwmw2 Exp $ +# $Id: Config.in,v 1.15 2004/10/01 21:47:13 gleixner Exp $ mainmenu_option next_comment @@ -10,12 +10,12 @@ if [ "$CONFIG_MTD_PMC551" = "y" -o "$CONFIG_MTD_PMC551" = "m" ]; then bool ' PMC551 256M DRAM Bugfix' CONFIG_MTD_PMC551_BUGFIX bool ' PMC551 Debugging' CONFIG_MTD_PMC551_DEBUG fi -if [ "$CONFIG_DECSTATION" = "y" ]; then - dep_tristate ' DEC MS02-NV NVRAM module support' CONFIG_MTD_MS02NV $CONFIG_MTD $CONFIG_DECSTATION +if [ "$CONFIG_MACH__DECSTATION" = "y" ]; then + dep_tristate ' DEC MS02-NV NVRAM module support' CONFIG_MTD_MS02NV $CONFIG_MTD fi dep_tristate ' Uncached system RAM' CONFIG_MTD_SLRAM $CONFIG_MTD if [ "$CONFIG_SA1100_LART" = "y" ]; then - dep_tristate ' 28F160xx flash driver for LART' CONFIG_MTD_LART $CONFIG_MTD + dep_tristate ' 28F160xx flash driver for LART' CONFIG_MTD_LART $CONFIG_MTD fi dep_tristate ' Test driver using RAM' CONFIG_MTD_MTDRAM $CONFIG_MTD if [ "$CONFIG_MTD_MTDRAM" = "y" -o "$CONFIG_MTD_MTDRAM" = "m" ]; then @@ -28,19 +28,29 @@ fi dep_tristate ' MTD emulation using block device' CONFIG_MTD_BLKMTD $CONFIG_MTD comment 'Disk-On-Chip Device Drivers' - dep_tristate ' M-Systems Disk-On-Chip 1000' CONFIG_MTD_DOC1000 $CONFIG_MTD - dep_tristate ' M-Systems Disk-On-Chip 2000 and Millennium' CONFIG_MTD_DOC2000 $CONFIG_MTD - dep_tristate ' M-Systems Disk-On-Chip Millennium-only alternative driver (see help)' CONFIG_MTD_DOC2001 $CONFIG_MTD - if [ "$CONFIG_MTD_DOC2001" = "y" -o "$CONFIG_MTD_DOC2000" = "y" ]; then + dep_tristate ' M-Systems Disk-On-Chip 2000 and Millennium (DEPRECATED)' CONFIG_MTD_DOC2000 $CONFIG_MTD + dep_tristate ' M-Systems Disk-On-Chip Millennium-only alternative driver (DEPRECATED)' CONFIG_MTD_DOC2001 $CONFIG_MTD + dep_tristate ' M-Systems Disk-On-Chip Millennium Plus driver (see help)' CONFIG_MTD_DOC2001PLUS $CONFIG_MTD + if [ "$CONFIG_MTD_DOC2001PLUS" = "y" -o "$CONFIG_MTD_DOC2001" = "y" -o "$CONFIG_MTD_DOC2000" = "y" ]; then define_bool CONFIG_MTD_DOCPROBE y else - if [ "$CONFIG_MTD_DOC2001" = "m" -o "$CONFIG_MTD_DOC2000" = "m" ]; then + if [ "$CONFIG_MTD_DOC2001PLUS" = "m" -o "$CONFIG_MTD_DOC2001" = "m" -o "$CONFIG_MTD_DOC2000" = "m" ]; then define_bool CONFIG_MTD_DOCPROBE m else define_bool CONFIG_MTD_DOCPROBE n fi fi + if [ "$CONFIG_MTD_DOCPROBE" = "y" ]; then + define_bool CONFIG_MTD_DOCECC y + else + if [ "$CONFIG_MTD_DOCPROBE" = "m" ]; then + define_bool CONFIG_MTD_DOCECC m + else + define_bool CONFIG_MTD_DOCECC n + fi + fi + if [ "$CONFIG_MTD_DOCPROBE" = "y" -o "$CONFIG_MTD_DOCPROBE" = "m" ]; then bool ' Advanced detection options for DiskOnChip' CONFIG_MTD_DOCPROBE_ADVANCED if [ "$CONFIG_MTD_DOCPROBE_ADVANCED" = "n" ]; then diff --git a/linux-2.4.x/drivers/mtd/devices/Makefile b/linux-2.4.x/drivers/mtd/devices/Makefile index f86ddf2..9deee09 100644 --- a/linux-2.4.x/drivers/mtd/devices/Makefile +++ b/linux-2.4.x/drivers/mtd/devices/Makefile @@ -1,26 +1,23 @@ # -# linux/drivers/devices/Makefile +# linux/drivers/maps/Makefile.24 +# Makefile for obsolete kernels # -# $Id: Makefile,v 1.4 2001/06/26 21:10:05 spse Exp $ +# $Id: Makefile.24,v 1.2 2004/08/09 18:46:04 dmarlin Exp $ O_TARGET := devlink.o +export-objs := docecc.o -# *** BIG UGLY NOTE *** -# -# The removal of get_module_symbol() and replacement with -# inter_module_register() et al has introduced a link order dependency -# here where previously there was none. We now have to ensure that -# doc200[01].o are linked before docprobe.o - -obj-$(CONFIG_MTD_DOC1000) += doc1000.o -obj-$(CONFIG_MTD_DOC2000) += doc2000.o -obj-$(CONFIG_MTD_DOC2001) += doc2001.o -obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o docecc.o -obj-$(CONFIG_MTD_SLRAM) += slram.o -obj-$(CONFIG_MTD_PMC551) += pmc551.o -obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o -obj-$(CONFIG_MTD_MTDRAM) += mtdram.o -obj-$(CONFIG_MTD_LART) += lart.o -obj-$(CONFIG_MTD_BLKMTD) += blkmtd.o +obj-$(CONFIG_MTD_DOC2000) += doc2000.o +obj-$(CONFIG_MTD_DOC2001) += doc2001.o +obj-$(CONFIG_MTD_DOC2001PLUS) += doc2001plus.o +obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o +obj-$(CONFIG_MTD_DOCECC) += docecc.o +obj-$(CONFIG_MTD_SLRAM) += slram.o +obj-$(CONFIG_MTD_PHRAM) += phram.o +obj-$(CONFIG_MTD_PMC551) += pmc551.o +obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o +obj-$(CONFIG_MTD_MTDRAM) += mtdram.o +obj-$(CONFIG_MTD_LART) += lart.o +obj-$(CONFIG_MTD_BLKMTD) += blkmtd-24.o include $(TOPDIR)/Rules.make diff --git a/linux-2.4.x/drivers/mtd/devices/Makefile.common b/linux-2.4.x/drivers/mtd/devices/Makefile.common new file mode 100644 index 0000000..c77294e --- /dev/null +++ b/linux-2.4.x/drivers/mtd/devices/Makefile.common @@ -0,0 +1,26 @@ +# +# linux/drivers/devices/Makefile +# +# $Id: Makefile.common,v 1.8 2005/05/13 09:45:48 joern Exp $ + +# *** BIG UGLY NOTE *** +# +# The removal of get_module_symbol() and replacement with +# inter_module_register() et al has introduced a link order dependency +# here where previously there was none. We now have to ensure that +# doc200[01].o are linked before docprobe.o + +obj-$(CONFIG_MTD_DOC2000) += doc2000.o +obj-$(CONFIG_MTD_DOC2001) += doc2001.o +obj-$(CONFIG_MTD_DOC2001PLUS) += doc2001plus.o +obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o +obj-$(CONFIG_MTD_DOCECC) += docecc.o +obj-$(CONFIG_MTD_SLRAM) += slram.o +obj-$(CONFIG_MTD_PHRAM) += phram.o +obj-$(CONFIG_MTD_PMC551) += pmc551.o +obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o +obj-$(CONFIG_MTD_MTDRAM) += mtdram.o +obj-$(CONFIG_MTD_LART) += lart.o +obj-$(CONFIG_MTD_BLKMTD) += blkmtd.o +obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o +obj-$(CONFIG_RAMTD) += ramtd.o diff --git a/linux-2.4.x/drivers/mtd/devices/blkmtd-24.c b/linux-2.4.x/drivers/mtd/devices/blkmtd-24.c new file mode 100644 index 0000000..1672fc5 --- /dev/null +++ b/linux-2.4.x/drivers/mtd/devices/blkmtd-24.c @@ -0,0 +1,1056 @@ +/* + * $Id: blkmtd-24.c,v 1.24 2005/11/07 11:14:24 gleixner Exp $ + * + * blkmtd.c - use a block device as a fake MTD + * + * Author: Simon Evans <spse@secret.org.uk> + * + * Copyright (C) 2001,2002 Simon Evans + * + * Licence: GPL + * + * How it works: + * The driver uses raw/io to read/write the device and the page + * cache to cache access. Writes update the page cache with the + * new data and mark it dirty and add the page into a kiobuf. + * When the kiobuf becomes full or the next extry is to an earlier + * block in the kiobuf then it is flushed to disk. This allows + * writes to remained ordered and gives a small and simple outgoing + * write cache. + * + * It can be loaded Read-Only to prevent erases and writes to the + * medium. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/blkdev.h> +#include <linux/iobuf.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/list.h> +#include <linux/mtd/mtd.h> + +#ifdef CONFIG_MTD_DEBUG +#ifdef CONFIG_PROC_FS +# include <linux/proc_fs.h> +# define BLKMTD_PROC_DEBUG + static struct proc_dir_entry *blkmtd_proc; +#endif +#endif + + +#define err(format, arg...) printk(KERN_ERR "blkmtd: " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO "blkmtd: " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "blkmtd: " format "\n" , ## arg) +#define crit(format, arg...) printk(KERN_CRIT "blkmtd: " format "\n" , ## arg) + + +/* Default erase size in KiB, always make it a multiple of PAGE_SIZE */ +#define CONFIG_MTD_BLKDEV_ERASESIZE (128 << 10) /* 128KiB */ +#define VERSION "1.10" + +/* Info for the block device */ +struct blkmtd_dev { + struct list_head list; + struct block_device *binding; + struct mtd_info mtd_info; + struct kiobuf *rd_buf, *wr_buf; + long iobuf_locks; + struct semaphore wrbuf_mutex; +}; + + +/* Static info about the MTD, used in cleanup_module */ +static LIST_HEAD(blkmtd_device_list); + + +static void blkmtd_sync(struct mtd_info *mtd); + +#define MAX_DEVICES 4 + +/* Module parameters passed by insmod/modprobe */ +char *device[MAX_DEVICES]; /* the block device to use */ +int erasesz[MAX_DEVICES]; /* optional default erase size */ +int ro[MAX_DEVICES]; /* optional read only flag */ +int sync; + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Simon Evans <spse@secret.org.uk>"); +MODULE_DESCRIPTION("Emulate an MTD using a block device"); +MODULE_PARM(device, "1-4s"); +MODULE_PARM_DESC(device, "block device to use"); +MODULE_PARM(erasesz, "1-4i"); +MODULE_PARM_DESC(erasesz, "optional erase size to use in KiB. eg 4=4KiB."); +MODULE_PARM(ro, "1-4i"); +MODULE_PARM_DESC(ro, "1=Read only, writes and erases cause errors"); +MODULE_PARM(sync, "i"); +MODULE_PARM_DESC(sync, "1=Synchronous writes"); + + +/** + * read_pages - read in pages via the page cache + * @dev: device to read from + * @pagenrs: list of page numbers wanted + * @pagelst: storage for struce page * pointers + * @pages: count of pages wanted + * + * Read pages, getting them from the page cache if available + * else reading them in from disk if not. pagelst must be preallocated + * to hold the page count. + */ +static int read_pages(struct blkmtd_dev *dev, int pagenrs[], struct page **pagelst, int pages) +{ + kdev_t kdev; + struct page *page; + int cnt = 0; + struct kiobuf *iobuf; + int err = 0; + + if(!dev) { + err("read_pages: PANIC dev == NULL"); + return -EIO; + } + kdev = to_kdev_t(dev->binding->bd_dev); + + DEBUG(2, "read_pages: reading %d pages\n", pages); + if(test_and_set_bit(0, &dev->iobuf_locks)) { + err = alloc_kiovec(1, &iobuf); + if (err) { + crit("cant allocate kiobuf"); + return -ENOMEM; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) + iobuf->blocks = kmalloc(KIO_MAX_SECTORS * sizeof(unsigned long), GFP_KERNEL); + if(iobuf->blocks == NULL) { + crit("cant allocate iobuf blocks"); + free_kiovec(1, &iobuf); + return -ENOMEM; + } +#endif + } else { + iobuf = dev->rd_buf; + } + + iobuf->nr_pages = 0; + iobuf->length = 0; + iobuf->offset = 0; + iobuf->locked = 1; + + for(cnt = 0; cnt < pages; cnt++) { + page = grab_cache_page(dev->binding->bd_inode->i_mapping, pagenrs[cnt]); + pagelst[cnt] = page; + if(!Page_Uptodate(page)) { + iobuf->blocks[iobuf->nr_pages] = pagenrs[cnt]; + iobuf->maplist[iobuf->nr_pages++] = page; + } + } + + if(iobuf->nr_pages) { + iobuf->length = iobuf->nr_pages << PAGE_SHIFT; + err = brw_kiovec(READ, 1, &iobuf, kdev, iobuf->blocks, PAGE_SIZE); + DEBUG(3, "blkmtd: read_pages: finished, err = %d\n", err); + if(err < 0) { + while(pages--) { + ClearPageUptodate(pagelst[pages]); + unlock_page(pagelst[pages]); + page_cache_release(pagelst[pages]); + } + } else { + while(iobuf->nr_pages--) { + SetPageUptodate(iobuf->maplist[iobuf->nr_pages]); + } + err = 0; + } + } + + + if(iobuf != dev->rd_buf) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) + kfree(iobuf->blocks); +#endif + free_kiovec(1, &iobuf); + } else { + clear_bit(0, &dev->iobuf_locks); + } + DEBUG(2, "read_pages: done, err = %d\n", err); + return err; +} + + +/** + * commit_pages - commit pages in the writeout kiobuf to disk + * @dev: device to write to + * + * If the current dev has pages in the dev->wr_buf kiobuf, + * they are written to disk using brw_kiovec() + */ +static int commit_pages(struct blkmtd_dev *dev) +{ + struct kiobuf *iobuf = dev->wr_buf; + kdev_t kdev = to_kdev_t(dev->binding->bd_dev); + int err = 0; + + iobuf->length = iobuf->nr_pages << PAGE_SHIFT; + iobuf->locked = 1; + if(iobuf->length) { + int i; + DEBUG(2, "blkmtd: commit_pages: nrpages = %d\n", iobuf->nr_pages); + /* Check all the pages are dirty and lock them */ + for(i = 0; i < iobuf->nr_pages; i++) { + struct page *page = iobuf->maplist[i]; + BUG_ON(!PageDirty(page)); + lock_page(page); + } + err = brw_kiovec(WRITE, 1, &iobuf, kdev, iobuf->blocks, PAGE_SIZE); + DEBUG(3, "commit_write: committed %d pages err = %d\n", iobuf->nr_pages, err); + while(iobuf->nr_pages) { + struct page *page = iobuf->maplist[--iobuf->nr_pages]; + ClearPageDirty(page); + SetPageUptodate(page); + unlock_page(page); + page_cache_release(page); + } + } + + DEBUG(2, "blkmtd: sync: end, err = %d\n", err); + iobuf->offset = 0; + iobuf->nr_pages = 0; + iobuf->length = 0; + return err; +} + + +/** + * write_pages - write block of data to device via the page cache + * @dev: device to write to + * @buf: data source or NULL if erase (output is set to 0xff) + * @to: offset into output device + * @len: amount to data to write + * @retlen: amount of data written + * + * Grab pages from the page cache and fill them with the source data. + * Non page aligned start and end result in a readin of the page and + * part of the page being modified. Pages are added to the wr_buf kiobuf + * until this becomes full or the next page written to has a lower pagenr + * then the current max pagenr in the kiobuf. + */ +static int write_pages(struct blkmtd_dev *dev, const u_char *buf, loff_t to, + size_t len, int *retlen) +{ + int pagenr, offset; + size_t start_len = 0, end_len; + int pagecnt = 0; + struct kiobuf *iobuf = dev->wr_buf; + int err = 0; + struct page *pagelst[2]; + int pagenrs[2]; + int readpages = 0; + int ignorepage = -1; + + pagenr = to >> PAGE_SHIFT; + offset = to & ~PAGE_MASK; + + DEBUG(2, "blkmtd: write_pages: buf = %p to = %ld len = %zd pagenr = %d offset = %d\n", + buf, (long)to, len, pagenr, offset); + + *retlen = 0; + /* see if we have to do a partial write at the start */ + if(offset) { + start_len = ((offset + len) > PAGE_SIZE) ? PAGE_SIZE - offset : len; + len -= start_len; + } + + /* calculate the length of the other two regions */ + end_len = len & ~PAGE_MASK; + len -= end_len; + + if(start_len) { + pagenrs[0] = pagenr; + readpages++; + pagecnt++; + } + if(len) + pagecnt += len >> PAGE_SHIFT; + if(end_len) { + pagenrs[readpages] = pagenr + pagecnt; + readpages++; + pagecnt++; + } + + DEBUG(3, "blkmtd: write: start_len = %zd len = %zd end_len = %zd pagecnt = %d\n", + start_len, len, end_len, pagecnt); + + down(&dev->wrbuf_mutex); + + if(iobuf->nr_pages && ((pagenr <= iobuf->blocks[iobuf->nr_pages-1]) + || (iobuf->nr_pages + pagecnt) >= KIO_STATIC_PAGES)) { + + if((pagenr == iobuf->blocks[iobuf->nr_pages-1]) + && ((iobuf->nr_pages + pagecnt) < KIO_STATIC_PAGES)) { + iobuf->nr_pages--; + ignorepage = pagenr; + } else { + DEBUG(3, "blkmtd: doing writeout pagenr = %d max_pagenr = %ld pagecnt = %d idx = %d\n", + pagenr, iobuf->blocks[iobuf->nr_pages-1], + pagecnt, iobuf->nr_pages); + commit_pages(dev); + } + } + + if(readpages) { + err = read_pages(dev, pagenrs, pagelst, readpages); + if(err < 0) + goto readin_err; + } + + if(start_len) { + /* do partial start region */ + struct page *page; + + DEBUG(3, "blkmtd: write: doing partial start, page = %d len = %zd offset = %d\n", + pagenr, start_len, offset); + page = pagelst[0]; + BUG_ON(!buf); + if(PageDirty(page) && pagenr != ignorepage) { + err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d ignorepage = %d\n", + to, start_len, len, end_len, pagenr, ignorepage); + BUG(); + } + memcpy(page_address(page)+offset, buf, start_len); + SetPageDirty(page); + SetPageUptodate(page); + unlock_page(page); + buf += start_len; + *retlen = start_len; + err = 0; + iobuf->blocks[iobuf->nr_pages] = pagenr++; + iobuf->maplist[iobuf->nr_pages] = page; + iobuf->nr_pages++; + } + + /* Now do the main loop to a page aligned, n page sized output */ + if(len) { + int pagesc = len >> PAGE_SHIFT; + DEBUG(3, "blkmtd: write: whole pages start = %d, count = %d\n", + pagenr, pagesc); + while(pagesc) { + struct page *page; + + /* see if page is in the page cache */ + DEBUG(3, "blkmtd: write: grabbing page %d from page cache\n", pagenr); + page = grab_cache_page(dev->binding->bd_inode->i_mapping, pagenr); + if(PageDirty(page) && pagenr != ignorepage) { + BUG(); + } + if(!page) { + warn("write: cant grab cache page %d", pagenr); + err = -ENOMEM; + goto write_err; + } + if(!buf) { + memset(page_address(page), 0xff, PAGE_SIZE); + } else { + memcpy(page_address(page), buf, PAGE_SIZE); + buf += PAGE_SIZE; + } + iobuf->blocks[iobuf->nr_pages] = pagenr++; + iobuf->maplist[iobuf->nr_pages] = page; + iobuf->nr_pages++; + SetPageDirty(page); + SetPageUptodate(page); + unlock_page(page); + pagesc--; + *retlen += PAGE_SIZE; + } + } + + if(end_len) { + /* do the third region */ + struct page *page; + DEBUG(3, "blkmtd: write: doing partial end, page = %d len = %zd\n", + pagenr, end_len); + page = pagelst[readpages-1]; + BUG_ON(!buf); + if(PageDirty(page) && pagenr != ignorepage) { + err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d ignorepage = %d\n", + to, start_len, len, end_len, pagenr, ignorepage); + BUG(); + } + memcpy(page_address(page), buf, end_len); + SetPageDirty(page); + SetPageUptodate(page); + unlock_page(page); + DEBUG(3, "blkmtd: write: writing out partial end\n"); + *retlen += end_len; + err = 0; + iobuf->blocks[iobuf->nr_pages] = pagenr; + iobuf->maplist[iobuf->nr_pages] = page; + iobuf->nr_pages++; + } + + DEBUG(2, "blkmtd: write: end, retlen = %zd, err = %d\n", *retlen, err); + + if(sync) { +write_err: + commit_pages(dev); + } + +readin_err: + up(&dev->wrbuf_mutex); + return err; +} + + +/* erase a specified part of the device */ +static int blkmtd_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct blkmtd_dev *dev = mtd->priv; + struct mtd_erase_region_info *einfo = mtd->eraseregions; + int numregions = mtd->numeraseregions; + size_t from; + u_long len; + int err = -EIO; + size_t retlen; + + /* check readonly */ + if(!dev->wr_buf) { + err("error: mtd%d trying to erase readonly device %s", + mtd->index, mtd->name); + instr->state = MTD_ERASE_FAILED; + goto erase_callback; + } + + instr->state = MTD_ERASING; + from = instr->addr; + len = instr->len; + + /* check erase region has valid start and length */ + DEBUG(2, "blkmtd: erase: dev = `%s' from = 0x%zx len = 0x%lx\n", + bdevname(dev->binding->bd_dev), from, len); + while(numregions) { + DEBUG(3, "blkmtd: checking erase region = 0x%08X size = 0x%X num = 0x%x\n", + einfo->offset, einfo->erasesize, einfo->numblocks); + if(from >= einfo->offset + && from < einfo->offset + (einfo->erasesize * einfo->numblocks)) { + if(len == einfo->erasesize + && ( (from - einfo->offset) % einfo->erasesize == 0)) + break; + } + numregions--; + einfo++; + } + + if(!numregions) { + /* Not a valid erase block */ + err("erase: invalid erase request 0x%lX @ 0x%08zX", len, from); + instr->state = MTD_ERASE_FAILED; + err = -EIO; + } + + if(instr->state != MTD_ERASE_FAILED) { + /* do the erase */ + DEBUG(3, "Doing erase from = %zd len = %ld\n", from, len); + err = write_pages(dev, NULL, from, len, &retlen); + if(err < 0) { + err("erase failed err = %d", err); + instr->state = MTD_ERASE_FAILED; + } else { + instr->state = MTD_ERASE_DONE; + err = 0; + } + } + + DEBUG(3, "blkmtd: erase: checking callback\n"); + erase_callback: + mtd_erase_callback(instr); + DEBUG(2, "blkmtd: erase: finished (err = %d)\n", err); + return err; +} + + +/* read a range of the data via the page cache */ +static int blkmtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct blkmtd_dev *dev = mtd->priv; + int err = 0; + int offset; + int pagenr, pages; + struct page **pagelst; + int *pagenrs; + int i; + + *retlen = 0; + + DEBUG(2, "blkmtd: read: dev = `%s' from = %lld len = %zd buf = %p\n", + bdevname(dev->binding->bd_dev), from, len, buf); + + pagenr = from >> PAGE_SHIFT; + offset = from - (pagenr << PAGE_SHIFT); + + pages = (offset+len+PAGE_SIZE-1) >> PAGE_SHIFT; + DEBUG(3, "blkmtd: read: pagenr = %d offset = %d, pages = %d\n", + pagenr, offset, pages); + + pagelst = kmalloc(sizeof(struct page *) * pages, GFP_KERNEL); + if(!pagelst) + return -ENOMEM; + pagenrs = kmalloc(sizeof(int) * pages, GFP_KERNEL); + if(!pagenrs) { + kfree(pagelst); + return -ENOMEM; + } + for(i = 0; i < pages; i++) + pagenrs[i] = pagenr+i; + + err = read_pages(dev, pagenrs, pagelst, pages); + if(err) + goto readerr; + + pagenr = 0; + while(pages) { + struct page *page; + int cpylen; + + DEBUG(3, "blkmtd: read: looking for page: %d\n", pagenr); + page = pagelst[pagenr]; + + cpylen = (PAGE_SIZE > len) ? len : PAGE_SIZE; + if(offset+cpylen > PAGE_SIZE) + cpylen = PAGE_SIZE-offset; + + memcpy(buf + *retlen, page_address(page) + offset, cpylen); + offset = 0; + len -= cpylen; + *retlen += cpylen; + pagenr++; + pages--; + unlock_page(page); + if(!PageDirty(page)) + page_cache_release(page); + } + + readerr: + kfree(pagelst); + kfree(pagenrs); + DEBUG(2, "blkmtd: end read: retlen = %zd, err = %d\n", *retlen, err); + return err; +} + + +/* write data to the underlying device */ +static int blkmtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct blkmtd_dev *dev = mtd->priv; + int err; + + *retlen = 0; + if(!len) + return 0; + + DEBUG(2, "blkmtd: write: dev = `%s' to = %lld len = %zd buf = %p\n", + bdevname(dev->binding->bd_dev), to, len, buf); + + /* handle readonly and out of range numbers */ + + if(!dev->wr_buf) { + err("error: trying to write to a readonly device %s", mtd->name); + return -EROFS; + } + + if(to >= mtd->size) { + return -ENOSPC; + } + + if(to + len > mtd->size) { + len = (mtd->size - to); + } + + err = write_pages(dev, buf, to, len, retlen); + if(err < 0) + *retlen = 0; + else + err = 0; + DEBUG(2, "blkmtd: write: end, err = %d\n", err); + return err; +} + + +/* sync the device - wait until the write queue is empty */ +static void blkmtd_sync(struct mtd_info *mtd) +{ + struct blkmtd_dev *dev = mtd->priv; + struct kiobuf *iobuf = dev->wr_buf; + + DEBUG(2, "blkmtd: sync: called\n"); + if(iobuf == NULL) + return; + + DEBUG(3, "blkmtd: kiovec: length = %d nr_pages = %d\n", + iobuf->length, iobuf->nr_pages); + down(&dev->wrbuf_mutex); + if(iobuf->nr_pages) + commit_pages(dev); + up(&dev->wrbuf_mutex); +} + + +#ifdef BLKMTD_PROC_DEBUG +/* procfs stuff */ +static int blkmtd_proc_read(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + struct list_head *temp1, *temp2; + + MOD_INC_USE_COUNT; + + /* Count the size of the page lists */ + + len = sprintf(page, "dev\twr_idx\tmax_idx\tnrpages\tclean\tdirty\tlocked\tlru\n"); + list_for_each_safe(temp1, temp2, &blkmtd_device_list) { + struct blkmtd_dev *dev = list_entry(temp1, struct blkmtd_dev, + list); + struct list_head *temp; + struct page *pagei; + + int clean = 0, dirty = 0, locked = 0, lru = 0; + /* Count the size of the page lists */ + list_for_each(temp, &dev->binding->bd_inode->i_mapping->clean_pages) { + pagei = list_entry(temp, struct page, list); + clean++; + if(PageLocked(pagei)) + locked++; + if(PageDirty(pagei)) + dirty++; + if(PageLRU(pagei)) + lru++; + } + list_for_each(temp, &dev->binding->bd_inode->i_mapping->dirty_pages) { + pagei = list_entry(temp, struct page, list); + if(PageLocked(pagei)) + locked++; + if(PageDirty(pagei)) + dirty++; + if(PageLRU(pagei)) + lru++; + } + list_for_each(temp, &dev->binding->bd_inode->i_mapping->locked_pages) { + pagei = list_entry(temp, struct page, list); + if(PageLocked(pagei)) + locked++; + if(PageDirty(pagei)) + dirty++; + if(PageLRU(pagei)) + lru++; + } + + len += sprintf(page+len, "mtd%d:\t%ld\t%d\t%ld\t%d\t%d\t%d\t%d\n", + dev->mtd_info.index, + (dev->wr_buf && dev->wr_buf->nr_pages) ? + dev->wr_buf->blocks[dev->wr_buf->nr_pages-1] : 0, + (dev->wr_buf) ? dev->wr_buf->nr_pages : 0, + dev->binding->bd_inode->i_mapping->nrpages, + clean, dirty, locked, lru); + } + + if(len <= count) + *eof = 1; + + MOD_DEC_USE_COUNT; + return len; +} +#endif + + +static void free_device(struct blkmtd_dev *dev) +{ + DEBUG(2, "blkmtd: free_device() dev = %p\n", dev); + if(dev) { + del_mtd_device(&dev->mtd_info); + info("mtd%d: [%s] removed", dev->mtd_info.index, + dev->mtd_info.name + strlen("blkmtd: ")); + if(dev->mtd_info.eraseregions) + kfree(dev->mtd_info.eraseregions); + if(dev->mtd_info.name) + kfree(dev->mtd_info.name); + + if(dev->rd_buf) { + dev->rd_buf->locked = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) + if(dev->rd_buf->blocks) + kfree(dev->rd_buf->blocks); +#endif + free_kiovec(1, &dev->rd_buf); + } + if(dev->wr_buf) { + dev->wr_buf->locked = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) + if(dev->wr_buf->blocks) + kfree(dev->rw_buf->blocks); +#endif + free_kiovec(1, &dev->wr_buf); + } + + if(dev->binding) { + kdev_t kdev = to_kdev_t(dev->binding->bd_dev); + invalidate_inode_pages(dev->binding->bd_inode); + set_blocksize(kdev, 1 << 10); + blkdev_put(dev->binding, BDEV_RAW); + } + kfree(dev); + } +} + + +/* For a given size and initial erase size, calculate the number + * and size of each erase region. Goes round the loop twice, + * once to find out how many regions, then allocates space, + * then round the loop again to fill it in. + */ +static struct mtd_erase_region_info *calc_erase_regions( + size_t erase_size, size_t total_size, int *regions) +{ + struct mtd_erase_region_info *info = NULL; + + DEBUG(2, "calc_erase_regions, es = %zd size = %zd regions = %d\n", + erase_size, total_size, *regions); + /* Make any user specified erasesize be a power of 2 + and at least PAGE_SIZE */ + if(erase_size) { + int es = erase_size; + erase_size = 1; + while(es != 1) { + es >>= 1; + erase_size <<= 1; + } + if(erase_size < PAGE_SIZE) + erase_size = PAGE_SIZE; + } else { + erase_size = CONFIG_MTD_BLKDEV_ERASESIZE; + } + + *regions = 0; + + do { + int tot_size = total_size; + int er_size = erase_size; + int count = 0, offset = 0, regcnt = 0; + + while(tot_size) { + count = tot_size / er_size; + if(count) { + tot_size = tot_size % er_size; + if(info) { + DEBUG(2, "adding to erase info off=%d er=%d cnt=%d\n", + offset, er_size, count); + (info+regcnt)->offset = offset; + (info+regcnt)->erasesize = er_size; + (info+regcnt)->numblocks = count; + (*regions)++; + } + regcnt++; + offset += (count * er_size); + } + while(er_size > tot_size) + er_size >>= 1; + } + if(info == NULL) { + info = kmalloc(regcnt * sizeof(struct mtd_erase_region_info), GFP_KERNEL); + if(!info) + break; + } + } while(!(*regions)); + DEBUG(2, "calc_erase_regions done, es = %zd size = %zd regions = %d\n", + erase_size, total_size, *regions); + return info; +} + + +extern kdev_t name_to_kdev_t(char *line) __init; + + +static struct blkmtd_dev *add_device(char *devname, int readonly, int erase_size) +{ + int maj, min; + kdev_t kdev; + int mode; + struct blkmtd_dev *dev; + +#ifdef MODULE + struct file *file = NULL; + struct inode *inode; +#endif + + if(!devname) + return NULL; + + /* Get a handle on the device */ + mode = (readonly) ? O_RDONLY : O_RDWR; + +#ifdef MODULE + + file = filp_open(devname, mode, 0); + if(IS_ERR(file)) { + err("error: cant open device %s", devname); + DEBUG(2, "blkmtd: filp_open returned %ld\n", PTR_ERR(file)); + return NULL; + } + + /* determine is this is a block device and + * if so get its major and minor numbers + */ + inode = file->f_dentry->d_inode; + if(!S_ISBLK(inode->i_mode)) { + err("%s not a block device", devname); + filp_close(file, NULL); + return NULL; + } + kdev = inode->i_rdev; + filp_close(file, NULL); +#else + kdev = name_to_kdev_t(devname); +#endif /* MODULE */ + + if(!kdev) { + err("bad block device: `%s'", devname); + return NULL; + } + + maj = MAJOR(kdev); + min = MINOR(kdev); + DEBUG(1, "blkmtd: found a block device major = %d, minor = %d\n", + maj, min); + + if(maj == MTD_BLOCK_MAJOR) { + err("attempting to use an MTD device as a block device"); + return NULL; + } + + DEBUG(1, "blkmtd: devname = %s\n", bdevname(kdev)); + + dev = kmalloc(sizeof(struct blkmtd_dev), GFP_KERNEL); + if(dev == NULL) + return NULL; + + memset(dev, 0, sizeof(struct blkmtd_dev)); + if(alloc_kiovec(1, &dev->rd_buf)) { + err("cant allocate read iobuf"); + goto devinit_err; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) + dev->rd_buf->blocks = kmalloc(KIO_MAX_SECTORS * sizeof(unsigned long), GFP_KERNEL); + if(dev->rd_buf->blocks == NULL) { + crit("cant allocate rd_buf blocks"); + goto devinit_err; + } +#endif + + if(!readonly) { + if(alloc_kiovec(1, &dev->wr_buf)) { + err("cant allocate kiobuf - readonly enabled"); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) + } else { + dev->wr_buf->blocks = kmalloc(KIO_MAX_SECTORS * sizeof(unsigned long), GFP_KERNEL); + if(dev->wr_buf->blocks == NULL) { + crit("cant allocate wr_buf blocks - readonly enabled"); + free_kiovec(1, &iobuf); + } +#endif + } + if(dev->wr_buf) + init_MUTEX(&dev->wrbuf_mutex); + } + + /* get the block device */ + dev->binding = bdget(kdev_t_to_nr(MKDEV(maj, min))); + if(blkdev_get(dev->binding, mode, 0, BDEV_RAW)) + goto devinit_err; + + if(set_blocksize(kdev, PAGE_SIZE)) { + err("cant set block size to PAGE_SIZE on %s", bdevname(kdev)); + goto devinit_err; + } + + dev->mtd_info.size = dev->binding->bd_inode->i_size & PAGE_MASK; + + /* Setup the MTD structure */ + /* make the name contain the block device in */ + dev->mtd_info.name = kmalloc(sizeof("blkmtd: ") + strlen(devname), GFP_KERNEL); + if(dev->mtd_info.name == NULL) + goto devinit_err; + + sprintf(dev->mtd_info.name, "blkmtd: %s", devname); + dev->mtd_info.eraseregions = calc_erase_regions(erase_size, dev->mtd_info.size, + &dev->mtd_info.numeraseregions); + if(dev->mtd_info.eraseregions == NULL) + goto devinit_err; + + dev->mtd_info.erasesize = dev->mtd_info.eraseregions->erasesize; + DEBUG(1, "blkmtd: init: found %d erase regions\n", + dev->mtd_info.numeraseregions); + + if(readonly) { + dev->mtd_info.type = MTD_ROM; + dev->mtd_info.flags = MTD_CAP_ROM; + } else { + dev->mtd_info.type = MTD_RAM; + dev->mtd_info.flags = MTD_CAP_RAM; + } + dev->mtd_info.erase = blkmtd_erase; + dev->mtd_info.read = blkmtd_read; + dev->mtd_info.write = blkmtd_write; + dev->mtd_info.sync = blkmtd_sync; + dev->mtd_info.point = 0; + dev->mtd_info.unpoint = 0; + dev->mtd_info.priv = dev; + dev->mtd_info.owner = THIS_MODULE; + + list_add(&dev->list, &blkmtd_device_list); + if (add_mtd_device(&dev->mtd_info)) { + /* Device didnt get added, so free the entry */ + list_del(&dev->list); + free_device(dev); + return NULL; + } else { + info("mtd%d: [%s] erase_size = %dKiB %s", + dev->mtd_info.index, dev->mtd_info.name + strlen("blkmtd: "), + dev->mtd_info.erasesize >> 10, + (dev->wr_buf) ? "" : "(read-only)"); + } + + return dev; + + devinit_err: + free_device(dev); + return NULL; +} + + +/* Cleanup and exit - sync the device and kill of the kernel thread */ +static void __devexit cleanup_blkmtd(void) +{ + struct list_head *temp1, *temp2; +#ifdef BLKMTD_PROC_DEBUG + if(blkmtd_proc) { + remove_proc_entry("blkmtd_debug", NULL); + } +#endif + + /* Remove the MTD devices */ + list_for_each_safe(temp1, temp2, &blkmtd_device_list) { + struct blkmtd_dev *dev = list_entry(temp1, struct blkmtd_dev, + list); + blkmtd_sync(&dev->mtd_info); + free_device(dev); + } +} + +#ifndef MODULE + +/* Handle kernel boot params */ + + +static int __init param_blkmtd_device(char *str) +{ + int i; + + for(i = 0; i < MAX_DEVICES; i++) { + device[i] = str; + DEBUG(2, "blkmtd: device setup: %d = %s\n", i, device[i]); + strsep(&str, ","); + } + return 1; +} + + +static int __init param_blkmtd_erasesz(char *str) +{ + int i; + for(i = 0; i < MAX_DEVICES; i++) { + char *val = strsep(&str, ","); + if(val) + erasesz[i] = simple_strtoul(val, NULL, 0); + DEBUG(2, "blkmtd: erasesz setup: %d = %d\n", i, erasesz[i]); + } + + return 1; +} + + +static int __init param_blkmtd_ro(char *str) +{ + int i; + for(i = 0; i < MAX_DEVICES; i++) { + char *val = strsep(&str, ","); + if(val) + ro[i] = simple_strtoul(val, NULL, 0); + DEBUG(2, "blkmtd: ro setup: %d = %d\n", i, ro[i]); + } + + return 1; +} + + +static int __init param_blkmtd_sync(char *str) +{ + if(str[0] == '1') + sync = 1; + return 1; +} + +__setup("blkmtd_device=", param_blkmtd_device); +__setup("blkmtd_erasesz=", param_blkmtd_erasesz); +__setup("blkmtd_ro=", param_blkmtd_ro); +__setup("blkmtd_sync=", param_blkmtd_sync); + +#endif + + +/* Startup */ +static int __init init_blkmtd(void) +{ + int i; + + /* Check args - device[0] is the bare minimum*/ + if(!device[0]) { + err("error: missing `device' name\n"); + return -EINVAL; + } + + for(i = 0; i < MAX_DEVICES; i++) + add_device(device[i], ro[i], erasesz[i] << 10); + + if(list_empty(&blkmtd_device_list)) + goto init_err; + + info("version " VERSION); + +#ifdef BLKMTD_PROC_DEBUG + /* create proc entry */ + DEBUG(2, "Creating /proc/blkmtd_debug\n"); + blkmtd_proc = create_proc_read_entry("blkmtd_debug", 0444, + NULL, blkmtd_proc_read, NULL); + if(blkmtd_proc == NULL) { + err("Cant create /proc/blkmtd_debug"); + } else { + blkmtd_proc->owner = THIS_MODULE; + } +#endif + + if(!list_empty(&blkmtd_device_list)) + /* Everything is ok if we got here */ + return 0; + + init_err: + return -EINVAL; +} + +module_init(init_blkmtd); +module_exit(cleanup_blkmtd); diff --git a/linux-2.4.x/drivers/mtd/devices/blkmtd.c b/linux-2.4.x/drivers/mtd/devices/blkmtd.c index 31e7174..34a0b38 100644 --- a/linux-2.4.x/drivers/mtd/devices/blkmtd.c +++ b/linux-2.4.x/drivers/mtd/devices/blkmtd.c @@ -1,979 +1,737 @@ -/* - * $Id: blkmtd.c,v 1.7 2001/11/10 17:06:30 spse Exp $ +/* + * $Id: blkmtd.c,v 1.31 2006/03/29 08:44:02 dwmw2 Exp $ * * blkmtd.c - use a block device as a fake MTD * * Author: Simon Evans <spse@secret.org.uk> * - * Copyright (C) 2001 Simon Evans - * + * Copyright (C) 2001,2002 Simon Evans + * * Licence: GPL * * How it works: - * The driver uses raw/io to read/write the device and the page - * cache to cache access. Writes update the page cache with the - * new data but make a copy of the new page(s) and then a kernel - * thread writes pages out to the device in the background. This - * ensures that writes are order even if a page is updated twice. - * Also, since pages in the page cache are never marked as dirty, - * we dont have to worry about writepage() being called on some - * random page which may not be in the write order. - * - * Erases are handled like writes, so the callback is called after - * the page cache has been updated. Sync()ing will wait until it is - * all done. + * The driver uses raw/io to read/write the device and the page + * cache to cache access. Writes update the page cache with the + * new data and mark it dirty and add the page into a BIO which + * is then written out. * - * It can be loaded Read-Only to prevent erases and writes to the - * medium. + * It can be loaded Read-Only to prevent erases and writes to the + * medium. * - * Todo: - * Make the write queue size dynamic so this it is not too big on - * small memory systems and too small on large memory systems. - * - * Page cache usage may still be a bit wrong. Check we are doing - * everything properly. - * - * Somehow allow writes to dirty the page cache so we dont use too - * much memory making copies of outgoing pages. Need to handle case - * where page x is written to, then page y, then page x again before - * any of them have been committed to disk. - * - * Reading should read multiple pages at once rather than using - * readpage() for each one. This is easy and will be fixed asap. */ - #include <linux/config.h> #include <linux/module.h> - #include <linux/fs.h> +#include <linux/blkdev.h> +#include <linux/bio.h> #include <linux/pagemap.h> -#include <linux/iobuf.h> -#include <linux/slab.h> -#include <linux/pagemap.h> -#include <linux/mtd/compatmac.h> +#include <linux/list.h> +#include <linux/init.h> +#include <linux/mount.h> #include <linux/mtd/mtd.h> +#include <linux/mutex.h> -#ifdef CONFIG_MTD_DEBUG -#ifdef CONFIG_PROC_FS -# include <linux/proc_fs.h> -# define BLKMTD_PROC_DEBUG - static struct proc_dir_entry *blkmtd_proc; -#endif -#endif +#define err(format, arg...) printk(KERN_ERR "blkmtd: " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO "blkmtd: " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "blkmtd: " format "\n" , ## arg) +#define crit(format, arg...) printk(KERN_CRIT "blkmtd: " format "\n" , ## arg) /* Default erase size in K, always make it a multiple of PAGE_SIZE */ -#define CONFIG_MTD_BLKDEV_ERASESIZE 128 -#define VERSION "1.7" -extern int *blk_size[]; -extern int *blksize_size[]; +#define CONFIG_MTD_BLKDEV_ERASESIZE (128 << 10) /* 128KiB */ +#define VERSION "$Revision: 1.31 $" /* Info for the block device */ -typedef struct mtd_raw_dev_data_s { - struct block_device *binding; - int sector_size, sector_bits; - int partial_last_page; // 0 if device ends on page boundary, else page no of last page - int last_page_sectors; // Number of sectors in last page if partial_last_page != 0 - size_t totalsize; - int readonly; - struct address_space as; - struct mtd_info mtd_info; -} mtd_raw_dev_data_t; - -/* Info for each queue item in the write queue */ -typedef struct mtdblkdev_write_queue_s { - mtd_raw_dev_data_t *rawdevice; - struct page **pages; - int pagenr; - int pagecnt; - int iserase; -} mtdblkdev_write_queue_t; - - -/* Our erase page - always remains locked. */ -static struct page *erase_page; +struct blkmtd_dev { + struct list_head list; + struct block_device *blkdev; + struct mtd_info mtd_info; + struct mutex wrbuf_mutex; +}; + /* Static info about the MTD, used in cleanup_module */ -static mtd_raw_dev_data_t *mtd_rawdevice; - -/* Write queue fixed size */ -#define WRITE_QUEUE_SZ 512 - -/* Storage for the write queue */ -static mtdblkdev_write_queue_t *write_queue; -static int write_queue_sz = WRITE_QUEUE_SZ; -static int volatile write_queue_head; -static int volatile write_queue_tail; -static int volatile write_queue_cnt; -static spinlock_t mbd_writeq_lock = SPIN_LOCK_UNLOCKED; - -/* Tell the write thread to finish */ -static volatile int write_task_finish; - -/* ipc with the write thread */ -#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,0) -static DECLARE_MUTEX_LOCKED(thread_sem); -static DECLARE_WAIT_QUEUE_HEAD(thr_wq); -static DECLARE_WAIT_QUEUE_HEAD(mtbd_sync_wq); -#else -static struct semaphore thread_sem = MUTEX_LOCKED; -DECLARE_WAIT_QUEUE_HEAD(thr_wq); -DECLARE_WAIT_QUEUE_HEAD(mtbd_sync_wq); -#endif +static LIST_HEAD(blkmtd_device_list); +static void blkmtd_sync(struct mtd_info *mtd); + +#define MAX_DEVICES 4 + /* Module parameters passed by insmod/modprobe */ -char *device; /* the block device to use */ -int erasesz; /* optional default erase size */ -int ro; /* optional read only flag */ -int bs; /* optionally force the block size (avoid using) */ -int count; /* optionally force the block count (avoid using) */ -int wqs; /* optionally set the write queue size */ +static char *device[MAX_DEVICES]; /* the block device to use */ +static int erasesz[MAX_DEVICES]; /* optional default erase size */ +static int ro[MAX_DEVICES]; /* optional read only flag */ +static int sync; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Simon Evans <spse@secret.org.uk>"); MODULE_DESCRIPTION("Emulate an MTD using a block device"); -MODULE_PARM(device, "s"); +module_param_array(device, charp, NULL, 0); MODULE_PARM_DESC(device, "block device to use"); -MODULE_PARM(erasesz, "i"); -MODULE_PARM_DESC(erasesz, "optional erase size to use in KB. eg 4=4K."); -MODULE_PARM(ro, "i"); +module_param_array(erasesz, int, NULL, 0); +MODULE_PARM_DESC(erasesz, "optional erase size to use in KiB. eg 4=4KiB."); +module_param_array(ro, bool, NULL, 0); MODULE_PARM_DESC(ro, "1=Read only, writes and erases cause errors"); -MODULE_PARM(bs, "i"); -MODULE_PARM_DESC(bs, "force the block size in bytes"); -MODULE_PARM(count, "i"); -MODULE_PARM_DESC(count, "force the block count"); -MODULE_PARM(wqs, "i"); -#endif +module_param(sync, bool, 0); +MODULE_PARM_DESC(sync, "1=Synchronous writes"); + +/* completion handler for BIO reads */ +static int bi_read_complete(struct bio *bio, unsigned int bytes_done, int error) +{ + if (bio->bi_size) + return 1; + + complete((struct completion*)bio->bi_private); + return 0; +} -/* Page cache stuff */ -/* writepage() - should never be called - catch it anyway */ -static int blkmtd_writepage(struct page *page) +/* completion handler for BIO writes */ +static int bi_write_complete(struct bio *bio, unsigned int bytes_done, int error) { - printk("blkmtd: writepage called!!!\n"); - return -EIO; + const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags); + struct bio_vec *bvec = bio->bi_io_vec + bio->bi_vcnt - 1; + + if (bio->bi_size) + return 1; + + if(!uptodate) + err("bi_write_complete: not uptodate\n"); + + do { + struct page *page = bvec->bv_page; + DEBUG(3, "Cleaning up page %ld\n", page->index); + if (--bvec >= bio->bi_io_vec) + prefetchw(&bvec->bv_page->flags); + + if (uptodate) { + SetPageUptodate(page); + } else { + ClearPageUptodate(page); + SetPageError(page); + } + clear_page_dirty(page); + unlock_page(page); + page_cache_release(page); + } while (bvec >= bio->bi_io_vec); + + complete((struct completion*)bio->bi_private); + return 0; } -/* readpage() - reads one page from the block device */ -static int blkmtd_readpage(mtd_raw_dev_data_t *rawdevice, struct page *page) -{ - int err; - int sectornr, sectors, i; - struct kiobuf *iobuf; - kdev_t dev; - unsigned long *blocks; - - if(!rawdevice) { - printk("blkmtd: readpage: PANIC file->private_data == NULL\n"); - return -EIO; - } - dev = to_kdev_t(rawdevice->binding->bd_dev); - - DEBUG(2, "blkmtd: readpage called, dev = `%s' page = %p index = %ld\n", - bdevname(dev), page, page->index); - - if(Page_Uptodate(page)) { - DEBUG(2, "blkmtd: readpage page %ld is already upto date\n", page->index); - UnlockPage(page); - return 0; - } - - ClearPageUptodate(page); - ClearPageError(page); - - /* see if page is in the outgoing write queue */ - spin_lock(&mbd_writeq_lock); - if(write_queue_cnt) { - int i = write_queue_tail; - while(i != write_queue_head) { - mtdblkdev_write_queue_t *item = &write_queue[i]; - if(page->index >= item->pagenr && page->index < item->pagenr+item->pagecnt) { - /* yes it is */ - int index = page->index - item->pagenr; - - DEBUG(2, "blkmtd: readpage: found page %ld in outgoing write queue\n", - page->index); - if(item->iserase) { - memset(page_address(page), 0xff, PAGE_SIZE); - } else { - memcpy(page_address(page), page_address(item->pages[index]), PAGE_SIZE); +/* read one page from the block device */ +static int blkmtd_readpage(struct blkmtd_dev *dev, struct page *page) +{ + struct bio *bio; + struct completion event; + int err = -ENOMEM; + + if(PageUptodate(page)) { + DEBUG(2, "blkmtd: readpage page %ld is already upto date\n", page->index); + unlock_page(page); + return 0; } - SetPageUptodate(page); - flush_dcache_page(page); - UnlockPage(page); - spin_unlock(&mbd_writeq_lock); - return 0; - } - i++; - i %= write_queue_sz; - } - } - spin_unlock(&mbd_writeq_lock); - - - DEBUG(3, "blkmtd: readpage: getting kiovec\n"); - err = alloc_kiovec(1, &iobuf); - if (err) { - printk("blkmtd: cant allocate kiobuf\n"); - SetPageError(page); - return err; - } - - /* Pre 2.4.4 doesn't have space for the block list in the kiobuf */ -#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) - blocks = kmalloc(KIO_MAX_SECTORS * sizeof(unsigned long)); - if(blocks == NULL) { - printk("blkmtd: cant allocate iobuf blocks\n"); - free_kiovec(1, &iobuf); - SetPageError(page); - return -ENOMEM; - } -#else - blocks = iobuf->blocks; -#endif - iobuf->offset = 0; - iobuf->nr_pages = 1; - iobuf->length = PAGE_SIZE; - iobuf->locked = 1; - iobuf->maplist[0] = page; - sectornr = page->index << (PAGE_SHIFT - rawdevice->sector_bits); - sectors = 1 << (PAGE_SHIFT - rawdevice->sector_bits); - if(rawdevice->partial_last_page && page->index == rawdevice->partial_last_page) { - DEBUG(3, "blkmtd: handling partial last page\n"); - sectors = rawdevice->last_page_sectors; - } - DEBUG(3, "blkmtd: readpage: sectornr = %d sectors = %d\n", sectornr, sectors); - for(i = 0; i < sectors; i++) { - blocks[i] = sectornr++; - } - /* If only a partial page read in, clear the rest of the page */ - if(rawdevice->partial_last_page && page->index == rawdevice->partial_last_page) { - int offset = rawdevice->last_page_sectors << rawdevice->sector_bits; - int count = PAGE_SIZE-offset; - DEBUG(3, "blkmtd: clear last partial page: offset = %d count = %d\n", offset, count); - memset(page_address(page)+offset, 0, count); - sectors = rawdevice->last_page_sectors; - } - - - DEBUG(3, "bklmtd: readpage: starting brw_kiovec\n"); - err = brw_kiovec(READ, 1, &iobuf, dev, blocks, rawdevice->sector_size); - DEBUG(3, "blkmtd: readpage: finished, err = %d\n", err); - iobuf->locked = 0; - free_kiovec(1, &iobuf); - -#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) - kfree(blocks); -#endif + ClearPageUptodate(page); + ClearPageError(page); + + bio = bio_alloc(GFP_KERNEL, 1); + if(bio) { + init_completion(&event); + bio->bi_bdev = dev->blkdev; + bio->bi_sector = page->index << (PAGE_SHIFT-9); + bio->bi_private = &event; + bio->bi_end_io = bi_read_complete; + if(bio_add_page(bio, page, PAGE_SIZE, 0) == PAGE_SIZE) { + submit_bio(READ_SYNC, bio); + wait_for_completion(&event); + err = test_bit(BIO_UPTODATE, &bio->bi_flags) ? 0 : -EIO; + bio_put(bio); + } + } - if(err != PAGE_SIZE) { - printk("blkmtd: readpage: error reading page %ld\n", page->index); - memset(page_address(page), 0, PAGE_SIZE); - SetPageError(page); - err = -EIO; - } else { - DEBUG(3, "blkmtd: readpage: setting page upto date\n"); - SetPageUptodate(page); - err = 0; - } - flush_dcache_page(page); - UnlockPage(page); - DEBUG(2, "blkmtd: readpage: finished, err = %d\n", err); - return 0; + if(err) + SetPageError(page); + else + SetPageUptodate(page); + flush_dcache_page(page); + unlock_page(page); + return err; } - -static struct address_space_operations blkmtd_aops = { - writepage: blkmtd_writepage, - readpage: NULL, -}; - -/* This is the kernel thread that empties the write queue to disk */ -static int write_queue_task(void *data) +/* write out the current BIO and wait for it to finish */ +static int blkmtd_write_out(struct bio *bio) { - int err; - struct task_struct *tsk = current; - struct kiobuf *iobuf; - unsigned long *blocks; - - DECLARE_WAITQUEUE(wait, tsk); - DEBUG(1, "blkmtd: writetask: starting (pid = %d)\n", tsk->pid); - daemonize(); - strcpy(tsk->comm, "blkmtdd"); - tsk->tty = NULL; - spin_lock_irq(&tsk->sigmask_lock); - sigfillset(&tsk->blocked); - recalc_sigpending(tsk); - spin_unlock_irq(&tsk->sigmask_lock); - exit_sighand(tsk); - - if(alloc_kiovec(1, &iobuf)) { - printk("blkmtd: write_queue_task cant allocate kiobuf\n"); - return 0; - } - - /* Pre 2.4.4 doesn't have space for the block list in the kiobuf */ -#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) - blocks = kmalloc(KIO_MAX_SECTORS * sizeof(unsigned long)); - if(blocks == NULL) { - printk("blkmtd: write_queue_task cant allocate iobuf blocks\n"); - free_kiovec(1, &iobuf); - return 0; - } -#else - blocks = iobuf->blocks; -#endif + struct completion event; + int err; - DEBUG(2, "blkmtd: writetask: entering main loop\n"); - add_wait_queue(&thr_wq, &wait); - - while(1) { - spin_lock(&mbd_writeq_lock); - - if(!write_queue_cnt) { - /* If nothing in the queue, wake up anyone wanting to know when there - is space in the queue then sleep for 2*HZ */ - spin_unlock(&mbd_writeq_lock); - DEBUG(4, "blkmtd: writetask: queue empty\n"); - if(waitqueue_active(&mtbd_sync_wq)) - wake_up(&mtbd_sync_wq); - interruptible_sleep_on_timeout(&thr_wq, 2*HZ); - DEBUG(4, "blkmtd: writetask: woken up\n"); - if(write_task_finish) - break; - } else { - /* we have stuff to write */ - mtdblkdev_write_queue_t *item = &write_queue[write_queue_tail]; - struct page **pages = item->pages; - - int i; - int sectornr = item->pagenr << (PAGE_SHIFT - item->rawdevice->sector_bits); - int sectorcnt = item->pagecnt << (PAGE_SHIFT - item->rawdevice->sector_bits); - int max_sectors = KIO_MAX_SECTORS >> (item->rawdevice->sector_bits - 9); - kdev_t dev = to_kdev_t(item->rawdevice->binding->bd_dev); - - /* If we are writing to the last page on the device and it doesn't end - * on a page boundary, subtract the number of sectors that dont exist. - */ - if(item->rawdevice->partial_last_page && - (item->pagenr + item->pagecnt -1) == item->rawdevice->partial_last_page) { - sectorcnt -= (1 << (PAGE_SHIFT - item->rawdevice->sector_bits)); - sectorcnt += item->rawdevice->last_page_sectors; - } - - DEBUG(3, "blkmtd: writetask: got %d queue items\n", write_queue_cnt); - set_current_state(TASK_RUNNING); - spin_unlock(&mbd_writeq_lock); - - DEBUG(2, "blkmtd: writetask: writing pagenr = %d pagecnt = %d sectornr = %d sectorcnt = %d\n", - item->pagenr, item->pagecnt, sectornr, sectorcnt); - - iobuf->offset = 0; - iobuf->locked = 1; - - /* Loop through all the pages to be written in the queue item, remembering - we can only write KIO_MAX_SECTORS at a time */ - - while(sectorcnt) { - int cursectors = (sectorcnt < max_sectors) ? sectorcnt : max_sectors; - int cpagecnt = (cursectors << item->rawdevice->sector_bits) + PAGE_SIZE-1; - cpagecnt >>= PAGE_SHIFT; - - for(i = 0; i < cpagecnt; i++) { - if(item->iserase) { - iobuf->maplist[i] = erase_page; - } else { - iobuf->maplist[i] = *(pages++); - } - } - - for(i = 0; i < cursectors; i++) { - blocks[i] = sectornr++; - } - - iobuf->nr_pages = cpagecnt; - iobuf->length = cursectors << item->rawdevice->sector_bits; - DEBUG(3, "blkmtd: write_task: about to kiovec\n"); - err = brw_kiovec(WRITE, 1, &iobuf, dev, blocks, item->rawdevice->sector_size); - DEBUG(3, "bklmtd: write_task: done, err = %d\n", err); - if(err != (cursectors << item->rawdevice->sector_bits)) { - /* if an error occured - set this to exit the loop */ - sectorcnt = 0; - } else { - sectorcnt -= cursectors; + if(!bio->bi_vcnt) { + bio_put(bio); + return 0; } - } - - /* free up the pages used in the write and list of pages used in the write - queue item */ - iobuf->locked = 0; - spin_lock(&mbd_writeq_lock); - write_queue_cnt--; - write_queue_tail++; - write_queue_tail %= write_queue_sz; - if(!item->iserase) { - for(i = 0 ; i < item->pagecnt; i++) { - UnlockPage(item->pages[i]); - __free_pages(item->pages[i], 0); + + init_completion(&event); + bio->bi_private = &event; + bio->bi_end_io = bi_write_complete; + submit_bio(WRITE_SYNC, bio); + wait_for_completion(&event); + DEBUG(3, "submit_bio completed, bi_vcnt = %d\n", bio->bi_vcnt); + err = test_bit(BIO_UPTODATE, &bio->bi_flags) ? 0 : -EIO; + bio_put(bio); + return err; +} + + +/** + * blkmtd_add_page - add a page to the current BIO + * @bio: bio to add to (NULL to alloc initial bio) + * @blkdev: block device + * @page: page to add + * @pagecnt: pages left to add + * + * Adds a page to the current bio, allocating it if necessary. If it cannot be + * added, the current bio is written out and a new one is allocated. Returns + * the new bio to add or NULL on error + */ +static struct bio *blkmtd_add_page(struct bio *bio, struct block_device *blkdev, + struct page *page, int pagecnt) +{ + + retry: + if(!bio) { + bio = bio_alloc(GFP_KERNEL, pagecnt); + if(!bio) + return NULL; + bio->bi_sector = page->index << (PAGE_SHIFT-9); + bio->bi_bdev = blkdev; } - kfree(item->pages); - } - item->pages = NULL; - spin_unlock(&mbd_writeq_lock); - /* Tell others there is some space in the write queue */ - if(waitqueue_active(&mtbd_sync_wq)) - wake_up(&mtbd_sync_wq); - } - } - remove_wait_queue(&thr_wq, &wait); - DEBUG(1, "blkmtd: writetask: exiting\n"); - free_kiovec(1, &iobuf); - -#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,4) - kfree(blocks); -#endif - /* Tell people we have exitd */ - up(&thread_sem); - return 0; + if(bio_add_page(bio, page, PAGE_SIZE, 0) != PAGE_SIZE) { + blkmtd_write_out(bio); + bio = NULL; + goto retry; + } + return bio; } -/* Add a range of pages into the outgoing write queue, making copies of them */ -static int queue_page_write(mtd_raw_dev_data_t *rawdevice, struct page **pages, - int pagenr, int pagecnt, int iserase) +/** + * write_pages - write block of data to device via the page cache + * @dev: device to write to + * @buf: data source or NULL if erase (output is set to 0xff) + * @to: offset into output device + * @len: amount to data to write + * @retlen: amount of data written + * + * Grab pages from the page cache and fill them with the source data. + * Non page aligned start and end result in a readin of the page and + * part of the page being modified. Pages are added to the bio and then written + * out. + */ +static int write_pages(struct blkmtd_dev *dev, const u_char *buf, loff_t to, + size_t len, size_t *retlen) { - struct page *outpage; - struct page **new_pages = NULL; - mtdblkdev_write_queue_t *item; - int i; - DECLARE_WAITQUEUE(wait, current); - DEBUG(2, "blkmtd: queue_page_write: adding pagenr = %d pagecnt = %d\n", pagenr, pagecnt); - - if(!pagecnt) - return 0; - - if(pages == NULL && !iserase) - return -EINVAL; - - /* create a array for the list of pages */ - if(!iserase) { - new_pages = kmalloc(pagecnt * sizeof(struct page *), GFP_KERNEL); - if(new_pages == NULL) - return -ENOMEM; - - /* make copies of the pages in the page cache */ - for(i = 0; i < pagecnt; i++) { - outpage = alloc_pages(GFP_KERNEL, 0); - if(!outpage) { - while(i--) { - UnlockPage(new_pages[i]); - __free_pages(new_pages[i], 0); + int pagenr, offset; + size_t start_len = 0, end_len; + int pagecnt = 0; + int err = 0; + struct bio *bio = NULL; + size_t thislen = 0; + + pagenr = to >> PAGE_SHIFT; + offset = to & ~PAGE_MASK; + + DEBUG(2, "blkmtd: write_pages: buf = %p to = %ld len = %zd pagenr = %d offset = %d\n", + buf, (long)to, len, pagenr, offset); + + /* see if we have to do a partial write at the start */ + if(offset) { + start_len = ((offset + len) > PAGE_SIZE) ? PAGE_SIZE - offset : len; + len -= start_len; } - kfree(new_pages); - return -ENOMEM; - } - lock_page(outpage); - memcpy(page_address(outpage), page_address(pages[i]), PAGE_SIZE); - new_pages[i] = outpage; - } - } - - /* wait until there is some space in the write queue */ - test_lock: - spin_lock(&mbd_writeq_lock); - if(write_queue_cnt == write_queue_sz) { - spin_unlock(&mbd_writeq_lock); - DEBUG(3, "blkmtd: queue_page: Queue full\n"); - current->state = TASK_UNINTERRUPTIBLE; - add_wait_queue(&mtbd_sync_wq, &wait); - wake_up_interruptible(&thr_wq); - schedule(); - current->state = TASK_RUNNING; - remove_wait_queue(&mtbd_sync_wq, &wait); - DEBUG(3, "blkmtd: queue_page_write: Queue has %d items in it\n", write_queue_cnt); - goto test_lock; - } - - DEBUG(3, "blkmtd: queue_page_write: qhead: %d qtail: %d qcnt: %d\n", - write_queue_head, write_queue_tail, write_queue_cnt); - - /* fix up the queue item */ - item = &write_queue[write_queue_head]; - item->pages = new_pages; - item->pagenr = pagenr; - item->pagecnt = pagecnt; - item->rawdevice = rawdevice; - item->iserase = iserase; - - write_queue_head++; - write_queue_head %= write_queue_sz; - write_queue_cnt++; - DEBUG(3, "blkmtd: queue_page_write: qhead: %d qtail: %d qcnt: %d\n", - write_queue_head, write_queue_tail, write_queue_cnt); - spin_unlock(&mbd_writeq_lock); - DEBUG(2, "blkmtd: queue_page_write: finished\n"); - return 0; + + /* calculate the length of the other two regions */ + end_len = len & ~PAGE_MASK; + len -= end_len; + + if(start_len) + pagecnt++; + + if(len) + pagecnt += len >> PAGE_SHIFT; + + if(end_len) + pagecnt++; + + mutex_lock(&dev->wrbuf_mutex); + + DEBUG(3, "blkmtd: write: start_len = %zd len = %zd end_len = %zd pagecnt = %d\n", + start_len, len, end_len, pagecnt); + + if(start_len) { + /* do partial start region */ + struct page *page; + + DEBUG(3, "blkmtd: write: doing partial start, page = %d len = %zd offset = %d\n", + pagenr, start_len, offset); + + BUG_ON(!buf); + page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev); + lock_page(page); + if(PageDirty(page)) { + err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d\n", + to, start_len, len, end_len, pagenr); + BUG(); + } + memcpy(page_address(page)+offset, buf, start_len); + set_page_dirty(page); + SetPageUptodate(page); + buf += start_len; + thislen = start_len; + bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt); + if(!bio) { + err = -ENOMEM; + err("bio_add_page failed\n"); + goto write_err; + } + pagecnt--; + pagenr++; + } + + /* Now do the main loop to a page aligned, n page sized output */ + if(len) { + int pagesc = len >> PAGE_SHIFT; + DEBUG(3, "blkmtd: write: whole pages start = %d, count = %d\n", + pagenr, pagesc); + while(pagesc) { + struct page *page; + + /* see if page is in the page cache */ + DEBUG(3, "blkmtd: write: grabbing page %d from page cache\n", pagenr); + page = grab_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr); + if(PageDirty(page)) { + BUG(); + } + if(!page) { + warn("write: cannot grab cache page %d", pagenr); + err = -ENOMEM; + goto write_err; + } + if(!buf) { + memset(page_address(page), 0xff, PAGE_SIZE); + } else { + memcpy(page_address(page), buf, PAGE_SIZE); + buf += PAGE_SIZE; + } + bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt); + if(!bio) { + err = -ENOMEM; + err("bio_add_page failed\n"); + goto write_err; + } + pagenr++; + pagecnt--; + set_page_dirty(page); + SetPageUptodate(page); + pagesc--; + thislen += PAGE_SIZE; + } + } + + if(end_len) { + /* do the third region */ + struct page *page; + DEBUG(3, "blkmtd: write: doing partial end, page = %d len = %zd\n", + pagenr, end_len); + BUG_ON(!buf); + page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev); + lock_page(page); + if(PageDirty(page)) { + err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d\n", + to, start_len, len, end_len, pagenr); + BUG(); + } + memcpy(page_address(page), buf, end_len); + set_page_dirty(page); + SetPageUptodate(page); + DEBUG(3, "blkmtd: write: writing out partial end\n"); + thislen += end_len; + bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt); + if(!bio) { + err = -ENOMEM; + err("bio_add_page failed\n"); + goto write_err; + } + pagenr++; + } + + DEBUG(3, "blkmtd: write: got %d vectors to write\n", bio->bi_vcnt); + write_err: + if(bio) + blkmtd_write_out(bio); + + DEBUG(2, "blkmtd: write: end, retlen = %zd, err = %d\n", *retlen, err); + mutex_unlock(&dev->wrbuf_mutex); + + if(retlen) + *retlen = thislen; + return err; } /* erase a specified part of the device */ static int blkmtd_erase(struct mtd_info *mtd, struct erase_info *instr) { - mtd_raw_dev_data_t *rawdevice = mtd->priv; - struct mtd_erase_region_info *einfo = mtd->eraseregions; - int numregions = mtd->numeraseregions; - size_t from; - u_long len; - int err = 0; - - /* check readonly */ - if(rawdevice->readonly) { - printk("blkmtd: error: trying to erase readonly device %s\n", device); - instr->state = MTD_ERASE_FAILED; - goto erase_callback; - } - - instr->state = MTD_ERASING; - from = instr->addr; - len = instr->len; - - /* check erase region has valid start and length */ - DEBUG(2, "blkmtd: erase: dev = `%s' from = 0x%x len = 0x%lx\n", - bdevname(rawdevice->binding->bd_dev), from, len); - while(numregions) { - DEBUG(3, "blkmtd: checking erase region = 0x%08X size = 0x%X num = 0x%x\n", - einfo->offset, einfo->erasesize, einfo->numblocks); - if(from >= einfo->offset && from < einfo->offset + (einfo->erasesize * einfo->numblocks)) { - if(len == einfo->erasesize && ( (from - einfo->offset) % einfo->erasesize == 0)) - break; - } - numregions--; - einfo++; - } - - if(!numregions) { - /* Not a valid erase block */ - printk("blkmtd: erase: invalid erase request 0x%lX @ 0x%08X\n", len, from); - instr->state = MTD_ERASE_FAILED; - err = -EIO; - } - - if(instr->state != MTD_ERASE_FAILED) { - /* start the erase */ - int pagenr, pagecnt; - struct page *page, **pages; - int i = 0; - - /* Handle the last page of the device not being whole */ - if(len < PAGE_SIZE) - len = PAGE_SIZE; - - pagenr = from >> PAGE_SHIFT; - pagecnt = len >> PAGE_SHIFT; - DEBUG(3, "blkmtd: erase: pagenr = %d pagecnt = %d\n", pagenr, pagecnt); - - pages = kmalloc(pagecnt * sizeof(struct page *), GFP_KERNEL); - if(pages == NULL) { - err = -ENOMEM; - instr->state = MTD_ERASE_FAILED; - goto erase_out; - } - - - while(pagecnt) { - /* get the page via the page cache */ - DEBUG(3, "blkmtd: erase: doing grab_cache_page() for page %d\n", pagenr); - page = grab_cache_page(&rawdevice->as, pagenr); - if(!page) { - DEBUG(3, "blkmtd: erase: grab_cache_page() failed for page %d\n", pagenr); - kfree(pages); - err = -EIO; - instr->state = MTD_ERASE_FAILED; - goto erase_out; - } - memset(page_address(page), 0xff, PAGE_SIZE); - pages[i] = page; - pagecnt--; - pagenr++; - i++; - } - DEBUG(3, "blkmtd: erase: queuing page write\n"); - err = queue_page_write(rawdevice, NULL, from >> PAGE_SHIFT, len >> PAGE_SHIFT, 1); - pagecnt = len >> PAGE_SHIFT; - if(!err) { - while(pagecnt--) { - SetPageUptodate(pages[pagecnt]); - UnlockPage(pages[pagecnt]); - page_cache_release(pages[pagecnt]); - flush_dcache_page(pages[pagecnt]); - } - kfree(pages); - instr->state = MTD_ERASE_DONE; - } else { - while(pagecnt--) { - SetPageError(pages[pagecnt]); - page_cache_release(pages[pagecnt]); - } - kfree(pages); - instr->state = MTD_ERASE_FAILED; - } - } - erase_out: - DEBUG(3, "blkmtd: erase: checking callback\n"); - erase_callback: - if (instr->callback) { - (*(instr->callback))(instr); - } - DEBUG(2, "blkmtd: erase: finished (err = %d)\n", err); - return err; + struct blkmtd_dev *dev = mtd->priv; + struct mtd_erase_region_info *einfo = mtd->eraseregions; + int numregions = mtd->numeraseregions; + size_t from; + u_long len; + int err = -EIO; + size_t retlen; + + instr->state = MTD_ERASING; + from = instr->addr; + len = instr->len; + + /* check erase region has valid start and length */ + DEBUG(2, "blkmtd: erase: dev = `%s' from = 0x%zx len = 0x%lx\n", + mtd->name+9, from, len); + while(numregions) { + DEBUG(3, "blkmtd: checking erase region = 0x%08X size = 0x%X num = 0x%x\n", + einfo->offset, einfo->erasesize, einfo->numblocks); + if(from >= einfo->offset + && from < einfo->offset + (einfo->erasesize * einfo->numblocks)) { + if(len == einfo->erasesize + && ( (from - einfo->offset) % einfo->erasesize == 0)) + break; + } + numregions--; + einfo++; + } + + if(!numregions) { + /* Not a valid erase block */ + err("erase: invalid erase request 0x%lX @ 0x%08zX", len, from); + instr->state = MTD_ERASE_FAILED; + err = -EIO; + } + + if(instr->state != MTD_ERASE_FAILED) { + /* do the erase */ + DEBUG(3, "Doing erase from = %zd len = %ld\n", from, len); + err = write_pages(dev, NULL, from, len, &retlen); + if(err || retlen != len) { + err("erase failed err = %d", err); + instr->state = MTD_ERASE_FAILED; + } else { + instr->state = MTD_ERASE_DONE; + } + } + + DEBUG(3, "blkmtd: erase: checking callback\n"); + mtd_erase_callback(instr); + DEBUG(2, "blkmtd: erase: finished (err = %d)\n", err); + return err; } /* read a range of the data via the page cache */ static int blkmtd_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) + size_t *retlen, u_char *buf) { - mtd_raw_dev_data_t *rawdevice = mtd->priv; - int err = 0; - int offset; - int pagenr, pages; - - *retlen = 0; - - DEBUG(2, "blkmtd: read: dev = `%s' from = %ld len = %d buf = %p\n", - bdevname(rawdevice->binding->bd_dev), (long int)from, len, buf); - - pagenr = from >> PAGE_SHIFT; - offset = from - (pagenr << PAGE_SHIFT); - - pages = (offset+len+PAGE_SIZE-1) >> PAGE_SHIFT; - DEBUG(3, "blkmtd: read: pagenr = %d offset = %d, pages = %d\n", pagenr, offset, pages); - - /* just loop through each page, getting it via readpage() - slow but easy */ - while(pages) { - struct page *page; - int cpylen; - DEBUG(3, "blkmtd: read: looking for page: %d\n", pagenr); - page = read_cache_page(&rawdevice->as, pagenr, (filler_t *)blkmtd_readpage, rawdevice); - if(IS_ERR(page)) { - return PTR_ERR(page); - } - wait_on_page(page); - if(!Page_Uptodate(page)) { - /* error reading page */ - printk("blkmtd: read: page not uptodate\n"); - page_cache_release(page); - return -EIO; - } - - cpylen = (PAGE_SIZE > len) ? len : PAGE_SIZE; - if(offset+cpylen > PAGE_SIZE) - cpylen = PAGE_SIZE-offset; - - memcpy(buf + *retlen, page_address(page) + offset, cpylen); - offset = 0; - len -= cpylen; - *retlen += cpylen; - pagenr++; - pages--; - page_cache_release(page); - } - - DEBUG(2, "blkmtd: end read: retlen = %d, err = %d\n", *retlen, err); - return err; + struct blkmtd_dev *dev = mtd->priv; + int err = 0; + int offset; + int pagenr, pages; + size_t thislen = 0; + + DEBUG(2, "blkmtd: read: dev = `%s' from = %lld len = %zd buf = %p\n", + mtd->name+9, from, len, buf); + + if(from > mtd->size) + return -EINVAL; + if(from + len > mtd->size) + len = mtd->size - from; + + pagenr = from >> PAGE_SHIFT; + offset = from - (pagenr << PAGE_SHIFT); + + pages = (offset+len+PAGE_SIZE-1) >> PAGE_SHIFT; + DEBUG(3, "blkmtd: read: pagenr = %d offset = %d, pages = %d\n", + pagenr, offset, pages); + + while(pages) { + struct page *page; + int cpylen; + + DEBUG(3, "blkmtd: read: looking for page: %d\n", pagenr); + page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev); + if(IS_ERR(page)) { + err = -EIO; + goto readerr; + } + + cpylen = (PAGE_SIZE > len) ? len : PAGE_SIZE; + if(offset+cpylen > PAGE_SIZE) + cpylen = PAGE_SIZE-offset; + + memcpy(buf + thislen, page_address(page) + offset, cpylen); + offset = 0; + len -= cpylen; + thislen += cpylen; + pagenr++; + pages--; + if(!PageDirty(page)) + page_cache_release(page); + } + + readerr: + if(retlen) + *retlen = thislen; + DEBUG(2, "blkmtd: end read: retlen = %zd, err = %d\n", thislen, err); + return err; } - -/* write a range of the data via the page cache. - * - * Basic operation. break the write into three parts. - * - * 1. From a page unaligned start up until the next page boundary - * 2. Page sized, page aligned blocks - * 3. From end of last aligned block to end of range - * - * 1,3 are read via the page cache and readpage() since these are partial - * pages, 2 we just grab pages from the page cache, not caring if they are - * already in memory or not since they will be completly overwritten. - * - */ - + +/* write data to the underlying device */ static int blkmtd_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) + size_t *retlen, const u_char *buf) { - mtd_raw_dev_data_t *rawdevice = mtd->priv; - int err = 0; - int offset; - int pagenr; - size_t len1 = 0, len2 = 0, len3 = 0; - struct page **pages; - int pagecnt = 0; - - *retlen = 0; - DEBUG(2, "blkmtd: write: dev = `%s' to = %ld len = %d buf = %p\n", - bdevname(rawdevice->binding->bd_dev), (long int)to, len, buf); - - /* handle readonly and out of range numbers */ - - if(rawdevice->readonly) { - printk("blkmtd: error: trying to write to a readonly device %s\n", device); - return -EROFS; - } - - if(to >= rawdevice->totalsize) { - return -ENOSPC; - } - - if(to + len > rawdevice->totalsize) { - len = (rawdevice->totalsize - to); - } - - - pagenr = to >> PAGE_SHIFT; - offset = to - (pagenr << PAGE_SHIFT); - - /* see if we have to do a partial write at the start */ - if(offset) { - if((offset + len) > PAGE_SIZE) { - len1 = PAGE_SIZE - offset; - len -= len1; - } else { - len1 = len; - len = 0; - } - } - - /* calculate the length of the other two regions */ - len3 = len & ~PAGE_MASK; - len -= len3; - len2 = len; - - - if(len1) - pagecnt++; - if(len2) - pagecnt += len2 >> PAGE_SHIFT; - if(len3) - pagecnt++; - - DEBUG(3, "blkmtd: write: len1 = %d len2 = %d len3 = %d pagecnt = %d\n", len1, len2, len3, pagecnt); - - /* get space for list of pages */ - pages = kmalloc(pagecnt * sizeof(struct page *), GFP_KERNEL); - if(pages == NULL) { - return -ENOMEM; - } - pagecnt = 0; - - if(len1) { - /* do partial start region */ - struct page *page; - - DEBUG(3, "blkmtd: write: doing partial start, page = %d len = %d offset = %d\n", pagenr, len1, offset); - page = read_cache_page(&rawdevice->as, pagenr, (filler_t *)blkmtd_readpage, rawdevice); - - if(IS_ERR(page)) { - kfree(pages); - return PTR_ERR(page); - } - memcpy(page_address(page)+offset, buf, len1); - pages[pagecnt++] = page; - buf += len1; - *retlen = len1; - err = 0; - pagenr++; - } - - /* Now do the main loop to a page aligned, n page sized output */ - if(len2) { - int pagesc = len2 >> PAGE_SHIFT; - DEBUG(3, "blkmtd: write: whole pages start = %d, count = %d\n", pagenr, pagesc); - while(pagesc) { - struct page *page; - - /* see if page is in the page cache */ - DEBUG(3, "blkmtd: write: grabbing page %d from page cache\n", pagenr); - page = grab_cache_page(&rawdevice->as, pagenr); - DEBUG(3, "blkmtd: write: got page %d from page cache\n", pagenr); - if(!page) { - printk("blkmtd: write: cant grab cache page %d\n", pagenr); - err = -EIO; - goto write_err; - } - memcpy(page_address(page), buf, PAGE_SIZE); - pages[pagecnt++] = page; - UnlockPage(page); - SetPageUptodate(page); - pagenr++; - pagesc--; - buf += PAGE_SIZE; - *retlen += PAGE_SIZE; - } - } - - - if(len3) { - /* do the third region */ - struct page *page; - DEBUG(3, "blkmtd: write: doing partial end, page = %d len = %d\n", pagenr, len3); - page = read_cache_page(&rawdevice->as, pagenr, (filler_t *)blkmtd_readpage, rawdevice); - if(IS_ERR(page)) { - err = PTR_ERR(page); - goto write_err; - } - memcpy(page_address(page), buf, len3); - DEBUG(3, "blkmtd: write: writing out partial end\n"); - pages[pagecnt++] = page; - *retlen += len3; - err = 0; - } - DEBUG(2, "blkmtd: write: end, retlen = %d, err = %d\n", *retlen, err); - /* submit it to the write task */ - err = queue_page_write(rawdevice, pages, to >> PAGE_SHIFT, pagecnt, 0); - if(!err) { - while(pagecnt--) { - SetPageUptodate(pages[pagecnt]); - flush_dcache_page(pages[pagecnt]); - page_cache_release(pages[pagecnt]); - } - kfree(pages); - return 0; - } + struct blkmtd_dev *dev = mtd->priv; + int err; - write_err: - while(--pagecnt) { - SetPageError(pages[pagecnt]); - page_cache_release(pages[pagecnt]); - } - kfree(pages); - return err; + if(!len) + return 0; + + DEBUG(2, "blkmtd: write: dev = `%s' to = %lld len = %zd buf = %p\n", + mtd->name+9, to, len, buf); + + if(to >= mtd->size) { + return -ENOSPC; + } + + if(to + len > mtd->size) { + len = mtd->size - to; + } + + err = write_pages(dev, buf, to, len, retlen); + if(err > 0) + err = 0; + DEBUG(2, "blkmtd: write: end, err = %d\n", err); + return err; } /* sync the device - wait until the write queue is empty */ static void blkmtd_sync(struct mtd_info *mtd) { - DECLARE_WAITQUEUE(wait, current); - mtd_raw_dev_data_t *rawdevice = mtd->priv; - if(rawdevice->readonly) - return; - - DEBUG(2, "blkmtd: sync: called\n"); - - stuff_inq: - spin_lock(&mbd_writeq_lock); - if(write_queue_cnt) { - spin_unlock(&mbd_writeq_lock); - current->state = TASK_UNINTERRUPTIBLE; - add_wait_queue(&mtbd_sync_wq, &wait); - DEBUG(3, "blkmtd: sync: waking up task\n"); - wake_up_interruptible(&thr_wq); - schedule(); - current->state = TASK_RUNNING; - remove_wait_queue(&mtbd_sync_wq, &wait); - DEBUG(3, "blkmtd: sync: waking up after write task\n"); - goto stuff_inq; - } - spin_unlock(&mbd_writeq_lock); - - DEBUG(2, "blkmtd: sync: finished\n"); + /* Currently all writes are synchronous */ } -#ifdef BLKMTD_PROC_DEBUG -/* procfs stuff */ -static int blkmtd_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data) +static void free_device(struct blkmtd_dev *dev) { - int clean = 0, dirty = 0, locked = 0; - struct list_head *temp; - int i, len, pages = 0, cnt; - MOD_INC_USE_COUNT; - spin_lock(&mbd_writeq_lock); - cnt = write_queue_cnt; - i = write_queue_tail; - while(cnt) { - if(!write_queue[i].iserase) - pages += write_queue[i].pagecnt; - i++; - i %= write_queue_sz; - cnt--; - } - - /* Count the size of the page lists */ - list_for_each(temp, &mtd_rawdevice->as.clean_pages) { - clean++; - } - list_for_each(temp, &mtd_rawdevice->as.dirty_pages) { - dirty++; - } - list_for_each(temp, &mtd_rawdevice->as.locked_pages) { - locked++; - } - - len = sprintf(page, "Write queue head: %d\nWrite queue tail: %d\n" - "Write queue count: %d\nPages in queue: %d (%dK)\n" - "Clean Pages: %d\nDirty Pages: %d\nLocked Pages: %d\n" - "nrpages: %ld\n", - write_queue_head, write_queue_tail, write_queue_cnt, - pages, pages << (PAGE_SHIFT-10), clean, dirty, locked, - mtd_rawdevice->as.nrpages); - if(len <= count) - *eof = 1; - spin_unlock(&mbd_writeq_lock); - MOD_DEC_USE_COUNT; - return len; + DEBUG(2, "blkmtd: free_device() dev = %p\n", dev); + if(dev) { + kfree(dev->mtd_info.eraseregions); + kfree(dev->mtd_info.name); + if(dev->blkdev) { + invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping); + close_bdev_excl(dev->blkdev); + } + kfree(dev); + } } -#endif -/* Cleanup and exit - sync the device and kill of the kernel thread */ -static void __exit cleanup_blkmtd(void) +/* For a given size and initial erase size, calculate the number + * and size of each erase region. Goes round the loop twice, + * once to find out how many regions, then allocates space, + * then round the loop again to fill it in. + */ +static struct mtd_erase_region_info *calc_erase_regions( + size_t erase_size, size_t total_size, int *regions) +{ + struct mtd_erase_region_info *info = NULL; + + DEBUG(2, "calc_erase_regions, es = %zd size = %zd regions = %d\n", + erase_size, total_size, *regions); + /* Make any user specified erasesize be a power of 2 + and at least PAGE_SIZE */ + if(erase_size) { + int es = erase_size; + erase_size = 1; + while(es != 1) { + es >>= 1; + erase_size <<= 1; + } + if(erase_size < PAGE_SIZE) + erase_size = PAGE_SIZE; + } else { + erase_size = CONFIG_MTD_BLKDEV_ERASESIZE; + } + + *regions = 0; + + do { + int tot_size = total_size; + int er_size = erase_size; + int count = 0, offset = 0, regcnt = 0; + + while(tot_size) { + count = tot_size / er_size; + if(count) { + tot_size = tot_size % er_size; + if(info) { + DEBUG(2, "adding to erase info off=%d er=%d cnt=%d\n", + offset, er_size, count); + (info+regcnt)->offset = offset; + (info+regcnt)->erasesize = er_size; + (info+regcnt)->numblocks = count; + (*regions)++; + } + regcnt++; + offset += (count * er_size); + } + while(er_size > tot_size) + er_size >>= 1; + } + if(info == NULL) { + info = kmalloc(regcnt * sizeof(struct mtd_erase_region_info), GFP_KERNEL); + if(!info) + break; + } + } while(!(*regions)); + DEBUG(2, "calc_erase_regions done, es = %zd size = %zd regions = %d\n", + erase_size, total_size, *regions); + return info; +} + + +static struct blkmtd_dev *add_device(char *devname, int readonly, int erase_size) { -#ifdef BLKMTD_PROC_DEBUG - if(blkmtd_proc) { - remove_proc_entry("blkmtd_debug", NULL); - } + struct block_device *bdev; + int mode; + struct blkmtd_dev *dev; + + if(!devname) + return NULL; + + /* Get a handle on the device */ + + +#ifdef MODULE + mode = (readonly) ? O_RDONLY : O_RDWR; + bdev = open_bdev_excl(devname, mode, NULL); +#else + mode = (readonly) ? FMODE_READ : FMODE_WRITE; + bdev = open_by_devnum(name_to_dev_t(devname), mode); #endif + if(IS_ERR(bdev)) { + err("error: cannot open device %s", devname); + DEBUG(2, "blkmtd: opening bdev returned %ld\n", PTR_ERR(bdev)); + return NULL; + } + + DEBUG(1, "blkmtd: found a block device major = %d, minor = %d\n", + MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); + + if(MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) { + err("attempting to use an MTD device as a block device"); + blkdev_put(bdev); + return NULL; + } + + dev = kmalloc(sizeof(struct blkmtd_dev), GFP_KERNEL); + if(dev == NULL) { + blkdev_put(bdev); + return NULL; + } + + memset(dev, 0, sizeof(struct blkmtd_dev)); + dev->blkdev = bdev; + if(!readonly) { + mutex_init(&dev->wrbuf_mutex); + } + + dev->mtd_info.size = dev->blkdev->bd_inode->i_size & PAGE_MASK; + + /* Setup the MTD structure */ + /* make the name contain the block device in */ + dev->mtd_info.name = kmalloc(sizeof("blkmtd: ") + strlen(devname), GFP_KERNEL); + if(dev->mtd_info.name == NULL) + goto devinit_err; + + sprintf(dev->mtd_info.name, "blkmtd: %s", devname); + dev->mtd_info.eraseregions = calc_erase_regions(erase_size, dev->mtd_info.size, + &dev->mtd_info.numeraseregions); + if(dev->mtd_info.eraseregions == NULL) + goto devinit_err; - if (mtd_rawdevice) { - /* sync the device */ - if (!mtd_rawdevice->readonly) { - blkmtd_sync(&mtd_rawdevice->mtd_info); - write_task_finish = 1; - wake_up_interruptible(&thr_wq); - down(&thread_sem); - } - del_mtd_device(&mtd_rawdevice->mtd_info); - if(mtd_rawdevice->binding != NULL) - blkdev_put(mtd_rawdevice->binding, BDEV_RAW); - - if(mtd_rawdevice->mtd_info.eraseregions) - kfree(mtd_rawdevice->mtd_info.eraseregions); - if(mtd_rawdevice->mtd_info.name) - kfree(mtd_rawdevice->mtd_info.name); - - kfree(mtd_rawdevice); - } - if(write_queue) - kfree(write_queue); - - if(erase_page) { - UnlockPage(erase_page); - __free_pages(erase_page, 0); - } - printk("blkmtd: unloaded for %s\n", device); + dev->mtd_info.erasesize = dev->mtd_info.eraseregions->erasesize; + DEBUG(1, "blkmtd: init: found %d erase regions\n", + dev->mtd_info.numeraseregions); + + if(readonly) { + dev->mtd_info.type = MTD_ROM; + dev->mtd_info.flags = MTD_CAP_ROM; + } else { + dev->mtd_info.type = MTD_RAM; + dev->mtd_info.flags = MTD_CAP_RAM; + dev->mtd_info.erase = blkmtd_erase; + dev->mtd_info.write = blkmtd_write; + dev->mtd_info.writev = default_mtd_writev; + dev->mtd_info.sync = blkmtd_sync; + } + dev->mtd_info.read = blkmtd_read; + dev->mtd_info.readv = default_mtd_readv; + dev->mtd_info.priv = dev; + dev->mtd_info.owner = THIS_MODULE; + + list_add(&dev->list, &blkmtd_device_list); + if (add_mtd_device(&dev->mtd_info)) { + /* Device didnt get added, so free the entry */ + list_del(&dev->list); + goto devinit_err; + } else { + info("mtd%d: [%s] erase_size = %dKiB %s", + dev->mtd_info.index, dev->mtd_info.name + strlen("blkmtd: "), + dev->mtd_info.erasesize >> 10, + readonly ? "(read-only)" : ""); + } + + return dev; + + devinit_err: + free_device(dev); + return NULL; } -extern struct module __this_module; + +/* Cleanup and exit - sync the device and kill of the kernel thread */ +static void __devexit cleanup_blkmtd(void) +{ + struct list_head *temp1, *temp2; + + /* Remove the MTD devices */ + list_for_each_safe(temp1, temp2, &blkmtd_device_list) { + struct blkmtd_dev *dev = list_entry(temp1, struct blkmtd_dev, + list); + blkmtd_sync(&dev->mtd_info); + del_mtd_device(&dev->mtd_info); + info("mtd%d: [%s] removed", dev->mtd_info.index, + dev->mtd_info.name + strlen("blkmtd: ")); + list_del(&dev->list); + free_device(dev); + } +} #ifndef MODULE @@ -982,335 +740,79 @@ extern struct module __this_module; static int __init param_blkmtd_device(char *str) { - device = str; - return 1; + int i; + + for(i = 0; i < MAX_DEVICES; i++) { + device[i] = str; + DEBUG(2, "blkmtd: device setup: %d = %s\n", i, device[i]); + strsep(&str, ","); + } + return 1; } static int __init param_blkmtd_erasesz(char *str) { - erasesz = simple_strtol(str, NULL, 0); - return 1; + int i; + for(i = 0; i < MAX_DEVICES; i++) { + char *val = strsep(&str, ","); + if(val) + erasesz[i] = simple_strtoul(val, NULL, 0); + DEBUG(2, "blkmtd: erasesz setup: %d = %d\n", i, erasesz[i]); + } + + return 1; } static int __init param_blkmtd_ro(char *str) { - ro = simple_strtol(str, NULL, 0); - return 1; -} - + int i; + for(i = 0; i < MAX_DEVICES; i++) { + char *val = strsep(&str, ","); + if(val) + ro[i] = simple_strtoul(val, NULL, 0); + DEBUG(2, "blkmtd: ro setup: %d = %d\n", i, ro[i]); + } -static int __init param_blkmtd_bs(char *str) -{ - bs = simple_strtol(str, NULL, 0); - return 1; + return 1; } -static int __init param_blkmtd_count(char *str) +static int __init param_blkmtd_sync(char *str) { - count = simple_strtol(str, NULL, 0); - return 1; + if(str[0] == '1') + sync = 1; + return 1; } __setup("blkmtd_device=", param_blkmtd_device); __setup("blkmtd_erasesz=", param_blkmtd_erasesz); __setup("blkmtd_ro=", param_blkmtd_ro); -__setup("blkmtd_bs=", param_blkmtd_bs); -__setup("blkmtd_count=", param_blkmtd_count); +__setup("blkmtd_sync=", param_blkmtd_sync); #endif -/* for a given size and initial erase size, calculate the number and size of each - erase region */ -static int __init calc_erase_regions(struct mtd_erase_region_info *info, size_t erase_size, size_t total_size) -{ - int count = 0; - int offset = 0; - int regions = 0; - - while(total_size) { - count = total_size / erase_size; - if(count) { - total_size = total_size % erase_size; - if(info) { - info->offset = offset; - info->erasesize = erase_size; - info->numblocks = count; - info++; - } - offset += (count * erase_size); - regions++; - } - while(erase_size > total_size) - erase_size >>= 1; - } - return regions; -} - - -extern kdev_t name_to_kdev_t(char *line) __init; - /* Startup */ static int __init init_blkmtd(void) { -#ifdef MODULE - struct file *file = NULL; - struct inode *inode; -#endif + int i; - int maj, min; - int i, blocksize, blocksize_bits; - loff_t size = 0; - int readonly = 0; - int erase_size = CONFIG_MTD_BLKDEV_ERASESIZE; - kdev_t rdev; - int err; - int mode; - int regions; - - /* Check args */ - if(device == 0) { - printk("blkmtd: error, missing `device' name\n"); - return -EINVAL; - } - - if(ro) - readonly = 1; - - if(erasesz) - erase_size = erasesz; - - if(wqs) { - if(wqs < 16) - wqs = 16; - if(wqs > 4*WRITE_QUEUE_SZ) - wqs = 4*WRITE_QUEUE_SZ; - write_queue_sz = wqs; - } - - DEBUG(1, "blkmtd: device = `%s' erase size = %dK readonly = %s queue size = %d\n", - device, erase_size, readonly ? "yes" : "no", write_queue_sz); - /* Get a handle on the device */ - mode = (readonly) ? O_RDONLY : O_RDWR; + info("version " VERSION); + /* Check args - device[0] is the bare minimum*/ + if(!device[0]) { + err("error: missing `device' name\n"); + return -EINVAL; + } -#ifdef MODULE + for(i = 0; i < MAX_DEVICES; i++) + add_device(device[i], ro[i], erasesz[i] << 10); - file = filp_open(device, mode, 0); - if(IS_ERR(file)) { - printk("blkmtd: error, cant open device %s\n", device); - DEBUG(2, "blkmtd: filp_open returned %ld\n", PTR_ERR(file)); - return 1; - } - - /* determine is this is a block device and if so get its major and minor - numbers */ - inode = file->f_dentry->d_inode; - if(!S_ISBLK(inode->i_mode)) { - printk("blkmtd: %s not a block device\n", device); - filp_close(file, NULL); - return 1; - } - rdev = inode->i_rdev; - filp_close(file, NULL); -#else - rdev = name_to_kdev_t(device); -#endif + if(list_empty(&blkmtd_device_list)) + return -EINVAL; - maj = MAJOR(rdev); - min = MINOR(rdev); - DEBUG(1, "blkmtd: found a block device major = %d, minor = %d\n", maj, min); - - if(!rdev) { - printk("blkmtd: bad block device: `%s'\n", device); - return 1; - } - - if(maj == MTD_BLOCK_MAJOR) { - printk("blkmtd: attempting to use an MTD device as a block device\n"); - return 1; - } - - DEBUG(1, "blkmtd: devname = %s\n", bdevname(rdev)); - blocksize = BLOCK_SIZE; - - if(bs) { - blocksize = bs; - } else { - if (blksize_size[maj] && blksize_size[maj][min]) { - DEBUG(2, "blkmtd: blksize_size = %d\n", blksize_size[maj][min]); - blocksize = blksize_size[maj][min]; - } - } - i = blocksize; - blocksize_bits = 0; - while(i != 1) { - blocksize_bits++; - i >>= 1; - } - - if(count) { - size = count; - } else { - if (blk_size[maj]) { - size = ((loff_t) blk_size[maj][min] << BLOCK_SIZE_BITS) >> blocksize_bits; - } - } - size *= blocksize; - DEBUG(1, "blkmtd: size = %ld\n", (long int)size); - - if(size == 0) { - printk("blkmtd: cant determine size\n"); - return 1; - } - - mtd_rawdevice = (mtd_raw_dev_data_t *)kmalloc(sizeof(mtd_raw_dev_data_t), GFP_KERNEL); - if(mtd_rawdevice == NULL) { - err = -ENOMEM; - goto init_err; - } - memset(mtd_rawdevice, 0, sizeof(mtd_raw_dev_data_t)); - /* get the block device */ - mtd_rawdevice->binding = bdget(kdev_t_to_nr(MKDEV(maj, min))); - err = blkdev_get(mtd_rawdevice->binding, mode, 0, BDEV_RAW); - if (err) { - goto init_err; - } - mtd_rawdevice->totalsize = size; - mtd_rawdevice->sector_size = blocksize; - mtd_rawdevice->sector_bits = blocksize_bits; - mtd_rawdevice->readonly = readonly; - - /* See if device ends on page boundary */ - if(size % PAGE_SIZE) { - mtd_rawdevice->partial_last_page = size >> PAGE_SHIFT; - mtd_rawdevice->last_page_sectors = (size & (PAGE_SIZE-1)) >> blocksize_bits; - } - - DEBUG(2, "sector_size = %d, sector_bits = %d, partial_last_page = %d last_page_sectors = %d\n", - mtd_rawdevice->sector_size, mtd_rawdevice->sector_bits, - mtd_rawdevice->partial_last_page, mtd_rawdevice->last_page_sectors); - - /* Setup the MTD structure */ - /* make the name contain the block device in */ - mtd_rawdevice->mtd_info.name = kmalloc(9 + strlen(device), GFP_KERNEL); - if(mtd_rawdevice->mtd_info.name == NULL) - goto init_err; - - sprintf(mtd_rawdevice->mtd_info.name, "blkmtd: %s", device); - if(readonly) { - mtd_rawdevice->mtd_info.type = MTD_ROM; - mtd_rawdevice->mtd_info.flags = MTD_CAP_ROM; - mtd_rawdevice->mtd_info.erasesize = erase_size << 10; - } else { - mtd_rawdevice->mtd_info.type = MTD_RAM; - mtd_rawdevice->mtd_info.flags = MTD_CAP_RAM; - mtd_rawdevice->mtd_info.erasesize = erase_size << 10; - } - mtd_rawdevice->mtd_info.size = size; - mtd_rawdevice->mtd_info.erase = blkmtd_erase; - mtd_rawdevice->mtd_info.read = blkmtd_read; - mtd_rawdevice->mtd_info.write = blkmtd_write; - mtd_rawdevice->mtd_info.sync = blkmtd_sync; - mtd_rawdevice->mtd_info.point = 0; - mtd_rawdevice->mtd_info.unpoint = 0; - - mtd_rawdevice->mtd_info.priv = mtd_rawdevice; - regions = calc_erase_regions(NULL, erase_size << 10, size); - DEBUG(1, "blkmtd: init: found %d erase regions\n", regions); - mtd_rawdevice->mtd_info.eraseregions = kmalloc(regions * sizeof(struct mtd_erase_region_info), GFP_KERNEL); - if(mtd_rawdevice->mtd_info.eraseregions == NULL) { - err = -ENOMEM; - goto init_err; - } - mtd_rawdevice->mtd_info.numeraseregions = regions; - calc_erase_regions(mtd_rawdevice->mtd_info.eraseregions, erase_size << 10, size); - - /* setup the page cache info */ - - mtd_rawdevice->as.nrpages = 0; - INIT_LIST_HEAD(&mtd_rawdevice->as.clean_pages); - INIT_LIST_HEAD(&mtd_rawdevice->as.dirty_pages); - INIT_LIST_HEAD(&mtd_rawdevice->as.locked_pages); - mtd_rawdevice->as.host = NULL; - spin_lock_init(&(mtd_rawdevice->as.i_shared_lock)); - - mtd_rawdevice->as.a_ops = &blkmtd_aops; - mtd_rawdevice->as.i_mmap = NULL; - mtd_rawdevice->as.i_mmap_shared = NULL; - mtd_rawdevice->as.gfp_mask = GFP_KERNEL; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) - mtd_rawdevice->mtd_info.module = THIS_MODULE; -#endif - if (add_mtd_device(&mtd_rawdevice->mtd_info)) { - err = -EIO; - goto init_err; - } - - if(!mtd_rawdevice->readonly) { - /* Allocate the write queue */ - write_queue = kmalloc(write_queue_sz * sizeof(mtdblkdev_write_queue_t), GFP_KERNEL); - if(!write_queue) { - err = -ENOMEM; - goto init_err; - } - /* Set up the erase page */ - erase_page = alloc_pages(GFP_KERNEL, 0); - if(erase_page == NULL) { - err = -ENOMEM; - goto init_err; - } - memset(page_address(erase_page), 0xff, PAGE_SIZE); - lock_page(erase_page); - - init_waitqueue_head(&thr_wq); - init_waitqueue_head(&mtbd_sync_wq); - DEBUG(3, "blkmtd: init: kernel task @ %p\n", write_queue_task); - DEBUG(2, "blkmtd: init: starting kernel task\n"); - kernel_thread(write_queue_task, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); - DEBUG(2, "blkmtd: init: started\n"); - printk("blkmtd loaded: version = %s using %s erase_size = %dK %s\n", - VERSION, device, erase_size, (readonly) ? "(read-only)" : ""); - } - -#ifdef BLKMTD_PROC_DEBUG - /* create proc entry */ - DEBUG(2, "Creating /proc/blkmtd_debug\n"); - blkmtd_proc = create_proc_read_entry("blkmtd_debug", 0444, - NULL, blkmtd_proc_read, NULL); - if(blkmtd_proc == NULL) { - printk("Cant create /proc/blkmtd_debug\n"); - } else { - blkmtd_proc->owner = THIS_MODULE; - } -#endif - - /* Everything is ok if we got here */ - return 0; - - init_err: - - if(mtd_rawdevice) { - if(mtd_rawdevice->mtd_info.eraseregions) - kfree(mtd_rawdevice->mtd_info.eraseregions); - if(mtd_rawdevice->mtd_info.name) - kfree(mtd_rawdevice->mtd_info.name); - if(mtd_rawdevice->binding) - blkdev_put(mtd_rawdevice->binding, BDEV_RAW); - kfree(mtd_rawdevice); - } - - if(write_queue) { - kfree(write_queue); - write_queue = NULL; - } - - if(erase_page) - __free_pages(erase_page, 0); - return err; + return 0; } module_init(init_blkmtd); diff --git a/linux-2.4.x/drivers/mtd/devices/block2mtd.c b/linux-2.4.x/drivers/mtd/devices/block2mtd.c new file mode 100644 index 0000000..208b20a --- /dev/null +++ b/linux-2.4.x/drivers/mtd/devices/block2mtd.c @@ -0,0 +1,494 @@ +/* + * $Id: block2mtd.c,v 1.32 2006/03/29 08:26:27 dwmw2 Exp $ + * + * block2mtd.c - create an mtd from a block device + * + * Copyright (C) 2001,2002 Simon Evans <spse@secret.org.uk> + * Copyright (C) 2004,2005 Jörn Engel <joern@wh.fh-wedel.de> + * + * Licence: GPL + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/blkdev.h> +#include <linux/bio.h> +#include <linux/pagemap.h> +#include <linux/list.h> +#include <linux/init.h> +#include <linux/mtd/mtd.h> +#include <linux/buffer_head.h> +#include <linux/mutex.h> + +#define VERSION "$Revision: 1.32 $" + + +#define ERROR(fmt, args...) printk(KERN_ERR "block2mtd: " fmt "\n" , ## args) +#define INFO(fmt, args...) printk(KERN_INFO "block2mtd: " fmt "\n" , ## args) + + +/* Info for the block device */ +struct block2mtd_dev { + struct list_head list; + struct block_device *blkdev; + struct mtd_info mtd; + struct mutex write_mutex; +}; + + +/* Static info about the MTD, used in cleanup_module */ +static LIST_HEAD(blkmtd_device_list); + + +#define PAGE_READAHEAD 64 +static void cache_readahead(struct address_space *mapping, int index) +{ + filler_t *filler = (filler_t*)mapping->a_ops->readpage; + int i, pagei; + unsigned ret = 0; + unsigned long end_index; + struct page *page; + LIST_HEAD(page_pool); + struct inode *inode = mapping->host; + loff_t isize = i_size_read(inode); + + if (!isize) { + INFO("iSize=0 in cache_readahead\n"); + return; + } + + end_index = ((isize - 1) >> PAGE_CACHE_SHIFT); + + read_lock_irq(&mapping->tree_lock); + for (i = 0; i < PAGE_READAHEAD; i++) { + pagei = index + i; + if (pagei > end_index) { + INFO("Overrun end of disk in cache readahead\n"); + break; + } + page = radix_tree_lookup(&mapping->page_tree, pagei); + if (page && (!i)) + break; + if (page) + continue; + read_unlock_irq(&mapping->tree_lock); + page = page_cache_alloc_cold(mapping); + read_lock_irq(&mapping->tree_lock); + if (!page) + break; + page->index = pagei; + list_add(&page->lru, &page_pool); + ret++; + } + read_unlock_irq(&mapping->tree_lock); + if (ret) + read_cache_pages(mapping, &page_pool, filler, NULL); +} + + +static struct page* page_readahead(struct address_space *mapping, int index) +{ + filler_t *filler = (filler_t*)mapping->a_ops->readpage; + cache_readahead(mapping, index); + return read_cache_page(mapping, index, filler, NULL); +} + + +/* erase a specified part of the device */ +static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len) +{ + struct address_space *mapping = dev->blkdev->bd_inode->i_mapping; + struct page *page; + int index = to >> PAGE_SHIFT; // page index + int pages = len >> PAGE_SHIFT; + u_long *p; + u_long *max; + + while (pages) { + page = page_readahead(mapping, index); + if (!page) + return -ENOMEM; + if (IS_ERR(page)) + return PTR_ERR(page); + + max = (u_long*)page_address(page) + PAGE_SIZE; + for (p=(u_long*)page_address(page); p<max; p++) + if (*p != -1UL) { + lock_page(page); + memset(page_address(page), 0xff, PAGE_SIZE); + set_page_dirty(page); + unlock_page(page); + break; + } + + page_cache_release(page); + pages--; + index++; + } + return 0; +} +static int block2mtd_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct block2mtd_dev *dev = mtd->priv; + size_t from = instr->addr; + size_t len = instr->len; + int err; + + instr->state = MTD_ERASING; + mutex_lock(&dev->write_mutex); + err = _block2mtd_erase(dev, from, len); + mutex_unlock(&dev->write_mutex); + if (err) { + ERROR("erase failed err = %d", err); + instr->state = MTD_ERASE_FAILED; + } else + instr->state = MTD_ERASE_DONE; + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + return err; +} + + +static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct block2mtd_dev *dev = mtd->priv; + struct page *page; + int index = from >> PAGE_SHIFT; + int offset = from & (PAGE_SIZE-1); + int cpylen; + + if (from > mtd->size) + return -EINVAL; + if (from + len > mtd->size) + len = mtd->size - from; + + if (retlen) + *retlen = 0; + + while (len) { + if ((offset + len) > PAGE_SIZE) + cpylen = PAGE_SIZE - offset; // multiple pages + else + cpylen = len; // this page + len = len - cpylen; + + // Get page + page = page_readahead(dev->blkdev->bd_inode->i_mapping, index); + if (!page) + return -ENOMEM; + if (IS_ERR(page)) + return PTR_ERR(page); + + memcpy(buf, page_address(page) + offset, cpylen); + page_cache_release(page); + + if (retlen) + *retlen += cpylen; + buf += cpylen; + offset = 0; + index++; + } + return 0; +} + + +/* write data to the underlying device */ +static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf, + loff_t to, size_t len, size_t *retlen) +{ + struct page *page; + struct address_space *mapping = dev->blkdev->bd_inode->i_mapping; + int index = to >> PAGE_SHIFT; // page index + int offset = to & ~PAGE_MASK; // page offset + int cpylen; + + if (retlen) + *retlen = 0; + while (len) { + if ((offset+len) > PAGE_SIZE) + cpylen = PAGE_SIZE - offset; // multiple pages + else + cpylen = len; // this page + len = len - cpylen; + + // Get page + page = page_readahead(mapping, index); + if (!page) + return -ENOMEM; + if (IS_ERR(page)) + return PTR_ERR(page); + + if (memcmp(page_address(page)+offset, buf, cpylen)) { + lock_page(page); + memcpy(page_address(page) + offset, buf, cpylen); + set_page_dirty(page); + unlock_page(page); + } + page_cache_release(page); + + if (retlen) + *retlen += cpylen; + + buf += cpylen; + offset = 0; + index++; + } + return 0; +} +static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct block2mtd_dev *dev = mtd->priv; + int err; + + if (!len) + return 0; + if (to >= mtd->size) + return -ENOSPC; + if (to + len > mtd->size) + len = mtd->size - to; + + mutex_lock(&dev->write_mutex); + err = _block2mtd_write(dev, buf, to, len, retlen); + mutex_unlock(&dev->write_mutex); + if (err > 0) + err = 0; + return err; +} + + +/* sync the device - wait until the write queue is empty */ +static void block2mtd_sync(struct mtd_info *mtd) +{ + struct block2mtd_dev *dev = mtd->priv; + sync_blockdev(dev->blkdev); + return; +} + + +static void block2mtd_free_device(struct block2mtd_dev *dev) +{ + if (!dev) + return; + + kfree(dev->mtd.name); + + if (dev->blkdev) { + invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping); + close_bdev_excl(dev->blkdev); + } + + kfree(dev); +} + + +/* FIXME: ensure that mtd->size % erase_size == 0 */ +static struct block2mtd_dev *add_device(char *devname, int erase_size) +{ + struct block_device *bdev; + struct block2mtd_dev *dev; + + if (!devname) + return NULL; + + dev = kmalloc(sizeof(struct block2mtd_dev), GFP_KERNEL); + if (!dev) + return NULL; + memset(dev, 0, sizeof(*dev)); + + /* Get a handle on the device */ + bdev = open_bdev_excl(devname, O_RDWR, NULL); + if (IS_ERR(bdev)) { + ERROR("error: cannot open device %s", devname); + goto devinit_err; + } + dev->blkdev = bdev; + + if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) { + ERROR("attempting to use an MTD device as a block device"); + goto devinit_err; + } + + mutex_init(&dev->write_mutex); + + /* Setup the MTD structure */ + /* make the name contain the block device in */ + dev->mtd.name = kmalloc(sizeof("block2mtd: ") + strlen(devname), + GFP_KERNEL); + if (!dev->mtd.name) + goto devinit_err; + + sprintf(dev->mtd.name, "block2mtd: %s", devname); + + dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK; + dev->mtd.erasesize = erase_size; + dev->mtd.type = MTD_BLOCK; + dev->mtd.flags = MTD_CAP_RAM; + dev->mtd.erase = block2mtd_erase; + dev->mtd.write = block2mtd_write; + dev->mtd.writev = default_mtd_writev; + dev->mtd.sync = block2mtd_sync; + dev->mtd.read = block2mtd_read; + dev->mtd.readv = default_mtd_readv; + dev->mtd.priv = dev; + dev->mtd.owner = THIS_MODULE; + + if (add_mtd_device(&dev->mtd)) { + /* Device didnt get added, so free the entry */ + goto devinit_err; + } + list_add(&dev->list, &blkmtd_device_list); + INFO("mtd%d: [%s] erase_size = %dKiB [%d]", dev->mtd.index, + dev->mtd.name + strlen("blkmtd: "), + dev->mtd.erasesize >> 10, dev->mtd.erasesize); + return dev; + +devinit_err: + block2mtd_free_device(dev); + return NULL; +} + + +static int ustrtoul(const char *cp, char **endp, unsigned int base) +{ + unsigned long result = simple_strtoul(cp, endp, base); + switch (**endp) { + case 'G' : + result *= 1024; + case 'M': + result *= 1024; + case 'k': + result *= 1024; + /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */ + if ((*endp)[1] == 'i') + (*endp) += 2; + } + return result; +} + + +static int parse_num(size_t *num, const char *token) +{ + char *endp; + size_t n; + + n = (size_t) ustrtoul(token, &endp, 0); + if (*endp) + return -EINVAL; + + *num = n; + return 0; +} + + +static int parse_name(char **pname, const char *token, size_t limit) +{ + size_t len; + char *name; + + len = strlen(token) + 1; + if (len > limit) + return -ENOSPC; + + name = kmalloc(len, GFP_KERNEL); + if (!name) + return -ENOMEM; + + strcpy(name, token); + + *pname = name; + return 0; +} + + +static inline void kill_final_newline(char *str) +{ + char *newline = strrchr(str, '\n'); + if (newline && !newline[1]) + *newline = 0; +} + + +#define parse_err(fmt, args...) do { \ + ERROR("block2mtd: " fmt "\n", ## args); \ + return 0; \ +} while (0) + +static int block2mtd_setup(const char *val, struct kernel_param *kp) +{ + char buf[80+12], *str=buf; /* 80 for device, 12 for erase size */ + char *token[2]; + char *name; + size_t erase_size = PAGE_SIZE; + int i, ret; + + if (strnlen(val, sizeof(buf)) >= sizeof(buf)) + parse_err("parameter too long"); + + strcpy(str, val); + kill_final_newline(str); + + for (i=0; i<2; i++) + token[i] = strsep(&str, ","); + + if (str) + parse_err("too many arguments"); + + if (!token[0]) + parse_err("no argument"); + + ret = parse_name(&name, token[0], 80); + if (ret == -ENOMEM) + parse_err("out of memory"); + if (ret == -ENOSPC) + parse_err("name too long"); + if (ret) + return 0; + + if (token[1]) { + ret = parse_num(&erase_size, token[1]); + if (ret) + parse_err("illegal erase size"); + } + + add_device(name, erase_size); + + return 0; +} + + +module_param_call(block2mtd, block2mtd_setup, NULL, NULL, 0200); +MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=<dev>[,<erasesize>]\""); + +static int __init block2mtd_init(void) +{ + INFO("version " VERSION); + return 0; +} + + +static void __devexit block2mtd_exit(void) +{ + struct list_head *pos, *next; + + /* Remove the MTD devices */ + list_for_each_safe(pos, next, &blkmtd_device_list) { + struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list); + block2mtd_sync(&dev->mtd); + del_mtd_device(&dev->mtd); + INFO("mtd%d: [%s] removed", dev->mtd.index, + dev->mtd.name + strlen("blkmtd: ")); + list_del(&dev->list); + block2mtd_free_device(dev); + } +} + + +module_init(block2mtd_init); +module_exit(block2mtd_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Simon Evans <spse@secret.org.uk> and others"); +MODULE_DESCRIPTION("Emulate an MTD using a block device"); diff --git a/linux-2.4.x/drivers/mtd/devices/doc2000.c b/linux-2.4.x/drivers/mtd/devices/doc2000.c index 837ed5f..bab6fde 100644 --- a/linux-2.4.x/drivers/mtd/devices/doc2000.c +++ b/linux-2.4.x/drivers/mtd/devices/doc2000.c @@ -4,7 +4,7 @@ * (c) 1999 Machine Vision Holdings, Inc. * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> * - * $Id: doc2000.c,v 1.46 2001/10/02 15:05:13 dwmw2 Exp $ + * $Id: doc2000.c,v 1.69 2006/03/29 08:26:27 dwmw2 Exp $ */ #include <linux/kernel.h> @@ -19,13 +19,15 @@ #include <linux/sched.h> #include <linux/init.h> #include <linux/types.h> +#include <linux/bitops.h> +#include <linux/mutex.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> -#include <linux/mtd/nand_ids.h> #include <linux/mtd/doc2000.h> #define DOC_SUPPORT_2000 +#define DOC_SUPPORT_2000TSOP #define DOC_SUPPORT_MILLENNIUM #ifdef DOC_SUPPORT_2000 @@ -34,7 +36,7 @@ #define DoC_is_2000(doc) (0) #endif -#ifdef DOC_SUPPORT_MILLENNIUM +#if defined(DOC_SUPPORT_2000TSOP) || defined(DOC_SUPPORT_MILLENNIUM) #define DoC_is_Millennium(doc) (doc->ChipID == DOC_ChipID_DocMil) #else #define DoC_is_Millennium(doc) (0) @@ -54,9 +56,12 @@ static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf, u_char *eccbuf); + size_t *retlen, u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel); static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf, u_char *eccbuf); + size_t *retlen, const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel); +static int doc_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen, + u_char *eccbuf, struct nand_oobinfo *oobsel); static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t *retlen, u_char *buf); static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, @@ -72,20 +77,20 @@ static void DoC_Delay(struct DiskOnChip *doc, unsigned short cycles) { volatile char dummy; int i; - + for (i = 0; i < cycles; i++) { if (DoC_is_Millennium(doc)) dummy = ReadDOC(doc->virtadr, NOP); else dummy = ReadDOC(doc->virtadr, DOCStatus); } - + } /* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ static int _DoC_WaitReady(struct DiskOnChip *doc) { - unsigned long docptr = doc->virtadr; + void __iomem *docptr = doc->virtadr; unsigned long timeo = jiffies + (HZ * 10); DEBUG(MTD_DEBUG_LEVEL3, @@ -93,16 +98,16 @@ static int _DoC_WaitReady(struct DiskOnChip *doc) /* Out-of-line routine to wait for chip response */ while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { + /* issue 2 read from NOP register after reading from CDSNControl register + see Software Requirement 11.4 item 2. */ + DoC_Delay(doc, 2); + if (time_after(jiffies, timeo)) { DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n"); return -EIO; } - if (current->need_resched) { - set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(1); - } - else - udelay(1); + udelay(1); + cond_resched(); } return 0; @@ -110,7 +115,8 @@ static int _DoC_WaitReady(struct DiskOnChip *doc) static inline int DoC_WaitReady(struct DiskOnChip *doc) { - unsigned long docptr = doc->virtadr; + void __iomem *docptr = doc->virtadr; + /* This is inline, to optimise the common case, where it's ready instantly */ int ret = 0; @@ -136,7 +142,7 @@ static inline int DoC_WaitReady(struct DiskOnChip *doc) static inline int DoC_Command(struct DiskOnChip *doc, unsigned char command, unsigned char xtraflags) { - unsigned long docptr = doc->virtadr; + void __iomem *docptr = doc->virtadr; if (DoC_is_2000(doc)) xtraflags |= CDSN_CTRL_FLASH_IO; @@ -150,6 +156,8 @@ static inline int DoC_Command(struct DiskOnChip *doc, unsigned char command, /* Send the command */ WriteDOC_(command, docptr, doc->ioreg); + if (DoC_is_Millennium(doc)) + WriteDOC(command, docptr, WritePipeTerm); /* Lower the CLE line */ WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl); @@ -166,10 +174,8 @@ static inline int DoC_Command(struct DiskOnChip *doc, unsigned char command, static int DoC_Address(struct DiskOnChip *doc, int numbytes, unsigned long ofs, unsigned char xtraflags1, unsigned char xtraflags2) { - unsigned long docptr; int i; - - docptr = doc->virtadr; + void __iomem *docptr = doc->virtadr; if (DoC_is_2000(doc)) xtraflags1 |= CDSN_CTRL_FLASH_IO; @@ -211,9 +217,12 @@ static int DoC_Address(struct DiskOnChip *doc, int numbytes, unsigned long ofs, } } + if (DoC_is_Millennium(doc)) + WriteDOC(ofs & 0xff, docptr, WritePipeTerm); + DoC_Delay(doc, 2); /* Needed for some slow flash chips. mf. */ - - /* FIXME: The SlowIO's for millennium could be replaced by + + /* FIXME: The SlowIO's for millennium could be replaced by a single WritePipeTerm here. mf. */ /* Lower the ALE line */ @@ -231,11 +240,9 @@ static void DoC_ReadBuf(struct DiskOnChip *doc, u_char * buf, int len) { volatile int dummy; int modulus = 0xffff; - unsigned long docptr; + void __iomem *docptr = doc->virtadr; int i; - docptr = doc->virtadr; - if (len <= 0) return; @@ -262,11 +269,9 @@ static void DoC_ReadBuf(struct DiskOnChip *doc, u_char * buf, int len) /* Write a buffer to DoC, taking care of Millennium odditys */ static void DoC_WriteBuf(struct DiskOnChip *doc, const u_char * buf, int len) { - unsigned long docptr; + void __iomem *docptr = doc->virtadr; int i; - docptr = doc->virtadr; - if (len <= 0) return; @@ -283,7 +288,7 @@ static void DoC_WriteBuf(struct DiskOnChip *doc, const u_char * buf, int len) static inline int DoC_SelectChip(struct DiskOnChip *doc, int chip) { - unsigned long docptr = doc->virtadr; + void __iomem *docptr = doc->virtadr; /* Software requirement 11.4.4 before writing DeviceSelect */ /* Deassert the CE line to eliminate glitches on the FCE# outputs */ @@ -307,7 +312,7 @@ static inline int DoC_SelectChip(struct DiskOnChip *doc, int chip) static inline int DoC_SelectFloor(struct DiskOnChip *doc, int floor) { - unsigned long docptr = doc->virtadr; + void __iomem *docptr = doc->virtadr; /* Select the floor (bank) of chips required */ WriteDOC(floor, docptr, FloorSelect); @@ -320,7 +325,7 @@ static inline int DoC_SelectFloor(struct DiskOnChip *doc, int floor) static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) { - int mfr, id, i; + int mfr, id, i, j; volatile char dummy; /* Page in the required floor/chip */ @@ -349,23 +354,33 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) /* Read the manufacturer and device id codes from the device */ - /* CDSN Slow IO register see Software Requirement 11.4 item 5. */ - dummy = ReadDOC(doc->virtadr, CDSNSlowIO); - DoC_Delay(doc, 2); - mfr = ReadDOC_(doc->virtadr, doc->ioreg); + if (DoC_is_Millennium(doc)) { + DoC_Delay(doc, 2); + dummy = ReadDOC(doc->virtadr, ReadPipeInit); + mfr = ReadDOC(doc->virtadr, LastDataRead); - /* CDSN Slow IO register see Software Requirement 11.4 item 5. */ - dummy = ReadDOC(doc->virtadr, CDSNSlowIO); - DoC_Delay(doc, 2); - id = ReadDOC_(doc->virtadr, doc->ioreg); + DoC_Delay(doc, 2); + dummy = ReadDOC(doc->virtadr, ReadPipeInit); + id = ReadDOC(doc->virtadr, LastDataRead); + } else { + /* CDSN Slow IO register see Software Req 11.4 item 5. */ + dummy = ReadDOC(doc->virtadr, CDSNSlowIO); + DoC_Delay(doc, 2); + mfr = ReadDOC_(doc->virtadr, doc->ioreg); + + /* CDSN Slow IO register see Software Req 11.4 item 5. */ + dummy = ReadDOC(doc->virtadr, CDSNSlowIO); + DoC_Delay(doc, 2); + id = ReadDOC_(doc->virtadr, doc->ioreg); + } /* No response - return failure */ if (mfr == 0xff || mfr == 0) return 0; - /* Check it's the same as the first chip we identified. + /* Check it's the same as the first chip we identified. * M-Systems say that any given DiskOnChip device should only - * contain _one_ type of flash part, although that's not a + * contain _one_ type of flash part, although that's not a * hardware restriction. */ if (doc->mfr) { if (doc->mfr == mfr && doc->id == id) @@ -378,20 +393,23 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) /* Print and store the manufacturer and ID codes. */ for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (mfr == nand_flash_ids[i].manufacture_id && - id == nand_flash_ids[i].model_id) { + if (id == nand_flash_ids[i].id) { + /* Try to identify manufacturer */ + for (j = 0; nand_manuf_ids[j].id != 0x0; j++) { + if (nand_manuf_ids[j].id == mfr) + break; + } printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, " - "Chip ID: %2.2X (%s)\n", mfr, id, - nand_flash_ids[i].name); + "Chip ID: %2.2X (%s:%s)\n", mfr, id, + nand_manuf_ids[j].name, nand_flash_ids[i].name); if (!doc->mfr) { doc->mfr = mfr; doc->id = id; doc->chipshift = - nand_flash_ids[i].chipshift; - doc->page256 = nand_flash_ids[i].page256; - doc->pageadrlen = - nand_flash_ids[i].pageadrlen; + ffs((nand_flash_ids[i].chipsize << 20)) - 1; + doc->page256 = (nand_flash_ids[i].pagesize == 256) ? 1 : 0; + doc->pageadrlen = doc->chipshift > 25 ? 3 : 2; doc->erasesize = nand_flash_ids[i].erasesize; return 1; @@ -411,20 +429,16 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) /* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */ -static void DoC_ScanChips(struct DiskOnChip *this) +static void DoC_ScanChips(struct DiskOnChip *this, int maxchips) { int floor, chip; int numchips[MAX_FLOORS]; - int maxchips = MAX_CHIPS; int ret = 1; this->numchips = 0; this->mfr = 0; this->id = 0; - if (DoC_is_Millennium(this)) - maxchips = MAX_CHIPS_MIL; - /* For each floor, find the number of valid chips it contains */ for (floor = 0; floor < MAX_FLOORS; floor++) { ret = 1; @@ -454,7 +468,7 @@ static void DoC_ScanChips(struct DiskOnChip *this) ret = 0; - /* Fill out the chip array with {floor, chipno} for each + /* Fill out the chip array with {floor, chipno} for each * detected chip in the device. */ for (floor = 0; floor < MAX_FLOORS; floor++) { for (chip = 0; chip < numchips[floor]; chip++) { @@ -514,39 +528,54 @@ static const char im_name[] = "DoC2k_init"; */ static void DoC2k_init(struct mtd_info *mtd) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + struct DiskOnChip *this = mtd->priv; struct DiskOnChip *old = NULL; + int maxchips; /* We must avoid being called twice for the same device. */ if (doc2klist) - old = (struct DiskOnChip *) doc2klist->priv; + old = doc2klist->priv; while (old) { if (DoC2k_is_alias(old, this)) { printk(KERN_NOTICE "Ignoring DiskOnChip 2000 at 0x%lX - already configured\n", this->physadr); - iounmap((void *) this->virtadr); + iounmap(this->virtadr); kfree(mtd); return; } if (old->nextdoc) - old = (struct DiskOnChip *) old->nextdoc->priv; + old = old->nextdoc->priv; else old = NULL; } switch (this->ChipID) { + case DOC_ChipID_Doc2kTSOP: + mtd->name = "DiskOnChip 2000 TSOP"; + this->ioreg = DoC_Mil_CDSN_IO; + /* Pretend it's a Millennium */ + this->ChipID = DOC_ChipID_DocMil; + maxchips = MAX_CHIPS; + break; case DOC_ChipID_Doc2k: mtd->name = "DiskOnChip 2000"; this->ioreg = DoC_2k_CDSN_IO; + maxchips = MAX_CHIPS; break; case DOC_ChipID_DocMil: mtd->name = "DiskOnChip Millennium"; this->ioreg = DoC_Mil_CDSN_IO; + maxchips = MAX_CHIPS_MIL; break; + default: + printk("Unknown ChipID 0x%02x\n", this->ChipID); + kfree(mtd); + iounmap(this->virtadr); + return; } printk(KERN_NOTICE "%s found at address 0x%lX\n", mtd->name, @@ -554,11 +583,12 @@ static void DoC2k_init(struct mtd_info *mtd) mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; + mtd->ecctype = MTD_ECC_RS_DiskOnChip; mtd->size = 0; mtd->erasesize = 0; mtd->oobblock = 512; mtd->oobsize = 16; - mtd->module = THIS_MODULE; + mtd->owner = THIS_MODULE; mtd->erase = doc_erase; mtd->point = NULL; mtd->unpoint = NULL; @@ -566,6 +596,7 @@ static void DoC2k_init(struct mtd_info *mtd) mtd->write = doc_write; mtd->read_ecc = doc_read_ecc; mtd->write_ecc = doc_write_ecc; + mtd->writev_ecc = doc_writev_ecc; mtd->read_oob = doc_read_oob; mtd->write_oob = doc_write_oob; mtd->sync = NULL; @@ -575,14 +606,14 @@ static void DoC2k_init(struct mtd_info *mtd) this->curfloor = -1; this->curchip = -1; - init_MUTEX(&this->lock); + mutex_init(&this->lock); /* Ident all the chips present. */ - DoC_ScanChips(this); + DoC_ScanChips(this, maxchips); if (!this->totlen) { kfree(mtd); - iounmap((void *) this->virtadr); + iounmap(this->virtadr); } else { this->nextdoc = doc2klist; doc2klist = mtd; @@ -597,146 +628,154 @@ static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf) { /* Just a special case of doc_read_ecc */ - return doc_read_ecc(mtd, from, len, retlen, buf, NULL); + return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL); } static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, - size_t * retlen, u_char * buf, u_char * eccbuf) + size_t * retlen, u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; - unsigned long docptr; + struct DiskOnChip *this = mtd->priv; + void __iomem *docptr = this->virtadr; struct Nand *mychip; unsigned char syndrome[6]; volatile char dummy; int i, len256 = 0, ret=0; - - docptr = this->virtadr; + size_t left = len; /* Don't allow read past end of device */ if (from >= this->totlen) return -EINVAL; - down(&this->lock); + mutex_lock(&this->lock); - /* Don't allow a single read to cross a 512-byte block boundary */ - if (from + len > ((from | 0x1ff) + 1)) - len = ((from | 0x1ff) + 1) - from; + *retlen = 0; + while (left) { + len = left; - /* The ECC will not be calculated correctly if less than 512 is read */ - if (len != 0x200 && eccbuf) - printk(KERN_WARNING - "ECC needs a full sector read (adr: %lx size %lx)\n", - (long) from, (long) len); + /* Don't allow a single read to cross a 512-byte block boundary */ + if (from + len > ((from | 0x1ff) + 1)) + len = ((from | 0x1ff) + 1) - from; - /* printk("DoC_Read (adr: %lx size %lx)\n", (long) from, (long) len); */ + /* The ECC will not be calculated correctly if less than 512 is read */ + if (len != 0x200 && eccbuf) + printk(KERN_WARNING + "ECC needs a full sector read (adr: %lx size %lx)\n", + (long) from, (long) len); + /* printk("DoC_Read (adr: %lx size %lx)\n", (long) from, (long) len); */ - /* Find the chip which is to be used and select it */ - mychip = &this->chips[from >> (this->chipshift)]; - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } - - this->curfloor = mychip->floor; - this->curchip = mychip->chip; + /* Find the chip which is to be used and select it */ + mychip = &this->chips[from >> (this->chipshift)]; - DoC_Command(this, - (!this->page256 - && (from & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0, - CDSN_CTRL_WP); - DoC_Address(this, ADDR_COLUMN_PAGE, from, CDSN_CTRL_WP, - CDSN_CTRL_ECC_IO); - - if (eccbuf) { - /* Prime the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, ECCConf); - WriteDOC(DOC_ECC_EN, docptr, ECCConf); - } else { - /* disable the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, ECCConf); - WriteDOC(DOC_ECC_DIS, docptr, ECCConf); - } - - /* treat crossing 256-byte sector for 2M x 8bits devices */ - if (this->page256 && from + len > (from | 0xff) + 1) { - len256 = (from | 0xff) + 1 - from; - DoC_ReadBuf(this, buf, len256); + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(this, mychip->floor); + DoC_SelectChip(this, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(this, mychip->chip); + } - DoC_Command(this, NAND_CMD_READ0, CDSN_CTRL_WP); - DoC_Address(this, ADDR_COLUMN_PAGE, from + len256, - CDSN_CTRL_WP, CDSN_CTRL_ECC_IO); - } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; - DoC_ReadBuf(this, &buf[len256], len - len256); + DoC_Command(this, + (!this->page256 + && (from & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0, + CDSN_CTRL_WP); + DoC_Address(this, ADDR_COLUMN_PAGE, from, CDSN_CTRL_WP, + CDSN_CTRL_ECC_IO); - /* Let the caller know we completed it */ - *retlen = len; + if (eccbuf) { + /* Prime the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, ECCConf); + WriteDOC(DOC_ECC_EN, docptr, ECCConf); + } else { + /* disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, ECCConf); + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); + } - if (eccbuf) { - /* Read the ECC data through the DiskOnChip ECC logic */ - /* Note: this will work even with 2M x 8bit devices as */ - /* they have 8 bytes of OOB per 256 page. mf. */ - DoC_ReadBuf(this, eccbuf, 6); + /* treat crossing 256-byte sector for 2M x 8bits devices */ + if (this->page256 && from + len > (from | 0xff) + 1) { + len256 = (from | 0xff) + 1 - from; + DoC_ReadBuf(this, buf, len256); - /* Flush the pipeline */ - if (DoC_is_Millennium(this)) { - dummy = ReadDOC(docptr, ECCConf); - dummy = ReadDOC(docptr, ECCConf); - i = ReadDOC(docptr, ECCConf); - } else { - dummy = ReadDOC(docptr, 2k_ECCStatus); - dummy = ReadDOC(docptr, 2k_ECCStatus); - i = ReadDOC(docptr, 2k_ECCStatus); + DoC_Command(this, NAND_CMD_READ0, CDSN_CTRL_WP); + DoC_Address(this, ADDR_COLUMN_PAGE, from + len256, + CDSN_CTRL_WP, CDSN_CTRL_ECC_IO); } - /* Check the ECC Status */ - if (i & 0x80) { - int nb_errors; - /* There was an ECC error */ + DoC_ReadBuf(this, &buf[len256], len - len256); + + /* Let the caller know we completed it */ + *retlen += len; + + if (eccbuf) { + /* Read the ECC data through the DiskOnChip ECC logic */ + /* Note: this will work even with 2M x 8bit devices as */ + /* they have 8 bytes of OOB per 256 page. mf. */ + DoC_ReadBuf(this, eccbuf, 6); + + /* Flush the pipeline */ + if (DoC_is_Millennium(this)) { + dummy = ReadDOC(docptr, ECCConf); + dummy = ReadDOC(docptr, ECCConf); + i = ReadDOC(docptr, ECCConf); + } else { + dummy = ReadDOC(docptr, 2k_ECCStatus); + dummy = ReadDOC(docptr, 2k_ECCStatus); + i = ReadDOC(docptr, 2k_ECCStatus); + } + + /* Check the ECC Status */ + if (i & 0x80) { + int nb_errors; + /* There was an ECC error */ #ifdef ECC_DEBUG - printk(KERN_ERR "DiskOnChip ECC Error: Read at %lx\n", (long)from); + printk(KERN_ERR "DiskOnChip ECC Error: Read at %lx\n", (long)from); #endif - /* Read the ECC syndrom through the DiskOnChip ECC logic. - These syndrome will be all ZERO when there is no error */ - for (i = 0; i < 6; i++) { - syndrome[i] = - ReadDOC(docptr, ECCSyndrome0 + i); - } - nb_errors = doc_decode_ecc(buf, syndrome); + /* Read the ECC syndrom through the DiskOnChip ECC logic. + These syndrome will be all ZERO when there is no error */ + for (i = 0; i < 6; i++) { + syndrome[i] = + ReadDOC(docptr, ECCSyndrome0 + i); + } + nb_errors = doc_decode_ecc(buf, syndrome); #ifdef ECC_DEBUG - printk(KERN_ERR "Errors corrected: %x\n", nb_errors); + printk(KERN_ERR "Errors corrected: %x\n", nb_errors); #endif - if (nb_errors < 0) { - /* We return error, but have actually done the read. Not that - this can be told to user-space, via sys_read(), but at least - MTD-aware stuff can know about it by checking *retlen */ - ret = -EIO; - } - } + if (nb_errors < 0) { + /* We return error, but have actually done the read. Not that + this can be told to user-space, via sys_read(), but at least + MTD-aware stuff can know about it by checking *retlen */ + ret = -EIO; + } + } #ifdef PSYCHO_DEBUG - printk(KERN_DEBUG "ECC DATA at %lxB: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long)from, eccbuf[0], eccbuf[1], eccbuf[2], - eccbuf[3], eccbuf[4], eccbuf[5]); + printk(KERN_DEBUG "ECC DATA at %lxB: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + (long)from, eccbuf[0], eccbuf[1], eccbuf[2], + eccbuf[3], eccbuf[4], eccbuf[5]); #endif - - /* disable the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr , ECCConf); - } - /* according to 11.4.1, we need to wait for the busy line - * drop if we read to the end of the page. */ - if(0 == ((from + *retlen) & 0x1ff)) - { - DoC_WaitReady(this); + /* disable the ECC engine */ + WriteDOC(DOC_ECC_DIS, docptr , ECCConf); + } + + /* according to 11.4.1, we need to wait for the busy line + * drop if we read to the end of the page. */ + if(0 == ((from + len) & 0x1ff)) + { + DoC_WaitReady(this); + } + + from += len; + left -= len; + buf += len; } - up(&this->lock); + mutex_unlock(&this->lock); return ret; } @@ -745,181 +784,261 @@ static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf) { char eccbuf[6]; - return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf); + return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL); } static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf, - u_char * eccbuf) + u_char * eccbuf, struct nand_oobinfo *oobsel) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + struct DiskOnChip *this = mtd->priv; int di; /* Yes, DI is a hangover from when I was disassembling the binary driver */ - unsigned long docptr; + void __iomem *docptr = this->virtadr; volatile char dummy; int len256 = 0; struct Nand *mychip; - - docptr = this->virtadr; + size_t left = len; + int status; /* Don't allow write past end of device */ if (to >= this->totlen) return -EINVAL; - down(&this->lock); + mutex_lock(&this->lock); - /* Don't allow a single write to cross a 512-byte block boundary */ - if (to + len > ((to | 0x1ff) + 1)) - len = ((to | 0x1ff) + 1) - to; + *retlen = 0; + while (left) { + len = left; - /* The ECC will not be calculated correctly if less than 512 is written */ - if (len != 0x200 && eccbuf) - printk(KERN_WARNING - "ECC needs a full sector write (adr: %lx size %lx)\n", - (long) to, (long) len); + /* Don't allow a single write to cross a 512-byte block boundary */ + if (to + len > ((to | 0x1ff) + 1)) + len = ((to | 0x1ff) + 1) - to; - /* printk("DoC_Write (adr: %lx size %lx)\n", (long) to, (long) len); */ + /* The ECC will not be calculated correctly if less than 512 is written */ +/* DBB- + if (len != 0x200 && eccbuf) + printk(KERN_WARNING + "ECC needs a full sector write (adr: %lx size %lx)\n", + (long) to, (long) len); + -DBB */ - /* Find the chip which is to be used and select it */ - mychip = &this->chips[to >> (this->chipshift)]; + /* printk("DoC_Write (adr: %lx size %lx)\n", (long) to, (long) len); */ - if (this->curfloor != mychip->floor) { - DoC_SelectFloor(this, mychip->floor); - DoC_SelectChip(this, mychip->chip); - } else if (this->curchip != mychip->chip) { - DoC_SelectChip(this, mychip->chip); - } + /* Find the chip which is to be used and select it */ + mychip = &this->chips[to >> (this->chipshift)]; - this->curfloor = mychip->floor; - this->curchip = mychip->chip; + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(this, mychip->floor); + DoC_SelectChip(this, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(this, mychip->chip); + } - /* Set device to main plane of flash */ - DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP); - DoC_Command(this, - (!this->page256 - && (to & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0, - CDSN_CTRL_WP); + this->curfloor = mychip->floor; + this->curchip = mychip->chip; - DoC_Command(this, NAND_CMD_SEQIN, 0); - DoC_Address(this, ADDR_COLUMN_PAGE, to, 0, CDSN_CTRL_ECC_IO); + /* Set device to main plane of flash */ + DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP); + DoC_Command(this, + (!this->page256 + && (to & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0, + CDSN_CTRL_WP); - if (eccbuf) { - /* Prime the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, ECCConf); - WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); - } else { - /* disable the ECC engine */ - WriteDOC(DOC_ECC_RESET, docptr, ECCConf); - WriteDOC(DOC_ECC_DIS, docptr, ECCConf); - } + DoC_Command(this, NAND_CMD_SEQIN, 0); + DoC_Address(this, ADDR_COLUMN_PAGE, to, 0, CDSN_CTRL_ECC_IO); - /* treat crossing 256-byte sector for 2M x 8bits devices */ - if (this->page256 && to + len > (to | 0xff) + 1) { - len256 = (to | 0xff) + 1 - to; - DoC_WriteBuf(this, buf, len256); + if (eccbuf) { + /* Prime the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, ECCConf); + WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); + } else { + /* disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, ECCConf); + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); + } + + /* treat crossing 256-byte sector for 2M x 8bits devices */ + if (this->page256 && to + len > (to | 0xff) + 1) { + len256 = (to | 0xff) + 1 - to; + DoC_WriteBuf(this, buf, len256); + + DoC_Command(this, NAND_CMD_PAGEPROG, 0); + + DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); + /* There's an implicit DoC_WaitReady() in DoC_Command */ + + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + + if (ReadDOC_(docptr, this->ioreg) & 1) { + printk(KERN_ERR "Error programming flash\n"); + /* Error in programming */ + *retlen = 0; + mutex_unlock(&this->lock); + return -EIO; + } + + DoC_Command(this, NAND_CMD_SEQIN, 0); + DoC_Address(this, ADDR_COLUMN_PAGE, to + len256, 0, + CDSN_CTRL_ECC_IO); + } + + DoC_WriteBuf(this, &buf[len256], len - len256); + + if (eccbuf) { + WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_CE, docptr, + CDSNControl); + + if (DoC_is_Millennium(this)) { + WriteDOC(0, docptr, NOP); + WriteDOC(0, docptr, NOP); + WriteDOC(0, docptr, NOP); + } else { + WriteDOC_(0, docptr, this->ioreg); + WriteDOC_(0, docptr, this->ioreg); + WriteDOC_(0, docptr, this->ioreg); + } + + WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_FLASH_IO | CDSN_CTRL_CE, docptr, + CDSNControl); + + /* Read the ECC data through the DiskOnChip ECC logic */ + for (di = 0; di < 6; di++) { + eccbuf[di] = ReadDOC(docptr, ECCSyndrome0 + di); + } + + /* Reset the ECC engine */ + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); + +#ifdef PSYCHO_DEBUG + printk + ("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], + eccbuf[4], eccbuf[5]); +#endif + } DoC_Command(this, NAND_CMD_PAGEPROG, 0); DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); /* There's an implicit DoC_WaitReady() in DoC_Command */ - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); + if (DoC_is_Millennium(this)) { + ReadDOC(docptr, ReadPipeInit); + status = ReadDOC(docptr, LastDataRead); + } else { + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + status = ReadDOC_(docptr, this->ioreg); + } - if (ReadDOC_(docptr, this->ioreg) & 1) { + if (status & 1) { printk(KERN_ERR "Error programming flash\n"); /* Error in programming */ *retlen = 0; - up(&this->lock); + mutex_unlock(&this->lock); return -EIO; } - DoC_Command(this, NAND_CMD_SEQIN, 0); - DoC_Address(this, ADDR_COLUMN_PAGE, to + len256, 0, - CDSN_CTRL_ECC_IO); - } + /* Let the caller know we completed it */ + *retlen += len; - DoC_WriteBuf(this, &buf[len256], len - len256); + if (eccbuf) { + unsigned char x[8]; + size_t dummy; + int ret; - if (eccbuf) { - WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_CE, docptr, - CDSNControl); + /* Write the ECC data to flash */ + for (di=0; di<6; di++) + x[di] = eccbuf[di]; - if (DoC_is_Millennium(this)) { - WriteDOC(0, docptr, NOP); - WriteDOC(0, docptr, NOP); - WriteDOC(0, docptr, NOP); - } else { - WriteDOC_(0, docptr, this->ioreg); - WriteDOC_(0, docptr, this->ioreg); - WriteDOC_(0, docptr, this->ioreg); - } + x[6]=0x55; + x[7]=0x55; - /* Read the ECC data through the DiskOnChip ECC logic */ - for (di = 0; di < 6; di++) { - eccbuf[di] = ReadDOC(docptr, ECCSyndrome0 + di); + ret = doc_write_oob_nolock(mtd, to, 8, &dummy, x); + if (ret) { + mutex_unlock(&this->lock); + return ret; + } } - /* Reset the ECC engine */ - WriteDOC(DOC_ECC_DIS, docptr, ECCConf); - -#ifdef PSYCHO_DEBUG - printk - ("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", - (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], - eccbuf[4], eccbuf[5]); -#endif + to += len; + left -= len; + buf += len; } - DoC_Command(this, NAND_CMD_PAGEPROG, 0); + mutex_unlock(&this->lock); + return 0; +} - DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); - /* There's an implicit DoC_WaitReady() in DoC_Command */ +static int doc_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen, + u_char *eccbuf, struct nand_oobinfo *oobsel) +{ + static char static_buf[512]; + static DEFINE_MUTEX(writev_buf_mutex); - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); + size_t totretlen = 0; + size_t thisvecofs = 0; + int ret= 0; - if (ReadDOC_(docptr, this->ioreg) & 1) { - printk(KERN_ERR "Error programming flash\n"); - /* Error in programming */ - *retlen = 0; - up(&this->lock); - return -EIO; - } + mutex_lock(&writev_buf_mutex); - /* Let the caller know we completed it */ - *retlen = len; - - if (eccbuf) { - unsigned char x[8]; - size_t dummy; - int ret; - - /* Write the ECC data to flash */ - for (di=0; di<6; di++) - x[di] = eccbuf[di]; - - x[6]=0x55; - x[7]=0x55; - - ret = doc_write_oob_nolock(mtd, to, 8, &dummy, x); - up(&this->lock); - return ret; + while(count) { + size_t thislen, thisretlen; + unsigned char *buf; + + buf = vecs->iov_base + thisvecofs; + thislen = vecs->iov_len - thisvecofs; + + + if (thislen >= 512) { + thislen = thislen & ~(512-1); + thisvecofs += thislen; + } else { + /* Not enough to fill a page. Copy into buf */ + memcpy(static_buf, buf, thislen); + buf = &static_buf[thislen]; + + while(count && thislen < 512) { + vecs++; + count--; + thisvecofs = min((512-thislen), vecs->iov_len); + memcpy(buf, vecs->iov_base, thisvecofs); + thislen += thisvecofs; + buf += thisvecofs; + } + buf = static_buf; + } + if (count && thisvecofs == vecs->iov_len) { + thisvecofs = 0; + vecs++; + count--; + } + ret = doc_write_ecc(mtd, to, thislen, &thisretlen, buf, eccbuf, oobsel); + + totretlen += thisretlen; + + if (ret || thisretlen != thislen) + break; + + to += thislen; } - up(&this->lock); - return 0; + + mutex_unlock(&writev_buf_mutex); + *retlen = totretlen; + return ret; } + static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t * retlen, u_char * buf) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + struct DiskOnChip *this = mtd->priv; int len256 = 0, ret; - unsigned long docptr; struct Nand *mychip; - down(&this->lock); - - docptr = this->virtadr; + mutex_lock(&this->lock); mychip = &this->chips[ofs >> this->chipshift]; @@ -962,10 +1081,10 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, /* Reading the full OOB data drops us off of the end of the page, * causing the flash device to go into busy mode, so we need * to wait until ready 11.4.1 and Toshiba TC58256FT docs */ - + ret = DoC_WaitReady(this); - up(&this->lock); + mutex_unlock(&this->lock); return ret; } @@ -973,11 +1092,12 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len, size_t * retlen, const u_char * buf) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + struct DiskOnChip *this = mtd->priv; int len256 = 0; - unsigned long docptr = this->virtadr; + void __iomem *docptr = this->virtadr; struct Nand *mychip = &this->chips[ofs >> this->chipshift]; volatile int dummy; + int status; // printk("doc_write_oob(%lx, %d): %2.2X %2.2X %2.2X %2.2X ... %2.2X %2.2X .. %2.2X %2.2X\n",(long)ofs, len, // buf[0], buf[1], buf[2], buf[3], buf[8], buf[9], buf[14],buf[15]); @@ -1026,10 +1146,16 @@ static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len, DoC_Command(this, NAND_CMD_STATUS, 0); /* DoC_WaitReady() is implicit in DoC_Command */ - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); + if (DoC_is_Millennium(this)) { + ReadDOC(docptr, ReadPipeInit); + status = ReadDOC(docptr, LastDataRead); + } else { + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + status = ReadDOC_(docptr, this->ioreg); + } - if (ReadDOC_(docptr, this->ioreg) & 1) { + if (status & 1) { printk(KERN_ERR "Error programming oob data\n"); /* There was an error */ *retlen = 0; @@ -1045,10 +1171,16 @@ static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len, DoC_Command(this, NAND_CMD_STATUS, 0); /* DoC_WaitReady() is implicit in DoC_Command */ - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); + if (DoC_is_Millennium(this)) { + ReadDOC(docptr, ReadPipeInit); + status = ReadDOC(docptr, LastDataRead); + } else { + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + status = ReadDOC_(docptr, this->ioreg); + } - if (ReadDOC_(docptr, this->ioreg) & 1) { + if (status & 1) { printk(KERN_ERR "Error programming oob data\n"); /* There was an error */ *retlen = 0; @@ -1059,39 +1191,38 @@ static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len, return 0; } - + static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t * retlen, const u_char * buf) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + struct DiskOnChip *this = mtd->priv; int ret; - down(&this->lock); + mutex_lock(&this->lock); ret = doc_write_oob_nolock(mtd, ofs, len, retlen, buf); - up(&this->lock); + mutex_unlock(&this->lock); return ret; } static int doc_erase(struct mtd_info *mtd, struct erase_info *instr) { - struct DiskOnChip *this = (struct DiskOnChip *) mtd->priv; + struct DiskOnChip *this = mtd->priv; __u32 ofs = instr->addr; __u32 len = instr->len; volatile int dummy; - unsigned long docptr; + void __iomem *docptr = this->virtadr; struct Nand *mychip; + int status; - down(&this->lock); + mutex_lock(&this->lock); if (ofs & (mtd->erasesize-1) || len & (mtd->erasesize-1)) { - up(&this->lock); + mutex_unlock(&this->lock); return -EINVAL; } instr->state = MTD_ERASING; - - docptr = this->virtadr; /* FIXME: Do this in the background. Use timers or schedule_task() */ while(len) { @@ -1112,10 +1243,16 @@ static int doc_erase(struct mtd_info *mtd, struct erase_info *instr) DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP); - dummy = ReadDOC(docptr, CDSNSlowIO); - DoC_Delay(this, 2); - - if (ReadDOC_(docptr, this->ioreg) & 1) { + if (DoC_is_Millennium(this)) { + ReadDOC(docptr, ReadPipeInit); + status = ReadDOC(docptr, LastDataRead); + } else { + dummy = ReadDOC(docptr, CDSNSlowIO); + DoC_Delay(this, 2); + status = ReadDOC_(docptr, this->ioreg); + } + + if (status & 1) { printk(KERN_ERR "Error erasing at 0x%x\n", ofs); /* There was an error */ instr->state = MTD_ERASE_FAILED; @@ -1127,10 +1264,9 @@ static int doc_erase(struct mtd_info *mtd, struct erase_info *instr) instr->state = MTD_ERASE_DONE; callback: - if (instr->callback) - instr->callback(instr); + mtd_erase_callback(instr); - up(&this->lock); + mutex_unlock(&this->lock); return 0; } @@ -1141,7 +1277,7 @@ static int doc_erase(struct mtd_info *mtd, struct erase_info *instr) * ****************************************************************************/ -int __init init_doc2000(void) +static int __init init_doc2000(void) { inter_module_register(im_name, THIS_MODULE, &DoC2k_init); return 0; @@ -1153,12 +1289,12 @@ static void __exit cleanup_doc2000(void) struct DiskOnChip *this; while ((mtd = doc2klist)) { - this = (struct DiskOnChip *) mtd->priv; + this = mtd->priv; doc2klist = this->nextdoc; del_mtd_device(mtd); - iounmap((void *) this->virtadr); + iounmap(this->virtadr); kfree(this->chips); kfree(mtd); } diff --git a/linux-2.4.x/drivers/mtd/devices/doc2001.c b/linux-2.4.x/drivers/mtd/devices/doc2001.c index b34591a..fcb28a6 100644 --- a/linux-2.4.x/drivers/mtd/devices/doc2001.c +++ b/linux-2.4.x/drivers/mtd/devices/doc2001.c @@ -4,7 +4,7 @@ * (c) 1999 Machine Vision Holdings, Inc. * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> * - * $Id: doc2001.c,v 1.35 2001/10/02 15:05:13 dwmw2 Exp $ + * $Id: doc2001.c,v 1.49 2005/11/07 11:14:24 gleixner Exp $ */ #include <linux/kernel.h> @@ -19,10 +19,10 @@ #include <linux/sched.h> #include <linux/init.h> #include <linux/types.h> +#include <linux/bitops.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> -#include <linux/mtd/nand_ids.h> #include <linux/mtd/doc2000.h> /* #define ECC_DEBUG */ @@ -38,9 +38,11 @@ static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf, u_char *eccbuf); + size_t *retlen, u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel); static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf, u_char *eccbuf); + size_t *retlen, const u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel); static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, size_t *retlen, u_char *buf); static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, @@ -50,7 +52,7 @@ static int doc_erase (struct mtd_info *mtd, struct erase_info *instr); static struct mtd_info *docmillist = NULL; /* Perform the required delay cycles by reading from the NOP register */ -static void DoC_Delay(unsigned long docptr, unsigned short cycles) +static void DoC_Delay(void __iomem * docptr, unsigned short cycles) { volatile char dummy; int i; @@ -60,7 +62,7 @@ static void DoC_Delay(unsigned long docptr, unsigned short cycles) } /* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ -static int _DoC_WaitReady(unsigned long docptr) +static int _DoC_WaitReady(void __iomem * docptr) { unsigned short c = 0xffff; @@ -77,7 +79,7 @@ static int _DoC_WaitReady(unsigned long docptr) return (c == 0); } -static inline int DoC_WaitReady(unsigned long docptr) +static inline int DoC_WaitReady(void __iomem * docptr) { /* This is inline, to optimise the common case, where it's ready instantly */ int ret = 0; @@ -101,7 +103,7 @@ static inline int DoC_WaitReady(unsigned long docptr) with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */ -static inline void DoC_Command(unsigned long docptr, unsigned char command, +static inline void DoC_Command(void __iomem * docptr, unsigned char command, unsigned char xtraflags) { /* Assert the CLE (Command Latch Enable) line to the flash chip */ @@ -121,7 +123,7 @@ static inline void DoC_Command(unsigned long docptr, unsigned char command, with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */ -static inline void DoC_Address(unsigned long docptr, int numbytes, unsigned long ofs, +static inline void DoC_Address(void __iomem * docptr, int numbytes, unsigned long ofs, unsigned char xtraflags1, unsigned char xtraflags2) { /* Assert the ALE (Address Latch Enable) line to the flash chip */ @@ -159,7 +161,7 @@ static inline void DoC_Address(unsigned long docptr, int numbytes, unsigned long } /* DoC_SelectChip: Select a given flash chip within the current floor */ -static int DoC_SelectChip(unsigned long docptr, int chip) +static int DoC_SelectChip(void __iomem * docptr, int chip) { /* Select the individual flash chip requested */ WriteDOC(chip, docptr, CDSNDeviceSelect); @@ -170,7 +172,7 @@ static int DoC_SelectChip(unsigned long docptr, int chip) } /* DoC_SelectFloor: Select a given floor (bank of flash chips) */ -static int DoC_SelectFloor(unsigned long docptr, int floor) +static int DoC_SelectFloor(void __iomem * docptr, int floor) { /* Select the floor (bank) of chips required */ WriteDOC(floor, docptr, FloorSelect); @@ -182,7 +184,7 @@ static int DoC_SelectFloor(unsigned long docptr, int floor) /* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) { - int mfr, id, i; + int mfr, id, i, j; volatile char dummy; /* Page in the required floor/chip @@ -194,10 +196,10 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) DoC_Command(doc->virtadr, NAND_CMD_RESET, CDSN_CTRL_WP); DoC_WaitReady(doc->virtadr); - /* Read the NAND chip ID: 1. Send ReadID command */ + /* Read the NAND chip ID: 1. Send ReadID command */ DoC_Command(doc->virtadr, NAND_CMD_READID, CDSN_CTRL_WP); - /* Read the NAND chip ID: 2. Send address byte zero */ + /* Read the NAND chip ID: 2. Send address byte zero */ DoC_Address(doc->virtadr, 1, 0x00, CDSN_CTRL_WP, 0x00); /* Read the manufacturer and device id codes of the flash device through @@ -216,14 +218,18 @@ static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) /* FIXME: to deal with multi-flash on multi-Millennium case more carefully */ for (i = 0; nand_flash_ids[i].name != NULL; i++) { - if (mfr == nand_flash_ids[i].manufacture_id && - id == nand_flash_ids[i].model_id) { + if ( id == nand_flash_ids[i].id) { + /* Try to identify manufacturer */ + for (j = 0; nand_manuf_ids[j].id != 0x0; j++) { + if (nand_manuf_ids[j].id == mfr) + break; + } printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, " - "Chip ID: %2.2X (%s)\n", - mfr, id, nand_flash_ids[i].name); + "Chip ID: %2.2X (%s:%s)\n", + mfr, id, nand_manuf_ids[j].name, nand_flash_ids[i].name); doc->mfr = mfr; doc->id = id; - doc->chipshift = nand_flash_ids[i].chipshift; + doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1; break; } } @@ -269,7 +275,7 @@ static void DoC_ScanChips(struct DiskOnChip *this) return; } - /* Fill out the chip array with {floor, chipno} for each + /* Fill out the chip array with {floor, chipno} for each * detected chip in the device. */ for (floor = 0, ret = 0; floor < MAX_FLOORS_MIL; floor++) { for (chip = 0 ; chip < numchips[floor] ; chip++) { @@ -303,7 +309,7 @@ static int DoCMil_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) tmp2 = ReadDOC(doc2->virtadr, AliasResolution); if (tmp1 != tmp2) return 0; - + WriteDOC((tmp1+1) % 0xff, doc1->virtadr, AliasResolution); tmp2 = ReadDOC(doc2->virtadr, AliasResolution); if (tmp2 == (tmp1+1) % 0xff) @@ -329,23 +335,23 @@ static const char im_name[] = "DoCMil_init"; */ static void DoCMil_init(struct mtd_info *mtd) { - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; + struct DiskOnChip *this = mtd->priv; struct DiskOnChip *old = NULL; /* We must avoid being called twice for the same device. */ if (docmillist) - old = (struct DiskOnChip *)docmillist->priv; + old = docmillist->priv; while (old) { if (DoCMil_is_alias(this, old)) { printk(KERN_NOTICE "Ignoring DiskOnChip Millennium at " "0x%lX - already configured\n", this->physadr); - iounmap((void *)this->virtadr); + iounmap(this->virtadr); kfree(mtd); return; } if (old->nextdoc) - old = (struct DiskOnChip *)old->nextdoc->priv; + old = old->nextdoc->priv; else old = NULL; } @@ -356,14 +362,15 @@ static void DoCMil_init(struct mtd_info *mtd) mtd->type = MTD_NANDFLASH; mtd->flags = MTD_CAP_NANDFLASH; + mtd->ecctype = MTD_ECC_RS_DiskOnChip; mtd->size = 0; - /* FIXME: erase size is not always 8kB */ + /* FIXME: erase size is not always 8KiB */ mtd->erasesize = 0x2000; mtd->oobblock = 512; mtd->oobsize = 16; - mtd->module = THIS_MODULE; + mtd->owner = THIS_MODULE; mtd->erase = doc_erase; mtd->point = NULL; mtd->unpoint = NULL; @@ -385,7 +392,7 @@ static void DoCMil_init(struct mtd_info *mtd) if (!this->totlen) { kfree(mtd); - iounmap((void *)this->virtadr); + iounmap(this->virtadr); } else { this->nextdoc = docmillist; docmillist = mtd; @@ -399,17 +406,18 @@ static int doc_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { /* Just a special case of doc_read_ecc */ - return doc_read_ecc(mtd, from, len, retlen, buf, NULL); + return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL); } static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf, u_char *eccbuf) + size_t *retlen, u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel) { int i, ret; volatile char dummy; unsigned char syndrome[6]; - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - unsigned long docptr = this->virtadr; + struct DiskOnChip *this = mtd->priv; + void __iomem *docptr = this->virtadr; struct Nand *mychip = &this->chips[from >> (this->chipshift)]; /* Don't allow read past end of device */ @@ -417,7 +425,7 @@ static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len, return -EINVAL; /* Don't allow a single read to cross a 512-byte block boundary */ - if (from + len > ((from | 0x1ff) + 1)) + if (from + len > ((from | 0x1ff) + 1)) len = ((from | 0x1ff) + 1) - from; /* Find the chip which is to be used and select it */ @@ -525,16 +533,17 @@ static int doc_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { char eccbuf[6]; - return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf); + return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL); } static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf, u_char *eccbuf) + size_t *retlen, const u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel) { int i,ret = 0; volatile char dummy; - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - unsigned long docptr = this->virtadr; + struct DiskOnChip *this = mtd->priv; + void __iomem *docptr = this->virtadr; struct Nand *mychip = &this->chips[to >> (this->chipshift)]; /* Don't allow write past end of device */ @@ -543,7 +552,7 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, #if 0 /* Don't allow a single write to cross a 512-byte block boundary */ - if (to + len > ( (to | 0x1ff) + 1)) + if (to + len > ( (to | 0x1ff) + 1)) len = ((to | 0x1ff) + 1) - to; #else /* Don't allow writes which aren't exactly one block */ @@ -623,7 +632,7 @@ static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len, /* write the block status BLOCK_USED (0x5555) at the end of ECC data FIXME: this is only a hack for programming the IPL area for LinuxBIOS - and should be replace with proper codes in user space utilities */ + and should be replace with proper codes in user space utilities */ WriteDOC(0x55, docptr, Mil_CDSN_IO); WriteDOC(0x55, docptr, Mil_CDSN_IO + 1); @@ -668,8 +677,8 @@ static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, int i; #endif volatile char dummy; - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - unsigned long docptr = this->virtadr; + struct DiskOnChip *this = mtd->priv; + void __iomem *docptr = this->virtadr; struct Nand *mychip = &this->chips[ofs >> this->chipshift]; /* Find the chip which is to be used and select it */ @@ -720,8 +729,8 @@ static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, #endif volatile char dummy; int ret = 0; - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; - unsigned long docptr = this->virtadr; + struct DiskOnChip *this = mtd->priv; + void __iomem *docptr = this->virtadr; struct Nand *mychip = &this->chips[ofs >> this->chipshift]; /* Find the chip which is to be used and select it */ @@ -787,13 +796,13 @@ static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, int doc_erase (struct mtd_info *mtd, struct erase_info *instr) { volatile char dummy; - struct DiskOnChip *this = (struct DiskOnChip *)mtd->priv; + struct DiskOnChip *this = mtd->priv; __u32 ofs = instr->addr; __u32 len = instr->len; - unsigned long docptr = this->virtadr; + void __iomem *docptr = this->virtadr; struct Nand *mychip = &this->chips[ofs >> this->chipshift]; - if (len != mtd->erasesize) + if (len != mtd->erasesize) printk(KERN_WARNING "Erase not right size (%x != %x)n", len, mtd->erasesize); @@ -836,8 +845,7 @@ int doc_erase (struct mtd_info *mtd, struct erase_info *instr) instr->state = MTD_ERASE_DONE; dummy = ReadDOC(docptr, LastDataRead); - if (instr->callback) - instr->callback(instr); + mtd_erase_callback(instr); return 0; } @@ -848,7 +856,7 @@ int doc_erase (struct mtd_info *mtd, struct erase_info *instr) * ****************************************************************************/ -int __init init_doc2001(void) +static int __init init_doc2001(void) { inter_module_register(im_name, THIS_MODULE, &DoCMil_init); return 0; @@ -860,12 +868,12 @@ static void __exit cleanup_doc2001(void) struct DiskOnChip *this; while ((mtd=docmillist)) { - this = (struct DiskOnChip *)mtd->priv; + this = mtd->priv; docmillist = this->nextdoc; - + del_mtd_device(mtd); - - iounmap((void *)this->virtadr); + + iounmap(this->virtadr); kfree(this->chips); kfree(mtd); } diff --git a/linux-2.4.x/drivers/mtd/devices/doc2001plus.c b/linux-2.4.x/drivers/mtd/devices/doc2001plus.c new file mode 100644 index 0000000..0595cc7 --- /dev/null +++ b/linux-2.4.x/drivers/mtd/devices/doc2001plus.c @@ -0,0 +1,1154 @@ +/* + * Linux driver for Disk-On-Chip Millennium Plus + * + * (c) 2002-2003 Greg Ungerer <gerg@snapgear.com> + * (c) 2002-2003 SnapGear Inc + * (c) 1999 Machine Vision Holdings, Inc. + * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> + * + * $Id: doc2001plus.c,v 1.14 2005/11/07 11:14:24 gleixner Exp $ + * + * Released under GPL + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/errno.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/bitops.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/doc2000.h> + +/* #define ECC_DEBUG */ + +/* I have no idea why some DoC chips can not use memcop_form|to_io(). + * This may be due to the different revisions of the ASIC controller built-in or + * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment + * this:*/ +#undef USE_MEMCPY + +static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf); +static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf); +static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel); +static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel); +static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t *retlen, u_char *buf); +static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t *retlen, const u_char *buf); +static int doc_erase (struct mtd_info *mtd, struct erase_info *instr); + +static struct mtd_info *docmilpluslist = NULL; + + +/* Perform the required delay cycles by writing to the NOP register */ +static void DoC_Delay(void __iomem * docptr, int cycles) +{ + int i; + + for (i = 0; (i < cycles); i++) + WriteDOC(0, docptr, Mplus_NOP); +} + +#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1) + +/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ +static int _DoC_WaitReady(void __iomem * docptr) +{ + unsigned int c = 0xffff; + + DEBUG(MTD_DEBUG_LEVEL3, + "_DoC_WaitReady called for out-of-line wait\n"); + + /* Out-of-line routine to wait for chip response */ + while (((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) && --c) + ; + + if (c == 0) + DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n"); + + return (c == 0); +} + +static inline int DoC_WaitReady(void __iomem * docptr) +{ + /* This is inline, to optimise the common case, where it's ready instantly */ + int ret = 0; + + /* read form NOP register should be issued prior to the read from CDSNControl + see Software Requirement 11.4 item 2. */ + DoC_Delay(docptr, 4); + + if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) + /* Call the out-of-line routine to wait */ + ret = _DoC_WaitReady(docptr); + + return ret; +} + +/* For some reason the Millennium Plus seems to occassionally put itself + * into reset mode. For me this happens randomly, with no pattern that I + * can detect. M-systems suggest always check this on any block level + * operation and setting to normal mode if in reset mode. + */ +static inline void DoC_CheckASIC(void __iomem * docptr) +{ + /* Make sure the DoC is in normal mode */ + if ((ReadDOC(docptr, Mplus_DOCControl) & DOC_MODE_NORMAL) == 0) { + WriteDOC((DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_DOCControl); + WriteDOC(~(DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_CtrlConfirm); + } +} + +/* DoC_Command: Send a flash command to the flash chip through the Flash + * command register. Need 2 Write Pipeline Terminates to complete send. + */ +static inline void DoC_Command(void __iomem * docptr, unsigned char command, + unsigned char xtraflags) +{ + WriteDOC(command, docptr, Mplus_FlashCmd); + WriteDOC(command, docptr, Mplus_WritePipeTerm); + WriteDOC(command, docptr, Mplus_WritePipeTerm); +} + +/* DoC_Address: Set the current address for the flash chip through the Flash + * Address register. Need 2 Write Pipeline Terminates to complete send. + */ +static inline void DoC_Address(struct DiskOnChip *doc, int numbytes, + unsigned long ofs, unsigned char xtraflags1, + unsigned char xtraflags2) +{ + void __iomem * docptr = doc->virtadr; + + /* Allow for possible Mill Plus internal flash interleaving */ + ofs >>= doc->interleave; + + switch (numbytes) { + case 1: + /* Send single byte, bits 0-7. */ + WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress); + break; + case 2: + /* Send bits 9-16 followed by 17-23 */ + WriteDOC((ofs >> 9) & 0xff, docptr, Mplus_FlashAddress); + WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress); + break; + case 3: + /* Send 0-7, 9-16, then 17-23 */ + WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress); + WriteDOC((ofs >> 9) & 0xff, docptr, Mplus_FlashAddress); + WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress); + break; + default: + return; + } + + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); +} + +/* DoC_SelectChip: Select a given flash chip within the current floor */ +static int DoC_SelectChip(void __iomem * docptr, int chip) +{ + /* No choice for flash chip on Millennium Plus */ + return 0; +} + +/* DoC_SelectFloor: Select a given floor (bank of flash chips) */ +static int DoC_SelectFloor(void __iomem * docptr, int floor) +{ + WriteDOC((floor & 0x3), docptr, Mplus_DeviceSelect); + return 0; +} + +/* + * Translate the given offset into the appropriate command and offset. + * This does the mapping using the 16bit interleave layout defined by + * M-Systems, and looks like this for a sector pair: + * +-----------+-------+-------+-------+--------------+---------+-----------+ + * | 0 --- 511 |512-517|518-519|520-521| 522 --- 1033 |1034-1039|1040 - 1055| + * +-----------+-------+-------+-------+--------------+---------+-----------+ + * | Data 0 | ECC 0 |Flags0 |Flags1 | Data 1 |ECC 1 | OOB 1 + 2 | + * +-----------+-------+-------+-------+--------------+---------+-----------+ + */ +/* FIXME: This lives in INFTL not here. Other users of flash devices + may not want it */ +static unsigned int DoC_GetDataOffset(struct mtd_info *mtd, loff_t *from) +{ + struct DiskOnChip *this = mtd->priv; + + if (this->interleave) { + unsigned int ofs = *from & 0x3ff; + unsigned int cmd; + + if (ofs < 512) { + cmd = NAND_CMD_READ0; + ofs &= 0x1ff; + } else if (ofs < 1014) { + cmd = NAND_CMD_READ1; + ofs = (ofs & 0x1ff) + 10; + } else { + cmd = NAND_CMD_READOOB; + ofs = ofs - 1014; + } + + *from = (*from & ~0x3ff) | ofs; + return cmd; + } else { + /* No interleave */ + if ((*from) & 0x100) + return NAND_CMD_READ1; + return NAND_CMD_READ0; + } +} + +static unsigned int DoC_GetECCOffset(struct mtd_info *mtd, loff_t *from) +{ + unsigned int ofs, cmd; + + if (*from & 0x200) { + cmd = NAND_CMD_READOOB; + ofs = 10 + (*from & 0xf); + } else { + cmd = NAND_CMD_READ1; + ofs = (*from & 0xf); + } + + *from = (*from & ~0x3ff) | ofs; + return cmd; +} + +static unsigned int DoC_GetFlagsOffset(struct mtd_info *mtd, loff_t *from) +{ + unsigned int ofs, cmd; + + cmd = NAND_CMD_READ1; + ofs = (*from & 0x200) ? 8 : 6; + *from = (*from & ~0x3ff) | ofs; + return cmd; +} + +static unsigned int DoC_GetHdrOffset(struct mtd_info *mtd, loff_t *from) +{ + unsigned int ofs, cmd; + + cmd = NAND_CMD_READOOB; + ofs = (*from & 0x200) ? 24 : 16; + *from = (*from & ~0x3ff) | ofs; + return cmd; +} + +static inline void MemReadDOC(void __iomem * docptr, unsigned char *buf, int len) +{ +#ifndef USE_MEMCPY + int i; + for (i = 0; i < len; i++) + buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i); +#else + memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len); +#endif +} + +static inline void MemWriteDOC(void __iomem * docptr, unsigned char *buf, int len) +{ +#ifndef USE_MEMCPY + int i; + for (i = 0; i < len; i++) + WriteDOC(buf[i], docptr, Mil_CDSN_IO + i); +#else + memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len); +#endif +} + +/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */ +static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip) +{ + int mfr, id, i, j; + volatile char dummy; + void __iomem * docptr = doc->virtadr; + + /* Page in the required floor/chip */ + DoC_SelectFloor(docptr, floor); + DoC_SelectChip(docptr, chip); + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); + + /* Reset the chip, see Software Requirement 11.4 item 1. */ + DoC_Command(docptr, NAND_CMD_RESET, 0); + DoC_WaitReady(docptr); + + /* Read the NAND chip ID: 1. Send ReadID command */ + DoC_Command(docptr, NAND_CMD_READID, 0); + + /* Read the NAND chip ID: 2. Send address byte zero */ + DoC_Address(doc, 1, 0x00, 0, 0x00); + + WriteDOC(0, docptr, Mplus_FlashControl); + DoC_WaitReady(docptr); + + /* Read the manufacturer and device id codes of the flash device through + CDSN IO register see Software Requirement 11.4 item 5.*/ + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + + mfr = ReadDOC(docptr, Mil_CDSN_IO); + if (doc->interleave) + dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */ + + id = ReadDOC(docptr, Mil_CDSN_IO); + if (doc->interleave) + dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */ + + dummy = ReadDOC(docptr, Mplus_LastDataRead); + dummy = ReadDOC(docptr, Mplus_LastDataRead); + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + /* No response - return failure */ + if (mfr == 0xff || mfr == 0) + return 0; + + for (i = 0; nand_flash_ids[i].name != NULL; i++) { + if (id == nand_flash_ids[i].id) { + /* Try to identify manufacturer */ + for (j = 0; nand_manuf_ids[j].id != 0x0; j++) { + if (nand_manuf_ids[j].id == mfr) + break; + } + printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, " + "Chip ID: %2.2X (%s:%s)\n", mfr, id, + nand_manuf_ids[j].name, nand_flash_ids[i].name); + doc->mfr = mfr; + doc->id = id; + doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1; + doc->erasesize = nand_flash_ids[i].erasesize << doc->interleave; + break; + } + } + + if (nand_flash_ids[i].name == NULL) + return 0; + return 1; +} + +/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */ +static void DoC_ScanChips(struct DiskOnChip *this) +{ + int floor, chip; + int numchips[MAX_FLOORS_MPLUS]; + int ret; + + this->numchips = 0; + this->mfr = 0; + this->id = 0; + + /* Work out the intended interleave setting */ + this->interleave = 0; + if (this->ChipID == DOC_ChipID_DocMilPlus32) + this->interleave = 1; + + /* Check the ASIC agrees */ + if ( (this->interleave << 2) != + (ReadDOC(this->virtadr, Mplus_Configuration) & 4)) { + u_char conf = ReadDOC(this->virtadr, Mplus_Configuration); + printk(KERN_NOTICE "Setting DiskOnChip Millennium Plus interleave to %s\n", + this->interleave?"on (16-bit)":"off (8-bit)"); + conf ^= 4; + WriteDOC(conf, this->virtadr, Mplus_Configuration); + } + + /* For each floor, find the number of valid chips it contains */ + for (floor = 0,ret = 1; floor < MAX_FLOORS_MPLUS; floor++) { + numchips[floor] = 0; + for (chip = 0; chip < MAX_CHIPS_MPLUS && ret != 0; chip++) { + ret = DoC_IdentChip(this, floor, chip); + if (ret) { + numchips[floor]++; + this->numchips++; + } + } + } + /* If there are none at all that we recognise, bail */ + if (!this->numchips) { + printk("No flash chips recognised.\n"); + return; + } + + /* Allocate an array to hold the information for each chip */ + this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL); + if (!this->chips){ + printk("MTD: No memory for allocating chip info structures\n"); + return; + } + + /* Fill out the chip array with {floor, chipno} for each + * detected chip in the device. */ + for (floor = 0, ret = 0; floor < MAX_FLOORS_MPLUS; floor++) { + for (chip = 0 ; chip < numchips[floor] ; chip++) { + this->chips[ret].floor = floor; + this->chips[ret].chip = chip; + this->chips[ret].curadr = 0; + this->chips[ret].curmode = 0x50; + ret++; + } + } + + /* Calculate and print the total size of the device */ + this->totlen = this->numchips * (1 << this->chipshift); + printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n", + this->numchips ,this->totlen >> 20); +} + +static int DoCMilPlus_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2) +{ + int tmp1, tmp2, retval; + + if (doc1->physadr == doc2->physadr) + return 1; + + /* Use the alias resolution register which was set aside for this + * purpose. If it's value is the same on both chips, they might + * be the same chip, and we write to one and check for a change in + * the other. It's unclear if this register is usuable in the + * DoC 2000 (it's in the Millennium docs), but it seems to work. */ + tmp1 = ReadDOC(doc1->virtadr, Mplus_AliasResolution); + tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution); + if (tmp1 != tmp2) + return 0; + + WriteDOC((tmp1+1) % 0xff, doc1->virtadr, Mplus_AliasResolution); + tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution); + if (tmp2 == (tmp1+1) % 0xff) + retval = 1; + else + retval = 0; + + /* Restore register contents. May not be necessary, but do it just to + * be safe. */ + WriteDOC(tmp1, doc1->virtadr, Mplus_AliasResolution); + + return retval; +} + +static const char im_name[] = "DoCMilPlus_init"; + +/* This routine is made available to other mtd code via + * inter_module_register. It must only be accessed through + * inter_module_get which will bump the use count of this module. The + * addresses passed back in mtd are valid as long as the use count of + * this module is non-zero, i.e. between inter_module_get and + * inter_module_put. Keith Owens <kaos@ocs.com.au> 29 Oct 2000. + */ +static void DoCMilPlus_init(struct mtd_info *mtd) +{ + struct DiskOnChip *this = mtd->priv; + struct DiskOnChip *old = NULL; + + /* We must avoid being called twice for the same device. */ + if (docmilpluslist) + old = docmilpluslist->priv; + + while (old) { + if (DoCMilPlus_is_alias(this, old)) { + printk(KERN_NOTICE "Ignoring DiskOnChip Millennium " + "Plus at 0x%lX - already configured\n", + this->physadr); + iounmap(this->virtadr); + kfree(mtd); + return; + } + if (old->nextdoc) + old = old->nextdoc->priv; + else + old = NULL; + } + + mtd->name = "DiskOnChip Millennium Plus"; + printk(KERN_NOTICE "DiskOnChip Millennium Plus found at " + "address 0x%lX\n", this->physadr); + + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->ecctype = MTD_ECC_RS_DiskOnChip; + mtd->size = 0; + + mtd->erasesize = 0; + mtd->oobblock = 512; + mtd->oobsize = 16; + mtd->owner = THIS_MODULE; + mtd->erase = doc_erase; + mtd->point = NULL; + mtd->unpoint = NULL; + mtd->read = doc_read; + mtd->write = doc_write; + mtd->read_ecc = doc_read_ecc; + mtd->write_ecc = doc_write_ecc; + mtd->read_oob = doc_read_oob; + mtd->write_oob = doc_write_oob; + mtd->sync = NULL; + + this->totlen = 0; + this->numchips = 0; + this->curfloor = -1; + this->curchip = -1; + + /* Ident all the chips present. */ + DoC_ScanChips(this); + + if (!this->totlen) { + kfree(mtd); + iounmap(this->virtadr); + } else { + this->nextdoc = docmilpluslist; + docmilpluslist = mtd; + mtd->size = this->totlen; + mtd->erasesize = this->erasesize; + add_mtd_device(mtd); + return; + } +} + +#if 0 +static int doc_dumpblk(struct mtd_info *mtd, loff_t from) +{ + int i; + loff_t fofs; + struct DiskOnChip *this = mtd->priv; + void __iomem * docptr = this->virtadr; + struct Nand *mychip = &this->chips[from >> (this->chipshift)]; + unsigned char *bp, buf[1056]; + char c[32]; + + from &= ~0x3ff; + + /* Don't allow read past end of device */ + if (from >= this->totlen) + return -EINVAL; + + DoC_CheckASIC(docptr); + + /* Find the chip which is to be used and select it */ + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(docptr, mychip->floor); + DoC_SelectChip(docptr, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(docptr, mychip->chip); + } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); + + /* Reset the chip, see Software Requirement 11.4 item 1. */ + DoC_Command(docptr, NAND_CMD_RESET, 0); + DoC_WaitReady(docptr); + + fofs = from; + DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0); + DoC_Address(this, 3, fofs, 0, 0x00); + WriteDOC(0, docptr, Mplus_FlashControl); + DoC_WaitReady(docptr); + + /* disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + + /* Read the data via the internal pipeline through CDSN IO + register, see Pipelined Read Operations 11.3 */ + MemReadDOC(docptr, buf, 1054); + buf[1054] = ReadDOC(docptr, Mplus_LastDataRead); + buf[1055] = ReadDOC(docptr, Mplus_LastDataRead); + + memset(&c[0], 0, sizeof(c)); + printk("DUMP OFFSET=%x:\n", (int)from); + + for (i = 0, bp = &buf[0]; (i < 1056); i++) { + if ((i % 16) == 0) + printk("%08x: ", i); + printk(" %02x", *bp); + c[(i & 0xf)] = ((*bp >= 0x20) && (*bp <= 0x7f)) ? *bp : '.'; + bp++; + if (((i + 1) % 16) == 0) + printk(" %s\n", c); + } + printk("\n"); + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + return 0; +} +#endif + +static int doc_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + /* Just a special case of doc_read_ecc */ + return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL); +} + +static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel) +{ + int ret, i; + volatile char dummy; + loff_t fofs; + unsigned char syndrome[6]; + struct DiskOnChip *this = mtd->priv; + void __iomem * docptr = this->virtadr; + struct Nand *mychip = &this->chips[from >> (this->chipshift)]; + + /* Don't allow read past end of device */ + if (from >= this->totlen) + return -EINVAL; + + /* Don't allow a single read to cross a 512-byte block boundary */ + if (from + len > ((from | 0x1ff) + 1)) + len = ((from | 0x1ff) + 1) - from; + + DoC_CheckASIC(docptr); + + /* Find the chip which is to be used and select it */ + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(docptr, mychip->floor); + DoC_SelectChip(docptr, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(docptr, mychip->chip); + } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); + + /* Reset the chip, see Software Requirement 11.4 item 1. */ + DoC_Command(docptr, NAND_CMD_RESET, 0); + DoC_WaitReady(docptr); + + fofs = from; + DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0); + DoC_Address(this, 3, fofs, 0, 0x00); + WriteDOC(0, docptr, Mplus_FlashControl); + DoC_WaitReady(docptr); + + if (eccbuf) { + /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/ + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf); + } else { + /* disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + } + + /* Let the caller know we completed it */ + *retlen = len; + ret = 0; + + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + + if (eccbuf) { + /* Read the data via the internal pipeline through CDSN IO + register, see Pipelined Read Operations 11.3 */ + MemReadDOC(docptr, buf, len); + + /* Read the ECC data following raw data */ + MemReadDOC(docptr, eccbuf, 4); + eccbuf[4] = ReadDOC(docptr, Mplus_LastDataRead); + eccbuf[5] = ReadDOC(docptr, Mplus_LastDataRead); + + /* Flush the pipeline */ + dummy = ReadDOC(docptr, Mplus_ECCConf); + dummy = ReadDOC(docptr, Mplus_ECCConf); + + /* Check the ECC Status */ + if (ReadDOC(docptr, Mplus_ECCConf) & 0x80) { + int nb_errors; + /* There was an ECC error */ +#ifdef ECC_DEBUG + printk("DiskOnChip ECC Error: Read at %lx\n", (long)from); +#endif + /* Read the ECC syndrom through the DiskOnChip ECC logic. + These syndrome will be all ZERO when there is no error */ + for (i = 0; i < 6; i++) + syndrome[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i); + + nb_errors = doc_decode_ecc(buf, syndrome); +#ifdef ECC_DEBUG + printk("ECC Errors corrected: %x\n", nb_errors); +#endif + if (nb_errors < 0) { + /* We return error, but have actually done the read. Not that + this can be told to user-space, via sys_read(), but at least + MTD-aware stuff can know about it by checking *retlen */ +#ifdef ECC_DEBUG + printk("%s(%d): Millennium Plus ECC error (from=0x%x:\n", + __FILE__, __LINE__, (int)from); + printk(" syndrome= %02x:%02x:%02x:%02x:%02x:" + "%02x\n", + syndrome[0], syndrome[1], syndrome[2], + syndrome[3], syndrome[4], syndrome[5]); + printk(" eccbuf= %02x:%02x:%02x:%02x:%02x:" + "%02x\n", + eccbuf[0], eccbuf[1], eccbuf[2], + eccbuf[3], eccbuf[4], eccbuf[5]); +#endif + ret = -EIO; + } + } + +#ifdef PSYCHO_DEBUG + printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], + eccbuf[4], eccbuf[5]); +#endif + + /* disable the ECC engine */ + WriteDOC(DOC_ECC_DIS, docptr , Mplus_ECCConf); + } else { + /* Read the data via the internal pipeline through CDSN IO + register, see Pipelined Read Operations 11.3 */ + MemReadDOC(docptr, buf, len-2); + buf[len-2] = ReadDOC(docptr, Mplus_LastDataRead); + buf[len-1] = ReadDOC(docptr, Mplus_LastDataRead); + } + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + return ret; +} + +static int doc_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + char eccbuf[6]; + return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL); +} + +static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf, u_char *eccbuf, + struct nand_oobinfo *oobsel) +{ + int i, before, ret = 0; + loff_t fto; + volatile char dummy; + struct DiskOnChip *this = mtd->priv; + void __iomem * docptr = this->virtadr; + struct Nand *mychip = &this->chips[to >> (this->chipshift)]; + + /* Don't allow write past end of device */ + if (to >= this->totlen) + return -EINVAL; + + /* Don't allow writes which aren't exactly one block (512 bytes) */ + if ((to & 0x1ff) || (len != 0x200)) + return -EINVAL; + + /* Determine position of OOB flags, before or after data */ + before = (this->interleave && (to & 0x200)); + + DoC_CheckASIC(docptr); + + /* Find the chip which is to be used and select it */ + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(docptr, mychip->floor); + DoC_SelectChip(docptr, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(docptr, mychip->chip); + } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect); + + /* Reset the chip, see Software Requirement 11.4 item 1. */ + DoC_Command(docptr, NAND_CMD_RESET, 0); + DoC_WaitReady(docptr); + + /* Set device to appropriate plane of flash */ + fto = to; + WriteDOC(DoC_GetDataOffset(mtd, &fto), docptr, Mplus_FlashCmd); + + /* On interleaved devices the flags for 2nd half 512 are before data */ + if (eccbuf && before) + fto -= 2; + + /* issue the Serial Data In command to initial the Page Program process */ + DoC_Command(docptr, NAND_CMD_SEQIN, 0x00); + DoC_Address(this, 3, fto, 0x00, 0x00); + + /* Disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + + if (eccbuf) { + if (before) { + /* Write the block status BLOCK_USED (0x5555) */ + WriteDOC(0x55, docptr, Mil_CDSN_IO); + WriteDOC(0x55, docptr, Mil_CDSN_IO); + } + + /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/ + WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf); + } + + MemWriteDOC(docptr, (unsigned char *) buf, len); + + if (eccbuf) { + /* Write ECC data to flash, the ECC info is generated by + the DiskOnChip ECC logic see Reed-Solomon EDC/ECC 11.1 */ + DoC_Delay(docptr, 3); + + /* Read the ECC data through the DiskOnChip ECC logic */ + for (i = 0; i < 6; i++) + eccbuf[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i); + + /* disable the ECC engine */ + WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); + + /* Write the ECC data to flash */ + MemWriteDOC(docptr, eccbuf, 6); + + if (!before) { + /* Write the block status BLOCK_USED (0x5555) */ + WriteDOC(0x55, docptr, Mil_CDSN_IO+6); + WriteDOC(0x55, docptr, Mil_CDSN_IO+7); + } + +#ifdef PSYCHO_DEBUG + printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", + (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3], + eccbuf[4], eccbuf[5]); +#endif + } + + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + + /* Commit the Page Program command and wait for ready + see Software Requirement 11.4 item 1.*/ + DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00); + DoC_WaitReady(docptr); + + /* Read the status of the flash device through CDSN IO register + see Software Requirement 11.4 item 5.*/ + DoC_Command(docptr, NAND_CMD_STATUS, 0); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + DoC_Delay(docptr, 2); + if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) { + printk("MTD: Error 0x%x programming at 0x%x\n", dummy, (int)to); + /* Error in programming + FIXME: implement Bad Block Replacement (in nftl.c ??) */ + *retlen = 0; + ret = -EIO; + } + dummy = ReadDOC(docptr, Mplus_LastDataRead); + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + /* Let the caller know we completed it */ + *retlen = len; + + return ret; +} + +static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t *retlen, u_char *buf) +{ + loff_t fofs, base; + struct DiskOnChip *this = mtd->priv; + void __iomem * docptr = this->virtadr; + struct Nand *mychip = &this->chips[ofs >> this->chipshift]; + size_t i, size, got, want; + + DoC_CheckASIC(docptr); + + /* Find the chip which is to be used and select it */ + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(docptr, mychip->floor); + DoC_SelectChip(docptr, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(docptr, mychip->chip); + } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect); + + /* disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + DoC_WaitReady(docptr); + + /* Maximum of 16 bytes in the OOB region, so limit read to that */ + if (len > 16) + len = 16; + got = 0; + want = len; + + for (i = 0; ((i < 3) && (want > 0)); i++) { + /* Figure out which region we are accessing... */ + fofs = ofs; + base = ofs & 0xf; + if (!this->interleave) { + DoC_Command(docptr, NAND_CMD_READOOB, 0); + size = 16 - base; + } else if (base < 6) { + DoC_Command(docptr, DoC_GetECCOffset(mtd, &fofs), 0); + size = 6 - base; + } else if (base < 8) { + DoC_Command(docptr, DoC_GetFlagsOffset(mtd, &fofs), 0); + size = 8 - base; + } else { + DoC_Command(docptr, DoC_GetHdrOffset(mtd, &fofs), 0); + size = 16 - base; + } + if (size > want) + size = want; + + /* Issue read command */ + DoC_Address(this, 3, fofs, 0, 0x00); + WriteDOC(0, docptr, Mplus_FlashControl); + DoC_WaitReady(docptr); + + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + MemReadDOC(docptr, &buf[got], size - 2); + buf[got + size - 2] = ReadDOC(docptr, Mplus_LastDataRead); + buf[got + size - 1] = ReadDOC(docptr, Mplus_LastDataRead); + + ofs += size; + got += size; + want -= size; + } + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + *retlen = len; + return 0; +} + +static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len, + size_t *retlen, const u_char *buf) +{ + volatile char dummy; + loff_t fofs, base; + struct DiskOnChip *this = mtd->priv; + void __iomem * docptr = this->virtadr; + struct Nand *mychip = &this->chips[ofs >> this->chipshift]; + size_t i, size, got, want; + int ret = 0; + + DoC_CheckASIC(docptr); + + /* Find the chip which is to be used and select it */ + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(docptr, mychip->floor); + DoC_SelectChip(docptr, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(docptr, mychip->chip); + } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect); + + + /* Maximum of 16 bytes in the OOB region, so limit write to that */ + if (len > 16) + len = 16; + got = 0; + want = len; + + for (i = 0; ((i < 3) && (want > 0)); i++) { + /* Reset the chip, see Software Requirement 11.4 item 1. */ + DoC_Command(docptr, NAND_CMD_RESET, 0); + DoC_WaitReady(docptr); + + /* Figure out which region we are accessing... */ + fofs = ofs; + base = ofs & 0x0f; + if (!this->interleave) { + WriteDOC(NAND_CMD_READOOB, docptr, Mplus_FlashCmd); + size = 16 - base; + } else if (base < 6) { + WriteDOC(DoC_GetECCOffset(mtd, &fofs), docptr, Mplus_FlashCmd); + size = 6 - base; + } else if (base < 8) { + WriteDOC(DoC_GetFlagsOffset(mtd, &fofs), docptr, Mplus_FlashCmd); + size = 8 - base; + } else { + WriteDOC(DoC_GetHdrOffset(mtd, &fofs), docptr, Mplus_FlashCmd); + size = 16 - base; + } + if (size > want) + size = want; + + /* Issue the Serial Data In command to initial the Page Program process */ + DoC_Command(docptr, NAND_CMD_SEQIN, 0x00); + DoC_Address(this, 3, fofs, 0, 0x00); + + /* Disable the ECC engine */ + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + + /* Write the data via the internal pipeline through CDSN IO + register, see Pipelined Write Operations 11.2 */ + MemWriteDOC(docptr, (unsigned char *) &buf[got], size); + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + WriteDOC(0x00, docptr, Mplus_WritePipeTerm); + + /* Commit the Page Program command and wait for ready + see Software Requirement 11.4 item 1.*/ + DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00); + DoC_WaitReady(docptr); + + /* Read the status of the flash device through CDSN IO register + see Software Requirement 11.4 item 5.*/ + DoC_Command(docptr, NAND_CMD_STATUS, 0x00); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + DoC_Delay(docptr, 2); + if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) { + printk("MTD: Error 0x%x programming oob at 0x%x\n", + dummy, (int)ofs); + /* FIXME: implement Bad Block Replacement */ + *retlen = 0; + ret = -EIO; + } + dummy = ReadDOC(docptr, Mplus_LastDataRead); + + ofs += size; + got += size; + want -= size; + } + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + *retlen = len; + return ret; +} + +int doc_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + volatile char dummy; + struct DiskOnChip *this = mtd->priv; + __u32 ofs = instr->addr; + __u32 len = instr->len; + void __iomem * docptr = this->virtadr; + struct Nand *mychip = &this->chips[ofs >> this->chipshift]; + + DoC_CheckASIC(docptr); + + if (len != mtd->erasesize) + printk(KERN_WARNING "MTD: Erase not right size (%x != %x)n", + len, mtd->erasesize); + + /* Find the chip which is to be used and select it */ + if (this->curfloor != mychip->floor) { + DoC_SelectFloor(docptr, mychip->floor); + DoC_SelectChip(docptr, mychip->chip); + } else if (this->curchip != mychip->chip) { + DoC_SelectChip(docptr, mychip->chip); + } + this->curfloor = mychip->floor; + this->curchip = mychip->chip; + + instr->state = MTD_ERASE_PENDING; + + /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */ + WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect); + + DoC_Command(docptr, NAND_CMD_RESET, 0x00); + DoC_WaitReady(docptr); + + DoC_Command(docptr, NAND_CMD_ERASE1, 0); + DoC_Address(this, 2, ofs, 0, 0x00); + DoC_Command(docptr, NAND_CMD_ERASE2, 0); + DoC_WaitReady(docptr); + instr->state = MTD_ERASING; + + /* Read the status of the flash device through CDSN IO register + see Software Requirement 11.4 item 5. */ + DoC_Command(docptr, NAND_CMD_STATUS, 0); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + dummy = ReadDOC(docptr, Mplus_ReadPipeInit); + if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) { + printk("MTD: Error 0x%x erasing at 0x%x\n", dummy, ofs); + /* FIXME: implement Bad Block Replacement (in nftl.c ??) */ + instr->state = MTD_ERASE_FAILED; + } else { + instr->state = MTD_ERASE_DONE; + } + dummy = ReadDOC(docptr, Mplus_LastDataRead); + + /* Disable flash internally */ + WriteDOC(0, docptr, Mplus_FlashSelect); + + mtd_erase_callback(instr); + + return 0; +} + +/**************************************************************************** + * + * Module stuff + * + ****************************************************************************/ + +static int __init init_doc2001plus(void) +{ + inter_module_register(im_name, THIS_MODULE, &DoCMilPlus_init); + return 0; +} + +static void __exit cleanup_doc2001plus(void) +{ + struct mtd_info *mtd; + struct DiskOnChip *this; + + while ((mtd=docmilpluslist)) { + this = mtd->priv; + docmilpluslist = this->nextdoc; + + del_mtd_device(mtd); + + iounmap(this->virtadr); + kfree(this->chips); + kfree(mtd); + } + inter_module_unregister(im_name); +} + +module_exit(cleanup_doc2001plus); +module_init(init_doc2001plus); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com> et al."); +MODULE_DESCRIPTION("Driver for DiskOnChip Millennium Plus"); diff --git a/linux-2.4.x/drivers/mtd/devices/docecc.c b/linux-2.4.x/drivers/mtd/devices/docecc.c index 67af87d..cd3db72 100644 --- a/linux-2.4.x/drivers/mtd/devices/docecc.c +++ b/linux-2.4.x/drivers/mtd/devices/docecc.c @@ -4,10 +4,10 @@ * GNU GPL License. The rest is simply to convert the disk on chip * syndrom into a standard syndom. * - * Author: Fabrice Bellard (fabrice.bellard@netgem.com) + * Author: Fabrice Bellard (fabrice.bellard@netgem.com) * Copyright (C) 2000 Netgem S.A. * - * $Id: docecc.c,v 1.4 2001/10/02 15:05:13 dwmw2 Exp $ + * $Id: docecc.c,v 1.7 2005/11/07 11:14:25 gleixner Exp $ * * 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 @@ -40,6 +40,7 @@ #include <linux/mtd/mtd.h> #include <linux/mtd/doc2000.h> +#define DEBUG_ECC 0 /* need to undef it (from asm/termbits.h) */ #undef B0 @@ -121,7 +122,7 @@ for(ci=(n)-1;ci >=0;ci--)\ a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1) we consider the integer "i" whose binary representation with a(0) being LSB and a(m-1) MSB is (a(0),a(1),...,a(m-1)) and locate the entry - "index_of[i]". Now, @^index_of[i] is that element whose polynomial + "index_of[i]". Now, @^index_of[i] is that element whose polynomial representation is (a(0),a(1),a(2),...,a(m-1)). NOTE: The element alpha_to[2^m-1] = 0 always signifying that the @@ -129,7 +130,7 @@ for(ci=(n)-1;ci >=0;ci--)\ Similarily, the element index_of[0] = A0 always signifying that the power of alpha which has the polynomial representation (0,0,...,0) is "infinity". - + */ static void @@ -175,7 +176,7 @@ generate_gf(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1]) * are written back. NOTE! This array must be at least NN-KK elements long. * The corrected data are written in eras_val[]. They must be xor with the data * to retrieve the correct data : data[erase_pos[i]] ^= erase_val[i] . - * + * * First "no_eras" erasures are declared by the calling program. Then, the * maximum # of errors correctable is t_after_eras = floor((NN-KK-no_eras)/2). * If the number of channel errors is not greater than "t_after_eras" the @@ -188,7 +189,7 @@ generate_gf(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1]) * */ static int eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], - gf bb[NN - KK + 1], gf eras_val[NN-KK], int eras_pos[NN-KK], + gf bb[NN - KK + 1], gf eras_val[NN-KK], int eras_pos[NN-KK], int no_eras) { int deg_lambda, el, deg_omega; @@ -211,7 +212,7 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], count = 0; goto finish; } - + for(i=1;i<=NN-KK;i++){ s[i] = bb[0]; } @@ -219,7 +220,7 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], if(bb[j] == 0) continue; tmp = Index_of[bb[j]]; - + for(i=1;i<=NN-KK;i++) s[i] ^= Alpha_to[modnn(tmp + (B0+i-1)*PRIM*j)]; } @@ -233,7 +234,7 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], tmp = modnn(tmp + 2 * KK * (B0+i-1)*PRIM); s[i] = tmp; } - + CLEAR(&lambda[1],NN-KK); lambda[0] = 1; @@ -248,10 +249,10 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], lambda[j] ^= Alpha_to[modnn(u + tmp)]; } } -#if DEBUG >= 1 +#if DEBUG_ECC >= 1 /* Test code that verifies the erasure locator polynomial just constructed Needed only for decoder debugging. */ - + /* find roots of the erasure location polynomial */ for(i=1;i<=no_eras;i++) reg[i] = Index_of[lambda[i]]; @@ -275,7 +276,7 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], count = -1; goto finish; } -#if DEBUG >= 2 +#if DEBUG_ECC >= 2 printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n"); for (i = 0; i < count; i++) printf("%d ", loc[i]); @@ -285,7 +286,7 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], } for(i=0;i<NN-KK+1;i++) b[i] = Index_of[lambda[i]]; - + /* * Begin Berlekamp-Massey algorithm to determine error+erasure * locator polynomial @@ -388,7 +389,7 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], omega[i] = Index_of[tmp]; } omega[NN-KK] = A0; - + /* * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = * inv(X(l))**(B0-1) and den = lambda_pr(inv(X(l))) all in poly-form @@ -401,14 +402,14 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], } num2 = Alpha_to[modnn(root[j] * (B0 - 1) + NN)]; den = 0; - + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ for (i = min(deg_lambda,NN-KK-1) & ~1; i >= 0; i -=2) { if(lambda[i+1] != A0) den ^= Alpha_to[modnn(lambda[i+1] + i * root[j])]; } if (den == 0) { -#if DEBUG >= 1 +#if DEBUG_ECC >= 1 printf("\n ERROR: denominator = 0\n"); #endif /* Convert to dual- basis */ @@ -435,11 +436,11 @@ eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1], /* The sector bytes are packed into NB_DATA MM bits words */ #define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / MM) -/* +/* * Correct the errors in 'sector[]' by using 'ecc1[]' which is the * content of the feedback shift register applyied to the sector and * the ECC. Return the number of errors corrected (and correct them in - * sector), or -1 if error + * sector), or -1 if error */ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) { @@ -453,7 +454,7 @@ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) Alpha_to = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL); if (!Alpha_to) return -1; - + Index_of = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL); if (!Index_of) { kfree(Alpha_to); @@ -469,7 +470,7 @@ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) bb[2] = ((ecc1[2] & 0xf0) >> 4) | ((ecc1[3] & 0x3f) << 4); bb[3] = ((ecc1[3] & 0xc0) >> 6) | ((ecc1[0] & 0xff) << 2); - nb_errors = eras_dec_rs(Alpha_to, Index_of, bb, + nb_errors = eras_dec_rs(Alpha_to, Index_of, bb, error_val, error_pos, 0); if (nb_errors <= 0) goto the_end; @@ -488,7 +489,7 @@ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) can be modified since pos is even */ index = (pos >> 3) ^ 1; bitpos = pos & 7; - if ((index >= 0 && index < SECTOR_SIZE) || + if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) { val = error_val[i] >> (2 + bitpos); parity ^= val; @@ -499,7 +500,7 @@ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) bitpos = (bitpos + 10) & 7; if (bitpos == 0) bitpos = 8; - if ((index >= 0 && index < SECTOR_SIZE) || + if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) { val = error_val[i] << (8 - bitpos); parity ^= val; @@ -508,7 +509,7 @@ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) } } } - + /* use parity to test extra errors */ if ((parity & 0xff) != 0) nb_errors = -1; @@ -519,6 +520,8 @@ int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6]) return nb_errors; } +EXPORT_SYMBOL_GPL(doc_decode_ecc); + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Fabrice Bellard <fabrice.bellard@netgem.com>"); MODULE_DESCRIPTION("ECC code for correcting errors detected by DiskOnChip 2000 and Millennium ECC hardware"); diff --git a/linux-2.4.x/drivers/mtd/devices/docprobe.c b/linux-2.4.x/drivers/mtd/devices/docprobe.c index 43ccd77..13178b9 100644 --- a/linux-2.4.x/drivers/mtd/devices/docprobe.c +++ b/linux-2.4.x/drivers/mtd/devices/docprobe.c @@ -1,24 +1,25 @@ /* Linux driver for Disk-On-Chip devices */ /* Probe routines common to all DoC devices */ -/* (c) 1999 Machine Vision Holdings, Inc. */ -/* Author: David Woodhouse <dwmw2@infradead.org> */ -/* $Id: docprobe.c,v 1.30 2001/10/02 15:05:13 dwmw2 Exp $ */ +/* (C) 1999 Machine Vision Holdings, Inc. */ +/* (C) 1999-2003 David Woodhouse <dwmw2@infradead.org> */ + +/* $Id: docprobe.c,v 1.46 2005/11/07 11:14:25 gleixner Exp $ */ /* DOC_PASSIVE_PROBE: - In order to ensure that the BIOS checksum is correct at boot time, and - hence that the onboard BIOS extension gets executed, the DiskOnChip - goes into reset mode when it is read sequentially: all registers - return 0xff until the chip is woken up again by writing to the - DOCControl register. - - Unfortunately, this means that the probe for the DiskOnChip is unsafe, - because one of the first things it does is write to where it thinks - the DOCControl register should be - which may well be shared memory - for another device. I've had machines which lock up when this is - attempted. Hence the possibility to do a passive probe, which will fail + In order to ensure that the BIOS checksum is correct at boot time, and + hence that the onboard BIOS extension gets executed, the DiskOnChip + goes into reset mode when it is read sequentially: all registers + return 0xff until the chip is woken up again by writing to the + DOCControl register. + + Unfortunately, this means that the probe for the DiskOnChip is unsafe, + because one of the first things it does is write to where it thinks + the DOCControl register should be - which may well be shared memory + for another device. I've had machines which lock up when this is + attempted. Hence the possibility to do a passive probe, which will fail to detect a chip in reset mode, but is at least guaranteed not to lock the machine. @@ -30,14 +31,12 @@ /* DOC_SINGLE_DRIVER: Millennium driver has been merged into DOC2000 driver. - The newly-merged driver doesn't appear to work for writing. It's the - same with the DiskOnChip 2000 and the Millennium. If you have a - Millennium and you want write support to work, remove the definition - of DOC_SINGLE_DRIVER below to use the old doc2001-specific driver. - - Otherwise, it's left on in the hope that it'll annoy someone with - a Millennium enough that they go through and work out what the - difference is :) + The old Millennium-only driver has been retained just in case there + are problems with the new code. If the combined driver doesn't work + for you, you can try the old one by undefining DOC_SINGLE_DRIVER + below and also enabling it in your configuration. If this fixes the + problems, please send a report to the MTD mailing list at + <linux-mtd@lists.infradead.org>. */ #define DOC_SINGLE_DRIVER @@ -46,18 +45,15 @@ #include <linux/module.h> #include <asm/errno.h> #include <asm/io.h> -#include <asm/uaccess.h> -#include <linux/miscdevice.h> -#include <linux/pci.h> #include <linux/delay.h> #include <linux/slab.h> -#include <linux/sched.h> #include <linux/init.h> #include <linux/types.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/doc2000.h> +#include <linux/mtd/compatmac.h> /* Where to look for the devices? */ #ifndef CONFIG_MTD_DOCPROBE_ADDRESS @@ -66,39 +62,42 @@ static unsigned long doc_config_location = CONFIG_MTD_DOCPROBE_ADDRESS; -MODULE_PARM(doc_config_location, "l"); - +module_param(doc_config_location, ulong, 0); +MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip"); static unsigned long __initdata doc_locations[] = { #if defined (__alpha__) || defined(__i386__) || defined(__x86_64__) #ifdef CONFIG_MTD_DOCPROBE_HIGH - 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, + 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000, - 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, - 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, + 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, + 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000, #else /* CONFIG_MTD_DOCPROBE_HIGH */ - 0xc8000, 0xca000, 0xcc000, 0xce000, + 0xc8000, 0xca000, 0xcc000, 0xce000, 0xd0000, 0xd2000, 0xd4000, 0xd6000, - 0xd8000, 0xda000, 0xdc000, 0xde000, - 0xe0000, 0xe2000, 0xe4000, 0xe6000, + 0xd8000, 0xda000, 0xdc000, 0xde000, + 0xe0000, 0xe2000, 0xe4000, 0xe6000, 0xe8000, 0xea000, 0xec000, 0xee000, #endif /* CONFIG_MTD_DOCPROBE_HIGH */ -#elif defined(__ppc__) +#elif defined(__PPC__) 0xe4000000, #elif defined(CONFIG_MOMENCO_OCELOT) 0x2f000000, -#else + 0xff000000, +#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C) + 0xff000000, +##else #warning Unknown architecture for DiskOnChip. No default probe locations defined #endif - 0 }; + 0xffffffff }; /* doccheck: Probe a given memory window to see if there's a DiskOnChip present */ -static inline int __init doccheck(unsigned long potential, unsigned long physadr) +static inline int __init doccheck(void __iomem *potential, unsigned long physadr) { - unsigned long window=potential; - unsigned char tmp, ChipID; + void __iomem *window=potential; + unsigned char tmp, tmpb, tmpc, ChipID; #ifndef DOC_PASSIVE_PROBE unsigned char tmp2; #endif @@ -112,50 +111,104 @@ static inline int __init doccheck(unsigned long potential, unsigned long physadr return 0; #endif /* CONFIG_MTD_DOCPROBE_55AA */ -#ifndef DOC_PASSIVE_PROBE +#ifndef DOC_PASSIVE_PROBE /* It's not possible to cleanly detect the DiskOnChip - the * bootup procedure will put the device into reset mode, and * it's not possible to talk to it without actually writing * to the DOCControl register. So we store the current contents * of the DOCControl register's location, in case we later decide * that it's not a DiskOnChip, and want to put it back how we - * found it. + * found it. */ tmp2 = ReadDOC(window, DOCControl); - + /* Reset the DiskOnChip ASIC */ - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, window, DOCControl); - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, window, DOCControl); - + /* Enable the DiskOnChip ASIC */ - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, window, DOCControl); - WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, window, DOCControl); -#endif /* !DOC_PASSIVE_PROBE */ +#endif /* !DOC_PASSIVE_PROBE */ + /* We need to read the ChipID register four times. For some + newer DiskOnChip 2000 units, the first three reads will + return the DiskOnChip Millennium ident. Don't ask. */ ChipID = ReadDOC(window, ChipID); - + switch (ChipID) { case DOC_ChipID_Doc2k: /* Check the TOGGLE bit in the ECC register */ - tmp = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; - if ((ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT) != tmp) + tmp = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; + tmpb = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; + tmpc = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT; + if (tmp != tmpb && tmp == tmpc) return ChipID; break; - + case DOC_ChipID_DocMil: + /* Check for the new 2000 with Millennium ASIC */ + ReadDOC(window, ChipID); + ReadDOC(window, ChipID); + if (ReadDOC(window, ChipID) != DOC_ChipID_DocMil) + ChipID = DOC_ChipID_Doc2kTSOP; + /* Check the TOGGLE bit in the ECC register */ - tmp = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; - if ((ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT) != tmp) + tmp = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; + tmpb = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; + tmpc = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT; + if (tmp != tmpb && tmp == tmpc) return ChipID; break; - + + case DOC_ChipID_DocMilPlus16: + case DOC_ChipID_DocMilPlus32: + case 0: + /* Possible Millennium+, need to do more checks */ +#ifndef DOC_PASSIVE_PROBE + /* Possibly release from power down mode */ + for (tmp = 0; (tmp < 4); tmp++) + ReadDOC(window, Mplus_Power); + + /* Reset the DiskOnChip ASIC */ + tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | + DOC_MODE_BDECT; + WriteDOC(tmp, window, Mplus_DOCControl); + WriteDOC(~tmp, window, Mplus_CtrlConfirm); + + mdelay(1); + /* Enable the DiskOnChip ASIC */ + tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | + DOC_MODE_BDECT; + WriteDOC(tmp, window, Mplus_DOCControl); + WriteDOC(~tmp, window, Mplus_CtrlConfirm); + mdelay(1); +#endif /* !DOC_PASSIVE_PROBE */ + + ChipID = ReadDOC(window, ChipID); + + switch (ChipID) { + case DOC_ChipID_DocMilPlus16: + case DOC_ChipID_DocMilPlus32: + /* Check the TOGGLE bit in the toggle register */ + tmp = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT; + tmpb = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT; + tmpc = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT; + if (tmp != tmpb && tmp == tmpc) + return ChipID; + default: + break; + } + /* FALL TRHU */ + default: -#ifndef CONFIG_MTD_DOCPROBE_55AA - printk(KERN_WARNING "Possible DiskOnChip with unknown ChipID %2.2X found at 0x%lx\n", + +#ifdef CONFIG_MTD_DOCPROBE_55AA + printk(KERN_DEBUG "Possible DiskOnChip with unknown ChipID %2.2X found at 0x%lx\n", ChipID, physadr); #endif #ifndef DOC_PASSIVE_PROBE @@ -174,13 +227,13 @@ static inline int __init doccheck(unsigned long potential, unsigned long physadr WriteDOC(tmp2, window, DOCControl); #endif return 0; -} +} static int docfound; static void __init DoC_Probe(unsigned long physadr) { - unsigned long docptr; + void __iomem *docptr; struct DiskOnChip *this; struct mtd_info *mtd; int ChipID; @@ -190,23 +243,29 @@ static void __init DoC_Probe(unsigned long physadr) char *im_modname = NULL; void (*initroutine)(struct mtd_info *) = NULL; - docptr = (unsigned long)ioremap(physadr, DOC_IOREMAP_LEN); - + docptr = ioremap(physadr, DOC_IOREMAP_LEN); + if (!docptr) return; - + if ((ChipID = doccheck(docptr, physadr))) { + if (ChipID == DOC_ChipID_Doc2kTSOP) { + /* Remove this at your own peril. The hardware driver works but nothing prevents you from erasing bad blocks */ + printk(KERN_NOTICE "Refusing to drive DiskOnChip 2000 TSOP until Bad Block Table is correctly supported by INFTL\n"); + iounmap(docptr); + return; + } docfound = 1; mtd = kmalloc(sizeof(struct DiskOnChip) + sizeof(struct mtd_info), GFP_KERNEL); if (!mtd) { printk(KERN_WARNING "Cannot allocate memory for data structures. Dropping.\n"); - iounmap((void *)docptr); + iounmap(docptr); return; } - + this = (struct DiskOnChip *)(&mtd[1]); - + memset((char *)mtd,0, sizeof(struct mtd_info)); memset((char *)this, 0, sizeof(struct DiskOnChip)); @@ -217,12 +276,18 @@ static void __init DoC_Probe(unsigned long physadr) sprintf(namebuf, "with ChipID %2.2X", ChipID); switch(ChipID) { + case DOC_ChipID_Doc2kTSOP: + name="2000 TSOP"; + im_funcname = "DoC2k_init"; + im_modname = "doc2000"; + break; + case DOC_ChipID_Doc2k: name="2000"; im_funcname = "DoC2k_init"; im_modname = "doc2000"; break; - + case DOC_ChipID_DocMil: name="Millennium"; #ifdef DOC_SINGLE_DRIVER @@ -233,6 +298,13 @@ static void __init DoC_Probe(unsigned long physadr) im_modname = "doc2001"; #endif /* DOC_SINGLE_DRIVER */ break; + + case DOC_ChipID_DocMilPlus16: + case DOC_ChipID_DocMilPlus32: + name="MillenniumPlus"; + im_funcname = "DoCMilPlus_init"; + im_modname = "doc2001plus"; + break; } if (im_funcname) @@ -244,8 +316,9 @@ static void __init DoC_Probe(unsigned long physadr) return; } printk(KERN_NOTICE "Cannot find driver for DiskOnChip %s at 0x%lX\n", name, physadr); + kfree(mtd); } - iounmap((void *)docptr); + iounmap(docptr); } @@ -255,15 +328,15 @@ static void __init DoC_Probe(unsigned long physadr) * ****************************************************************************/ -int __init init_doc(void) +static int __init init_doc(void) { int i; - + if (doc_config_location) { printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location); DoC_Probe(doc_config_location); } else { - for (i=0; doc_locations[i]; i++) { + for (i=0; (doc_locations[i] != 0xffffffff); i++) { DoC_Probe(doc_locations[i]); } } @@ -271,11 +344,7 @@ int __init init_doc(void) found, so the user knows we at least tried. */ if (!docfound) printk(KERN_INFO "No recognised DiskOnChip devices found\n"); - /* So it looks like we've been used and we get unloaded */ - MOD_INC_USE_COUNT; - MOD_DEC_USE_COUNT; - return 0; - + return -EAGAIN; } module_init(init_doc); diff --git a/linux-2.4.x/drivers/mtd/devices/lart.c b/linux-2.4.x/drivers/mtd/devices/lart.c index e049ace..0320e0f 100644 --- a/linux-2.4.x/drivers/mtd/devices/lart.c +++ b/linux-2.4.x/drivers/mtd/devices/lart.c @@ -2,7 +2,7 @@ /* * MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART. * - * $Id: lart.c,v 1.2 2001/10/02 15:05:13 dwmw2 Exp $ + * $Id: lart.c,v 1.10 2006/03/29 08:31:11 dwmw2 Exp $ * * Author: Abraham vd Merwe <abraham@2d3d.co.za> * @@ -42,8 +42,9 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/types.h> -#include <linux/version.h> +#include <linux/init.h> #include <linux/errno.h> +#include <linux/string.h> #include <linux/mtd/mtd.h> #ifdef HAVE_PARTITIONS #include <linux/mtd/partitions.h> @@ -121,7 +122,7 @@ static char module_name[] = "lart"; /* * The data line mapping on LART is as follows: - * + * * U2 CPU | U3 CPU * ------------------- * 0 20 | 0 12 @@ -180,7 +181,7 @@ static char module_name[] = "lart"; (((x) & 0x00004000) >> 13) \ ) -/* +/* * The address line mapping on LART is as follows: * * U3 CPU | U2 CPU @@ -203,7 +204,7 @@ static char module_name[] = "lart"; * 12 15 | 12 15 * 13 14 | 13 14 * 14 16 | 14 16 - * + * * MAIN BLOCK BOUNDARY * * 15 17 | 15 18 @@ -433,7 +434,7 @@ static int flash_erase (struct mtd_info *mtd,struct erase_info *instr) } instr->state = MTD_ERASE_DONE; - if (instr->callback) instr->callback (instr); + mtd_erase_callback(instr); return (0); } @@ -580,50 +581,43 @@ static int flash_write (struct mtd_info *mtd,loff_t to,size_t len,size_t *retlen /***************************************************************************************************/ -#define NB_OF(x) (sizeof (x) / sizeof (x[0])) - static struct mtd_info mtd; -static struct mtd_erase_region_info erase_regions[] = -{ - /* parameter blocks */ - { - offset: 0x00000000, - erasesize: FLASH_BLOCKSIZE_PARAM, - numblocks: FLASH_NUMBLOCKS_16m_PARAM - }, - /* main blocks */ - { - offset: FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM, - erasesize: FLASH_BLOCKSIZE_MAIN, - numblocks: FLASH_NUMBLOCKS_16m_MAIN - } +static struct mtd_erase_region_info erase_regions[] = { + /* parameter blocks */ + { + .offset = 0x00000000, + .erasesize = FLASH_BLOCKSIZE_PARAM, + .numblocks = FLASH_NUMBLOCKS_16m_PARAM, + }, + /* main blocks */ + { + .offset = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM, + .erasesize = FLASH_BLOCKSIZE_MAIN, + .numblocks = FLASH_NUMBLOCKS_16m_MAIN, + } }; #ifdef HAVE_PARTITIONS -static struct mtd_partition lart_partitions[] = -{ - /* blob */ - { - name: "blob", - offset: BLOB_START, - size: BLOB_LEN, - mask_flags: 0 - }, - /* kernel */ - { - name: "kernel", - offset: KERNEL_START, /* MTDPART_OFS_APPEND */ - size: KERNEL_LEN, - mask_flags: 0 - }, - /* initial ramdisk / file system */ - { - name: "file system", - offset: INITRD_START, /* MTDPART_OFS_APPEND */ - size: INITRD_LEN, /* MTDPART_SIZ_FULL */ - mask_flags: 0 - } +static struct mtd_partition lart_partitions[] = { + /* blob */ + { + .name = "blob", + .offset = BLOB_START, + .size = BLOB_LEN, + }, + /* kernel */ + { + .name = "kernel", + .offset = KERNEL_START, /* MTDPART_OFS_APPEND */ + .size = KERNEL_LEN, + }, + /* initial ramdisk / file system */ + { + .name = "file system", + .offset = INITRD_START, /* MTDPART_OFS_APPEND */ + .size = INITRD_LEN, /* MTDPART_SIZ_FULL */ + } }; #endif @@ -644,12 +638,12 @@ int __init lart_flash_init (void) mtd.flags = MTD_CAP_NORFLASH; mtd.size = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM + FLASH_BLOCKSIZE_MAIN * FLASH_NUMBLOCKS_16m_MAIN; mtd.erasesize = FLASH_BLOCKSIZE_MAIN; - mtd.numeraseregions = NB_OF (erase_regions); + mtd.numeraseregions = ARRAY_SIZE(erase_regions); mtd.eraseregions = erase_regions; - mtd.module = THIS_MODULE; mtd.erase = flash_erase; mtd.read = flash_read; mtd.write = flash_write; + mtd.owner = THIS_MODULE; #ifdef LART_DEBUG printk (KERN_DEBUG @@ -674,9 +668,9 @@ int __init lart_flash_init (void) result,mtd.eraseregions[result].numblocks); #ifdef HAVE_PARTITIONS - printk ("\npartitions = %d\n",NB_OF (lart_partitions)); + printk ("\npartitions = %d\n", ARRAY_SIZE(lart_partitions)); - for (result = 0; result < NB_OF (lart_partitions); result++) + for (result = 0; result < ARRAY_SIZE(lart_partitions); result++) printk (KERN_DEBUG "\n\n" "lart_partitions[%d].name = %s\n" @@ -691,7 +685,7 @@ int __init lart_flash_init (void) #ifndef HAVE_PARTITIONS result = add_mtd_device (&mtd); #else - result = add_mtd_partitions (&mtd,lart_partitions,NB_OF (lart_partitions)); + result = add_mtd_partitions (&mtd,lart_partitions, ARRAY_SIZE(lart_partitions)); #endif return (result); diff --git a/linux-2.4.x/drivers/mtd/devices/ms02-nv.c b/linux-2.4.x/drivers/mtd/devices/ms02-nv.c index 47e8be3..306d735 100644 --- a/linux-2.4.x/drivers/mtd/devices/ms02-nv.c +++ b/linux-2.4.x/drivers/mtd/devices/ms02-nv.c @@ -1,10 +1,12 @@ /* - * Copyright (c) 2001 Maciej W. Rozycki + * Copyright (c) 2001 Maciej W. Rozycki * - * 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 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. + * + * $Id: ms02-nv.c,v 1.12 2006/03/29 08:31:11 dwmw2 Exp $ */ #include <linux/init.h> @@ -27,18 +29,18 @@ static char version[] __initdata = - "ms02-nv.c: v.1.0.0 13 Aug 2001 Maciej W. Rozycki.\n"; + "ms02-nv.c: v.1.0.0 13 Aug 2001 Maciej W. Rozycki.\n"; -MODULE_AUTHOR("Maciej W. Rozycki <macro@ds2.pg.gda.pl>"); +MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>"); MODULE_DESCRIPTION("DEC MS02-NV NVRAM module driver"); MODULE_LICENSE("GPL"); /* * Addresses we probe for an MS02-NV at. Modules may be located - * at any 8MB boundary within a 0MB up to 112MB range or at any 32MB - * boundary within a 0MB up to 448MB range. We don't support a module - * at 0MB, though. + * at any 8MiB boundary within a 0MiB up to 112MiB range or at any 32MiB + * boundary within a 0MiB up to 448MiB range. We don't support a module + * at 0MiB, though. */ static ulong ms02nv_addrs[] __initdata = { 0x07000000, 0x06800000, 0x06000000, 0x05800000, 0x05000000, @@ -57,7 +59,7 @@ static struct mtd_info *root_ms02nv_mtd; static int ms02nv_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct ms02nv_private *mp = (struct ms02nv_private *)mtd->priv; + struct ms02nv_private *mp = mtd->priv; if (from + len > mtd->size) return -EINVAL; @@ -71,7 +73,7 @@ static int ms02nv_read(struct mtd_info *mtd, loff_t from, static int ms02nv_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - struct ms02nv_private *mp = (struct ms02nv_private *)mtd->priv; + struct ms02nv_private *mp = mtd->priv; if (to + len > mtd->size) return -EINVAL; @@ -97,8 +99,8 @@ static inline uint ms02nv_probe_one(ulong addr) * The firmware writes MS02NV_ID at MS02NV_MAGIC and also * a diagnostic status at MS02NV_DIAG. */ - ms02nv_diagp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_DIAG)); - ms02nv_magicp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_MAGIC)); + ms02nv_diagp = (ms02nv_uint *)(CKSEG1ADDR(addr + MS02NV_DIAG)); + ms02nv_magicp = (ms02nv_uint *)(CKSEG1ADDR(addr + MS02NV_MAGIC)); err = get_dbe(ms02nv_magic, ms02nv_magicp); if (err) return 0; @@ -128,7 +130,7 @@ static int __init ms02nv_init_one(ulong addr) int ret = -ENODEV; - /* The module decodes 8MB of address space. */ + /* The module decodes 8MiB of address space. */ mod_res = kmalloc(sizeof(*mod_res), GFP_KERNEL); if (!mod_res) return -ENOMEM; @@ -220,7 +222,7 @@ static int __init ms02nv_init_one(ulong addr) mtd->flags = MTD_CAP_RAM | MTD_XIP; mtd->size = fixsize; mtd->name = (char *)ms02nv_name; - mtd->module = THIS_MODULE; + mtd->owner = THIS_MODULE; mtd->read = ms02nv_read; mtd->write = ms02nv_write; @@ -231,7 +233,7 @@ static int __init ms02nv_init_one(ulong addr) goto err_out_csr_res; } - printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %uMB.\n", + printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %zuMiB.\n", mtd->index, ms02nv_name, addr, size >> 20); mp->next = root_ms02nv_mtd; @@ -263,7 +265,7 @@ err_out_mod_res: static void __exit ms02nv_remove_one(void) { struct mtd_info *mtd = root_ms02nv_mtd; - struct ms02nv_private *mp = (struct ms02nv_private *)mtd->priv; + struct ms02nv_private *mp = mtd->priv; root_ms02nv_mtd = mp->next; @@ -291,12 +293,13 @@ static int __init ms02nv_init(void) switch (mips_machtype) { case MACH_DS5000_200: - csr = (volatile u32 *)KN02_CSR_ADDR; + csr = (volatile u32 *)CKSEG1ADDR(KN02_SLOT_BASE + KN02_CSR); if (*csr & KN02_CSR_BNK32M) stride = 2; break; case MACH_DS5000_2X0: - csr = (volatile u32 *)KN03_MCR_BASE; + case MACH_DS5900: + csr = (volatile u32 *)CKSEG1ADDR(KN03_SLOT_BASE + IOASIC_MCR); if (*csr & KN03_MCR_BNK32M) stride = 2; break; @@ -305,7 +308,7 @@ static int __init ms02nv_init(void) break; } - for (i = 0; i < (sizeof(ms02nv_addrs) / sizeof(*ms02nv_addrs)); i++) + for (i = 0; i < ARRAY_SIZE(ms02nv_addrs); i++) if (!ms02nv_init_one(ms02nv_addrs[i] << stride)) count++; diff --git a/linux-2.4.x/drivers/mtd/devices/ms02-nv.h b/linux-2.4.x/drivers/mtd/devices/ms02-nv.h index cd10b6b..8a6eef7 100644 --- a/linux-2.4.x/drivers/mtd/devices/ms02-nv.h +++ b/linux-2.4.x/drivers/mtd/devices/ms02-nv.h @@ -1,32 +1,96 @@ /* - * Copyright (c) 2001 Maciej W. Rozycki + * Copyright (c) 2001, 2003 Maciej W. Rozycki * - * 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. + * DEC MS02-NV (54-20948-01) battery backed-up NVRAM module for + * DECstation/DECsystem 5000/2x0 and DECsystem 5900 and 5900/260 + * systems. + * + * 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. + * + * $Id: ms02-nv.h,v 1.3 2003/08/19 09:25:36 dwmw2 Exp $ */ #include <linux/ioport.h> #include <linux/mtd/mtd.h> +/* + * Addresses are decoded as follows: + * + * 0x000000 - 0x3fffff SRAM + * 0x400000 - 0x7fffff CSR + * + * Within the SRAM area the following ranges are forced by the system + * firmware: + * + * 0x000000 - 0x0003ff diagnostic area, destroyed upon a reboot + * 0x000400 - ENDofRAM storage area, available to operating systems + * + * but we can't really use the available area right from 0x000400 as + * the first word is used by the firmware as a status flag passed + * from an operating system. If anything but the valid data magic + * ID value is found, the firmware considers the SRAM clean, i.e. + * containing no valid data, and disables the battery resulting in + * data being erased as soon as power is switched off. So the choice + * for the start address of the user-available is 0x001000 which is + * nicely page aligned. The area between 0x000404 and 0x000fff may + * be used by the driver for own needs. + * + * The diagnostic area defines two status words to be read by an + * operating system, a magic ID to distinguish a MS02-NV board from + * anything else and a status information providing results of tests + * as well as the size of SRAM available, which can be 1MiB or 2MiB + * (that's what the firmware handles; no idea if 2MiB modules ever + * existed). + * + * The firmware only handles the MS02-NV board if installed in the + * last (15th) slot, so for any other location the status information + * stored in the SRAM cannot be relied upon. But from the hardware + * point of view there is no problem using up to 14 such boards in a + * system -- only the 1st slot needs to be filled with a DRAM module. + * The MS02-NV board is ECC-protected, like other MS02 memory boards. + * + * The state of the battery as provided by the CSR is reflected on + * the two onboard LEDs. When facing the battery side of the board, + * with the LEDs at the top left and the battery at the bottom right + * (i.e. looking from the back side of the system box), their meaning + * is as follows (the system has to be powered on): + * + * left LED battery disable status: lit = enabled + * right LED battery condition status: lit = OK + */ + /* MS02-NV iomem register offsets. */ #define MS02NV_CSR 0x400000 /* control & status register */ +/* MS02-NV CSR status bits. */ +#define MS02NV_CSR_BATT_OK 0x01 /* battery OK */ +#define MS02NV_CSR_BATT_OFF 0x02 /* battery disabled */ + + /* MS02-NV memory offsets. */ #define MS02NV_DIAG 0x0003f8 /* diagnostic status */ #define MS02NV_MAGIC 0x0003fc /* MS02-NV magic ID */ -#define MS02NV_RAM 0x000400 /* general-purpose RAM start */ +#define MS02NV_VALID 0x000400 /* valid data magic ID */ +#define MS02NV_RAM 0x001000 /* user-exposed RAM start */ -/* MS02-NV diagnostic status constants. */ -#define MS02NV_DIAG_SIZE_MASK 0xf0 /* RAM size mask */ -#define MS02NV_DIAG_SIZE_SHIFT 0x10 /* RAM size shift (left) */ +/* MS02-NV diagnostic status bits. */ +#define MS02NV_DIAG_TEST 0x01 /* SRAM test done (?) */ +#define MS02NV_DIAG_RO 0x02 /* SRAM r/o test done */ +#define MS02NV_DIAG_RW 0x04 /* SRAM r/w test done */ +#define MS02NV_DIAG_FAIL 0x08 /* SRAM test failed */ +#define MS02NV_DIAG_SIZE_MASK 0xf0 /* SRAM size mask */ +#define MS02NV_DIAG_SIZE_SHIFT 0x10 /* SRAM size shift (left) */ /* MS02-NV general constants. */ #define MS02NV_ID 0x03021966 /* MS02-NV magic ID value */ +#define MS02NV_VALID_ID 0xbd100248 /* valid data magic ID value */ #define MS02NV_SLOT_SIZE 0x800000 /* size of the address space decoded by the module */ + typedef volatile u32 ms02nv_uint; struct ms02nv_private { diff --git a/linux-2.4.x/drivers/mtd/devices/mtdram.c b/linux-2.4.x/drivers/mtd/devices/mtdram.c index f105f7d..95a8b65 100644 --- a/linux-2.4.x/drivers/mtd/devices/mtdram.c +++ b/linux-2.4.x/drivers/mtd/devices/mtdram.c @@ -1,171 +1,154 @@ /* * mtdram - a test mtd device - * $Id: mtdram.c,v 1.26 2001/12/01 10:24:18 dwmw2 Exp $ + * $Id: mtdram.c,v 1.38 2005/11/06 10:25:42 gleixner Exp $ * Author: Alexander Larsson <alex@cendio.se> * * Copyright (c) 1999 Alexander Larsson <alex@cendio.se> + * Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de> * * This code is GPL - * */ - #include <linux/config.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/ioport.h> +#include <linux/vmalloc.h> +#include <linux/init.h> #include <linux/mtd/compatmac.h> #include <linux/mtd/mtd.h> -#ifndef CONFIG_MTDRAM_ABS_POS - #define CONFIG_MTDRAM_ABS_POS 0 -#endif - -#if CONFIG_MTDRAM_ABS_POS > 0 - #include <asm/io.h> -#endif - -#ifdef MODULE static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE; static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE; -MODULE_PARM(total_size,"l"); -MODULE_PARM(erase_size,"l"); #define MTDRAM_TOTAL_SIZE (total_size * 1024) #define MTDRAM_ERASE_SIZE (erase_size * 1024) -#else -#define MTDRAM_TOTAL_SIZE (CONFIG_MTDRAM_TOTAL_SIZE * 1024) -#define MTDRAM_ERASE_SIZE (CONFIG_MTDRAM_ERASE_SIZE * 1024) -#endif +#ifdef MODULE +module_param(total_size, ulong, 0); +MODULE_PARM_DESC(total_size, "Total device size in KiB"); +module_param(erase_size, ulong, 0); +MODULE_PARM_DESC(erase_size, "Device erase block size in KiB"); +#endif // We could store these in the mtd structure, but we only support 1 device.. static struct mtd_info *mtd_info; - -static int -ram_erase(struct mtd_info *mtd, struct erase_info *instr) +static int ram_erase(struct mtd_info *mtd, struct erase_info *instr) { - DEBUG(MTD_DEBUG_LEVEL2, "ram_erase(pos:%ld, len:%ld)\n", (long)instr->addr, (long)instr->len); - if (instr->addr + instr->len > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL1, "ram_erase() out of bounds (%ld > %ld)\n", (long)(instr->addr + instr->len), (long)mtd->size); - return -EINVAL; - } - - memset((char *)mtd->priv + instr->addr, 0xff, instr->len); - - instr->state = MTD_ERASE_DONE; - - if (instr->callback) - (*(instr->callback))(instr); - return 0; + if (instr->addr + instr->len > mtd->size) + return -EINVAL; + + memset((char *)mtd->priv + instr->addr, 0xff, instr->len); + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; } -static int ram_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf) +static int ram_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char **mtdbuf) { - if (from + len > mtd->size) - return -EINVAL; - - *mtdbuf = mtd->priv + from; - *retlen = len; - return 0; + if (from + len > mtd->size) + return -EINVAL; + + *mtdbuf = mtd->priv + from; + *retlen = len; + return 0; } -static void ram_unpoint (struct mtd_info *mtd, u_char *addr) +static void ram_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from, + size_t len) { - DEBUG(MTD_DEBUG_LEVEL2, "ram_unpoint\n"); } static int ram_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, u_char *buf) + size_t *retlen, u_char *buf) { - DEBUG(MTD_DEBUG_LEVEL2, "ram_read(pos:%ld, len:%ld)\n", (long)from, (long)len); - if (from + len > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL1, "ram_read() out of bounds (%ld > %ld)\n", (long)(from + len), (long)mtd->size); - return -EINVAL; - } + if (from + len > mtd->size) + return -EINVAL; - memcpy(buf, mtd->priv + from, len); + memcpy(buf, mtd->priv + from, len); - *retlen=len; - return 0; + *retlen = len; + return 0; } static int ram_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const u_char *buf) + size_t *retlen, const u_char *buf) { - DEBUG(MTD_DEBUG_LEVEL2, "ram_write(pos:%ld, len:%ld)\n", (long)to, (long)len); - if (to + len > mtd->size) { - DEBUG(MTD_DEBUG_LEVEL1, "ram_write() out of bounds (%ld > %ld)\n", (long)(to + len), (long)mtd->size); - return -EINVAL; - } + if (to + len > mtd->size) + return -EINVAL; - memcpy ((char *)mtd->priv + to, buf, len); + memcpy((char *)mtd->priv + to, buf, len); - *retlen=len; - return 0; + *retlen = len; + return 0; } static void __exit cleanup_mtdram(void) { - if (mtd_info) { - del_mtd_device(mtd_info); - if (mtd_info->priv) -#if CONFIG_MTDRAM_ABS_POS > 0 - iounmap(mtd_info->priv); -#else - vfree(mtd_info->priv); -#endif - kfree(mtd_info); - } + if (mtd_info) { + del_mtd_device(mtd_info); + vfree(mtd_info->priv); + kfree(mtd_info); + } } -int __init init_mtdram(void) +int mtdram_init_device(struct mtd_info *mtd, void *mapped_address, + unsigned long size, char *name) { - // Allocate some memory - mtd_info = (struct mtd_info *)kmalloc(sizeof(struct mtd_info), GFP_KERNEL); - if (!mtd_info) - return -ENOMEM; - - memset(mtd_info, 0, sizeof(*mtd_info)); - - // Setup the MTD structure - mtd_info->name = "mtdram test device"; - mtd_info->type = MTD_RAM; - mtd_info->flags = MTD_CAP_RAM; - mtd_info->size = MTDRAM_TOTAL_SIZE; - mtd_info->erasesize = MTDRAM_ERASE_SIZE; -#if CONFIG_MTDRAM_ABS_POS > 0 - mtd_info->priv = ioremap(CONFIG_MTDRAM_ABS_POS, MTDRAM_TOTAL_SIZE); -#else - mtd_info->priv = vmalloc(MTDRAM_TOTAL_SIZE); -#endif + memset(mtd, 0, sizeof(*mtd)); + + /* Setup the MTD structure */ + mtd->name = name; + mtd->type = MTD_RAM; + mtd->flags = MTD_CAP_RAM; + mtd->size = size; + mtd->erasesize = MTDRAM_ERASE_SIZE; + mtd->priv = mapped_address; + + mtd->owner = THIS_MODULE; + mtd->erase = ram_erase; + mtd->point = ram_point; + mtd->unpoint = ram_unpoint; + mtd->read = ram_read; + mtd->write = ram_write; + + if (add_mtd_device(mtd)) { + return -EIO; + } + + return 0; +} - if (!mtd_info->priv) { - DEBUG(MTD_DEBUG_LEVEL1, "Failed to vmalloc(/ioremap) memory region of size %ld (ABS_POS:%ld)\n", (long)MTDRAM_TOTAL_SIZE, (long)CONFIG_MTDRAM_ABS_POS); - kfree(mtd_info); - mtd_info = NULL; - return -ENOMEM; - } - memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE); - - mtd_info->module = THIS_MODULE; - mtd_info->erase = ram_erase; - mtd_info->point = ram_point; - mtd_info->unpoint = ram_unpoint; - mtd_info->read = ram_read; - mtd_info->write = ram_write; - - if (add_mtd_device(mtd_info)) { -#if CONFIG_MTDRAM_ABS_POS > 0 - iounmap(mtd_info->priv); -#else - vfree(mtd_info->priv); -#endif - kfree(mtd_info); - mtd_info = NULL; - return -EIO; - } - - return 0; +static int __init init_mtdram(void) +{ + void *addr; + int err; + + if (!total_size) + return -EINVAL; + + /* Allocate some memory */ + mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL); + if (!mtd_info) + return -ENOMEM; + + addr = vmalloc(MTDRAM_TOTAL_SIZE); + if (!addr) { + kfree(mtd_info); + mtd_info = NULL; + return -ENOMEM; + } + err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device"); + if (err) { + vfree(addr); + kfree(mtd_info); + mtd_info = NULL; + return err; + } + memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE); + return err; } module_init(init_mtdram); @@ -174,4 +157,3 @@ module_exit(cleanup_mtdram); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>"); MODULE_DESCRIPTION("Simulated MTD driver for testing"); - diff --git a/linux-2.4.x/drivers/mtd/devices/phram.c b/linux-2.4.x/drivers/mtd/devices/phram.c new file mode 100644 index 0000000..e8685ee --- /dev/null +++ b/linux-2.4.x/drivers/mtd/devices/phram.c @@ -0,0 +1,300 @@ +/** + * $Id: phram.c,v 1.16 2005/11/07 11:14:25 gleixner Exp $ + * + * Copyright (c) ???? Jochen Schäuble <psionic@psionic.de> + * Copyright (c) 2003-2004 Jörn Engel <joern@wh.fh-wedel.de> + * + * Usage: + * + * one commend line parameter per device, each in the form: + * phram=<name>,<start>,<len> + * <name> may be up to 63 characters. + * <start> and <len> can be octal, decimal or hexadecimal. If followed + * by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or + * gigabytes. + * + * Example: + * phram=swap,64Mi,128Mi phram=test,900Mi,1Mi + */ +#include <asm/io.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/mtd/mtd.h> + +#define ERROR(fmt, args...) printk(KERN_ERR "phram: " fmt , ## args) + +struct phram_mtd_list { + struct mtd_info mtd; + struct list_head list; +}; + +static LIST_HEAD(phram_list); + + +static int phram_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + u_char *start = mtd->priv; + + if (instr->addr + instr->len > mtd->size) + return -EINVAL; + + memset(start + instr->addr, 0xff, instr->len); + + /* This'll catch a few races. Free the thing before returning :) + * I don't feel at all ashamed. This kind of thing is possible anyway + * with flash, but unlikely. + */ + + instr->state = MTD_ERASE_DONE; + + mtd_erase_callback(instr); + + return 0; +} + +static int phram_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char **mtdbuf) +{ + u_char *start = mtd->priv; + + if (from + len > mtd->size) + return -EINVAL; + + *mtdbuf = start + from; + *retlen = len; + return 0; +} + +static void phram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, + size_t len) +{ +} + +static int phram_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + u_char *start = mtd->priv; + + if (from >= mtd->size) + return -EINVAL; + + if (len > mtd->size - from) + len = mtd->size - from; + + memcpy(buf, start + from, len); + + *retlen = len; + return 0; +} + +static int phram_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + u_char *start = mtd->priv; + + if (to >= mtd->size) + return -EINVAL; + + if (len > mtd->size - to) + len = mtd->size - to; + + memcpy(start + to, buf, len); + + *retlen = len; + return 0; +} + + + +static void unregister_devices(void) +{ + struct phram_mtd_list *this, *safe; + + list_for_each_entry_safe(this, safe, &phram_list, list) { + del_mtd_device(&this->mtd); + iounmap(this->mtd.priv); + kfree(this); + } +} + +static int register_device(char *name, unsigned long start, unsigned long len) +{ + struct phram_mtd_list *new; + int ret = -ENOMEM; + + new = kmalloc(sizeof(*new), GFP_KERNEL); + if (!new) + goto out0; + + memset(new, 0, sizeof(*new)); + + ret = -EIO; + new->mtd.priv = ioremap(start, len); + if (!new->mtd.priv) { + ERROR("ioremap failed\n"); + goto out1; + } + + + new->mtd.name = name; + new->mtd.size = len; + new->mtd.flags = MTD_CAP_RAM | MTD_ERASEABLE | MTD_VOLATILE; + new->mtd.erase = phram_erase; + new->mtd.point = phram_point; + new->mtd.unpoint = phram_unpoint; + new->mtd.read = phram_read; + new->mtd.write = phram_write; + new->mtd.owner = THIS_MODULE; + new->mtd.type = MTD_RAM; + new->mtd.erasesize = PAGE_SIZE; + + ret = -EAGAIN; + if (add_mtd_device(&new->mtd)) { + ERROR("Failed to register new device\n"); + goto out2; + } + + list_add_tail(&new->list, &phram_list); + return 0; + +out2: + iounmap(new->mtd.priv); +out1: + kfree(new); +out0: + return ret; +} + +static int ustrtoul(const char *cp, char **endp, unsigned int base) +{ + unsigned long result = simple_strtoul(cp, endp, base); + + switch (**endp) { + case 'G': + result *= 1024; + case 'M': + result *= 1024; + case 'k': + result *= 1024; + /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */ + if ((*endp)[1] == 'i') + (*endp) += 2; + } + return result; +} + +static int parse_num32(uint32_t *num32, const char *token) +{ + char *endp; + unsigned long n; + + n = ustrtoul(token, &endp, 0); + if (*endp) + return -EINVAL; + + *num32 = n; + return 0; +} + +static int parse_name(char **pname, const char *token) +{ + size_t len; + char *name; + + len = strlen(token) + 1; + if (len > 64) + return -ENOSPC; + + name = kmalloc(len, GFP_KERNEL); + if (!name) + return -ENOMEM; + + strcpy(name, token); + + *pname = name; + return 0; +} + + +static inline void kill_final_newline(char *str) +{ + char *newline = strrchr(str, '\n'); + if (newline && !newline[1]) + *newline = 0; +} + + +#define parse_err(fmt, args...) do { \ + ERROR(fmt , ## args); \ + return 0; \ +} while (0) + +static int phram_setup(const char *val, struct kernel_param *kp) +{ + char buf[64+12+12], *str = buf; + char *token[3]; + char *name; + uint32_t start; + uint32_t len; + int i, ret; + + if (strnlen(val, sizeof(buf)) >= sizeof(buf)) + parse_err("parameter too long\n"); + + strcpy(str, val); + kill_final_newline(str); + + for (i=0; i<3; i++) + token[i] = strsep(&str, ","); + + if (str) + parse_err("too many arguments\n"); + + if (!token[2]) + parse_err("not enough arguments\n"); + + ret = parse_name(&name, token[0]); + if (ret == -ENOMEM) + parse_err("out of memory\n"); + if (ret == -ENOSPC) + parse_err("name too long\n"); + if (ret) + return 0; + + ret = parse_num32(&start, token[1]); + if (ret) + parse_err("illegal start address\n"); + + ret = parse_num32(&len, token[2]); + if (ret) + parse_err("illegal device length\n"); + + register_device(name, start, len); + + return 0; +} + +module_param_call(phram, phram_setup, NULL, NULL, 000); +MODULE_PARM_DESC(phram,"Memory region to map. \"map=<name>,<start>,<length>\""); + + +static int __init init_phram(void) +{ + return 0; +} + +static void __exit cleanup_phram(void) +{ + unregister_devices(); +} + +module_init(init_phram); +module_exit(cleanup_phram); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jörn Engel <joern@wh.fh-wedel.de>"); +MODULE_DESCRIPTION("MTD driver for physical RAM"); diff --git a/linux-2.4.x/drivers/mtd/devices/pmc551.c b/linux-2.4.x/drivers/mtd/devices/pmc551.c index 80afcc6..8d577c8 100644 --- a/linux-2.4.x/drivers/mtd/devices/pmc551.c +++ b/linux-2.4.x/drivers/mtd/devices/pmc551.c @@ -1,5 +1,5 @@ /* - * $Id: pmc551.c,v 1.20 2002/03/05 13:47:08 dwmw2 Exp $ + * $Id: pmc551.c,v 1.33 2005/11/29 20:01:27 gleixner Exp $ * * PMC551 PCI Mezzanine Ram Device * @@ -27,7 +27,7 @@ * it as high speed swap or for a high speed disk device of some * sort. Which becomes very useful on diskless systems in the * embedded market I might add. - * + * * Notes: * Due to what I assume is more buggy SROM, the 64M PMC551 I * have available claims that all 4 of it's DRAM banks have 64M @@ -63,10 +63,10 @@ * Minyard set up the card to utilize a 1M sliding apature. * * Corey Minyard <minyard@nortelnetworks.com> - * * Modified driver to utilize a sliding aperture instead of + * * Modified driver to utilize a sliding aperture instead of * mapping all memory into kernel space which turned out to * be very wasteful. - * * Located a bug in the SROM's initialization sequence that + * * Located a bug in the SROM's initialization sequence that * made the memory unusable, added a fix to code to touch up * the DRAM some. * @@ -98,8 +98,6 @@ #include <linux/ioctl.h> #include <asm/io.h> #include <asm/system.h> -#include <asm/segment.h> -#include <stdarg.h> #include <linux/pci.h> #ifndef CONFIG_PCI @@ -110,17 +108,11 @@ #include <linux/mtd/pmc551.h> #include <linux/mtd/compatmac.h> -#if LINUX_VERSION_CODE > 0x20300 -#define PCI_BASE_ADDRESS(dev) (dev->resource[0].start) -#else -#define PCI_BASE_ADDRESS(dev) (dev->base_address[0]) -#endif - static struct mtd_info *pmc551list; static int pmc551_erase (struct mtd_info *mtd, struct erase_info *instr) { - struct mypriv *priv = (struct mypriv *)mtd->priv; + struct mypriv *priv = mtd->priv; u32 soff_hi, soff_lo; /* start address offset hi/lo */ u32 eoff_hi, eoff_lo; /* end address offset hi/lo */ unsigned long end; @@ -176,16 +168,14 @@ out: printk(KERN_DEBUG "pmc551_erase() done\n"); #endif - if (instr->callback) { - (*(instr->callback))(instr); - } + mtd_erase_callback(instr); return 0; } static int pmc551_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf) { - struct mypriv *priv = (struct mypriv *)mtd->priv; + struct mypriv *priv = mtd->priv; u32 soff_hi; u32 soff_lo; @@ -216,7 +206,7 @@ static int pmc551_point (struct mtd_info *mtd, loff_t from, size_t len, size_t * } -static void pmc551_unpoint (struct mtd_info *mtd, u_char *addr) +static void pmc551_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len) { #ifdef CONFIG_MTD_PMC551_DEBUG printk(KERN_DEBUG "pmc551_unpoint()\n"); @@ -226,7 +216,7 @@ static void pmc551_unpoint (struct mtd_info *mtd, u_char *addr) static int pmc551_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct mypriv *priv = (struct mypriv *)mtd->priv; + struct mypriv *priv = mtd->priv; u32 soff_hi, soff_lo; /* start address offset hi/lo */ u32 eoff_hi, eoff_lo; /* end address offset hi/lo */ unsigned long end; @@ -288,7 +278,7 @@ out: static int pmc551_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - struct mypriv *priv = (struct mypriv *)mtd->priv; + struct mypriv *priv = mtd->priv; u32 soff_hi, soff_lo; /* start address offset hi/lo */ u32 eoff_hi, eoff_lo; /* end address offset hi/lo */ unsigned long end; @@ -399,7 +389,7 @@ static u32 fixup_pmc551 (struct pci_dev *dev) bcmd |= (0x40|0x20); pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd); - /* + /* * Take care and turn off the memory on the device while we * tweak the configurations */ @@ -417,7 +407,7 @@ static u32 fixup_pmc551 (struct pci_dev *dev) * Grab old BAR0 config so that we can figure out memory size * This is another bit of kludge going on. The reason for the * redundancy is I am hoping to retain the original configuration - * previously assigned to the card by the BIOS or some previous + * previously assigned to the card by the BIOS or some previous * fixup routine in the kernel. So we read the old config into cfg, * then write all 1's to the memory space, read back the result into * "size", and then write back all the old config. @@ -489,7 +479,7 @@ static u32 fixup_pmc551 (struct pci_dev *dev) } while ( (PCI_COMMAND_IO) & cmd ); /* - * Turn on auto refresh + * Turn on auto refresh * The loop is taken directly from Ramix's example code. I assume that * this must be held high for some duration of time, but I can find no * documentation refrencing the reasons why. @@ -565,7 +555,7 @@ static u32 fixup_pmc551 (struct pci_dev *dev) (size<1024)?size:(size<1048576)?size>>10:size>>20, (size<1024)?'B':(size<1048576)?'K':'M', size, ((dcmd&(0x1<<3)) == 0)?"non-":"", - PCI_BASE_ADDRESS(dev)&PCI_BASE_ADDRESS_MEM_MASK ); + (dev->resource[0].start)&PCI_BASE_ADDRESS_MEM_MASK ); /* * Check to see the state of the memory @@ -624,7 +614,7 @@ static u32 fixup_pmc551 (struct pci_dev *dev) pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd ); printk( KERN_DEBUG "pmc551: EEPROM is under %s control\n" "pmc551: System Control Register is %slocked to PCI access\n" - "pmc551: System Control Register is %slocked to EEPROM access\n", + "pmc551: System Control Register is %slocked to EEPROM access\n", (bcmd&0x1)?"software":"hardware", (bcmd&0x20)?"":"un", (bcmd&0x40)?"":"un"); #endif @@ -639,10 +629,6 @@ static u32 fixup_pmc551 (struct pci_dev *dev) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>"); MODULE_DESCRIPTION(PMC551_VERSION); -MODULE_PARM(msize, "i"); -MODULE_PARM_DESC(msize, "memory size in Megabytes [1 - 1024]"); -MODULE_PARM(asize, "i"); -MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]"); /* * Stuff these outside the ifdef so as to not bust compiled in driver support @@ -654,10 +640,15 @@ static int asize=CONFIG_MTD_PMC551_APERTURE_SIZE static int asize=0; #endif +module_param(msize, int, 0); +MODULE_PARM_DESC(msize, "memory size in Megabytes [1 - 1024]"); +module_param(asize, int, 0); +MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]"); + /* * PMC551 Card Initialization */ -int __init init_pmc551(void) +static int __init init_pmc551(void) { struct pci_dev *PCI_Device = NULL; struct mypriv *priv; @@ -683,11 +674,6 @@ int __init init_pmc551(void) printk(KERN_INFO PMC551_VERSION); - if(!pci_present()) { - printk(KERN_NOTICE "pmc551: PCI not enabled.\n"); - return -ENODEV; - } - /* * PCU-bus chipset probe. */ @@ -700,7 +686,7 @@ int __init init_pmc551(void) } printk(KERN_NOTICE "pmc551: Found PCI V370PDC at 0x%lX\n", - PCI_BASE_ADDRESS(PCI_Device)); + PCI_Device->resource[0].start); /* * The PMC551 device acts VERY weird if you don't init it @@ -754,10 +740,10 @@ int __init init_pmc551(void) printk(KERN_NOTICE "pmc551: Using specified aperture size %dM\n", asize>>20); priv->asize = asize; } - priv->start = ioremap((PCI_BASE_ADDRESS(PCI_Device) + priv->start = ioremap(((PCI_Device->resource[0].start) & PCI_BASE_ADDRESS_MEM_MASK), priv->asize); - + if (!priv->start) { printk(KERN_NOTICE "pmc551: Unable to map IO space\n"); kfree(mtd->priv); @@ -778,7 +764,7 @@ int __init init_pmc551(void) priv->curr_map0 ); #ifdef CONFIG_MTD_PMC551_DEBUG - printk( KERN_DEBUG "pmc551: aperture set to %d\n", + printk( KERN_DEBUG "pmc551: aperture set to %d\n", (priv->base_map0 & 0xF0)>>4 ); #endif @@ -789,10 +775,10 @@ int __init init_pmc551(void) mtd->write = pmc551_write; mtd->point = pmc551_point; mtd->unpoint = pmc551_unpoint; - mtd->module = THIS_MODULE; mtd->type = MTD_RAM; mtd->name = "PMC551 RAM board"; mtd->erasesize = 0x10000; + mtd->owner = THIS_MODULE; if (add_mtd_device(mtd)) { printk(KERN_NOTICE "pmc551: Failed to register new device\n"); @@ -834,15 +820,15 @@ static void __exit cleanup_pmc551(void) struct mypriv *priv; while((mtd=pmc551list)) { - priv = (struct mypriv *)mtd->priv; + priv = mtd->priv; pmc551list = priv->nextpmc551; - + if(priv->start) { printk (KERN_DEBUG "pmc551: unmapping %dM starting at 0x%p\n", priv->asize>>20, priv->start); iounmap (priv->start); } - + kfree (mtd->priv); del_mtd_device (mtd); kfree (mtd); diff --git a/linux-2.4.x/drivers/mtd/devices/ramtd.c b/linux-2.4.x/drivers/mtd/devices/ramtd.c new file mode 100644 index 0000000..882ff33 --- /dev/null +++ b/linux-2.4.x/drivers/mtd/devices/ramtd.c @@ -0,0 +1,249 @@ +/** + * Simple mtd driver that dynamically allocates/frees memory + * + * $Id: ramtd.c,v 1.7 2006/03/08 17:48:28 joern Exp $ + * + * Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de> + * Copyright (c) 2006 iSteve <isteve@bofh.cz> + * - Added module parameters, immediate allocation and fixed few leaks. + */ +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/vmalloc.h> + +struct ramtd { + struct mtd_info mtd; + struct list_head list; + u32 size; + void *page[]; +}; + +static LIST_HEAD(ramtd_list); +static DECLARE_MUTEX(ramtd_mutex); + +static unsigned long ramtd_size = 4*1024*1024; +static int ramtd_now = 0; + +module_param(ramtd_size, ulong, 0); +MODULE_PARM_DESC(ramtd_size, "Total device size in bytes"); + +module_param(ramtd_now, bool, 0); +MODULE_PARM_DESC(ramtd_now, "Allocate all memory when loaded"); + + +static void *get_pool_page(void) +{ + void *ret = (void*)__get_free_page(GFP_KERNEL); + return ret; +} + + +static void free_pool_page(void *page) +{ + free_page((unsigned long)page); +} + +static void free_all_pages(struct ramtd *this) +{ + u32 page; + + for (page = 0; page < this->mtd.size / PAGE_SIZE; page++) { + if (this->page[page]) + free_pool_page(this->page[page]); + } +} + +static int alloc_all_pages(struct ramtd *this) +{ + u32 page; + + if (!ramtd_now) + return 0; + + for (page = 0; page < this->mtd.size / PAGE_SIZE; page++) { + if (this->page[page]) + continue; + + this->page[page] = get_pool_page(); + if (!this->page[page]) + return -ENOMEM; + + memset(this->page[page], 0xff, PAGE_SIZE); + } + + return 0; +} + + +static int ramtd_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct ramtd *ramtd = container_of(mtd, typeof(*ramtd), mtd); + u32 addr = instr->addr; + u32 len = instr->len; + + if (addr + len > mtd->size) + return -EINVAL; + if (addr % PAGE_SIZE) + return -EINVAL; + if (len % PAGE_SIZE) + return -EINVAL; + + while (len) { + u32 page = addr / PAGE_SIZE; + + down_interruptible(&ramtd_mutex); + free_pool_page(ramtd->page[page]); + ramtd->page[page] = NULL; + up(&ramtd_mutex); + + addr += PAGE_SIZE; + len -= PAGE_SIZE; + } + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; +} + + +static int ramtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct ramtd *ramtd = container_of(mtd, typeof(*ramtd), mtd); + + if (from >= mtd->size) + return -EINVAL; + + if (len > mtd->size - from) + len = mtd->size - from; + + *retlen = 0; + while (len) { + u32 page = from / PAGE_SIZE; + u32 ofs = from % PAGE_SIZE; + u32 rlen = min_t(u32, len, PAGE_SIZE - ofs); + + down_interruptible(&ramtd_mutex); + if (!ramtd->page[page]) + memset(buf, 0xff, rlen); + else + memcpy(buf, ramtd->page[page] + ofs, rlen); + up(&ramtd_mutex); + + buf += rlen; + from += rlen; + *retlen += rlen; + len -= rlen; + } + return 0; +} + + +static int ramtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct ramtd *ramtd = container_of(mtd, typeof(*ramtd), mtd); + + if (to >= mtd->size) + return -EINVAL; + + if (len > mtd->size - to) + len = mtd->size - to; + + *retlen = 0; + while (len) { + u32 page = to / PAGE_SIZE; + u32 ofs = to % PAGE_SIZE; + u32 wlen = min_t(u32, len, PAGE_SIZE - ofs); + + down_interruptible(&ramtd_mutex); + if (!ramtd->page[page]) { + ramtd->page[page] = get_pool_page(); + memset(ramtd->page[page], 0xff, PAGE_SIZE); + } + if (!ramtd->page[page]) + return -EIO; + + memcpy(ramtd->page[page] + ofs, buf, wlen); + up(&ramtd_mutex); + + buf += wlen; + to += wlen; + *retlen += wlen; + len -= wlen; + } + return 0; +} + + +static int register_device(const char *name, u32 size) +{ + struct ramtd *new; + u32 pages_size; + int err; + + size = PAGE_ALIGN(size); + pages_size = size / PAGE_SIZE * sizeof(void*); + new = vmalloc(sizeof(*new) + pages_size); + if (!new) + return -ENOMEM; + memset(new, 0, sizeof(*new) + pages_size); + + new->mtd.name = (char*)name; + new->mtd.size = size; + new->mtd.flags = MTD_CAP_RAM | MTD_ERASEABLE | MTD_VOLATILE; + new->mtd.owner = THIS_MODULE; + new->mtd.type = MTD_RAM; + new->mtd.erasesize = PAGE_SIZE; + new->mtd.write = ramtd_write; + new->mtd.read = ramtd_read; + new->mtd.erase = ramtd_erase; + + if (add_mtd_device(&new->mtd)) { + free_pool_page(new); + return -EAGAIN; + } + + err = alloc_all_pages(new); + if (err) { + free_all_pages(new); + vfree(new); + return err; + } + + down_interruptible(&ramtd_mutex); + list_add(&new->list, &ramtd_list); + up(&ramtd_mutex); + return 0; +} + + +static int __init ramtd_init(void) +{ + return register_device("ramtd", ramtd_size); /* FIXME */ +} + +static void __exit ramtd_exit(void) +{ + struct ramtd *this, *next; + + down_interruptible(&ramtd_mutex); + list_for_each_entry_safe(this, next, &ramtd_list, list) { + free_all_pages(this); + del_mtd_device(&this->mtd); + vfree(this); + } + up(&ramtd_mutex); +} + + +module_init(ramtd_init); +module_exit(ramtd_exit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joern Engel <joern@wh.fh-wedel.de>"); +MODULE_AUTHOR("iSteve <isteve@bofh.cz>"); +MODULE_DESCRIPTION("MTD using dynamic memory allocation"); diff --git a/linux-2.4.x/drivers/mtd/devices/slram.c b/linux-2.4.x/drivers/mtd/devices/slram.c index 6220e73..6faee6c 100644 --- a/linux-2.4.x/drivers/mtd/devices/slram.c +++ b/linux-2.4.x/drivers/mtd/devices/slram.c @@ -1,6 +1,32 @@ /*====================================================================== - $Id: slram.c,v 1.25 2001/10/02 15:05:13 dwmw2 Exp $ + $Id: slram.c,v 1.36 2005/11/07 11:14:25 gleixner Exp $ + + This driver provides a method to access memory not used by the kernel + itself (i.e. if the kernel commandline mem=xxx is used). To actually + use slram at least mtdblock or mtdchar is required (for block or + character device access). + + Usage: + + if compiled as loadable module: + modprobe slram map=<name>,<start>,<end/offset> + if statically linked into the kernel use the following kernel cmd.line + slram=<name>,<start>,<end/offset> + + <name>: name of the device that will be listed in /proc/mtd + <start>: start of the memory region, decimal or hex (0xabcdef) + <end/offset>: end of the memory region. It's possible to use +0x1234 + to specify the offset instead of the absolute address + + NOTE: + With slram it's only possible to map a contigous memory region. Therfore + if there's a device mapped somewhere in the region specified slram will + fail to load (see kernel log if modprobe fails). + + - + + Jochen Schaeuble <psionic@psionic.de> ======================================================================*/ @@ -20,12 +46,11 @@ #include <linux/init.h> #include <asm/io.h> #include <asm/system.h> -#include <asm/segment.h> -#include <stdarg.h> #include <linux/mtd/mtd.h> #define SLRAM_MAX_DEVICES_PARAMS 6 /* 3 parameters / device */ +#define SLRAM_BLK_SZ 0x4000 #define T(fmt, args...) printk(KERN_DEBUG fmt, ## args) #define E(fmt, args...) printk(KERN_NOTICE fmt, ## args) @@ -42,77 +67,84 @@ typedef struct slram_mtd_list { #ifdef MODULE static char *map[SLRAM_MAX_DEVICES_PARAMS]; + +module_param_array(map, charp, NULL, 0); +MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\""); #else static char *map; #endif -MODULE_PARM(map, "3-" __MODULE_STRING(SLRAM_MAX_DEVICES_PARAMS) "s"); -MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\""); - static slram_mtd_list_t *slram_mtdlist = NULL; -int slram_erase(struct mtd_info *, struct erase_info *); -int slram_point(struct mtd_info *, loff_t, size_t, size_t *, u_char **); -void slram_unpoint(struct mtd_info *, u_char *); -int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *); -int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static int slram_erase(struct mtd_info *, struct erase_info *); +static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, u_char **); +static void slram_unpoint(struct mtd_info *, u_char *, loff_t, size_t); +static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); -int slram_erase(struct mtd_info *mtd, struct erase_info *instr) +static int slram_erase(struct mtd_info *mtd, struct erase_info *instr) { slram_priv_t *priv = mtd->priv; if (instr->addr + instr->len > mtd->size) { return(-EINVAL); } - + memset(priv->start + instr->addr, 0xff, instr->len); - /* This'll catch a few races. Free the thing before returning :) + /* This'll catch a few races. Free the thing before returning :) * I don't feel at all ashamed. This kind of thing is possible anyway * with flash, but unlikely. */ instr->state = MTD_ERASE_DONE; - if (instr->callback) { - (*(instr->callback))(instr); - } - else { - kfree(instr); - } + mtd_erase_callback(instr); return(0); } -int slram_point(struct mtd_info *mtd, loff_t from, size_t len, +static int slram_point(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf) { - slram_priv_t *priv = (slram_priv_t *)mtd->priv; + slram_priv_t *priv = mtd->priv; + + if (from + len > mtd->size) + return -EINVAL; *mtdbuf = priv->start + from; *retlen = len; return(0); } -void slram_unpoint(struct mtd_info *mtd, u_char *addr) +static void slram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len) { } -int slram_read(struct mtd_info *mtd, loff_t from, size_t len, +static int slram_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - slram_priv_t *priv = (slram_priv_t *)mtd->priv; - + slram_priv_t *priv = mtd->priv; + + if (from > mtd->size) + return -EINVAL; + + if (from + len > mtd->size) + len = mtd->size - from; + memcpy(buf, priv->start + from, len); *retlen = len; return(0); } -int slram_write(struct mtd_info *mtd, loff_t to, size_t len, +static int slram_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - slram_priv_t *priv = (slram_priv_t *)mtd->priv; + slram_priv_t *priv = mtd->priv; + + if (to + len > mtd->size) + return -EINVAL; memcpy(priv->start + to, buf, len); @@ -122,7 +154,7 @@ int slram_write(struct mtd_info *mtd, loff_t to, size_t len, /*====================================================================*/ -int register_device(char *name, unsigned long start, unsigned long length) +static int register_device(char *name, unsigned long start, unsigned long length) { slram_mtd_list_t **curmtd; @@ -138,12 +170,12 @@ int register_device(char *name, unsigned long start, unsigned long length) } (*curmtd)->mtdinfo = kmalloc(sizeof(struct mtd_info), GFP_KERNEL); (*curmtd)->next = NULL; - + if ((*curmtd)->mtdinfo) { memset((char *)(*curmtd)->mtdinfo, 0, sizeof(struct mtd_info)); (*curmtd)->mtdinfo->priv = - (void *)kmalloc(sizeof(slram_priv_t), GFP_KERNEL); - + kmalloc(sizeof(slram_priv_t), GFP_KERNEL); + if (!(*curmtd)->mtdinfo->priv) { kfree((*curmtd)->mtdinfo); (*curmtd)->mtdinfo = NULL; @@ -156,7 +188,7 @@ int register_device(char *name, unsigned long start, unsigned long length) E("slram: Cannot allocate new MTD device.\n"); return(-ENOMEM); } - + if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start = ioremap(start, length))) { E("slram: ioremap failed\n"); @@ -169,15 +201,15 @@ int register_device(char *name, unsigned long start, unsigned long length) (*curmtd)->mtdinfo->name = name; (*curmtd)->mtdinfo->size = length; (*curmtd)->mtdinfo->flags = MTD_CLEAR_BITS | MTD_SET_BITS | - MTD_WRITEB_WRITEABLE | MTD_VOLATILE; + MTD_WRITEB_WRITEABLE | MTD_VOLATILE | MTD_CAP_RAM; (*curmtd)->mtdinfo->erase = slram_erase; (*curmtd)->mtdinfo->point = slram_point; (*curmtd)->mtdinfo->unpoint = slram_unpoint; (*curmtd)->mtdinfo->read = slram_read; (*curmtd)->mtdinfo->write = slram_write; - (*curmtd)->mtdinfo->module = THIS_MODULE; + (*curmtd)->mtdinfo->owner = THIS_MODULE; (*curmtd)->mtdinfo->type = MTD_RAM; - (*curmtd)->mtdinfo->erasesize = 0x10000; + (*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ; if (add_mtd_device((*curmtd)->mtdinfo)) { E("slram: Failed to register new device\n"); @@ -191,10 +223,10 @@ int register_device(char *name, unsigned long start, unsigned long length) T("slram: Mapped from 0x%p to 0x%p\n", ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start, ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end); - return(0); + return(0); } -void unregister_devices(void) +static void unregister_devices(void) { slram_mtd_list_t *nextitem; @@ -209,7 +241,7 @@ void unregister_devices(void) } } -unsigned long handle_unit(unsigned long value, char *unit) +static unsigned long handle_unit(unsigned long value, char *unit) { if ((*unit == 'M') || (*unit == 'm')) { return(value * 1024 * 1024); @@ -219,12 +251,12 @@ unsigned long handle_unit(unsigned long value, char *unit) return(value); } -int parse_cmdline(char *devname, char *szstart, char *szlength) +static int parse_cmdline(char *devname, char *szstart, char *szlength) { char *buffer; unsigned long devstart; unsigned long devlength; - + if ((!devname) || (!szstart) || (!szlength)) { unregister_devices(); return(-EINVAL); @@ -232,7 +264,7 @@ int parse_cmdline(char *devname, char *szstart, char *szlength) devstart = simple_strtoul(szstart, &buffer, 0); devstart = handle_unit(devstart, buffer); - + if (*(szlength) != '+') { devlength = simple_strtoul(szlength, &buffer, 0); devlength = handle_unit(devlength, buffer) - devstart; @@ -242,11 +274,11 @@ int parse_cmdline(char *devname, char *szstart, char *szlength) } T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n", devname, devstart, devlength); - if ((devstart < 0) || (devlength < 0)) { + if ((devstart < 0) || (devlength < 0) || (devlength % SLRAM_BLK_SZ != 0)) { E("slram: Illegal start / length parameter.\n"); return(-EINVAL); } - + if ((devstart = register_device(devname, devstart, devlength))){ unregister_devices(); return((int)devstart); @@ -266,7 +298,7 @@ __setup("slram=", mtd_slram_setup); #endif -int init_slram(void) +static int init_slram(void) { char *devname; int i; @@ -303,7 +335,7 @@ int init_slram(void) } #else int count; - + for (count = 0; (map[count]) && (count < SLRAM_MAX_DEVICES_PARAMS); count++) { } @@ -318,10 +350,10 @@ int init_slram(void) if (parse_cmdline(devname, map[i * 3 + 1], map[i * 3 + 2])!=0) { return(-EINVAL); } - + } #endif /* !MODULE */ - + return(0); } |