mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-18 09:18:06 +11:00
Extract "acia.asm" from shell
Also, come up with a way to make parts play well together memory-wise.
This commit is contained in:
parent
ac22a206ae
commit
6bb454232a
@ -2,7 +2,37 @@
|
|||||||
|
|
||||||
Bits and pieces of code that you can assemble to build an OS for your machine.
|
Bits and pieces of code that you can assemble to build an OS for your machine.
|
||||||
|
|
||||||
|
These parts are made to be glued together in a single `main.asm` file you write
|
||||||
|
yourself.
|
||||||
|
|
||||||
As of now, the z80 assembler code is written to be assembled with [scas][scas],
|
As of now, the z80 assembler code is written to be assembled with [scas][scas],
|
||||||
but this is going to change in the future as a new hosted assembler is written.
|
but this is going to change in the future as a new hosted assembler is written.
|
||||||
|
|
||||||
|
## Defines
|
||||||
|
|
||||||
|
Each part can have its own constants, but some constant are made to be defined
|
||||||
|
externally. We already have some of those external definitions in platform
|
||||||
|
includes, but we can have more defines than this.
|
||||||
|
|
||||||
|
Each part has a "DEFINES" section listing the constant it expects to be defined.
|
||||||
|
Make sure that you have these constants defined before you include the file.
|
||||||
|
|
||||||
|
## Variable management
|
||||||
|
|
||||||
|
Each part can define variables. These variables are defined as addresses in
|
||||||
|
RAM. We know where RAM start from the `RAMSTART` constant in platform includes,
|
||||||
|
but because those parts are made to be glued together in no pre-defined order,
|
||||||
|
we need a system to align variables from different modules in RAM.
|
||||||
|
|
||||||
|
This is why each part that has variable expect a `<PARTNAME>_RAMSTART`
|
||||||
|
constant to be defined and, in turn, defines a `<PARTNAME>_RAMEND` constant to
|
||||||
|
carry to the following part.
|
||||||
|
|
||||||
|
Thus, code that glue parts together coould look like:
|
||||||
|
|
||||||
|
MOD1_RAMSTART .equ RAMSTART
|
||||||
|
#include "mod1.asm"
|
||||||
|
MOD2_RAMSTART .equ MOD1_RAMEND
|
||||||
|
#include "mod2.asm"
|
||||||
|
|
||||||
[scas]: https://github.com/KnightOS/scas
|
[scas]: https://github.com/KnightOS/scas
|
||||||
|
97
parts/acia.asm
Normal file
97
parts/acia.asm
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
; acia
|
||||||
|
;
|
||||||
|
; Manage I/O from an asynchronous communication interface adapter (ACIA).
|
||||||
|
; provides "aciaPutC" to put c char on the ACIA as well as an input buffer.
|
||||||
|
; You have to call "aciaInt" on interrupt for this module to work well.
|
||||||
|
;
|
||||||
|
; "aciaInit" also has to be called on boot, but it doesn't call "ei" and "im 1",
|
||||||
|
; which is the responsibility of the main asm file, but is needed.
|
||||||
|
|
||||||
|
; *** DEFINES ***
|
||||||
|
; ACIA_CTL: IO port for the ACIA's control registers
|
||||||
|
; ACIA_IO: IO port for the ACIA's data registers
|
||||||
|
; ACIA_RAMSTART: Address at which ACIA-related variables should be stored in
|
||||||
|
; RAM.
|
||||||
|
|
||||||
|
; *** CONSTS ***
|
||||||
|
; size of the input buffer. If our input goes over this size, we echo
|
||||||
|
; immediately.
|
||||||
|
ACIA_BUFSIZE .equ 0x20
|
||||||
|
|
||||||
|
; *** VARIABLES ***
|
||||||
|
; Our input buffer starts there
|
||||||
|
ACIA_BUF .equ ACIA_RAMSTART
|
||||||
|
|
||||||
|
; index, in the buffer, where our next character will go. 0 when the buffer is
|
||||||
|
; empty, BUFSIZE-1 when it's almost full.
|
||||||
|
ACIA_BUFIDX .equ ACIA_BUF+ACIA_BUFSIZE
|
||||||
|
ACIA_RAMEND .equ ACIA_BUFIDX+1
|
||||||
|
|
||||||
|
aciaInit:
|
||||||
|
; initialize variables
|
||||||
|
xor a
|
||||||
|
ld (ACIA_BUFIDX), a ; starts at 0
|
||||||
|
|
||||||
|
; setup ACIA
|
||||||
|
; CR7 (1) - Receive Interrupt enabled
|
||||||
|
; CR6:5 (00) - RTS low, transmit interrupt disabled.
|
||||||
|
; CR4:2 (101) - 8 bits + 1 stop bit
|
||||||
|
; CR1:0 (10) - Counter divide: 64
|
||||||
|
ld a, 0b10010110
|
||||||
|
out (ACIA_CTL), a
|
||||||
|
ret
|
||||||
|
|
||||||
|
; read char in the ACIA and put it in the read buffer
|
||||||
|
aciaInt:
|
||||||
|
push af
|
||||||
|
push hl
|
||||||
|
|
||||||
|
; Read our character from ACIA into our BUFIDX
|
||||||
|
in a, (ACIA_CTL)
|
||||||
|
bit 0, a ; is our ACIA rcv buffer full?
|
||||||
|
jr z, .end ; no? a interrupt was triggered for nothing.
|
||||||
|
|
||||||
|
call aciaBufPtr ; HL set, A set
|
||||||
|
; is our input buffer full? If yes, we don't read anything. Something
|
||||||
|
; is wrong: we don't process data fast enough.
|
||||||
|
cp ACIA_BUFSIZE
|
||||||
|
jr z, .end ; if BUFIDX == BUFSIZE, our buffer is full.
|
||||||
|
|
||||||
|
; increase our buf ptr while we still have it in A
|
||||||
|
inc a
|
||||||
|
ld (ACIA_BUFIDX), a
|
||||||
|
|
||||||
|
in a, (ACIA_IO)
|
||||||
|
ld (hl), a
|
||||||
|
|
||||||
|
.end:
|
||||||
|
pop hl
|
||||||
|
pop af
|
||||||
|
ei
|
||||||
|
reti
|
||||||
|
|
||||||
|
; Set current buffer pointer in HL. The buffer pointer is where our *next* char
|
||||||
|
; will be written. A is set to the value of (BUFIDX)
|
||||||
|
aciaBufPtr:
|
||||||
|
push bc
|
||||||
|
|
||||||
|
ld a, (ACIA_BUFIDX)
|
||||||
|
ld hl, ACIA_BUF
|
||||||
|
xor b
|
||||||
|
ld c, a
|
||||||
|
add hl, bc ; hl now points to INPTBUF + BUFIDX
|
||||||
|
|
||||||
|
pop bc
|
||||||
|
ret
|
||||||
|
|
||||||
|
; spits character in A in port SER_OUT
|
||||||
|
aciaPutC:
|
||||||
|
push af
|
||||||
|
.stwait:
|
||||||
|
in a, (ACIA_CTL) ; get status byte from SER
|
||||||
|
bit 1, a ; are we still transmitting?
|
||||||
|
jr z, .stwait ; if yes, wait until we aren't
|
||||||
|
pop af
|
||||||
|
out (ACIA_IO), a ; push current char
|
||||||
|
ret
|
||||||
|
|
81
parts/shell.asm
Normal file
81
parts/shell.asm
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
; shell
|
||||||
|
;
|
||||||
|
; Runs a shell over an asynchronous communication interface adapter (ACIA).
|
||||||
|
; for now, this unit is tightly coupled to acia.asm, but it will eventually be
|
||||||
|
; more general than that.
|
||||||
|
|
||||||
|
; Incomplete. For now, this outputs a welcome prompt and then waits for input.
|
||||||
|
; Whenever input is CR or LF, we echo back what we've received and empty the
|
||||||
|
; input buffer. This also happen when the buffer is full.
|
||||||
|
|
||||||
|
; *** CONSTS ***
|
||||||
|
CR .equ 0x0d
|
||||||
|
LF .equ 0x0a
|
||||||
|
|
||||||
|
shellInit:
|
||||||
|
; print prompt
|
||||||
|
ld hl, d_welcome
|
||||||
|
call printstr
|
||||||
|
call printcrlf
|
||||||
|
ret
|
||||||
|
|
||||||
|
shellLoop:
|
||||||
|
call chkbuf
|
||||||
|
jr shellLoop
|
||||||
|
|
||||||
|
; print null-terminated string pointed to by HL
|
||||||
|
printstr:
|
||||||
|
ld a, (hl) ; load character to send
|
||||||
|
or a ; is it zero?
|
||||||
|
ret z ; if yes, we're finished
|
||||||
|
call aciaPutC
|
||||||
|
inc hl
|
||||||
|
jr printstr
|
||||||
|
; no ret because our only way out is ret z above
|
||||||
|
|
||||||
|
printcrlf:
|
||||||
|
ld a, CR
|
||||||
|
call aciaPutC
|
||||||
|
ld a, LF
|
||||||
|
call aciaPutC
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
; check if the input buffer is full or ends in CR or LF. If it does, prints it
|
||||||
|
; back and empty it.
|
||||||
|
chkbuf:
|
||||||
|
call aciaBufPtr
|
||||||
|
cp 0
|
||||||
|
ret z ; 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 CR
|
||||||
|
jr z, .do ; char is CR? do!
|
||||||
|
cp LF
|
||||||
|
jr z, .do ; char is LF? do!
|
||||||
|
|
||||||
|
; nothing matched? don't do anything
|
||||||
|
ret
|
||||||
|
|
||||||
|
.do:
|
||||||
|
; terminate our string with 0
|
||||||
|
xor a
|
||||||
|
ld (hl), a
|
||||||
|
; reset buffer index
|
||||||
|
ld (ACIA_BUFIDX), a
|
||||||
|
|
||||||
|
; alright, let's go!
|
||||||
|
ld hl, ACIA_BUF
|
||||||
|
call printstr
|
||||||
|
call printcrlf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; *** DATA ***
|
||||||
|
d_welcome: .byte "Welcome to Collapse OS", 0
|
@ -1,177 +0,0 @@
|
|||||||
; shell
|
|
||||||
;
|
|
||||||
; Runs a shell over an asynchronous communication interface adapter (ACIA).
|
|
||||||
|
|
||||||
; Incomplete. For now, this outputs a welcome prompt and then waits for input.
|
|
||||||
; Whenever input is CR or LF, we echo back what we've received and empty the
|
|
||||||
; input buffer. This also happen when the buffer is full.
|
|
||||||
|
|
||||||
#include "platform.inc"
|
|
||||||
|
|
||||||
; *** CONSTS ***
|
|
||||||
CR .equ 0x0d
|
|
||||||
LF .equ 0x0a
|
|
||||||
|
|
||||||
; size of the input buffer. If our input goes over this size, we echo
|
|
||||||
; immediately.
|
|
||||||
BUFSIZE .equ 0x20
|
|
||||||
|
|
||||||
; *** VARIABLES ***
|
|
||||||
; Our input buffer starts there
|
|
||||||
INPTBUF .equ RAMSTART
|
|
||||||
|
|
||||||
; index, in the buffer, where our next character will go. 0 when the buffer is
|
|
||||||
; empty, BUFSIZE-1 when it's almost full.
|
|
||||||
BUFIDX .equ INPTBUF+BUFSIZE
|
|
||||||
|
|
||||||
; *** CODE ***
|
|
||||||
jr init
|
|
||||||
|
|
||||||
.fill 0x38-$
|
|
||||||
jr handleInterrupt
|
|
||||||
|
|
||||||
init:
|
|
||||||
di
|
|
||||||
|
|
||||||
; setup stack
|
|
||||||
ld hl, RAMEND
|
|
||||||
ld sp, hl
|
|
||||||
|
|
||||||
; initialize variables
|
|
||||||
xor a
|
|
||||||
ld (BUFIDX), a ; starts at 0
|
|
||||||
|
|
||||||
; RC2014's serial I/O is based on interrupt mode 1. We'd prefer im 2,
|
|
||||||
; but for now, let's go with the simpler im 1.
|
|
||||||
im 1
|
|
||||||
|
|
||||||
; setup ACIA
|
|
||||||
; CR7 (1) - Receive Interrupt enabled
|
|
||||||
; CR6:5 (00) - RTS low, transmit interrupt disabled.
|
|
||||||
; CR4:2 (101) - 8 bits + 1 stop bit
|
|
||||||
; CR1:0 (10) - Counter divide: 64
|
|
||||||
ld a, 0b10010110
|
|
||||||
out (ACIA_CTL), a
|
|
||||||
|
|
||||||
; print prompt
|
|
||||||
ld hl, d_welcome
|
|
||||||
call printstr
|
|
||||||
call printcrlf
|
|
||||||
|
|
||||||
; alright, ready to receive
|
|
||||||
ei
|
|
||||||
|
|
||||||
mainloop:
|
|
||||||
call chkbuf
|
|
||||||
jr mainloop
|
|
||||||
|
|
||||||
; read char in the ACIA and put it in the read buffer
|
|
||||||
handleInterrupt:
|
|
||||||
push af
|
|
||||||
push hl
|
|
||||||
|
|
||||||
; Read our character from ACIA into our BUFIDX
|
|
||||||
in a, (ACIA_CTL)
|
|
||||||
bit 0, a ; is our ACIA rcv buffer full?
|
|
||||||
jr z, .end ; no? a interrupt was triggered for nothing.
|
|
||||||
|
|
||||||
call getbufptr ; HL set, A set
|
|
||||||
; is our input buffer full? If yes, we don't read anything. Something
|
|
||||||
; is wrong: we don't process data fast enough.
|
|
||||||
cp BUFSIZE
|
|
||||||
jr z, .end ; if BUFIDX == BUFSIZE, our buffer is full.
|
|
||||||
|
|
||||||
; increase our buf ptr while we still have it in A
|
|
||||||
inc a
|
|
||||||
ld (BUFIDX), a
|
|
||||||
|
|
||||||
in a, (ACIA_IO)
|
|
||||||
ld (hl), a
|
|
||||||
|
|
||||||
.end:
|
|
||||||
pop hl
|
|
||||||
pop af
|
|
||||||
ei
|
|
||||||
reti
|
|
||||||
|
|
||||||
; spits character in A in port SER_OUT
|
|
||||||
printc:
|
|
||||||
push af
|
|
||||||
.stwait:
|
|
||||||
in a, (ACIA_CTL) ; get status byte from SER
|
|
||||||
bit 1, a ; are we still transmitting?
|
|
||||||
jr z, .stwait ; if yes, wait until we aren't
|
|
||||||
pop af
|
|
||||||
out (ACIA_IO), a ; push current char
|
|
||||||
ret
|
|
||||||
|
|
||||||
; print null-terminated string pointed to by HL
|
|
||||||
printstr:
|
|
||||||
ld a, (hl) ; load character to send
|
|
||||||
or a ; is it zero?
|
|
||||||
ret z ; if yes, we're finished
|
|
||||||
call printc
|
|
||||||
inc hl
|
|
||||||
jr printstr
|
|
||||||
; no ret because our only way out is ret z above
|
|
||||||
|
|
||||||
printcrlf:
|
|
||||||
ld a, CR
|
|
||||||
call printc
|
|
||||||
ld a, LF
|
|
||||||
call printc
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
; check if the input buffer is full or ends in CR or LF. If it does, prints it
|
|
||||||
; back and empty it.
|
|
||||||
chkbuf:
|
|
||||||
call getbufptr
|
|
||||||
cp 0
|
|
||||||
ret z ; BUFIDX is zero? nothing to check.
|
|
||||||
|
|
||||||
cp 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 CR
|
|
||||||
jr z, .do ; char is CR? do!
|
|
||||||
cp LF
|
|
||||||
jr z, .do ; char is LF? do!
|
|
||||||
|
|
||||||
; nothing matched? don't do anything
|
|
||||||
ret
|
|
||||||
|
|
||||||
.do:
|
|
||||||
; terminate our string with 0
|
|
||||||
xor a
|
|
||||||
ld (hl), a
|
|
||||||
; reset buffer index
|
|
||||||
ld (BUFIDX), a
|
|
||||||
|
|
||||||
; alright, let's go!
|
|
||||||
ld hl, INPTBUF
|
|
||||||
call printstr
|
|
||||||
call printcrlf
|
|
||||||
ret
|
|
||||||
|
|
||||||
; Set current buffer pointer in HL. The buffer pointer is where our *next* char
|
|
||||||
; will be written. A is set to the value of (BUFIDX)
|
|
||||||
getbufptr:
|
|
||||||
push bc
|
|
||||||
|
|
||||||
ld a, (BUFIDX)
|
|
||||||
ld hl, INPTBUF
|
|
||||||
xor b
|
|
||||||
ld c, a
|
|
||||||
add hl, bc ; hl now points to INPTBUF + BUFIDX
|
|
||||||
|
|
||||||
pop bc
|
|
||||||
ret
|
|
||||||
|
|
||||||
; *** DATA ***
|
|
||||||
d_welcome: .byte "Welcome to Collapse OS", 0
|
|
@ -31,18 +31,69 @@ device I use in this recipe.
|
|||||||
|
|
||||||
### Gathering parts
|
### Gathering parts
|
||||||
|
|
||||||
* `parts/platforms/rc2014.inc` as `platform.inc`
|
* Collapse OS parts in `/path/to/parts`
|
||||||
* `parts/shell/shell.asm` as `shell.asm`
|
|
||||||
* [scas][scas]
|
* [scas][scas]
|
||||||
* [romwrite][romwrite] and its specified dependencies
|
* [romwrite][romwrite] and its specified dependencies
|
||||||
* [GNU screen][screen]
|
* [GNU screen][screen]
|
||||||
* A FTDI-to-TTL cable to connect to the Serial I/O module of the RC2014
|
* A FTDI-to-TTL cable to connect to the Serial I/O module of the RC2014
|
||||||
|
|
||||||
|
### Write main.asm
|
||||||
|
|
||||||
|
This is what your glue code would look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
#include "platforms/rc2014.inc"
|
||||||
|
|
||||||
|
jr init
|
||||||
|
|
||||||
|
.fill 0x38-$
|
||||||
|
jr aciaInt
|
||||||
|
|
||||||
|
init:
|
||||||
|
di
|
||||||
|
|
||||||
|
; setup stack
|
||||||
|
ld hl, RAMEND
|
||||||
|
ld sp, hl
|
||||||
|
|
||||||
|
im 1
|
||||||
|
|
||||||
|
call aciaInit
|
||||||
|
call shellInit
|
||||||
|
ei
|
||||||
|
call shellLoop
|
||||||
|
|
||||||
|
ACIA_RAMSTART .equ RAMSTART
|
||||||
|
#include "acia.asm"
|
||||||
|
#include "shell.asm"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `platform.inc` include is there to load all platform-specific constants
|
||||||
|
(such as `RAMSTART` and `RAMEND`).
|
||||||
|
|
||||||
|
Then come the reset vectors. If course, we have our first jump to our main init
|
||||||
|
routine, and then we have a jump to the interrupt handler defined in `acia.asm`.
|
||||||
|
|
||||||
|
We need to plug this one in so that we can receive characters from the ACIA.
|
||||||
|
|
||||||
|
Then comes the usual `di` to aoid interrupts during init, and stack setup.
|
||||||
|
|
||||||
|
We set interrupt mode to 1 because that's what `acia.asm` is written around.
|
||||||
|
|
||||||
|
Then, we init ACIA, shell, enable interrupt and give control of the main loop
|
||||||
|
to `shell.asm`.
|
||||||
|
|
||||||
|
What comes below is actual code include from the acia and shell modules. As you
|
||||||
|
can see, we need to tell each module where to put their variables. `shell.asm`
|
||||||
|
doesn't have variables, but if it did, we would have a `SHELL_RAMSTART .equ
|
||||||
|
ACIA_RAMEND` just below the `acia.asm` include. `ACIA_RAMEND` is defined in
|
||||||
|
`acia.asm`.
|
||||||
|
|
||||||
### Build the image
|
### Build the image
|
||||||
|
|
||||||
We only have the shell to build, so it's rather straightforward:
|
We only have the shell to build, so it's rather straightforward:
|
||||||
|
|
||||||
scas -o rom.bin shell.asm
|
scas -I /path/to/parts -o rom.bin main.asm
|
||||||
|
|
||||||
### Write to the ROM
|
### Write to the ROM
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user