[SCSI] add scsi changer driver
authorGerd Knorr <kraxel@bytesex.org>
Tue, 10 May 2005 08:59:13 +0000 (10:59 +0200)
committerJames Bottomley <jejb@mulgrave.(none)>
Fri, 20 May 2005 17:53:50 +0000 (12:53 -0500)
This patch adds a device driver for scsi media changer devices.

Signed-off-by: Gerd Knorr <kraxel@bytesex.org>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
Documentation/scsi/scsi-changer.txt [new file with mode: 0644]
drivers/scsi/Kconfig
drivers/scsi/Makefile
drivers/scsi/ch.c [new file with mode: 0644]
include/linux/chio.h [new file with mode: 0644]
include/linux/major.h
include/scsi/scsi.h

diff --git a/Documentation/scsi/scsi-changer.txt b/Documentation/scsi/scsi-changer.txt
new file mode 100644 (file)
index 0000000..c132687
--- /dev/null
@@ -0,0 +1,180 @@
+
+README for the SCSI media changer driver
+========================================
+
+This is a driver for SCSI Medium Changer devices, which are listed
+with "Type: Medium Changer" in /proc/scsi/scsi.
+
+This is for *real* Jukeboxes.  It is *not* supported to work with
+common small CD-ROM changers, neither one-lun-per-slot SCSI changers
+nor IDE drives.
+
+Userland tools available from here:
+       http://linux.bytesex.org/misc/changer.html
+
+
+General Information
+-------------------
+
+First some words about how changers work: A changer has 2 (possibly
+more) SCSI ID's. One for the changer device which controls the robot,
+and one for the device which actually reads and writes the data. The
+later may be anything, a MOD, a CD-ROM, a tape or whatever. For the
+changer device this is a "don't care", he *only* shuffles around the
+media, nothing else.
+
+
+The SCSI changer model is complex, compared to - for example - IDE-CD
+changers. But it allows to handle nearly all possible cases. It knows
+4 different types of changer elements:
+
+  media transport - this one shuffles around the media, i.e. the
+                    transport arm.  Also known as "picker".
+  storage         - a slot which can hold a media.
+  import/export   - the same as above, but is accessable from outside,
+                    i.e. there the operator (you !) can use this to
+                    fill in and remove media from the changer.
+                   Sometimes named "mailslot".
+  data transfer   - this is the device which reads/writes, i.e. the
+                   CD-ROM / Tape / whatever drive.
+
+None of these is limited to one: A huge Jukebox could have slots for
+123 CD-ROM's, 5 CD-ROM readers (and therefore 6 SCSI ID's: the changer
+and each CD-ROM) and 2 transport arms. No problem to handle.
+
+
+How it is implemented
+---------------------
+
+I implemented the driver as character device driver with a NetBSD-like
+ioctl interface. Just grabbed NetBSD's header file and one of the
+other linux SCSI device drivers as starting point. The interface
+should be source code compatible with NetBSD. So if there is any
+software (anybody knows ???) which supports a BSDish changer driver,
+it should work with this driver too.
+
+Over time a few more ioctls where added, volume tag support for example
+wasn't covered by the NetBSD ioctl API.
+
+
+Current State
+-------------
+
+Support for more than one transport arm is not implemented yet (and
+nobody asked for it so far...).
+
+I test and use the driver myself with a 35 slot cdrom jukebox from
+Grundig.  I got some reports telling it works ok with tape autoloaders
+(Exabyte, HP and DEC).  Some People use this driver with amanda.  It
+works fine with small (11 slots) and a huge (4 MOs, 88 slots)
+magneto-optical Jukebox.  Probably with lots of other changers too, most
+(but not all :-) people mail me only if it does *not* work...
+
+I don't have any device lists, neither black-list nor white-list.  Thus
+it is quite useless to ask me whenever a specific device is supported or
+not.  In theory every changer device which supports the SCSI-2 media
+changer command set should work out-of-the-box with this driver.  If it
+doesn't, it is a bug.  Either within the driver or within the firmware
+of the changer device.
+
+
+Using it
+--------
+
+This is a character device with major number is 86, so use
+"mknod /dev/sch0 c 86 0" to create the special file for the driver.
+
+If the module finds the changer, it prints some messages about the
+device [ try "dmesg" if you don't see anything ] and should show up in
+/proc/devices. If not....  some changers use ID ? / LUN 0 for the
+device and ID ? / LUN 1 for the robot mechanism. But Linux does *not*
+look for LUN's other than 0 as default, becauce there are to many
+broken devices. So you can try:
+
+  1) echo "scsi add-single-device 0 0 ID 1" > /proc/scsi/scsi
+     (replace ID with the SCSI-ID of the device)
+  2) boot the kernel with "max_scsi_luns=1" on the command line
+     (append="max_scsi_luns=1" in lilo.conf should do the trick)
+
+
+Trouble?
+--------
+
+If you insmod the driver with "insmod debug=1", it will be verbose and
+prints a lot of stuff to the syslog.  Compiling the kernel with
+CONFIG_SCSI_CONSTANTS=y improves the quality of the error messages alot
+because the kernel will translate the error codes into human-readable
+strings then.
+
+You can display these messages with the dmesg command (or check the
+logfiles).  If you email me some question becauce of a problem with the
+driver, please include these messages.
+
+
+Insmod options
+--------------
+
+debug=0/1
+       Enable debug messages (see above, default: 0).
+
+verbose=0/1
+       Be verbose (default: 1).
+
+init=0/1
+       Send INITIALIZE ELEMENT STATUS command to the changer
+       at insmod time (default: 1).
+
+timeout_init=<seconds>
+       timeout for the INITIALIZE ELEMENT STATUS command
+       (default: 3600).
+
+timeout_move=<seconds>
+       timeout for all other commands (default: 120).
+
+dt_id=<id1>,<id2>,...
+dt_lun=<lun1>,<lun2>,...
+       These two allow to specify the SCSI ID and LUN for the data
+       transfer elements.  You likely don't need this as the jukebox
+       should provide this information.  But some devices don't ...
+
+vendor_firsts=
+vendor_counts=
+vendor_labels=
+       These insmod options can be used to tell the driver that there
+       are some vendor-specific element types.  Grundig for example
+       does this.  Some jukeboxes have a printer to label fresh burned
+       CDs, which is addressed as element 0xc000 (type 5).  To tell the
+       driver about this vendor-specific element, use this:
+               $ insmod ch                     \
+                       vendor_firsts=0xc000    \
+                       vendor_counts=1         \
+                       vendor_labels=printer
+       All three insmod options accept up to four comma-separated
+       values, this way you can configure the element types 5-8.
+       You likely need the SCSI specs for the device in question to
+       find the correct values as they are not covered by the SCSI-2
+       standard.
+
+
+Credits
+-------
+
+I wrote this driver using the famous mailing-patches-around-the-world
+method.  With (more or less) help from:
+
+       Daniel Moehwald <moehwald@hdg.de>
+       Dane Jasper <dane@sonic.net>
+       R. Scott Bailey <sbailey@dsddi.eds.com>
+       Jonathan Corbet <corbet@lwn.net>
+
+Special thanks go to
+       Martin Kuehne <martin.kuehne@bnbt.de>
+for a old, second-hand (but full functional) cdrom jukebox which I use
+to develop/test driver and tools now.
+
+Have fun,
+
+   Gerd
+
+-- 
+Gerd Knorr <kraxel@bytesex.org>
index 2ef5aee..ba88be3 100644 (file)
@@ -137,6 +137,24 @@ config CHR_DEV_SG
 
          If unsure, say N.
 
