home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-387-Vol-3of3.iso
/
f
/
ftp-102.zip
/
ftape-1.02
/
driver
/
fdtape.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-10-24
|
59KB
|
2,459 lines
/* Floppy Tape driver.
Copyright (C) 1992 David L. Brown, Jr.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
/*
* fdtape.c,v 1.24 1992/10/24 19:28:26 dbrown Exp
*
* fdtape.c,v
* Revision 1.24 1992/10/24 19:28:26 dbrown
* Fixed up the buffering code to work without allocs.
*
* Revision 1.23 1992/10/13 01:44:31 dbrown
* Added FSF copyright.
*
* Revision 1.22 1992/10/12 05:12:59 dbrown
* Added ioctl to set drive data rate.
*
* Fixed more write bugs.
*
* Revision 1.21 1992/10/11 18:31:31 dbrown
* Made interrupt handler static.
*
* Revision 1.20 1992/10/11 02:23:40 dbrown
* Fixed more write bugs.
*
* Revision 1.19 1992/10/10 23:18:10 dbrown
* Fixed bug in _do_write_cease causing it to ignore many error conditions.
*
* Revision 1.18 1992/10/10 22:38:06 dbrown
* Added locking mechnisms to prevent other operations during read and
* write and to stop the read or write when closing.
*
* Revision 1.17 1992/10/10 07:02:32 dbrown
* Fixed initial write acting like and error.
*
* Revision 1.16 1992/10/10 06:15:13 dbrown
* Force to COLORADO or MOUNTAIN drive type.
*
* Some fixes to write.
*
* Revision 1.15 1992/10/10 03:19:26 dbrown
* Added write error handling so that errors and their location can be
* passed back to the user.
*
* Revision 1.14 1992/10/09 05:53:31 dbrown
* Read is reliable. Write is flaky and can't handle errors.
*
* Revision 1.13 1992/10/09 02:18:24 dbrown
* Attempted to fix stopping of tape problem. This is very difficult to
* test because it occurs so rarely.
*
* Revision 1.12 1992/10/09 01:29:13 dbrown
* Write now streams and works somewhat robustly. One little possible
* crashing position left.
*
* Revision 1.11 1992/10/09 00:36:03 dbrown
* Initial writing of full write. Untested.
*
* Revision 1.10 1992/10/08 23:45:16 dbrown
* Read now does a copyout instead of using mapped buffers. Before, read
* didn't have any control of when the user would actually get the data
* out of the buffer. If the user delayed, then that data could be
* overwritten with the read from tape.
*
* Revision 1.9 1992/10/08 23:29:15 dbrown
* Performs streaming reliable reads.
*
* Revision 1.8 1992/10/08 22:26:36 dbrown
* Tried fixing read code, but didn't.
*
* Revision 1.7 1992/10/08 04:52:29 dbrown
* First attempt to add streaming read. Doesn't really work and panics
* periodically.
*
* Revision 1.6 1992/10/06 19:56:31 dbrown
* Removed unused code.
*
* Revision 1.5 1992/10/05 04:34:40 dbrown
* Remove some commented out code.
*
* Revision 1.4 1992/10/04 23:10:08 dbrown
* Added facilities for performing seek test.
*
* Revision 1.3 1992/10/04 21:45:54 dbrown
* Now supports non-streaming, single sector reads.
*
* Revision 1.2 1992/10/03 23:56:16 dbrown
* Moved enough of the old driver into the new to get drive status, and
* seek the tape and the tape head.
*
* Revision 1.1 1992/10/03 20:55:09 dbrown
* Moved from old fdtape driver.
*
* Revision 4.1 92/06/07 17:58:07 dbrown
* Moved to a dynamically linked driver.
*/
#ifdef LOADABLE
# define NFDTAPE 1
#else
# include <fdtape.h>
#endif
#if NFDTAPE > 0
#include <errno.h>
#include <sys/types.h>
#include <i386/ipl.h>
#include <sys/ioctl.h>
#include <i386/pio.h>
#include "ftape-regs.h"
#include "fdtape-io.h"
#include "kernel-interface.h"
#if 0
#include <errno.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <i386/ipl.h>
#include <i386/pio.h>
#include <i386at/atbus.h>
#include <vm/vm_kern.h>
#include <mach/vm_param.h>
#include <sys/ioctl.h>
#include "fdtape-io.h"
#include "ftape-regs.h"
#ifdef LOADABLE
# include <sys/conf.h>
# include <i386at/vd_data.h>
#endif
#endif
/* End debugging. */
#define STATIC static
#ifdef LOADABLE
# define LSTATIC static
#else
# define LSTATIC /**/
#endif
#define FDPIC 6 /* Should grab it, figure this out later. */
static int (*oldvect)();
static int oldunit;
/* Although there may be multiple units, the unit number is only to
select which drive unit the drive is attached to. Although two
tape drives could be conceivably be connected, this driver will not
support their simultaneous use. */
static int busy_flag = 0;
static int tape_unit = 0;
/* Forward functions. */
static void reset_controller (), disable_controller ();
/*static void fdt_sleep (int time);*/
#define fdt_sleep(x) _th_sleep(x)
/*static int interrupt_wait (int unit, int time);*/
#define interrupt_wait(unit, time) _th_interrupt_wait(time)
/*static void fdt_waker (void *arg);*/
static int controller_wait ();
static int issue_command (char *out_data, int out_count,
char *in_data, int in_count);
static int specify (int hut, int srt, int hlt, int nd);
static int sense_drive_status (int unit, int *st3);
static int sense_interrupt_status (int *st0, int *pcn);
static int recalibrate (int unit);
static int tape_command (int unit, int command);
static int report_status_operation (int unit, int *status,
int command, int result_length);
static int report_drive_status (int unit, int *status);
static int report_error (int unit, int *status);
static int report_configuration (int unit, int *status);
static int report_rom_version (int unit, int *status);
static int report_vendor_id (int unit, int *status);
static int read_id (int unit, struct _fdt_id *location);
/*static int read_data (int unit, int segment, void *addr);*/
static int _wait_for_ready ();
static void read_init ();
static void write_init ();
/* Device (unit) structure. */
struct fdtape_unit_data {
#if 0
int *wakeup; /* What to wakeup on interrupt. */
int expect_stray_interrupt; /* Expect stray interrupts. */
#endif
int pcn; /* Present cylinder number. */
int drive_type; /* What kind of drive was detected. */
int track; /* Last track we think the drive is on. */
};
extern int expect_stray_interrupt;
static struct fdtape_unit_data unit_data[NFDTAPE];
/* Tape length of current tape. Just tackily set here by the user
program to allow shorter length tapes to be used. */
static int segments_per_track = 150;
static long error_results[2]; /* Error results from reads. */
static int buffered_segment[2]; /* Segment that is in the numbered
buffer. -1 indicates that the
buffer has nothing. */
static int segment_being_read; /* What segment is being read in. */
static int segment_count; /* How many more do we expect to read. */
static int read_into; /* Which buffer to read into. */
static int last_actual_segments[2];
#define CURRENT_BUFFER read_into
#define PREVIOUS_BUFFER (read_into ^ 1)
#define INCREMENT_BUFFER (read_into ^= 1)
/* Transfer operations. */
static void
_open_thread (void *arg)
{
int result = 0;
int value;
int status;
int unit = (int) arg;
reset_controller (unit, /*motor*/ 0);
if (recalibrate (unit) != 0)
{
result = EIO;
goto error;
}
value = report_drive_status (unit, &status);
#ifdef COLORADO
/* Try colorado. */
value = tape_command (unit, QIC_COLORADO_ENABLE1);
fdt_sleep (MILLISECOND);
value = tape_command (unit, QIC_COLORADO_ENABLE2);
fdt_sleep (MILLISECOND);
value = report_drive_status (unit, &status);
if (value == 0)
{
unit_data[unit].drive_type = DRIVE_IS_COLORADO;
}
else
{
result = ENODEV;
goto error;
}
#elif MOUNTAIN
/* Try mountain. */
value = tape_command (unit, QIC_MOUNTAIN_ENABLE1);
fdt_sleep (MILLISECOND);
value = tape_command (unit, QIC_MOUNTAIN_ENABLE2);
fdt_sleep (MILLISECOND);
value = report_drive_status (unit, &status);
if (value == 0)
{
unit_data[unit].drive_type = DRIVE_IS_MOUNTAIN;
}
else
{
result = ENODEV;
goto error;
}
#else
if (value == 0)
{
unit_data[unit].drive_type = DRIVE_IS_UNKNOWN;
}
else
{
/* Drive doesn't respond. Try and wake it up using the known
methods of drive wakeup. */
/* Try colorado. */
value = tape_command (unit, QIC_COLORADO_ENABLE1);
fdt_sleep (MILLISECOND);
value = tape_command (unit, QIC_COLORADO_ENABLE2);
fdt_sleep (MILLISECOND);
value = report_drive_status (unit, &status);
if (value == 0)
{
unit_data[unit].drive_type = DRIVE_IS_COLORADO;
}
else
{
/* Try mountain. */
value = tape_command (unit, QIC_MOUNTAIN_ENABLE1);
fdt_sleep (MILLISECOND);
value = tape_command (unit, QIC_MOUNTAIN_ENABLE2);
fdt_sleep (MILLISECOND);
value = report_drive_status (unit, &status);
if (value == 0)
{
unit_data[unit].drive_type = DRIVE_IS_MOUNTAIN;
}
else
{
result = ENODEV;
goto error;
}
}
}
#endif
/* Now we have valid status from the drive. */
#if 0
printf ("Drive status = 0x%x, type = %d\n", status,
unit_data[unit].drive_type);
#endif
result = ESUCCESS;
buffered_segment[0] = -1;
buffered_segment[1] = -1;
segment_being_read = -1;
segment_count = 0;
read_into = 0;
read_init (); /* Above should move into read_init. */
write_init ();
error:
_th_done (result);
}
static void
_close_thread (void *arg)
{
int result;
int unit = (int) arg;
result = ESUCCESS;
switch (unit_data[unit].drive_type)
{
case DRIVE_IS_COLORADO:
tape_command (unit, QIC_COLORADO_DISABLE);
break;
case DRIVE_IS_MOUNTAIN:
tape_command (unit, QIC_MOUNTAIN_DISABLE);
break;
default:
break;
}
_th_done (result);
}
static void
_get_status_thread (int *status)
{
int result;
result = ((report_drive_status (tape_unit, status) == 0)
? ESUCCESS : EIO);
_th_done (result);
}
static void
_report_error_thread (struct fdt_error *error)
{
int code;
int value;
int result;
value = report_error (tape_unit, &code);
if (value == 0)
{
error->error = code & 0xff;
error->command = (code & 0xff00) >> 8;
result = ESUCCESS;
}
else
result = EIO;
_th_done (result);
}
static void
_report_configuration_thread (int *config)
{
int result;
result = ((report_configuration (tape_unit, config) == 0)
? ESUCCESS : EIO);
_th_done (result);
}
static void
_report_rom_version_thread (int *version)
{
int result;
result = ((report_rom_version (tape_unit, version) == 0)
? ESUCCESS : EIO);
_th_done (result);
}
static void
_report_vendor_id_thread (int *id)
{
int result;
result = ((report_rom_version (tape_unit, id) == 0)
? ESUCCESS : EIO);
_th_done (result);
}
/* Perform the specified tape operation and wait up to time_limit
seconds for the operation to complete. The resultant status will
be placed into *status. Returns ESUCCESS for success, or EIO for
an error. */
static int
operate_and_wait (int command, int time_limit, int *status)
{
int count;
int result;
/* Assume the user process will wait for drive ready. Should
actually do that here. */
tape_command (tape_unit, command);
/* Now poll the drive every 1/4 second and wait for the drive to
become ready. */
result = report_drive_status (tape_unit, status);
if (result != 0)
return EIO;
count = time_limit * 4; /* Wait up to 85 seconds for
operation. */
while (count-- > 0
&& (*status & QIC_STATUS_READY) == 0)
{
fdt_sleep (HZ / 4); /* Sleep for 1/4 second and try again. */
result = report_drive_status (tape_unit, status);
if (result != 0)
return EIO;
}
fdt_sleep (HZ / 4);
result = report_drive_status (tape_unit, status);
if (result != 0)
return EIO;
return ESUCCESS;
}
/* Seek to the end of the tape, and wait. */
static void
_seek_to_end_thread (void *arg)
{
int result, status;
result = _wait_for_ready ();
if (result != 0)
goto error;
result = operate_and_wait (QIC_PHYSICAL_FORWARD, 85, &status);
if (result == ESUCCESS)
{
/* Now make sure the drive thinks we're at the end of the tape. */
if ((status & QIC_STATUS_AT_EOT) == 0)
result = EIO;
}
error:
_th_done (result);
}
/* Seek to the beginning of the tape. */
static void
_seek_to_beginning_thread (void *arg)
{
int result, status;
result = _wait_for_ready ();
if (result != 0)
goto error;
result = operate_and_wait (QIC_PHYSICAL_REVERSE, 85, &status);
if (result == ESUCCESS)
{
/* Now make sure the drive thinks we're at the start of the tape. */
if ((status & QIC_STATUS_AT_BOT) == 0)
result = EIO;
}
error:
_th_done (result);
}
/* Perform the specified seek. The absolute value of the argument is
one more than the number of segments to issue to the qic command.
If the argument is negative, the seek is done in the reverse
logical direction, otherwise it is done in the forward logical
direction. */
static void
_seek_thread (int *amount)
{
int dir, count;
int status;
int result = ESUCCESS;
count = *amount;
if (count > 0)
{
dir = QIC_SKIP_FORWARD;
count = count - 1;
}
else
{
dir = QIC_SKIP_REVERSE;
count = -1 - count;
}
result = _wait_for_ready ();
if (result != 0)
goto error;
/* Issue this tape command first. */
result = tape_command (tape_unit, dir);
if (result == ESUCCESS)
{
/* Issue the low nibble of the command. */
result = tape_command (tape_unit, (count & 0x0f) + 2);
if (result == ESUCCESS)
{
/* Issue the high nibble and wait for the command to complete. */
result = operate_and_wait (((count & 0xf0) >> 4) + 2,
85, &status);
}
}
error:
_th_done (result);
}
/* Seek the head to the specified track. */
static void
_seek_to_track_thread (int *track)
{
int count;
int status;
int result;
count = *track;
unit_data[tape_unit].track = *track;
if (count < 0 || count > 27)
{
result = EINVAL;
goto error;
}
result = _wait_for_ready ();
if (result != 0)
goto error;
/* Issue this tape command first. */
result = tape_command (tape_unit, QIC_SEEK_HEAD_TO_TRACK);
if (result == ESUCCESS)
{
/* Issue the track and wait for the command to complete. */
result = operate_and_wait (count + 2, 85, &status);
}
error:
_th_done (result);
}
/* Reading from tape. */
/* Current error map. */
unsigned long error_masks[150];
/* Wait for the drive to be ready. */
static int
_wait_for_ready ()
{
int count = 200; /* 20 seconds. */
int result, status;
result = report_drive_status (tape_unit, &status);
if (result != 0)
return EIO;
while (count > 0
&& (status & QIC_STATUS_READY) == 0)
{
fdt_sleep (HZ / 10);
result = report_drive_status (tape_unit, &status);
if (result != 0)
return EIO;
}
return 0;
}
/* Start the tape moving and wait for the segment before the given
segment to pass by. Returns 0 if the tape is spinning and ready to
read the proper segment. If an error is returned, actual_segment
will be set to the segment that just passed by, or a value < 0 if
the location cannot be determined (such as end of a track). */
static int
_start_tape (int segment, int *actual_segment,
int *first_segment /* Temporary! */
)
{
int retries = 10 * 32; /* Read up to 10 segments ahead. */
int result;
int trash;
if (first_segment == 0)
first_segment = &trash;
*first_segment = -1;
/* Wait for the drive to be ready. */
result = _wait_for_ready ();
if (result != 0)
return result;
if (tape_command (tape_unit, QIC_LOGICAL_FORWARD) != 0)
return EIO;
/* Wait for ID of previous segment to go by unless we are at the
beginning of the tape. */
if ((segment % segments_per_track) != 0)
{
struct _fdt_id location;
do
{
result = read_id (tape_unit, &location);
if (result != 0)
{
*actual_segment = -2;
fdt_sleep (HZ/10); /* A read id error usually means the
tape has run off of the end, wait
here to avoid trouble. */
_wait_for_ready (); /* Even wait for it to become ready.
This even causes a lot of problems
if the drive isn't given a chance
to calm down. */
goto error;
}
*actual_segment = (600 * location.head
+ 4 * location.cylinder
+ (location.sector - 1) / 32);
if (*first_segment < 0)
*first_segment = *actual_segment;
if (*actual_segment >= segment ||
segment - *actual_segment > 9)
goto error;
} while (*actual_segment != segment - 1
&& retries-- > 0);
}
if (retries <= 0)
return EIO;
return ESUCCESS;
error:
return EIO;
}
/* Read one segment. The tape is assumed to be spinning and the
proper segment should pass next under the head. The bits in
error_mask that are set will not have their sectors read.
actual_segment will be set as per _start_tape, with -1 being a
successful read, and -3 being a real read error. */
static int
_read_segment (int segment,
unsigned long error_mask,
char *buffer,
int *actual_segment,
unsigned long *error_bits)
{
char out[9], in[7];
unsigned long error_runner;
int address;
int result;
int head, cylinder, sector;
int offset, remaining;
int data_offset;
int count;
int s;
int return_value = EIO;
*error_bits = 0;
/* Clear out actual segment. */
*actual_segment = -1;
head = segment / 600;
cylinder = (segment % 600) / 4;
sector = (segment % 4) * 32 + 1;
offset = 0;
data_offset = 0;
remaining = 32;
error_runner = error_mask;
while (remaining > 0)
{
if (error_runner & 1)
{
error_runner >>= 1;
offset++;
remaining--;
continue;
}
#if 0
for (count = 0; (count < remaining
&& (error_runner & 1) == 0); count++)
error_runner >>= 1;
#else
count = 1;
error_runner >>= 1;
#endif
/* Program the DMA controller to read. */
address = kvtophys (buffer + data_offset * 1024);
s = splhigh ();
outb (DMA_COMMAND, 0);
outb (DMA_MASK_BIT, 2 | 4);
outb (DMA_CLEAR_FLIP_FLOP, DMA_MODE_READ);
outb (DMA_MODE, DMA_MODE_READ);
outb (DMA_ADDRESS, address & 0xff);
outb (DMA_ADDRESS, (address & 0xff00) >> 8);
outb (DMA_PAGE, (address & 0xff0000) >> 16);
outb (DMA_COUNT, (1024 * count - 1) & 0xff);
outb (DMA_COUNT, ((1024 * count - 1) & 0xff00) >> 8);
outb (DMA_MASK_BIT, 2);
splx (s);
/* Issue FDC command to start reading. */
out[0] = FDC_READ_DATA;
out[1] = tape_unit;
out[2] = cylinder;
out[3] = head;
out[4] = sector + offset;
out[5] = 3; /* Sector size of 1K. */
out[6] = sector + offset + count - 1; /* Read appropriate number
of sectors. */
out[7] = 116; /* Gap length. From the suggested
value for 1K MFM sectors 500K data
rate. The value 116 I made up myself. */
out[8] = 0xff; /* No limit to transfer size. */
result = issue_command (out, 9, 0, 0);
if (result != 0)
{
printf ("fdtape: read issue error\n");
goto error;
}
result = interrupt_wait (tape_unit, READ_TIMEOUT);
if (result != 0)
{
printf ("fdtape: read timeout\n");
goto error;
}
result = issue_command (0, 0, in, 7);
if (result != 0)
{
printf ("fdtape: read result error\n");
return result;
}
if ((in[0] & ST0_CODE_MASK) != ST0_NORMAL)
{
/* Figure out the type of error. */
if (in[1] & ST1_DATA_ERROR)
{
/* Rewind the tape. */
tape_command (tape_unit, QIC_PAUSE);
printf ("fdtape: crc error in sector %d or segment %d (%d)\n",
(in[5] - 1) % 32 + 1, segment, in[5]);
/* Make sure the floppy disk controller is sane and
reporting a sector of a reasonable range. */
if (in[5] < sector + offset
|| in[5] >= sector + offset + count)
{
printf ("fdtape: crc error in unrealted data\n");
goto error;
}
/* Run backward into the error. */
while (sector + offset + count - 1 > in[5])
count--;
/* Set the error bit. */
*error_bits |= 1 << (offset + count - 1);
/* Restart the tape. */
result = _start_tape (segment, actual_segment,
0);
if (result == ESUCCESS)
*actual_segment = -1;
else
return_value = result;
printf ("fdtape: start=%d,count=%d,bits=0x%x, do=%d\n",
sector + offset, count, *error_bits, data_offset);
}
else
{
printf ("fdtape: error st0 = 0x%x, st1 = 0x%x, st2 = 0x%x\n",
in[0], in[1], in[2]);
printf ("fdtape: read_segment, error cyl=%d, head=%d, sec=%d, ss=%d\n",
in[3], in[4], in[5], in[6]);
*actual_segment = -3; /* Some kind of read error. */
goto error;
}
}
offset += count;
data_offset += count;
remaining -= count;
}
return_value = ESUCCESS;
error:
return return_value;
}
/* Buffering and reading the data. */
/* The read routines exist in one of these states:
XXX This isn't correct.
Name Buffers User process.
Thread
------------------------------------------------------------
- idle empty Nothing
Not running
- request cur:read Waiting
Reading->cur
- read_next cur:read, prev:used Nothing
Reading->cur
- got cur:read, prev:data Waiting
Reading->cur.
- idle_buffer cur:data prev:data Nothing
Not running.
- idle_one_buffer prev:data, cur:empty Nothing
Not running
State transition conditions:
idle: if user request -> request
request: if finish read then
if more to read -> read_next
else -> idle_one_buffer
read_next: if user gets buffer -> got
if finish read -> idle_buffer
got: if finish read then
if more to read -> read_next
else -> idle
idle_buffer: if user gets buffer then
if more to read -> request
else -> idle_one_buffer
idle_one_buffer: if user gets buffer -> idle
*/
enum read_states {
READ_IDLE, READ_REQUEST, READ_NEXT, READ_GOT, READ_BUFFER,
READ_WAIT, READ_STOPPING, READ_STOPPING_WAIT
};
static int read_state = READ_IDLE;
static char *read_state_names[] = {
"Idle", "Request", "Next", "Got", "Buffer", "Wait",
"Stopping", "Stopping Wait"
};
static void
read_init ()
{
read_state = READ_IDLE;
}
/* Transitions within thread. These are indexed by
2*read_state + (count>0). */
struct read_thread_transitions {
enum read_states next; /* Next state. */
int wake; /* Wake up the user process. */
int done; /* Done reading? */
};
static struct read_thread_transitions _read_thread_transitions[8*2] =
{
/* next, wake, continue. */
/* i0 */ -1, -1, -1,
-1, -1, -1,
/* q0 */ READ_IDLE, 1, 1,
READ_NEXT, 1, 0,
/* n0 */ READ_BUFFER, 1, 1,
READ_WAIT, 0, 1,
/* g0 */ READ_IDLE, 1, 1,
READ_NEXT, 1, 0,
/* b0 */ -1, -1, -1,
-1, -1, -1,
/* w0 */ -1, -1, -1,
-1, -1, -1,
/* s0 */ -1, -1, -1,
-1, -1, -1,
/* S0 */ -1, -1, -1,
-1, -1, -1,
};
static void
_read_thread (void *nothing)
{
int old_state;
int result = 0;
int done = 0;
int wake_them = 0;
int index;
#if 0
printf ("_read_thread starts, state = %s (%d)\n",
read_state_names[read_state], segment_count);
#endif
result = _start_tape (segment_being_read,
&last_actual_segments[CURRENT_BUFFER],
0);
if (result != 0)
{
read_state = READ_IDLE;
INCREMENT_BUFFER;
segment_count == 0;
}
else
{
while (!done)
{
result = _read_segment (segment_being_read,
error_masks[segment_being_read
% segments_per_track],
(tape_buffer
+ 32768 * CURRENT_BUFFER),
&last_actual_segments[CURRENT_BUFFER],
&error_results[CURRENT_BUFFER]);
INCREMENT_BUFFER;
segment_being_read++;
segment_count--;
old_state = read_state;
if (result != 0)
{
read_state = READ_IDLE;
segment_count == 0;
done = 1;
}
else
{
index = 2 * old_state + (segment_count > 0);
if (_read_thread_transitions[index].next == -1)
{
printf ("ftape error: invalid read thread state %d\n",
index);
read_state = READ_IDLE;
_th_done (0);
}
read_state = _read_thread_transitions[index].next;
wake_them = _read_thread_transitions[index].wake;
done = _read_thread_transitions[index].done;
}
if (!done && wake_them)
#if 0
printf ("_read_thread wakes, not done, state = %s (%d)\n",
read_state_names[read_state], segment_count),
#endif
_th_wakeup (0); /* Process needs to be awoken. */
}
}
old_state = read_state;
read_state = READ_STOPPING;
/* Stop tape appropriately */
/* Can't wait for ready, because if the tape is moving, the drive
won't be ready. The stop seems to fail if we're at the beginning
of the tape. */
/*_wait_for_ready ();*/
if (old_state == READ_IDLE)
tape_command (tape_unit, QIC_STOP_TAPE);
else
tape_command (tape_unit, QIC_PAUSE);
#if 0
if (read_state == READ_STOPPING_WAIT)
_th_wakeup (0);
#endif /* The done wakes us up. */
read_state = old_state;
#if 0
printf ("_read_thread finishes, state = %s (%d)\n",
read_state_names[read_state], segment_count);
#endif
_th_done (0);
}
struct read_transitions {
enum read_states next; /* Next state. */
int start; /* Start thread? */
int block; /* Block for thread? */
};
/* These are indexed by read_state. */
struct read_transitions read_transitions[8] = {
/* i */ READ_REQUEST, 1, 1,
/* q */ -1, -1, -1,
/* n */ READ_GOT, 0, 1,
/* g */ -1, -1, -1,
/* b */ READ_IDLE, 0, 0,
/* w */ READ_NEXT, 1, 0,
/* s */ -1, -1, -1,
/* S */ -1, -1, -1,
};
static int
_do_read (struct tape_read *tr)
{
int s;
int buf;
int old_state;
int result = 0;
int dont_really_wait = 0;
/*printf ("Read is in state %s\n", read_state_names[read_state]);*/
s = splhigh ();
if (read_state == READ_STOPPING)
{
read_state == READ_STOPPING_WAIT;
_th_wait ();
dont_really_wait = 1;
}
old_state = read_state;
if (read_transitions[old_state].next == -1)
{
printf ("ftape error: Read in invalid state %s\n",
read_state_names[read_state]);
goto abnormal;
}
else
{
if (old_state == READ_IDLE)
{
segment_being_read = tr->segment;
segment_count = tr->count;
}
read_state = read_transitions[old_state].next;
switch (read_transitions[old_state].start * 2
+ read_transitions[old_state].block)
{
case 0:
break;
case 1:
if (!dont_really_wait)
_th_wait ();
break;
case 2:
_th_async_begin (_read_thread, 0);
break;
case 3:
_th_begin (_read_thread, 0);
break;
}
tr->actual_segment = last_actual_segments[PREVIOUS_BUFFER];
result = copyout (&tape_buffer[PREVIOUS_BUFFER * 32768],
tr->buffer, 32768);
tr->error_bits = error_results[PREVIOUS_BUFFER];
}
/*printf ("Read finished in state %s\n", read_state_names[read_state]);*/
abnormal:
splx (s);
return result;
}
struct read_stop_trans {
int count; /* What to set segment count to. */
int block; /* Should we block? */
};
struct read_stop_trans _read_stop_trans[9] = {
/* i */ 0, 0,
/* q */ 1, 1,
/* n */ 1, 1,
/* g */ 0, 0,
/* b */ 0, 0,
/* w */ 0, 0,
/* s */ 0, 0,
/* S */ 0, 0,
};
static int
_do_read_stop ()
{
int s;
s = splhigh ();
if (read_state == READ_STOPPING)
{
read_state == READ_STOPPING_WAIT;
_th_wait ();
}
else
{
segment_count = _read_stop_trans[read_state].count;
if (_read_stop_trans[read_state].block)
_th_wait ();
}
read_state = READ_IDLE;
splx (s);
return 0;
}
static void
_read_id_thread (struct _fdt_find_me *fm)
{
int result = 0;
struct _fdt_id location;
long error_bits;
result = _start_tape (fm->segment, &fm->actual_segment,
&fm->first_segment);
#if 0
if (result == 0)
{
result = read_id (tape_unit, &location);
}
#endif
#if 0
if (result == 0)
result = _read_segment (fm->segment,
0, /* No error mask for test. */
tape_buffer, /* First tape buffer. */
&fm->actual_segment,
&error_bits);
#endif
/* Don't return the error result, or the data won't be passed back.
Instead, put the error code in the argument structure. */
fm->result = result;
tape_command (tape_unit, QIC_STOP_TAPE);
tape_command (tape_unit, QIC_STOP_TAPE);
_th_done (0);
}
/* Writing to tape. */
static int write_actual_segment = -1;
static int write_error_location = -1;
/* Write one segment. The tape is assumed to be spinning and the
proper segment should pass next under the head. */
static int
_write_segment (int segment,
unsigned long error_mask,
char *buffer)
{
char out[9], in[7];
unsigned long error_runner;
int address;
int result;
int head, cylinder, sector;
int offset, remaining;
int data_offset;
int count;
int s;
int return_value = EIO;
/* Clear out actual segment. */
write_actual_segment = -1;
head = segment / 600;
cylinder = (segment % 600) / 4;
sector = (segment % 4) * 32 + 1;
offset = 0;
data_offset = 0;
remaining = 32;
error_runner = error_mask;
while (remaining > 0)
{
if (error_runner & 1)
{
error_runner >>= 1;
offset++;
remaining--;
continue;
}
#if 0
/* This is for larger chunks. */
for (count = 0; (count < remaining
&& (error_runner & 1) == 0); count++)
error_runner >>= 1;
#else
count = 1;
error_runner >>= 1;
#endif
/* Program the DMA controller to write. */
address = kvtophys (buffer + data_offset * 1024);
s = splhigh ();
outb (DMA_COMMAND, 0);
outb (DMA_MASK_BIT, 2 | 4);
outb (DMA_CLEAR_FLIP_FLOP, DMA_MODE_WRITE);
outb (DMA_MODE, DMA_MODE_WRITE);
outb (DMA_ADDRESS, address & 0xff);
outb (DMA_ADDRESS, (address & 0xff00) >> 8);
outb (DMA_PAGE, (address & 0xff0000) >> 16);
outb (DMA_COUNT, (1024 * count - 1) & 0xff);
outb (DMA_COUNT, ((1024 * count - 1) & 0xff00) >> 8);
outb (DMA_MASK_BIT, 2);
splx (s);
/* Issue FDC command to start reading. */
out[0] = FDC_WRITE_DATA;
out[1] = tape_unit;
out[2] = cylinder;
out[3] = head;
out[4] = sector + offset;
out[5] = 3; /* Sector size of 1K. */
out[6] = sector + offset + count - 1; /* Read appropriate number
of sectors. */
out[7] = 1; /* Gap length. From the suggested
value for 1K MFM sectors 500K data
rate. The value 116 I made up myself.
I don't know what this is for.
Since write has more problems than
read, I'll set it to 1 and see if
that helps. Does this affect the
data written? */
out[8] = 0xff; /* No limit to transfer size. */
result = issue_command (out, 9, 0, 0);
if (result != 0)
{
printf ("fdtape: write issue error\n");
goto error;
}
result = interrupt_wait (tape_unit, WRITE_TIMEOUT);
if (result != 0)
{
printf ("fdtape: write timeout\n");
goto error;
}
result = issue_command (0, 0, in, 7);
if (result != 0)
{
printf ("fdtape: write result error\n");
return result;
}
if ((in[0] & ST0_CODE_MASK) != ST0_NORMAL)
{
printf ("fdtape: write error st0 = 0x%x, st1 = 0x%x, st2 = 0x%x\n",
in[0], in[1], in[2]);
printf ("fdtape: write_segment, error cyl=%d, head=%d, sec=%d, ss=%d\n",
in[3], in[4], in[5], in[6]);
write_actual_segment = -3; /* Some kind of write error. */
goto error;
}
offset += count;
data_offset += count;
remaining -= count;
}
return_value = ESUCCESS;
error:
return return_value;
}
/* Writing exists in one of seven states, i, q, qq, s, pp, w, and ww (for
lack of better names). */
enum write_states { WRITE_i, WRITE_q, WRITE_qq, WRITE_s, WRITE_pp,
WRITE_w, WRITE_ww,
WRITE_stopping, WRITE_stopping_wait,
WRITE_error,
};
enum write_states write_state = WRITE_i;
/* Transitions for write thread. */
struct write_thread_transitions {
enum write_states next; /* Next state. */
int wake; /* Wake up user process? */
int done; /* Done writing? */
};
static struct write_thread_transitions _write_thread_transitions[10] = {
/* i */ -1, -1, -1,
/* q */ WRITE_s, 1, 1,
/* qq */ WRITE_q, 0, 0,
/* s */ -1, -1, -1,
/* pp */ WRITE_qq, 1, 0,
/* w */ WRITE_i, 1, 1,
/* ww */ WRITE_w, 0, 0,
/* st */ -1, -1, -1,
/* ST */ -1, -1, -1,
/* ER */ -1, -1, -1,
};
static char *write_state_names[10] = {
"i", "q", "Q", "s", "P", "w", "W", "st", "ST", "Error"
};
static void
write_init ()
{
write_state = WRITE_i;
write_actual_segment = -1;
}
static void
_write_thread (void *nothing)
{
int old_state;
int result = 0;
int done = 0;
int wake_them = 0;
int buffer;
int segment;
#if 0
printf ("_write_thread starts, state = %s (%d)\n",
write_state_names[write_state], segment_count);
#endif
buffer = PREVIOUS_BUFFER;
result = _start_tape (segment_being_read,
&write_actual_segment,
0);
if (result != 0)
{
write_state = WRITE_error;
write_error_location = segment_being_read;
INCREMENT_BUFFER;
}
else
{
while (!done)
{
result = _write_segment (segment_being_read,
error_masks[segment_being_read
% segments_per_track],
(tape_buffer
+ 32768 * buffer));
segment_being_read++;
old_state = write_state;
if (result != 0)
{
write_state = WRITE_error;
write_error_location = segment_being_read - 1;
done = 1;
}
else
{
if (_write_thread_transitions[old_state].next == -1)
{
printf ("ftape error: invalid write thread state %d\n",
old_state);
write_state = WRITE_error;
_th_done (0);
}
write_state = _write_thread_transitions[old_state].next;
wake_them = _write_thread_transitions[old_state].wake;
done = _write_thread_transitions[old_state].done;
}
if (!done && wake_them)
#if 0
printf ("_write_thread wakes, not done, state = %s (%d)\n",
write_state_names[write_state], segment_count),
#endif
_th_wakeup (0); /* Process needs to be awoken. */
buffer = PREVIOUS_BUFFER;
}
}
old_state = write_state;
/* Stop tape appropriately */
/* Can't wait for ready, because if the tape is moving, the drive
won't be ready. The stop seems to fail if we're at the beginning
of the tape. */
/*_wait_for_ready ();*/
if (old_state == WRITE_w)
tape_command (tape_unit, QIC_STOP_TAPE);
else
tape_command (tape_unit, QIC_PAUSE);
#if 0
if (read_state == READ_STOPPING_WAIT)
_th_wakeup (0);
#endif /* The done wakes us up. */
write_state = old_state;
#if 0
printf ("_write_thread finishes, state = %s (%d)\n",
write_state_names[write_state], segment_count);
#endif
_th_done (0);
}
struct write_transitions {
enum write_states next; /* Next state. */
int start; /* Start thread? */
int block; /* Block? */
int cease_next; /* Next thread for cease. */
int cease_block; /* Block on cease? */
};
static struct write_transitions write_transitions[10] = {
/* Next start Blk cNext cBlock */
/* i */ WRITE_q, 1, 0, WRITE_i, 0,
/* q */ WRITE_qq, 0, 0, WRITE_w, 1,
/* qq */ WRITE_pp, 0, 1, WRITE_ww, 1,
/* s */ WRITE_q, 1, 0, WRITE_i, 0,
/* pp */ -1, -1, -1, -1, -1,
/* w */ -1, -1, -1, -1, -1,
/* ww */ -1, -1, -1, -1, -1,
/* st */ -1, -1, -1, -1, -1,
/* ST */ -1, -1, -1, -1, -1,
/* ER */ -1, -1, -1, WRITE_i, 0,
};
static int
_do_write (struct tape_write *tw)
{
int s;
int buf;
int old_state;
int result = 0;
int dont_really_wait = 0;
/*printf ("Write is in state %s\n", write_state_names[write_state]);*/
s = splhigh ();
if (write_state == WRITE_stopping)
{
write_state == WRITE_stopping_wait;
_th_wait ();
dont_really_wait = 1;
}
old_state = write_state;
if (old_state == WRITE_error)
{
real_bad:
tw->actual_segment = write_actual_segment;
if (tw->actual_segment == -1)
tw->actual_segment = -4;
tw->error_location = write_error_location;
goto abnormal;
}
if (write_transitions[old_state].next == -1)
{
printf ("ftape error: Write in invalid state %s\n",
write_state_names[write_state]);
goto abnormal;
}
else
{
write_state = write_transitions[old_state].next;
if (write_transitions[old_state].block
&& !dont_really_wait)
_th_wait ();
if (write_state == WRITE_error)
goto real_bad; /* Yuck. */
if (old_state == WRITE_i)
segment_being_read = tw->segment;
result = copyin (tw->buffer,
&tape_buffer[CURRENT_BUFFER * 32768],
32768);
INCREMENT_BUFFER;
if (write_transitions[old_state].start)
_th_async_begin (_write_thread, 0);
tw->actual_segment = -1;
tw->error_location = -1;
}
/*printf ("Write finished in state %s\n", write_state_names[write_state]);*/
abnormal:
splx (s);
return result;
}
static int
_do_write_cease (struct write_cease *tc)
{
int s;
int result = 0;
int block;
int dont_really_wait = 0;
int was_error = 0;
/*printf ("Cease is in state %s\n", write_state_names[write_state]);*/
s = splhigh ();
was_error |= write_state == WRITE_error;
if (write_state == WRITE_stopping)
{
write_state == WRITE_stopping_wait;
_th_wait ();
dont_really_wait = 1;
}
was_error |= write_state == WRITE_error;
if (write_transitions[write_state].cease_next == -1)
{
printf ("ftape: write cease, impossible state %s\n",
write_state_names[write_state]);
goto abnormal;
}
block = write_transitions[write_state].cease_block;
write_state = write_transitions[write_state].cease_next;
if (block & !dont_really_wait)
_th_wait ();
/* Yes, check again. */
if (write_state == WRITE_stopping)
{
write_state == WRITE_stopping_wait;
_th_wait ();
dont_really_wait = 1;
}
was_error |= write_state == WRITE_error;
if (was_error)
{
write_state = WRITE_i;
tc->actual_segment = write_actual_segment;
write_actual_segment = -1;
tc->error_location = write_error_location;
}
else
{
tc->actual_segment = -1;
tc->error_location = -1;
}
abnormal:
splx (s);
return (result);
}
/* Set data rate. */
static int
_set_data_rate_thread (int *rate)
{
int vfo;
int qic_rate;
int result;
int status;
switch (*rate)
{
case FDT_RATE_250:
vfo = 0x02;
qic_rate = 0;
break;
case FDT_RATE_500:
vfo = 0x00;
qic_rate = 2;
break;
default:
result = EINVAL;
goto error;
}
result = _wait_for_ready ();
if (result != 0)
goto error;
result = tape_command (tape_unit, QIC_SELECT_RATE);
if (result == ESUCCESS)
{
result = operate_and_wait (qic_rate + 2, 1, &status);
}
if (result == ESUCCESS)
{
outb (FDC_VFO_REGISTER, vfo);
}
error:
_th_done (result);
}
_fdtape_attach ()
{
busy_flag = 0;
tape_unit = -1;
printf ("fdtape: Present.\n");
}
static int
fdtape_intr (unit)
int unit;
{
unit &= 3;
_th_got_interrupt ();
#if 0
if (unit_data[unit].wakeup != 0)
{
*unit_data[unit].wakeup = WAKEN_BY_INTERRUPT;
wakeup (unit_data[unit].wakeup);
}
else if (unit_data[unit].expect_stray_interrupt)
;
else
{
printf ("fdtape: stray interrupt.\n");
}
#endif
}
/* Open */
int
_fdtape_open (unit, flags)
int unit;
int flags;
{
int result;
int s;
if (busy_flag)
return (EBUSY);
tape_unit = unit;
/* Wedge in interrupt. */
s = sploff ();
oldvect = ivect[FDPIC];
oldunit = iunit[FDPIC];
ivect[FDPIC] = fdtape_intr;
iunit[FDPIC] = unit;
splon (s);
result = _th_begin (_open_thread, (void *) unit);
if (result == ESUCCESS)
{
busy_flag = 1;
}
else
{
s = sploff ();
ivect[FDPIC] = oldvect;
iunit[FDPIC] = oldunit;
splon (s);
}
return result;
}
int
_fdtape_close (unit, flags)
int unit;
int flags;
{
int s;
int result;
struct write_cease tc;
unit &= 0x03;
/* Make sure thing is shut down. */
if (read_state != READ_IDLE)
_do_read_stop ();
if (read_state != READ_IDLE)
printf ("ftape: warning: close can't stop read\n");
if (write_state != WRITE_i)
_do_write_cease (&tc);
if (read_state != WRITE_i)
printf ("ftape: warning: close can't stop write\n");
result = _th_begin (_close_thread, (void *) unit);
s = sploff ();
ivect[FDPIC] = oldvect;
iunit[FDPIC] = oldunit;
splon (s);
disable_controller (unit);
busy_flag = 0;
return (ESUCCESS);
}
int
_fdtape_ioctl (int unit, unsigned command, void *arg, int mode)
{
int result = EINVAL;
/* Don't do anything if reading or writing. */
if ((read_state != READ_IDLE
&& command != FDT_READ && command != FDT_READ_STOP)
|| (write_state != WRITE_i
&& command != FDT_WRITE && command != FDT_CEASE_WRITING))
{
result = EBUSY;
goto abnormal;
}
switch (command)
{
case FDT_REPORT_STATUS:
result = _th_begin (_get_status_thread, arg);
break;
case FDT_REPORT_ERROR_CODE:
result = _th_begin (_report_error_thread, arg);
break;
case FDT_REPORT_CONFIGURATION:
result = _th_begin (_report_configuration_thread, arg);
break;
case FDT_REPORT_ROM_VERSION:
result = _th_begin (_report_rom_version_thread, arg);
break;
case FDT_REPORT_VENDOR_ID:
result = _th_begin (_report_vendor_id_thread, arg);
break;
case FDT_SEEK_TO_END:
result = _th_begin (_seek_to_end_thread, arg);
break;
case FDT_SEEK_TO_BEGINNING:
result = _th_begin (_seek_to_beginning_thread, arg);
break;
case FDT_SEEK_FORWARD:
case FDT_SEEK_REVERSE:
{
int value = *((int *) arg);
if (value < 0 || value > 255)
return EINVAL;
value++;
if (command == FDT_SEEK_REVERSE)
value = -value;
result = _th_begin (_seek_thread, &value);
}
break;
case FDT_SEEK_TO_TRACK:
result = _th_begin (_seek_to_track_thread, arg);
break;
case FDT_FIND_ME:
result = _th_begin (_read_id_thread, arg);
break;
case FDT_SUBMIT_ERROR_MAP:
{
struct error_map *map = arg;
/* Zero out error map. */
bzero (error_masks, segments_per_track * sizeof (unsigned long));
result = copyin (map->error_masks,
error_masks,
map->count * sizeof (unsigned long));
}
break;
case FDT_SET_TRACK_LENGTH:
{
int value = *((int *) arg);
if (value != 150 && value != 100)
result = EINVAL;
else
{
segments_per_track = value;
result = ESUCCESS;
}
}
break;
case FDT_READ:
result = _do_read ((struct tape_read *) arg);
break;
case FDT_READ_STOP:
result = _do_read_stop ();
break;
case FDT_WRITE:
result = _do_write ((struct tape_write *) arg);
break;
case FDT_CEASE_WRITING:
result = _do_write_cease ((struct write_cease *) arg);
break;
case FDT_SET_DATA_RATE:
result = _th_begin (_set_data_rate_thread, (int*) arg);
break;
default:
result = EINVAL;
break;
}
abnormal:
return result;
}
#if 0
LSTATIC int
fdtape_read (int dev, struct uio *uiop)
{
#if 0
int cylinder, segment;
int unit = minor (dev);
if ((uiop->uio_offset & 32767) != 0)
return EINVAL;
if (uiop->uio_resid != 32768)
return EINVAL;
if (uiop->uio_iovcnt != 1)
return EINVAL;
segment = uiop->uio_offset / 32768;
uiop->uio_resid -= 32768;
return (read_data (unit, segment, uiop->uio_iov[0].iov_base));
#else
return EINVAL;
#endif
}
#endif
/* Floppy disk controller communication. */
static int old_vfo;
/* Reset the floppy disk controller. Leaves the FDTAPE unit selected. */
static void
reset_controller (int unit, int motor)
{
int data;
/*unit_data[unit].*/expect_stray_interrupt = 1;
/* Assert the reset line. Leave the proper unit selected. */
data = unit;
outb (FDC_CONTROL_REGISTER, data);
fdt_sleep (MILLISECOND);
/* Now lower the reset line. */
data |= FDC_RESET;
outb (FDC_CONTROL_REGISTER, data);
fdt_sleep (MILLISECOND);
/* Enable dma mode. */
data |= FDC_DMA_REQUEST;
if (motor)
data |= FDC_MOTOR_0 << unit;
outb (FDC_CONTROL_REGISTER, data);
fdt_sleep (MILLISECOND);
/* Select clock for fdc. May need to be changed to select data
rate. */
old_vfo = inb (FDC_VFO_REGISTER);
outb (FDC_VFO_REGISTER, 0x00);
/*unit_data[unit].*/expect_stray_interrupt = 0;
}
/* When we're done, put the fdc into reset mode so that the regular
floppy disk driver will figure out that something is wrong and
initialize the controller the way it wants. */
static void
disable_controller (int unit)
{
outb (FDC_VFO_REGISTER, old_vfo);
outb (FDC_CONTROL_REGISTER, unit);
}
/* Floppy disk controller commands. */
/* Wait for the controller to be ready to send or receive data. This
returns 0 if the FDC is ready to receive, 1 if the FDC has data to
send, or -1 if the FDC does not respond. */
static int
controller_wait ()
{
int count, data;
count = FDC_STATUS_RETRIES;
while (count--)
{
data = inb (FDC_STATUS_REGISTER);
if (data & FDC_STATUS_REQUEST_FOR_MASTER)
return ((data & FDC_STATUS_DATA_OUT) ? 1 : 0);
}
return -1;
}
/* Issue one floppy disk command. out_count bytes of data from
out_data are sent to the FDC and in_count bytes are read in. If
in_count is zero, then in_data may be null. Returns 0 if the
operation was successful, or -1 if a timeout occured. */
static int
issue_command (char *out_data, int out_count,
char *in_data, int in_count)
{
while (out_count--)
{
if (controller_wait () != 0)
goto error;
outb (FDC_DATA_REGISTER, *out_data);
out_data++;
}
while (in_count--)
{
if (controller_wait () != 1)
goto error;
*in_data = inb (FDC_DATA_REGISTER);
in_data++;
}
return 0;
error:
return -1;
}
/* Specific fdc commands. Refer to ftape-regs.h for more detail on
these commands. */
/* Specify timing parameters. Accepts hut (head unload time), srt
(seek rate time), hlt (head load time), and (nd) non-dma. Returns
0 if no error, -1 on error. */
static int
specify (int hut, int srt, int hlt, int nd)
{
char cmd[3];
cmd[0] = FDC_SPECIFY;
cmd[1] = (srt << 4) | hut;
cmd[2] = (hlt << 1) | nd;
return (issue_command (cmd, 3, 0, 0));
}
/* Sense drive status. Given a unit, set the drive status. Returns 0
for no error. */
static int
sense_drive_status (int unit, int *st3)
{
int result;
char out[2], in[1];
out[0] = FDC_SENSE_DRIVE_STATUS;
out[1] = unit;
result = issue_command (out, 2, in, 1);
if (result)
return result;
else
{
*st3 = in[0] & 0xff;
return 0;
}
}
/* Return the interrupt status. Returns 0 for success. */
static int
sense_interrupt_status (int *st0, int *pcn)
{
int result;
char out[1];
char in[2];
out[0] = FDC_SENSE_INTERRUPT_STATUS;
result = issue_command (out, 1, in, 2);
if (result)
return result;
else
{
*st0 = in[0] & 0xff;
*pcn = in[1] & 0xff;
return 0;
}
}
/* Recalibrate and wait until recalibration completed. (Returns 0 for
success). */
static int
recalibrate (int unit)
{
int result;
char out[2];
int st0, pcn;
int count;
result = specify_recalibrate ();
if (result != 0)
{
printf ("fdtape: recalibrate,specify timed out.\n");
return result;
}
out[0] = FDC_RECALIBRATE;
out[1] = unit;
result = issue_command (out, 2, 0, 0);
if (result != 0)
{
printf ("fdtape: recalibrate, recalibrate timed out.\n");
return result;
}
for (count = RECALIBRATE_RESULT_RETRIES; count >= 0; count--)
{
result = interrupt_wait (unit, RECALIBRATE_TIMEOUT);
if (result != 0)
{
printf ("fdtape: recalibrate, interrupt(inner) timeout\n");
return result;
}
result = sense_interrupt_status (&st0, &pcn);
if (result != 0)
{
printf ("fdtape: recalibrate, sis timeout\n");
return result;
}
if (st0 & ST0_SEEK_END)
break;
}
if (count < 0)
{
printf ("fdtape: recalibrate, seek interrupt timeout\n");
return -1;
}
#if 0
result = interrupt_wait (unit, RECALIBRATE_TIMEOUT);
if (result != 0)
{
printf ("fdtape: recalibrate, interrupt timed out.\n");
return result;
}
result = sense_interrupt_status (&st0, &pcn);
if (result != 0)
{
printf ("fdtape: recalibrate, sis timed out.\n");
return result;
}
#endif
unit_data[unit].pcn = pcn;
result = specify_normal ();
if (result != 0)
{
printf ("fdtape: recalibrate, specify(2) timed out.\n");
return result;
}
/* We don't really care if the tape drive responded or not (it
really shouldn't respond. */
return 0;
}
/* Issue a tape command. Seeks command tracks from the current track. */
static int
tape_command (int unit, int command)
{
int result;
char out[3];
int st0, pcn;
int destination;
int count;
/* printf ("fdtape: tape_command (%d) %d\n", unit, command); */
fdt_sleep (MILLISECOND);
/* Figure out where to seek to. Seek toward zero if possible to
keep these as small integers. */
if (unit_data[unit].pcn >= command)
destination = unit_data[unit].pcn - command;
else
destination = unit_data[unit].pcn + command;
unit_data[unit].pcn = destination;
out[0] = FDC_SEEK;
out[1] = unit;
out[2] = destination;
result = issue_command (out, 3, 0, 0);
if (result != 0)
{
printf ("fdtape: tape_command, seek command error (%d)\n",
command);
{
int real_old_vfo = old_vfo;
int res;
/* Try hitting the fdc over the head. */
reset_controller (tape_unit, 0);
res = specify_normal ();
printf ("fdtape: seek command error FDC reset didn't even work\n");
old_vfo = real_old_vfo;
}
return result;
}
for (count = SEEK_RESULT_RETRIES; count >= 0; count--)
{
result = interrupt_wait (unit, SEEK_TIMEOUT);
if (result != 0)
{
printf ("fdtape: tape_command, seek(inner) timeout\n");
return result;
}
result = sense_interrupt_status (&st0, &pcn);
if (result != 0)
{
printf ("fdtape: seek, sis timeout\n");
return result;
}
if (st0 & ST0_SEEK_END)
break;
}
if (count < 0)
{
printf ("fdtape: tape_command, seek interrupt timeout\n");
return -1;
}
/* Verify that we seek to the proper track. */
if (pcn != destination)
{
unit_data[unit].pcn = pcn;
printf ("fdtape: tape_command, seek to incorrect track.\n");
return -1;
}
return 0;
}
/* Query the drive about its status. The command is sent and
result_length bits of status are returned (2 extra bits are read
for start and stop). */
static int
report_status_operation (int unit, int *status,
int command, int result_length)
{
int i, st3;
int result;
tape_command (unit, QIC_REPORT_NEXT_BIT);
tape_command (unit, QIC_REPORT_NEXT_BIT);
tape_command (unit, QIC_REPORT_NEXT_BIT);
tape_command (unit, QIC_REPORT_NEXT_BIT);
result = tape_command (unit, command);
if (result != 0)
{
printf ("fdtape: report_drive_status: tape_command timeout\n");
return result;
}
fdt_sleep (MILLISECOND);
result = sense_drive_status (unit, &st3);
if (result != 0)
{
printf ("fdtape: report_drive_status: sds timeout\n");
return result;
}
if (!(st3 & ST3_TRACK_0))
{
#if 0
printf ("fdtape: report_drive_status: drive doesn't respond\n");
#endif
return -1;
}
*status = 0;
for (i = 0; i < result_length + 1; i++)
{
result = tape_command (unit, QIC_REPORT_NEXT_BIT);
if (result != 0)
{
printf ("fdtape: report_drive_status: report next bit timeout\n");
return result;
}
fdt_sleep (MILLISECOND);
result = sense_drive_status (unit, &st3);
if (result != 0)
{
printf ("fdtape; report_drive_status: sds(2) timeout\n");
return result;
}
*status >>= 1;
if (i < result_length)
*status |= ((st3 & ST3_TRACK_0) != 0) << result_length;
else if (!(st3 & ST3_TRACK_0))
{
printf ("fdtape: report_status, stop bit not present.\n");
return -1;
}
}
tape_command (unit, QIC_REPORT_NEXT_BIT);
return 0;
}
/* Status and error handling. */
/* List of error codes, and their fatalness. */
struct _fdtape_error {
char *message; /* Text describing the error. */
int fatal; /* Non-zero if the error is fatal. */
};
/* These error messages were taken from the Quarter-Inch Cartridge
Drive Standards, Inc. document titled ``Common Command Set
Interface Specification for Flexible Disk Controller Based
Minicartridge Tape Drives,'' document QIC-117 Revision B, 6 Dec 89.
For more information, contact,
Quarter-Inch Cartridge Drive Standards, Inc.
311 East Carrillo Street
Santa Barbara, California 93101
Telephone (805) 963-3853
Fax (805) 962-1541 */
static struct _fdtape_error fdtape_errors[] = {
/* 0*/ { "No error", 0, },
/* 1*/ { "Command Received while Drive Not Ready", 0, },
/* 2*/ { "Cartridge Not Present or Removed", 1, },
/* 3*/ { "Motor Speed Error", 1, },
/* 4*/ { "Motor Speed fault", 1, },
/* 5*/ { "Cartridge Write Protected", 1, },
/* 6*/ { "Undefined or Reserved Command Code", 1, },
/* 7*/ { "Illegal Track address specified for Seek", 1, },
/* 8*/ { "Illegal Command in Report Subcontext", 0, },
/* 9*/ { "Illegal Entry into a Diagnostic Mode", 1, },
/*10*/ { "Broken Tape Detected", 1, },
/*11*/ { "Warning--Read Gain Setting Error", 1, },
/*12*/ { "Command Received while Error Status Pending", 1, },
/*13*/ { "Command Received while New Cartridge Pending", 1, },
/*14*/ { "Command Illegal or Undefined in Primary Mode", 1, },
/*15*/ { "Command Illegal or Undefined in Format Mode", 1, },
/*16*/ { "Command Illegal or Undefined in Verify Mode", 1, },
/*17*/ { "Logical Forward not a Logical BOT in Format Mode", 1, },
/*18*/ { "Logical EOT before all Segments generated", 1, },
/*19*/ { "Command Illegal when Cartridge Not Referenced", 1, },
/*20*/ { "Self-Diagnostic Failed", 1, },
/*21*/ { "Warning EEPROM Not Initialized, defaults set", 1, },
/*22*/ { "EEPROM Corrupted or Hardware Failure", 1, },
/*23*/ { "Motion Time-out error", 1, },
/*24*/ { "Datat Segment Too Long--Logical Forward or Pause", 1, },
/*25*/ { "Transmit Overrun", 1, },
/*26*/ { "Power On Reset Occurred", 0, },
/*27*/ { "Software Reset Occurred", 0, },
/*28*/ { "Diagnostic Mode 1 Error", 1, },
/*29*/ { "Diagnostic Mode 2 Error", 1, },
/*30*/ { "Command Received During Non-Interruptable Process", 1, },
/*31*/ { "Rate Selection error", 1, },
/*32*/ { "Illegal command while in high speed mode", 1, },
/*33*/ { "Illegal seek segment value", 1, },
};
#define MAXIMUM_ERROR 33
/* Report the current drive status. */
static int
report_drive_status (int unit, int *status)
{
int result, count;
int command;
int error;
for (count = 3; count >= 0; count--)
{
result = report_status_operation (unit, status,
QIC_REPORT_DRIVE_STATUS,
8);
if (result == 0)
break;
}
/* If there is an error pending and new cartridge present. */
if ((*status & QIC_STATUS_READY) != 0
&& (*status & QIC_STATUS_ERROR) != 0
&& (*status & QIC_STATUS_NEW_CARTRIDGE) == 0)
{
result = report_error (unit, &error);
if (result == 0)
{
command = error >> 8;
error &= 0xff;
if (error < 0 || error > MAXIMUM_ERROR)
printf ("fdtape: tape error out of range (%d).\n",
error);
else
{
printf ("fdtape: error %d(%d): \"%s\"\n",
error, command, fdtape_errors[error].message);
if (fdtape_errors[error].fatal)
result = EIO;
}
}
/* Get the new drive status. */
if (result == 0)
result = report_status_operation (unit, status,
QIC_REPORT_DRIVE_STATUS,
8);
}
return result;
}
static int
report_error (int unit, int *error)
{
return (report_status_operation (unit, error,
QIC_REPORT_ERROR_CODE,
16));
}
static int
report_configuration (int unit, int *configuration)
{
return (report_status_operation (unit, configuration,
QIC_REPORT_DRIVE_CONFIGURATION,
8));
}
static int
report_rom_version (int unit, int *version)
{
return (report_status_operation (unit, version,
QIC_REPORT_ROM_VERSION,
8));
}
static int
report_vendor_id (int unit, int *id)
{
return (report_status_operation (unit, id,
QIC_REPORT_VENDOR_ID,
16));
}
/* Begin a read. */
static int
read_id (int unit, struct _fdt_id *location)
{
int result;
char out[2];
char in[7];
out[0] = FDC_READ_ID;
out[1] = unit;
result = issue_command (out, 2, 0, 0);
if (result != 0)
{
printf ("fdtape: read_id, error issuing command\n");
return result;
}
result = interrupt_wait (unit, READ_TIMEOUT);
if (result != 0)
{
printf ("fdtape: read_id, timeout\n");
return result;
}
result = issue_command (0, 0, in, 7);
if (result != 0)
{
printf ("fdtape: read_id, result phase error\n");
return result;
}
#if 0
printf ("fdtape: read_id: 0:0x%x, 1:0x%x, 2:0x%x, 3:0x%x, 4:0x%x, 5:0x%x, 6:0x%x\n",
in[0], in[1], in[2], in[3], in[4], in[5], in[6]);
#endif
/* Check return for normal. */
if ((in[0] & ST0_CODE_MASK) == ST0_NORMAL)
{
location->cylinder = in[3] & 0xff;
location->head = in[4] & 0xff;
location->sector = in[5] & 0xff;
}
else
{
printf ("fdtape: read_id, error st0=0x%x, st1=0x%x, st2=0x%x\n",
in[0] & 0xff, in[1] & 0xff, in[2] & 0xff);
return -1;
}
return 0;
}
#endif NFDTAPE > 0