Minix1.5/kernel/ps_wini.c

Compare this file to the similar file:
Show the results in this format:

/* This file contains a driver for the IBM PS/2 ST506 types 1 and 2 adapters.
 * The original version was written by Wim van Leersum.  It has been
 * modified extensively to make it run on PS/2s that have the MCA.
 * The w_transfer routine and the code #ifdef'ed for the PS/2 Model 30/286
 * comes from Rene Nieuwenhuizen's ps_wini.c.  This driver has been tested
 * on a Model 80, a Model 55/SX, and a Model 30/286.  It will hopefully
 * run on a regular Model 30, but it hasn't been tested.
 *
 *
 * The driver supports the following operations (using message format m2):
 *
 *    m_type      DEVICE    PROC_NR     COUNT    POSITION  ADRRESS
 * ----------------------------------------------------------------
 * |  DISK_READ | device  | proc nr |  bytes  |  offset | buf ptr |
 * |------------+---------+---------+---------+---------+---------|
 * | DISK_WRITE | device  | proc nr |  bytes  |  offset | buf ptr |
 * ----------------------------------------------------------------
 * |SCATTERED_IO| device  | proc nr | requests|         | iov ptr |
 * ----------------------------------------------------------------
 *
 * The file contains one entry point:
 *
 *   winchester_task:	main entry when system is brought up
 *
 */

#include "kernel.h"
#include <minix/callnr.h>
#include <minix/com.h>
#include <minix/partition.h>

/* I/O Ports used by winchester disk controller. */
#define DATA		0x320	/* data register */
#define ASR		0x322	/* Attachment Status Register */
#define ATT_REG		0x324	/* Attention register */
#define ISR		0x324	/* Interrupt status register */
#define ACR		0x322	/* Attachment control register */

/* Winchester disk controller status bytes. */
#define BUSY		0x04	/* controller busy? */
#define DATA_REQUEST	0x10	/* controller asking for data */
#define IR		0x02	/* Interrupt Request */

/* Winchester disk controller command bytes. */
#define CSB	        0x40	/* Get controlers attention for a CSB */
#define DR	        0x10	/* Get controlers attention for data transfer*/
#define CCB	        0x80	/* same for command control block */
#define WIN_READ  	0x15	/* command for the drive to read */
#define WIN_WRITE 	0x95	/* command for the drive to write */

/* Parameters for the disk drive. */
#define SECTOR_SIZE      512	/* physical sector size in bytes */

/* Error codes */
#define ERR		  -1	/* general error */

/* Miscellaneous. */
#define MAX_ERRORS     	4	/* how often to try rd/wt before quitting */
#define MAX_DRIVES	2	/* maximum amount of drives we can handle */
#define NR_DEVICES      (MAX_DRIVES * DEV_PER_DRIVE)
#define MAX_WIN_RETRY 	10000	/* max # times to try to output to WIN */
#define DEV_PER_DRIVE   (1 + NR_PARTITIONS)	/* whole drive & each partn */
#define NUM_COM_BYTES	14	/* number of command bytes controller expects*/
#define SYS_PORTA	0x92	/* MCA System Port A */
#define LIGHT_ON	0xC0    /* Bits to turn drive light on */
#define DRIVE_2		0x4	/* Bit to select Drive_2 */

/* This driver uses DMA */
#define DMA_RESET_VAL   0x06    /* DMA reset value */
#define DMA_READ	0x47	/* DMA read opcode */
#define DMA_WRITE	0x4B	/* DMA write opcode */
#define DMA_ADDR       0x006	/* port for low 16 bits of DMA address */
#define DMA_TOP	       0x082	/* port for top 4 bits of 20-bit DMA addr */
#define DMA_COUNT      0x007	/* port for DMA count (count =	bytes - 1) */
#define DMA_M2	       0x00C	/* DMA status port */
#define DMA_M1	       0x00B	/* DMA status port */
#define DMA_INIT       0x00A	/* DMA init port */
#define DMA_STATUS	0x08    /* DMA status port for channels 0-3 */

