================================================================================ 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