NetBSD-5.0.2/dist/iscsi/src/so.c

Compare this file to the similar file:
Show the results in this format:


/*
 * IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. By downloading, copying, installing or
 * using the software you agree to this license. If you do not agree to this license, do not download, install,
 * copy or use the software. 
 *
 * Intel License Agreement 
 *
 * Copyright (c) 2002, Intel Corporation
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that
 * the following conditions are met: 
 *
 * -Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *  following disclaimer. 
 *
 * -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *  following disclaimer in the documentation and/or other materials provided with the distribution. 
 *
 * -The name of Intel Corporation may not be used to endorse or promote products derived from this software
 *  without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE. 
 */

/*
 * Linux SCSI upper layer driver for OSD
 */


#include <linux/config.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/hdreg.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/smp.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/blk.h>
#include <linux/blkpg.h>
#include "scsi.h"
#include "hosts.h"
#include "so.h"
#include <scsi/scsi_ioctl.h>
#include "constants.h"
#include <scsi/scsicam.h>	
#include <linux/genhd.h>
#include "iscsiutil.h"
#include "osd.h"

/*
 * Macros
 */

#define SCSI_OSD_MAJOR          232                 /* major.h */
#define MAJOR_NR                SCSI_OSD_MAJOR      /* hosts.h */
#define DEVICE_NAME             "scsiosd"           /* blk.h   */
#define TIMEOUT_VALUE           (2*HZ)              /* blk.h   */
#define DEVICE_NR(device)       MINOR(device)       /* blk.h   */
#define SCSI_OSDS_PER_MAJOR     256
#define MAX_RETRIES             5
#define SO_TIMEOUT              (30 * HZ)
#define SO_MOD_TIMEOUT          (75 * HZ)

/*
 * Globals
 */

struct hd_struct *so;
static Scsi_Osd *rscsi_osds;
static int *so_sizes;
static int *so_blocksizes;
static int *so_hardsizes;
static int check_scsiosd_media_change(kdev_t);
static int so_init_oneosd(int);

/*
 * Function prototypes
 */

static int so_init(void);
static void so_finish(void);
static int so_attach(Scsi_Device *);
static int so_detect(Scsi_Device *);
static void so_detach(Scsi_Device *);
static int so_init_command(Scsi_Cmnd *);
static void rw_intr(Scsi_Cmnd * SCpnt);
static int fop_revalidate_scsiosd(kdev_t);
static int revalidate_scsiosd(kdev_t dev, int maxusage);


/*
 * Templates
 */

static struct Scsi_Device_Template so_template = {
	name:		"osd",
	tag:		"so",
	scsi_type:	TYPE_OSD,
	major:		SCSI_OSD_MAJOR,
	blk:		1,
	detect:		so_detect,
	init:		so_init,
	finish:		so_finish,
	attach:		so_attach,
	detach:		so_detach,
	init_command:	so_init_command,
};

/*
 * Functions
 */

static int so_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg)
{
	kdev_t dev = inode->i_rdev;
	struct Scsi_Host * host;
	Scsi_Device * SDev;
	int osdinfo[4];

	iscsi_trace(TRACE_OSDSO, "so_ioctl()\n");
    
	SDev = rscsi_osds[DEVICE_NR(dev)].device;
	/*
	 * If we are in the middle of error recovery, don't let anyone
	 * else try and use this device.  Also, if error recovery fails, it
	 * may try and take the device offline, in which case all further
	 * access to the device is prohibited.
	 */

	if( !scsi_block_when_processing_errors(SDev) )
	{
		return -ENODEV;
	}

	switch (cmd) 
	{
		case HDIO_GETGEO:   /* Return BIOS osd parameters */
		{
			struct hd_geometry *loc = (struct hd_geometry *) arg;
			if(!loc)
				return -EINVAL;

			host = rscsi_osds[DEVICE_NR(dev)].device->host;
	
			/* default to most commonly used values */
	
		        osdinfo[0] = 0x40;
	        	osdinfo[1] = 0x20;
	        	osdinfo[2] = rscsi_osds[DEVICE_NR(dev)].capacity >> 11;
	
			/* override with calculated, extended default, or driver values */
	
#if 0
			if(host->hostt->bios_param != NULL)
				host->hostt->bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]);
			else scsicam_bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]);

			if (put_user(osdinfo[0], &loc->heads) ||
				put_user(osdinfo[1], &loc->sectors) ||
				put_user(osdinfo[2], &loc->cylinders) ||
				put_user(so[MINOR(inode->i_rdev)].start_sect, &loc->start))
				return -EFAULT;
			return 0;
