/*
  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.

  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 may not work correctly because of the buffering.
  If you want to do multiple tape backups relying on end of tape detection,
  you should disable write behind and in addition to that check that the
  tapes are readable.

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

  Last modified: Sat Jul 10 17:40:05 1993 by root
*/

#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"

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

/* 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 */

/* #define DEBUG */

#define ST_TIMEOUT 9000
#define ST_LONG_TIMEOUT 200000

/* 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)

/* 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

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);



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

  if ((st_nbr = SCpnt->request.dev) < NR_ST && st_nbr >= 0) {
    if (scsi_tapes[st_nbr].buffer->writing &&
	(SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	(SCpnt->sense_buffer[2] & 0x40))
      scsi_tapes[st_nbr].buffer->last_result = INT_MAX;
    else
      scsi_tapes[st_nbr].buffer->last_result = SCpnt->result;
    if (scsi_tapes[st_nbr].buffer->writing)
      SCpnt->request.dev = -1;
    else
      SCpnt->request.dev = 0xffff;
    if (scsi_tapes[st_nbr].buffer->writing <= 0)
      wake_up( &scsi_tapes[st_nbr].waiting );
  }
#ifdef DEBUG
  else
    printk("st?: Illegal interrupt device %x\n", st_nbr);
#endif
}

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

  if (!result)
    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) /* || ((sense[2] & 0x0f) == 8) */ ))
    return 0;
  return (-EIO);
}


