162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * SCSI Media Changer device driver for Linux 2.6
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *     (c) 1996-2003 Gerd Knorr <kraxel@bytesex.org>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define VERSION "0.25"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/mm.h>
1662306a36Sopenharmony_ci#include <linux/major.h>
1762306a36Sopenharmony_ci#include <linux/string.h>
1862306a36Sopenharmony_ci#include <linux/errno.h>
1962306a36Sopenharmony_ci#include <linux/interrupt.h>
2062306a36Sopenharmony_ci#include <linux/blkdev.h>
2162306a36Sopenharmony_ci#include <linux/completion.h>
2262306a36Sopenharmony_ci#include <linux/compat.h>
2362306a36Sopenharmony_ci#include <linux/chio.h>			/* here are all the ioctls */
2462306a36Sopenharmony_ci#include <linux/mutex.h>
2562306a36Sopenharmony_ci#include <linux/idr.h>
2662306a36Sopenharmony_ci#include <linux/slab.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include <scsi/scsi.h>
2962306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h>
3062306a36Sopenharmony_ci#include <scsi/scsi_driver.h>
3162306a36Sopenharmony_ci#include <scsi/scsi_ioctl.h>
3262306a36Sopenharmony_ci#include <scsi/scsi_host.h>
3362306a36Sopenharmony_ci#include <scsi/scsi_device.h>
3462306a36Sopenharmony_ci#include <scsi/scsi_eh.h>
3562306a36Sopenharmony_ci#include <scsi/scsi_dbg.h>
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define CH_DT_MAX       16
3862306a36Sopenharmony_ci#define CH_TYPES        8
3962306a36Sopenharmony_ci#define CH_MAX_DEVS     128
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ciMODULE_DESCRIPTION("device driver for scsi media changer devices");
4262306a36Sopenharmony_ciMODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org>");
4362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
4462306a36Sopenharmony_ciMODULE_ALIAS_CHARDEV_MAJOR(SCSI_CHANGER_MAJOR);
4562306a36Sopenharmony_ciMODULE_ALIAS_SCSI_DEVICE(TYPE_MEDIUM_CHANGER);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int init = 1;
4862306a36Sopenharmony_cimodule_param(init, int, 0444);
4962306a36Sopenharmony_ciMODULE_PARM_DESC(init, \
5062306a36Sopenharmony_ci    "initialize element status on driver load (default: on)");
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int timeout_move = 300;
5362306a36Sopenharmony_cimodule_param(timeout_move, int, 0644);
5462306a36Sopenharmony_ciMODULE_PARM_DESC(timeout_move,"timeout for move commands "
5562306a36Sopenharmony_ci		 "(default: 300 seconds)");
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int timeout_init = 3600;
5862306a36Sopenharmony_cimodule_param(timeout_init, int, 0644);
5962306a36Sopenharmony_ciMODULE_PARM_DESC(timeout_init,"timeout for INITIALIZE ELEMENT STATUS "
6062306a36Sopenharmony_ci		 "(default: 3600 seconds)");
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic int verbose = 1;
6362306a36Sopenharmony_cimodule_param(verbose, int, 0644);
6462306a36Sopenharmony_ciMODULE_PARM_DESC(verbose,"be verbose (default: on)");
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic int debug;
6762306a36Sopenharmony_cimodule_param(debug, int, 0644);
6862306a36Sopenharmony_ciMODULE_PARM_DESC(debug,"enable/disable debug messages, also prints more "
6962306a36Sopenharmony_ci		 "detailed sense codes on scsi errors (default: off)");
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic int dt_id[CH_DT_MAX] = { [ 0 ... (CH_DT_MAX-1) ] = -1 };
7262306a36Sopenharmony_cistatic int dt_lun[CH_DT_MAX];
7362306a36Sopenharmony_cimodule_param_array(dt_id,  int, NULL, 0444);
7462306a36Sopenharmony_cimodule_param_array(dt_lun, int, NULL, 0444);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/* tell the driver about vendor-specific slots */
7762306a36Sopenharmony_cistatic int vendor_firsts[CH_TYPES-4];
7862306a36Sopenharmony_cistatic int vendor_counts[CH_TYPES-4];
7962306a36Sopenharmony_cimodule_param_array(vendor_firsts, int, NULL, 0444);
8062306a36Sopenharmony_cimodule_param_array(vendor_counts, int, NULL, 0444);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic const char * vendor_labels[CH_TYPES-4] = {
8362306a36Sopenharmony_ci	"v0", "v1", "v2", "v3"
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci// module_param_string_array(vendor_labels, NULL, 0444);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci#define ch_printk(prefix, ch, fmt, a...) \
8862306a36Sopenharmony_ci	sdev_prefix_printk(prefix, (ch)->device, (ch)->name, fmt, ##a)
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci#define DPRINTK(fmt, arg...)						\
9162306a36Sopenharmony_cido {									\
9262306a36Sopenharmony_ci	if (debug)							\
9362306a36Sopenharmony_ci		ch_printk(KERN_DEBUG, ch, fmt, ##arg);			\
9462306a36Sopenharmony_ci} while (0)
9562306a36Sopenharmony_ci#define VPRINTK(level, fmt, arg...)					\
9662306a36Sopenharmony_cido {									\
9762306a36Sopenharmony_ci	if (verbose)							\
9862306a36Sopenharmony_ci		ch_printk(level, ch, fmt, ##arg);			\
9962306a36Sopenharmony_ci} while (0)
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/* ------------------------------------------------------------------- */
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci#define MAX_RETRIES   1
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic struct class * ch_sysfs_class;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_citypedef struct {
10862306a36Sopenharmony_ci	struct kref         ref;
10962306a36Sopenharmony_ci	struct list_head    list;
11062306a36Sopenharmony_ci	int                 minor;
11162306a36Sopenharmony_ci	char                name[8];
11262306a36Sopenharmony_ci	struct scsi_device  *device;
11362306a36Sopenharmony_ci	struct scsi_device  **dt;        /* ptrs to data transfer elements */
11462306a36Sopenharmony_ci	u_int               firsts[CH_TYPES];
11562306a36Sopenharmony_ci	u_int               counts[CH_TYPES];
11662306a36Sopenharmony_ci	u_int               unit_attention;
11762306a36Sopenharmony_ci	u_int		    voltags;
11862306a36Sopenharmony_ci	struct mutex	    lock;
11962306a36Sopenharmony_ci} scsi_changer;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic DEFINE_IDR(ch_index_idr);
12262306a36Sopenharmony_cistatic DEFINE_SPINLOCK(ch_index_lock);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic const struct {
12562306a36Sopenharmony_ci	unsigned char  sense;
12662306a36Sopenharmony_ci	unsigned char  asc;
12762306a36Sopenharmony_ci	unsigned char  ascq;
12862306a36Sopenharmony_ci	int	       errno;
12962306a36Sopenharmony_ci} ch_err[] = {
13062306a36Sopenharmony_ci/* Just filled in what looks right. Hav'nt checked any standard paper for
13162306a36Sopenharmony_ci   these errno assignments, so they may be wrong... */
13262306a36Sopenharmony_ci	{
13362306a36Sopenharmony_ci		.sense  = ILLEGAL_REQUEST,
13462306a36Sopenharmony_ci		.asc    = 0x21,
13562306a36Sopenharmony_ci		.ascq   = 0x01,
13662306a36Sopenharmony_ci		.errno  = EBADSLT, /* Invalid element address */
13762306a36Sopenharmony_ci	},{
13862306a36Sopenharmony_ci		.sense  = ILLEGAL_REQUEST,
13962306a36Sopenharmony_ci		.asc    = 0x28,
14062306a36Sopenharmony_ci		.ascq   = 0x01,
14162306a36Sopenharmony_ci		.errno  = EBADE,   /* Import or export element accessed */
14262306a36Sopenharmony_ci	},{
14362306a36Sopenharmony_ci		.sense  = ILLEGAL_REQUEST,
14462306a36Sopenharmony_ci		.asc    = 0x3B,
14562306a36Sopenharmony_ci		.ascq   = 0x0D,
14662306a36Sopenharmony_ci		.errno  = EXFULL,  /* Medium destination element full */
14762306a36Sopenharmony_ci	},{
14862306a36Sopenharmony_ci		.sense  = ILLEGAL_REQUEST,
14962306a36Sopenharmony_ci		.asc    = 0x3B,
15062306a36Sopenharmony_ci		.ascq   = 0x0E,
15162306a36Sopenharmony_ci		.errno  = EBADE,   /* Medium source element empty */
15262306a36Sopenharmony_ci	},{
15362306a36Sopenharmony_ci		.sense  = ILLEGAL_REQUEST,
15462306a36Sopenharmony_ci		.asc    = 0x20,
15562306a36Sopenharmony_ci		.ascq   = 0x00,
15662306a36Sopenharmony_ci		.errno  = EBADRQC, /* Invalid command operation code */
15762306a36Sopenharmony_ci	},{
15862306a36Sopenharmony_ci	        /* end of list */
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/* ------------------------------------------------------------------- */
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic int ch_find_errno(struct scsi_sense_hdr *sshdr)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	int i,errno = 0;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	/* Check to see if additional sense information is available */
16962306a36Sopenharmony_ci	if (scsi_sense_valid(sshdr) &&
17062306a36Sopenharmony_ci	    sshdr->asc != 0) {
17162306a36Sopenharmony_ci		for (i = 0; ch_err[i].errno != 0; i++) {
17262306a36Sopenharmony_ci			if (ch_err[i].sense == sshdr->sense_key &&
17362306a36Sopenharmony_ci			    ch_err[i].asc   == sshdr->asc &&
17462306a36Sopenharmony_ci			    ch_err[i].ascq  == sshdr->ascq) {
17562306a36Sopenharmony_ci				errno = -ch_err[i].errno;
17662306a36Sopenharmony_ci				break;
17762306a36Sopenharmony_ci			}
17862306a36Sopenharmony_ci		}
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci	if (errno == 0)
18162306a36Sopenharmony_ci		errno = -EIO;
18262306a36Sopenharmony_ci	return errno;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic int
18662306a36Sopenharmony_cich_do_scsi(scsi_changer *ch, unsigned char *cmd, int cmd_len,
18762306a36Sopenharmony_ci	   void *buffer, unsigned int buflength, enum req_op op)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	int errno, retries = 0, timeout, result;
19062306a36Sopenharmony_ci	struct scsi_sense_hdr sshdr;
19162306a36Sopenharmony_ci	const struct scsi_exec_args exec_args = {
19262306a36Sopenharmony_ci		.sshdr = &sshdr,
19362306a36Sopenharmony_ci	};
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	timeout = (cmd[0] == INITIALIZE_ELEMENT_STATUS)
19662306a36Sopenharmony_ci		? timeout_init : timeout_move;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci retry:
19962306a36Sopenharmony_ci	errno = 0;
20062306a36Sopenharmony_ci	result = scsi_execute_cmd(ch->device, cmd, op, buffer, buflength,
20162306a36Sopenharmony_ci				  timeout * HZ, MAX_RETRIES, &exec_args);
20262306a36Sopenharmony_ci	if (result < 0)
20362306a36Sopenharmony_ci		return result;
20462306a36Sopenharmony_ci	if (scsi_sense_valid(&sshdr)) {
20562306a36Sopenharmony_ci		if (debug)
20662306a36Sopenharmony_ci			scsi_print_sense_hdr(ch->device, ch->name, &sshdr);
20762306a36Sopenharmony_ci		errno = ch_find_errno(&sshdr);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci		switch(sshdr.sense_key) {
21062306a36Sopenharmony_ci		case UNIT_ATTENTION:
21162306a36Sopenharmony_ci			ch->unit_attention = 1;
21262306a36Sopenharmony_ci			if (retries++ < 3)
21362306a36Sopenharmony_ci				goto retry;
21462306a36Sopenharmony_ci			break;
21562306a36Sopenharmony_ci		}
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci	return errno;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci/* ------------------------------------------------------------------------ */
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic int
22362306a36Sopenharmony_cich_elem_to_typecode(scsi_changer *ch, u_int elem)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	int i;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	for (i = 0; i < CH_TYPES; i++) {
22862306a36Sopenharmony_ci		if (elem >= ch->firsts[i]  &&
22962306a36Sopenharmony_ci		    elem <  ch->firsts[i] +
23062306a36Sopenharmony_ci	            ch->counts[i])
23162306a36Sopenharmony_ci			return i+1;
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci	return 0;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int
23762306a36Sopenharmony_cich_read_element_status(scsi_changer *ch, u_int elem, char *data)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	u_char  cmd[12];
24062306a36Sopenharmony_ci	u_char  *buffer;
24162306a36Sopenharmony_ci	int     result;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	buffer = kmalloc(512, GFP_KERNEL);
24462306a36Sopenharmony_ci	if(!buffer)
24562306a36Sopenharmony_ci		return -ENOMEM;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci retry:
24862306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
24962306a36Sopenharmony_ci	cmd[0] = READ_ELEMENT_STATUS;
25062306a36Sopenharmony_ci	cmd[1] = ((ch->device->lun & 0x7) << 5) |
25162306a36Sopenharmony_ci		(ch->voltags ? 0x10 : 0) |
25262306a36Sopenharmony_ci		ch_elem_to_typecode(ch,elem);
25362306a36Sopenharmony_ci	cmd[2] = (elem >> 8) & 0xff;
25462306a36Sopenharmony_ci	cmd[3] = elem        & 0xff;
25562306a36Sopenharmony_ci	cmd[5] = 1;
25662306a36Sopenharmony_ci	cmd[9] = 255;
25762306a36Sopenharmony_ci	if (0 == (result = ch_do_scsi(ch, cmd, 12,
25862306a36Sopenharmony_ci				      buffer, 256, REQ_OP_DRV_IN))) {
25962306a36Sopenharmony_ci		if (((buffer[16] << 8) | buffer[17]) != elem) {
26062306a36Sopenharmony_ci			DPRINTK("asked for element 0x%02x, got 0x%02x\n",
26162306a36Sopenharmony_ci				elem,(buffer[16] << 8) | buffer[17]);
26262306a36Sopenharmony_ci			kfree(buffer);
26362306a36Sopenharmony_ci			return -EIO;
26462306a36Sopenharmony_ci		}
26562306a36Sopenharmony_ci		memcpy(data,buffer+16,16);
26662306a36Sopenharmony_ci	} else {
26762306a36Sopenharmony_ci		if (ch->voltags) {
26862306a36Sopenharmony_ci			ch->voltags = 0;
26962306a36Sopenharmony_ci			VPRINTK(KERN_INFO, "device has no volume tag support\n");
27062306a36Sopenharmony_ci			goto retry;
27162306a36Sopenharmony_ci		}
27262306a36Sopenharmony_ci		DPRINTK("READ ELEMENT STATUS for element 0x%x failed\n",elem);
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci	kfree(buffer);
27562306a36Sopenharmony_ci	return result;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic int
27962306a36Sopenharmony_cich_init_elem(scsi_changer *ch)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	int err;
28262306a36Sopenharmony_ci	u_char cmd[6];
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	VPRINTK(KERN_INFO, "INITIALIZE ELEMENT STATUS, may take some time ...\n");
28562306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
28662306a36Sopenharmony_ci	cmd[0] = INITIALIZE_ELEMENT_STATUS;
28762306a36Sopenharmony_ci	cmd[1] = (ch->device->lun & 0x7) << 5;
28862306a36Sopenharmony_ci	err = ch_do_scsi(ch, cmd, 6, NULL, 0, REQ_OP_DRV_IN);
28962306a36Sopenharmony_ci	VPRINTK(KERN_INFO, "... finished\n");
29062306a36Sopenharmony_ci	return err;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic int
29462306a36Sopenharmony_cich_readconfig(scsi_changer *ch)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	u_char  cmd[10], data[16];
29762306a36Sopenharmony_ci	u_char  *buffer;
29862306a36Sopenharmony_ci	int     result,id,lun,i;
29962306a36Sopenharmony_ci	u_int   elem;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	buffer = kzalloc(512, GFP_KERNEL);
30262306a36Sopenharmony_ci	if (!buffer)
30362306a36Sopenharmony_ci		return -ENOMEM;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
30662306a36Sopenharmony_ci	cmd[0] = MODE_SENSE;
30762306a36Sopenharmony_ci	cmd[1] = (ch->device->lun & 0x7) << 5;
30862306a36Sopenharmony_ci	cmd[2] = 0x1d;
30962306a36Sopenharmony_ci	cmd[4] = 255;
31062306a36Sopenharmony_ci	result = ch_do_scsi(ch, cmd, 10, buffer, 255, REQ_OP_DRV_IN);
31162306a36Sopenharmony_ci	if (0 != result) {
31262306a36Sopenharmony_ci		cmd[1] |= (1<<3);
31362306a36Sopenharmony_ci		result  = ch_do_scsi(ch, cmd, 10, buffer, 255, REQ_OP_DRV_IN);
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci	if (0 == result) {
31662306a36Sopenharmony_ci		ch->firsts[CHET_MT] =
31762306a36Sopenharmony_ci			(buffer[buffer[3]+ 6] << 8) | buffer[buffer[3]+ 7];
31862306a36Sopenharmony_ci		ch->counts[CHET_MT] =
31962306a36Sopenharmony_ci			(buffer[buffer[3]+ 8] << 8) | buffer[buffer[3]+ 9];
32062306a36Sopenharmony_ci		ch->firsts[CHET_ST] =
32162306a36Sopenharmony_ci			(buffer[buffer[3]+10] << 8) | buffer[buffer[3]+11];
32262306a36Sopenharmony_ci		ch->counts[CHET_ST] =
32362306a36Sopenharmony_ci			(buffer[buffer[3]+12] << 8) | buffer[buffer[3]+13];
32462306a36Sopenharmony_ci		ch->firsts[CHET_IE] =
32562306a36Sopenharmony_ci			(buffer[buffer[3]+14] << 8) | buffer[buffer[3]+15];
32662306a36Sopenharmony_ci		ch->counts[CHET_IE] =
32762306a36Sopenharmony_ci			(buffer[buffer[3]+16] << 8) | buffer[buffer[3]+17];
32862306a36Sopenharmony_ci		ch->firsts[CHET_DT] =
32962306a36Sopenharmony_ci			(buffer[buffer[3]+18] << 8) | buffer[buffer[3]+19];
33062306a36Sopenharmony_ci		ch->counts[CHET_DT] =
33162306a36Sopenharmony_ci			(buffer[buffer[3]+20] << 8) | buffer[buffer[3]+21];
33262306a36Sopenharmony_ci		VPRINTK(KERN_INFO, "type #1 (mt): 0x%x+%d [medium transport]\n",
33362306a36Sopenharmony_ci			ch->firsts[CHET_MT],
33462306a36Sopenharmony_ci			ch->counts[CHET_MT]);
33562306a36Sopenharmony_ci		VPRINTK(KERN_INFO, "type #2 (st): 0x%x+%d [storage]\n",
33662306a36Sopenharmony_ci			ch->firsts[CHET_ST],
33762306a36Sopenharmony_ci			ch->counts[CHET_ST]);
33862306a36Sopenharmony_ci		VPRINTK(KERN_INFO, "type #3 (ie): 0x%x+%d [import/export]\n",
33962306a36Sopenharmony_ci			ch->firsts[CHET_IE],
34062306a36Sopenharmony_ci			ch->counts[CHET_IE]);
34162306a36Sopenharmony_ci		VPRINTK(KERN_INFO, "type #4 (dt): 0x%x+%d [data transfer]\n",
34262306a36Sopenharmony_ci			ch->firsts[CHET_DT],
34362306a36Sopenharmony_ci			ch->counts[CHET_DT]);
34462306a36Sopenharmony_ci	} else {
34562306a36Sopenharmony_ci		VPRINTK(KERN_INFO, "reading element address assignment page failed!\n");
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	/* vendor specific element types */
34962306a36Sopenharmony_ci	for (i = 0; i < 4; i++) {
35062306a36Sopenharmony_ci		if (0 == vendor_counts[i])
35162306a36Sopenharmony_ci			continue;
35262306a36Sopenharmony_ci		if (NULL == vendor_labels[i])
35362306a36Sopenharmony_ci			continue;
35462306a36Sopenharmony_ci		ch->firsts[CHET_V1+i] = vendor_firsts[i];
35562306a36Sopenharmony_ci		ch->counts[CHET_V1+i] = vendor_counts[i];
35662306a36Sopenharmony_ci		VPRINTK(KERN_INFO, "type #%d (v%d): 0x%x+%d [%s, vendor specific]\n",
35762306a36Sopenharmony_ci			i+5,i+1,vendor_firsts[i],vendor_counts[i],
35862306a36Sopenharmony_ci			vendor_labels[i]);
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/* look up the devices of the data transfer elements */
36262306a36Sopenharmony_ci	ch->dt = kcalloc(ch->counts[CHET_DT], sizeof(*ch->dt),
36362306a36Sopenharmony_ci			 GFP_KERNEL);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	if (!ch->dt) {
36662306a36Sopenharmony_ci		kfree(buffer);
36762306a36Sopenharmony_ci		return -ENOMEM;
36862306a36Sopenharmony_ci	}
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	for (elem = 0; elem < ch->counts[CHET_DT]; elem++) {
37162306a36Sopenharmony_ci		id  = -1;
37262306a36Sopenharmony_ci		lun = 0;
37362306a36Sopenharmony_ci		if (elem < CH_DT_MAX  &&  -1 != dt_id[elem]) {
37462306a36Sopenharmony_ci			id  = dt_id[elem];
37562306a36Sopenharmony_ci			lun = dt_lun[elem];
37662306a36Sopenharmony_ci			VPRINTK(KERN_INFO, "dt 0x%x: [insmod option] ",
37762306a36Sopenharmony_ci				elem+ch->firsts[CHET_DT]);
37862306a36Sopenharmony_ci		} else if (0 != ch_read_element_status
37962306a36Sopenharmony_ci			   (ch,elem+ch->firsts[CHET_DT],data)) {
38062306a36Sopenharmony_ci			VPRINTK(KERN_INFO, "dt 0x%x: READ ELEMENT STATUS failed\n",
38162306a36Sopenharmony_ci				elem+ch->firsts[CHET_DT]);
38262306a36Sopenharmony_ci		} else {
38362306a36Sopenharmony_ci			VPRINTK(KERN_INFO, "dt 0x%x: ",elem+ch->firsts[CHET_DT]);
38462306a36Sopenharmony_ci			if (data[6] & 0x80) {
38562306a36Sopenharmony_ci				VPRINTK(KERN_CONT, "not this SCSI bus\n");
38662306a36Sopenharmony_ci				ch->dt[elem] = NULL;
38762306a36Sopenharmony_ci			} else if (0 == (data[6] & 0x30)) {
38862306a36Sopenharmony_ci				VPRINTK(KERN_CONT, "ID/LUN unknown\n");
38962306a36Sopenharmony_ci				ch->dt[elem] = NULL;
39062306a36Sopenharmony_ci			} else {
39162306a36Sopenharmony_ci				id  = ch->device->id;
39262306a36Sopenharmony_ci				lun = 0;
39362306a36Sopenharmony_ci				if (data[6] & 0x20) id  = data[7];
39462306a36Sopenharmony_ci				if (data[6] & 0x10) lun = data[6] & 7;
39562306a36Sopenharmony_ci			}
39662306a36Sopenharmony_ci		}
39762306a36Sopenharmony_ci		if (-1 != id) {
39862306a36Sopenharmony_ci			VPRINTK(KERN_CONT, "ID %i, LUN %i, ",id,lun);
39962306a36Sopenharmony_ci			ch->dt[elem] =
40062306a36Sopenharmony_ci				scsi_device_lookup(ch->device->host,
40162306a36Sopenharmony_ci						   ch->device->channel,
40262306a36Sopenharmony_ci						   id,lun);
40362306a36Sopenharmony_ci			if (!ch->dt[elem]) {
40462306a36Sopenharmony_ci				/* should not happen */
40562306a36Sopenharmony_ci				VPRINTK(KERN_CONT, "Huh? device not found!\n");
40662306a36Sopenharmony_ci			} else {
40762306a36Sopenharmony_ci				VPRINTK(KERN_CONT, "name: %8.8s %16.16s %4.4s\n",
40862306a36Sopenharmony_ci					ch->dt[elem]->vendor,
40962306a36Sopenharmony_ci					ch->dt[elem]->model,
41062306a36Sopenharmony_ci					ch->dt[elem]->rev);
41162306a36Sopenharmony_ci			}
41262306a36Sopenharmony_ci		}
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci	ch->voltags = 1;
41562306a36Sopenharmony_ci	kfree(buffer);
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	return 0;
41862306a36Sopenharmony_ci}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci/* ------------------------------------------------------------------------ */
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_cistatic int
42362306a36Sopenharmony_cich_position(scsi_changer *ch, u_int trans, u_int elem, int rotate)
42462306a36Sopenharmony_ci{
42562306a36Sopenharmony_ci	u_char  cmd[10];
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	DPRINTK("position: 0x%x\n",elem);
42862306a36Sopenharmony_ci	if (0 == trans)
42962306a36Sopenharmony_ci		trans = ch->firsts[CHET_MT];
43062306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
43162306a36Sopenharmony_ci	cmd[0]  = POSITION_TO_ELEMENT;
43262306a36Sopenharmony_ci	cmd[1]  = (ch->device->lun & 0x7) << 5;
43362306a36Sopenharmony_ci	cmd[2]  = (trans >> 8) & 0xff;
43462306a36Sopenharmony_ci	cmd[3]  =  trans       & 0xff;
43562306a36Sopenharmony_ci	cmd[4]  = (elem  >> 8) & 0xff;
43662306a36Sopenharmony_ci	cmd[5]  =  elem        & 0xff;
43762306a36Sopenharmony_ci	cmd[8]  = rotate ? 1 : 0;
43862306a36Sopenharmony_ci	return ch_do_scsi(ch, cmd, 10, NULL, 0, REQ_OP_DRV_IN);
43962306a36Sopenharmony_ci}
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_cistatic int
44262306a36Sopenharmony_cich_move(scsi_changer *ch, u_int trans, u_int src, u_int dest, int rotate)
44362306a36Sopenharmony_ci{
44462306a36Sopenharmony_ci	u_char  cmd[12];
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	DPRINTK("move: 0x%x => 0x%x\n",src,dest);
44762306a36Sopenharmony_ci	if (0 == trans)
44862306a36Sopenharmony_ci		trans = ch->firsts[CHET_MT];
44962306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
45062306a36Sopenharmony_ci	cmd[0]  = MOVE_MEDIUM;
45162306a36Sopenharmony_ci	cmd[1]  = (ch->device->lun & 0x7) << 5;
45262306a36Sopenharmony_ci	cmd[2]  = (trans >> 8) & 0xff;
45362306a36Sopenharmony_ci	cmd[3]  =  trans       & 0xff;
45462306a36Sopenharmony_ci	cmd[4]  = (src   >> 8) & 0xff;
45562306a36Sopenharmony_ci	cmd[5]  =  src         & 0xff;
45662306a36Sopenharmony_ci	cmd[6]  = (dest  >> 8) & 0xff;
45762306a36Sopenharmony_ci	cmd[7]  =  dest        & 0xff;
45862306a36Sopenharmony_ci	cmd[10] = rotate ? 1 : 0;
45962306a36Sopenharmony_ci	return ch_do_scsi(ch, cmd, 12, NULL, 0, REQ_OP_DRV_IN);
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_cistatic int
46362306a36Sopenharmony_cich_exchange(scsi_changer *ch, u_int trans, u_int src,
46462306a36Sopenharmony_ci	    u_int dest1, u_int dest2, int rotate1, int rotate2)
46562306a36Sopenharmony_ci{
46662306a36Sopenharmony_ci	u_char  cmd[12];
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	DPRINTK("exchange: 0x%x => 0x%x => 0x%x\n",
46962306a36Sopenharmony_ci		src,dest1,dest2);
47062306a36Sopenharmony_ci	if (0 == trans)
47162306a36Sopenharmony_ci		trans = ch->firsts[CHET_MT];
47262306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
47362306a36Sopenharmony_ci	cmd[0]  = EXCHANGE_MEDIUM;
47462306a36Sopenharmony_ci	cmd[1]  = (ch->device->lun & 0x7) << 5;
47562306a36Sopenharmony_ci	cmd[2]  = (trans >> 8) & 0xff;
47662306a36Sopenharmony_ci	cmd[3]  =  trans       & 0xff;
47762306a36Sopenharmony_ci	cmd[4]  = (src   >> 8) & 0xff;
47862306a36Sopenharmony_ci	cmd[5]  =  src         & 0xff;
47962306a36Sopenharmony_ci	cmd[6]  = (dest1 >> 8) & 0xff;
48062306a36Sopenharmony_ci	cmd[7]  =  dest1       & 0xff;
48162306a36Sopenharmony_ci	cmd[8]  = (dest2 >> 8) & 0xff;
48262306a36Sopenharmony_ci	cmd[9]  =  dest2       & 0xff;
48362306a36Sopenharmony_ci	cmd[10] = (rotate1 ? 1 : 0) | (rotate2 ? 2 : 0);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	return ch_do_scsi(ch, cmd, 12, NULL, 0, REQ_OP_DRV_IN);
48662306a36Sopenharmony_ci}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_cistatic void
48962306a36Sopenharmony_cich_check_voltag(char *tag)
49062306a36Sopenharmony_ci{
49162306a36Sopenharmony_ci	int i;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	for (i = 0; i < 32; i++) {
49462306a36Sopenharmony_ci		/* restrict to ascii */
49562306a36Sopenharmony_ci		if (tag[i] >= 0x7f || tag[i] < 0x20)
49662306a36Sopenharmony_ci			tag[i] = ' ';
49762306a36Sopenharmony_ci		/* don't allow search wildcards */
49862306a36Sopenharmony_ci		if (tag[i] == '?' ||
49962306a36Sopenharmony_ci		    tag[i] == '*')
50062306a36Sopenharmony_ci			tag[i] = ' ';
50162306a36Sopenharmony_ci	}
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_cistatic int
50562306a36Sopenharmony_cich_set_voltag(scsi_changer *ch, u_int elem,
50662306a36Sopenharmony_ci	      int alternate, int clear, u_char *tag)
50762306a36Sopenharmony_ci{
50862306a36Sopenharmony_ci	u_char  cmd[12];
50962306a36Sopenharmony_ci	u_char  *buffer;
51062306a36Sopenharmony_ci	int result;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	buffer = kzalloc(512, GFP_KERNEL);
51362306a36Sopenharmony_ci	if (!buffer)
51462306a36Sopenharmony_ci		return -ENOMEM;
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	DPRINTK("%s %s voltag: 0x%x => \"%s\"\n",
51762306a36Sopenharmony_ci		clear     ? "clear"     : "set",
51862306a36Sopenharmony_ci		alternate ? "alternate" : "primary",
51962306a36Sopenharmony_ci		elem, tag);
52062306a36Sopenharmony_ci	memset(cmd,0,sizeof(cmd));
52162306a36Sopenharmony_ci	cmd[0]  = SEND_VOLUME_TAG;
52262306a36Sopenharmony_ci	cmd[1] = ((ch->device->lun & 0x7) << 5) |
52362306a36Sopenharmony_ci		ch_elem_to_typecode(ch,elem);
52462306a36Sopenharmony_ci	cmd[2] = (elem >> 8) & 0xff;
52562306a36Sopenharmony_ci	cmd[3] = elem        & 0xff;
52662306a36Sopenharmony_ci	cmd[5] = clear
52762306a36Sopenharmony_ci		? (alternate ? 0x0d : 0x0c)
52862306a36Sopenharmony_ci		: (alternate ? 0x0b : 0x0a);
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	cmd[9] = 255;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	memcpy(buffer,tag,32);
53362306a36Sopenharmony_ci	ch_check_voltag(buffer);
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	result = ch_do_scsi(ch, cmd, 12, buffer, 256, REQ_OP_DRV_OUT);
53662306a36Sopenharmony_ci	kfree(buffer);
53762306a36Sopenharmony_ci	return result;
53862306a36Sopenharmony_ci}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_cistatic int ch_gstatus(scsi_changer *ch, int type, unsigned char __user *dest)
54162306a36Sopenharmony_ci{
54262306a36Sopenharmony_ci	int retval = 0;
54362306a36Sopenharmony_ci	u_char data[16];
54462306a36Sopenharmony_ci	unsigned int i;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	mutex_lock(&ch->lock);
54762306a36Sopenharmony_ci	for (i = 0; i < ch->counts[type]; i++) {
54862306a36Sopenharmony_ci		if (0 != ch_read_element_status
54962306a36Sopenharmony_ci		    (ch, ch->firsts[type]+i,data)) {
55062306a36Sopenharmony_ci			retval = -EIO;
55162306a36Sopenharmony_ci			break;
55262306a36Sopenharmony_ci		}
55362306a36Sopenharmony_ci		put_user(data[2], dest+i);
55462306a36Sopenharmony_ci		if (data[2] & CESTATUS_EXCEPT)
55562306a36Sopenharmony_ci			VPRINTK(KERN_INFO, "element 0x%x: asc=0x%x, ascq=0x%x\n",
55662306a36Sopenharmony_ci				ch->firsts[type]+i,
55762306a36Sopenharmony_ci				(int)data[4],(int)data[5]);
55862306a36Sopenharmony_ci		retval = ch_read_element_status
55962306a36Sopenharmony_ci			(ch, ch->firsts[type]+i,data);
56062306a36Sopenharmony_ci		if (0 != retval)
56162306a36Sopenharmony_ci			break;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci	mutex_unlock(&ch->lock);
56462306a36Sopenharmony_ci	return retval;
56562306a36Sopenharmony_ci}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci/* ------------------------------------------------------------------------ */
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_cistatic void ch_destroy(struct kref *ref)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	scsi_changer *ch = container_of(ref, scsi_changer, ref);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	ch->device = NULL;
57462306a36Sopenharmony_ci	kfree(ch->dt);
57562306a36Sopenharmony_ci	kfree(ch);
57662306a36Sopenharmony_ci}
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_cistatic int
57962306a36Sopenharmony_cich_release(struct inode *inode, struct file *file)
58062306a36Sopenharmony_ci{
58162306a36Sopenharmony_ci	scsi_changer *ch = file->private_data;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	scsi_device_put(ch->device);
58462306a36Sopenharmony_ci	file->private_data = NULL;
58562306a36Sopenharmony_ci	kref_put(&ch->ref, ch_destroy);
58662306a36Sopenharmony_ci	return 0;
58762306a36Sopenharmony_ci}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_cistatic int
59062306a36Sopenharmony_cich_open(struct inode *inode, struct file *file)
59162306a36Sopenharmony_ci{
59262306a36Sopenharmony_ci	scsi_changer *ch;
59362306a36Sopenharmony_ci	int minor = iminor(inode);
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	spin_lock(&ch_index_lock);
59662306a36Sopenharmony_ci	ch = idr_find(&ch_index_idr, minor);
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	if (ch == NULL || !kref_get_unless_zero(&ch->ref)) {
59962306a36Sopenharmony_ci		spin_unlock(&ch_index_lock);
60062306a36Sopenharmony_ci		return -ENXIO;
60162306a36Sopenharmony_ci	}
60262306a36Sopenharmony_ci	spin_unlock(&ch_index_lock);
60362306a36Sopenharmony_ci	if (scsi_device_get(ch->device)) {
60462306a36Sopenharmony_ci		kref_put(&ch->ref, ch_destroy);
60562306a36Sopenharmony_ci		return -ENXIO;
60662306a36Sopenharmony_ci	}
60762306a36Sopenharmony_ci	/* Synchronize with ch_probe() */
60862306a36Sopenharmony_ci	mutex_lock(&ch->lock);
60962306a36Sopenharmony_ci	file->private_data = ch;
61062306a36Sopenharmony_ci	mutex_unlock(&ch->lock);
61162306a36Sopenharmony_ci	return 0;
61262306a36Sopenharmony_ci}
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_cistatic int
61562306a36Sopenharmony_cich_checkrange(scsi_changer *ch, unsigned int type, unsigned int unit)
61662306a36Sopenharmony_ci{
61762306a36Sopenharmony_ci	if (type >= CH_TYPES  ||  unit >= ch->counts[type])
61862306a36Sopenharmony_ci		return -1;
61962306a36Sopenharmony_ci	return 0;
62062306a36Sopenharmony_ci}
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_cistruct changer_element_status32 {
62362306a36Sopenharmony_ci	int		ces_type;
62462306a36Sopenharmony_ci	compat_uptr_t	ces_data;
62562306a36Sopenharmony_ci};
62662306a36Sopenharmony_ci#define CHIOGSTATUS32  _IOW('c', 8, struct changer_element_status32)
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_cistatic long ch_ioctl(struct file *file,
62962306a36Sopenharmony_ci		    unsigned int cmd, unsigned long arg)
63062306a36Sopenharmony_ci{
63162306a36Sopenharmony_ci	scsi_changer *ch = file->private_data;
63262306a36Sopenharmony_ci	int retval;
63362306a36Sopenharmony_ci	void __user *argp = (void __user *)arg;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	retval = scsi_ioctl_block_when_processing_errors(ch->device, cmd,
63662306a36Sopenharmony_ci			file->f_flags & O_NDELAY);
63762306a36Sopenharmony_ci	if (retval)
63862306a36Sopenharmony_ci		return retval;
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	switch (cmd) {
64162306a36Sopenharmony_ci	case CHIOGPARAMS:
64262306a36Sopenharmony_ci	{
64362306a36Sopenharmony_ci		struct changer_params params;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci		params.cp_curpicker = 0;
64662306a36Sopenharmony_ci		params.cp_npickers  = ch->counts[CHET_MT];
64762306a36Sopenharmony_ci		params.cp_nslots    = ch->counts[CHET_ST];
64862306a36Sopenharmony_ci		params.cp_nportals  = ch->counts[CHET_IE];
64962306a36Sopenharmony_ci		params.cp_ndrives   = ch->counts[CHET_DT];
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci		if (copy_to_user(argp, &params, sizeof(params)))
65262306a36Sopenharmony_ci			return -EFAULT;
65362306a36Sopenharmony_ci		return 0;
65462306a36Sopenharmony_ci	}
65562306a36Sopenharmony_ci	case CHIOGVPARAMS:
65662306a36Sopenharmony_ci	{
65762306a36Sopenharmony_ci		struct changer_vendor_params vparams;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci		memset(&vparams,0,sizeof(vparams));
66062306a36Sopenharmony_ci		if (ch->counts[CHET_V1]) {
66162306a36Sopenharmony_ci			vparams.cvp_n1  = ch->counts[CHET_V1];
66262306a36Sopenharmony_ci			strncpy(vparams.cvp_label1,vendor_labels[0],16);
66362306a36Sopenharmony_ci		}
66462306a36Sopenharmony_ci		if (ch->counts[CHET_V2]) {
66562306a36Sopenharmony_ci			vparams.cvp_n2  = ch->counts[CHET_V2];
66662306a36Sopenharmony_ci			strncpy(vparams.cvp_label2,vendor_labels[1],16);
66762306a36Sopenharmony_ci		}
66862306a36Sopenharmony_ci		if (ch->counts[CHET_V3]) {
66962306a36Sopenharmony_ci			vparams.cvp_n3  = ch->counts[CHET_V3];
67062306a36Sopenharmony_ci			strncpy(vparams.cvp_label3,vendor_labels[2],16);
67162306a36Sopenharmony_ci		}
67262306a36Sopenharmony_ci		if (ch->counts[CHET_V4]) {
67362306a36Sopenharmony_ci			vparams.cvp_n4  = ch->counts[CHET_V4];
67462306a36Sopenharmony_ci			strncpy(vparams.cvp_label4,vendor_labels[3],16);
67562306a36Sopenharmony_ci		}
67662306a36Sopenharmony_ci		if (copy_to_user(argp, &vparams, sizeof(vparams)))
67762306a36Sopenharmony_ci			return -EFAULT;
67862306a36Sopenharmony_ci		return 0;
67962306a36Sopenharmony_ci	}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci	case CHIOPOSITION:
68262306a36Sopenharmony_ci	{
68362306a36Sopenharmony_ci		struct changer_position pos;
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci		if (copy_from_user(&pos, argp, sizeof (pos)))
68662306a36Sopenharmony_ci			return -EFAULT;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci		if (0 != ch_checkrange(ch, pos.cp_type, pos.cp_unit)) {
68962306a36Sopenharmony_ci			DPRINTK("CHIOPOSITION: invalid parameter\n");
69062306a36Sopenharmony_ci			return -EBADSLT;
69162306a36Sopenharmony_ci		}
69262306a36Sopenharmony_ci		mutex_lock(&ch->lock);
69362306a36Sopenharmony_ci		retval = ch_position(ch,0,
69462306a36Sopenharmony_ci				     ch->firsts[pos.cp_type] + pos.cp_unit,
69562306a36Sopenharmony_ci				     pos.cp_flags & CP_INVERT);
69662306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
69762306a36Sopenharmony_ci		return retval;
69862306a36Sopenharmony_ci	}
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	case CHIOMOVE:
70162306a36Sopenharmony_ci	{
70262306a36Sopenharmony_ci		struct changer_move mv;
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci		if (copy_from_user(&mv, argp, sizeof (mv)))
70562306a36Sopenharmony_ci			return -EFAULT;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci		if (0 != ch_checkrange(ch, mv.cm_fromtype, mv.cm_fromunit) ||
70862306a36Sopenharmony_ci		    0 != ch_checkrange(ch, mv.cm_totype,   mv.cm_tounit  )) {
70962306a36Sopenharmony_ci			DPRINTK("CHIOMOVE: invalid parameter\n");
71062306a36Sopenharmony_ci			return -EBADSLT;
71162306a36Sopenharmony_ci		}
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci		mutex_lock(&ch->lock);
71462306a36Sopenharmony_ci		retval = ch_move(ch,0,
71562306a36Sopenharmony_ci				 ch->firsts[mv.cm_fromtype] + mv.cm_fromunit,
71662306a36Sopenharmony_ci				 ch->firsts[mv.cm_totype]   + mv.cm_tounit,
71762306a36Sopenharmony_ci				 mv.cm_flags & CM_INVERT);
71862306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
71962306a36Sopenharmony_ci		return retval;
72062306a36Sopenharmony_ci	}
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	case CHIOEXCHANGE:
72362306a36Sopenharmony_ci	{
72462306a36Sopenharmony_ci		struct changer_exchange mv;
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci		if (copy_from_user(&mv, argp, sizeof (mv)))
72762306a36Sopenharmony_ci			return -EFAULT;
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci		if (0 != ch_checkrange(ch, mv.ce_srctype,  mv.ce_srcunit ) ||
73062306a36Sopenharmony_ci		    0 != ch_checkrange(ch, mv.ce_fdsttype, mv.ce_fdstunit) ||
73162306a36Sopenharmony_ci		    0 != ch_checkrange(ch, mv.ce_sdsttype, mv.ce_sdstunit)) {
73262306a36Sopenharmony_ci			DPRINTK("CHIOEXCHANGE: invalid parameter\n");
73362306a36Sopenharmony_ci			return -EBADSLT;
73462306a36Sopenharmony_ci		}
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci		mutex_lock(&ch->lock);
73762306a36Sopenharmony_ci		retval = ch_exchange
73862306a36Sopenharmony_ci			(ch,0,
73962306a36Sopenharmony_ci			 ch->firsts[mv.ce_srctype]  + mv.ce_srcunit,
74062306a36Sopenharmony_ci			 ch->firsts[mv.ce_fdsttype] + mv.ce_fdstunit,
74162306a36Sopenharmony_ci			 ch->firsts[mv.ce_sdsttype] + mv.ce_sdstunit,
74262306a36Sopenharmony_ci			 mv.ce_flags & CE_INVERT1, mv.ce_flags & CE_INVERT2);
74362306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
74462306a36Sopenharmony_ci		return retval;
74562306a36Sopenharmony_ci	}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	case CHIOGSTATUS:
74862306a36Sopenharmony_ci	{
74962306a36Sopenharmony_ci		struct changer_element_status ces;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci		if (copy_from_user(&ces, argp, sizeof (ces)))
75262306a36Sopenharmony_ci			return -EFAULT;
75362306a36Sopenharmony_ci		if (ces.ces_type < 0 || ces.ces_type >= CH_TYPES)
75462306a36Sopenharmony_ci			return -EINVAL;
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci		return ch_gstatus(ch, ces.ces_type, ces.ces_data);
75762306a36Sopenharmony_ci	}
75862306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
75962306a36Sopenharmony_ci	case CHIOGSTATUS32:
76062306a36Sopenharmony_ci	{
76162306a36Sopenharmony_ci		struct changer_element_status32 ces32;
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci		if (copy_from_user(&ces32, argp, sizeof(ces32)))
76462306a36Sopenharmony_ci			return -EFAULT;
76562306a36Sopenharmony_ci		if (ces32.ces_type < 0 || ces32.ces_type >= CH_TYPES)
76662306a36Sopenharmony_ci			return -EINVAL;
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci		return ch_gstatus(ch, ces32.ces_type,
76962306a36Sopenharmony_ci				  compat_ptr(ces32.ces_data));
77062306a36Sopenharmony_ci	}
77162306a36Sopenharmony_ci#endif
77262306a36Sopenharmony_ci	case CHIOGELEM:
77362306a36Sopenharmony_ci	{
77462306a36Sopenharmony_ci		struct changer_get_element cge;
77562306a36Sopenharmony_ci		u_char ch_cmd[12];
77662306a36Sopenharmony_ci		u_char *buffer;
77762306a36Sopenharmony_ci		unsigned int elem;
77862306a36Sopenharmony_ci		int     result,i;
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci		if (copy_from_user(&cge, argp, sizeof (cge)))
78162306a36Sopenharmony_ci			return -EFAULT;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci		if (0 != ch_checkrange(ch, cge.cge_type, cge.cge_unit))
78462306a36Sopenharmony_ci			return -EINVAL;
78562306a36Sopenharmony_ci		elem = ch->firsts[cge.cge_type] + cge.cge_unit;
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci		buffer = kmalloc(512, GFP_KERNEL);
78862306a36Sopenharmony_ci		if (!buffer)
78962306a36Sopenharmony_ci			return -ENOMEM;
79062306a36Sopenharmony_ci		mutex_lock(&ch->lock);
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	voltag_retry:
79362306a36Sopenharmony_ci		memset(ch_cmd, 0, sizeof(ch_cmd));
79462306a36Sopenharmony_ci		ch_cmd[0] = READ_ELEMENT_STATUS;
79562306a36Sopenharmony_ci		ch_cmd[1] = ((ch->device->lun & 0x7) << 5) |
79662306a36Sopenharmony_ci			(ch->voltags ? 0x10 : 0) |
79762306a36Sopenharmony_ci			ch_elem_to_typecode(ch,elem);
79862306a36Sopenharmony_ci		ch_cmd[2] = (elem >> 8) & 0xff;
79962306a36Sopenharmony_ci		ch_cmd[3] = elem        & 0xff;
80062306a36Sopenharmony_ci		ch_cmd[5] = 1;
80162306a36Sopenharmony_ci		ch_cmd[9] = 255;
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci		result = ch_do_scsi(ch, ch_cmd, 12, buffer, 256, REQ_OP_DRV_IN);
80462306a36Sopenharmony_ci		if (!result) {
80562306a36Sopenharmony_ci			cge.cge_status = buffer[18];
80662306a36Sopenharmony_ci			cge.cge_flags = 0;
80762306a36Sopenharmony_ci			if (buffer[18] & CESTATUS_EXCEPT) {
80862306a36Sopenharmony_ci				cge.cge_errno = EIO;
80962306a36Sopenharmony_ci			}
81062306a36Sopenharmony_ci			if (buffer[25] & 0x80) {
81162306a36Sopenharmony_ci				cge.cge_flags |= CGE_SRC;
81262306a36Sopenharmony_ci				if (buffer[25] & 0x40)
81362306a36Sopenharmony_ci					cge.cge_flags |= CGE_INVERT;
81462306a36Sopenharmony_ci				elem = (buffer[26]<<8) | buffer[27];
81562306a36Sopenharmony_ci				for (i = 0; i < 4; i++) {
81662306a36Sopenharmony_ci					if (elem >= ch->firsts[i] &&
81762306a36Sopenharmony_ci					    elem <  ch->firsts[i] + ch->counts[i]) {
81862306a36Sopenharmony_ci						cge.cge_srctype = i;
81962306a36Sopenharmony_ci						cge.cge_srcunit = elem-ch->firsts[i];
82062306a36Sopenharmony_ci					}
82162306a36Sopenharmony_ci				}
82262306a36Sopenharmony_ci			}
82362306a36Sopenharmony_ci			if ((buffer[22] & 0x30) == 0x30) {
82462306a36Sopenharmony_ci				cge.cge_flags |= CGE_IDLUN;
82562306a36Sopenharmony_ci				cge.cge_id  = buffer[23];
82662306a36Sopenharmony_ci				cge.cge_lun = buffer[22] & 7;
82762306a36Sopenharmony_ci			}
82862306a36Sopenharmony_ci			if (buffer[9] & 0x80) {
82962306a36Sopenharmony_ci				cge.cge_flags |= CGE_PVOLTAG;
83062306a36Sopenharmony_ci				memcpy(cge.cge_pvoltag,buffer+28,36);
83162306a36Sopenharmony_ci			}
83262306a36Sopenharmony_ci			if (buffer[9] & 0x40) {
83362306a36Sopenharmony_ci				cge.cge_flags |= CGE_AVOLTAG;
83462306a36Sopenharmony_ci				memcpy(cge.cge_avoltag,buffer+64,36);
83562306a36Sopenharmony_ci			}
83662306a36Sopenharmony_ci		} else if (ch->voltags) {
83762306a36Sopenharmony_ci			ch->voltags = 0;
83862306a36Sopenharmony_ci			VPRINTK(KERN_INFO, "device has no volume tag support\n");
83962306a36Sopenharmony_ci			goto voltag_retry;
84062306a36Sopenharmony_ci		}
84162306a36Sopenharmony_ci		kfree(buffer);
84262306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_ci		if (copy_to_user(argp, &cge, sizeof (cge)))
84562306a36Sopenharmony_ci			return -EFAULT;
84662306a36Sopenharmony_ci		return result;
84762306a36Sopenharmony_ci	}
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	case CHIOINITELEM:
85062306a36Sopenharmony_ci	{
85162306a36Sopenharmony_ci		mutex_lock(&ch->lock);
85262306a36Sopenharmony_ci		retval = ch_init_elem(ch);
85362306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
85462306a36Sopenharmony_ci		return retval;
85562306a36Sopenharmony_ci	}
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	case CHIOSVOLTAG:
85862306a36Sopenharmony_ci	{
85962306a36Sopenharmony_ci		struct changer_set_voltag csv;
86062306a36Sopenharmony_ci		int elem;
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_ci		if (copy_from_user(&csv, argp, sizeof(csv)))
86362306a36Sopenharmony_ci			return -EFAULT;
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_ci		if (0 != ch_checkrange(ch, csv.csv_type, csv.csv_unit)) {
86662306a36Sopenharmony_ci			DPRINTK("CHIOSVOLTAG: invalid parameter\n");
86762306a36Sopenharmony_ci			return -EBADSLT;
86862306a36Sopenharmony_ci		}
86962306a36Sopenharmony_ci		elem = ch->firsts[csv.csv_type] + csv.csv_unit;
87062306a36Sopenharmony_ci		mutex_lock(&ch->lock);
87162306a36Sopenharmony_ci		retval = ch_set_voltag(ch, elem,
87262306a36Sopenharmony_ci				       csv.csv_flags & CSV_AVOLTAG,
87362306a36Sopenharmony_ci				       csv.csv_flags & CSV_CLEARTAG,
87462306a36Sopenharmony_ci				       csv.csv_voltag);
87562306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
87662306a36Sopenharmony_ci		return retval;
87762306a36Sopenharmony_ci	}
87862306a36Sopenharmony_ci
87962306a36Sopenharmony_ci	default:
88062306a36Sopenharmony_ci		return scsi_ioctl(ch->device, file->f_mode & FMODE_WRITE, cmd,
88162306a36Sopenharmony_ci				  argp);
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci	}
88462306a36Sopenharmony_ci}
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci/* ------------------------------------------------------------------------ */
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_cistatic int ch_probe(struct device *dev)
88962306a36Sopenharmony_ci{
89062306a36Sopenharmony_ci	struct scsi_device *sd = to_scsi_device(dev);
89162306a36Sopenharmony_ci	struct device *class_dev;
89262306a36Sopenharmony_ci	int ret;
89362306a36Sopenharmony_ci	scsi_changer *ch;
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	if (sd->type != TYPE_MEDIUM_CHANGER)
89662306a36Sopenharmony_ci		return -ENODEV;
89762306a36Sopenharmony_ci
89862306a36Sopenharmony_ci	ch = kzalloc(sizeof(*ch), GFP_KERNEL);
89962306a36Sopenharmony_ci	if (NULL == ch)
90062306a36Sopenharmony_ci		return -ENOMEM;
90162306a36Sopenharmony_ci
90262306a36Sopenharmony_ci	idr_preload(GFP_KERNEL);
90362306a36Sopenharmony_ci	spin_lock(&ch_index_lock);
90462306a36Sopenharmony_ci	ret = idr_alloc(&ch_index_idr, ch, 0, CH_MAX_DEVS + 1, GFP_NOWAIT);
90562306a36Sopenharmony_ci	spin_unlock(&ch_index_lock);
90662306a36Sopenharmony_ci	idr_preload_end();
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_ci	if (ret < 0) {
90962306a36Sopenharmony_ci		if (ret == -ENOSPC)
91062306a36Sopenharmony_ci			ret = -ENODEV;
91162306a36Sopenharmony_ci		goto free_ch;
91262306a36Sopenharmony_ci	}
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci	ch->minor = ret;
91562306a36Sopenharmony_ci	sprintf(ch->name,"ch%d",ch->minor);
91662306a36Sopenharmony_ci	ret = scsi_device_get(sd);
91762306a36Sopenharmony_ci	if (ret) {
91862306a36Sopenharmony_ci		sdev_printk(KERN_WARNING, sd, "ch%d: failed to get device\n",
91962306a36Sopenharmony_ci			    ch->minor);
92062306a36Sopenharmony_ci		goto remove_idr;
92162306a36Sopenharmony_ci	}
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	mutex_init(&ch->lock);
92462306a36Sopenharmony_ci	kref_init(&ch->ref);
92562306a36Sopenharmony_ci	ch->device = sd;
92662306a36Sopenharmony_ci	class_dev = device_create(ch_sysfs_class, dev,
92762306a36Sopenharmony_ci				  MKDEV(SCSI_CHANGER_MAJOR, ch->minor), ch,
92862306a36Sopenharmony_ci				  "s%s", ch->name);
92962306a36Sopenharmony_ci	if (IS_ERR(class_dev)) {
93062306a36Sopenharmony_ci		sdev_printk(KERN_WARNING, sd, "ch%d: device_create failed\n",
93162306a36Sopenharmony_ci			    ch->minor);
93262306a36Sopenharmony_ci		ret = PTR_ERR(class_dev);
93362306a36Sopenharmony_ci		goto put_device;
93462306a36Sopenharmony_ci	}
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	mutex_lock(&ch->lock);
93762306a36Sopenharmony_ci	ret = ch_readconfig(ch);
93862306a36Sopenharmony_ci	if (ret) {
93962306a36Sopenharmony_ci		mutex_unlock(&ch->lock);
94062306a36Sopenharmony_ci		goto destroy_dev;
94162306a36Sopenharmony_ci	}
94262306a36Sopenharmony_ci	if (init)
94362306a36Sopenharmony_ci		ch_init_elem(ch);
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	mutex_unlock(&ch->lock);
94662306a36Sopenharmony_ci	dev_set_drvdata(dev, ch);
94762306a36Sopenharmony_ci	sdev_printk(KERN_INFO, sd, "Attached scsi changer %s\n", ch->name);
94862306a36Sopenharmony_ci
94962306a36Sopenharmony_ci	return 0;
95062306a36Sopenharmony_cidestroy_dev:
95162306a36Sopenharmony_ci	device_destroy(ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR, ch->minor));
95262306a36Sopenharmony_ciput_device:
95362306a36Sopenharmony_ci	scsi_device_put(sd);
95462306a36Sopenharmony_ciremove_idr:
95562306a36Sopenharmony_ci	idr_remove(&ch_index_idr, ch->minor);
95662306a36Sopenharmony_cifree_ch:
95762306a36Sopenharmony_ci	kfree(ch);
95862306a36Sopenharmony_ci	return ret;
95962306a36Sopenharmony_ci}
96062306a36Sopenharmony_ci
96162306a36Sopenharmony_cistatic int ch_remove(struct device *dev)
96262306a36Sopenharmony_ci{
96362306a36Sopenharmony_ci	scsi_changer *ch = dev_get_drvdata(dev);
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci	spin_lock(&ch_index_lock);
96662306a36Sopenharmony_ci	idr_remove(&ch_index_idr, ch->minor);
96762306a36Sopenharmony_ci	dev_set_drvdata(dev, NULL);
96862306a36Sopenharmony_ci	spin_unlock(&ch_index_lock);
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci	device_destroy(ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR,ch->minor));
97162306a36Sopenharmony_ci	scsi_device_put(ch->device);
97262306a36Sopenharmony_ci	kref_put(&ch->ref, ch_destroy);
97362306a36Sopenharmony_ci	return 0;
97462306a36Sopenharmony_ci}
97562306a36Sopenharmony_ci
97662306a36Sopenharmony_cistatic struct scsi_driver ch_template = {
97762306a36Sopenharmony_ci	.gendrv     	= {
97862306a36Sopenharmony_ci		.name	= "ch",
97962306a36Sopenharmony_ci		.owner	= THIS_MODULE,
98062306a36Sopenharmony_ci		.probe  = ch_probe,
98162306a36Sopenharmony_ci		.remove = ch_remove,
98262306a36Sopenharmony_ci	},
98362306a36Sopenharmony_ci};
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_cistatic const struct file_operations changer_fops = {
98662306a36Sopenharmony_ci	.owner		= THIS_MODULE,
98762306a36Sopenharmony_ci	.open		= ch_open,
98862306a36Sopenharmony_ci	.release	= ch_release,
98962306a36Sopenharmony_ci	.unlocked_ioctl	= ch_ioctl,
99062306a36Sopenharmony_ci	.compat_ioctl	= compat_ptr_ioctl,
99162306a36Sopenharmony_ci	.llseek		= noop_llseek,
99262306a36Sopenharmony_ci};
99362306a36Sopenharmony_ci
99462306a36Sopenharmony_cistatic int __init init_ch_module(void)
99562306a36Sopenharmony_ci{
99662306a36Sopenharmony_ci	int rc;
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	printk(KERN_INFO "SCSI Media Changer driver v" VERSION " \n");
99962306a36Sopenharmony_ci        ch_sysfs_class = class_create("scsi_changer");
100062306a36Sopenharmony_ci        if (IS_ERR(ch_sysfs_class)) {
100162306a36Sopenharmony_ci		rc = PTR_ERR(ch_sysfs_class);
100262306a36Sopenharmony_ci		return rc;
100362306a36Sopenharmony_ci        }
100462306a36Sopenharmony_ci	rc = register_chrdev(SCSI_CHANGER_MAJOR,"ch",&changer_fops);
100562306a36Sopenharmony_ci	if (rc < 0) {
100662306a36Sopenharmony_ci		printk("Unable to get major %d for SCSI-Changer\n",
100762306a36Sopenharmony_ci		       SCSI_CHANGER_MAJOR);
100862306a36Sopenharmony_ci		goto fail1;
100962306a36Sopenharmony_ci	}
101062306a36Sopenharmony_ci	rc = scsi_register_driver(&ch_template.gendrv);
101162306a36Sopenharmony_ci	if (rc < 0)
101262306a36Sopenharmony_ci		goto fail2;
101362306a36Sopenharmony_ci	return 0;
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_ci fail2:
101662306a36Sopenharmony_ci	unregister_chrdev(SCSI_CHANGER_MAJOR, "ch");
101762306a36Sopenharmony_ci fail1:
101862306a36Sopenharmony_ci	class_destroy(ch_sysfs_class);
101962306a36Sopenharmony_ci	return rc;
102062306a36Sopenharmony_ci}
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_cistatic void __exit exit_ch_module(void)
102362306a36Sopenharmony_ci{
102462306a36Sopenharmony_ci	scsi_unregister_driver(&ch_template.gendrv);
102562306a36Sopenharmony_ci	unregister_chrdev(SCSI_CHANGER_MAJOR, "ch");
102662306a36Sopenharmony_ci	class_destroy(ch_sysfs_class);
102762306a36Sopenharmony_ci	idr_destroy(&ch_index_idr);
102862306a36Sopenharmony_ci}
102962306a36Sopenharmony_ci
103062306a36Sopenharmony_cimodule_init(init_ch_module);
103162306a36Sopenharmony_cimodule_exit(exit_ch_module);
1032