/*
 * $Log: fldrvlnx.c,v $
 * Revision 1.2  2005/06/16 13:46:22  cgoh
 * 0114_050615_2nd
 *
 * 
 */

/***********************************************************************************/
/*                        M-Systems Confidential                                   */
/*           Copyright (C) M-Systems Flash Disk Pioneers Ltd. 1995-2002            */
/*                         All Rights Reserved                                     */
/***********************************************************************************/
/*                            NOTICE OF M-SYSTEMS OEM                              */
/*                           SOFTWARE LICENSE AGREEMENT                            */
/*                                                                                 */
/*      THE USE OF THIS SOFTWARE IS GOVERNED BY A SEPARATE LICENSE                 */
/*      AGREEMENT BETWEEN THE OEM AND M-SYSTEMS. REFER TO THAT AGREEMENT           */
/*      FOR THE SPECIFIC TERMS AND CONDITIONS OF USE,                              */
/*      OR CONTACT M-SYSTEMS FOR LICENSE ASSISTANCE:                               */
/*      E-MAIL = info@m-sys.com                                                    */
/***********************************************************************************/

/* get the kernel version */
#include <linux/version.h>
/* older kernels didn't define this */
#ifndef KERNEL_VERSION
	#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#endif

/* not supported kernels */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
#	error "Linux Kernel Version 2.5.0 and later not supported"
#else
#	if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#	else
#		if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
#			error "Linux Kernel Version 2.3.0 not supported"
#		else
#			if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
#			else
#				if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
#					error "Linux Kernel Version 2.1.0 not supported"
#				else
#					if LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,0)
#					else
#						error "Linux Kernel Version less that 2.0.0 not supported"
#					endif
#				endif
#			endif
#		endif
#	endif
#endif

#ifndef MODVERSIONS
#	define MODVERSIONS
#	include <linux/modversions.h>
#endif
#include <linux/module.h>			/* must be included after <linux/modversions.h> */

short GetFlDebugParam(void);
#define DebugPrintk(fmt,args...)		if(GetFlDebugParam()){printk(KERN_DEBUG"fl: "fmt,## args);}

#include "fldrvlnx.h"

#include <asm/io.h>				/* isa_write...() */
#include <linux/ioport.h>			/* request_mem_reqion() */
#include <linux/delay.h>			/* udelay() */

#ifdef DEEPDEBUG
	#define LOG_DELAY	 flsleep(1)
#else
	#define LOG_DELAY
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)

#include <linux/init.h>				/* __initdata */
#include <linux/sched.h>
#include <linux/kernel.h>			/* printk() */
#include <linux/slab.h>				/* kmalloc() */
#include <linux/vmalloc.h>
#include <linux/fs.h>				/* everything... */
#include <linux/errno.h>			/* error codes */
#include <linux/timer.h>
#include <linux/fcntl.h>			/* O_ACCMODE */
#include <linux/hdreg.h>			/* HDIO_GETGEO */
#include <linux/devfs_fs_kernel.h>	/* devfs_handle_t */
#include <linux/smp_lock.h>			/* lock_kernel() */
#include <linux/proc_fs.h>			/* create_proc_read_entry() */
#include <asm/system.h>				/* cli(), *_flags */
#include <asm/uaccess.h>			/* verify_area() */

#define grab_io_request_lock(flags) spin_lock_irqsave(&io_request_lock,flags)
#define rlse_io_request_lock(flags) spin_unlock_irqrestore(&io_request_lock,flags)
static devfs_handle_t devfs_handle = NULL;

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/ioctl.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/sockios.h>
#include <linux/smp_lock.h>

#include <asm/io.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/uaccess.h>

extern spinlock_t io_request_lock;
#define grab_io_request_lock(flags) spin_lock_irqsave(&io_request_lock,flags)
#define rlse_io_request_lock(flags) spin_unlock_irqrestore(&io_request_lock,flags)

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,0)

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioctl.h>
#include <linux/genhd.h>
#include <linux/malloc.h>			/* kmalloc() */
#include <linux/hdreg.h>			/* struct hd_geometry */
#include <linux/proc_fs.h>
#include <asm/system.h>
#include <asm/segment.h>
//extern unsigned long loops_per_sec; /* needed by macro udelay() */

//#include <asm/delay.h>				/* macro udelay() */
#include <asm/io.h>					/* memcpy_*io() */
#ifndef __KERNEL_SYSCALLS__
/* need this to get declaration of kernel_thread() */
#define __KERNEL_SYSCALLS__
#endif
#include <linux/unistd.h>			/* kernel_thread() */
#include <linux/sched.h>			/* jiffies */

typedef struct spinlock
{
	int lock;
}spinlock_t;

#define test_and_set_bit(bnr,lp)			set_bit(bnr,lp)
#define test_and_clear_bit(bnr,lp)			clear_bit(bnr,lp)
#define set_current_state(state_value)		do {current->state = state_value; } while (0)
#define grab_io_request_lock(flags) do { save_flags(flags); cli(); } while (0)
	/* spin_lock_irqsave(&io_request_lock,*(flgptr)) */
#define rlse_io_request_lock(flags) do { restore_flags(flags); } while (0)
	/* spin_unlock_irqsave(&io_request_lock,*(flgptr)) */
#define lock_kernel()	/* ToDo */
#define unlock_kernel()	/* ToDo */
#define sigmask(signum) (1UL << ((signum) - 1))

/* Kernel threads. */
#define exit_fs(foo)
#define exit_files(foo)

static void siginitsetinv(sigset_t *set, unsigned long mask);
static void sigemptyset(sigset_t *set);
static inline unsigned long copy_to_user(void *to,const void *from, unsigned long n);
static void spin_lock_init(spinlock_t *splp);
static void spin_lock(spinlock_t *splp);
static void spin_unlock(spinlock_t *splp);
static inline unsigned long copy_to_user(void *to,const void *from, unsigned long n);

#endif

/* definitions for blk.h */

#include <linux/major.h>
int flMajor;	/* our major device number */
#define MAJOR_NR flMajor
#define DEVICE_NAME "fl"
#define DEVICE_OFF(d)
#define DEVICE_REQUEST fl_request_fn1
#define DEVICE_NR(device) (MINOR(device)>>FL_SHIFT)
#define DEVICE_NO_RANDOM
#include <linux/blk.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
#include <linux/blkpg.h>
#endif

/* defines after blk.h - need struct request from blkdev.h */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)
static int end_that_request_first(struct request *req,int uptodate,char *devname);
static void end_that_request_last(struct request *req);
#endif

/* end of blk.h definitions */

/* private includes for M-Systems OSAK */
#include "tffs2lnx.h"

/* module parameters */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
MODULE_AUTHOR("yurid@msys.com");

MODULE_PARM(major, "i");		/* set to zero to get dynamic major */
MODULE_PARM_DESC(major, "Major device number to use.");

MODULE_PARM(rahead, "i");
MODULE_PARM_DESC(rahead, "Set readahead flag for driver.");

MODULE_PARM(hardsect, "1-4i");	/* NB this should be the same as MAX_DEVICES */
MODULE_PARM_DESC(hardsect, "Set hardsect parameter for each DOC device.");

MODULE_PARM(blksize, "1-4i");	/* NB this should be the same as MAX_DEVICES */
MODULE_PARM_DESC(blksize, "Set blksize parameter for each DOC device.");

MODULE_PARM(prio,"i");

MODULE_PARM(fl_debug,"h");
MODULE_PARM(fl_winl,"l");
MODULE_PARM(fl_winh,"l");
MODULE_PARM(fl_part_prot,"h");
MODULE_PARM(fl_key_prot,"s");

