MICRONOTES
================================================================================
Note 44.0                 VAX/VMS Realtime Programming                No replies
FURILO::PROPPER                                    1235 lines  13-AUG-1986 15:08
--------------------------------------------------------------------------------
      +---------------+                                    +-----------------+
      | d i g i t a l |                                    |  uNOTE # 044    |
      +---------------+                                    +-----------------+

                                                                   
      +----------------------------------------------------+-----------------+
      | Title:    VAX/VMS REALTIME PROGRAMMING             | Date: 10-Jun-86 |
      +----------------------------------------------------+-----------------+
      | Originator:   Bill Forbes                          | Page 1 of 21    |
      +----------------------------------------------------+-----------------+


      The realtime programmer who faces a VAX/VMS system for  the  first  time
      may  feel  intimidated  by  the  seeming  complexity  of  the  system in
      comparison to, say, PDP-11 systems  running  RT-11  SJ.   In  fact,  the
      program  development  environment under VAX/VMS is very rich, comprising
      many tools, utilities and system services  for  developing  and  running
      realtime application programs.

      The material contained in this application note is intended to help  you
      get  started using some basic VAX/VMS services for realtime programming.
      A more informal title might have been "How to Do  Programmed  I/O  under
      VAX/VMS  without  Writing a Device Driver." Specifically, information is
      presented on two relevant techniques:  accessing the VAX  I/O  page  for
      direct  device  control, and using the connect-to-interrupt driver to do
      interrupt programming.  An emphasis has been placed  on  explaining  the
      principles  underlying the use of these services rather than the details
      of their operation.   A  more  detailed  description  may  be  found  in
      Appendix C of the VAX/VMS Release notes for Version 4.0.  For additional
      copies of these notes or for technical assistance, call  the  Laboratory
      Data Products Hotline at (617) 467-5441.


         1  DIRECT PROGRAM CONTROL OF I/O MODULES UNDER MICROVMS

      The most basic level at which realtime devices can be controlled  is  by
      direct manipulation of the contents of device registers.  Direct control
      of device registers is relatively simple in PDP-11 systems and, in fact,
      is  a  common  programming  technique  for  certain  kinds  of  realtime
      applications.   For  example,  the  following  MACRO-11  code   fragment
      illustrates the acquisition of a single data value from an A/D device.

                 ADCSR   = 770400        ;Address of device CSR
                 ADBUF   = ADCSR+2       ;Address of device buffer

                 MTPS    #340            ;Set processor priority level to 7
                 MOV     #1,ADCSR        ;Set the GO bit to start a conversion
         LOOP:   BIT     #200,ADCSR      ;Test the A/D DONE bit
                 BEQ     LOOP            ;If clear, test again
                 MOV     ADBUF,R0        ;If set, move the data to R0
                 MTPS    #0              ;Drop priority level back to 0


                                  413

uNOTE # 044
Page 2 of 21


      In this example, the processor priority level  is  first  raised  to  7.
      This insures that the processor will execute the subsequent instructions
      without interruption.  An A/D conversion is then  initiated  by  setting
      bit  0 of the device control/status register (CSR).  When the conversion
      is complete, bit 7 of the  CSR  is  set  by  the  device.   The  program
      repeatedly  tests  this  bit until it finds it to be set, then moves the
      data value from the device's  buffer  register  into  a  general-purpose
      processor  register  (or  into a storage location in main memory).  This
      technique is called "polled I/O".

      Since many polled I/O operations require that the processor  be  totally
      dedicated  to  the polling loop, this technique has not been widely used
      on VAX/VMS systems, which typically support multiple tasks or users at a
      time.   However,  the  increasing  use of MicroVAX systems for dedicated
      realtime processing has brought about a demand for polled  I/O  routines
      implemented under MicroVMS.

      As with RT-11 based PDP-11  systems,  improper  manipulation  of  device
      registers in the MicroVAX I/O page can lead to undesirable consequences.
      Generally speaking, direct device control techniques should be  used  to
      manipulate  only  process-dedicated  realtime  option  module registers.
      Inadvertently changing other device register contents may cause a system
      crash or the corruption of a mass storage volume.  You should be sure to
      have a secure backup copy of the system whenever debugging direct device
      control routines.

      To develop polled I/O (or other direct control routines) under MicroVMS,
      two special techniques are called for:


         1.  A means of addressing device registers in the I/O page, and

         2.  A means of raising and lowering the processor priority level.

      This  section  illustrates  the  techniques  used  to  accomplish  these
      operations  in  the  context  of  programs for performing single-channel
      clocked analog input using an AXV11-C analog module and a KWV11-C  clock
      module.


         1.1  Accessing Device Registers In The VAX I/O Page

      On PDP-11  systems  running  the  RT-11  Single-Job  monitor,  accessing
      addresses  in the I/O page involves nothing more than supplying a 16-bit
      address  in  the  range  160000  -  177776  (octal).   Those   addresses
      correspond  to  the  portion  of the physical address space within which
      device registers are defined - the I/O page.  As  with  PDP-11  systems,
      the VAX architecture reserves a region of the physical address space for
      device registers.  For MicroVAXes (I and II)  the  I/O  page  starts  at
      address 20000000 (hex), and is 2000 (hex) bytes in length.



                                  414

                                                           uNOTE # 044
                                                          Page 3 of 21


      Under MicroVMS, the 32-bit virtual address space of the system is mapped
      into physical memory, so that a user-supplied address does not


      ordinarily correspond to any particular physical address.  The  problem,
      then,  is  to  establish  a  means  of  addressing a particular range of
      physical addresses - that is, the MicroVAX I/O page.

      This is accomplished by mapping the physical addresses which  constitute
      the I/O page into a portion of the process's virtual address space.  The
      VMS $CRMPSC system service is used.  The following parameters are passed
      to the $CRMPSC service:


         o  The starting and ending virtual addresses into which  the  section
            is  to  be  mapped;  this virtual address block should be 8K bytes
            long and page-aligned

         o  The starting page frame number of the section to be mapped (a page
            frame  is  a  512-byte block of physical memory); computed as /512

         o  The number of pages in the section (16 in this case)


      In addition, the $CRMPSC system service accepts a flag  mask  specifying
      the  type  of section to be created, as well as its characteristics.  In
      the present context, the essential flags are SEC$M_PFNMAP and SEC$M_WRT,
      which  define  the  section  as a page frame section with the read/write
      attribute.

      Finally, the call to the $CRMPSC system  service  must  specify  storage
      locations  into  which  the  starting  and ending virtual pages actually
      mapped and the channel number assigned by the system to the section  may
      be  written.   Ordinarily, these data are not used by the programmer and
      need not be elaborated on here.  For a fuller discussion of the  $CRMPSC
      system service, see the VAX/VMS System Services Reference Manual.

      Once the mapped section has been  created,  instructions  which  address
      locations  within  the  virtual address space into which the section has
      been mapped will effectively address the corresponding locations in  the
      I/O  page.  To address a particular device register in the I/O page, the
      absolute offset in the I/O page of the register is  added  to  the  base
      address  of  the virtual address block, yielding a virtual address which
      corresponds to the desired physical address.








                                  415

