Dispatcher Overview

It is now time to start deveoloping a dispatcher to control the excution of Units Of Work or executable tasks.  Each dispatchable unit of work will be defined by a control block we will call TCB.  It will contain status information about the unit of work and will be used to manage interrupts.  When an interrupt occurs the dispatcher will save the current environment (registers and PSW) into the active TCB.  A new unit of work can be dispatched by restoring the environment frrom a TCB.

A second control block will be used along side the TCB to manage unit of work execution.  It will function as a stack to manage levels of interirupts for a TCB.  This block will be the RB.  It will contain the PSW and Registers for a level of interruption under a TCB.

The RB is pretty simple containing the following data:

RBNEXT   DS    A     - Next RB for this TCB
RBWTCNT  DS    X     - Wait Count
RBPSW    DS    D     - PSW Contents
RBREGS   DS    16F   - Register Contents

The TCB is a little more complex and will contain the following data:

TCBNEXT  DS    A     - Next TCB in the dispatcher chain
*
TCBFLGS  DS    X     - Flags
TCBFWAIT EQU   X'80' - TCB waiting on ECB
TCBFNDSP EQU   X'40' - Do not dispatch
TCBFETS  EQU   X'20' - Exceeded Time Slice
TCBFLONG EQU   X'01' - Long Running Task
*
TCBFLGS2 DS    X     - Flags 2
*
TCBPRI   DS    X     - Execution Priority
TCBKEY   DS    X     - Execution Key
TCBECB   DS    A     - ECB to be Posted on Termination
TCBERC   DS    F     - Error/Return Code
TCBPSW   DS    D     - PSW
TCBREGS  DS    16F   - Registers

The TCB blocks will be maintained in a chain with each TCB pointing to the next ont he chain.  The end of the chain will be indicated with a null pointer.  The TCB on the chain is the Wait TCB.  It does not represent any actual work but becomes the active TCB when the CPU is in a wait state waiting on work to be performed.  The Wait TCB provides a place for the system status to be stored when an interrupt occurs and the CPU is waiting.

The flags indicate the dispatchability of the TCB.  If the TCB is waiting on an ECB the wait count field will be non-zero indicating the nubmer of events it is waiting on.  The Exceded Time Slice flag indicates the TCB did not give up control of the CPU unitl it was forced as a result of the timer expiring.  This flag indicates this TCB should not be dispatched again until no other work is waiting to be dispatched.

[Next – Nucleus Low Core]

 

TXXNUC00 – Beginning Of An OS

Our first version of a nucleus will not do much – but it will do something!  It will set a value in the Interval Timer.  The CPU will subtract from this value several times every second.  When the value goes to zero an External Interrupt will be generated.  This will cause the CPU to save the current PSW into the External Old PSW area in low core and load a new PSW from the External New PSW location.  In our interrupt routine we will reset the Interval Timer and load a PSW with the wait bit set – but enabled for interrupts.

TXXNUC   CSECT ,
         DC    X'00',X'00',AL2(0),AL4(DISPINIT)  INITIAL PSW
         DC    D'0'                              RST NEW PSW
*
         DC    A(CVT)                            COMM VECT TABLE
         DC    A(MVT)                            MOD  VECT TABLE
*
         DC    D'0'          EXT OLD PSW
         DC    D'0'          SVC OLD PSW
         DC    D'0'          PGM OLD PSW
         DC    D'0'          MCK OLD PSW
         DC    D'0'          I/O OLD PSW
*
         DC    D'0'          CHANNEL STATUS WORD
         DC    F'0'          CHANNEL ADDRESS WORD
         DC    F'0'
         DC    F'0'          INTERVAL TIMER
         DC    F'0'
*
         DC    X'00',X'00',AL2(0),AL4(EXTHNDLR)  EXT NEW PSW
         DC    X'00',X'00',AL2(0),AL4(SVCHNDLR)  SVC NEW PSW
         DC    X'00',X'00',AL2(0),AL4(PGMHNDLR)  PGM NEW PSW
         DC    X'00',X'00',AL2(0),AL4(@E@MCK)    MCK NEW PSW
         DC    X'00',X'00',AL2(0),AL4(IOHNDLR)   I/O NEW PSW