/* I/O Ports used for Programmable Option Selection */
#define SBSER		0x94	/* System Board Setup Enable Register */
#define POS2		0x102	/* POS 2 register */
#define POS3		0x103	/* POS 3 register */

/*
 * This driver is written to run on all versions of the PS/2.  The
 * models < 50 are based on the XT.  The models >= 50 are based on the AT.
 * en_wini_int will call the appropriate cim routine to enable interrupts.
 */
#define	en_wini_int()	{ \
				if (pc_at) \
					cim_at_wini(); \
				else \
					cim_xt_wini(); \
			}

/* Variables. */
PRIVATE struct wini {		/* main drive struct, one entry per drive */
  int wn_opcode;		/* DISK_READ or DISK_WRITE */
  int wn_procnr;		/* which proc wanted this operation? */
  int wn_drive;			/* drive number addressed */
  int wn_cylinder;		/* cylinder number addressed */
  int wn_sector;		/* sector addressed */
  int wn_head;			/* head number addressed */
  int wn_heads;			/* maximum number of heads */
  int wn_maxsec;		/* maximum number of sectors per track */
  int wn_maxcyl;		/* maximum number of cylinders */
  int wn_ctlbyte;		/* control byte (steprate) */
  int wn_precomp;		/* write precompensation cylinder */
  long wn_low;			/* lowest cylinder of partition */
  long wn_size;			/* size of partition in blocks */
  int wn_count;			/* byte count */
  vir_bytes wn_address;		/* user virtual address */
} wini[NR_DEVICES];

PUBLIC int using_bios = FALSE;	/* this disk driver does not use the BIOS */

PRIVATE int w_need_reset = FALSE;      /* TRUE when controller must be reset */
PRIVATE int nr_drives;		       /* Number of drives */

PRIVATE message w_mess;		       /* message buffer for in and out */

PRIVATE int command[NUM_COM_BYTES];    /* Common command block */

PRIVATE unsigned char buf[BLOCK_SIZE]; /* Buffer used by the startup routine */

FORWARD void ch_select();
FORWARD void ch_unselect();
FORWARD int com_out();
FORWARD int controller_ready();
FORWARD void copy_params();
FORWARD void copy_prt();
FORWARD int drive_busy();
FORWARD void init_params();
FORWARD void sort();
FORWARD int w_do_rdwt();
FORWARD int w_reset();
FORWARD int w_transfer();
FORWARD int win_init();
FORWARD int win_results();
FORWARD void set_command();
FORWARD void w_dma_setup();
FORWARD void abort_com();
FORWARD int status();

/*===========================================================================*
 *				winchester_task				     * 
 *===========================================================================*/
PUBLIC void winchester_task()
{
/* Main program of the winchester disk driver task. */

  int r, caller, proc_nr;

  /* Initialize the controller */
  init_params();

  /* Here is the main loop of the disk task.  It waits for a message, carries
   * it out, and sends a reply.
   */

  while (TRUE) {
	/* First wait for a request to read or write a disk block. */
	receive(ANY, &w_mess);	/* get a request to do some work */
	if (w_mess.m_source < 0) {
		printf("winchester task got message from %d ", w_mess.m_source);
		continue;
	}
	caller = w_mess.m_source;
	proc_nr = w_mess.PROC_NR;

	/* Now carry out the work. */
	switch(w_mess.m_type) {
	    case DISK_READ:
	    case DISK_WRITE:	r = w_do_rdwt(&w_mess);	break;
	    case SCATTERED_IO:	r = do_vrdwt(&w_mess, w_do_rdwt); break;
	    default:		r = EINVAL;		break;
	}

	/* Finally, prepare and send the reply message. */
	w_mess.m_type = TASK_REPLY;	
	w_mess.REP_PROC_NR = proc_nr;

	w_mess.REP_STATUS = r;	/* # of bytes transferred or error code */
	send(caller, &w_mess);	/* send reply to caller */
  }
}


