*** To apply this patch, cd into your source directory, *** eg, cd /usr/src/linux-2.4.21-15.EL.Jukebox *** and patch -bp1 < scsi-changer.2.4.21-15.EL.patch *** *** 1) Adds 15 lines to Documentation/Configure.help re: CONFIG_CHR_DEV_SCH --- linux-2.4/Documentation/Configure.help Fri Apr 26 07:55:27 2002 +++ Jukebox/Documentation/Configure.help Wed Apr 24 08:08:21 2002 @@ -7581,6 +7581,21 @@ . The module will be called sg.o. If unsure, say N. +SCSI media changer support +CONFIG_CHR_DEV_SCH + 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 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 and + . The module will be called ch.o. + If unsure, say N. + Probe all LUNs on each SCSI device CONFIG_SCSI_MULTI_LUN If you have a SCSI device that supports more than one LUN (Logical *** *** 2) Creates a new 184-line file, Documentation/scsi-changer.txt --- linux-2.4/Documentation/scsi-changer.txt 2004-06-22 10:21:15.000000000 -0700 +++ Jukebox/Documentation/scsi-changer.txt 2004-06-22 10:16:29.000000000 -0700 @@ -0,0 +1,184 @@ + +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: http://bytesex.org/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). + +check_busy=0/1 + When moving media from/to data transfer elements, check + whenever the device is busy and refuse to move if so + (default: 1). + +timeout_init= + timeout for the INITIALIZE ELEMENT STATUS command + (default: 3600). + +timeout_move= + timeout for all other commands (default: 120). + +dt_id=,,... +dt_lun=,,... + 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 + Dane Jasper + R. Scott Bailey + Jonathan Corbet + +Special thanks go to + Martin Kuehne +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 *** *** 3) Adds 1 dep_tristate line to drivers/scsi/Config.in --- linux-2.4/drivers/scsi/Config.in Fri Apr 26 07:57:33 2002 +++ Jukebox/drivers/scsi/Config.in Wed Apr 24 08:10:43 2002 @@ -16,6 +16,7 @@ bool ' Enable vendor-specific extensions (for SCSI CDROM)' CONFIG_BLK_DEV_SR_VENDOR int 'Maximum number of CDROM devices that can be loaded as modules' CONFIG_SR_EXTRA_DEVS 2 fi +dep_tristate ' SCSI media changer support' CONFIG_CHR_DEV_SCH $CONFIG_SCSI dep_tristate ' SCSI generic support' CONFIG_CHR_DEV_SG $CONFIG_SCSI comment 'Some SCSI devices (e.g. CD jukebox) support multiple LUNs' *** *** 4) Adds 1 line to drivers/scsi/Makefile: obj-$(CONFIG_CHR_DEV_SCH) += ch.o --- linux-2.4/drivers/scsi/Makefile Wed Dec 31 16:00:00 1969 +++ Jukebox/drivers/scsi/Makefile Thu Apr 25 10:38:29 2002 @@ -147,6 +147,7 @@ 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 list-multi := scsi_mod.o sd_mod.o sr_mod.o initio.o a100u2w.o cpqfc.o \ zalon7xx_mod.o libata.o *** *** 5) Creates a new 1066-line scsi/drivers/scsi/ch.c file. --- linux-2.4/drivers/scsi/ch.c Wed Dec 31 16:00:00 1969 +++ Jukebox/drivers/scsi/ch.c Thu Apr 25 10:38:29 2002 @@ -0,0 +1,1066 @@ +/* + * SCSI Media Changer device driver for Linux 2.4 + * + * (c) 1996-2002 Gerd Knorr + * + */ + +#define VERSION "0.20" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* here are all the ioctls */ + +#define MAJOR_NR SCSI_CHANGER_MAJOR +#define DEVICE_ON(device) +#define DEVICE_OFF(device) +#define CH_EXTRA_DEVS 4 + +#define CH_DT_MAX 16 +#define CH_TYPES 8 + +#include "scsi.h" +#include "hosts.h" +#include "constants.h" +#include + +MODULE_SUPPORTED_DEVICE("sch"); +MODULE_DESCRIPTION("device driver for scsi media changer devices"); +MODULE_AUTHOR("Gerd Knorr "); +MODULE_LICENSE("GPL"); + +static int check_busy = 1; +MODULE_PARM(check_busy,"i"); +MODULE_PARM_DESC(check_busy, \ + "enable/disable busy check for data transfer elements (default: on)"); + +static int init = 1; +MODULE_PARM(init,"i"); +MODULE_PARM_DESC(init, \ + "initialize element status on driver load (default: on)"); + +static int timeout_move = 120; +MODULE_PARM(timeout_move,"i"); +MODULE_PARM_DESC(timeout_move,"timeout for move commands " + "(default: 120 seconds)"); + +static int timeout_init = 3600; +MODULE_PARM(timeout_init,"i"); +MODULE_PARM_DESC(timeout_init,"timeout for INITIALIZE ELEMENT STATUS " + "(default: 3600 seconds)"); + +static int verbose = 1; +MODULE_PARM(verbose,"i"); +MODULE_PARM_DESC(verbose,"be verbose (default: on)"); + +static int debug = 0; +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug,"enable/disable debug messages, also prints more " + "detailed sense codes on scsi errors (default: off)"); + +static int dt_id[CH_DT_MAX] = { [ 0 ... (CH_DT_MAX-1) ] = -1 }; +static int dt_lun[CH_DT_MAX]; +MODULE_PARM(dt_id,"1-" __MODULE_STRING(CH_DT_MAX) "i"); +MODULE_PARM(dt_lun,"1-" __MODULE_STRING(CH_DT_MAX) "i"); + +/* tell the driver about vendor-specific slots */ +static int vendor_firsts[CH_TYPES-4]; +static int vendor_counts[CH_TYPES-4]; +static char *vendor_labels[CH_TYPES-4]; +MODULE_PARM(vendor_firsts,"1-4i"); +MODULE_PARM(vendor_counts,"1-4i"); +MODULE_PARM(vendor_labels,"1-4s"); + +#define dprintk(fmt, arg...) if (debug) \ + printk(KERN_DEBUG "%s: " fmt, ch[target].name, ##arg) +#define vprintk(fmt, arg...) if (verbose) \ + printk(KERN_INFO "%s: " fmt, ch[target].name, ##arg) + +/* ------------------------------------------------------------------- */ + +#define MAX_RETRIES 1 + +static int ch_init(void); +static void ch_finish(void); +static int ch_attach(Scsi_Device *); +static int ch_detect(Scsi_Device *); +static void ch_detach(Scsi_Device *); +static int ch_open(struct inode * inode, struct file * filp); +static int ch_release(struct inode * inode, struct file * filp); +static int ch_ioctl(struct inode * inode, struct file * filp, + unsigned int cmd, unsigned long arg); + +typedef struct { + char name[8]; + Scsi_Device *device; + Scsi_Device **dt; /* ptrs to data transfer elements */ + u_int firsts[CH_TYPES]; + u_int counts[CH_TYPES]; + u_int unit_attention; + u_int voltags; + devfs_handle_t de; + struct semaphore lock; +} Scsi_Changer; + +struct Scsi_Device_Template ch_template = +{ + module: THIS_MODULE, + name: "media changer", + tag: "sch", + scsi_type: TYPE_MEDIUM_CHANGER, + major: SCSI_CHANGER_MAJOR, + detect: ch_detect, + init: ch_init, + finish: ch_finish, + attach: ch_attach, + detach: ch_detach, +}; + +static struct file_operations changer_fops = +{ + owner: THIS_MODULE, + open: ch_open, + release: ch_release, + ioctl: ch_ioctl, +}; + +static struct { + unsigned char sense; + unsigned char asc; + unsigned char ascq; + int errno; +} err[] = { +/* Just filled in what looks right. Hav'nt checked any standard paper for + these errno assignments, so they may be wrong... */ + { + sense: ILLEGAL_REQUEST, + asc: 0x21, + ascq: 0x01, + errno: EBADSLT, /* Invalid element address */ + },{ + sense: ILLEGAL_REQUEST, + asc: 0x28, + ascq: 0x01, + errno: EBADE, /* Import or export element accessed */ + },{ + sense: ILLEGAL_REQUEST, + asc: 0x3B, + ascq: 0x0D, + errno: EXFULL, /* Medium destination element full */ + },{ + sense: ILLEGAL_REQUEST, + asc: 0x3B, + ascq: 0x0E, + errno: EBADE, /* Medium source element empty */ + },{ + sense: ILLEGAL_REQUEST, + asc: 0x20, + ascq: 0x00, + errno: EBADRQC, /* Invalid command operation code */ + },{ + /* end of list */ + } +}; + +Scsi_Changer *ch = NULL; + +/* ------------------------------------------------------------------- */ + +static int ch_find_errno(unsigned char *sense_buffer) +{ + int i,errno = 0; + + /* Check to see if additional sense information is available */ + if (sense_buffer[7] > 5 && + sense_buffer[12] != 0) { + for (i = 0; err[i].errno != 0; i++) { + if (err[i].sense == sense_buffer[ 2] && + err[i].asc == sense_buffer[12] && + err[i].ascq == sense_buffer[13]) { + errno = -err[i].errno; + break; + } + } + } + if (errno == 0) + errno = -EIO; + return errno; +} + +static void +ch_request_done (Scsi_Cmnd * SCpnt) +{ + struct request * req; + + req = &SCpnt->request; + req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */ + if (req->waiting != NULL) { + complete(req->waiting); + } +} + +static int +ch_do_cmd(int target, unsigned char * sr_cmd, + void * buffer, unsigned buflength) +{ +#if 0 + static u_char init_cmd[6] = { INITIALIZE_ELEMENT_STATUS }; +#endif + int errno, ua = 0, retries = 0, timeout; + Scsi_Cmnd * SCpnt; + DECLARE_COMPLETION(wait); + + if(!scsi_block_when_processing_errors(ch[target].device)) + return -ENXIO; + + SCpnt = scsi_allocate_device(ch[target].device, 1, TRUE); + retry: + errno = 0; + SCpnt->request.waiting = &wait; +#if 0 + if (ua && reinit) { + init_cmd[1] = ch[target].device->lun << 5; + scsi_do_cmd(SCpnt, (void *) init_cmd, NULL, 0, + ch_request_done, CH_TIMEOUT, MAX_RETRIES); + wait_for_completion(&wait); + } +#endif + if (debug) { + dprintk("command: %s",""); + print_command(sr_cmd); + } + timeout = (sr_cmd[0] == INITIALIZE_ELEMENT_STATUS) + ? timeout_init : timeout_move; + scsi_do_cmd(SCpnt, (void *) sr_cmd, buffer, buflength, + ch_request_done, timeout*HZ, MAX_RETRIES); + wait_for_completion(&wait); + SCpnt->request.waiting = NULL; + dprintk("result: 0x%x\n",SCpnt->result); + + if (driver_byte(SCpnt->result) != 0) { + if (debug) { + dprintk("sense: %s",""); + print_sense("", SCpnt); + } + errno = ch_find_errno(SCpnt->sense_buffer); + switch(SCpnt->sense_buffer[2] & 0xf) { + case UNIT_ATTENTION: + ch[target].unit_attention = 1; + ua = 1; + if (retries++ < 3) + goto retry; + break; + } + } + + scsi_release_command(SCpnt); + return errno; +} + +/* ------------------------------------------------------------------------ */ + +static Scsi_Device* +find_device(struct Scsi_Host *host, u_char channel, u_char id, u_char lun) +{ + Scsi_Device *ret; + ret = host->host_queue; + + while (ret != NULL && + (ret->host != host || ret->channel != channel || + ret->id != id || ret->lun != lun)) + ret = ret->next; + + return ret; +} + +static int +ch_elem_to_typecode(int target, u_int elem) +{ + int i; + + for (i = 0; i < CH_TYPES; i++) { + if (elem >= ch[target].firsts[i] && + elem < ch[target].firsts[i] + + ch[target].counts[i]) + return i+1; + } + return 0; +} + +static int +ch_read_element_status(int target, u_int elem, char *data) +{ + u_char cmd[12]; + u_char *buffer; + int result; + + buffer = (unsigned char *) scsi_malloc(512); + if(!buffer) + return -ENOMEM; + + retry: + memset(cmd,0,sizeof(cmd)); + cmd[0] = READ_ELEMENT_STATUS; + cmd[1] = (ch[target].device->lun << 5) | + (ch[target].voltags ? 0x10 : 0) | + ch_elem_to_typecode(target,elem); + cmd[2] = (elem >> 8) & 0xff; + cmd[3] = elem & 0xff; + cmd[5] = 1; + cmd[9] = 255; + if (0 == (result = ch_do_cmd(target, cmd, buffer, 256))) { + if (((buffer[16] << 8) | buffer[17]) != elem) { + dprintk("asked for element 0x%02x, got 0x%02x\n", + elem,(buffer[16] << 8) | buffer[17]); + scsi_free(buffer, 512); + return -EIO; + } + memcpy(data,buffer+16,16); + } else { + if (ch[target].voltags) { + ch[target].voltags = 0; + vprintk("device has no volume tag support%s\n",""); + goto retry; + } + dprintk("READ ELEMENT STATUS for element 0x%x failed\n",elem); + } + scsi_free(buffer, 512); + return result; +} + +static int +ch_init_elem(int target) +{ + int err; + u_char cmd[6]; + + vprintk("INITIALIZE ELEMENT STATUS, may take some time ...%s\n",""); + memset(cmd,0,sizeof(cmd)); + cmd[0] = INITIALIZE_ELEMENT_STATUS; + cmd[1] = ch[target].device->lun << 5; + err = ch_do_cmd(target, cmd, NULL, 0); + vprintk("... finished%s\n",""); + return err; +} + +static int +ch_readconfig(int target) +{ + u_char cmd[10], data[16]; + u_char *buffer; + int result,id,lun,i; + u_int elem; + + buffer = (unsigned char *) scsi_malloc(512); + if (!buffer) + return -ENOMEM; + memset(buffer,0,512); + + memset(cmd,0,sizeof(cmd)); + cmd[0] = MODE_SENSE; + cmd[1] = ch[target].device->lun << 5; + cmd[2] = 0x1d; + cmd[4] = 255; + result = ch_do_cmd(target, cmd, buffer, 255); + if (0 != result) { + cmd[1] |= (1<<3); + result = ch_do_cmd(target, cmd, buffer, 255); + } + if (0 == result) { + ch[target].firsts[CHET_MT] = + (buffer[buffer[3]+ 6] << 8) | buffer[buffer[3]+ 7]; + ch[target].counts[CHET_MT] = + (buffer[buffer[3]+ 8] << 8) | buffer[buffer[3]+ 9]; + ch[target].firsts[CHET_ST] = + (buffer[buffer[3]+10] << 8) | buffer[buffer[3]+11]; + ch[target].counts[CHET_ST] = + (buffer[buffer[3]+12] << 8) | buffer[buffer[3]+13]; + ch[target].firsts[CHET_IE] = + (buffer[buffer[3]+14] << 8) | buffer[buffer[3]+15]; + ch[target].counts[CHET_IE] = + (buffer[buffer[3]+16] << 8) | buffer[buffer[3]+17]; + ch[target].firsts[CHET_DT] = + (buffer[buffer[3]+18] << 8) | buffer[buffer[3]+19]; + ch[target].counts[CHET_DT] = + (buffer[buffer[3]+20] << 8) | buffer[buffer[3]+21]; + vprintk("type #1 (mt): 0x%x+%d [medium transport]\n", + ch[target].firsts[CHET_MT], + ch[target].counts[CHET_MT]); + vprintk("type #2 (st): 0x%x+%d [storage]\n", + ch[target].firsts[CHET_ST], + ch[target].counts[CHET_ST]); + vprintk("type #3 (ie): 0x%x+%d [import/export]\n", + ch[target].firsts[CHET_IE], + ch[target].counts[CHET_IE]); + vprintk("type #4 (dt): 0x%x+%d [data transfer]\n", + ch[target].firsts[CHET_DT], + ch[target].counts[CHET_DT]); + } else { + vprintk("reading element address assigment page failed!%s\n", + ""); + } + + /* vendor specific element types */ + for (i = 0; i < 4; i++) { + if (0 == vendor_counts[i]) + continue; + if (NULL == vendor_labels[i]) + continue; + ch[target].firsts[CHET_V1+i] = vendor_firsts[i]; + ch[target].counts[CHET_V1+i] = vendor_counts[i]; + vprintk("type #%d (v%d): 0x%x+%d [%s, vendor specific]\n", + i+5,i+1,vendor_firsts[i],vendor_counts[i], + vendor_labels[i]); + } + + /* look up the devices of the data transfer elements */ + ch[target].dt = + kmalloc(ch[target].counts[CHET_DT]*sizeof(Scsi_Device*), + GFP_ATOMIC); + for (elem = 0; elem < ch[target].counts[CHET_DT]; elem++) { + id = -1; + lun = 0; + if (-1 != dt_id[elem]) { + id = dt_id[elem]; + lun = dt_lun[elem]; + vprintk("dt 0x%x: [insmod option] ", + elem+ch[target].firsts[CHET_DT]); + } else if (0 != ch_read_element_status + (target,elem+ch[target].firsts[CHET_DT],data)) { + vprintk("dt 0x%x: READ ELEMENT STATUS failed\n", + elem+ch[target].firsts[CHET_DT]); + } else { + vprintk("dt 0x%x: ",elem+ch[target].firsts[CHET_DT]); + if (data[6] & 0x80) { + if (verbose) + printk("not this SCSI bus\n"); + ch[target].dt[elem] = NULL; + } else if (0 == (data[6] & 0x30)) { + if (verbose) + printk("ID/LUN unknown\n"); + ch[target].dt[elem] = NULL; + } else { + id = ch[target].device->id; + lun = 0; + if (data[6] & 0x20) id = data[7]; + if (data[6] & 0x10) lun = data[6] & 7; + } + } + if (-1 != id) { + if (verbose) + printk("ID %i, LUN %i, ",id,lun); + ch[target].dt[elem] = + find_device(ch[target].device->host, + ch[target].device->channel, + id,lun); + if (!ch[target].dt[elem]) { + /* should not happen */ + if (verbose) + printk("Huh? device not found!\n"); + } else { + if (verbose) + printk("name: %8.8s %16.16s %4.4s\n", + ch[target].dt[elem]->vendor, + ch[target].dt[elem]->model, + ch[target].dt[elem]->rev); + } + } + } + ch[target].voltags = 1; + scsi_free(buffer, 512); + + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static int +ch_position(int target, u_int trans, u_int elem, int rotate) +{ + u_char cmd[10]; + + dprintk("position: 0x%x\n",elem); + if (0 == trans) + trans = ch[target].firsts[CHET_MT]; + memset(cmd,0,sizeof(cmd)); + cmd[0] = POSITION_TO_ELEMENT; + cmd[1] = ch[target].device->lun << 5; + cmd[2] = (trans >> 8) & 0xff; + cmd[3] = trans & 0xff; + cmd[4] = (elem >> 8) & 0xff; + cmd[5] = elem & 0xff; + cmd[8] = rotate ? 1 : 0; + return ch_do_cmd(target, cmd, NULL,0); +} + +static int +ch_move(int target, u_int trans, u_int src, u_int dest, int rotate) +{ + u_char cmd[12]; + + dprintk("move: 0x%x => 0x%x\n",src,dest); + if (0 == trans) + trans = ch[target].firsts[CHET_MT]; + memset(cmd,0,sizeof(cmd)); + cmd[0] = MOVE_MEDIUM; + cmd[1] = ch[target].device->lun << 5; + cmd[2] = (trans >> 8) & 0xff; + cmd[3] = trans & 0xff; + cmd[4] = (src >> 8) & 0xff; + cmd[5] = src & 0xff; + cmd[6] = (dest >> 8) & 0xff; + cmd[7] = dest & 0xff; + cmd[10] = rotate ? 1 : 0; + return ch_do_cmd(target, cmd, NULL,0); +} + +static int +ch_exchange(int target, u_int trans, u_int src, + u_int dest1, u_int dest2, int rotate1, int rotate2) +{ + u_char cmd[12]; + + dprintk("exchange: 0x%x => 0x%x => 0x%x\n", + src,dest1,dest2); + if (0 == trans) + trans = ch[target].firsts[CHET_MT]; + memset(cmd,0,sizeof(cmd)); + cmd[0] = EXCHANGE_MEDIUM; + cmd[1] = ch[target].device->lun << 5; + cmd[2] = (trans >> 8) & 0xff; + cmd[3] = trans & 0xff; + cmd[4] = (src >> 8) & 0xff; + cmd[5] = src & 0xff; + cmd[6] = (dest1 >> 8) & 0xff; + cmd[7] = dest1 & 0xff; + cmd[8] = (dest2 >> 8) & 0xff; + cmd[9] = dest2 & 0xff; + cmd[10] = (rotate1 ? 1 : 0) | (rotate2 ? 2 : 0); + + return ch_do_cmd(target, cmd, NULL,0); +} + +static void +ch_check_voltag(char *tag) +{ + int i; + + for (i = 0; i < 32; i++) { + /* restrict to ascii */ + if (tag[i] >= 0x7f || tag[i] < 0x20) + tag[i] = ' '; + /* don't allow search wildcards */ + if (tag[i] == '?' || + tag[i] == '*') + tag[i] = ' '; + } +} + +static int +ch_set_voltag(int target, u_int elem, + int alternate, int clear, u_char *tag) +{ + u_char cmd[12]; + u_char *buffer; + int result; + + buffer = scsi_malloc(512); + if (!buffer) + return -ENOMEM; + memset(buffer,0,512); + + dprintk("%s %s voltag: 0x%x => \"%s\"\n", + clear ? "clear" : "set", + alternate ? "alternate" : "primary", + elem, tag); + memset(cmd,0,sizeof(cmd)); + cmd[0] = SEND_VOLUME_TAG; + cmd[1] = (ch[target].device->lun << 5) | + ch_elem_to_typecode(target,elem); + cmd[2] = (elem >> 8) & 0xff; + cmd[3] = elem & 0xff; + cmd[5] = clear + ? (alternate ? 0x0d : 0x0c) + : (alternate ? 0x0b : 0x0a); + + cmd[9] = 255; + + memcpy(buffer,tag,32); + ch_check_voltag(buffer); + + result = ch_do_cmd(target, cmd, buffer, 256); + scsi_free(buffer, 512); + return result; +} + +/* ------------------------------------------------------------------------ */ + +static int +ch_release(struct inode * inode, struct file * filp) +{ + int minor = MINOR(inode->i_rdev); + + ch[minor].device->access_count--; + if (ch[minor].device->host->hostt->module) + __MOD_DEC_USE_COUNT(ch[minor].device->host->hostt->module); + return 0; +} + +static int +ch_open(struct inode * inode, struct file * filp) +{ + int minor = MINOR(inode->i_rdev); + + if (minor >= ch_template.nr_dev || !ch[minor].device) + return -ENXIO; + + ch[minor].device->access_count++; + if (ch[minor].device->host->hostt->module) + __MOD_INC_USE_COUNT(ch[minor].device->host->hostt->module); + + return 0; +} + +static int +ch_checkrange(int target, int type, int unit) +{ + if (type < 0 || type >= CH_TYPES || unit < 0 || + unit >= ch[target].counts[type]) + return -1; + return 0; +} + +/* for data transfer elements: check if they are busy */ +static int +ch_is_busy(int target, int type, int unit) +{ + if (!check_busy) + return 0; + if (type != CHET_DT) + return 0; + if (!ch[target].dt[unit]) + return 0; + return ch[target].dt[unit]->access_count; +} + +static int +ch_ioctl(struct inode * inode, struct file * filp, + unsigned int cmd, unsigned long arg) +{ + int target = MINOR(inode->i_rdev); + int retval; + + if (target >= ch_template.nr_dev || !ch[target].device) + return -ENXIO; + + switch (cmd) { + case CHIOGPARAMS: + { + struct changer_params params; + + params.cp_curpicker = 0; + params.cp_npickers = ch[target].counts[CHET_MT]; + params.cp_nslots = ch[target].counts[CHET_ST]; + params.cp_nportals = ch[target].counts[CHET_IE]; + params.cp_ndrives = ch[target].counts[CHET_DT]; + + if (copy_to_user((void *) arg, ¶ms, sizeof(params))) + return -EFAULT; + return 0; + } + case CHIOGVPARAMS: + { + struct changer_vendor_params vparams; + + memset(&vparams,0,sizeof(vparams)); + if (ch[target].counts[CHET_V1]) { + vparams.cvp_n1 = ch[target].counts[CHET_V1]; + strncpy(vparams.cvp_label1,vendor_labels[0],16); + } + if (ch[target].counts[CHET_V2]) { + vparams.cvp_n2 = ch[target].counts[CHET_V2]; + strncpy(vparams.cvp_label2,vendor_labels[1],16); + } + if (ch[target].counts[CHET_V3]) { + vparams.cvp_n3 = ch[target].counts[CHET_V3]; + strncpy(vparams.cvp_label3,vendor_labels[2],16); + } + if (ch[target].counts[CHET_V4]) { + vparams.cvp_n4 = ch[target].counts[CHET_V4]; + strncpy(vparams.cvp_label4,vendor_labels[3],16); + } + if (copy_to_user((void *) arg, &vparams, sizeof(vparams))) + return -EFAULT; + return 0; + } + + case CHIOPOSITION: + { + struct changer_position pos; + + if (copy_from_user(&pos, (void*)arg, sizeof (pos))) + return -EFAULT; + + if (0 != ch_checkrange(target, pos.cp_type, pos.cp_unit)) { + dprintk("CHIOPOSITION: invalid parameter%s\n",""); + return -EBADSLT; + } + down(&ch[target].lock); + retval = ch_position(target,0, + ch[target].firsts[pos.cp_type] + pos.cp_unit, + pos.cp_flags & CP_INVERT); + up(&ch[target].lock); + return retval; + } + + case CHIOMOVE: + { + struct changer_move mv; + + if (copy_from_user(&mv, (void*)arg, sizeof (mv))) + return -EFAULT; + + if (0 != ch_checkrange(target, mv.cm_fromtype, mv.cm_fromunit) || + 0 != ch_checkrange(target, mv.cm_totype, mv.cm_tounit )) { + dprintk("CHIOMOVE: invalid parameter%s\n",""); + return -EBADSLT; + } + if (ch_is_busy(target, mv.cm_fromtype, mv.cm_fromunit) || + ch_is_busy(target, mv.cm_totype, mv.cm_tounit )) + return -EBUSY; + + down(&ch[target].lock); + retval = ch_move(target,0, + ch[target].firsts[mv.cm_fromtype] + mv.cm_fromunit, + ch[target].firsts[mv.cm_totype] + mv.cm_tounit, + mv.cm_flags & CM_INVERT); + up(&ch[target].lock); + return retval; + } + + case CHIOEXCHANGE: + { + struct changer_exchange mv; + + if (copy_from_user(&mv, (void*)arg, sizeof (mv))) + return -EFAULT; + + if (0 != ch_checkrange(target, mv.ce_srctype, mv.ce_srcunit ) || + 0 != ch_checkrange(target, mv.ce_fdsttype, mv.ce_fdstunit) || + 0 != ch_checkrange(target, mv.ce_sdsttype, mv.ce_sdstunit)) { + dprintk("CHIOEXCHANGE: invalid parameter%s\n",""); + return -EBADSLT; + } + if (0 != ch_is_busy(target, mv.ce_srctype, mv.ce_srcunit ) || + 0 != ch_is_busy(target, mv.ce_fdsttype, mv.ce_fdstunit) || + 0 != ch_is_busy(target, mv.ce_sdsttype, mv.ce_sdstunit)) + return -EBUSY; + + down(&ch[target].lock); + retval = ch_exchange + (target,0, + ch[target].firsts[mv.ce_srctype] + mv.ce_srcunit, + ch[target].firsts[mv.ce_fdsttype] + mv.ce_fdstunit, + ch[target].firsts[mv.ce_sdsttype] + mv.ce_sdstunit, + mv.ce_flags & CE_INVERT1, mv.ce_flags & CE_INVERT2); + up(&ch[target].lock); + return retval; + } + + case CHIOGSTATUS: + { + struct changer_element_status ces; + u_char data[16]; + int i; + + if (copy_from_user(&ces, (void*)arg, sizeof (ces))) + return -EFAULT; + + if (ces.ces_type < 0 || ces.ces_type >= CH_TYPES) + return -EINVAL; + + down(&ch[target].lock); + for (i = 0; i < ch[target].counts[ces.ces_type]; i++) { + if (0 != ch_read_element_status + (target, ch[target].firsts[ces.ces_type]+i,data)) + return -EIO; + put_user(data[2], ces.ces_data+i); + if (data[2] & CESTATUS_EXCEPT) + vprintk("element 0x%x: asc=0x%x, ascq=0x%x\n", + ch[target].firsts[ces.ces_type]+i, + (int)data[4],(int)data[5]); + retval = ch_read_element_status + (target, ch[target].firsts[ces.ces_type]+i,data); + if (0 != retval) { + up(&ch[target].lock); + return retval; + } + } + up(&ch[target].lock); + return 0; + } + + case CHIOGELEM: + { + struct changer_get_element cge; + u_char cmd[12]; + u_char *buffer; + int elem,result,i; + + if (copy_from_user(&cge, (void*)arg, sizeof (cge))) + return -EFAULT; + + if (0 != ch_checkrange(target, cge.cge_type, cge.cge_unit)) + return -EINVAL; + elem = ch[target].firsts[cge.cge_type] + cge.cge_unit; + + buffer = (unsigned char *) scsi_malloc(512); + if (!buffer) + return -ENOMEM; + down(&ch[target].lock); + + voltag_retry: + memset(cmd,0,sizeof(cmd)); + cmd[0] = READ_ELEMENT_STATUS; + cmd[1] = (ch[target].device->lun << 5) | + (ch[target].voltags ? 0x10 : 0) | + ch_elem_to_typecode(target,elem); + cmd[2] = (elem >> 8) & 0xff; + cmd[3] = elem & 0xff; + cmd[5] = 1; + cmd[9] = 255; + + if (0 == (result = ch_do_cmd(target, cmd, buffer, 256))) { + cge.cge_status = buffer[18]; + cge.cge_flags = 0; + if (buffer[18] & CESTATUS_EXCEPT) { + /* FIXME: fill cge_errno */ + } + if (buffer[25] & 0x80) { + cge.cge_flags |= CGE_SRC; + if (buffer[25] & 0x40) + cge.cge_flags |= CGE_INVERT; + elem = (buffer[26]<<8) | buffer[27]; + for (i = 0; i < 4; i++) { + if (elem >= ch[target].firsts[i] && + elem < ch[target].firsts[i]+ + ch[target].counts[i]) { + cge.cge_srctype = i; + cge.cge_srcunit = elem-ch[target].firsts[i]; + } + } + } + if ((buffer[22] & 0x30) == 0x30) { + cge.cge_flags |= CGE_IDLUN; + cge.cge_id = buffer[23]; + cge.cge_lun = buffer[22] & 7; + } + if (buffer[9] & 0x80) { + cge.cge_flags |= CGE_PVOLTAG; + memcpy(cge.cge_pvoltag,buffer+28,36); + } + if (buffer[9] & 0x40) { + cge.cge_flags |= CGE_AVOLTAG; + memcpy(cge.cge_avoltag,buffer+64,36); + } + } else if (ch[target].voltags) { + ch[target].voltags = 0; + vprintk("device has no volume tag support%s\n",""); + goto voltag_retry; + } + scsi_free(buffer, 512); + up(&ch[target].lock); + + if (copy_to_user((void*)arg, &cge, sizeof (cge))) + return -EFAULT; + return result; + } + + case CHIOINITELEM: + { + down(&ch[target].lock); + retval = ch_init_elem(target); + up(&ch[target].lock); + return retval; + } + + case CHIOSVOLTAG: + { + struct changer_set_voltag csv; + int elem; + + if (copy_from_user(&csv, (void*)arg, sizeof(csv))) + return -EFAULT; + + if (0 != ch_checkrange(target, csv.csv_type, csv.csv_unit)) { + dprintk("CHIOSVOLTAG: invalid parameter%s\n",""); + return -EBADSLT; + } + elem = ch[target].firsts[csv.csv_type] + csv.csv_unit; + down(&ch[target].lock); + retval = ch_set_voltag(target, elem, + csv.csv_flags & CSV_AVOLTAG, + csv.csv_flags & CSV_CLEARTAG, + csv.csv_voltag); + up(&ch[target].lock); + return retval; + } + + default: + return scsi_ioctl(ch[target].device, cmd, (void*)arg); + + } +} + +/* ------------------------------------------------------------------------ */ + +static int +ch_detect(Scsi_Device * SDp) +{ + if(SDp->type != TYPE_MEDIUM_CHANGER) + return 0; + + printk(KERN_INFO "Detected scsi changer ch%d " + "at scsi%d, channel %d, id %d, lun %d\n", + ch_template.dev_noticed++, + SDp->host->host_no, SDp->channel, SDp->id, SDp->lun); + + return 1; +} + +static int ch_attach(Scsi_Device * SDp){ + Scsi_Changer * cpnt; + int i; + + if(SDp->type != TYPE_MEDIUM_CHANGER) + return 1; + + if (ch_template.nr_dev >= ch_template.dev_max) { + SDp->attached--; + return 1; + } + + for (cpnt = ch, i=0; idevice) + break; + + if (i >= ch_template.dev_max) + panic("scsi_devicelist corrupt (ch)"); + + sprintf(ch[i].name,"ch%d",i); + ch[i].device = SDp; + ch[i].de = devfs_register (ch[i].device->de, "changer", + DEVFS_FL_DEFAULT, MAJOR_NR, i, + S_IFCHR | S_IRUGO | S_IWUGO, + &changer_fops, NULL); + ch_template.nr_dev++; + if (ch_template.nr_dev > ch_template.dev_max) + panic("scsi_devicelist corrupt (ch)"); + return 0; +} + +static int ch_registered = 0; + +static int ch_init() +{ + if (ch_template.dev_noticed == 0) + return 0; + + if (!ch_registered) { + if (devfs_register_chrdev(MAJOR_NR,"ch",&changer_fops)) { + printk("Unable to get major %d for SCSI-Changer\n", + MAJOR_NR); + return 1; + } + ch_registered++; + } + + if (ch) + return 0; + ch_template.dev_max = + ch_template.dev_noticed + CH_EXTRA_DEVS; + ch = kmalloc(ch_template.dev_max * sizeof(Scsi_Changer), GFP_ATOMIC); + memset(ch, 0, ch_template.dev_max * sizeof(Scsi_Changer)); + return 0; +} + +void +ch_finish() +{ + int i; + + for (i = 0; i < ch_template.nr_dev; i++) { + if (!ch[i].device) + continue; + init_MUTEX(&ch[i].lock); + ch_readconfig(i); + if (init) + ch_init_elem(i); + } + + return; +} + +static void ch_detach(Scsi_Device * SDp) +{ + Scsi_Changer * cpnt; + int i; + + for(cpnt = ch, i=0; idevice != SDp) + continue; + /* + * Reset things back to a sane state so that one can + * re-load a new driver (perhaps the same one). + */ + devfs_unregister(cpnt->de); + kfree(cpnt->dt); + cpnt->device = NULL; + SDp->attached--; + ch_template.nr_dev--; + ch_template.dev_noticed--; + return; + } + return; +} + +static int __init init_ch_module(void) +{ + printk(KERN_INFO "SCSI Media Changer driver v" VERSION + " for Linux " UTS_RELEASE "\n"); + return scsi_register_module(MODULE_SCSI_DEV, &ch_template); +} + +static void __exit exit_ch_module(void) +{ + scsi_unregister_module(MODULE_SCSI_DEV, &ch_template); + devfs_unregister_chrdev(MAJOR_NR, "ch"); + ch_registered--; + if(ch != NULL) + kfree((char *)ch); + ch_template.dev_max = 0; +} + +module_init(init_ch_module); +module_exit(exit_ch_module); +EXPORT_NO_SYMBOLS; + +/* + * Local variables: + * c-basic-offset: 8 + * End: + */ *** *** 6) Creates a 160-line include/linux/chio.h file. --- linux-2.4/include/linux/chio.h Wed Dec 31 16:00:00 1969 +++ Jukebox/include/linux/chio.h Thu Apr 25 02:44:44 2002 @@ -0,0 +1,160 @@ +/* + * 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) *** *** 7) Adds 2 lines to include/linux/major.h for device major # 86, SCSI Changers --- linux-2.4/include/linux/major.h Fri Apr 26 07:59:00 2002 +++ Jukebox/include/linux/major.h Wed Apr 24 08:12:22 2002 @@ -132,6 +132,8 @@ #define SCSI_DISK16_MAJOR 134 #define SCSI_DISK17_MAJOR 135 +#define SCSI_CHANGER_MAJOR 86 + #define DASD_MAJOR 94 /* Official assignations from Peter */ #define MDISK_MAJOR 95 /* Official assignations from Peter */ *** *** 8) Adds 3 lines to include/scsi/scsi.h --- linux-2.4/include/scsi/scsi.h Fri Apr 26 07:59:07 2002 +++ Jukebox/include/scsi/scsi.h Wed Apr 24 08:12:29 2002 @@ -24,6 +24,7 @@ #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 @@ -48,6 +49,7 @@ #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 @@ -79,6 +81,7 @@ #define PERSISTENT_RESERVE_IN 0x5e #define PERSISTENT_RESERVE_OUT 0x5f #define MOVE_MEDIUM 0xa5 +#define EXCHANGE_MEDIUM 0xa6 #define READ_12 0xa8 #define WRITE_12 0xaa #define WRITE_VERIFY_12 0xae