+config CHR_DEV_SCH
+       tristate "SCSI media changer support"
+       depends on SCSI
+       ---help---
+         This is a driver for SCSI media changers.  Most common devices are
+         tape libraries and MOD/CDROM jukeboxes.  *Real* jukeboxes, you
+         don't need this for those tiny 6-slot cdrom changers.  Media
+         changers are listed as "Type: Medium Changer" in /proc/scsi/scsi.
+         If you have such hardware and want to use it with linux, say Y
+         here.  Check <file:Documentation/scsi-changer.txt> for details.
+       
+         If you want to compile this as a module ( = code which can be
+         inserted in and removed from the running kernel whenever you want),
+         say M here and read <file:Documentation/modules.txt> and
+         <file:Documentation/scsi.txt>. The module will be called ch.o.
+         If unsure, say N.
+       
+
 comment "Some SCSI devices (e.g. CD jukebox) support multiple LUNs"
        depends on SCSI
 
index 51d9c1e..3746fb9 100644 (file)
@@ -140,6 +140,7 @@ obj-$(CONFIG_CHR_DEV_OSST)  += osst.o
 obj-$(CONFIG_BLK_DEV_SD)       += sd_mod.o
 obj-$(CONFIG_BLK_DEV_SR)       += sr_mod.o
 obj-$(CONFIG_CHR_DEV_SG)       += sg.o
+obj-$(CONFIG_CHR_DEV_SCH)      += ch.o
 
 scsi_mod-y                     += scsi.o hosts.o scsi_ioctl.o constants.o \
                                   scsicam.o scsi_error.o scsi_lib.o \
