Writing An EXCP Program

Now we are ready to take on the rest of the code necessary to execute our own channel programs using EXCP.  An I/O request is represented by an Input Output Block (IOB).  Here is the layout of the IOB.

IOB      DS    0F
IOBFLAGS DS    XL2                 IOB FLAGS                                     
*                                                          
IOBSENSE DS    XL2                 SENSE BYTES 0 AND 1
IOBECBAD DS    A                   ADDRESS OF ECB          
IOBCSW   DS    A                   CHANNEL STATUS WORD     
IOBCSWFG DS    XL2                 CSW FLAGS               
IOBRESDL DS    H                   RESIDUAL COUNT          
IOBCCWAD DS    A                   ADDRESS OF CCW CHAIN    
IOBDCBAD DS    A                   ADDRESS OF DCB          
         DS    2A                                          
IOBSEEK  DS    XL3                 SEEK ADDRESS   MBBCCHHR 
IOBSRCH  DS    XL5                 SEARCH ADDRESS    CCHHR

The IOB is mapped by the system macro IEZIOB but it is much less confusing to define the IOB ourselves sicne we are only concerned with the fields related to EXCP processing.

IOBFLAGS       Two bytes of flag bits that provide information about our I/O request to the EXCP code.  Some of the flags in the first byte we are interested in are:

IOBDATCH EQU   X'80'    - Data Chaining Used In CCW Program
IOBCMDCH EQU   X'40'    - Command Chaining Used In CCW Program
IOBUNREL EQU   X'02'    - Unrelated (Non-Sequential) Request
IOBSPSVC EQU   X'01'    - Do Not Use BSAM, BPAM, QSAM I/O Appendages

IOBSENSE      Two byte area where EXCP returns the first two sense bytes from the device if execution of the channel program does not complete successfully and Unit Check is returned in the CSW.
IOBECBAD    Address of an ECB that is posted when the EXCP request completes. The high order byte will contain the completion for the request.

7F - Normal I/O Completion
42 - Extent Error
41 - I/O Completed With Error/Exceptional Status

There are other completion codes that may be returned but these what we normally expect to encounter.
IOBCSW        CCW address from the CSW.
IOBCSWFG  Device Status and Channel Status bytes from the CSW.
IOBRESDL   Residual Count from the CSW.
IOBCCWAD Is the address of the first CCW in the channel program.
IOBDCBAD  Address of the DCB.
IOBSEEK      DASD Seek address (MBBCCHHR)

Here are the highlights of a program that reads a dsad device using excp.  I don’t include all of the program here, just the parts directly related to performing an EXCP.  Please refer to program EXCP01 in the downloads.

         OPEN  (EXCPDCB,INPUT)  
         TM    EXCPDCB+48,X'10' 
         BO    OPEN020

We begin by opening our EXCP DCB. Here we check the DCB open flags to verify the DCB open request was successful.

EXCPDCB  DCB   DSORG=PS,MACRF=E,DDNAME=EXCP

Here is the DCB definition. Note the MACRF type of ‘E’ for EXCP.

IOB      DS    0F                      
IOBFLAGS DC    XL2'4300'               
IOBSENSE DC    XL2'0000'               
IOBECBAD DC    A(ECB)                  
IOBCSW   DC    A(0)                    
IOBCSWFL DC    XL2'0000'               
IOBRESDL DC    H'00'                   
IOBCCWAD DC    A(CCWSRCH)              
IOBDCBAD DC    A(EXCPDCB)              
         DC    2A(0)                   
IOBSEEK  DC    XL3'000000'       MBB   
IOBSRCH  DC    XL5'0000000000'   CCHHR

Here is our IOB. Note the IOBFLAGS value. The IOB points to an ECB, our opened DCB, and our CCW chain.

ECB      DC    F'0'                                 
*                                                   
*                                                   
         DS    0D                                   
CCWSRCH  DC    X'31',AL3(IOBSRCH),X'40',X'00',AL2(5)
CCWTIC   DC    X'08',AL3(CCWSRCH),X'40',X'00',AL2(0)
CCWREAD  DC    X'06',AL3(IOBUF),X'00',X'00',AL2(80)

Here is our ECB and our CCW chain. The first CCW is a SEARCH for ID Equal. It is follow by a TIC that points back to the Search. Finally we have a READ DATA CCW that attempts to read 80 bytes.

Now we have to deal with getting the actual disk address (MBBCCHHR) of the record we want to read. We will refer to our dataset using realative track address that are converted to physical address. A relative address simply refers to a dasd dataset as a collection of tracks. It doesn’t deal with cylinders or extents. The first relative track is zero, followed by track one, etc. MVS does provide a routine to convert from relative address (TTR – Track/Record) to physical address (MBBCCHHR). We are familiar with the CCHHR part of the address. The BB is always two bytes of zeros. The M is the extent number (beginning with zero) of the dataset extent containing the track.

*                                                                      
***********************************************************************
*    CONVERT RELATIVE TRAK TO CCHH                                     
*       R1 = RELATIVE TRACK (TT)                                       
*       RESULT STORED IN MBBCCHHR                                      
*       (MODIFIED R1,R2,R3,R4,R5,R15)                                  
***********************************************************************
*                                                                      
GETCCHH  DS    0H                                                      
         ST    R14,XTGET

Here is the begenning of our converstion routine. On entry R1 contains the relative track number. The physical address will be calculated and stored in a field called MBBCCHHR. This routine ignores the record number since it does not require any conversion.

         LA    R2,EXCPDCB              POINT TO DCB     
         L     R2,44(,R2)              POINT TO DEB     
         SLR   R3,R3                                    
         IC    R3,16(,R2)              NUMBER OF EXTENTS
         LA    R5,32(,R2)              FIRST EXTENT INFO
         SLR   R4,R4                                    
         SLR   R2,R2

First we get the DEB address from the DCB. Next we get the number of dataset extents from the DEB. I am using offsets into the control blocks but you can use the mapping macros (IEZDEB). The first extent begins at offset 32 in the DEB. At this point R3 contains the number of extents and R5 has the address of the first extent data.

GET010   DS    0H                                                
         ICM   R4,B'0011',14(R5)       NUMBER OF TRACKS IN EXTENT
         CR    R1,R4                   DOES IT FIT IN THIS EXTENT
         BL    GET020                     YES - BRANCH

Here is the main loop of our conversion routine. Here we get the number of tracks in this extent. We compare it to the relative track nubmer to see if the relative track is in this extent. If so we can exit the loop.

         SR    R1,R4                   ADJUST RELATIVE TRACK  
         LA    R5,16(,R5)              POINT TO NEXT EXTENT   
         LA    R2,1(,R2)               BUMP EXTENT COUNT      
         BCT   R3,GET010               LOOP BACK              
*                                                             
         B     GETRC12                 TRACK NOT VALID

If the relative track number is larger than the tracks in the current extent we subtract the number of tracks in the current extent from the relative track number. We then increment our pointer to the next extent entry in the DEB. We also keep track of the extent number in R2. We use a BCT instruction to loop back as long as we have more extents to process. If we exhaust all extents then the relative track is not contained within any dataset extent and we return with a RC of 12 in R15.

GET020   DS    0H                                          
         XC    MBBCCHHR(0),MBBCCHHR    CLEAR RESULT AREA   
         STC   R2,MBBCCHHR             SAVE "M" (EXTENT #) 
         LH    R2,6(,R5)               START CC            
         AH    R1,8(,R5)               ADD IN START HH     
         SLR   R0,R0                                       
         L     R15,=F'30'             3350 - 30 TRKS/CYL   
         DR    R0,R15                                      
         AR    R2,R1                   UPDATE CC           
         STCM  R2,B'0011',MBBCCHHR+3   SAVE CC
         STCM  R0,B'0011',MBBCCHHR+5   SAVE HH

Once the proper extent is located we complete the conversion process. We begin by clearing the result area and storing the extent number in the “M” byte. We get the beginning cylinder of the extent in R2. We then add the beginning head (track) of the extent to our remaining relative track value. We divide this total track value by the number of tracks in a cylinder. For a 3350 this number is 30. This code must be adjusted for other device types. We add the beginning cylinder of the extent to the result to get our “CC” value. The remainder is our “TT” value. These values are saved into the result area.

         SLR   R15,R15                 SET RC 
         L     R14,XTGET                      
         BR    R14                            
*                                             
*                                             
GETRC12  DS    0H                             
         LA    R15,12                  SET RC 
         L     R14,XTGET                      
         BR    R14

And we finish up with our exit code.

         LA    R6,1          RECORD NUMBER, INIT TO ONE 
         LA    R1,0          RELATIVE TRACK NUMBER      
         BAL   R14,GETCCHH   CONVERT TO MBBCCHHR        
         LTR   R15,R15            CHECK RETURN CODE     
         BZ    READ010            - BRANCH IF GOOD      
*                                                       
         WTO   'TTR CONVERSION FAILED',ROUTCDE=(1,11)   
         B     EXIT

Now we are read to read some records using EXCP. We will keep our current record number in R6. We start with 1 since that is the first data record (remember R0 is not used for data). We set our relative track number (zero) in R1 and call our conversion routine to get the physical address. We do check the return code to verify the result is valid.

READ010  DS    0H                                           
         MVC   IOBSEEK(8),MBBCCHHR                          
         STC   R6,IOBSEEK+7  PUT IN RECORD NUMBER           
*                                                           
         XC    ECB,ECB            CLEAR ECB                 
         EXCP  IOB                ISSUE I/O REQUEST         
*                                                           
         WAIT  1,ECB=ECB          WAIT FOR I/O TO COMPLETE  
*                                                           
         BAL   R14,PRINTIOB       GO FORMAT IOB             
*                                                           
         CLI   IOBECBAD,X'7F'     NORMAL COMPLETION         
         BNE   EXIT                                         
*                                                           
         LA    R6,1(,R6)          NEXT RECORD NUMBER        
         B     READ010

Here is the main read loop. First we copy the physical track address into our IOB. We then store in our current record number. Next we clear our ECB, issue the EXCP, and wait for the I/O to complete. I have written a routine called PRINTIOB that will format various fields from the IOB and print them (see the EXCP01 program for the code). We then check the completion code. If there were no exceptional conditions we increment the record number and issue the EXCP again. We continue until an exceptional condition results.

//GENR   EXEC  PGM=IEBGENER                 
//SYSPRINT DD  SYSOUT=*                     
//SYSIN    DD  DUMMY                        
//SYSUT2   DD  DSN=TCS3.EXCP01.DATA,        
//         DISP=(NEW,CATLG),                
//         UNIT=SYSDA,VOL=SER=WORK01,       
//         SPACE=(CYL,(2,2)),               
//         DCB=(BLKSIZE=80,LRECL=80,RECFM=F)
//SYSUT1   DD  *                            
0001                                        
0002                                        
0003

I created a test dataset to read using IEBGENER. It is a simple non-blocked 80/80 dataset containing three records. Here is the output from our EXCP01 program when run against this test data.

I/O REQUEST                                                          
   COMPLETION CODE = 7F                                              
   CSW = 095E70 DEV STAT = 0C CHAN STAT = 00 RESIDUAL = 0000 (      )
   --- DEVICE STATUS  = CE DE                                        
   --- CHANNEL STATUS =                                              
   SENSE = 0000                                                      
   SEEK = 00000001AE000001                                           

I/O REQUEST                                                          
   COMPLETION CODE = 7F                                              
   CSW = 095E70 DEV STAT = 0C CHAN STAT = 00 RESIDUAL = 0000 (      )
   --- DEVICE STATUS  = CE DE                                        
   --- CHANNEL STATUS =                                              
   SENSE = 0000                                                      
   SEEK = 00000001AE000002                                           

I/O REQUEST                                                          
   COMPLETION CODE = 7F                                              
   CSW = 095E70 DEV STAT = 0C CHAN STAT = 00 RESIDUAL = 0000 (      )
   --- DEVICE STATUS  = CE DE                                        
   --- CHANNEL STATUS =                                              
   SENSE = 0000                                                      
   SEEK = 00000001AE000003                                           

I/O REQUEST                                                          
   COMPLETION CODE = 41                                              
   CSW = 095E60 DEV STAT = 0E CHAN STAT = 40 RESIDUAL = 0005 (     5)
   --- DEVICE STATUS  = CE DE UC                                     
   --- CHANNEL STATUS = IL                                           
   SENSE = 0008                                                      
   SEEK = 00000001AE000004

Here we see four I/O requests.  The first three have a completion code of X’7F’ indicating no exceptional condition.  Next the CSW is formatted.  The CSW points past the end of the last CCW executed (we have to back up 8 bytes to get the CCW address).  The Device Status and Channel Status are displayed as hex as well as being formatted on the next two lines.  The residual length is zero indicating we have read 80 bytes of data as expected.  Finally we format the SEEK address and we can see the record number increasing for each request.  Since we wrote three records using IEBGENER we expect to read three records.  When we attempt to read record number four we get a completion code of X’41’ indicating an exceptional condition has occured.  We also notice that in addition to Channel End and Device End the Unit Status bit is set.  We also have Incorrect Length set in the Channel Status, a residual length of 5, and Sense bytes containing X’0008′.  The CCW address points to the TIC CCW so the failing CCW is 8 bytes before it – the Search ID Equal CCW.  This is why the residual length is 5.  The Sense data tells us why we received an Unit Exception.

Sense Byte 0

1... .... - Command Reject - Invalid command or command sequence 
.1.. .... - Intervention Required - Device not ready 
..1. .... - Bus Out Parity Check - Parity check occured transfering data 
...1 .... - Equipment Check - Unusual hardware error 
.... 1... - Data Check - Error reading from device 
.... .1.. - Overrun - Data received later than expected 
.... ..00 - Not used, zero

Sense Byte 1 

1... .... - Permanent Error - Unrecoverbale error has occured 
.1.. .... - Invalid Track Format - Write exceedes track capacity 
..1. .... - End of Cylinder - Multitrack opeartion has encountered cylinder end 
...0 .... - Not used, zero 
.... 1... - No Record Found - Two index points encoutered without interveaning read 
.... .1.. - File Protected - File Mask violated 
.... ..1. - Write Inhibited 
.... ...1 - Operation Incomplete

Since we have a Sense value of 0008 we see that no record was found.  This is exactly what we should expect since we wrote three records to the data set.  That is unless you were expecting the EOF mark to be the fourth record.  The EOF is in our dataset, but it is the first record on the next track.