*
*
LOWCLEN  EQU   *-TXXNUC
LOWCFILL EQU   1024-LOWCLEN
         DC    (LOWCFILL)X'00'

We begin by filling in some low core values needed by the CPU.  We specify the PSW’s to be loaded when the corresponding interrupt occurs.  Finally we pad low core up to 1k (1024) with binary zeros.

EXTREGS  DC    16F'0'             EXT INTERRUPT REG SAVE
IOREGS   DC    16F'0'             I/O INTERRUPT REG SAVE
SVCREGS  DC    16F'0'             SVC INTERRUPT REG SAVE
PGMREGS  DC    16F'0'             PGM INTERRUPT REG SAVE
*
*
CVT      DC    A(0)     +++ PLACE HOLDER +++
MVT      DC    A(0)     +++ PLACE HOLDER +++

Next we go ahead and define some data areas.  These will not be used right now but they will be needed in later versions of our nucleus code.

         USING TXXNUC,0
*
SVCHNDLR DS    0H
PGMHNDLR DS    0H
IOHNDLR  DS    0H
         LPSW  WAITPSW
*
         DS    0D
WAITPSW  DC    X'00',X'02',AL2(0),A(X'EE9999')

If an SVC, Program, or I/O interrupt occurs we load a disabled wait PSW.

EXTHNDLR DS    0H
         L     R1,=A(X'80')       INTERVAL TIMER VALUE
         ST    R1,ITIMER-@LOWCORE SAVE INTO TIMER
*
         LPSW  IWAIT              LOAD WAIT PSW
*
         DS    0D
IWAIT    DC    AL1(255,2,0,0),AL4(X'FEAD')
*
*
DISPINIT DS    0H
         B     EXTHNDLR

When an External interrupt occurs we store a new value into the interval timer and then load an Enabled Wait PSW.  The PSW must be enabled for External interrupts so our interrupt routine will be invoked when the timer expires.

         @LOWCORE ,
*
         @REGS ,
         @@ERR ,
         END   ,

Finally we include our macros for mapping low core and registers.  I have also created a new macro called @@ERR to contain wait state codes.

Once the nucleus is assembled and link edited as TXXNUC we can fire up Hercules and IPL from our DASD volume.  We should see our enabled wait PSW with the code ‘FEAD’ and we should observe the CPU waking up and performing a few instructions before going  back into the wait state.

[Next – Dispatcher]

Low Core

There are several memory locations in low core that are mapped to specific CPU functions.  These memory locations provide a direct way for the program to communicate with the CPU.  We will reserve the first 1k of memory for CPU usage.  Here are some of the memory mapped locations we will need to use:

RSTNPSW  DS    D  RESTART NEW PSW
RSTOPSW  DS    D  RESTART OLD PSW
         DS    F
         DS    F
EXTOPSW  DS    D  EXTERNAL OLD PSW
SVCOPSW  DS    D  SVC      OLD PSW
PGMOPSW  DS    D  PROGRAM  OLD PSW
MCKOPSW  DS    D  MACHINE CHECK OLD PSW
IO$OPSW  DS    D  I/O      OLD PSW
CSW      DS    D  CHANNEL STATUS WORD
CAW      DS    F  CHANNEL ADDRESS WORD
         DS    F
ITIMER   DS    F  INTERVAL TIMER
         DS    F
EXTNPSW  DS    D  EXTERNAL NEW PSW
SVCNPSW  DS    D  SVC      NEW PSW
PGMNPSW  DS    D  PROGRAM  NEW PSW
MCKNPSW  DS    D  MACHINE CHECK NEW PSW
IO$NPSW  DS    D  I/O           NEW PSW

I have created a macro (@LOWCORE) to map these memory locations.  We will use the two words following the Restart Old PSW as pointers to control blocks.  This will allow the control blocks to be easily located since they are anchored at a fixed location.

[Next – TXXNUC00]

TXXNUC – Operating System Nucleus

Now that we can IPL from DASD and load a nucleus we need to start writing some nucleus code.  The nucleus will load at location zero and should contain a PSW at location zero.  Once the nucleus is loaded control will be passed to the nucleus by  loading the PSW from location zero.

The load module name for the nucleus is expected to be TXXNUC.  For developing I will version the source code by appending a two digit value but I will link it without the suffix.  This will allow an incremental approach to developing our small operating system.

An operating system needs to do a few specific things.  It needs to manage processes, manage storage and manage I/O devices.  Once those basic functions are operational additional higher level functions like file access and program management can be added.

Initially a very simple approach will be taken for storage management.  Instead of actually managing storage we will make storage available when requested but will ignore the free requests.  With several megabytes of memory we will be able to run for a while before running out of memory.  Once we get some of the other functionality implemented we can return to memory management.

[Next – Low Core]

TXXIPL2D – Writing IPL Text To DASD

Before writing to a DASD volume it is a good idea to make a backup.  While I was testing TXXIPL2D I managed to erase the VOL1 record.  It is very easy to make a coding mistake that could leave the volume completely unusable.  In my case I was able to write a program that simply rewrote a VOL1 record and all was good.  If I had managed to damage the VTOC I would have had to format the volume resulting in a loss of all the data.  Since the only data on the volume I am writing to is for testing my TXX code it wouldn’t be the end of the world.  My source code is safely on another volume.

Writing the IPL1 and IPL2 records is pretty straight forward.  Because they already exist on the volume we only need to update them.  We can use the Write Data (x’05’) to update the record data.  The Write Data command will only overwrite the data portion of the record. We Seek/Search ID for the record we want to update and then chain on the Write Data.  If we write less data than the record size, the remainder of the record will be padded with zeros.

To write the IPL Text in record 4 we need to use the Write CKD (Count, Key, Data) command.  This command will format a record on the track.  Any records following the record on the track will be erased.  (This is how I managed to erase the VOL1 record).  To use Write CKD we have to seek for the previous record. So to write record 4 we need to seek to record 3.

The data transferred must include the eight byte count area, the key data (if any), and the data.  A record with a data length of zero is an End Of File (EOF).

The count area is eight bytes long and has three fields – the record ID (CCHHR), the Key Length, and the Data Length.  The key length is 1  byte (maximum 256) and the Data Length is 2 bytes.

For our IPL text record no key is needed and the length is determined by the size of the IPL text data.

To get the IPL text data we can read it from an object deck.  We can use a procedure similar to how we loaded an object deck in our Absolute Loader.  In this case we can load the the object deck into a work area that becomes the buffer for our write command.

I also included the ability to read the DASD device address from a card.  The format 0f the control card is: DEVICE=xxx where xxx is the device address we will write to.

To write the IPL text to our DASD volume we need to begin with our Absolute Loader.  It will then load and transfer execution to TXXIPL2D.  It will then the control card from the card reader.  Immediately following the control card should be the IPL object deck to write to the DASD.

Here is the JCL I used:

//IPL2D    JOB 5222,'IPL TO DASD',CLASS=A,MSGCLASS=A,
//        MSGLEVEL=(1,1)
/*JOBPARM K=0
//*
//*
//TXXOSRUN EXEC  PGM=ABSLOAD
//STEPLIB  DD    DISP=SHR,DSN=TXXOS.CARD.LOAD
//PUNCH    DD    UNIT=00D
//*
//*
//ASM      EXEC  PGM=IFOX00,REGION=1024K,
//         PARM='LINECOUNT(44),TERM'
//SYSLIB   DD    DISP=SHR,DSN=SYS1.MACLIB
//         DD    DISP=SHR,DSN=SYS1.AMODGEN
//         DD    DISP=SHR,DSN=TXXOS.DOS.ASM
//SYSUT1   DD    DSN=&&SYSUT1,UNIT=VIO,SPACE=(1700,(600,100))
//SYSUT2   DD    DSN=&&SYSUT2,UNIT=VIO,SPACE=(1700,(300,50))
//SYSUT3   DD    DSN=&&SYSUT3,UNIT=VIO,SPACE=(1700,(300,50))
//SYSTERM  DD    SYSOUT=*
//SYSPRINT DD    SYSOUT=*
//SYSPUNCH DD    UNIT=00D
//SYSIN    DD    DISP=SHR,DSN=TXXOS.DOS.ASM(TXXIPL2D)
//*
//GENR1    EXEC  PGM=IEBGENER
//SYSPRINT   DD  SYSOUT=*
//SYSIN      DD  DUMMY
//SYSUT2     DD  UNIT=00D
//SYSUT1     DD  *
DEVICE=345
/*
//*
//GENR2    EXEC  PGM=IEBGENER
//SYSPRINT   DD  SYSOUT=*
//SYSIN      DD  DUMMY
//SYSUT2     DD  UNIT=00D,DCB=(BLKSIZE=80,LRECL=80)
//SYSUT1     DD  DISP=SHR,DSN=TXXOS.DOS.OBJ(TXXIPL)
//*

My TXXIPL module is simply an updated copy of the DBOOT3 program.  It will locate a data set named TXXOS.SYSLIB and then load TXXNUC into memory and transfer control.

We now have the ability to IPL from a DASD volume.

[Next –  TXXNUC]

 

DASD IPL Records

Now we are ready to start preparing to IPL directly from a DASD volume instead of the card reader.  When the CPU IPL’s from DASD the IPL record at CC=0000 HH=00000 R=01 will be read into memory.  Just like our first IPL card it contains a PSW and two CCW’s.  The first CCW is a Read Data (X’06’). Since it is chained from the initial IPL CCW we do not need a Seek or Search CCW.  The Read CCW will read the next record on the track (CC=0000 HH=0000 R=02).  This second IPL record contains additional CCW’s and data.

The first IPL record has a Key Length of 4 with the value ‘IPL1’ and a Data Length of 24.  The second IPL record has a Key Length of 4 with a value of ‘IPL2′  and a Data Length of 144 (or at least it does on my 3350).  These records were created when the volume was initialized.

The third record is the Volume Label and is followed by the IPL text in record 4.

Our IPL1 record needs to contain a PSW and two CCW’s.  We will load the contents of the second record at location x’4000’ so our first record needs to contain:

00000000 00005000  PSW
06004000 60000060  Read Data into location x'4000', Command Chain, SLI, x'60' byes of data
08004000 00000000  TIC location x'4000'

This will cause x’60’ byes from our IPL2 record to be read into location x’4000′ and then the CCW program will TIC (branch) to get the next CCW from that location.

Our IPL2 record needs to contain the necessary CCW’s and data fields to read in the IPL text from record 4.  We will read the IPL text into location x’5000′.  Our IPL2 record needs to contain:

07004021 40000006 SEEK, Command Chain
31004023 40000005 SEARCH ID Equal, Command Chain
08004008 00000000 TIC (back to search CCW)
06005000 2000**** READ Data, SLI
00000000 00000004 MBBCCHHR (for Seek and Search)

The data length in the Read Data CCW should be the same as the actual length of our IPL text record.

[Next – TXXIPL2D – Writing IPL Text To DASD]

Nucleus Load Module Loader

We have already developed code that can read the Volume Label, search the VTOC, locate a PDS directory member, and read the data associated with that member. Now we can write some code to load the contents of a load module into memory.

*
         SLR   R3,R3
         ICM   R3,B'0011',CCWREAD+6  LENGTH ATTEMPTED TO READ
         SLR   R1,R1
         ICM   R1,B'0011',IORB+(IORCSW-IOR)+6  GET RESUDUAL LENGTH
         SR    R3,R1              CALC ACTUAL BLOCK LENGTH
*
         B     LOADNUC            LOAD NUCLES LOADMODULE

In our BREAD routine from DISK3 we will calculate the length of the record just read and place the length into Register 3 before branching to LOADNUC.

LOADNUC  DS    0H
         LA    R4,INBUF           POINT TO RECORD DATA
*
         CLI   TEXTSW,0           PROCESSING A TEXT RECORD?
         BNE   TEXT               YES - BRANCH
*
         CLI   0(R4),X'01'        CTL RECORD ?
         BE    CTL
*
         CLI   0(R4),X'05'        CTL RECORD ?
         BE    CTL
*
         CLI   0(R4),X'0D'        CTL RECORD ?
         BE    CTL
*
         CLI   0(R4),X'03'        CTL+RLD RECORD ?
         BE    CTL
*
         CLI   0(R4),X'07'        CTL+RLD RECORD ?
         BE    CTL
*
         CLI   0(R4),X'0F'        CTL+RLD RECORD ?
         BE    CTL
*
         CLI   0(R4),X'02'        RLD RECORD ?
         BE    RLD
*
         CLI   0(R4),X'06'        RLD RECORD ?
         BE    RLD
*
         CLI   0(R4),X'0E'        RLD RECORD ?
         BE    RLD
*
         B     BREAD              SKIP RECORD AND READ NEXT'
*

We start by inspecting the first byte of the record to determine the type of record it is.  We are only concerned with three types of records: Control (CTL), Relocation (RLD) and Text.  It is possible a record may be both a Control and Relocation.  A Text record follows a Control record and the first byte contains data not a record type.  If the record type doesn’t indicate a Control or Relocation record then we can ignore it.  We use TEXTSW as an indicator this record is a Text record.

CTL      DS    0H
         MVI   TEXTSW,1           NEXT RECORD IS A TEXT RECORD
*
         SLR   R1,R1              ZERO R1
         ICM   R1,B'0011',9(R4)   GET LOAD ADDRESS
         ST    R1,TEXTOFF         SAVE OFFSET
         ICM   R1,B'0111',13(R4)  GET LENGTH
         ST    R1,TEXTLEN         SAVE LENGTH
*
         TM    0(R4),X'02'        RLD ALSO
         BO    RLD                YES - BRANCH
*
         B     BREAD              GO READ NEXT RECORD
*

In processing a Control record we start by setting TEXTSW to indicate the next record read will be a Text record.  We then get the address the data is to be loaded at and save it in TEXTOFF and the length of the data which is saved in TEXTLEN.  We check to see if the Control record also contains Relocation data.  If it does we can continue on to RLD else we read the next record.

TEXT     DS    0H
         L     R2,TEXTOFF         GET TEXT OFFSET
         L     R3,TEXTLEN         GET TEXT LENGTH
         LR    R5,R3              COPY LENGTH
*                                 R4 HAD TEXT RECORD ADDRESS
         MVCL  R2,R4              COPY THE DATA
*
         MVI   TEXTSW,0           RESET TEXT SWITCH
         B     BREAD              PROCESS NEXT RECORD
*

Processing of a Text record is straight forward.  We recall the offset and the length of the data and use MVCL to copy it into place.  We then reset TEXTSW to zero and read the next record.

RLD      DS    0H
         B     BREAD              NOT RELOCATING NUCLEUS

Normally when loading a Load Module we have to deal with relocatable symbols.  The load module is based off an address of zero and gets loaded into a higher address.  We would use the RLD entries to add the load address to the value in the relocatable symbol.  Sine we are loading the Nucleus at location zero our relocation offset is zero.  This means we can ignore RLD data for loading the Nucleus.  We simply loop back to read the next record.

BREAD030 DS    0H
*+++++++ EOF READING DATA ++++++++
         LPSW  0                  LOAD NEW PSW
*                                     TO TRANSFER CONTROL TO NUCLEUS

When we get an End of File condition we transfer control to the newly loaded Nucleus by executing a LPSW to load a new PSW from location zero.  The work of the IPL loader is complete.

The memory size, relocation to high memory, and Load Module loader are all implemented in DBOOT3.  It can be run using the Absolute Loader.

DBOOT3 Source Code

[Next – DASD IPL Records]

Copy To High Memory

Once we have determined the highest memory address available we can copy our loader code into high memory.

*
* RELOCATE LOADER TO HIGH MEMORY
*
         LA    R2,LOADER          ADDRESS OF ROUTINE TO RELOCATE
         LA    R3,LOADERLN        LENGTH OF ROUTINE TO RELOCATE
         LR    R4,R9              HIGH MEM ADDRESS
         SR    R4,R3              BACK UP LENGTH OF CODE
         S     R4,M32K            LEAVE A 32K BUFFER
         N     R4,MASK            START ON A CLEAN BOUNDRY
         LR    R8,R4              SAVE START ADDRESS IN R8
*
         LA    R1,256             MAX MVC LENGTH

We start by loading the address of our loader routine into Register 2 and the length of the routine into Register 3.  We copy the highest memory address into Register 4 and then subtract the length of the code.  Because we use the storage immediately following our  loader code for the disk I/O buffer we subtract out another 32k leaving more then plenty of room.  Finally we use a logical AND to start loading on an even boundary (we at least have to start on a double word boundary to avoid alignment errors).  Finally save the beginning load address into Register 8 (so we can branch to it when relocation is completed) and we set Register 1 to 256 or the maximum length for a MVC instruction.

MCOPY    DS    0H
         CR    R3,R1              CHECK FOR MAX LEGNTH
         BL    MCOPY010           NO - SHORT MOVE
*
         MVC   0(256,R4),0(R2)    MOVE 256 BYTES
         AR    R2,R1              NEXT AREA
         AR    R4,R1                       TO MOVE
         SR    R3,R1              ADJUST LENGTH
         B     MCOPY              LOOP BACK
*
*
MCOPY010 DS    0H
         LTR   R3,R3              CHECK FOR ANYTHING TO MOVE
         BZ    MCOPY020           ALL DONE
*
         BCTR  R3,0               SUBTRACT ONE FOR EXECUTE
         EX    R3,MVC             COPY LAST OF DATA
*
MCOPY020 DS    0H
         LR    R15,R8             ADDRESS OF RELOCATED LOADER
         BR    R15                BRANCH TO IT

The copy routine is pretty straight forward.  First we check to see if we have at least 256 bytes left to copy.  If not we branch to MCOPY010 to handle a short move.   We then copy 256 bytes of data, increment our source and target pointers, and then decrement the remaining length.

When we finally have less than 256 bytes to copy we must first check that there is at least one byte to move.  We then subtract one from the length and use an Executed Move Characters to copy the remainder of the data.

Finally we branch to the routine we just moved.

 *
 M32K     DC    A(1024*32)         * CONSTANT 32K
 MASK     DC    A(X'00FFF000')     * BOUNDARY MASK
 MVC      MVC   0(1,R4),0(R2)      * EXECUTED INSTRUCTION

Finally we define our constants.

It is important to point out that we don’t do any relocation of pointers for the routine we copy to high memory.  It is important that when we write the code for our loader we avoid using any relocatable constants.  For example if we used something like:

PTR      DC    A(BUFFER)

we would generate a relocatable constant.  Once we copied the routine to high memory the address pointer would still refer to the original location in low memory.  Instead we need to calculate the pointer address at run time and instead use something like:

         LA    R2,BUFFER
         ST    R2,PTR  
PTR      DC    A(0)

This will give us the proper value in our pointer.

The Assembler output will help  us determine if we have used any relocatable addresses in our program.  Near the end of the assembly listing we find the Relocation Dictionary.  It provides us with a list of all the relocatable symbols in the program and the address where it is located.  If we find any relocatable symbols that are contained within our code to be relocated we can then go back to the source and correct the problem.

                                     RELOCATION DICTIONARY
 POS.ID   REL.ID   FLAGS   ADDRESS
  0001     0001      08     000005
  0001     0001      08     000D81
  0001     0001      08     000D89
  0001     0001      08     000D91
  0001     0001      08     000D99

[Next – Nucleus Load Module Loader]

Determine Memory Size

In order to relocate to high memory we must first determine how much memory is available.  Hercules allows us to define a memory size from 1 to 16 megabytes in 1 megabyte increments.

To determine the memory we have available at IPL time we will simply scan through all memory beginning at location zero until we reach 16 Meg or we get an error attempting to access memory.

We will use the SSK (Set Storage Key) instruction to test for vaild storage.  It has the format SSK R1,R2.  Bits 21-27 of R2 specify a 2k (2048) block memory.  The storage key is set to the value specified by bits 24-30 of R1.  If the storage block does not exist an Access Specification interrupt is generated.

By using the SSK instruction we not only determine how much memory is available but we also set the access key for all memory to zero.

 *
 *
          LA    R2,0
          LA    R3,0
          L     R4,=A(X'01000000')   16 MEG
          LA    R1,2048              2K
 *
          MVC   104(8),=A(0,MDONE) PROGRAM CHECK NEW PSW
 *
 MLOOP    DS    0H
          SSK   R2,R3              SET STORAGE KEY
          AR    R3,R1              NEXT 2K PAGE
          CR    R3,R4              HIT 16 MEG?
          BL    MLOOP              LOOP BACK
 *
 MDONE    DS    0H
          MVC   104(8),=A(X'00020000',X'00010BAD') PC NEW PSW
 *
 *

We start by setting our access key value to zero in Register 2.  We set the initial address in Register 3 to zero (storage location zero).  Register 4 is set to the maximum memory size (16 Meg) and Register 1 is set to 2k for our increment value.

Before beginning the loop we specify a New Program Check PSW.  If a program check interruption occurs, the CPU will load the value contained at location 104 into the PSW.  Here we specify a PSW with MDONE as the instruction address value.  If a program check occurs it will effectively cause a branch to the label MDONE.

The loop is very simple.  We use the SSK instruction to set the storage key of a 2k block of storage.  If the storage address does not exist a program check interruption occurs which causes a branch in execution to MDONE.  If the SSK was successful we add 2k to the address, check to see if we have reached the maximum memory size (16 Meg) and then loop back.

When we reach MDONE either because of a program check interruption or because fell through the branch, Register 3 contains the high memory address and tells us how much memory is available.

The first thing we need to do is to set a new program check PSW.  Here we load a PSW with the wait bit set and an error code of x’010BAD’.  If we failed to specify a new program check PSW and a a program check  occurred, we could possibly end up in an endless loop.

*
         ST    R3,WORK4           SAVE MEMORY SIZE
         UNPK  WORK8(9),WORK4(5)  UNPACK
         TR    WORK8,TRTAB-C'0'   MAKE PRINTABLE
*
*
         LR    R1,R3              COPY MEMORY SIZE
         SLR   R0,R0
         D     R0,=A(X'00100000')  DIVIDE BY 1MEG
         CVD   R1,DOUBLE          SAVE RESULT AS DECIMAL
         ED    EDWK,DOUBLE+6
         MVC   MEMSIZE2,EDWK+1
*
*
         MVC   MEMSIZE1,WORK8     MOVE IN MEMORY SIZE
*
         @PRINT MSG1,LEN=L'MSG1
         @PRINT MSG2,LEN=L'MSG2
         @PRINT MSG1,LEN=L'MSG1
*
*
         LPSW   DONE
*

For testing we can us UNPK and then TR to get a printable hex value.  We then divide the max memory size by 1 Meg, and use CVD and ED to get a decimal value.  These values are moved into our print line and then printed.

DONE     DC    A(X'00020000',X'0099FACE')   WAIT STATE PSW
*                                                                      

MSG1     DC    C'*****************************************************'
MSG2     DC    CL132' '
         ORG   MSG2
         DC    C'  MEMORY SIZE=X'''
MEMSIZE1 DC    CL8'********',C'''  '
MEMSIZE2 DC    CL3'***',C' MEG'
         ORG   ,
*
DOUBLE   DS    D
FSA      DS    18F
WORK4    DS    F
WORK8    DC    CL8'********',C'**'
*
EDWK     DC    X'40202020'
*
TRTAB    DC    C'0123456789ABCDEF'
*

The complete program is available at DBOOT1.

[Next – Copy To High Memory]

IPL From Disk

Now that we understand how to access a file on a disk volume we can look at how to IPL from disk.  Our goal is to write a bootstrap loader that will then load a standard load module from a PDS.

The bootstrap loader will reside on cylinder zero, track zero of our IPL volume.  The initial IPL record is on record 1 and will consist of a PSW and two CCW’s (read data and TIC).  Record two will contain additional CCW’s to read in a block of executable code that will be contained in record 4.  Record 3 is skipped and is reserved for use as the Volume Label (VOL1) record.

Our bootstrap program will relocate itself to high memory then it will search the VTOC for the PDS containing our initial load module.  We will use the name “TXXOS.NUCLEUS” as our data set name.  The initial load module will be named “TXXNUC01”

Once the nucleus is loaded into memory the bootstrap program will transfer control.

We will start by working on the bootstrap loader but will use our Absolute Card Loader.  Once we get the bootstrap loader working properly we will tackle how to set up the IPL records on the disk volume.

[Next – Determine Memory Size]