PGM RDRIV
        REL
**********************************************************
*                                                        *
* device driver for 4 channel expansion board.           *
* it only uses ports 0, 1 and 2.                         *
*                                                        *
**********************************************************
*
*
MPICMSK EQU  #C2
COKRDY  EQU  2
COPRDY  EQU  1
ERRVEC  EQU  #3FC                 * ERROR TRAP VECTOR
BRKERR  EQU #30
PARERR  EQU 8
BR4800   EQU   16            baud rate = 4800
CONBLOCK EQU   #C
DATBLOCK EQU   #D
PICOCW3  EQU   8
PIC0W1   EQU   8
PIC0W2   EQU   9
PIT0CTRL EQU   3
PIT0CNT  EQU   0
BRESET   EQU   #F
*
* Pkt types
*
A.BRKSTP EQU   995
A.BRKREQ EQU   996
A.SPDCHG EQU   997
A.TTYIN  EQU   999
A.TTYOUT EQU   1000
*
* Device control block symbols
*
*               0               ; device driver ptr (BCPL)
*               0               ; link to code (=0)
*               0
D.ID    EQU       6               * id
D.WKQ   EQU       8               * work queue
D.START EQU      10               * start routine - for QPKT
D.STOP  EQU      14               * stop routine - for DQPKT
D.CALL  EQU      18               * subroutine jump to
D.INT   EQU      20               * interrupt routine offset address
D.I     EQU D.CALL+6              * offset for interrupt rtn
D.VEC   EQU      24               * interrupt vector number
D.CSW   EQU      26               * control and status word
D.IOW   EQU      28            i/o address
D.RWKQ0  EQU   30            work queues for each USART
D.RWKQ1  EQU   32
D.RWKQ2  EQU   34
D.WWKQ0  EQU   36
D.WWKQ1  EQU   38
D.WWKQ2  EQU   40
*
* Packet symbols
*
P.ID    EQU       2               * task or device id
P.TYPE  EQU       4               * type or action
P.RES1  EQU       6               * first result
P.RES2  EQU       8               * second result
P.A1    EQU      10               * argument 1, character
P.A2    EQU      12               * argument 2, port number
*
* The rootnode
*
CRNTSK  EQU  #506
DEVMVP  EQU  #51A * MOVPKT for device drivers (MC addr)
DEVINT  EQU  #51E * INTENT for device drivers (MC addr)
DEVRET  EQU  #522 * INTRET for device drivers (MC addr)
        DW   RINITI          * initialisation rtn
        DW   RUNINI          * uninitialisation rtn
* Device initialisation routine. It is entered with the
* address of the DCB in BX. DX, DI, BP must be preserved.
* assume console USART has been set up by the monitor
RINIT   MOV D.START(BX),!RSTART
        MOV D.STOP(BX),!RSTOP
        MOV D.START+2(BX),CS
        MOV D.STOP+2(BX),CS
* Reset board
         PUSH  DX
         MOV   DX,D.IOW(BX)
         ADD   DX,!BRESET
         OUT
* PIT initialisation
*    select control block
         ADD   DX,!CONBLOCK-BRESET
         OUT
*    write similar mode control word to all 3 PITs (timers)
*    options = load 2 bytes, baud rate generator, binary counter
*    sets up for 4800 bps in div. by 16 mode
         ADD   DX,!PIT0CTRL-CONBLOCK
         MOV   AL,!#T00110110 pit 0
         OUT
         ADD   DX,!PIT0CNT-PIT0CTRL
         MOV   AX,!BR4800
         OUT                 lsb of count
         MOV   AL,AH
         OUT                 msb of count
         MOV   AL,!#T01110110 pit 1
         ADD   DX,!PIT0CTRL-PIT0CNT
         OUT
         ADD   DX,!PIT0CNT-PIT0CTRL
         MOV   AX,!BR4800
         OUT                 lsb of count
         MOV   AL,AH
         OUT                 msb of count
         MOV   AL,!#T10110110 pit 2
         ADD   DX,!PIT0CTRL-PIT0CNT
         OUT
         ADD   DX,!PIT0CNT-PIT0CTRL
         MOV   AX,!BR4800
         OUT                 lsb of count
         MOV   AL,AH
         OUT                 msb of count
