|
|
Power 6/32 Unix version 1.21
/*
** Author: John R. Franks
** Date: 16-Aug-85
**
** This driver is used to control a Tapemaster tape controller. The
** Tapemaster controller is a particularly unreasonable controller to work
** with.
**
** We implement both a block device and a raw device with this driver.
** (of course) Each device has it's own special requirement as defined below.
**
** The block device handles files which consist of a series of 1k byte
** blocks. It can be read or written to like any random access device, except,
** you can not read past the last position written to on tape. The reason
** for this is entirly mechanical. Tape drive positioning is not accurate
** enough to guarantee that we will not write over a portion of a following
** record on the tape. I.e. If we had five known blocks on a tape and we
** rewrote the fourth block on the tape, then the fifth block probably
** had it's leading gap or the beginning of it's data overwritten. Clearly
** the data can not be trusted, so, we just make it a rule that writting
** to the tape in any form also defines the end of volume.
**
** The block device will seek automatically to the next block number if
** the tape is mispositioned before the read is done.
**
** In general the block device should not be used except maybe to
** read an exact disk image off of it.
**
** The raw device is responsible for large reads and writes, and all
** ioctl control commands. Each command has to keep the tape in a consistant
** state so that they will not be messed up by the sequence of user requests.
** As an example: if a user back spaces after a write an end of volume record
** will be written before the spacing occurs so that we will be able to
** find the end of the file subsequent read operatons.
**
** The raw device also has the same restrictions on writes as the block
** device.
**
** The ioctls supported by the system are:
**
** internal name value comments
** DO_W_FM 0 Write a file mark to tape
** DO_SFMF 1 search for a file mark in the forward direction
** DO_SFMB 2 search fo a file mark in the backward direction.
** DO_SPF 3 space forward one record
** DO_SPB 4 space backward one record
** DO_RWTA 5 rewind and wait
** DO_RWUN 6 rewind and unload tape
** DO_STAT 7 get drive status
** DO_RWOV 8 rewind overlapped
** DO_WAIT 9 wait for rewind to complete
*/
/* Includes */
#include "cy.h"
#if NCY > 0
int cydebug = 0;
#include "../h/param.h"
#include "../h/systm.h"
#include "../machine/mtpr.h"
#include "../h/vm.h"
#include "../h/buf.h"
#include "../machine/pte.h"
#include "../h/file.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/signal.h"
#include "../h/uio.h"
#include "../h/ioctl.h"
#include "../h/mtio.h"
#include "../h/errno.h"
#include "../h/cmap.h"
#include "../vba/vbavar.h"
#include "../vba/cipher.h"
/* Definitions */
#define MAXCONTROLLERS 4
#define MAX_BLOCKSIZE (TBUFSIZ*NBPG)
#define NUM_UNIT (NCY * 4)
#define TRUE 1
#define FALSE 0
#define NOERROR 0
#define RETRY 1
#define EXTEND 2
#define FATAL 3
#define MAINTAIN_POSITION 0
#define DONT_MAINTAIN_POSITION 1
#define PROCESSED 0x80000000
#define SLEEPING 0x80000000
#define b_cmd av_back /* only unused word in request */
/*
** ioctl command offset definitions. (so we can issue ioctls internally)
*/
#define DO_W_FM 0
#define DO_SFMF 1
#define DO_SFMB 2
#define DO_SPF 3
#define DO_SPB 4
#define DO_RWTA 5
#define DO_RWUN 6
#define DO_STAT 7
#define DO_RWOV 8
#define DO_WAIT 9
#define DO_WEOV 10
#define DO_RRD 11
#define DO_RWT 12
#define DO_BRD 13
#define DO_BWT 14
/*
** Declarations for ioctl subroutines (needed for jump table below.
*/
extern int cywrite_filemark(), cysearch_fm_forw(), cysearch_fm_back();
extern int cy_space_forw(), cy_space_back(), cyrewind_tape_ta();
extern int cyrewind_tape_unl(), cydrive_status(), cyrewind_tape_ov();
extern int cyraw_read(), cyraw_write(), cybuf_read(), cybuf_write();
extern int cywait_until_ready(), cywrite_0_fm(), cywrite_1_fm();
extern int cywrite_2_fm(), cyno_op(), cywrite_eov();
/*
** Jump table for ioctl functions (used in cystart).
*/
static int (*cmd_tbl[15])() = {
cywrite_filemark, cysearch_fm_forw, cysearch_fm_back, cy_space_forw,
cy_space_back, cyrewind_tape_ta, cyrewind_tape_unl, cydrive_status,
cyrewind_tape_ov, cywait_until_ready, cywrite_eov, cyraw_read,
cyraw_write, cybuf_read, cybuf_write
};
/* Variables */
/* Autoconfigure entry point definitions */
extern int cyprobe(), cyslave(), cyattach(), cydgo();
/* physio routines */
extern unsigned cyminsize();
/* Define driver structures for UNIX */
extern char cy0utl[];
#if NCY > 0
extern char cy1utl[];
#endif
static fmt_scp *scp_ptrs[MAXCONTROLLERS] = {
(fmt_scp *)0xc0000c06, (fmt_scp *)0xc0000c16,
};
struct vba_ctlr *cyminfo[NCY];
struct vba_device *cydinfo[NUM_UNIT];
struct vba_driver cydriver =
{
cyprobe, cyslave, cyattach, cydgo, (long *)scp_ptrs,
"cipher", cydinfo, "", cyminfo
};
/* Define data structures for controllers */
typedef struct {
struct pte *map;
char *utl;
int (*interupt_path)();
label_t environ; /* Environment variable for longjmps */
struct buf *my_request;
struct buf *wakeup_request;
short bs; /* buffer size */
fmt_ccb ccb; /* Channel control blocks */
fmt_scb scb; /* System configuration blocks */
fmt_tpb tpb; /* Tape parameter blocks */
fmt_tpb last; /* Tape parameter blocks */
fmt_tpb noop; /* Tape parameter blocks */
long rawbuf[MAX_BLOCKSIZE/sizeof(long)+1];
} ctlr_tab;
extern int cy_normal_path();
ctlr_tab ctlr_info[NCY] = {
{CY0map, cy0utl, cy_normal_path}
#if NCY > 1
,{CY1map, cy1utl, cy_normal_path}
#if NCY > 2
error /* Only 2 controllers can be used at this time */
#endif
#endif
};
/* information needed for each drive */
typedef struct {
int (*cleanup)();
struct buf u_queue;
struct buf rawbp;
long blkno;
long file_number;
short last_control;
short last_status;
short last_resid;
unsigned long bad_count;
unsigned control_proto: 16;
unsigned error_count : 8;
unsigned open : 1;
unsigned eof : 1;
unsigned bot : 1;
unsigned eot : 1;
char *message;
}unit_tab;
unit_tab unit_info[NUM_UNIT];
static char *long_block_msg =
"\ncy%d: Block contained %d bytes: not %d bytes.\n";
/*
** Cyprobe checks to see if a controller is present on the VERSAbus.
** An attempt is made to read from the controller's first register, if
** a buss error does not occur then the controller is assumed to be
** there.
**
** If the controller responds to the read request, then we go ahead
** and initialize the controller for UNIX's use. If no problems were
** reported during system initialization, then we return TRUE to the
** system to indicate that the controller is there and everything is OK.
*/
cyprobe(ctlr_vaddr)
register caddr_t ctlr_vaddr;
{
static int ctlr = -1;
ctlr++;
if (!badcyaddr(ctlr_vaddr + 1))
return cy_init_controller(ctlr_vaddr, ctlr, 1);
return FALSE;
}
/*
** Cy_init_controller is called to initialize the controller after the
** controller is reset or during Autoconfigure. All of the system control
** blocks are initialized and the controller is asked to configure itself
** for later use.
**
** If the print value is true cy_first_TM_attention will anounce
** the type of controller we are (Tapemasher) and will print the size
** of the internal controller buffer.
*/
cy_init_controller(ctlr_vaddr, ctlr, print)
register caddr_t ctlr_vaddr;
register int ctlr;
register int print;
{
cy_init_sys_config_ptr(ctlr);
cy_init_sys_config_blk(ctlr);
cy_init_channel_control_blk(ctlr);
return cy_first_TM_attention(ctlr_vaddr, ctlr, print);
}
/*
** Cyslave checks to see if a drive is attached to a controller.
** There are no signals, on the serial buses from the tape drive to the
** controller, to indicate that a drive is physically present on the
** buss. We can only tell that a drive is there if a tape is loaded
** on the drive and the drive is placed online.
**
** Since it would be ridiculus to force system operators to load
** tapes on every tape drive on the system before every boot operation,
** we simply indicate that the drive is there every time we are asked.
**
** In theory the system should be configured according to the
** hardware configuration anyway and should not present a problem.
*/
cyslave(vba_device_info, ctlr_vaddr)
register struct vba_device *vba_device_info;
register caddr_t ctlr_vaddr;
{
/*
* assume tape is connected because there is
* no way on earth to tell if the drive is connected or not.
*/
return TRUE;
}
/*
** cyattach is used to add a drive to our internal tables.
*/
cyattach(dev_info)
struct vba_device *dev_info;
{
register unit_tab *u_info = &unit_info[dev_info->ui_unit];
register struct buf *ctlr_queue = &dev_info->ui_mi->um_tab;
register struct buf *unit_queue = ctlr_queue->b_forw;
register struct buf *start_queue = unit_queue;
/* Add unit to controllers queue */
if(ctlr_queue->b_forw == NULL) {
ctlr_queue->b_forw = &u_info->u_queue;
u_info->u_queue.b_forw = &u_info->u_queue;
}
else {
while(unit_queue->b_forw != start_queue)
unit_queue = unit_queue->b_forw;
u_info->u_queue.b_forw = start_queue;
unit_queue->b_forw = &u_info->u_queue;
}
u_info->cleanup = cyno_op;
u_info->last_status = 0;
u_info->last_control = 0;
u_info->file_number = 0;
u_info->bad_count = 0;
u_info->blkno = 0;
u_info->open = FALSE;
u_info->bot = TRUE;
u_info->eot = FALSE;
u_info->eof = FALSE;
u_info->message = NULL;
}
/*
** Historic routine left over from VAX port but the definition is still
** hanging around.
*/
cydgo()
{
}
/*
** cy_init_sys_config_ptr initializes the Tapemaster system configuration
** pointer. The Tapemaster controller requires it's own system pointer
** in low memory. The absolute addresses are 0xc06 for controller #1, and
** 0xc16 for controller #2. (The space definitions are in locore.s if you
** want to add anther controller to the system. (good luck))
**
** This routine sets the correct page to give kernel write access,
** loads in the appropriate values, and then resets the page to kernel read
** only access to prevent other routines from stepping all over the scp
** and causing obscure tape problems.
**
** The format of the system configuration pointer is as follows:
**
** 8 bits 8 bits 32 bits (20 that count)
** +----------+----------+----------------+
** | bus size | unused | Pointer to scb |
** +----------+----------+----------------+
*/
cy_init_sys_config_ptr(ctlr)
register int ctlr;
{
register int *pte_ptr;
register fmt_scp *SCP = scp_ptrs[ctlr];
/* Set the page to kernel write access */
pte_ptr = (int *)vtopte(0, btop(SCP));
*pte_ptr &= ~PG_PROT; /* clear all protection bits */
*pte_ptr |= PG_KW /* allow kernal writes */;
mtpr(SCP, TBIS);
/* load the correct values in the scp */
SCP->bus_size = _16_BITS;
load_mbus_addr(&ctlr_info[ctlr].scb, SCP->scb_ptr);
/* Give read only privialages to the kernel */
*pte_ptr &= ~PG_PROT; /* clear all protection bits */
*pte_ptr |= PG_KR; /* allow only kernal */
mtpr(SCP, TBIS);
}
/*
** cy_init_sys_config_blk loads the appropriate values into the
** system configuration block for the Tapemaster controller.
**
** This data structure does not contain any useful information
** for us. The only possible use for this block is for a
** consistancy check by the controller itself using the fixed value.
** but that could have been done elsewhere. However the controller will
** not run without it so we maintain this structure here.....
**
** The format of the system configuration block is as follows:
**
** 8 bits 8 bits 32 bits (20 that count)
** +-----------+----------+----------------+
** | must be 3 | unused | Pointer to ccb |
** +-----------+----------+----------------+
*/
cy_init_sys_config_blk(ctlr)
register int ctlr;
{
register fmt_scb *SCB = &ctlr_info[ctlr].scb;
SCB->fixed_value = 0x3;
/* set pointer to the channel control block */
load_mbus_addr(&ctlr_info[ctlr].ccb, SCB->ccb_ptr);
}
/*
** Cy_init_channel_control_blk is used to load the initial values into
** the ccb structures for the controller.
**
** The format of the channel control block is as follows:
**
** 8 bits 8 bits 32 bits (20 that count)
** +-----------+----------+----------------+
** | CCW | gate | Pointer to tpb |
** +-----------+----------+----------------+
**
** the CCW field is used to control interupt operations. If the field is
** equal to 11(hex) then interupts are enabled, if it contains a 9(hex) then
** the Tapemaster controller is instructed to stop interupting (it will
** flood the system with interupts until it is instructed to stop!)
**
** The gate field is used to syncronize the processor operations and
** the controller. We close it when an operation is started,
** It is opened by the controller when the operation is done.
*/
cy_init_channel_control_blk(ctlr)
register int ctlr;
{
register fmt_ccb *CCB = &ctlr_info[ctlr].ccb;
CCB->ccw = CLEAR_INTERUPT;
CCB->gate = GATE_OPEN;
/* set pointer to the tape parameter block */
load_mbus_addr(&ctlr_info[ctlr].tpb, CCB->tpb_ptr);
}
/*
** Cy_first_TM_attention is used to issue the very first command
** after boot or after the controller is reset. This case is special
** since we 1) should not interupt duing this sequence, 2) really need
** to issue two commands, and 3) we need to get the controller's internal
** buffer size for future reference.
**
** The print flag is used so that we only print out the greeting
** message during Autoconfigure time. (it would be obnoxious if it
** printed every time the drive times out.)
**
** The first NOOP command is issued to get the drive's attention.
** The tpb is never even looked during the first attention.
**
** The second command actually configures the controller and returns
** the internal buffersize for buffered I/O.
*/
cy_first_TM_attention(ctlr_vaddr, ctlr, print)
register caddr_t ctlr_vaddr;
register int ctlr, print;
{
register ctlr_tab *c_info = &ctlr_info[ctlr];
/* set command to be CONFIGURE */
c_info->tpb.cmd = NO_OP;
c_info->tpb.control = CW_16bits;
c_info->ccb.gate = GATE_CLOSED;
CY_ATTENTION(ctlr_vaddr); /* execute! */
if(cywait(&c_info->ccb) || (c_info->tpb.status & CS_ERm)) {
printf("Tapemaster controller time-out during initialization!\n");
return FALSE;
}
c_info->tpb.cmd = CONFIG;
c_info->tpb.control = CW_16bits;
c_info->ccb.gate = GATE_CLOSED;
CY_ATTENTION(ctlr_vaddr); /* execute! */
if(cywait(&c_info->ccb) || (c_info->tpb.status & CS_ERm)) {
cyprint_err("Tapemaster configuration failure",
0, c_info->tpb.status);
return FALSE;
}
uncache(&c_info->tpb.count);
c_info->bs = MULTIBUS_SHORT(c_info->tpb.count);
if(print)
printf("Tapemaster with %dkb buffer: controller #",
c_info->bs/1024);
return TRUE;
}
/*
** Cyopen is called every time a process opens the tape for reading
** or writting. Tape drives are single access in that only one process
** can have the drive open at any one time. It is responsibility of
** cyopen to keep track of whether the drive is already open and to
** refuse access to everybody else on the system.
**
** The other functions of cyopen are to make sure a tape is mounted
** and the drive is on-line, If the drive is currently rewinding we
** should wait for it to complete before returning, if the drive is
** at load point then our internal file pointers are reset, and to
** set up a proto-type control word for use during later tape operations.
**
** The control proto-type is set up in open to save time later on.
** It contains all the invariant information the controller needs to
** access a drive. This information includes the unpacked unit number,
** (See the UNIT macro below), the drive speed/density (always set at the
** drive), the buss width (always 16 bit) and the interupts emnable bit
** (always enabled).
*/
/* macro to pack the unit number into Tapemaster format */
#define UNIT(d) (((cydinfo[CYUNIT(d)]->ui_slave & 1) << 11) | \
((cydinfo[CYUNIT(d)]->ui_slave & 2) << 9) | \
((cydinfo[CYUNIT(d)]->ui_slave & 4) >> 2))
cyopen(dev, flag)
register int flag;
register dev_t dev;
{
register int status, unit = CYUNIT(dev);
register unit_tab *u_info = &unit_info[unit];
if (!(status = cyvalid_drive(unit))) {
u_info->control_proto = UNIT(dev) | CW_INTR | CW_16bits;
u_info->blkno = 0;
u_info->bad_count = 0;
u_info->eof = FALSE;
u_info->open = TRUE;
if(status = cy_open_error(dev,flag))
u_info->open = FALSE;
}
return status;
}
/*
** cyvalid_drive is called to make sure a drive is attached to
** to the system and, if it is, if it is already open by another process.
** If the drive is already open busy status is returned, if it is not
** attached to the system then non-existant device is returned, otherwise,
** zero is returned to indicate no error.
*/
cyvalid_drive(unit)
register int unit;
{
/* if unit is less than maximum possible unit */
if (unit < NUM_UNIT)
/* if the drive is attached */
if (cydinfo[unit])
/* if drive is not already open */
if(!unit_info[unit].open)
return NOERROR;
else
return EBUSY;
return ENXIO;
}
/*
** Cy_open_error is called by open after verifing that the drive
** is eligable to be opened. The hardware status is checked and if
** any errors are found (i.e. not online, write protected when opened for
** writes, and opening past the end of tape marker) then the error number
** is returned.
**
** As part of the sequence we must wait around if the drive is online
** and rewinding until the rewind is done or the operator takes the drive
** offline. During the wait we poll the drive every 5 seconds until
** either of the above conditions are met.
*/
cy_open_error(dev,flag)
register int flag;
register dev_t dev;
{
register int unit = CYUNIT(dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register ctlr_tab *c_info = &ctlr_info[cydinfo[unit]->ui_ctlr];
cycmd(dev, DO_WAIT, 1);
if(!(u_info->last_status & CS_OL))
return ENXIO;
if((flag & FWRITE) && (u_info->last_status & CS_P)) {
uprintf("\ncy%d: Tape is write protected!\n", unit);
return ENXIO;
}
if(u_info->last_status & CS_LP) {
u_info->file_number = 0;
u_info->bot = TRUE;
u_info->eof = u_info->eot = FALSE;
}
return NOERROR;
}
/*
** Cyclose is called every time a process closes a tape file, exits,
** or is otherwise removed for the run queue. We take this oportunity to
** mark the drive closed, write end of volume records (two tape marks), and
** rewind the tape (if requested that we do so).
**
** Take note that we write the end of volume records by spacing backwards.
** This is done because the all the commands keep track of the necessary
** special cases. It just so happens that spacing back after a write will
** generate an end of volume record before spacing back. After the space back
** is called we space forward to position ourselfs between the two filemarks
** in anticipation of further writes to tape.
**
** Also, the rewind logic takes care of end of volume records so if
** we just issue a overlapped rewind request if we were opened using
** the rewinding special file.
*/
cyclose(dev, flag)
register dev_t dev;
register flag;
{
register int unit = CYUNIT(dev);
register unit_tab *u_info = &unit_info[unit];
if(u_info->last_status & CS_OL)
if((flag & FWRITE) && (minor(dev) & T_NOREWIND))
cycmd(dev, DO_WEOV, 1);
else if(!(minor(dev) & T_NOREWIND))
cycmd(dev, DO_RWOV, 1);
if(u_info->bad_count != 0) {
u_info->bad_count *= 889;
uprintf("\ncy%d: Warning - %d.%dcm of tape were used for recovering bad spots.\n", unit, u_info->bad_count/100, u_info->bad_count%100);
u_info->bad_count = 0;
}
u_info->open = 0;
}
/*
** Cycmd is used intrernally to implement all the ioctl functions
** that are needed by the driver. We duplicate the code in physio
** that is used for syncronizing the processes (sleep / wakeup) so
** that we can treat our internal command requests exactly like
** regular reads and writes. They get put on the controller queue,
** start processes them and iodone is called to wake us up on completion.
**
** We don't call physio directly because it expects data to be moved
** and has a lot more overhead than we really need.
*/
cycmd(dev, command, count)
register dev_t dev;
register long command;
register int count;
{
register int unit = CYUNIT(dev);
register unit_tab *u_info = &unit_info[unit];
register unsigned short error;
register int priority = spl3();
while (u_info->rawbp.b_flags & B_BUSY) {
u_info->rawbp.b_flags |= B_WANTED;
sleep(&u_info->rawbp, PRIBIO+1);
}
splx(priority);
/* load the request queue element */
u_info->rawbp.b_error = 0;
u_info->rawbp.b_dev = dev;
u_info->rawbp.b_cmd = (struct buf *)command;
u_info->rawbp.b_bcount = count;
u_info->rawbp.b_flags = B_PHYS | B_BUSY;
queue_request(&u_info->rawbp, &u_info->u_queue, cydinfo[unit]->ui_mi);
/* wait for operation to complete */
while(!(u_info->rawbp.b_flags & B_DONE))
sleep(&u_info->rawbp, PRIBIO);
u_info->rawbp.b_flags &= ~(B_PHYS | B_BUSY);
if(u_info->rawbp.b_flags & B_WANTED)
wakeup (&u_info->rawbp);
return geterror(&u_info->rawbp);
}
/*
** Check the validity of the request and then place
** the request on the controller's request queue if it is ok.
**
** Take note that the only validity check is the block size.
** all other checking, such as, drive number, online, controller attached,
** is done in the open routine.
**
** If the drive dropped offline this will be notced int the
** start / interupt routine (depending on when it dropped offline).
*/
cystrategy(request)
register struct buf *request;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[unit];
register struct buf *unit_queue;
/* check the validity of the request */
if (request->b_bcount <= MAX_BLOCKSIZE) {
/* place request on queue and start it if everything is ok */
unit_queue = &u_info->u_queue;
buf_setup(request, MAX_BLOCKSIZE);
if(request->b_flags & B_PHYS)
if(request->b_flags & B_READ)
request->b_cmd = (struct buf *)DO_RRD;
else
request->b_cmd = (struct buf *)DO_RWT;
else
if(request->b_flags & B_READ)
request->b_cmd = (struct buf *)DO_BRD;
else {
request->b_cmd = (struct buf *)DO_BWT;
}
queue_request(request, unit_queue, cydinfo[unit]->ui_mi);
return;
}
uprintf("\ncy%d: Maximum block size is %dk!\n", unit, MAX_BLOCKSIZE/1024);
request->b_error = EIO;
request->b_resid = request->b_bcount;
request->b_flags |= B_ERROR;
iodone(request);
}
/*
** The routines below are used to handle a unit's request queue.
** The queue is a linked list of buf structures. The linkage is as follows:
**
** +------------------------------------------------+
** | v
** | +-----------+ +-----------+ +-----------+
** | | av_forw |------>| av_forw |--~ ~-->| av_forw |-->NULL
** | +-----------+ +-----------+ +-----------+
** +-| av_back | | ......... | | ......... |
** +-----------+ +-----------+ +-----------+
** | ......... | First queue Last queue
** +-----------+ element element
** head of unit queue
** (unit_tab[unit].queue)
*/
/*
** Queue_request places a queue element on the end of a unit's
** request queue.
*/
queue_request(request, unit_queue, vba_ctlr_info)
register struct buf *request;
register struct buf *unit_queue;
struct vba_ctlr *vba_ctlr_info;
{
register int priority = spl3();
request->av_forw = NULL;
if (unit_queue->av_forw == NULL)
unit_queue->av_forw = request;
else
unit_queue->av_back->av_forw = request;
unit_queue->av_back = request;
cystart(vba_ctlr_info, request, priority);
}
/*
** Dequeue_request removes the first element in a unit's request queue.
*/
dequeue_request(unit_queue)
register struct buf *unit_queue;
{
register int priority = spl3();
if ((unit_queue->av_forw = unit_queue->av_forw->av_forw) == NULL)
unit_queue->av_back = NULL;
splx(priority);
}
/*
** Cystart is called once for every request that is placed on a
** controller's queue. Start is responsible for fetching requests for
** a controller queue, starting the operation, and waiting for completion,
** and releasing the buf structure back to UNIX or cycmd, before fetching
** the next request.
**
** The controller's queue looks like this:
**
** +---------------------------------------+
** | |
** +-----------+ | +-----------+ +-----------+ |
** | b_forw |---+-->| b_forw |--~ ~-->| b_forw |--+
** +-----------+ +-----------+ +-----------+
** | b_back | | ......... | | ......... |
** +-----------+ +-----------+ +-----------+
** | ......... | First unit queue Last unit queue
** +-----------+ element element
** head of controller queue
** (cyminfo[ctlr].um_tab)
**
** To access the unit queues we simply get the controller's unit queue
** pointer and if anything is on the unit queue that we are pointing to
** we start that command, otherwise we get the next pointer and go on.
** we know to stop looking when we have gone through the entire list
** without starting any activity. We know we are at the end of the
** queue when the next pointer equals the original pointer int the queue.
**
** To provind a fair scheduling policy we simply repoint the controller's
** queue pointer to the next unit queue every time a command is started.
**
** If the controller is currently busy then we just return so that the
** calling routine can go to sleep until we are finished processing the
** request.
**
** Each buf structure has an index into a jump table loaded into
** it by strategy or cycmd depending on where the buff originated.
** this index is loaded so that start processing can continue without
** much delay, and so that each operation can have it's unique requirements
** satisfied in a clear and consistent manor.
**
** Each and every routine that is called by start is responsible for
** proper tape positioning, writting end of file marks correctly, error
** recovery, and whatever else may unique about the particular operation.
** Each routine calls cyexecute whenever it actually wants to issue a
** command to the controller. Each routine is also responsible for releasing
** the request when is is done with it.
*/
cystart(vba_ctlr_info, request, priority)
register struct vba_ctlr *vba_ctlr_info;
register struct buf *request;
{
struct buf *cyget_next();
extern int cystart_timeout();
register int unit = CYUNIT(request->b_dev);
register int ctlr = vba_ctlr_info->um_ctlr;
register struct buf *next, *ctlr_queue = &vba_ctlr_info->um_tab;
register unit_tab *u_info = &unit_info[unit];
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(ctlr_queue->b_active & SLEEPING) {
untimeout(cystart_timeout, ctlr_queue);
cystart_timeout(ctlr_queue);
}
if(ctlr_queue->b_active) {
sleep(request, PRIBIO-1);
if(request->b_flags & PROCESSED) {
if(u_info->message != NULL) {
uprintf("\ncy%d: %s!\n", unit, u_info->message);
u_info->message = NULL;
}
request->b_flags &= ~PROCESSED;
iodone(request);
return;
}
}
ctlr_queue->b_active = TRUE;
splx(priority);
c_info->my_request = request;
cydo_my_command(ctlr, ctlr_queue, c_info);
if(u_info->message != NULL) {
uprintf("\ncy%d: %s!\n", unit, u_info->message);
u_info->message = NULL;
}
request->b_flags &= ~PROCESSED;
iodone(request);
if((next = cyget_next(ctlr_queue)) != NULL)
wakeup(next);
else
ctlr_queue->b_active = FALSE;
}
/*
** Cystart_timeout wakes up the start routine after it's 3
** second wait time is up or when a new command enters the queue.
**
** The timer is used to give up the processor while all drives
** on the queue are rewinding and we need to wait for them to be dome.
** Without this feature we would hog the processor polling for the drives
** to be done.
*/
cystart_timeout(ctlr_queue)
register struct buf *ctlr_queue;
{
ctlr_queue->b_active &= ~SLEEPING;
wakeup(ctlr_queue);
}
/*
** Cydo_my command scans the request queues once for a
** particular controller and calls the appropriate processing routine
** each time we find a request that can be started.
**
** We return TRUE if a command is executed during this round. IF either
** the queue is empty or all commands on the queue are waiting for the drives
** to rewind we return FALSE.
*/
cydo_my_command(ctlr, ctlr_queue, c_info)
register struct buf *ctlr_queue;
register ctlr_tab *c_info;
{
struct buf *cyget_next();
register struct buf *unit_queue, *next;
while((next = cyget_next(ctlr_queue)) != NULL) {
if(ctlr_queue->b_forw->b_active & SLEEPING) {
ctlr_queue->b_active |= SLEEPING;
timeout(cystart_timeout, ctlr_queue, 1*60);
sleep(ctlr_queue, PRIBIO);
continue;
}
if(setjmp(&ctlr_info[ctlr].environ))
cydone(ctlr_queue);
else {
register int cmd=(int)(next->b_cmd);
(*cmd_tbl[cmd])(next, ctlr_queue);
}
if(next->b_flags & PROCESSED)
if(c_info->my_request != next)
wakeup(next);
else
return;
}
}
struct buf *cyget_next(ctlr_queue)
register struct buf *ctlr_queue;
{
register struct buf *request, *unit_queue, *next = NULL;
ctlr_queue->b_forw = ctlr_queue->b_forw->b_forw;
unit_queue = ctlr_queue->b_forw;
do {
if((request = unit_queue->av_forw) != NULL)
if(!(unit_queue->b_active & SLEEPING)) {
ctlr_queue->b_forw = unit_queue;
return request;
}
else
next = unit_queue;
unit_queue = unit_queue->b_forw;
} while(unit_queue != ctlr_queue->b_forw);
if(next != NULL) {
ctlr_queue->b_forw = next;
return next->av_forw;
}
return NULL;
}
/*
** Cydone is called by each routine that is thorugh processing a
** user request. It removes the request from our queues, counts down
** the number of active requests that we have in our queues and releases
** the request back to UNIX.
*/
cydone(ctlr_queue)
register struct buf *ctlr_queue;
{
register struct buf *unit_queue = ctlr_queue->b_forw;
register struct buf *request = unit_queue->av_forw;
register int unit = CYUNIT(request->b_dev);
register ctlr_tab *c_info = &ctlr_info[cydinfo[unit]->ui_ctlr];
unit_queue->av_forw->b_flags |= PROCESSED;
dequeue_request(unit_queue);
}
/*
** All the routines between here and Cyintr are used to process the
** individual commands (read, write, rewind, ...) that can possibly be
** generated by the system.
**
** Each command is responsible for a few things. 1) Each has to keep
** track of special cases that are related to the individual command and
** the previous commands sequence, 2) each is required to call iodone when
** command is actually finished, 3) it must use cyexecute to actually
** start the controller, and 4) they are required to keep the tape in
** a consistant state so that other commands will not be messed up.
/*
*/
/*
** cyraw_read handles the read requests from the raw device (cyread).
**
** The special cases are:
** 1) we can not read after a write. (writting defines end of file)
** 2) reading past end of file returns 0 bytes;
*/
cyraw_read(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
register int addr, lock_flag, command, bytes;
if(u_info->cleanup != cyno_op) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
if(u_info->eof){
u_info->blkno = 0;
u_info->file_number++;
}
if(request->b_bcount > c_info->bs)
command = READ_TA, lock_flag = CW_LOCK;
else
command = READ_BU, lock_flag = 0;
u_info->blkno++;
addr = get_ioadr(request,c_info->rawbuf,c_info->map,c_info->utl);
cyexecute(command, request->b_bcount, addr, lock_flag, unit, 10, FALSE);
end_transfer(request, c_info->rawbuf, c_info->map, c_info->utl);
if((bytes = MULTIBUS_SHORT(c_info->tpb.rec_over)) > request->b_bcount) {
uprintf(long_block_msg, unit, bytes, request->b_bcount);
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
cydone(ctlr_queue);
}
/*
** cyraw_write handles the write requests from the raw device.
**
** The special cases are:
** 1) we don't allow writes after end of tape is reached.
*/
cyraw_write(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
register int addr, lock_flag, command;
if(u_info->eot) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
u_info->cleanup = cywrite_2_fm;
if(request->b_bcount > c_info->bs)
command = WRIT_TA, lock_flag = CW_LOCK;
else
command = WRIT_BU, lock_flag = 0;
u_info->blkno++;
addr = get_ioadr(request,c_info->rawbuf,c_info->map,c_info->utl);
cyexecute(command, request->b_bcount, addr, lock_flag, unit, 10, FALSE);
end_transfer(request, c_info->rawbuf, c_info->map, c_info->utl);
cydone(ctlr_queue);
}
/*
** cywrite_filemark processes the ioctl to write filemarks to tape.
*/
cywrite_filemark(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(request->b_bcount) {
request->b_bcount--;
if(u_info->cleanup == cywrite_1_fm)
u_info->cleanup = cywrite_0_fm;
if((u_info->cleanup==cywrite_2_fm)||(u_info->cleanup==cyno_op))
u_info->cleanup = cywrite_1_fm;
u_info->file_number++;
u_info->eof = TRUE;
u_info->blkno = 0;
cyexecute(WRIT_FM, (short)1, 0, 0, unit, 10, FALSE);
return;
}
cydone(ctlr_queue);
}
/*
** cysearch_fm_forw is the ioctl to search for a filemark in the
** forward direction on tape.
**
** Since only one device can be active on a given controller at any
** given instant in time, we try to be nice and let onther devices on
** this controller be scheduled after we space over each record. This will
** at least give the apperance of overlapped operations on the controller.
**
** The special cases are:
** 1) if the last command was a write the we can't search.
*/
cysearch_fm_forw(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
if((u_info->cleanup != cyno_op) || u_info->eot) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
if(request->b_bcount && !u_info->eot) {
if(!u_info->eot) {
u_info->blkno++;
cyexecute(SPAC_FM, 1, 0, 0, unit, 5, FALSE);
if(!(u_info->eof || u_info->eot))
return;
}
request->b_bcount--;
u_info->eof = FALSE;
if(!u_info->eot) {
u_info->file_number++;
u_info->blkno = 0;
return;
}
}
if(u_info->eot) {
request->b_resid = request->b_bcount;
request->b_flags |= B_ERROR, request->b_error = ENXIO;
}
cydone(ctlr_queue);
}
/*
** cysearch_fm_back is the ioctl to search for a filemark in the
** reverse direction on tape.
**
** Since only one device can be active on a given controller at any
** given instant in time, we try to be nice and let onther devices on
** this controller be scheduled after we space over each record. This will
** at least give the apperance of overlapped operations on the controller.
**
** The special cases are:
** 1) can't search past begining of tape.
** 2) if the lasr operation was a write data then we need to add
** an end of volume record before we start searching.
*/
cysearch_fm_back(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(!u_info->bot) {
(*u_info->cleanup)(unit, MAINTAIN_POSITION);
if(u_info->blkno == 0)
request->b_bcount++;
u_info->blkno = 0xffffffff;
if(request->b_bcount && !u_info->bot) {
cyexecute(SPAC_FM, 1, 0, CW_REV, unit, 6, FALSE);
if(u_info->eof) {
u_info->eof = FALSE;
u_info->file_number--;
request->b_bcount--;
}
return;
}
if(u_info->bot) {
u_info->file_number = 0;
if(request->b_bcount) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO;
request->b_flags |= B_ERROR;
}
}
else {
request->b_cmd = (struct buf *)DO_SFMF;
request->b_bcount = 1;
return;
}
}
u_info->blkno = 0;
u_info->eof = FALSE;
cydone(ctlr_queue);
}
/*
** cy_space_forw is used to search forward a given number of records on
** tape.
**
** Since only one device can be active on a given controller at any
** given instant in time, we try to be nice and let onther devices on
** this controller be scheduled after we space over each record. This will
** at least give the apperance of overlapped operations on the controller.
**
** The special cases are:
** 1) we can't space over a filemark.
** 2) if the last command was a write data or filemark we can't space forward.
*/
cy_space_forw(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
if((u_info->cleanup != cyno_op) || u_info->eof) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
if(request->b_bcount) {
u_info->blkno++;
cyexecute(SPAC_FM, 1, 0, 0, unit, 10, FALSE);
if(!u_info->eof && request->b_bcount) {
request->b_bcount--;
return;
}
}
if(u_info->eof) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO, request->b_flags |= B_ERROR;
}
cydone(ctlr_queue);
}
/*
** Cy_space_back spaces backward a given number of records.
**
** Since only one device can be active on a given controller at any
** given instant in time, we try to be nice and let onther devices on
** this controller be scheduled after we space over each record. This will
** at least give the apperance of overlapped operations on the controller.
**
** The special cases are:
** 1) we can't space over a filemark.
** 2) we can't space past the beginning of tape.
** 3) if the last operation was a write data then we need to add
** an end of volume record before we start searching.
*/
cy_space_back(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(!u_info->bot) {
(*u_info->cleanup)(unit, MAINTAIN_POSITION);
if(request->b_bcount+1 && !u_info->bot && !u_info->eof) {
request->b_bcount--;
u_info->blkno--;
cyexecute(SPACE, 1, 0, CW_REV, unit, 15, FALSE);
return;
}
if(!u_info->bot) {
request->b_bcount = 1;
cy_space_forw(request);
}
u_info->eof = FALSE;
}
cydone(ctlr_queue);
}
/*
** cyrewind_tape_ta does a waiting rewind to of the tape.
**
** An overlapped rewind is issued and then we change the command type to
** a wait for ready ioctl. Wait for ready contains the logic to poll
** without blocking anything in the system, until the drive becomes ready or
** drops off line whichever comes first.
*/
cyrewind_tape_ta(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
cyrewind_tape(request, REWD_OV);
request->b_cmd = (struct buf *)DO_WAIT;
}
/*
** cyrewind_tape_unl does an overlapped rewind and then unloads the
** tape after the tape completes the rewind. This feature is handled by
** the individual tape drive and in some cases can not unload a tape. In
** this case it acts exactly like an overlapped rewind.
*/
cyrewind_tape_unl(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
cyrewind_tape(request, OFF_UNL);
cydone(ctlr_queue);
}
/*
** cyrewind_tape_ov is used to do overlapped rewinds on the system.
** Close is a classic example of where an overlapped rewind is needed.
** It would be stupid in that case to force the user to wait around
** (up to 5 minutes) until the tape has finished rewind.
*/
cyrewind_tape_ov(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
cyrewind_tape(request, REWD_OV);
cydone(ctlr_queue);
}
/*
** cyrewind tape is the common code for all rewind commands.
**
** The special cases are:
** 3) if the last operation was a write data then we need to add
** an end of volume record before we start searching.
*/
cyrewind_tape(request, command)
register struct buf *request;
long command;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
register int time = (command == REWD_OV) ? 10 : 10*60;
(*u_info->cleanup)(unit, DONT_MAINTAIN_POSITION);
u_info->blkno = 0;
u_info->eof = FALSE;
u_info->bot = TRUE;
u_info->eot = FALSE;
u_info->file_number = 0;
request->b_resid = 0;
u_info->cleanup = cyno_op;
cyexecute(command, 0, 0, 0, unit, time, FALSE);
}
/*
** Cywait_until_ready is used to wait for rewinds to complete.
** We check the status and if the tape is still rewinding we re-enter ourself
** on the activity queue to give other requests a chance to execute before we
** check the status again. One other thing is that we only want to check
** the status every five seconds. so we set a timer for five seconds and
** check the time left every time we enter this routine. If there is still
** time left then we simply reinsert ourself on the queue again and wait
** until next time ..
*/
cywait_until_ready(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
extern int cywait_timeout();
register int unit = CYUNIT(request->b_dev);
register int ctlr = cydinfo[unit]->ui_ctlr;
register unit_tab *u_info = &unit_info[unit];
cyexecute(DRIVE_S, 0, 0, 0, unit, 10, FALSE);
if((!(u_info->last_status & CS_OL)) || (u_info->last_status & CS_RDY)) {
cydone(ctlr_queue);
return;
}
ctlr_queue->b_forw->b_active |= SLEEPING;
timeout(cywait_timeout, ctlr_queue->b_forw, 2*60);
}
/*
** cywait_timeout resets the timing flag for nice_wait after 3 seconds
** is up. This makes this drive eligible for scheduling again.
*/
cywait_timeout(unit_queue)
struct buf *unit_queue;
{
unit_queue->b_active &= ~SLEEPING;
}
/*
** cydrive_status is used to process the status ioctl request.
** it depends entirly on the interupt routines to load the last_XXX
** registers in unit_info[].
*/
cydrive_status(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
cyexecute(DRIVE_S, 0, 0, 0, unit, 10, FALSE);
cydone(ctlr_queue);
}
/*
** cybuf_read handles the read requests from the block device.
**
** The special cases are:
** 1) we can not read after a write. (writting defines end of file)
** 2) reading past end of file returns 0 bytes;
** 3) if we are mispositioned we have to seek to the correct block.
** 4) we can hit end of tape while seeking.
** 5) we want to be nice to other processes while seeking so we
** break the request up into smaller requests.
** 6) returns error if the block was larger than requested.
*/
cybuf_read(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
register int addr, command, bus_lock, bytes;
if(u_info->cleanup != cyno_op) {
request->b_error = ENXIO, request->b_flags |= B_ERROR;
request->b_resid = request->b_bcount;
longjmp(&c_info->environ);
}
if(cyseek(request, ctlr_queue)) {
if(request->b_bcount > c_info->bs)
command = READ_TA, bus_lock = CW_LOCK;
else
command = READ_BU, bus_lock = 0;
u_info->blkno++;
addr=get_ioadr(request,c_info->rawbuf,c_info->map,c_info->utl);
cyexecute(command,request->b_bcount,addr,bus_lock,unit,8,FALSE);
end_transfer(request, c_info->rawbuf, c_info->map, c_info->utl);
if((bytes=MULTIBUS_SHORT(c_info->tpb.rec_over))>request->b_bcount) {
uprintf(long_block_msg,unit,bytes,request->b_bcount);
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
cydone(ctlr_queue);
}
}
/*
** cybuf_write handles the write requests from the block device.
**
** The special cases are:
** 1) if we are mispositioned we have to seek to the correct block.
** 2) we can hit end of tape while seeking.
** 3) we want to be nice to other processes while seeking so we
** break the request up into smaller requests.
** 4) we don't allow writes after end of tape is reached.
*/
cybuf_write(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
register int addr, command, bus_lock;
if(u_info->eot && (request->b_blkno >= u_info->blkno)) {
request->b_error = ENXIO, request->b_flags |= B_ERROR;
request->b_resid = request->b_bcount;
longjmp(&c_info->environ);
}
if(cyseek(request, ctlr_queue)) {
u_info->cleanup = cywrite_2_fm;
u_info->blkno++;
if(request->b_bcount > c_info->bs)
command = WRIT_TA, bus_lock |= CW_LOCK;
else
command = WRIT_BU, bus_lock = 0;
addr=get_ioadr(request,c_info->rawbuf,c_info->map,c_info->utl);
load_mbus_addr((char *)addr, &c_info->tpb.data_ptr);
cyexecute(command,request->b_bcount,addr,bus_lock,unit,5,FALSE);
end_transfer(request, c_info->rawbuf, c_info->map, c_info->utl);
cydone(ctlr_queue);
}
}
/*
** cyseek is used by the block device to position the tape correctly
** before each read or write request.
**
** The special cases are:
** 1) we can hit end of tape while seeking.
** 2) we want to be nice to other processes while seeking so we
** break the request up into smaller requests.
*/
cyseek(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
register int ctlr = cydinfo[unit]->ui_ctlr;
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(request->b_blkno < u_info->blkno) {
register int count;
(*u_info->cleanup)(unit, MAINTAIN_POSITION);
count = ((request->b_blkno+1) == u_info->blkno) ? 2 : 1;
u_info->blkno -= count;
cyexecute(SPAC_FM, 1, 0, CW_REV, unit, 10, FALSE);
if(!u_info->eof)
return FALSE;
u_info->eof = FALSE;
request->b_blkno = u_info->blkno + 1;
}
if(request->b_blkno > u_info->blkno) {
if((u_info->cleanup != cyno_op) || u_info->eof || u_info->eot) {
request->b_resid = request->b_bcount;
request->b_error = ENXIO, request->b_flags |= B_ERROR;
longjmp(&c_info->environ);
}
u_info->blkno++;
cyexecute(SPAC_FM, 1, 0, 0, unit, 10, FALSE);
return FALSE;
}
return TRUE;
}
/*
*/
cywrite_eov(request, ctlr_queue)
register struct buf *request;
register struct buf *ctlr_queue;
{
extern int cyno_op();
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[CYUNIT(unit)];
if(u_info->cleanup != cyno_op) {
(*u_info->cleanup)(unit, DONT_MAINTAIN_POSITION);
cyexecute(SPACE, 2, 0, CW_REV, unit, 10, FALSE);
cyexecute(SPACE, 1, 0, 0, unit, 10, FALSE);
unit_info[unit].cleanup = cyno_op;
u_info->blkno = 0;
}
cydone(ctlr_queue);
}
/*
** Do nothing
*/
cyno_op(unit, action)
int unit, action;
{
}
/*
** Write 0 file marks to tape
*/
cywrite_0_fm(unit, action)
int unit, action;
{
unit_info[unit].cleanup = cyno_op;
}
/*
** Write 1 file mark to tape
*/
cywrite_1_fm(unit, action)
int unit, action;
{
cyexecute(WRIT_FM, 1, 0, 0, unit, 5, FALSE);
if(action == MAINTAIN_POSITION) {
cyexecute(SPACE, 2, 0, CW_REV, unit, 10, FALSE);
cyexecute(SPACE, 1, 0, 0, unit, 10, FALSE);
}
unit_info[unit].cleanup = cyno_op;
}
/*
** Write 2 file marks to tape
*/
cywrite_2_fm(unit, action)
int unit, action;
{
cyexecute(WRIT_FM, 1, 0, 0, unit, 5, FALSE);
cyexecute(WRIT_FM, 1, 0, 0, unit, 5, FALSE);
if(action == MAINTAIN_POSITION) {
cyexecute(SPACE, 3, 0, CW_REV, unit, 10, FALSE);
cyexecute(SPACE, 1, 0, 0, unit, 2, FALSE);
}
unit_info[unit].cleanup = cyno_op;
}
/*
** Cyexecute is used to start all commands to the controller. We
** do all common code here before starting.
*/
cyexecute(command, count, addr, control_flags, unit, time, interupt_routine)
register int command;
int count, addr, control_flags, unit, time, interupt_routine;
{
extern int cytimeout();
extern int cy_normal_path();
register int priority;
register int ctlr = cydinfo[unit]->ui_ctlr;
register unit_tab *u_info = &unit_info[unit];
register ctlr_tab *c_info = &ctlr_info[ctlr];
register struct buf *request = u_info->u_queue.av_forw;
c_info->tpb.cmd = command;
c_info->tpb.control = u_info->control_proto | control_flags;
c_info->tpb.status = c_info->tpb.count = (short)0;
load_mbus_addr((char *)addr, &c_info->tpb.data_ptr);
switch(command) {
case READ_BU :
case READ_TA :
case WRIT_BU :
case WRIT_TA :
c_info->tpb.size = MULTIBUS_SHORT((short)count);
c_info->tpb.rec_over = (short)0;
break;
default:
c_info->tpb.size = (short)0;
c_info->tpb.rec_over = MULTIBUS_SHORT((short)count);
break;
}
load_mbus_addr((char *)0, c_info->tpb.link_ptr);
if(!interupt_routine)
c_info->last = c_info->tpb;
/*
gag! but it the last possible moment to wait
for this controller to get out of it's own way.....
*/
uncache(&c_info->ccb.gate);
while(c_info->ccb.gate == GATE_CLOSED)
uncache(&c_info->ccb.gate);
load_mbus_addr(&c_info->tpb, c_info->ccb.tpb_ptr);
c_info->ccb.ccw = NORMAL_INTERUPT;
c_info->ccb.gate = GATE_CLOSED;
if(!interupt_routine)
c_info->interupt_path = cy_normal_path;
timeout(cytimeout, ctlr, time*60);
priority = spl3();
CY_ATTENTION(cyminfo[ctlr]->um_addr);
if(!interupt_routine) {
sleep(c_info, PRIBIO+3);
splx(priority);
if(request->b_flags & B_ERROR) {
if((command == READ_BU) || (command == READ_TA) ||
(command == WRIT_BU) || (command == WRIT_TA))
end_transfer(request, c_info->rawbuf,
c_info->map,c_info->utl);
longjmp(&c_info->environ);
}
return;
}
splx(priority);
}
/*
** cytimeout is the interupt timeout routine. We assume that a
** particular command has gone astray, so we completely reset the controller,
** and call the interupt routine to help us clean up. Before the interupt
** routine is called we jam a controller timeout value in the status register
** to fake out the calling routines.
*/
cytimeout(ctlr)
register int ctlr;
{
register int priority = spl3();
register char *ctlr_vaddr = cyminfo[ctlr]->um_addr;
register int tmp_stat;
uncache(&ctlr_info[ctlr].tpb.status);
tmp_stat = ctlr_info[ctlr].tpb.status;
CY_RESET(ctlr_vaddr);
cy_init_controller(ctlr_vaddr, ctlr, 0);
splx(priority);
ctlr_info[ctlr].tpb = ctlr_info[ctlr].last;
ctlr_info[ctlr].tpb.status = (tmp_stat & ~CS_ERm) | CS_OL | ER_TIMOUT;
cyintr(ctlr);
}
/*
** Cyintr is the interupt routine for the Tapemaster controller.
**
** Due to controller problems, the first thing we have to do is turn
** off the Tapemaster interupting mechanism. If we don't we will be flooded
** with bogus interupts and the system will spend all it's time processing
** them. To Turn the interupts off we issue a NOOP command with the 'turn
** off interupts' code in the ccb.
**
** take note that since this command TURNS OFF the interupts it
** itself CANNOT interupt... This means that polling must be done
** at sometime to make sure that tis command is completed. The polling
** is done before the next command is issued to reduce polling (halting
** UNIX) time.
**
** After we turn off interupts we uncache all the values in the tpb
** and call the correct processing routine. This routine can be for normal
** interupts or for interupts generated during a retry operation.
*/
cyintr(ctlr)
register int ctlr;
{
extern int cytimeout();
register ctlr_tab *c_info = &ctlr_info[ctlr];
untimeout(cytimeout, ctlr);
/* turn off interupts for the stupid controller */
c_info->ccb.ccw = CLEAR_INTERUPT;
c_info->noop.cmd = NO_OP;
c_info->noop.control = (short)0;
load_mbus_addr(&c_info->noop, c_info->ccb.tpb_ptr);
c_info->ccb.gate = GATE_CLOSED;
CY_ATTENTION(cyminfo[ctlr]->um_addr);
uncache_tpb(c_info);
(*c_info->interupt_path)(ctlr);
}
/*
** This is the portion of the interupt routine that processes all
** normal cases i.e. non retry cases. We check the operations status
** if it is retryable we set the interupt path to the retry routines and
** start the backward spaceing. when the spacing is done the retry logic
** will be called and this routine will be skipped entirely.
**
** If the command is ok or not retryable we set the status accordingly
** and wakeup cyexecute to continue processing.
*/
cy_normal_path(ctlr)
register int ctlr;
{
extern int cy_retry_path();
extern int cy_extended_gap_path();
register int error;
register struct buf *ctlr_queue = &cyminfo[ctlr]->um_tab;
register struct buf *unit_queue = ctlr_queue->b_forw;
register struct buf *request = unit_queue->av_forw;
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[unit];
register ctlr_tab *c_info = &ctlr_info[ctlr];
if (error = cydecode_error(unit, c_info->tpb.status)) {
if(error != FATAL) {
if (error == RETRY)
c_info->interupt_path = cy_retry_path;
else
c_info->interupt_path = cy_extended_gap_path;
cyexecute(SPACE, 2, 0, CW_REV, unit, 5, TRUE);
return;
}
}
request->b_resid=request->b_bcount-MULTIBUS_SHORT(c_info->tpb.count);
u_info->error_count = 0;
u_info->last_resid = request->b_resid;
u_info->last_status = c_info->tpb.status;
u_info->last_control = c_info->tpb.control;
if (error == FATAL)
request->b_flags |= B_ERROR, request->b_error = EIO;
wakeup(c_info);
}
/*
** Cy_retry_path finishes up the retry sequence for the tape.
** If we were going in the reverse direction it means that we have to
** space forward to correctly position ourselfs in back of the tape gap
** instead of in front of it. If we were going forward it means that
** we are positioned correctly and we can actually restart the instruction
** that failed before.
*/
cy_retry_path(ctlr)
register int ctlr;
{
extern int cy_do_again_path();
register struct buf *ctlr_queue = &cyminfo[ctlr]->um_tab;
register struct buf *unit_queue = ctlr_queue->b_forw;
register struct buf *request = unit_queue->av_forw;
register int unit = CYUNIT(request->b_dev);
register unit_tab *u_info = &unit_info[unit];
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(!(c_info->tpb.status & CS_OL)) {
c_info->interupt_path = cy_normal_path;
cy_normal_path(ctlr);
return;
}
if(c_info->tpb.control & CW_REV) {
if(!(c_info->tpb.status & CS_LP)) {
c_info->interupt_path = cy_do_again_path;
cyexecute(SPACE, 1, 0, 0, unit, 5, TRUE);
return;
}
cy_do_again_path(ctlr);
}
}
/*
**
*/
cy_extended_gap_path(ctlr)
register int ctlr;
{
extern int cy_do_again_path();
register ctlr_tab *c_info = &ctlr_info[ctlr];
register struct buf *ctlr_queue = &cyminfo[ctlr]->um_tab;
register struct buf *unit_queue = ctlr_queue->b_forw;
register struct buf *request = unit_queue->av_forw;
register int unit = CYUNIT(request->b_dev);
if(!(c_info->tpb.status & CS_OL)) {
c_info->interupt_path = cy_normal_path;
cy_normal_path(ctlr);
return;
}
if(c_info->tpb.control & CW_REV) {
if(!(c_info->tpb.status & CS_LP)) {
cyexecute(SPACE, 1, 0, 0, unit, 5, TRUE);
return;
}
}
c_info->interupt_path = cy_do_again_path;
cyexecute(ERASE_F, unit_info[unit].error_count, 0, 0, unit, 5, TRUE);
}
/*
**
*/
cy_do_again_path(ctlr)
register int ctlr;
{
extern int cy_normal_path();
register ctlr_tab *c_info = &ctlr_info[ctlr];
if(!(c_info->tpb.status & CS_OL)) {
c_info->interupt_path = cy_normal_path;
cy_normal_path(ctlr);
return;
}
c_info->tpb = c_info->last;
uncache(&c_info->ccb.gate);
while(c_info->ccb.gate == GATE_CLOSED)
uncache(&c_info->ccb.gate);
load_mbus_addr(&c_info->tpb, c_info->ccb.tpb_ptr);
c_info->ccb.ccw = NORMAL_INTERUPT;
c_info->ccb.gate = GATE_CLOSED;
c_info->interupt_path = cy_normal_path;
CY_ATTENTION(cyminfo[ctlr]->um_addr);
}
/*
** for each longword in the tpb we call uncache to purge it from
** the cache. This is done so that we can correctly access tpb data
** that was placed there by the controller.
*/
uncache_tpb(c_info)
ctlr_tab *c_info;
{
register long *ptr = (long *)&c_info->tpb;
register int i;
for(i=0; i<((sizeof(fmt_tpb)+sizeof(long)-1)/sizeof(long)); i++)
uncache(ptr++);
}
/*
** Cyprint_error is the common printing routine for all messages
** that need to print the tape status along with it. This is so we
** we can save space, have consistant messages, and we can send the messages
** to the correct places.
*/
cyprint_err(message, unit, status)
register char *message;
register int unit, status;
{
status &= 0xffff;
printf("\ncy%d: %s! Status = %x\n", unit, message, status);
}
/*
** Decode the error to determine whether the previous command was
** ok, retryable, or fatal and return the value. If it was a hardware
** problem we print the message to the console, otherwise we print it
** to the user's terminal later when execute returns.
*/
cydecode_error(unit, status)
register int unit, status;
{
register unit_tab *u_info = &unit_info[unit];
register ctlr_tab *c_info = &ctlr_info[cydinfo[unit]->ui_ctlr];
int ctlr = cydinfo[unit]->ui_ctlr;
if(!(status & CS_OL) && (c_info->tpb.cmd != OFF_UNL)) {
u_info->message = "Drive is not on-line";
cyprint_err(u_info->message, unit, status);
return FATAL;
}
u_info->bot = ((status & CS_LP) != 0);
u_info->eof = ((status & CS_FM) != 0);
switch(status & CS_ERm) {
case ER_EOT:
if(c_info->tpb.control & CW_REV) {
u_info->bot = TRUE;
u_info->eot = FALSE;
}
else if(!u_info->eot){
u_info->message = "End of tape";
u_info->bot = FALSE;
u_info->eot = TRUE;
}
case 0 :
case ER_FM:
case ER_NOSTRM:
return NOERROR;
case ER_TIMOUT:
case ER_TIMOUT1:
case ER_TIMOUT2:
case ER_TIMOUT3:
case ER_TIMOUT4:
u_info->message = "Drive timed out during transfer";
cyprint_err(u_info->message, unit, status);
return FATAL;
case ER_NEX:
u_info->message =
"Controller referenced non-existant system memory";
cyprint_err(u_info->message, unit, status);
return FATAL;
case ER_DIAG:
case ER_JUMPER:
u_info->message = "Controller diagnostics failed";
cyprint_err(u_info->message, unit, status);
return FATAL;
case ER_STROBE:
if (c_info->tpb.cmd == READ_BU) {
c_info->last.cmd = READ_TA;
return RETRY;
}
if(c_info->tpb.cmd == READ_TA)
return NOERROR;
u_info->message = "Unsatisfactory media found";
return FATAL;
case ER_FIFO:
case ER_NOTRDY:
u_info->error_count = 1;
return RETRY;
case ER_PROT:
u_info->message = "Tape is write protected";
return FATAL;
case ER_CHKSUM:
u_info->message = "Checksum error in controller proms";
cyprint_err(u_info->message, unit, status);
return FATAL;
case ER_HARD:
u_info->error_count++;
if((c_info->tpb.cmd == WRIT_TA) ||
(c_info->tpb.cmd == WRIT_BU) ||
(c_info->tpb.cmd == WRIT_FM)) {
u_info->bad_count++;
return EXTEND;
}
u_info->message = "Unrecoverable media error during read";
return FATAL;
case ER_PARITY:
if(++u_info->error_count < 8)
return RETRY;
u_info->message = "Unrecoverable tape parity error";
return FATAL;
case ER_BLANK:
u_info->message = "Blank tape found (data expected)";
return FATAL;
case ER_HDWERR:
default:
u_info->message = "Unrecoverble hardware error";
cyprint_err(u_info->message, unit, status);
return FATAL;
}
}
/*
** Raw read interface to unix. Since all the checking is done at a
** lower level we don't check anything here and we simply return the results.
*/
cyread(dev, uio)
register dev_t dev;
register struct uio *uio;
{
register int unit = CYUNIT(dev);
register unit_tab *u_info = &unit_info[unit];
return physio(cystrategy, &u_info->rawbp, dev, B_READ, cyminsize, uio);
}
/*
** Raw write interface to unix. Since all the checking is done at a
** lower level we don't check anything here and we simply return the results.
*/
cywrite(dev, uio)
register dev_t dev;
register struct uio *uio;
{
register int unit = CYUNIT(dev);
register unit_tab *u_info = &unit_info[unit];
return physio(cystrategy,&u_info->rawbp, dev, B_WRITE, cyminsize, uio);
}
/*
** Cyioctl is called by UNIX every time an ioctl call is made by a user
** program. We don't really do much here except call cycmd to process the
** ioctl command. We don't decode the ioctl type here because our internal
** jump table accessed by start is ordered by the ioctl number passes here.
**
** The only special processing is in the status command. This is because
** we actually return various data to the user's program with only that
** command.
*/
cyioctl(dev, command, data, flag)
register dev_t dev;
register int command;
register struct mtop *data;
register int flag;
{
if(command == MTIOCTOP)
if((unsigned)(data->mt_op <= DO_WAIT))
return cycmd(dev, data->mt_op, data->mt_count);
else
return EIO;
else if(command == MTIOCGET) {
register unit_tab *u_info = &unit_info[CYUNIT(dev)];
((struct mtget *)data)->mt_type = MT_ISCY;
((struct mtget *)data)->mt_dsreg = u_info->last_control;
((struct mtget *)data)->mt_erreg = u_info->last_status;
((struct mtget *)data)->mt_resid = u_info->last_resid;
((struct mtget *)data)->mt_fileno = u_info->file_number;
((struct mtget *)data)->mt_blkno = u_info->blkno;
cycmd(dev, DO_STAT, 1);
return NOERROR;
}
return ENXIO;
}
/*
** Cydump is called during a system crash to dump all of main memory.
** We don't do any special checking here except we will exit early if
** an i/o error occurs. The other point is we poll for completion of each
** command since we don't want to do any special processing, we are
** the only process running anyway, and possibly the cpu interupt's
** were not working anyway. (what if sombody stepped on low core?)
*/
cydump(dev)
register dev_t dev;
{
register int unit = CYUNIT(dev);
register int ctlr = cydinfo[unit]->ui_ctlr;
register unit_tab *u_info = &unit_info[unit];
register ctlr_tab *c_info = &ctlr_info[ctlr];
register int blk_siz;
register int num = maxfree;
register int start = 0x800;
if ((unit >= NCY) || cydinfo[unit])
return(ENXIO);
u_info->control_proto = CW_LOCK | CW_25ips | CW_16bits;
if (cywait(&c_info->ccb))
return(EFAULT);
while (num > 0) {
blk_siz = num > TBUFSIZ ? TBUFSIZ : num;
bcopy(start*NBPG, c_info->rawbuf, blk_siz*NBPG);
c_info->tpb.cmd = WRIT_TA;
c_info->tpb.control = u_info->control_proto;
c_info->tpb.status = 0;
c_info->tpb.size = MULTIBUS_SHORT(blk_siz*NBPG);
load_mbus_addr((char *)0, c_info->tpb.link_ptr);
load_mbus_addr(c_info->rawbuf,&(c_info->tpb.data_ptr));
load_mbus_addr(&c_info->tpb, c_info->ccb.tpb_ptr);
c_info->ccb.gate = GATE_CLOSED;
CY_ATTENTION(cyminfo[ctlr]->um_addr);
start += blk_siz;
num -= blk_siz;
if (cywait(&c_info->ccb))
return(EFAULT);
uncache(&c_info->tpb);
if (c_info->tpb.status&CS_ERm) /* error */
return (EIO);
}
for(num=0; num<2; num++) {
c_info->tpb.cmd = WRIT_FM;
c_info->tpb.control = u_info->control_proto;
c_info->tpb.status = c_info->tpb.size = 0;
c_info->tpb.count = MULTIBUS_SHORT(1);
load_mbus_addr((char *)0, c_info->tpb.link_ptr);
load_mbus_addr(c_info->rawbuf,&(c_info->tpb.data_ptr));
load_mbus_addr(&c_info->tpb, c_info->ccb.tpb_ptr);
c_info->ccb.gate = GATE_CLOSED;
CY_ATTENTION(cyminfo[ctlr]->um_addr);
if (cywait(&c_info->ccb))
return(EFAULT);
uncache(&c_info->tpb);
if (c_info->tpb.status&CS_ERm) /* error */
return (EIO);
}
c_info->tpb.cmd = REWD_OV;
c_info->tpb.control = u_info->control_proto;
c_info->tpb.status = c_info->tpb.size = 0;
c_info->tpb.count = MULTIBUS_SHORT(1);
load_mbus_addr((char *)0, c_info->tpb.link_ptr);
load_mbus_addr(c_info->rawbuf,&(c_info->tpb.data_ptr));
load_mbus_addr(&c_info->tpb, c_info->ccb.tpb_ptr);
c_info->ccb.gate = GATE_CLOSED;
CY_ATTENTION(cyminfo[ctlr]->um_addr);
if (cywait(&c_info->ccb))
return EFAULT;
uncache(&c_info->tpb);
return 0;
}
/*
** Poll until the controller is ready.
*/
cywait(ccb_ptr)
register fmt_ccb *ccb_ptr;
{
register int cnt = 5000;
uncache(&ccb_ptr->gate);
while ((cnt-- > 0) && (ccb_ptr->gate == GATE_CLOSED)) {
DELAY(1000);
uncache(&ccb_ptr->gate);
}
return cnt <= 0;
}
/*
** Load_mbus_addr is used to load a 20 bit pointer into the
** Tapemaster registers. Take note of all the strange convolutions
** this controller forces us through to get the job done.
*/
load_mbus_addr(in, out)
char *in;
short *out;
{
register int tmp_in = (int)in;
register char *out_ptr = (char *)out;
*out_ptr++ = (char)(tmp_in & 0xff);
*out_ptr++ = (char)((tmp_in >> 8) & 0xff);
*out_ptr++ = (char)0;
*out_ptr++ = (char)((tmp_in & 0xf0000) >> 12);
}
/*
** CYMINSIZE s supposed to adjust the buffer size for any raw i/o.
** since tapes can not read the tail end of partial blocks we ignore
** this request and strategy will return an appropriate error message later.
**
** If this is not done UNIX will lose data that is on the tape.
*/
unsigned cyminsize(request)
register struct buf *request;
{
if(request->b_bcount > MAX_BLOCKSIZE)
request->b_bcount = MAX_BLOCKSIZE;
}
/*
** cyreset is used to unconditionally reset all controllers to
** their initial state.
*/
cyreset(vba)
register int vba;
{
register int ctlr;
register caddr_t ctlr_vaddr;
for(ctlr = 0; ctlr<NCY; ctlr++)
if(cyminfo[ctlr])
if(cyminfo[ctlr]->um_vbanum == vba) {
ctlr_vaddr = cyminfo[ctlr]->um_addr;
CY_RESET(ctlr_vaddr);
if(!cy_init_controller(ctlr_vaddr, ctlr, 0)) {
printf("cy: controller #%d failed to reset!\n", ctlr);
cyminfo[ctlr] = NULL;
}
}
}
#endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.