/*===========================================================================*
 *				w_do_rdwt				     * 
 *===========================================================================*/
PRIVATE int w_do_rdwt(m_ptr)
message *m_ptr;			/* pointer to read or write w_message */
{
/* Carry out a read or write request from the disk. */
  register struct wini *wn;
  int r, device, errors = 0;
  long sector;

  /* Decode the w_message parameters. */
  device = m_ptr->DEVICE;
  if (device < 0 || device >= NR_DEVICES)
	return(EIO);
  if (m_ptr->COUNT != BLOCK_SIZE)
	return(EINVAL);

  wn = &wini[device];		/* 'wn' points to entry for this drive */
  wn->wn_drive = device/DEV_PER_DRIVE;	/* save drive number */
  if (wn->wn_drive >= nr_drives)
	return(EIO);
  wn->wn_opcode = m_ptr->m_type;	/* DISK_READ or DISK_WRITE */
  if (m_ptr->POSITION % BLOCK_SIZE != 0)
	return(EINVAL);
  sector = m_ptr->POSITION/SECTOR_SIZE;
  if ((sector+BLOCK_SIZE/SECTOR_SIZE) > wn->wn_size)
	return(0);
  sector += wn->wn_low;
  wn->wn_cylinder = sector / (wn->wn_heads * wn->wn_maxsec);
  wn->wn_sector =  (sector % wn->wn_maxsec) + 1;
  wn->wn_head = (sector % (wn->wn_heads * wn->wn_maxsec) )/wn->wn_maxsec;
  wn->wn_count = m_ptr->COUNT;
  wn->wn_address = (vir_bytes) m_ptr->ADDRESS;
  wn->wn_procnr = m_ptr->PROC_NR;

  ch_select();		/* Select fixed disk chip */

  /* This loop allows a failed operation to be repeated. */
  while (errors <= MAX_ERRORS) {
	errors++;		/* increment count once per loop cycle */
	if (errors > MAX_ERRORS)
		return(EIO);

	/* First check to see if a reset is needed. */
	if (w_need_reset) w_reset();

	/* Perform the transfer. */
	r = w_transfer(wn);
	if (r == OK) break;	/* if successful, exit loop */

  }

  ch_unselect();		/* Do not select fixed disk chip anymore */

  return(r == OK ? BLOCK_SIZE : EIO);
}

/*===========================================================================*
 *				w_transfer				     * 
 *===========================================================================*/
PRIVATE int w_transfer(wn)
register struct wini *wn;	/* pointer to the drive struct */
{
	register int i, j, r;	/* indices */
	message dummy;		/* dummy message to recieve interrupts */

	set_command(wn);	/* setup command block */

	if (com_out(6, (wn->wn_drive) ? CCB | DRIVE_2 : CCB) != OK) 
		return(ERR);	/* output CCB to controller */

	for (i = 0; i < MAX_WIN_RETRY; i++)
		if (in_byte(ASR) & IR) break;	/* wait for acknowledgment */

	if (i == MAX_WIN_RETRY) {
		w_need_reset = TRUE;	/* ACK! this shouldn't happen -- bug?*/
		return(ERR);
	}

	if (win_results() != OK) {
		abort_com();
		return(ERR);
	}

	r = OK;				/* be optimistic! */
	w_dma_setup(wn);		/* setup DMA */

	out_byte(ACR, 0x03);		/* turn on interrupts and DMA */
  	out_byte(DMA_INIT, 3);		/* initialize DMA controller */
	en_wini_int();			/* OK to receive interrupts */

	if (com_out(0, DR) != OK) return(ERR); /* ask for data transfer */

	receive(HARDWARE, &dummy);	/* receive interrupt */

	out_byte(ACR, 0x0);		/* disable interrupt and dma */
	out_byte(DMA_INIT, 7);		/* shut off DMA controller */

  	if (win_results() != OK) {
		abort_com();		/* stop the current command */
		r = ERR;
  	} 

  	return(r);
}

