This Week With My Coleco ADAM 9709.07

by Richard F. Drushel (drushel@apk.net)

I. More About the EOS-8 Project.

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.

II. A Detailed Description of EOS-8.

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.


*** EOS87.HLP *** help file for EOS8 rev.7 (9209.20)

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.


WHAT'S NEW IN EOS8 rev.7.


WHAT'S MISSING FROM EOS8 rev.7.


HOW TO LOAD EOS8 rev.7.

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

NEW GLOBAL ENTRY POINTS IN EOS8 rev.7

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

III. Universal Non-ADAMnet Serial and Parallel Drivers.

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

IV. For Next Time.

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).

V. Administrivia.

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