#else
			return -1;
#endif
		}
		case HDIO_GETGEO_BIG:
		{
			struct hd_big_geometry *loc = (struct hd_big_geometry *) arg;

			if(!loc)
				return -EINVAL;

			host = rscsi_osds[DEVICE_NR(dev)].device->host;

			/* default to most commonly used values */

			osdinfo[0] = 0x40;
			osdinfo[1] = 0x20;
			osdinfo[2] = rscsi_osds[DEVICE_NR(dev)].capacity >> 11;

			/* override with calculated, extended default, or driver values */

#if 0
			if(host->hostt->bios_param != NULL)
				host->hostt->bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]);
			else scsicam_bios_param(&rscsi_osds[DEVICE_NR(dev)], dev, &osdinfo[0]);

			if (put_user(osdinfo[0], &loc->heads) ||
				put_user(osdinfo[1], &loc->sectors) ||
				put_user(osdinfo[2], (unsigned int *) &loc->cylinders) ||
				put_user(so[MINOR(inode->i_rdev)].start_sect, &loc->start))
				return -EFAULT;
			return 0;
#else
			return -1;
#endif
		}
		case BLKGETSIZE:   /* Return device size */
			if (!arg)
				return -EINVAL;
			return put_user(so[MINOR(inode->i_rdev)].nr_sects, (long *) arg);

		case BLKROSET:
		case BLKROGET:
		case BLKRASET:
		case BLKRAGET:
		case BLKFLSBUF:
		case BLKSSZGET:
		case BLKPG:
                case BLKELVGET:
                case BLKELVSET:
			return blk_ioctl(inode->i_rdev, cmd, arg);

		case BLKRRPART: /* Re-read partition tables */
		        if (!capable(CAP_SYS_ADMIN))
		                return -EACCES;
			return revalidate_scsiosd(dev, 1);

		default:
			return scsi_ioctl(rscsi_osds[DEVICE_NR(dev)].device , cmd, (void *) arg);
	}
}

static void so_devname(unsigned int index, char *buffer) {
  iscsi_trace(TRACE_OSDSO, "so_devname(%i)\n", index);
  sprintf(buffer, "so%i", index);
}

static request_queue_t *so_find_queue(kdev_t dev)
{
	Scsi_Osd *dpnt;
 	int target;

	iscsi_trace(TRACE_OSDSO, "so_find_queue()\n");

 	target = DEVICE_NR(dev);

	dpnt = &rscsi_osds[target];
	if (!dpnt) {
		iscsi_trace_error("no such device\n");
		return NULL;
	}
	return &dpnt->device->request_queue;
}