* Now the PIC.
         MOV   DX,D.IOW(BX)
         ADD   DX,!DATBLOCK
         OUT
*    ICW1 no slaves
         MOV   AL,!#T00010110
         MOV   DX,D.IOW(BX)
         ADD   DX,!PIC0W1
         OUT
*    ICW2 0
         XOR   AL,AL
         INC   DX
         OUT
*    mask all interrupts (OCW1)
         NOT   AL
         OUT
*    set auto-rotate priority mode
         MOV   AL,!#T10100000
         DEC   DX
         OUT
*
        MOV   D.INT(BX),!RINT  make offset for CALL in DCB
        MOV D.INT+2(BX),CS      correct code seg in DCB
        MOV SI,D.VEC(BX)       get interrupt vector number
* Enable interrupts for board (individual devices are masked
* by local PIC).
         MOV   CX,SI
         MOV   DL,!1
         SHL   DL,CL
         NOT   DL
         IN    MPICMSK
         AND   AL,DL
         OUT   MPICMSK
* Set up 3 USARTs.
*
         MOV   DX,D.IOW(BX)
         ADD   DX,!DATBLOCK
         OUT
         SUB   DX,!DATBLOCK-1 control address
         CALL  USET          port 0
         ADD   DX,!2
         CALL  USET          port 1
         ADD   DX,!2
         CALL  USET          port 2
         POP   DX
        ADD SI,!40
        SHL SI
        SHL SI
        MOV (SI),BX            plug interrupt vector with
        ADD (SI),!D.CALL       address
        MOV 2(SI),!0           interrupt CS = 0
        RETS
* USET routine sets up USARTs, called by init. code.
*    the board has been reset so can write mode word
*    mode set is: 1 stop bit, no parity, 8 data bits, clock factor 16
USET     MOV   AL,!#T01001110
         OUT
*    set command: rts, dtr, error reset, enable rx and tx
         MOV   AL,!#T00110111
         OUT
         RET
* Device uninitialisation routine. It is entered with
* the address of the DCB in BX, which must be preserved.
RUNIN   MOV AX,ERRVEC
        PUSH BX
        MOV BX,D.VEC(BX)       get address to plug
* Disable interrupts
         MOV   CX,BX
         MOV   DL,!1
         SHL   DL,CL
         IN    MPICMSK
         OR    AL,DL
         OUT   MPICMSK
        ADD BX,!40
        SHL BX
        SHL BX
        MOV (BX),AX            plug it
        MOV AX,ERRVEC+2
        MOV 2(BX),AX
        POP BX
        RETS
* Device start routine. This is entered whenever a pkt
* is sent to the device and its work queue is empty. It
* is entered with the address of the DCB in BX, and the
* BCPL address of the packet in SI. It returns non-zero.
* Preserve BP and DI. Work q should always be empty.
* Examine pkt type
RSTART   SHL   SI
         MOV   D.WKQ(BX),!0 clear work q
         MOV   AX,P.TYPE(SI)
         CMP   AX,!A.SPDCHG
         JNE   R0
*    speed change, arg 1 contains count to be loaded (2 bytes)
         MOV   AX,P.A2(SI)   get port
         MOV   DX,D.IOW(BX)  base address
         ADD   DX,!CONBLOCK
         OUT                 select control block
         MOV   DX,D.IOW(BX)
         ADD   DX,AX         counter address
         MOV   AX,P.A1(SI)   get count
         OUT                 lsb
         MOV   AL,AH
         OUT                 msb
*      return pkt
RSRT     MOV   CX,D.ID(BX)
         MOV   BX,CRNTSK
         SHL   BX
*      and return from routine
         JIS   DEVMVP
*
R0       CMP   AX,!A.BRKSTP
         JNE   R1
* stop break condition
         MOV   AX,P.A2(SI)   port no
         MOV   DX,D.IOW(BX)  board address
         ADD   DX,!DATBLOCK
         OUT
         MOV   DX,D.IOW(BX)
         INC   DX
         SHL   AX
         ADD   DX,AX         control address
         MOV   AL,!#T00110111
         OUT                 stop break
         DEC   DX
         MOV   TIOW,DX      save i/o add.