MODULE_PARM(fl_nftl_cache,"h");
MODULE_PARM(fl_is_ram_check,"h");
MODULE_PARM(fl_8bit_access,"h");
MODULE_PARM(fl_mtd_bus_access_type,"l");
MODULE_PARM(fl_verify_write_bdtl,"l");
MODULE_PARM(fl_verify_write_binary,"l");
MODULE_PARM(fl_verify_write_other,"l");
MODULE_PARM(fl_sectors_verified_per_folding,"l");

#else
#warning No parameters yet.
#endif

/* these are used only during module initialization */
#ifndef __initdata
#	define __initdata
#endif

static int __initdata major = FLASH_MAJOR;
static int __initdata rahead = 0;
static int __initdata hardsect[MAX_DEVICES];
static int __initdata blksize[MAX_DEVICES];
#ifdef DEEPDEBUG
static short __initdata			fl_debug=1;
#else
static short __initdata			fl_debug=0;
#endif
static unsigned long __initdata		fl_winl=FL_DOC_PHYS_ADDR_LOW;
static unsigned long __initdata		fl_winh=FL_DOC_PHYS_ADDR_HIGH;
static void * __initdata		fl_virl=NULL;
static void * __initdata		fl_virh=NULL;
static short __initdata			fl_part_prot=MAX_DEVICES;
static char * __initdata		fl_key_prot=NULL;
static short __initdata			fl_nftl_cache=1;
static short __initdata			fl_is_ram_check=0;
static short __initdata			fl_8bit_access=1;
static unsigned long __initdata		fl_mtd_bus_access_type=7;	/* FL_BUS_HAS_8BIT_ACCESS|FL_BUS_HAS_16BIT_ACCESS|FL_BUS_HAS_32BIT_ACCESS|FL_NO_ADDR_SHIFT) */
static unsigned long __initdata		fl_verify_write_bdtl=0;
static unsigned long __initdata		fl_verify_write_binary=0;
static unsigned long __initdata		fl_verify_write_other=0;
static unsigned long __initdata		fl_sectors_verified_per_folding=64;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static int __initdata prio=-10;	/* max -20 */
#else
static int __initdata prio=30;		/* max 40 */
#endif

#define FL_SHIFT		6
#define MINOR_NR(r)		(r<<FL_SHIFT)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static DECLARE_MUTEX_LOCKED(start_stop_sem);
#else
static struct semaphore start_stop_sem = MUTEX_LOCKED;
#endif

#define SHUTDOWN_SIGS   (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM)|sigmask(SIGQUIT))

static void fl_setsid(void)
{
	/* Simple setsid, used at start of kernel thread,
	   no error checks needed, or at least none made :). */
	current->leader = 1;
	current->session = current->pgrp = current->pid;
	current->tty = NULL;
	current->tty_old_pgrp = 0;
}

static spinlock_t o_c_lock;

// private structure for driver
typedef struct Fl_Dev
{
	int             volume;     // DOC volume number
	int             size;       // DOC volume size in XXX kb
	int             usage;
	int             heads;
	int             sectors;
	int             cylinders;
	int             read_only;
	unsigned int    requests;
	unsigned int    requests_Read;
	unsigned int    requests_Write;

	// Every once in a while the DOC wants to "think" for a while.  We
	// can't sleep while this is happening if we are in the ordinary
	// request fn context.  What we do instead is use the reqest fn to
	// move the requests to a private, per device list, and schedule
	// a kernel task (capable of sleeping when required) to complete
	// the requests.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	struct list_head queue_head;  // private list for I/O requests 
#else
	struct request *queue;  // Older kernels used singly linked list
	struct request *revq;   // reversed list, used to simplify transfer
#endif
	struct semaphore *sem;
	int terminating;
}FL_DEV;

// Start of structures dynamically allocated with kmalloc() during init and
// (if a module) de-allocated with kfree() if unloaded.

// per DEVICE structures
static FL_DEV*Fl_devices;         // this is an array FL_Flash_Devices in length

// per DEVICE*MINOR structures
static int*Fl_blksize_size;    // used in global blksize_size array
static int*Fl_hardsect_size;   // used in global hardsect_size array
static int*Fl_blk_size;        // used in gendisk structure and global blk_size array

static struct hd_struct *Fl_partitions;

// End of dynamically allocated structures.

// function declarations

int             fl_open(struct inode *inode, struct file *filp);
int             fl_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
int             fl_check_change(kdev_t i_rdev);
int             fl_revalidate(kdev_t i_rdev);
int             fl_init(void);
static void     fl_geninit(struct gendisk *);

// copies flioctl_t data into kernel
/* static char *	fl_usr2kern(unsigned long); */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
int             fl_release(struct inode *inode, struct file *filp);
#else // > 2.1.0
void            fl_release(struct inode *inode, struct file *filp);
#endif // > 2.1.0

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
void            fl_request_fn1(request_queue_t *q);
#else
void            fl_request_fn1(void);
#endif // < 2.4.0

// driver globals
int             flMajor;           // our major device number
int             Fl_read_ahead;      // used in global read_ahead array 

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)

static struct block_device_operations fl_fops =
{
	open: fl_open,                   // open
	release: fl_release,             // release 
	ioctl: fl_ioctl,                 // ioctl 
};

static struct gendisk fl_gendisk =
{
	0,                         // major        Major number, assigned at runtime 
	DEVICE_NAME,               // major_name   Major name 
	FL_SHIFT,                  // minor_shift  Bits to shift to get real from partition 
	MINOR_NR(1),               // max_p        Number of partitions per real 
	NULL,                      // hd struct
	NULL,                      // block sizes
	0,                         // nr_real      maximum number of real, assigned at runtime 
	NULL,                      // internal
	NULL,                      // next         linked list (not used for modules) 
	&fl_fops,
	NULL,
	NULL
};

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)

static struct file_operations fl_fops =
{
	NULL,                      // lseek - default
	block_read,                // read - block dev read 
	block_write,               // write - block dev write 
	NULL,                      // readdir - not here! 
	NULL,                      // select 
	fl_ioctl,                  // ioctl 
	NULL,                      // mmap 
	fl_open,                   // open
	NULL,                      // flush 
	fl_release,                // release 
	block_fsync,               // fsync 
	NULL,                      // media change 
	fl_revalidate,             // revalidate 
	NULL
};

static struct gendisk fl_gendisk =
{
	0,                         // major        Major number, assigned at runtime 
	DEVICE_NAME,               // major_name   Major name 
	FL_SHIFT,                  // minor_shift  Bits to shift to get real from partition 
	MINOR_NR(1),               // max_p        Number of partitions per real 
	0,                         // max_nr       maximum number of real, assigned at runtime 
	fl_geninit,                // init         init function 
	NULL,                      // part         hd struct (partition table), assigned at runtime 
	NULL,                      // sizes        block sizes, assigned at runtime 
	0,                         // nr_real      number, assigned at runtime 
	NULL,                      // real_devices internal use
	NULL                       // next         linked list (not used for modules) 
};

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,0,0)

static struct file_operations fl_fops =
{
	NULL,                      // lseek - default
	block_read,                // read - block dev read 
	block_write,               // write - block dev write 
	NULL,                      // readdir - not here! 
	NULL,                      // select 
	fl_ioctl,                  // ioctl 
	NULL,                      // mmap 
	fl_open,                   // open
	fl_release,                // release 
	block_fsync,               // fsync 
	NULL,                      // media change 
	fl_revalidate              // revalidate 
};

static struct gendisk fl_gendisk =
{
	0,                         // major        Major number, assigned at runtime 
	DEVICE_NAME,               // major_name   Major name 
	FL_SHIFT,                  // minor_shift  Bits to shift to get real from partition 
	MINOR_NR(1),               // max_p        Number of partitions per real 
	0,                         // max_nr       maximum number of real, assigned at runtime 
	fl_geninit,                // init         init function 
	NULL,                      // part         hd struct (partition table), assigned at runtime 
	NULL,                      // sizes        block sizes, assigned at runtime 
	0,                         // nr_real      number, assigned at runtime 
	NULL,                      // real_devices internal use
	NULL                       // next         linked list (not used for modules) 
};

