by Richard F. Drushel (drushel@apk.net)
I've had a few inquiries about the internals of EOS-8, my in-progress/abandoned upgrade to the EOS operating system. I was digging through some old correspondence and found some informational descriptions which might be of interest. I also decided that I would post some commented code for the universal serial and parallel drivers that I wrote, since these are in active use in ADAMserve (and also SmartBASIC 1.x, whence they were derived). This makes a rather easy TWWMCA article for me, since I did the actual writing in 1992 :-) But *you've* never seen it before, so I'm not blushing too badly about dredging it up.
Here is a helpfile I wrote for the benefit of the other programmers who were nominally helping with the EOS-8 project. This accompanied a complete source listing and bootable SmartBASIC 2.0 binary which had been uploaded to Mark Gordon's BBS. I haven't found my disk with this binary yet, but when I do, I will put an .IMG of the disk up for download.
Two errors of faulty recollection I made in TWWMCA 9708.31: the prototype ADAMnet parallel device is accessed as PR #2 from SmartBASIC 2.0, and the serial device as PR #4 and IN #4. I will correct the archived version of this article; newsletter editors and other archivers, please make this fix or grab my fixed version.
by Richard F. Drushel
This file provides a brief overview of the design philosophy and implementation of EOS8, specifically rev.7. This should serve as a guide for outside reviewers and programmers as EOS8 is finalized.
Logical-to-physical device mapping. ADAMnet device numbers can be reassigned to non-ADAMnet physical devices, to allow existing programs to function with non-ADAMnet hardware (i.e., use a parallel printer whenever a call is made to the ADAM printer). Device numbers 32-63 are reserved for up to 32 non-ADAMnet devices; the following are defined:
32 USERPAR_ID parallel port (64) 33 USERSER_ID serial port (any Eve/Orphanware/MIB2) 34 USERAMDISK_ID RAMdisk using standard XRAM 35 USERHD_ID Hard disk (any Mini Wini/SASI/IDE)
This includes complete emulation of ADAMnet devices by non-ADAMnet equivalents. Physical devices use dummy DCBs which are set up as if they were controlling an actual ADAMnet device. This includes the status byte at (DCB+0) and the device-dependent status at (DCB+20). For serial devices, the (DCB+20) status bits for # characters pending and ok to send are maintained exactly like the prototype ADAMnet serial board.
I/O for non-ADAMnet devices is *NOT* concurrent/background. Thus, all I/O initiated with a _START_... call is performed to completion before exiting the call, including updating the dummy DCB. This means that _END_... calls for non-ADAMnet I/O don't do anything except return the status from the dummy DCB.
Defined overlay area for hard drives. The overlay has a defined code and data interface so that any type of hard drive (or other block device) can be used. The overlay includes the volume offset table which is used to specify the volume sizes of the particular EOS partition. This overlay is positioned so that the current HD volume remains at absolute address 58343, for compatibility with existing programs. The overlay must be less than 400 bytes long. The overlay interface accommodates up to 16 EOS volumes, and has provisions for 2 hard drives per physical interface:
HD_MW1_CODE EQU 1
HD_MW2_CODE EQU 2
HD_SASI1_CODE EQU 3
HD_SASI2_CODE EQU 4
HD_IDE1_CODE EQU 5
HD_IDE2_CODE EQU 6
Note: the current version was assembled with Mini Wini HD drivers in place, assuming 10 EOS volumes 1024 blocks each. You will have to reassemble it for your own HD characteristics to access any volume other than 0; 0 will always work :)
Hotkey functions for hard drives. The following keys are trapped:
Shift-Undo reboots HD from volume 0/block 0 Shift-WildCard parks HD heads Shift-Tab toggles ahead the current HD volume, wrapping back to 0 when MAX_VOLUMES is exceeded
User-defined hotkey. This was implemented as part of EOS7. The user specifies a key to trap, and a vector to a handler routine. When this key is pressed, the handler routine is called; control returns to the main program.
User-defined number of FCBs and DTAs. Instead of being hard-coded at 3, the user can change the contents of NUM_FCBS to have as many files open at once as desired. Make sure you _FMGR_INIT after changing NUM_FCBS :) This was also part of EOS7.
NMI clock driver routine. Each CALL to this routine updates the clock by one tick, complete with weekday, day, month, and year rollover. Additional locations in EOS_GLOBAL_RAM are defined to hold 60th of second, second, minute, hour, and weekday.
Auxiliary jump table for direct access to non-ADAMnet devices and extra EOS functions.
Hardware initialization routines for both Eve/Orphanware and MIB2 serial ports. A management decision is needed regarding the parameter passing mechanism--namely, will we remain compatible with the prototype ADAMnet serial board (device 14)? The difficulty with this approach is that the values 0-5 will have to become reserved data values which flag initialization parameters--you would lose the ability to send them as serial data independent of initialization, for the non-ADAMnet serial boards. If initialization compatibility is not maintained, then (1) dedicated init routines will be required for non-ADAMnet serial boards, and (2) it will not be possible for non-ADAMnet serial boards to emulate the initialization code for device 14. SmartBASIC 2.0 does not attempt to initialize device 14, but it is unknown if any other Coleco EOS software attempts to do so.
Trapping of serial port overrun, parity, and framing errors. The source code has conditional assembly of traps for these errors. The enclosed binary has these left out, to avoid problems with SmartBASIC 2.0. (The way the code is in SmartBASIC 2.0, if you type too fast and overrun the port, the routine will loop forever; unlike SmartBASIC 1.x, it will not force a PR#0/IN#0 if an error occurs.) You can reassemble the source with the traps put back in if you like :)
Software RAMdisk routines. Since EOS8 ver.7 is currently larger than 8K, EOS code has spilled into what under EOS5 is the 2nd user DTA. The existing prototype RAMdisk routine (demonstrated at ADAMcon IV) appropriates this area for its own inter-bank transfers. While it is possible to rewrite the RAMdisk I/O to page through a smaller buffer, I have decided to wait until both the hardware clock I/O routines and the VRAM interrupt deferral routines are implemented, so I know how large a RAMdisk buffer I will have available (either 256 or 512 byte).
The 2nd user DTA. To my knowledge, only SmartBASIC actually allows the user to open 2 files at once. Moreover, because the file I/O routines in SmartBASIC are defective, there is no practical use for 2 open files. EOS8 allows the application programmer to both set the number of DTAs and to relocate the DTAs/FCBs, so future programs can have more than 1 user file open at once if proper setup is done. At least one existing program, however (ADAMlink V) uses the 2nd DTA at its absolute address under EOS5 (not relative to the pointer to start of DTAs) as a transfer buffer; these unfortunate programs overwrite the larger EOS8. SmartBASIC hackers also have been known to store machine code routines here.
Hardware clock I/O routines. I can extract them from SmartBASIC 1.x, but have decided to wait for comments about what I have done already.
VRAM I/O deferral during interrupts. The logic of this deferral has been described previously (in VRAMDISC.TXT).
BLOCKS LEFT. EOS7 unfortunately does *NOT* write BLOCKS LEFT as the name of the "not a file" directory entry. Many existing programs look specifically for BLOCKS LEFT, and hence will crash under EOS7-8. I have not yet looked into fixing this.
Bidirectional parallel printer emulation for SmartWriter. I have not included the routine which intercepts the printhead direction changes sent by SmartWriter to the ADAM printer. This means that text printed from SmartWriter will have every other line spelled backwards. (This does not happen from the Electronic Typewriter mode.) An 80-byte line buffer and line inversion software must be added to __PR_CH and __PR_BUFF.
EOS8 rev.7 is currently part of a modified SmartBASIC 2.0 which lacks all the EXTMEM features. (I did it this way as a quick-and-dirty loader, also to test the logical-to-physical remapping of PR#2/PR#4/IN#4.) Put the disk in any drive and pull the reset. Once SmartBASIC 2.0 comes up, you can put any other bootable disk in the drive and CALL 64560 (_EOS_START). The disk will be booted, but the EOS8 will still be in effect.
Since there aren't yet any serial port init routines, you must use some other program to init them *BEFORE* you load EOS8, or else boot SB1.x under EOS8 and use the SERIAL command. My primary testing tool has been SB1.x, because it lets me do EOS function calls directly via register variable setups and CALL EOS(nn). Also, the default value at USER_BASEPORT is 0 (no serial port). You will have to POKE 65206,baseport in order to use device 14 as a non-ADAMnet serial port, where baseport maps as:
SER68_PORT EQU 68
SER76_PORT EQU 76
SER84_PORT EQU 84
SER92_PORT EQU 92
SER_MIB21_PORT EQU 24
SER_MIB22_PORT EQU 16
The current logical-to-physical mapping is:
device 2 (ADAM printer) ---> USERPAR_ID
device 13 (Coleco Centronics) ---> USERPAR_ID
device 14 (Coleco RS-232) ---> USERSER_ID
device 24 (tape 2) ---> USERHD_ID
58343 HD_OVERLAY:
HD_CURRENT_VOLUME:
DS 1
58344 HD_VOL_OFFSET_TABLE:
DS 16
;note: the following 4 JPs are for *INTERNAL EOS USE ONLY*, but are
;fixed entry points which the overlay programmer must provide.
58360 HD_STATUS: ;get dummy DCB status
JP __HD_STATUS
58363 HD_RESET: ;park heads
JP __HD_RESET
58366 HD_WRITE: ;write 1 block
JP __HD_WRITE
58369 HD_READ: ;read 1 block
JP __HD_READ
58372 USER_HD: ;code for HD type
DS 1
[...]
;SECONDARY EOS JUMP TABLE.
; This is for the additional EOS function calls. The bottom of the
;table is fixed, so new entries must be added to the *BEGINNING*. Do *NOT*
;delete any entries! Also, make sure that the JUMP2_COUNT variable is
;updated whenever you add jump table entries!
JUMP2_COUNT EQU 9
64484 _GET_HW_CLOCK EQU $ ;read hardware clock time to system clock
JP __GET_HW_CLOCK
64487 _SET_HW_CLOCK EQU $ ;write system clock time to hardware clock
JP __SET_HW_CLOCK
64490 _UPDATE_NMI_CLOCK EQU $ ;system clock tick (every NMI)
JP __UPDATE_NMI_CLOCK
64493 _WRITE_PAR EQU $ ;write character to parallel port
JP __WRITE_PAR
64496 _READ_SER EQU $ ;read character from serial port
JP __READ_SER
64499 _WRITE_SER EQU $ ;write character to serial port
JP __WRITE_SER
64502 _INIT_SER EQU $ ;initialize serial port
JP __INIT_SER
64505 _GET_PHYS EQU $ ;get the physical device now using a
;logical device
JP __GET_PHYS
64508 _SET_PHYS EQU $ ;specify the physical device to be used
;by a logical device
JP __SET_PHYS
; *** _SET_PHYS IS THE LAST ENTRY!! ***
; *** DO NOT ADD TO THE END OF THIS TABLE!! ***
[...]
65196 TRIGGER_CHAR:
DS 1
65197 USR_DFND_RTN:
DS 2
65199 NUM_FCBS:
DS 1
;begin EOS8 global RAM
65200 EOS_60TH:
DS 1 ;RFD
65201 EOS_SECOND:
DS 1 ;RFD
65202 EOS_MINUTE:
DS 1 ;RFD
65203 EOS_HOUR:
DS 1 ;RFD
65204 EOS_WEEKDAY:
DS 1 ;RFD
65205 INTERRUPT_FLAGS: ;store INT/NMI flags for VRAM routines
DS 1 ;RFD
65206 USER_BASEPORT: ;serial port to be used for ADAMnet
DS 1 ;RFD ;serial board emulations
65207 USER_SER_STATS: ;in case we emulate ADAMnet serial board
DS 6 ;RFD ;initializations (hold parameters)
DS 3 ;added by RFD to reserve space
65216 PCB:
DS P_SIZE
65220 DCBS: ;only 12 real ADAMnet devices permitted
DS 12*D_SIZE
;*************************************************************************
DUMMY_DCBS:
; Note: these will *NOT* be relocated if the PCB is
;relocated! So if you application is switching to XRAM or
;ROM up here, you will *LOSE* these devices!!!
; Note 2: these are *NOT* user entry points! This is for reference
;*ONLY*!! Always access the DCBs indirectly, via _FIND_DCB. The parallel
;DCB is currently stored at DCB_IMAGE, but this is subject to change.
65472 SERIAL_DCB:
DS 21
65493 RAMDISK_DCB:
DS 21
65514 HARDDISK12_DCB:
DS 21
65535 RESERVED_BYTE:
DS 1
Here is commented source code for drivers for the Orphanware and MIB2 serial ports and the Orphanware parallel printer port. These were written to be a "universal" module--just include it in your program, and it can support all 6 serial ports as well as the printer port. For the serial ports, the serial baseport to use is passed in the C register; characters are passed in the A register; the Z flag reflects the status; if an error occurred, A has the error code.
The other great advantage of this code is that it implements a 10-second timeout. If the I/O is not successfully completed within 10 seconds (software loop timing assuming 4 mHz Z80), it times out and returns an error code. This prevents infinite polling loops which require a hard reboot to get out of if you specify the wrong port, or if a printer or modem isn't on-line.
Feel free to use this code. Note the conditional assembly of tests for overrun, framing, or parity errors for the serial drivers. I left them conditional in EOS-8 to save some space in EOS RAM; they are active in the SmartBASIC 1.x code. If these errors occur, the only way to clear them is to reinitialize the serial port.
I haven't included universal serial port initialization code here. It's present in the boot for the ADAMserve boot disk, again requiring the serial baseport passed in the C register, and the other serial port parameters (bitrate, parity, number of stop bits) passed in other registers. I can post this code at a later time.
;**************************************************************************
;Non-ADAMnet Serial Read Character With Timeout Check.
; On entry, C=base port. On exit, if the read was ok, ZF=1 and
;A=character, else ZF=0 and A=error code. Routine retries approximately 10
;seconds before timing out. C is preserved.
;errors
;CHAR_DEV_TIMEOUT_ERR EQU 25 ;parallel or serial timed out
;CHAR_DEV_NO_PORT_ERR EQU 26 ;no port found for parallel or serial
;CHAR_DEV_OR_F_P_ERR EQU 27 ;serial had overrun/framing/parity error
;PRINTER_OFFLINE_ERR EQU 28 ;parallel printer is off-line
;serial baseports
; SER68_PORT EQU 68
; SER76_PORT EQU 76
; SER84_PORT EQU 84
; SER92_PORT EQU 92
; SER_MIB21_PORT EQU 24
; SER_MIB22_PORT EQU 16
;********
__READ_SER:
PUSH HL ;save so we can use it for inner loop counter
PUSH DE ;save so we can use it for outer loop counter
PUSH AF ;not needed per se, but makes exits compatible
;with SER_SEND_TCHK
INC C ;point to status port
LD D,8 ;outer loop counter
LD A,C ;get baseport
CP SER68_PORT ;is it Eve-Orphanware?
JR NC,EVE_OWARE_READ ;YES
MIB2_READ:
OUTR_LP_MI_RD:
LD HL,65535 ;inner loop counter
INNR_LP_MI_RD:
IN A,(C) ;check the status port
LD E,A ;save it in E
CP 255 ;does this port even exist?
JR Z,NO_PORT ;NO
IF OFP_CHK
AND 240 ;YES, so 11110000 check upper 4 bits
JR NZ,OR_FR_P_ERR ;sorry, errors
LD A,E ;check the status again
ELSE
ENDIF
BIT 0,A ;is there a character to read?
JR NZ,READ_RDY_MI ;YES (bit set)
DEC HL ;NO, so one less inner loop
LD A,H
OR L ;down to zero yet?
JR NZ,INNR_LP_MI_RD ;NO, so keep going on inner loop
DEC D ;YES, so one less outer loop
JR NZ,OUTR_LP_MI_RD ;reset the inner loop and keep trying
JR TIME_OUT ;sorry, we've timed out!
;********
READ_RDY_MI:
POP AF ;restore character
POP DE ;all done with outer loop counter
INC C
INC C ;point to data port
IN A,(C) ;get character
RDY2:
DEC C
DEC C
DEC C ;restore C to entry state (baseport)
JR SET_ZF2 ;ZF=1 for ok exit, leaving A=character
;********
EVE_OWARE_READ:
OUTR_LP_EO_RD:
LD HL,65535 ;inner loop counter
INNR_LP_EO_RD:
IN A,(C) ;read status port
LD E,A ;save it in E
CP 255 ;does the port exist?
JR Z,NO_PORT ;NO
IF OFP_CHK
AND 56 ;YES, 00111000 any framing/parity/overrun errors?
JR NZ,OR_FR_P_ERR ;YES, so exit
LD A,E ;NO, so check status again
ELSE
ENDIF
AND 2 ;is there a character ready?
JR NZ,READ_RDY_EO ;YES, so get it
DEC HL ;NO, not yet, so one less inner loop
LD A,H
OR L ;are we down to zero?
JR NZ,INNR_LP_EO_RD ;NO, so keep trying on inner loop
DEC D ;YES, so one less outer loop
JR NZ,OUTR_LP_EO_RD ;not done yet, so reset inner loop and try again
JR TIME_OUT ;we have timed out!
;********
READ_RDY_EO:
POP AF ;restore character
POP DE ;all done with big loop counter
DEC C ;back up to data port
IN A,(C) ;read the character
INC C ;restore C=status port
JR SET_ZF2 ;ZF=1 for ok exit, leaving character in A
;********
SET_ZF:
PUSH HL
SET_ZF2:
LD L,A ;save A
XOR A ;ZF=1
LD A,L ;restore A
POP HL
RET
;**************************************************************************
;Non-ADAMnet Serial Send Character With Timeout Check.
;Modified PR #3 routine from SmartBASIC 1.x version 20Y by Richard F. Drushel
;Removed error jumps to force PR #0 and print error messages, and cleaned up
;some spaghetti left over from binary patching.
; On entry, C=base port and A=character. On exit, if the send was ok,
;ZF=1 and A=character, else ZF=0 and A=error code. Routine retries
;approximately 10 seconds before timing out. C is preserved.
;errors:
;CHAR_DEV_TIMEOUT_ERR EQU 25 ;parallel or serial timed out
;CHAR_DEV_NO_PORT_ERR EQU 26 ;no port found for parallel or serial
;CHAR_DEV_OR_F_P_ERR EQU 27 ;serial had overrun/framing/parity error
__WRITE_SER:
PUSH HL ;save so we can use it for inner loop counter
PUSH DE ;save so we can use it for outer loop counter
PUSH AF ;save character
INC C ;make status port
LD D,8 ;outer loop counter
LD A,C ;get baseport
CP SER68_PORT ;is it Eve-Orphanware?
JR NC,EVE_OWARE_SEND ;YES
MIB2_SEND:
OUTR_LP_MI_SND:
LD HL,65535 ;inner loop counter
INNR_LP_MI_SND:
IN A,(C) ;check the status port
LD E,A ;save it in E
CP 255 ;does this port even exist?
JR Z,NO_PORT ;NO
IF OFP_CHK
AND 240 ;YES, so 11110000 check upper 4 bits
JR NZ,OR_FR_P_ERR ;sorry, errors
ELSE
ENDIF
BIT 3,E ;can we send a character?
JR NZ,SEND_RDY_MI ;YES (bit set)
DEC HL ;NO, so one less inner loop
LD A,H
OR L ;down to zero yet?
JR NZ,INNR_LP_MI_SND ;NO, so keep going on inner loop
DEC D ;YES, so one less outer loop
JR NZ,OUTR_LP_MI_SND ;reset the inner loop and keep trying
TIME_OUT:
LD A,CHAR_DEV_TIMEOUT_ERR ;say we're timed out
SER_SEND_BYE:
SER_READ_BYE:
POP DE ;clear AF off stack
POP DE ;the real DE
POP HL ;restore HL
DEC C ;restore C to entry state (baseport)
OR A ;ZF=0 for error
RET
;********
NO_PORT:
LD A,CHAR_DEV_NO_PORT_ERR ;missing port
JR SER_SEND_BYE ;error exit
;********
OR_FR_P_ERR:
LD A,CHAR_DEV_OR_F_P_ERR ;mark overrun/framing error
JR SER_SEND_BYE ;error exit
;********
SEND_RDY_MI:
POP AF ;restore character
POP DE ;all done with outer loop counter
INC C
INC C ;point to data port
OUT (C),A ;send it
JR RDY2 ;restore C, set ZF=1 and exit with A=character
;********
EVE_OWARE_SEND:
OUTR_LP_EO_SND:
LD HL,65535 ;inner loop counter
INNR_LP_EO_SND:
IN A,(C) ;read status port
LD E,A ;save it in E
CP 255 ;does the port exist?
JR Z,NO_PORT ;NO
IF OFP_CHK
AND 56 ;YES, so 00111000 any framing/parity/overrun errors?
JR NZ,OR_FR_P_ERR ;YES, so exit
LD A,E ;NO, so check status again
ELSE
ENDIF
AND 1 ;is it ready to receive a character?
JR NZ,SEND_RDY_EO ;YES, so send it
DEC HL ;NO, not yet, so one less inner loop
LD A,H
OR L ;are we down to zero?
JR NZ,INNR_LP_EO_SND ;NO, so keep trying on inner loop
DEC D ;YES, so one less outer loop
JR NZ,OUTR_LP_EO_SND ;not done yet, so reset inner loop and try again
JR TIME_OUT ;we have timed out!
;********
SEND_RDY_EO:
POP AF ;restore character
POP DE ;all done with outer loop counter
DEC C ;back up to data port
OUT (C),A ;send the character
INC C ;restore C=status port
JR SET_ZF2 ;ZF=1 for ok exit, leaving character in A
;************************************************************************
;Parallel printer send with timeout check.
;Modified PR #2 routine from SmartBASIC 1.x version 20Y by Richard F. Drushel
;On entry, A=character to send. On exit, if the send was successful, ZF=1 and
;A=entry character. If the printer timed out (either not on-line or non-
;existent), ZF=0 and A=error code.
;errors:
;CHAR_DEV_TIMEOUT_ERR EQU 25 ;parallel or serial timed out
;CHAR_DEV_NO_PORT_ERR EQU 26 ;no port found for parallel or serial
;PRINTER_OFFLINE_ERR EQU 28 ;parallel printer is off-line
__WRITE_PAR:
PUSH HL
PUSH BC ;save for loop counters
PUSH AF ;save character
LD B,9 ;big loop counter
LITTLE_LOOP:
LD HL,65535 ;little loop counter
ONLINE_CHECK:
IN A,(PAR_PORT) ;read the parallel port
CP 255 ;does it exist?
JR Z,NO_PAR_PORT ;NO, so no parallel port error
AND 1 ;is it on-line?
JR NZ,PAR_READY_CHECK ;YES
PAR_NOT_READY:
DEC HL ;NO, so decrement little loop counter
LD A,H
OR L ;are we down to zero?
JR NZ,ONLINE_CHECK ;NO, so keep trying
DEC B ;YES, so decrement big loop counter
JR NZ,LITTLE_LOOP ;not done yet, so restart little loop
PAR_TIMEOUT:
IN A,(PAR_PORT) ;we've timed out! so let's find out why
BIT 1,A ;so was it off-line?
LD A,PRINTER_OFFLINE_ERR ;let's assume not on-line
JR NZ,PAR_ERR_EXIT ;YES
LD A,CHAR_DEV_TIMEOUT_ERR ;NO, anything else is busy
PAR_ERR_EXIT:
OR A ;ZF=0 for error
POP BC ;get rid of AF on stack
JR PAR_BYE_BYE ;and exit
;********
PAR_READY_CHECK:
IN A,(PAR_PORT) ;read the parallel port
AND 2 ;is it ready to receive another character?
JR NZ,PAR_NOT_READY ;NO, so keep trying
POP AF ;YES, so restore character
OUT (PAR_PORT),A ;send it
CALL SET_ZF ;ZF=1 for ok exit while preserving A
PAR_BYE_BYE:
POP BC
POP HL
RET
;**********
NO_PAR_PORT:
LD A,CHAR_DEV_NO_PORT_ERR ;no parallel port found
JR PAR_ERR_EXIT ;so error exit
In my next article, I will discuss interrupt deferral techniques to prevent reentrancy in the EOS video RAM routines. This will include a transcript of some discussions that Chris Braymen and I had on the subject, as well a complete pseudocode for the algorithms, which I successfully implemented in EOS-8, and in a modified version of SmartBASIC 1.x (as proof of concept).
I finally unpacked my 486 system after ADAMcon 09, so now I can start working on all the leftovers in my buffer, including a master set of distribution disks for ADAMserve (including ADAMserve PowerPaint, and user-configurability of server hardware). I will get lynched soon if I don't clear this out.
See you next week!
*Rich*
Next Article
Previous Article
TWWMCA Archive Main Page