patch-2.1.121 linux/drivers/scsi/st.c

Next file: linux/drivers/scsi/st.h
Previous file: linux/drivers/scsi/sr_ioctl.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.120/linux/drivers/scsi/st.c linux/drivers/scsi/st.c
@@ -8,10 +8,10 @@
   order) Klaus Ehrenfried, Wolfgang Denk, Steve Hirsch, Andreas Koppenh"ofer,
   Michael Leodolter, Eyal Lebedinsky, J"org Weule, and Eric Youngdale.
 
-  Copyright 1992 - 1997 Kai Makisara
+  Copyright 1992 - 1998 Kai Makisara
 		 email Kai.Makisara@metla.fi
 
-  Last modified: Wed Nov  5 23:39:52 1997 by makisara@home
+  Last modified: Sun Sep  6 09:34:49 1998 by root@home
   Some small formal changes - aeb, 950809
 */
 
@@ -47,24 +47,27 @@
 #include "scsi.h"
 #include "hosts.h"
 #include <scsi/scsi_ioctl.h>
+
+#define ST_KILOBYTE 1024
+
+#include "st_options.h"
 #include "st.h"
+
 #include "constants.h"
 
 #ifdef MODULE
 MODULE_PARM(buffer_kbs, "i");
 MODULE_PARM(write_threshold_kbs, "i");
 MODULE_PARM(max_buffers, "i");
+MODULE_PARM(max_sg_segs, "i");
 static int buffer_kbs = 0;
 static int write_threshold_kbs = 0;
 static int max_buffers = 0;
+static int max_sg_segs = 0;
 #endif
 
 /* The default definitions have been moved to st_options.h */
 
-#define ST_KILOBYTE 1024
-
-#include "st_options.h"
-
 #define ST_BUFFER_SIZE (ST_BUFFER_BLOCKS * ST_KILOBYTE)
 #define ST_WRITE_THRESHOLD (ST_WRITE_THRESHOLD_BLOCKS * ST_KILOBYTE)
 
@@ -98,6 +101,7 @@
 static int st_buffer_size = ST_BUFFER_SIZE;
 static int st_write_threshold = ST_WRITE_THRESHOLD;
 static int st_max_buffers = ST_MAX_BUFFERS;
+static int st_max_sg_segs = ST_MAX_SG;
 
 static Scsi_Tape * scsi_tapes = NULL;
 
@@ -106,6 +110,8 @@
 static ST_buffer *new_tape_buffer(int, int);
 static int enlarge_buffer(ST_buffer *, int, int);
 static void normalize_buffer(ST_buffer *);
+static int append_to_buffer(const char *, ST_buffer *, int);
+static int from_buffer(ST_buffer *, char *, int);
 
 static int st_init(void);
 static int st_attach(Scsi_Device *);
@@ -142,7 +148,12 @@
   if (!result /* && SCpnt->sense_buffer[0] == 0 */ )
     return 0;
 
-  scode = sense[2] & 0x0f;
+  if (driver_byte(result) & DRIVER_SENSE)
+      scode = sense[2] & 0x0f;
+  else {
+      sense[0] = 0;    /* We don't have sense data if this byte is zero */
+      scode = 0;
+  }
 
 #if DEBUG
   if (debugging) {
@@ -172,7 +183,10 @@
       print_sense("st", SCpnt);
     }
     else
-      printk(KERN_WARNING "st%d: Error %x.\n", dev, result);
+      printk(KERN_WARNING
+	     "st%d: Error %x (sugg. bt 0x%x, driver bt 0x%x, host bt 0x%x).\n",
+	     dev, result, suggestion(result), driver_byte(result),
+	     host_byte(result));
   }
 
   if ((sense[0] & 0x70) == 0x70 &&
@@ -246,39 +260,54 @@
 }
 
 
