Separate shell and acia input buffers

They serve a different purpose. The goal of the ACIA buffer is to ensure
that we don't miss an input. The goal of the shell buffer is to wait
until the user presses return.

The ACIA buffer has been moved to shell and replaced with a circular
buffer, a more appropriate data structure for this kind of purpose.

Also, introduce `aciaGetC`.
This commit is contained in:
Virgil Dupras 2019-04-14 13:56:04 -04:00
parent 902c6a5dd3
commit 8ccddbcb0e
3 changed files with 123 additions and 49 deletions

View File

@ -14,23 +14,29 @@
; RAM. ; RAM.
; *** CONSTS *** ; *** CONSTS ***
; size of the input buffer. If our input goes over this size, we echo ; size of the input buffer. If our input goes over this size, we start losing
; immediately. ; data.
ACIA_BUFSIZE .equ 0x20 ACIA_BUFSIZE .equ 0x20
; *** VARIABLES *** ; *** VARIABLES ***
; Our input buffer starts there ; Our input buffer starts there. This is a circular buffer.
ACIA_BUF .equ ACIA_RAMSTART ACIA_BUF .equ ACIA_RAMSTART
; index, in the buffer, where our next character will go. 0 when the buffer is ; The "read" index of the circular buffer. It points to where the next char
; empty, BUFSIZE-1 when it's almost full. ; should be read. If rd == wr, the buffer is empty. Not touched by the
ACIA_BUFIDX .equ ACIA_BUF+ACIA_BUFSIZE ; interrupt.
ACIA_RAMEND .equ ACIA_BUFIDX+1 ACIA_BUFRDIDX .equ ACIA_BUF+ACIA_BUFSIZE
; The "write" index of the circular buffer. Points to where the next char
; should be written. Should only be touched by the interrupt. if wr == rd-1,
; the interrupt will *not* write in the buffer until some space has been freed.
ACIA_BUFWRIDX .equ ACIA_BUFRDIDX+1
ACIA_RAMEND .equ ACIA_BUFWRIDX+1
aciaInit: aciaInit:
; initialize variables ; initialize variables
xor a xor a
ld (ACIA_BUFIDX), a ; starts at 0 ld (ACIA_BUFRDIDX), a ; starts at 0
ld (ACIA_BUFWRIDX), a
; setup ACIA ; setup ACIA
; CR7 (1) - Receive Interrupt enabled ; CR7 (1) - Receive Interrupt enabled
@ -41,6 +47,16 @@ aciaInit:
out (ACIA_CTL), a out (ACIA_CTL), a
ret ret
; Increase the circular buffer index in A, properly considering overflow.
; returns value in A.
aciaIncIndex:
inc a
cp ACIA_BUFSIZE
ret nz ; not equal? nothing to do
; equal? reset
xor a
ret
; read char in the ACIA and put it in the read buffer ; read char in the ACIA and put it in the read buffer
aciaInt: aciaInt:
push af push af
@ -51,18 +67,27 @@ aciaInt:
bit 0, a ; is our ACIA rcv buffer full? bit 0, a ; is our ACIA rcv buffer full?
jr z, .end ; no? a interrupt was triggered for nothing. jr z, .end ; no? a interrupt was triggered for nothing.
call aciaBufPtr ; HL set, A set ; Load both read and write indexes so we can compare them. To do so, we
; is our input buffer full? If yes, we don't read anything. Something ; perform a "fake" read increase and see if it brings it to the same
; is wrong: we don't process data fast enough. ; value as the write index.
cp ACIA_BUFSIZE ld a, (ACIA_BUFRDIDX)
jr z, .end ; if BUFIDX == BUFSIZE, our buffer is full. call aciaIncIndex
ld l, a
ld a, (ACIA_BUFWRIDX)
cp l
jr z, .end ; Equal? buffer is full
; Alrighty, buffer not full. let's write.
ld de, ACIA_BUF
; A already contains our write index, add it to DE
call addDE
; increase our buf ptr while we still have it in A ; increase our buf ptr while we still have it in A
inc a call aciaIncIndex
ld (ACIA_BUFIDX), a ld (ACIA_BUFWRIDX), a
; And finially, fetch the value and write it.
in a, (ACIA_IO) in a, (ACIA_IO)
ld (hl), a ld (de), a
.end: .end:
pop hl pop hl
@ -70,18 +95,30 @@ aciaInt:
ei ei
reti reti
; Set current buffer pointer in HL. The buffer pointer is where our *next* char ; Read a character from the input buffer. If the buffer is empty, loop until
; will be written. A is set to the value of (BUFIDX) ; there something to fetch. Returns value in A.
aciaBufPtr: aciaGetC:
push bc push de
ld a, (ACIA_BUFIDX) .loop:
ld hl, ACIA_BUF ld a, (ACIA_BUFWRIDX)
xor b ld e, a
ld c, a ld a, (ACIA_BUFRDIDX)
add hl, bc ; hl now points to INPTBUF + BUFIDX cp e
jr z, .loop ; equal? buffer empty, wait.
pop bc ; Alrighty, buffer not empty. let's read.
ld de, ACIA_BUF
; A already contains our read index, add it to DE
call addDE
; increase our buf ptr while we still have it in A
call aciaIncIndex
ld (ACIA_BUFRDIDX), a
; And finially, fetch the value.
ld a, (de)
pop de
ret ret
; spits character in A in port SER_OUT ; spits character in A in port SER_OUT