static int so_init_command(Scsi_Cmnd * SCpnt)
{
	int block, this_count;
	Scsi_Osd *dpnt;
	char nbuff[6];
	osd_args_t args;
	int index;

	iscsi_trace(TRACE_OSDSO, "so_init_command(MAJOR %i, MINOR %i)\n", 
          MAJOR(SCpnt->request.rq_dev), MINOR(SCpnt->request.rq_dev));
        index = MINOR(SCpnt->request.rq_dev);
        so_devname(index, nbuff);
        block = SCpnt->request.sector;
        this_count = SCpnt->request_bufflen >> 9;
        dpnt = &rscsi_osds[index];

        if (index >= so_template.dev_max || !dpnt || !dpnt->device->online ||
            block + SCpnt->request.nr_sectors > so[index].nr_sects) {
          iscsi_trace_error("index %i: request out of range: %i offset + %li count > %li total sectors\n",
                      index, block, SCpnt->request.nr_sectors, so[index].nr_sects);
          return 0;
        }

        block += so[index].start_sect;
        if (dpnt->device->changed) {
          iscsi_trace_error("SCSI osd has been changed. Prohibiting further I/O\n");
          return 0;
        }

	switch (SCpnt->request.cmd) {
	case WRITE:

                iscsi_trace(TRACE_OSDSO, "Translating BLOCK WRITE to OBJECT WRITE\n");
                if (!dpnt->device->writeable) {
                        iscsi_trace_error("device is not writable\n");
                        return 0;
                }
                iscsi_trace(TRACE_OSDSO, "Translating BLOCK WRITE (sector %i, len %i) to OBJECT WRITE\n", block, this_count);
                memset(&args, 0, sizeof(osd_args_t));
                args.opcode = 0x7f;
                args.add_cdb_len = CONFIG_OSD_CDB_LEN-7;
                args.service_action = OSD_WRITE;
                args.GroupID = 0; args.UserID = 0;
                args.length = 512*this_count;
                args.offset = 512*block;
                OSD_ENCAP_CDB(&args, SCpnt->cmnd);
                SCpnt->sc_data_direction = SCSI_DATA_WRITE;
                SCpnt->cmd_len = CONFIG_OSD_CDB_LEN;
		SCpnt->result = 0;
                break;

	case READ:

                iscsi_trace(TRACE_OSDSO, "Translating BLOCK READ (sector %i, len %i) to OBJECT READ\n", block, this_count);
                memset(&args, 0, sizeof(osd_args_t));
                args.opcode = 0x7f;
                args.add_cdb_len = CONFIG_OSD_CDB_LEN-7;
                args.service_action = OSD_READ;
                args.GroupID = 0; args.UserID = 0;
                args.length = 512*this_count;
                args.offset = 512*block;
                OSD_ENCAP_CDB(&args, SCpnt->cmnd);
                SCpnt->sc_data_direction = SCSI_DATA_READ;
                SCpnt->cmd_len = CONFIG_OSD_CDB_LEN;
		SCpnt->result = 0;
                break;

	default:
		panic("Unknown so command %d\n", SCpnt->request.cmd);
	}

        /*
	 * We shouldn't disconnect in the middle of a sector, so with a dumb
	 * host adapter, it's safe to assume that we can at least transfer
	 * this many bytes between each connect / disconnect.
	 */

	SCpnt->transfersize = dpnt->device->sector_size;
	SCpnt->underflow = this_count << 9;
	SCpnt->allowed = MAX_RETRIES;
	SCpnt->timeout_per_command = (SCpnt->device->type == TYPE_OSD ?
				      SO_TIMEOUT : SO_MOD_TIMEOUT);

	/*
	 * This is the completion routine we use.  This is matched in terms
	 * of capability to this function.
	 */

	SCpnt->done = rw_intr;

	/*
	 * This indicates that the command is ready from our end to be
	 * queued.
	 */

	return 1;
}

static int so_open(struct inode *inode, struct file *filp)
{
	int target;
	Scsi_Device * SDev;
	target = DEVICE_NR(inode->i_rdev);

	iscsi_trace(TRACE_OSDSO, "so_open()\n");

	SCSI_LOG_HLQUEUE(1, printk("target=%d, max=%d\n", target, so_template.dev_max));

	if (target >= so_template.dev_max || !rscsi_osds[target].device)
		return -ENXIO;	/* No such device */

	/*
	 * If the device is in error recovery, wait until it is done.
	 * If the device is offline, then disallow any access to it.
	 */
	if (!scsi_block_when_processing_errors(rscsi_osds[target].device)) {
		return -ENXIO;
	}
	/*
	 * Make sure that only one process can do a check_change_osd at one time.
	 * This is also used to lock out further access when the partition table
	 * is being re-read.
	 */

	while (rscsi_osds[target].device->busy)
		barrier();
	if (rscsi_osds[target].device->removable) {
		check_disk_change(inode->i_rdev);

		/*
		 * If the drive is empty, just let the open fail.
		 */
		if (!rscsi_osds[target].ready)
			return -ENXIO;

		/*
		 * Similarly, if the device has the write protect tab set,
		 * have the open fail if the user expects to be able to write
		 * to the thing.
		 */
		if ((rscsi_osds[target].write_prot) && (filp->f_mode & 2))
			return -EROFS;
	}
	SDev = rscsi_osds[target].device;
	/*
	 * It is possible that the osd changing stuff resulted in the device
	 * being taken offline.  If this is the case, report this to the user,
	 * and don't pretend that
	 * the open actually succeeded.
	 */
	if (!SDev->online) {
		return -ENXIO;
	}
	/*
	 * See if we are requesting a non-existent partition.  Do this
	 * after checking for osd change.
	 */
	if (so_sizes[MINOR(inode->i_rdev)] == 0)
		return -ENXIO;

	if (SDev->removable)
		if (!SDev->access_count)
			if (scsi_block_when_processing_errors(SDev))
				scsi_ioctl(SDev, SCSI_IOCTL_DOORLOCK, NULL);

	SDev->access_count++;
	if (SDev->host->hostt->module)
		__MOD_INC_USE_COUNT(SDev->host->hostt->module);
	if (so_template.module)
		__MOD_INC_USE_COUNT(so_template.module);
	return 0;
}