/*===========================================================================*
 *				w_reset					     * 
 *===========================================================================*/
PRIVATE int w_reset()
{
/* Issue a reset to the controller.  This is done after any catastrophe,
 * like the controller refusing to respond.
 */

  int i, r;
  message dummy;

  out_byte(ACR, 0x80);	/* Strobe reset bit high. */
  out_byte(ACR, 0);	/* Strobe reset bit low. */

  for (i = 0; i < MAX_WIN_RETRY; i++) {
		if ((in_byte(ASR) & BUSY) != BUSY) break;
		milli_delay(20);
  }

  if (i == MAX_WIN_RETRY) panic("Winchester won't reset!", 0);

  /* Reset succeeded.  Tell WIN drive parameters. */
  if (win_init() != OK) {		/* Initialize the controler */
	printf("Winchester wouldn't accept parameters\n");
	return(ERR);
  }
  
  w_need_reset = FALSE;
  return(OK);
}

/*===========================================================================*
 *				win_init				     * 
 *===========================================================================*/
PRIVATE int win_init()
{
/* Routine to initialize the drive parameters after boot or reset */

  register int i, cyl;
  message dummy;

/*
 * Command Specify Bytes
 */
#define CSB0	0x8F		/* Command Specify Block byte 0 */
#define	CSB1	0x83		/* ST506/2 Interface, 5 Mbps transfer rate */
#define CSB2	0x0B		/* Drive Gap 1 (value gotten from bios) */
#define CSB3	0x04		/* Drive Gap 2 (value gotten from bios) */
#define CSB4	0x1B		/* Drive Gap 3 (value gotten from bios) */
#define CSB5	0x0D		/* Sync field length */
#define CSB6	0x0		/* Step rate in 50 microseconds */
#define CSB7	0x0		/* IBM reserved */
	
  command[0] = CSB0;

  if (pc_at) { 
	/* Setup the Command Specify Block */
  	command[1] = CSB1;
  	command[2] = CSB2;
  	command[3] = CSB3;
  	command[4] = CSB4;
  	command[5] = CSB5;
  	command[6] = CSB6;
  	command[7] = CSB7;
  	command[8] = (unsigned) wini[0].wn_precomp < wini[0].wn_maxcyl ?
			(wini[0].wn_precomp >> 8) & BYTE : 
			(wini[0].wn_maxcyl >> 8) & BYTE;
  	command[9] = (unsigned) wini[0].wn_precomp < wini[0].wn_maxcyl ?
			wini[0].wn_precomp & BYTE :
			wini[0].wn_maxcyl & BYTE;
  	command[10] = (wini[0].wn_maxcyl >> 8) & BYTE;
  	command[11] = wini[0].wn_maxcyl & BYTE;
  	command[12] = wini[0].wn_maxsec;
  	command[13] = wini[0].wn_heads;
  } else {
	/*
	 * The original ps_wini driver set all the bytes in the Command
	 * Specify Block to 0.  According to my documentation, the adapter
  	 * should have complained.  However, supposedly it did work on a
	 * model 30, so if we're running on a NON-mca machine (i.e. model 30)
	 * set the CSB to all 0's
	 */
	for (i = 1; i < 14; i ++)
		command[i] = 0;
  }

  out_byte(ACR, 0);			/* Make sure accepts 8 bits */
  if (com_out(14, CSB) != OK)		/* Output command block */
	return(ERR);

  out_byte(ACR, 2);			/* turn on interrupts */
  en_wini_int();
  receive(HARDWARE, &dummy);	 	/* receive interrupt */
  if (win_results() != OK) {
	w_need_reset = TRUE;
	return(ERR);
  }

  if (nr_drives > 1) {
  	command[8] = (unsigned) wini[5].wn_precomp < wini[5].wn_maxcyl ?
			(wini[5].wn_precomp >> 8) & BYTE : 
			(wini[5].wn_maxcyl >> 8) & BYTE;
  	command[9] = (unsigned) wini[5].wn_precomp < wini[5].wn_maxcyl ?
			wini[5].wn_precomp & BYTE :
			wini[5].wn_maxcyl & BYTE;
	command[10] = (wini[5].wn_maxcyl >> 8) & BYTE;
	command[11] = wini[5].wn_maxcyl & BYTE;
  	command[12] = wini[5].wn_maxsec;
  	command[13] = wini[5].wn_heads;

	if (com_out(14, CSB | DRIVE_2) != OK)	/* Output command block */
		return(ERR);

	en_wini_int();				/* enable interrupts */
	receive(HARDWARE, &dummy);	        /* receive interrupt */
	if (win_results() != OK) {
		w_need_reset = TRUE;
		return(ERR);
	}

  }

  out_byte(ACR, 0);		/* no interrupts and no DMA */
  return(OK);
}

