Would you like to make this site your homepage? It's fast and easy...
Yes, Please make this my home page!
================================================================================
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