uNOTE # 044
Page 4 of 21


         1.2  Raising And Lowering Processor Priority Level

      VAX architecture defines 32 interrupt priority levels (IPL 0 - IPL  31).
      At  any  point  in  time,  the  processor will be operating at some IPL,
      usually 0 during execution of user code.  Device interrupts occur at IPL
      20  -  23,  corresponding  to  bus request levels 4 - 7.  On MicroVAXes,
      driver code executes at IPL 23, regardless of the  level  at  which  the
      device  interrupted.   For example, a device interrupt on BR 4 would not
      be granted until the processor priority dropped below 20.  However, when
      the request was granted, the processor priority would be

      set to IPL 23, thus blocking ALL  device  interrupts  until  the  driver
      lowered  the  IPL.  The system timer interrupts at IPL 22 and is granted
      at the same level.

      When the processor is executing code at a low  IPL,  device,  timer,  or
      software  interrupts at a higher IPL can pre-empt the system.  When this
      happens, the processor saves the  context  of  the  currently  executing
      image (program counter, processor status longword) and transfers control
      to the interrupt service routine (ISR).   When  the  ISR  completes  the
      context  of  the pre-empted image is restored and it continues executing
      where it left off (assuming there are no new interrupts pending).  Thus,
      an  image executing at a low IPL may be suspended for various periods of
      time due to the occurrence of interrupts.

      When performing polled I/O it may be  desirable  for  the  processor  to
      respond  as  quickly  as  possible  to the availability of data from the
      device  being  polled.   To  prevent  interrupts  from  distracting  the
      processor  from the polling operation, the polling code must be executed
      at an IPL above that of any device which might  generate  a  pre-emptive
      interrupt.   To accomplish this, the code raises the IPL to 30 using the
      DSBINT system macro.  The value of 30 is chosen to enable  a  power-fail
      interrupt (IPL 31) to be processed, should one occur.  This is advisable
      since allowing the power-fail ISR to execute may prevent  corruption  of
      the  system  device.  Besides, blocking the power-fail ISR will probably
      not salvage the application.

      VAX processor design imposes the restriction that IPL cannot  be  raised
      except  while  the  processor  is operating in kernel mode.  The $CHMKNL
      system service must be invoked to change mode to kernel before executing
      the  DSBINT  macro.   The  polling  code  then  executes in kernel mode.
      Whenever the processor is executing above IPL 2, page faults  are  fatal
      (the system will crash).  Thus, before entering kernel mode, the $LCKPAG
      system service must be called to lock any data areas that are  addressed
      in  the  polling routine into physical memory.  When the polling routine
      completes, the program should restore  the  original  IPL  (usually  0),
      return  to user mode, and, optionally, unlock the pages addressed in the
      polling routine.





                                  416

                                                           uNOTE # 044
                                                          Page 5 of 21


      In summary, the sequence of program steps in performing  polled  I/O  at
      elevated IPL is as follows:

         1.  The I/O page is mapped into process virtual address space using
             the $CRMPSC system service.

         2.  Any required device initialization is performed at IPL 0, user
             mode.

         3.  Any pages addressed in the polling routine are locked into
             physical memory using the $LCKPAG system service.

         4.  The processor mode is changed to kernel using the $CHMKNL system
             service.

         5.  In kernel mode, the IPL is raised to 30.

         6.  The polling code executes and data is moved into a process buffer
             which has been locked into memory (step 3, above).

         7.  When data transfer is complete the IPL is returned to its prior
             value (usually 0).

         8.  The processor mode is changed back to user by exiting the kernel
             mode routine.

         9.  The device is reset, if necessary.

        10.  Optionally, the pages addressed in kernel mode are unlocked using
             the $ULKPAG system service.

        11.  Post-processing of the acquired data is performed in user mode 
             at IPL 0.


         The following FORTRAN and MACRO-32 modules  illustrate  this  sequence
         for  single-channel  clocked  analog  input  using AXV11-C and KWV11-C
         modules.

         To execute:
                 $ FORTRAN POLLED_AD
                 $ MACRO FASTAD32
                 $ LINK POLLED_AD,FASTAD32
                 $ SET PROCESS/PRIV=(CMKRNL,PSWAPM,PFNMAP) !Required privileges
                 $ RUN POLLED_AD








                                  417