/*============================================================================*
 *				win_results				      *
 *============================================================================*/
PRIVATE int win_results()
{
/* Extract results from the controller after an operation.
 */

	if ((in_byte(ISR) & 0xE3) != 0) return(ERR);
	return(OK);
}

/*==========================================================================*
 *				controller_ready			    *
 *==========================================================================*/
PRIVATE int controller_ready()
{
/* Wait until controller is ready for output; return zero if this times out. */

#define MAX_CONTROLLER_READY_RETRIES	1000	/* should calibrate this */

  register int retries;

  retries = MAX_CONTROLLER_READY_RETRIES + 1;
  while ((--retries != 0) && ((status() & BUSY) == BUSY))
	;			/* wait until not busy */
  return(retries);		/* nonzero if ready */
}

/*============================================================================*
 *				com_out					      *
 *============================================================================*/
PRIVATE int com_out(nr_words, attention)
int nr_words;
int attention;
{
/* Output the command block to the winchester controller and return status */

	register int i, j;

  	if (!controller_ready()) {
		printf("Controller not ready in com_out\n");
		w_need_reset = TRUE;
		return(ERR);
  	}

	out_byte(ATT_REG, attention);	/* get controller's attention */

	if (nr_words == 0) return(OK);	/* may not want to output command */

	for (i = 0; i < nr_words; i++) {	/* output command block */
		for (j = 0; j < MAX_WIN_RETRY; j++)	/* wait  */
			if (status() & DATA_REQUEST) break;

		if (j == MAX_WIN_RETRY) {
			w_need_reset = TRUE;
			dump_isr();
			return(ERR);
		}
		out_byte(DATA, command[i]);   	/* issue command */
  	}

  return(OK);

}

/*============================================================================*
 *				init_params				      *
 *============================================================================*/