*    enable interrupts if something on q
         MOV   CX,P.A2(SI)
         SHL   CX
         PUSH  BX
         ADD   BX,!D.WWKQ0
         ADD   BX,CX
         CMP   (BX),!0
         POP   BX
         JE    R00       leave interrupts disabled
         INC   CX
         MOV   CH,!1
         SHL   CH,CL
         NOT   CH
         MOV   DX,D.IOW(BX)
         ADD   DX,!PIC0W2
         IN
         AND   AL,CH
         OUT
R00      MOV   DX,TIOW
         XOR   AL,AL
         OUT                provoke interrupt
         B     RSRT          go return pkt and leave
R1       EQU   $
         MOV   DX,D.IOW(BX)
         MOV   TBASE,DX        save board address
         ADD   DX,!DATBLOCK
         OUT
*    put on a port work q
         ADD   BX,!D.RWKQ0
         CMP   P.TYPE(SI),!A.TTYIN
         JE    RST0
         ADD   BX,!D.WWKQ0-D.RWKQ0
RST0     MOV   CX,P.A2(SI)   get port
         SHL   CX
         ADD   BX,CX         find q start
         MOV   DX,(BX)
         SHL   DX
         JNE   RST1
*    enable interrupts for device
         MOV   DX,TBASE
         ADD   DX,CX
         CMP   P.TYPE(SI),!A.TTYIN
         JE    RST0A
         INC   CX
RST0A    MOV   CH,!1
         SHL   CH,CL
         NOT   CH
         MOV   DX,TBASE
         ADD   DX,!PIC0W2
         IN
         AND   AL,CH         enable
         OUT
         B     RST2
RST1     MOV   BX,DX
RST1A    MOV   DX,(BX)
         SHL   DX
         JNE   RST1          not end of q yet
RST2     MOV   (SI),!0
         SHR   SI
         MOV   (BX),SI       add to end of q
*        RETS
* Device stop routine. This is entered if the head pkt
* is dequeued from the device. It is entered with the
* DCB in BX, which must
* be preserved.
RSTOP   RETS
* This code is entered by the subroutine jump in the DCB
* which in turn was entered via the interrupt vector. The last
* word pushed onto the m/c stack therefore is the address of the
* DCB + D.I
RINT    PUSH BX
        PUSH AX
        PUSH CX
        PUSH DX
        PUSH SI
        MOV BX,SP              get stack pointer
        MOV BX,10(BX)          get IP (points into DCB)
*    find interrupting device
         MOV   DX,D.IOW-D.I(BX)
         MOV   TBASE,DX      base address of board
         ADD   DX,!DATBLOCK
         OUT                 select 'data block'
         MOV   AL,!#C
         ADD   DX,!PICOCW3-DATBLOCK
         OUT                 request poll
         IN                  get device number (and top bit set)
         SHL   AL            clear top bit
         JCS   RINT00
         JMP   RINTRET       no interrupt! (?!)
RINT00   XOR   AH,AH         clear top byte
         MOV   TXFLG,!0
         SHR   AX
         SHR   AX            AX contains port no. carry is set if tx interrupt
         ADC   TXFLG,!0
         MOV   TNUM,AX       save port no.
         SHL   AX            port no. * 2
         MOV   DX,TBASE
         ADD   DX,AX         form i/o address
         MOV   TIOW,DX       save it
         INC   DX            form status address
         MOV   TSTAT,DX      save it
         TEST  TXFLG,!1
         JE    RINT01
         JMP   TINT          handle tx interrupt
RINT01   LEA   SI,D.RWKQ0-D.I(BX)
         ADD   SI,AX         form work q address
         MOV   TWKQ,SI       save it
         MOV   SI,(SI)       get pkt
        SHL SI                 MC pkt address
         JNE RINT02
         JMP RINTRET
RINT02   EQU $
        XOR CX,CX              prepare pkt RES2
         MOV   DX,TSTAT
        IN                       get device status
        TEST AL,!COKRDY        character received?
        JNE RINT2              ok USART agrees
        NOT CX                 error