#if ST_WRITE_THRESHOLD_BLOCKS < ST_BUFFER_BLOCKS
/* Handle the write-behind checking */
static void write_behind_check(int dev)
{
  cli();
  if (scsi_tapes[dev].buffer->last_result < 0) {
    scsi_tapes[dev].buffer->writing = (- scsi_tapes[dev].buffer->writing);
    sleep_on( &scsi_tapes[dev].waiting );
    scsi_tapes[dev].buffer->writing = (- scsi_tapes[dev].buffer->writing);
  }
  sti();

  if (scsi_tapes[dev].buffer->writing < scsi_tapes[dev].buffer->buffer_bytes)
    memcpy(scsi_tapes[dev].buffer->b_data,
	   scsi_tapes[dev].buffer->b_data + scsi_tapes[dev].buffer->writing,
	   scsi_tapes[dev].buffer->buffer_bytes -
	   scsi_tapes[dev].buffer->writing);
  scsi_tapes[dev].buffer->buffer_bytes -= scsi_tapes[dev].buffer->writing;
  scsi_tapes[dev].buffer->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;

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

  result = 0;
  if (scsi_tapes[dev].dirty==1) {
    SCpnt = allocate_device(NULL, scsi_tapes[dev].device->index, 1);

    offset = scsi_tapes[dev].buffer->buffer_bytes;
    transfer = ((offset + scsi_tapes[dev].block_size - 1) /
		scsi_tapes[dev].block_size) * scsi_tapes[dev].block_size;
#ifdef DEBUG
    printk("st%d: Flushing %d bytes.\n", dev, transfer);
#endif
    memset(scsi_tapes[dev].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 / scsi_tapes[dev].block_size;
    cmd[2] = blks >> 16;
    cmd[3] = blks >> 8;
    cmd[4] = blks;
    SCpnt->request.dev = dev;
    scsi_do_cmd (SCpnt,
		 (void *) cmd, scsi_tapes[dev].buffer->b_data, transfer,
		 st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

    if (SCpnt->result != 0) {
      printk("st%d: Error on flush:\n", dev);
#ifdef DEBUG
      st_chk_result(SCpnt);
#endif
      result = (-EIO);
    }
    else {
      scsi_tapes[dev].dirty = 0;
      scsi_tapes[dev].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;

  dev = MINOR(inode->i_rdev) & 127;

  if (scsi_tapes[dev].rw == 2)  /* Writing */
    return flush_write_buffer(dev);

  if (scsi_tapes[dev].block_size == 0)
    return 0;

  backspace = (scsi_tapes[dev].buffer->buffer_bytes +
    scsi_tapes[dev].buffer->read_pointer) / scsi_tapes[dev].block_size -
      (scsi_tapes[dev].buffer->read_pointer + scsi_tapes[dev].block_size - 1) /
	scsi_tapes[dev].block_size;
  scsi_tapes[dev].buffer->buffer_bytes = 0;
  scsi_tapes[dev].buffer->read_pointer = 0;
  result = 0;
  if (!seek_next && backspace > 0) {
    result = st_int_ioctl(inode, filp, MTBSR, backspace);
    if (!result) {
      scsi_tapes[dev].eof = 0;
      scsi_tapes[dev].eof_hit = 0;
    }
  }
  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;

    dev = MINOR(inode->i_rdev) & 127;
    if (dev >= NR_ST)
      return (-ENODEV);
    if (scsi_tapes[dev].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);
    }
    st_buffers[i]->in_use = 1;
    st_buffers[i]->writing = 0;
    scsi_tapes[dev].buffer = st_buffers[i];
    scsi_tapes[dev].in_use = 1;

    flags = filp->f_flags;
    scsi_tapes[dev].write_prot = ((flags & O_ACCMODE) == O_RDONLY);

    scsi_tapes[dev].dirty = 0;
    scsi_tapes[dev].rw = 0;
    scsi_tapes[dev].eof = 0;
    scsi_tapes[dev].eof_hit = 0;

    SCpnt = allocate_device(NULL, scsi_tapes[dev].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 *) scsi_tapes[dev].buffer->b_data,
                ST_BLOCK_SIZE, st_sleep_done, ST_LONG_TIMEOUT,
		MAX_READY_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

    if ((SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	(SCpnt->sense_buffer[2] & 0x0f) == UNIT_ATTENTION) { /* New media? */
#ifdef DEBUG
      print_sense("st", SCpnt);
#endif
      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 *) scsi_tapes[dev].buffer->b_data,
		  ST_BLOCK_SIZE, st_sleep_done, ST_LONG_TIMEOUT,
		  MAX_READY_RETRIES);

      if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );
    }

    if (SCpnt->result != 0) {
#ifdef DEBUG
      print_sense("st", SCpnt);
#endif
      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);
      scsi_tapes[dev].buffer->in_use = 0;
      scsi_tapes[dev].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 *) scsi_tapes[dev].buffer->b_data,
                ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

    if (!SCpnt->result && !SCpnt->sense_buffer[0]) {
      scsi_tapes[dev].max_block = (scsi_tapes[dev].buffer->b_data[1] << 16) |
	(scsi_tapes[dev].buffer->b_data[2] << 8) | scsi_tapes[dev].buffer->b_data[3];
      scsi_tapes[dev].min_block = (scsi_tapes[dev].buffer->b_data[4] << 8) |
	scsi_tapes[dev].buffer->b_data[5];
#ifdef DEBUG
      printk("st%d: Block limits %d - %d bytes.\n", dev, scsi_tapes[dev].min_block,
	     scsi_tapes[dev].max_block);
#endif
    }
    else {
      scsi_tapes[dev].min_block = scsi_tapes[dev].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 *) scsi_tapes[dev].buffer->b_data,
                ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);

    if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

    i = st_chk_result(SCpnt);
    if (i) {
#ifdef DEBUG
      printk("st%d: No Mode Sense.\n", dev);
#endif
      scsi_tapes[dev].buffer->b_data[2] =
      scsi_tapes[dev].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,
	   scsi_tapes[dev].buffer->b_data[0], scsi_tapes[dev].buffer->b_data[1],
	   scsi_tapes[dev].buffer->b_data[2], scsi_tapes[dev].buffer->b_data[3]);