static int so_release(struct inode *inode, struct file *file)
{
	int target;
	Scsi_Device * SDev;

	iscsi_trace(TRACE_OSDSO, "so_release()\n");

	target = DEVICE_NR(inode->i_rdev);
	SDev = rscsi_osds[target].device;

	SDev->access_count--;

	if (SDev->removable) {
		if (!SDev->access_count)
			if (scsi_block_when_processing_errors(SDev))
				scsi_ioctl(SDev, SCSI_IOCTL_DOORUNLOCK, NULL);
	}
	if (SDev->host->hostt->module)
		__MOD_DEC_USE_COUNT(SDev->host->hostt->module);
	if (so_template.module)
		__MOD_DEC_USE_COUNT(so_template.module);
	return 0;
}

static struct block_device_operations so_fops =
{
	open:			so_open,
	release:		so_release,
	ioctl:			so_ioctl,
	check_media_change:	check_scsiosd_media_change,
	revalidate:		fop_revalidate_scsiosd
};

/*
 *  If we need more than one SCSI osd major (i.e. more than
 *  16 SCSI osds), we'll have to kmalloc() more gendisks later.
 */

static struct gendisk so_gendisk =
{
	SCSI_OSD_MAJOR,    	/* Major number */
	"so",			/* Major name */
	0,			/* Bits to shift to get real from partition */
	1,			/* Number of partitions per real */
	NULL,			/* hd struct */
	NULL,			/* block sizes */
	0,			/* number */
	NULL,			/* internal */
	NULL,			/* next */
        &so_fops,		/* file operations */
};

/*
 * rw_intr is the interrupt routine for the device driver.
 * It will be notified on the end of a SCSI read / write, and
 * will take one of several actions based on success or failure.
 */

static void rw_intr(Scsi_Cmnd * SCpnt)
{
	int result = SCpnt->result;
	char nbuff[6];
	int this_count = SCpnt->bufflen >> 9;
	int good_sectors = (result == 0 ? this_count : 0);
	int block_sectors = 1;

	so_devname(DEVICE_NR(SCpnt->request.rq_dev), nbuff);
        iscsi_trace(TRACE_OSDSO, "rw_intr(/dev/%s, host %d, result 0x%x)\n", nbuff, SCpnt->host->host_no, result);

	/*
	   Handle MEDIUM ERRORs that indicate partial success.  Since this is a
	   relatively rare error condition, no care is taken to avoid
	   unnecessary additional work such as memcpy's that could be avoided.
	 */

	/* An error occurred */
	if (driver_byte(result) != 0) {
		/* Sense data is valid */
		if (SCpnt->sense_buffer[0] == 0xF0 && SCpnt->sense_buffer[2] == MEDIUM_ERROR) {
			long error_sector = (SCpnt->sense_buffer[3] << 24) |
			(SCpnt->sense_buffer[4] << 16) |
			(SCpnt->sense_buffer[5] << 8) |
			SCpnt->sense_buffer[6];
			if (SCpnt->request.bh != NULL)
				block_sectors = SCpnt->request.bh->b_size >> 9;
			switch (SCpnt->device->sector_size) {
			case 1024:
				error_sector <<= 1;
				if (block_sectors < 2)
					block_sectors = 2;
				break;
			case 2048:
				error_sector <<= 2;
				if (block_sectors < 4)
					block_sectors = 4;
				break;
			case 4096:
				error_sector <<=3;
				if (block_sectors < 8)
					block_sectors = 8;
				break;
			case 256:
				error_sector >>= 1;
				break;
			default:
				break;
			}
			error_sector -= so[MINOR(SCpnt->request.rq_dev)].start_sect;
			error_sector &= ~(block_sectors - 1);
			good_sectors = error_sector - SCpnt->request.sector;
			if (good_sectors < 0 || good_sectors >= this_count)
				good_sectors = 0;
		}
		if (SCpnt->sense_buffer[2] == ILLEGAL_REQUEST) {
			if (SCpnt->device->ten == 1) {
				if (SCpnt->cmnd[0] == READ_10 ||
				    SCpnt->cmnd[0] == WRITE_10)
					SCpnt->device->ten = 0;
			}
		}
	}
	/*
	 * This calls the generic completion function, now that we know
	 * how many actual sectors finished, and how many sectors we need
	 * to say have failed.
	 */
	scsi_io_completion(SCpnt, good_sectors, block_sectors);
}
/*
 * requeue_so_request() is the request handler function for the so driver.
 * Its function in life is to take block device requests, and translate
 * them to SCSI commands.
 */