PRIVATE void init_params()
{
/* This routine is called at startup to initialize the partition table,
 * the number of drives and the controller
 */
  unsigned int i, segment, offset;
  phys_bytes address;

  /* Copy the parameter vector from the saved vector table */
  offset = vec_table[2 * WINI_0_PARM_VEC];
  segment = vec_table[2 * WINI_0_PARM_VEC + 1];

  /* Calculate the address off the parameters and copy them to buf */
  address = hclick_to_physb(segment) + offset;
  phys_copy(address, umap(proc_ptr, D, (vir_bytes)buf, 16), 16L);

  /* Copy the parameters to the structures */
  copy_params(buf, &wini[0]);

  /* Copy the parameter vector from the saved vector table */
  offset = vec_table[2 * WINI_1_PARM_VEC];
  segment = vec_table[2 * WINI_1_PARM_VEC + 1];

  /* Calculate the address off the parameters and copy them to buf */
  address = hclick_to_physb(segment) + offset;
  phys_copy(address, umap(proc_ptr, D, (vir_bytes)buf, 16), 16L);

  /* Copy the parameters to the structures */
  copy_params(buf, &wini[5]);

  /* Get the nummer of drives from the bios */
  phys_copy(0x475L, umap(proc_ptr, D, (vir_bytes)buf, 1), 1L);
  nr_drives = (int) *buf;
  if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES;

  /* Set the parameters in the drive structure */
  wini[0].wn_low = wini[5].wn_low = 0L;

  /* Initialize the controller */
  if (nr_drives > 0) {
  	ch_select();  /* select fixed disk chip */
  	if (win_init() != OK) {	
		/* Probably controller not a ST506 */
		nr_drives = 0;
		printf("Controller does not appear to be a ST506\n");
	}
  	ch_unselect(); /* unselect the fixed disk chip */
  }

  /* Read the partition table for each drive and save them */
  for (i = 0; i < nr_drives; i++) {
	w_mess.DEVICE = i * 5;
	w_mess.POSITION = 0L;
	w_mess.COUNT = BLOCK_SIZE;
	w_mess.ADDRESS = (char *) buf;
	w_mess.PROC_NR = WINCHESTER;
	w_mess.m_type = DISK_READ;
	if (w_do_rdwt(&w_mess) != BLOCK_SIZE) {
		printf("Can't read partition table on winchester %d\n",i);
		milli_delay(20000);
		continue;
	}
	if (buf[510] != 0x55 || buf[511] != 0xAA) {
		printf("Invalid partition table on winchester %d\n",i);
		milli_delay(20000);
		continue;
	}
	copy_prt((int)i*5);
  }

}

/*============================================================================*
 *				copy_params				      *
 *============================================================================*/
PRIVATE void copy_params(src, dest)
register unsigned char *src;
register struct wini *dest;
{
/* This routine copies the parameters from src to dest
 * and sets the parameters for partition 0 and 5
*/
  register int i;
  long cyl, heads, sectors;

  cyl = (long)(*(u16_t *)src);

  for (i=0; i<5; i++) {
	dest[i].wn_heads = (int)src[2];
	dest[i].wn_precomp = *(u16_t *)&src[5];
	dest[i].wn_ctlbyte = (int)src[8];
	dest[i].wn_maxsec = (int)src[14];
	dest[i].wn_maxcyl = (int)cyl;
  }

  heads = (long)dest[0].wn_heads;
  sectors = (long)dest[0].wn_maxsec;
  dest[0].wn_size = cyl * heads * sectors;
}

/*===========================================================================*
 *				copy_prt				     *
 *===========================================================================*/
PRIVATE void copy_prt(base_dev)
int base_dev;			/* base device for drive */
{
/* This routine copies the partition table for the selected drive to
 * the variables wn_low and wn_size
 */

  register struct part_entry *pe;
  register struct wini *wn;

  for (pe = (struct part_entry *) &buf[PART_TABLE_OFF],
       wn = &wini[base_dev + 1];
       pe < ((struct part_entry *) &buf[PART_TABLE_OFF]) + NR_PARTITIONS;
       ++pe, ++wn) {
	wn->wn_low = pe->lowsec;
	wn->wn_size = pe->size;

	/* Adjust low sector to a multiple of (BLOCK_SIZE/SECTOR_SIZE) for old
	 * Minix partitions only.  We can assume the ratio is 2 and round to
	 * even, which is slightly simpler.
	 */
	if (pe->sysind == OLD_MINIX_PART && wn->wn_low & 1) {
		++wn->wn_low;
		--wn->wn_size;
	}
  }
  sort(&wini[base_dev + 1]);
}

/*===========================================================================*
 *					sort				     *
 *===========================================================================*/
PRIVATE void sort(wn)
register struct wini wn[];
{
  register int i,j;
  struct wini tmp;

  for (i = 0; i < NR_PARTITIONS; i++)
	for (j = 0; j < NR_PARTITIONS-1; j++)
		if ((wn[j].wn_low == 0 && wn[j+1].wn_low != 0) ||
		    (wn[j].wn_low > wn[j+1].wn_low && wn[j+1].wn_low != 0)) {
			tmp = wn[j];
			wn[j] = wn[j+1];
			wn[j+1] = tmp;
		}
}

