
;
;       MicroKernel OS for VAX.
;
;       This is used to support development and testing of VAX programs.
;       It is not a full OS but provides minimal support for basic functions
;       like writing output to the console
;
;       KERNEL SERVICES SUPPORTED
;
;       rc = lib$put_output( struct dsc$descriptor * str );
;
;               Writes a string to the console device.  The string is
;               defined by a string descriptor.  Note that if the 'str'
;               parameter is a byte passed by value rather than the
;               address of a descriptor, then the single byte is written.
;
;       rc = lib$format_dec( long value, char * buff, long * size );
;
;               The longword 'value' is formated as a decimal number in
;               the supplied buffer.  On entry to the call, the 'size'
;               value is the address of the length of the buffer.  On
;               exit from the call, this is updated to the actual number
;               of bytes used in the buffer.  If the buffer is too small
;               the routine returns SS$_BUFFEROVF.
;
;
;
;       HOW TO ADD A NEW KERNEL-mode SERVICE
;
;       Services have two parts, the user mode part and the kernel mode
;       part.  The user mode part should validate that the arguments are
;       valid for user mode (to prevent calling with Kernel addresses,
;       for example) and otherwise check the arguments for correctness.
;       Note that for some services (lib$format_dec, for example) the
;       entire service runs in user mode and requires no system service.
;
;       When it's time to run the portion(s) of the service that must
;       run in kernel mode, write the routine as a function.  You must
;       assume that the user mode part will put it's arguments in 
;       registers for the call.  By convention, R5 is used for the
;       argument or the pointer to the arguments as appropriate.
;
;       The function should return any information it needs to via
;       the argument list by address, and a return code in R0.
;
;       Place the address of the kernel handler in the EXE$TABLE
;       below.  The position in the table must match the kernel mode
;       selector that invokes it.
;

;
;       We must have the system return codes defined for us

                .if defined( "SS$_NORMAL") = 0  .include        "ssdef.asm"

;       Note that this can only be built if we are in kernel mode.  The
;       system initializes in kernel mode at IPL 31 to prevent interrupts,
;       etc. from occuring.  We assume this state for the system to work
;       correctly.

                .region         system
exe$base:       .byte 0


;--------------------------------------------------------------------
;   CONSOLE TRANSMIT INTERRUPT HANDLER
;--------------------------------------------------------------------


                .align          8
exe$tx:         movl            #1, @#exe$tx_ready
                rei


;--------------------------------------------------------------------
;   CHMK EXCEPTION HANDLER
;--------------------------------------------------------------------

                .align          8

;       Turn the exception into a call.  This lets us use registers
;       much more easily.  The #1 makes the stack a one-element arg
;       list.  When done with the call, we must discard the #1 and
;       the PSL, leaving just the PC for the REI.

exe$chmk:       pushl           #1
                callg           sp, @#exe$dispatch
                moval           b^8(sp), sp
                rei

;--------------------------------------------------------------------
;   ACCVIO EXCEPTION HANDLER
;--------------------------------------------------------------------

                .align          8

;       Turn the exception into a call.  Add the signal value, and
;       account for the fault addr, mode, PC, and PSL arguments.

exe$accvio:     pushl           #SS$_ACCVIO
                pushl           #5
                callg           sp, @#exe$signal
                moval           b^0C(sp), sp
                moval           @#exe$halt, (sp)
                rei

;--------------------------------------------------------------------
;   RESERVED OPCODE EXCEPTION HANDLER
;--------------------------------------------------------------------

                .align          8

;       Turn the exception into a call.  Add the signal value, and
;       account for the PC, and PSL arguments.

exe$resop:      pushl           #SS$_ROPRAND
                pushl           #3
                callg           sp, @#exe$signal
                moval           b^0C(sp), sp
                moval           @#exe$halt, (sp)
                rei

;--------------------------------------------------------------------
;   SOFTWARE INTERRUPT 2 (AST HANDLER)
;--------------------------------------------------------------------

                .align          8

exe$deliver_ast:nop

_next:          remque          @#exe$ast_list, r0
                bvs             _done
                pushl           r0
                pushl           r1
                pushl           r0
                calls           #1, @#exe$handle_ast
                blbc            r0, _err
                movl            -(sp), r1
                movl            -(sp), r0
                brb             _next

;   Handle errors in ast services if needed, and then pop R1 and R0
;   before getting out.

_err:           movl            -(sp), r1
                movl            -(sp), r0

_done:          rei

;--------------------------------------------------------------------
;   RESERVED ADDRESSING MODE EXCEPTION HANDLER
;--------------------------------------------------------------------

                .align          8

;       Turn the exception into a call.  Add the signal value, and
;       account for the  PC, and PSL arguments.

exe$resaddr:    pushl           #SS$_RADRMOD
                pushl           #3
                callg           sp, @#exe$signal
                moval           b^0C(sp), sp
                moval           @#exe$halt, (sp)
                rei

;--------------------------------------------------------------------
;   EXCEPTIONS LAND HERE AFTER FINISHING HANDLER
;--------------------------------------------------------------------

exe$halt:       halt
                halt

;--------------------------------------------------------------------
;   INTERVAL TIMER HANDLER
;--------------------------------------------------------------------

                .align          8
exe$interval:   incl            @#exe$int_count
                mtpr            #0FF, #VAX$PR_ICCS
                rei

;--------------------------------------------------------------------
;   GENERAL SIGNAL HANDLER
;--------------------------------------------------------------------

;       This is called when a hardware or software exception occurs.
;       At the time of the call, a varying number of arguments are
;       on the stack.  The last two are always the PC and PSL where
;       the fault occurred.  The first is always the exception.  Any
;       in between make up the signal vector.

                .entry          exe$signal, ^m<r2,r3>

                pushl           #0A
                pushl           #0d
                pushal          @#_sigmsg
                calls           #3, @#lib$put_output

                ret

_sigmsg:        .ascid          "An error has occurred."
       
;--------------------------------------------------------------------
;   CHMK SELECTOR DISPATCHER
;--------------------------------------------------------------------

;       Here's the real dispatcher.  It validates the kernel selector
;       value, and if it's okay, generates a call to an address stored
;       in the exe$table array.

                .entry          exe$dispatch, ^m<r2,r3>
                movl            b^4(ap), r2
                tstl            r2
                bgeq            _10
                movl            #SS$_INVARG, r0
                brb             exe$exit
_10:            cmpl            r2, @#exe$table_len
                blss            _20
                movl            #SS$_INVARG, r0
                brb             exe$exit
_20:            moval           @#exe$table, r3
                movl            (r3)[r2], r0
                calls           #0, (r0)

exe$exit:       ret

;       Selector #0 is the write byte to console function.

                .set            EXE$PUT_CONSOLE 0
                .entry          exe$$put_console

;       First, see if the device is ready or not.  This is set
;       by the ready interrupt service routine when a byte is
;       successsfully written.


_wait:          movl            @#exe$tx_ready, r0
                tstl            r0
                beql            _wait

;       Mark the device as busy, and write a byte to it.

                clrl            @#exe$tx_ready                              
                mtpr            r5, #VAX$PR_TXDB
                
                movl            #SS$_NORMAL, r0
                ret

;       This is a dummy service routine for selector #1

                .entry          exe$f2
                movl            #2, r0
                ret

;--------------------------------------------------------------------
;   CHMK SELECTOR FUNCTION DISPATCH TABLE
;--------------------------------------------------------------------

;       This is the dispatch table.  There must be a entry point
;       address in each slot that is in the table.

exe$table:      .long           exe$$put_console
                .long           exe$f2

;       Store the length in longwords of the table

exe$table_len:  .long         ( . - exe$table ) / 4


;--------------------------------------------------------------------
;   AST HANDLER
;--------------------------------------------------------------------

;       This is called by the AST interrupt handler to process an
;       item that had been queued up for AST delivery.  The single
;       parameter is the AST control block.  The first two longwords
;       are undefined (part of the AST queue block).  The remaining
;       data after the linkages are where the AST data resides.

                .entry          exe$handle_ast

                ret


;--------------------------------------------------------------------
;   KERNEL INITIALIZATION
;--------------------------------------------------------------------

;       This is called by the vax.init file after the kernel is built.
;       This completes the runtime initialization of the system.  Put
;       things here that you want to have built by the VAX as opposed
;       to the assembler.  You must GO this code, not CALL it because
;       the console CALL code will screw up the registers on the RET
;       instruction.


exe$initialize: tstl            @#exe$init_done ; If we've already done
                bneq            exe$init_exit   ; this, just return

;       Set the bit in the TXCS register that says to interrupt
;       us when the data is successfully written.  This will cause
;       the EXE$TX interrupt handler to be hit again, which resets 
;       our internal ready flag at exe$tx_ready.

                mtpr            #60,#VAX$PR_TXCS

;       We want an interval timer interrupt every 500 instructions
;       or so.  Currently the ICR is based on instructions, not real
;       time (this is a must-fix for later).

                mcoml           #^d500, r0
                mtpr            r0, #VAX$PR_NICR

;       Now that NICR is set up, turn on interrupts, and reload the
;       ICR from the NICR.

                mtpr            #0ff,#VAX$PR_ICCS
                
;       Mark the flag that says we're done

                movl            #1, @#exe$init_done

;       We've got to get out of kernel mode.  To do this at runtime,
;       we fake out a REI instruction.  Push the desired PSL and PC
;       on the stack and do an REI.  This will take us to the exit
;       point, in user mode with IPL of zero (so interrupts will
;       start happening).

                pushl           #03000000       ; desired PSL
                pushal          @#exe$init_exit ; desired PC
                rei                             ; do it

;       This is the exit point from the initialization routine.

exe$init_exit:  pushl           #0a
                pushl           #0d
                pushal          @#exe$init_msg
                calls           #3, @#LIB$PUT_OUTPUT
                halt

;       Let's protect the dispatcher so that you can't even read/see it
;       unless you are in kernel mode.  This keeps prying eyes out of 
;       the system.  But, we can't do it yet because doing so would prevent
;       forward references to fix up in the assembler.  So, save where we 
;       are and use it later.

                .set            exe$end .

 
;       Because of that, we need to be sure to start on a new boundary.

                .align          0200
                .set            exe$ubase .

;--------------------------------------------------------------------
;   USER MODE FRONT-END TO SYSTEM SERVICES
;--------------------------------------------------------------------

;
;       Write a list of items, passed as parameters via standard calling
;       conventions.  Each item is passed to lib$put_output() as a single
;       item for output.
;

                .entry			lib$put_output, ^m<r3,r4>
                movl		    (ap), r3                ; Count of arguments
                tstl			r3                      ; Are there any?
                beql			_exit                   ; No, we're done
                addl3			#4, ap, r4              ; Yes, find first one

_loop:          pushl			(r4)+                   ; push the parameter
                calls			#1, @#lib$put_one       ; call output
                sobgtr			r3, _loop               ; loop if more
                movl			#1, r0                  ; else done
                ret

_exit:          movl			#1, r0
                ret

;               LIB$PUT_ONE( struct dsc$descriptor_s * msg );

                .entry          lib$put_one, ^m<r2,r3,r4,r5,r6>

                movl            b^4(ap), r5          ; Get descriptor address

                cmpl            r5, #0ff             ; see if it's one char.
                blequ           _byte                ; if so, print it

                clrl            r6                   ; Empty counter register
                movw            (r5), r6             ; Get count word from desc
                beql            _exit                ; If zero, then no work
                movl            b^4(r5), r2          ; Else get address
                clrl            r4                   ; Clear data register

_loop:          movb            (r2)+, r5            ; Get byte, advance ptr
                chmk            #EXE$PUT_CONSOLE     ; Output single byte
                blbc            r0, _errexit         ; If bad RC, done
                sobgtr          r6, _loop            ; Decrement counter, rpt
                brb             _exit

_byte:          chmk            #EXE$PUT_CONSOLE     ; Output single byte in R5
                blbc            r0, _errexit         ; If bad RC, done