static int check_scsiosd_media_change(kdev_t full_dev)
{
	int retval;
	int target;
	int flag = 0;
	Scsi_Device * SDev;

	iscsi_trace(TRACE_OSDSO, "check_scsiosd_media_change()\n");

	target = DEVICE_NR(full_dev);
	SDev = rscsi_osds[target].device;

	if (target >= so_template.dev_max || !SDev) {
		printk("SCSI osd request error: invalid device.\n");
		return 0;
	}
	if (!SDev->removable)
		return 0;

	/*
	 * If the device is offline, don't send any commands - just pretend as
	 * if the command failed.  If the device ever comes back online, we
	 * can deal with it then.  It is only because of unrecoverable errors
	 * that we would ever take a device offline in the first place.
	 */
	if (SDev->online == FALSE) {
		rscsi_osds[target].ready = 0;
		SDev->changed = 1;
		return 1;	/* This will force a flush, if called from
				 * check_disk_change */
	}

	/* Using Start/Stop enables differentiation between drive with
	 * no cartridge loaded - NOT READY, drive with changed cartridge -
	 * UNIT ATTENTION, or with same cartridge - GOOD STATUS.
	 * This also handles drives that auto spin down. eg iomega jaz 1GB
	 * as this will spin up the drive.
	 */
	retval = -ENODEV;
	if (scsi_block_when_processing_errors(SDev))
		retval = scsi_ioctl(SDev, SCSI_IOCTL_START_UNIT, NULL);

	if (retval) {		/* Unable to test, unit probably not ready.
				 * This usually means there is no disc in the
				 * drive.  Mark as changed, and we will figure
				 * it out later once the drive is available
				 * again.  */

		rscsi_osds[target].ready = 0;
		SDev->changed = 1;
		return 1;	/* This will force a flush, if called from
				 * check_disk_change */
	}
	/*
	 * for removable scsi osd ( FLOPTICAL ) we have to recognise the
	 * presence of osd in the drive. This is kept in the Scsi_Osd
	 * struct and tested at open !  Daniel Roche ( dan@lectra.fr )
	 */

	rscsi_osds[target].ready = 1;	/* FLOPTICAL */

	retval = SDev->changed;
	if (!flag)
		SDev->changed = 0;
	return retval;
}

static int so_init_oneosd(int i) {
	unsigned char cmd[10];
	char nbuff[6];
	Scsi_Request *SRpnt;

	iscsi_trace(TRACE_OSDSO, "so_init_oneosd(%i)\n", i);

	so_devname(i, nbuff);
	if (rscsi_osds[i].device->online == FALSE) {
		iscsi_trace_error("device is offline??\n");
		return i;
	}

        /* 
         * TEST_UNIT_READY
         */

	SRpnt = scsi_allocate_request(rscsi_osds[i].device);
	cmd[0] = TEST_UNIT_READY;
	cmd[1] = (rscsi_osds[i].device->lun << 5) & 0xe0;
	memset((void *) &cmd[2], 0, 8);
	SRpnt->sr_cmd_len = 0;
	SRpnt->sr_sense_buffer[0] = 0;
	SRpnt->sr_sense_buffer[2] = 0;
	SRpnt->sr_data_direction = SCSI_DATA_READ;
	scsi_wait_req (SRpnt, (void *) cmd, NULL, 0, SO_TIMEOUT, MAX_RETRIES);
        if (SRpnt->sr_result!=0) {
		iscsi_trace_error("OSD not ready\n");
		return i;
        }

	/* Initialize device */

	rscsi_osds[i].capacity = 1048576*512;
	rscsi_osds[i].device->changed = 0;
	rscsi_osds[i].ready = 0;
	rscsi_osds[i].write_prot = 0;
	rscsi_osds[i].device->removable = 0;
	SRpnt->sr_device->ten = 1;
	SRpnt->sr_device->remap = 1;
	SRpnt->sr_device->sector_size = 512;

	/* printk("%s : block size assumed to be 512 bytes, osd size 1GB.  \n", nbuff); */

	/* Wake up a process waiting for device */

	scsi_release_request(SRpnt);  

	/* Cleanup */

	SRpnt = NULL;
	return i;
}