/*===========================================================================*
 *				ch_select	 		  	     * 
 *===========================================================================*/
PRIVATE void ch_select() 
{
/*
 * The original comment said that this function select the fixed disk
 * drive "chip".  I have no idea what this does, but it sounds important.
 * For mca, this function will turn on the fixed disk activity light.
 */

 if (ps_mca)	/* If MCA, turn on fixed disk activity light */
	out_byte(SYS_PORTA, in_byte(SYS_PORTA) | LIGHT_ON); 
 else if (pc_at) {
	/* Select fixed disk chip. Extracted from a debugged BIOS listing.
   	   Seems to be some magic to get controler on the phone. */
	lock();
	out_byte(SBSER, 0x20);
	out_byte(POS2, (in_byte(POS2) | 1));
	out_byte(POS3, (in_byte(POS3) | 8));
	out_byte(SBSER, 0xA0);
	unlock();
 } else		/* else bit 1 of Planar Control Reg selects it (disk?) */
        out_byte(PCR, in_byte(PCR) | 1); 	/* This must be a Model 30 */

}

/*==========================================================================*
 *			ch_unselect				 	    * 
 *==========================================================================*/
PRIVATE void ch_unselect() 
{
/*
 * The original comment said that this function unselects the fixed disk
 * drive "chip".  I have no idea what this does, but it sounds important.
 * For mca, this function will turn off the fixed disk activity light.
 */
 if (ps_mca)	/* If MCA, turn off fixed disk activity light */
	out_byte(SYS_PORTA, in_byte(SYS_PORTA)  & ~LIGHT_ON); 
 else if (pc_at) {
	/* This must be a Model 30/286 
	   Select fixed disk chip. Extracted from a debugged BIOS listing.
	   Seems to be some magic to get controler on the phone. */
	lock();
	out_byte(SBSER, 0x20);
	out_byte(POS2, (in_byte(POS2) | 1));
	out_byte(POS3, (in_byte(POS3) & 7));
	out_byte(SBSER, 0xA0);
	unlock();
 } else		/* else bit 1 of Planar Control Reg selects it (disk?) */
 	out_byte(PCR, in_byte(PCR) & ~1);	/* This must be a Model 30 */
}

/*==========================================================================*
 *			w_dma_setup				  	    * 
 *==========================================================================*/
PRIVATE void w_dma_setup(wn)
register struct wini *wn;
{
/* The IBM PC can perform DMA operations by using the DMA chip.	 To use it,
 * the DMA (Direct Memory Access) chip is loaded with the 20-bit memory address
 * to by read from or written to, the byte count minus 1, and a read or write
 * opcode.  This routine sets up the DMA chip.	Note that the PS/2 MCA 
 * chip IS capable of doing a DMA across a 64K boundary, but not sure about
 * model 30, so test for it anyway.
 */

  int mode, low_addr, high_addr, top_addr, low_ct, high_ct, top_end;
  vir_bytes vir, ct;
  phys_bytes user_phys;

  mode = (wn->wn_opcode == DISK_READ ? DMA_READ : DMA_WRITE);
  vir = (vir_bytes) wn->wn_address;
  ct = (vir_bytes) BLOCK_SIZE;
  user_phys = numap(wn->wn_procnr, vir, BLOCK_SIZE);

  low_addr  = (int) user_phys & BYTE;
  high_addr = (int) (user_phys >>  8) & BYTE;
  top_addr  = (int) (user_phys >> 16) & BYTE;
  low_ct    = (int) (ct - 1) & BYTE;
  high_ct   = (int) ((ct - 1) >> 8) & BYTE;

  /* Check to see if the transfer will require the DMA address counter to
   * go from one 64K segment to another.  If so, do not even start it, since
   * the hardware does not carry from bit 15 to bit 16 of the DMA address.
   * Also check for bad buffer address.	 These errors mean FS contains a bug.
   */
  if (user_phys == 0)
	  panic("FS gave winchester disk driver bad addr", (int) vir);
  top_end = (int) (((user_phys + ct - 1) >> 16) & BYTE);
  if (top_end != top_addr) 
	panic("Trying to DMA across 64K boundary", top_addr);

  (void)in_byte(DMA_STATUS);	/* clear the status byte */

  /* Now set up the DMA registers. */
  out_byte(DMA_INIT, DMA_RESET_VAL); /* reset the dma controller */
  out_byte(DMA_M2, mode);	/* set the DMA mode */
  out_byte(DMA_M1, mode);	/* set it again */
  out_byte(DMA_ADDR, low_addr);	/* output low-order 8 bits */
  out_byte(DMA_ADDR, high_addr);/* output next 8 bits */
  out_byte(DMA_TOP, top_addr);	/* output highest 4 bits */
  out_byte(DMA_COUNT, low_ct);	/* output low 8 bits of count - 1 */
  out_byte(DMA_COUNT, high_ct);	/* output high 8 bits of count - 1 */
}