RINT2   MOV P.RES2(SI),CX      set RES2
        TEST AL,!BRKERR        break hit?
        JE RINT2A              probably not
        MOV P.RES2(SI),!1
        MOV DX,TIOW
        IN                    clear bum char
        MOV AL,!#37
        MOV DX,TSTAT
        OUT                    clear error
        XOR AX,AX              put 0 in pkt
        B RINT2B
*
RINT2A  TEST AL,!PARERR        parity error
        JE RINT2AB             no
        MOV DX,TIOW
        IN
        MOV AL,!#37
        MOV DX,TSTAT
        OUT                    clear error
        B RINTRET
*
RINT2AB MOV DX,TIOW            get address of i/o register
        IN                     fetch character
        XOR AH,AH              clear top byte of word
RINT2B  MOV P.RES1(SI),AX      store char. in pkt
        MOV DX,(SI)            deq pkt
         PUSH  BX
         MOV   BX,TWKQ
         MOV   (BX),DX
         POP   BX
        OR DX,DX
        JNE RINTPK
        MOV CX,TNUM            last dequeued so stop device
        SHL CX               port no. * 2
        MOV CH,!1
        SHL CH,CL
         MOV   DX,TBASE
         ADD   DX,!PIC0W2
        IN                   read mask
        OR AL,CH
        OUT                  set mask
* return pkt
RINTPK  MOV CX,D.ID-D.I(BX)    id of sender
        MOV BX,CRNTSK          setting args for MOVPKT
        SHL BX
        CIS DEVMVP
         PUSH  AX
         MOV   AL,!#20
         MOV   DX,TBASE
         ADD   DX,!PIC0W1
         OUT                 send EOI to local PIC
         POP   AX
        JIS DEVINT              exit via INTENT
*
RINTRET  MOV   AL,!#20       EOI to local PIC
         OUT   PIC0W1        then return immediately
         JIS   DEVRET
*
* Handle tx interrupt
TINT     LEA   SI,D.WWKQ0-D.I(BX)
         ADD   SI,AX         form work q address
         MOV   TWKQ,SI       save it
         XOR   CX,CX
         MOV   DX,TSTAT
         IN                  get status
         TEST  AL,!COPRDY
         JNE   TINT0
         NOT   CX
         MOV   AL,!#37
         OUT                 reset error
TINT0    MOV   SI,(SI)
         SHL   SI
         JNE   TINT01
         CALL  TSTOP         no pkts, make sure interrupts stopped
         B     RINTRET
TINT01   EQU   $
         MOV   P.RES1(SI),CX set result
         MOV   DX,(SI)       deq pkt
         PUSH  BX
         MOV   BX,TWKQ
         MOV   (BX),DX       new q head
         POP   BX
         OR    DX,DX
         JNE   TINT1
         CALL  TSTOP
*
TINT1    CMP   P.RES1(SI),!0
         JNE   RINTPK
         CMP   P.TYPE(SI),!A.BRKREQ
         JNE   TINT2
*    make a break condition
         MOV   DX,TSTAT
         MOV   AL,!#T00111111
         OUT
*    stop interrupts
         CALL  TSTOP
*    return pkt
         B     RINTPK
*
TINT2    MOV   DX,TIOW
         MOV   AX,P.A1(SI)   get char
         OUT                 write it
         JMP   RINTPK        return pkt
*
TSTOP    MOV   CX,TNUM       get device number
         SHL   CX
         INC   CX            form bit pos for tx
         MOV   CH,!1
         SHL   CH,CL
         MOV   DX,TBASE
         ADD   DX,!PIC0W2
         IN
         OR    AL,CH
         OUT
         RET
        EVEN
        DSEG
RINITI   DW    RINIT
         DW    0
RUNINI   DW    RUNIN
         DW    0
TBASE    DW    0             base address of board
TSTAT    DW    0             current status address
TIOW     DW    0                "      i/o     "
TWKQ     DW    0                "    work q    "
TNUM     DW    0                " interrupting device
TXFLG    DW    0             non-zero if tx interrupt
        END