#endif

    if (scsi_tapes[dev].buffer->b_data[3] >= 8) {
      scsi_tapes[dev].drv_buffer = (scsi_tapes[dev].buffer->b_data[2] >> 4) & 7;
      scsi_tapes[dev].density = scsi_tapes[dev].buffer->b_data[4];
      scsi_tapes[dev].block_size = scsi_tapes[dev].buffer->b_data[9] * 65536 +
	scsi_tapes[dev].buffer->b_data[10] * 256 + scsi_tapes[dev].buffer->b_data[11];
#ifdef DEBUG
      printk("st%d: Density %x, tape length: %x, blocksize: %d, drv buffer: %d\n", dev,
	     scsi_tapes[dev].buffer->b_data[4], scsi_tapes[dev].buffer->b_data[5] *
	     65536 + scsi_tapes[dev].buffer->b_data[6] * 256 +
	     scsi_tapes[dev].buffer->b_data[7], scsi_tapes[dev].buffer->b_data[9] *
	     65536 + scsi_tapes[dev].buffer->b_data[10] * 256 +
	     scsi_tapes[dev].buffer->b_data[11],
	     scsi_tapes[dev].drv_buffer);
#endif
      if (scsi_tapes[dev].block_size > ST_BUFFER_SIZE) {
	printk("st%d: Blocksize %d too large for buffer.\n", dev,
	       scsi_tapes[dev].block_size);
	scsi_tapes[dev].buffer->in_use = 0;
	scsi_tapes[dev].in_use = 0;
	return (-EIO);
      }

    }
    else
      scsi_tapes[dev].block_size = ST_BLOCK_SIZE;

    if (scsi_tapes[dev].block_size > 0) {
      scsi_tapes[dev].buffer->buffer_blocks =
	ST_BUFFER_SIZE / scsi_tapes[dev].block_size;
      scsi_tapes[dev].buffer->buffer_size =
	scsi_tapes[dev].buffer->buffer_blocks * scsi_tapes[dev].block_size;
    }
    else {
      scsi_tapes[dev].buffer->buffer_blocks = 1;
      scsi_tapes[dev].buffer->buffer_size = ST_BUFFER_SIZE;
    }
    scsi_tapes[dev].buffer->buffer_bytes = scsi_tapes[dev].buffer->read_pointer = 0;

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

    if (scsi_tapes[dev].buffer->b_data[2] & 0x80) {
      scsi_tapes[dev].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;
   
    dev = MINOR(inode->i_rdev);
    rewind = (dev & 0x80) == 0;
    dev = dev & 127;

    if ( scsi_tapes[dev].rw == 2) {

      result = flush_write_buffer(dev);

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

      if (!result) {
	SCpnt = allocate_device(NULL, scsi_tapes[dev].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 *) scsi_tapes[dev].buffer->b_data,
		    ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

	if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

	if (SCpnt->result) {
	  printk("st%d: Error on write filemark.\n", dev);
#ifdef DEBUG
	  st_chk_result(SCpnt);
#endif
	}
	SCpnt->request.dev = -1;  /* Mark as not busy */
      }

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

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

    scsi_tapes[dev].buffer->in_use = 0;
    scsi_tapes[dev].in_use = 0;

    return;
}

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

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

    if (scsi_tapes[dev].write_prot)
      return (-EACCES);

    if (scsi_tapes[dev].block_size == 0 && count > ST_BUFFER_SIZE)
      return (-EOVERFLOW);

    if (scsi_tapes[dev].rw == 1) {
      retval = flush_buffer(inode, filp, 0);
      if (retval)
	return retval;
      scsi_tapes[dev].rw = 2;
    }

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

    SCpnt = allocate_device(NULL, scsi_tapes[dev].device->index, 1);

    total = count;

    memset(cmd, 0, 10);
    cmd[0] = WRITE_6;
    cmd[1] = (scsi_tapes[dev].block_size != 0);

    scsi_tapes[dev].rw = 2;

    b_point = buf;
    while(
#if ST_WRITE_THRESHOLD_BLOCKS  < ST_BUFFER_BLOCKS
	  scsi_tapes[dev].block_size != 0 &&
	  (scsi_tapes[dev].buffer->buffer_bytes + count) >
	  scsi_tapes[dev].buffer->buffer_size)
#else
	  (scsi_tapes[dev].block_size == 0 && count > 0) ||
	  (scsi_tapes[dev].buffer->buffer_bytes + count) >=
	  scsi_tapes[dev].buffer->buffer_size)