-/* Do the scsi command */
+/* 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 Scsi_Cmnd *
 st_do_scsi(Scsi_Cmnd *SCpnt, Scsi_Tape *STp, unsigned char *cmd, int bytes,
-	   int timeout, int retries)
+	   int timeout, int retries, int do_wait)
 {
   unsigned long flags;
+  unsigned char *bp;
 
   spin_lock_irqsave(&io_request_lock, flags);
   if (SCpnt == NULL)
     if ((SCpnt = scsi_allocate_device(NULL, STp->device, 1)) == NULL) {
       printk(KERN_ERR "st%d: Can't get SCSI request.\n", TAPE_NR(STp->devt));
+      spin_unlock_irqrestore(&io_request_lock, flags);
       return NULL;
     }
 
   cmd[1] |= (SCpnt->lun << 5) & 0xe0;
   STp->sem = MUTEX_LOCKED;
+  SCpnt->use_sg = (bytes > (STp->buffer)->sg[0].length) ?
+      (STp->buffer)->use_sg : 0;
+  if (SCpnt->use_sg) {
+      bp = (char *)&((STp->buffer)->sg[0]);
+      if ((STp->buffer)->sg_segs < SCpnt->use_sg)
+	  SCpnt->use_sg = (STp->buffer)->sg_segs;
+  }
+  else
+      bp = (STp->buffer)->b_data;
   SCpnt->request.sem = &(STp->sem);
   SCpnt->request.rq_status = RQ_SCSI_BUSY;
   SCpnt->request.rq_dev = STp->devt;
 
-  scsi_do_cmd(SCpnt, (void *)cmd, (STp->buffer)->b_data, bytes,
+  scsi_do_cmd(SCpnt, (void *)cmd, bp, bytes,
 	      st_sleep_done, timeout, retries);
   spin_unlock_irqrestore(&io_request_lock, flags);
 
-  down(SCpnt->request.sem);
+  if (do_wait) {
+      down(SCpnt->request.sem);
 
-  (STp->buffer)->last_result_fatal = st_chk_result(SCpnt);
+      (STp->buffer)->last_result_fatal = st_chk_result(SCpnt);
+  }
 
   return SCpnt;
 }
 
 
-/* Handle the write-behind checking */
+/* Handle the write-behind checking (downs the semaphore) */
 	static void
 write_behind_check(Scsi_Tape *STp)
 {
@@ -300,9 +329,13 @@
   scsi_release_command((STp->buffer)->last_SCpnt);
 
   if (STbuffer->writing < STbuffer->buffer_bytes)
+#if 0
     memcpy(STbuffer->b_data,
 	   STbuffer->b_data + STbuffer->writing,
 	   STbuffer->buffer_bytes - STbuffer->writing);
+#else
+  printk(KERN_WARNING "st: write_behind_check: something left in buffer!\n");
+#endif
   STbuffer->buffer_bytes -= STbuffer->writing;
   STps = &(STp->ps[STp->partition]);
   if (STps->drv_block >= 0) {
@@ -340,7 +373,7 @@
 	   TAPE_NR(STp->devt), forward ? "forward" : "backward");
 #endif
 
-  SCpnt = st_do_scsi(NULL, STp, cmd, 0, STp->timeout, MAX_RETRIES);
+  SCpnt = st_do_scsi(NULL, STp, cmd, 0, STp->timeout, MAX_RETRIES, TRUE);
   if (!SCpnt)
     return (-EBUSY);
 
@@ -402,7 +435,8 @@
     cmd[3] = blks >> 8;
     cmd[4] = blks;
 
-    SCpnt = st_do_scsi(NULL, STp, cmd, transfer, STp->timeout, MAX_WRITE_RETRIES);
+    SCpnt = st_do_scsi(NULL, STp, cmd, transfer, STp->timeout, MAX_WRITE_RETRIES,
+		       TRUE);
     if (!SCpnt)
       return (-EBUSY);
 
@@ -551,10 +585,9 @@
     if (dev >= st_template.dev_max || !scsi_tapes[dev].device)
       return (-ENXIO);
 
-    if( !scsi_block_when_processing_errors(scsi_tapes[dev].device) )
-      {
+    if( !scsi_block_when_processing_errors(scsi_tapes[dev].device) ) {
         return -ENXIO;
-      }
+    }
 
     STp = &(scsi_tapes[dev]);
     if (STp->in_use) {
@@ -576,7 +609,7 @@
     }
     STm = &(STp->modes[STp->current_mode]);
 
-    /* Allocate buffer for this user */
+    /* Allocate a buffer for this user */
     need_dma_buffer = STp->restr_dma;
     for (i=0; i < st_nbr_buffers; i++)
       if (!st_buffers[i]->in_use &&
@@ -594,6 +627,16 @@
     (STp->buffer)->in_use = 1;
     (STp->buffer)->writing = 0;
     (STp->buffer)->last_result_fatal = 0;
+    (STp->buffer)->use_sg = STp->device->host->sg_tablesize;
+
+    /* Compute the usable buffer size for this SCSI adapter */
+    if (!(STp->buffer)->use_sg)
+	(STp->buffer)->buffer_size = (STp->buffer)->sg[0].length;
+    else {
+	for (i=0, (STp->buffer)->buffer_size = 0; i < (STp->buffer)->use_sg &&
+		 i < (STp->buffer)->sg_segs; i++)
+	    (STp->buffer)->buffer_size += (STp->buffer)->sg[i].length;
+    }
 
     flags = filp->f_flags;
     STp->write_prot = ((flags & O_ACCMODE) == O_RDONLY);
@@ -617,7 +660,8 @@
     memset ((void *) &cmd[0], 0, 10);
     cmd[0] = TEST_UNIT_READY;
 
-    SCpnt = st_do_scsi(NULL, STp, cmd, 0, STp->long_timeout, MAX_READY_RETRIES);
+    SCpnt = st_do_scsi(NULL, STp, cmd, 0, STp->long_timeout, MAX_READY_RETRIES,
+		       TRUE);
     if (!SCpnt) {
 	if (scsi_tapes[dev].device->host->hostt->module)
 	    __MOD_DEC_USE_COUNT(scsi_tapes[dev].device->host->hostt->module);
@@ -631,7 +675,8 @@
       memset ((void *) &cmd[0], 0, 10);
       cmd[0] = TEST_UNIT_READY;
 
-      SCpnt = st_do_scsi(SCpnt, STp, cmd, 0, STp->long_timeout, MAX_READY_RETRIES);
+      SCpnt = st_do_scsi(SCpnt, STp, cmd, 0, STp->long_timeout, MAX_READY_RETRIES,
+			 TRUE);
 
       (STp->device)->was_reset = 0;
       STp->partition = STp->new_partition = 0;
@@ -675,7 +720,7 @@
       memset ((void *) &cmd[0], 0, 10);
       cmd[0] = READ_BLOCK_LIMITS;
 
-      SCpnt = st_do_scsi(SCpnt, STp, cmd, 6, STp->timeout, MAX_READY_RETRIES);
+      SCpnt = st_do_scsi(SCpnt, STp, cmd, 6, STp->timeout, MAX_READY_RETRIES, TRUE);
 
       if (!SCpnt->result && !SCpnt->sense_buffer[0]) {
 	STp->max_block = ((STp->buffer)->b_data[1] << 16) |
@@ -701,7 +746,7 @@
     cmd[0] = MODE_SENSE;
     cmd[4] = 12;
 
-    SCpnt = st_do_scsi(SCpnt, STp, cmd, 12, STp->timeout, MAX_READY_RETRIES);
+    SCpnt = st_do_scsi(SCpnt, STp, cmd, 12, STp->timeout, MAX_READY_RETRIES, TRUE);
 
     if ((STp->buffer)->last_result_fatal != 0) {
 #if DEBUG
@@ -754,7 +799,7 @@
     SCpnt = NULL;
 
     if (STp->block_size > 0)
-      (STp->buffer)->buffer_blocks = st_buffer_size / STp->block_size;
+      (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;
@@ -830,9 +875,9 @@
 }
 
 
-/* Close the device*/
+/* Flush the tape buffer before close */
 	static int
-scsi_tape_close(struct inode * inode, struct file * filp)
+scsi_tape_flush(struct file * filp)
 {
     int result = 0, result2;
     static unsigned char cmd[10];
@@ -841,6 +886,7 @@
     ST_mode * STm;
     ST_partstat * STps;
 
+    struct inode *inode = filp->f_dentry->d_inode;
     kdev_t devt = inode->i_rdev;
     int dev;
 
@@ -877,7 +923,8 @@
 	cmd[0] = WRITE_FILEMARKS;
 	cmd[4] = 1 + STp->two_fm;
 
-	SCpnt = st_do_scsi(NULL, STp, cmd, 0, STp->timeout, MAX_WRITE_RETRIES);
+	SCpnt = st_do_scsi(NULL, STp, cmd, 0, STp->timeout, MAX_WRITE_RETRIES,
+			   TRUE);
 	if (!SCpnt)
 	  goto out;
 
@@ -947,6 +994,23 @@
 	  result = result2;
     }
 
+    return result;
+}
+
+
+/* Close the device and release it */
+	static int
+scsi_tape_close(struct inode * inode, struct file * filp)
+{
+    int result = 0;
+    Scsi_Tape * STp;
+
+    kdev_t devt = inode->i_rdev;
+    int dev;
+
+    dev = TAPE_NR(devt);
+    STp = &(scsi_tapes[dev]);
+
     if (STp->door_locked == ST_LOCKED_AUTO)
       st_int_ioctl(inode, MTUNLOCK, 0);
 
@@ -981,7 +1045,6 @@
     ST_mode * STm;
     ST_partstat * STps;
     int dev = TAPE_NR(inode->i_rdev);
-    unsigned long flags;
 
     STp = &(scsi_tapes[dev]);
 
@@ -991,10 +1054,9 @@
      * may try and take the device offline, in which case all further
      * access to the device is prohibited.
      */
-    if( !scsi_block_when_processing_errors(STp->device) )
-      {
+    if( !scsi_block_when_processing_errors(STp->device) ) {
         return -ENXIO;
-      }
+    }
     
     if (ppos != &filp->f_pos) {
       /* "A request was outside the capabilities of the device." */
@@ -1027,6 +1089,10 @@
     }
 #endif
 
+    /* Write must be integral number of blocks */
+    if (STp->block_size != 0 && (count % STp->block_size) != 0)
+	return (-EIO);
+
     if (STp->can_partitions &&
 	(retval = update_partition(inode)) < 0)
       return retval;
@@ -1092,8 +1158,10 @@
 	return (-EFAULT);
 
     if (!STm->do_buffer_writes) {
+#if 0
       if (STp->block_size != 0 && (count % STp->block_size) != 0)
 	return (-EIO);   /* Write must be integral number of blocks */
+#endif
       write_threshold = 1;
     }
     else
@@ -1110,9 +1178,9 @@
     STps->rw = ST_WRITING;
 
     b_point = buf;
-    while((STp->block_size == 0 && !STm->do_async_writes && count > 0) ||
-	  (STp->block_size != 0 &&
-	   (STp->buffer)->buffer_bytes + count > write_threshold))
+    while ((STp->block_size == 0 && !STm->do_async_writes && count > 0) ||
+	   (STp->block_size != 0 &&
+	    (STp->buffer)->buffer_bytes + count > write_threshold))
     {
       doing_write = 1;
       if (STp->block_size == 0)
@@ -1124,21 +1192,19 @@
 	  do_count = count;
       }
 
-      i = copy_from_user((STp->buffer)->b_data +
-			 (STp->buffer)->buffer_bytes, b_point, do_count);
+      i = append_to_buffer(b_point, STp->buffer, do_count);
       if (i) {
-	  if (SCpnt != NULL)
-	    {
+	  if (SCpnt != NULL) {
 	      scsi_release_command(SCpnt);
 	      SCpnt = NULL;
-	    }
-	  return (-EFAULT);
+	  }
+	  return i;
       }
 
       if (STp->block_size == 0)
 	blks = transfer = do_count;
       else {
-	blks = ((STp->buffer)->buffer_bytes + do_count) /
+	blks = (STp->buffer)->buffer_bytes /
 	  STp->block_size;
 	transfer = blks * STp->block_size;
       }
@@ -1146,7 +1212,8 @@
       cmd[3] = blks >> 8;
       cmd[4] = blks;
 
-      SCpnt = st_do_scsi(SCpnt, STp, cmd, transfer, STp->timeout, MAX_WRITE_RETRIES);
+      SCpnt = st_do_scsi(SCpnt, STp, cmd, transfer, STp->timeout,
+			 MAX_WRITE_RETRIES, TRUE);
       if (!SCpnt)
 	return (-EBUSY);
 
@@ -1223,18 +1290,15 @@
     }
     if (count != 0) {
       STp->dirty = 1;
-      i = copy_from_user((STp->buffer)->b_data +
-			 (STp->buffer)->buffer_bytes, b_point, count);
+      i = append_to_buffer(b_point, STp->buffer, count);
       if (i) {
-	  if (SCpnt != NULL)
-	    {
+	  if (SCpnt != NULL) {
 	      scsi_release_command(SCpnt);
 	      SCpnt = NULL;
-	    }
-	  return (-EFAULT);
+	  }
+	  return i;
       }
       filp->f_pos += count;
-      (STp->buffer)->buffer_bytes += count;
       count = 0;
     }
 
@@ -1249,11 +1313,6 @@
 	  (STp->buffer)->buffer_bytes >= STp->block_size) ||
 	 STp->block_size == 0) ) {
       /* Schedule an asynchronous write */
-      if (!SCpnt) {
-	SCpnt = scsi_allocate_device(NULL, STp->device, 1);
-	if (!SCpnt)
-	  return (-EBUSY);
-      }
       if (STp->block_size == 0)
 	(STp->buffer)->writing = (STp->buffer)->buffer_bytes;
       else
@@ -1269,26 +1328,19 @@
       cmd[2] = blks >> 16;
       cmd[3] = blks >> 8;
       cmd[4] = blks;
-      STp->sem = MUTEX_LOCKED;
-      SCpnt->request.sem = &(STp->sem);
-      SCpnt->request.rq_status = RQ_SCSI_BUSY;
-      SCpnt->request.rq_dev = STp->devt;
 #if DEBUG
       STp->write_pending = 1;
 #endif
 
-      spin_lock_irqsave(&io_request_lock, flags);
-      scsi_do_cmd (SCpnt,
-		   (void *) cmd, (STp->buffer)->b_data,
-		   (STp->buffer)->writing,
-		   st_sleep_done, STp->timeout, MAX_WRITE_RETRIES);
-      spin_unlock_irqrestore(&io_request_lock, flags);
+      SCpnt = st_do_scsi(SCpnt, STp, cmd, (STp->buffer)->writing, STp->timeout,
+			 MAX_WRITE_RETRIES, FALSE);
+      if (SCpnt == NULL)
+	  return (-EIO);
     }
-    else if (SCpnt != NULL)
-      {
+    else if (SCpnt != NULL) {
 	scsi_release_command(SCpnt);
 	SCpnt = NULL;
-      }
+    }
     STps->at_sm &= (total == 0);
     if (total > 0)
 	STps->eof = ST_NOEOF;
@@ -1343,7 +1395,7 @@
     cmd[4] = blks;
 
     SCpnt = *aSCpnt;
-    SCpnt = st_do_scsi(SCpnt, STp, cmd, bytes, STp->timeout, MAX_RETRIES);
+    SCpnt = st_do_scsi(SCpnt, STp, cmd, bytes, STp->timeout, MAX_RETRIES, TRUE);
     *aSCpnt = SCpnt;
     if (!SCpnt)
 	return (-EBUSY);
@@ -1351,7 +1403,6 @@
     (STp->buffer)->read_pointer = 0;
     STps->at_sm = 0;
 
-
     /* Something to check */
     if ((STp->buffer)->last_result_fatal) {
 	retval = 1;
@@ -1507,10 +1558,9 @@
      * may try and take the device offline, in which case all further
      * access to the device is prohibited.
      */
-    if( !scsi_block_when_processing_errors(STp->device) )
-      {
+    if( !scsi_block_when_processing_errors(STp->device) ) {
         return -ENXIO;
-      }
+    }
     
     if (ppos != &filp->f_pos) {
       /* "A request was outside the capabilities of the device." */
@@ -1590,10 +1640,9 @@
       if ((STp->buffer)->buffer_bytes == 0) {
 	  special = read_tape(inode, count - total, &SCpnt);
 	  if (special < 0) { /* No need to continue read */
-	      if (SCpnt != NULL)
-		{
+	      if (SCpnt != NULL) {
 		  scsi_release_command(SCpnt);
-		}
+	      }
 	      return special;
 	  }
       }
@@ -1607,21 +1656,17 @@
 #endif
 	transfer = (STp->buffer)->buffer_bytes < count - total ?
 	  (STp->buffer)->buffer_bytes : count - total;
-	i = copy_to_user(buf, (STp->buffer)->b_data +
-			 (STp->buffer)->read_pointer, transfer);
+	i = from_buffer(STp->buffer, buf, transfer);
 	if (i) {
-	    if (SCpnt != NULL)
-	      {
+	    if (SCpnt != NULL) {
 		scsi_release_command(SCpnt);
 		SCpnt = NULL;
-	      }
-	    return (-EFAULT);
+	    }
+	    return i;
 	}
 	filp->f_pos += transfer;
 	buf += transfer;
 	total += transfer;
-	(STp->buffer)->buffer_bytes -= transfer;
-	(STp->buffer)->read_pointer += transfer;
       }
 
       if (STp->block_size == 0)
@@ -1629,11 +1674,10 @@
 
     } /* for (total = 0, special = 0; total < count && !special; ) */
 
-    if (SCpnt != NULL)
-      {
+    if (SCpnt != NULL) {
 	scsi_release_command(SCpnt);
 	SCpnt = NULL;
-      }
+    }
 
     /* Change the eof state if no data from tape or buffer */
     if (total == 0) {
@@ -1867,7 +1911,7 @@
   cmd[2] = COMPRESSION_PAGE;
   cmd[4] = COMPRESSION_PAGE_LENGTH + MODE_HEADER_LENGTH;
 
-  SCpnt = st_do_scsi(SCpnt, STp, cmd, cmd[4], STp->timeout, 0);
+  SCpnt = st_do_scsi(SCpnt, STp, cmd, cmd[4], STp->timeout, 0, TRUE);
   if (SCpnt == NULL)
     return (-EBUSY);
   dev = TAPE_NR(SCpnt->request.rq_dev);
@@ -1912,7 +1956,7 @@
   (STp->buffer)->b_data[0] = 0;  /* Reserved data length */
   (STp->buffer)->b_data[1] = 0;  /* Reserved media type byte */
   (STp->buffer)->b_data[MODE_HEADER_LENGTH] &= 0x3f;
-  SCpnt = st_do_scsi(SCpnt, STp, cmd, cmd[4], STp->timeout, 0);
+  SCpnt = st_do_scsi(SCpnt, STp, cmd, cmd[4], STp->timeout, 0, TRUE);
 
   if ((STp->buffer)->last_result_fatal != 0) {
 #if DEBUG
@@ -2298,7 +2342,7 @@
        return (-ENOSYS);
      }
 
-   SCpnt = st_do_scsi(NULL, STp, cmd, datalen, timeout, MAX_RETRIES);
+   SCpnt = st_do_scsi(NULL, STp, cmd, datalen, timeout, MAX_RETRIES, TRUE);
    if (!SCpnt)
      return (-EBUSY);
 
@@ -2472,7 +2516,7 @@
       if (!logical && !STp->scsi2_logical)
 	scmd[1] = 1;
     }
-    SCpnt = st_do_scsi(NULL, STp, scmd, 20, STp->timeout, MAX_READY_RETRIES);
+    SCpnt = st_do_scsi(NULL, STp, scmd, 20, STp->timeout, MAX_READY_RETRIES, TRUE);
     if (!SCpnt)
       return (-EBUSY);
 
@@ -2596,7 +2640,7 @@
     timeout = STp->timeout;
 #endif
 
-    SCpnt = st_do_scsi(NULL, STp, scmd, 20, timeout, MAX_READY_RETRIES);
+    SCpnt = st_do_scsi(NULL, STp, scmd, 20, timeout, MAX_READY_RETRIES, TRUE);
     if (!SCpnt)
       return (-EBUSY);
 
@@ -2692,7 +2736,7 @@
     cmd[2] = PART_PAGE;
     cmd[4] = 200;
 
-    SCpnt = st_do_scsi(SCpnt, STp, cmd, 200, STp->timeout, MAX_READY_RETRIES);
+    SCpnt = st_do_scsi(SCpnt, STp, cmd, 200, STp->timeout, MAX_READY_RETRIES, TRUE);
     if (SCpnt == NULL)
       return (-EBUSY);
     scsi_release_command(SCpnt);
@@ -2767,7 +2811,8 @@
     cmd[1] = 0x10;
     cmd[4] = length + MODE_HEADER_LENGTH;
 
-    SCpnt = st_do_scsi(SCpnt, STp, cmd, cmd[4], STp->long_timeout, MAX_READY_RETRIES);
+    SCpnt = st_do_scsi(SCpnt, STp, cmd, cmd[4], STp->long_timeout,
+		       MAX_READY_RETRIES, TRUE);
     if (SCpnt == NULL)
       return (-EBUSY);
     scsi_release_command(SCpnt);
@@ -2815,10 +2860,9 @@
      * may try and take the device offline, in which case all further
      * access to the device is prohibited.
      */
-    if( !scsi_block_when_processing_errors(STp->device) )
-      {
+    if( !scsi_block_when_processing_errors(STp->device) ) {
         return -ENXIO;
-      }
+    }
 
    cmd_type = _IOC_TYPE(cmd_in);
    cmd_nr   = _IOC_NR(cmd_in);
@@ -3034,29 +3078,69 @@
 	static ST_buffer *
 new_tape_buffer( int from_initialization, int need_dma )
 {
-  int priority, a_size;
+  int i, priority, b_size, got = 0, segs = 0;
   ST_buffer *tb;
 
   if (st_nbr_buffers >= st_template.dev_max)
     return NULL;  /* Should never happen */
 
-  if (from_initialization) {
+  if (from_initialization)
     priority = GFP_ATOMIC;
-    a_size = st_buffer_size;
-  }
-  else {
+  else
     priority = GFP_KERNEL;
-    for (a_size = PAGE_SIZE; a_size < st_buffer_size; a_size <<= 1)
-      ; /* Make sure we allocate efficiently */
-  }
-  tb = (ST_buffer *)scsi_init_malloc(sizeof(ST_buffer), priority);
+
+  i = sizeof(ST_buffer) + (st_max_sg_segs - 1) * sizeof(struct scatterlist);
+  tb = (ST_buffer *)scsi_init_malloc(i, priority);
   if (tb) {
+    tb->this_size = i;
     if (need_dma)
       priority |= GFP_DMA;
-    tb->b_data = (unsigned char *)scsi_init_malloc(a_size, priority);
-    if (!tb->b_data) {
-      scsi_init_free((char *)tb, sizeof(ST_buffer));
-      tb = NULL;
+
+    /* Try to allocate the first segment up to ST_FIRST_ORDER and the
+       others big enough to reach the goal */
+    for (b_size = PAGE_SIZE << ST_FIRST_ORDER;
+	 b_size / 2 >= st_buffer_size && b_size > PAGE_SIZE; )
+	b_size /= 2;
+    for ( ; b_size >= PAGE_SIZE; b_size /= 2) {
+	tb->sg[0].address =
+	    (unsigned char *)scsi_init_malloc(b_size, priority);
+	if (tb->sg[0].address != NULL) {
+	    tb->sg[0].alt_address = NULL;
+	    tb->sg[0].length = b_size;
+	    break;
+	}
+    }
+    if (tb->sg[segs].address == NULL) {
+	scsi_init_free((char *)tb, tb->this_size);
+	tb = NULL;
+    }
+    else {  /* Got something, continue */
+
+	for (b_size = PAGE_SIZE;
+	     st_buffer_size > tb->sg[0].length + (ST_FIRST_SG - 1) * b_size; )
+	    b_size *= 2;
+
+	for (segs=1, got=tb->sg[0].length;
+	     got < st_buffer_size && segs < ST_FIRST_SG; ) {
+	    tb->sg[segs].address =
+		(unsigned char *)scsi_init_malloc(b_size, priority);
+	    if (tb->sg[segs].address == NULL) {
+		if (st_buffer_size - got <=
+		    (ST_FIRST_SG - segs) * b_size / 2) {
+		    b_size /= 2; /* Large enough for the rest of the buffers */
+		    continue;
+		}
+		for (i=0; i < segs - 1; i++)
+		    scsi_init_free(tb->sg[i].address, tb->sg[i].length);
+		scsi_init_free((char *)tb, tb->this_size);
+		tb = NULL;
+		break;
+	    }
+	    tb->sg[segs].alt_address = NULL;
+	    tb->sg[segs].length = b_size;
+	    got += b_size;
+	    segs++;
+	}
     }
   }
   if (!tb) {
@@ -3064,18 +3148,25 @@
 	   st_nbr_buffers);
     return NULL;
   }
+  tb->sg_segs = tb->orig_sg_segs = segs;
+  tb->b_data = tb->sg[0].address;
+
 #if DEBUG
-  if (debugging)
+  if (debugging) {
+    printk(ST_DEB_MSG
+    "st: Allocated tape buffer %d (%d bytes, %d segments, dma: %d, a: %p).\n",
+	   st_nbr_buffers, got, tb->sg_segs, need_dma, tb->b_data);
     printk(ST_DEB_MSG
-	   "st: Allocated tape buffer %d (%d bytes, dma: %d, a: %p).\n",
-	   st_nbr_buffers, a_size, need_dma, tb->b_data);
+	   "st: segment sizes: first %d, last %d bytes.\n",
+	   tb->sg[0].length, tb->sg[segs-1].length);
+  }
 #endif
   tb->in_use = 0;
   tb->dma = need_dma;
-  tb->buffer_size = a_size;
+  tb->buffer_size = got;
   tb->writing = 0;
-  tb->orig_b_data = NULL;
   st_buffers[st_nbr_buffers++] = tb;
+
   return tb;
 }
 
@@ -3084,31 +3175,51 @@
 	static int
 enlarge_buffer(ST_buffer *STbuffer, int new_size, int need_dma)
 {
-  int a_size, priority;
-  unsigned char *tbd;
+  int segs, nbr, max_segs, b_size, priority, got;
 
   normalize_buffer(STbuffer);
 
-  for (a_size = PAGE_SIZE; a_size < new_size; a_size <<= 1)
-    ;  /* Make sure that we allocate efficiently */
+  max_segs = STbuffer->use_sg;
+  if (max_segs > st_max_sg_segs)
+      max_segs = st_max_sg_segs;
+  nbr = max_segs - STbuffer->sg_segs;
+  if (nbr <= 0)
+      return FALSE;
 
   priority = GFP_KERNEL;
   if (need_dma)
     priority |= GFP_DMA;
-  tbd = (unsigned char *)scsi_init_malloc(a_size, priority);
-  if (!tbd)
-    return FALSE;
+  for (b_size = PAGE_SIZE; b_size * nbr < new_size - STbuffer->buffer_size; )
+      b_size *= 2;
+
+  for (segs=STbuffer->sg_segs, got=STbuffer->buffer_size;
+       segs < max_segs && got < new_size; ) {
+      STbuffer->sg[segs].address =
+	  (unsigned char *)scsi_init_malloc(b_size, priority);
+      if (STbuffer->sg[segs].address == NULL) {
+	  if (new_size - got <= (max_segs - segs) * b_size / 2) {
+	      b_size /= 2;  /* Large enough for the rest of the buffers */
+	      continue;
+	  }
+	  printk(KERN_NOTICE "st: failed to enlarge buffer to %d bytes.\n",
+		 new_size);
+	  normalize_buffer(STbuffer);
+	  return FALSE;
+      }
+      STbuffer->sg[segs].alt_address = NULL;
+      STbuffer->sg[segs].length = b_size;
+      STbuffer->sg_segs += 1;
+      got += b_size;
+      STbuffer->buffer_size = got;
+      segs++;
+  }
 #if DEBUG
   if (debugging)
-    printk(ST_DEB_MSG
-	   "st: Buffer at %p enlarged to %d bytes (dma: %d, a: %p).\n",
-	   STbuffer->b_data, a_size, need_dma, tbd);
+      printk(ST_DEB_MSG
+	     "st: Succeeded to enlarge buffer to %d bytes (segs %d->%d, %d).\n",
+	     got, STbuffer->orig_sg_segs, STbuffer->sg_segs, b_size);
 #endif
 
-  STbuffer->orig_b_data = STbuffer->b_data;
-  STbuffer->orig_size = STbuffer->buffer_size;
-  STbuffer->b_data = tbd;
-  STbuffer->buffer_size = a_size;
   return TRUE;
 }
 
