Further testing has determined that I can read and write both of POC V2.1's DUARTs, which means I should be able to produce a display on the console screen and read the console keyboard. It was relatively easy to take the interrupt part of POC V1.1's serial I/O (SIO) driver and adapt it to V2. Some minor rework was required to adapt the foreground part of the driver. Accordingly, I'm working up some firmware that I hope to have ready to go in a few days. While doing so, I have a bit of a conundrum to resolve.
V1 has two SIO channels, A and B, indexed as 0 and 1—channel 0 is the console. There are four BIOS API calls for doing SIO: read from channel A, write to channel A, read from channel B and write to channel B. That arrangement has worked well and in fact, came about because all of the firmware, which includes the machine language monitor, as well as reset, interrupt code and the BIOS, has to fit within 8 KB of ROM. In contrast, V2 has 8 KB of ROM devoted to the BIOS, interrupt handlers and reset code, and a separate 4 KB ROM segment into which the M/L monitor will be loaded. So I have more elbow room for BIOS API refinements, among which could be the consolidation of the SIO functions into two calls, using channel indexing to select the desired input or output path.
V2 has four SIO channels, 0, 1, 2 and 3, hence there are theoretically eight separate API functions for serial I/O, four read and four write. Internally, the code that does SIO is common to all channels—internal indexing selects direct page pointers that select the correct data structures and hardware registers. The indirect indexing principle that selects a channel has been tested on POC V1.1, so I know it works. The question will be how to set the channel index when an SIO API call is made.
Before going any further, the general form of an API call with V2 is:
Code:
pea #<api_num> ;push API index
cop $00 ;call API ("trap")
where
<API_NUM> is a 1-based index that ultimately selects the code to be run. Registers are loaded with parameters as required for the call. Returns likewise are passed in the registers, and carry is used to signal "success" or "failure."
Internally, the code that does the SIO processing works with a table that points at the data structures and hardware registers associated with each channel. So I started thinking about a different way of accepting SIO calls that might be more convenient for application programming and came up with several ways in which I could set up a call to, say, write a datum to serial channel C:
Code:
lda #datum ;load datum
pea #_siowrtc_ ;push write channel C API number
cop $00 ;write to channel C
The API index would have to be different for each channel. That could be a problem within, say, a general purpose character string printing function. How do you tell it which channel should get the output? The API number corresponding to the desired channel could be pushed via a register with each call, but then that register gets clobbered.
Another method would be:
Code:
lda #datum ;load datum
ldx #siochcix ;channel C's index
pea #_siowrt_ ;push write API number
cop $00 ;write to channel C
Here a single API is used for all channels and the channel index is passed as a parameter to the call, improving generality. However,
.X would have to be preserved by the caller if its value is important and if it is being used in a loop that is calling the API, would have to be loaded with the channel index at each call.
Still another method would be:
Code:
lda #datum ;load datum
pea #siochcix ;push channel C's index
pea #_siowrt_ ;push write API index
cop $00 ;write to channel C
The only difference between this one and the previous method is how the channel index is passed to the API.
I pondered this for some time and then thought about how I/O was done in eight bit Commodore computers. A single "kernal" call,
CHROUT, is used to write to anything and a complementary call,
CHRIN, is used to read from anything. In order to tell the kernel from where to get input or where to send output, the
CHKIN (input) and
CHKOUT (output) calls are used. For example, once
CHKOUT has been called and has returned error-free, output through
CHROUT will "magically" go to the selected device or file. Typical code for doing so would be as follows:
Code:
;Commodore chkout function call...
;
ldx #5 ;file descriptor
jsr chkout ;set it as output
bcs error
;
lda #'A' ;write 'A'
jsr chrout ;will go to file opened on 5
When done, a call to
CLRCHN will break the connections set up with
CHKIN and
CHKOUT, restoring I/O to the screen and keyboard. Obviously, this arrangement produces a degree of generality: a general purpose output subroutine can print anywhere. All the caller has to do is say where to print.
In V2, something similar could be arranged:
Code:
lda #siochcix ;channel C's index
pea #_sioout_ ;set SIO channel API number
cop $00 ;set channel C as output
bcs error ;channel index out of range
;
; At this point, we can endlessly write to channel C, as the BIOS will be
; configured to write to the channel we've selected. The following code
; writes 40 'A's to channel C...
;
lda #'A'
ldx #40
;
loop pea #_siowrt_ ;SIO write API index
cop $00 ;write to 'C' to channel A
dex ;repeat...
bne loop ;39 times
;
;
; restore default SIO channel...
;
pea #_siodef_ ;default SIO channel's API index
cop $00 ;channel A becomes default
The default SIO channel is channel A (0), which is the console. So it's convenient to have a quick call to re-select the default. A similar arrangement to the above would be required to handle input, using the same calling methodology.
SIO API calls read and write circular FIFOs (hardware access is via IRQs), which can happen very fast once the required pointers have been selected. As the channel index doesn't have to be passed with each SIO input or output call, the BIOS primitives code that process the call don't have as much work to do—no channel pointer setup has to be done—and hence should run faster.
There is more complexity in having separate calls to set input and output paths. Also, more direct page storage will be consumed to keep track of which channel is current input and which channel is current output. That said, I'm looking ahead to when a version of POC gets built with eight serial channels. Whatever method I used to select a channel for I/O will have to be sufficiently flexible to handle that many channels. So some planning ahead is probably in order.