#endif
    {
      if (scsi_tapes[dev].block_size == 0)
	do_count = count;
      else
	do_count = scsi_tapes[dev].buffer->buffer_size -
	  scsi_tapes[dev].buffer->buffer_bytes;
      memcpy_fromfs(scsi_tapes[dev].buffer->b_data +
		    scsi_tapes[dev].buffer->buffer_bytes, b_point, do_count);

      if (scsi_tapes[dev].block_size == 0)
        blks = do_count;
      else
	blks = scsi_tapes[dev].buffer->buffer_blocks;
      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, scsi_tapes[dev].buffer->b_data,
		   scsi_tapes[dev].buffer->buffer_size,
		   st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

      if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

      if (SCpnt->result || SCpnt->sense_buffer[0] != 0) {
#ifdef DEBUG
	printk("st%d: Error on write:\n", dev);
	st_chk_result(SCpnt);
#endif
	if ((SCpnt->sense_buffer[0] & 0x70) == 0x70 &&
	    (SCpnt->sense_buffer[2] & 0x40))
	  retval = (-ENOSPC); /* EOM */
	else
	  retval = (-EIO);
	SCpnt->request.dev = -1;  /* Mark as not busy */
	if (count < total)
	  return total - count;
	else
	  return retval;
      }
      filp->f_pos += do_count;
      b_point += do_count;
      count -= do_count;
      scsi_tapes[dev].buffer->buffer_bytes = 0;
      scsi_tapes[dev].dirty = 0;
    }
    if (count != 0) {
      scsi_tapes[dev].dirty = 1;
      memcpy_fromfs(scsi_tapes[dev].buffer->b_data +
		    scsi_tapes[dev].buffer->buffer_bytes,b_point,count);
      filp->f_pos += count;
      scsi_tapes[dev].buffer->buffer_bytes += count;
      count = 0;
    }

    do_count = st_chk_result(SCpnt);
    if (do_count) {
      SCpnt->request.dev = -1;
      return do_count;
    }

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

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

    return( total);
}   