@@ -3117,19 +3228,87 @@
 	static void
 normalize_buffer(ST_buffer *STbuffer)
 {
-  if (STbuffer->orig_b_data == NULL)
-    return;
-
-  scsi_init_free(STbuffer->b_data, STbuffer->buffer_size);
-  STbuffer->b_data = STbuffer->orig_b_data;
-  STbuffer->orig_b_data = NULL;
-  STbuffer->buffer_size = STbuffer->orig_size;
+  int i;
 
+  for (i=STbuffer->orig_sg_segs; i < STbuffer->sg_segs; i++) {
+      scsi_init_free(STbuffer->sg[i].address, STbuffer->sg[i].length);
+      STbuffer->buffer_size -= STbuffer->sg[i].length;
+  }
 #if DEBUG
-  if (debugging)
-    printk(ST_DEB_MSG "st: Buffer at %p normalized to %d bytes.\n",
-	   STbuffer->b_data, STbuffer->buffer_size);
+  if (debugging && STbuffer->orig_sg_segs < STbuffer->sg_segs)
+      printk(ST_DEB_MSG "st: Buffer at %p normalized to %d bytes (segs %d).\n",
+	     STbuffer->b_data, STbuffer->buffer_size, STbuffer->sg_segs);
 #endif
+  STbuffer->sg_segs = STbuffer->orig_sg_segs;
+}
+
+
+/* Move data from the user buffer to the tape buffer. Returns zero (success) or
+   negative error code. */
+	static int
+append_to_buffer(const char *ubp, ST_buffer *st_bp, int do_count)
+{
+    int i, cnt, res, offset;
+
+    for (i=0, offset=st_bp->buffer_bytes;
+	 i < st_bp->sg_segs && offset >= st_bp->sg[i].length; i++)
+	offset -= st_bp->sg[i].length;
+    if (i == st_bp->sg_segs) {  /* Should never happen */
+	printk(KERN_WARNING "st: append_to_buffer offset overflow.\n");
+	return (-EIO);
+    }
+    for ( ; i < st_bp->sg_segs && do_count > 0; i++) {
+	cnt = st_bp->sg[i].length - offset < do_count ?
+	    st_bp->sg[i].length - offset : do_count;
+	res = copy_from_user(st_bp->sg[i].address + offset, ubp, cnt);
+	if (res)
+	    return (-EFAULT);
+	do_count -= cnt;
+	st_bp->buffer_bytes += cnt;
+	ubp += cnt;
+	offset = 0;
+    }
+    if (do_count) {  /* Should never happen */
+	printk(KERN_WARNING "st: append_to_buffer overflow (left %d).\n",
+	       do_count);
+	return (-EIO);
+    }
+    return 0;
+}
+
+
+/* Move data from the tape buffer to the user buffer. Returns zero (success) or
+   negative error code. */
+	static int
+from_buffer(ST_buffer *st_bp, char *ubp, int do_count)
+{
+    int i, cnt, res, offset;
+
+    for (i=0, offset=st_bp->read_pointer;
+	 i < st_bp->sg_segs && offset >= st_bp->sg[i].length; i++)
+	offset -= st_bp->sg[i].length;
+    if (i == st_bp->sg_segs) {  /* Should never happen */
+	printk(KERN_WARNING "st: from_buffer offset overflow.\n");
+	return (-EIO);
+    }
+    for ( ; i < st_bp->sg_segs && do_count > 0; i++) {
+	cnt = st_bp->sg[i].length - offset < do_count ?
+	    st_bp->sg[i].length - offset : do_count;
+	res = copy_to_user(ubp, st_bp->sg[i].address + offset, cnt);
+	if (res)
+	    return (-EFAULT);
+	do_count -= cnt;
+	st_bp->buffer_bytes -= cnt;
+	st_bp->read_pointer += cnt;
+	ubp += cnt;
+	offset = 0;
+    }
+    if (do_count) {  /* Should never happen */
+	printk(KERN_WARNING "st: from_buffer overflow (left %d).\n",
+	       do_count);
+	return (-EIO);
+    }
+    return 0;
 }
 
 
@@ -3162,7 +3341,7 @@
    st_ioctl,        /* ioctl */
    NULL,            /* mmap */
    scsi_tape_open,  /* open */
-   NULL,	    /* flush */
+   scsi_tape_flush, /* flush */
    scsi_tape_close, /* release */
    NULL		    /* fsync */
 };
@@ -3173,13 +3352,13 @@
    ST_partstat * STps;
    int i;
 
-   if(SDp->type != TYPE_TAPE) return 1;
+   if (SDp->type != TYPE_TAPE)
+       return 1;
 
-   if(st_template.nr_dev >= st_template.dev_max)
-     {
-     	SDp->attached--;
-     	return 1;
-     }
+   if (st_template.nr_dev >= st_template.dev_max) {
+       SDp->attached--;
+       return 1;
+   }
 
    for(tpnt = scsi_tapes, i=0; i<st_template.dev_max; i++, tpnt++)
      if(!tpnt->device) break;
@@ -3250,7 +3429,7 @@
 {
   if(SDp->type != TYPE_TAPE) return 0;
 
-  printk(KERN_INFO
+  printk(KERN_WARNING
 	 "Detected scsi tape st%d at scsi%d, channel %d, id %d, lun %d\n",
 	 st_template.dev_noticed++,
 	 SDp->host->host_no, SDp->channel, SDp->id, SDp->lun);
@@ -3376,11 +3555,6 @@
 int init_module(void) {
   int result;
 
-  st_template.module = &__this_module;
-  result = scsi_register_module(MODULE_SCSI_DEV, &st_template);
-  if (result)
-      return result;
-
   if (buffer_kbs > 0)
       st_buffer_size = buffer_kbs * ST_KILOBYTE;
   if (write_threshold_kbs > 0)
@@ -3389,15 +3563,22 @@
       st_write_threshold = st_buffer_size;
   if (max_buffers > 0)
       st_max_buffers = max_buffers;
-printk(KERN_INFO "st: bufsize %d, wrt %d, max buffers %d.\n",
-st_buffer_size, st_write_threshold, st_max_buffers);
+  if (max_sg_segs >= ST_FIRST_SG)
+      st_max_sg_segs = max_sg_segs;
+  printk(KERN_INFO "st: bufsize %d, wrt %d, max buffers %d, s/g segs %d.\n",
+	 st_buffer_size, st_write_threshold, st_max_buffers, st_max_sg_segs);
+
+  st_template.module = &__this_module;
+  result = scsi_register_module(MODULE_SCSI_DEV, &st_template);
+  if (result)
+      return result;
 
   return 0;
 }
 
 void cleanup_module( void)
 {
-  int i;
+  int i, j;
 
   scsi_unregister_module(MODULE_SCSI_DEV, &st_template);
   unregister_chrdev(SCSI_TAPE_MAJOR, "st");
@@ -3409,9 +3590,10 @@
     if (st_buffers != NULL) {
       for (i=0; i < st_nbr_buffers; i++)
 	if (st_buffers[i] != NULL) {
-	  scsi_init_free((char *) st_buffers[i]->b_data,
-			 st_buffers[i]->buffer_size);
-	  scsi_init_free((char *) st_buffers[i], sizeof(ST_buffer));
+	  for (j=0; j < st_buffers[i]->sg_segs; j++)
+	      scsi_init_free((char *) st_buffers[i]->sg[j].address,
+			     st_buffers[i]->sg[j].length);
+	  scsi_init_free((char *) st_buffers[i], st_buffers[i]->this_size);
 	}
 
       scsi_init_free((char *) st_buffers,

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov