[comp.unix.microport] SCSI driver for Seagate ST-01 controller

ylo@sauna.HUT.FI (Tatu Yl|nen) (06/14/89)

I have written an scsi driver for microport unix.  I uses the Seagate
ST-01 controller.  It has been in use for about an year now on several
machines, and has worked well.  It supports multiple drives, mutiple
partitions on drive, etc.  I have used with a Priam 738 (337 megabytes)
drive, some others with Seagate ST277N (64 megabytes) drives.

For more information, read the README file.

------------ cut here ------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  README config makefile scsi.c scsi.h scsiasm.s scsipart.c
# Wrapped by ylo@sauna on Wed Jun 14 13:34:18 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(7397 characters\)
sed "s/^X//" >README <<'END_OF_README'
XReadme for ST-01 SCSI driver for microport unix system V
X
X
XCopyright (c) 13.6.1989 Tatu Ylonen
X
X
XI hereby allow use and distribution of this device driver, provided
Xthat no charge is asked for it.
X
X
XThis is an SCSI device driver for Microport Unix System V/386 with
XSeagate ST-01 SCSI adaptor.  (This probably works with other 386 unix
Xas well, but has not been tested).
X
XThis driver offers the following features
X  - can use almost any scsi disks
X  - no limit on disk or partition size
X  - each physical drive can partitioned in upto 15 partitions
X  - supports multiple drives (up to 7) on a single controller
X  - transfer rate four to six times that of the standard MFM disk
X    driver supplied by microport
X
XThe following drawbacks are known
X  - does not use dma, as the controller does not support dma
X  - character devices (raw io) do not work
X  - this is a disk driver only.  SCSI tapes have not been tested.
X  - you cannot boot from the scsi drive (I do not have boot source
X    code)
X  - lots of data movement is done at interrupt time (although this
X    tries to enable higher priority interrupts, like the serial ports)
X
XI have used this for about an year with a Priam 738 (337 megabytes
Xformatted, 20 ms) drive.  Others have used this with Seagate ST227N
Xdrives (64 megabytes).  I have had no problems with the driver (I have
Xnever (after the initial creation of the driver) had a panic that I
Xcould have associated with the driver (and only a couple of panics
Xaltogether)).  The drive has been partitioned to three partitions:
X10MB, 20MB and 307 MB.  (I have never had any problems with large
Xpartitions).  I archieved the best performance when using 1024 byte
Xsectors on the disk.  The ST227N drive had some problems with 1024
Xbyte sectors (the machine rebooting randomly during heavy disk
Xaccesses); I have never had these problems with the Priam disk, and
Xthe Seagate drives appear to work ok with 512 byte sectors.
X
XThe driver works with Dosmerge without any problems.
X
X
XINSTALLATION
X
X(Assuming you have installed the st01 adaptor).
X(BTW, I removed the bios rom on the controller.  It does not work with
Xall drives.  Also, the machine boots faster.  This driver does not
Xneed the rom and will wait for the drives to become ready if needed)
X
X1. Create the directory /etc/atconf/modules/scsi, and copy all files to
X   that directory.
X
X2. Check the config file for correct parameters (major device number,
X   interrupt number).  Major device number and the interrupt number
X   should both be exclusively used for the scsi driver.  (I have heard
X   that some optional driver conflicts with the default major number
X   here, 9)
X
X3. Type make.
X
X4. Add the line 'scsi,' in your system file (usually
X   /etc/atconf/systems/system.std).  Remember to back up your old system
X   file first.  Type mkunix (being logged in as root).
X
X5. Back up your old /unix (move to, say, /unix.old, so that you can
X   boot it if something goes wrong).
X
X6. Bring the system down (init 0).
X
X7. Connect the scsi drive(s).  You will need the appropriate cables.
X   Remember to have terminators in the last drive (if you have only
X   one drive, you need to do nothing, as the terminators are probably
X   connected by default).
X
X8. Reboot.  While booting, you should see a copyright message from the
X   driver, and a message identifying the manufacturer of the drive.
X   The message contains drive number and a manufacturer-specified text
X   (usually drive model).  If the drive has been partitioned, the
X   sizes of the partitions will also be shown.
X
X   If you get repeated messages "someone is sitting on the bus,
X   sending hard reset to scsi bus" (or something like that), it
X   probably means that the drive is not powered on (or that something
X   else is wrong with the connections).
X
X   If you get scsi timeouts when trying to use the disk (at a later
X   time, after formatting), it probably means that the interrupt
X   number is not set correctly.  Check config file and jumpers on the
X   card.
X
X9. Create device i-nodes for the scsi disk.
X   I have the following i-nodes.  Here 9 is the major device number
X   (must match that in config file).  The minor device contains the
X   partition number in the lower four bits, and disk number in the
X   upper four bits.  Partition number 15 is reserved (contains the
X   entire disk, including the partition table).
X
X   The devices /dev/rscsi<drive>s and /dev/scsi<drive>s are mandatory.
X   In addition, you can create as many i-nodes for partitions as you
X   wish.
X
X   Here is a list of scsi-related i-nodes on my system.
X   
Xcrw-------   1 root     sys        9, 15 Apr 22 03:13 /dev/rscsi0s
Xbrw-------   1 root     sys        9, 15 Jan 14 06:01 /dev/scsi0s
Xbrw-------   1 root     sys        9,  0 Jun 13 18:08 /dev/scsi0s0
Xbrw-------   1 root     sys        9,  1 Jun 13 18:08 /dev/scsi0s1
Xbrw-------   1 root     sys        9,  2 Jun 13 18:08 /dev/scsi0s2
X
X10. Run scsipart.  The program asks for drive number.  Enter 0 (or the
X    number of your drive).  When the program asks whether to format,
X    answer yes, and confirm.  Formatting the Priam 337 megabyte disk
X    takes about half an hour.  In that time the driver does not print
X    anything.
X
X    You cannot specify your own defects.  The scsi drive will
X    automatically use all manufacturer-supplied defects, plus those
X    found during formatting.  (The scsi interface supports automatic
X    remapping of bad blocks even after formatting, but the driver does
X    not support this.)
X
X    When formatting is complete, the number of available disk sectors
X    is printed.
X
X    You can now define the partitions.  Note that partitions are
X    defined in sectors, while filesystems for mkfs are specified in
X    1024 byte blocks.  Thus, if you have 512 byte sectors, the lengths
X    of partitions should be twice the number of blocks.  The block 0
X    contains the partition table and cannot be used.  Otherwise, you
X    can freely create the partitions as you wish.  The first partition
X    in the table is number 0, the second number 1 and so on.
X
X    The partitions become valid when you exit scsipart.
X
X    I recommend that you try with several different interleave values
X    for optimal performance.  I got acceptable performance with 1024
X    byte sectors (use them if they work) and interleave 9 (sic).  That
X    was with an early version; the data transfer routines have been
X    optimized quite a bit since then (see the assembler hacks).
X
X11. Create file systems on the partitions.  See mkfs(1M).  Remeber to
X    give the correct block size.  Specify that mkfs leave no
X    interleave (so it is easier to specify the interleave when
X    formatting).
X
X12. Add the partitions to /etc/fstab.  My fstab looks like
X
X/dev/scsi0s0 /tmp
X/dev/scsi0s1 /u2
X/dev/scsi0s2 /u
X
X13.  Reboot.  The partitions should now get mounted and the system be
Xready for use.  (Check /etc/rc2.d if they do not get mounted).  You
Xcan label the file systems (see labelfs(1M) if you don't want to see
Xthe messages from mount.
X
X
XYou can use the file systems just like any other file systems.  All
Xnormal utilities work (except of course things like mkpart etc).  Fsck
Xand others work ok.
X
X----------------
X
XI can give only very limited support to anyone using this.  However,
XI can be reached as
X
X   ylo@hupu.hut.fi
X
Xor my normal mail
X
X  Tatu Ylonen
X  Tunnelitie 14 c 13
X  00320 HELSINKI
X  Finland
X
X  Tel. +358-0-573290
X
END_OF_README
if test 7397 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f config -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"config\"
else
echo shar: Extracting \"config\" \(260 characters\)
sed "s/^X//" >config <<'END_OF_config'
X*
X* config file for scsi driver on Microport System V/386
X* Copyright (c) 9.6.1988 Tatu Yl|nen
X*               All rights reserved.
X*
X
Xcharacter(9)
Xblock(9)
X
Xprefix = scsi
X
Xintvec = 5
X
Xintpri = SPL5
X
Xfunctions = init, open, close, read, write, ioctl, strategy
END_OF_config
if test 260 -ne `wc -c <config`; then
    echo shar: \"config\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f makefile -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"makefile\"
else
echo shar: Extracting \"makefile\" \(271 characters\)
sed "s/^X//" >makefile <<'END_OF_makefile'
X#
X# makefile for scsi driver
X#
X# Copyright (c) 8.6.1988 Tatu Yl|nen
X#               All rights reserved.
X#
X#
X
XCFLAGS = -DDEBUG # -DDEBUG0
X
Xall: scsi.o scsiasm.o scsipart
X
Xscsi.o: scsi.c scsi.h
Xscsiasm.o: scsiasm.s
X
Xscsipart: scsipart.c scsi.h
X	cc -o scsipart scsipart.c
X
END_OF_makefile
if test 271 -ne `wc -c <makefile`; then
    echo shar: \"makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f scsi.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"scsi.c\"
else
echo shar: Extracting \"scsi.c\" \(42481 characters\)
sed "s/^X//" >scsi.c <<'END_OF_scsi.c'
X/*
X
XSCSI disk driver for unix system V (Microport system V/386)
XThis driver uses the ST-01 controller.  This supports multiple initiators
Xand multiple targets.
X
XCopyright (c) 9.6.1988 Tatu Yl|nen
X              All rights reserved.
X
X*/
X
X#include <sys/sysmacros.h>
X#include <sys/types.h>
X#include <sys/param.h>
X#include <sys/signal.h>
X#include <sys/errno.h>
X#include <sys/dir.h>
X#include <sys/file.h>
X#include <sys/user.h>
X#include <sys/buf.h>
X#include <sys/iobuf.h>
X#include <sys/immu.h>
X#include <sys/region.h>
X#include <sys/proc.h>
X#include "scsi.h"
X
X#define COPYRIGHT "scsi disk driver V1.0  Copyright (c) 9.6.1988 Tatu Yl|nen"
X
X#define ASM   /* use certain routines coded in assembly */
X
X#define TICKSPERSECOND HZ /* system timer ticks per second */
X#define RWTIMEOUT       5 /* timeout for read/write waiting for reconnect */
X#define MYSLEEPPRI      (PZERO+1) /* sleeping priority (allow signals) */
X#define PAGESIZE     4096 /* page size in sptalloc */
X
X#define UNITNO(minornum) ((minornum)>>4) /* minor device to unit */
X#define PARTNO(minornum) ((minornum)&15) /* minor device to partition */
X#define BLTOSEC(unit,bl) ((long)(bl)*NBPSCTR/d[unit].blocksize)
X                                 /* converts block number to sector number */
X#define BLPTOSEC(unit,part,bl) (BLTOSEC(unit,bl)+d[unit].parts[part].start)
X                        /* calculates sector number from block and partition */
X
X#define splnointrs() spl6() /* disable any interrupts to this driver (clock!)*/
X
X#define NULL 0
X
X#define SCSIBASE        0x000cb000l /* address of the scsi controller(no rom)*/
X#define SCSICONTROLOFS  0x00000a00l /* control/status port offset */
X#define SCSIDATAOFS     0x00000c00l /* data port offset */
X#define SCSIDATAPORTSZ  1024        /* size of data port */
X#define SCSISIZE        4096        /* size of controller memory area */
X
X#define MYADDR          0x80        /* my address as bit mask */
X
X#define CMDENABLE       0x80        /* scsi enable */
X#define CMDENINTR       0x40        /* enable scsi interrupts */
X#define CMDPARENB       0x20        /* enable scsi parity generation */
X#define CMDSTARB        0x10        /* start arbitration bit */
X#define CMDATTN         0x08        /* scsi attention */
X#define CMDBSY          0x04        /* scsi busy */
X#define CMDSEL          0x02        /* scsi select */
X#define CMDRST          0x01        /* scsi reset */
X
X#define STARBCOMPL      0x80        /* arbitration complete bit */
X#define STPARERR        0x40        /* parity error bit */
X#define STSEL           0x20        /* scsi select */
X#define STREQ           0x10        /* scsi req */
X#define STCD            0x08        /* scsi c/d */
X#define STIO            0x04        /* scsi i/o */
X#define STMSG           0x02        /* scsi msg */
X#define STBSY           0x01        /* scsi busy */
X
X#define CMDBASE         (CMDPARENB|CMDENINTR) /* cmd when doing nothing */
X
X#define SCSIREAD        0x28        /* read command code (10-byte) */
X#define SCSIWRITE       0x2a        /* write command code (10-byte) */
X#define SCSIINQUIRY     0x12        /* inquiry command (6-byte) */
X#define SCSIREADCAPACITY 0x25       /* read drive capacity and block size */
X#define SCSIMODESELECT  0x15        /* select format parameters */
X#define SCSIFORMATUNIT  0x04        /* hard format the scsi drive */
X#define SCSIREQSENSE    0x03        /* request sense command */
X#define SCSITESTREADY   0x00        /* test unit ready command */
X
X#define MSGMYIDENTIFY   0xc0        /* our identify message to send to target */
X
X#define MSGCOMPLETE     0x00        /* command complete */
X#define MSGSAVEDATAPTR  0x02        /* save data pointer */
X#define MSGRESTOREPTR   0x03        /* restore pointer */
X#define MSGDISCONNECT   0x04        /* disconnect message */
X#define MSGIDETECTERR   0x05        /* initiator detected error */
X#define MSGABORT        0x06        /* scsi abort message */
X#define MSGMSGREJECT    0x07        /* message reject */
X#define MSGNOP          0x08        /* no operation message */
X#define MSGIDENTIFY     0x80        /* identify message from target */
X
X#define COK             0 /* command completed successfully */
X#define CNOCONNECT      1 /* no connection could be made to the drive */
X#define CBUSBUSY        2 /* the bus is busy and cannot be cleared */
X#define CTIMEOUT        3 /* timeout waiting for the drive */
X#define CERROR          4 /* an error was returned by the target */
X#define CBUSY           5 /* the drive is busy - wait and retry later */
X#define CDISCONNECT     6 /* target disconnected; this is not an error */
X
Xstatic char *baseaddr=NULL; /* controller base address */
Xstatic char *cmdport;  /* controller command port */
Xchar *scsidataport; /* controller data port */
X
Xstatic SCSIDRIVE d[SCSIMAXDRIVES]; /* drive information */
X
Xstatic struct buf scsibuf; /* used for raw io */
Xstatic char rawiobuf[SCSIDATAPORTSZ]; /* data copied temporarily here */
X
Xstatic char timeouting=0;
Xstatic char intrserviced=0;
X
Xstatic marknotbusy(); /* marks the unit as not busy and starts any pending io */
X
X/* This generates a hard reset on the scsi bus by asserting the reset line */
X
Xstatic resetscsibus()
X{
X  long l;
X  int a;
X
X  printf("scsi: sending hard reset to scsi bus\n");
X  *cmdport=CMDBASE|CMDENABLE|CMDRST;
X  for (l=0;l<10000l;l++); /* keep rst asserted for a while */
X  *cmdport=CMDBASE;
X  for (l=0;l<500000l;l++); /* give some time to recover before returning */
X  for (a=0;a<SCSIMAXDRIVES;a++)
X    d[a].connected=0; /* do this just in case */
X}
X
X/* This arbitrates for the scsi bus and selects the desired target.  This
X   returns a C* result code.  This will also set the connected flag
X   if appropriate.  If there are possibly recoverable errors, this will
X   retry.  The calling procedure should not retry if this returns
X   failure. */
X
Xstatic int arbitrate(unit)
Xint unit;
X{
X  long l;
X  int arbcnt,bsycnt; /* retry counts */
X
X  arbcnt=0;
X  bsycnt=0;
X retryarb:
X  *cmdport=CMDBASE;
X  *scsidataport=MYADDR;
X  *cmdport=(CMDBASE&~CMDENINTR)|CMDSTARB;
X  /* wait for arbitration complete */
X  for (l=0;l<300000l;l++)
X    if (*cmdport & STARBCOMPL)
X      goto gotarb;
X  /* arbitration timeout - someone is keeping the bus reserved. */
X  *cmdport=CMDBASE;
X  if (arbcnt >= 2) /* retry twice, then give up */
X    {
X      printf("scsi: arbitration timeout - someone is sitting on the bus?\n");
X      return CBUSBUSY;
X    }
X  resetscsibus(); /* reset the bus and hope the condition clears */
X  arbcnt++;
X  goto retryarb;
X gotarb:
X  arbcnt=0;
X  *scsidataport|=1<<unit;
X  *cmdport=CMDBASE|CMDENABLE|CMDSEL|CMDATTN;
X  for (l=0;l<200000l;l++)
X    if (*cmdport & STBSY)
X      goto gotbusy;
X  /* timeout waiting for busy */
X  *cmdport=CMDBASE;
X  if (bsycnt >= 2)
X    {
X#ifdef DEBUG0
X      printf("scsi: arbitrate returning CNOCONNECT\n");
X#endif
X      return CNOCONNECT; /* probably no drive present */
X    }
X  bsycnt++;
X  for (l=0;l<2000l;l++); /* give some time for the drive */
X#ifdef DEBUG0
X  printf("scsi: busy timeout on drive %d\n",unit);
X#endif
X  goto retryarb;
X gotbusy:
X  d[unit].connected=1;
X  if (!d[unit].nomsgs)
X    {
X      *cmdport=CMDBASE|CMDENABLE|CMDATTN;
X      for (l=0;l<200000l;l++)
X        if ((*cmdport & (STMSG|STCD|STIO|STREQ)) == (STMSG|STCD|0|STREQ))
X          goto gotmsgreq;
X      /* timeout waiting for msg out */
X      printf("scsi: timeout identify msg out - drive %d does not support messages?\n",
X             unit);
X      d[unit].nomsgs=1; /* don't try messages again */
X      *cmdport=CMDBASE|CMDENABLE;
X      return COK;
X     gotmsgreq:
X      *scsidataport=MSGMYIDENTIFY; /* this enables disconnect */
X      /* fall to successful completion */
X    }
X#ifdef DEBUG
X  if (!(*cmdport & STBSY))
X    {
X      printf("scsi: after successful arbitrate !STBSY\n");
X      for (l=0;l<10000000l;l++);
X      arbcnt++;
X      goto retryarb;
X    }
X#endif /* DEBUG */
X  *cmdport=CMDBASE|CMDENABLE;
X  return COK;
X}
X
X#ifndef ASM
X
X/* This copies data to the scsi data port as fast as possible.  This could
X   even be coded in assembly language for efficiency. */
X
Xstatic sendtoscsi(buf,len)
Xregister char *buf;
Xregister int len;
X{
X  while (len--)
X    *scsidataport=(*buf++);
X}
X
X/* This reads data from the scsi data port as fast as possible. */
X
Xstatic getfromscsi(buf,len)
Xregister char *buf;
Xregister int len;
X{
X  while (len--)
X    *buf++=(*scsidataport);
X}
X
X#endif /* ASM */
X
X/* This implements the scsi data out phase.  There are several operating
X   modes for this.  1) normal as fast as possible io 2) slow io where we
X   check req individually for each character 3) moving data directly from
X   user space. If en error is encountered (such as a protection fault when
X   moving data from user space), this will return 0.  Moving data from
X   user space is only implemented in "fast" mode. */
X
Xstatic int dataout(unit)
Xint unit;
X{
X  register int le;
X  register long l;
X  char slow;
X
X  slow=d[unit].xferslow;
X  for (;d[unit].xferlen > 0;d[unit].xferlen-=le,d[unit].xferbuf+=le)
X    {
X      for (l=0;l<100000l;l++)
X        if (*cmdport & STREQ)
X          goto gotreq;
X      /* timeout */
X      break;
X     gotreq:
X      if ((*cmdport & (STMSG|STCD|STIO)) != (0|0|0))
X        break;
X      if (slow)
X        {
X          le=1;
X          *scsidataport=(*d[unit].xferbuf);
X          continue;
X        }
X      le=d[unit].xferlen;
X      if (le > SCSIDATAPORTSZ)
X        le=SCSIDATAPORTSZ;
X      if (le > d[unit].blocksize)
X        le=d[unit].blocksize;
X      if (d[unit].xferphys)
X        {
X	  if (le > sizeof(rawiobuf))
X	    le=sizeof(rawiobuf);
X          if (copyin(d[unit].xferbuf,rawiobuf,le) == -1)
X            return 0;
X          sendtoscsi(rawiobuf,le);
X        }
X       else
X        sendtoscsi(d[unit].xferbuf,le);
X    }
X  while ((*cmdport & (STMSG|STCD|STIO|STREQ)) == (0|0|0|STREQ))
X    {
X      *scsidataport=0;
X      for (l=0;l<1000l;l++)
X        if (*cmdport & STREQ)
X          break;
X    }
X  return 1;
X}
X
X/* this implements the scsi data in phase.  This copies data from
X   scsi bus to system memory.  There are three modes of operation:
X   1) "slow" transfer to kernel memory 2) "fast" transfer to kernel
X   memory 3) "fast" transfer to user memory */
X
Xstatic int datain(unit)
Xint unit;
X{
X  register int le;
X  register long l;
X  char slow;
X
X  slow=d[unit].xferslow;
X  for (;d[unit].xferlen > 0;d[unit].xferlen-=le,d[unit].xferbuf+=le)
X    {
X      for (l=0;l<100000l;l++)
X        if (*cmdport & STREQ)
X          goto gotreq;
X      /* timeout */
X      break;
X     gotreq:
X      if ((*cmdport & (STMSG|STCD|STIO)) != (0|0|STIO))
X        break;
X      if (slow)
X        {
X          le=1;
X          *d[unit].xferbuf=(*scsidataport);
X          continue;
X        }
X      le=d[unit].xferlen;
X      if (le > SCSIDATAPORTSZ)
X        le=SCSIDATAPORTSZ;
X      if (le > d[unit].blocksize)
X        le=d[unit].blocksize;
X      if (d[unit].xferphys)
X        { /* directly to user space */
X	  if (le > sizeof(rawiobuf))
X	    le=sizeof(rawiobuf);
X          getfromscsi(rawiobuf,le);
X          if (copyout(rawiobuf,d[unit].xferbuf,le) == -1)
X            return 0;
X        }
X       else
X        getfromscsi(d[unit].xferbuf,le);
X    }
X  while ((*cmdport & (STMSG|STCD|STIO|STREQ)) == (0|0|STIO|STREQ))
X    {
X      le=(*scsidataport);
X      for (l=0;l<1000l;l++)
X        if (*cmdport & STREQ)
X          break;
X    }
X  return 1;
X}
X
X/* This is called when we are connected to the target on the scsi bus.
X   This will do any exchange of data with the target.  The dialog is
X   controlled by the target.  This will remain connected until the
X   target sends a disconnect message, the command is complete, or a timeout
X   if encountered. There should be no interrupts while this is executing,
X   as the unit should be connected all the time.  This returns a C* completion
X   status. Normally, this should return quite fast.  This will never sleep
X   and will also be called at interrupt time.  With dumb drives not supporting
X   disconnect (are there any?) this would block the system for the duration
X   of this call.  This will only mark the drive not busy if the command
X   completed successfully.  If an error is returned, the drive has not
X   been marked not busy. */
X
Xstatic int doxfernosleep(unit)
Xint unit;
X{
X  int a;
X  long l;
X
X  for (l=0;l<1000000l || d[unit].xfertimeout == 0;l++)
X    {
X      if (!(*cmdport & STBSY))
X        {
X#ifdef DEBUG
X          printf("scsi: doxfernosleep: !STBSY unit %d\n",unit);
X#endif
X          d[unit].connected=0;
X          return CERROR; /* we are no longer connected??? */
X        }
X      if (!(*cmdport & STREQ))
X        continue; /* loop until target requesting something */
X#ifdef DEBUG1
X      printf("scsi: doxfernosleep: new state=%x\n",*cmdport);
X#endif
X      switch ((*cmdport & (STMSG|STCD|STIO)) & 0xff)
X        {
X          case 0|0|0: /* data out */
X            if (!dataout(unit))
X              {
X#ifdef DEBUG
X                printf("scsi: dataout returned error; unit=%d\n",unit);
X#endif
X                return CERROR;
X              }
X            break;
X          case 0|0|STIO: /* data in */
X            if (!datain(unit))
X              {
X#ifdef DEBUG
X                printf("scsi: datain returned error; unit=%d\n",unit);
X#endif
X                return CERROR;
X              }
X            break;
X          case 0|STCD|0: /* command out */
X            *scsidataport=(*d[unit].xfercmd++);
X            break;
X          case 0|STCD|STIO: /* status in */
X            d[unit].xferstatus=(*scsidataport);
X            break;
X          case STMSG|STCD|0: /* msg out */
X            /* we should never get here.  We don't want to send a message.
X               Lets just drop attention and hope the drive understands. */
X#ifdef DEBUG0
X            printf("scsi: unexpected msg out state; status=%x\n",*cmdport);
X#endif
X            *scsidataport=MSGNOP; /* send a no-operation message */
X            *cmdport=CMDBASE|CMDENABLE;
X            break;
X          case STMSG|STCD|STIO: /* msg in */
X            a=(*scsidataport) & 0xff;
X            switch (a)
X              {
X                case MSGCOMPLETE:
X                  d[unit].connected=0;
X                  *cmdport=CMDBASE;
X#ifdef DEBUG0
X                  printf("scsi: command complete message received\n");
X#endif
X                  if (d[unit].xferstatus == 0) /* completed succesfully */
X                    {
X                      marknotbusy(unit,COK);
X                      return COK;
X                    }
X                  return CERROR;
X                case MSGSAVEDATAPTR:
X                  d[unit].savedbuf=d[unit].xferbuf;
X                  d[unit].savedlen=d[unit].xferlen;
X                  break;
X                case MSGRESTOREPTR:
X                  d[unit].xferbuf=d[unit].savedbuf;
X                  d[unit].xferlen=d[unit].savedlen;
X                  d[unit].xfercmd=d[unit].savedcmd;
X                  break;
X                case MSGDISCONNECT:
X                  d[unit].connected=0;
X                  d[unit].xfertime=1;
X                  *cmdport=CMDBASE;
X#ifdef DEBUG0
X                  printf("scsi: disconnected\n");
X#endif
X                  return CDISCONNECT;
X                case MSGMSGREJECT:
X                  break; /* the target rejected some message... Who cares. */
X                case MSGNOP:
X                  break; /* this should not be sent by the target, but... */
X                case MSGIDENTIFY:
X                  break; /* we don't care about targets identify messages */
X                default:
X                  if (a & 0x80)
X                    break; /* assume it is an identify message */
X                  printf("scsi: unknown message received from drive %d: %x\n",
X                         unit,a);
X                  break;
X              }
X            break;
X          default:
X            /* unexpected stack state.  Now I don't know what to do.  Lets
X               hope the drive changes to another state. */
X#ifdef DEBUG
X            printf("scsi: unexpected bus state: status=%x\n",*cmdport);
X#endif
X            break;
X        }
X    }
X  return CTIMEOUT;
X}
X
X/* This implements polled wait for reconnect.  This is mainly used at
X   system initialization time when the interrupt system may not be fully
X   initialized.  This returns true if reconnect was encountered.
X   If there is no successful reconnect, this will time out after a few
X   seconds and return false. */
X
Xstatic int polledwaitreconnect(unit)
Xint unit;
X{
X  long l;
X  unsigned char ch;
X
X  *cmdport=CMDBASE&~CMDENINTR;
X  for (l=0;l<2000000l;l++)
X    {
X      if ((*cmdport & (STSEL|STIO|STBSY)) != (STSEL|STIO|0))
X        continue;
X      ch=(*scsidataport);
X      if (!(ch & MYADDR))
X        {
X#ifdef DEBUG
X          printf("scsi: polled releselection was not for me: %x\n",ch);
X#endif
X          continue;
X        }
X      ch&=~MYADDR;
X      if (!(ch & (1 << unit)))
X        {
X#ifdef DEBUG
X          printf("scsi: reselecting (polled) unit other than expected: %x\n",
X                 ch);
X#endif
X          continue;
X        }
X      *cmdport=(CMDBASE&~CMDENINTR)|CMDBSY|CMDENABLE;
X      for (l=0;l<100000l;l++)
X        if (!(*cmdport & STSEL))
X          break;
X      for (l=0;l<100000l;l++)
X	if (!(*cmdport & STBSY))
X	  break;
X      *cmdport=CMDBASE|CMDENABLE;
X      d[unit].connected=1;
X      return 1;
X    }
X  *cmdport=CMDBASE;
X#ifdef DEBUG
X  printf("scsi: timeout polled wait for reselection from %d\n",unit);
X#endif
X  return 0;
X}
X
X/* This starts the scsi command.  Interrupts may be enabled when this is
X   called.  When this retuns, either the drive must have been marked not
X   busy (error or completion), or the target has disconnected and the drive
X   will be marked not busy when an interrupt or timeout comes.  A failure
X   to mark the drive not busy will block the drive from all future
X   requests.  If retries are made for a command, this will be called to
X   start the retry. */
X
Xstatic int startscsi(unit)
Xint unit;
X{
X  int a;
X
X  d[unit].xferbuf=d[unit].savedbuf=d[unit].origbuf;
X  d[unit].xferlen=d[unit].savedlen=d[unit].origlen;
X  d[unit].xfercmd=d[unit].savedcmd=d[unit].origcmd;
X
X startagain:
X
X#ifdef DEBUG0
X  printf("scsi: arbitrating for %d\n",unit);
X#endif
X  a=arbitrate(unit);
X  if (a != COK) /* arbitrate does the necessary retries */
X    return a;
X
X#ifdef DEBUG0
X  printf("scsi: arbitration complete\n");
X#endif
X  while (1)
X    {
X      a=doxfernosleep(unit);
X#ifdef DEBUG0
X      printf("scsi: doxfernosleep returned %d\n",a);
X#endif
X      if (a == CDISCONNECT)
X        { /* The target disconnected */
X          if (d[unit].xferpolled)
X            {
X#ifdef DEBUG0
X              printf("scsi: polled wait\n");
X#endif
X              if (!polledwaitreconnect(unit))
X                goto retry;
X#ifdef DEBUG0
X              printf("scsi: polled wait complete - reconnected\n");
X#endif
X              continue;
X            }
X          if (d[unit].currentbuf)
X            { /* We are doing io for a buffer */
X              /* All we have to do is to return; intr will call iodone. */
X              d[unit].xfertime=1; /* enable timeouts */
X              return CDISCONNECT;
X            }
X
X          /* disconnect; we do not have a buffer but may use intrs */
X          /* This is not too efficient, as the delay from wakeup to
X             continuing execution might be substantial, but this is not
X             a typical case, as transfers do not normally go to
X             internal buffers. */
X          d[unit].xfertime=1; /* enable timeouts */
X          if (sleep(&d[unit].connected,MYSLEEPPRI|PCATCH) == 1)
X            { /* cought a signal */
X              d[unit].busy=0; /* I guess this is an atomic operation */
X              return CERROR;
X            }
X          if (!d[unit].connected)
X            goto retry; /* it must have been a timeout */
X          continue;
X        }
X      if (a == COK || a == CNOCONNECT || a == CBUSBUSY)
X        {
X          if (a != COK)
X            marknotbusy(unit,a);
X          return a;
X        }
X      goto retry;
X    }
X retry:
X  /* a possibly recoverable error was encountered */
X#ifdef DEBUG
X  printf("scsi: startscsi: retrying or failing\n");
X#endif
X  if (d[unit].xferretries > 1)
X    {
X      d[unit].xferretries--;
X      goto startagain;
X    }
X  if (a == CTIMEOUT || a == CBUSBUSY)
X    resetscsibus(); /* in case the drive was hanging on the bus */
X  d[unit].connected=0;
X  *cmdport=CMDBASE;
X  marknotbusy(unit,a);
X  return a; /* too many retries - return error */
X}
X
X/* This executes the given command on the unit.  This returns command status
X   (C* constants).  There is no need to retry the operation after calling
X   this. */
X
Xstatic int doscsicmd(unit,cmd,buf,len,timeout,retries,slow,phys,polled,bp)
Xint unit,    /* drive number */
X    len,     /* buffer size */
X    timeout, /* timeout in 1/10 secs if != 0 */
X    retries, /* number of tries before returning failure; 1=try only once */
X    slow,    /* set to true if slow transfer (only true for read & write) */
X    phys,    /* set to true if xfer directly to/from user space (raw io) */
X    polled;  /* set to true if polled transfer */
Xchar *cmd,   /* command to execute */
X     *buf;   /* transfer buffer address */
Xstruct buf *bp; /* io buffer being executed, or NULL */
X{
X  int x;
X
X#ifdef DEBUG0
X  printf("scsi: cmd unit=%d buf=%x len=%d timeout=%d retries=%d slow=%d phys=%d polled=%d bp=%x\n",
X         unit,buf,len,timeout,retries,slow,phys,polled,bp);
X#endif
X
X  x=splnointrs();
X  if (d[unit].busy)
X    {
X      if (bp)
X        {
X          splx(x);
X          return CBUSY;
X        }
X      while (d[unit].busy)
X        {
X          splx(x);
X          if (sleep(&d[unit].busy,MYSLEEPPRI) == 1)
X            { /* cought a signal */
X              return CERROR;
X            }
X          x=splnointrs();
X        }
X    }
X  d[unit].origbuf=buf;
X  d[unit].origlen=len;
X  d[unit].origcmd=cmd;
X  d[unit].xferslow=slow;
X  d[unit].xferstatus=0x01; /* indicates error */
X  d[unit].xfertimeout=timeout?timeout+1:0;
X  d[unit].xfertime=0;
X  d[unit].xferretries=retries;
X  d[unit].xferphys=phys;
X  d[unit].xferpolled=polled;
X  d[unit].currentbuf=bp;
X  d[unit].busy=1;
X  splx(x);
X
X  return startscsi(unit);
X}
X
Xstatic int dorw(unit,sec,buf,len,flags,polled,bp)
Xint unit;
Xlong sec;
Xchar *buf;
Xint len,flags,polled;
Xstruct buf *bp;
X{
X  char cmd[10];
X  int nblocks,a;
X
X  a=d[unit].blocksize;
X  if (a == 0)
X    a=512;
X  nblocks=(len+a-1)/a;
X
X#ifdef DEBUG0
X  printf("scsi: dorw: unit=%d sec=%d buf=%x len=%d flags=%x polled=%d bp=%x\n",
X         unit,sec,buf,len,flags,polled,bp);
X#endif
X
X  cmd[0]=(flags & B_READ)?SCSIREAD:SCSIWRITE;
X  cmd[1]=0x00; /* LU & RADDR */
X  cmd[2]=sec >> 24;
X  cmd[3]=sec >> 16;
X  cmd[4]=sec >> 8;
X  cmd[5]=sec;
X  cmd[6]=0;
X  cmd[7]=nblocks >> 8;
X  cmd[8]=nblocks;
X  cmd[9]=0;
X
X  return doscsicmd(unit,cmd,buf,len,RWTIMEOUT,3,0,flags & B_PHYS,polled,bp);
X}
X
X/* This starts an io operation on the given buffer.  This is called when
X   a new buffer is added to the io queue, and when a previous operation has
X   completed, to start io on the next buffer.  If the unit is busy, this will
X   do nothing.  If it is not busy, this will start the request.  This should
X   be called with splnointrs.  Any routines called by this will not change
X   the interrupt level to a lower value. */
X
Xstatic startbufferio(unit,bp)
Xint unit;
Xstruct buf *bp;
X{
X  dorw(unit,BLPTOSEC(unit,PARTNO(minor(bp->b_dev)),bp->b_blkno),
X       bp->b_un.b_addr,bp->b_bcount,bp->b_flags,0,bp);
X}
X
X/* This will start a pending io request in the system.  If bp (the previous
X   request) is non-NULL, this will first remove bp from the list of
X   pending requests.  This will then start a new request if there are any.
X   This can only be called with splnointrs, and when the unit is not busy. */
X
Xstatic startpendingreq(unit,bp)
Xint unit;
Xstruct buf *bp;
X{
X  struct buf *ap;
X  int x;
X
X  x=splnointrs();
X  if (bp)
X    {
X      ap=bp->av_forw;
X      if (!ap)
X        if (d[unit].reqlist != bp)
X          ap=d[unit].reqlist;
X    }
X   else
X    ap=d[unit].reqlist;
X  /* ap is the next request to process, or NULL if there are none pending. */
X  if (bp)
X    {
X      if (bp == d[unit].reqlist)
X        d[unit].reqlist=bp->av_forw;
X      if (bp->av_back)
X        bp->av_back->av_forw=bp->av_forw;
X      if (bp->av_forw)
X        bp->av_forw->av_back=bp->av_back;
X      bp->av_forw=NULL;
X      bp->av_back=NULL;
X      /* bp has now been removed from the list of requests. */
X    }
X
X  if (ap) /* start the next pending request if there are any */
X    startbufferio(unit,ap);
X
X  splx(x);
X}
X
X/* This marks the unit not busy.  This is used to mark the completion
X   of a command.  This must absolutely be called exactly once for each and
X   every i/o request made.  If the request was for an io buffer, this will
X   set b_flags&B_ERROR according to the completion; COK marks good completion.
X   If there are any processes sleeping for the drive to become not busy,
X   this will wake them up.  If there is any pending block io, this will
X   start i/o for the next buffer.  After a call to this, all data in the
X   d[unit] structure for the previous request will have been lost and the
X   next operation may be in progress. The scsi driver and controller should
X   be set to bus free phase before calling this. */
X
Xstatic marknotbusy(unit,completion)
Xint unit,completion;
X{
X  int x;
X  struct buf *ap;
X
X#ifdef DEBUG0
X  printf("scsi: marknotbusy unit=%d completion=%d\n",
X         unit,completion);
X#endif
X  x=splnointrs();
X  d[unit].busy=0;
X  d[unit].connected=0; /* just in case */
X  d[unit].xfertime=0; /* we don't want any timeouts any more */
X  ap=d[unit].currentbuf;
X  if (ap)
X    {
X      if (completion != COK)
X        ap->b_flags|=B_ERROR;
X    }
X   else
X    if (!d[unit].xferpolled)
X      wakeup(&d[unit].connected);
X  startpendingreq(unit,ap); /* This will start any pending io */
X  if (ap)
X    iodone(ap);
X#ifdef DEBUG0
X  printf("scsi: marknotbusy returning\n");
X#endif
X  splx(x);
X}
X
X/* This is the scsi interrupt service routine.  This is called with a priority
X   lower than that of the timer tick, which is used to detect timeouts.
X   This is called (this ignores other calls) when a target is reselecting this
X   initiator.  This will continue processing the reconnected request, and
X   if the request completes, this will (in lower-level routines) start the
X   next request automatically. */
X
Xscsiintr()
X{
X  int a,x,unit;
X  long l;
X
X  if (!(*cmdport & STSEL))
X    {
X#ifdef DEBUG0
X      printf("scsi: intr ignored (no SEL)\n");
X#endif
X      return; /* The controller should only generate interrupts when select
X                 rises. */
X    }
X  for (l=0;l<10000l;l++)
X    if (*cmdport & STIO)
X      goto gotio;
X#ifdef DEBUG0
X  printf("scsi: intr ignored (IO timeout)\n");
X#endif
X  return;
X gotio:
X  a=(*scsidataport) & 0xff;
X  if (!(a & MYADDR))
X    {
X#ifdef DEBUG
X      printf("scsi: intr ignored (not my addr); addr=%x\n",a);
X#endif
X      return;
X    }
X  a&=(~MYADDR);
X  for (unit=0;unit < 8;unit++)
X    if (a & (unsigned char)(1<<unit))
X      break;
X  if (unit >= 8 || (a & ~(unsigned char)(1<<unit)))
X    {
X#ifdef DEBUG
X      printf("scsi: intr ignored (invalid id); unit=%d a=%x\n",unit,a);
X#endif
X      return;
X    }
X  if (unit >= SCSIMAXDRIVES)
X    {
X#ifdef DEBUG
X      printf("scsi: intr ignored (unit %d >= SCSIMAXDRIVES %d)\n",
X             unit,SCSIMAXDRIVES);
X#endif
X      return;
X    }
X  x=splnointrs();
X  if (d[unit].connected || !d[unit].busy)
X    {
X#ifdef DEBUG
X      printf("scsi: intr ignored (internal state): unit=%d connected=%d busy=%d\n",
X             unit,d[unit].connected,d[unit].busy);
X#endif
X      splx(x);
X      return;
X    }
X  if (d[unit].xferpolled)
X    { /* ooops... This is not the way it was supposed to happen... */
X#ifdef DEBUG
X      printf("scsi: intr ignored (xfer is polled); unit=%d\n",unit);
X#endif
X      splx(x);
X      return;
X    }
X  *cmdport=CMDBASE|CMDBSY|CMDENABLE; /* acknowledge reselection */
X  for (l=0;l<10000l;l++)
X    if (!(*cmdport & STSEL))
X      goto selreleased;
X  /* timeout waiting for sel to be released */
X  *cmdport=CMDBASE;
X#ifdef DEBUG
X  printf("scsi: intr ignored (timeout waiting for sel to be released); unit=%d\n",
X         unit);
X#endif
X  splx(x);
X  return;
X selreleased:
X  for (l=0;l<10000l;l++)
X    if (*cmdport & STBSY)
X      goto selectedandhavebsy;
X  /* timeout waiting for sel to be released */
X  *cmdport=CMDBASE;
X#ifdef DEBUG
X  printf("scsi: intr ignored (timeout waiting for bsy after sel to be released); unit=%d\n",
X         unit);
X#endif
X  splx(x);
X  return;
X selectedandhavebsy:
X  *cmdport=CMDBASE|CMDENABLE;
X  d[unit].connected=1;
X  d[unit].xfertime=0;
X  intrserviced=1;
X  splx(x); /* allow timer ticks */
X  if (d[unit].currentbuf)
X    {
X      a=doxfernosleep(unit);
X     doxferbufagain:
X      d[unit].connected=0; /* just in case */
X      if (a != COK && a != CDISCONNECT)
X        { /* We got an error.  We must retry the operation, and if the retry
X             count has elapsed, complete the operation with error. */
X          if (d[unit].xferretries <= 0 || a == CBUSBUSY || a == CNOCONNECT)
X            {
X#ifdef DEBUG
X              printf("scsi: intr: cmd failed (%d); returning error\n",a);
X#endif
X              *cmdport=CMDBASE;
X              marknotbusy(unit,a); /* This may start a new operation */
X            }
X           else
X            {
X              d[unit].xferretries--;
X#ifdef DEBUG
X              printf("scsi: intr: retrying command\n");
X#endif
X              a=startscsi(unit); /* this will restart the command */
X              goto doxferbufagain;
X            }
X        }
X    }
X   else
X    { /* it must be an interrupt-driven operation to an internal buffer */
X      wakeup(&d[unit].connected);
X      /* leave the connected indicator on, and do no further processing
X         here.  This will signal the sleeping operation that we are once
X         again connected. */
X    }
X  intrserviced=0;
X  return;
X}
X
X/* This is called using timeout() every 1/10th of a second.  This is used
X   to solve timeout conditions related to lost interrupts and the like.
X   Note that this is usually entered with a priority higher than that of
X   the scsi driver.  splnointrs should be defined so that this interrupt
X   is also masked out.  There may also be a drawback in using splnointrs:
X   if the system clock is incremented with these timer interrupts, it might
X   lose some ticks when the scsi disk is being used.  I hope Microport has
X   implemented the system clock in some more clever way.  */
X
Xscsitick()
X{
X  int a,unit,x;
X
X  x=splnointrs();
X  if (!intrserviced) /* if in the middle of a scsi interrupt, do nothing */
X    {
X      for (unit=0;unit<SCSIMAXDRIVES;unit++)
X        {
X          if (!d[unit].busy || d[unit].connected || d[unit].xfertime == 0 ||
X              d[unit].xfertimeout == 0 || d[unit].xferpolled)
X            continue;
X          d[unit].xfertime++;
X          if (d[unit].xfertime < d[unit].xfertimeout)
X            continue;
X          /* the timeout has elapsed.  We can only assume that we have lost an
X             interrupt or there are problems on the target which prevent it from
X             reconnecting and completing the command. */
X          d[unit].xfertime=0; /* will be reset in retry if appropriate */
X          if (!d[unit].currentbuf)
X            { /* interrupt-driven transter to local buffer */
X#ifdef DEBUG
X              printf("scsi: local intr driven xfer woken up by timer tick; unit=%d\n",
X                     unit);
X#endif
X              wakeup(&d[unit].connected); /* !connected tells it to retry */
X              continue;
X            }
X          a=CTIMEOUT;
X         retrytickforbuf:
X          if (a == COK || a == CDISCONNECT)
X            continue;
X          if (d[unit].xferretries == 0 || a == CBUSBUSY || a == CNOCONNECT)
X            {
X#ifdef DEBUG
X              printf("scsi: block xfer fails in timer tick; unit=%d, err=%d\n",
X                     unit,a);
X#endif
X              marknotbusy(unit,a); /* This may start a new operation */
X              continue;
X            }
X          d[unit].xferretries--;
X#ifdef DEBUG
X          printf("scsi: xfer retried in timer tick; unit=%d, err=%d\n",
X                 unit,a);
X#endif
X          a=startscsi(unit);
X          goto retrytickforbuf;
X        }
X    }
X  timeout(scsitick,0,TICKSPERSECOND/10);
X  splx(x);
X}
X
X/* This is the normal strcpy */
X
Xstatic strcpy(d,s)
Xchar *d,*s;
X{
X  while (*s)
X    *d++=(*s++);
X  *d=0;
X}
X
X/* This implements the request sense command.  This returns a C* status. */
X
Xstatic int requestsense(unit,buf,len,polled)
Xint unit,len,polled;
Xchar *buf;
X{
X  char cmd[6];
X
X  if (len > 18)
X    len=18;
X  cmd[0]=SCSIREQSENSE;
X  cmd[1]=0;
X  cmd[2]=0;
X  cmd[3]=0;
X  cmd[4]=len;
X  cmd[5]=0;
X
X  return doscsicmd(unit,cmd,buf,len,2,3,1,0,polled,NULL);
X}
X
X/* This tests for drive readyness (with the scsi test unit ready command).
X   This returns a C* status. */
X
Xstatic int testready(unit)
Xint unit;
X{
X  char cmd[6];
X
X  cmd[0]=SCSITESTREADY;
X  cmd[1]=0;
X  cmd[2]=0;
X  cmd[3]=0;
X  cmd[4]=0;
X  cmd[5]=0;
X
X  return doscsicmd(unit,cmd,NULL,0,1,1,1,0,1,NULL);
X}
X
X/* This issues the inquiry command to the scsi drive to get its drive type
X   and other characteristics.  This returns a C* status. */
X
Xstatic int doinquiry(unit,buf,len,polled)
Xint unit,len,polled;
Xchar *buf;
X{
X  char cmd[6];
X
X  if (len > 36)
X    len=36;
X  cmd[0]=SCSIINQUIRY;
X  cmd[1]=0;
X  cmd[2]=0;
X  cmd[3]=0;
X  cmd[4]=len;
X  cmd[5]=0;
X  return doscsicmd(unit,cmd,buf,len,150,3,1,0,polled,NULL);
X    /* the timeout is quite long to allow time for startup */
X}
X
X/* This reads the storage capacity and block size of the scsi drive */
X
Xstatic int readcapacity(unit,buf,len,polled)
Xint unit,len,polled;
Xchar *buf;
X{
X  char cmd[10];
X
X  if (len > 8)
X    len=8;
X
X  cmd[0]=SCSIREADCAPACITY;
X  cmd[1]=0;
X  cmd[2]=0;
X  cmd[3]=0;
X  cmd[4]=0;
X  cmd[5]=0;
X  cmd[6]=0;
X  cmd[7]=0;
X  cmd[8]=0;
X  cmd[9]=0;
X
X  if (doscsicmd(unit,cmd,buf,len,150,2,1,0,polled,NULL) != COK)
X    return 0; /* the timeout period is quite long to allow time for startup */
X  return 1;
X}
X
X/* This is used to initialize the drive at system startup time */
X
Xstatic initdrive(unit)
Xint unit;
X{
X  int a,bs;
X  char buf[100];
X  long s,l;
X  unsigned char *cp;
X
X  d[unit].blocksize=0;
X  d[unit].busy=0;
X  d[unit].connected=0;
X  d[unit].nparts=0;
X  d[unit].nomsgs=0;
X
X  a=testready(unit);
X  if (a != COK)
X    {
X      if (a != CERROR && a != CBUSY)
X        return 0;  /* no point in waiting */
X      printf("Waiting for unit %d powerup...\n",unit);
X      for (l=0;l<10000000l;l++)
X        if (l % 100000l == 0)
X          {
X            a=testready(unit);
X            if (a == COK)
X              break;
X          }
X      if (a != COK)
X        {
X          printf("Powerup timeout on drive %d\n",unit);
X          return 0;
X        }
X    }
X  a=requestsense(unit,buf,sizeof(buf),1);
X  if (a == CNOCONNECT || a == CBUSBUSY)
X    return 0;
X  if (a != COK)
X    {
X      printf("scsi drive %d is not responding properly.\n",unit);
X      return 0;
X    }
X#ifdef DEBUG0
X  printf("scsi: initdrive: requestsense ok\n");
X#endif
X  a=doinquiry(unit,buf,sizeof(buf),1);
X  if (a != COK)
X    {
X      printf("scsi drive %d: inquiry failed.\n",unit);
X      return 0;
X    }
X#ifdef DEBUG0
X  printf("scsi: initdrive: doinquiry ok\n");
X#endif
X  if (buf[0] != 0)
X    {
X      printf("scsi drive %d is on a direct access device\n",unit);
X      return 0;
X    }
X  buf[buf[4]+6]=0;
X  strcpy(d[unit].drivename,buf+8);
X  if (!readcapacity(unit,buf,sizeof(buf),1))
X    {
X      d[unit].capacity=0;
X      bs=d[unit].blocksize=512;
X      printf("scsi drive %d: cannot read capacity\n",unit);
X    }
X   else
X    {
X      bs=d[unit].blocksize=((unsigned char)buf[6]<<8)+(unsigned char)buf[7];
X      if (bs > BSIZE)
X        printf("scsi drive %d: blocksize=%d BSIZE=%d not supported\n",
X               unit,bs,BSIZE);
X      d[unit].capacity=(long)((unsigned char)buf[0]<<24)+
X                       (long)((unsigned char)buf[1]<<16)+
X                       (long)((unsigned char)buf[2]<<8)+
X                       (long)((unsigned char)buf[3]);
X    }
X  printf("scsi drive %d: %ldM (%d byte sectors): %s\n",
X         unit,(d[unit].capacity*d[unit].blocksize+524288l)/1048576l,
X         d[unit].blocksize,d[unit].drivename);
X  a=dorw(unit,0l,buf,sizeof(buf),B_READ,1,NULL);
X  if (a != COK)
X    {
X      printf("scsi drive %d: could not read partition table\n",unit);
X      return 0;
X    }
X  for (cp=(unsigned char *)buf,a=0;
X       a<SCSIMAXPARTS-1 &&
X        (cp[0] || cp[1] || cp[2] || cp[3] || cp[4] || cp[5]);
X       a++,cp+=6)
X    {
X      s=(long)(cp[0]<<16)+(cp[1]<<8)+cp[2];
X      l=(long)(cp[3]<<16)+(cp[4]<<8)+cp[5];
X      if (s == 0)
X        {
X          s++;
X          l--;
X        }
X      d[unit].parts[a].start=s;
X      d[unit].parts[a].len=l;
X      if (a == 0)
X        printf("partitions:");
X      printf(" %ldM",(l*bs+524288l)/1048576l);
X    }
X  if (a != 0)
X    printf("\n");
X  d[unit].nparts=a;
X  d[unit].parts[SCSIMAXPARTS-1].start=0;
X  d[unit].parts[SCSIMAXPARTS-1].len=d[unit].capacity;
X  return 1;
X}
X
Xscsiinit()
X{
X  int a;
X  extern char *sptalloc();
X
X  printf("\n%s\n",COPYRIGHT);
X  if (!baseaddr)
X    baseaddr=sptalloc(SCSISIZE/PAGESIZE,PG_P,(int)(SCSIBASE/PAGESIZE),
X                      NOSLEEP);
X  if (!baseaddr)
X    {
X      printf("scsi driver error: could not sptalloc controller memory\n");
X      return;
X    }
X  cmdport=baseaddr+SCSICONTROLOFS;
X  scsidataport=baseaddr+SCSIDATAOFS;
X
X  timeouting=0;
X  for (a=0;a<SCSIMAXDRIVES;a++)
X    {
X      d[a].currentbuf=NULL;
X      d[a].reqlist=NULL;
X    }
X  for (a=0;a<SCSIMAXDRIVES;a++)
X    {
X      initdrive(a);
X    }
X  printf("\n");
X}
X
Xscsiopen(dev,flags)
Xdev_t dev;
Xint flags;
X{
X  int unit=UNITNO(minor(dev)),
X      part=PARTNO(minor(dev)),
X      x;
X
X#ifdef DEBUG0
X  printf("scsiopen: unit=%d part=%d\n",unit,part);
X#endif
X  if (!timeouting)
X    {
X      x=splnointrs(); /* in case scsitick makes any assumptions about spl */
X      scsitick();
X      splx(x);
X      timeouting=1;
X    }
X  if (unit >= SCSIMAXDRIVES ||
X      (part != SCSIMAXPARTS-1 &&
X       (d[unit].blocksize == 0 || part >= d[unit].nparts)))
X    u.u_error=ENXIO;
X}
X
Xscsiclose()
X{
X#ifdef DEBUG0
X  printf("scsiclose called\n");
X#endif
X  /* do nothing */
X}
X
Xscsistrategy(bp)
Xstruct buf *bp;
X{
X  int unit=UNITNO(minor(bp->b_dev)),
X      part=PARTNO(minor(bp->b_dev)),
X      nsecs,x;
X  long sec;
X  struct buf *ap,**app;
X
X#ifdef DEBUG0
X  printf("scsistrategy: unit=%d part=%d b_dev=%x b_bcount=%d b_blkno=%d b_flags=%x\n",
X         unit,part,bp->b_dev,bp->b_bcount,bp->b_blkno,bp->b_flags);
X#endif
X  if (unit >= SCSIMAXDRIVES || d[unit].blocksize == 0 ||
X      bp->b_bcount % d[unit].blocksize != 0)
X    {
X      bp->b_flags|=B_ERROR;
X      bp->b_resid=bp->b_bcount;
X      iodone(bp);
X      return;
X    }
X  if (part == SCSIMAXPARTS-1)
X    sec=BLTOSEC(unit,bp->b_blkno);
X   else
X    {
X      sec=BLTOSEC(unit,bp->b_blkno);
X      if (part >= d[unit].nparts || sec > d[unit].parts[part].len)
X        {
X          bp->b_flags|=B_ERROR;
X          bp->b_resid=bp->b_bcount;
X          iodone(bp);
X          return;
X        }
X      if (sec == d[unit].parts[part].len)
X        {
X          bp->b_resid=bp->b_bcount;
X          iodone(bp);
X          return;
X        }
X      nsecs=(bp->b_bcount+d[unit].blocksize-1)/d[unit].blocksize;
X      if (sec+nsecs > d[unit].parts[part].len)
X        {
X          nsecs=d[unit].parts[part].len-sec;
X          bp->b_resid=bp->b_bcount-nsecs*d[unit].blocksize;
X        }
X       else
X        bp->b_resid=0;
X      sec+=d[unit].parts[part].start;
X    }
X  x=splnointrs();
X  for (app=(&d[unit].reqlist),ap=NULL;
X       *app;
X       ap=(*app),app=(&(*app)->av_forw))
X    {
X      if (sec < BLPTOSEC(unit,part,(*app)->b_blkno))
X        {
X          bp->av_back=ap;
X          (*app)->av_back=bp;
X          bp->av_forw=(*app);
X          *app=bp;
X          goto haveinserted;
X        }
X    }
X  *app=bp;
X  bp->av_forw=NULL;
X  bp->av_back=ap;
X haveinserted:
X  if (!d[unit].busy)
X    startbufferio(unit,bp);
X  splx(x);
X}
X
X/* raw io read on the device */
X
Xscsiread(dev)
Xint dev;
X{
X  physio(scsistrategy,&scsibuf,dev,B_READ);
X}
X
X/* raw io write on the device */
X
Xscsiwrite(dev)
Xint dev;
X{
X  physio(scsistrategy,&scsibuf,dev,B_WRITE);
X}
X
X/* This formats the entire scsi drive. */
X
Xstatic int formatscsidrive(unit,blocksize,interleave)
Xint unit,blocksize,interleave;
X{
X  char cmd[10],buf[12];
X
X  if (blocksize == 0)
X    blocksize=512;
X  printf("scsi: formatting unit %d with blocksize=%d, interleave=%d\n",
X         unit,blocksize,interleave);
X
X  cmd[0]=SCSIMODESELECT;
X  cmd[1]=0;
X  cmd[2]=0;
X  cmd[3]=0;
X  cmd[4]=12;
X  cmd[5]=0;
X
X  buf[0]=0;
X  buf[1]=0;
X  buf[2]=0;
X  buf[3]=8;
X  buf[4]=0;
X  buf[5]=0;
X  buf[6]=0;
X  buf[7]=0;
X  buf[8]=0;
X  buf[9]=blocksize>>16;
X  buf[10]=blocksize>>8;
X  buf[11]=blocksize;
X
X  if (doscsicmd(unit,cmd,buf,12,5,2,1,0,0,NULL) != COK)
X    printf("scsi: warning: mode select command returned error from drive %d\n",
X           unit);
X  cmd[0]=SCSIFORMATUNIT;
X  cmd[1]=0; /* primary and grown defect list only */
X  cmd[2]=0; /* data pattern */
X  cmd[3]=interleave>>8;
X  cmd[4]=interleave;
X  cmd[5]=0;
X
X  if (doscsicmd(unit,cmd,NULL,0,0,0,1,0,0,NULL) != COK)
X    {
X      printf("scsi: format failure.\n");
X      return 0;
X    }
X  printf("scsi: format complete.\n");
X  return 1;
X}
X
X/* This checks that the current user is the super-user.  Returns 0 if not. */
X
Xstatic int chksuper()
X{
X  if (u.u_uid != 0)
X    {
X      u.u_error=EPERM;
X      return 0;
X    }
X  return 1;
X}
X
X/* ioctl() for this device */
X
Xscsiioctl(dev,cmd,arg,mode)
Xint dev,cmd,mode;
Xchar *arg;
X{
X  int unit=UNITNO(minor(dev)),
X      part=PARTNO(minor(dev));
X  char *cp;
X
X#ifdef DEBUG0
X  printf("scsiioctl called: unit=%d part=%d cmd=%d arg=%d mode=%d\n",
X         unit,part,cmd,arg,mode);
X#endif /* DEBUG */
X  if (unit >= SCSIMAXPARTS ||
X      (part != SCSIMAXPARTS-1 && part >= d[unit].nparts))
X    {
X      u.u_error=EINVAL;
X      return;
X    }
X  switch (cmd)
X    {
X      case SCSIIOREADCAP:
X        if (part == 15)
X          suword((int *)arg,(int)d[unit].capacity);
X         else
X          suword((int *)arg,(int)d[unit].parts[part].len);
X        break;
X      case SCSIIOREADTYPE:
X        for (cp=d[unit].drivename;*cp;cp++,arg++)
X          subyte(arg,*cp);
X        subyte(arg,0);
X        break;
X      case SCSIIOSETBLK:
X        if (!chksuper())
X          break;
X        d[unit].blocksize=fuword((int *)arg);
X        break;
X      case SCSIIOFORMAT:
X        if (!chksuper())
X          break;
X        if (!formatscsidrive(unit,d[unit].blocksize,fuword((int *)arg)))
X          u.u_error=EIO;
X        /* fall to next case */
X      case SCSIIOINITUNIT:
X        initdrive(unit);
X        break;
X      case SCSIIOGETBLKSZ:
X        suword((int *)arg,d[unit].blocksize);
X        break;
X      default:
X        u.u_error=EINVAL;
X        break;
X    }
X}
END_OF_scsi.c
if test 42481 -ne `wc -c <scsi.c`; then
    echo shar: \"scsi.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f scsi.h -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"scsi.h\"
else
echo shar: Extracting \"scsi.h\" \(2737 characters\)
sed "s/^X//" >scsi.h <<'END_OF_scsi.h'
X/*
X
XSCSI disk driver for unix system V (Microport system V/386)
XThis driver uses the ST-01 controller.  This supports multiple initiators
Xand multiple targets.
X
XCopyright (c) 9.6.1988 Tatu Yl|nen
X              All rights reserved.
X
X*/
X
X#define SCSIMAXDRIVES  4   /* max # disk drives supported */
X#define SCSIMAXPARTS   16  /* max partitions/drive */
X
Xtypedef struct scsidrivest
X{
X  char drivename[64]; /* drive type identification string */
X  int blocksize;  /* logical block size; if 0, not present */
X  long capacity;  /* total disk capacity in blocks */
X  char nomsgs;    /* set if drive does not support messages */
X  int nparts;     /* # partitions */
X  struct partst
X    {
X      long start; /* starting sector number */
X      long len;   /* partition length */
X    } parts[SCSIMAXPARTS];
X
X  char *xferbuf;  /* transfer buffer address */
X  int xferlen;    /* data len to transfer */
X  char *xfercmd;  /* command to transfer */
X  char xferslow;  /* true if watch out for slow transfers */
X  char xferstatus; /* status byte received from target */
X  int xfertimeout; /* if nonzero, timeout in 1/10 sec */
X  int xfertime;    /* if nonzero, elapsed time waiting (1/10 sec) */
X  int xferretries; /* if nonzero, retry the current request and decrement*/
X  char xferphys;   /* if true, transferring data with raw io */
X  char xferpolled; /* if true, transfer must be polled */
X
X  char *savedbuf;  /* saved buffer */
X  int savedlen;    /* saved lenght */
X  char *savedcmd;  /* saved command */
X
X  char *origbuf;   /* original buffer */
X  int origlen;     /* original length */
X  char *origcmd;   /* original command */
X
X  struct buf *reqlist; /* queued requests */
X  struct buf *currentbuf; /* buffer being executed */
X
X  char connected; /* true if connection to drive established */
X  char busy;      /* true if currently executing a command */
X} SCSIDRIVE;
X
X#define SCSIIOREADCAP  1 /* read capacity of partition or drive
X                            (if part == 15); arg=&capacity */
X#define SCSIIOREADTYPE 2 /* read drive type name (as a string); arg=buf */
X#define SCSIIOFORMAT   3 /* reformat the drive; arg=&interleave */
X#define SCSIIOSETBLK   4 /* set drive block size for format; arg=&size */
X#define SCSIIOINITUNIT 5 /* re-initializes the unit, reading partition table */
X#define SCSIIOGETBLKSZ 6 /* read sector size */
X
X/* Partitioning is done by writing on the first block of partition 15
X   (the entire drive).  Note that drive block size may not be the same as
X   system block size.  Partition table format: each entry 3 bytes start,
X   3 bytes length (in blocks, msb first), terminated by 6 zeroes.  If first
X   partition starts at 0, it will be moved to 1 automatically; this appears
X   to be the convention under dos. */
X
END_OF_scsi.h
if test 2737 -ne `wc -c <scsi.h`; then
    echo shar: \"scsi.h\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f scsiasm.s -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"scsiasm.s\"
else
echo shar: Extracting \"scsiasm.s\" \(805 characters\)
sed "s/^X//" >scsiasm.s <<'END_OF_scsiasm.s'
X	.file "scsiasm.s"
X/
X/ fast transfer to/from the scsi controller
X/
X/ Copyright (c) 1988 Tatu Yl|nen
X/               All rights reserved.
X/
X
X	.text
X	.align	4
X	.globl	getfromscsi
X
Xgetfromscsi:
X        pushl %ebp
X        movl %esp,%ebp
X        pushl %esi
X        pushl %edi
X	push %es
X	movw %ds,%ax
X	movw %ax,%es
X        movl 8(%ebp),%edi
X        movl 12(%ebp),%ecx
X        movl scsidataport,%esi
X	cld
X	rep
X	smovb
X	pop %es
X        popl %edi
X        popl %esi
X	popl %ebp
X        ret
X
X        .globl  sendtoscsi
Xsendtoscsi:
X        pushl %ebp
X        movl %esp,%ebp
X        pushl %esi
X        pushl %edi
X	push %es
X	movw %ds,%ax
X	movw %ax,%es
X        movl 8(%ebp),%esi
X        movl 12(%ebp),%ecx
X        movl scsidataport,%edi
X	cld
X	rep
X	smovb
X	pop %es
X        popl %edi
X        popl %esi
X	popl %ebp
X        ret
END_OF_scsiasm.s
if test 805 -ne `wc -c <scsiasm.s`; then
    echo shar: \"scsiasm.s\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f scsipart.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"scsipart.c\"
else
echo shar: Extracting \"scsipart.c\" \(5756 characters\)
sed "s/^X//" >scsipart.c <<'END_OF_scsipart.c'
X/*
X
XSCSI disk partitioning program
X
XCopyright (c) 9.6.1988 Tatu Yl|nen
X              All rights reserved.
X
X*/
X
X#include <stdio.h>
X#include <fcntl.h>
X#include "scsi.h"
X
X#define DEV "/dev/rscsi%ds" /* must be raw device to use ioctls */
X
Xchar devname[80];
Xchar drivename[80];
Xchar buf[4096];
Xint capacity;
Xint interleave,blocksize;
X
Xint nparts;
Xlong partstart[SCSIMAXPARTS];
Xlong partlen[SCSIMAXPARTS];
X
Xint scsiunit;
Xint scsihandle;
X
Xint asknumber(s,low,high)
Xchar *s;
Xint low,high;
X{
X  int a;
X
X  while (1)
X    {
X      printf("%s\n",s);
X      if (scanf("%d",&a) == 1 && a >= low && a <= high)
X        return a;
X      printf("invalid input, try again.\n");
X      fflush(stdin);
X    }
X}
X
Xreadparts()
X{
X  unsigned char *cp;
X
X  lseek(scsihandle,0l,0);
X  if (read(scsihandle,buf,1024) != 1024)
X    {
X      perror("Could not read partition table");
X      exit(1);
X    }
X  for (nparts=0,cp=(unsigned char *)buf;
X       cp[0] || cp[1] || cp[2] || cp[3] || cp[4] || cp[5];
X       nparts++,cp+=6)
X     {
X       if (nparts >= SCSIMAXPARTS-1)
X         {
X           printf("Invalid partition table - assuming no partitions\n");
X           nparts=0;
X           return;
X         }
X       partstart[nparts]=(cp[0] << 16) + (cp[1] << 8) + cp[2];
X       partlen[nparts]=(cp[3] << 16) + (cp[4] << 8) + cp[5];
X     }
X}
X
Xsaveparts()
X{
X  int a;
X  char *cp;
X
X  for (a=0,cp=buf;a<nparts;a++,cp+=6)
X    {
X      cp[0]=partstart[a]>>16;
X      cp[1]=partstart[a]>>8;
X      cp[2]=partstart[a];
X      cp[3]=partlen[a]>>16;
X      cp[4]=partlen[a]>>8;
X      cp[5]=partlen[a];
X    }
X  memset(cp,0,6);
X  lseek(scsihandle,0l,0);
X  if (write(scsihandle,buf,1024) != 1024)
X    {
X      printf("error saving partition table\n");
X      exit(1);
X    }
X}
X
Xint addpart(s,l)
Xlong s,l;
X{
X  if (nparts >= SCSIMAXPARTS-1)
X    {
X      printf("too many partitions\n");
X      return 0;
X    }
X  partstart[nparts]=s;
X  partlen[nparts]=l;
X  nparts++;
X  return 1;
X}
X
Xint delpart(n)
Xint n;
X{
X  int a;
X
X  if (n < 0 || n >= nparts)
X    {
X      printf("invalid partition number\n");
X      return 0;
X    }
X  for (a=n;a<nparts-1;a++)
X    {
X      partstart[a]=partstart[a+1];
X      partlen[a]=partlen[a+1];
X    }
X  nparts--;
X  return 1;
X}
X
Xprintparts()
X{
X  int a;
X
X  printf("capacity=%ld.  Defined partitions:\n",capacity);
X  for (a=0;a<nparts;a++)
X    {
X      printf("  %d: start=%ld len=%ld blocks\n",a,partstart[a],partlen[a]);
X    }
X  if (nparts == 0)
X    printf("  no partitions defined.\n");
X}
X
Xmain()
X{
X  int a;
X  long s,l;
X
X  printf("\nscsi disk drive formatting and partitioning utility  V1.0\n");
X  printf("Copyright (c) 9.6.1988 Tatu Yl|nen\n\n");
X  printf("Warning: It is easy to destroy data with this program.  Abort now\n");
X  printf("if you are not sure what you are doing.\n");
X  scsiunit=asknumber("Enter number of the scsi disk you wish to partition?",
X                     0,7);
X  sprintf(devname,DEV,scsiunit);
X  scsihandle=open(devname,O_RDWR);
X  if (scsihandle == -1)
X    {
X      perror(devname);
X      exit(1);
X    }
X  if (ioctl(scsihandle,SCSIIOREADTYPE,drivename) == -1)
X    perror("SCSIIOREADTYPE ioctl");
X  if (ioctl(scsihandle,SCSIIOREADCAP,&capacity) == -1)
X    perror("SCSIIOREADCAP ioctl");
X  if (ioctl(scsihandle,SCSIIOGETBLKSZ,&blocksize) == -1)
X    perror("SCSIIOGETBLKSZ ioctl");
X  printf("Drive %d: %d blocks, blocksize=%d\n",scsiunit,capacity,blocksize);
X  printf("%s\n",drivename);
X  printf("Do you wish to format the unit (y/n)?\n");
X  fflush(stdin);
X  gets(buf);
X  if (buf[0] == 'y' || buf[0] == 'Y')
X    {
X      printf("FORMATTING WILL DESTROY ALL AND ANY DATA ON THE DRIVE.\n");
X      printf("ARE YOU SURE YOU WANT TO DO THIS (Y/N)?\n");
X      gets(buf);
X      if (buf[0] != 'y' && buf[0] != 'Y')
X        exit(1);
X      blocksize=asknumber("Enter block size for the drive (usually 512)?",
X                          0,4096);
X      interleave=asknumber("Enter interleave factor for the drive (usually between 1 and 10)?",
X                           0,34);
X      if (ioctl(scsihandle,SCSIIOSETBLK,&blocksize) == -1 ||
X          ioctl(scsihandle,SCSIIOFORMAT,&interleave) == -1)
X        {
X          perror("Format failure");
X          exit(1);
X        }
X      if (ioctl(scsihandle,SCSIIOREADCAP,&capacity) == -1)
X        perror("SCSIIOREADCAP ioctl");
X      nparts=0;
X      saveparts();
X      printf("Format complete.  Drive capacity is %d blocks.\n",capacity);
X    }
X  if (ioctl(scsihandle,SCSIIOINITUNIT,NULL) == -1)
X    perror("SCSIIOINITUNIT ioctl");
X  if (ioctl(scsihandle,SCSIIOREADTYPE,drivename) == -1)
X    perror("SCSIIOREADTYPE ioctl");
X  if (ioctl(scsihandle,SCSIIOREADCAP,&capacity) == -1)
X    perror("SCSIIOREADCAP ioctl");
X  if (ioctl(scsihandle,SCSIIOGETBLKSZ,&blocksize) == -1)
X    perror("SCSIIOGETBLKSZ ioctl");
X  if (!readparts())
X    nparts=0;
X  while (1)
X    {
X      printparts();
X      a=asknumber("1 add partition 2 delete partition 8 quit (no save) 9 save",
X                  1,9);
X      switch (a)
X        {
X          case 1: /* add partition */
X            s=asknumber("enter partition start in blocks?",0,capacity);
X            l=asknumber("enter partition length in blocks?",0,capacity-s);
X            addpart(s,l);
X            break;
X          case 2: /* delete partition */
X            a=asknumber("enter partition number to delete?",0,nparts-1);
X            delpart(a);
X            break;
X          case 8: /* quit no save */
X            printf("partition table not modified\n");
X            exit(1);
X          case 9: /* quit, save modifications */
X            printf("saving partition table\n");
X            saveparts();
X            if (ioctl(scsihandle,SCSIIOINITUNIT,NULL) == -1)
X              perror("SCSIIOINITUNIT ioctl");
X            exit(0);
X          default:
X            printf("invalid command\n");
X            break;
X        }
X    }
X}
END_OF_scsipart.c
if test 5756 -ne `wc -c <scsipart.c`; then
    echo shar: \"scsipart.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0