ervin@pinbot.enet.dec.com (Joseph James Ervin) (05/16/91)
The initial post of this source code has a bug in the GET_CUR_TIME routine.
This post is the one I should have sent out the first time. Sorry for the
inconvenience.
>>>Joe Ervin
****************************************CUT HERE*******************************
; MPE V1.0. Copyright Joseph James Ervin 1991.
; The following is a simple multiprogramming environment (MPE) which allows
; users to program concurrent processes to run on the HP48SX.
; Addresses of Rom routines
save_registers = ^x679B ; Saves system registers. Uses C, D0.
restore_registers = ^x67d2 ; Restores system register.
rplcont = ^x71BE ; Jumps to the next RPL instruction.
rr_rplcont = ^x5143 ; Restores the system registers and
; then does rplcont.
get_short = ^x6641 ; Pops the short of TOS and returns it
; into A.A
push_a_cont = ^x357C ; Push the contents of A.A onto stack
; and continue RPL.
push_r0_short = ^x6537 ; Push R0 as new system binary. This
; routine uses the SAVED system
ROM_GET_TICKS = ^x130E ; Returns the number of timer ticks
; in C.13. Trashes just about every
; other register.
radix ^d10
; General address definitions
bos = ^x7057E ; Pointer to beginning of the stack.
pict_pointer = ^x70565 ; Pointer to PICT grob.
menu_pointer = ^x70556 ; Pointer to Stack Grob.
keybuf = ^x704ea ; keyboard buffer.
; ***************************************************************************
; The following macro is used to generate a process header and to allocate
; storage for the process context. The macro requires as its only parameter
; the ID of the process for which the process header is to be generated.
macro gen_headers n=0, label
save n
n = $n
if (n==0) ; This is for the scheduler.
$(label)_context:
DATA.W 0, 0 ; Storage for B, D.
DATA.A 0, 0 ; Storage for D0, D1.
DATA.W 0, 0, 0, 0, 0 ; Storage for R0, R1, R2, R3, R4
$(label)_header: data.a 0, 0
else ; If not for scheduler.
if def process$(n)_init
; The user got it right and defined the _init section of the
; process. Now we need to allocate storage for the context area and build the
; process header.
$(label)_context:
DATA.W 0, 0 ; Storage for B, D.
DATA.A 0, 0 ; Storage for D0, D1.
DATA.W 0, 0, 0, 0, 0 ; Storage for R0, R1, R2, R3, R4
$(label)_header: data.a 0, 0
endif
endif
restore n
endmacro
hide gen_headers
; ***************************************************************************
; The following macro is used to generate the process descriptor list used
; by the scheduler. This macro checks to see that the processes declared by
; the user have contiguous process IDs. If not, then an error message is
; generated.
macro gen_descriptor n
save n
n = $n
if (n == 0)
DESCRIPTOR_LIST: DATA.A scheduler_header ; Process descriptors.
contig = 1
num_of_proc = 0
endif
if (!def process$(n)_init && !(n==0))
contig = 0
endif
if def process$(n)_init
num_of_proc = (num_of_proc + 1)
if (contig==0)
error Defined processes are not contiguously numbered.
endif
; The user got it right and defined both the _init and _code sections of the
; process. The process context storage and header should already have been
; set up, so now we need to add the entry for the process descriptor.
DATA.A process$(n)_header ; Process descriptor.
endif
restore n
endmacro
hide gen_descriptor
; ***************************************************************************
; The following macro is used to make the relative addresses stored into the
; process header into absolute addresses.
macro header_init n
save n, dest, src1, src2
n = $n
if ((def process$(n)_init) || (n==0))
; The user has defined this process, so we need to write the address pointers
; in the process header.
if (n==0)
dest = scheduler_header
src1 = scheduler_context
src2 = scheduler_code
else
dest = process$(n)_header
src1 = process$(n)_context
src2 = process$(n)_init
endif
addr $dest, D0 ; Point D0 to the process (scheduler) header.
addr $src1, c ; Get address of context.
move.a c, @d0
addr $src2, c
add.a ^d5, d0
move.a c, @d0
endif
restore n, dest, src1, src2
endmacro
hide header_init
; ***************************************************************************
macro fill n
save n
n = $n
if def process$(n)_init
addr process$(n)_header, c
move.a c, @d0 ; Fill in the process descriptor.
add.a 5, d0 ; D0 now points to the next process descriptor.
endif
restore n
endmacro
hide fill
; The following macro writes the address pointers in the process descriptor
; list.
macro descriptor_init
; We know that the scheduler process exists, so we can fill in that
; descriptor.
addr descriptor_list, d0 ; First, point d0 at the descriptors.
addr scheduler_header, c ; Get the scheduler descriptor.
move.a c, @d0 ; Fill in scheduler descriptor.
add.a 5, d0 ; D0 now points to the next process descriptor.
; Now we want to sequence through the processes. For each process, if the
; user has defined the process, then we need to fill in the descriptor.
fill 1 ; fill in descriptors for the 20 processes.
fill 2
fill 3
fill 4
fill 5
fill 6
fill 7
fill 8
fill 9
fill 10
fill 11
fill 12
fill 13
fill 14
fill 15
fill 16
fill 17
fill 18
fill 19
fill 20
endmacro
hide descriptor_init
; ****************************************************************************
; The following macro enables the user to easily configure a process to start
; at the INIT or CODE sections. The first argument is an integer specifying
; the process to be reconfigured. The second argument is the label of the new
; entry point.
macro process_start n, where
addr process$(n)_header, d0 ; Point d0 at the process header.
add.a 5, d0 ; Move down to the code pointer.
addr $where, c
move.a c, @d0 ; Reconfigures process "n" to run from "where"
; the next time it comes due.
endmacro
hide process_start
; ****************************************************************************
; The following macro enables the user to easily configure a process to start
; at a new label. This macro takes no arguments, but expects that C.A contains
; a pointer to the new entry point. This macro then
; automatically changes the entry point of the current process to
; the address in C.A. It is expected that the process would load C.A with the
; address label, possibly with the ADDR macro. The purpose behind this is to
; allow the programmer to easily redirect the entry point of current process
; without requiring the use of the IF_PROC/ENDIF_PROC construct. This is
; particularly advantageous when many processes share a section of code.
macro cur_process_start
PUSH.A C ; Save the pointer to the new entry point.
CALL GET_CURRENT_ID ; Get the process ID in A.B. Trashes C.
CLR.W C ;
MOVE.B A, C ;
MOVE.W C, A ; Zero out upper bits of A.
ADD.A C, C ;
ADD.A C, C ;
ADD.A A, C ; ...and multiply by 5.
; C.A now contains the offset from beginning of the process descriptor list.
ADDR DESCRIPTOR_LIST, D0 ; Address of descriptor list. Trashes A.
SWAP.A A, D0
ADD.A C, A ; Add in the offset. D0 points at process descriptor.
SWAP.A A, D0
MOVE.A @D0, C ; Get the descriptor (pointer to the process header).
MOVE.A C, D0 ; Point D0 to the process header.
add.a 5, d0 ; Move down to the code pointer.
POP.A C ; Pop the new entry point back into C.
move.a c, @d0 ; Reconfigures the current process to start at the
; new entry point the next time it comes due.
endmacro
hide cur_process_start
;**************************************************************************
; The following macro is used by the MPE programmer to execute a certain piece
; of code conditioned on whether a specified process is current.
macro if_proc n
n=$n
; Do the macro.
save nosym
nosym = gensym
swap.a c, d0 ; Save D0 in C.A
addr current_context_id, d0 ; Point to current context id variable.
move.b @d0, a ; Get the current context id.
swap.a c, d0 ; Restore old D0.
move.p2 $(n), c ;
brne.b c, a, $nosym ; Skip the code if specified process is not
; current.
endmacro
hide if_proc
macro endif_proc
$NOSYM: ; Execution jumps to here if the specified process was not current.
restore nosym
endmacro
hide endif_proc
header
code
; Descriptions of global RAM routines
; ADD_PROCESS ; Arguments: A.A contains the address of the process
; header of the process to be added.
; C.W contains the execution time.
; RUN_IT ; Jumped-to from the SCHEDULER. Runs the process
; whos ID is given in A.B.
; SAVE_CONTEXT ; Saves the context of the current process, as
; indicated by the CURRENT_CONTEXT_ID variable.
; RESTORE_CONTEXT ; Restores the context of the process whos ID is given
; in A.B. This routine also leaves a pointer to the
; code of the new process in C.A, so the calling
; process can either jump to the new process, or just
; use RESTORE_CONTEXT to copy the context of another
; process to itself without actually transfering
; execution to the new process.
jump START ; Skip over all the global variables below and go
; straight into the initialization code.
; ****************************************************************************
; The following state variables are intended to be used with the
; SAVE_STATE and RESTORE_STATE routines. These routines save the system
; registers into the following global state variables. A and C are never
; saved or restored. SAVE_STATE trashes A and C. RESTORE_STATE trashes A, but
; preserves C.
STATE_DATA1: DATA.W 0, 0
DATA.5 0, 0 ; Scratch storage for B, D, D0, D1
; This storage area can be used by each process
; whenever some scratch storage is needed. This
; storage is used by the SAVE_STATE and
; RESTORE_STATE routines.
; ****************************************************************************
; ****************************************************************************
; The following variable is used to hold the global time variable.
CUR_TIME: DATA.W 0 ; Global variable to hold current time.
; Set by scheduler. Read by processes.
; ***************************************************************************
; ***************************************************************************
; The following storage areas are to hold the register context for each
; process running in the system.
CURRENT_CONTEXT_ID: DATA.B 0 ; Indicates the ID of the processor context
; we are currently in. This variable will be
; used by the SAVE_CONTEXT and RESTORE_CONTEXT
; routines. A zero identifies the scheduler.
; The other processes are numbered 1-255.
;***************************************************************************
; The following is the memory area where the schedulers run list is kept.
; Each entry consists of a one BYTE process ID (1-255; 0 is reserved for the
; scheduler), followed by a 13 nibble time value which represents when this
; process should be run. The scheduler will step down this list, comparing the
; current time value stored in the CUR_TIME variable to the execution time of
; each process in the run list. When a process comes due, the scheduler will
; call the SWAP_CONTEXT routine which will swap in the context of the due
; process and then jump to the address given in the process header.
RUN_LIST_SIZE = ^d10 ; There are 10 process slots.
RUN_LIST: DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
DATA.B 0 ; ID of a process.
DATA.W 0 ; Execution time of process.
; *************************************************************************
; *************************************************************************
gen_headers 0, scheduler ; Create scheduler header.
gen_headers 1, process1 ; Create process headers.
gen_headers 2, process2
gen_headers 3, process3
gen_headers 4, process4
gen_headers 5, process5
gen_headers 6, process6
gen_headers 7, process7
gen_headers 8, process8
gen_headers 9, process9
gen_headers 10, process10
gen_headers 11, process11
gen_headers 12, process12
gen_headers 13, process13
gen_headers 14, process14
gen_headers 15, process15
gen_headers 16, process16
gen_headers 17, process17
gen_headers 18, process18
gen_headers 19, process19
gen_headers 20, process20
gen_descriptor 0
gen_descriptor 1
gen_descriptor 2
gen_descriptor 3
gen_descriptor 4
gen_descriptor 5
gen_descriptor 6
gen_descriptor 7
gen_descriptor 8
gen_descriptor 9
gen_descriptor 10
gen_descriptor 11
gen_descriptor 12
gen_descriptor 13
gen_descriptor 14
gen_descriptor 15
gen_descriptor 16
gen_descriptor 17
gen_descriptor 18
gen_descriptor 19
gen_descriptor 20
START:
; move.a pc, a ; debug
; move.a a, r0
; call save_registers
; call restore_registers
; call push_r0_short
; call save_registers
call save_registers
header_init 0 ; Fill in the scheduler header.
header_init 1 ; Fill in the process headers.
header_init 2
header_init 3
header_init 4
header_init 5
header_init 6
header_init 7
header_init 8
header_init 9
header_init 10
header_init 11
header_init 12
header_init 13
header_init 14
header_init 15
header_init 16
header_init 17
header_init 18
header_init 19
header_init 20
; Now fill in the address pointers in the process descriptor list.
descriptor_init
; Now we need to clear out the RUN_LIST so we know we are starting with
; a clean slate.
move.p5 run_list_size, c ; Number of entries.
addr run_list, d0 ; Point at top of RUN_LIST. Trashes A.
CLR.W A
CLR_RUN_LIST:
MOVE.B A, @D0
ADD.A ^d2, d0
add.a ^d16, d0
dec.a c
brnz.a c, clr_run_list ; Clear out all the process IDs from RUN_LIST.
; Now we need to make the scheduler the current process.
ADDR CURRENT_CONTEXT_ID, D0
CLR.W A
MOVE.B A, @D0 ; Make scheduler the current context.
; ****************************************************************************
; NOTES for SCHEDULER:
; R0 ; Index into RUN_LIST (slot #), numbered 0 through N-1.
; R1 ; Copy of the ID of the new process to be run.
; R2 ;
; R3 ;
; R4 ;
; D0 ;
; D1 ; Address Pointer into RUN_LIST.
; B ; Contains a local copy of the CUR_TIME variable.
; D ; Number of process slots in RUN_LIST.
; Now we need to start up the scheduler. The scheduler will step down the run
; list, running each process as it comes due. The process header
; will initially point to the init code for the process. At the end of the
; initialization phase, the user may mofify the process header to
; point at the body of the process, where the real work is done.
CALL GET_TICKS ; Get the time into C.13.
MOVE.P2 ^d1, A ; Process ID.
CALL ADD_PROCESS ; Schedule process1 for immediate execution.
MOVE.P2 RUN_LIST_SIZE, A ; Number of process slots in RUN_LIST.
DEC.B A ; Decrement this number by 1; 0 to (N-1) limit.
MOVE.B A, C ;
MOVE.B C, D ; Keep RUN_LIST_SIZE in D.
CLR.W A ;
MOVE.W A, R0 ; R0 holds current slot pointer.
ADDR RUN_LIST, C ; Get the address of RUN_LIST into C.
SWAP C, D1 ; Initialize RUN_LIST index to top of RUN_LIST.
ADDR CUR_TIME, D0 ; Put the address of the CUR_TIME variable
; in D0. Trashes A.
CALL GET_TICKS ; Puts the value of ticks in C.13. Trashes A.
MOVE.W C, @D0 ; Update the value of the CUR_TIME variable.
MOVE.W C, B ; Put a copy in B. This is the local
; copy for the scheduler.
; Now we need to step down the run list, checking the run time of each slot
; which has a nonzero ID against the current time. I will keep the run_list
; slot count in R0. Whenever the scheduler is entered from the SCHEDULER entry
; point, the scheduler will pick up at the point in the run list where it left
; off. For example, if the scheduler runs the process in slot #5, then when
; that process completes and returns to the SCHEDULER entry point, the next
; slot to be checked against the clock will be slot #6.
SCHEDULER_CODE:
; This is the loop that scans the run list and runs the processes.
; This is the point where execution will return when a process completes.
; First, the scheduler needs to capture the current time, which will be used
; to determine whether a given process is due.
CLR.W A ; Clear upper bits of A.
SCHED: MOVE.B @D1, A ; Get the process ID of this slot.
BRNZ.B A, GOOD_ID ; Is this a real process ID or is it zero?
CALL NEXT_SLOT ; Else point to the next slot in the RUN_LIST.
JUMP SCHEDULER_CODE ; Keep scanning the RUN_LIST for due processes.
GOOD_ID: ; Check the time and run the process if it is due.
ADD.A ^d2, D1 ; Point D1 at the execution time of this slot.
MOVE.W @D1, C ; Get the execution time for this process.
SUB.A ^d2, D1 ; Point back at ID; Required by NEXT_SLOT.
BRGE.W B, C, RUN_IT ; Run the process if it has come due.
; The process ID is in A.B.
CALL NEXT_SLOT ; Point to the next slot in the RUN_LIST.
JUMP SCHEDULER_CODE ; Keep scanning the RUN_LIST for due processes.
; ****************************************************************************
NEXT_SLOT: ; Point to the next process in the RUN_LIST.
; This is the routine that advances the RUN_LIST slot counter (R0) and address
; pointer (D1) to the next slot in a wraparound fashion.
ADD.A 2, D1 ;
ADD.A 16, D1 ; Point D1 to the next ID in the RUN_LIST.
MOVE.B R0, A ; Get the RUN_LIST slot counter.
INC.B A ; Indicate the next process slot.
MOVE.B A, R0 ; Update R0 with the new slot count.
MOVE.B D, C ; Get the RUN_LIST index limit.
BRGE.B C, A, DONE_NEXT_SLOT ; If not end of list, then return.
; We have reached the end of the RUN_LIST, so we need to reset the RUN_LIST
; slot counter in R0 and the memory pointer in D1.
CALL GET_TICKS ; Puts the value of ticks in C.13. Trashes A.
MOVE.W C, B ; Put a copy in B. This is the local
; copy for the scheduler.
CLR.B A
MOVE.B A, R0 ; Clear out the slot counter in R0.
ADDR RUN_LIST, C ; Get the address of RUN_LIST into C.
SWAP C, D1 ; Initialize RUN_LIST index to top of RUN_LIST.
DONE_NEXT_SLOT:
RETCLRC
; ****************************************************************************
RUN_IT: ; Run the process pointed to by A.B.
; This is the routine that runs the process pointed to by A.B. This is done
; by getting the process header pointer from the process descriptor list.
; The process header contains pointers to the process context and to the code
; for the process. The process context is first restored, and then execution
; jumps to the process code.
MOVE.B A, R1 ; Save the new process ID for now.
ADDR CUR_TIME, D0 ; Put the address of the CUR_TIME variable
; in D0. Trashes A.
MOVE.W B, C ; Time that was compared to time stamp.
MOVE.W C, @D0 ; Update the value of the CUR_TIME variable.
CLR.W A
MOVE.B A, @D1 ; Write a zero over the process ID in the
; RUN_LIST, thus deleting the process from the
; RUN_LIST.
CALL NEXT_SLOT ; Point SCHEDULER to next slot in the RUN_LIST.
CALL SAVE_CONTEXT ; Uses CURRENT_CONTEXT_ID as a process ID to
; save the current register context to the
; appropriate place. Does not save A or C.
ADDR CURRENT_CONTEXT_ID, D0 ; Point D0 at curr context variable.
; Trashes A.
MOVE.B R1, A ; Restore the new process ID.
MOVE.B A, @D0 ; Store the new process ID into the
; CURRENT_CONTEXT_ID variable.
CALL RESTORE_CONTEXT ; Restores the context of the new process, and
; leaves a pointer to the new process code in
; C.A.
JUMP C ; Jump to new process code.
; ****************************************************************************
RESTORE_CONTEXT:
; This routine uses the context ID in A.B and restores that process context.
; The pointer to the code for the new process is left in C.A.
CLR.W C ;
MOVE.B A, C ;
MOVE.W C, A ; Zero out upper bits of A.
ADD.A C, C ;
ADD.A C, C ;
ADD.A A, C ; ...and multiply by 5.
; C.A now contains the offset from beginning of the process descriptor list.
ADDR DESCRIPTOR_LIST, D0 ; Address of descriptor list. Trashes A.
SWAP.A A, D0
ADD.A C, A ; Add in the offset. D0 points at process descriptor.
SWAP.A A, D0
MOVE.A @D0, C ; Get the descriptor (pointer to the process header).
; Now that we have the descriptor (a pointer to the process header), we need
; to fetch the context pointer, restore the context, and then leave a pointer
; to the process code in C.A.
MOVE.A C, D0 ; Point D0 to the process header.
MOVE.A @D0, A ; Get the address of the process context.
ADD.A ^d5, D0 ; Point D0 to the code pointer.
MOVE.A @D0, C ; Get the code pointer.
PUSH.A C ; Save the code pointer on the stack for now.
MOVE.A A, D0 ; Point D0 to the context area.
MOVE.W @D0, C ;
MOVE.W C, B ; Restore B.
ADD.A ^d16, D0
MOVE.W @D0, C ;
MOVE.W C, D ; Restore D.
ADD.A ^d16, D0
MOVE.A @D0, C ; Restore D0 (in C for now).
ADD.A ^d5, D0
SWAP.A D1, C ; Save D0 contents into D1 for now.
MOVE.A @D0, C ; Get D1 contents into C.
SWAP.A D1, C ; Restore D1. C holds restored D0 contents.
ADD.A ^d5, D0
MOVE.W @D0, A ;
MOVE.W A, R0 ; Restore R0.
ADD.A ^d16, D0
MOVE.W @D0, A ;
MOVE.W A, R1 ; Restore R1.
ADD.A ^d16, D0
MOVE.W @D0, A ;
MOVE.W A, R2 ; Restore R2.
ADD.A ^d16, D0
MOVE.W @D0, A ;
MOVE.W A, R3 ; Restore R3.
ADD.A ^d16, D0
MOVE.W @D0, A ;
MOVE.W A, R4 ; Restore R4.
SWAP.A C, D0 ; Restore D0.
POP.A C ; Pop the pointer to the process code in C.
RET ; ...and return.
; ****************************************************************************
SAVE_CONTEXT:
; This routine uses the context ID in CURRENT_CONTEXT_ID and saves that
; process context.
; Note that this routine makes sure to not alter the states of the system
; registers or the general purpose registers in the process of saving them.
; Note also that A and C are not saved. The contents of B, D, D0, D1, and
; R0-R4 are preserved by this routine.
CALL GET_CURRENT_ID ; Get CURRENT_CONTEXT_ID into A.B
SAVE_CONTEXT_BY_A: ; This entry point allows the process to save the
; current context to that of the process given in A.B.
CLR.W C ;
MOVE.B A, C ;
MOVE.W C, A ; Zero out upper bits of A.
ADD.A C, C ;
ADD.A C, C ;
ADD.A A, C ; ...and multiply by 5 to give an offset into the
; descriptor list.
; C.A now contains the offset from beginning of the process descriptor list.
SWAP.A A, D0 ; Get a copy of D0 into A.
SWAP.A A, C ; Do not lose the descriptor offset (save it in A).
PUSH.A C ; Save old D0 on the return stack.
SWAP.A A, C ; Restore the descriptor offset into C.
ADDR DESCRIPTOR_LIST, D0 ; Address of descriptor list. Trashes A.
SWAP.A A, D0 ; Put pointer to descriptor list into A.
ADD.A C, A ; Add in the offset.
MOVE.A A, D0 ; D0 points at process descriptor.
MOVE.A @D0, C ; Get the descriptor (pointer to the process header).
; Now that we have the descriptor (a pointer to the process header), we need
; to fetch the context pointer and save the process context.
MOVE.A C, D0 ; Point D0 to the process header.
MOVE.A @D0, C ; Get the address of the context.
MOVE.A C, D0 ; Point D0 to the context area.
MOVE.W B, A ;
MOVE.W A, @D0 ; Save B.
ADD.A ^d16, D0
SWAP.W D, C
MOVE.W C, @D0 ; Save D.
SWAP.W D, C
ADD.A ^d16, D0
POP.A C ; Recover the old D0 (pushed previously).
MOVE.A C, @D0 ; Save D0. Leave copy of D0 in C.
ADD.A ^d5, D0
SWAP.A D1, C ;
MOVE.A C, @D0 ; Save D1.
SWAP.A D1, C ; Restore D1. copy of old D0 still in C.
ADD.A ^d5, D0
MOVE.W R0, A ;
MOVE.W A, @D0 ; Save R0.
ADD.A ^d16, D0
MOVE.W R1, A ;
MOVE.W A, @D0 ; Save R1.
ADD.A ^d16, D0
MOVE.W R2, A ;
MOVE.W A, @D0 ; Save R2.
ADD.A ^d16, D0
MOVE.W R3, A ;
MOVE.W A, @D0 ; Save R3.
ADD.A ^d16, D0
MOVE.W R4, A ;
MOVE.W A, @D0 ; Save R4.
SWAP.A C, D0 ; Restore D0.
RET ; Clear carry bit and return.
; ****************************************************************************
ADD_PROCESS:
; This routine will add a process to the process list. The basic method used
; is simply to insert the new process ID and execution time into the first
; empty slot (defined as one which has a zero for its process ID).
; The process ID is expected in A.B, and the process execution time is expected
; in C.13.
;
; Note: This routine changes many of the system registers, as well as the
; general purpose registers. For this reason, it is highly recommended
; that the calling routine do a SAVE_CONTEXT before executing this
; routine, followed immediately by a RESTORE_CONTEXT or a jump to the
; scheduler.
MOVE.B A, R2 ; Save the new process ID.
BRZ.B A, BAD_PROC_ID ; Zero is not a valid process ID.
MOVE.W C, R3 ; Save the execution time of the new process.
MOVE.P2 NUM_OF_PROC, C ; Get the number of defined processes.
BRGE.B C, A, J_VPI ; Did the user pass in a valid ID?
JUMP BAD_PROC_ID ; If not then output the error information.
J_VPI: JUMP VALID_PROC_ID ; Else schedule the process.
BAD_PROC_ID: CALL RESTORE_REGISTERS
MOVE.P5 ^d98, C
MOVE.A C, R0
CALL PUSH_R0_SHORT ; Push an error code of 98 to indicate that
; a process tried to schedule an invalid ID.
CALL SAVE_REGISTERS
ADDR CURRENT_CONTEXT_ID, D0
CLR.W C
MOVE.B @D0, C
MOVE.W C, R0 ; Push the ID of the process which
CALL RESTORE_REGISTERS ; caused the error.
CALL PUSH_R0_SHORT
CALL SAVE_REGISTERS
CLR.W A
MOVE.B R2, A
MOVE.A A, R0
CALL RESTORE_REGISTERS
CALL PUSH_R0_SHORT ; Push the erroneous ID that the current
CALL SAVE_REGISTERS ; process tried to schedule.
JUMP RR_RPLCONT
VALID_PROC_ID:
CLR.W C
MOVE.P2 RUN_LIST_SIZE, C ; Number of process slots in RUN_LIST.
DEC.B C ; Decrement this number by 1; 0 to (N-1) limit.
MOVE.W C, D ; Keep RUN_LIST_SIZE limit in D.
CLR.W A ;
MOVE.W A, R0 ; R0 holds current slot pointer.
ADDR RUN_LIST, C ; Get the address of RUN_LIST into C.
SWAP.A C, D1 ; Initialize RUN_LIST index to top of RUN_LIST.
ADD_PROCESS_LOOP:
MOVE.B @D1, A ; Check for an empty slot.
BRZ.B A, FOUND_EMPTY ;
CALL NEXT_SLOT ; Look at the next slot.
MOVE.B R0, A ; What slot are we looking at?
BRNZ.B A, ADD_PROCESS_LOOP ; Fall through the loop when every slot
; is non-empty.
; If we fall through this loop, then we are in a serious error scenario,
; because the process table has filled up and there are no empty slots.
; This is very bad.
move.p5 ^d99, a ; Error code for run_list overflow.
ERROR: MOVE.A A, R0
CALL RESTORE_REGISTERS
CALL PUSH_R0_SHORT ; Push ^d99 error code to indicate a run-list
CALL SAVE_REGISTERS ; overflow.
ADDR CURRENT_CONTEXT_ID, D0
CLR.W C
MOVE.B @D0, C
MOVE.W C, R0 ; Push the ID of the process which
CALL RESTORE_REGISTERS ; caused the error.
CALL PUSH_R0_SHORT
CALL SAVE_REGISTERS
JUMP RR_RPLCONT ; Push the error code onto the stack and
; kill the program.
FOUND_EMPTY: ; We have found an empty slot! D1 should be pointing to the
; empty slot, so all we have to do is fill it in.
MOVE.B R2, A ; Get the process ID.
MOVE.B A, @D1 ; Write the ID into the slot.
ADD.A ^d2, D1 ; Point D1 at the execution time.
MOVE.W R3, C ; Get the process execution time.
MOVE.W C, @D1 ; Write the execution time into the slot.
RET
; ****************************************************************************
TO_SCHEDULER:
; This routine is used by processes to tell the system to swap back to the
; scheduler. This routine just restores the scheduler context and jumps to
; the scheduler. It is up to each process to save its own context.
ADDR CURRENT_CONTEXT_ID, D0
CLR.W A
MOVE.B A, @D0 ; Make scheduler the current context.
CALL RESTORE_CONTEXT ; Restore the context of the scheduler.
JUMP C ; Jump to the scheduler.
; ****************************************************************************
SAVE_STATE: SWAP C, D0 ; Save D0 in C for now. Trashes C.
ADDR STATE_DATA1, D0 ; Point to STATE_DATA. Trashes A.
MOVE.W B, A ;
MOVE.W A, @D0 ; Save B.
ADD 16, D0
SWAP.W D, C
MOVE.W C, A ;
SWAP.W D, C
MOVE.W A, @D0 ; Save D.
ADD 16, D0
MOVE.A C, @D0 ; Save D0 (copied to C previously).
ADD 5, D0
SWAP D1, C
MOVE.A C, @D0 ; Save D1.
SWAP D1, C ; Restore D1.
SWAP C, D0 ; Restore D0.
RETCLRC ; Clear carry bit and return.
RESTORE_STATE:
ADDR STATE_DATA1, D0 ; Point to STATE_DATA. Trashes A.
MOVE.W C, A ; Save C in A for now.
MOVE.W @D0, C ;
MOVE.W C, B ; Restore B.
ADD 16, D0
MOVE.W @D0, C ; Restore D.
MOVE.W C, D ; Restore B.
ADD 16, D0
MOVE.A @D0, C ; Restore D0 (in C for now).
ADD 5, D0
SWAP.A D1, C ; Save D0 contents into D1 for now.
MOVE.A @D0, C ; Get D1 contents into C.
SWAP D1, C ; Restore D1.
SWAP C, D0 ; Restore D0.
MOVE.W A, C ; Preserve the value of C saved at top.
RETCLRC ; Clear carry bit and return.
GET_TICKS:
CALL SAVE_STATE ; Save system registers.
CALL ROM_GET_TICKS ; Get the ticks value from hardware timer.
CALL RESTORE_STATE ; Restore system registers. Trashes A.
RET ; Ticks is returned in C.13.
GET_CURRENT_ID:
ADDR CURRENT_CONTEXT_ID, C
SWAP.A C, D0 ; Point D0 at CURRENT_CONTEXT_ID.
MOVE.B @D0, A
SWAP.A C, D0 ; Restore D0.
RET
GET_CUR_TIME:
ADDR CUR_TIME, C
SWAP.A C, D0 ; Point D0 at CUR_TIME.
MOVE.W @D0, A
SWAP.A C, D0 ; Restore D0.
RET
; ****************************************************************************
; This is where the definition of the multiprocessing environment ends.
; ****************************************************************************
;
; ****************************************************************************
; ****************************************************************************
; THE PROCESS DEFINITIONS START HERE.
; The code below comprises the process definitions for 7 processes. Refer to
; the MPE users guide for an explanation of what each process does, and how
; the processes interact.
;
PROCESS1_DATA:
info: data.a ^xAAAAA ; This is a simple data pattern for process 1.
data.a ^xAAAAA
data.a ^xAAAAA
data.1 ^xA ; This is broken up for users who lack GNU-C version
; of star.
PROCESS1_INIT:
process_start 1, process1_code ; reconfigures this process to start
; at the process1_code label from now on.
; This is process #1. This process will kick off the keyboard scanner process
; (process #7) and then this process will perform an ongoing register integrity
; test. This process basically implements a simple sanity check over the MPE
; system and halts execution and pushes an error code onto the stack if it
; detects a problem. All this process really does is check that MPE is doing
; the process context save/restore function properly. This process runs rather
; infrequently, so it does not add much overhead to the system.
ADDR CUR_TIME, D0
MOVE.W @D0, C ; Put CUR_TIME into C.
MOVE.P2 ^d7, A ; Process 7 ID. (keyboard scanner)
CALL ADD_PROCESS ; schedule process 7 for execution.
; Now we need to set up the registers so that each register contains a unique
; value. Then these values will be checked each time this process is run.
addr info, d0
move.w @d0, a ; Now A contains all As.
move.w a, b
inc.w a
move.w a, c
move.w c, d
inc.w c
move.a c, d0
inc.w c
move.a c, d1
inc.w c
move.w c, r0
inc.w c
move.w c, r1
inc.w c
move.w c, r2
inc.w c
move.w c, r3
inc.w c
move.w c, r4
PROCESS1_CODE:
; This is the point to where execution will jump when the process is run. The
; above initialization code is only executed the very first time this process
; is run.
ADDR INFO, c ; Trashes A, and C obviously.
SWAP.A C, D0 ; Point D0 at the data.
MOVE.W @D0, A
SWAP.A C, D0 ; Restore D0.
MOVE.W A, C ; copy the data into c because only C can be compared
; with D.
BREQ.W A, B, ok1 ; Test B
MOVE.P5 ^D0, A ; Error code 0 for register B.
JUMP ERROUT ;
OK1:
INC.W C
BREQ.W D, C, OK2 ; Test D.
MOVE.P5 ^X1, A ; Error code 1 for register D. FAILED.
JUMP ERROUT ;
OK2: INC.W A
INC.W A
SWAP.A C, D0
BREQ.A C, A, OK3 ; Test D0.
MOVE.P5 ^X2, A ; Error code 2 for register D0.
JUMP ERROUT ;
OK3: INC.W A
SWAP.A C, D0 ; Restore D0 from the previous test.
SWAP.A C, D1 ;
BREQ.A C, A, OK4 ; Test D1.
MOVE.P5 ^X3, A ; Error code 3 for register D1. FAILED.
JUMP ERROUT ;
OK4: SWAP.A C, D1 ; Restore D1 from the previous test.
INC.W A
MOVE.W R0, C
BREQ.A C, A, OK5 ; Test r0.
MOVE.P5 ^X4, A ; Error code 4 for register r0.
JUMP ERROUT ;
OK5: INC.W A
MOVE.W R1, C
BREQ.A C, A, OK6 ; Test r1.
MOVE.P5 ^X5, A ; Error code 5 for register r1.
JUMP ERROUT ;
OK6: INC.W A
MOVE.W R2, C
BREQ.A C, A, OK7 ; Test r2.
MOVE.P5 ^X6, A ; Error code 6 for register r2.
JUMP ERROUT ;
OK7: INC.W A
MOVE.W R3, C
BREQ.A C, A, OK8 ; Test r3.
MOVE.P5 ^X7, A ; Error code 7 for register r3.
JUMP ERROUT ;
OK8: INC.W A
MOVE.W R4, C
BREQ.A C, A, OK9 ; Test r4.
MOVE.P5 ^X8, A ; Error code 8 for register r4.
JUMP ERROUT ;
OK9:
CALL SAVE_CONTEXT
ADDR CUR_TIME, d0 ; What time is it?
MOVE.W @D0, C
CLR.W A
MOVE.P5 ^x1FFF, A
ADD.W A, C ;
MOVE.P2 ^d2, A ; Process 2 ID.
CALL ADD_PROCESS ; schedule process 2 for execution in 1 sec.
JUMP TO_SCHEDULER
ERROUT: MOVE.A A, R0 ; Error code was placed in A above.
CALL RESTORE_REgisters
CALL PUSH_R0_SHort ; Push the error code for the bad register.
CALL SAVE_REGISters
JUMP RR_RPLCONT
; This concludes the definition of process #1.
; ****************************************************************************
; ****************************************************************************
; The definition of process #2 starts here. The purpose of this process
; is just to clear out all of the process registers. That way, if process
; #1 shows that the processor registers have the correct context for that
; process, then I know that the context save/restore by MPE was successfull.
PROCESS2_INIT:
CLR.W A
MOVE.A A, D0
MOVE.A A, D1
MOVE.W A, B
MOVE.W A, C
MOVE.W C, D
MOVE.W A, R0
MOVE.W A, R1
MOVE.W A, R2
MOVE.W A, R3
MOVE.W A, R4
CALL SAVE_CONTEXT
ADDR CUR_TIME, d0 ; What time is it?
MOVE.W @D0, C
MOVE.P2 ^d1, A ; Process 1 ID.
CALL ADD_PROCESS ; schedule process1 for immediate execution.
JUMP TO_SCHEDULER
; This concludes the definition of process #2.
; ****************************************************************************
; ****************************************************************************
; The following process definition is for processes 3#, #4, and #5. The code
; differentiates between the three processes where necessary by using the
; IF_PROC and ENDIF_PROC macros, which are described in the users guide.
process3_init:
process4_init:
process5_init:
; ****************************************************************************
; ****************************************************************************
; This is the beginning of the bullet process. The purpose of this
; process is to move an "F" through screen memory one pixel at a time. This
; process will move the "F" one pixel to the right each time it is called.
; At the bottom of this process, it checks to see whether it has reached the
; end of screen memory, and if not it reschedules itself for future
; execution based on the time inverval specified. This process definition
; differentiates between processes #3, #4, and #5 and reschedules these
; different processes at different intervals. This gives different speeds of
; motion for the three "bullets" on the screen.
; The RPL from which MPE was called clears the screen and tells the display
; controller to use PICT for the display (by doing a PVIEW command).
; First I need to modify the process header of this process so that execution
; will start at the PROCESS_CODE label in future invocations.
addr process3_code, C ; Get address of new entry point for this
; process.
cur_process_start ; Reconfigures the current process to start
; from the entry point given in C.A.
MOVE.A PICT_POINTER, D0 ; Put the address of PICT in D0.
MOVE.A @D0, C ; Get the pointer to PICT.
ADD.A 10, C ; Skip over prolog and size fields.
ADD.A 10, C ; Skip over row and column fields.
MOVE.A C, D ; Save the address where the screen memory
; starts.
MOVE.P5 ^d2174, A ; Number of nibbles - 2 in PICT.
ADD.A A, C ; A = address of last-1 nibble in PICT.
MOVE.A C, R2 ; Save it in R2.
; Now stick an "F" at the beginning of the screen memory.
CLR.W A ; Clear register A.
MOVE.B A, B ; Clear out the bottom byte of B (bit counter).
MOVE.P1 ^xF, A ; Make A.A = F.
MOVE.A D, C ; Get the pointer into PICT.
MOVE.A C, D0 ; Point D0 at PICT.
MOVE.1 A, @D0 ; Set the first nibble of the screen to be F.
PROCESS3_CODE:
PROCESS4_CODE:
PROCESS5_CODE:
; Now what we want to do is to move the "F" one pixel at a time through the
; screen memory. I think I'll do it the easy way, by keeping track of the
; counter in B.B and just doing compares on that; there are only 4 cases to
; test.
INC.1 B ; Increment the bit counter to the next value.
MOVE.A D, C ; Get pointer into PICT.
SWAP.A C, D1 ; Point D1 into PICT.
CLR.B A ; Use A.B to test the current value of the bit
; counter.
BREQ.1 A, B, ZERO ;
INC.1 A
BREQ.1 A, B, ONE
INC.1 A
BREQ.1 A, B, TWO
THREE: MOVE.P1 ^x8, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x7, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
INC.A D ; Increment the PICT pointer.
MOVE.P2 ^xF, C ;
MOVE.B C, B ; So B resets to 0 on next iteration.
JUMP DOIT
ZERO:
MOVE.A D, C ; Get the pointer into PICT.
DEC.A C ; point to the nibble we just left.
CLR.B A
SWAP C, D1 ; Point D1 to the old PICT nibble,
MOVE.1 A, @D1 ; and clear it.
SWAP C, D1 ; Restore the PICT pointer in D1.
MOVE.P1 ^xF, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x0, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
JUMP DOIT
ONE: MOVE.P1 ^xE, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x1, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
JUMP DOIT
TWO: MOVE.P1 ^xC, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x3, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
DOIT:
MOVE.A R2, C ; Get the address of the last nibble in PICT.
BRLT.A D, C, CONTINUE ; If we havent written the last location, then
; we should reschedule this process for future
; execution.
JUMP rr_rplcont ; Else, we can just exit.
CONTINUE:
CALL SAVE_CONTEXT ; Save the context for this process.
ADDR CUR_TIME, D0 ; Get the current run-time.
MOVE.W @D0, C
MOVE.W C, R0 ; Save it in R0 for now.
IF_PROC 3
CLR.W A
MOVE.P5 ^x11F, A ; Reschedule interval for process 3.
JUMP PROCESS3_CONT2
ENDIF_PROC
IF_PROC 4
CLR.W A
MOVE.P5 ^x9F, A ; Reschedule interval for process 4.
JUMP PROCESS3_CONT2
ENDIF_PROC
IF_PROC 5
CLR.W A
MOVE.P5 ^xDF, A ; Reschedule interval for process 5.
JUMP PROCESS3_CONT2
ENDIF_PROC
PROCESS3_CONT2: MOVE.W R0, C ; The current run-time.
ADD.W A, C ; Next time that the hyphen should move.
MOVE.W C, R0 ; Save it in R0.
CALL GET_CURRENT_ID ; Get process ID into A.B. Trashes C.
MOVE.W R0, C ; Next time process should be run.
CALL ADD_PROCESS ; Schedule this process to run at the
; time given in C.A.
JUMP TO_SCHEDULER ; Go back to the scheduler.
; The process definition for processes #3, #4, and #5 ends here.
; *************************************************************************
; *************************************************************************
; The definition of process #6 starts here. This process is very similar to
; the above definition for processes #3, #4, and #5. Like the above
; definition, process #6 will also move a "bullet" through screen memory. The
; difference is that this process will schedule itself based on absolute time,
; using the hardware timer to keep track of where the "bullet" should be on the
; screen, and then catching itself up to that point. In this manner, this
; process will not slow down as the system becomes heavily loaded, but rather
; it will use a constant amount of CPU time on the average, regardless of how
; many processes are running on the system.
PROCESS6_INIT:
process_start 6, PROCESS6_CODE
MOVE.A PICT_POINTER, D0 ; Put the address of PICT in D0.
MOVE.A @D0, C ; Get the pointer to PICT.
ADD.A 10, C ; Skip over prolog and size fields.
ADD.A 10, C ; Skip over row and column fields.
MOVE.A C, D ; Save the address where the screen memory
; starts.
MOVE.P5 ^d2174, A ; Number of nibbles - 2 in PICT.
ADD.A A, C ; A = address of last-1 nibble in PICT.
MOVE.A C, R2 ; Save it in R2.
CLR.W A
MOVE.W A, R0
ADDR CUR_TIME, D0
MOVE.W @D0, C
MOVE.W C, R0 ; Save time of first invocation of process.
; Now stick an "F" at the beginning of the screen memory.
CLR.W A ; Clear register A.
MOVE.B A, B ; Clear out the bottom byte of B (bit counter).
MOVE.P1 ^xF, A ; Make A.A = F.
MOVE.A D, C ; Get the pointer into PICT.
MOVE.A C, D0 ; Point D0 at PICT.
MOVE.1 A, @D0 ; Set the first nibble of the screen to be F.
PROCESS6_CODE:
; Now what we want to do is to move the "F" one pixel at a time through the
; screen memory. I think I'll do it the easy way, by keeping track of the
; counter in B.B and just doing compares on that; there are only 4 cases to
; test.
PROCESS6_LOOP:
INC.1 B ; Increment the bit counter to the next value.
MOVE.A D, C ; Get pointer into PICT.
SWAP.A C, D1 ; Point D1 into PICT.
CLR.B A ; Use A.B to test the current value of the bit
; counter.
BREQ.1 A, B, process6_ZERO ;
INC.1 A
BREQ.1 A, B, process6_ONE
INC.1 A
BREQ.1 A, B, process6_TWO
PROCESS6_THREE: MOVE.P1 ^x8, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x7, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
INC.A D ; Increment the PICT pointer.
MOVE.P2 ^xF, C ;
MOVE.B C, B ; So B resets to 0 on next iteration.
JUMP process6_DOIT
PROCESS6_ZERO:
MOVE.A D, C ; Get the pointer into PICT.
DEC.A C ; point to the nibble we just left.
CLR.B A
SWAP C, D1 ; Point D1 to the old PICT nibble,
MOVE.1 A, @D1 ; and clear it.
SWAP C, D1 ; Restore the PICT pointer in D1.
MOVE.P1 ^xF, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x0, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
JUMP process6_DOIT
PROCESS6_ONE: MOVE.P1 ^xE, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x1, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
JUMP process6_DOIT
PROCESS6_TWO: MOVE.P1 ^xC, A ; Put New bit pattern of nibble in A.
INC.1 P ;
MOVE.P1 ^x3, A ; Put new bit pattern of nibble+1 in A.
MOVE.1 0, P ; Zero out the Pointer register.
MOVE.2 A, @D1 ; Write out the two data values to the display.
PROCESS6_DOIT:
MOVE.A R2, C ; Get the address of the last nibble in PICT.
BRLT.A D, C, PROCESS6_CONTINUE
; If we havent written the last location, then
; we should reschedule this process for future
; execution.
JUMP rr_rplcont ; Else, we can just exit.
PROCESS6_CONTINUE:
CLR.W A
MOVE.P5 ^x9F, A
MOVE.W R0, C ; The last scheduled run-time.
ADD.W C, A ; Next time that the hyphen should move.
MOVE.W A, R0 ; Save next run-time.
CALL GET_TICKS ; Current time.
MOVE.W R0, A ; Get next run-time.
BRGE.W C, A, PROCESS6_LOOPER ; Run it again if it is time.
CALL SAVE_CONTEXT ; Save the context for this process.
MOVE.W R0, C ; Next time process should be run.
CLR.W A
MOVE.P2 ^d6, A ; ID for this process into A.B.
CALL ADD_PROCESS ; Schedule this process to run at the
; time given in C.
JUMP TO_SCHEDULER ; Go back to the scheduler.
PROCESS6_LOOPER: JUMP PROCESS6_LOOP
;**************************************************************************
;**************************************************************************
; This process is responsible for reading the keyboad, and spawning a new
; processes whenever the [1] key is pressed. All other key presses are
; discarded without action.
; This process will schedule processes #3, #4, #5, and #6 in that sequence.
; One processes will be spawned for each press of the [1] key. This process
; does one scan of the key buffer on each invocation. After process #6 is
; scheduled, this process ceases to reschedule.
PROCESS7_INIT:
process_start 7, process7_code
move.p2 ^d2, C ; Pointer to the next-1 bullet process.
MOVE.B C, B ; Save it in B.
MOVE.P2 ^d6, C ; Upper process ID limit.
MOVE.B C, D ; Save it in D.
PROCESS7_CODE:
call kb_poll ; Get key, if any
brcs process7_check_key
CALL SAVE_CONTEXT
jump process7_sched ; If not then reschedule.
PROCESS7_CHECK_KEY:
move.p2 ^d49, C ; Scan code for the [+] key.
breq.b c, a, process7_valid_key ; Did user press [+]?
MOVE.P2 ^d47, C
BREQ.B C, A, PROCESS7_ABORT
CALL SAVE_CONTEXT
jump process7_sched ; If not then reschedule.
PROCESS7_ABORT:
; The user has pressed the [.] key, so we want to abort the application.
JUMP RR_RPLCONT ; This kills the application and continues
; with the RPL thread.
PROCESS7_VALID_KEY: ; The user pressed the [+] key!
INC.B B ; Point to the next bullet ID.
MOVE.B B, A ; Next process ID to start.
MOVE.B A, R0 ; Save it in R0 for later (see below).
MOVE.B D, C ; Upper process ID limit.
BRGE.B C, A, PROCESS7_SPAWN ; If process number <=6 then run it.
DEC.B B ; Else un-increment B,
CALL SAVE_CONTEXT ; save the process context and
JUMP PROCESS7_SCHED ; reschedule the process.
PROCESS7_SPAWN:
CALL SAVE_CONTEXT
ADDR CUR_TIME, D0 ; Trashes A.
MOVE.B R0, A
MOVE.W @D0, C
CALL ADD_PROCESS
PROCESS7_SCHED:
; The current context of this process should have been saved by now.
ADDR CUR_TIME, D0 ; Point to the CUR_TIME variable.
MOVE.W @D0, C ; Get the current time.
CLR.W A
MOVE.P3 ^x3FF, A ; Reschedule this process to look at the
; keyboard roughly 8 times per second.
ADD.W A, C
MOVE.P2 ^d7, A ; ID for this process.
CALL ADD_PROCESS
PROCESS7_EXIT:
JUMP TO_SCHEDULER ; Return control to the scheduler.
;; Poll keyboard. System-based version. Returns the scan code in A.A.
;; Trashes C.A, B.B, and D0. The C bit is set if a key was pressed,
;; otherwise it's cleared. This keyboard polling software was written
;; by Jan Brittenson (J.E.)
;;
;; This is a mutation of the ROM prefixed machine code routine to
;; get/peek the next entry in the keyboard buffer.
kb_poll:
move.5 keybuf+1, d0 ; KB Put ptr
move.s @d0, a ; A.S = put ctr
dec d0
move.s @d0, c ; C.S = get ctr
breq.s c, a, $100 ; Ctrs are equal - buffer empty
move c.15, p ; P = get ctr
inc.s c ; Remove key
move.s c, @d0
swap c, d0
add p+1, c
add p+1, c ; C += get ctr, in bytes
clr p
move c, d0 ; D0 = &next key
clr.a a
move.b @d0, a ; A.A = key
retsetc
$100:
clr.a a
retclrc
; **************************************************************************
endcode
end