File:  [Power 6/32 Unix Tahoe 4.2BSD] / cci / sys / vba / cy.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Sun Jul 28 12:30:10 2019 UTC (6 years, 11 months ago) by root
Branches: bsd, MAIN
CVS tags: v121, HEAD
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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.