/*
 * SCSI Media Changer device driver for Linux 2.4
 *
 *     (c) 1996-2002 Gerd Knorr <kraxel@bytesex.org>
 *
 */

#define VERSION "0.20"

#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/blk.h>
#include <linux/completion.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#include <linux/chio.h>			/* here are all the ioctls */

#define MAJOR_NR	SCSI_CHANGER_MAJOR
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
#define CH_EXTRA_DEVS   4

#define CH_DT_MAX       16
#define CH_TYPES        8

#include "scsi.h"
#include "hosts.h"
#include "constants.h"
#include <scsi/scsi_ioctl.h>

MODULE_SUPPORTED_DEVICE("sch");
MODULE_DESCRIPTION("device driver for scsi media changer devices");
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org>");
MODULE_LICENSE("GPL");

static int check_busy = 1;
MODULE_PARM(check_busy,"i");
MODULE_PARM_DESC(check_busy, \
    "enable/disable busy check for data transfer elements (default: on)");

static int init = 1;
MODULE_PARM(init,"i");
MODULE_PARM_DESC(init, \
    "initialize element status on driver load (default: on)");

static int timeout_move = 120;
MODULE_PARM(timeout_move,"i");
MODULE_PARM_DESC(timeout_move,"timeout for move commands "
		 "(default: 120 seconds)");

static int timeout_init = 3600;
MODULE_PARM(timeout_init,"i");
MODULE_PARM_DESC(timeout_init,"timeout for INITIALIZE ELEMENT STATUS "
		 "(default: 3600 seconds)");

static int verbose = 1;
MODULE_PARM(verbose,"i");
MODULE_PARM_DESC(verbose,"be verbose (default: on)");

static int debug = 0;
MODULE_PARM(debug,"i");
MODULE_PARM_DESC(debug,"enable/disable debug messages, also prints more "
		 "detailed sense codes on scsi errors (default: off)");

static int dt_id[CH_DT_MAX] = { [ 0 ... (CH_DT_MAX-1) ] = -1 };
static int dt_lun[CH_DT_MAX];
MODULE_PARM(dt_id,"1-" __MODULE_STRING(CH_DT_MAX) "i");
MODULE_PARM(dt_lun,"1-" __MODULE_STRING(CH_DT_MAX) "i");

/* tell the driver about vendor-specific slots */
static int vendor_firsts[CH_TYPES-4];
static int vendor_counts[CH_TYPES-4];
static char *vendor_labels[CH_TYPES-4];
MODULE_PARM(vendor_firsts,"1-4i");
MODULE_PARM(vendor_counts,"1-4i");
MODULE_PARM(vendor_labels,"1-4s");