uNOTE # 044
Page 6 of 21





                 PROGRAM POLLED_AD

                 INCLUDE '($SYSSRVNAM)'

                 INTEGER*2 IOFF, IVAL, IBUF(60000)
                 INTEGER*4 ISTATUS, MAPIOP, NPNTS, LOCKS(2)

         o  Use the $LCKPAG system service to lock the input buffer into 
         o  physical memory.

                 LOCKS(1)=%LOC(IBUF(1))
                 LOCKS(2)=%LOC(IBUF(60000))
                 ISTATUS=SYS$LCKPAG(LOCKS,,)
                 IF(.NOT.ISTATUS) CALL EXIT(ISTATUS)

         o  Call routine MAPIOP to map the I/O page in virtual address space.

                 ISTATUS=MAPIOP()
                 IF(.NOT.ISTATUS) CALL EXIT(ISTATUS)

         o  Input/initialize data acquisition parameters

                 TYPE 9060
         9060    FORMAT('$Base clock rate, clock preset, number of samples?')
                 ACCEPT *, IRATE, KOUNT, NPNTS
                 ICHAN = 0
                 MODE  = 0

         o  Call the sampling routine. Control will return to the main program 
         o  only after I/O is complete. 

                 CALL FASTAD32(ICHAN,KOUNT,IRATE,IBUF,NPNTS,MODE,ISTATUS)

         o  Output data, return status (residual AXV CSR)

                 TYPE 9130, (J, IBUF(J),J=1,NPNTS)
         9130    FORMAT(1X,2I10)
                 TYPE 9140,ISTATUS
         9140    FORMAT(/' ISTATUS =',O10)

                 END









                                  418

                                                           uNOTE # 044
                                                          Page 7 of 21





              .TITLE FASTAD32

              .LIBRARY /SYS$LIBRARY:LIB.MLB/

              .PSECT  MAPIOP_MAIN     RD,WRT,PAGE

      ; NOTE: all subsequent MACRO-32 code in this example is contained in this
      ;       .PSECT.


      VIOP:           .BLKB   8192            ; The virtual pages to which 
      VIOP_END:                               ;  the I/O page will be mapped

      PIOPAGE:        .LONG   VIOP            ; Starting and ending virtual page
                      .LONG   VIOP_END        ;  addresses
      RETPAGE:        .BLKL   2               ; Starting and ending page addrs
                                              ;  used (should be the same)
      SECNAME:        .ASCID  /IOPAG_GLSEC/   ; Name of the section to be 
                                              ;  created
      IOPAGECHAN:     .LONG                   ; Channel # to be associated with 
                                              ;  the created section
      PFNUM:          .LONG   ^X100000        ; Page frame number of the I/O 
                                              ;  page (20000000 hex) /(200 hex)

      ;++
      ; Routine to map the I/O page into process virtual address space
      ;--

              .ENTRY  MAPIOP,^M<>

              $CRMPSC_S-
                      INADR=PIOPAGE,-
                      RETADR=RETPAGE,-
                      FLAGS=#SEC$M_PFNMAP+SEC$M_WRT,-
                      GSDNAM=SECNAME,-
                      CHAN=IOPAGECHAN,-
                      PAGCNT=#16.,-
                      VBN=PFNUM
                RET
        ;++
        ;       FASTAD32 - Performs single channel, polled I/O using AXV11-C 
        ;                  and KWV11-C modules.
        ;
        ;       The FORTRAN calling interface is:
        ;
        ;       CALL FASTAD32(ichan,kount,irate,ibuf,npnts,mode,istatus)




                                  419

