------------------------------- Page    i -------------------------------

                           The UTS I/O System




                                                 Dennis Ritchie



                                                 Edited for UTS

                                                 February 14, 1981

------------------------------- Page   ii -------------------------------

                            TABLE OF CONTENTS


1.    Introduction  . . . . . . . . . . . . . . . . . . . . . . . . .   1

2.    Device Classes  . . . . . . . . . . . . . . . . . . . . . . . .   1

3.    Overview of I/O . . . . . . . . . . . . . . . . . . . . . . . .   2

4.    Character device drivers  . . . . . . . . . . . . . . . . . . .   3

5.    The Block-device Interface  . . . . . . . . . . . . . . . . . .   6

6.    Block Device Drivers  . . . . . . . . . . . . . . . . . . . . .   9

7.    The I/O Supervisor  . . . . . . . . . . . . . . . . . . . . . .  11

7.1      I/O Calls  . . . . . . . . . . . . . . . . . . . . . . . . .  11
7.2      Interrupt Routines . . . . . . . . . . . . . . . . . . . . .  12
7.3      Other I/O Services . . . . . . . . . . . . . . . . . . . . .  13
7.4      Missing Interrupt Handler  . . . . . . . . . . . . . . . . .  13


                                                            Last Page  14

-------------------------------- Page  1 --------------------------------

1.    INTRODUCTION

This paper gives an overview of the workings  of the UTS I/O system.   It
was written with an  eye toward providing  guidance to writers of  device
driver routines, and is oriented  more toward describing the  environment
and nature of device drivers than the implementation of that  part of the
file system which deals with ordinary files.  This paper is a revision of
The UNIX- I/O System by Dennis M. Ritchie.

It is assumed that the reader has a good knowledge of the overall  struc-
ture of the file system  as discussed in the paper The UNIX  Time-sharing
System.  Moreover the present document is intended to be used in conjunc-
tion with a copy of the system code, since it is basically an exegesis of
that code.




2.    DEVICE CLASSES

There are two device classes:  block and character.  The block  interface
is suitable for devices like disks which can work with  addressable 4096-
byte blocks.  Block devices  can at least  potentially contain a  mounted
file system.  The interface to  block devices is very highly  structured;
the drivers for these devices  share a great many routines  as well as  a
pool of buffers.

Character-type  devices  have  a  much  more  straightforward  interface,
although more work must be done by the driver itself.

Devices of both types  are named by  a major and  a minor device  number.
These numbers are generally stored as a word with the minor device number
in the lowest order byte and the major  device number in the next  higher
byte.  The major device  number selects which  driver will deal with  the
device; the minor device number is not used by the rest of the system but
is passed to the driver at appropriate times.  Typically the minor number
selects a subdevice attached  to a  given controller, or  one of  several
similar hardware interfaces.

The major device  numbers for  block and  character devices  are used  as
indices in separate tables; they both start at 0 and therefore overlap.




_______________
  -UNIX is a trademark of Bell Laboratories.

-------------------------------- Page  2 --------------------------------

3.    OVERVIEW OF I/O

The purpose of the open  and creat system calls is  to set up entries  in
three separate system tables.  The first of these is the u.u_ofile table,
which is stored in the system's per-process  data area u.  This table  is
indexed by  the file descriptor  returned by  the open or  creat, and  is
accessed during a read, write, or other  operation on the open file.   An
entry contains  only a  pointer to  the corresponding entry  of the  file
table, which is a per-system data base.  There  is one entry in the  file
table for  each instance  of  open or  creat.  This  table is  per-system
because the  same instance  of an  open  file must  be shared  among  the
several processes which can result  from forks after the file is  opened.
A file table entry  contains flags  which indicate whether  the file  was
open for reading or writing  or is a pipe, and  a count which is used  to
decide when all processes using the  entry have terminated or closed  the
file (so the entry can be abandoned).  There is also  a file offset which
is used to indicate where  in the file the next  read or write will  take
place.  Finally, there  is a  pointer to the  entry for  the file in  the
inode table, which contains a copy of the file's i-node.

