aboutsummaryrefslogtreecommitdiff
path: root/drivers/scsi/st.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /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.c4438
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