/* Read command */
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;

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

    if (scsi_tapes[dev].block_size == 0 && count > ST_BUFFER_SIZE)
      return (-EOVERFLOW);

    if (scsi_tapes[dev].rw == 2) {
      transfer = flush_buffer(inode, filp, 0);
      if (transfer)
	return transfer;
      scsi_tapes[dev].rw = 1;
    }

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

    scsi_tapes[dev].rw = 1;

    SCpnt = allocate_device(NULL, scsi_tapes[dev].device->index, 1);

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

      if (scsi_tapes[dev].buffer->buffer_bytes == 0 &&
	  scsi_tapes[dev].eof == 0) {

	memset(cmd, 0, 10);
	cmd[0] = READ_6;
	cmd[1] = (scsi_tapes[dev].block_size != 0);
	if (scsi_tapes[dev].block_size == 0)
	  blks = bytes = count;
	else {
	  blks = scsi_tapes[dev].buffer->buffer_blocks;
	  bytes = blks * scsi_tapes[dev].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, scsi_tapes[dev].buffer->b_data,
		     scsi_tapes[dev].buffer->buffer_size,
		     st_sleep_done, ST_TIMEOUT, MAX_RETRIES);

	if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

	scsi_tapes[dev].buffer->read_pointer = 0;
	scsi_tapes[dev].eof_hit = 0;

	if (SCpnt->result != 0 || SCpnt->sense_buffer[0] != 0) {
#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 */
	      transfer = (SCpnt->sense_buffer[3] << 24) |
		(SCpnt->sense_buffer[4] << 16) |
		  (SCpnt->sense_buffer[5] << 8) | SCpnt->sense_buffer[6];

	      if (SCpnt->sense_buffer[2] & 0x20) {
		if (scsi_tapes[dev].block_size == 0) {
		  if (transfer <= 0)
		    transfer = 0;
		  scsi_tapes[dev].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) {
		scsi_tapes[dev].eof = 2; /* What should be done at EOM ? */
		scsi_tapes[dev].buffer->buffer_bytes =
		  (scsi_tapes[dev].buffer->buffer_blocks - transfer) *
		    scsi_tapes[dev].block_size;
#ifdef DEBUG
		printk("st%d: EOM detected (%d blocks read).\n", dev,
		       scsi_tapes[dev].buffer->buffer_blocks - transfer);
#endif
	      }
	      else if (SCpnt->sense_buffer[2] & 0x80) {
		scsi_tapes[dev].eof = 1;
		if (scsi_tapes[dev].block_size == 0)
		  scsi_tapes[dev].buffer->buffer_bytes =
		    count - transfer;
		else
		  scsi_tapes[dev].buffer->buffer_bytes =
		    (scsi_tapes[dev].buffer->buffer_blocks - transfer) *
		      scsi_tapes[dev].block_size;
#ifdef DEBUG
		printk("st%d: EOF detected (%d blocks read, transferred %d bytes).\n",
		       dev, scsi_tapes[dev].buffer->buffer_blocks - transfer, total);
#endif
	      } /* end of EOF, EOM, ILI test */
	    }
	    else { /* nonzero sense key */
#ifdef DEBUG
	      printk("st%d: Tape error. Sense key %x\n", dev,
		     SCpnt->sense_buffer[2] & 0x0f);
	      print_sense("st", SCpnt);
#endif
	      SCpnt->request.dev = -1;
	      if (total)
		return total;
	      else
		return -EIO;
	    }
	  }
	  else {
	    transfer = st_chk_result(SCpnt);
	    SCpnt->request.dev = -1;  /* Mark as not busy */
	    return transfer;
	  }
	}
	else /* Read successfu| */
	  scsi_tapes[dev].buffer->buffer_bytes = bytes;

      } /* if (scsi_tapes[dev].buffer->buffer_bytes == 0 &&
	   scsi_tapes[dev].eof == 0) */

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

      if (scsi_tapes[dev].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;

   dev = dev & 127;

   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 %d files.\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 %d files.\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 (scsi_tapes[dev].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 (scsi_tapes[dev].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 (scsi_tapes[dev].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 (scsi_tapes[dev].dirty || scsi_tapes[dev].buffer->buffer_bytes != 0)
	 return (-EIO);   /* Not allowed if data in buffer */
       if (cmd_in == MTSETBLK &&
	   arg != 0 &&
	   (arg < scsi_tapes[dev].min_block || arg > scsi_tapes[dev].max_block ||
	    arg > ST_BUFFER_SIZE)) {
	 printk("st%d: Illegal block size.\n", dev);
	 return (-EINVAL);
       }
       cmd[0] = MODE_SELECT;
       cmd[4] = 12;

       memset(scsi_tapes[dev].buffer->b_data, 0, 12);
       if (cmd_in == MTSETDRVBUFFER)
	 scsi_tapes[dev].buffer->b_data[2] = (arg & 7) << 4;
       else
	 scsi_tapes[dev].buffer->b_data[2] = 
	   scsi_tapes[dev].drv_buffer << 4;
       scsi_tapes[dev].buffer->b_data[3] = 8;     /* block descriptor length */
       if (cmd_in == MTSETDENSITY)
	 scsi_tapes[dev].buffer->b_data[4] = arg;
       else
	 scsi_tapes[dev].buffer->b_data[4] = scsi_tapes[dev].density;
       if (cmd_in == MTSETBLK)
	 ltmp = arg;
       else
	 ltmp = scsi_tapes[dev].block_size;
       scsi_tapes[dev].buffer->b_data[9] = (ltmp >> 16);
       scsi_tapes[dev].buffer->b_data[10] = (ltmp >> 8);
       scsi_tapes[dev].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,
		scsi_tapes[dev].buffer->b_data[9] * 65536 +
		scsi_tapes[dev].buffer->b_data[10] * 256 +
		scsi_tapes[dev].buffer->b_data[11]);
       else if (cmd_in == MTSETDENSITY)
	 printk("st%d: Setting density code to %x.\n", dev,
		scsi_tapes[dev].buffer->b_data[4]);
       else
	 printk("st%d: Setting drive buffer code to %d.\n",
		(scsi_tapes[dev].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, scsi_tapes[dev].device->index, 1);
   SCpnt->sense_buffer[0] = 0;
   SCpnt->request.dev = dev;
   scsi_do_cmd(SCpnt,
	       (void *) cmd, (void *) scsi_tapes[dev].buffer->b_data, ST_BLOCK_SIZE,
	       st_sleep_done, timeout, MAX_RETRIES);

   if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );

   ioctl_result = st_chk_result(SCpnt);

   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) {
       scsi_tapes[dev].block_size = arg;
       if (arg != 0) {
	 scsi_tapes[dev].buffer->buffer_blocks =
	   ST_BUFFER_SIZE / scsi_tapes[dev].block_size;
	 scsi_tapes[dev].buffer->buffer_size =
	   scsi_tapes[dev].buffer->buffer_blocks * scsi_tapes[dev].block_size;
       }
       else {
	 scsi_tapes[dev].buffer->buffer_blocks = 1;
	 scsi_tapes[dev].buffer->buffer_size = ST_BUFFER_SIZE;
       }
       scsi_tapes[dev].buffer->buffer_bytes =
	 scsi_tapes[dev].buffer->read_pointer = 0;
     }
     else if (cmd_in == MTSETDRVBUFFER)
       scsi_tapes[dev].drv_buffer = arg;
     else if (cmd_in == MTSETDENSITY)
       scsi_tapes[dev].density = arg;
     if (cmd_in == MTEOM || cmd_in == MTWEOF) {
       scsi_tapes[dev].eof = 2;
       scsi_tapes[dev].eof_hit = 0;
     }
     else if (cmd_in != MTSETBLK && cmd_in != MTNOP) {
       scsi_tapes[dev].eof = 0;
       scsi_tapes[dev].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;

   dev = dev & 127;
#ifdef DEBUG
   if (!scsi_tapes[dev].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 *)scsi_tapes[dev].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, scsi_tapes[dev].device->index, 1);

     SCpnt->sense_buffer[0]=0;
     memset (scmd, 0, 10);
     if (scsi_tapes[dev].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 *) scsi_tapes[dev].buffer->b_data,
		 ST_BLOCK_SIZE, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);

     if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );
     
     if (SCpnt->result || SCpnt->sense_buffer[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 (scsi_tapes[dev].device->scsi_level < SCSI_2)
	 mt_pos.mt_blkno = (scsi_tapes[dev].buffer->b_data[0] << 16) 
	   + (scsi_tapes[dev].buffer->b_data[1] << 8) 
	     + scsi_tapes[dev].buffer->b_data[2];
       else
	 mt_pos.mt_blkno = (scsi_tapes[dev].buffer->b_data[4] << 24)
	   + (scsi_tapes[dev].buffer->b_data[5] << 16) 
	     + (scsi_tapes[dev].buffer->b_data[6] << 8) 
	       + scsi_tapes[dev].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(scsi_tapes[dev].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 = 0;
    scsi_tapes[i].eof = 0;
    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: %x\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;
}
