


                          NOTES ON WRITING VAX/VMS ACP'S




                                 Charles W. Bolz
                         Boeing Computer Services Company

          PREFACE

          NOTES ON WRITING VAX/VMS ACP's supplements a seminar  on  writing
          an Ancillary Control Process (ACP) and  associated  pseudo-driver
          interface  for  VAX/VMS  2.0.   The  interfaces  and   mechanisms
          presented  have  executed  successfully  on  a  VAX/VMS  prelease
          version and should execute under future versions of VMS.  However
          this cannot be guaranteed.

          INTENDED AUDIENCE

          These notes are intended for systems programmers with  a  working
          knowledge of VAX/VMS internals.

          NOTE STRUCTURE

          The seminar is  organized  into  three  parts.   The  first  part
          introduces  ACP's,  then  discusses  user  interfaces  to  ACP's,
          VAX/VMS data structures which support  ACP's,  and  communicating
          with an ACP.  The second part discusses a template  pseudo-device
          driver  and  ACP.   The  third  presents  a  case  history  which
          illustrates some  common  uses  of  ACP's,  then  overviews  some
          VAX/VMS ACP's.  These notes cover the  first  two  parts  of  the
          seminar.

          ACKNOWLEDGEMENTS

          Vik Muznieks created the only known DEC documentation on  VAX/VMS
          ACP's as a short appendix to  the  VAX/VMS  Device  Driver  class
          notes; his excellent presentation inspired Sections 2.1  and  2.2
          of these notes.  The aspiring ACP implementor will, before he has
          a  working  program,  have  thoroughly  dog-eared   the   VAX/VMS
          Internals Manual and left lots of fingerprints on the Fiche; much
          material from these sources is summarized in Section 2.3 to  save
          a lot of looking.  However, responsibility for  errors  in  these
          notes is solely mine.

          I am indebted to Art Murphy and Barry  Wick  of  Boeing  Computer
          Services Company who shared the adventure of creating an ACP from
          scratch.








                                        1



                  WRITING VAX/VMS ACP'S
                     SEMINAR OUTLINE

           1.0    INTRODUCTION TO ACP'S
           1.1      What is an ACP
           1.2      Why use an ACP
           1.3      When not to use an ACP

           2.0    HOW THE ACP FITS INTO VAX/VMS
           2.1      The ACP Interface
           2.1.1      Driver FDT routine interfaces
           2.1.2      Flow through a pseudo-device driver and ACP
           2.2      VAX/VMS Data Structures Supporting ACP's
           2.2.1      The ACP Queue Block
           2.2.2      The Volume Control Block
           2.2.3      Communicating with the ACP
           2.3      Flow of an I/O request packet
           2.3.1      The $QIO system service
           2.3.2      The device driver
           2.3.3      I/O request completion
           2.3.4      I/O request cancellation and image rundown
           2.4      Pseudo-Devices and UCB's

           3.0    IMPLEMENTING THE ACP
           3.1      The ACP Mount Sequence
           3.2      The Pseudo-Device Driver
           3.2.1      Functionality
           3.2.2      Assigning a UCB
           3.2.3      Handling of User Buffers
           3.2.4      Queing an IRP to the ACP
           3.2.5      Establishing Attention AST's
           3.2.6      Dismounting the ACP
           3.2.7      Cancelling I/O
           3.3      The ACP
           3.3.1      The Grand Loop
           3.3.2      Queue Processing in the ACP
           3.3.3      Posting IRP's
           3.3.4      Buffer Management
           3.3.5      Asynchronous System Traps
           3.3.6      Dismounting an ACP
           3.3.7      Cancelling I/O
           3.3.8      ACP error notification

           4.0    CODING, MOUNTING AND DEBUGGING AN ACP
           4.1      Kernel Mode Coding
           4.2      Mounting the ACP
           4.3      Debugging the ACP
           4.3.1      Using DELTA
           4.3.2      Using SDA
           4.4      Some Common ACP bugs
           4.5      Declaring an Exception Handler





                                        2






          5.0     CASE HISTORY - AN INTELLIGENT DATABASE MACHINE

                  (Seminar notes not provided)

          6.0     ADVANCED ACP TOPICS

                  (Seminar notes not provided)














































                                        3



          1.0 INTRODUCTION TO ACP'S

          1.1 WHAT IS AN ACP

          An Ancillary Control Process (ACP) is a privileged  process  that
          interfaces between the user and  the  operating  system,  usually
          without the user's knowledge.  An ACP typically  interfaces  with
          the I/O subsystem to arbitrate or manage resources that  must  be
          shared among users.  What an ACP does is best shown by examples:

          (1) Allowing individual users to allocate  their  own  space  and
              define their own file structure on shared disk would at  best
              complicate system management.  The  Disk  ACP  allocates  and
              structures the disk for all  users  and  makes  file  mapping
              information available to the driver.

          (2) Implementing DECNET requires that non-sharable devices,  such
              as the DMC-11, be  shared  among  users  and  that  a  common
              protocol be observed among nodes  on  the  net.   The  NETACP
              performs these functions.

          (3) Logging onto remote nodes of DECNET requires that a  terminal
              communicate  with  its  process  via  DECNET.    The   REMACP
              facilitates this.

          (4) Several real-time processes must talk to  several  laboratory
              devices over a bus.  An ACP manages traffic on the bus.

          (5) Processes talk  to  an  intelligent  database  computer  over
              multiple   communications   lines.    An   ACP   assigns    a
              communications line and manages a callback protocol.

          (6) Microprocessor-controlled overhead cranes move material in  a
              warehouse under central computer control.  An ACP  implements
              the protocol to control the cranes.

          Although ACP's generally manage I/O-related activities, there  is
          no reason to restrict them to such.  For example,  an  ACP  could
          implement  a  "SUPERGROUP"  privilege,  similar  to  the  "WORLD"
          privilege, to allow user control of remote work stations.   Users
          would issue requests to delete print  queue  entries,  reschedule
          batch jobs, etc.  The ACP would check if the user belonged to the
          "supergroup" owning the work station and execute the  request  if
          he did.


          1.2 WHY USE AN ACP

          A logical question to  ask  is:  why  not  handle  ancillary  I/O
          activities in the device driver, and avoid the added complexities
          of having ACP's in the system.  There  are  several  reasons.   A
          driver has limited context, consisting mainly of the Unit Control
          Block (UCB) and the I/O  Request  Packet  (IRP)  and  buffer.   A



                                        4



          driver executes at elevated IPL, which  means  it  cannot  safely
          call  system  services  and  that  it  can  do  little  ancillary
          processing without blocking the whole system.  And maintenance is
          difficult.  If the device is replaced with a better one  all  the
          code written to handle ancillary activities must be threaded into
          the new driver.

          But perhaps the most compelling reason comes about because, as  a
          privileged process, the ACP has its own  process  virtual  memory
          space, its  own  privileges  and  quotas,  it  can  issue  system
          services, and it can access  system  data  structures  and  code.
          This means that, rather than modifying the  problem  to  fit  the
          strictures imposed on drivers, the system programmer designs  and
          codes the ACP in a manner natural to his problem.  He may  define
          his own data structures and  tables  to  allocate  and  arbitrate
          resources and implement protocols.  He may issue system  services
          without restriction; this includes $QIO's to I/O devices  and  to
          mailboxes.  He may even communicate with other ACP's!


          1.3 WHEN NOT TO USE AN ACP

          An ACP is a sophisticated piece  of  system  code  that  requires
          expertise to develop and maintain.  It should not  be  used  when
          simpler alternatives are available.  Two examples follow:

          (1) Problem: several users in the same  group  must  occasionally
              transmit to a non-sharable device and wait for a response.

              Solution: use a common event flag to control the  device.   A
              user waits on the flag, checks to ensure  it  is  still  set,
              then assigns the device.  He then issues a $QIO write, a $QIO
              read, and deassigns the device.

          (2) Problem: several users must transmit requests to a number  of
              laboratory devices attached to a bus.  The  devices  must  be
              sampled periodically and data  made  available  to  all  user
              processes.

              Solution: a detached  user-mode  process  controls  the  bus.
              Users assign a channel to a  global  mailbox  and  map  to  a
              global section.  The detached process accepts  user  requests
              from  the  mailbox  and  transmits  them  to  the  laboratory
              devices.  It reads the devices periodically and puts the data
              in the global section.

          ACP's might not be suitable for  real-time  applications  with  a
          cycle time of only a few milliseconds.  An  ACP,  as  a  process,
          must  be  scheduled  and  context-switched;   context   switching
          requires about half a millisecond.  As a rule of  thumb,  if  the
          real-time application can handle $QIO overhead, it  can  probably
          tolerate ACP overhead.  If it must  use  connect-to-interrupt  or
          similar schemes to avoid $QIO overhead, it  probably  should  not



                                        5



          use an ACP.




          2.0 HOW THE ACP FITS INTO VAX/VMS

          2.1 THE ACP INTERFACE

          2.1.1 DRIVER FDT ROUTINE INTERFACES

          Since an ACP is a process which interfaces with other  processes,
          any  suitable  means  of  interprocess  communication,  such   as
          mailboxes, global  sections  and  common  event  flags  could  be
          adapted to ACP synchronization and data transfer.  However, ACP's
          evolved to supplement disk and  tape  I/O;  the  interfaces  that
          developed are based on the $QIO system service and use the IRP to
          convey the user request to the ACP.

          Two types of $QIO interfaces developed.  In the first, the ACP is
          located logically between the device driver FDT routines and  the
          Start I/O routine, as illustrated in figure 2-1.   This  type  of
          interface is used by the disk and magnetic tape ACP's.   The  FDT
          code decides whether an IRP can be queued directly to  the  Start
          I/O routine or requires intermediate processing by the  ACP.   If
          the ACP gets the IRP, it queues the IRP to the Start I/O  routine
          when it has processed it.

          The second kind  of  interface,  shown  in  figure  2-2,  puts  a
          "pseudo-device" between the user and the ACP.  The user assigns a
          channel to the  pseudo-device  and  issues  $QIO's  to  it.   The
          pseudo-device driver (which normally contains only  FDT  routines
          and a Cancel I/O  routine)  validates  the  $QIO  parameters  and
          passes the IRP on to the ACP.

          This approach is used by the NETACP to arbitrate  a  non-sharable
          device, the DMC-11.  Users issue $QIO's to pseudo-device NET: the
          NET pseudo-driver queues IRP's to the NETACP.  NETACP assigns the
          DMC-11 to itself, then issues $QIO's to it, observing the network
          protocol.  Without a pseudo-device each user would have to assign
          the DMC-11, transmit a complete message (observing the protocol),
          then deassign the DMC-11.  An ACP could not be  used  (since  the
          ACP and the user process could not each assign  the  device)  and
          there would be no effective traffic management on the device.

          Figure 2-3 shows an interesting use of  pseudo-devices.   When  a
          DECNET user logs onto another node using the $SET HOST command, a
          remote "pseudo-terminal" is created  on  that  node.   Input  and
          output  for  the  process  go   through   the   remote   terminal
          pseudo-driver (RTTDRIVER) to the remote  terminal  ACP  (REMACP).
          This ACP issues $QIO's to the NET pseudo-device!  This talks, via
          a NETACP on each side of the link to the user's  terminal.   Thus
          remote terminal I/O passes through three  device  drivers,  three



                                        6



          pseudo-drivers and three ACP's!


          2.1.2 FLOW THROUGH A PSEUDO-DEVICE DRIVER AND ACP

          This seminar emphasizes the  pseudo-driver  interface  to  ACP's,
          since it is the more versatile.  It is instructive  to  follow  a
          user request through such  an  interface,  step-by-step.   Assume
          that the pseudo-device driver  supports  a  single  pseudo-device
          called XX, and the ACP manages a real device, called YY.

          (1) XXDRIVER and YYDRIVER are loaded, and the ACP is mounted.

          (2) The ACP assigns a channel to the real device YY and saves the
          channel number.

          (3) A user assigns  a  channel  to  pseudo-device  XX,  which  is
          sharable.

          (4) The user issues a $QIO to device XX.

          (5) XXDRIVER validates the I/O request,  makes  the  user  buffer
              available to the ACP (either  by  assigning  an  intermediate
              system buffer or by locking down the user buffer), and passes
              the IRP to the ACP.

          (6) The ACP examines the IRP function  code,  maps  to  the  user
              buffer or accesses the system buffer, and issues  a  $QIO  to
              the real device YY.

          (7) YYDRIVER  initiates  device  YY  to  perform  the   requested
              operation, then posts status back to the ACP  when  the  real
              I/O completes.

          (8) The ACP copies data to the user  buffer,  or  to  the  system
              buffer assigned to the user's request.  It then fills the I/O
              status fields in the IRP  and  posts  the  IRP  to  the  user
              process via IOPOST.

          (9) The user's $QIO completes.  The user  may  now  repeat  steps
              4-9, or another user may perform steps 3-9.









          2.2 VAX/VMS DATA STRUCTURES SUPPORTING ACP'S

          2.2.1 THE ACP QUEUE BLOCK (AQB)



                                        7




          Several VAX/VMS data  structures  are  of  interest  to  the  ACP
          implementor.  The UCB and the IRP have  already  been  mentioned.
          The Window Control Block (WCB) and File Control Block  (FCB)  are
          used by disk ACP's for mapping logical to physical blocks and for
          holding the file home block, respectively.  The ACP  Queue  Block
          (AQB) and the Volume Control Block (VCB) are used  by  all  ACP's
          and are discussed in detail below.

          The AQB, illustrated in figure 2-4, contains the  listhead  where
          the ACP looks for IRP's to service.   The  ACP  implementor  must
          allocate the AQB from non-paged pool and fill  in  the  size  and
          type fields, and the ACP PID field.  He must then use the pointer
          field to link the AQB into the singly-linked list found at global
          listhead  IOC$GL_AQBLIST.   The  count  field  is  set  to   one,
          indicating that the ACP process is handling  one  "volume".   The
          last word is not used by most ACP's.  For information, the  Class
          byte specifies ODS-1 or ODS-2 file structure, the ACP  Type  byte
          specifies the disk type, and the Status bits are  used  for  disk
          and tape ACP consistency checks.


          2.2.2 THE VOLUME CONTROL BLOCK (VCB)

          The Volume Control Block is  shown  in  figure  2-5  .   The  ACP
          implementor must allocate this, too, from non-paged pool and fill
          in the type and size fields.  He must also point the VCB  to  the
          AQB (VCB$L_AQB)  and  point  the  UCB  to  the  VCB  (UCB$L_VCB).
          Finally he must set the transaction count.  This count tracks the
          number of currently active requests on the "volume".  If it  ever
          goes to zero, the VCB could be deallocated by the system; to keep
          this from happening, the count is usually initialized at one.

          The VCB has many fields for use by disk and tape ACP's which  may
          be used by  the  ACP  implementor  for  his  own  purposes.   The
          implementor may allocate a VCB for each device, or a  single  VCB
          for all devices serving the ACP.


          2.2.3 QUEUING AN IRP TO THE ACP

          A driver FDT  routine  queues  an  IRP  to  the  ACP  by  calling
          EXE$QIOACPPKT.  This system routine does the following:

          (1) Follows the pointer in the UCB to the VCB and increments  the
              VCB transaction count.

          (2) Follows the pointer in the VCB to the AQB.

          (3) Calls EXE$INSERTIRP to insert the IRP by priority in the  AQB
              queue.

          (4) If the queue is empty, takes the ACP process ID from the  AQB



                                        8



              and wakes the ACP.




          2.3 FLOW OF AN I/O REQUEST PACKET

          The  I/O  Request  Packet  (IRP)  is  the  primary   vehicle   of
          communication between the user process and the I/O subsystem.  It
          is essential to understand the normal flow of an IRP through  the
          system because an ACP changes that flow.  Improper handling of an
          IRP can hang a user process or even corrupt the system.

          Figure 2-6 presents an overview of IRP flow.


          2.3.1 THE $QIO SYSTEM SERVICE

          The primary function of the $QIO system service is to  build  the
          IRP and queue it to the device driver.   $QIO  allocates  an  IRP
          from the lookaside list  of  non-paged  pool  and  fills  in  all
          device-independent parameters, such as  the  I/O  function  code,
          data transfer type and size, and process ID (figure 2-7).  If the
          I/O function code is a read/write virtual block and the device is
          not a file-oriented device, the function code is converted  to  a
          logical function.  It then  increments  the  channel  I/O  count,
          CCB$W_IOC, and calls device driver Function Decision Table  (FDT)
          routines as coroutines to build the  device-dependent  fields  of
          the IRP.

          The $QIO system service  and  driver  FDT  routines  run  in  the
          context of the process issuing the $QIO.  While they are running,
          the address of the IRP is kept in a register (R3).  If  the  user
          process were to be deleted at this time, the address of  the  IRP
          would be lost and the physical memory allocated to  it  would  be
          lost to the system until the next reboot.  To prevent  this,  the
          FDT routines run at IPL 2, IPL$_ASTDEL, which blocks the  special
          kernel AST required to delete the process.  (This means that,  in
          general, the FDT routines cannot call system  services,  as  they
          may drop IPL to zero.)


          2.3.2 THE DEVICE DRIVER

          The FDT routines normally complete their processing of the IRP by
          queuing it to the device UCB (UCB$L_IOQBL, figure 2-8).  Whenever
          the device is not busy, the next IRP  queued  to  the  device  is
          dequeued, its address stored in UCB$L_IRP, and the  driver  Start
          I/O routine called.  From this point on the driver runs in system
          space in the limited context of the UCB and the IRP.  The  driver
          processes the IRP at fork  or  interrupt  IPL;  this  blocks  all
          normal processing, which occurs at IPL$_SYNCH or lower.




                                        9




          2.3.3 I/O REQUEST COMPLETION

          Once the driver completes servicing an I/O  request,  it  inserts
          final status information into the IRP, then posts it  by  queuing
          it to IOC$GL_PSBL, the I/O  post-processing  queue.   The  driver
          initiates  a  software  interrupt  at  IPL$_IOPOST  (IPL  4)   if
          necessary, then checks  the  device  queue  for  another  IRP  to
          service.

          When IPL drops to  IPL$_IOPOST,  the  interrupt  service  routine
          IOC$IOPOST  is  called.   This  routine  dequeues  an  IRP,  then
          performs those post-processing steps that  do  not  need  process
          context, such as setting event flags and unlocking buffer  pages.
          It  then  issues  a   special   kernel   AST   to   perform   any
          post-processing requiring access to process address  space,  such
          as writing the I/O Status Block and copying a system buffer to  a
          user buffer.  The AST routine also decrements the  I/O  count  on
          the channel assigned to the device.

          The IRP is used as the AST Control Block for the  special  kernel
          AST and also for the I/O completion AST if one was  requested  by
          the user.  The IRP is deallocated by the AST post-processing code
          in the latter case.


          2.3.4 I/O REQUEST CANCELLATION

          I/O  requests  can  be  cancelled  in  three  general  ways:   by
          cancelling current I/O on the channel ($CANCEL);  by  deassigning
          the channel ($DASSIGN); or by  image  exit  or  process  deletion
          (SYS$RUNDWN)

          $CANCEL cancels all I/O issued to a  device  from  a  channel  by
          scanning all IRP's queued to the device  UCB.   If  the  PID  and
          channel number in the IRP match those of the  requesting  process
          and  channel,  and  the  request  is  not   a   virtual   request
          (IRP$V_VIRTUAL clear), SS$_CANCEL is placed  in  the  IRP  status
          field and the IRP is posted to  the  I/O  post-processing  queue.
          Now the driver Cancel I/O routine is called.  It checks  the  I/O
          request currently in progress and is expected to clean  up  after
          it and, if appropriate, cancel it before returning to $CANCEL.

          If  there  is  ACP-controlled  disk  or  tape  file  activity  in
          progress, (IRP$V_VIRTUAL bit set) $CANCEL builds an IRP on behalf
          of the user with function code of IO$_ACPCONTROL and queues it to
          the ACP so the ACP can perform necessary cleanup.

          $DASSIGN calls $CANCEL to cancel  outstanding  I/O,  then  checks
          CCB$W_IOC to see if any I/O is still outstanding.  If  there  is,
          it places the process into an AST wait state, waiting for the I/O
          completion special kernel AST's to be delivered.




                                       10



          SYS$RUNDWN deassigns all channels without open files.


          2.3.5 IMPLICATIONS FOR THE ACP IMPLEMENTOR

          The above discussion has the following implications for  the  ACP
          implementor.

          (1) The ACP must carefully track all IRP's queued to it and  post
              them properly when finished with them.

          (2) If the ACP must clean up after a  process,  the  driver  must
              notify the ACP when a user cancels I/O on a channel  and  the
              ACP must cancel and post any IRP belonging to that channel.

          (3) When the ACP dismounts it must  cancel  and  post  all  IRP's
              queued to it.









          2.4 PSEUDO-DEVICES AND UCB'S

          The ACP implementor must  decide  whether  to  provide  a  single
          shared pseudo-device and UCB  for  all  users,  or  a  non-shared
          pseudo-device unit and UCB for each user.  The decision is  based
          primarily on whether  the  ACP  must  be  notified  when  I/O  is
          cancelled on a channel, and secondarily on whether the ACP  needs
          to perform housekeeping for each  process  using  the  ACP.   Two
          examples will clarify this.

          (1) Processes periodically send messages to an  external  device.
              Once the process has sent the  message  via  a  $QIO  to  the
              pseudo-device, it is no longer  concerned  with  that  event.
              The  ACP  simply  forwards  the  messages  on  a  first-come,
              first-served  basis,  managing  the   external   device   and
              implementing  the   device   protocol.    A   single   shared
              pseudo-device is obviously sufficient.

          (2) The  external  device   above   sends   acknowledgements   to
              processes;  a  process   must   issue   a   read   for   this
              acknowledgement before proceeding.  The ACP,  upon  receiving
              an acknowledgement, must make certain  that  the  process  is
              still active, find the IRP for the acknowledgement read, fill
              the buffer, and post the IRP.  This is simple to  keep  track
              of if each process has a pseudo-unit UCB assigned; it is only
              necessary to relate each acknowledgement to a UCB.  When  the
              read acknowledgement IRP is dequeued from the ACP  queue,  it



                                       11



              can be requeued to  the  UCB  until  the  acknowledgement  is
              received from the  external  device.   And  the  ACP  can  be
              notified automatically if the process is cancelled.

          There are several  ways  to  implement  multiple  pseudo-devices.
          VAX/VMS provides a  convenient  way  to  dynamically  create  and
          assign pseudo-unit UCB's.  If bit DEV$V_TEMPLATE is  set  in  the
          UCB status word UCB$W_STS,the $ASSIGN system service will clone a
          UCB for each user assigning to the template UCB.  It will set the
          UCB characteristic DEV$M_MBX so that the $DASSIGN system  service
          will delete the UCB when the  user  finishes  with  the  channel.
          This method is used by the NET pseudo-driver and ACP.

          The above method is not straight-forward to  implement  when  the
          ACP must clean up after a process (see note  below)  so  for  the
          template ACP a simpler method is used.  One method is  simply  to
          connect a number of pseudo-units and leave it to the user to find
          a free one.  But the method  used  by  the  template  ACP  is  to
          connect a number of pseudo-units equal to the  maximum  allowable
          number of  users,  plus  one.   Users  assign  to  unit  0  (more
          specifically to an equivalent logical name) and the pseudo-driver
          changes the channel assignment to point to an available unit.

          (Note: there are at least two ways to notify the  ACP  of  Cancel
          I/O on channel but they are both a little tricky.  One is to  set
          the IRP$M_VIRTUAL status bit: this  causes  a  special  IRP  with
          IO$_ACPCONTROL function to be sent to the ACP; but since this  is
          intended for use with FILES 11 ACP's, it may have unintended side
          affects.  A second is to use a template UCB  as  the  NET  device
          does, but clear the DEV$M_MBX bit.  The driver Cancel I/O routine
          can queue this UCB to the ACP and the ACP can call IOC$DELMBX  to
          delete the UCB.  Both these methods require careful attention  to
          the fiche, and future releases of VAX/VMS could modify details of
          either).






















                                       12



          3.0 IMPLEMENTATING THE ACP

          The following sections discuss implementation details for an  ACP
          and pseudo-driver based on the template ACP.


          3.1 THE ACP MOUNT SEQUENCE

          The VAX/VMS file-oriented ACP's use a  separate  mount  image  to
          mount the ACP, but the  template  ACP  mounts  itself.   Mounting
          consists of the following steps.

          (1) Create  the  AQB  by  calling  EXE$ALONPAGED  with   argument
              AQB$C_LENGTH to allocate an AQB, fill in the fields discussed
              in Section 2.2.1 above, and link it  into  the  singly-linked
              global  AQB  listhead  IOC$GL_AQBLIST.   The  AQB  becomes  a
              VAX/VMS data structure, so it must be created at IPL$_SYNCH.

          (2) Allocate the VCB in  similar  fashion,  fill  in  the  fields
              discussed in Section  2.2.2,  and  increment  the  AQB  mount
              count.

          (3) Point the template UCB to the VCB by putting the VCB  address
              in UCB$L_VCB.  Point the VCB to the AQB using VCB$L_AQB.

          (4) Set the on-line bit UCB$M_ONLINE in UCB$W_STS, so the  pseudo
              driver can receive QIO's from users.





          3.2 THE PSEUDO-DEVICE DRIVER

          3.2.1 FUNCTIONALITY

          The pseudo-device driver normally has no initialization  routines
          and no Start I/O routine;  only  Function  Decision  Table  (FDT)
          routines and (perhaps) a Cancel I/O routine.  The driver prologue
          table typically specifies a sharable  input/output  device.   The
          FDT  routines  set  up  I/O  buffers  and  Attention  AST's   and
          facilitate dismounting  of  the  ACP.   The  Cancel  I/O  routine
          notifies the ACP to clean up after a user.

          The implementor may  specify  whatever  I/O  function  codes  and
          modifiers are appropriate to his problem.  It is a good  idea  to
          use IO$_READxBLK and IO$_WRITExBLK for data  transfer  functions;
          as mentioned in Section 2.3,  VAX/VMS  will  take  care  of  many
          buffer management details automatically if  these  functions  are
          specified.

          The template pseudo-driver accepts  the  following  I/O  function
          codes which, with user-defined modifiers,  should  be  sufficient



                                       13



          for most user-written ACP's.

          (1) IO$_READVBLK performs read-related functions.

          (2) IO$_WRITEVBLK performs write-related functions

          (3) IO$_SETMODE + IO$M_ATTNAST sets up an Attention AST which  is
              delivered to the user process by the ACP if an  ACP-supported
              device fails or if the ACP dismounts itself.

          (4) IO$_ACPCONTROL + IO$M_DMOUNT  allows  a  privileged  user  to
              dismount the ACP.

          (5) IO$_ACPCONTROL with any other modifier allows a user to  send
              an "urgent"  request  to  the  ACP  (e.g.,  ignore  the  last
              request).  The IRP is queued to the head of the ACP queue.





          3.2.2 ASSIGNING A UCB

          All processes issuing a $ASSIGN to the  pseudo-driver  assign  to
          pseudo-unit 0.  Upon detecting a request assigned to unit  0  FDT
          routine ASSIGN_UCB finds an unassigned UCB, allocates it  to  the
          issuing process, points the user's channel assignment (CCB$L_UCB)
          to the assigned UCB, and marks the UCB online.   It  also  points
          the IRP to the assigned UCB, and points the UCB to  the  VCB  for
          the device.


          3.2.3 HANDLING OF I/O BUFFERS

          I/O buffers are handled either by locking down the user's  buffer
          or by intermediate buffering  through  a  system  buffer.   Large
          buffers are normally locked down to avoid the overhead of  system
          buffering, and to avoid waiting for  contiguous  non-paged  pool.
          The  pseudo-driver  locks  down  a  buffer  by   simply   calling
          EXE$MODIFYLOCKR.

          System  buffering  makes  the   pseudo-driver   a   little   more
          complicated .  The implementor must take the following steps.

          (1) Establish that the user buffer can  be  accessed  by  calling
              EXE$READCHK.

          (2) Assign  twelve  overhead  bytes  to  the  buffer,  then  call
              EXE$BUFFRQUOTA to see if the user  has  sufficient  quota  to
              allocate the buffer.

          (3) Allocate the buffer by calling EXE$ALLOCBUF.  The first  four
              bytes of the allocated buffer point to the beginning  of  the



                                       14



              data area.  The next eight bytes hold information used by the
              system for eventually deallocating the buffer.

          (4) Deduct the buffer size from the user's  system  buffer  quota
              JIB$L_BYTCNT in the Job Information Block.

          (5) Record the system buffer address and size in the  IRP  fields
              IRP$L_SVAPTE and IRP$W_BOFF.  Put the  user  buffer  size  in
              field IRP$W_BCNT.

          (6) For a write request, copy the user buffer to the data area of
              the system buffer.

          On looking at the relative  complexity  of  system  buffering  as
          opposed to locking down pages, the implementor might well  wonder
          if its all worth it.  One answer to that question is that locking
          down pages requires substantial overhead that should  be  avoided
          for small buffers, and system buffers are easier for the  ACP  to
          handle.  But ultimately the implementor must choose  a  buffering
          method best suited to his problem.


          3.2.4 QUEUING AN IRP TO THE ACP

          Queuing an IRP to the ACP is fully  described  in  Section  2.2.3
          above.  The FDT routine calls EXE$QIOACPPKT at IPL$_SYNCH.   This
          system routine follows the UCB to the VCB to the AQB, inserts the
          IRP by priority in the AQB queue, and wakes the ACP if the  queue
          was empty.


          3.2.5 ESTABLISHING ATTENTION AST'S

          A UCB extension,  UCB$L_ASTLIST,  provides  a  listhead  for  the
          singly-linked  AST  queue.   FDT  routine  SET_AST  provides  the
          address of this longword, and the address  of  the  user-supplied
          Attention AST routine to  system  routine  COM$SETATTNAST.   This
          system routine builds the AST Control Block and links it into the
          listhead.

          SET_AST does not queue the IRP to the ACP,  but  returns  control
          directly to the user by calling EXE$FINISHIOC.


          3.2.6 DISMOUNTING THE ACP

          FDT routine ACP_CONTROL mediates dismounting of  the  ACP.   This
          routine checks user privilege, then queues the  dismount  request
          to the ACP.







                                       15



          3.2.7 CANCELLING I/O

          The pseudo-driver Cancel I/O routine XXX_CANCEL flushes any  user
          Attention AST requests by calling system routine  COM$FLUSHATTNS.
          The Cancel I/O routine then notifies the ACP to  perform  process
          specific cleanup by queuing the process UCB to the AQB!  The  ACP
          recognizes a UCB in its queue as a cancel request.  (The code  to
          queue the UCB is adapted from EXE$QIOACPPKT, but inserts the  IRP
          at the front of the queue for immediate attention.)





          3.3 THE ACP

          As we saw in Section 3.1 the first thing an ACP does is to  mount
          itself by creating necessary ACP data structures and linking them
          into the system database.   Then  it  is  ready  to  receive  and
          process user requests.  The following sections  discuss  how  the
          template  ACP  manages  user   requests   and   handles   special
          conditions.


          3.3.1 THE GRAND LOOP

          The ACP normally starts life by calling $LKWSET to lock into  its
          working set all code and data which might be referenced above IPL
          2, thus avoiding page faults.  It then goes  to  kernel  mode  by
          calling $CMKRNL and mounts the ACP as described in Section 3.1.

          The ACP now enters its grand processing loop  (Figure  3-1).   It
          first checks its queue; if there is  anything  in  its  queue  it
          calls IRP processing code.  It then rechecks its  queue  in  case
          something slipped in while it was off processing  IRP's;  if  the
          queue is empty, it is almost ready  to  call  $HIBER  and  go  to
          sleep.

          The implementor may want the ACP to make one further check before
          hibernating.  An ACP may have to suspend  processing  of  an  IRP
          until it is notified by AST that processing may be  resumed.   In
          this case, the AST routine should, before waking the ACP,  set  a
          flag  to  indicate  that  the  IRP  needing  attention   is   not
          necessarily in the ACP queue.

          In the template ACP all routines pass a status code back to their
          caller in R0.  If an  error  is  returned,  the  calling  routine
          either fixes it or returns the status  to  its  caller.   If  the
          error status is returned to the grand loop, it assumes the  error
          is irrecoverable and dismounts the ACP.


          3.3.2 QUEUE PROCESSING IN THE ACP



                                       16




          One of  the  more  esoteric  aspects  of  ACP  writing  is  queue
          handling.  The implementor is free to implement whatever  queuing
          scheme  is  appropriate  for  his  problem.   The  template   ACP
          implements a fairly general scheme which is readily modified.

          The philosophy of the template ACP is that IRP's should be queued
          to the UCB assigned to the process issuing the IRP.   So  Queuing
          is handled in routine QUEUE_HANDLER as follows.

          (1) An element is removed from the ACP queue  and  checked.   The
              following cases are processed immediately.  If it is  a  UCB,
              it was placed in the queue by the  pseudo-driver  Cancel  I/O
              routine to tell the ACP to clean up after a process.   If  it
              is an IRP with an ACP Control function and dismount modifier,
              it is a request to post all queued  IRP's  and  dismount  the
              ACP.  If it is an IRP with ACP Control function and any other
              modifier bit set (or none) it is an  implementation-dependent
              "urgent" request to perform some process-specific action.

          (2) For all other cases, the UCB is located  from  the  IRP$L_UCB
              field, and the element is requeued to the UCB.

          (3) When the ACP queue is  empty,  the  IRP  processing  loop  is
              entered.  The template  ACP  uses  a  round-robin  scheduling
              discipline.  It assumes that several IRP's may be  queued  to
              any UCB, but services only one IRP per UCB per loop to ensure
              equal service to all users.  The processing loop is  iterated
              until no IRP's are serviced during a pass.

          The IRP processing routine PROCESS_IRP typically dequeues an  IRP
          from  the  current  UCB  and  performs   implementation-dependent
          processing.   If  it  can  complete  processing   for   the   IRP
          immediately, it posts it and  returns.   If  it  cannot  complete
          processing, it requeues the IRP to the UCB  and  returns.   (This
          would happen if, say, one of many devices controlled by  the  ACP
          could not respond immediately, but the ACP could process requests
          directed to other devices.) Whether the ACP requeues the  IRP  at
          the front or the back of the queue is implementation-dependent.


          3.3.3 POSTING IRP'S

          Once the ACP is finished with  an  I/O  Request  Packet  POST_IRP
          queues the IRP to the I/O post-processing queue as follows.

          (1) Move completion status to IRP$L_IOST1 and IRP$L_IOST2.   This
              will be copied to the user's I/O Status Block.

          (2) Increment the UCB operation count UCB$L_OPCNT  and  decrement
              the VCB transaction count VCB$L_TRANS.

          (3) Insert the IRP at the end of global listhead IOC$GL_PSFL and,



                                       17



              if the  queue  was  empty,  issue  a  software  interrupt  at
              IPL$_IOPOST.







          3.3.4 BUFFER MANAGEMENT

          3.3.4.1 SYSTEM BUFFERING

          If the  ACP  uses  system  buffers,  then  buffer  management  is
          trivial.  Assuming that the IRP address is in R3, then:

                  MOVL @IRP$L_SVAPTE(R3),R8

          places the system buffer data area address into R8, and  the  ACP
          can manipulate the buffer by offsetting from this base register.

          3.3.4.2 MAPPING TO THE USER BUFFER VIA BUFFERCTL

          If the ACP references a  locked-down  user  buffer,  it  must  be
          double-mapped  to  user  process  space.   The  user  buffer   is
          available to the ACP because, when the pseudo-driver locked  down
          the buffer, the system virtual address of a  virtually-contiguous
          list of page table entries (PTE) defining the buffer  page  frame
          numbers (PFN) was placed in IRP$L_SVAPTE.

          Having a list of page frame  numbers,  the  user  buffer  can  be
          mapped into the ACP in at least two ways.  The easiest is to  map
          a single page of data at a time and transfer the data using entry
          points in BUFFERCTL.  This method is useful to copy a large  user
          buffer to a static ACP buffer.  It is implemented as follows:

          (1) In the pseudo-driver, specify to the DPTAB  (driver  prologue
              table) macro the "system virtual page" driver loader flag:

                  DPTAB -
                          FLAGS=DPT$M_SVP,-

              This permanently allocates  to  the  pseudo-device  a  system
              virtual page table entry; an index to the PTE is written into
              UCB$L_SVPN.

          (2) In the ACP, copy the IRP buffer address fields  IRP$L_SVAPTE,
              IRP$W_BCNT  and  IRP$W_BOFF  to  the  equivalent  UCB  fields
              UCB$L_SVAPTE, UCB$W_BCNT and UCB$W_BOFF.  (The system  buffer
              control routines obtain their addressing information from the
              UCB)





                                       18



          (3) To copy a whole buffer of information, set

                          R1 = address of information
                          R2 = number of bytes to transfer
                          R5 = UCB address


              and JSB to IOC$MOVTOUSER to copy to the user process  buffer,
              or IOC$MOVFRUSER to copy from the user process buffer.

          (4) To copy a byte at a time to or from the user buffer,  JSB  to
              IOC$INITBUFWIND with the UCB  address  in  R5,  then  JSB  to
              IOC$GETBYTE or IOC$PUTBYTE in a loop, taking care to preserve
              R0.

          The Buffer  Control  routines  destroy  the  UCB  buffer  address
          fields, so these must be reinitialized if the buffer is  accessed
          again.


          3.3.4.3 MAPPING TO THE USER BUFFER VIA $CRMPSC

          The above method is not particularly useful if the buffer must be
          accessed randomly or if it  must  be  made  available  to  system
          services (such as $QIO).  In such cases, it may be  advisable  to
          map the locked-down user buffer to ACP process space  utilitizing
          the Create and Map Process Section ($CRMPSC) system service  with
          PFN mapping.  This must be done one page  at  a  time.   The  ACP
          space may be static storage, or the $EXPREG system service may be
          used to obtain dynamic memory.   The  implementor  must  remember
          that user process pages map byte for byte to ACP  process  pages,
          so he must keep track of the byte offset into the first  page  of
          the buffer.

          An actual ACP that had to  do  I/O  to  an  external  device  and
          compute a checksum on the data mapped as  shown  in  figure  3-2.
          The Expand Region system service was used to obtain  a  piece  of
          dynamic memory for the buffer.  Then $CRMPSC was used to map this
          virtual memory to the user buffer.  The address  of  this  memory
          was passed as the $QIO P1 parameter to the device  that  the  ACP
          controlled.  Once the ACP had completed processing of the buffer,
          the assigned virtual memory was returned using the $DELTVA system
          service.

          This scheme has the disadvantage that process memory  can  become
          very fragmented; $EXPREG and $DELTVA do  no  garbage  collection.
          The ACP this code came from, however, had periodic  lulls  during
          which all IRP's  in  progress  finished  up  and  returned  their
          dynamic memory.  Thus, process memory bounds were never exceeded.


          3.3.4.4 MAPPING NOT NEEDED




                                       19



          It may be that the ACP need do nothing with the user  buffer  but
          make it available to a real  device.   In  this  case,  the  real
          device driver could be modified to use a buffer that was  already
          locked down.  A special function modifier would tell  the  driver
          that P1, P2 and  P3  arguments  to  the  $QIO  request  were  the
          IRP$L_SVAPTE, IRP$W_BCNT and IRP$W_BOFF created when  the  pseudo
          driver locked down the buffer.   The  advantage  of  this  scheme
          would be that locked down pages would not be charged against  the
          ACP.  The device driver would have to use some kind  of  function
          code which would not cause $QIO or I/O  post-processing  routines
          to do something adverse like decrement PFN  reference  counts  or
          ACP quotas.

          THIS METHOD HAS NOT BEEN TESTED!


          3.3.5 ASYNCHRONOUS SYSTEM TRAPS

          The ACP uses AST's in two ways: (1) to be notified by the outside
          world of some event that affects the ACP; and (2) to  notify  the
          user about some event in the ACP that affects him.


          3.3.5.1 AST's DELIVERED TO THE ACP

          The ACP normally asks to be notified by AST if  the  real  device
          that the ACP controls encounters an unsolicited interrupt (input)
          that  the  ACP  must  act  on.   The  ACP  typically   asks   for
          notification by  issuing  a  $QIO  to  the  real  device  with  a
          IO$_SETMODE function.  The $QIO declares  an  AST  routine  which
          resides within the ACP.  When the desired event occurs,  the  AST
          routine is called as a procedure: this  means  it  must  have  an
          entry mask and must exit using a RET instruction.

          Since the AST is  by  definition  asynchronous,  the  implementor
          cannot predict the state of the ACP at AST  delivery.   Therefore
          the AST routine should not take any action that could modify  the
          current state of the ACP.  It should instead use techniques  such
          as flag bits in the UCB$W_DEVSTS field.  The AST  routine  should
          also set a flag to be read by the main ACP processing  loop,  and
          should wake the ACP if it is hibernating.


          3.3.5.2 AST's DELIVERED TO THE USER

          As discussed in Section 3.2, the user might want to  be  notified
          of some catastrophic event, such  as  the  ACP-controlled  device
          failing, or the ACP dismounting.  In this case, the user declares
          an AST routine in his own process, and the pseudo-driver sets  up
          the AST.  All the ACP must do is deliver the AST  by  moving  the
          UCB address to R5, then:

                  MOVAB   UCB$L_ASTLIST,R4        ; Get AST listhead



                                       20



                  JSB     G^COM$DELATTNAST        ; Deliver the AST





          3.3.6 CANCELLING I/O

          The template ACP is notified that a $CANCEL  Cancel  I/O  request
          has been issued on behalf of a user when it sees  a  UCB  in  its
          queue.   The  ACP  then  calls  CANCEL_PROCESS  which  takes  the
          following actions.

          (1) Remove  each  IRP  from  the  UCB$L_IOQFL  queue,   mark   it
              SS$_CANCEL, and post it.

          (2) Remove each IRP from the AQB and see if  it  belongs  to  the
              cancelling process.  If it does, mark it SS$_CANCEL and  post
              it.  Otherwise, reinsert it in the queue.

          (3) Perform implementation-dependent cleanup of the IRP currently
              in processing, if any.






          3.3.7 DISMOUNTING THE ACP

          Dismounting the ACP is in some respects the inverse  of  mounting
          it, with the exception that all I/O must be cleaned  up  and  all
          AST's cancelled first.  In the template ACP routine  DISMOUNT_ACP
          handles this in the following manner.

          (1) Cycle through all  UCB's,  dequeuing  each  IRP,  marking  it
              SS$_OPRABORT, and posting it.  Mark the UCB as offline.

          (2) Remove all IRP's queued to the AQB,  mark  them  SS$_OPRABORT
              and post them.

          (3) Deliver AST's to all users who have requested them.

          (4) Clear the pointers from the UCB's to the VCB to the AQB.

          (5) Do any required implementation-dependent ACP cleanup.

          (6) We should at this point deallocate the AQB and VCB, but  this
              causes an unexplained system crash!  (Leaving them  allocated
              does no harm - it just removes about a hundred  bytes  or  so
              from nonpaged pool until the next reboot)





                                       21







          3.3.8 ACP ERROR NOTIFICATION

          ACP error notification really covers two topics.   The  first  is
          notification by the system when the  ACP  terminates  abnormally.
          The template ACP merely writes a message,  "Device  Offline",  to
          the operator's  console.   The  implementor  may  wish  to  write
          diagnostic information to the console or to a file.

          A second topic is notification of user  errors.   The  user  must
          always supply an I/O Status Block for ACP error notification.  As
          with device driver requests, an I/O  status  code  of  SS$_NORMAL
          returned in R0 merely means that the $QIO system service and  the
          pseudo-driver discovered no errors in the request and queued  the
          IRP to the ACP.





          4.0 CODING, MOUNTING AND DEBUGGING THE ACP

          4.1 KERNEL MODE CODING

          If the ACP did not handle sensitive system data structures, often
          at elevated IPL, it would be just  another  applications  program
          which  could  be  coded  using  just  about  any   language   and
          programming techniques the implementor felt suitable.  But  since
          the ACP does manipulate the systemwide I/O database, it must  run
          in kernel mode, and the default kernel mode exception handler  is
          Bugcheck!  Therefore the implementor should use proven code, such
          as system services, as much as possible.

          It is also beneficial to make the ACP look as much  like  VAX/VMS
          system code as possible.  Some suggestions are:

          (1) Use the JSB - RSB calling sequence for  transferring  control
              between procedures.  (but an AST routine must start  with  an
              entry mask and exit with a RET instruction)

          (2) Use  R0  and  R1  for  passing  status  information   between
              procedures.

          (3) Use general registers R3 and R5 for holding  the  address  of
              the current IRP  and  UCB,  respectively.   Use  R4  for  the
              Process Control Block (PCB) when it is needed.

              (Note: some system routines require the PCB as  an  argument,
              usually for adjusting quotas.  This is always available  from
              SCH$GL_CURPCB)



                                       22




          (4) Use R2, R6 and R7 for passing parameters other than IRP,  PCB
              and UCB addresses.  Use R8 - R11 for scratch purposes  within
              a routine.

          (5) Do not use lower case characters in device names!

          (6) Use the ENBINT - DSBINT macros for changing IPL.  (Be careful
              not to branch around the DSBINT macro).






          4.2 MOUNTING THE ACP

          Figure 4-1 shows the DCL sequence for loading  the  pseudo-driver
          and mounting the ACP.  The CONNECT /NOADAPTER  tells  the  SYSGEN
          driver loading procedure that this  is  a  pseudo-device  driver.
          The pseudo-driver should be loaded from directory SYS$SYSTEM: The
          pseudo-driver must be loaded before the ACP process is created in
          order that the pseudo-device UCB will be available  to  the  ACP.
          The template ACP is run as  a  detached  process  and,  as  noted
          earlier, mounts itself.

          It is essential to give the  ACP  a  sufficiently  large  maximum
          working set to hold all code that can execute  at  elevated  IPL,
          and all buffers that might be locked down.


          4.3 DEBUGGING THE ACP

          The ACP debugger should have at least three terminals  available:
          one to execute the ACP under DELTA; one to monitor IRP flow using
          SDA,  and  at  least  one  to  submit  $QIO   requests   to   the
          pseudo-driver  and   ACP.    The   following   sections   contain
          suggestions for using DELTA and SDA.  The implementor should read
          the VAX/VMS GUIDE TO WRITING A  DEVICE  DRIVER  and  the  VAX/VMS
          SYSTEM DUMP ANALYZER REFERENCE MANUAL for full details  on  these
          debugging tools.

          4.3.1 USING DELTA

          DELTA and its twin XDELTA are executive debugging tools  intended
          for debugging privileged code.  XDELTA can be run at  all  IPL's,
          but must be run from the system console.  DELTA can be  run  from
          any terminal, but cannot be used to breakpoint  or  step  through
          code that runs at elevated IPL.  The ACP implementor who uses the
          template driver and ACP will not need to test at IPL > 0.

          DELTA allows the user to set breakpoints, to step  through  code,
          and to examine registers and  addresses  (in  process  space  and



                                       23



          system space).  An exception at IPL 0 will not crash  the  system
          when  DELTA  is  invoked;  rather  the  user  can   examine   the
          environment, then exit gracefully, log back  in  and  try  again.
          However  an  exception  at  high  IPL  will  crash  the   system.
          Attempting to step into, or breakpoint in code at high  IPL  will
          also crash the system.  DELTA uses addresses, not symbols, so the
          user should have a load map!

          The user invokes DELTA using the following commands:

                  $ LINK XXXACP                           ; Link normally
                  $ DEFINE LIB$DEBUG SYS$LIBRARY:DELTA    ; Redefine DEBUG
                  $ RUN/DEBUG XXXACP                      ; Run with DEBUG

          DELTA will respond as follows (assuming entry at address 40C):

                  DELTA Version 2.1
                  0000040C/contents





          At this time breakpoints may be set in the code using the DELTA
          registers B2 to B8.  These are set (or reset) as follows.

                  first break address,2;B
                  second break address,3;B
                  ...





          When done, enter ;P to proceed to the first encountered
          breakpoint.  Then the following subset of DELTA commands may be
          used to examine the environment.

                  Rn/                     Display Contents of register n

                  Virtual address/        Display contents of a virtual
                                          address

                  Line Feed               Display contents of next register
                                          or longword

                  Rn/[TAB]                Examine contents of address
                                          pointed to by register n

                  S                       Step to next instruction

                  ;B                      Display all breakpoints




                                       24








          4.3.2 USING SDA

          SDA is generally thought of as a post-mortem debugging tool,  but
          it may also be used for on-line inspection of the system.   Enter
          SDA the usual way, $RUN SYS$SYSTEM:SDA, but when prompted  for  a
          dump file, enter an asterisk (*).

          The most useful command for the ACP debugger is "SHOW_DEVICE_XX".
          This prints  out  the  I/O  database  for  device  XX,  including
          detailed information about the UCB's, the IRP's  queued  to  each
          UCB, the VCB and ACP associated with the device,  and  the  IRP's
          queued to the ACP.  The  debugger  might  also  use  the  command
          "SHOW_SUMMARY" to ascertain the state (and process ID) of the ACP
          and of each process created to exercise the ACP.  He  might  then
          "SET_PROCESS/INDEX=nn" where nn is the last four  digits  of  the
          PID, and "SHOW_PROCESS" to examine the Process Control Block,  or
          "SHOW_PROCESS/PHD" to examine the Process Header.


          4.4 SOME COMMON ACP BUGS

          Unless the implementor is  very  good  or  very  lucky,  he  will
          generate a fair share of bugchecks while debugging his ACP.   The
          VAX/VMS System  Dump  Analyzer  (SDA)  Manual  gives  details  on
          debugging  using  a  system  dump.   Following  are   some   bugs
          encountered by and/or recounted to the author.

          (1) Failure to post an IRP.  This will not normally bugcheck  the
              system, but it  will  hang  the  user  process.   It  can  be
              detected by using SDA online to  examine  the  pseudo  device
              queue.

          (2) Posting an IRP twice.

          (3) Corrupting the stack.  The  PC  (address)  in  the  exception
              vector doesnt make sense (it "couldnt" have  gotten  there!).
              The most common cause is a branch around the DSBINT  (disable
              interrupt) corresponding  to  an  ENBINT  (enable  interrupt)
              macro.  May also occur if a JMP is used instead  of  JSB  (or
              vice versa).

          (4) Improper queuing.  The VAX queue instructions are not  models
              of clarity.  Remember that one normally inserts at  the  tail
              of a queue, and removes from the head.  Or,

                          INSQUE  (Rn),@(tail)
                  or      INSQUE  (Rn),@4(head)




                                       25



                          REMQUE  @(head),Rn

          (5) Page faulting at IPL > 2.  This  gives  a  very  clear  error
              message, "Page fault with IPL too high." The faulting address
              in the ACP will be found in the signal vector.  (Look on  the
              stack for a  longword  "00000005",  followed  by  a  longword
              "0000000C", and count down three words to get this address)






          4.5 DECLARING AN EXCEPTION HANDLER

          Whether to declare an exception handler to catch otherwise  fatal
          errors in the ACP is a philosophical problem.  Obviously  it  can
          be done - just look up the DELTA code in the  VAX/VMS  microfiche
          to see how.  But is it desirable?  The author believes not.  When
          debugging the ACP, DELTA provides  a  benign  environment  and  a
          handler for the ACP is  not  necessary.   Once  in  production  a
          handler is not desirable.  An error in the ACP may  cause  subtle
          corruption of the system  with  unforseeable  results.   BUGCHECK
          should be the production "exception handler" to signal the  error
          to the analyst!































                                       26



                              THE ACP CONTROL BLOCK




          +---------------+---------------+---------------+---------------+
          I                       AQB$L_ACPQFL                            I
          +---------------+---------------+---------------+---------------+
          I                       AQB$L_ACPQBL                            I
          +---------------+---------------+---------------+---------------+
          I $B_MNTCNT     I $B_TYPE       I       AQB$W_SIZE              I
          +---------------+---------------+---------------+---------------+
          I                       AQB$T_ACPPID                            I
          +---------------+---------------+---------------+---------------+
          I                       AQB$L_LINK                              I
          +---------------+---------------+---------------+---------------+
          I    (spare)    I $B_CLASS      I $B_ACPTYPE   I $B_STATUS      I
          +---------------+---------------+---------------+---------------+




                                      Figure 2-4

                            THE VOLUME CONTROL BLOCK

          +---------------+---------------+---------------+---------------+
          I                       VCB$L_BLOCKFL                           I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_BLOCKBL                           I
          +---------------+---------------+---------------+---------------+
          I $B_STATUS     I $B_TYPE       I       VCB$W_SIZE              I
          +---------------+---------------+---------------+---------------+
          I       VCB$W_RVN               I               VCB$W_TRANS     I
          I                       VCB$T_QNAME                             I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_AQB                               I
          +---------------+---------------+---------------+---------------+
          I                       VCB$T_VOLNAME                           I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_RVT                               I
          +---------------+---------------+---------------+---------------+
          I       VCB$W_CUR_SEQ           I       VCB$W_CUR_NUM           I
          I                       VCB$L_HOMELBN                           I
          I                       VCB$L_CUR_FID                           I
          +---------------+---------------+---------------+---------------+
          I       VCB$W_STARTSEQ          I       VCB$W_STARTNUM          I
          I                       VCB$L_HOME2LBN                          I
          I                       VCB$L_START_FID                         I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_IXHDR2LBN                         I
          I $B_CUR_RVN     $B_TM          I       VCB$W_MODE              I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_IBMAPLBN                          I
          I                       VCB$L_ST_RECORD                         I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_MVL                               I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_WCB                               I
          I $B_SBMAPVBN   I $B_IBMAPVBN   I $B_SBMAPSIZE  I $B_IBMAPSIZE  I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_VPFL                              I
          I       VCB$W_EXTEND            I       VCB$W_CLUSTER           I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_VPBL                              I
          I                       VCB$L_FREE                              I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_USLBLAST                          I
          I                       VCB$L_MAXFILES                          I
          +---------------+---------------+---------------+---------------+
          I       VCB$W_FILEPROT          I $B_LRU_LIM    I $B_WINDOW     I
          +---------------+---------------+---------------+---------------+
          I $B_RESFILES   I $B_EOFDELTA   I       VCB$W_MCOUNT            I
          +---------------+---------------+---------------+---------------+
          I $B_STATUS2    I $B_BLOCKFACT  I       VCB$W_RECORDSZ          I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_QUOTAFCB                          I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_CACHE                             I
          +---------------+---------------+---------------+---------------+
          I                       VCB$L_QUOCACHE                          I
          +---------------+---------------+---------------+---------------+
          I       VCB$W_RENDERR           I       VCB$W_QUOSIZE           I
          +---------------+---------------+---------------+---------------+
                                      Figure 2-5








                                Figure 3-1

                              ACP GRAND LOOP






          DO (Forever or until the VAX crashes)

                  IF (ACP queue empty .AND. AST flag clear)

                          Hibernate

                  ELSE

                          Dequeue IRP's from ACP Queue Block
                          Requeue to UCB's

                          DO (until all IRP's posted or blocked)

                                  Process an IRP

                          ENDDO

                          IF (Irrecoverable error)

                                  Dismount ACP


                          ENDIF
                  ENDIF
          ENDDO




                                Figure 3-2

                  MAPPING A USER PROCESS BUFFER FROM AN ACP



          The following code fragment gets some process virtual memory and
          maps a user buffer locked down by a driver to it.  The buffer
          must be locked down one page at a time.

                  IRP$L_BUFF      = IRP$L_IOST1
                          ...
                  CLRL    R9                      ; Scratch register
                  ADDW3   IRP$W_BOFF(R3),-        ; Compute memory needed
                          IRP$W_BCNT(R3),R9       ;   to map user buffer
                  ADDW2   #511,R9                 ; Round up one page and
                  ASHL    #-9,R9,R9               ;   shift for page count
          ;
                  $EXPREG_S -                     ; Go get some more memory
                          RETADR=VIRTADDR -       ;   scratch descriptor
                          PAGCNT=R9               ;   # of pages needed
                  MOVZWL  IRP$W_BOFF(R3),-        ; Compute buffer starting
                          IRP$L_BUFF(R3)          ;   address in ACP space
                  ADDL2   VIRTADDR,IRP$L_BUFF(R3) ;   and put it in IRP$L_B
                  CLRL    R6                      ; Loop counter
                  MOVL    IRP$L_SVAPTE(R3),R8     ; Start of PTE list
                  MOVL    VIRTADDR,-(SP)          ; Build INADR descriptor
                  MOVL    VIRTADDR,-(SP)          ;   on the stack, and put
                  MOVL    SP,R7                   ;     address in R7
          10$:
                  $CRMPSC_S -                     ; Map user buffer to ACP
                          FLAGS=#<SEC$M_PFNMAP+SEC$M_WRT>,- ; PFN mapping
                          PAGCNT=#1,-             ;   one page at a time
                          INADR=(R7),-            ;   virt. addr. descripto
                          VBN=(R8)                ;   page frame number

                  ADDL    #4,R8                   ; Next PFN
                  ADDL    #512,(R7)               ; Update descriptor to
                  ADDL    #512,4(R7)              ;   point to next page
                  AOBLSS  R9,R6,10$               ; Loop until each PFN map

          Once the ACP has completed processing of the buffer, the assigned
          virtual memory is returned by rebuilding the descriptor and cal-
          ling $DELTVA, as follows:

                  MOVZWL  IRP$W_BCNT(R3),R9       ; Transfer byte count
                  ADDL3   IRP$L_BUFF(R3),R9,-     ; Compute ending address
                          VIRTADDR+4              ;   of buffer
                  SUBL3   R9,VIRTADDR+4,VIRTADDR  ; And starting address of
                  $DELTVA_S       INADR=VIRTADDR  ; Free up virtual memory






                                        1



		Figure 4-1
	     MOUNTING THE ACP

 !
 ! Command Procedure to Load the Template ACP and Pseudo-driver.
 !
 ! (1) Connect a number of units equal to the maximum allowable
 !     number of users, plus one.
 !
 MCR SYSGEN
ONNECT XXA0/NOADAPTER/DRIVER=XXXDRIVER
ONNECT XXA1/NOADAPTER/DRIVER=XXXDRIVER
ONNECT XXA2/NOADAPTER/DRIVER=XXXDRIVER
XIT
 !
 ! (2) Load the ACP with all the privileges it could ever need.
 !
 RUN -
/NOACCOUNTING-
/AST_LIMIT=100-
/NOAUTHORIZE-
/BUFFER_LIMIT=65535-
/FILE_LIMIT=70-
/IO_BUFFERED=65535-
/PRIORITY=8-
/PRIVILEGES=(CMKRNL,LOG_IO,PHY_IO,OPER)-
/PROCESS_NAME=XXXACP-
/UIC=[1,3]-
			SYS$SYSTEM:XXXACP
