diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/st.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/scsi/st.c')
-rw-r--r-- | drivers/scsi/st.c | 4438 |
1 files changed, 4438 insertions, 0 deletions
diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c new file mode 100644 index 00000000000..265d1eed64f --- /dev/null +++ b/drivers/scsi/st.c @@ -0,0 +1,4438 @@ +/* + SCSI Tape Driver for Linux version 1.1 and newer. See the accompanying + file Documentation/scsi/st.txt for more information. + + History: + Rewritten from Dwayne Forsyth's SCSI tape driver by Kai Makisara. + Contribution and ideas from several people including (in alphabetical + order) Klaus Ehrenfried, Eugene Exarevsky, Eric Lee Green, Wolfgang Denk, + Steve Hirsch, Andreas Koppenh"ofer, Michael Leodolter, Eyal Lebedinsky, + Michael Schaefer, J"org Weule, and Eric Youngdale. + + Copyright 1992 - 2005 Kai Makisara + email Kai.Makisara@kolumbus.fi + + Some small formal changes - aeb, 950809 + + Last modified: 18-JAN-1998 Richard Gooch <rgooch@atnf.csiro.au> Devfs support + */ + +static char *verstr = "20050312"; + +#include <linux/module.h> + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/mtio.h> +#include <linux/ioctl.h> +#include <linux/fcntl.h> +#include <linux/spinlock.h> +#include <linux/blkdev.h> +#include <linux/moduleparam.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/cdev.h> +#include <linux/delay.h> + +#include <asm/uaccess.h> +#include <asm/dma.h> +#include <asm/system.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_dbg.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_driver.h> +#include <scsi/scsi_eh.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_ioctl.h> +#include <scsi/scsi_request.h> + + +/* The driver prints some debugging information on the console if DEBUG + is defined and non-zero. */ +#define DEBUG 0 + +#if DEBUG +/* The message level for the debug messages is currently set to KERN_NOTICE + so that people can easily see the messages. Later when the debugging messages + in the drivers are more widely classified, this may be changed to KERN_DEBUG. */ +#define ST_DEB_MSG KERN_NOTICE +#define DEB(a) a +#define DEBC(a) if (debugging) { a ; } +#else +#define DEB(a) +#define DEBC(a) +#endif + +#define ST_KILOBYTE 1024 + +#include "st_options.h" +#include "st.h" + +static int buffer_kbs; +static int max_sg_segs; +static int try_direct_io = TRY_DIRECT_IO; +static int try_rdio = 1; +static int try_wdio = 1; + +static int st_dev_max; +static int st_nr_dev; + +static struct class_simple *st_sysfs_class; + +MODULE_AUTHOR("Kai Makisara"); +MODULE_DESCRIPTION("SCSI Tape Driver"); +MODULE_LICENSE("GPL"); + +/* Set 'perm' (4th argument) to 0 to disable module_param's definition + * of sysfs parameters (which module_param doesn't yet support). + * Sysfs parameters defined explicitly later. + */ +module_param_named(buffer_kbs, buffer_kbs, int, 0); +MODULE_PARM_DESC(buffer_kbs, "Default driver buffer size for fixed block mode (KB; 32)"); +module_param_named(max_sg_segs, max_sg_segs, int, 0); +MODULE_PARM_DESC(max_sg_segs, "Maximum number of scatter/gather segments to use (256)"); +module_param_named(try_direct_io, try_direct_io, int, 0); +MODULE_PARM_DESC(try_direct_io, "Try direct I/O between user buffer and tape drive (1)"); + +/* Extra parameters for testing */ +module_param_named(try_rdio, try_rdio, int, 0); +MODULE_PARM_DESC(try_rdio, "Try direct read i/o when possible"); +module_param_named(try_wdio, try_wdio, int, 0); +MODULE_PARM_DESC(try_wdio, "Try direct write i/o when possible"); + +#ifndef MODULE +static int write_threshold_kbs; /* retained for compatibility */ +static struct st_dev_parm { + char *name; + int *val; +} parms[] __initdata = { + { + "buffer_kbs", &buffer_kbs + }, + { /* Retained for compatibility with 2.4 */ + "write_threshold_kbs", &write_threshold_kbs + }, + { + "max_sg_segs", NULL + }, + { + "try_direct_io", &try_direct_io + } +}; +#endif + +/* Restrict the number of modes so that names for all are assigned */ +#if ST_NBR_MODES > 16 +#error "Maximum number of modes is 16" +#endif +/* Bit reversed order to get same names for same minors with all + mode counts */ +static char *st_formats[] = { + "", "r", "k", "s", "l", "t", "o", "u", + "m", "v", "p", "x", "a", "y", "q", "z"}; + +/* The default definitions have been moved to st_options.h */ + +#define ST_FIXED_BUFFER_SIZE (ST_FIXED_BUFFER_BLOCKS * ST_KILOBYTE) + +/* The buffer size should fit into the 24 bits for length in the + 6-byte SCSI read and write commands. */ +#if ST_FIXED_BUFFER_SIZE >= (2 << 24 - 1) +#error "Buffer size should not exceed (2 << 24 - 1) bytes!" +#endif + +static int debugging = DEBUG; + +#define MAX_RETRIES 0 +#define MAX_WRITE_RETRIES 0 +#define MAX_READY_RETRIES 0 +#define NO_TAPE NOT_READY + +#define ST_TIMEOUT (900 * HZ) +#define ST_LONG_TIMEOUT (14000 * HZ) + +/* Remove mode bits and auto-rewind bit (7) */ +#define TAPE_NR(x) ( ((iminor(x) & ~255) >> (ST_NBR_MODE_BITS + 1)) | \ + (iminor(x) & ~(-1 << ST_MODE_SHIFT)) ) +#define TAPE_MODE(x) ((iminor(x) & ST_MODE_MASK) >> ST_MODE_SHIFT) + +/* Construct the minor number from the device (d), mode (m), and non-rewind (n) data */ +#define TAPE_MINOR(d, m, n) (((d & ~(255 >> (ST_NBR_MODE_BITS + 1))) << (ST_NBR_MODE_BITS + 1)) | \ + (d & (255 >> (ST_NBR_MODE_BITS + 1))) | (m << ST_MODE_SHIFT) | ((n != 0) << 7) ) + +/* Internal ioctl to set both density (uppermost 8 bits) and blocksize (lower + 24 bits) */ +#define SET_DENS_AND_BLK 0x10001 + +static DEFINE_RWLOCK(st_dev_arr_lock); + +static int st_fixed_buffer_size = ST_FIXED_BUFFER_SIZE; +static int st_max_sg_segs = ST_MAX_SG; + +static struct scsi_tape **scsi_tapes = NULL; + +static int modes_defined; + +static struct st_buffer *new_tape_buffer(int, int, int); +static int enlarge_buffer(struct st_buffer *, int, int); +static void normalize_buffer(struct st_buffer *); +static int append_to_buffer(const char __user *, struct st_buffer *, int); +static int from_buffer(struct st_buffer *, char __user *, int); +static void move_buffer_data(struct st_buffer *, int); +static void buf_to_sg(struct st_buffer *, unsigned int); + +static int st_map_user_pages(struct scatterlist *, const unsigned int, + unsigned long, size_t, int, unsigned long); +static int sgl_map_user_pages(struct scatterlist *, const unsigned int, + unsigned long, size_t, int); +static int sgl_unmap_user_pages(struct scatterlist *, const unsigned int, int); + +static int st_probe(struct device *); +static int st_remove(struct device *); +static int st_init_command(struct scsi_cmnd *); + +static void do_create_driverfs_files(void); +static void do_remove_driverfs_files(void); +static void do_create_class_files(struct scsi_tape *, int, int); + +static struct scsi_driver st_template = { + .owner = THIS_MODULE, + .gendrv = { + .name = "st", + .probe = st_probe, + .remove = st_remove, + }, + .init_command = st_init_command, +}; + +static int st_compression(struct scsi_tape *, int); + +static int find_partition(struct scsi_tape *); +static int switch_partition(struct scsi_tape *); + +static int st_int_ioctl(struct scsi_tape *, unsigned int, unsigned long); + + +#include "osst_detect.h" +#ifndef SIGS_FROM_OSST +#define SIGS_FROM_OSST \ + {"OnStream", "SC-", "", "osst"}, \ + {"OnStream", "DI-", "", "osst"}, \ + {"OnStream", "DP-", "", "osst"}, \ + {"OnStream", "USB", "", "osst"}, \ + {"OnStream", "FW-", "", "osst"} +#endif + +struct st_reject_data { + char *vendor; + char *model; + char *rev; + char *driver_hint; /* Name of the correct driver, NULL if unknown */ +}; + +static struct st_reject_data reject_list[] = { + /* {"XXX", "Yy-", "", NULL}, example */ + SIGS_FROM_OSST, + {NULL, }}; + +/* If the device signature is on the list of incompatible drives, the + function returns a pointer to the name of the correct driver (if known) */ +static char * st_incompatible(struct scsi_device* SDp) +{ + struct st_reject_data *rp; + + for (rp=&(reject_list[0]); rp->vendor != NULL; rp++) + if (!strncmp(rp->vendor, SDp->vendor, strlen(rp->vendor)) && + !strncmp(rp->model, SDp->model, strlen(rp->model)) && + !strncmp(rp->rev, SDp->rev, strlen(rp->rev))) { + if (rp->driver_hint) + return rp->driver_hint; + else + return "unknown"; + } + return NULL; +} + + +static inline char *tape_name(struct scsi_tape *tape) +{ + return tape->disk->disk_name; +} + + +static void st_analyze_sense(struct scsi_request *SRpnt, struct st_cmdstatus *s) +{ + const u8 *ucp; + const u8 *sense = SRpnt->sr_sense_buffer; + + s->have_sense = scsi_request_normalize_sense(SRpnt, &s->sense_hdr); + s->flags = 0; + + if (s->have_sense) { + s->deferred = 0; + s->remainder_valid = + scsi_get_sense_info_fld(sense, SCSI_SENSE_BUFFERSIZE, &s->uremainder64); + switch (sense[0] & 0x7f) { + case 0x71: + s->deferred = 1; + case 0x70: + s->fixed_format = 1; + s->flags = sense[2] & 0xe0; + break; + case 0x73: + s->deferred = 1; + case 0x72: + s->fixed_format = 0; + ucp = scsi_sense_desc_find(sense, SCSI_SENSE_BUFFERSIZE, 4); + s->flags = ucp ? (ucp[3] & 0xe0) : 0; + break; + } + } +} + + +/* Convert the result to success code */ +static int st_chk_result(struct scsi_tape *STp, struct scsi_request * SRpnt) +{ + int result = SRpnt->sr_result; + u8 scode; + DEB(const char *stp;) + char *name = tape_name(STp); + struct st_cmdstatus *cmdstatp; + + if (!result) + return 0; + + cmdstatp = &STp->buffer->cmdstat; + st_analyze_sense(STp->buffer->last_SRpnt, cmdstatp); + + if (cmdstatp->have_sense) + scode = STp->buffer->cmdstat.sense_hdr.sense_key; + else + scode = 0; + + DEB( + if (debugging) { + printk(ST_DEB_MSG "%s: Error: %x, cmd: %x %x %x %x %x %x Len: %d\n", + name, result, + SRpnt->sr_cmnd[0], SRpnt->sr_cmnd[1], SRpnt->sr_cmnd[2], + SRpnt->sr_cmnd[3], SRpnt->sr_cmnd[4], SRpnt->sr_cmnd[5], + SRpnt->sr_bufflen); + if (cmdstatp->have_sense) + scsi_print_req_sense("st", SRpnt); + } ) /* end DEB */ + if (!debugging) { /* Abnormal conditions for tape */ + if (!cmdstatp->have_sense) + printk(KERN_WARNING + "%s: Error %x (sugg. bt 0x%x, driver bt 0x%x, host bt 0x%x).\n", + name, result, suggestion(result), + driver_byte(result) & DRIVER_MASK, host_byte(result)); + else if (cmdstatp->have_sense && + scode != NO_SENSE && + scode != RECOVERED_ERROR && + /* scode != UNIT_ATTENTION && */ + scode != BLANK_CHECK && + scode != VOLUME_OVERFLOW && + SRpnt->sr_cmnd[0] != MODE_SENSE && + SRpnt->sr_cmnd[0] != TEST_UNIT_READY) { + printk(KERN_WARNING "%s: Error with sense data: ", name); + scsi_print_req_sense("st", SRpnt); + } + } + + if (cmdstatp->fixed_format && + STp->cln_mode >= EXTENDED_SENSE_START) { /* Only fixed format sense */ + if (STp->cln_sense_value) + STp->cleaning_req |= ((SRpnt->sr_sense_buffer[STp->cln_mode] & + STp->cln_sense_mask) == STp->cln_sense_value); + else + STp->cleaning_req |= ((SRpnt->sr_sense_buffer[STp->cln_mode] & + STp->cln_sense_mask) != 0); + } + if (cmdstatp->have_sense && + cmdstatp->sense_hdr.asc == 0 && cmdstatp->sense_hdr.ascq == 0x17) + STp->cleaning_req = 1; /* ASC and ASCQ => cleaning requested */ + + STp->pos_unknown |= STp->device->was_reset; + + if (cmdstatp->have_sense && + scode == RECOVERED_ERROR +#if ST_RECOVERED_WRITE_FATAL + && SRpnt->sr_cmnd[0] != WRITE_6 + && SRpnt->sr_cmnd[0] != WRITE_FILEMARKS +#endif + ) { + STp->recover_count++; + STp->recover_reg++; + + DEB( + if (debugging) { + if (SRpnt->sr_cmnd[0] == READ_6) + stp = "read"; + else if (SRpnt->sr_cmnd[0] == WRITE_6) + stp = "write"; + else + stp = "ioctl"; + printk(ST_DEB_MSG "%s: Recovered %s error (%d).\n", name, stp, + STp->recover_count); + } ) /* end DEB */ + + if (cmdstatp->flags == 0) + return 0; + } + return (-EIO); +} + + +/* Wakeup from interrupt */ +static void st_sleep_done(struct scsi_cmnd * SCpnt) +{ + struct scsi_tape *STp = container_of(SCpnt->request->rq_disk->private_data, + struct scsi_tape, driver); + + (STp->buffer)->cmdstat.midlevel_result = SCpnt->result; + SCpnt->request->rq_status = RQ_SCSI_DONE; + (STp->buffer)->last_SRpnt = SCpnt->sc_request; + DEB( STp->write_pending = 0; ) + + complete(SCpnt->request->waiting); +} + +/* Do the scsi command. Waits until command performed if do_wait is true. + Otherwise write_behind_check() is used to check that the command + has finished. */ +static struct scsi_request * +st_do_scsi(struct scsi_request * SRpnt, struct scsi_tape * STp, unsigned char *cmd, + int bytes, int direction, int timeout, int retries, int do_wait) +{ + unsigned char *bp; + + if (SRpnt == NULL) { + SRpnt = scsi_allocate_request(STp->device, GFP_ATOMIC); + if (SRpnt == NULL) { + DEBC( printk(KERN_ERR "%s: Can't get SCSI request.\n", + tape_name(STp)); ); + if (signal_pending(current)) + (STp->buffer)->syscall_result = (-EINTR); + else + (STp->buffer)->syscall_result = (-EBUSY); + return NULL; + } + } + + init_completion(&STp->wait); + SRpnt->sr_use_sg = STp->buffer->do_dio || (bytes > (STp->buffer)->frp[0].length); + if (SRpnt->sr_use_sg) { + if (!STp->buffer->do_dio) + buf_to_sg(STp->buffer, bytes); + SRpnt->sr_use_sg = (STp->buffer)->sg_segs; + bp = (char *) &((STp->buffer)->sg[0]); + } else + bp = (STp->buffer)->b_data; + SRpnt->sr_data_direction = direction; + SRpnt->sr_cmd_len = 0; + SRpnt->sr_request->waiting = &(STp->wait); + SRpnt->sr_request->rq_status = RQ_SCSI_BUSY; + SRpnt->sr_request->rq_disk = STp->disk; + STp->buffer->cmdstat.have_sense = 0; + + scsi_do_req(SRpnt, (void *) cmd, bp, bytes, + st_sleep_done, timeout, retries); + + if (do_wait) { + wait_for_completion(SRpnt->sr_request->waiting); + SRpnt->sr_request->waiting = NULL; + (STp->buffer)->syscall_result = st_chk_result(STp, SRpnt); + } + return SRpnt; +} + + +/* Handle the write-behind checking (waits for completion). Returns -ENOSPC if + write has been correct but EOM early warning reached, -EIO if write ended in + error or zero if write successful. Asynchronous writes are used only in + variable block mode. */ +static int write_behind_check(struct scsi_tape * STp) +{ + int retval = 0; + struct st_buffer *STbuffer; + struct st_partstat *STps; + struct st_cmdstatus *cmdstatp; + + STbuffer = STp->buffer; + if (!STbuffer->writing) + return 0; + + DEB( + if (STp->write_pending) + STp->nbr_waits++; + else + STp->nbr_finished++; + ) /* end DEB */ + + wait_for_completion(&(STp->wait)); + (STp->buffer)->last_SRpnt->sr_request->waiting = NULL; + + (STp->buffer)->syscall_result = st_chk_result(STp, (STp->buffer)->last_SRpnt); + scsi_release_request((STp->buffer)->last_SRpnt); + + STbuffer->buffer_bytes -= STbuffer->writing; + STps = &(STp->ps[STp->partition]); + if (STps->drv_block >= 0) { + if (STp->block_size == 0) + STps->drv_block++; + else + STps->drv_block += STbuffer->writing / STp->block_size; + } + + cmdstatp = &STbuffer->cmdstat; + if (STbuffer->syscall_result) { + retval = -EIO; + if (cmdstatp->have_sense && !cmdstatp->deferred && + (cmdstatp->flags & SENSE_EOM) && + (cmdstatp->sense_hdr.sense_key == NO_SENSE || + cmdstatp->sense_hdr.sense_key == RECOVERED_ERROR)) { + /* EOM at write-behind, has all data been written? */ + if (!cmdstatp->remainder_valid || + cmdstatp->uremainder64 == 0) + retval = -ENOSPC; + } + if (retval == -EIO) + STps->drv_block = -1; + } + STbuffer->writing = 0; + + DEB(if (debugging && retval) + printk(ST_DEB_MSG "%s: Async write error %x, return value %d.\n", + tape_name(STp), STbuffer->cmdstat.midlevel_result, retval);) /* end DEB */ + + return retval; +} + + +/* Step over EOF if it has been inadvertently crossed (ioctl not used because + it messes up the block number). */ +static int cross_eof(struct scsi_tape * STp, int forward) +{ + struct scsi_request *SRpnt; + unsigned char cmd[MAX_COMMAND_SIZE]; + + cmd[0] = SPACE; + cmd[1] = 0x01; /* Space FileMarks */ + if (forward) { + cmd[2] = cmd[3] = 0; + cmd[4] = 1; + } else + cmd[2] = cmd[3] = cmd[4] = 0xff; /* -1 filemarks */ + cmd[5] = 0; + + DEBC(printk(ST_DEB_MSG "%s: Stepping over filemark %s.\n", + tape_name(STp), forward ? "forward" : "backward")); + + SRpnt = st_do_scsi(NULL, STp, cmd, 0, DMA_NONE, + STp->device->timeout, MAX_RETRIES, 1); + if (!SRpnt) + return (STp->buffer)->syscall_result; + + scsi_release_request(SRpnt); + SRpnt = NULL; + + if ((STp->buffer)->cmdstat.midlevel_result != 0) + printk(KERN_ERR "%s: Stepping over filemark %s failed.\n", + tape_name(STp), forward ? "forward" : "backward"); + + return (STp->buffer)->syscall_result; +} + + +/* Flush the write buffer (never need to write if variable blocksize). */ +static int flush_write_buffer(struct scsi_tape * STp) +{ + int offset, transfer, blks; + int result; + unsigned char cmd[MAX_COMMAND_SIZE]; + struct scsi_request *SRpnt; + struct st_partstat *STps; + + result = write_behind_check(STp); + if (result) + return result; + + result = 0; + if (STp->dirty == 1) { + + offset = (STp->buffer)->buffer_bytes; + transfer = ((offset + STp->block_size - 1) / + STp->block_size) * STp->block_size; + DEBC(printk(ST_DEB_MSG "%s: Flushing %d bytes.\n", + tape_name(STp), transfer)); + + memset((STp->buffer)->b_data + offset, 0, transfer - offset); + + memset(cmd, 0, MAX_COMMAND_SIZE); + cmd[0] = WRITE_6; + cmd[1] = 1; + blks = transfer / STp->block_size; + cmd[2] = blks >> 16; + cmd[3] = blks >> 8; + cmd[4] = blks; + + SRpnt = st_do_scsi(NULL, STp, cmd, transfer, DMA_TO_DEVICE, + STp->device->timeout, MAX_WRITE_RETRIES, 1); + if (!SRpnt) + return (STp->buffer)->syscall_result; + + STps = &(STp->ps[STp->partition]); + if ((STp->buffer)->syscall_result != 0) { + struct st_cmdstatus *cmdstatp = &STp->buffer->cmdstat; + + if (cmdstatp->have_sense && !cmdstatp->deferred && + (cmdstatp->flags & SENSE_EOM) && + (cmdstatp->sense_hdr.sense_key == NO_SENSE || + cmdstatp->sense_hdr.sense_key == RECOVERED_ERROR) && + (!cmdstatp->remainder_valid || + cmdstatp->uremainder64 == 0)) { /* All written at EOM early warning */ + STp->dirty = 0; + (STp->buffer)->buffer_bytes = 0; + if (STps->drv_block >= 0) + STps->drv_block += blks; + result = (-ENOSPC); + } else { + printk(KERN_ERR "%s: Error on flush.\n", + tape_name(STp)); + STps->drv_block = (-1); + result = (-EIO); + } + } else { + if (STps->drv_block >= 0) + STps->drv_block += blks; + STp->dirty = 0; + (STp->buffer)->buffer_bytes = 0; + } + scsi_release_request(SRpnt); + SRpnt = NULL; + } + return result; +} + + +/* Flush the tape buffer. The tape will be positioned correctly unless + seek_next is true. */ +static int flush_buffer(struct scsi_tape *STp, int seek_next) +{ + int backspace, result; + struct st_buffer *STbuffer; + struct st_partstat *STps; + + STbuffer = STp->buffer; + + /* + * If there was a bus reset, block further access + * to this device. + */ + if (STp->pos_unknown) + return (-EIO); + + if (STp->ready != ST_READY) + return 0; + STps = &(STp->ps[STp->partition]); + if (STps->rw == ST_WRITING) /* Writing */ + return flush_write_buffer(STp); + + if (STp->block_size == 0) + return 0; + + backspace = ((STp->buffer)->buffer_bytes + + (STp->buffer)->read_pointer) / STp->block_size - + ((STp->buffer)->read_pointer + STp->block_size - 1) / + STp->block_size; + (STp->buffer)->buffer_bytes = 0; + (STp->buffer)->read_pointer = 0; + result = 0; + if (!seek_next) { + if (STps->eof == ST_FM_HIT) { + result = cross_eof(STp, 0); /* Back over the EOF hit */ + if (!result) + STps->eof = ST_NOEOF; + else { + if (STps->drv_file >= 0) + STps->drv_file++; + STps->drv_block = 0; + } + } + if (!result && backspace > 0) + result = st_int_ioctl(STp, MTBSR, backspace); + } else if (STps->eof == ST_FM_HIT) { + if (STps->drv_file >= 0) + STps->drv_file++; + STps->drv_block = 0; + STps->eof = ST_NOEOF; + } + return result; + +} + +/* Set the mode parameters */ +static int set_mode_densblk(struct scsi_tape * STp, struct st_modedef * STm) +{ + int set_it = 0; + unsigned long arg; + char *name = tape_name(STp); + + if (!STp->density_changed && + STm->default_density >= 0 && + STm->default_density != STp->density) { + arg = STm->default_density; + set_it = 1; + } else + arg = STp->density; + arg <<= MT_ST_DENSITY_SHIFT; + if (!STp->blksize_changed && + STm->default_blksize >= 0 && + STm->default_blksize != STp->block_size) { + arg |= STm->default_blksize; + set_it = 1; + } else + arg |= STp->block_size; + if (set_it && + st_int_ioctl(STp, SET_DENS_AND_BLK, arg)) { + printk(KERN_WARNING + "%s: Can't set default block size to %d bytes and density %x.\n", + name, STm->default_blksize, STm->default_density); + if (modes_defined) + return (-EINVAL); + } + return 0; +} + + +/* Lock or unlock the drive door. Don't use when scsi_request allocated. */ +static int do_door_lock(struct scsi_tape * STp, int do_lock) +{ + int retval, cmd; + DEB(char *name = tape_name(STp);) + + + cmd = do_lock ? SCSI_IOCTL_DOORLOCK : SCSI_IOCTL_DOORUNLOCK; + DEBC(printk(ST_DEB_MSG "%s: %socking drive door.\n", name, + do_lock ? "L" : "Unl")); + retval = scsi_ioctl(STp->device, cmd, NULL); + if (!retval) { + STp->door_locked = do_lock ? ST_LOCKED_EXPLICIT : ST_UNLOCKED; + } + else { + STp->door_locked = ST_LOCK_FAILS; + } + return retval; +} + + +/* Set the internal state after reset */ +static void reset_state(struct scsi_tape *STp) +{ + int i; + struct st_partstat *STps; + + STp->pos_unknown = 0; + for (i = 0; i < ST_NBR_PARTITIONS; i++) { + STps = &(STp->ps[i]); + STps->rw = ST_IDLE; + STps->eof = ST_NOEOF; + STps->at_sm = 0; + STps->last_block_valid = 0; + STps->drv_block = -1; + STps->drv_file = -1; + } + if (STp->can_partitions) { + STp->partition = find_partition(STp); + if (STp->partition < 0) + STp->partition = 0; + STp->new_partition = STp->partition; + } +} + +/* Test if the drive is ready. Returns either one of the codes below or a negative system + error code. */ +#define CHKRES_READY 0 +#define CHKRES_NEW_SESSION 1 +#define CHKRES_NOT_READY 2 +#define CHKRES_NO_TAPE 3 + +#define MAX_ATTENTIONS 10 + +static int test_ready(struct scsi_tape *STp, int do_wait) +{ + int attentions, waits, max_wait, scode; + int retval = CHKRES_READY, new_session = 0; + unsigned char cmd[MAX_COMMAND_SIZE]; + struct scsi_request *SRpnt = NULL; + struct st_cmdstatus *cmdstatp = &STp->buffer->cmdstat; + + max_wait = do_wait ? ST_BLOCK_SECONDS : 0; + + for (attentions=waits=0; ; ) { + memset((void *) &cmd[0], 0, MAX_COMMAND_SIZE); + cmd[0] = TEST_UNIT_READY; + SRpnt = st_do_scsi(SRpnt, STp, cmd, 0, DMA_NONE, + STp->long_timeout, MAX_READY_RETRIES, 1); + + if (!SRpnt) { + retval = (STp->buffer)->syscall_result; + break; + } + + if (cmdstatp->have_sense) { + + scode = cmdstatp->sense_hdr.sense_key; + + if (scode == UNIT_ATTENTION) { /* New media? */ + new_session = 1; + if (attentions < MAX_ATTENTIONS) { + attentions++; + continue; + } + else { + retval = (-EIO); + break; + } + } + + if (scode == NOT_READY) { + if (waits < max_wait) { + if (msleep_interruptible(1000)) { + retval = (-EINTR); + break; + } + waits++; + continue; + } + else { + if ((STp->device)->scsi_level >= SCSI_2 && + cmdstatp->sense_hdr.asc == 0x3a) /* Check ASC */ + retval = CHKRES_NO_TAPE; + else + retval = CHKRES_NOT_READY; + break; + } + } + } + + retval = (STp->buffer)->syscall_result; + if (!retval) + retval = new_session ? CHKRES_NEW_SESSION : CHKRES_READY; + break; + } + + if (SRpnt != NULL) + scsi_release_request(SRpnt); + return retval; +} + + +/* See if the drive is ready and gather information about the tape. Return values: + < 0 negative error code from errno.h + 0 drive ready + 1 drive not ready (possibly no tape) +*/ +static int check_tape(struct scsi_tape *STp, struct file *filp) +{ + int i, retval, new_session = 0, do_wait; + unsigned char cmd[MAX_COMMAND_SIZE], saved_cleaning; + unsigned short st_flags = filp->f_flags; + struct scsi_request *SRpnt = NULL; + struct st_modedef *STm; + struct st_partstat *STps; + char *name = tape_name(STp); + struct inode *inode = filp->f_dentry->d_inode; + int mode = TAPE_MODE(inode); + + STp->ready = ST_READY; + + if (mode != STp->current_mode) { + DEBC(printk(ST_DEB_MSG "%s: Mode change from %d to %d.\n", + name, STp->current_mode, mode)); + new_session = 1; + STp->current_mode = mode; + } + STm = &(STp->modes[STp->current_mode]); + + saved_cleaning = STp->cleaning_req; + STp->cleaning_req = 0; + + do_wait = ((filp->f_flags & O_NONBLOCK) == 0); + retval = test_ready(STp, do_wait); + + if (retval < 0) + goto err_out; + + if (retval == CHKRES_NEW_SESSION) { + STp->pos_unknown = 0; + STp->partition = STp->new_partition = 0; + if (STp->can_partitions) + STp->nbr_partitions = 1; /* This guess will be updated later + if necessary */ + for (i = 0; i < ST_NBR_PARTITIONS; i++) { + STps = &(STp->ps[i]); + STps->rw = ST_IDLE; + STps->eof = ST_NOEOF; + STps->at_sm = 0; + STps->last_block_valid = 0; + STps->drv_block = 0; + STps->drv_file = 0; + } + new_session = 1; + } + else { + STp->cleaning_req |= saved_cleaning; + + if (retval == CHKRES_NOT_READY || retval == CHKRES_NO_TAPE) { + if (retval == CHKRES_NO_TAPE) + STp->ready = ST_NO_TAPE; + else + STp->ready = ST_NOT_READY; + + STp->density = 0; /* Clear the erroneous "residue" */ + STp->write_prot = 0; + STp->block_size = 0; + STp->ps[0].drv_file = STp->ps[0].drv_block = (-1); + STp->partition = STp->new_partition = 0; + STp->door_locked = ST_UNLOCKED; + return CHKRES_NOT_READY; + } + } + + if (STp->omit_blklims) + STp->min_block = STp->max_block = (-1); + else { + memset((void *) &cmd[0], 0, MAX_COMMAND_SIZE); + cmd[0] = READ_BLOCK_LIMITS; + + SRpnt = st_do_scsi(SRpnt, STp, cmd, 6, DMA_FROM_DEVICE, + STp->device->timeout, MAX_READY_RETRIES, 1); + if (!SRpnt) { + retval = (STp->buffer)->syscall_result; + goto err_out; + } + + if (!SRpnt->sr_result && !STp->buffer->cmdstat.have_sense) { + STp->max_block = ((STp->buffer)->b_data[1] << 16) | + ((STp->buffer)->b_data[2] << 8) | (STp->buffer)->b_data[3]; + STp->min_block = ((STp->buffer)->b_data[4] << 8) | + (STp->buffer)->b_data[5]; + if ( DEB( debugging || ) !STp->inited) + printk(KERN_WARNING + "%s: Block limits %d - %d bytes.\n", name, + STp->min_block, STp->max_block); + } else { + STp->min_block = STp->max_block = (-1); + DEBC(printk(ST_DEB_MSG "%s: Can't read block limits.\n", + name)); + } + } + + memset((void *) &cmd[0], 0, MAX_COMMAND_SIZE); + cmd[0] = MODE_SENSE; + cmd[4] = 12; + + SRpnt = st_do_scsi(SRpnt, STp, cmd, 12, DMA_FROM_DEVICE, + STp->device->timeout, MAX_READY_RETRIES, 1); + if (!SRpnt) { + retval = (STp->buffer)->syscall_result; + goto err_out; + } + + if ((STp->buffer)->syscall_result != 0) { + DEBC(printk(ST_DEB_MSG "%s: No Mode Sense.\n", name)); + STp->block_size = ST_DEFAULT_BLOCK; /* Educated guess (?) */ + (STp->buffer)->syscall_result = 0; /* Prevent error propagation */ + STp->drv_write_prot = 0; + } else { + DEBC(printk(ST_DEB_MSG + "%s: Mode sense. Length %d, medium %x, WBS %x, BLL %d\n", + name, + (STp->buffer)->b_data[0], (STp->buffer)->b_data[1], + (STp->buffer)->b_data[2], (STp->buffer)->b_data[3])); + + if ((STp->buffer)->b_data[3] >= 8) { + STp->drv_buffer = ((STp->buffer)->b_data[2] >> 4) & 7; + STp->density = (STp->buffer)->b_data[4]; + STp->block_size = (STp->buffer)->b_data[9] * 65536 + + (STp->buffer)->b_data[10] * 256 + (STp->buffer)->b_data[11]; + DEBC(printk(ST_DEB_MSG + "%s: Density %x, tape length: %x, drv buffer: %d\n", + name, STp->density, (STp->buffer)->b_data[5] * 65536 + + (STp->buffer)->b_data[6] * 256 + (STp->buffer)->b_data[7], + STp->drv_buffer)); + } + STp->drv_write_prot = ((STp->buffer)->b_data[2] & 0x80) != 0; + } + scsi_release_request(SRpnt); + SRpnt = NULL; + STp->inited = 1; + + if (STp->block_size > 0) + (STp->buffer)->buffer_blocks = + (STp->buffer)->buffer_size / STp->block_size; + else + (STp->buffer)->buffer_blocks = 1; + (STp->buffer)->buffer_bytes = (STp->buffer)->read_pointer = 0; + + DEBC(printk(ST_DEB_MSG + "%s: Block size: %d, buffer size: %d (%d blocks).\n", name, + STp->block_size, (STp->buffer)->buffer_size, + (STp->buffer)->buffer_blocks)); + + if (STp->drv_write_prot) { + STp->write_prot = 1; + + DEBC(printk(ST_DEB_MSG "%s: Write protected\n", name)); + + if (do_wait && + ((st_flags & O_ACCMODE) == O_WRONLY || + (st_flags & O_ACCMODE) == O_RDWR)) { + retval = (-EROFS); + goto err_out; + } + } + + if (STp->can_partitions && STp->nbr_partitions < 1) { + /* This code is reached when the device is opened for the first time + after the driver has been initialized with tape in the drive and the + partition support has been enabled. */ + DEBC(printk(ST_DEB_MSG + "%s: Updating partition number in status.\n", name)); + if ((STp->partition = find_partition(STp)) < 0) { + retval = STp->partition; + goto err_out; + } + STp->new_partition = STp->partition; + STp->nbr_partitions = 1; /* This guess will be updated when necessary */ + } + + if (new_session) { /* Change the drive parameters for the new mode */ + STp->density_changed = STp->blksize_changed = 0; + STp->compression_changed = 0; + if (!(STm->defaults_for_writes) && + (retval = set_mode_densblk(STp, STm)) < 0) + goto err_out; + + if (STp->default_drvbuffer != 0xff) { + if (st_int_ioctl(STp, MTSETDRVBUFFER, STp->default_drvbuffer)) + printk(KERN_WARNING + "%s: Can't set default drive buffering to %d.\n", + name, STp->default_drvbuffer); + } + } + + return CHKRES_READY; + + err_out: + return retval; +} + + +/* Open the device. Needs to be called with BKL only because of incrementing the SCSI host + module count. */ +static int st_open(struct inode *inode, struct file *filp) +{ + int i, retval = (-EIO); + struct scsi_tape *STp; + struct st_partstat *STps; + int dev = TAPE_NR(inode); + char *name; + + /* + * We really want to do nonseekable_open(inode, filp); here, but some + * versions of tar incorrectly call lseek on tapes and bail out if that + * fails. So we disallow pread() and pwrite(), but permit lseeks. + */ + filp->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE); + + write_lock(&st_dev_arr_lock); + if (dev >= st_dev_max || scsi_tapes == NULL || + ((STp = scsi_tapes[dev]) == NULL)) { + write_unlock(&st_dev_arr_lock); + return (-ENXIO); + } + filp->private_data = STp; + name = tape_name(STp); + + if (STp->in_use) { + write_unlock(&st_dev_arr_lock); + DEB( printk(ST_DEB_MSG "%s: Device already in use.\n", name); ) + return (-EBUSY); + } + + if(scsi_device_get(STp->device)) { + write_unlock(&st_dev_arr_lock); + return (-ENXIO); + } + STp->in_use = 1; + write_unlock(&st_dev_arr_lock); + STp->rew_at_close = STp->autorew_dev = (iminor(inode) & 0x80) == 0; + + if (!scsi_block_when_processing_errors(STp->device)) { + retval = (-ENXIO); + goto err_out; + } + + /* See that we have at least a one page buffer available */ + if (!enlarge_buffer(STp->buffer, PAGE_SIZE, STp->restr_dma)) { + printk(KERN_WARNING "%s: Can't allocate one page tape buffer.\n", + name); + retval = (-EOVERFLOW); + goto err_out; + } + + (STp->buffer)->writing = 0; + (STp->buffer)->syscall_result = 0; + + STp->writ |