/*
  SCSI Tape Driver for Linux

  Version 0.02 for Linux 0.98.4 and Eric Youngdale's new scsi driver

  History:
  Rewritten from Dwayne Forsyth's SCSI tape driver by Kai Makisara.
  Contribution and ideas from several people including Eric Youngdale and
  Wolfgang Denk.

  Features:
  - support for different block sizes and internal buffering
  - support for fixed and variable block size (within buffer limit;
    blocksize set to zero)
  - *nix-style ioctl with codes from mtio.h from the QIC-02 driver by
    Hennus Bergman (command MTSETBLK added)
  - character device
  - rewind and non-rewind devices
  - capability to handle several tape drives simultaneously
  - one buffer if one drive, two buffers if more than one drive (limits the
    number of simultaneously open drives to two)
  - write behind
  - seek and tell (Tandberg compatible and SCSI-2)

  Devices:
  Autorewind devices have minor numbers equal to the tape numbers (0 > ).
  Nonrewind device has the minor number equal to tape number + 128.

  Problems:
  The end of media detection works correctly in writing only if the drive
  writes the buffer contents after the early-warning mark. If you want to
  be sure that EOM is reported correctly, you should uncomment the line
  defining ST_NO_DELAYED_WRITES. Note that when delayed writes are disabled
  each write byte count must be an integral number of blocks.

  Copyright 1992, 1993 Kai Makisara
		 email makisara@vtinsx.ins.vtt.fi or Kai.Makisara@vtt.fi

  Last modified: Thu Nov 25 21:49:02 1993 by root@kai.home
*/

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mtio.h>
#include <linux/ioctl.h>
#include <linux/fcntl.h>
#include <asm/segment.h>
#include <asm/system.h>

#define MAJOR_NR SCSI_TAPE_MAJOR
#include "../block/blk.h"
#include "scsi.h"
#include "scsi_ioctl.h"
#include "st.h"
#include "constants.h"

/* Uncomment the following if you want the rewind, etc. commands return
   before command completion. */
/* #define ST_NOWAIT */

/* Uncomment the following if you want the tape to be positioned correctly
   within file after close (the tape is positioned correctly with respect
   to the filemarks even wihout ST_IN_FILE_POS defined */
/* #define ST_IN_FILE_POS */

/* Uncomment the following if you want recovered write errors to be
   fatal. */
/* #define ST_RECOVERED_WRITE_FATAL */

/* Uncomment the following if you want all data from a write command to
   be written to tape before the command returns. Disables write-behind. */
/* #define ST_NO_DELAYED_WRITES */

/* Number of ST_BLOCK_SIZE blocks in the buffers */
#define ST_BUFFER_BLOCKS 64
/* Write-behind can be disabled by setting ST_WRITE_THRESHOLD_BLOCKS equal
   to or larger than ST_BUFFER_BLOCKS */
#define ST_WRITE_THRESHOLD_BLOCKS 60
#define ST_BLOCK_SIZE 512
#define ST_BUFFER_SIZE (ST_BUFFER_BLOCKS * ST_BLOCK_SIZE)
#define ST_WRITE_THRESHOLD (ST_WRITE_THRESHOLD_BLOCKS * ST_BLOCK_SIZE)

#ifdef ST_NO_DELAYED_WRITES
#undef ST_WRITE_THRESHOLD_BLOCKS
#define ST_WRITE_THRESHOLD_BLOCKS ST_BUFFER_BLOCKS
#endif

/* The buffer size should fit into the 24 bits reserved for length in the
   6-byte SCSI read and write commands. */
#if ST_BUFFER_SIZE >= (2 << 24 - 1)
#error "Buffer size should not exceed (2 << 24 - 1) bytes!"
#endif

/* #define DEBUG */

#define MAX_RETRIES 0
#define MAX_READY_RETRIES 5
#define NO_TAPE  NOT_READY

#define ST_TIMEOUT 9000
#define ST_LONG_TIMEOUT 200000

static int st_nbr_buffers;
static ST_buffer *st_buffers[2];

static Scsi_Tape * scsi_tapes;
int NR_ST=0;
int MAX_ST=0;

static int st_int_ioctl(struct inode * inode,struct file * file,
	     unsigned int cmd_in, unsigned long arg);




/* Convert the result to success code */
	static int
st_chk_result(Scsi_Cmnd * SCpnt)
{
  int dev = SCpnt->request.dev;
  int result = SCpnt->result;
  unsigned char * sense = SCpnt->sense_buffer;
  char *stp;

  if (!result && SCpnt->sense_buffer[0] == 0)
    return 0;
#ifdef DEBUG
  printk("st%d: Error: %x\n", dev, result);
  print_sense("st", SCpnt);
#endif
/*  if ((sense[0] & 0x70) == 0x70 &&
       ((sense[2] & 0x80) ))
    return 0; */
  if ((sense[0] & 0x70) == 0x70 &&
      sense[2] == RECOVERED_ERROR
#ifdef ST_RECOVERED_WRITE_FATAL
      && SCpnt->cmnd[0] != WRITE_6
      && SCpnt->cmnd[0] != WRITE_FILEMARKS
#endif
      ) {
    scsi_tapes[dev].recover_count++;
    if (SCpnt->cmnd[0] == READ_6)
      stp = "read";
    else if (SCpnt->cmnd[0] == WRITE_6)
      stp = "write";
    else
      stp = "ioctl";
    printk("st%d: Recovered %s error (%d).\n", dev, stp,
	   scsi_tapes[dev].recover_count);
    return 0;
  }
  return (-EIO);
}


/* Wakeup from interrupt */
	static void