/*===========================================================================*
 *				abort_com				     *
 *===========================================================================*/
PRIVATE void abort_com()
{
/*
 * Abort_com will terminate the current command the controller is working
 * on.  Most likely, a bad sector has been found on the disk.
 */

	message dummy;		/* dummy message for receive() */
	
	out_byte(ACR, 0x2);	/* Turn off everything except interrupts */
	if (com_out(0, 0x1) != OK)
		panic("Winchester controller not accepting abort command", 0);

	en_wini_int();			/* can now allow interrupts */
	receive(HARDWARE, &dummy);	/* wait for the interrupt */

	if (win_results() != OK)
		w_reset();		/* this should not be necessary */

	out_byte(ACR, 0x0);	/* shut off interrupts */

}

/*===========================================================================*
 *				dump_isr				     *
 *===========================================================================*/
PRIVATE dump_isr()
{
/*
 * Dump_isr will print out an informative message of what the controller
 * has reported to the system.  This is for use in debugging and in case
 * of hardware error.
 */
	register int stat = in_byte(ISR);

#define ISR_TERM_ERROR(x)	(x & 0x80)
#define	ISR_INVALID_COM(x)	(x & 0x40)
#define ISR_COMMAND_REJ(x)	(x & 0x20)
#define ISR_DRIVE(x)		(x & 0x04) ? 2 : 1
#define ISR_ERROR_REC(x)	(x & 0x02)
#define ISR_EQP_CHECK(x)	(x & 0x01)

	printf("Drive #: %d, ST506 WINI adapter reports:\n", ISR_DRIVE(stat));
	if (ISR_TERM_ERROR(stat)) printf("\t\tTermination Error\n");
	if (ISR_INVALID_COM(stat)) printf("\t\tInvalid Command Sent\n");
	if (ISR_COMMAND_REJ(stat)) printf("\t\tCommand was rejected\n");
	if (ISR_ERROR_REC(stat)) printf("\t\tError rec. procedure invoked\n");
	if (ISR_EQP_CHECK(stat)) printf("\t\tEquipment check, reset needed\n");

}

/*===========================================================================*
 *				set_command			             *
 *===========================================================================*/
PRIVATE void set_command(wn)
register struct wini *wn;
{
	command[0] = wn->wn_opcode == DISK_READ ? WIN_READ : WIN_WRITE;
	command[1] = ((wn->wn_head << 4) & 0xF0) | 
					((wn->wn_cylinder >> 8) & 0x03);
	command[2] = wn->wn_cylinder & BYTE;
	command[3] = wn->wn_sector;
	command[4] = 2;
	command[5] = BLOCK_SIZE/SECTOR_SIZE;	
}


/*===========================================================================*
 *				status					     *
 *===========================================================================*/
PRIVATE int status()
{
/* Get status of the controler */

  return in_byte(ASR);
}