#else
#error "OS Not supported"
#endif 

/* __init ckmalloc()
 *
 * kmalloc and clear, only used during init
 */
#if defined(__init)
void * __init ckmalloc(int size, int mode)
#elif defined(__initfunc)
__initfunc( void * ckmalloc(int size, int mode))
#else
void * ckmalloc(int size, int mode)
#endif
{
	void *ptr;
	if ((ptr = kmalloc(size, mode)))
		memset(ptr, 0, size);
	return(ptr);
}

/* IFF !NULL free and set to NULL */
void lkfree(void **ptr)
{
	if(*ptr)
	{
		kfree(*ptr);
		*ptr = NULL;
	}
}

int fl_open(struct inode *inode, struct file *filp)
{
	FL_DEV*dev;	/* device information */
	int dev_nr;

	/* get the real device number */
	/* (device&0xff)>>6) */
	dev_nr = DEVICE_NR(inode->i_rdev);

	if (dev_nr >= fl_gendisk.nr_real)
	{
		return -ENODEV;
	}

	if(FlGetProtection(dev_nr))
	{
		if (filp->f_mode & 0x02)
		{
			return -EROFS;
		}
	}

	/* get pointer to device structure and increment use count */
	dev = Fl_devices + dev_nr;

	spin_lock(&o_c_lock);
	dev->usage++;
	MOD_INC_USE_COUNT;
	spin_unlock(&o_c_lock);
	return 0;					/* success */
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
int
#else
void
#endif
fl_release(struct inode *inode, struct file *filp)
{
	FL_DEV         *dev;        // device information 
	int             dev_nr;

	// get the real device number
	dev_nr = DEVICE_NR(inode->i_rdev);

	if (dev_nr >= fl_gendisk.nr_real)
	{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
		return -ENODEV;
#else
		return;
#endif
	}

	dev = Fl_devices + dev_nr;

	// lock and dec usage count
	spin_lock(&o_c_lock);
	dev->usage--;
	// if there are no more open we want to ensure that we flush all blocks.
	// sync_dev can cause sleep, so this should be _before_ MOD_DEC_USE_COUNT.
	if (!dev->usage)
	{
		// flush it right now
		sync_dev(inode->i_rdev);
		invalidate_buffers(inode->i_rdev);
	}
	MOD_DEC_USE_COUNT;
	spin_unlock(&o_c_lock);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
	return (0);
#endif
}

/* Derived from OSAK dosformt.c getDriveGeometry() */
static void get_drive_geo(long capacity, struct hd_geometry *geo)
{
    unsigned long temp;

    geo->heads = 16;                              /* Max out number of heads             */
    geo->cylinders=1024;
    temp = geo->cylinders * geo->heads;            /* Compute divisor for heads           */
    geo->sectors = capacity / temp;                /* Compute value for sectors per track */
    while (geo->sectors > 0x3f ){                  /* While number of sectors too big     */
      geo->heads *= 2;                             /* use one more head                   */
      temp = geo->cylinders * geo->heads;          /* Recompute divisor for heads         */
      geo->sectors = capacity / temp;              /* Recompute sectors per track         */
    }
    if (capacity % temp) {                     /* If no remainder, done!              */
      geo->sectors++;                            /* Else, increment number of sectors   */
      temp = geo->cylinders * geo->sectors;        /* Compute divisor for heads           */
      geo->heads = capacity / temp;                /* Compute value for heads             */
      if (capacity % temp) {                   /* If no remainder, done!              */
	geo->heads++;                            /* Else, increment number of heads     */
        temp = geo->heads * geo->sectors;          /* Compute divisor for cylinders       */
        geo->cylinders = (unsigned long)(capacity / temp); /* Compute number of cylinders         */
      }
    }
/*
	geo->heads = 16;
	geo->cylinders = 1024;
	temp = geo->cylinders * geo->heads;
	geo->sectors = capacity / temp;

	if (capacity % temp)
	{
		geo->sectors++;
		temp = geo->cylinders * geo->sectors;
		geo->heads = capacity / temp;
		if (capacity % temp)
		{
			geo->heads++;
			temp = geo->heads * geo->sectors;
			geo->cylinders = capacity / temp;
		}
	}
*/
}

/* The ioctl() implementation */
int fl_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	int size;
	unsigned int dev_nr;
	FL_DEV*device;
	int res;

	dev_nr = DEVICE_NR(inode->i_rdev);
	device = Fl_devices + dev_nr;

	/* verify we have sysadmin capabilities for certain ioctl calls */
	switch (cmd)
	{
	case FL_IOCTL_GET_INFO:
	case FL_IOCTL_DEFRAGMENT:
	case FL_IOCTL_WRITE_PROTECT:
	case FL_IOCTL_MOUNT_VOLUME:
	case FL_IOCTL_FORMAT_VOLUME:
	case FL_IOCTL_BDK_OPERATION:
	case FL_IOCTL_DELETE_SECTORS:
	case FL_IOCTL_READ_SECTORS:
	case FL_IOCTL_WRITE_SECTORS:
	case FL_IOCTL_FORMAT_PHYSICAL_DRIVE:
	case FL_IOCTL_FORMAT_LOGICAL_DRIVE:
	case FL_IOCTL_BDTL_HW_PROTECTION:
	case FL_IOCTL_BINARY_HW_PROTECTION:
	case FL_IOCTL_OTP:
	case FL_IOCTL_CUSTOMER_ID:
	case FL_IOCTL_UNIQUE_ID:
	case FL_IOCTL_NUMBER_OF_PARTITIONS:
	case FL_IOCTL_INQUIRE_CAPABILITIES:
	case FL_IOCTL_SET_ENVIRONMENT_VARIABLES:
	case FL_IOCTL_PLACE_EXB_BY_BUFFER:
	case FL_IOCTL_WRITE_IPL:
	case FL_IOCTL_DEEP_POWER_DOWN_MODE:
	case FL_IOCTL_EXTENDED_ENVIRONMENT_VARIABLES:
	case FL_IOCTL_VERIFY_VOLUME:
	case FL_IOCTL_SET_ACCESS_ROUTINE:
	case FL_IOCTL_GET_ACCESS_ROUTINE:
	case FL_IOCTL_EXTENDED_WRITE_IPL:
	case FL_IOCTL_LNX:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
		if (!capable(CAP_SYS_ADMIN))
		{
			return -EACCES;
		}
#endif
	}
	/* ioctl's that don't require libosak access */
	switch(cmd)
	{
	case BLKGETSIZE:
		// Return the device size, expressed in sectors 
		if(!arg)
		{
			return -EINVAL;		/* NULL pointer: not valid  */
		}
		if((res=verify_area(VERIFY_WRITE, (long *) arg, sizeof(long))))
		{
			return res;
		}
		size = fl_gendisk.part[MINOR(inode->i_rdev)].nr_sects;
		put_user(size, (long *) arg);
		return 0;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
	case BLKRAGET:             // return the readahead value 

		if (!arg)
			return -EINVAL;

		if ((res = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long))))
		{
			return res;
		}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0) /* By Changlae Jo 2.1.0 -> 2.3.0 */
						/* Funny... This kernel doesn't support put_user_ret ^^; */
	/* QQQ no need for verify area? ask SL */
		put_user_ret(read_ahead[MAJOR(inode->i_rdev)], (long *) arg, -EFAULT);
#else
		put_user(read_ahead[MAJOR(inode->i_rdev)], (long *) arg);
#endif
		return 0;
