TXXDISP – Dispatcher Routine

The Dispatcher looks at the list of TCB’s on the work queue and selects one to become the active task.  If there are no TCB’s ready to dispatch an enabled wait will be entered.

000000                                6 TXXDISP  CSECT ,
000000 18CF                           7          LR    R12,R15            ESTABLISH
                            00000     8          USING TXXDISP,R12                 BASE REG
                                      9 *
000002 1F11                          10          SLR   R1,R1              CLEAR TIME EXPIRED SWITCH
                                     11 *
000004 58B0 0010      00010          12          L     R11,16             GET CVT ADDRESS
                            00000    13          USING @CVT,R11
                                     14 *
000008 5840 B004      00004          15          L     R4,CVTTASKQ        START AT TOP OF TASK QUEUE
                                     16 *
                            00000    17          USING @TCB,R4
                                     18 *
00000C                               19 DISP010  DS    0H
00000C 91D0 4004      00004          20          TM    TCBFLGS,TCBFWAIT+TCBFNDSP+TCBFETS  DISPATCHABLE?
000010 4780 C05C      0005C          21          BZ    DISP050             YES - GO RUN IT
                                     22 *
000014 9110 4004      00004          23          TM    TCBFLGS,TCBFETS    TIME EXPIRED FOR THIS TASK?
000018 47E0 C020      00020          24          BNO   DISP020            NO - BRANCH
                                     25 *
00001C 4110 0001      00001          26          LA    R1,1               SET EXPIRED SWITCH
000020                               27 DISP020  DS    0H
000020 5840 4000      00000          28          L     R4,TCBNEXT         POINT TO NEXT TCB
000024 1244                          29          LTR   R4,R4              END OF CHAIN
000026 4770 C00C      0000C          30          BNZ   DISP010            LOOP BACK
                                     31 *
00002A 1211                          32          LTR   R1,R1              ANY TIME EXPIRED TASKS
00002C 4780 C06A      0006A          33          BZ    DISP060              NO - GO INTO WAIT

The Dispatcher starts at the top of the TCB queue.  It looks for a TCB that is eligible to be dispatched.  If a TCB is found it becomes the current TCB and control is passed to that task.  While scanning the queue a switch is set if any TCB on the queue is not dispatchable because it was interrupted by the timer task.  If no any TCB’s were found that were not dispatched because of the time exceeded flag, the dispatcher will clear these bits and allows the TCB’s to be dispatched.

000030 5840 B004      00004          35          L     R4,CVTTASKQ        LOAD TOP OF TASK QUEUE
000034                               36 DISP030  DS    0H
000034 94FE 4004      00004          37          NI    TCBFLGS,X'FF'-TCBFLONG  CLEAR LONG RUNNING FLAG
                                     38 *
000038 9110 4004      00004          39          TM    TCBFLGS,TCBFETS    TIME EXPIRED FOR THIS TASK?
00003C 47E0 C048      00048          40          BNO   DISP040            NO - BRANCH
                                     41 *
000040 94EF 4004      00004          42          NI    TCBFLGS,X'FF'-TCBFETS    CLEAR FLAG
000044 9601 4004      00004          43          OI    TCBFLGS,TCBFLONG         SET LONG RUNNING FLAG
                                     44 *
000048                               45 DISP040  DS    0H
000048 5840 4000      00000          46          L     R4,TCBNEXT               NEXT TCB ON CHAIN
00004C 1244                          47          LTR   R4,R4                    END OF CHAIN
00004E 4770 C034      00034          48          BNZ   DISP030                   NO - LOOP BACK
                                     49 *
000052 5840 B004      00004          50          L     R4,CVTTASKQ              POINT TO TOP OF QUEUE
000056 1F11                          51          SLR   R1,R1                    CLEAR SWITCH
000058 47F0 C020      00020          52          B     DISP020                  LOOK AGAIN FOR WORK

Here the Dispatcher loops through the TCB queue resetting time exceeded bit.  If the TCBFETS flag is reset in a TCB, the TCBFLONG bit is set.  This bit will not make the task non-dispatchable.  It only indicates the TCB had previously been prohibited from dispatch.  Once the entire queue has been scanned the main dispatcher loop is once again entered to select a task for dispatch.

00005C                               58 DISP050  DS    0H
00005C 5040 B000      00000          59          ST    R4,CVTCTCB
000060 58F0 0014      00014          60          L     R15,20                   MVT ADDRESS
000064 58F0 F008      00008          61          L     R15,MVTLLDSP-@MVT(,R15)  LLDISP ROUTINE
000068 07FF                          62          BR    R15

Once a TCB is selected for dispatch the address of the TCB is stored in the CVT at CVTCTCB  (current TCB).  Control is then passed the the Low Core Dispatch routine in low memory to restore the registers and PSW from the TCB.

00006A                               68 DISP060  DS    0H
00006A 5840 B004      00004          69          L     R4,CVTTASKQ              WAIT TASK
00006E 5040 B000      00000          70          ST    R4,CVTCTCB                 MAKE IT CURRENT TASK
000072 8200 C078      00078          71          LPSW  DISPWPSW           ENABLED WAIT
000078                               75          DS    0D
000078 FF0200000000FEAD              76 DISPWPSW DC    AL1(255,2,0,0),A(X'FEAD')  ENABLED WAIT PSW

If no TCB is available for dispatch the dummy Wait Task TCB is made the current TCB and an enabled wait is entered.  The dummy Wait Task TCB is needed so that when an interruption occurs while in the disabled wait there will be a TCB for the interrupt handler to store registers and the PSW.  Although this information is saved at the interrupt, it is never used since the Wait Task is always associated with the enabled wait PSW.

[Next – TXXDINIT – Dispatcher Initialization ]

TXXEXTIR – External Interrupt Routine

The External Interrupt Routine is called from the first level Ext Interrupt handler in low memory when an external interrupt occurs.

000000                                8 TXXEXTIR CSECT ,
000000 18CF                           9          LR    R12,R15
                            00000    10          USING TXXEXTIR,R12                BASE REG
                                     11 *
                            00000    12          USING @TCB,R4
                                     13 *
000002 9180 401B      0001B          14          TM    TCBPSW+3,X'80'     TIMER INTERRUPT ?
000006 4710 C016      00016          15          BO    TIMER              YES - BRANCH
                                     16 *
00000A 9140 401B      0001B          17          TM    TCBPSW+3,X'40'     EXT KEY PRESSED ?
00000E 4710 C026      00026          18          BO    EXTKEY             YES - BRANCH
                                     19 *
000012 8200 C038      00038          20          LPSW  EXTWPSW            GO INTO WAIT STATE

We begin by examining the PSW to determine what type of external interruption occurred.  The two conditions we are interested in are the Interval Timer expired and an operator generated interruption.  If neither of these types of interruptions is indicated a wait state PSW is loaded.

000016                               26 TIMER    DS    0H
000016 5810 C040      00040          27          L     R1,=A(X'80')       RESTART TIMER
00001A 5010 0050      00050          28          ST    R1,ITIMER-@LOWCORE
                                     29 *
00001E 9610 4004      00004          30          OI    TCBFLGS,X'10'      INDICATE TIME EXPIRED
000022 47F0 C02A      0002A          31          B     EXIT               AND EXIT

If the interruption is the result of the Interval Timer expiring then it means the executing task is hogging the CPU.  We set a bit in the TCB flags indicating the task was interrupted by the timer.  This will cause the task to not be dispatched until all other tasks have been given the opportunity to run.

000026                               37 EXTKEY   DS    0H
000026 47F0 C02A      0002A          38          B     EXIT               IGNORE FOR NOW 

00002A                               44 EXIT     DS    0H
00002A 58F0 0014      00014          45          L     R15,20             GET CVT ADDRESS
00002E 58F0 F004      00004          46          L     R15,MVTDISP-@MVT(,R15)   GET DISPATCHER EP
000032 07FF                          47          BR    R15                      BRANCH TO DISPATCHER

000038                               51          DS    0D
000038 0002000000EE0002              52 EXTWPSW  DC    AL1(0,2,0,0),A(@E@EXT)  DISABLED WAIT PSW

For now we will just ignore an external interrupt caused by the operator.  Exit is simply branching to the Dispatcher routine.

[Next – TXXDISP – Dispatcher Routine]

TXXNUC – Nucleus Low Core

Now we can begin creating our small operating system.  It starts with the low core module for the nucleus.  The low core module must be loaded first at location zero.  When we link edit the nucleus we must make sure it is included first.

It begins with the data for mapping the hardware specific addresses in low memory.

000000                                1 TXXNUC   CSECT ,
000000 0000000000000578               2          DC    X'00',X'00',AL2(0),AL4(DISPINIT)  INITIAL PSW
000008 0000000000000000               3          DC    D'0'                              RST NEW PSW
                                      4 *
000010 00000000                       5          DC    V(TXXCVT)                         COMM VECT TABLE
000014 00000000                       6          DC    V(TXXMVT)                         MOD  VECT TABLE
                                      7 *
000018 0000000000000000               8          DC    D'0'          EXT OLD PSW
000020 0000000000000000               9          DC    D'0'          SVC OLD PSW
000028 0000000000000000              10          DC    D'0'          PGM OLD PSW
000030 0000000000000000              11          DC    D'0'          MCK OLD PSW
000038 0000000000000000              12          DC    D'0'          I/O OLD PSW
                                     13 *
000040 0000000000000000              14          DC    D'0'          CHANNEL STATUS WORD
000048 00000000                      15          DC    F'0'          CHANNEL ADDRESS WORD
00004C 00000000                      16          DC    F'0'
000050 00000000                      17          DC    F'0'          INTERVAL TIMER
000054 00000000                      18          DC    F'0'
                                     19 *
000058 0000000000000530              20          DC    X'00',X'00',AL2(0),AL4(EXTHNDLR)  EXT NEW PSW
000060 0000000000000554              21          DC    X'00',X'00',AL2(0),AL4(SVCHNDLR)  SVC NEW PSW
000068 0000000000000520              22          DC    X'00',X'00',AL2(0),AL4(PGMHNDLR)  PGM NEW PSW
000070 0000000000EE0001              23          DC    X'00',X'02',AL2(0),AL4(@E@MCK)    MCK NEW PSW
000078 0000000000000520              24          DC    X'00',X'00',AL2(0),AL4(IOHNDLR)   I/O NEW PSW

The restart PSW is the initial PSW that will be loaded by the nucleus loader.  It will cause execution to begin at DISPINIT – the dispatcher initialization routine.  Locations 16 (x’10’) and 20 (x’14’) are not used by the CPU so we will use them as pointers.  Location 16 will point to a control block that contains system related data and pointers (the Communication Vector Table – or CVT).  Location 20 will point the the Module Vector Table (MVT) which is a list of pointers to other modules.  Our system modules in general will not contain a direct pointer V(module) to a module.  Instead the module address will be obtained from the MVT.

Next we have the OLD PSW areas for external, SVC, program, machine check, and I/O interruptions.  This is where the CPU will store the current PSW when an interrupt occurs.  It is followed by the Channel Status Word and Channel Address Word.  The Interval Timer is used to generate an external interrupt.  A value is loaded into the Interval Timer word.  The CPU will decrement the value.  When the timer is expired an external interrupt will be generated.

Finally we define the NEW SVC values for the various interrupts.  When an interrupt occurs the CPU will load the PSW with the associated value.  If a machine check occurs we load a disabled wait PSW.

                            00080    27 LOWCLEN  EQU   *-TXXNUC
                            00380    28 LOWCFILL EQU   1024-LOWCLEN
000080 0000000000000000              29          DC    (LOWCFILL)X'00'
                                     30 *
000400 0000000000000000              32 EXTREGS  DC    16F'0'             EXT INTERRUPT REG SAVE
000440 0000000000000000              33 IOREGS   DC    16F'0'             I/O INTERRUPT REG SAVE
000480 0000000000000000              34 SVCREGS  DC    16F'0'             SVC INTERRUPT REG SAVE
0004C0 0000000000000000              35 PGMREGS  DC    16F'0'             PGM INTERRUPT REG SAVE

Next we fill the remainder of memory up to 1k with zeros.  Beginning at address 1k we define interrupt register save areas.  This gives us a place to save current register contents when an interrupt occurs.  Because the save areas are in the first 4k of memory we will  not need a base register to access them.

                            00000    39          USING TXXNUC,0      NO BASE REGISTER NEEDED FOR FIRST 4K
                                     48          ENTRY LLDISP
000500                               49 LLDISP   DS    0H
                            00000    50          USING @TCB,R4
                                     51 *
000500 5810 05B8      005B8          52          L     R1,=A(X'80')       RESTART TIMER
000504 5010 0050      00050          53          ST    R1,ITIMER-@LOWCORE              AT DISPATCH
                                     54 *
000508 D207 0518 4018 00518 00018    55          MVC   LLRSTPSW,TCBPSW    COPY TCB PSW INTO LOW CORE
00050E 980F 4020      00020          56          LM    R0,R15,TCBREGS     RESTORE TCB REGISTERS
000512 8200 0518      00518          57          LPSW  LLRSTPSW           DISPATCH
                                     58 *
000516 0000
000518 0000000000000000              60 LLRSTPSW DC    D'0'               DISPATCH PSW
                                     62          DROP  R4

Now comes our Low Level Dispatch routine.  We need some code in low memory (first 4k) to restore registers and PSW when returning from an interrupt.  This code needs to be in low memory so we will not require the use of a base register.  We start by resetting the Interval Timer.  When it expires it will interrupt the currently running task so we reset it to give each task the same execution interval.  On entry to LLDISP Register 4 must contain the address of the TCB to be dispatched.  First the PSW is copied out of the TCB and into low core.  Next the registers are restored from the TCB.  A Load PSW instruction loads the PSW that was saved from the TCB.

000520                               68 PGMHNDLR DS    0H
000520                               69 IOHNDLR  DS    0H
000520 8200 0528      00528          70          LPSW  WAITPSW
                                     71 *
000528                               72          DS    0D
000528 0002000000EE9999              73 WAITPSW  DC    X'00',X'02',AL2(0),A(X'EE9999')

For now if a program or I/O interrupt occurs we will load a wait state PSW.  Later we will provide interrupt handling for these interrupts.

000530                               81 EXTHNDLR DS    0H
000530 900F 0400      00400          82          STM   R0,R15,EXTREGS     SAVE REGISTERS
                                     83 *
000534 05C0                          84          BALR  R12,0              ESTABLISH BASE REGISTER
                            00536    85          USING *,R12
                                     86 *
000536 58B0 0010      00010          87          L     R11,16             GET CVT ADDRESS
00053A 5840 B000      00000          88          L     R4,CVTCTCB-@CVT(,R11) GET CURRENT TCB
                            00000    89          USING @TCB,R4
                                     90 *
00053E D207 4018 0018 00018 00018    91          MVC   TCBPSW,EXTOPSW-@LOWCORE  SAVE INT PSW
000544 D23F 4020 0400 00020 00400    92          MVC   TCBREGS(64),EXTREGS      SAVE INT   REGISTERS
                                     93 *
00054A 58B0 0014      00014          94          L     R11,20             GET MVT ADDRESS
00054E 58F0 B000      00000          95          L     R15,MVTEXTIR-@MVT(,R11)   GET EXT INTRUPT RTN
000552 07FF                          96          BR    R15                BRANCH
                                     97 *
                                     98          DROP  R4,R12

Now comes the External Interrupt handler.  First the contents of the registers are saved into our low core register save area.  The current TCB address is obtained from the CVT [Current TCB – CVTCTCB].  The interrupt PSW and registers are copied into the TCB.  The external interrupt routine is located in the MVT and control is passed to it.

000554                              105 SVCHNDLR DS    0H
000554 900F 0480      00480         106          STM   R0,R15,SVCREGS     SAVE INTERRUPT REGISTERS
                                    107 *
000558 05C0                         108          BALR  R12,0              ESTABLISH
                            0055A   109          USING *,R12                       BASE REGISTER
                                    110 *
00055A 5840 0010      00010         111          L     R4,16              CVT
00055E 5840 4000      00000         112          L     R4,CVTCTCB-@CVT(,R4)  GET CURRENT TCB
                            00000   113          USING @TCB,R4
                                    114 *
000562 D207 4018 0020 00018 00020   115          MVC   TCBPSW,SVCOPSW-@LOWCORE   SAVE INTERRUPT PSW
000568 D23F 4020 0480 00020 00480   116          MVC   TCBREGS(64),SVCREGS       SAVE INTERRUPT REGS
                                    117 *
00056E 58F0 0014      00014         118          L     R15,20             MVT
000572 58F0 F010      00010         119          L     R15,MVTSVCIR-@MVT(,R15)   SVC INTERRUPT RTN
000576 07FF                         120          BR    R15
                                    121 *
                                    123          DROP  R4,R12

The SVC interrupt handler is the same as the external interrupt handler except it passes control the SVC Interrupt Routine.

000578                              127 DISPINIT DS    0H
                            00000   128          USING @IPLPARM,R10
                                    129 *
000578 D503 05BC A000 005BC 00000   130          CLC   =C'@IPL',@IPLID
00057E 4780 0586      00586         131          BE    DISPI010
                                    132 *
000582 8200 05A0      005A0         133          LPSW  ERRINIT
                                    134 *
                                    135 *
000586                              136 DISPI010 DS    0H
000586 58F0 0014      00014         137          L     R15,20             MVT ADDRESS
00058A 58F0 F00C      0000C         138          L     R15,MVTDINIT-@MVT(,R15)   DISPATCER INIT ROUTINE
00058E 05EF                         139          BALR  R14,R15            INTIALIZE DISPATCHER
                                    140 *
000590 58F0 0014      00014         141          L     R15,20             MVT ADDRESS
000594 58F0 F004      00004         142          L     R15,MVTDISP-@MVT(,R15)  DISPATCHER ADDRESS
000598 07FF                         143          BR    R15
                                    144 *
0005A0                              146          DS    0D
0005A0 0000000000000002             147 ERRINIT  DC    A(0,2,0,0),A(@E@IPL)

Finally we have the Dispatcher Initialization.  We expect a pointer to some information from the LOADER (memory size, IPL Device).  If the IPL Parameters are not valid a wait stat PSW is loaded.  Once the IPL Parm Block is validated the  Dispatcher Initialization Module is called.  After initialization is complete control is passed to the Dispatcher.

[Next – TXXEXTIR – External Interrupt Routine]

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]