I've noticed a lot of people on this list seem to use the ppc 8xx port in embedded systems, running from flash memory. There have been some questions about it. Therefore i give you: ftp://ftp.signum.se/pub/alex/flash.diff Patches against Linux 2.2.5 The Flash drivers include: General detection of CFI compliant flash memories. A general interface for flash-drivers Specific support for AMD Flash-chips (only tested on Am29LV017B 4x8bib configuration) Implemented char device over the flash interface which can read & write to flash, writing on erase-sector boundaries implies automatic erase, ioctls exists to force sector erase and to get information about the chips. FTL, for flash filesystems: FTL layer which implements a 512 byte blockdevice with wear-leveling from the flash-device (or parts of it). This is a modified version of the ftl code from the pcmcia code (taken with permission). This can be used to place a normal ext2fs fs in flash. For optimum performance some modifications to ext2fs whould be done so that it frees blocks in the ftl layer that are not used by the fs. Note that there *might* be patent problems using this code in the US. / Alex (alex@signum.se) Index: drivers/block/Config.in =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/drivers/block/Config.in,v retrieving revision 1.1.1.2 retrieving revision 1.3 diff -u -r1.1.1.2 -r1.3 --- Config.in 1999/04/21 11:50:24 1.1.1.2 +++ Config.in 1999/04/21 14:26:18 1.3 @@ -113,6 +113,11 @@ if [ "$CONFIG_BLK_DEV_RAM" = "y" ]; then bool ' Initial RAM disk (initrd) support' CONFIG_BLK_DEV_INITRD fi + +if [ "$CONFIG_FLASH" = "y" ]; then + bool 'Flash translation layer support' CONFIG_BLK_DEV_FTL +fi + tristate 'XT hard disk support' CONFIG_BLK_DEV_XD # PARIDE doesn't need PARPORT, but if PARPORT is configured as a module, Index: drivers/block/Makefile =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/drivers/block/Makefile,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- Makefile 1999/02/09 11:04:51 1.1.1.1 +++ Makefile 1999/04/16 09:00:35 1.2 @@ -86,6 +86,11 @@ endif endif +ifeq ($(CONFIG_BLK_DEV_FTL),y) +L_OBJS += ftl.o +endif + + ifeq ($(CONFIG_BLK_DEV_LOOP),y) LX_OBJS += loop.o else Index: drivers/block/ftl.c =================================================================== RCS file: ftl.c diff -N ftl.c --- /dev/null Tue May 5 22:32:27 1998 +++ /tmp/cvs00400Eaa Mon Apr 26 16:45:42 1999 @@ -0,0 +1,1424 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ---------------------------- + * + * This was originally part of the pcmcia 3.0.9 distribution. + * It has been modified to work with the flash device /dev/flash0 + * and redistrubited as GPL according to the copyright notice. + * + * Changes are (c) Alexander Larsson (alla@lysator.liu.se or alex@signum.se) + * + * Original notice follows: + *====================================================================== + * + * A Flash Translation Layer memory card driver + * + * This driver implements a disk-like block device driver with an + * apparent block size of 512 bytes for flash memory cards. + * + * Written by David Hinds, dhinds@allegro.stanford.edu + * + *====================================================================== + */ + +/** + TODO: + * Sometimes an erase is done and the newly erased sector is not + immediately needed. Then the code currently unnessecary blocks. + To fix this i would need to add a schedule_erase_sector to the + flash interface. + */ + +//#define PSYCHO_DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/*#include */ + +//#define FLASH_DEBUG + +#ifdef FLASH_DEBUG +#define DEBUG(args...) printk(args) +#else +#define DEBUG(args...) +#endif + +/*====================================================================*/ + +/* Boot parameters: */ + +struct boot_ftl { + int minor; + off_t start; + size_t size; +}; + +static struct boot_ftl boot_table[] __initdata = { + { 0, 0, 7*1024*1024 } +}; + + + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int shuffle_freq = 50; + +/* +MODULE_PARM(shuffle_freq, "i"); +*/ + +/*====================================================================*/ + +/* Funky stuff for setting up a block device */ +#define MAJOR_NR FTL_MAJOR +#define DEVICE_NAME "ftl" +#define DEVICE_REQUEST do_ftl_request +#define DEVICE_ON(device) +#define DEVICE_OFF(device) +#define DEVICE_NR(minor) (MINOR(minor)>>3) +#define DEVICE_NO_RANDOM + +#define PART_NR(minor) ((minor)&15) +#define MINOR_NR(dev,part) (((dev)<<4)+(part)) + +#include + +/*====================================================================*/ + +/* Maximum number of separate ftl devices we'll allow */ +#define MAX_DEV 4 /* /devftl[a-d] */ + +/* Maximum number of partitions in an flash memory */ +#define PART_BITS 4 +#define MAX_PART 16 /* /dev/ftla + /dev/ftla[1-15] */ + +/* Maximum number of outstanding erase requests per socket */ +#define MAX_ERASE 8 + +/* Sector size -- shouldn't need to change */ +#define SECTOR_SIZE 512 + +/* Block IO request handler stuff */ +static void do_ftl_request(void); + +typedef struct mem_region_info_t { + u_int RegionSize; + u_int BlockSize; +} mem_region_info_t; + + +/* Partition state flags */ +#define FTL_FORMATTED 0x01 + +/* Transfer unit states */ +#define XFER_UNKNOWN 0x00 +#define XFER_ERASING 0x01 +#define XFER_ERASED 0x02 +#define XFER_PREPARED 0x03 +#define XFER_FAILED 0x04 + +/* Each memory region corresponds to a minor device */ +typedef struct ftl_dev_t { + flash_device_info_t *flash; + off_t base_offset; + kdev_t dev; /* The whole device (eg. /dev/ftla) */ + u_int state; + u_int *VirtualBlockMap; + u_int *VirtualPageMap; + u_int FreeTotal; + struct eun_info_t { + u_int Offset; + u_int EraseCount; + u_int Free; + u_int Deleted; + } *EUNInfo; + struct xfer_info_t { + u_int Offset; + u_int EraseCount; + u_short state; + } *XferInfo; + u_short bam_index; + u_int *bam_cache; + u_short DataUnits; + u_int BlocksPerUnit; + erase_unit_header_t header; + mem_region_info_t region; + int open; + int locked; + + /* + eraseq_handle_t eraseq_handle; + eraseq_entry_t eraseq[MAX_ERASE]; + struct wait_queue *erase_pending; + */ +} ftl_dev_t; + +static ftl_dev_t *dev_table[MAX_DEV] = { NULL, /* ... */ }; + +static struct hd_struct ftl_hd[MINOR_NR(MAX_DEV, 0)]; +static int ftl_sizes[MINOR_NR(MAX_DEV, 0)]; +static int ftl_blocksizes[MINOR_NR(MAX_DEV, 0)]; + +static struct wait_queue *ftl_wait_open = NULL; + +static void ftl_geninit(struct gendisk *gendisk); + +static struct gendisk ftl_gendisk = { + 0, /* Major device number */ + "ftl", + PART_BITS, /* Bits in partition # */ + MAX_PART, /* Partitions per device */ + MAX_DEV, /* maximum number of devices */ + ftl_geninit, /* init function */ + ftl_hd, /* hd_struct */ + ftl_sizes, /* block sizes */ + MAX_DEV, /* number of "real" devices */ + NULL, + NULL +}; + +/*====================================================================*/ + +static int ftl_ioctl(struct inode *inode, struct file *file, + u_int cmd, u_long arg); +static int ftl_open(struct inode *inode, struct file *file); +static int ftl_close(struct inode *inode, struct file *file); +static int ftl_reread_partitions(int minor); + +static struct file_operations ftl_blk_fops = { + NULL, /* lseek */ + block_read, /* read */ + block_write, /* write */ + NULL, /* readdir */ + NULL, /* poll */ + ftl_ioctl, /* ioctl */ + NULL, /* mmap */ + ftl_open, /* open */ + NULL, /* flush */ + ftl_close, /* release */ + block_fsync /* fsync */ +}; + +/*====================================================================== + + Scan_header() checks to see if a memory region contains an FTL + partition. build_maps() reads all the erase unit headers, builds + the erase unit map, and then builds the virtual page map. + +======================================================================*/ + +static int scan_header(ftl_dev_t *dev) +{ + erase_unit_header_t header; + off_t offset; + size_t count; + int ret; + + DEBUG("scan_header(%p)\n", dev); + + /* Search first megabyte for a valid FTL header */ + count = sizeof(header); + for (offset = 0; + offset < 0x100000; + offset += dev->region.BlockSize) { + ret = (*dev->flash->ops->read)(dev->flash, (char *)&header, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error reading flash mem.\n"); + return -1; + } + if (strcmp(header.DataOrgTuple+3, "FTL100") == 0) break; + } + if (offset == 0x100000) { + printk(KERN_NOTICE "ftl: FTL header not found.\n"); + return -1; + } + if ((header.NumEraseUnits > 65536) || (header.BlockSize != 9) || + (header.EraseUnitSize < 10) || (header.EraseUnitSize > 31) || + (header.NumTransferUnits >= header.NumEraseUnits)) { + printk(KERN_NOTICE "ftl: FTL header corrupt!\n"); + return -1; + } + dev->header = header; + return 0; +} + +static int build_maps(ftl_dev_t *dev) +{ + erase_unit_header_t header; + u_short xvalid, xtrans, i; + u_int blocks, j; + int hdr_ok, ret; + size_t count; + off_t offset; + + DEBUG("build_maps(%p)\n", dev); + + /* Set up erase unit maps */ + dev->DataUnits = dev->header.NumEraseUnits - dev->header.NumTransferUnits; + dev->EUNInfo = kmalloc(dev->DataUnits * sizeof(struct eun_info_t), GFP_KERNEL); + for (i = 0; i < dev->DataUnits; i++) + dev->EUNInfo[i].Offset = 0xffffffff; + dev->XferInfo = + kmalloc(dev->header.NumTransferUnits * sizeof(struct xfer_info_t), GFP_KERNEL); + + count = sizeof(header); + xvalid = xtrans = 0; + for (i = 0; i < dev->header.NumEraseUnits; i++) { + offset = ((i + dev->header.FirstPhysicalEUN) + << dev->header.EraseUnitSize); + ret = (*dev->flash->ops->read)(dev->flash, (char *)&header, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error reading flash mem.\n"); + return -1; + } + /* Is this a transfer partition? */ + hdr_ok = (strcmp(header.DataOrgTuple+3, "FTL100") == 0); + if (hdr_ok && (header.LogicalEUN < dev->DataUnits) && + (dev->EUNInfo[header.LogicalEUN].Offset == 0xffffffff)) { + dev->EUNInfo[header.LogicalEUN].Offset = offset; + dev->EUNInfo[header.LogicalEUN].EraseCount = header.EraseCount; + xvalid++; + } else { + if (xtrans == dev->header.NumTransferUnits) { + printk(KERN_NOTICE + "ftl: format error: too many transfer units!\n"); + return -1; + } + if (hdr_ok && (header.LogicalEUN == 0xffff)) { + dev->XferInfo[xtrans].state = XFER_PREPARED; + dev->XferInfo[xtrans].EraseCount = header.EraseCount; + } else { + dev->XferInfo[xtrans].state = XFER_UNKNOWN; + /* Pick anything reasonable for the erase count */ + dev->XferInfo[xtrans].EraseCount = + dev->header.EraseCount; + } + dev->XferInfo[xtrans].Offset = offset; + xtrans++; + } + } + /* Check for format trouble */ + header = dev->header; + if ((xtrans != header.NumTransferUnits) || + (xvalid+xtrans != header.NumEraseUnits)) { + printk(KERN_NOTICE "cs: format error: erase units don't add up!\n"); + return -1; + } + + /* Set up virtual page map */ + blocks = header.FormattedSize >> header.BlockSize; + dev->VirtualBlockMap = vmalloc(blocks * sizeof(u_int)); + memset(dev->VirtualBlockMap, 0xff, blocks * sizeof(u_int)); + dev->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize; + count = dev->BlocksPerUnit * sizeof(u_int); + + dev->bam_cache = kmalloc(dev->BlocksPerUnit * sizeof(u_int), GFP_KERNEL); + dev->bam_index = 0xffff; + dev->FreeTotal = 0; + for (i = 0; i < dev->DataUnits; i++) { + dev->EUNInfo[i].Free = 0; + dev->EUNInfo[i].Deleted = 0; + offset = dev->EUNInfo[i].Offset + header.BAMOffset; + ret = (*dev->flash->ops->read)(dev->flash, (char *)dev->bam_cache, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error reading flash mem.\n"); + return -1; + } + for (j = 0; j < dev->BlocksPerUnit; j++) { + if (BLOCK_FREE(dev->bam_cache[j])) { + dev->EUNInfo[i].Free++; + dev->FreeTotal++; + } else if ((BLOCK_TYPE(dev->bam_cache[j]) == BLOCK_DATA) && + (BLOCK_NUMBER(dev->bam_cache[j]) < blocks)) { + dev->VirtualBlockMap[BLOCK_NUMBER(dev->bam_cache[j])] = + (i << header.EraseUnitSize) + (j << header.BlockSize); + } else if (BLOCK_DELETED(dev->bam_cache[j])) { + dev->EUNInfo[i].Deleted++; + } + } + } + + return 0; + +} /* build_maps */ + +/*====================================================================== + + Erase_xfer() schedules an asynchronous erase operation for a + transfer unit. + +======================================================================*/ + +static int erase_xfer(ftl_dev_t *dev, u_short xfernum) +{ + int ret; + struct xfer_info_t *xfer; + off_t offset; + size_t count; + + xfer = &dev->XferInfo[xfernum]; + DEBUG("ftl: erasing xfer unit at 0x%x\n", xfer->Offset); + xfer->state = XFER_ERASING; + +#if 0 + /* Is there a free erase slot? */ + for (;;) { + for (i = 0; i < MAX_ERASE; i++) + if (!ERASE_IN_PROGRESS(dev->eraseq[i].State)) break; + if (i < MAX_ERASE) break; + DEBUG("ftl_cs: erase queue is full\n"); + sleep_on(&dev->erase_pending); + } + + /* Queue the request */ + dev->eraseq[i].State = ERASE_QUEUED; + dev->eraseq[i].Handle = part->handle; + dev->eraseq[i].Offset = xfer->Offset; + dev->eraseq[i].Size = part->region.BlockSize; + dev->eraseq[i].Optional = part; + ret = CardServices(CheckEraseQueue, dev->eraseq_handle); + if (ret != CS_SUCCESS) { + cs_error(CheckEraseQueue, ret); + return -EIO; + } +#endif /* 0 */ + + offset = xfer->Offset; + count = dev->region.BlockSize; + + ret = (*dev->flash->ops->erase_sector)(dev->flash, + offset + dev->base_offset, count); + if (ret) { + xfer->state = XFER_FAILED; + printk("Error erasing flash mem.\n"); + return -EIO; + } else { + xfer->state = XFER_ERASED; + } + + xfer->EraseCount++; + return ret; +} /* erase_xfer */ + + +#if 0 +static void save_status(eraseq_entry_t *erase) +{ + partition_t *part; + struct xfer_info_t *xfer; + int i; + + /* Look up the transfer unit */ + part = (partition_t *)(erase->Optional); + for (i = 0; i < part->header.NumTransferUnits; i++) + if (part->XferInfo[i].Offset == erase->Offset) break; + if (i == part->header.NumTransferUnits) { + printk(KERN_NOTICE "ftl_cs: internal error: " + "erase lookup failed!\n"); + return; + } + xfer = &part->XferInfo[i]; + if (erase->State == ERASE_PASSED) + xfer->state = XFER_ERASED; + else { + xfer->state = XFER_FAILED; + printk(KERN_NOTICE "ftl_cs: erase failed: state = %d\n", + erase->State); + } +} +#endif /* 0 */ + +/*====================================================================== + + Prepare_xfer() takes a freshly erased transfer unit and gives + it an appropriate header. + +======================================================================*/ + +static void prepare_xfer(ftl_dev_t *dev, int i) +{ + erase_unit_header_t header; + struct xfer_info_t *xfer; + int nbam, ret; + u_int ctl; + off_t offset; + size_t count; + + xfer = &dev->XferInfo[i]; + xfer->state = XFER_FAILED; + + DEBUG("ftl: preparing xfer unit at 0x%x\n", xfer->Offset); + + /* Write the transfer unit header */ + header = dev->header; + header.LogicalEUN = 0xffff; + header.EraseCount = xfer->EraseCount; + count = sizeof(header); + offset = xfer->Offset; + ret = (*dev->flash->ops->write)(dev->flash, (char *)&header, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error writing flash mem.\n"); + return; + } + + /* Write the BAM stub */ + nbam = (dev->BlocksPerUnit * sizeof(u_int) + + dev->header.BAMOffset + SECTOR_SIZE - 1) / SECTOR_SIZE; + offset = xfer->Offset + dev->header.BAMOffset; + count = sizeof(u_int); + ctl = BLOCK_CONTROL; + for (i = 0; i < nbam; i++, offset += sizeof(u_int)) { + ret = (*dev->flash->ops->write)(dev->flash, (char *)&ctl, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error writing flash mem.\n"); + return; + } + } + xfer->state = XFER_PREPARED; + +} /* prepare_xfer */ + +/*====================================================================== + + Copy_erase_unit() takes a full erase block and a transfer unit, + copies everything to the transfer unit, then swaps the block + pointers. + + All data blocks are copied to the corresponding blocks in the + target unit, so the virtual block map does not need to be + updated. + +======================================================================*/ + +static int copy_erase_unit(ftl_dev_t *dev, u_short srcunit, u_short xferunit) +{ + u_char buf[SECTOR_SIZE]; + struct eun_info_t *eun; + struct xfer_info_t *xfer; + u_int src, dest, free, i; + u_short unit; + int ret; + off_t offset; + size_t count; + + eun = &dev->EUNInfo[srcunit]; + xfer = &dev->XferInfo[xferunit]; + + DEBUG("ftl: copying block 0x%x to 0x%x\n", eun->Offset, xfer->Offset); + + /* Read current BAM */ + if (dev->bam_index != srcunit) { + offset = eun->Offset + dev->header.BAMOffset; + count = dev->BlocksPerUnit * sizeof(u_int); + ret = (*dev->flash->ops->read)(dev->flash, (char *)dev->bam_cache, + count, offset + dev->base_offset, 0); + /* mark the cache bad, in case we get an error later */ + dev->bam_index = 0xffff; + if (ret) + goto read_error; + } + + /* Write the LogicalEUN for the transfer unit */ + xfer->state = XFER_UNKNOWN; + count = sizeof(u_short); + offset = xfer->Offset + 20; /* Bad! */ + unit = 0x7fff; + ret = (*dev->flash->ops->write)(dev->flash, (char *)&unit, + count, offset + dev->base_offset, 0); + if (ret) + goto write_error; + + /* Copy all data blocks from source unit to transfer unit */ + src = eun->Offset; dest = xfer->Offset; + count = SECTOR_SIZE; + free = 0; + ret = 0; + for (i = 0; i < dev->BlocksPerUnit; i++) { + switch (BLOCK_TYPE(dev->bam_cache[i])) { + case BLOCK_CONTROL: + /* This gets updated later */ + break; + case BLOCK_DATA: + case BLOCK_REPLACEMENT: + offset = src; + ret = (*dev->flash->ops->read)(dev->flash, &buf[0], + count, offset + dev->base_offset, 0); + if (ret) goto read_error; + offset = dest; + ret = (*dev->flash->ops->write)(dev->flash, &buf[0], + count, offset + dev->base_offset, 0); + if (ret) goto write_error; + break; + default: + /* All other blocks must be free */ + dev->bam_cache[i] = 0xffffffff; + free++; + break; + } + src += SECTOR_SIZE; + dest += SECTOR_SIZE; + } + + /* Write the BAM to the transfer unit */ + offset = xfer->Offset + dev->header.BAMOffset; + count = dev->BlocksPerUnit * sizeof(u_int); + ret = (*dev->flash->ops->write)(dev->flash, (char *)dev->bam_cache, + count, offset + dev->base_offset, 0); + if (ret) goto write_error; + + /* All clear? Then update the LogicalEUN again */ + offset = xfer->Offset + 20; /* Bad! */ + count = sizeof(u_short); + ret = (*dev->flash->ops->write)(dev->flash, (char *)&srcunit, + count, offset + dev->base_offset, 0); + if (ret) goto write_error; + + /* Update the maps and usage stats*/ + i = xfer->EraseCount; + xfer->EraseCount = eun->EraseCount; + eun->EraseCount = i; + i = xfer->Offset; + xfer->Offset = eun->Offset; + eun->Offset = i; + dev->FreeTotal -= eun->Free; + dev->FreeTotal += free; + eun->Free = free; + eun->Deleted = 0; + + /* Now, the cache should be valid for the new block */ + dev->bam_index = srcunit; + + return 0; + +read_error: + printk("Error reading flash mem.\n"); + return ret; + +write_error: + printk("Error writing flash mem.\n"); + return ret; +} /* copy_erase_unit */ + +/*====================================================================== + + reclaim_block() picks a full erase unit and a transfer unit and + then calls copy_erase_unit() to copy one to the other. Then, it + schedules an erase on the expired block. + + What's a good way to decide which transfer unit and which erase + unit to use? Beats me. My way is to always pick the transfer + unit with the fewest erases, and usually pick the data unit with + the most deleted blocks. But with a small probability, pick the + oldest data unit instead. This means that we generally postpone + the next reclaimation as long as possible, but shuffle static + stuff around a bit for wear leveling. + +======================================================================*/ + +static int reclaim_block(ftl_dev_t *dev) +{ + u_short i, eun, xfer; + u_int best; + int queued, ret; + + DEBUG("ftl: reclaiming space...\n"); + + /* Pick the least erased transfer unit */ + best = 0xffffffff; xfer = 0xffff; + do { + queued = 0; + for (i = 0; i < dev->header.NumTransferUnits; i++) { + if (dev->XferInfo[i].state == XFER_UNKNOWN) + erase_xfer(dev, i); + if (dev->XferInfo[i].state == XFER_ERASING) + queued = 1; + else if (dev->XferInfo[i].state == XFER_ERASED) + prepare_xfer(dev, i); + if ((dev->XferInfo[i].state == XFER_PREPARED) && + (dev->XferInfo[i].EraseCount <= best)) { + best = dev->XferInfo[i].EraseCount; + xfer = i; + } + } + if (xfer == 0xffff) { + if (queued) { + DEBUG("ftl: waiting for transfer unit to be prepared...\n"); + DEBUG("ftl: Hupp...\n"); + /*sleep_on(&dev->erase_pending); */ + } else { + static int ne = 0; + if (++ne < 5) + printk(KERN_NOTICE "ftl: reclaim failed: no suitable transfer units!\n"); + return -EIO; + } + } + } while (xfer == 0xffff); + + eun = 0; + if ((jiffies % shuffle_freq) == 0) { + DEBUG("ftl: recycling freshest block...\n"); + best = 0xffffffff; + for (i = 0; i < dev->DataUnits; i++) + if (dev->EUNInfo[i].EraseCount <= best) { + best = dev->EUNInfo[i].EraseCount; + eun = i; + } + } else { + best = 0; + for (i = 0; i < dev->DataUnits; i++) + if (dev->EUNInfo[i].Deleted >= best) { + best = dev->EUNInfo[i].Deleted; + eun = i; + } + if (best == 0) { + static int ne = 0; + if (++ne < 5) + printk(KERN_NOTICE "ftl: reclaim failed: no free blocks!\n"); + return -EIO; + } + } + + ret = copy_erase_unit(dev, eun, xfer); + if (!ret) + erase_xfer(dev, xfer); + else + printk(KERN_NOTICE "ftl: copy_erase_unit failed!\n"); + return ret; +} /* reclaim_block */ + +/*====================================================================== + + Find_free() searches for a free block. If necessary, it updates + the BAM cache for the erase unit containing the free block. It + returns the block index -- the erase unit is just the currently + cached unit. If there are no free blocks, it returns 0 -- this + is never a valid data block because it contains the header. + +======================================================================*/ + +#ifdef PSYCHO_DEBUG +static void dump_lists(ftl_dev_t *dev) +{ + int i; + printk(KERN_DEBUG "ftl: Free total = %d\n", dev->FreeTotal); + for (i = 0; i < dev->DataUnits; i++) + printk(KERN_DEBUG + "ftl: unit %d: %d phys, %d free, %d deleted\n", i, + dev->EUNInfo[i].Offset >> dev->header.EraseUnitSize, + dev->EUNInfo[i].Free, dev->EUNInfo[i].Deleted); +} +#endif + +static u_int find_free(ftl_dev_t *dev) +{ + u_short stop, eun; + u_int blk; + int ret; + off_t offset; + size_t count; + + /* Find an erase unit with some free space */ + stop = (dev->bam_index == 0xffff) ? 0 : dev->bam_index; + eun = stop; + do { + if (dev->EUNInfo[eun].Free != 0) break; + /* Wrap around at end of table */ + if (++eun == dev->DataUnits) eun = 0; + } while (eun != stop); + + if (dev->EUNInfo[eun].Free == 0) + return 0; + + /* Is this unit's BAM cached? */ + if (eun != dev->bam_index) { + /* Invalidate cache */ + dev->bam_index = 0xffff; + count = dev->BlocksPerUnit * sizeof(u_int); + offset = dev->EUNInfo[eun].Offset + dev->header.BAMOffset; + ret = (*dev->flash->ops->read)(dev->flash, (char *)dev->bam_cache, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error reading flash mem.\n"); + return 0; + } + dev->bam_index = eun; + } + + /* Find a free block */ + for (blk = 0; blk < dev->BlocksPerUnit; blk++) + if (BLOCK_FREE(dev->bam_cache[blk])) break; + if (blk == dev->BlocksPerUnit) { +#ifdef PSYCHO_DEBUG + static int ne = 0; + if (++ne == 1) + dump_lists(dev); +#endif + printk(KERN_NOTICE "ftl: bad free list!\n"); + return 0; + } + /*DEBUG("ftl: found free block at %d in %d\n", blk, eun);*/ + return blk; + +} /* find_free */ + +/*====================================================================== + + This gets a memory handle for the region corresponding to the + minor device number. + +======================================================================*/ + +static int ftl_open(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + ftl_dev_t *dev; + + DEBUG("ftl_open(%d)\n", minor); + + dev = dev_table[DEVICE_NR(minor)]; + if ( dev == NULL ) + return -ENODEV; + + if (dev->region.RegionSize == 0) + return -ENODEV; + + while (dev->locked) { + DEBUG("dev->locked, sleeping on ftl_wait_open\n"); + sleep_on(&ftl_wait_open); + } + + if ((scan_header(dev) == 0) && + (build_maps(dev) == 0)) { + dev->state = FTL_FORMATTED; + ftl_reread_partitions(minor); +#ifdef FLASH_DEBUG + printk(KERN_INFO "ftl: opening %d kb FTL partition\n", + dev->header.FormattedSize >> 10); +#endif + } else { + printk(KERN_NOTICE "ftl: FTL partition is invalid.\n"); + return -ENODEV; + } + + dev->open++; + + /* MOD_INC_USE_COUNT; */ + return 0; +} /* ftl_open */ + +/*====================================================================*/ + +static int ftl_close(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + ftl_dev_t *dev; + int i; + struct super_block *sb; + + DEBUG("ftl: ftl_close(%d)\n", minor); + + /* Flush all writes */ + fsync_dev(inode->i_rdev); + sb = get_super(inode->i_rdev); + if (sb) + invalidate_inodes(sb); + invalidate_buffers(inode->i_rdev); + + dev = dev_table[DEVICE_NR(minor)]; + + /* Wait for any pending erase operations to complete */ + for (i = 0; i < dev->header.NumTransferUnits; i++) { + if (dev->XferInfo[i].state == XFER_ERASING) { + printk("ftl: Hmmm... close before all erased...\n"); + /* sleep_on(&dev->erase_pending); */ + } + if (dev->XferInfo[i].state == XFER_ERASED) + prepare_xfer(dev, i); + } + + dev->open--; + if (dev->open == 0) { + DEBUG("ftl: Last close. deallocating stuff\n"); + if (dev->VirtualBlockMap) { + vfree(dev->VirtualBlockMap); + dev->VirtualBlockMap = NULL; + } + if (dev->VirtualPageMap) { + kfree(dev->VirtualPageMap); + dev->VirtualPageMap = NULL; + } + if (dev->EUNInfo) { + kfree(dev->EUNInfo); + dev->EUNInfo = NULL; + } + if (dev->XferInfo) { + kfree(dev->XferInfo); + dev->XferInfo = NULL; + } + if (dev->bam_cache) { + kfree(dev->bam_cache); + dev->bam_cache = NULL; + } + } + + /* MOD_DEC_USE_COUNT; */ + return 0; +} /* ftl_close */ + +/*====================================================================== + + Read a series of sectors from an FTL partition. + +======================================================================*/ + +static int ftl_read(ftl_dev_t *dev, caddr_t buffer, + u_long sector, u_long nblocks) +{ + u_int log_addr, bsize; + u_long i; + int ret; + off_t offset; + size_t count; + + /*DEBUG("ftl: ftl_read(0x%p, 0x%lx, %ld)\n", dev, sector, nblocks);*/ + if (!(dev->state & FTL_FORMATTED)) { + printk(KERN_NOTICE "ftl: bad partition\n"); + return -EIO; + } + bsize = dev->region.BlockSize; + + count = SECTOR_SIZE; + for (i = 0; i < nblocks; i++) { + if (((sector+i) * SECTOR_SIZE) >= dev->header.FormattedSize) { + printk(KERN_NOTICE "ftl: bad read offset\n"); + return -EIO; + } + log_addr = dev->VirtualBlockMap[sector+i]; + if (log_addr == 0xffffffff) + memset(buffer, 0, SECTOR_SIZE); + else { + offset = (dev->EUNInfo[log_addr / bsize].Offset + (log_addr % bsize)); + ret = (*dev->flash->ops->read)(dev->flash, buffer, + count, offset + dev->base_offset, 0); + if (ret) { + printk("Error reading flash mem.\n"); + return -EIO; + } + } + buffer += SECTOR_SIZE; + } + return 0; +} /* ftl_read */ + +/*====================================================================== + + Write a series of sectors to an FTL partition + +======================================================================*/ + +static int set_bam_entry(ftl_dev_t *dev, + u_int log_addr, u_int virt_addr) +{ + u_int bsize, blk; +#ifdef PSYCHO_DEBUG + u_int old_addr; +#endif + u_short eun; + int ret; + off_t offset; + size_t count; + + /*DEBUG("ftl: set_bam_entry(0x%p, 0x%x, 0x%x)\n", dev, log_addr, virt_addr);*/ + + bsize = dev->region.BlockSize; + eun = log_addr / bsize; + blk = (log_addr % bsize) / SECTOR_SIZE; + count = sizeof(u_int); + offset = (dev->EUNInfo[eun].Offset + blk * sizeof(u_int) + + dev->header.BAMOffset); + +#ifdef PSYCHO_DEBUG + ret = (*dev->flash->ops->read)(dev->flash, (char *)&old_addr, + count, offset + dev->base_offset, 0); + if (((virt_addr == 0xfffffffe) && !BLOCK_FREE(old_addr)) || + ((virt_addr == 0) && (BLOCK_TYPE(old_addr) != BLOCK_DATA)) || + (!BLOCK_DELETED(virt_addr) && (old_addr != 0xfffffffe))) { + static int ne = 0; + if (++ne < 5) { + printk(KERN_NOTICE "ftl: set_bam_entry() inconsistency!\n"); + printk(KERN_NOTICE "ftl: log_addr = 0x%x, old = 0x%x" + ", new = 0x%x\n", log_addr, old_addr, virt_addr); + } + return -1; + } +#endif + if (dev->bam_index == eun) { +#ifdef PSYCHO_DEBUG + if (dev->bam_cache[blk] != old_addr) { + static int ne = 0; + if (++ne < 5) { + printk(KERN_NOTICE "ftl: set_bam_entry() inconsistency!\n"); + printk(KERN_NOTICE "ftl: log_addr = 0x%x, cache" + " = 0x%x, card = 0x%x\n", + dev->bam_cache[blk], old_addr, 0); + } + return -1; + } +#endif + dev->bam_cache[blk] = virt_addr; + } + + ret = (*dev->flash->ops->write)(dev->flash, (char *)&virt_addr, + count, offset + dev->base_offset, 0); +#ifdef PSYCHO_DEBUG + if (ret) { + printk(KERN_NOTICE "ftl: set_bam_entry() failed!\n"); + printk(KERN_NOTICE "ftl: log_addr = 0x%x, old = 0x%x," + " new = 0x%x\n", log_addr, old_addr, virt_addr); + printk("Error writing flash mem.\n"); + } +#endif + return ret; +} /* set_bam_entry */ + +static int ftl_write(ftl_dev_t *dev, caddr_t buffer, + u_long sector, u_long nblocks) +{ + u_int bsize, log_addr, virt_addr, old_addr, blk; + u_long i; + int ret; + off_t offset; + size_t count; + + /*DEBUG("ftl: ftl_write(0x%p, %ld, %ld)\n", dev, sector, nblocks);*/ + + if (!(dev->state & FTL_FORMATTED)) { + printk(KERN_NOTICE "ftl: bad partition\n"); + return -EIO; + } + /* See if we need to reclaim space, before we start */ + while (dev->FreeTotal < nblocks) { + ret = reclaim_block(dev); + if (ret) + return ret; + } + + bsize = dev->region.BlockSize; + count = SECTOR_SIZE; + virt_addr = sector * SECTOR_SIZE | BLOCK_DATA; + for (i = 0; i < nblocks; i++) { + if (virt_addr >= dev->header.FormattedSize) { + printk(KERN_NOTICE "ftl: bad write offset\n"); + return -EIO; + } + + /* Grab a free block */ + blk = find_free(dev); + if (blk == 0) { + static int ne = 0; + if (++ne < 5) + printk(KERN_NOTICE "ftl: internal error: no free blocks!\n"); + return -ENOSPC; + } + + /* Tag the BAM entry, and write the new block */ + log_addr = dev->bam_index * bsize + blk * SECTOR_SIZE; + dev->EUNInfo[dev->bam_index].Free--; + dev->FreeTotal--; + if (set_bam_entry(dev, log_addr, 0xfffffffe)) + return -EIO; + dev->EUNInfo[dev->bam_index].Deleted++; + offset = (dev->EUNInfo[dev->bam_index].Offset + blk * SECTOR_SIZE); + ret = (*dev->flash->ops->write)(dev->flash, buffer, + count, offset + dev->base_offset, 0); + if (ret) { + printk(KERN_NOTICE "ftl: block write failed!\n"); + printk(KERN_NOTICE "ftl: log_addr = 0x%x, virt_addr" + " = 0x%x, Offset = 0x%lx\n", log_addr, virt_addr, + offset); + printk("Error writing flash mem.\n"); + return -EIO; + } + + /* Only delete the old entry when the new entry is ready */ + old_addr = dev->VirtualBlockMap[sector+i]; + if (old_addr != 0xffffffff) { + dev->VirtualBlockMap[sector+i] = 0xffffffff; + dev->EUNInfo[old_addr/bsize].Deleted++; + if (set_bam_entry(dev, old_addr, 0)) + return -EIO; + } + + /* Finally, set up the new pointers */ + if (set_bam_entry(dev, log_addr, virt_addr)) + return -EIO; + dev->VirtualBlockMap[sector+i] = log_addr; + dev->EUNInfo[dev->bam_index].Deleted--; + + buffer += SECTOR_SIZE; + virt_addr += SECTOR_SIZE; + } + return 0; +} /* ftl_write */ + +/*====================================================================== + + IOCTL calls for getting device parameters. + +======================================================================*/ + +static int ftl_ioctl(struct inode *inode, struct file *file, + u_int cmd, u_long arg) +{ + struct hd_geometry *geo = (struct hd_geometry *)arg; + int ret = 0, minor = MINOR(inode->i_rdev); + ftl_dev_t *dev; + u_long sect; + + dev = dev_table[DEVICE_NR(minor)]; + if (dev==NULL) + return -ENODEV; + + switch (cmd) { + case HDIO_GETGEO: + if (!access_ok(VERIFY_WRITE, (long *)arg, sizeof(*geo))) + return -EFAULT; + /* Sort of arbitrary: round size down to 4K boundary */ + sect = dev->header.FormattedSize/SECTOR_SIZE; + put_user(1, (char *)&geo->heads); + put_user(8, (char *)&geo->sectors); + put_user((sect>>3), (short *)&geo->cylinders); + put_user(ftl_hd[minor].start_sect, (u_long *)&geo->start); + break; + case BLKGETSIZE: + if (!access_ok(VERIFY_WRITE, (long *)arg, sizeof(long))) + return -EFAULT; + put_user(dev->header.FormattedSize/SECTOR_SIZE, (long *)arg); + break; + case BLKFLSBUF: + if (!suser()) + return -EACCES; + fsync_dev(inode->i_rdev); + invalidate_buffers(inode->i_rdev); + break; + case BLKRRPART: + ret = ftl_reread_partitions(minor); + break; + RO_IOCTLS(inode->i_rdev, arg); + default: + ret = -EINVAL; + } + + return ret; +} /* ftl_ioctl */ + +/*====================================================================== + + Handler for block device requests + +======================================================================*/ + +static int ftl_reread_partitions(int minor) +{ + int d = DEVICE_NR(minor); + ftl_dev_t *dev = dev_table[d]; + int i, whole; + struct super_block *sb; + + DEBUG("ftl: ftl_reread_partition(%d)\n", minor); + if (dev->locked || (dev->open > 1)) + return -EBUSY; + dev->locked = 1; + + whole = minor & ~(MAX_PART-1); + for (i = 0; i < MAX_PART; i++) { + if (ftl_hd[whole+i].nr_sects > 0) { + kdev_t rdev = MKDEV(FTL_MAJOR, whole+i); + sync_dev(rdev); + sb = get_super(rdev); + if (sb) + invalidate_inodes(sb); + invalidate_buffers(rdev); + } + ftl_hd[whole+i].start_sect = 0; + ftl_hd[whole+i].nr_sects = 0; + } + + if (scan_header(dev) == 0) + ftl_hd[whole].nr_sects = + dev->header.FormattedSize/SECTOR_SIZE; + else + ftl_hd[whole].start_sect = -1; + resetup_one_dev(&ftl_gendisk, whole); + +#ifdef FLASH_DEBUG + for (i = 0; i < MAX_PART; i++) { + if (ftl_hd[whole+i].nr_sects > 0) + printk(KERN_INFO " %d: start %ld size %ld\n", i, + ftl_hd[whole+i].start_sect, + ftl_hd[whole+i].nr_sects); + } +#endif + + dev->locked = 0; + wake_up(&ftl_wait_open); + return 0; +} + +/** + gendisk init function. + */ + +static void ftl_geninit(struct gendisk *gendisk) +{ + int i; + ftl_dev_t *dev; + for (i = 0; i < MAX_DEV; i++) { + dev = dev_table[i]; + if (dev==NULL) + break; + } + gendisk->nr_real = i; +} + + +/*====================================================================== + + Handler for block device requests + +======================================================================*/ + +static void do_ftl_request(void) +{ + int ret, minor; + ftl_dev_t *dev; + + /*DEBUG("ftl: starting do_ftl_request()\n");*/ + + do { + sti(); + INIT_REQUEST; + + minor = MINOR(CURRENT->rq_dev); + + dev = dev_table[DEVICE_NR(minor)]; + ret = 0; + + switch (CURRENT->cmd) { + + case READ: + ret = ftl_read(dev, CURRENT->buffer, + CURRENT->sector+ftl_hd[minor].start_sect, + CURRENT->current_nr_sectors); + break; + + case WRITE: + ret = ftl_write(dev, CURRENT->buffer, + CURRENT->sector+ftl_hd[minor].start_sect, + CURRENT->current_nr_sectors); + break; + + default: + panic("ftl: unknown block command!\n"); + + } + end_request((ret == 0) ? 1 : 0); + } while (1); +} /* do_ftl_request */ + +/*====================================================================*/ + +#ifdef OLD_PCMCIA +int init_module(void) +{ + servinfo_t serv; + int i; + + DEBUG("%s\n", version); + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "ftl_cs: Card Services release " + "does not match!\n"); + return -1; + } + + register_pcmcia_driver(&dev_info, &ftl_attach, &ftl_detach); + + major_dev = register_blkdev(major_dev, "ftl", &ftl_blk_fops); + if (major_dev == 0) + printk(KERN_NOTICE "ftl_cs: unable to grab major " + "device number!\n"); + + for (i = 0; i < MINOR_NR(MAX_DEV, 0, 0); i++) + ftl_blocksizes[i] = 1024; + for (i = 0; i < MAX_DEV*MAX_PART; i++) { + ftl_hd[i].nr_sects = 0; + ftl_hd[i].start_sect = -1; + } + blksize_size[major_dev] = ftl_blocksizes; + ftl_gendisk.major = major_dev; + blk_dev[major_dev].request_fn = DEVICE_REQUEST; + ftl_gendisk.next = gendisk_head; + gendisk_head = &ftl_gendisk; + init_waitqueue(&ftl_wait_open); + + return 0; +} + +void cleanup_module(void) +{ + int i; + dev_link_t *link; + struct gendisk *gd, **gdp; + + DEBUG("ftl_cs: unloading\n"); + unregister_pcmcia_driver(&dev_info); + if (major_dev != 0) + unregister_blkdev(major_dev, "ftl"); + for (i = 0; i < MAX_DEV; i++) { + link = dev_table[i]; + if (link) { + if (link->state & DEV_CONFIG) + ftl_release((u_long)link); + ftl_detach(link); + } + } + blk_dev[major_dev].request_fn = NULL; + blksize_size[major_dev] = NULL; + for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) + if (*gdp == &ftl_gendisk) { + gd = *gdp; *gdp = gd->next; + break; + } +} + +#endif /* OLD_PCMCIA */ + + +static ftl_dev_t *ftl_attach(flash_device_info_t *flash, + off_t offset, size_t size) +{ + ftl_dev_t *dev; + int i; + int minor_dev; + u_int block_size; + + DEBUG("ftl_attach()\n"); + + for (i = 0; i < MAX_DEV; i++) + if (dev_table[i] == NULL) break; + + if (i == MAX_DEV) { + printk(KERN_NOTICE "ftl: no devices available\n"); + return NULL; + } + + minor_dev = i; + dev = kmalloc(sizeof(ftl_dev_t), GFP_KERNEL); + if (dev == NULL) + return NULL; + + dev_table[minor_dev] = dev; + memset(dev, 0, sizeof(ftl_dev_t)); + /* init_waitqueue(&dev->erase_pending); */ + dev->flash = flash; + dev->base_offset = offset; + block_size = 0; + for (i=0;inum_regions;i++) { + if ( (offset + size >= flash->region[i].offset) && + (offset < flash->region[i].offset + + flash->region[i].sector_size*flash->region[i].num_sectors)) { + if (flash->region[i].sector_size > block_size) + block_size = flash->region[i].sector_size; + } + } + dev->region.BlockSize = block_size; + dev->region.RegionSize = size; + dev->dev = MKDEV(FTL_MAJOR, MINOR_NR(minor_dev, 0)); + + printk("ftl: /dev/ftl%c - size: %dkb blocksize: %dkb at flash%d\n", + minor_dev+'a', dev->region.RegionSize/1024, dev->region.BlockSize/1024, + MINOR(flash->dev)); + + return dev; +} /* ftl_attach */ + +int ftl_init(void) +{ + int i; + flash_device_info_t *flash; + + DEBUG("ftl_init()\n"); + + if (register_blkdev(FTL_MAJOR, "ftl", &ftl_blk_fops)) { + printk("unable to get major %d for ftl devices\n", FTL_MAJOR); + return -1; + } + + for (i = 0; i < MINOR_NR(MAX_DEV, 0); i++) + ftl_blocksizes[i] = 1024; + for (i = 0; i < MINOR_NR(MAX_DEV, 0); i++) { + ftl_hd[i].nr_sects = 0; + ftl_hd[i].start_sect = -1; + } + blksize_size[FTL_MAJOR] = ftl_blocksizes; + ftl_gendisk.major = FTL_MAJOR; + blk_dev[FTL_MAJOR].request_fn = DEVICE_REQUEST; + ftl_gendisk.next = gendisk_head; + gendisk_head = &ftl_gendisk; + init_waitqueue(&ftl_wait_open); + + for (i=0;idev)==boot_table[i].minor) + break; + } + if (flash==NULL) { + printk("No flash device %d found. Can't create ftl device for it.\n", i); + } else { + ftl_attach(flash, boot_table[i].start, boot_table[i].size); + } + } + + return 0; +} + Index: drivers/block/ll_rw_blk.c =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/drivers/block/ll_rw_blk.c,v retrieving revision 1.1.1.2 retrieving revision 1.3 diff -u -r1.1.1.2 -r1.3 --- ll_rw_blk.c 1999/04/21 11:51:16 1.1.1.2 +++ ll_rw_blk.c 1999/04/21 14:26:19 1.3 @@ -743,6 +743,9 @@ #ifdef CONFIG_BLK_DEV_RAM rd_init(); #endif +#ifdef CONFIG_BLK_DEV_FTL + ftl_init(); +#endif #ifdef CONFIG_BLK_DEV_LOOP loop_init(); #endif Index: drivers/char/Config.in =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/drivers/char/Config.in,v retrieving revision 1.1.1.2 retrieving revision 1.3 diff -u -r1.1.1.2 -r1.3 --- Config.in 1999/04/21 11:52:18 1.1.1.2 +++ Config.in 1999/04/21 14:26:20 1.3 @@ -106,6 +106,11 @@ bool 'Tadpole ANA H8 Support' CONFIG_H8 fi +bool '/dev/flash' CONFIG_FLASH +if [ "$CONFIG_FLASH" = "y" ]; then + bool ' AMD flash chip support' CONFIG_AMD_FLASH +fi + mainmenu_option next_comment comment 'Video For Linux' Index: drivers/char/Makefile =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/drivers/char/Makefile,v retrieving revision 1.1.1.2 retrieving revision 1.3 diff -u -r1.1.1.2 -r1.3 --- Makefile 1999/04/21 11:52:19 1.1.1.2 +++ Makefile 1999/04/21 14:26:21 1.3 @@ -301,6 +301,13 @@ endif endif +ifeq ($(CONFIG_FLASH),y) +LX_OBJS += flash.o + ifeq ($(CONFIG_AMD_FLASH),y) + LX_OBJS += amd_flash.o + endif +endif + ifeq ($(CONFIG_BUS_I2C),y) L_I2C=y else Index: drivers/char/amd_flash.c =================================================================== RCS file: amd_flash.c diff -N amd_flash.c --- /dev/null Tue May 5 22:32:27 1998 +++ /tmp/cvs00400Laa Mon Apr 26 16:45:46 1999 @@ -0,0 +1,497 @@ +/* + * linux/drivers/char/amd_flash.c Version 0.1 + * + ********************************************************** + * NOTE: + * + * This driver has only been verified with an Am29LV017B 4x8bit configuration. + * + ********************************************************** + * + * Copyright (C) 1999 Alexander Larsson (alla@lysator.liu.se or alex@signum.se) + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +//#define DEBUGFLASH + +#ifdef DEBUGFLASH +#define DEBUG(args...) printk(args) +#else +#define DEBUG(args...) +#endif + +static int amd_read(struct flash_device_info *, char *, size_t, off_t, int ); +static int amd_write(struct flash_device_info *, const char *, size_t, off_t, int); +static int amd_erase_sector(struct flash_device_info *, off_t, size_t); +static int amd_protect_sector(struct flash_device_info *, off_t, size_t, int); +static int amd_get_info(struct flash_device_info *, flash_info_t *); + +struct amd_flash { + volatile char *ptr; + u_long size; + short bit_width; /* 0==8bit, 1==16bit, 2==32bit */ + short chip_cfg; /* 0==1 chip, 1== 2 chip, 2== 4 chip */ + char erase_suspend; /* 0=unsupported, 1=read only, 2=r/w */ + struct semaphore mutex; + /* etc.. sector protect etc... */ +}; +#define individual_bit_width(flash) ((flash)->bit_width-(flash)->chip_cfg) + +struct flash_device_ops amd_ops = { + amd_read, + amd_write, + amd_erase_sector, + amd_protect_sector, + amd_get_info +}; + +void __init amd_flash_init(char *ptr, u_long size, int bit_width, int chip_cfg) +{ + struct amd_flash *flash; + struct flash_device_info *fdi; + int i; + u_long offset; + u_long reg_info; + int sector_size; + + flash = kmalloc(sizeof(struct amd_flash), GFP_KERNEL); + if (!flash) + return; + + fdi = kmalloc(sizeof(struct flash_device_info), GFP_KERNEL); + if (!fdi) { + kfree(flash); + return; + } + + fdi->num_regions = cfi_read_char(ptr, CFI_NUM_ERASE_BLOCKS_OFFSET, + 0, bit_width); + DEBUG("Num regions: %d\n", fdi->num_regions); + + fdi->region = kmalloc(sizeof(flash_region_info_t)*fdi->num_regions, GFP_KERNEL); + if (!fdi->region) { + kfree(flash); + kfree(fdi); + return; + } + + flash->ptr = ptr; + flash->size = size; + flash->bit_width = bit_width; + flash->chip_cfg = chip_cfg; + flash->mutex = MUTEX; + + fdi->ops = &amd_ops; + fdi->next = NULL; + fdi->handle = flash; + fdi->dev = 0; + + offset = 0; + for (i=0;inum_regions;i++) { + reg_info = cfi_read_int(ptr, CFI_ERASE_REGION_INFO_OFFSET, + 0, bit_width, chip_cfg); + fdi->region[i].offset = offset; + sector_size = (reg_info >> 16) & 0xFFFF; + if (sector_size==0) + fdi->region[i].sector_size = 128<region[i].sector_size = (sector_size*256)<region[i].num_sectors = (reg_info & 0xFFFF) + 1; + + DEBUG("sector %i size: %d, num_sectors: %d, total size: %d\n", i, + fdi->region[i].sector_size , fdi->region[i].num_sectors, + fdi->region[i].sector_size * fdi->region[i].num_sectors); + + offset += fdi->region[i].sector_size * fdi->region[i].num_sectors; + } + + register_flash(fdi); + + /* Return to read array mode: */ + + ptr[0] = 0xff; + ptr[1] = 0xff; + ptr[2] = 0xff; + ptr[3] = 0xff; +} + +static u_int read_with_chip_width(struct amd_flash *flash, u_long offs) +{ + u_int data; + volatile char *ptr = flash->ptr; + + switch(individual_bit_width(flash)) { + case 0: /* 8bit */ + data = (ptr[offs]); + break; + case 1: /* 16bit */ + data = (*(u_short *)&ptr[offs]); + break; + case 2: /* 32bit */ + data = (*(u_int *)&ptr[offs]); + break; + } + return data; +} + +#define PROGRAM_WRITE 1 +#define PROGRAM_ERASE 2 + +static int program_in_progress(struct amd_flash *flash) +{ + /* This function doesn't work reliably if erase_suspend is used. */ + volatile char *ptr; + int i; + int num_chip; + u_int data1, data2, data; + + num_chip = 1<chip_cfg; + + ptr = flash->ptr; + + for (i=0;istate = TASK_INTERRUPTIBLE; + schedule_timeout(10*100/HZ); /* Time out 0.1 sec. */ + while ((data = *(volatile int *)&ptr[offset])!=0xffffffff) { + /* DEBUG("data= %x, delaying more...\n", data);*/ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(10*100/HZ); /* Time out 0.1 sec. */ + } + + j = jiffies - j; + DEBUG("Erasing %ld took %ld jiffies\n", offset, j); +} + +static int amd_read(struct flash_device_info *fdi, + char *buf, size_t count, off_t offset, int userspace_ptr) +{ + struct amd_flash *flash = fdi->handle; + int ret; + + ret = 0; + + /* DEBUG("amd_read buf=%x, count=%d, offset=%x, us_ptr=%d\n", + (int)buf, (int)count, (int)offset, (int) userspace_ptr);*/ + + down(&flash->mutex); + + if (program_in_progress(flash)) { + printk("Error, embedded program in progress during flash read\n"); + ret = -EIO; + goto error; + } + + if (userspace_ptr) { + if (copy_to_user(buf, (char *)flash->ptr+offset, count) > 0) { + ret = -EFAULT; + } + } else { + memcpy(buf, (char *)flash->ptr+offset, count); + } + + error: + + up(&flash->mutex); + + return ret; +} + +static void unlock_bypass(volatile char *ptr, off_t offset) +{ + *(volatile int *)&ptr[offset] = 0xaaaaaaaa; + *(volatile int *)&ptr[offset] = 0x55555555; + *(volatile int *)&ptr[offset] = 0x20202020; +} + +static void unlock_bypass_reset(volatile char *ptr, off_t offset) +{ + *(volatile int *)&ptr[offset] = 0x90909090; + *(volatile int *)&ptr[offset] = 0x00000000; +} + +static void do_write_int(volatile char *ptr, off_t offset, u_int data ) +{ + int wait; + + if ( ((*(volatile u_int *)&ptr[offset])&data) != data) { + printk("Flash write error, not fully erased at %lx\n", offset); + data = (*(volatile u_int *)&ptr[offset])&data; + } + /* Program word: */ + *(volatile u_int *)&ptr[offset] = 0xa0a0a0a0; + *(volatile u_int *)&ptr[offset] = data; + + wait=0; + while ((*(volatile u_int *)&ptr[offset]) != data) { + wait++; + if (wait==1000) { + DEBUG("do_write_int, wait==1000!\n"); + DEBUG("offet = %lx, data = %x, flash: %x\n", + offset, data, (*(volatile u_int *)&ptr[offset])); + } + /* Busy wait */ + } + +} + +static void do_write_char(volatile u_char *ptr, off_t offset, u_char data ) +{ + int wait; + if ( ((ptr[offset])&data) != data) { + printk("Flash write error, not fully erased at %lx\n", offset); + data = (ptr[offset])&data; + } + /* Program byte: */ + ptr[offset] = 0xa0; + ptr[offset] = data; + + wait=0; + while (ptr[offset] != data) { + wait++; + if (wait==1000) { + DEBUG("do_write_char, wait==1000!\n"); + DEBUG("offet = %lx, data = %x, flash: %x\n", + offset, (u_int) data, (u_int) ptr[offset]); + } + /* Busy wait */ + } +} + + +static int amd_write(struct flash_device_info *fdi, + const char *buf, size_t count, off_t offset, int userspace_ptr) +{ + struct amd_flash *flash = fdi->handle; + struct timeval time1, time2; + volatile char *ptr; + int ret; + u_long end_offset; + u_int data; + int loops; + + ret = 0; + ptr = flash->ptr; + + /*DEBUG("amd_write buf=%x, count=%d, offset=%x, us_ptr=%d\n", + (int)buf, (int)count, (int)offset, (int) userspace_ptr);*/ + + down(&flash->mutex); + + if (program_in_progress(flash)) { + printk("Error, embedded program in progress during flash read\n"); + ret = -EIO; + goto error; + } + + end_offset = offset + count; + + if (userspace_ptr && (!access_ok(VERIFY_READ, buf, count)) ) { + ret = -EFAULT; + goto error; + } + + unlock_bypass(ptr, offset); + + do_gettimeofday(&time1); + + loops = 0; + while (offset= 4)) { + if (userspace_ptr) { + if (__get_user(data, (int *)buf)) { + ret = -EFAULT; + goto unlock_bypass_reset_error; + } + } else { + data = *(int *)buf; + } + + do_write_int(ptr, offset, data); + buf += 4; + offset += 4; + } else { + if (userspace_ptr) { + if (__get_user(data, (char *)buf)) { + ret = -EFAULT; + goto unlock_bypass_reset_error; + } + } else { + data = *(char *)buf; + } + do_write_char(ptr, offset, data); + buf += 1; + offset += 1; + } + + /* every 32 writes we check if >1msec has passed. then we schedule */ + if ((++loops & 0x1f) == 0) { + do_gettimeofday(&time2); + time2.tv_sec -= time1.tv_sec; + time2.tv_usec -= time1.tv_usec; + + if ((time2.tv_sec>0) || (time2.tv_usec>1000)) { + /* May wait to short if tv_sec wraps. Who cares... */ + current->state = TASK_RUNNING; + schedule(); + if (userspace_ptr && (!access_ok(VERIFY_READ, buf, count))) { + ret = -EFAULT; + goto unlock_bypass_reset_error; + } + do_gettimeofday(&time1); + } + } + } + + unlock_bypass_reset_error: + unlock_bypass_reset(ptr, offset); + error: + up(&flash->mutex); + + return ret; +} + +static int amd_erase_sector(struct flash_device_info *fdi, + off_t offset, size_t count) +{ + struct amd_flash *flash = fdi->handle; + flash_region_info_t *region = NULL; + u_long region_size; + u_long start, end; + int ret; + int i; + + ret = 0; + + /*DEBUG("amd_erase count=%d, offset=%x\n", + (int)count, (int)offset);*/ + + down(&flash->mutex); + + if (!flash_on_sector_boundaries(fdi, offset, count)) { + DEBUG("Trying to erase, but not at sector boundaries. offs=%x, count=%x\n", + (int)offset, (int)count); + ret = -EINVAL; + goto error; + } + + if (program_in_progress(flash)) { + printk("Error, embedded program in progress during flash erase\n"); + ret = -EIO; + goto error; + } + + /* erase all sectors in mem region */ + /* Now guaranteed to be on sector boundaries */ + for (i = 0; inum_regions; i++) { + region = &fdi->region[i]; + region_size = region->sector_size*region->num_sectors; + if ( (offset >= region->offset) && + (offset < region->offset+region_size) ) { + start = offset - region->offset; + end = start + count; + + if (end > region_size) { + offset = region->offset+region_size; + count -= region_size - end; + end = region_size; + } + + while (startptr, start); + start += region->sector_size; + } + } + } + + + + error: + + up(&flash->mutex); + + return ret; +} + +static int amd_protect_sector(struct flash_device_info *fdi, + off_t offset, size_t count, int protected) +{ + return -ENOSYS; +} + +static int amd_get_info(struct flash_device_info *fdi, + flash_info_t *flash_info) +{ + struct amd_flash *flash = fdi->handle; + flash_info_t t; + int ret; + + t.size = flash->size; + t.bitwidth = 8<chip_cfg; + t.num_regions = fdi->num_regions; + t.num_regions = fdi->num_regions; + + ret = copy_to_user(flash_info, &t, sizeof(flash_info_t)); + if (ret) + return -EFAULT; + + return 0; +} Index: drivers/char/flash.c =================================================================== RCS file: flash.c diff -N flash.c --- /dev/null Tue May 5 22:32:27 1998 +++ /tmp/cvs00400Maa Mon Apr 26 16:45:47 1999 @@ -0,0 +1,513 @@ +/* + * linux/drivers/char/flash.c Version 0.1 + * + * Copyright (C) 1999 Alexander Larsson (alla@lysator.liu.se or alex@signum.se) + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +//#define DEBUGFLASH + +#ifdef DEBUGFLASH +#define DEBUG(args...) printk(args) +#else +#define DEBUG(args...) +#endif + +flash_device_info_t *flash_devices = NULL; +static int next_minor = 0; + +struct flash_chip_data { + u_long addr; + u_long size; +}; + +static struct flash_chip_data probe_data[] __initdata = { + { 0x10000000, 8*1024*1024}, +}; +#define NUM_FLASH_CHIP (sizeof(probe_data)/sizeof(struct flash_chip_data)) + + +#define MAKE_TWO(byte) (((u_short)byte)<<8 | byte) +#define MAKE_FOUR(byte) (((u_int)byte)<<24 | byte<<16 | byte<<8 | byte) + + +int register_flash(struct flash_device_info *fdi) +{ + fdi->dev = MKDEV(FLASH_MAJOR, next_minor++); + fdi->next = flash_devices; + flash_devices = fdi; + + printk("registered flash device /dev/flash%d\n", MINOR(fdi->dev)); + + return 0; +} + +int unregister_flash(struct flash_device_info *unreg) +{ + struct flash_device_info *fdi, *prev; + + prev = NULL; + fdi = flash_devices; + while (fdi != NULL && fdi != unreg) { + prev = fdi; + fdi = fdi->next; + } + + if (fdi == NULL) + return -1; + + if (prev) + prev->next = fdi->next; + else + flash_devices = fdi->next; + + printk("unregistered flash device\n"); + + return 0; +} + +static loff_t flash_lseek(struct file * file, loff_t offset, int orig) +{ + switch (orig) { + case 0: + file->f_pos = offset; + return file->f_pos; + case 1: + file->f_pos += offset; + return file->f_pos; + default: + return -EINVAL; + } +} + +static u_long end_of_flash(struct flash_device_info *fdi) +{ + flash_region_info_t *last_region; + + last_region = &fdi->region[fdi->num_regions-1]; + + return last_region->offset + + last_region->sector_size*last_region->num_sectors; +} + +/* + * This funcion reads the flash memory. + */ +static ssize_t flash_read(struct file * file, char * buf, + size_t count, loff_t *ppos) +{ + struct flash_device_info *fdi = file->private_data; + unsigned long p = *ppos; + unsigned long end_flash; + int res; + + end_flash = end_of_flash(fdi); + + if (p >= end_flash) + return 0; + + if (count > end_flash - p) + count = end_flash - p; + + res = (*fdi->ops->read)(fdi, buf, count, p, 1); + + if (res) + return res; + + *ppos += count; + return count; +} + +static ssize_t flash_write(struct file * file, const char * buf, + size_t count, loff_t *ppos) +{ + struct flash_device_info *fdi = file->private_data; + unsigned long p = *ppos; + unsigned long end_flash; + int res; + + end_flash = end_of_flash(fdi); + + if (p >= end_flash) + return 0; + + if (count > end_flash - p) + count = end_flash - p; + + + if (flash_on_sector_boundaries(fdi, p, count)) { + /* On a sector boundary => Erase the sectors */ + res = (*fdi->ops->erase_sector)(fdi, p, count); + if (res) + return res; + } + + res = (*fdi->ops->write)(fdi, buf, count, p, 1); + if (res) + return res; + + *ppos += count; + return count; +} + +static int flash_ioctl(struct inode * inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct flash_device_info *fdi = filp->private_data; + erase_info_t erase_info; + int ret; + + switch (cmd) { + case FLASHGETINFO: + ret = (*fdi->ops->get_info)(fdi, (flash_info_t *)arg); + return ret; + break; + case FLASHGETREGIONINFO: + ret = copy_to_user((char *)arg, fdi->region, + sizeof(flash_region_info_t)*fdi->num_regions); + if (ret) + return -EFAULT; + return 0; + break; + case FLASHERASE: + ret = copy_from_user(&erase_info, (erase_info_t *)arg, sizeof(erase_info_t)); + if (ret) + return ret; + ret = (*fdi->ops->erase_sector)(fdi, erase_info.offset, erase_info.size); + return ret; + break; + default: + return -EINVAL; + } + return -EINVAL; +} + +static +struct flash_device_info *flash_find_device (kdev_t dev) +{ + struct flash_device_info *fdi; + + fdi = flash_devices; + while (fdi != NULL && fdi->dev != dev) + fdi = fdi->next; + return fdi; +} + + +static int flash_open(struct inode * inode, struct file * filp) +{ + kdev_t dev = inode->i_rdev; + struct flash_device_info *fdi = flash_find_device(dev); + + DEBUG("entering flash_open, minor==%d\n", MINOR(dev)); + if (fdi == NULL) + return -ENODEV; + + filp->private_data = fdi; + + DEBUG("opened device /dev/flash%d\n", MINOR(dev)); + + return 0; +} + + +static struct file_operations flash_fops = { + flash_lseek, /* lseek */ + flash_read, /* read */ + flash_write, /* write */ + NULL, /* readdir */ + NULL, /* poll */ + flash_ioctl, /* ioctl */ + NULL, /* mmap */ + flash_open, /* open */ + NULL, /* flush */ + NULL, /* release */ + NULL /* fsync */ +}; + +int flash_on_sector_boundaries(struct flash_device_info *fdi, + off_t offset, size_t count) +{ + flash_region_info_t *region = NULL; + int i; + u_long start, end; + u_long region_size; + + for (i = 0; inum_regions; i++) { + region = &fdi->region[i]; + region_size = region->sector_size*region->num_sectors; + if ( (offset >= region->offset) && + (offset < region->offset+region_size) ) { + start = offset - region->offset; + end = start + count; + + if (start % region->sector_size != 0) + return 0; /* Not on sector boundaries */ + + if (end > region_size) { + offset = region->offset+region_size; + count -= region_size - end; + /* This region ok, Continue loop */ + } else { + if (end % region->sector_size != 0) + return 0; /* Not on sector boundaries */ + else + return 1; /* Both start and end on sector boundaries. */ + } + } + } + + return 0; /* Not on sector boundaries */ +} + + + +/********** Init stuff: ********/ + +#ifdef CONFIG_AMD_FLASH +extern void amd_flash_init(char *ptr, u_long size, int bit_width, int chip_cfg) __init; +#endif + +static char *vendor_name_table[] __initdata = { + "Unknown", + "None", + "Intel/Sharp extended", + "AMD/Fujitsu standard", + "Intel standard", + "AMD/Fujitsu extended", + "Mitsubishi standard", + "Mitsubishi extended" + /* Add new at end */ +}; + +static void __init cfi_flash_init(void) +{ + int i; + char *ptr; + char qry[6]; + int total_buswidth=0; + int chip_buswidth=0; + int found_flash; + int bit_width; + int chip_cfg; + u_short primary_vendor; + char *vendor_name; + u_long size; + + for (i=0;i #include #include +#include #include #include @@ -592,6 +593,9 @@ lp_m68k_init(); #endif misc_init(); +#ifdef CONFIG_FLASH + flash_init(); +#endif #ifdef CONFIG_SOUND soundcore_init(); #ifdef CONFIG_SOUND_OSS Index: include/linux/blk.h =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/include/linux/blk.h,v retrieving revision 1.1.1.1 diff -u -r1.1.1.1 blk.h --- blk.h 1999/02/09 11:00:30 1.1.1.1 +++ blk.h 1999/04/22 16:59:54 @@ -63,6 +63,7 @@ extern int ez_init(void); extern int bpcd_init(void); extern int ps2esdi_init(void); +extern int ftl_init(void); extern void set_device_ro(kdev_t dev,int flag); void add_blkdev_randomness(int major); @@ -73,6 +74,8 @@ extern int rd_doload; /* 1 = load ramdisk, 0 = don't load */ extern int rd_prompt; /* 1 = prompt for ramdisk, 0 = don't prompt */ extern int rd_image_start; /* starting block # of image */ + + #ifdef CONFIG_BLK_DEV_INITRD Index: include/linux/flash.h =================================================================== RCS file: flash.h diff -N flash.h --- /dev/null Tue May 5 22:32:27 1998 +++ /tmp/cvs00400Waa Mon Apr 26 16:47:28 1999 @@ -0,0 +1,161 @@ +#ifndef _LINUX_FLASH_H +#define _LINUX_FLASH_H + +/* + * /usr/include/linux/flash.h Version 0.1 + * + * Copyright (C) 1999 Alexander Larsson (alla@lysator.liu.se or alex@signum.se) + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +typedef struct flash_info_t { + u_int size; /* Total size of flash array in bytes. */ + u_int bitwidth; /* Bit width of individual chips. */ + u_int chip_config; /* What chip configuration is used, 1 chip, 2 chips etc. */ + u_int num_regions; /* Number of regions with equal sized sectors */ +} flash_info_t; + +typedef struct flash_region_info_t { + u_long offset; + u_int sector_size; /* What size are the erase zones (in bytes) */ + u_int num_sectors; +} flash_region_info_t; + +typedef struct erase_info_t { + u_long offset; + u_long size; +} erase_info_t; + +/* IOCTL:s */ +#define FLASHGETINFO _IOR('F', 1, flash_info_t) +#define FLASHGETREGIONINFO _IOR('F', 2, flash_region_info_t) +#define FLASHERASE _IOW('F', 3, erase_info_t) + +#ifdef __KERNEL__ + +/* Defines used by CFI (Common Flash Interface) */ +#define CFI_QRY_OFFSET 0x10 +#define CFI_PRIMARY_VENDOR_ID_OFFSET 0x13 +#define CFI_PRIMARY_QUERY_TABLE_OFFSET 0x15 +#define CFI_ALT_VENDOR_ID_OFFSET 0x17 +#define CFI_ALT_QUERY_TABLE_OFFSET 0x19 + +#define CFI_DEVICE_SIZE_OFFSET 0x27 +#define CFI_NUM_ERASE_BLOCKS_OFFSET 0x2C +#define CFI_ERASE_REGION_INFO_OFFSET 0x2D + +#define CFI_QUERY_CMD 0x98 +#define CFI_QUERY_OFFSET 0x55 + +#define CFI_VENDOR_NONE 0x0000 +#define CFI_VENDOR_INTEL_SHARP_EXTENDED 0x0001 +#define CFI_VENDOR_AMD_FUJITSU_STANDARD 0x0002 +#define CFI_VENDOR_INTEL_STANDARD 0x0003 +#define CFI_VENDOR_AMD_FUJITSU_EXTENDED 0x0004 +#define CFI_VENDOR_MITSUBISHI_STANDARD 0x0100 +#define CFI_VENDOR_MITSUBISHI_EXTENDED 0x0101 +#define CFI_VENDOR_RESERVED 0xffff + +extern inline u_short cfi_read_short(char *baseptr, int offset, int chip, + int bit_width, int chip_cfg) +{ + /* bit_width: 0 => 8 bit total width, 1 => 16 bit, 2 => 32 bit */ + /* chip_config: 0 => 1 chip, 1 => 2 chip, 2 => 4 chip */ + u_short res; + u_char *ptr; + + ptr = &baseptr[offset< 8 bit total width, 1 => 16 bit, 2 => 32 bit */ + /* chip_config: 0 => 1 chip, 1 => 2 chip, 2 => 4 chip */ + u_int res; + u_char *ptr; + + ptr = &baseptr[offset<> 9) +#define BLOCK_CONTROL 0x30 +#define BLOCK_DATA 0x40 +#define BLOCK_REPLACEMENT 0x60 +#define BLOCK_BAD 0x70 + +#endif /* _LINUX_FTL_H */ Index: include/linux/major.h =================================================================== RCS file: /home/signum/cvsroot/inu/linux-embedded/include/linux/major.h,v retrieving revision 1.1.1.1 retrieving revision 1.3 diff -u -r1.1.1.1 -r1.3 --- major.h 1999/02/09 11:00:24 1.1.1.1 +++ major.h 1999/04/19 14:29:09 1.3 @@ -73,6 +73,7 @@ #define APBLOCK_MAJOR 38 /* AP1000 Block device */ #define DDV_MAJOR 39 /* AP1000 DDV block device */ #define NBD_MAJOR 43 /* Network block device */ +#define FTL_MAJOR 44 #define RISCOM8_NORMAL_MAJOR 48 #define RISCOM8_CALLOUT_MAJOR 49 #define MKISS_MAJOR 55 @@ -80,6 +81,9 @@ #define IDE4_MAJOR 56 #define IDE5_MAJOR 57 + +#define FLASH_MAJOR 60 /* in experimental/local area 60-63 */ + #define SCSI_DISK1_MAJOR 65 #define SCSI_DISK2_MAJOR 66