!
Example.s -- Serial I/O Interface Routines
!
! Harry
Porter - 08/01/01
!
! This
program serves as an example of BLITZ assembly code and
! of the
recommended style for indenting and commenting assembly code.
!
! This
program provides a "main" function which reads input characters
! from the
terminal and echoes them back. It
can be used to explore
! the
differences between "raw" and "cooked" serial input modes.
!
! In
addition to the "main" function, this program also provides the
!
following interface for the serial I/O device; these routines might
! provide
the starting point for some other program.
!
.export getChar
.export putChar
.export putString
.export flush
.export initSerial
.export checkSerialDevice
!
! Program
entry point
!
.text
_entry:
!
! Here is
the interrupt vector, which will be loaded at address 0x00000000.
! Each
entry is 4 bytes. They are located
at fixed, pre-defined addresses.
! This program
will only handle SERIAL_INTERRUPTS. The asynchronous,
! hardware
interrupts (i.e., TIMER and DISK) will be ignored by returning
!
immediately. None of the other
interrupts should occur; if they do, this
! program
will get stuck in an infinite loop.
!
PowerOnReset:
jmp main
TimerInterrupt:
reti
DiskInterrupt:
reti
SerialInterrupt:
jmp SerialInterruptHandler
HardwareFault:
jmp HardwareFault
IllegalInstruction:
jmp IllegalInstruction
ArithmeticException:
jmp ArithmeticException
AddressException:
jmp AddressException
PageInvalidException:
jmp PageInvalidException
PageReadonlyException:
jmp PageReadonlyException
PrivilegedInstruction:
jmp PrivilegedInstruction
AlignmentException:
jmp AlignmentException
ExceptionDuringInterrupt:
jmp ExceptionDuringInterrupt
SyscallTrap:
jmp SyscallTrap
!
!
Interrupt Service routines
!
SerialInterruptHandler:
call checkSerialDevice
reti
!
! main
!
! The main
program repeatedly prints a prompt, then gets and echoes characters
! until a
NEWLINE is entered. It then
repeats the prompt.
!
main:
set STACK_START,r15 ! initialize the
stack pointer
call initSerial ! initialize the serial I/O device
seti ! enable interrupts
loop1: ! loop
set prompt,r1 ! putString ("Enter
something: ")
call putString ! .
loop2: ! loop
call getChar ! r1 :=
getChar
cmp r1,'\n' ! if (r1
== '\n' or '\r') then
be then ! .
cmp r1,'\r' ! .
bne else ! .
then: ! .
mov '\r',r1 ! putChar ('\r')
call putChar ! .
mov '\n',r1 ! putChar ('\n')
call putChar ! .
jmp exit ! break
else: ! else
cmp r1,'q' ! if (r1 == 'q') then
bne else2 ! .
set bye,r1 ! print "Good
bye"
call putString ! .
call flush ! wait until I/O completes
debug ! Invoke DEBUG
instruction
jmp cont ! else
else2: ! .
call putChar ! putChar (r1)
cont: ! end
jmp loop2 ! end
exit: ! .
jmp loop1 ! end
prompt:
.ascii "Enter something (or
'q' to terminate): \n\r\0"
bye: .ascii "\n\rAbout to execute DEBUG
instruction (type 'go' to resume)...
\n\r\0"
.align
!
! getChar
!
! This
routine reads one character from the terminal and returns it in r1.
! It does
not echo the character or process special characters in any way.
! It
checks the input buffer and gets a character from there if one is
!
available. Otherwise, it waits for
a key to be typed.
!
! r1 = the
character
! r2 =
addr of inBufferCount
! r3 =
inBufferCount
! r4 =
addr of inBufferOut
! r5 =
inBufferOut
!
!
Registers modified: r1
!
getChar:
push r2 ! save registers
push r3 ! .
push r4 ! .
push r5 !
.
set inBufferCount,r2 ! initialize address
registers
set inBufferOut,r4 ! .
getChLoop: ! loop
! loop
cleari ! disable interrupts
load [r2],r3 ! if
(inBufferCount != 0)
cmp r3,0 ! .
bne getChExit ! then break
seti ! enable interrupts
jmp getChLoop ! end
getChExit: ! .
sub r3,1,r3 ! inBufferCount --
store r3,[r2] ! .
load [r4],r5 ! r1 := *inBufferOut
loadb [r5],r1 ! .
add r5,1,r5 ! inBufferOut ++
cmp r5,inBufferEnd ! if (inBufferOut == inBufferEnd)
bne getChElse ! .
set inBuffer,r5 ! inBufferOut := &inBuffer
getChElse: ! end
store r5,[r4] ! save inBufferOut
seti ! enable interrupts
cmp r1,'\0' ! until (r1 != '\0')
be getChLoop ! .
pop r5 ! restore regs
pop r4 ! .
pop r3 ! .
pop r2 ! .
ret ! return
!
! putChar
!
! This
routine is passed a character in r1. It writes it to the terminal
! exactly
as it is. Normally, the output
character is added to a buffer
! and will
be written as soon as the device is ready. If the buffer is
! full,
this routine will busy-wait for the buffer to become not-full.
!
! r1 = the
character
! r2 =
addr of outBufferCount
! r3 =
outBufferCount
! r4 =
addr of outBufferIn
! r5 =
outBufferIn
!
! Registers
modified: none
!
putChar:
push r2 ! save registers
push r3 ! .
push r4 ! .
push r5 ! .
set outBufferCount,r2 ! initialize address registers
set outBufferIn,r4 ! .
putChLoop: ! loop
cleari ! disable interrupts
load [r2],r3 ! if (outBufferCount
< BUFFER_SIZE)
cmp r3,BUFFER_SIZE ! .
bl putChExit ! then
break
seti ! enable interrupts
jmp putChLoop ! end
putChExit: ! .
add r3,1,r3 ! outBufferCount ++
store r3,[r2] ! .
load [r4],r5 ! *outBufferIn := r1
storeb r1,[r5] ! .
add r5,1,r5 ! outBufferIn ++
cmp r5,outBufferEnd ! if (outBufferIn
== outBufferEnd) then
bne putChElse ! .
set outBuffer,r5 ! outBufferIn :=
&outBuffer
putChElse: ! end
store r5,[r4] ! save outBufferIn
call checkSerialDevice ! start output if necessary
seti ! enable interrupts
pop r5 ! restore regs
pop r4 ! .
pop r3 ! .
pop r2 ! .
ret ! return
!
!
putString
!
! This
routine is passed a pointer to a string of characters, terminated
! by
'\0'. It sends all of them except
the final '\0' to the terminal by
! calling
'putChar' repeatedly.
!
!
Registers modified: none
!
putString:
push r1 ! save registers
push r2 ! .
mov r1,r2 ! r2 := ptr into string
putStLoop: ! loop
loadb [r2],r1 ! r1 := next char
add r2,1,r2 ! incr ptr
cmp r1,0 ! if (r1 == '\0')
be putStExit ! then
break
call putChar ! putChar (r1)
jmp putStLoop ! end
putStExit: ! .
pop r2 !
restore regs
pop r1 ! .
ret ! return
!
! flush
!
! This
routine waits until the output buffer has been emptied, then returns.
! It
busy-waits until the buffer has been emptied.
!
!
Registers modified: none
!
flush:
push r1 ! save registers
push r2 ! .
flushLoop: ! loop
cleari ! disable interrupts
set outBufferCount,r1 ! r2 = outBufferCount
load [r1],r2 ! .
cmp r2,0 ! if (r2 == 0)
be flushLoopEx ! break
seti ! re-enable interrupts
jmp flushLoop ! end
flushLoopEx: ! .
seti ! re-enable interrupts
pop r2 ! restore regs
pop r1 ! .
ret ! return
!
!
initSerial
!
! This
routine initializes the serial input and output buffers.
!
!
Registers modified: r1, r2
!
initSerial: set inBuffer,r1 ! inBuferIn = &inBuffer
set inBufferIn,r2 ! .
store r1,[r2] ! .
set inBufferOut,r2 ! inBufferOut = &inBuffer
store r1,[r2] ! .
set outBuffer,r1 ! outBufferIn = &outBuffer
set outBufferIn,r2 ! .
store r1,[r2] ! .
set outBufferOut,r2 ! outBufferOut = &outBuffer
store r1,[r2] ! .
clr r1 ! inBufferCount = 0
set inBufferCount,r2 ! .
store r1,[r2] ! .
set outBufferCount,r2 ! outBufferCount = 0
store r1,[r2] ! .
ret ! return
!
!
checkSerialDevice
!
! This
routine is called whenever there is a SerialInterrupt. If a character
! is ready
on the input, it is moved into the inBuffer. If there is no
! more
room in the buffer, the character is simply dropped, with no
! error
indication. If the output device
is ready to for another character
! and
there are any characters in the outBuffer, then the next character is
!
transmitted to the output device.
!
! No
arguments, no result.
! This
routine must be called with interrupts disabled!
!
Registers modified: none
!
! r8 =
addr of SERIAL_STATUS_WORD
! r1 =
SERIAL_STATUS_WORD
! r11 =
addr of SERIAL_DATA_WORD
! r2 = the
character
! r9 =
addr of inBufferCount
! r5 =
inBufferCount
! r10 =
addr of inBufferIn
! r3 =
inBufferIn
! r7 =
addr of outBufferOut
! r4 =
outBufferOut
! r6 =
addr of outBufferCount
! r5 =
outBufferCount
!
checkSerialDevice:
push r1 ! save all registers we use
push r2 ! .
push r3 ! .
push r4 ! .
push r5 ! .
push r6 !
.
push r7 ! .
push r8 ! .
push r9 ! .
push r10 ! .
push r11 ! .
set SERIAL_DATA,r11 ! r11 = addr of SERIAL_DATA_WORD
set SERIAL_STAT,r8 ! r1 :=
serial status word
load [r8],r1 ! .
btst 0x00000001,r1 ! if
status[charAvail] == 1 then
be end1 ! .
load [r11],r2 ! r2 := input char
set inBufferCount,r9 ! if inBufferCount <bufSize then
load [r9],r5 ! .
cmp r5,BUFFER_SIZE ! .
bge end1 ! .
set inBufferIn,r10 ! *inBufferIn := char
load [r10],r3 ! .
storeb r2,[r3] ! .
add r5,1,r5 ! inBufferCount++
store r5,[r9] ! .
add r3,1,r3 ! inBufferIn++
cmp r3,inBufferEnd ! if inBufferIn ==
inBufferEnd then
bne end2 ! .
set inBuffer,r3 ! inBufferIn = &inBuffer
end2: ! end
store r3,[r10] ! store
inBufferIn
end1: ! end
!
end
btst 0x00000002,r1 ! if
status[outputReady] == 1 then
be end4 ! .
set outBufferCount,r6 ! if outBufferCount>0 then
load [r6],r5 ! .
cmp r5,0 ! .
ble end4 ! .
set outBufferOut,r7 ! r2 := *outBufferOut
load [r7],r4 ! .
loadb [r4],r2 ! .
store r2,[r11] ! send
char in r2 to serial output
sub r5,1,r5 ! outBufferCount--
store r5,[r6] ! .
add r4,1,r4 ! outBufferOut++
cmp r4,outBufferEnd ! if
outBufferOut==outBufferEnd then
bne end3 ! .
set outBuffer,r4 ! outBufferOut = &outBuffer
end3: ! end
store r4,[r7] ! store
outBufferOut
end4: ! end
! end
pop r11 ! restore all registers
pop r10 ! .
pop r9 ! .
pop r8 ! .
pop r7 ! .
pop r6 ! .
pop r5 ! .
pop r4 ! .
pop r3 ! .
pop r2 ! .
pop r1 ! .
ret ! return
.data
BUFFER_SIZE = 128
inBuffer: .skip BUFFER_SIZE ! Serial Input buffer
area
inBufferEnd:
inBufferIn: .word 0 ! Addr of next place to add to
inBufferOut: .word 0 ! Addr of next place to remove from
inBufferCount: .word 0 ! Number of characters in inBuffer
outBuffer: .skip BUFFER_SIZE ! Serial Output
buffer area
outBufferEnd:
outBufferIn: .word 0 ! Addr of next place to add to
outBufferOut: .word 0 ! Addr of next place to remove from
outBufferCount:
.word 0 ! Number of characters in outBuffer
STACK_START = 0x00ffff00
SERIAL_STAT = 0x00ffff00 ! Addr of SERIAL_STATUS_WORD
SERIAL_DATA = 0x00ffff04 ! Addr of SERIAL_DATA_WORD