#endif /* < 2.4.0 */

	case HDIO_GETGEO:
		{
			struct hd_geometry *geo = (struct hd_geometry *) arg;
			struct hd_geometry geometry;

			/* check arg */
			if (geo == NULL)
			{
				return -EINVAL;
			}
			if ((res = verify_area(VERIFY_WRITE, geo, sizeof(*geo))))
			{
				return res;
			}
			size = device->size;

			memset(&geometry,0,sizeof(geometry));
			get_drive_geo(size, &geometry);
			geometry.start = fl_gendisk.part[MINOR(inode->i_rdev)].start_sect;

			copy_to_user(geo, &geometry, sizeof(geometry));
			return 0;
		}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	// This is a list of "standard" ioctls.
	// The ones that modify things are protected by a semaphore below.
	case BLKROSET:
	case BLKROGET:
	// case BLKRASET:
	case BLKRAGET:
	// case BLKPG:
		res = blk_ioctl(inode->i_rdev, cmd, arg);
		return(res);
	case BLKPG:      // play with partitions
	case BLKFLSBUF:  // flush
		// This will call the low-level block I/O functions which lock
		// libosak access themselves, so we'd better not do it here.
		res = blk_ioctl(inode->i_rdev, cmd, arg);
		return(res);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)

	case BLKFLSBUF:            // flush 
		fsync_dev(inode->i_rdev);
		invalidate_buffers(inode->i_rdev);
		break;

		// The default RO operations
		RO_IOCTLS(inode->i_rdev, arg);
#endif // >= 2.4.0

	case BLKRRPART:            // re-read partition table
		// This will call the low-level block I/O functions which lock
		// libosak access themselves, so we'd better not do it here.
		res = fl_revalidate(inode->i_rdev);
		return(res);
	}

	// ioctl's that require libosak access (we are modifying something)
	res = 0;
	switch(cmd)
	{
	/* private stuff */
	case FL_IOCTL_GET_INFO:
	case FL_IOCTL_DEFRAGMENT:
	case FL_IOCTL_WRITE_PROTECT:
	case FL_IOCTL_MOUNT_VOLUME:
	case FL_IOCTL_FORMAT_VOLUME:
	case FL_IOCTL_BDK_OPERATION:
	case FL_IOCTL_DELETE_SECTORS:
	case FL_IOCTL_READ_SECTORS:
	case FL_IOCTL_WRITE_SECTORS:
	case FL_IOCTL_FORMAT_PHYSICAL_DRIVE:
	case FL_IOCTL_FORMAT_LOGICAL_DRIVE:
	case FL_IOCTL_BDTL_HW_PROTECTION:
	case FL_IOCTL_BINARY_HW_PROTECTION:
	case FL_IOCTL_OTP:
	case FL_IOCTL_CUSTOMER_ID:
	case FL_IOCTL_UNIQUE_ID:
	case FL_IOCTL_NUMBER_OF_PARTITIONS:
	case FL_IOCTL_INQUIRE_CAPABILITIES:
	case FL_IOCTL_SET_ENVIRONMENT_VARIABLES:
	case FL_IOCTL_PLACE_EXB_BY_BUFFER:
	case FL_IOCTL_WRITE_IPL:
	case FL_IOCTL_DEEP_POWER_DOWN_MODE:
	case FL_IOCTL_EXTENDED_ENVIRONMENT_VARIABLES:
	case FL_IOCTL_VERIFY_VOLUME:
	case FL_IOCTL_SET_ACCESS_ROUTINE:
	case FL_IOCTL_GET_ACCESS_ROUTINE:
	case FL_IOCTL_EXTENDED_WRITE_IPL:
	case FL_IOCTL_LNX:
		if(!FlIoctl(DEVICE_NR(inode->i_rdev), cmd, arg))
		{
			res=-EINVAL;
		}
		break;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)

	case BLKRASET:             // set the readahead value 
		res = blk_ioctl(inode->i_rdev, cmd, arg);
	break;
#else //>= 2.4.0

	case BLKRASET:             // set the readahead value 
		if (arg > 0xff)
		{
			res = -EINVAL;      // limit it 
			break;
		}
		read_ahead[MAJOR(inode->i_rdev)] = arg;
		break;
#endif /* >= 2.4.0 */
	default: // unknown command
		res = -EINVAL; /* ? ENOSYS ? */
	}
	return res;
}

/* Support for removable devices */
int fl_check_change(kdev_t i_rdev)
{
	// TODO
	return 0;
}

int fl_revalidate(kdev_t i_rdev)
{
	// first partition, # of partitions 
	int             part1 = MINOR_NR(DEVICE_NR(i_rdev)) + 1;
	int             npart = MINOR_NR(1) - 1;
	int             dev_nr;

	// first clear old partition information 
	memset(fl_gendisk.sizes + part1, 0, npart * sizeof(int));
	memset(fl_gendisk.part + part1, 0, npart * sizeof(struct hd_struct));

	// then fill new info 
	dev_nr = DEVICE_NR(i_rdev);
	// The partition code eventually wants to read
	// a block and wait for it.  We'd better not lock out the
	// low level routine fl_do1_request() or we're hosed.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	grok_partitions(&fl_gendisk, dev_nr, fl_gendisk.max_p,Fl_devices[dev_nr].size);
				   /* device->size); */
#else
	resetup_one_dev(&fl_gendisk, dev_nr);
#endif
	return 0;
}

