||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| Tiny BETA Operating System Demo -- 4/94 Steve ward ||| ||| User-mode code. Includes 3 processes: ||| ||| PROCESS 0: ||| (1) Prompts the user for new lines of input. ||| (2) Reads lines from the keyboard (using the GetKey() SVC), ||| and pipes it to PROCESS 1 through a bounded buffer. ||| It does this using the Send procedure. ||| ||| PROCESS 1: ||| Reads lines of input from PROCESS 0, using the Rcv procedure, ||| translates them to Piglatin, and types them out (using ||| the SVCs WrCh() and WrMsg(). ||| ||| Note that Send and Rcv, used by processes 0 and 1, communicate ||| using a bounded buffer and synchronize using semaphores ||| implemented as the Wait(S) and Signal(S) SVCs. ||| ||| PROCESS 2: ||| On each quantum, simply increments a counter and uses the Yield() ||| SVC to give up the remainder of its quantum. The resulting ||| count thus becomes a count of the number of quanta which have ||| been allocated to each process. This count (in HEX) is used ||| as the prompt typed by process 0. ||| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| Definitions of macros used to interface with Kernel code: .macro Halt() SVC(0) | Stop a process. .macro WrMsg() SVC(1) | Write the 0-terminated msg following SVC .macro WrCh() SVC(2) | Write a character whose code is in R0 .macro GetKey() SVC(3) | Read a key from the keyboard into R0 .macro HexPrt() SVC(4) | Hex Print the value in R0. .macro Yield() SVC(7) | Give up remaining quantum ||| Semaphore macros. ||| Wait(S) waits on semaphore S; Signal(S) signals on S. ||| Both preserve all registers, by pushing & popping R3. .macro Wait(S) { PUSH(r3) | Save old , LDR(S,r3) | put semaphore address into r3 SVC(5) | Wait on semaphore whose adr is in R3 POP(r3) } | and restore former .macro Signal(S) { PUSH(r3) | Save old , LDR(S,r3) | put semaphore address into r3 SVC(6) | Signal on semaphore whose adr is in R3 POP(r3) } | and restore former ||| Allocate a semaphore: used like ||| name: semaphore(size) .macro semaphore(N) { | Allocate a semaphore, and build a ptr LONG(.+4) | Pointer to semaphore LONG(N) } | Semaphore itself, init value N. ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| User-mode code: Process 0 ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| Prompt: semaphore(1) | To keep us from typing next prompt | while P1 is typing previous output. P0Start:WrMsg() .text "Start typing, Bunky.\n\n" P0Read: Wait(Prompt) | Wait until P1 has caught up... WrMsg() | First a newline character, then .text "\n" LD(Count3, r0) | print out the quantum count HexPrt() | as part of the count, then WrMsg() | the remainder. .text "> " LD(P0LinP, r3) | ...then read a line into buffer... P0RdCh: GetKey() | read next character, WrCh() | echo back to user CALL(UCase) | Convert it to upper case, ST(r0,0,r3) | Store it in buffer. ADDC(r3,4,r3) | Incr pointer to next char... CMPEQC(r0,0xA,r1) | End of line? BF(r1,P0RdCh) | nope, keep filling buffer. LD(P0LinP,r2) | Prepare to empty buffer. P0PutC: LD(r2,0,r0) | read next char from buf, CALL(Send) | send to P2 CMPEQC(r0,0xA,r1) | Is it end of line? BT(r1,P0Read) | Yup, read another line. ADDC(r2,4,r2) | Else move to next char. BR(P0PutC) P0Line: STORAGE(100) | Line buffer. P0LinP: LONG(P0Line) P0Stack: STORAGE(256) |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| Some auxilliaries for our little application: |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| | Auxilliary routine: convert char in r0 to upper case: UCase: PUSH(r1) CMPLEC(r0,'z',r1) | Is it beyond 'z'? BF(r1,UCase1) | yup, don't convert. CMPLTC(r0,'a',r1) | Is it before 'a'? BT(r1, UCase1) | yup, no change. SUBC(r0,'a'-'A',r0) | Map to UPPER CASE... UCase1: POP(r1) RTN() | Auxilliary routine: Test if is a vowel; boolean into r1. VowelP: CMPEQC(r0,'A',r1) | Sorta brute force... BT(r1,Vowel1) CMPEQC(r0,'E',r1) BT(r1,Vowel1) CMPEQC(r0,'I',r1) BT(r1,Vowel1) CMPEQC(r0,'O',r1) BT(r1,Vowel1) CMPEQC(r0,'U',r1) BT(r1,Vowel1) CMPEQC(r0,'Y',r1) BT(r1,Vowel1) CMOVE(0,r1) | Return FALSE. Vowel1: RTN() |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| Bounded-buffer FIFO routines for Beta USER MODE ||| CALL(Send) - sends datum in r0 thru pipe ||| CALL(Rcv) - reads datum from pipe into r0 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| FIFOSIZE = 100 FIFO: STORAGE(FIFOSIZE) | FIFO buffer. IN: LONG(0) | IN pointer: index into FIFO OUT: LONG(0) | OUT pointer: index into FIFO Chars: semaphore(0) | Flow-control semaphore 1 Holes: semaphore(FIFOSIZE) | Flow-control semaphore 2 ||| Send: put into fifo. Send: PUSH(r1) | Save some regs... PUSH(r2) Wait(Holes) | Wait for space in buffer... LD(IN,r1) | IN pointer... MULC(r1,4,r2) | Compute 4*IN, word offset ST(r0,FIFO,r2) | FIFO[IN] = ch ADDC(r1,1,r1) | Next time, next slot. CMPEQC(r1,FIFOSIZE,r2) | End of buffer? BF(r2,Send1) | nope. CMOVE(0,r1) | yup, wrap around. Send1: ST(r1,IN) | Tuck away input pointer Signal(Chars) | Now another Rcv() can happen POP(R2) POP(r1) RTN() ||| Rcv: Get char from fifo into r0. Rcv: PUSH(r1) PUSH(r2) Wait(Chars) | Wait until FIFO non-empty LD(OUT,r1) | OUT pointer... MULC(r1,4,r2) | Compute 4*OUT, word offset LD(r2,FIFO,r0) | result = FIFO[OUT] ADDC(r1,1,r1) | Next time, next slot. CMPEQC(r1,FIFOSIZE,r2) | End of buffer? BF(r2,Rcv1) | nope. CMOVE(0,r1) | yup, wrap around. Rcv1: ST(r1,OUT) | Tuck away input pointer Signal(Holes) | Now theres space for 1 more. POP(R2) POP(r1) RTN() |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| USER MODE Process 1: Translate English to Piglatin |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| P1Start: LD(P1BufP, r9) | Buffer pointer in r9. P1Word: MOVE(r9,r5) | Read initial consonants. P1Cons: CALL(Rcv) CALL(VowelP) | Is it a vowel? BT(r1,P1Vowl) | yup, move on. CMPLEC(r0,' ',r1) | Is it white space? BT(r1,P1Spc) ST(r0,0,r5) | Else store it into buffer... ADDC(r5,4,r5) | ... and bump pointer. BR(P1Cons) | Back for more. P1Vowl: WrCh() | Output the vowel, CALL(Rcv) | then check again. CMPLEC(r0,' ',r1) | White space? BF(r1,P1Vowl) P1Spc: MOVE(r0,r3) | Save input char, then MOVE(r9,r4) | Output initial consonant. P1Spc2: CMPEQ(r4,r5,r1) | Any left? BT(r1,P1Spc1) | nope... LD(r4,0,r0) | Fetch next char, ADDC(r4,4,r4) | (next time, next char) WrCh() | and write it out. BR(P1Spc2) P1Spc1: WrMsg() | Add the "AY" suffix. .text "AY" MOVE(r3,r0) | Then the saved input char. WrCh() CMPEQC(r3,0xA,r0) | Was it end-of-line? BF(r0,P1Word) | nope. Signal(Prompt) | it was; allow proc 0 to re-prompt. BR(P1Word) | ... and start another word. P1Buf: STORAGE(100) | Line buffer. P1BufP: LONG(P1Buf) | Address of line buffer. P1Stack: STORAGE(256) | Stack for process 2. |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ||| USER MODE Process 2: Simply counts quanta. |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| P2Start: LD(Count3, r0) | Another quantum, incr count3. ADDC(r0,1,r0) ST(r0,Count3) Yield() | Invoke scheduler BR(P2Start) | return here after others run. P2Stack: STORAGE(256) Count3: LONG(0)