#define dprintk(fmt, arg...)    if (debug) \
        printk(KERN_DEBUG "%s: " fmt, ch[target].name, ##arg)
#define vprintk(fmt, arg...)    if (verbose) \
        printk(KERN_INFO "%s: " fmt, ch[target].name, ##arg)

/* ------------------------------------------------------------------- */

#define MAX_RETRIES   1

static int  ch_init(void);
static void ch_finish(void);
static int  ch_attach(Scsi_Device *);
static int  ch_detect(Scsi_Device *);
static void ch_detach(Scsi_Device *);
static int  ch_open(struct inode * inode, struct file * filp);
static int  ch_release(struct inode * inode, struct file * filp);
static int  ch_ioctl(struct inode * inode, struct file * filp,
		     unsigned int cmd, unsigned long arg);

typedef struct {
	char                name[8];
	Scsi_Device  	    *device;
	Scsi_Device         **dt;        /* ptrs to data transfer elements */
	u_int               firsts[CH_TYPES];
	u_int               counts[CH_TYPES];
	u_int               unit_attention;
	u_int		    voltags;
	devfs_handle_t      de;
	struct semaphore    lock;
} Scsi_Changer;

struct Scsi_Device_Template ch_template =
{
	module:    THIS_MODULE,
	name:      "media changer",
	tag:       "sch",
	scsi_type: TYPE_MEDIUM_CHANGER,
	major:     SCSI_CHANGER_MAJOR,
	detect:    ch_detect,
	init:      ch_init,
	finish:    ch_finish,
	attach:    ch_attach,
	detach:    ch_detach,
};

static struct file_operations changer_fops =
{
	owner:   THIS_MODULE,
	open:    ch_open,
	release: ch_release,
	ioctl:   ch_ioctl,
};

static struct {
	unsigned char  sense;
	unsigned char  asc;
	unsigned char  ascq;
	int	       errno;
} err[] = {
/* Just filled in what looks right. Hav'nt checked any standard paper for
   these errno assignments, so they may be wrong... */
	{
		sense:  ILLEGAL_REQUEST,
		asc:    0x21,
		ascq:   0x01,
		errno:  EBADSLT, /* Invalid element address */
	},{
		sense:  ILLEGAL_REQUEST,
		asc:    0x28,
		ascq:   0x01,
		errno:  EBADE, /* Import or export element accessed */
	},{
		sense:  ILLEGAL_REQUEST,
		asc:    0x3B,
		ascq:   0x0D,
		errno:  EXFULL, /* Medium destination element full */
	},{
		sense:  ILLEGAL_REQUEST,
		asc:    0x3B,
		ascq:   0x0E,
		errno:  EBADE, /* Medium source element empty */
	},{
		sense:  ILLEGAL_REQUEST,
		asc:    0x20,
		ascq:   0x00,
		errno:  EBADRQC, /* Invalid command operation code */
	},{
	        /* end of list */
	}
};

Scsi_Changer *ch = NULL;

/* ------------------------------------------------------------------- */

static int ch_find_errno(unsigned char *sense_buffer)
{
	int i,errno = 0;

	/* Check to see if additional sense information is available */
	if (sense_buffer[7]  > 5 &&
	    sense_buffer[12] != 0) {
		for (i = 0; err[i].errno != 0; i++) {
			if (err[i].sense == sense_buffer[ 2] &&
			    err[i].asc   == sense_buffer[12] &&
			    err[i].ascq  == sense_buffer[13]) {
				errno = -err[i].errno;
				break;
			}
		}
	}
	if (errno == 0)
		errno = -EIO;
	return errno;
}

static void
ch_request_done (Scsi_Cmnd * SCpnt)
{
	struct request * req;
	
	req = &SCpnt->request;
	req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */
        if (req->waiting != NULL) {
		complete(req->waiting);
	}
}

static int
ch_do_cmd(int target, unsigned char * sr_cmd,
	  void * buffer, unsigned buflength)
{
#if 0
	static u_char init_cmd[6] = { INITIALIZE_ELEMENT_STATUS };
#endif
	int errno, ua = 0, retries = 0, timeout;
	Scsi_Cmnd * SCpnt;
	DECLARE_COMPLETION(wait);
	
	if(!scsi_block_when_processing_errors(ch[target].device))
		return -ENXIO;

	SCpnt = scsi_allocate_device(ch[target].device, 1, TRUE);
 retry:
	errno = 0;
	SCpnt->request.waiting = &wait;
#if 0
	if (ua && reinit) {
		init_cmd[1] = ch[target].device->lun << 5;
		scsi_do_cmd(SCpnt, (void *) init_cmd, NULL, 0,
			    ch_request_done, CH_TIMEOUT, MAX_RETRIES);
		wait_for_completion(&wait);
	}
#endif
	if (debug) {
		dprintk("command: %s","");
		print_command(sr_cmd);
	}
	timeout = (sr_cmd[0] == INITIALIZE_ELEMENT_STATUS)
		? timeout_init : timeout_move;
	scsi_do_cmd(SCpnt, (void *) sr_cmd, buffer, buflength,
		    ch_request_done, timeout*HZ, MAX_RETRIES);
	wait_for_completion(&wait);
	SCpnt->request.waiting = NULL;
	dprintk("result: 0x%x\n",SCpnt->result);
	
	if (driver_byte(SCpnt->result) != 0) {
		if (debug) {
			dprintk("sense: %s","");
			print_sense("", SCpnt);
		}
		errno = ch_find_errno(SCpnt->sense_buffer);
		switch(SCpnt->sense_buffer[2] & 0xf) {
		case UNIT_ATTENTION:
			ch[target].unit_attention = 1;
			ua = 1;
			if (retries++ < 3)
				goto retry;
			break;
		}
	}
	
	scsi_release_command(SCpnt);
	return errno;
}

/* ------------------------------------------------------------------------ */

static Scsi_Device*
find_device(struct Scsi_Host *host, u_char channel, u_char id, u_char lun)
{
	Scsi_Device *ret;
	ret = host->host_queue;
	
	while (ret != NULL &&
	       (ret->host    != host || ret->channel != channel ||
		ret->id      != id   || ret->lun     != lun))
		ret = ret->next;
	
	return ret;
}

static int
ch_elem_to_typecode(int target, u_int elem)
{
	int i;
	
	for (i = 0; i < CH_TYPES; i++) {
		if (elem >= ch[target].firsts[i]  &&
		    elem <  ch[target].firsts[i] +
	            ch[target].counts[i])
			return i+1;
	}
	return 0;
}

static int
ch_read_element_status(int target, u_int elem, char *data)
{
	u_char  cmd[12];
	u_char  *buffer;
	int     result;
	
	buffer = (unsigned char *) scsi_malloc(512);
	if(!buffer)
		return -ENOMEM;
	
 retry:
	memset(cmd,0,sizeof(cmd));
	cmd[0] = READ_ELEMENT_STATUS;
	cmd[1] = (ch[target].device->lun << 5) | 
		(ch[target].voltags ? 0x10 : 0) |
		ch_elem_to_typecode(target,elem);
	cmd[2] = (elem >> 8) & 0xff;
	cmd[3] = elem        & 0xff;
	cmd[5] = 1;
	cmd[9] = 255;
	if (0 == (result = ch_do_cmd(target, cmd, buffer, 256))) {
		if (((buffer[16] << 8) | buffer[17]) != elem) {
			dprintk("asked for element 0x%02x, got 0x%02x\n",
				elem,(buffer[16] << 8) | buffer[17]);
			scsi_free(buffer, 512);
			return -EIO;
		}
		memcpy(data,buffer+16,16);
	} else {
		if (ch[target].voltags) {
			ch[target].voltags = 0;
			vprintk("device has no volume tag support%s\n","");
			goto retry;
		}
		dprintk("READ ELEMENT STATUS for element 0x%x failed\n",elem);
	}
	scsi_free(buffer, 512);
	return result;
}

static int 
ch_init_elem(int target)
{
	int err;
	u_char cmd[6];

	vprintk("INITIALIZE ELEMENT STATUS, may take some time ...%s\n","");
	memset(cmd,0,sizeof(cmd));
	cmd[0] = INITIALIZE_ELEMENT_STATUS;
	cmd[1] = ch[target].device->lun << 5;
	err = ch_do_cmd(target, cmd, NULL, 0);
	vprintk("... finished%s\n","");
	return err;
}

static int
ch_readconfig(int target)
{
	u_char  cmd[10], data[16];
	u_char  *buffer;
	int     result,id,lun,i;
	u_int   elem;
	
	buffer = (unsigned char *) scsi_malloc(512);
	if (!buffer)
		return -ENOMEM;
	memset(buffer,0,512);
	
	memset(cmd,0,sizeof(cmd));
	cmd[0] = MODE_SENSE;
	cmd[1] = ch[target].device->lun << 5;
	cmd[2] = 0x1d;
	cmd[4] = 255;
	result = ch_do_cmd(target, cmd, buffer, 255);
	if (0 != result) {
		cmd[1] |= (1<<3);
		result  = ch_do_cmd(target, cmd, buffer, 255);
	}
	if (0 == result) {
		ch[target].firsts[CHET_MT] =
			(buffer[buffer[3]+ 6] << 8) | buffer[buffer[3]+ 7];
		ch[target].counts[CHET_MT] =
			(buffer[buffer[3]+ 8] << 8) | buffer[buffer[3]+ 9];
		ch[target].firsts[CHET_ST] =
			(buffer[buffer[3]+10] << 8) | buffer[buffer[3]+11];
		ch[target].counts[CHET_ST] =
			(buffer[buffer[3]+12] << 8) | buffer[buffer[3]+13];
		ch[target].firsts[CHET_IE] =
			(buffer[buffer[3]+14] << 8) | buffer[buffer[3]+15];
		ch[target].counts[CHET_IE] =
			(buffer[buffer[3]+16] << 8) | buffer[buffer[3]+17];
		ch[target].firsts[CHET_DT] =
			(buffer[buffer[3]+18] << 8) | buffer[buffer[3]+19];
		ch[target].counts[CHET_DT] =
			(buffer[buffer[3]+20] << 8) | buffer[buffer[3]+21];
		vprintk("type #1 (mt): 0x%x+%d [medium transport]\n",
			ch[target].firsts[CHET_MT],
			ch[target].counts[CHET_MT]);
		vprintk("type #2 (st): 0x%x+%d [storage]\n",
			ch[target].firsts[CHET_ST],
			ch[target].counts[CHET_ST]);
		vprintk("type #3 (ie): 0x%x+%d [import/export]\n",
			ch[target].firsts[CHET_IE],
			ch[target].counts[CHET_IE]);
		vprintk("type #4 (dt): 0x%x+%d [data transfer]\n",
			ch[target].firsts[CHET_DT],
			ch[target].counts[CHET_DT]);
	} else {
		vprintk("reading element address assigment page failed!%s\n",
			"");
	}
	
	/* vendor specific element types */
	for (i = 0; i < 4; i++) {
		if (0 == vendor_counts[i])
			continue;
		if (NULL == vendor_labels[i])
			continue;
		ch[target].firsts[CHET_V1+i] = vendor_firsts[i];
		ch[target].counts[CHET_V1+i] = vendor_counts[i];
		vprintk("type #%d (v%d): 0x%x+%d [%s, vendor specific]\n",
			i+5,i+1,vendor_firsts[i],vendor_counts[i],
			vendor_labels[i]);
	}

	/* look up the devices of the data transfer elements */
	ch[target].dt =
		kmalloc(ch[target].counts[CHET_DT]*sizeof(Scsi_Device*),
			GFP_ATOMIC);
	for (elem = 0; elem < ch[target].counts[CHET_DT]; elem++) {
		id  = -1;
		lun = 0;
		if (-1 != dt_id[elem]) {
			id  = dt_id[elem];
			lun = dt_lun[elem];
			vprintk("dt 0x%x: [insmod option] ",
				elem+ch[target].firsts[CHET_DT]);
		} else if (0 != ch_read_element_status
			   (target,elem+ch[target].firsts[CHET_DT],data)) {
			vprintk("dt 0x%x: READ ELEMENT STATUS failed\n",
				elem+ch[target].firsts[CHET_DT]);
		} else {
			vprintk("dt 0x%x: ",elem+ch[target].firsts[CHET_DT]);
			if (data[6] & 0x80) {
				if (verbose)
					printk("not this SCSI bus\n");
				ch[target].dt[elem] = NULL;
			} else if (0 == (data[6] & 0x30)) {
				if (verbose)
					printk("ID/LUN unknown\n");
				ch[target].dt[elem] = NULL;
			} else {
				id  = ch[target].device->id;
				lun = 0;
				if (data[6] & 0x20) id  = data[7];
				if (data[6] & 0x10) lun = data[6] & 7;
			}
		}
		if (-1 != id) {
			if (verbose)
				printk("ID %i, LUN %i, ",id,lun);
			ch[target].dt[elem] =
				find_device(ch[target].device->host,
					    ch[target].device->channel,
					    id,lun);
			if (!ch[target].dt[elem]) {
				/* should not happen */
				if (verbose)
					printk("Huh? device not found!\n");
			} else {
				if (verbose)
					printk("name: %8.8s %16.16s %4.4s\n",
					       ch[target].dt[elem]->vendor,
					       ch[target].dt[elem]->model,
					       ch[target].dt[elem]->rev);
			}
		}
	}
	ch[target].voltags = 1;
	scsi_free(buffer, 512);

	return 0;
}

/* ------------------------------------------------------------------------ */

static int
ch_position(int target, u_int trans, u_int elem, int rotate)
{
	u_char  cmd[10];
	
	dprintk("position: 0x%x\n",elem);
	if (0 == trans)
		trans = ch[target].firsts[CHET_MT];
	memset(cmd,0,sizeof(cmd));
	cmd[0]  = POSITION_TO_ELEMENT;
	cmd[1]  = ch[target].device->lun << 5;
	cmd[2]  = (trans >> 8) & 0xff;
	cmd[3]  =  trans       & 0xff;
	cmd[4]  = (elem  >> 8) & 0xff;
	cmd[5]  =  elem        & 0xff;
	cmd[8]  = rotate ? 1 : 0;
	return ch_do_cmd(target, cmd, NULL,0);
}

static int
ch_move(int target, u_int trans, u_int src, u_int dest, int rotate)
{
	u_char  cmd[12];
	
	dprintk("move: 0x%x => 0x%x\n",src,dest);
	if (0 == trans)
		trans = ch[target].firsts[CHET_MT];
	memset(cmd,0,sizeof(cmd));
	cmd[0]  = MOVE_MEDIUM;
	cmd[1]  = ch[target].device->lun << 5;
	cmd[2]  = (trans >> 8) & 0xff;
	cmd[3]  =  trans       & 0xff;
	cmd[4]  = (src   >> 8) & 0xff;
	cmd[5]  =  src         & 0xff;
	cmd[6]  = (dest  >> 8) & 0xff;
	cmd[7]  =  dest        & 0xff;
	cmd[10] = rotate ? 1 : 0;
	return ch_do_cmd(target, cmd, NULL,0);
}

static int
ch_exchange(int target, u_int trans, u_int src,
	    u_int dest1, u_int dest2, int rotate1, int rotate2)
{
	u_char  cmd[12];
	
	dprintk("exchange: 0x%x => 0x%x => 0x%x\n",
		src,dest1,dest2);
	if (0 == trans)
		trans = ch[target].firsts[CHET_MT];
	memset(cmd,0,sizeof(cmd));
	cmd[0]  = EXCHANGE_MEDIUM;
	cmd[1]  = ch[target].device->lun << 5;
	cmd[2]  = (trans >> 8) & 0xff;
	cmd[3]  =  trans       & 0xff;
	cmd[4]  = (src   >> 8) & 0xff;
	cmd[5]  =  src         & 0xff;
	cmd[6]  = (dest1 >> 8) & 0xff;
	cmd[7]  =  dest1       & 0xff;
	cmd[8]  = (dest2 >> 8) & 0xff;
	cmd[9]  =  dest2       & 0xff;
	cmd[10] = (rotate1 ? 1 : 0) | (rotate2 ? 2 : 0);
	
	return ch_do_cmd(target, cmd, NULL,0);
}

static void
ch_check_voltag(char *tag)
{
	int i;

	for (i = 0; i < 32; i++) {
		/* restrict to ascii */
		if (tag[i] >= 0x7f || tag[i] < 0x20)
			tag[i] = ' ';
		/* don't allow search wildcards */
		if (tag[i] == '?' ||
		    tag[i] == '*')
			tag[i] = ' ';
	}
}

static int
ch_set_voltag(int target, u_int elem,
	      int alternate, int clear, u_char *tag)
{
	u_char  cmd[12];
	u_char  *buffer;
	int result;

	buffer = scsi_malloc(512);
	if (!buffer)
		return -ENOMEM;
	memset(buffer,0,512);

	dprintk("%s %s voltag: 0x%x => \"%s\"\n",
		clear     ? "clear"     : "set",
		alternate ? "alternate" : "primary",
		elem, tag);
	memset(cmd,0,sizeof(cmd));
	cmd[0]  = SEND_VOLUME_TAG;
	cmd[1] = (ch[target].device->lun << 5) | 
		ch_elem_to_typecode(target,elem);
	cmd[2] = (elem >> 8) & 0xff;
	cmd[3] = elem        & 0xff;
	cmd[5] = clear
		? (alternate ? 0x0d : 0x0c)
		: (alternate ? 0x0b : 0x0a);
	
	cmd[9] = 255;

	memcpy(buffer,tag,32);
	ch_check_voltag(buffer);

	result = ch_do_cmd(target, cmd, buffer, 256);
	scsi_free(buffer, 512);
	return result;
}

/* ------------------------------------------------------------------------ */

static int
ch_release(struct inode * inode, struct file * filp)
{
	int minor = MINOR(inode->i_rdev);
	
	ch[minor].device->access_count--;
	if (ch[minor].device->host->hostt->module)
		__MOD_DEC_USE_COUNT(ch[minor].device->host->hostt->module);
	return 0;
}

static int
ch_open(struct inode * inode, struct file * filp)
{    
	int minor = MINOR(inode->i_rdev);
	
	if (minor >= ch_template.nr_dev || !ch[minor].device)
		return -ENXIO;
	
	ch[minor].device->access_count++;
	if (ch[minor].device->host->hostt->module)
		__MOD_INC_USE_COUNT(ch[minor].device->host->hostt->module);
	
	return 0;
}

static int
ch_checkrange(int target, int type, int unit)
{
	if (type < 0 || type >= CH_TYPES || unit < 0 ||
	    unit >= ch[target].counts[type])
		return -1;
	return 0;
}

/* for data transfer elements: check if they are busy */
static int
ch_is_busy(int target, int type, int unit)
{
	if (!check_busy)
		return 0;
	if (type != CHET_DT)
		return 0;
	if (!ch[target].dt[unit])
		return 0;
	return ch[target].dt[unit]->access_count;
}

static int
ch_ioctl(struct inode * inode, struct file * filp,
	 unsigned int cmd, unsigned long arg)
{
	int target = MINOR(inode->i_rdev);
	int retval;
	
	if (target >= ch_template.nr_dev || !ch[target].device)
		return -ENXIO;
	
	switch (cmd) {
	case CHIOGPARAMS:
	{
		struct changer_params params;
		
		params.cp_curpicker = 0;
		params.cp_npickers  = ch[target].counts[CHET_MT];
		params.cp_nslots    = ch[target].counts[CHET_ST];
		params.cp_nportals  = ch[target].counts[CHET_IE];
		params.cp_ndrives   = ch[target].counts[CHET_DT];
		
		if (copy_to_user((void *) arg, &params, sizeof(params)))
			return -EFAULT;
		return 0;
	}
	case CHIOGVPARAMS:
	{
		struct changer_vendor_params vparams;

		memset(&vparams,0,sizeof(vparams));
		if (ch[target].counts[CHET_V1]) {
			vparams.cvp_n1  = ch[target].counts[CHET_V1];
			strncpy(vparams.cvp_label1,vendor_labels[0],16);
		}
		if (ch[target].counts[CHET_V2]) {
			vparams.cvp_n2  = ch[target].counts[CHET_V2];
			strncpy(vparams.cvp_label2,vendor_labels[1],16);
		}
		if (ch[target].counts[CHET_V3]) {
			vparams.cvp_n3  = ch[target].counts[CHET_V3];
			strncpy(vparams.cvp_label3,vendor_labels[2],16);
		}
		if (ch[target].counts[CHET_V4]) {
			vparams.cvp_n4  = ch[target].counts[CHET_V4];
			strncpy(vparams.cvp_label4,vendor_labels[3],16);
		}
		if (copy_to_user((void *) arg, &vparams, sizeof(vparams)))
			return -EFAULT;
		return 0;
	}
	
	case CHIOPOSITION:
	{
		struct changer_position pos;
		
		if (copy_from_user(&pos, (void*)arg, sizeof (pos)))
			return -EFAULT;

		if (0 != ch_checkrange(target, pos.cp_type, pos.cp_unit)) {
			dprintk("CHIOPOSITION: invalid parameter%s\n","");
			return -EBADSLT;
		}
		down(&ch[target].lock);
		retval = ch_position(target,0,
				     ch[target].firsts[pos.cp_type] + pos.cp_unit,
				     pos.cp_flags & CP_INVERT);
		up(&ch[target].lock);
		return retval;
	}
	
	case CHIOMOVE:
	{
		struct changer_move mv;

		if (copy_from_user(&mv, (void*)arg, sizeof (mv)))
			return -EFAULT;

		if (0 != ch_checkrange(target, mv.cm_fromtype, mv.cm_fromunit) ||
		    0 != ch_checkrange(target, mv.cm_totype,   mv.cm_tounit  )) {
			dprintk("CHIOMOVE: invalid parameter%s\n","");
			return -EBADSLT;
		}
		if (ch_is_busy(target, mv.cm_fromtype, mv.cm_fromunit) ||
		    ch_is_busy(target, mv.cm_totype,   mv.cm_tounit  ))
			return -EBUSY;
		
		down(&ch[target].lock);
		retval = ch_move(target,0,
				 ch[target].firsts[mv.cm_fromtype] + mv.cm_fromunit,
				 ch[target].firsts[mv.cm_totype]   + mv.cm_tounit,
				 mv.cm_flags & CM_INVERT);
		up(&ch[target].lock);
		return retval;
	}

	case CHIOEXCHANGE:
	{
		struct changer_exchange mv;
		
		if (copy_from_user(&mv, (void*)arg, sizeof (mv)))
			return -EFAULT;

		if (0 != ch_checkrange(target, mv.ce_srctype,  mv.ce_srcunit ) ||
		    0 != ch_checkrange(target, mv.ce_fdsttype, mv.ce_fdstunit) ||
		    0 != ch_checkrange(target, mv.ce_sdsttype, mv.ce_sdstunit)) {
			dprintk("CHIOEXCHANGE: invalid parameter%s\n","");
			return -EBADSLT;
		}
		if (0 != ch_is_busy(target, mv.ce_srctype,  mv.ce_srcunit ) ||
		    0 != ch_is_busy(target, mv.ce_fdsttype, mv.ce_fdstunit) ||
		    0 != ch_is_busy(target, mv.ce_sdsttype, mv.ce_sdstunit))
			return -EBUSY;
		
		down(&ch[target].lock);
		retval = ch_exchange
			(target,0,
			 ch[target].firsts[mv.ce_srctype]  + mv.ce_srcunit,
			 ch[target].firsts[mv.ce_fdsttype] + mv.ce_fdstunit,
			 ch[target].firsts[mv.ce_sdsttype] + mv.ce_sdstunit,
			 mv.ce_flags & CE_INVERT1, mv.ce_flags & CE_INVERT2);
		up(&ch[target].lock);
		return retval;
	}

	case CHIOGSTATUS:
	{
		struct changer_element_status ces;
		u_char data[16];
		int i;
		
		if (copy_from_user(&ces, (void*)arg, sizeof (ces)))
			return -EFAULT;

		if (ces.ces_type < 0 || ces.ces_type >= CH_TYPES)
			return -EINVAL;
		
		down(&ch[target].lock);
		for (i = 0; i < ch[target].counts[ces.ces_type]; i++) {
			if (0 != ch_read_element_status
			    (target, ch[target].firsts[ces.ces_type]+i,data))
				return -EIO;
			put_user(data[2], ces.ces_data+i);
			if (data[2] & CESTATUS_EXCEPT)
				vprintk("element 0x%x: asc=0x%x, ascq=0x%x\n",
					ch[target].firsts[ces.ces_type]+i,
					(int)data[4],(int)data[5]);
			retval = ch_read_element_status
			    (target, ch[target].firsts[ces.ces_type]+i,data);
			if (0 != retval) {
				up(&ch[target].lock);
				return retval;
			}
		}
		up(&ch[target].lock);
		return 0;
	}
    
	case CHIOGELEM:
	{
		struct changer_get_element cge;
		u_char  cmd[12];
		u_char  *buffer;
		int     elem,result,i;
		
		if (copy_from_user(&cge, (void*)arg, sizeof (cge)))
			return -EFAULT;

		if (0 != ch_checkrange(target, cge.cge_type, cge.cge_unit))
			return -EINVAL;
		elem = ch[target].firsts[cge.cge_type] + cge.cge_unit;
		
		buffer = (unsigned char *) scsi_malloc(512);
		if (!buffer)
			return -ENOMEM;
		down(&ch[target].lock);
		
	voltag_retry:
		memset(cmd,0,sizeof(cmd));
		cmd[0] = READ_ELEMENT_STATUS;
		cmd[1] = (ch[target].device->lun << 5) |
			(ch[target].voltags ? 0x10 : 0) |
			ch_elem_to_typecode(target,elem);
		cmd[2] = (elem >> 8) & 0xff;
		cmd[3] = elem        & 0xff;
		cmd[5] = 1;
		cmd[9] = 255;
		
		if (0 == (result = ch_do_cmd(target, cmd, buffer, 256))) {
			cge.cge_status = buffer[18];
			cge.cge_flags = 0;
			if (buffer[18] & CESTATUS_EXCEPT) {
				/* FIXME: fill cge_errno */
			}
			if (buffer[25] & 0x80) {
				cge.cge_flags |= CGE_SRC;
				if (buffer[25] & 0x40)
					cge.cge_flags |= CGE_INVERT;
				elem = (buffer[26]<<8) | buffer[27];
				for (i = 0; i < 4; i++) {
					if (elem >= ch[target].firsts[i] &&
					    elem < ch[target].firsts[i]+
					    ch[target].counts[i]) {
						cge.cge_srctype = i;
						cge.cge_srcunit = elem-ch[target].firsts[i];
					}
				}
			}
			if ((buffer[22] & 0x30) == 0x30) {
				cge.cge_flags |= CGE_IDLUN;
				cge.cge_id  = buffer[23];
				cge.cge_lun = buffer[22] & 7;
			}
			if (buffer[9] & 0x80) {
				cge.cge_flags |= CGE_PVOLTAG;
				memcpy(cge.cge_pvoltag,buffer+28,36);
			}
			if (buffer[9] & 0x40) {
				cge.cge_flags |= CGE_AVOLTAG;
				memcpy(cge.cge_avoltag,buffer+64,36);
			}
		} else if (ch[target].voltags) {
			ch[target].voltags = 0;
			vprintk("device has no volume tag support%s\n","");
			goto voltag_retry;
		}
		scsi_free(buffer, 512);
		up(&ch[target].lock);
		
		if (copy_to_user((void*)arg, &cge, sizeof (cge)))
			return -EFAULT;
		return result;
	}

	case CHIOINITELEM:
	{
		down(&ch[target].lock);
		retval = ch_init_elem(target);
		up(&ch[target].lock);
		return retval;
	}
		
	case CHIOSVOLTAG:
	{
		struct changer_set_voltag csv;
		int elem;

		if (copy_from_user(&csv, (void*)arg, sizeof(csv)))
			return -EFAULT;

		if (0 != ch_checkrange(target, csv.csv_type, csv.csv_unit)) {
			dprintk("CHIOSVOLTAG: invalid parameter%s\n","");
			return -EBADSLT;
		}
		elem = ch[target].firsts[csv.csv_type] + csv.csv_unit;
		down(&ch[target].lock);
		retval = ch_set_voltag(target, elem,
				       csv.csv_flags & CSV_AVOLTAG,
				       csv.csv_flags & CSV_CLEARTAG,
				       csv.csv_voltag);
		up(&ch[target].lock);
		return retval;
	}

	default:
		return scsi_ioctl(ch[target].device, cmd, (void*)arg);

	}
}

/* ------------------------------------------------------------------------ */

static int
ch_detect(Scsi_Device * SDp)
{
	if(SDp->type != TYPE_MEDIUM_CHANGER)
		return 0;
	
	printk(KERN_INFO "Detected scsi changer ch%d "
	       "at scsi%d, channel %d, id %d, lun %d\n", 
	       ch_template.dev_noticed++,
	       SDp->host->host_no, SDp->channel, SDp->id, SDp->lun); 
	
	return 1;
}

static int ch_attach(Scsi_Device * SDp){
	Scsi_Changer * cpnt;
	int i;
	
	if(SDp->type != TYPE_MEDIUM_CHANGER)
		return 1;
    
	if (ch_template.nr_dev >= ch_template.dev_max) {
		SDp->attached--;
		return 1;
	}
	
	for (cpnt = ch, i=0; i<ch_template.dev_max; i++, cpnt++) 
		if (!cpnt->device)
			break;
	
	if (i >= ch_template.dev_max)
		panic("scsi_devicelist corrupt (ch)");
	
	sprintf(ch[i].name,"ch%d",i);
	ch[i].device = SDp;
	ch[i].de = devfs_register (ch[i].device->de, "changer",
				   DEVFS_FL_DEFAULT, MAJOR_NR, i,
				   S_IFCHR | S_IRUGO | S_IWUGO,
				   &changer_fops, NULL);
	ch_template.nr_dev++;
	if (ch_template.nr_dev > ch_template.dev_max)
		panic("scsi_devicelist corrupt (ch)");
	return 0;
}

static int ch_registered = 0;

static int ch_init()
{
	if (ch_template.dev_noticed == 0)
		return 0;

	if (!ch_registered) {
		if (devfs_register_chrdev(MAJOR_NR,"ch",&changer_fops)) {
			printk("Unable to get major %d for SCSI-Changer\n",
			       MAJOR_NR);
			return 1;
		}
		ch_registered++;
	}
	
	if (ch)
		return 0;
	ch_template.dev_max =
		ch_template.dev_noticed + CH_EXTRA_DEVS;
	ch = kmalloc(ch_template.dev_max * sizeof(Scsi_Changer), GFP_ATOMIC);
	memset(ch, 0, ch_template.dev_max * sizeof(Scsi_Changer));
	return 0;
}

void
ch_finish()
{
	int i;
	
	for (i = 0; i < ch_template.nr_dev; i++) {
		if (!ch[i].device)
			continue;
		init_MUTEX(&ch[i].lock);
		ch_readconfig(i);
		if (init)
			ch_init_elem(i);
	}
	
	return;
}	

static void ch_detach(Scsi_Device * SDp)
{
	Scsi_Changer * cpnt;
	int i;
	
	for(cpnt = ch, i=0; i<ch_template.dev_max; i++, cpnt++) {
		if (cpnt->device != SDp)
			continue;
		/*
		 * Reset things back to a sane state so that one can
		 * re-load a new driver (perhaps the same one).
		 */
		devfs_unregister(cpnt->de);
		kfree(cpnt->dt);
		cpnt->device = NULL;
		SDp->attached--;
		ch_template.nr_dev--;
		ch_template.dev_noticed--;
		return;
	}
	return;
}

static int __init init_ch_module(void)
{
	printk(KERN_INFO "SCSI Media Changer driver v" VERSION
	       " for Linux " UTS_RELEASE "\n");
	return scsi_register_module(MODULE_SCSI_DEV, &ch_template);
}

static void __exit exit_ch_module(void) 
{
	scsi_unregister_module(MODULE_SCSI_DEV, &ch_template);
	devfs_unregister_chrdev(MAJOR_NR, "ch");
	ch_registered--;
	if(ch != NULL)
		kfree((char *)ch);
	ch_template.dev_max = 0;
}

module_init(init_ch_module);
module_exit(exit_ch_module);
EXPORT_NO_SYMBOLS;

/*
 * Local variables:
 * c-basic-offset: 8
 * End:
 */