/* Block-driver specific functions */
static int fl_do1_request(FL_DEV *device, struct request *req)
{
	/* Do the I/O for _one_ request, but don't mess with the request queues. */
	/* All validity checking done before calling this fn. */

	unsigned int    blocks;
	unsigned int    block;
	void           *buffer;
	int             minor;
	int             dev_nr;
	int fl_last_error=0;

	// get device information
	dev_nr = DEVICE_NR(req->rq_dev);
	minor = MINOR(req->rq_dev);

	// get io information
	blocks = req->current_nr_sectors;
	buffer = req->buffer;
	block = req->sector;

	// translate through partition table
	block += Fl_partitions[minor].start_sect;

	/* iterate doing io until we are done */
	for (; blocks--; block++, buffer += 512)
	{
		// determine if we are READing or WRITEing, then select io type
		switch (req->cmd)
		{
		case READ:
			device->requests_Read++;
			fl_last_error=FlRead(dev_nr, buffer, block, 1);
			if(!fl_last_error)
			{
				return 0;
			}
			break;

		case WRITE:
			device->requests_Write++;
			fl_last_error=FlWrite(dev_nr, buffer, block, 1);
			if(!fl_last_error)
			{
				return 0;
			}
			break;
		}
	}
	return 1;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
void fl_request_fn1(request_queue_t *q)
#else
void fl_request_fn1(void)
#endif	/* < 2.4.0 */
{
	// Move all the requests into the private device queue.
	struct request *req;
	FL_DEV         *device;
	int             minor;
	int             dev_nr;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	int             code;
#endif

	// Standard block device request loop
	//
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	while (code = 0, !QUEUE_EMPTY)
#else
	while (1)
#endif
	{
		// Standard block device boiler plate
		INIT_REQUEST;
		req = CURRENT;

		// get device information
		dev_nr = CURRENT_DEV;
		minor = MINOR(req->rq_dev);

		// Check if the minor number is in range 
		if ((dev_nr > fl_gendisk.nr_real) || (minor > MINOR_NR(fl_gendisk.nr_real)))
		{
			static int      count = 0;
			if (count++ < 5)    // print the message at most five times 
				printk(KERN_WARNING DEVICE_NAME": _request_fn: request for non-existent device\n");
			end_request(0);
			continue;
		}
		// get device structure
		device = Fl_devices + dev_nr;
		device->requests++;
		// check we are in range
		if ((req->sector + req->current_nr_sectors) > fl_gendisk.part[minor].nr_sects)
		{
			printk(KERN_WARNING DEVICE_NAME": _request_fn: request past end of device: %4lx >= %4lx\n",req->sector + req->current_nr_sectors, fl_gendisk.part[minor].nr_sects);
			if (req->sector != 0)
			{
				end_request(0);
				continue;
			}
		}
		// check we have a valid command
		if (req->cmd != READ && req->cmd != WRITE)
		{
			end_request(0);
			continue;
		}

		// Check that there is somebody to handle it :).
		if (NULL == device->sem)
		{
			// Oops, there isn't.
			end_request(0);
			continue;
		}

		if(req->cmd==WRITE && FlGetProtection(DEVICE_NR(req->rq_dev))>=1)
		{
			end_request(0);
			continue;
		}
		if(req->cmd==READ && FlGetProtection(DEVICE_NR(req->rq_dev))>=2)
		{
			end_request(0);
			continue;
		}

		// Move to private list (io_request_lock aquired before calling this request fn).
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
		list_del(&req->queue);
		INIT_LIST_HEAD(&req->queue);
		list_add_tail(&req->queue,&device->queue_head);
#else
		// no easy way to add to tail for singly linked list,
		// so build list in reverse order and flip it when processing starts
		CURRENT = req->next;
		req->next = device->revq;
		device->revq = req;
#endif
		// Make sure the next stage is set up to run.  We can
		// do this more than once because it's a semaphore,
		// and we _must_ do it now because some kernel versions
		// have a function exit inside the INIT_REQUEST macro
		// used above.
		up(device->sem);
	}
}

static struct request*gimme_req(FL_DEV *device)
{
	struct request *req;
	unsigned long flags;

	// Use the same lock that's protecting fl_request_fn1()
	grab_io_request_lock(flags);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	req = list_empty(&device->queue_head) ? NULL :list_entry(device->queue_head.next,struct request,queue);
#else
	if (NULL == (req = device->queue) && NULL != (req = device->revq))
	{
		// There are none on the private queue, but the reversed
		// queue has at least one, flip the reversed queue into
		// the private queue.
		do{
			device->revq = req->next;
			req->next = device->queue;
			device->queue = req;
		}while (NULL != (req = device->revq));
		// Pick up the first request
		req = device->queue;
	}
#endif
	/* A tiny bit of paranoia from INIT_REQ */
	if (NULL != req && NULL != req->bh)
	{
		if (!buffer_locked(req->bh))
			panic(DEVICE_NAME ": block not locked");
	}
	/* End paranoia */
	rlse_io_request_lock(flags);
	return(req);
}

static void request_completed(FL_DEV *device, struct request *req,int result)
{
	// Do the equivalent of end_request(1), but don't use "CURRENT"
	unsigned long flags;
	/* We really don't need to lock just yet, because the request is in
	   our private queue and we won't be returning it to the public free
	   list until _after_ end_that_request_first() returns 0. */
	if (end_that_request_first(req,result,DEVICE_NAME))
	{
		// This request is multi-sector and is not finished yet,
		// the various buffer pointers and counters have been adjusted,
		// but we need another crack at the remaining sectors.
		return;
	}
#if !defined(DEVICE_NO_RANDOM)
	add_blkdev_randomness(MAJOR(req->rq_dev));
#endif
	grab_io_request_lock(flags);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	blkdev_dequeue_request(req);
	INIT_LIST_HEAD(&req->queue);
#else
	device->queue = req->next;
	req->next = NULL;
#endif
	end_that_request_last(req);
	rlse_io_request_lock(flags);
}

/* Note: this runs as a kernel thread without aquired locks, so any list operations must aquire the appropriate lock. (See gimme_req() and request_completed(), above.) */
/* struct task_struct current */
int fl_request_fn2(void *data)
{
	FL_DEV         *device = (FL_DEV *) data;
	struct request *req;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	DECLARE_MUTEX_LOCKED(reqs2do);
#else
	struct semaphore reqs2do = MUTEX_LOCKED;
#endif
	struct semaphore *initterm = device->sem;

	/* Detach as best we can from the process that started us. */
	lock_kernel();
	exit_mm(current);
	exit_files(current);
	exit_fs(current);
	fl_setsid();
	current->exit_signal = SIGCHLD;
	/* QQQ should terminate when we detect 'em.....if we detect 'em */
	siginitsetinv(&current->blocked, SHUTDOWN_SIGS);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	spin_lock(&current->sigmask_lock);
	flush_signals(current);
	spin_unlock(&current->sigmask_lock);
#else
	sigemptyset(&current->signal);
#endif
	/* Name this thread */
	sprintf(current->comm, "msys/fl%c", 'a'+(device-Fl_devices));
	/* Run at a high priority, ahead of sync and friends */
	/* QQQ priority > bdflush??? */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	current->nice = prio;
#else
	current->priority = prio;
#endif
	current->policy = SCHED_OTHER;
	/* Replace the initialization/termination semaphore with our "requests to do" semaphore. */
	device->sem = &reqs2do;
	unlock_kernel();

	/* Let the process that started us know we are ready to roll. */
	up(initterm);
	// Process all requests for this device.
	while (!device->terminating)
	{
		/* Wait for some requests. */
		down(device->sem);
		/* Do 'em. */
		while (NULL != (req = gimme_req(device)))
		{
			request_completed(device,req,fl_do1_request(device,req));
		}
	}
	// Let the process terminating us know we are done. */
	up(initterm);
	return(0);
}

static void kickoff_thread(FL_DEV *device, int (*th_fn)(void *data))
{
	device->terminating = 0;
	device->sem = &start_stop_sem;
	kernel_thread(th_fn,(void *)device,0);
	/* Wait for the function to finish initialization. */
	down(&start_stop_sem);
}

static void killoff_thread(FL_DEV *device)
{
	if (NULL == device->sem || device->terminating)
	{
		/* It's already dead or dying. */
		return;
	}
	/* Tell the thread to quit. */
	device->terminating = 1;
	/* Wake up the function for the last time (if it isn't already awake). */
	up(device->sem);
	/* Wait for the function to finish termination. */
	down(&start_stop_sem);
	/* Make sure no one tries to wake up the function again. */
	device->sem = NULL;
}

#define fl_init init_module

static void fl_geninit(struct gendisk *gendisk)
{
	int mdev;
	FL_DEV*device;
	int	bDevice;

	/* iterate across the per minor device arrays and prepare them */
	for (mdev = 0; mdev < MINOR_NR(gendisk->nr_real); mdev++)
	{
		/* prepare the Fl_blksize_size array and zero it */
		Fl_blksize_size[mdev] = blksize[mdev>>FL_SHIFT];
		/* prepare the Fl_hardsect_size array and zero it */
		Fl_hardsect_size[mdev] = hardsect[mdev>>FL_SHIFT];
		Fl_blk_size[mdev] = 0;
	}

	/* iterate across the flash devices filling in the device information structure */
	for(bDevice=0;bDevice<gendisk->nr_real;bDevice++)
	{
		// we still increment dev
		// so that we can access the underlying device via ioctls for formatting etc
		device = Fl_devices + bDevice;

		/* We have a private queue per physical device, but only one "public" queue for the major number */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
		INIT_LIST_HEAD(&device->queue_head);
#else
		device->queue = device->revq = NULL;
#endif
		kickoff_thread(device,fl_request_fn2);

		/* remove read protection */
		if(bDevice==fl_part_prot)
		{
			if(!FlRemoveProtectionFromParm(fl_part_prot,fl_key_prot))
			{
				printk(KERN_ERR DEVICE_NAME": device %d: Error removing protection.\n",(int)bDevice);
			}
		}
		/* does this DOC volume exist? */
		if(FlMount(bDevice))
		{
			/* start to fill in the required info */
			device->volume = bDevice;
			device->size=FlGetNumberOfSectors(bDevice);

			Fl_blk_size[MINOR_NR(bDevice)] = device->size;
			Fl_blksize_size[MINOR_NR(bDevice)] = 1024;
			Fl_partitions[MINOR_NR(bDevice)].start_sect = 0;
			Fl_partitions[MINOR_NR(bDevice)].nr_sects = 0;
		}
		else
		{
			/* start to fill in the required info */
			device->volume = -1;
			device->size = 0;

			Fl_blk_size[MINOR_NR(bDevice)] = 0;
			Fl_blksize_size[MINOR_NR(bDevice)] = 0;
			Fl_partitions[MINOR_NR(bDevice)].start_sect = 0;
			Fl_partitions[MINOR_NR(bDevice)].nr_sects = 0;  
		}
	}

	/* fill in partition info for whole disks so we can do resetup_one_dev() */
	for (bDevice=0;bDevice<gendisk->nr_real;bDevice++)
	{
		device = Fl_devices+bDevice;
		Fl_partitions[MINOR_NR(bDevice)].nr_sects = device->size; 
	}

	/* fill in the gendisk structure  */
	gendisk->sizes = Fl_blk_size;

	/* set global variables */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	blk_init_queue(BLK_DEFAULT_QUEUE(flMajor),fl_request_fn1);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
	blk_dev[flMajor].request_fn = fl_request_fn1;
#endif	/* >= 2.4.0 */
	read_ahead[flMajor] = Fl_read_ahead;
	blk_size[flMajor] = Fl_blk_size;
	blksize_size[flMajor] = Fl_blksize_size;
	hardsect_size[flMajor] = Fl_hardsect_size;

	// call resetup/register_disk for each physical device
	for(bDevice=0;bDevice<gendisk->nr_real;bDevice++)
	{
		device = Fl_devices+bDevice;
		if (device->size)
		{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
			register_disk(gendisk,MKDEV(flMajor,(bDevice<<gendisk->minor_shift)),gendisk->max_p,gendisk->fops,device->size);
#else
			resetup_one_dev(gendisk,bDevice);
#endif
		}
	}

	// grab blk_size information and spit out some info
	for (mdev = 0; mdev < MINOR_NR(gendisk->nr_real); mdev++)
	{
		Fl_blk_size[mdev] = Fl_partitions[mdev].nr_sects / 2;

		if ((mdev%64 == 0) || (mdev%64 == 1)  || (mdev%64 == 2) || (mdev%64 == 3))
		{
			printk(KERN_INFO DEVICE_NAME": partition: %d: start_sect: %lx, nr_sects: %lx Fl_blk_size[]: %xkb\n",mdev, Fl_partitions[mdev].start_sect, Fl_partitions[mdev].nr_sects, Fl_blk_size[mdev]);
		}
	}

	return;
}

/* DDD Here for debugging
 * print out the size of the request queue when /proc/fla_queue is read. */
static int fl_read_proc(char *buffer, char **start, off_t offset,int length, int *eof, void *data)
{
	int len;
	off_t begin=0;

	len = sprintf(buffer, "ReadQ WriteQ\n%5d %6d\n",Fl_devices->requests_Read,Fl_devices->requests_Write);

	if (len<offset)
	{
		begin = len;
		len=0;
		*eof = 1;
	}
	*start = buffer+(offset-begin);
	len-=(offset-begin);
	if (len < 0)
	{
		len = 0;
	}
	if (len > length)
	{
		len = length;
	}
	return len;
}

#ifdef COTULLA
#define MSC2_CNTR_REG_ADDR 0x48000000
#define MSC2_OFFSET        0x10
#define MCS5_INIT_VAL      0xFFF80000
#endif

/* Init DiskOnChip CS in H/W if needed */
void fl_docCsInit( void )
{
#ifdef COTULLA
  volatile void *myMSC2ptr;
  unsigned long msc2Val;
  
  myMSC2ptr = (volatile void *)ioremap_nocache(MSC2_CNTR_REG_ADDR, 0x1000);
  /* MSC2 Static Memory Control Register - 16-bit mode for CS5 */
  msc2Val = readl( myMSC2ptr + MSC2_OFFSET );
  writel( ((msc2Val & 0x0000FFFF) | MCS5_INIT_VAL), myMSC2ptr + MSC2_OFFSET );
/*  *(myMCS5 + 4) = MCS5_INIT_VAL; */
  iounmap( (void *)myMSC2ptr ); 
#endif
}

/* Initialize the DiskOnChip Memory and Chip Select if needed */
int fl_mapPhysMem( unsigned long winl, unsigned long winh )
{
  unsigned long docSize = FL_DOC_WIN_SIZE + (winh - winl);
  unsigned long docAddr = winl;

  /* Init DiskOnChip CS in H/W if needed */
  fl_docCsInit(); 

#if 0
  if( !request_mem_region( docAddr, docSize, "fla") ) {
    printk(KERN_ERR "fl_mapPhysMem: request region failure %lx\n",docAddr);
    return 1;
  }
#endif
  
  /* Get Mapped address */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) /* By Changlae Jo 2.4.0 -> 2.2.0  */
  						/* Virtual Address ... */
  fl_virl = (void *)ioremap_nocache( bus_to_phys(docAddr), docSize );
#else
  fl_virl = docAddr;
#endif  
  fl_virh = fl_virl + (docSize - FL_DOC_WIN_SIZE);
  return 0;
}