View File

@ -18,6 +18,26 @@ addDE:
ld e, a ld e, a
ret ret
; Increase HL until the memory address it points to is null for a maximum of
; 0xff bytes. Returns the new HL value as well as the number of bytes iterated
; in A.
findnull:
push bc
ld a, 0xff
ld b, a
.loop: ld a, (hl)
cp 0
jr z, .end
inc hl
djnz .loop
.end:
; We ran 0xff-B loops. That's the result that goes in A.
ld a, 0xff
sub a, b
pop bc
ret
; Format the lower nibble of A into a hex char and stores the result in A. ; Format the lower nibble of A into a hex char and stores the result in A.
fmtHex: fmtHex:
and a, 0xf and a, 0xf
@ -191,3 +211,4 @@ upcase:
; 'a' - 'A' == 0x20 ; 'a' - 'A' == 0x20
sub 0x20 sub 0x20
ret ret

View File

@ -26,18 +26,29 @@ SHELL_ERR_UNKNOWN_CMD .equ 0x01
; Arguments for the command weren't properly formatted ; Arguments for the command weren't properly formatted
SHELL_ERR_BAD_ARGS .equ 0x02 SHELL_ERR_BAD_ARGS .equ 0x02
; Size of the shell command buffer. If a typed command reaches this size, the
; command is flushed immediately (same as pressing return).
SHELL_BUFSIZE .equ 0x20
; *** VARIABLES *** ; *** VARIABLES ***
; Memory address that the shell is currently "pointing at" for peek and deek ; Memory address that the shell is currently "pointing at" for peek and deek
; operations. Set with seek. ; operations. Set with seek.
SHELL_MEM_PTR .equ SHELL_RAMSTART SHELL_MEM_PTR .equ SHELL_RAMSTART
; Used to store formatted hex values just before printing it. ; Used to store formatted hex values just before printing it.
SHELL_HEX_FMT .equ SHELL_MEM_PTR+2 SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
SHELL_RAMEND .equ SHELL_HEX_FMT+2
; Command buffer. We read types chars into this buffer until return is pressed
; This buffer is null-terminated and we don't keep an index around: we look
; for the null-termination every time we write to it. Simpler that way.
SHELL_BUF .equ SHELL_HEX_FMT+2
SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
; *** CODE *** ; *** CODE ***
shellInit: shellInit:
xor a xor a
ld (SHELL_MEM_PTR), a ld (SHELL_MEM_PTR), a
ld (SHELL_BUF), a
; print prompt ; print prompt
ld hl, .prompt ld hl, .prompt
@ -49,37 +60,42 @@ shellInit:
.db "Collapse OS", 0 .db "Collapse OS", 0
shellLoop: shellLoop:
; check if the input buffer is full or ends in CR or LF. If it does, ; First, let's wait until something is typed.
; prints it back and empty it. call aciaGetC
call aciaBufPtr ; got it. Now, is it a CR or LF?
cp 0
jr z, shellLoop ; BUFIDX is zero? nothing to check.
cp ACIA_BUFSIZE
jr z, .do ; if BUFIDX == BUFSIZE? do!
; our previous char is in BUFIDX - 1. Fetch this
dec hl
ld a, (hl) ; now, that's our char we have in A
inc hl ; put HL back where it was
cp ASCII_CR cp ASCII_CR
jr z, .do ; char is CR? do! jr z, .do ; char is CR? do!
cp ASCII_LF cp ASCII_LF
jr z, .do ; char is LF? do! jr z, .do ; char is LF? do!
; nothing matched? don't do anything ; Ok, gotta add it do the buffer
jr shellLoop ; save char for later
.do: ex af, af'
; terminate our string with 0 ld hl, SHELL_BUF
call findnull ; HL points to where we need to write
; A is the number of chars in the buf
cp SHELL_BUFSIZE
jr z, .do ; A == bufsize? then our buffer is full. do!
; bring the char back in A
ex af, af'
; Buffer not full, not CR or LF. Let's put that char in our buffer and
; read again.
ld (hl), a
; Now, write a zero to the next byte to properly terminate our string.
inc hl
xor a xor a
ld (hl), a ld (hl), a
; reset buffer index
ld (ACIA_BUFIDX), a
; alright, let's go! jr shellLoop
ld hl, ACIA_BUF
.do:
ld hl, SHELL_BUF
call shellParse call shellParse
; empty our buffer by writing a zero to its first char
xor a
ld (hl), a
jr shellLoop jr shellLoop
printcrlf: printcrlf: