blob: c8e226f46301f6e1a0b0f429404dc53cbf692b47 [file] [log] [blame]
/*
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
- *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.
Kai Makisara, Nov 9, 1992 email makisara@vtinsx.ins.vtt.fi or
Kai.Makisara@vtt.fi
Last changes Jan 3, 1993.
*/
#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 9
#include "../blk.h"
#include "scsi.h"
#include "scsi_ioctl.h"
#include "st.h"
#define MAX_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 6000
#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)
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 = 0x7fffffff;
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
}
#ifdef DEBUG
/* Print sense information */
static void decode_sns(int dev, char *sense_buffer)
{
static char *snstext[] = {
"None","Recovered Error","Not Ready","Medium Error","Hardware Error",
"Illegal Request","Unit Attention","Data Protect","Blank Check",
"Key=E","Key=F","Filemark","End-Of-Medium","Incorrect Block Length",
"14","15"};
if (sense_buffer[0]!=0) {
if ((sense_buffer[0] & 0x70) == 0x70) {
if (sense_buffer[2] & 0x80) printk( "FMK ");
if (sense_buffer[2] & 0x40) printk( "EOM ");
if (sense_buffer[2] & 0x20) printk( "ILI ");
printk( "st%d: sense key %s\n", dev, snstext[sense_buffer[2] & 0x0f]);
} else {
if (sense_buffer[0] < 15)
printk("st%d: old sense key %s\n", dev, snstext[sense_buffer[0] & 0x0f]);
else
printk("st%d: sns = %2x %2x\n", dev, sense_buffer[0], sense_buffer[2]);
}
}
return;
}
#endif
/* Convert the result to success code */
static int st_chk_result(int dev, int result, unsigned char *sense)
{
if (!result)
return 0;
#ifdef DEBUG
printk("st%d: Error: %x\n", dev, result);
decode_sns(dev, sense);
#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 */
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(dev, SCpnt->result, SCpnt->sense_buffer);
#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 = inode->i_rdev & 127;
if (scsi_tapes[dev].rw == 2) /* Writing */
return flush_write_buffer(dev);
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 = 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_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
decode_sns(dev, SCpnt->sense_buffer);
#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_RETRIES);
if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );
}
if (SCpnt->result != 0) {
#ifdef DEBUG
decode_sns(dev, SCpnt->sense_buffer);
#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_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_RETRIES);
if (SCpnt->request.dev == dev) sleep_on( &scsi_tapes[dev].waiting );
i = st_chk_result(dev, SCpnt->result, SCpnt->sense_buffer);
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].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\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]);
#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);
}
if (scsi_tapes[dev].block_size == 0) {
printk("st%d: Fixing block size to 512 bytes.\n", dev);
if (st_int_ioctl(inode, filp, MTSETBLK, ST_BLOCK_SIZE)) {
printk("st%d: Can't set fixed block size.\n", dev);
scsi_tapes[dev].buffer->in_use = 0;
scsi_tapes[dev].in_use = 0;
return (-EIO);
}
scsi_tapes[dev].block_size = ST_BLOCK_SIZE;
}
}
else
scsi_tapes[dev].block_size = ST_BLOCK_SIZE;
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;
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 = 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(dev, SCpnt->result, SCpnt->sense_buffer);
#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 = 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].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 = 0x7fffffff)
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] = 1;
scsi_tapes[dev].rw = 2;
b_point = buf;
while((scsi_tapes[dev].buffer->buffer_bytes + count) >=
scsi_tapes[dev].buffer->buffer_size) {
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);
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(dev, SCpnt->result, SCpnt->sense_buffer);
#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(dev, SCpnt->result, SCpnt->sense_buffer);
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) {
/* Schedule an asynchronous write */
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;
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;
static unsigned char cmd[10];
Scsi_Cmnd * SCpnt;
dev = 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].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] = 1;
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 );
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) {
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;
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);
decode_sns(dev, SCpnt->sense_buffer);
#endif
SCpnt->request.dev = -1;
if (total)
return total;
else
return -EIO;
}
}
else {
transfer = st_chk_result(dev, SCpnt->result, SCpnt->sense_buffer);
SCpnt->request.dev = -1; /* Mark as not busy */
return transfer;
}
}
else
scsi_tapes[dev].buffer->buffer_bytes =
scsi_tapes[dev].buffer->buffer_size;
} /* 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;
}
} /* 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 = 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 */
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 < 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);
scsi_tapes[dev].buffer->b_data[2] = 0x10; /* buffered mode */
scsi_tapes[dev].buffer->b_data[3] = 8; /* block descriptor length */
if (cmd_in == MTSETBLK)
ltmp = arg;
else {
scsi_tapes[dev].buffer->b_data[4] = arg;
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
printk("st%d: Setting density code to %x.\n", dev,
scsi_tapes[dev].buffer->b_data[4]);
#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 );
SCpnt->request.dev = -1; /* Mark as not busy */
ioctl_result = st_chk_result(dev, SCpnt->result, SCpnt->sense_buffer);
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;
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;
scsi_tapes[dev].buffer->buffer_bytes =
scsi_tapes[dev].buffer->read_pointer = 0;
}
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 = 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_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;
}
/* 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;
}