/* Release the DiskOnChip Memory */
void fl_releasePhysMem( unsigned long winl, unsigned long winh )
{
#if 0
  unsigned long docSize = FL_DOC_WIN_SIZE + (winh - winl);
#endif  
  if( fl_virl ) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) /* By Changlae Jo 2.4.0 -> 2.2.0 */
	  					/* Virtual Address */
    iounmap( fl_virl );
#endif    
#if 0    
    release_mem_region( winl, docSize );
#endif    
  } 
}

/* Initialize the driver, register major and link into gendisk_list. */
#if defined(__init)
int __init fl_init(void)
#elif defined(__initfunc)
__initfunc( int fl_init(void))
#else
int fl_init(void)
#endif
{
	int result = 0;
	int i;

#ifdef DEEPDEBUG
	printk(KERN_DEBUG"linuxdrv begin\n");
#endif	/* DEEPDEBUG */

	// Map Physical Memory of the DiskOnChip window
	if( fl_mapPhysMem( fl_winl, fl_winh ) ) {
		printk(KERN_ERR"Cannot Map M-Systems DiskOnChip Memory\n");
		goto fl_init_error;
	}
	
	// Copy the (static) cfg variables to public prefixed ones to allow snoozing with a debugger.
	// major device number
	flMajor = major;		/* 100 by default */
	Fl_read_ahead = rahead;	/* 0 by default */

	// set default blksize and hardsect if they where not passed as module parameters
	for (i = 0; i < MAX_DEVICES; i++)
	{
		if (!blksize[i])
			blksize[i] = 1024;
		if (!hardsect[i])
			hardsect[i] = 512;
	}

	spin_lock_init(&o_c_lock);

	// can we initialize OSAK?
	if(!FlInit())
	{
		printk(KERN_ERR DEVICE_NAME": Cannot initialize M-Systems DOC 2000 OSAK\n");
		goto fl_init_error;
	}

	DebugPrintk("fl_init: FlInit OK\n");

	/* find out how many devices we have */
	if(!(fl_gendisk.nr_real=FlGetNumberOfDevices(MAX_DEVICES)))
	{
		printk(KERN_ERR DEVICE_NAME": Cannot find any M-Systems flash devices\n");
		goto fl_init_error;		
	}
	DebugPrintk("fl_init: Found %d devices\n",fl_gendisk.nr_real);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
	fl_gendisk.max_nr = fl_gendisk.nr_real;  // for the sake of parnoia...
#endif
	// partition array must be allocated here, setup_dev() accesses it before calling fl_genit()
	if (!(Fl_partitions = ckmalloc(MINOR_NR(fl_gendisk.nr_real) * sizeof(struct hd_struct), GFP_KERNEL)))
		goto fl_init_error;

	// allocate per device dynamic structures, these are done here so we can release ckmalloc()
	if (!(Fl_devices = ckmalloc(fl_gendisk.nr_real * sizeof(FL_DEV), GFP_KERNEL)))
		goto fail_malloc;

	// allocate per device * minor blk dynamic arrays
	if (!(Fl_blksize_size = ckmalloc(MINOR_NR(fl_gendisk.nr_real) * sizeof(unsigned int), GFP_KERNEL)))
		goto fail_malloc;

	if (!(Fl_hardsect_size = ckmalloc(MINOR_NR(fl_gendisk.nr_real) * sizeof(unsigned int), GFP_KERNEL)))
		goto fail_malloc;

	if (!(Fl_blk_size = ckmalloc(MINOR_NR(fl_gendisk.nr_real) * sizeof(unsigned int), GFP_KERNEL)))
		goto fail_malloc;

	// setup gendisk structure with partition information
	fl_gendisk.part = Fl_partitions;

	/* ok, we have at least one DOC, proceed */
	printk(KERN_INFO DEVICE_NAME": Flash disk driver for DiskOnChip\n");
	printk(KERN_INFO DEVICE_NAME": DOC device(s) found: %d\n", fl_gendisk.nr_real);

	// Register your major, and/or accept a dynamic number
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	if ((result = devfs_register_blkdev(flMajor,DEVICE_NAME,&fl_fops)) < 0)
#else
	if ((result = register_blkdev(flMajor,DEVICE_NAME,&fl_fops)) < 0)
#endif
	{
		printk(KERN_WARNING DEVICE_NAME": can't get major %d\n",flMajor);
		goto fail_malloc;
	}

	/* save major if we are using a dynamic major */
	if (flMajor == 0)
	{
		flMajor = result;			/* dynamic */
	}

	fl_gendisk.major = flMajor;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	devfs_handle = devfs_mk_dir(NULL, fl_gendisk.major_name, NULL);
	/* DDD this is in here for debugging */
	create_proc_read_entry("fl_queue", 0, 0, fl_read_proc, NULL);
#endif // >= 2.4.0

	printk(KERN_INFO DEVICE_NAME": _init: registered device at major: %d\n",flMajor);

	/* link into global gendisk list */
	fl_gendisk.next = gendisk_head;
	gendisk_head = &fl_gendisk;

	fl_geninit(&fl_gendisk);

	return 0;

fail_malloc:

	// Reset global block array entries
	// QQQ - the first part of this cleanup does not make real sense,
	//       we have not done the setup until fl_geninit(), and we
	//       will not be getting to here once we have called it.
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	blk_cleanup_queue(BLK_DEFAULT_QUEUE(flMajor));
	devfs_unregister_blkdev(flMajor,DEVICE_NAME);
#else
	blk_dev[flMajor].request_fn = NULL;
#endif // >= 2.4.0
	read_ahead[flMajor] = 0;
	blk_size[flMajor] = NULL;
	blksize_size[flMajor] = NULL;
	hardsect_size[flMajor] = NULL;

	// Failed, free any memory that was allocated and set pointers to NULL
	lkfree((void **)&Fl_devices);
	lkfree((void **)&Fl_blksize_size);
	lkfree((void **)&Fl_hardsect_size);
	lkfree((void **)&Fl_blk_size);
	lkfree((void **)&Fl_partitions);

fl_init_error:
    	fl_releasePhysMem( fl_winl, fl_winh );
	return(1);
}