Certain open files  can be  designated "multiplexed"  files, and  several
other flags  apply to  such  channels.  In  such a  case,  instead of  an
offset, there  is a  pointer to  an associated  multiplex channel  table.
Multiplex channels will not be discussed here.

An entry in the file table corresponds  precisely to an instance of  open
or creat; if the same file is opened several times,  it will have several
entries in this table.  However, there is at most one entry in the  inode
table for a given file.  Also, a file may enter  the inode table not only
because it is open, but also because it is the current directory of  some
process or because it  is a special  file containing a  currently-mounted
file system.

An entry in the inode  table differs somewhat  from the corresponding  i-
node as  stored on  the disk;  the modified  and accessed  times are  not
stored, and the entry is augmented by a flag word containing  information
about the entry,  a count  used to determine  when it  may be allowed  to
disappear, and the device and i-number whence the entry came.  Also,  the
several block numbers that give  addressing information for the file  are
expanded from the 3-byte, compressed format  used on disk to full  4-byte
quantities.

During the processing of an  open or creat call for  a special file,  the
system always calls the  device's open routine  to allow for any  special
processing required.  However, the close routine is called only when  the
last process closes a file, that is, when the i-node table entry is being
deallocated.  Thus it is not feasible for a device to maintain, or depend
on, a count of its  users, although it is quite possible to implement  an
exclusive-use device which cannot be reopened until it has been closed.

-------------------------------- Page  3 --------------------------------

When a read or write takes place, the user's arguments and the file table
entry  are  used  to  set  up  the  variables  u.u_base,  u.u_count,  and
u.u_offset which respectively contain the (user) address of the I/O  tar-
get area, the byte-count  for the transfer,  and the current location  in
the file.  If the file referred to is a character-type special file,  the
appropriate read  or  write routine  is  called; it  is  responsible  for
transferring data and updating the  count and current location  appropri-
ately as discussed  below.  Otherwise,  the current location  is used  to
calculate a logical block number in the file.  If the file is an ordinary
file the logical  block number  must be mapped  (possibly using  indirect
blocks) to a physical block number; a block-type special file need not be
mapped.  This mapping is  performed by the  bmap routine.  In any  event,
the resulting physical block number is used, as discussed below, to  read
or write the appropriate device.




4.    CHARACTER DEVICE DRIVERS

The cdevsw table specifies the  interface routines present for  character
devices.  Each major  device provides five  routines: open, close,  read,
write, and special-function (to implement the ioctl system call).  Any of
these may be missing.  If a call on the routine  should be ignored, (e.g.
open on non-exclusive devices  which require no  setup) the cdevsw  entry
can be given as nulldev; if it should be considered an error, (e.g. write
on read-only devices) nodev  is used.  In  addition, cdevsw contains  the
pointer d_addrs, which usually addresses  a list of 370 device  addresses
corresponding to the minor device numbers for a driver.

The open routine is  called each time  the file is  opened with the  full
device number as argument.  The  second argument is a flag which is  non-
zero only if the device is to  be written upon.  Unfortunately, the  flag
gives no distinction  between an open  for just writing  and an open  for
both reading and writing.

The close routine is  called only when  the file is  closed for the  last
time, that is when the very last process in which the file is open closes
it.  This means it  is not possible  for the driver  to maintain its  own
count of its users.  The first argument is the device  number; the second
is a flag which is non-zero if the file was open for writing in the  pro-
cess which performs the final close.

When write is called, it  is supplied the device  as argument.  The  per-
user variable u.u_count has  been set to  the number of characters  indi-
cated by the user; for character devices, this number may be 0 initially.
u.u_base is the address supplied  by the user from which to start  taking
characters.  The  system may  call the  routine internally,  so the  flag

-------------------------------- Page  4 --------------------------------

u.u_segflg is supplied which  indicates, if on,  that u.u_base refers  to
the system address space instead of the user's.

The write routine should copy up to u.u_count characters from the  user's
buffer to the device,  decrementing u.u_count for each character  passed.
For most drivers, which work one character at a time, the routine

     cpass()

is used to pick up characters  from the user's buffer.  Successive  calls
on it return the characters to be written until u.u_count goes to 0 or an
error occurs,  when it  returns -1.   Cpass takes  care of  interrogating
u.u_segflg and updating u.u_count.

Write routines which want to transfer a probably large number of  charac-
ters into an internal buffer may also use the routine

     iomove(addr, count, flag)

which is faster when many characters must be moved.  Iomove transfers  up
to count characters into the location addr; flag should be B_WRITE (which
is 0) in the write case.  Caution:  the caller is responsible for  making
sure the count is not too large and is non-zero.

The device's read routine  is called under  conditions similar to  write,
except that u.u_count is guaranteed to be non-zero.  To return characters
to the user, the routine

     passc(c)

is available; it takes care of housekeeping like cpass and returns -1  as
the last character specified by u.u_count is returned to the user; before
that time, 0 is returned.  Iomove is also usable as with write; the  flag
should be B_READ but the same cautions apply.

The "special-functions" routine is  invoked by the  ioctl system call  as
follows:

     (*p) (dev, cmd, argp, flag)

where p is a pointer to the device's  routine, dev is the device  number,
cmd is the function  requested (driver dependent),  argp is an  arbitrary
argument (usually the user address  of an sgtty  structure), and flag  is
the f_flag value from the file structure associated with the  user's file
descriptor (see "file.h").

Finally, each  device should  have appropriate  interrupt-time  routines.
When an interrupt occurs,  it is turned  into a C-compatible call on  the
devices' interrupt routine.  Arguments passed  to interrupt routines  are
described later in the I/O  Supervisor section.  After the interrupt  has

-------------------------------- Page  5 --------------------------------

been processed, a return from the interrupt handler will return from  the
interrupt itself.

A number of subroutines are available which are useful to character  dev-
ice drivers.  Most of these handlers, for example, need a place to buffer
characters  in   the  internal   interface  between   their  "top   half"
(read/write) and "bottom half" (interrupt) routines.  For relatively  low
data-rate devices, the best mechanism  is the character queue  maintained
by the routines getc and putc.  A queue header has the structure

     struct {
             int     c_cc;   /* character count */
             char    *c_cf;  /* first character */
             char    *c_cl;  /* last character */
     } queue;

A character is placed on the end of a queue by

     putc(c, &queue)

where c is  the character  and queue is  the queue  header.  The  routine
returns -1 if there is  no space to put the character, 0 otherwise.   The
first character on the queue may be retrieved by

     getc(&queue)

which returns either the (non-negative) character  or -1 if the queue  is
empty.

Notice that the space for characters in  queues is shared among all  dev-
ices in  the system.   Thus device handlers,  especially write  routines,
must take care to avoid gobbling up excessive numbers of characters.

The other major  help available  to device handlers  is the  sleep-wakeup
mechanism.  The call

     sleep(event, priority)

causes the process to wait  (allowing other processes  to run) until  the
event occurs; at that  time, the process  is marked ready-to-run and  the
call will return when there is no process with higher priority.

The call

     wakeup(event)

indicates that the event has happened, that is, causes processes sleeping
on the event to be  awakened.  The event is an arbitrary quantity  agreed
upon by the sleeper and the waker-up.   By convention, it is the  address
of some data area  used by the  driver, which guarantees that events  are

-------------------------------- Page  6 --------------------------------

unique.

Processes sleeping on an event must not assume that the event has  really
happened; they  should check  that the  conditions which  caused them  to
sleep no longer hold.

Drivers should be especially careful about using sleep calls in open  and
close routines.  If one  process sleeps while  closing a device,  another
process may then open the device, and the first process may then complete
the close even though the  device should then be open.  Conversely, if  a
process sleeping during an open receives a signal, the close routine  may
be called even though  the open never  completed.  Also, processes  which
hang in  close routines  become  unkillable, since  the kernel  closes  a
process's files before removing it from the process table.

Priorities can range from 0 to 127; a higher numerical value indicates  a
less-favored  scheduling  situation.   A  distinction  is  made   between
processes sleeping at priority less than the parameter PZERO and those at
numerically larger priorities.  The former cannot be interrupted or  ter-
minated by signals.  Thus it  is a bad idea to  sleep with priority  less
than PZERO on an event which might never occur.  On the other hand, calls
to sleep with non-negative priority  may never return  if the process  is
interrupted by some signal in the meantime.  Incidentally, it is  a gross
error to call sleep in a routine called at interrupt time, since the pro-
cess which is running is almost certainly not the process which should go
to sleep.  Likewise, none of the variables in  the user area u should  be
touched, let alone changed, by an interrupt routine.