/*
 * The so_init() function looks at all SCSI drives present, determines
 * their size, and reads partition table entries for them.
 */

static int so_registered;

static int so_init() {
        int i;

	iscsi_trace(TRACE_OSDSO, "so_init()\n");

        if (so_template.dev_noticed == 0) {
          iscsi_trace_error("no OSDs noticed\n");
          return 0;
        }
        if (!rscsi_osds) {
          printf("%i osds detected \n", so_template.dev_noticed);
          so_template.dev_max = so_template.dev_noticed;
        }
        if (so_template.dev_max > SCSI_OSDS_PER_MAJOR) {
          iscsi_trace_error("so_template.dev_max (%i) > SCSI_OSDS_PER_MAJOR\n", so_template.dev_max);
          so_template.dev_max = SCSI_OSDS_PER_MAJOR;
        }
        if (!so_registered) {
          if (devfs_register_blkdev(SCSI_OSD_MAJOR, "so", &so_fops)) {
            printk("Unable to get major %d for SCSI osd\n", SCSI_OSD_MAJOR);
            return 1;
          }
          so_registered++;
        }

	/* No loadable devices yet */

        if (rscsi_osds) return 0;

        /* Real devices */

        if ((rscsi_osds = kmalloc(so_template.dev_max * sizeof(Scsi_Osd), GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_devfs;
        }
        memset(rscsi_osds, 0, so_template.dev_max * sizeof(Scsi_Osd));
        so_gendisk.real_devices = (void *) rscsi_osds;

        /* Partition sizes */

        if ((so_sizes=kmalloc(so_template.dev_max*sizeof(int), GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_rscsi_osds;
        }
        memset(so_sizes, 0, so_template.dev_max*sizeof(int));
        so_gendisk.sizes = so_sizes;

        /* Block sizes */

        if ((so_blocksizes=kmalloc(so_template.dev_max*sizeof(int), GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_so_sizes;
        }
        blksize_size[SCSI_OSD_MAJOR] = so_blocksizes;

        /* Sector sizes */

        if ((so_hardsizes=kmalloc(so_template.dev_max*sizeof(int), GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_so_blocksizes;
        }
        hardsect_size[SCSI_OSD_MAJOR] = so_hardsizes;

        /* Partitions */

        if ((so=kmalloc(so_template.dev_max*sizeof(struct hd_struct), GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_so_hardsizes;
        }
        memset(so, 0, so_template.dev_max*sizeof(struct hd_struct));
        so_gendisk.part = so;

        /* Initialize Things */

        for (i=0; i<so_template.dev_max; i++) {
          so_blocksizes[i] = 1024;  /*  minimum request/block size */
          so_sizes[i]      = 1;     /*  number of blocks */
          so_hardsizes[i]  = 512;   /*  sector size */
          so[i].nr_sects   = 2;     /*  number of sectors */
        }

        /* ??? */

        if ((so_gendisk.de_arr=kmalloc(SCSI_OSDS_PER_MAJOR*sizeof(*so_gendisk.de_arr), GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_so;
        }
        memset(so_gendisk.de_arr, 0, SCSI_OSDS_PER_MAJOR * sizeof *so_gendisk.de_arr);

        /* Flags */

        if ((so_gendisk.flags=kmalloc(SCSI_OSDS_PER_MAJOR*sizeof(*so_gendisk.flags),GFP_ATOMIC))==NULL) {
          iscsi_trace_error("kmalloc() failed\n");
          goto cleanup_so_gendisk_de_arr;
        }
        memset(so_gendisk.flags, 0, SCSI_OSDS_PER_MAJOR * sizeof *so_gendisk.flags);

        if (so_gendisk.next) {
          iscsi_trace_error("Why is this not NULL?\n");
	  so_gendisk.next = NULL;
        }

	return 0;

cleanup_so_gendisk_de_arr:
        kfree(so_gendisk.de_arr);
cleanup_so:
        kfree(so);
cleanup_so_hardsizes:
        kfree(so_hardsizes);
cleanup_so_blocksizes:
        kfree(so_blocksizes);
cleanup_so_sizes:
        kfree(so_sizes);
cleanup_rscsi_osds:
        kfree(rscsi_osds);
cleanup_devfs:
        devfs_unregister_blkdev(SCSI_OSD_MAJOR, "so");
        so_registered--;
        return 1;
}


static void so_finish()
{
	struct gendisk *gendisk;
	int i;

	iscsi_trace(TRACE_OSDSO, "so_finish()\n");

	blk_dev[SCSI_OSD_MAJOR].queue = so_find_queue;
	for (gendisk = gendisk_head; gendisk != NULL; gendisk = gendisk->next)
		if (gendisk == &so_gendisk)
			break;
	if (gendisk == NULL) {
		so_gendisk.next = gendisk_head;
		gendisk_head = &so_gendisk;
	}

	for (i = 0; i < so_template.dev_max; ++i)
		if (!rscsi_osds[i].capacity && rscsi_osds[i].device) {
			so_init_oneosd(i);
			if (!rscsi_osds[i].has_part_table) {
				so_sizes[i] = rscsi_osds[i].capacity;
				register_disk(&so_gendisk, MKDEV(MAJOR(i),MINOR(i)), 1, &so_fops, rscsi_osds[i].capacity);
				rscsi_osds[i].has_part_table = 1;
			}
		}

        /* No read-ahead right not */

        read_ahead[SCSI_OSD_MAJOR] = 0;

	return;
}

static int so_detect(Scsi_Device * SDp)
{
	char nbuff[6];
	iscsi_trace(TRACE_OSDSO, "so_detect()\n");

	if (SDp->type != TYPE_OSD && SDp->type != TYPE_MOD)
		return 0;

	so_devname(so_template.dev_noticed++, nbuff);
	printk("Detected scsi %sosd %s at scsi%d, channel %d, id %d, lun %d\n",
	       SDp->removable ? "removable " : "",
	       nbuff,
	       SDp->host->host_no, SDp->channel, SDp->id, SDp->lun);

	return 1;
}

static int so_attach(Scsi_Device * SDp)
{
        unsigned int devnum;
	Scsi_Osd *dpnt;
	int i;

	iscsi_trace(TRACE_OSDSO, "so_attach(SDpnt 0x%p)\n", SDp);
	if (SDp->type != TYPE_OSD && SDp->type != TYPE_MOD)
		return 0;

	if (so_template.nr_dev >= so_template.dev_max) {
		SDp->attached--;
		return 1;
	}
	for (dpnt = rscsi_osds, i = 0; i < so_template.dev_max; i++, dpnt++)
		if (!dpnt->device)
			break;

	if (i >= so_template.dev_max)
		panic("scsi_devices corrupt (so)");

	rscsi_osds[i].device = SDp;
	rscsi_osds[i].has_part_table = 0;
	so_template.nr_dev++;
	so_gendisk.nr_real++;
        devnum = i % SCSI_OSDS_PER_MAJOR;
        so_gendisk.de_arr[devnum] = SDp->de;
        if (SDp->removable)
		so_gendisk.flags[devnum] |= GENHD_FL_REMOVABLE;
	return 0;
}

#define DEVICE_BUSY rscsi_osds[target].device->busy
#define USAGE rscsi_osds[target].device->access_count
#define CAPACITY rscsi_osds[target].capacity
#define MAYBE_REINIT  so_init_oneosd(target)

/* This routine is called to flush all partitions and partition tables
 * for a changed scsi osd, and then re-read the new partition table.
 * If we are revalidating a osd because of a media change, then we
 * enter with usage == 0.  If we are using an ioctl, we automatically have
 * usage == 1 (we need an open channel to use an ioctl :-), so this
 * is our limit.
 */

int revalidate_scsiosd(kdev_t dev, int maxusage)
{
	int target;
	int max_p;
	int start;
	int i;

	iscsi_trace(TRACE_OSDSO, "revalidate_scsiosd()\n");

	target = DEVICE_NR(dev);

	if (DEVICE_BUSY || USAGE > maxusage) {
		printk("Device busy for revalidation (usage=%d)\n", USAGE);
		return -EBUSY;
	}
	DEVICE_BUSY = 1;

	max_p = so_gendisk.max_p;
	start = target << so_gendisk.minor_shift;

	for (i = max_p - 1; i >= 0; i--) {
		int index = start + i;
		/* invalidate_device(MKDEV(MAJOR(index),MINOR(index)), 1); */
		so_gendisk.part[index].start_sect = 0;
		so_gendisk.part[index].nr_sects = 0;
		/*
		 * Reset the blocksize for everything so that we can read
		 * the partition table.  Technically we will determine the
		 * correct block size when we revalidate, but we do this just
		 * to make sure that everything remains consistent.
		 */
		so_blocksizes[index] = 1024;
		if (rscsi_osds[target].device->sector_size == 2048)
			so_blocksizes[index] = 2048;
		else
			so_blocksizes[index] = 1024;
	}

#ifdef MAYBE_REINIT
	MAYBE_REINIT;
#endif

	grok_partitions(&so_gendisk, target % SCSI_OSDS_PER_MAJOR, 1, CAPACITY);

	DEVICE_BUSY = 0;
	return 0;
}

static int fop_revalidate_scsiosd(kdev_t dev)
{
	iscsi_trace(TRACE_OSDSO, "fop_revalidate_scsiosd()\n");
	return revalidate_scsiosd(dev, 0);
}
static void so_detach(Scsi_Device * SDp)
{
	Scsi_Osd *dpnt;
	int i, j;
	int max_p;
	int start;

	iscsi_trace(TRACE_OSDSO, "so_detach()\n");

	for (dpnt = rscsi_osds, i = 0; i < so_template.dev_max; i++, dpnt++)
		if (dpnt->device == SDp) {

			/* If we are disconnecting a osd driver, sync and invalidate
			 * everything */
			max_p = so_gendisk.max_p;
			start = i << so_gendisk.minor_shift;

			for (j = max_p - 1; j >= 0; j--) {
				int index = start + j;
				/* invalidate_device(MKDEV(MAJOR(index),MINOR(index)), 1); */
				so_gendisk.part[index].start_sect = 0;
				so_gendisk.part[index].nr_sects = 0;
				so_sizes[index] = 0;
			}
                        devfs_register_partitions (&so_gendisk, MINOR(start), 1);
			/* unregister_disk() */
			dpnt->has_part_table = 0;
			dpnt->device = NULL;
			dpnt->capacity = 0;
			SDp->attached--;
			so_template.dev_noticed--;
			so_template.nr_dev--;
			so_gendisk.nr_real--;
			return;
		}
	return;
}

static int __init init_so(void)
{
	iscsi_trace(TRACE_OSDSO, "init_so()\n");
	so_template.module = THIS_MODULE;
	return scsi_register_module(MODULE_SCSI_DEV, &so_template);
}

static void __exit exit_so(void)
{
	struct gendisk **prev_sogd_link;
	struct gendisk *sogd;
	int removed = 0;

	iscsi_trace(TRACE_OSDSO, "exit_so()\n");

	scsi_unregister_module(MODULE_SCSI_DEV, &so_template);

	devfs_unregister_blkdev(SCSI_OSD_MAJOR, "so");

	so_registered--;
	if (rscsi_osds != NULL) {
		kfree(rscsi_osds);
		kfree(so_sizes);
		kfree(so_blocksizes);
		kfree(so_hardsizes);
		kfree((char *) so);

		/*
		 * Now remove &so_gendisk from the linked list
		 */
		prev_sogd_link = &gendisk_head;
		while ((sogd = *prev_sogd_link) != NULL) {
			if (sogd >= &so_gendisk && sogd <= &so_gendisk) {
				removed++;
				*prev_sogd_link = sogd->next;
				continue;
			}
			prev_sogd_link = &sogd->next;
		}

		if (removed != 1)
			printk("%s %d &so_gendisk in osd chain", removed > 1 ? "total" : "just", removed);

	}
	blk_size[SCSI_OSD_MAJOR] = NULL;
	hardsect_size[SCSI_OSD_MAJOR] = NULL;
	read_ahead[SCSI_OSD_MAJOR] = 0;
	so_template.dev_max = 0;
}

module_init(init_so);
module_exit(exit_so);