void cleanup_module(void)
{
	int             i;
	struct gendisk**gd;
	int	bDevice;

	/* first of all, flush it all and reset all the data structures */
	for (i = 0; i < MINOR_NR(fl_gendisk.nr_real); i++)
	{
		fsync_dev(MKDEV(flMajor, i));  /* flush the devices */
	}

	/* Terminate the request threads. */
	for (i = 0; i < fl_gendisk.nr_real; i++)
	{
		 killoff_thread(Fl_devices+i);
	}

	for(bDevice=0;bDevice<fl_gendisk.nr_real;bDevice++)
	{
		FlDismount(bDevice);
	}

	/* remove our gendisk structure from the global linked list */
	for (gd = &gendisk_head; *gd; gd = &((*gd)->next))
	{
		if (*gd == &fl_gendisk)
		{
			*gd = (*gd)->next;
			break;
		}
	}

	// complain if we didn't find ourselves in gendisk list
	if (!*gd)
	{
		printk(KERN_ERR DEVICE_NAME": cleanup_module: entry in gendisk chain missing!\n" );
	}

	// reset global blk array entries
	read_ahead[flMajor] = 0;
	blk_size[flMajor] = NULL;
	blksize_size[flMajor] = NULL;
	hardsect_size[flMajor] = NULL;

	// unregister our device
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	devfs_unregister_blkdev(flMajor, DEVICE_NAME);
	blk_cleanup_queue(BLK_DEFAULT_QUEUE(flMajor));
	devfs_unregister(devfs_handle);
	devfs_handle = NULL;
	// DDD again, this is for debugging
	remove_proc_entry("fl_queue", 0);
#else
	blk_dev[flMajor].request_fn = NULL;
	unregister_blkdev(flMajor, DEVICE_NAME);
#endif	/* >= 2.4.0 */

	/* free our data structures */
	lkfree((void **)&Fl_blk_size);
	lkfree((void **)&Fl_blksize_size);
	lkfree((void **)&Fl_hardsect_size);
	lkfree((void **)&Fl_devices);
	lkfree((void **)&Fl_partitions);
	printk(KERN_INFO DEVICE_NAME": Driver removed.\n");
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)

static void spin_lock_init(spinlock_t *splp)
{
	splp->lock = 0;
}

static void spin_lock(spinlock_t *splp)
{
	/* spin on bit lock */
	while (set_bit(0,&(splp->lock)) != 0)/* spin */;
}

static void spin_unlock(spinlock_t *splp)
{
	/* unlock simple bit lock */
	if (clear_bit(0,&(splp->lock)) == 0)
	{
		printk(KERN_ERR DEVICE_NAME": spin_unlock() failed\n");
	}
}

/* Signals */

static void siginitsetinv(sigset_t *set, unsigned long mask)
{
	*set = ~mask;
}

static void sigemptyset(sigset_t *set)
{
	*set = 0;
}

/* copy user space to/from kernel space */

static inline unsigned long copy_to_user(void *to,const void *from, unsigned long n)
{
	int i;
	if ((i = verify_area(VERIFY_WRITE, to, n)) != 0)
		return i;
	memcpy_tofs(to, from, n);
	return 0;
}

static inline unsigned long copy_from_user(void *to,const void *from, unsigned long n)
{
	int i;
	if ((i = verify_area(VERIFY_READ, from, n)) != 0)
	{
		return i;
	}
	memcpy_fromfs(to, from, n);
	return 0;
}

/* I/O requests and locking */

static int end_that_request_first(struct request *req,int uptodate,char *devname)
{
	struct buffer_head * bh;
	int nsect;

	req->errors = 0;
	if (!uptodate)
	{
		printk(KERN_ERR DEVICE_NAME": msys/fl/end_request_1st: I/O error, dev %s (%s), sector %lu\n",kdevname(req->rq_dev), devname, req->sector);
		if (NULL != (bh = req->bh))
		{
			nsect = bh->b_size >> 9;
			req->nr_sectors--;
			req->nr_sectors &= ~(nsect - 1);
			req->sector += nsect;
			req->sector &= ~(nsect - 1);
		}
	}

	if (NULL != (bh = req->bh))
	{
		req->bh = bh->b_reqnext;
		bh->b_reqnext = NULL;

		mark_buffer_uptodate(bh, uptodate);
		unlock_buffer(bh);
		if (NULL != (bh = req->bh))
		{
			req->current_nr_sectors = bh->b_size >> 9;
			if (req->nr_sectors < req->current_nr_sectors)
			{
				req->nr_sectors = req->current_nr_sectors;
				printk(KERN_ERR DEVICE_NAME": msys/fl/end_request_1st: buffer-list destroyed\n");
			}
			req->buffer = bh->b_data;
			return 1;
		}
	}
	return 0;
}