Since the UTS kernel  always runs disabled  for interrupts, drivers  need
not be  concerned with  disabling and enabling  interrupts.  Only  during
calls to sleep (or to any routine  which eventually calls sleep) will  an
interrupt occur.  Calls  to the  I/O supervisor may  produce fake  inter-
rupts.




5.    THE BLOCK-DEVICE INTERFACE

Handling of block devices is mediated  by a collection of routines  which
manage a set of  buffers containing the images  of blocks of data on  the
various devices.   The most  important purpose  of these  routines is  to
assure that several  processes which  access the same  block of the  same
device in multiprogrammed fashion maintain a consistent view of the  data
in the block.  A secondary but still important purpose is to increase the
efficiency of the system by keeping in-memory copies of blocks which  are
being accessed frequently.  The main data base for this mechanism  is the
table of buffers buf.   Each buffer  header contains a  pair of  pointers

-------------------------------- Page  7 --------------------------------

(b_forw, b_back) which maintain a doubly-linked list of the buffers asso-
ciated with a particular block device,  and a pair of pointers  (av_forw,
av_back) which generally  maintain a doubly-linked  list of blocks  which
are "free," that is, eligible to be reallocated for another  transaction.
Buffers which have I/O in progress or are busy for  other purposes do not
appear in this  list.  The  buffer header  also contains  the device  and
block number  to which the  buffer refers,  and a pointer  to the  actual
storage associated with the buffer.  There is  a byte count which is  the
number of bytes to be transferred to or from the buffer; there is also an
error byte and a residual word count used to communicate information from
an I/O routine to  its caller.  Finally, there  is a flag word with  bits
indicating the  status of  the  buffer.  These  flags will  be  discussed
below.

Six routines constitute the most important part of the interface with the
rest of  the system.  Given  a device  and block number,  both bread  and
getblk return a pointer to a buffer header for the block; the  difference
is that bread is  guaranteed to return  a buffer actually containing  the
current data for the block, while getblk returns a buffer which  contains
the data in the block  only if it is already in memory (whether it is  or
not is  indicated by  the B_DONE  bit; see  below).  In  either case  the
buffer, and the corresponding device block, is made "busy," so that other
processes referring to  it are  obliged to  wait until  it becomes  free.
Getblk is used, for example, when a block is about  to be totally rewrit-
ten, so that its previous contents are  not useful; still, no other  pro-
cess can be allowed  to refer to the block  until the new data is  placed
into it.

Given a pointer to a  buffer, the brelse routine  makes the buffer  again
available to other processes.  It is called, for example, after  data has
been extracted following a bread.  There are three subtly-different write
routines, all  of which take  a buffer  pointer as argument,  and all  of
which logically release the buffer for use by others and place it on  the
free list.  Bwrite puts the buffer on the appropriate device queue, waits
for the write to  be done, and  sets the user's  error flag if  required.
Bawrite places the buffer  on the device's  queue, but does not wait  for
completion, so  that errors  cannot be  reflected directly  to the  user.
Bdwrite does not  start any I/O  operation at all,  but merely marks  the
buffer so that if it happens to be grabbed from the free list to  contain
data from some other block, the data in it will first be written out.

Bwrite is used when one wants to be sure that I/O takes place  correctly,
and that errors are reflected  to the proper user; it is used, for  exam-
ple, when updating i-nodes.   Bawrite is useful  when more efficiency  is
desired (because no wait  is required for I/O  to finish) but when it  is
reasonably certain that the  write is really  required.  Bdwrite is  used
when there is doubt that the write is needed at the moment.  For example,
bdwrite is called when the last byte of  a write system call falls  short
of the end of a block, on the assumption that another write will be given
soon which will re-use the same block.  On the other hand, as the end  of

-------------------------------- Page  8 --------------------------------

a block is passed, bawrite  is called, since probably the block will  not
be accessed again soon and one might as well start the writing process as
soon as possible.