diff --git a/drivers/scsi/ch.c b/drivers/scsi/ch.c
new file mode 100644 (file)
index 0000000..44f5a71
--- /dev/null
@@ -0,0 +1,1025 @@
+/*
+ * SCSI Media Changer device driver for Linux 2.6
+ *
+ *     (c) 1996-2003 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+
+#define VERSION "0.25"
+
+#include <linux/config.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/blkdev.h>
+#include <linux/completion.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ioctl32.h>
+#include <linux/compat.h>
+#include <linux/chio.h>                        /* here are all the ioctls */
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_driver.h>
+#include <scsi/scsi_ioctl.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_request.h>
+#include <scsi/scsi_dbg.h>
+
+#define CH_DT_MAX       16
+#define CH_TYPES        8
+
+MODULE_DESCRIPTION("device driver for scsi media changer devices");
+MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org>");
+MODULE_LICENSE("GPL");
+
+static int init = 1;
+module_param(init, int, 0444);
+MODULE_PARM_DESC(init, \
+    "initialize element status on driver load (default: on)");
+
+static int timeout_move = 300;
+module_param(timeout_move, int, 0644);
+MODULE_PARM_DESC(timeout_move,"timeout for move commands "
+                "(default: 300 seconds)");
+
+static int timeout_init = 3600;
+module_param(timeout_init, int, 0644);
+MODULE_PARM_DESC(timeout_init,"timeout for INITIALIZE ELEMENT STATUS "
+                "(default: 3600 seconds)");
+
+static int verbose = 1;
+module_param(verbose, int, 0644);
+MODULE_PARM_DESC(verbose,"be verbose (default: on)");
+
+static int debug = 0;
+module_param(debug, int, 0644);
+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_param_array(dt_id,  int, NULL, 0444);
+module_param_array(dt_lun, int, NULL, 0444);
+
+/* tell the driver about vendor-specific slots */
+static int vendor_firsts[CH_TYPES-4];
+static int vendor_counts[CH_TYPES-4];
+module_param_array(vendor_firsts, int, NULL, 0444);
+module_param_array(vendor_counts, int, NULL, 0444);
+
+static char *vendor_labels[CH_TYPES-4] = {
+       "v0", "v1", "v2", "v3"
+};
+// module_param_string_array(vendor_labels, NULL, 0444);
+
+#define dprintk(fmt, arg...)    if (debug) \
+        printk(KERN_DEBUG "%s: " fmt, ch->name , ## arg)
+#define vprintk(fmt, arg...)    if (verbose) \
+        printk(KERN_INFO "%s: " fmt, ch->name , ## arg)
+
+/* ------------------------------------------------------------------- */
+
+#define MAX_RETRIES   1
+
+static int  ch_probe(struct device *);
+static int  ch_remove(struct 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);
+#ifdef CONFIG_COMPAT
+static long ch_ioctl_compat(struct file * filp,
+                           unsigned int cmd, unsigned long arg);
+#endif
+
+static struct class_simple * ch_sysfs_class;
+
+typedef struct {
+       struct list_head    list;
+       int                 minor;
+       char                name[8];
+       struct scsi_device  *device;
+       struct 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;
+       struct semaphore    lock;
+} scsi_changer;
+
+static LIST_HEAD(ch_devlist);
+static spinlock_t ch_devlist_lock = SPIN_LOCK_UNLOCKED;
+static int ch_devcount;
+
+static struct scsi_driver ch_template =
+{
+       .owner          = THIS_MODULE,
+       .gendrv         = {
+               .name   = "ch",
+               .probe  = ch_probe,
+               .remove = ch_remove,
+       },
+};
+
+static struct file_operations changer_fops =
+{
+       .owner        = THIS_MODULE,
+       .open         = ch_open,
+       .release      = ch_release,
+       .ioctl        = ch_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl = ch_ioctl_compat,
+#endif
+};
+
+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 */
+       }
+};
+
+/* ------------------------------------------------------------------- */
+
+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 int
+ch_do_scsi(scsi_changer *ch, unsigned char *cmd,
+          void *buffer, unsigned buflength,
+          enum dma_data_direction direction)
+{
+       int errno, retries = 0, timeout;
+       struct scsi_request *sr;
+       
+       sr = scsi_allocate_request(ch->device, GFP_KERNEL);
+       if (NULL == sr)
+               return -ENOMEM;
+
+       timeout = (cmd[0] == INITIALIZE_ELEMENT_STATUS)
+               ? timeout_init : timeout_move;
+
+ retry:
+       errno = 0;
+       if (debug) {
+               dprintk("command: ");
+               __scsi_print_command(cmd);
+       }
+
+        scsi_wait_req(sr, cmd, buffer, buflength,
+                     timeout * HZ, MAX_RETRIES);
+
+       dprintk("result: 0x%x\n",sr->sr_result);
+       if (driver_byte(sr->sr_result) & DRIVER_SENSE) {
+               if (debug)
+                       scsi_print_req_sense(ch->name, sr);
+               errno = ch_find_errno(sr->sr_sense_buffer);
+
+               switch(sr->sr_sense_buffer[2] & 0xf) {
+               case UNIT_ATTENTION:
+                       ch->unit_attention = 1;
+                       if (retries++ < 3)
+                               goto retry;
+                       break;
+               }
+       }
+       scsi_release_request(sr);
+       return errno;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int
+ch_elem_to_typecode(scsi_changer *ch, u_int elem)
+{
+       int i;
+       
+       for (i = 0; i < CH_TYPES; i++) {
+               if (elem >= ch->firsts[i]  &&
+                   elem <  ch->firsts[i] +
+                   ch->counts[i])
+                       return i+1;
+       }
+       return 0;
+}
+
+static int
+ch_read_element_status(scsi_changer *ch, u_int elem, char *data)
+{
+       u_char  cmd[12];
+       u_char  *buffer;
+       int     result;
+       
+       buffer = kmalloc(512, GFP_KERNEL | GFP_DMA);
+       if(!buffer)
+               return -ENOMEM;
+       
+ retry:
+       memset(cmd,0,sizeof(cmd));
+       cmd[0] = READ_ELEMENT_STATUS;
+       cmd[1] = (ch->device->lun << 5) | 
+               (ch->voltags ? 0x10 : 0) |
+               ch_elem_to_typecode(ch,elem);
+       cmd[2] = (elem >> 8) & 0xff;
+       cmd[3] = elem        & 0xff;
+       cmd[5] = 1;
+       cmd[9] = 255;
+       if (0 == (result = ch_do_scsi(ch, cmd, buffer, 256, DMA_FROM_DEVICE))) {
+               if (((buffer[16] << 8) | buffer[17]) != elem) {
+                       dprintk("asked for element 0x%02x, got 0x%02x\n",
+                               elem,(buffer[16] << 8) | buffer[17]);
+                       kfree(buffer);
+                       return -EIO;
+               }
+               memcpy(data,buffer+16,16);
+       } else {
+               if (ch->voltags) {
+                       ch->voltags = 0;
+                       vprintk("device has no volume tag support\n");
+                       goto retry;
+               }
+               dprintk("READ ELEMENT STATUS for element 0x%x failed\n",elem);
+       }
+       kfree(buffer);
+       return result;
+}
+
+static int 
+ch_init_elem(scsi_changer *ch)
+{
+       int err;
+       u_char cmd[6];
+
+       vprintk("INITIALIZE ELEMENT STATUS, may take some time ...\n");
+       memset(cmd,0,sizeof(cmd));
+       cmd[0] = INITIALIZE_ELEMENT_STATUS;
+       cmd[1] = ch->device->lun << 5;
+       err = ch_do_scsi(ch, cmd, NULL, 0, DMA_NONE);
+       vprintk("... finished\n");
+       return err;
+}
+
+static int
+ch_readconfig(scsi_changer *ch)
+{
+       u_char  cmd[10], data[16];
+       u_char  *buffer;
+       int     result,id,lun,i;
+       u_int   elem;
+
+       buffer = kmalloc(512, GFP_KERNEL | GFP_DMA);
+       if (!buffer)
+               return -ENOMEM;
+       memset(buffer,0,512);
+       
+       memset(cmd,0,sizeof(cmd));
+       cmd[0] = MODE_SENSE;
+       cmd[1] = ch->device->lun << 5;
+       cmd[2] = 0x1d;
+       cmd[4] = 255;
+       result = ch_do_scsi(ch, cmd, buffer, 255, DMA_FROM_DEVICE);
+       if (0 != result) {
+               cmd[1] |= (1<<3);
+               result  = ch_do_scsi(ch, cmd, buffer, 255, DMA_FROM_DEVICE);
+       }
+       if (0 == result) {
+               ch->firsts[CHET_MT] =
+                       (buffer[buffer[3]+ 6] << 8) | buffer[buffer[3]+ 7];
+               ch->counts[CHET_MT] =
+                       (buffer[buffer[3]+ 8] << 8) | buffer[buffer[3]+ 9];
+               ch->firsts[CHET_ST] =
+                       (buffer[buffer[3]+10] << 8) | buffer[buffer[3]+11];
+               ch->counts[CHET_ST] =
+                       (buffer[buffer[3]+12] << 8) | buffer[buffer[3]+13];
+               ch->firsts[CHET_IE] =
+                       (buffer[buffer[3]+14] << 8) | buffer[buffer[3]+15];
+               ch->counts[CHET_IE] =
+                       (buffer[buffer[3]+16] << 8) | buffer[buffer[3]+17];
+               ch->firsts[CHET_DT] =
+                       (buffer[buffer[3]+18] << 8) | buffer[buffer[3]+19];
+               ch->counts[CHET_DT] =
+                       (buffer[buffer[3]+20] << 8) | buffer[buffer[3]+21];
+               vprintk("type #1 (mt): 0x%x+%d [medium transport]\n",
+                       ch->firsts[CHET_MT],
+                       ch->counts[CHET_MT]);
+               vprintk("type #2 (st): 0x%x+%d [storage]\n",
+                       ch->firsts[CHET_ST],
+                       ch->counts[CHET_ST]);
+               vprintk("type #3 (ie): 0x%x+%d [import/export]\n",
+                       ch->firsts[CHET_IE],
+                       ch->counts[CHET_IE]);
+               vprintk("type #4 (dt): 0x%x+%d [data transfer]\n",
+                       ch->firsts[CHET_DT],
+                       ch->counts[CHET_DT]);
+       } else {
+               vprintk("reading element address assigment page failed!\n");
+       }
+       
+       /* vendor specific element types */
+       for (i = 0; i < 4; i++) {
+               if (0 == vendor_counts[i])
+                       continue;
+               if (NULL == vendor_labels[i])
+                       continue;
+               ch->firsts[CHET_V1+i] = vendor_firsts[i];
+               ch->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->dt = kmalloc(ch->counts[CHET_DT]*sizeof(struct scsi_device),
+                        GFP_KERNEL);
+       for (elem = 0; elem < ch->counts[CHET_DT]; elem++) {
+               id  = -1;
+               lun = 0;
+               if (elem < CH_DT_MAX  &&  -1 != dt_id[elem]) {
+                       id  = dt_id[elem];
+                       lun = dt_lun[elem];
+                       vprintk("dt 0x%x: [insmod option] ",
+                               elem+ch->firsts[CHET_DT]);
+               } else if (0 != ch_read_element_status
+                          (ch,elem+ch->firsts[CHET_DT],data)) {
+                       vprintk("dt 0x%x: READ ELEMENT STATUS failed\n",
+                               elem+ch->firsts[CHET_DT]);
+               } else {
+                       vprintk("dt 0x%x: ",elem+ch->firsts[CHET_DT]);
+                       if (data[6] & 0x80) {
+                               if (verbose)
+                                       printk("not this SCSI bus\n");
+                               ch->dt[elem] = NULL;
+                       } else if (0 == (data[6] & 0x30)) {
+                               if (verbose)
+                                       printk("ID/LUN unknown\n");
+                               ch->dt[elem] = NULL;
+                       } else {
+                               id  = ch->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->dt[elem] =
+                               scsi_device_lookup(ch->device->host,
+                                                  ch->device->channel,
+                                                  id,lun);
+                       if (!ch->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->dt[elem]->vendor,
+                                              ch->dt[elem]->model,
+                                              ch->dt[elem]->rev);
+                       }
+               }
+       }
+       ch->voltags = 1;
+       kfree(buffer);
+
+       return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int
+ch_position(scsi_changer *ch, u_int trans, u_int elem, int rotate)
+{
+       u_char  cmd[10];
+       
+       dprintk("position: 0x%x\n",elem);
+       if (0 == trans)
+               trans = ch->firsts[CHET_MT];
+       memset(cmd,0,sizeof(cmd));
+       cmd[0]  = POSITION_TO_ELEMENT;
+       cmd[1]  = ch->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_scsi(ch, cmd, NULL, 0, DMA_NONE);
+}
+
+static int
+ch_move(scsi_changer *ch, 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->firsts[CHET_MT];
+       memset(cmd,0,sizeof(cmd));
+       cmd[0]  = MOVE_MEDIUM;
+       cmd[1]  = ch->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_scsi(ch, cmd, NULL,0, DMA_NONE);
+}
+
+static int
+ch_exchange(scsi_changer *ch, 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->firsts[CHET_MT];
+       memset(cmd,0,sizeof(cmd));
+       cmd[0]  = EXCHANGE_MEDIUM;
+       cmd[1]  = ch->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_scsi(ch, cmd, NULL,0, DMA_NONE);
+}
+
+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(scsi_changer *ch, u_int elem,
+             int alternate, int clear, u_char *tag)
+{
+       u_char  cmd[12];
+       u_char  *buffer;
+       int result;
+
+       buffer = kmalloc(512, GFP_KERNEL);
+       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->device->lun << 5) | 
+               ch_elem_to_typecode(ch,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_scsi(ch, cmd, buffer, 256, DMA_TO_DEVICE);
+       kfree(buffer);
+       return result;
+}
+
+static int ch_gstatus(scsi_changer *ch, int type, unsigned char *dest)
+{
+       int retval = 0;
+       u_char data[16];
+       unsigned int i;
+       
+       down(&ch->lock);
+       for (i = 0; i < ch->counts[type]; i++) {
+               if (0 != ch_read_element_status
+                   (ch, ch->firsts[type]+i,data)) {
+                       retval = -EIO;
+                       break;
+               }
+               put_user(data[2], dest+i);
+               if (data[2] & CESTATUS_EXCEPT)
+                       vprintk("element 0x%x: asc=0x%x, ascq=0x%x\n",
+                               ch->firsts[type]+i,
+                               (int)data[4],(int)data[5]);
+               retval = ch_read_element_status
+                       (ch, ch->firsts[type]+i,data);
+               if (0 != retval)
+                       break;
+       }
+       up(&ch->lock);
+       return retval;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int
+ch_release(struct inode *inode, struct file *file)
+{
+       scsi_changer *ch = file->private_data;
+
+       scsi_device_put(ch->device);
+       file->private_data = NULL;
+       return 0;
+}
+
+static int
+ch_open(struct inode *inode, struct file *file)
+{
+       scsi_changer *tmp, *ch;
+       int minor = iminor(inode);
+
+       spin_lock(&ch_devlist_lock);
+       ch = NULL;
+       list_for_each_entry(tmp,&ch_devlist,list) {
+               if (tmp->minor == minor)
+                       ch = tmp;
+       }
+       if (NULL == ch || scsi_device_get(ch->device)) {
+               spin_unlock(&ch_devlist_lock);
+               return -ENXIO;
+       }
+       spin_unlock(&ch_devlist_lock);
+
+       file->private_data = ch;
+       return 0;
+}
+
+static int
+ch_checkrange(scsi_changer *ch, unsigned int type, unsigned int unit)
+{
+       if (type >= CH_TYPES  ||  unit >= ch->counts[type])
+               return -1;
+       return 0;
+}
+
+static int ch_ioctl(struct inode * inode, struct file * file,
+                   unsigned int cmd, unsigned long arg)
+{
+       scsi_changer *ch = file->private_data;
+       int retval;
+       
+       switch (cmd) {
+       case CHIOGPARAMS:
+       {
+               struct changer_params params;
+               
+               params.cp_curpicker = 0;
+               params.cp_npickers  = ch->counts[CHET_MT];
+               params.cp_nslots    = ch->counts[CHET_ST];
+               params.cp_nportals  = ch->counts[CHET_IE];
+               params.cp_ndrives   = ch->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->counts[CHET_V1]) {
+                       vparams.cvp_n1  = ch->counts[CHET_V1];
+                       strncpy(vparams.cvp_label1,vendor_labels[0],16);
+               }
+               if (ch->counts[CHET_V2]) {
+                       vparams.cvp_n2  = ch->counts[CHET_V2];
+                       strncpy(vparams.cvp_label2,vendor_labels[1],16);
+               }
+               if (ch->counts[CHET_V3]) {
+                       vparams.cvp_n3  = ch->counts[CHET_V3];
+                       strncpy(vparams.cvp_label3,vendor_labels[2],16);
+               }
+               if (ch->counts[CHET_V4]) {
+                       vparams.cvp_n4  = ch->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(ch, pos.cp_type, pos.cp_unit)) {
+                       dprintk("CHIOPOSITION: invalid parameter\n");
+                       return -EBADSLT;
+               }
+               down(&ch->lock);
+               retval = ch_position(ch,0,
+                                    ch->firsts[pos.cp_type] + pos.cp_unit,
+                                    pos.cp_flags & CP_INVERT);
+               up(&ch->lock);
+               return retval;
+       }
+       
+       case CHIOMOVE:
+       {
+               struct changer_move mv;
+
+               if (copy_from_user(&mv, (void*)arg, sizeof (mv)))
+                       return -EFAULT;
+
+               if (0 != ch_checkrange(ch, mv.cm_fromtype, mv.cm_fromunit) ||
+                   0 != ch_checkrange(ch, mv.cm_totype,   mv.cm_tounit  )) {
+                       dprintk("CHIOMOVE: invalid parameter\n");
+                       return -EBADSLT;
+               }
+               
+               down(&ch->lock);
+               retval = ch_move(ch,0,
+                                ch->firsts[mv.cm_fromtype] + mv.cm_fromunit,
+                                ch->firsts[mv.cm_totype]   + mv.cm_tounit,
+                                mv.cm_flags & CM_INVERT);
+               up(&ch->lock);
+               return retval;
+       }
+
+       case CHIOEXCHANGE:
+       {
+               struct changer_exchange mv;
+               
+               if (copy_from_user(&mv, (void*)arg, sizeof (mv)))
+                       return -EFAULT;
+
+               if (0 != ch_checkrange(ch, mv.ce_srctype,  mv.ce_srcunit ) ||
+                   0 != ch_checkrange(ch, mv.ce_fdsttype, mv.ce_fdstunit) ||
+                   0 != ch_checkrange(ch, mv.ce_sdsttype, mv.ce_sdstunit)) {
+                       dprintk("CHIOEXCHANGE: invalid parameter\n");
+                       return -EBADSLT;
+               }
+               
+               down(&ch->lock);
+               retval = ch_exchange
+                       (ch,0,
+                        ch->firsts[mv.ce_srctype]  + mv.ce_srcunit,
+                        ch->firsts[mv.ce_fdsttype] + mv.ce_fdstunit,
+                        ch->firsts[mv.ce_sdsttype] + mv.ce_sdstunit,
+                        mv.ce_flags & CE_INVERT1, mv.ce_flags & CE_INVERT2);
+               up(&ch->lock);
+               return retval;
+       }
+
+       case CHIOGSTATUS:
+       {
+               struct changer_element_status ces;
+               
+               if (copy_from_user(&ces, (void*)arg, sizeof (ces)))
+                       return -EFAULT;
+               if (ces.ces_type < 0 || ces.ces_type >= CH_TYPES)
+                       return -EINVAL;
+
+               return ch_gstatus(ch, ces.ces_type, ces.ces_data);
+       }
+
+       case CHIOGELEM:
+       {
+               struct changer_get_element cge;
+               u_char  cmd[12];
+               u_char  *buffer;
+               unsigned int elem;
+               int     result,i;
+               
+               if (copy_from_user(&cge, (void*)arg, sizeof (cge)))
+                       return -EFAULT;
+
+               if (0 != ch_checkrange(ch, cge.cge_type, cge.cge_unit))
+                       return -EINVAL;
+               elem = ch->firsts[cge.cge_type] + cge.cge_unit;
+               
+               buffer = kmalloc(512, GFP_KERNEL | GFP_DMA);
+               if (!buffer)
+                       return -ENOMEM;
+               down(&ch->lock);
+               
+       voltag_retry:
+               memset(cmd,0,sizeof(cmd));
+               cmd[0] = READ_ELEMENT_STATUS;
+               cmd[1] = (ch->device->lun << 5) |
+                       (ch->voltags ? 0x10 : 0) |
+                       ch_elem_to_typecode(ch,elem);
+               cmd[2] = (elem >> 8) & 0xff;
+               cmd[3] = elem        & 0xff;
+               cmd[5] = 1;
+               cmd[9] = 255;
+               
+               if (0 == (result = ch_do_scsi(ch, cmd, buffer, 256, DMA_FROM_DEVICE))) {
+                       cge.cge_status = buffer[18];
+                       cge.cge_flags = 0;
+                       if (buffer[18] & CESTATUS_EXCEPT) {
+                               cge.cge_errno = EIO;
+                       }
+                       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->firsts[i] &&
+                                           elem <  ch->firsts[i] + ch->counts[i]) {
+                                               cge.cge_srctype = i;
+                                               cge.cge_srcunit = elem-ch->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->voltags) {
+                       ch->voltags = 0;
+                       vprintk("device has no volume tag support\n");
+                       goto voltag_retry;
+               }
+               kfree(buffer);
+               up(&ch->lock);
+               
+               if (copy_to_user((void*)arg, &cge, sizeof (cge)))
+                       return -EFAULT;
+               return result;
+       }
+
+       case CHIOINITELEM:
+       {
+               down(&ch->lock);
+               retval = ch_init_elem(ch);
+               up(&ch->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(ch, csv.csv_type, csv.csv_unit)) {
+                       dprintk("CHIOSVOLTAG: invalid parameter\n");
+                       return -EBADSLT;
+               }
+               elem = ch->firsts[csv.csv_type] + csv.csv_unit;
+               down(&ch->lock);
+               retval = ch_set_voltag(ch, elem,
+                                      csv.csv_flags & CSV_AVOLTAG,
+                                      csv.csv_flags & CSV_CLEARTAG,
+                                      csv.csv_voltag);
+               up(&ch->lock);
+               return retval;
+       }
+
+       default:
+               return scsi_ioctl(ch->device, cmd, (void*)arg);
+
+       }
+}
+
+#ifdef CONFIG_COMPAT
+
+struct changer_element_status32 {
+       int             ces_type;
+       compat_uptr_t   ces_data;
+};
+#define CHIOGSTATUS32  _IOW('c', 8,struct changer_element_status32)
+
+static long ch_ioctl_compat(struct file * file,
+                           unsigned int cmd, unsigned long arg)
+{
+       scsi_changer *ch = file->private_data;
+       
+       switch (cmd) {
+       case CHIOGPARAMS:
+       case CHIOGVPARAMS:
+       case CHIOPOSITION:
+       case CHIOMOVE:
+       case CHIOEXCHANGE:
+       case CHIOGELEM:
+       case CHIOINITELEM:
+       case CHIOSVOLTAG:
+               /* compatible */
+               return ch_ioctl(NULL /* inode, unused */,
+                               file, cmd, arg);
+       case CHIOGSTATUS32:
+       {
+               struct changer_element_status32 ces32;
+               unsigned char *data;
+               
+               if (copy_from_user(&ces32, (void*)arg, sizeof (ces32)))
+                       return -EFAULT;
+               if (ces32.ces_type < 0 || ces32.ces_type >= CH_TYPES)
+                       return -EINVAL;
+
+               data = compat_ptr(ces32.ces_data);
+               return ch_gstatus(ch, ces32.ces_type, data);
+       }
+       default:
+               // return scsi_ioctl_compat(ch->device, cmd, (void*)arg);
+               return -ENOIOCTLCMD;
+
+       }
+}
+#endif
+
+/* ------------------------------------------------------------------------ */
+
+static int ch_probe(struct device *dev)
+{
+       struct scsi_device *sd = to_scsi_device(dev);
+       scsi_changer *ch;
+       
+       if (sd->type != TYPE_MEDIUM_CHANGER)
+               return -ENODEV;
+    
+       ch = kmalloc(sizeof(*ch), GFP_KERNEL);
+       if (NULL == ch)
+               return -ENOMEM;
+
+       memset(ch,0,sizeof(*ch));
+       ch->minor = ch_devcount;
+       sprintf(ch->name,"ch%d",ch->minor);
+       init_MUTEX(&ch->lock);
+       ch->device = sd;
+       ch_readconfig(ch);
+       if (init)
+               ch_init_elem(ch);
+
+       devfs_mk_cdev(MKDEV(SCSI_CHANGER_MAJOR,ch->minor),
+                     S_IFCHR | S_IRUGO | S_IWUGO, ch->name);
+       class_simple_device_add(ch_sysfs_class,
+                               MKDEV(SCSI_CHANGER_MAJOR,ch->minor),
+                               dev, "s%s", ch->name);
+
+       printk(KERN_INFO "Attached scsi changer %s "
+              "at scsi%d, channel %d, id %d, lun %d\n", 
+              ch->name, sd->host->host_no, sd->channel, sd->id, sd->lun);
+       
+       spin_lock(&ch_devlist_lock);
+       list_add_tail(&ch->list,&ch_devlist);
+       ch_devcount++;
+       spin_unlock(&ch_devlist_lock);
+       return 0;
+}
+
+static int ch_remove(struct device *dev)
+{
+       struct scsi_device *sd = to_scsi_device(dev);
+       scsi_changer *tmp, *ch;
+
+       spin_lock(&ch_devlist_lock);
+       ch = NULL;
+       list_for_each_entry(tmp,&ch_devlist,list) {
+               if (tmp->device == sd)
+                       ch = tmp;
+       }
+       BUG_ON(NULL == ch);
+       list_del(&ch->list);
+       spin_unlock(&ch_devlist_lock);
+
+       class_simple_device_remove(MKDEV(SCSI_CHANGER_MAJOR,ch->minor));
+       devfs_remove(ch->name);
+       kfree(ch->dt);
+       kfree(ch);
+       ch_devcount--;
+       return 0;
+}
+
+static int __init init_ch_module(void)
+{
+       int rc;
+       
+       printk(KERN_INFO "SCSI Media Changer driver v" VERSION " \n");
+        ch_sysfs_class = class_simple_create(THIS_MODULE, "scsi_changer");
+        if (IS_ERR(ch_sysfs_class)) {
+               rc = PTR_ERR(ch_sysfs_class);
+               return rc;
+        }
+       rc = register_chrdev(SCSI_CHANGER_MAJOR,"ch",&changer_fops);
+       if (rc < 0) {
+               printk("Unable to get major %d for SCSI-Changer\n",
+                      SCSI_CHANGER_MAJOR);
+               goto fail1;
+       }
+       rc = scsi_register_driver(&ch_template.gendrv);
+       if (rc < 0)
+               goto fail2;
+       return 0;
+
+ fail2:
+       unregister_chrdev(SCSI_CHANGER_MAJOR, "ch");
+ fail1:
+       class_simple_destroy(ch_sysfs_class);
+       return rc;
+}
+
+static void __exit exit_ch_module(void) 
+{
+       scsi_unregister_driver(&ch_template.gendrv);
+       unregister_chrdev(SCSI_CHANGER_MAJOR, "ch");
+       class_simple_destroy(ch_sysfs_class);
+}
+
+module_init(init_ch_module);
+module_exit(exit_ch_module);
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/include/linux/chio.h b/include/linux/chio.h
new file mode 100644 (file)
index 0000000..63035ae
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * ioctl interface for the scsi media changer driver
+ */
+
+/* changer element types */
+#define CHET_MT   0    /* media transport element (robot) */
+#define CHET_ST   1    /* storage element (media slots) */
+#define CHET_IE   2    /* import/export element */
+#define CHET_DT   3    /* data transfer element (tape/cdrom/whatever) */
+#define CHET_V1   4    /* vendor specific #1 */
+#define CHET_V2   5    /* vendor specific #2 */
+#define CHET_V3   6    /* vendor specific #3 */
+#define CHET_V4   7    /* vendor specific #4 */
+
+
+/*
+ * CHIOGPARAMS
+ *    query changer properties
+ *
+ * CHIOVGPARAMS
+ *    query vendor-specific element types
+ *
+ *    accessing elements works by specifing type and unit of the element.
+ *    for eample, storage elements are addressed with type = CHET_ST and
+ *    unit = 0 .. cp_nslots-1
+ *
+ */
+struct changer_params {
+       int cp_curpicker;  /* current transport element */
+       int cp_npickers;   /* number of transport elements      (CHET_MT) */
+       int cp_nslots;     /* number of storage elements        (CHET_ST) */
+       int cp_nportals;   /* number of import/export elements  (CHET_IE) */
+       int cp_ndrives;    /* number of data transfer elements  (CHET_DT) */
+};
+struct changer_vendor_params {
+       int  cvp_n1;       /* number of vendor specific elems   (CHET_V1) */
+       char cvp_label1[16];
+       int  cvp_n2;       /* number of vendor specific elems   (CHET_V2) */
+       char cvp_label2[16];
+       int  cvp_n3;       /* number of vendor specific elems   (CHET_V3) */
+       char cvp_label3[16];
+       int  cvp_n4;       /* number of vendor specific elems   (CHET_V4) */
+       char cvp_label4[16];
+       int  reserved[8];
+};
+
+
+/*
+ * CHIOMOVE
+ *    move a medium from one element to another
+ */
+struct changer_move {
+       int cm_fromtype;        /* type/unit of source element */
+       int cm_fromunit;        
+       int cm_totype;  /* type/unit of destination element */
+       int cm_tounit;
+       int cm_flags;
+};
+#define CM_INVERT   1   /* flag: rotate media (for double-sided like MOD) */
+
+
+/*
+ * CHIOEXCHANGE
+ *    move one medium from element #1 to element #2,
+ *    and another one from element #2 to element #3.
+ *    element #1 and #3 are allowed to be identical.
+ */
+struct changer_exchange {
+       int ce_srctype;     /* type/unit of element #1 */
+       int ce_srcunit;
+       int ce_fdsttype;    /* type/unit of element #2 */
+       int ce_fdstunit;
+       int ce_sdsttype;    /* type/unit of element #3 */
+       int ce_sdstunit;
+       int ce_flags;
+};
+#define CE_INVERT1   1
+#define CE_INVERT2   2
+
+
+/*
+ * CHIOPOSITION
+ *    move the transport element (robot arm) to a specific element.
+ */
+struct changer_position {
+       int cp_type;
+       int cp_unit;
+       int cp_flags;
+};
+#define CP_INVERT   1
+
+
+/*
+ * CHIOGSTATUS
+ *    get element status for all elements of a specific type
+ */
+struct changer_element_status {
+       int             ces_type;
+       unsigned char   *ces_data;
+};
+#define CESTATUS_FULL     0x01 /* full */
+#define CESTATUS_IMPEXP   0x02 /* media was imported (inserted by sysop) */
+#define CESTATUS_EXCEPT   0x04 /* error condition */
+#define CESTATUS_ACCESS   0x08 /* access allowed */
+#define CESTATUS_EXENAB   0x10 /* element can export media */
+#define CESTATUS_INENAB   0x20 /* element can import media */
+
+
+/*
+ * CHIOGELEM
+ *    get more detailed status informtion for a single element
+ */
+struct changer_get_element {
+       int     cge_type;        /* type/unit */
+       int     cge_unit;
+       int     cge_status;      /* status */
+       int     cge_errno;       /* errno */
+       int     cge_srctype;     /* source element of the last move/exchange */
+       int     cge_srcunit;
+       int     cge_id;          /* scsi id  (for data transfer elements) */
+       int     cge_lun;         /* scsi lun (for data transfer elements) */
+       char    cge_pvoltag[36]; /* primary volume tag */
+       char    cge_avoltag[36]; /* alternate volume tag */
+       int     cge_flags;
+};
+/* flags */
+#define CGE_ERRNO     0x01       /* errno available       */
+#define CGE_INVERT    0x02       /* media inverted        */
+#define CGE_SRC       0x04       /* media src available   */
+#define CGE_IDLUN     0x08       /* ID+LUN available      */
+#define CGE_PVOLTAG   0x10       /* primary volume tag available */
+#define CGE_AVOLTAG   0x20       /* alternate volume tag available */
+
+
+/*
+ * CHIOSVOLTAG
+ *    set volume tag
+ */
+struct changer_set_voltag {
+       int     csv_type;        /* type/unit */
+       int     csv_unit;
+       char    csv_voltag[36];  /* volume tag */
+       int     csv_flags;
+};
+#define CSV_PVOLTAG   0x01       /* primary volume tag */
+#define CSV_AVOLTAG   0x02       /* alternate volume tag */
+#define CSV_CLEARTAG  0x04       /* clear volume tag */
+
+/* ioctls */
+#define CHIOMOVE       _IOW('c', 1,struct changer_move)
+#define CHIOEXCHANGE   _IOW('c', 2,struct changer_exchange)
+#define CHIOPOSITION   _IOW('c', 3,struct changer_position)
+#define CHIOGPICKER    _IOR('c', 4,int)                        /* not impl. */
+#define CHIOSPICKER    _IOW('c', 5,int)                        /* not impl. */
+#define CHIOGPARAMS    _IOR('c', 6,struct changer_params)
+#define CHIOGSTATUS    _IOW('c', 8,struct changer_element_status)
+#define CHIOGELEM      _IOW('c',16,struct changer_get_element)
+#define CHIOINITELEM   _IO('c',17)
+#define CHIOSVOLTAG    _IOW('c',18,struct changer_set_voltag)
+#define CHIOGVPARAMS   _IOR('c',19,struct changer_vendor_params)
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */
index 4b62c42..e36a467 100644 (file)
 #define I2O_MAJOR              80      /* 80->87 */
 
 #define SHMIQ_MAJOR            85   /* Linux/mips, SGI /dev/shmiq */
+#define SCSI_CHANGER_MAJOR      86
 
 #define IDE6_MAJOR             88
 #define IDE7_MAJOR             89
index 659ecf4..ca1e3b4 100644 (file)
@@ -41,6 +41,7 @@ extern const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE];
 #define FORMAT_UNIT           0x04
 #define READ_BLOCK_LIMITS     0x05
 #define REASSIGN_BLOCKS       0x07
+#define INITIALIZE_ELEMENT_STATUS 0x07
 #define READ_6                0x08
 #define WRITE_6               0x0a
 #define SEEK_6                0x0b
@@ -65,6 +66,7 @@ extern const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE];
 #define READ_10               0x28
 #define WRITE_10              0x2a
 #define SEEK_10               0x2b
+#define POSITION_TO_ELEMENT   0x2b
 #define WRITE_VERIFY          0x2e
 #define VERIFY                0x2f
 #define SEARCH_HIGH           0x30
@@ -97,6 +99,7 @@ extern const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE];
 #define PERSISTENT_RESERVE_OUT 0x5f
 #define REPORT_LUNS           0xa0
 #define MOVE_MEDIUM           0xa5
+#define EXCHANGE_MEDIUM       0xa6
 #define READ_12               0xa8
 #define WRITE_12              0xaa
 #define WRITE_VERIFY_12       0xae