static void end_that_request_last(struct request *req)
{
	if (NULL != req->sem)
	{
		up(req->sem);
	}
	req->rq_status = RQ_INACTIVE;
	wake_up(&wait_for_request);
}

#endif	/* LINUX_VERSION_CODE < 2.1.0 */

/*****************	EXPORTED TO TRUEFFS	*****************/

short GetFlDebugParam(void)
{
	return fl_debug;
}

void GetFlParams(unsigned long*p_fl_winl,unsigned long*p_fl_winh,
	unsigned char*p_fl_is_ram_check,unsigned char*p_fl_nftl_cache,
	unsigned char*p_fl_8bit_access,unsigned long*p_fl_mtd_bus_access_type,
	unsigned long*p_fl_verify_write_bdtl,unsigned long*p_fl_verify_write_binary,
	unsigned long*p_fl_verify_write_other,unsigned long*p_fl_sectors_verified_per_folding)
{
	*p_fl_winl=(unsigned long)fl_virl;
	*p_fl_winh=(unsigned long)fl_virh;
	*p_fl_is_ram_check=fl_is_ram_check;
	*p_fl_nftl_cache=fl_nftl_cache;
	*p_fl_8bit_access=fl_8bit_access;
	*p_fl_mtd_bus_access_type=fl_mtd_bus_access_type;
	*p_fl_verify_write_bdtl=fl_verify_write_bdtl;
	*p_fl_verify_write_binary=fl_verify_write_binary;
	*p_fl_verify_write_other=fl_verify_write_other;
	*p_fl_sectors_verified_per_folding=fl_sectors_verified_per_folding;
}

/* The following are the Linux versions of various things required by the M-Systems OSAK that must be compiled here in the Linux kernel environment. */
void flDelayMsecs(unsigned milliseconds)
{
	register int    i;
	for (i = 0; i < milliseconds; i++)
		udelay(1000L);
}

void flsleep(unsigned long milliseconds)
{
	unsigned long deltaj;
	/* Timeout has to be expressed in jiffies, there are HZ jiffies/sec. 
	We add one to it anyway, because the "first" jiffy is really some
	random fraction of a jiffy. */
	deltaj = 1 + ((HZ * milliseconds) / 1000);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0)
	/* QQQ: why TASK_UNINTERRUPTIBLE? */
	current->state = TASK_UNINTERRUPTIBLE;
	while ((deltaj = schedule_timeout(deltaj)) > 0) /* skip */ ;
#else
	current->timeout = jiffies + deltaj;
	current->state = TASK_INTERRUPTIBLE; /* uninterruptible => no timeout ! */
	schedule();
	current->timeout = 0;
#endif
}

void *flmemcpy(void *dest, const void *src, size_t length)
{
	return(memcpy(dest, src, length));
}

void *flmemset(void *dest, int value, unsigned int length)
{
	return(memset(dest, value, length));
}

int flmemcmp(const void * dest,const void  *src,size_t count)
{
	return(memcmp(dest, src, count));
}

/*
 * Do not use isa_xxx() functions in order to access memory because
 * they are x86 specific and doesn't get proper virtual address from MMU
 * Instead use the pointer from ioremap_nocache(physAddr) with usual access
 * functions.
 */
void flmemcpy_fromio(void *dest, const void * src, unsigned int length)
{
/*#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	isa_memcpy_fromio(dest, (unsigned long)src, length);
#else
	memcpy_fromio(dest, src, length);
#endif
*/
	memcpy_fromio(dest, src, length);
}

void flmemcpy_toio(void *dest, const void * src, unsigned int length)
{
/*#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	isa_memcpy_toio((unsigned long)dest, src, length);
#else
	memcpy_toio(dest, src, length);
#endif
*/
	memcpy_toio(dest, src, length);
}

void flmemset_io(void *dest, int value, unsigned int length)
{
/*#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	isa_memset_io((unsigned long)dest, value, length);
#else
	memset_io(dest, value, length);
#endif
*/
	memset_io(dest, value, length);
}

void *flmalloc(unsigned long size)
{
	return vmalloc(size);
}

void flfree(void*addr)
{
	vfree(addr);
}

void FlDebugPrint(char*str)
{
	if(GetFlDebugParam())
	{
		LOG_DELAY;
		printk(KERN_DEBUG DEVICE_NAME": %s",str);
	}
}

void FlErrorPrint(char*str,int stat)
{
	LOG_DELAY;
	printk(KERN_DEBUG DEVICE_NAME": Error %s: %d\n",str,stat);
}

#ifdef DEEPDEBUG
int FlPrintk(const char *fmt, ...)
{
	va_list args;
	char buf[1024];     // hopefully enough, kernel/printk.c thinks so
	int i;

	va_start(args, fmt);
	i = vsprintf(buf, fmt, args); 
	va_end(args);
	// make sure we have terminated the string
	buf[i--] = '\0';

	// libosak uses \n\r, this make kerneld unhappy
	if (buf[i-1] == '\n' && buf[i] == '\r')
		buf[i--] = '\0';
	LOG_DELAY;
	printk(KERN_DEBUG"fl :%s",buf);
	return(i);
}

void FlLogData(unsigned short*wBuffPtr,unsigned short wLen,unsigned char fWrite)
{
	unsigned short w=0;

	LOG_DELAY;
	if(fWrite)
	{
		printk(KERN_DEBUG DEVICE_NAME": Write %u words\n",wLen);
	}
	else
	{
		printk(KERN_DEBUG DEVICE_NAME": Read %u words\n",wLen);
	}

	LOG_DELAY;
	while(w<wLen)
	{
		unsigned short w1;

		for(w1=0;w1<16&&w<wLen;w1++,w++)
		{
			printk("%4.4x ",wBuffPtr[w]);
		}
		printk("\n");
	}
}
#endif	/* DEEPDEBUG */

unsigned char flreadb(volatile void *addr)
{
	return(readb(addr));
}

unsigned short flreadw(volatile void *addr)
{
	return(readw(addr));
}

unsigned long flreadl(volatile void *addr)
{
	return(readl(addr));
}

void flwriteb(unsigned char value, volatile void *addr)
{
	writeb(value, addr);
}

void flwritew(unsigned short value, volatile void*addr)
{
	writew(value, addr);
}

void flwritel(unsigned long value, volatile void *addr)
{
	writel(value, addr);
}

unsigned int flRandByte(void)
{
	struct timeval time;
	do_gettimeofday(&time);
	return (time.tv_usec >> 1) & 0xff;
}

void *flAddLongToFarPointer(void*ptr, unsigned long offset)
{
	return (void*)(((unsigned long)(ptr)) + offset);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static struct semaphore osak_sem;
#else
static struct semaphore osak_sem=MUTEX_LOCKED;
#endif

void flSysfunInit(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
	sema_init(&osak_sem,1);
#endif
}

int flCreateMutex(int*mutex)
{
	up(&osak_sem);
	return 0;
}

void flDeleteMutex(int*mutex)
{
	up(&osak_sem);
}

int flTakeMutex(int*mutex)
{
	down(&osak_sem);
	return 1;
}

void flFreeMutex(int*mutex)
{
	up(&osak_sem);
}