In any event,  notice that  the routines  getblk and  bread dedicate  the
given block exclusively to the  use of the caller, and make others  wait,
while one  of brelse,  bwrite,  bawrite, or  bdwrite must  eventually  be
called to free the block for use by others.

As mentioned, each buffer header contains a flag word which indicates the
status of  the buffer.   Since  they provide  one important  channel  for
information between the drivers and the block I/O system, it is important
to understand these  flags.  The following  names are manifest  constants
which select the associated flag bits.

B_READ    This bit is set when the buffer  is handed to the device  stra-
          tegy routine  (see below)  to indicate a  read operation.   The
          symbol B_WRITE is defined as 0 and  does not define a flag;  it
          is provided as  a mnemonic convenience  to callers of  routines
          which have a separate argument which indicates read or write.

B_DONE    This bit is set to 0 when a block  is handed to the the  device
          strategy routine and is turned on when the operation completes,
          whether normally or as the result of an error.  It is also used
          as part of the return argument of getblk to indicate  if 1 that
          the returned buffer actually contains the data in the requested
          block.

B_ERROR   This bit may be set to 1 when B_DONE is set to indicate that an
          I/O or other error occurred.  If it is set the  b_error byte of
          the buffer header may contain an error code if it is  non-zero.
          If b_error  is 0  the  nature of  the error  is not  specified.
          Actually no driver at present sets b_error; the latter is  pro-
          vided for a future  improvement whereby a more detailed  error-
          reporting scheme may be implemented.

B_BUSY    This bit indicates that the  buffer header is  not on the  free
          list, i.e. is dedicated to someone's exclusive use.  The buffer
          still remains attached to  the list of  blocks associated  with
          its device, however.   When getblk (or  bread, which calls  it)
          searches the  buffer list  for  a given  device and  finds  the
          requested block  with this  bit  on, it  sleeps until  the  bit
          clears.

B_WANTED  This flag is used in  conjunction with the B_BUSY bit.   Before
          sleeping as described just above, getblk sets this flag.   Con-
          versely, when the block is freed and the busy bit goes down (in
          brelse) a  wakeup  is  given  for  the  block  header  whenever
          B_WANTED is on.  This stratagem  avoids the overhead of  having
          to call wakeup every time a buffer is freed on  the chance that

-------------------------------- Page  9 --------------------------------

          someone might want it.

B_AGE     This bit may be set on  buffers just before releasing them;  if
          it is on, the  buffer is placed at  the head of the free  list,
          rather than at the  tail.  It is  a performance heuristic  used
          when the caller  judges that  the same block  will not soon  be
          used again.

B_ASYNC   This bit is set by bawrite to indicate to the appropriate  dev-
          ice driver that the  buffer should be  released when the  write
          has been finished, usually at  interrupt time.  The  difference
          between bwrite and bawrite is that the former starts I/O, waits
          until it is done, and frees the buffer.  The latter merely sets
          this bit and starts I/O.  The bit indicates that brelse  should
          be called for the buffer on completion.

B_DELWRI  This bit is set by  bdwrite before releasing the buffer.   When
          getblk, while searching for a free block, discovers the bit  is
          1 in a buffer it would otherwise  grab, it causes the block  to
          be written out before re-using it.




6.    BLOCK DEVICE DRIVERS

The bdevsw table contains the names of the interface routines and that of
a table for each block device.

Just as for character devices,  block device drivers  may supply an  open
and a close  routine called respectively  on each open  and on the  final
close of the device.  Instead of  separate read and write routines,  each
block device driver has a strategy routine which is called with a pointer
to a buffer header as argument.  As discussed, the buffer header contains
a read/write flag, the  memory address, the  block number, a byte  count,
and the major and minor device number.  The role of the strategy  routine
is to  carry out the  operation as  requested by the  information in  the
buffer header.  When the transaction is complete the B_DONE (and possibly
the B_ERROR) bits should be set.  Then if the B_ASYNC  bit is set, brelse
should be called; otherwise, wakeup.  In cases where the device is  capa-
ble,  under  error-free  operation,  of  transferring  fewer  bytes  than
requested, the channel's residual count should be placed in the  residual
count slot of the buffer header; otherwise, the residual count  should be
set to 0.