st_sleep_done (Scsi_Cmnd * SCpnt)
{
  int st_nbr, remainder;
  Scsi_Tape * STp;

  if ((st_nbr = SCpnt->request.dev) < NR_ST && st_nbr >= 0) {
    STp = &(scsi_tapes[st_nbr]);
    if ((STp->buffer)->writing &&
	(SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	(SCpnt->sense_buffer[2] & 0x40)) {
      /* EOM at write-behind, has all been written? */
      if ((SCpnt->sense_buffer[0] & 0x80) != 0)
	remainder = (SCpnt->sense_buffer[3] << 24) |
	      (SCpnt->sense_buffer[4] << 16) |
		(SCpnt->sense_buffer[5] << 8) | SCpnt->sense_buffer[6];
      else
	remainder = 0;
      if ((SCpnt->sense_buffer[2] & 0x0f) == VOLUME_OVERFLOW ||
	  remainder > 0)
	(STp->buffer)->last_result = SCpnt->result; /* Error */
      else
	(STp->buffer)->last_result = INT_MAX; /* OK */
    }
    else
      (STp->buffer)->last_result = SCpnt->result;
    (STp->buffer)->last_result_fatal = st_chk_result(SCpnt);
    if ((STp->buffer)->writing)
      SCpnt->request.dev = -1;
    else
      SCpnt->request.dev = 0xffff;
    if ((STp->buffer)->writing <= 0)
      wake_up( &(STp->waiting) );
  }
#ifdef DEBUG
  else
    printk("st?: Illegal interrupt device %x\n", st_nbr);
#endif
}


#if ST_WRITE_THRESHOLD_BLOCKS < ST_BUFFER_BLOCKS
/* Handle the write-behind checking */
	static void
write_behind_check(int dev)
{
  Scsi_Tape * STp;
  ST_buffer * STbuffer;

  STp = &(scsi_tapes[dev]);
  STbuffer = STp->buffer;

  cli();
  if (STbuffer->last_result < 0) {
    STbuffer->writing = (- STbuffer->writing);
    sleep_on( &(STp->waiting) );
    STbuffer->writing = (- STbuffer->writing);
  }
  sti();

  if (STbuffer->writing < STbuffer->buffer_bytes)
    memcpy(STbuffer->b_data,
	   STbuffer->b_data + STbuffer->writing,
	   STbuffer->buffer_bytes - STbuffer->writing);
  STbuffer->buffer_bytes -= STbuffer->writing;
  STbuffer->writing = 0;

  return;
}
#endif


/* Flush the write buffer (never need to write if variable blocksize). */
	static int
flush_write_buffer(int dev)
{
  int offset, transfer, blks;
  int result;
  unsigned char cmd[10];
  Scsi_Cmnd *SCpnt;
  Scsi_Tape *STp = &(scsi_tapes[dev]);

#if ST_WRITE_THRESHOLD_BLOCKS < ST_BUFFER_BLOCKS
  if ((STp->buffer)->writing) {
    write_behind_check(dev);
    if ((STp->buffer)->last_result_fatal) {
#ifdef DEBUG
      printk("st%d: Async write error %x.\n", dev,
	     (STp->buffer)->last_result);
#endif
      if ((STp->buffer)->last_result == INT_MAX)
	return (-ENOSPC);
      return (-EIO);
    }
  }
#endif

  result = 0;
  if (STp->dirty == 1) {
    SCpnt = allocate_device(NULL, (STp->device)->index, 1);

    offset = (STp->buffer)->buffer_bytes;
    transfer = ((offset + STp->block_size - 1) /
		STp->block_size) * STp->block_size;
#ifdef DEBUG
    printk("st%d: Flushing %d bytes.\n", dev, transfer);
#endif
    memset((STp->buffer)->b_data + offset, 0, transfer - offset);

    SCpnt->sense_buffer[0] = 0;
    memset(cmd, 0, 10);
    cmd[0] = WRITE_6;
    cmd[1] = 1;
    blks = transfer / STp->block_size;
    cmd[2] = blks >> 16;
    cmd[3] = blks >> 8;
    cmd[4] = blks;
    SCpnt->request.dev = dev;
    scsi_do_cmd (SCpnt,
		 (void *) cmd, (STp->buffer)->b_data, transfer,
		 st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

    if ((STp->buffer)->last_result_fatal != 0) {
      printk("st%d: Error on flush.\n", dev);
      if ((SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	  (SCpnt->sense_buffer[2] & 0x40) &&
	  (SCpnt->sense_buffer[2] & 0x0f) != VOLUME_OVERFLOW) {
	STp->dirty = 0;
	(STp->buffer)->buffer_bytes = 0;
	result = (-ENOSPC);
      }
      else
	result = (-EIO);
    }
    else {
      STp->dirty = 0;
      (STp->buffer)->buffer_bytes = 0;
    }
    SCpnt->request.dev = -1;  /* Mark as not busy */
  }
  return result;
}


/* Flush the tape buffer. The tape will be positioned correctly unless
   seek_next is true. */
	static int
flush_buffer(struct inode * inode, struct file * filp, int seek_next)
{
  int dev;
  int backspace, result;
  Scsi_Tape * STp;
  ST_buffer * STbuffer;

  dev = MINOR(inode->i_rdev) & 127;
  STp = &(scsi_tapes[dev]);
  STbuffer = STp->buffer;

  if (STp->rw == ST_WRITING)  /* Writing */
    return flush_write_buffer(dev);

  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 ((STp->eof == ST_FM) && !STp->eof_hit) {
      result = st_int_ioctl(inode, filp, MTBSF, 1); /* Back over the EOF hit */
      if (!result) {
	STp->eof = ST_NOEOF;
	STp->eof_hit = 0;
      }
    }
    if (!result && backspace > 0)
      result = st_int_ioctl(inode, filp, MTBSR, backspace);
  }
  return result;

}


/* Open the device */
	static int
scsi_tape_open(struct inode * inode, struct file * filp)
{
    int dev;
    unsigned short flags;
    int i;
    unsigned char cmd[10];
    Scsi_Cmnd * SCpnt;
    Scsi_Tape * STp;

    dev = MINOR(inode->i_rdev) & 127;
    if (dev >= NR_ST)
      return (-ENXIO);
    STp = &(scsi_tapes[dev]);
    if (STp->in_use) {
      printk("st%d: Device already in use.\n", dev);
      return (-EBUSY);
    }

    /* Allocate buffer for this user */
    for (i=0; i < st_nbr_buffers; i++)
      if (!st_buffers[i]->in_use)
	break;
    if (i >= st_nbr_buffers) {
      printk("st%d: No free buffers.\n", dev);
      return (-EBUSY);
    }
    STp->buffer = st_buffers[i];
    (STp->buffer)->in_use = 1;
    (STp->buffer)->writing = 0;
    STp->in_use = 1;

    flags = filp->f_flags;
    STp->write_prot = ((flags & O_ACCMODE) == O_RDONLY);

    STp->dirty = 0;
    STp->rw = ST_IDLE;
    STp->eof = ST_NOEOF;
    STp->eof_hit = 0;
    STp->recover_count = 0;

    SCpnt = allocate_device(NULL, (STp->device)->index, 1);
    if (!SCpnt) {
      printk("st%d: Tape request not allocated", dev);
      return (-EBUSY);
    }

    SCpnt->sense_buffer[0]=0;
    memset ((void *) &cmd[0], 0, 10);
    cmd[0] = TEST_UNIT_READY;
    SCpnt->request.dev = dev;
    scsi_do_cmd(SCpnt,
                (void *) cmd, (void *) (STp->buffer)->b_data,
                ST_BLOCK_SIZE, st_sleep_done, ST_LONG_TIMEOUT,
		MAX_READY_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

    if ((SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	(SCpnt->sense_buffer[2] & 0x0f) == UNIT_ATTENTION) { /* New media? */
      SCpnt->sense_buffer[0]=0;
      memset ((void *) &cmd[0], 0, 10);
      cmd[0] = TEST_UNIT_READY;
      SCpnt->request.dev = dev;
      scsi_do_cmd(SCpnt,
		  (void *) cmd, (void *) (STp->buffer)->b_data,
		  ST_BLOCK_SIZE, st_sleep_done, ST_LONG_TIMEOUT,
		  MAX_READY_RETRIES);

      if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );
    }

    if ((STp->buffer)->last_result_fatal != 0) {
      if ((SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	  (SCpnt->sense_buffer[2] & 0x0f) == NO_TAPE)
	printk("st%d: No tape.\n", dev);
      else
	printk("st%d: Error %x.\n", dev, SCpnt->result);
      (STp->buffer)->in_use = 0;
      STp->in_use = 0;
      SCpnt->request.dev = -1;  /* Mark as not busy */
      return (-EIO);
    }

    SCpnt->sense_buffer[0]=0;
    memset ((void *) &cmd[0], 0, 10);
    cmd[0] = READ_BLOCK_LIMITS;
    SCpnt->request.dev = dev;
    scsi_do_cmd(SCpnt,
                (void *) cmd, (void *) (STp->buffer)->b_data,
                ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

    if (!SCpnt->result && !SCpnt->sense_buffer[0]) {
      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];
#ifdef DEBUG
      printk("st%d: Block limits %d - %d bytes.\n", dev, STp->min_block,
	     STp->max_block);
#endif
    }
    else {
      STp->min_block = STp->max_block = (-1);
#ifdef DEBUG
      printk("st%d: Can't read block limits.\n", dev);
#endif
    }

    SCpnt->sense_buffer[0]=0;
    memset ((void *) &cmd[0], 0, 10);
    cmd[0] = MODE_SENSE;
    cmd[4] = 12;
    SCpnt->request.dev = dev;
    scsi_do_cmd(SCpnt,
                (void *) cmd, (void *) (STp->buffer)->b_data,
                ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

    if ((STp->buffer)->last_result_fatal != 0) {
#ifdef DEBUG
      printk("st%d: No Mode Sense.\n", dev);
#endif
      (STp->buffer)->b_data[2] =
      (STp->buffer)->b_data[3] = 0;
    }
    SCpnt->request.dev = -1;  /* Mark as not busy */

#ifdef DEBUG
    printk("st%d: Mode sense. Length %d, medium %x, WBS %x, BLL %d\n", dev,
	   (STp->buffer)->b_data[0], (STp->buffer)->b_data[1],
	   (STp->buffer)->b_data[2], (STp->buffer)->b_data[3]);
#endif

    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];
#ifdef DEBUG
      printk(
	"st%d: Density %x, tape length: %x, blocksize: %d, drv buffer: %d\n",
	     dev, STp->density, (STp->buffer)->b_data[5] * 65536 +
	     (STp->buffer)->b_data[6] * 256 + (STp->buffer)->b_data[7],
	     STp->block_size, STp->drv_buffer);
#endif
      if (STp->block_size > ST_BUFFER_SIZE) {
	printk("st%d: Blocksize %d too large for buffer.\n", dev,
	       STp->block_size);
	(STp->buffer)->in_use = 0;
	STp->in_use = 0;
	return (-EIO);
      }

    }
    else
      STp->block_size = ST_BLOCK_SIZE;

    if (STp->block_size > 0) {
      (STp->buffer)->buffer_blocks = ST_BUFFER_SIZE / STp->block_size;
      (STp->buffer)->buffer_size =
	(STp->buffer)->buffer_blocks * STp->block_size;
    }
    else {
      (STp->buffer)->buffer_blocks = 1;
      (STp->buffer)->buffer_size = ST_BUFFER_SIZE;
    }
    (STp->buffer)->buffer_bytes = (STp->buffer)->read_pointer = 0;

#ifdef DEBUG
    printk("st%d: Block size: %d, buffer size: %d (%d blocks).\n", dev,
	   STp->block_size, (STp->buffer)->buffer_size,
	   (STp->buffer)->buffer_blocks);
#endif

    if ((STp->buffer)->b_data[2] & 0x80) {
      STp->write_prot = 1;
#ifdef DEBUG
      printk( "st%d: Write protected\n", dev);
#endif
    }

    return 0;
}


/* Close the device*/
	static void
scsi_tape_close(struct inode * inode, struct file * filp)
{
    int dev;
    int result;
    int rewind;
    static unsigned char cmd[10];
    Scsi_Cmnd * SCpnt;
    Scsi_Tape * STp;
   
    dev = MINOR(inode->i_rdev);
    rewind = (dev & 0x80) == 0;
    dev = dev & 127;
    STp = &(scsi_tapes[dev]);

    if ( STp->rw == ST_WRITING) {

      result = flush_write_buffer(dev);

#ifdef DEBUG
      printk("st%d: File length %d bytes.\n", dev, filp->f_pos);
#endif

      if (result == 0 || result == (-ENOSPC)) {
	SCpnt = allocate_device(NULL, (STp->device)->index, 1);

	SCpnt->sense_buffer[0] = 0;
	memset(cmd, 0, 10);
	cmd[0] = WRITE_FILEMARKS;
	cmd[4] = 1;
	SCpnt->request.dev = dev;
	scsi_do_cmd( SCpnt,
		    (void *) cmd, (void *) (STp->buffer)->b_data,
		    ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

	if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

	if ((STp->buffer)->last_result_fatal != 0)
	  printk("st%d: Error on write filemark.\n", dev);

	SCpnt->request.dev = -1;  /* Mark as not busy */
      }

#ifdef DEBUG
      printk("st%d: Buffer flushed, EOF written\n", dev);
#endif
    }
    else if (!rewind) {
#ifndef ST_IN_FILE_POS
      if ((STp->eof == ST_FM) && !STp->eof_hit)
	st_int_ioctl(inode, filp, MTBSF, 1); /* Back over the EOF hit */
#else
      flush_buffer(inode, filp, 0);
#endif
    }

    if (rewind)
      st_int_ioctl(inode, filp, MTREW, 1);

    (STp->buffer)->in_use = 0;
    STp->in_use = 0;

    return;
}


/* Write command */
	static int
st_write(struct inode * inode, struct file * filp, char * buf, int count)
{
    int dev;
    int total, do_count, blks, retval, transfer;
    int write_threshold;
    static unsigned char cmd[10];
    char *b_point;
    Scsi_Cmnd * SCpnt;
    Scsi_Tape * STp;

    dev = MINOR(inode->i_rdev) & 127;
    STp = &(scsi_tapes[dev]);
#ifdef DEBUG
    if (!STp->in_use) {
      printk("st%d: Incorrect device.\n", dev);
      return (-EIO);
    }
#endif

    if (STp->write_prot)
      return (-EACCES);

    if (STp->block_size == 0 && count > ST_BUFFER_SIZE)
      return (-EOVERFLOW);

    if (STp->rw == ST_READING) {
      retval = flush_buffer(inode, filp, 0);
      if (retval)
	return retval;
      STp->rw = ST_WRITING;
    }

#if ST_WRITE_THRESHOLD_BLOCKS < ST_BUFFER_BLOCKS
    if ((STp->buffer)->writing) {
      write_behind_check(dev);
      if ((STp->buffer)->last_result_fatal) {
#ifdef DEBUG
	printk("st%d: Async write error %x.\n", dev,
	       (STp->buffer)->last_result);
#endif
	if ((STp->buffer)->last_result == INT_MAX) {
	  retval = (-ENOSPC);  /* All has been written */
	  STp->eof = ST_EOM_OK;
	}
	else
	  retval = (-EIO);
	return retval;
      }
    }
#endif

    if (STp->eof == ST_EOM_OK)
      return (-ENOSPC);
    else if (STp->eof == ST_EOM_ERROR)
      return (-EIO);

#ifdef ST_NO_DELAYED_WRITES
    if (STp->block_size != 0 && (count % STp->block_size) != 0)
      return (-EIO);   /* Write must be integral number of blocks */
    write_threshold = 1;
#else
    write_threshold = (STp->buffer)->buffer_size;
#endif

    SCpnt = allocate_device(NULL, (STp->device)->index, 1);

    total = count;

    memset(cmd, 0, 10);
    cmd[0] = WRITE_6;
    cmd[1] = (STp->block_size != 0);

    STp->rw = ST_WRITING;

    b_point = buf;
    while(
#if ST_WRITE_THRESHOLD_BLOCKS  < ST_BUFFER_BLOCKS
	  STp->block_size != 0 &&
	  ((STp->buffer)->buffer_bytes + count) >
	  write_threshold)
#else
	  (STp->block_size == 0 && count > 0) ||
	  ((STp->buffer)->buffer_bytes + count) >=
	  write_threshold)
#endif
    {
      if (STp->block_size == 0)
	do_count = count;
      else {
	do_count = (STp->buffer)->buffer_size - (STp->buffer)->buffer_bytes;
	if (do_count > count)
	  do_count = count;
      }
      memcpy_fromfs((STp->buffer)->b_data +
		    (STp->buffer)->buffer_bytes, b_point, do_count);

      if (STp->block_size == 0)
        blks = do_count;
      else
	blks = ((STp->buffer)->buffer_bytes + do_count) /
	  STp->block_size;
      cmd[2] = blks >> 16;
      cmd[3] = blks >> 8;
      cmd[4] = blks;
      SCpnt->sense_buffer[0] = 0;
      SCpnt->request.dev = dev;
      scsi_do_cmd (SCpnt,
		   (void *) cmd, (STp->buffer)->b_data,
		   (STp->buffer)->buffer_size,
		   st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

      if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

      if ((STp->buffer)->last_result_fatal != 0) {
#ifdef DEBUG
	printk("st%d: Error on write:\n", dev);
#endif
	if ((SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	    (SCpnt->sense_buffer[2] & 0x40)) {
	  if (STp->block_size != 0 && (SCpnt->sense_buffer[0] & 0x80) != 0)
	    transfer = (SCpnt->sense_buffer[3] << 24) |
	      (SCpnt->sense_buffer[4] << 16) |
		(SCpnt->sense_buffer[5] << 8) | SCpnt->sense_buffer[6];
	  else if (STp->block_size == 0 &&
		   (SCpnt->sense_buffer[2] & 0x0f) == VOLUME_OVERFLOW)
	    transfer = do_count;
	  else
	    transfer = 0;
	  if (STp->block_size != 0)
	    transfer *= STp->block_size;
	  if (transfer <= do_count) {
	    filp->f_pos += do_count - transfer;
	    count -= do_count - transfer;
	    STp->eof = ST_EOM_OK;
	    retval = (-ENOSPC); /* EOM within current request */
#ifdef DEBUG
	    printk("st%d: EOM with %d bytes unwritten.\n",
		   dev, transfer);
#endif
	  }
	  else {
	    STp->eof = ST_EOM_ERROR;
	    retval = (-EIO); /* EOM for old data */
#ifdef DEBUG
	    printk("st%d: EOM with lost data.\n", dev);
#endif
	  }
	}
	else
	  retval = (-EIO);

	SCpnt->request.dev = -1;  /* Mark as not busy */
	(STp->buffer)->buffer_bytes = 0;
	STp->dirty = 0;
	if (count < total)
	  return total - count;
	else
	  return retval;
      }
      filp->f_pos += do_count;
      b_point += do_count;
      count -= do_count;
      (STp->buffer)->buffer_bytes = 0;
      STp->dirty = 0;
    }
    if (count != 0) {
      STp->dirty = 1;
      memcpy_fromfs((STp->buffer)->b_data +
		    (STp->buffer)->buffer_bytes,b_point,count);
      filp->f_pos += count;
      (STp->buffer)->buffer_bytes += count;
      count = 0;
    }

    if ((STp->buffer)->last_result_fatal != 0) {
      SCpnt->request.dev = -1;
      return (STp->buffer)->last_result_fatal;
    }

#if ST_WRITE_THRESHOLD_BLOCKS < ST_BUFFER_BLOCKS
    if ((STp->buffer)->buffer_bytes >= ST_WRITE_THRESHOLD ||
	STp->block_size == 0) {
      /* Schedule an asynchronous write */
      if (STp->block_size == 0)
	(STp->buffer)->writing = (STp->buffer)->buffer_bytes;
      else
	(STp->buffer)->writing = ((STp->buffer)->buffer_bytes /
	  STp->block_size) * STp->block_size;
      STp->dirty = 0;

      if (STp->block_size == 0)
	blks = (STp->buffer)->writing;
      else
	blks = (STp->buffer)->writing / STp->block_size;
      cmd[2] = blks >> 16;
      cmd[3] = blks >> 8;
      cmd[4] = blks;
      SCpnt->result = (STp->buffer)->last_result = -1;
      SCpnt->sense_buffer[0] = 0;
      SCpnt->request.dev = dev;
      scsi_do_cmd (SCpnt,
		   (void *) cmd, (STp->buffer)->b_data,
		   (STp->buffer)->writing,
		   st_sleep_done, ST_TIMEOUT, MAX_RETRIES);
    }
    else
#endif
      SCpnt->request.dev = -1;  /* Mark as not busy */

    return( total);
}   


/* Read command */
	static int
st_read(struct inode * inode, struct file * filp, char * buf, int count)
{
    int dev;
    int total;
    int transfer, blks, bytes;
    static unsigned char cmd[10];
    Scsi_Cmnd * SCpnt;
    Scsi_Tape * STp;

    dev = MINOR(inode->i_rdev) & 127;
    STp = &(scsi_tapes[dev]);
#ifdef DEBUG
    if (!STp->in_use) {
      printk("st%d: Incorrect device.\n", dev);
      return (-EIO);
    }
#endif

    if (STp->block_size == 0 && count > ST_BUFFER_SIZE)
      return (-EOVERFLOW);

    if (STp->rw == ST_WRITING) {
      transfer = flush_buffer(inode, filp, 0);
      if (transfer)
	return transfer;
      STp->rw = ST_READING;
    }

#ifdef DEBUG
    if (STp->eof != ST_NOEOF)
      printk("st%d: EOF flag up. Bytes %d\n", dev,
	     (STp->buffer)->buffer_bytes);
#endif
    if (((STp->buffer)->buffer_bytes == 0) &&
	STp->eof == ST_EOM_OK)  /* EOM or Blank Check */
      return (-EIO);

    STp->rw = ST_READING;

    SCpnt = allocate_device(NULL, (STp->device)->index, 1);

    for (total = 0; total < count; ) {

      if ((STp->buffer)->buffer_bytes == 0 &&
	  STp->eof == ST_NOEOF) {

	memset(cmd, 0, 10);
	cmd[0] = READ_6;
	cmd[1] = (STp->block_size != 0);
	if (STp->block_size == 0)
	  blks = bytes = count;
	else {
	  blks = (STp->buffer)->buffer_blocks;
	  bytes = blks * STp->block_size;
	}
	cmd[2] = blks >> 16;
	cmd[3] = blks >> 8;
	cmd[4] = blks;

	SCpnt->sense_buffer[0] = 0;
	SCpnt->request.dev = dev;
	scsi_do_cmd (SCpnt,
		     (void *) cmd, (STp->buffer)->b_data,
		     (STp->buffer)->buffer_size,
		     st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

	if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

	(STp->buffer)->read_pointer = 0;
	STp->eof_hit = 0;

	if ((STp->buffer)->last_result_fatal) {
#ifdef DEBUG
	  printk("st%d: Sense: %2x %2x %2x %2x %2x %2x %2x %2x\n", dev,
		 SCpnt->sense_buffer[0], SCpnt->sense_buffer[1],
		 SCpnt->sense_buffer[2], SCpnt->sense_buffer[3],
		 SCpnt->sense_buffer[4], SCpnt->sense_buffer[5],
		 SCpnt->sense_buffer[6], SCpnt->sense_buffer[7]);
#endif
	  if ((SCpnt->sense_buffer[0] & 0x70) == 0x70) { /* extended sense */

	    if ((SCpnt->sense_buffer[2] & 0xe0) != 0) { /* EOF, EOM, or ILI */

	      if ((SCpnt->sense_buffer[0] & 0x80) != 0)
		transfer = (SCpnt->sense_buffer[3] << 24) |
		  (SCpnt->sense_buffer[4] << 16) |
		    (SCpnt->sense_buffer[5] << 8) | SCpnt->sense_buffer[6];
	      else
		transfer = 0;
	      if (STp->block_size == 0 &&
		  (SCpnt->sense_buffer[2] & 0x0f) == MEDIUM_ERROR)
		transfer = bytes;

	      if (SCpnt->sense_buffer[2] & 0x20) {
		if (STp->block_size == 0) {
		  if (transfer <= 0)
		    transfer = 0;
		  (STp->buffer)->buffer_bytes = count - transfer;
		}
		else {
		  printk("st%d: Incorrect block size.\n", dev);
		  SCpnt->request.dev = -1;  /* Mark as not busy */
		  return (-EIO);
		}
	      }
	      else if (SCpnt->sense_buffer[2] & 0x40) {
		STp->eof = ST_EOM_OK;
		if (STp->block_size == 0)
		  (STp->buffer)->buffer_bytes = count - transfer;
		else
		  (STp->buffer)->buffer_bytes =
		    ((STp->buffer)->buffer_blocks - transfer) *
		      STp->block_size;
#ifdef DEBUG
		printk("st%d: EOM detected (%d bytes read).\n", dev,
		       (STp->buffer)->buffer_bytes);
#endif
	      }
	      else if (SCpnt->sense_buffer[2] & 0x80) {
		STp->eof = ST_FM;
		if (STp->block_size == 0)
		  (STp->buffer)->buffer_bytes = 0;
		else
		  (STp->buffer)->buffer_bytes =
		    ((STp->buffer)->buffer_blocks - transfer) *
		      STp->block_size;
#ifdef DEBUG
		printk(
		 "st%d: EOF detected (%d bytes read, transferred %d bytes).\n",
		       dev, (STp->buffer)->buffer_bytes, total);
#endif
	      } /* end of EOF, EOM, ILI test */
	    }
	    else { /* nonzero sense key */
#ifdef DEBUG
	      printk("st%d: Tape error while reading.\n", dev);
#endif
	      SCpnt->request.dev = -1;
	      if (total)
		return total;
	      else
		return -EIO;
	    }
	  }
	  else {
	    transfer = (STp->buffer)->last_result_fatal;
	    SCpnt->request.dev = -1;  /* Mark as not busy */
	    return transfer;
	  }
	}
	else /* Read successful */
	  (STp->buffer)->buffer_bytes = bytes;

      } /* if ((STp->buffer)->buffer_bytes == 0 &&
	   STp->eof == ST_NOEOF) */

      if ((STp->buffer)->buffer_bytes > 0) {
#ifdef DEBUG
	if (STp->eof != ST_NOEOF)
	  printk("st%d: EOF up. Left %d, needed %d.\n", dev,
		 (STp->buffer)->buffer_bytes, count - total);
#endif
	transfer = (STp->buffer)->buffer_bytes < count - total ?
	  (STp->buffer)->buffer_bytes : count - total;
	memcpy_tofs(buf, (STp->buffer)->b_data +
		    (STp->buffer)->read_pointer,transfer);
	filp->f_pos += transfer;
	buf += transfer;
	total += transfer;
	(STp->buffer)->buffer_bytes -= transfer;
	(STp->buffer)->read_pointer += transfer;
      }
      else if (STp->eof != ST_NOEOF) {
	STp->eof_hit = 1;
	SCpnt->request.dev = -1;  /* Mark as not busy */
	if (total == 0 && STp->eof == ST_FM)
	  STp->eof = 0;
	if (total == 0 && STp->eof == ST_EOM_OK)
	  return (-EIO);  /* ST_EOM_ERROR not used in read */
	return total;
      }

      if (STp->block_size == 0)
	count = total;  /* Read only one variable length block */

    } /* for (total = 0; total < count; ) */

    SCpnt->request.dev = -1;  /* Mark as not busy */

    return total;
}


/* Internal ioctl function */
	static int
st_int_ioctl(struct inode * inode,struct file * file,
	     unsigned int cmd_in, unsigned long arg)
{
   int dev = MINOR(inode->i_rdev);
   int timeout = ST_LONG_TIMEOUT;
   long ltmp;
   int ioctl_result;
   unsigned char cmd[10];
   Scsi_Cmnd * SCpnt;
   Scsi_Tape * STp;

   dev = dev & 127;
   STp = &(scsi_tapes[dev]);

   memset(cmd, 0, 10);
   switch (cmd_in) {
     case MTFSF:
     case MTFSFM:
       cmd[0] = SPACE;
       cmd[1] = 0x01; /* Space FileMarks */
       cmd[2] = (arg >> 16);
       cmd[3] = (arg >> 8);
       cmd[4] = arg;
#ifdef DEBUG
       printk("st%d: Spacing tape forward over %d filemarks.\n", dev,
	      cmd[2] * 65536 + cmd[3] * 256 + cmd[4]);
#endif
       break; 
     case MTBSF:
     case MTBSFM:
       cmd[0] = SPACE;
       cmd[1] = 0x01; /* Space FileMarks */
       ltmp = (-arg);
       cmd[2] = (ltmp >> 16);
       cmd[3] = (ltmp >> 8);
       cmd[4] = ltmp;
#ifdef DEBUG
       if (cmd[2] & 0x80)
	 ltmp = 0xff000000;
       ltmp = ltmp | (cmd[2] << 16) | (cmd[3] << 8) | cmd[4];
       printk("st%d: Spacing tape backward over %d filemarks.\n", dev, (-ltmp));
#endif
       break; 
      case MTFSR:
       cmd[0] = SPACE;
       cmd[1] = 0x00; /* Space Blocks */
       cmd[2] = (arg >> 16);
       cmd[3] = (arg >> 8);
       cmd[4] = arg;
#ifdef DEBUG
       printk("st%d: Spacing tape forward %d blocks.\n", dev,
	      cmd[2] * 65536 + cmd[3] * 256 + cmd[4]);
#endif
       break; 
     case MTBSR:
       cmd[0] = SPACE;
       cmd[1] = 0x00; /* Space Blocks */
       ltmp = (-arg);
       cmd[2] = (ltmp >> 16);
       cmd[3] = (ltmp >> 8);
       cmd[4] = ltmp;
#ifdef DEBUG
       if (cmd[2] & 0x80)
	 ltmp = 0xff000000;
       ltmp = ltmp | (cmd[2] << 16) | (cmd[3] << 8) | cmd[4];
       printk("st%d: Spacing tape backward %d blocks.\n", dev, (-ltmp));
#endif
       break; 
     case MTWEOF:
       if (STp->write_prot)
	 return (-EACCES);
       cmd[0] = WRITE_FILEMARKS;
       cmd[2] = (arg >> 16);
       cmd[3] = (arg >> 8);
       cmd[4] = arg;
       timeout = ST_TIMEOUT;
#ifdef DEBUG
       printk("st%d: Writing %d filemarks.\n", dev,
	      cmd[2] * 65536 + cmd[3] * 256 + cmd[4]);
#endif
       break; 
     case MTREW:
       cmd[0] = REZERO_UNIT;
#ifdef ST_NOWAIT
       cmd[1] = 1;  /* Don't wait for completion */
       timeout = ST_TIMEOUT;
#endif
#ifdef DEBUG
       printk("st%d: Rewinding tape.\n", dev);
#endif
       break; 
     case MTOFFL:
       cmd[0] = START_STOP;
#ifdef ST_NOWAIT
       cmd[1] = 1;  /* Don't wait for completion */
       timeout = ST_TIMEOUT;
#endif
#ifdef DEBUG
       printk("st%d: Unloading tape.\n", dev);
#endif
       break; 
     case MTNOP:
#ifdef DEBUG
       printk("st%d: No op on tape.\n", dev);
#endif
       return 0;  /* Should do something ? */
       break;
     case MTRETEN:
       cmd[0] = START_STOP;
#ifdef ST_NOWAIT
       cmd[1] = 1;  /* Don't wait for completion */
       timeout = ST_TIMEOUT;
#endif
       cmd[4] = 3;
#ifdef DEBUG
       printk("st%d: Retensioning tape.\n", dev);
#endif
       break; 
     case MTEOM:
       cmd[0] = SPACE;
       cmd[1] = 3;
#ifdef DEBUG
       printk("st%d: Spacing to end of recorded medium.\n", dev);
#endif
       break; 
     case MTERASE:
       if (STp->write_prot)
	 return (-EACCES);
       cmd[0] = ERASE;
       cmd[1] = 1;  /* To the end of tape */
#ifdef DEBUG
       printk("st%d: Erasing tape.\n", dev);
#endif
       break;
     case MTSEEK:
       if ((STp->device)->scsi_level < SCSI_2) {
	 cmd[0] = QFA_SEEK_BLOCK;
	 cmd[2] = (arg >> 16);
	 cmd[3] = (arg >> 8);
	 cmd[4] = arg;
	 cmd[5] = 0;
       }
       else {
	 cmd[0] = SEEK_10;
	 cmd[1] = 4;
	 cmd[3] = (arg >> 24);
	 cmd[4] = (arg >> 16);
	 cmd[5] = (arg >> 8);
	 cmd[6] = arg;
       }
#ifdef ST_NOWAIT
       cmd[1] |= 1;  /* Don't wait for completion */
       timeout = ST_TIMEOUT;
#endif
#ifdef DEBUG
       printk("st%d: Seeking tape to block %d.\n", dev, arg);
#endif
       break;
     case MTSETBLK:  /* Set block length */
     case MTSETDENSITY: /* Set tape density */
     case MTSETDRVBUFFER: /* Set drive buffering */
       if (STp->dirty || (STp->buffer)->buffer_bytes != 0)
	 return (-EIO);   /* Not allowed if data in buffer */
       if (cmd_in == MTSETBLK &&
	   arg != 0 &&
	   (arg < STp->min_block || arg > STp->max_block ||
	    arg > ST_BUFFER_SIZE)) {
	 printk("st%d: Illegal block size.\n", dev);
	 return (-EINVAL);
       }
       cmd[0] = MODE_SELECT;
       cmd[4] = 12;

       memset((STp->buffer)->b_data, 0, 12);
       if (cmd_in == MTSETDRVBUFFER)
	 (STp->buffer)->b_data[2] = (arg & 7) << 4;
       else
	 (STp->buffer)->b_data[2] = 
	   STp->drv_buffer << 4;
       (STp->buffer)->b_data[3] = 8;     /* block descriptor length */
       if (cmd_in == MTSETDENSITY)
	 (STp->buffer)->b_data[4] = arg;
       else
	 (STp->buffer)->b_data[4] = STp->density;
       if (cmd_in == MTSETBLK)
	 ltmp = arg;
       else
	 ltmp = STp->block_size;
       (STp->buffer)->b_data[9] = (ltmp >> 16);
       (STp->buffer)->b_data[10] = (ltmp >> 8);
       (STp->buffer)->b_data[11] = ltmp;
       timeout = ST_TIMEOUT;
#ifdef DEBUG
       if (cmd_in == MTSETBLK)
	 printk("st%d: Setting block size to %d bytes.\n", dev,
		(STp->buffer)->b_data[9] * 65536 +
		(STp->buffer)->b_data[10] * 256 +
		(STp->buffer)->b_data[11]);
       else if (cmd_in == MTSETDENSITY)
	 printk("st%d: Setting density code to %x.\n", dev,
		(STp->buffer)->b_data[4]);
       else
	 printk("st%d: Setting drive buffer code to %d.\n", dev,
		((STp->buffer)->b_data[2] >> 4) & 7);
#endif
       break;
     default:
       printk("st%d: Unknown st_ioctl command %x.\n", dev, cmd_in);
       return (-ENOSYS);
     }

   SCpnt = allocate_device(NULL, (STp->device)->index, 1);
   SCpnt->sense_buffer[0] = 0;
   SCpnt->request.dev = dev;
   scsi_do_cmd(SCpnt,
	       (void *) cmd, (void *) (STp->buffer)->b_data, ST_BLOCK_SIZE,
	       st_sleep_done, timeout, MAX_RETRIES);

   if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );

   ioctl_result = (STp->buffer)->last_result_fatal;

   SCpnt->request.dev = -1;  /* Mark as not busy */

   if (!ioctl_result) {
     if (cmd_in == MTBSFM)
       ioctl_result = st_int_ioctl(inode, file, MTFSF, 1);
     else if (cmd_in == MTFSFM)
       ioctl_result = st_int_ioctl(inode, file, MTBSF, 1);
     else if (cmd_in == MTSETBLK) {
       STp->block_size = arg;
       if (arg != 0) {
	 (STp->buffer)->buffer_blocks =
	   ST_BUFFER_SIZE / STp->block_size;
	 (STp->buffer)->buffer_size =
	   (STp->buffer)->buffer_blocks * STp->block_size;
       }
       else {
	 (STp->buffer)->buffer_blocks = 1;
	 (STp->buffer)->buffer_size = ST_BUFFER_SIZE;
       }
       (STp->buffer)->buffer_bytes =
	 (STp->buffer)->read_pointer = 0;
     }
     else if (cmd_in == MTSETDRVBUFFER)
       STp->drv_buffer = arg;
     else if (cmd_in == MTSETDENSITY)
       STp->density = arg;
     if (cmd_in == MTEOM || cmd_in == MTWEOF) {
       STp->eof = ST_EOM_OK;
       STp->eof_hit = 0;
     }
     else if (cmd_in != MTSETBLK && cmd_in != MTNOP) {
       STp->eof = ST_NOEOF;
       STp->eof_hit = 0;
     }
   }

   return ioctl_result ;
}



/* The ioctl command */
	static int
st_ioctl(struct inode * inode,struct file * file,
	 unsigned int cmd_in, unsigned long arg)
{
   int dev = MINOR(inode->i_rdev);
   int i, cmd, result;
   struct mtop mtc;
   struct mtpos mt_pos;
   unsigned char scmd[10];
   Scsi_Cmnd *SCpnt;
   Scsi_Tape *STp;

   dev = dev & 127;
   STp = &(scsi_tapes[dev]);
#ifdef DEBUG
   if (!STp->in_use) {
     printk("st%d: Incorrect device.\n", dev);
     return (-EIO);
   }
#endif

   cmd = cmd_in & IOCCMD_MASK;
   if (cmd == (MTIOCTOP & IOCCMD_MASK)) {

     if (((cmd_in & IOCSIZE_MASK) >> IOCSIZE_SHIFT) != sizeof(mtc))
       return (-EINVAL);

     i = verify_area(VERIFY_WRITE, (void *)arg, sizeof(mtc));
     if (i)
        return i;

     memcpy_fromfs((char *) &mtc, (char *)arg, sizeof(struct mtop));

     i = flush_buffer(inode, file, mtc.mt_op == MTSEEK ||
		      mtc.mt_op == MTREW || mtc.mt_op == MTOFFL ||
		      mtc.mt_op == MTRETEN || mtc.mt_op == MTEOM);
     if (i < 0)
       return i;

     return st_int_ioctl(inode, file, mtc.mt_op, mtc.mt_count);
   }
   else if (cmd == (MTIOCGET & IOCCMD_MASK)) {

     if (((cmd_in & IOCSIZE_MASK) >> IOCSIZE_SHIFT) != sizeof(struct mtget))
       return (-EINVAL);
     i = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct mtget));
     if (i)
       return i;

     memcpy_tofs((char *)arg, (char *)(STp->buffer)->mt_status,
		 sizeof(struct mtget));
     return 0;
   }
   else if (cmd == (MTIOCPOS & IOCCMD_MASK)) {
#ifdef DEBUG
     printk("st%d: get tape position.\n", dev);
#endif
     if (((cmd_in & IOCSIZE_MASK) >> IOCSIZE_SHIFT) != sizeof(struct mtpos))
       return (-EINVAL);

     i = flush_buffer(inode, file, 0);
     if (i < 0)
       return i;

     i = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct mtpos));
     if (i)
       return i;

     SCpnt = allocate_device(NULL, (STp->device)->index, 1);

     SCpnt->sense_buffer[0]=0;
     memset (scmd, 0, 10);
     if ((STp->device)->scsi_level < SCSI_2) {
       scmd[0] = QFA_REQUEST_BLOCK;
       scmd[4] = 3;
     }
     else {
       scmd[0] = READ_POSITION;
       scmd[1] = 1;
     }
     SCpnt->request.dev = dev;
     SCpnt->sense_buffer[0] = 0;
     scsi_do_cmd(SCpnt,
		 (void *) scmd, (void *) (STp->buffer)->b_data,
		 ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);

     if (SCpnt->request.dev == dev) sleep_on( &(STp->waiting) );
     
     if ((STp->buffer)->last_result_fatal != 0) {
       mt_pos.mt_blkno = (-1);
#ifdef DEBUG
       printk("st%d: Can't read tape position.\n", dev);
#endif
       result = (-EIO);
     }
     else {
       result = 0;
       if ((STp->device)->scsi_level < SCSI_2)
	 mt_pos.mt_blkno = ((STp->buffer)->b_data[0] << 16) 
	   + ((STp->buffer)->b_data[1] << 8) 
	     + (STp->buffer)->b_data[2];
       else
	 mt_pos.mt_blkno = ((STp->buffer)->b_data[4] << 24)
	   + ((STp->buffer)->b_data[5] << 16) 
	     + ((STp->buffer)->b_data[6] << 8) 
	       + (STp->buffer)->b_data[7];

     }

     SCpnt->request.dev = -1;  /* Mark as not busy */

     memcpy_tofs((char *)arg, (char *) (&mt_pos), sizeof(struct mtpos));
     return result;
   }
   else
     return scsi_ioctl(STp->device, cmd_in, (void *) arg);
}



static struct file_operations st_fops = {
   NULL,            /* lseek - default */
   st_read,         /* read - general block-dev read */
   st_write,        /* write - general block-dev write */
   NULL,            /* readdir - bad */
   NULL,            /* select */
   st_ioctl,        /* ioctl */
   NULL,            /* mmap */
   scsi_tape_open,  /* open */
   scsi_tape_close, /* release */
   NULL		    /* fsync */
};

void st_attach(Scsi_Device * SDp){
  scsi_tapes[NR_ST++].device = SDp;
  if(NR_ST > MAX_ST) panic ("scsi_devices corrupt (st)");
};

unsigned long st_init1(unsigned long mem_start, unsigned long mem_end){
  scsi_tapes = (Scsi_Tape *) mem_start;
  mem_start += MAX_ST * sizeof(Scsi_Tape);
  return mem_start;
};

/* Driver initialization */
unsigned long st_init(unsigned long mem_start, unsigned long mem_end)
{
  int i;

  if (register_chrdev(MAJOR_NR,"st",&st_fops)) {
    printk("Unable to get major %d for SCSI tapes\n",MAJOR_NR);
    return mem_start;
  }
  if (NR_ST == 0) return mem_start;

#ifdef DEBUG
  printk("st: Init tape.\n");
#endif

  for (i=0; i < NR_ST; ++i) {
    scsi_tapes[i].capacity = 0xfffff;
    scsi_tapes[i].dirty = 0;
    scsi_tapes[i].rw = ST_IDLE;
    scsi_tapes[i].eof = ST_NOEOF;
    scsi_tapes[i].waiting = NULL;
    scsi_tapes[i].in_use = 0;
    scsi_tapes[i].drv_buffer = 1;  /* Try buffering if no mode sense */
    scsi_tapes[i].density = 0;
  }


  /* Allocate the buffers */
  if (NR_ST == 1)
    st_nbr_buffers = 1;
  else
    st_nbr_buffers = 2;
  for (i=0; i < st_nbr_buffers; i++) {
    st_buffers[i] = (ST_buffer *) mem_start;
#ifdef DEBUG
    printk("st: Buffer address: %p\n", st_buffers[i]);
#endif
    mem_start += sizeof(ST_buffer) - 1 + ST_BUFFER_BLOCKS * ST_BLOCK_SIZE;
    st_buffers[i]->mt_status = (struct mtget *) mem_start;
    mem_start += sizeof(struct mtget);
    st_buffers[i]->in_use = 0;
    st_buffers[i]->writing = 0;

    /* "generic" status */
    memset((void *) st_buffers[i]->mt_status, 0, sizeof(struct mtget));
    st_buffers[i]->mt_status->mt_type = MT_ISSCSI1;
  }

  return mem_start;
}