_exit:          movl            #SS$_NORMAL, r0      ; Signal success
_errexit:       ret                                  ; And flee


;               len = LIB$FORMAT_DEC( long value, char * buffer, long *size );

                .entry  lib$format_dec, ^m<r2,r3,r4,r5,r6,r7,r8, r9>

;
;   We're going to allocate some stack space.  So move the SP down to
;   prepare room for it.

                subl2   #4,sp

                movl    b^0C(ap), r9            ; Get length pointer

                movl    b^4(ap), r2             ; Get the value to format
                tstl    r2                      ; Is it zero?

                beql    _zero                   ; If so, special case

;
;   Allocate some "automatic storage" on the stack by marking where we
;   are (since we want the end of the buffer anyway) and move the SP
;   down in case we need it for something else.
;

                movl    sp,r6                   ; Point to end of space
                subl2   r9, sp                  ; and move end of stack down
                                                ; by size of user's buffer

                clrl    r7                      ; init the counter

_loop:          divl3   #^d10, r2, r3           ; Shift off digit
                mull3   r3, #^d10, r4           ; And back again
                subl3   r4, r2, r5              ; Difference is digit
                addl2   #30, r5                 ; Make ASCII

                movb    r5, -(r6)               ; copy to buffer, decrement
                incl    r7                      ; and increment counter
                cmpl    r7, r9                  ; too big?
                bgtr    _oflow                  ; yes, bail out

                tstl   r4                       ; Is there more?
                beql   _exit                    ; No, done

                movl   r3, r2                   ; Yes, set it up
                brb    _loop                    ; And loop again

_oflow:         movl   #SS$_BUFFEROVF, r0       ; Return overflow
                ret

_zero:          movl   sp, r6                   ; Get address of stack buffer
                movb   #30, (r6)                ; Write "0" to buffer
                movl   #1, r7                   ; return length of 1

_exit:          movl   b^8(ap), r8              ; Get address of buffer
                movc3  r7, (r6), (r8)           ; Copy to user's buffer
                movl   b^0c(ap),r8
                movl   r7, (r8)                 ; and return the length
                movl   #SS$_NORMAL, r0          ; signal success
                ret                             ; Go home.


                .set    exe$uend    .

;--------------------------------------------------------------------
;   WRITABLE STORAGE AREA FOR KERNEL
;--------------------------------------------------------------------

;       This is the kernel writable data area.  It has different page
;       attributes so it must be aligned on a new page boundary.

                .align          0200
                .set            exe$wbase .
                
exe$tx_ready:   .long           1               ; Is TXCS available?
exe$init_done:  .long           0               ; Is kernel initialized?
exe$int_count:  .long           0				; Interval timer count

exe$ast_list:	.long			exe$ast_list	; Head of AST queue
				.long			exe$ast_list

;       Message descriptors

exe$init_msgb:  .ascii          "Kernel initialized..."
                .byte           0a,0d
exe$init_msg:   .long           . - exe$init_msgb
                .long           exe$init_msgb   

                .set            exe$wend .

;--------------------------------------------------------------------
;   RESET STATE FOR CONSOLE ASSEMBLER
;--------------------------------------------------------------------

;       This section takes care of miscellanous housekeeping.
;       Define the exception handlers in the SCB and make the 
;       entire kernel area protected from being written on.

                .scb            exc$chmk,      exe$chmk
                .scb            exc$accvio,    exe$accvio
                .scb            exc$resop,     exe$resop
                .scb            exc$resaddr,   exe$resaddr
                .scb            exc$interval,  exe$interval
                .scb			exc$software2, exe$deliver_ast
                .scb            exc$conwrite,  exe$tx

;       Set page protections on the segments of the kernel.  This MUST
;       be done after all symbol fixups are generated, because the
;       microassembler is forced to abide by page table protections.

                .console        set page exe$ubase to exe$uend prot=pte$k_ur
                .console        set page exe$base to exe$end prot=pte$k_ur
                .console        set page exe$wbase to exe$wend prot=pte$k_kwur
                

;       All done, let's set the console back so user assembly can
;       occur normally

                .region         p0