Notice that although the most usual argument to the strategy routines  is
a genuine buffer header allocated  as discussed above, all that is  actu-
ally required is that the argument be a pointer to a place containing the

-------------------------------- Page 10 --------------------------------

appropriate information.

The device's table specified by  bdevsw has a byte  to contain an  active
flag and an error count, a pair of links which constitute the head of the
chain of buffers for the  device (b_forw, b_back), and  a first and  last
pointer for a device queue.  Of these things, all are  used solely by the
device driver itself except for the buffer-chain pointers.  Typically the
flag encodes the state of  the device, and is used at a minimum to  indi-
cate that the device is currently engaged in transferring information and
no new command should be issued.  The error count is  useful for counting
retries when errors occur.  The device queue is used to remember  stacked
requests; in the simplest case it may be maintained as  a first-in first-
out list.  Since buffers which have been handed over to the strategy rou-
tines are never on the  list of free buffers, the pointers in the  buffer
which maintain the free list (av_forw, av_back) are also used to  contain
the pointers which maintain the device queues.

Since there is only one  device table per major device,  it is normal  to
have only one I/O operation  active on the major device at a time.   Thus
each major device usually corresponds  to a single physical device.   The
370 device address of this physical device is contained in  d_baseaddr in
bdevsw.  In this situation, the minor device number can be used to select
a particular area or mode of the physical device.

A couple  of routines  are  provided which  are useful  to  block  device
drivers.

     iodone(bp)

arranges that the buffer to which bp  points be released or awakened,  as
appropriate, when  the  strategy module  has  finished with  the  buffer,
either normally or after an error.  (In  the latter case the B_ERROR  bit
has presumably been set.)

The routine

     geterror(bp)

can be used to examine the error bit in a buffer header and arrange  that
any error indication found therein  is reflected to the user.  It may  be
called only in the non-interrupt part of a driver when I/O has  completed
(B_DONE has been set).

-------------------------------- Page 11 --------------------------------

7.    THE I/O SUPERVISOR

The UTS I/O Supervisor controls all machine level I/O operations.  It  is
called from drivers to start I/O, and in turn calls drivers after gaining
control from I/O interrupts.  The prime purpose of the I/O supervisor  is
to isolate  channel and  control unit peculiarities  from the  individual
device drivers.  It also allows different drivers to use a device  simul-
taneously without confusion.

The I/O supervisor does not need to be initialized with channel,  control
unit, or  device types.   Channel types are  determined dynamically;  all
control units are treated  identically; all devices  are treated  identi-
cally.  Because of this, a number of hardware features are not supported.
These include multiple  path support,  control units with  other than  16
devices, and some support of shared subchannel control units.

Data areas  for each  device,  control unit,  and channel  must  be  pre-
allocated.  This is done in  the file "iotab.c".  Each channel  structure
points at 16 control unit structures (or zero); each control unit  struc-
ture points at 16 unit  structures (or zero).  The array chans points  to
each of the channel structures.


7.1      I/O CALLS

There are 5 calls to the I/O supervisor:

     sio(addr, caw, intr, arg)
     tio(addr, intr, arg)
     hio(addr)
     setax(addr, intr, arg)
     getax(addr, &intr, &arg)


Sio, tio, and hio are  used to issue the machine  operations of the  same
name (actually siof, tio, and hdv).  Setax is used to set an asynchronous
interrupt exit for a device; getax is  used to discover the current  exit
so it can be saved.  In each of these calls,  addr specifies the 370 dev-
ice address.  Caw is just the address of the CCWs, since protect keys may
not be  assigned by  drivers.  Intr  specifies the  interrupt routine  to
which all status of  the I/O operation  will be passed.   In the case  of
setax and getax,  intr specifies  the routine to  which all  asynchronous
interrupts from the device are  passed.  Arg can be  any one word  value.
It is passed  to the  interrupt routine unexamined  and unchanged; it  is
typically a pointer to a structure containing information about the  dev-
ice.

Although the I/O supervisor will queue  multiple I/O requests for a  dev-
ice, any  one driver  should have  only one or  two outstanding  requests
active at a time.  This is because there is just a small amount of  space

