;;; Chris Dent ;;; ;;; A593 Final Project ;;; ;;; Goal: ;;; Make a process management system for multiple calculator virtual ;;; machines to experiment with the structures required for process ;;; management. ;;; ;;; References: ;;; http://wheelie.tees.ac.uk/users/a.clements/MICROmay99.pdf ;;; wakerly ;;; magic garden ;;; c335/timer_sample ;;; clements, alan. 1994. 68000 family assembly language. PWS ;;; publishing company. boston ;;; ;;; State: ;;; ;;; Experiment with interrupts to determine how to do context switching ;;; ;;; $Log$ ;;; ;;; ;;; process structure ;;; pointer to next process ;;; process id ;;; process status ;;; process status register ;;; process program counter ;;; 30 words of registers SECTION code START: jsr output_header ; output header jsr init_model ; setup global data structures ;;; setup the interrupt handler move.l #0x320,a0 ; get the address of the old vector move.l (a0),-(sp) ; and put it on the stack move.l #timer_interrupt,(a0); handler address into vector table jsr init_timer ; start up the interrupt timer ;;; ################### ;;; # main loop ;;; # ;;; # this is our little shell, we can think of it as init ;;; # we loop forever, faking a fork now and again ;;; # ;;; ################### main_start: lea task_blocks,a2 move.l a2,current_task move.l current_task,task_blocks; get task_blocks starts move.w #0,4(a2) ; set task number move.w #1,6(a2) ; set status move.w #0xffff,d2 ; set d2 to big number main: dbra d2,main_do ; skip output unless d2 hits zero jsr output_main ; announce in init move.w #0xffff,d2 ; reset d2 ;;; get a character but don't block if there isn't one there main_do: clr.l d0 ; clear out d0, all the way jsr check_for_char ; get a single character into d0 and #%01111111,d0 ; mask off the msb to be sure cmp.b #0x0,d0 ; was it null? beq main ; if it was just keep on looping cmp.b #'q',d0 ; was it q? beq quit ; then quit, otherwise... ;jsr put_char ; print it to screen for confirmation jsr start_process ; create a process with a key press bra main ; loop forever ;; clean up quit: ;; shutdown the timer move.l #0x320,a0 move.l (sp)+,(a0) ; get original vector value off stack ;; exit clr.b d1 trap #15 ;;; ########################### ;;; # The Subroutines ;;; ########################### start_process: lea task_blocks,a2 ; look at the start of the task blocks clr.l d4 ; wipe out d4, we'll use it as a counter next_task: movea.l (a2),a3 ; load the address of the next task addi #1,d4 ; increment the counter (we have d4 tasks) cmp.l #task_blocks,a3 ; compare it with the start of the blocks beq new_process ; if it is the same add a new task move.l a3,a2 ; otherwise get the address at that location bra next_task ; and look there new_process: cmp.b #10,d4 ; compare counter with the max beq proc_max ; and error if we've got too many move.w 4(a2),d3 ; store the current task number addi.w #1,d3 ; add 1 to it move.l a2,a3 ; get the most recent address adda #76,a2 ; make it the next block move.w d3,4(a2) ; store the task number move.l d0,14(a2) ; d0 contains the character input, store it move.l a2,(a3) ; store the address of this new task in move.l #task_blocks,(a2); old task and make this next task the start jsr init_process ; fill in the process rts proc_max: lea proc_max_message,a0 jsr do_output ; output error message rts ;;; init_process ;;; copy a program into a memory location and unleash it init_process: move.l #null_code,10(a2); point to code for pc move.w #0x2000,8(a2) ; set the status register to something move.w #1,6(a2) ; set process status rts ;;; init_model ;;; figure out the memory init_model: nop rts ;;; we try to turn off IRQs around the traps because things get weird ;;; then ;;; do_output ;;; generic screen writing routine ;;; perfect world would have this using the stack to pass ;;; parameters do_output: ori.w #0x0700,SR ; disable interrupts move.b (a0)+,d0 ; get the first byte beq output_done ; if it is \0 we are done moveq.l #0x1,d1 ; set up for output trap trap #15 ; echo nop bra do_output output_done: andi.w #0xF0FF,SR ; enable interrupts rts put_char: ori.w #0x0700,SR ; disable interrupts moveq.l #0x1,d1 trap #15 nop andi.w #0xF0FF,SR ; enable interrupts rts check_for_char: ori.w #0x0700,SR ; disable interrupts moveq.l #0x3,d1 trap #15 nop andi.w #0xF0FF,SR ; enable interrupts rts ;;; output_header ;;; print a message saying we are here output_header: lea banner,a0 ; load address of message jsr do_output ; call output rts output_main: lea in_init_message,a0; load address of message jsr do_output ; call output rts output_proc: lea in_proc_message,a0; load address of message jsr do_output ; call output rts output_proc_start: lea start_proc_message,a0; get message jsr do_output ; call output rts output_proc_exit: lea exit_proc_message,a0; get mesage jsr do_output ; call output rts output_int: lea in_int_message,a0; load address of message jsr do_output ; call output rts fake_output: nop rts ;;; handle_interrupt ;;; the place where we actually do something handle_interrupt: move.w current_id,d0 ; get the pid into d0 addi.b #0x30,d0 ; make d0 ascii jsr put_char ; print it cmp #0,current_status; is current status zero beq term_process ; terminate the process if it is movea.l current_task,a2 ; movea.l (a2),a2 ; get the address of the next task rts ;;; term_process ;;; do what it takes to clean up a process that is no longer in ;;; used. we take the process out of the linked list term_process: movea.l current_task,a2 ; get current task movea.l (a2),a3 ; get the address of the next task search_backwards: adda #-76,a2 ; get the address of the previous task cmp #0,6(a2) ; is the process dead or alive beq search_backwards; if it is dead, keep looking backwards ;; init never dies so it will have a one, we are avoid a doubly ;; linked list move.l a3,(a2) ; store the next task on the previous task move.l (a2),a2 ; get that address into a2 for current rts ;;; timer_interrupt ;;; perform interrupt handling ;;; for the time being we are not passing around any info timer_interrupt: link a6,#0 ; save the stack pointer movem.l d0-d7/a0-a5,-(sp); save regs of current task move.b #0x01,0xC0006B.L; reset timer status move.w 4(a6),current_sr; save the current sr move.l 6(a6),current_pc; record the current pc movea.l current_task,a2 ; save point to current task move.w 4(a2),current_id; save point to current task id move.w 6(a2),current_status; save point to current task status lea 14(a2),a2 ; point to the volatile portion move.w #30,d0 ; start loop copy_regs: move.w (sp)+,(a2)+ ; copy from the stack to memory dbra d0,copy_regs ; keep going until d0 is 0 movea.l current_task,a2 ; get the current task again move.l current_pc,10(a2); save pc move.w current_sr,8(a2); save sr ;; do our business jsr handle_interrupt; do the thing we do during an int jsr output_int ; make some noise about being in handler ;; address of next task now in a2 adda #76,a2 ; go to end of saved registers ;; get current task info and save it move.w #30,d0 ; start loop copy_back: move.w -(a2),-(sp) ; move data dbra d0,copy_back ; copy it all back from "ram" move.l -(a2),current_pc; retrieve the stored pc adda #-2,a2 ; move a2 down half an address move.w (a2),current_sr; retrieve the stored sr adda #-8,a2 ; get to the start of the block move.l a2,current_task ; set the global current task movem.l (sp)+,d0-d7/a0-a5; save regs of current task move.l current_pc,6(a6); stored pc move.w current_sr,4(a6); and status register unlk a6 ; restore frame rte ; return from exception ;;; init_timer ;;; set up timing for the interrupt init_timer: ;; load timer with 1 second rate move.b #0x02,0xC0004F.L ; Timer Preload High Byte ;move.b #0x00,0xC0004F.L ; Timer Preload High Byte move.b #0xFA,0xC00053.L ; Timer Preload Middle Byte move.b #0xF0,0xC00057.L ; Timer Preload Low Byte move.b #0xC8,0xC00047.L ; Interrupt vector #200 generated by timer move.b #0xA1,0xC00043.L ; countdown/reload mode, gen. IRQ, count on rts ;;; d0 contains the character pressed on input to start process null_code: ori.w #0x0700,SR ; turn off interrupts during process start move.w d0,d4 ; save the arg to d4, we'll count on it jsr put_char ; output pressed key jsr output_proc_start; announce start of process movea.l current_task,a1 ; get global task value move.l #0xffff,d2 ; put a big value in d2 andi.w #0xF0FF,SR ; interrupts okay now null_top: addi.l #-1,d2 ; dec d2 bne null_top ; down to zero, causing a delay move.w 4(a1),d0 ; get pid addi.b #0x30,d0 ; make pid ascii jsr put_char ; call output routinel jsr output_proc ; send proc message move.l #0xffff,d2 ; reset d2, the delay counter dbra d4,null_top ; decrement our counter, when it is zero we jmp process_exit ; jump to the place the dead processes go ;;; process_exit ;;; when a process is done it goes here ;;; signal that it is done so the process manager can reap it process_exit: ;jsr put_char ; show our pid ;jsr output_proc_exit; announce exit movea.l current_task,a0 ; get current task location move.w #0x0,6(a0) ; set the status byte to dead dead_loop: bra dead_loop ; busy wait SECTION data banner: dc.b "\r\nHello and Welcome to the Show\r\n\0" proc_max_message: dc.b "\r\nMax Processes In Use\r\n\0" in_proc_message: dc.b " process running\r\n\0" start_proc_message: dc.b " started new process\r\n\0" exit_proc_message: dc.b " exiting\r\n\0" in_init_message: dc.b "init process running\r\n\0" in_int_message: dc.b " interrupted\r\n\0" ;;; the address of the current task current_task: ds.l 1 current_pc: ds.l 1 current_sr: ds.w 1 current_id: ds.w 1 current_status: ds.w 1 ;;; control blocks for each task task_blocks: ds.b 100*76