uNOTE # 044
Page 8 of 21


        ;
        ;       where:
        ;               ichan   =       AXV11-C A/D channel number
        ;               kount   =       KWV11-C preset value
        ;               irate   =       clock rate:
        ;                                       1 = 1 MHz
        ;                                       2 = 100 KHz
        ;                                       3 = 10 KHz
        ;                                       4 = 1 KHz
        ;                                       5 = 100 Hz
        ;               ibuf    =       array to store data
        ;               npnts   =       number of elements in ibuf
        ;               mode    =       if 0 then start immediately; 
        ;                               if<>0 then start on ST2
        ;               istatus =       return status; if<0 then error
        ;
        ; This routine should be in the same .PSECT as the MAPIOP routine.
        ;
        ; The CLK OVFL pin on the KWV is strapped to the RTC IN pin on the AXV.
        ;
        ;--

                ADCSR   = VIOP+^O10400  ;address of AXV11-C A/D CSR
                ADBUF   = ADCSR+2       ;address of AXV11-C A/D BUFFER
                KWCSR   = VIOP+^O10420  ;address of KWV11-C CSR
                KWPRE   = KWCSR+2       ;address of KWV11-C BUFFER/PRESET

                ; Argument pointer offsets

                ICHAN   =  4
                KOUNT   =  8
                IRATE   = 12
                IBUF    = 16
                NPNTS   = 20
                MODE    = 24
                ISTATUS = 28


                .ENTRY  FASTAD32,^M

        ; Note that all instructions which address the I/O page are WORD MODE.

                TSTW    @#ADBUF         ;clear A/D DONE flag
                MOVZWL  @ICHAN(AP),R6   ;channel # to sample
                ASHL    #8,R6,R6        ;move channel # to byte 2 of R6
                BISB    #^O40,R6        ;enable clock driven

                MOVW    R6,@#ADCSR      ;load A/D CSR





                                  420

                                                           uNOTE # 044
                                                          Page 9 of 21




                MOVZWL  @IRATE(AP),R6   ;clock rate in R6
                BICL    #^O177770,R6    ;clear excess bits
                ASHL    #3,R6,R6        ;shift rate to bits 3 - 5
                MOVW    R6,@#KWCSR      ;load KW CSR

                MNEGW   @KOUNT(AP), -
                        @#KWPRE         ;load KW preset register

                MOVL    IBUF(AP),R6     ;load address of IBUF into R6
                MOVZWL  @NPNTS(AP),R7   ;load NPNTS into R7
                MOVL    #ADCSR,R8       ;R8 points to A/D CSR
                MOVL    #ADBUF,R9       ;use R9 as pointer to A/D buffer reg
                MOVZWL  @MODE(AP),R10   ;pass MODE arg to POLL in R10

                 $LCKPAG_S -             ;lock polling code into memory
                        INADR=LCKPAG, -
                        RETADR=LCKRET

                $CMKRNL_S POLL          ;execute routine POLL in kernel mode

                CLRW    @#KWCSR         ;zero KWCSR - turns clock off
                MOVZWL  (R8), -         ;return status is residual AXV CSR
                        @ISTATUS(AP)
                CLRW    (R8)            ;clear A/D CSR

                RET                     ;return to fortran

        LCKPAG: .LONG   POLL
                .LONG   POLL_END
        LCKRET: .BLKL   2

                .ENTRY  POLL,^M

                DSBINT  #30             ;disable all interrupts (except pwrfail)
                TSTW    R10             ;test mode
                BEQL    1$              ;if 0 then trigger immediately
                BISW    #20002,@#KWCSR  ;set clock to wait for ST2
                BRB     2$              ;and skip over next instruction
        1$:     BISW    #3,@#KWCSR      ;trigger immediately

        2$:     BBC     #7,(R8),2$      ;is conversion done?
                MOVW    (R9),(R6)+      ;store A/D value
                SOBGTR  R7,2$           ;decrement NPNTS; if not zero loop again

                ENBINT                  ;restore IPL to prior value

        POLL_END:
                RET

                .END

                                  421

uNOTE # 044
Page 10 of 21




        ;++
        ;  These routines are not used in the present example. They are provided
        ;  only for general reference.
        ;
        ;  FORTRAN calling interface:
        ;
        ;     CALL IPEEK32 ( OFFSET, VALUE )
        ;
        ;     CALL IPOKE32 ( OFFSET, VALUE )
        ;
        ;        where
        ;
        ;           OFFSET    = integer*2 offset in the I/O page
        ;
        ;           VALUE     = integer*2 value from/for the register selected
        ;
        ; These routines should be in the same .PSECT as the MAPIOP routine.
        ;
        ;--

                .ENTRY  IPEEK32,^M

                MOVZWL  @4(AP),R6               ;put OFFSET in R6
                MOVZWL  L^VIOP(R6),@8(AP)       ;put value from iopage in VALUE

                RET


                .ENTRY  IPOKE32,^M

                MOVZWL  @4(AP),R6               ;put OFFSET in R6
                MOVW    @8(AP),L^VIOP(R6)       ;put VALUE into iopage

                RET

















                                  422

                                                           uNOTE # 044
                                                         Page 11 of 21


         2  INTERRUPT PROGRAMMING UNDER VMS USING CONNECT-TO-INTERRUPT

      Interrupt service routines on unmapped, single-tasking systems  such  as
      PDP11s  running RT11-SJ are fairly straightforward.  An interrupt occurs
      and control is transfered directly  to  the  interrupt  service  routine
      (ISR).   This  involves  saving  the  current  program  counter (PC) and
      processor status word (PSW) and replacing  them  with  the  PC  and  PSW
      stored  at  the vector address for the interrupting device.  When I/O is
      complete, a completion flag may be set to notify  the  mainline  program
      that  the  data  have  been  stored  (or output).  The interrupt service
      routine returns control to the mainline  program  by  executing  an  RTI
      instruction which restores the saved PC and PSW registers.  The mainline
      program may test the completion flag to detect I/O completion.

      The virtual, multi-tasking, multi-user nature of VAX/VMS makes interrupt
      handling  more  complicated.   Since the process which requested the I/O
      might not be current at the time an interrupt occurred, the ISR code and
      data  must  reside  in  system virtual address space and must execute in
      system context.  If this were not the case, a context switch to that  of
      the  requesting  process  might  be needed before the interrupt could be
      serviced and this would impose too great a  penalty  in  response  time.
      Similarly,    notification   of   I/O   completion   must   be   handled
      asynchronously.  Finally, the sharing of I/O resources  among  multiple,
      possibly  non-cooperating  processes  imposes  additional  architectural
      demands.

      VMS resolves these issues through  the  implementation  of  an  elegant,
      though complex, I/O subsystem.  All VMS device drivers must interface to
      the I/O subsystem to insure the orderly flow of information between  the
      various  users'  processes  and  shareable  I/O  devices.  However, most
      realtime application programmers would prefer to avoid  writing  a  full
      VMS  device  driver  for  controlling  realtime  I/O  modules.   This is
      particularly true in light of the fact that realtime  devices  generally
      need  not  be  shareable among non-cooperating processes, whereas device
      shareability is one of the sources of complexity in the  I/O  subsystem.
      The  connect-to-interrupt  driver  enables  users  to  program interrupt
      service routines and asynchronous I/O completion routines without having
      to write a full VMS device driver.



         2.1  The Connect-to-Interrupt User Interface

      The connect-to-interrupt driver (CONINTERR or CIN)  is  a  template  VMS
      device  driver  into  which blocks of user-supplied code and data may be
      linked.  The user-supplied code and  data  are  contained  in  a  single
      .PSECT  hereafter  referred  to  as the "CIN buffer".  The CIN buffer is
      compiled and linked as part of the application  program.   The  linkages
      between  the  CIN  driver and the user-supplied CIN buffer are formed at
      run-time by the $QIO system service.   As  a  part  of  the  process  of
      building  these  links,  the  CIN  buffer  is mapped into system virtual
      address space (S0) while  preserving  the  mapping  in  process  virtual

                                  423

uNOTE # 044
Page 12 of 21


      address  space (P0).  The result is that the CIN buffer is doubly-mapped
      in S0 and P0 virtual address space.  Thus, code  and  data  in  the  CIN
      buffer are accessible in both system and process context.
        The CIN buffer comprises 5 sections:

        1. A data area containing all data structures to be addressed  during
           the execution of user-supplied CIN code.

        2. A device initialization routine which is executed during  recovery
           from a power failure.

        3. A start I/O routine which is executed at  the  time  the  $QIO  is
           issued.

        4. An interrupt service routine which is executed in  response  to  a
           device interrupt.

        5. A cancel I/O routine which  is  executed  when  the  user  process
           issues a cancel I/O request.

      In addition, the user may specify an  AST  routine  to  be  executed  in
      process  context  on  I/O  completion  (or  partial  completion).  It is
      important to note that any user-supplied code in the CIN buffer may only
      address  data  and  code  contained  within  the CIN buffer.  Code which
      executes in process context, including the user-specified  AST  routine,
      may  also address data and code in the CIN buffer and, of course, in any
      other portion of process virtual address space.   The  sections  of  the
      user-supplied CIN buffer are illustrated diagramatically below.

                   .PSECT  CIN_USER        PIC,USR,CON,REL,LCL,NOSHR,EXE,RD,WRT
                    ________________________________
                   |         Data buffers           |
                   |________________________________|
                   |         INIT routine           |
                   |    Executed after powerfail    |
                   |________________________________|
                   |        START routine           |
                   |       Executed by $QIO         |
                   |________________________________|
                   |         INT routine            |
                   |  Executed on device interrupt  |
                   |________________________________|
                   |       CANCEL routine           |
                   |     Executed by $CANCEL        |
                   |________________________________|

      This application note is intended to provide an  overview  of  CONINTERR
      concepts  for  intermediate  to  advanced  programmers  who  want to get
      started  using  this  facility.   Many  of  the  details  of   CONINTERR
      functionality  and internals have been omitted, though what is presented
      is sufficient for many applications.  For a more detailed description of
      CONINTERR, see the VAX/VMS Release Notes, Appendix C.

                                  424

                                                           uNOTE # 044
                                                         Page 13 of 21


           2.2  Example Code Internals

      The example program performs continuous interrupt-driven analog input to
      a  process  buffer  using  an  AXV11-C analog module and a KWV11-C clock
      module for timebase generation.  Data are sampled into a 4096-word  ring
      buffer  with  2  sub-buffers.   The  ring buffer is contained in the CIN
      buffer and is therefore doubly mapped in S0 and P0.  When  a  sub-buffer
      is  filled, an AST is delivered to the calling process.  The AST routine
      moves the data from the sub-buffer to a process buffer (singly mapped in
      P0  virtual  address space).  It then checks to determine whether I/O is
      complete; that is, whether the process buffer has received all the  data
      requested.   If  it has, the AST issues a $CANCEL system service call to
      terminate I/O and sets a completion flag to notify the  calling  program
      of I/O completion.



                           SYSTEM SETUP FOR CONNECT-TO-INTERRUPT


           1. Log in to SYSTEM account.

           2. Insert the following line in the file SYS$SYSTEM:MODPARAMS.DAT

              REALTIME_SPTS = 20

           3. Enter:

              $ @SYS$UPDATE:AUTOGEN SAVPARAMS REBOOT
                   .
                   .
              (system reboots)

           4. Log in to SYSTEM account or other privileged account and enter:

              $ MCR SYSGEN
              SYSGEN> CONNECT 
                      AXA0:/ADA=0/CSR=%O770400/VEC=%O400/DRIVER=CONINTERR
              SYSGEN> EXIT

              $ MACRO AXV
              $ FORTRAN CALL_AXV
              $ LINK CALL_AXV,AXV
              $ SET PROCESS/PRIV=(PSWAPM,CMKRNL)   !Required privileges
              $ SET PROCESS/PRIORITY=17
              $ RUN CALL_AXV


      .page
      System page table entries for double-mapping of  CONINTERR  buffers  are
      drawn  from  a  pre-allocated  pool.  In steps 1 - 3, above, the size of
      this pool is set by modifying the SYSGEN parameter  REALTIME_SPTS.   The

                                  425