-------------------------------- Page 12 --------------------------------

for the I/O queues.   All sio requests for  a device proceed in a  strict
FIFO sequence.  Tio  and hio  requests are  also FIFO,  but if  a sio  is
active, a tio or hio will not wait for the sio to finish.

When using sio, caw must point to double-word aligned CCWs.


7.2      INTERRUPT ROUTINES

All status from an I/O operation, including both interrupts and condition
codes, is  passed to  the interrupt  routine specified when  the I/O  was
requested.  Only status relevant to  a specific device  is passed to  the
interrupt routine; channel busy, control  unit busy, etc. are never  seen
by a driver.  When a  unit check occurs, the  I/O supervisor will  obtain
the device's sense information  before reflecting the  unit check to  the
interrupt routine.

Interrupt routines are always entered with I/O interrupts disabled.  They
are allowed to issue  further I/O requests;  any I/O previously  enqueued
will be started when the interrupt routine returns.

The call

     (*intr) (arg, csw, sense)

is used to invoke interrupt routines.  Intr and arg are obtained from the
call to sio, tio, hio, or setax.  Csw is a  pointer to the CSW which con-
tains the status  for the  operation.  Sense is  a pointer  to the  sense
information for the device  (only valid when  unit check is indicated  in
the CSW).

The I/O supervisor uses the deferred condition  code field in the CSW  to
pass condition codes 1 and 3 to the interrupt routine, whether or not the
condition code was actually deferred.  Condition code 2, channel busy, is
never passed to the driver.

When a sio  receives a  zero condition  code, the  operation is  actually
started, and any calls to the interrupt routine will indicate channel end
or device end in the CSW.

When a  tio receives  a condition  code zero,  the interrupt  routine  is
called with the CSW  set to all zeros.   When a tio receives a  condition
code 1, one of status modifier, busy, or unit check will be indicated.  A
tio interrupt routine is never passed information caused by a sio.

The hio request never causes an interrupt routine to be invoked, however,
the interrupt routine for the halted operation (if any) may be invoked.

The setax call must be used to specify an asynchronous interrupt routine.
All attention interrupts,  and all "device  ready" device end  interrupts

-------------------------------- Page 13 --------------------------------

will be passed to this routine.  Each call to setax replaces the  routine
address with a new  one, so at most one  routine will see any one  inter-
rupt.  The getax call may be used so that a driver can save, change,  and
restore the asynchronous interrupt exit.  When the intr argument to setax
is zero, asynchronous interrupts are ignored.

The header file "io.h" contains  field definitions for  the CSW and  CCW.
For consistency, drivers should use only these definitions.


7.3      OTHER I/O SERVICES

The call

     makidaw(flag, idaw, base, count)

is used  by drivers  which read  or write  directly from  a user  process
buffer.  It  uses its  arguments  to construct  a channel  indirect  data
address word list.  The argument idaw is a pointer to up to 33 words (for
65535 bytes) which  will be the IDAW  list.  The argument  flag is 0  for
reading, and 1 for writing.  It is used to prevent the user from  reading
into shared text.  The argument base is the address of the user's buffer;
count is the length of his buffer.  The variable u.u_error is set if IDAW
construction fails.   Typically,  base and  count  should be  taken  from
u.u_base and u.u_count.  The residual length of the data transfer  should
be placed in u.u_count when the I/O operation is completed.

Two VM dependent calls are available:

     reset(addr)

and

     cpclose(addr)

These routines issue the  CP RESET  or CLOSE commands  for the  specified
device.  Also, reset causes the I/O supervisor to believe that  no I/O is
active on device addr.  No values are returned from these calls.


7.4      MISSING INTERRUPT HANDLER

A facility is available  for detecting and  recovery from missing  inter-
rupts.  The call

     iosetup(addr, mint)

requests that an interrupt  for device addr  be considered missing  after
mint seconds.  If a sio  successfully starts an operation which does  not
complete in (approximately) mint seconds,  the device is halted  (through

-------------------------------- Page 14 --------------------------------

hdv and clrio instructions) and the interrupt routine is called  with the
CSW set to all zeroes to indicate missing interrupt.

-------------------------------- The End --------------------------------