uNOTE # 044
Page 14 of 21


      number  of  page  table  entries allocated must be sufficient to map the
      user buffers (data and code) of all CONINTERR-driven devices  which  are
      connected  at  any  given  time.   In the present example, 20 page table
      entries are allocated, sufficient to map 5120 words of  data  and  code.
      This  step  needs  to  be  taken  only when additional REALTIME_SPTS are
      required for mapping CONINTERR buffers.

      In step 4, above, the device is connected to the system.  In this  case,
      the  device  was  given  the  name  "AXA0"; this is the name used in the
      $ASSIGN system service call.  Switches are used to designate the adapter
      number  (always  0  on  MicroVAXes), the CSR and vector addresses of the
      module, and the driver name (CONINTERR).  This step needs  to  be  taken
      each  time  the system is booted.  The commands may be inserted into the
      file SYS$MANAGER:SYCONFIG.COM, if desired.


                 PROGRAM CALL_AXV        !Calling program for AXV_SAMPLE

                 INCLUDE '($SYSSRVNAM)'

                 BYTE ICMPF
                 INTEGER*2 PBUFF(30000)
                 INTEGER*4 ISTATUS, ICHAN, IPRESET, ICOUNT
                 INTEGER*4 LOCKS(2)
                 INTEGER*4 AXV_SAMPLE

                 COMMON /PROCESS_DATA/ PBUFF, ICMPF

         o  Lock the process buffer into the working set. This is not essential,
         o  but it helps to avoid page faulting in the AST routine.

                 LOCKS(1) = %LOC(PBUFF(1))
                 LOCKS(2) = %LOC(PBUFF(30000))
                 ISTATUS = SYS$LCKPAG (LOCKS,,)
                 IF(.NOT.ISTATUS) CALL EXIT (ISTATUS)

         o  Assign a channel number for the AXV

                 ISTATUS = SYS$ASSIGN ('_AXA0:' ,ICHAN,,)
                 IF(.NOT.ISTATUS) CALL EXIT (ISTATUS)

         o  Input/initialize arguments to be passed to the calling routine

                 TYPE *, 'KWV preset, sample count?'
                 ACCEPT *, IPRESET, ICOUNT








                                  426

                                                           uNOTE # 044
                                                         Page 15 of 21




         o  Routine AXV_SAMPLE handles all of the details of setting up and
         o  executing the I/O operations. When the process buffer is filled,
         o  the completion flag is set.

                 ICMPF = 0       ! Clear the completion flag
                 ISTATUS = AXV_SAMPLE (ICHAN, IPRESET, ICOUNT)
                 IF(.NOT.ISTATUS) CALL EXIT (ISTATUS)

         o  By looping on the completion flag, the process is insured of 
         o  remaining computable throughout the I/O operation.

           100     IF(ICMPF.EQ.0) GO TO 100

         o  For the sake of simplicity in this example, only a few of the 
         o  acquired data values are output. In a real application, the 
         o  following step would be replaced with file/graphic output.

                   TYPE 9000, (J,PBUFF(J),J=1,1000,100)
           9000    FORMAT(2I10)

                   END


                 .TITLE  AXV

                 .LIBRARY /SYS$LIBRARY:LIB.MLB/

                 $IDBDEF                 ; Definition for I/O drivers
                 $UCBDEF                 ; Data structurs
                 $IODEF                  ; I/O function codes
                 $CINDEF                 ; Connect-to-interrupt 
                 $CRBDEF                 ; CRB stuff
                 $VECDEF                 ; more


                 .PSECT PROCESS_ROUTINES   PIC,USR,CON,REL,LCL,NOSHR,EXE,RD,WRT

         ;++
         ; This example routine issues the QIO to connect to the AXV 
         ; interrupts.  It takes care of the internals associated with the 
         ; connect-to-interrupt QIO. 
         ;
         ; FORTRAN calling sequence:
         ;
         ;   STATUS = AXV_SAMPLE ( CHAN, PRESET, COUNT )
         ;
         ;   where
         ;
         ;      CHAN     = longword channel # for device _AXA0
         ;

                                  427

uNOTE # 044
Page 16 of 21


         ;      PRESET   = longword preset value for KWV
         ;                 (positive value <= 32K)
         ;
         ;      COUNT    = longword # of samples
         ;
         ;      STATUS   = longword return status of $QIO call
         ;
         ; In this example, the AXV and KWV CSRs are "firm-wired" for the 
         ; following characteristics:
         ;
         ;       AXV - gain=1, RTC ENABLE, DONE INT ENABLE, channel=0
         ;
         ;       KWV - GO, mode=1, rate=1 (1 MHz)
         ;
         ; Ordinarily, they would be configured according to arguments passed
         ; to this routine from the calling program.
         ;
         ; The CLK OVFL pin on the KWV is strapped to the RTC IN pin on the AXV.
         ;
         ;--

                 .ENTRY  AXV_SAMPLE,^M<>

         ; These values are stored in process address space for use in the AST
         ; routine

                 MOVL    @4(AP),AXV_CHAN         ; Channel # for $CANCEL
                 MOVL    @12(AP),POINT_COUNT     ; Point count


         ; These values are stored in system address space for use in the CIN 
         ; user start routine.

                 MOVW    #^O13,KWV_CSR_VALUE     ; GO, MODE=1, 1MHz
                 MOVW    #^O140,AXV_CSR_VALUE    ; RTC ENABLE, DONE INT ENABLE
                 MNEGW   @8(AP),KWV_PRESET_VALUE ; Negated KWV preset value

                 $QIO_S  CHAN=@4(AP),-           ;Channel
                         FUNC=#IO$_CONINTWRITE,- ;Allow writes to data buffer
                         IOSB=AXV_CIN_IOSB,-     ;I/O status Block
                         P1=AXV_CIN_BUF_DESC,-   ;Buffer descriptor
                         P2=#AXV_CIN_ENTRY,-     ;Entry list
                         P3=#AXV_CIN_MASK,-      ;Status bits,etc
                         P4=#USER_AST,-          ;AST service routine
                         P6=#10                  ;preallocate some AST control
                                                 ; blocks
                 RET                             ;Return to calling routine

         AXV_CIN_BUF_DESC:                       ;Buffer descriptor for CIN
                 .LONG   USER_END - RINGBUF
                 .LONG   RINGBUF


                                  428

                                                           uNOTE # 044
                                                         Page 17 of 21


         AXV_CIN_ENTRY:
                 .LONG   USER_INIT - RINGBUF     ;Init code
                 .LONG   USER_START - RINGBUF    ;Start code
                 .LONG   USER_INT - RINGBUF      ;Interrupt service routine
                 .LONG   USER_CNCL - RINGBUF     ;I/O cancel routine

         AXV_CIN_IOSB:
                 .LONG   0                       ; I/O Status Block
                 .LONG   0

         ; Control mask - see VMS Release Notes, Appendix C for explication

         AXV_CIN_MASK = CIN$M_REPEAT!CIN$M_START!CIN$M_ISR!CIN$M_CANCEL



                 .SBTTL  USER_AST, User AST routine

         ;++
         ; This routine is invoked when I/O complete is signaled by the USER_INT
         ; routine. It is queued to the user's process in the access mode of the
         ; $QIO which initiated the I/O. It executes in process context at
         ; IPL$_ASTDEL (IPL 2).
         ;
         ; I/O completion is signalled by loading SS$_NORMAL (1) into R0 on 
         ; exiting the USER_INT routine. In the present example, this does not 
         ; signal full completion of the I/O, since the CIN$M_REPEAT flag was 
         ; set in the $QIO call.
         ;
         ;--

                 .ENTRY  USER_AST,^M



                 MOVAL   RINGBUF,R2              ; Get CIN buffer address
                 MOVZWL  RINGBUF_INDEX,R3        ; Get buffer index
                 BBS     #11,R3,10$              ; Is bit 11 set?
                 ADDL    #4096,R2                ; No, xfer the top half
         10$:    MOVAL   PBUFF,R4                ; Get process buffer address
                 ADDL    PBUFF_INDEX,R4          ; Add Index
                 MOVC3   #4096,(R2),(R4)         ; Move data (trashes R0-R5)
                 ADDL    #4096,PBUFF_INDEX       ; Update process buffer "index"
                 SUBL2   #2048,POINT_COUNT       ; Update point count
                 BGTR    20$                     ; Have we moved all the data?
                 JMP     ALL_DONE                ; Yes - Finish up
         20$:    MOVZWL  #SS$_NORMAL,R0          ; Set return status
                 RET                             ; And return

         ALL_DONE:
                 $CANCEL_S -                     ; Cancel the I/O request
                         CHAN=AXV_CHAN
                 CLRL    PBUFF_INDEX             ; Reset index into PBUFF

                                  429

uNOTE # 044
Page 18 of 21


                 MOVB    #1,ICMPF                ; Set the completion flag
                 MOVZWL  #SS$_NORMAL,R0          ; Set return status
                 RET                             ; And return

         PBUFF_INDEX:
                 .LONG   0

         POINT_COUNT:
                 .LONG   0

         AXV_CHAN:
                 .LONG   0


                 .PSECT PROCESS_DATA     PIC,OVR,REL,GBL,SHR,NOEXE,RD,WRT,LONG

         ;++
         ; This is the FORTRAN common block /PROCESS_DATA/ 
         ;--

         PBUFF:  .BLKW   30000

         ICMPF:  .BYTE   0

                 .PSECT CIN_USER         PIC,USR,CON,REL,LCL,NOSHR,EXE,RD,WRT

         ;++
         ; This is the CIN buffer. In the present example, it is contained in
         ; the same source code module as the process routines shown above.
         ;--

                 .SBTTL  DATA STRUCTURES

         DATA_BUF_SIZE  =        4096

         RINGBUF:                .BLKW   DATA_BUF_SIZE
         RINGBUF_END:
         RINGBUF_INDEX:          .WORD

         AXV_CSR_VALUE:          .WORD
         KWV_CSR_VALUE:          .WORD
         KWV_PRESET_VALUE:       .WORD

         ; In the CIN user routines, the system virtual address of the CSR of 
         ; the device is supplied. Other addresses in the I/O page used by the
         ; code must be handled as offsets from the device CSR.

         AXV_ADDRESS     = ^O170400               ;Address of AXV
         KWV_ADDRESS     = ^O170420               ;Address of KWV
         KWV_OFFSET      = KWV_ADDRESS-AXV_ADDRESS ;Offset for KWV CSR
         PRESET_OFFSET   = KWV_OFFSET+2           ;Offset for KWV BPR
         DBR_OFFSET      =2                       ;Offset for AXV DBR 

                                  430

                                                           uNOTE # 044
                                                         Page 19 of 21



                 .SBTTL  USER_INIT       ; Dummy dev intialization routine
         ;++
         ; This routine is invoked after power recovery. It executes in system
         ; context at IPL$_POWER (IPL 31). 
         ;
         ; This routine is not implemented in the present example. However, the
         ; entry point must be defined and included in the entry list of the
         ; $QIO (P2).
         ;
         ; See VAX/VMS Release Notes, Appendix C for inputs.
         ;
         ;--

         USER_INIT::
                 RSB


                 .SBTTL  USER_START, Start I/O routine

         ;++
         ; This routine is invoked by the $QIO system service. It executes in
         ; system context at IPL$_QUEUEAST (IPL 6). 
         ;
         ;   On entry:


         ;
         ;       0(R2)  - arg count of 4
         ;       4(R2)  - Address of the process buffer (system mapped)
         ;       12(R2) - Address of the device's CSR
         ;
         ; See VAX/VMS Release Notes, Appendix C for other inputs.
         ;
         ; The routine must preserve all registers except R0-R4.
         ;
         ;--

         CSR_ADD = 12               ; Argument list offset of CSR addr
                                    ; (only valid in USER_START and USER_INT)

         USER_START::
                 CLRW    RINGBUF_INDEX           ; Clear ring buffer index
                 MOVL    CSR_ADD(R2),R0          ; Get address of the AXV CSR 
                 TSTW    DBR_OFFSET(R0)          ; Clear AD DONE bit, if set,
                                                   ;   by reading AXV DBR
                 MOVW    AXV_CSR_VALUE,(R0)      ; Set up the AXV
                 MOVW    KWV_PRESET_VALUE, -     ; Set KWV preset
                         PRESET_OFFSET(R0)
                 MOVW    KWV_CSR_VALUE, -        ; Set KWV CSR
                         KWV_OFFSET(R0)
                 MOVZWL  #SS$_NORMAL,R0          ; Load a success code into R0.

                                  431

uNOTE # 044
Page 20 of 21


                 RSB                             ; Return


                 .SBTTL  USER_INTERRUPT, Interrupt service routine

         ;++
         ; This routine is invoked on device interrupt. It executes in system
         ; context at IPL$_DIPL (IPL 23 in MicroVMS).
         ;
         ; On entry:
         ;
         ;       0(R2)  - arg count of 5
         ;       4(R2)  - Address of the process buffer
         ;       8(R2)  - Address of the AST parameter
         ;       12(R2) - Address of the device's CSR
         ;
         ; See VAX/VMS Release Notes, Appendix C for other inputs.
         ;
         ; The routine must preserve all registers except R0-R4
         ;
         ;--

         USER_INT::
                 MOVL    CSR_ADD(R2),R0          ; Get AXV CSR address
                 MOVAL   RINGBUF,R1              ; Get CIN buffer address
                 MOVZWL  RINGBUF_INDEX,R3        ; Get buffer index
                 MOVW    DBR_OFFSET(R0),(R1)[R3] ; Read data (assume no error)
                 INCW    R3                      ; Increment buffer index
                 BICW    #^O170000,R3            ; Clear all but bottom 12 bits 
                                                 ; of (ring) buffer index


                 MOVW    R3,RINGBUF_INDEX        ; Put updated index in storage
                 CLRL    R0                      ; Clear return status
                 BICW    #^O174000,R3            ; Now test bottom 11 bits of
                                                 ;   buffer index
                 BNEQ    10$                     ; Skip AST if not zero

         ; The user AST will be queued if the LSB of R0 is set on return

         5$:     MOVZWL  #SS$_NORMAL,R0          ; Queue the AST
         10$:    RSB


                 .SBTTL  USER_CANCEL, Cancel I/O routine

         ;++
         ; This routine is invoked by the $CANCEL system service. It executes in
         ; system context at IPL$_QUEUEAST (IPL 6). 
         ;
         ; On entry:
         ;

                                  432

                                                           uNOTE # 044
                                                         Page 21 of 21


         ;   JSB interface:
         ;
         ;       R3      -  Addr of current IRP
         ;       R4      -  Addr of PCB of cancelling process
         ;       R5      -  Addr of the UCB
         ;
         ;   CALL interface:
         ;
         ;       0(AP)   Arg count 4
         ;       8(AP)   Addr of IRP
         ;       12(AP)  Addr of PCB
         ;       16(AP)  Addr of UCB
         ;
         ; See VAX/VMS Release Notes, Appendix C for other inputs.
         ;
         ; The routine must preserve all registers except R0 and R3.
         ;
         ;--

         USER_CNCL::
                 MOVL    UCB$L_CRB(R5),R0         ; Get Address of the CRB
                 MOVL    CRB$L_INTD+VEC$L_IDB(R0),R0 ; Address of the IDB
                 MOVL    IDB$L_CSR(R0),R0         ; Get addr of AXV
                 CLRW    KWV_OFFSET(R0)           ; Stop clock
                 TSTW    DBR_OFFSET(R0)           ; Clear AD DONE bit
                 CLRW    (R0)                     ; Clear AXV CSR
                 MOVZWL  #SS$_NORMAL,R0           ; Load return status
                 RSB                              ; And return

         ; Label that marks the end of the module

         USER_END:                               ; Last location in module
                 .LONG

                 .END


















                                  433