.title IO DLV11-J I/O handler .ident /v03.00/ .sbttl RT-11/TSX-Plus DLV11-J port I/O handler ; ; COPYRIGHT 1982, 1983, 1984, 1985 by ; APPLIED MATHEMATICS DIVISION, D.S.I.R., ; P.O. BOX 1335, WELLINGTON, NEW ZEALAND. ; ; Written by R.D. BROWNRIGG, January, 1985. ; ;Latest Modifications: ; tidy up timeouts for RT-11 XM 16-JAN-85 ; tidy up timeouts for TSX-Plus v4.1 29-JUN-84 ; implement high-speed ring buffer 09-DEC-83 ; clear i.exit on entry 20-OCT-83 ; .sbttl macros and definitions .enable lc .nlist bex ; xm and tsx switches ; <<<<<<<<<<<<<<<<<<<<<<<<<<<< xm = 0 ; 0 => RT-11 SJ (or FB) or TSX-Plus, 1 => RT-11 XM. tsx = 0 ; 0 => RT-11, 1 => TSX-Plus. stats = 0 ; collect statistics on # of chars in HSRB. ; ; number of physical ports ; <<<<<<<<<<<<<<<<<<<<<<<<<<<< ports = 3 ; (octal). ; ; length of high speed ring buffer ; bufl = 60 ; (octal). ; .iif ndf mmg$t, mmg$t = tsx!xm tim$it = 1 ; base CSR and vector addresses ; <<<<<<<<<<<<<<<<<<<<<<<<<<<< io$csr = 176500 io$vec = 300 ; .mcall .drdef .drdef io, 377, abtio$!spfun$!hndlr$!wonly$, 0, io$csr, io$vec io$fun = 200 ; spfun code for I/O request. in.pri = 4 ; input device (isr) priority. ie = 100 done = 200 ; ; buffer offsets (in octal). og.ptr = 0 op.ptr = 2 a.diff = 4 o.bufl = 6 o.full = 10 o.stat = 11 ig.ptr = 12 ip.ptr = 14 i.bufl = 16 i.grn = 20 i.red = 22 i.full = 24 i.exit = 25 i.resp = 26 spare = 27 i.rbo = 30 i.ovr = 32 i.fra = 34 i.par = 36 comml = 40 ; length of communication area. ; local table offsets (in octal). qadd = 0 i.csr = 2 i.dbr = 4 o.csr = 6 o.dbr = 10 o.beg = 12 o.end = 14 i.beg = 14 i.end = 16 i.xofs = 20 ; for I/O. i.sxof = 21 ; for I/O. o.xofr = 20 ; for output only. o.abor = 21 ; for output only. ltlen = 22 ; .if eq tsx kparv = 172342 ; kernel par1 address for XM. paradd = 20000 ; par1 memory address for XM. .iff kparv = 172354 ; kernel par6 address for TSX. paradd = 140000 ; par6 memory address for TSX. .endc rmonb = 54 ; address of base of RMON. qcomp = 270 ; RMON offset for I/O queue exit. xon = 21 xof = 23 space = 40 ovr = 40000 ; bit 14. fra = 20000 ; bit 13. par = 10000 ; bit 12. ; .sbttl driver entry .drbeg io entry: mov iocqe, r4 ; get queue element. movb q$unit(r4), r5 ; get unit number. bic #177770, r5 ; its only 3 bits. cmpb #ports, r5 ; valid port number? ble noport ; no. mul #ltlen, r5 ; multiply by local table length. add pc, r5 ; convert to address of add #lts-., r5 ; start of local table. movb q$func(r4), r0 ; get special function code. beq notsp ; its not a .SPFUN request. cmpb r0, #io$fun ; if it is an I/O request bne noport ; then tst (r5) ; if this port already active then bne noport ; can't do it. mov q$buff(r4), r3 ; get user's buffer address. mov r3, r0 ; base address. add #comml, r0 ; plus communication area length. mov r0, o.beg(r5) ; equals start of output buffer. .if ne mmg$t mov @#kparv, -(sp) ; save kernel par value. mov q$par(r4), @#kparv ; borrow kernel par value for (r3). .endc mov r0, (r3) ; and output 'get' pointer. add o.bufl(r3), r0 ; plus output buffer length. mov r0, o.end(r5) ; equals end of output and start of input. mov r0, ip.ptr(r3) ; and input 'put' pointer. add i.bufl(r3), r0 ; plus input buffer length. mov r0, i.end(r5) ; equals end of input buffer. .if ne mmg$t sub #paradd, r0 ; subtract kparv memory address. bit #160000, r0 ; test length > 8K? beq addok ; no, its ok. mov (sp)+, @#kparv ; restore kernel par value. br adderr ; yes. .endc addok: mov ip.ptr(r3), r0 ; get input 'put' pointer. sub ig.ptr(r3), r0 ; minus input 'get' pointer. mov r0, a.diff(r3) ; equals input address difference. clr i.full(r3) ; input buffer not full, don't exit. add #i.rbo, r3 ; adjust to address of overflows. clr (r3)+ ; no overflows, clr (r3)+ ; overruns, clr (r3)+ ; frame errors, clr (r3) ; or parity errors. .iif ne mmg$t, mov (sp)+, @#kparv ; restore kernel par value. retrn: mov q$link(r4), iocqe ; delink cqe bne 1$ ; from the clr iolqe ; monitor's queue. 1$: clr q$link(r4) ; clear the link from this queue element. mtps #0 ; low priority ok now (see notsp). tst endbuf ; check if buffer pointers set up. bne strtd ; they have been. mov pc, r0 ; set up the add #stbuf-., r0 ; pointers mov r0, getp ; for the mov r0, putp ; high speed add #endbuf-stbuf, r0 ; ring mov r0, endbuf ; buffer. strtd: tst (r5) ; if port not already busy bne e.rts ; then mov r4, (r5) ; store cqe to signal port in use. incb nports ; increment count of active ports. tst @r4 ; if block number is zero bne e.bis ; then clr i.xofs(r5) ; (also clears i.sxof, o.xofr, o.abor), tst @i.dbr(r5) ; clear any input interrupt, tst @i.csr(r5) ; and clear any dataset interrupt. e.bis: bis #ie, @i.csr(r5) ; enable input, e.rts: tst iocqe ; any more queued? (there shouldn't be). bne entry ; yes, so do it all again. tstb flag ; if not already active beq timst ; then go to start timer. rts pc ; everything else is interrupt driven, ; noport: bis #hderr$, @q$csw(r4) ; signal hard error. adderr: bis #eof$, @q$csw(r4) ; signal end of file. .drfin io ; return queue element. ; notsp: asl q$wcnt(r4) ; convert word count to byte count. bcc noport ; it's positive, we don't do input. mov (r5), r2 ; if the port is already active beq retrn ; then cmpb q$jnum(r2), q$jnum(r4) ; if we have the same job bne noport ; then tstb q$func(r2) ; if the active function isn't .SPFUN bne noport ; then mtps #340 ; bump up priority, 1$: mov q$link(r2), r0 ; link the new beq 2$ ; queue element mov r0, r2 ; into the br 1$ ; local queue 2$: mov r4, q$link(r2) ; for the port. br retrn ; away we go. .sbttl vector table ; vector table entries ; <<<<<<<<<<<<<<<<<<<<<<<<<<<< .drvtb io, io$vec, ioint, 10 .drvtb ,io$vec+4, ioint, 0 .if gt ports-1 .drvtb ,io$vec+10, ioint, 11 .drvtb ,io$vec+14, ioint, 1 .if gt ports-2 .drvtb ,io$vec+20, ioint, 12 .drvtb ,io$vec+24, ioint, 2 .if gt ports-3 .drvtb ,io$vec+30, ioint, 13 .drvtb ,io$vec+34, ioint, 3 .if gt ports-4 .drvtb ,io$vec+40, ioint, 14 .drvtb ,io$vec+44, ioint, 4 .if gt ports-5 .drvtb ,io$vec+50, ioint, 15 .drvtb ,io$vec+54, ioint, 5 .if gt ports-6 .drvtb ,io$vec+60, ioint, 16 .drvtb ,io$vec+64, ioint, 6 .if gt ports-7 .drvtb ,io$vec+70, ioint, 17 .drvtb ,io$vec+74, ioint, 7 .endc .endc .endc .endc .endc .endc .endc .sbttl timout processing nports: .byte 0 ; count of number of active ports. flag: .byte 0 ; timer busy flag. ; timblk: .word 0, 0, 0 ; .timio macro block. timjn: .word 0, 177377, 0 timcr: .word 0 ; timst: incb flag ; indicate busy. timout: tstb nports ; if no ports active bne 1$ ; then clrb flag ; turn timer off, br t.rts ; and go away. ; 1$: mov r4, -(sp) ; save r4 (only r0, r1 available for comp.) mov pc, r1 ; get address add #lts-., r1 ; of local tables. mov #ports, r0 ; set up count of number of ports. testc: bit #ie, @o.csr(r1) ; if port is not outputting bne nextc ; then mov (r1), r4 ; if port is active beq nextc ; then .if eq mmg$t ; (if SJ then check for abort of .WRITE) bit #ie, @i.csr(r1) ; if SJ has disabled input bne t.bis ; then decb nports ; decrement active port count, clr (r1) ; and clear active port indicator. mov (sp)+, r4 ; restore r4. br timout ; start again. t.bis: .endc bis #ie, @o.csr(r1) ; cause an interrupt. .if eq tsx movb q$jnum(r4), r4 ; get his job number. ash #-3, r4 ; move it and bic #177740, r4 ; mask it to 5 bits. mov r4, timjn ; save it in timer block. .endc nextc: add #ltlen, r1 ; increment table address sob r0, testc ; and check if finished. mov (sp)+, r4 ; restore r4. mov pc, r0 ; get address of add #timout-., r0 ; timeout routine. mov r0, timcr ; set alarm to come back .timio timblk, 0, 3 ; in a few ticks. (Note must be at least 3 ; ticks to prevent NO FREE FORK BLOCKS error) t.rts: rts pc ; we have finished. .sbttl abort processing abort: mov r1, -(sp) ; save r1, mov r2, -(sp) ; r2, mov r3, -(sp) ; r3, .iif ne mmg$t, mov @#kparv, -(sp) ; save kernel par value, mov r4, -(sp) ; and aborting job number. mov pc, r5 ; get address add #lts-., r5 ; of local tables. mov #ports, r2 ; set up port count. testp: mov (r5), r4 ; is port active? beq nextp ; no. movb q$jnum(r4), r1 ; get port's ash #-3, r1 ; job bic #177740, r1 ; number. cmp r1, (sp) ; is it the same as the aborting job? bne nextp ; no. tstb q$func(r4) ; is it a .SPFUN request? bne ioabor ; yes. incb o.abor(r5) ; set abort bit for .WRITE request. br nextp ; go to look at next port. ; ioabor: mov q$buff(r4), r3 ; yes, get user's buffer address. .iif ne mmg$t, mov q$par(r4), @#kparv ; borrow kernel par value. incb i.exit(r3) ; set exit bit. nextp: add #ltlen, r5 ; move to next table sob r2, testp ; and test if finished. mov (sp)+, r4 ; restore r4, .iif ne mmg$t, mov (sp)+, @#kparv ; kernel par value, mov (sp)+, r3 ; r3, mov (sp)+, r2 ; r2, mov (sp)+, r1 ; and r1. rts pc ; return. .sbttl interrupt service br abort ; abort entry point. ioint:: ; interrupt entry point. mfps -(sp) ; save processor status. mov r5, -(sp) ; save r5, mov r4, -(sp) ; and r4. mov putp, r4 ; get HSRB pointer. tst ioflow ; was there an overflow? beq contin ; no. tst (r4) ; yes, check room in HSRB. bne contin ; none. mov ioflow, (r4)+ ; yes, put overflow back in HSRB, clr ioflow ; and clear it. cmp r4, endbuf ; if at end of buffer bne contin ; then add #stbuf-endbuf, r4 ; set to start. contin: mov 4(sp), r5 ; get status word. bic #177760, r5 ; clear to 4 bits. bit #10, r5 ; input interrupt? bne inpint ; yes. tst (r4) ; if room in HSRB then beq outint ; go put it away. mul #ltlen, r5 ; multiply by local table length. add pc, r5 ; clear clr @lts+o.csr-.(r5) ; interrupt enable bit br idone ; before leaving. ; inpint: mov r5, -(sp) ; save on stack swab (sp) ; move to high byte. bic #10, r5 ; remove input bit. mul #ltlen, r5 ; multiply by local table length. add pc, r5 ; input mov @lts+i.dbr-.(r5), r5 ; char. bis (sp)+, r5 ; mask on channel bits. tst (r4) ; if room in HSRB then beq saveit ; go put it away. tst ioflow ; no room, if already an overflow beq 1$ ; then bis #140000, r5 ; set data overrun bits. 1$: mov r5, ioflow ; store new data in overflow word. br idone ; now finished. ; outint: bis #200, r5 ; ensure word non-zero. swab r5 ; move to high byte. saveit: mov r5, (r4)+ ; save in HSRB. cmp r4, endbuf ; if at end of buffer bne idone ; then add #stbuf-endbuf, r4 ; set to start. idone: mov r4, putp ; restore pointer, .if ne stats sub getp, r4 ; get difference bhi 1$ ; between add #endbuf-stbuf, r4 ; pointers. 1$: add pc, r4 ; convert to add #hist-2-., r4 ; histogram address. inc @r4 ; increment count. .endc mov (sp)+, r4 ; restore r4 mov (sp)+, r5 ; and r5. tst (sp)+ ; discard status. tstb ibusy ; check if fork already busy. beq dofork ; fork if not else rti ; return from interrupt. ; ibusy: .byte 0 ; fork busy flag. .even putp: .word 0 ; HSRB put pointer. getp: .word 0 ; HSRB get pointer. stbuf: .rept bufl ; high speed ring buffer. .word 0 .endr endbuf: .word 0 ; address of end of buffer. ioflow: .word 0 ; HSRB overflow word. iofblk: .word 0, 0, 0, 0 ; fork block. .sbttl check HSRB dofork: incb ibusy ; indicate forking. .globl $inptr ; now we want the equivalent jsr r5, @$inptr ; of the .DRAST macro .word ^C&^O340 ; .fork iofblk ; reduce to fork level. .iif ne mmg$t, mov @#kparv, -(sp) ; save kernel par value, loop: mov getp, r1 ; get pointer to HSRB. mov (r1), r0 ; get char from HSRB. bne movb ; if no more there, mtps #340 ; go to high priority. mov (r1), r0 ; if still nothing in HSRB bne 1$ ; then clrb ibusy ; clear FORK busy flag. .iif ne mmg$t, mov (sp)+, @#kparv ; restore kernel par value, return ; then return. ; 1$: mtps #0 ; something was there. movb: movb 1(r1), r5 ; get high byte in r5 also. clr (r1)+ ; clear the HSRB entry. cmp r1, endbuf ; if at end of buffer bne 10$ ; then add #stbuf-endbuf, r1 ; point to start. 10$: mov r1, getp ; store pointer back bic #177770, r5 ; clear channel byte to 3 bits. mul #ltlen, r5 ; get address (Note must be odd register.) add pc, r5 ; of local table for add #lts-., r5 ; requested port. mov (r5), r4 ; get CQE address mov q$buff(r4), r3 ; and user buffer address. .iif ne mmg$t, mov q$par(r4), @#kparv ; borrow kernel par value. bit #4000, r0 ; was it an input interrupt? beq check ; no. input: tst r4 ; if no queue element then beq 1$ ; assume .WRITE. tstb q$func(r4) ; was it a .WRITE request? bne inchar ; no. 1$: bicb #200, r0 ; yes, mask off parity. cmpb r0, #xof ; if it's an XOF bne 2$ ; then movb #1, o.xofr(r5) ; indicate. 2$: cmpb r0, #xon ; if it's an XON bne loop ; then clrb o.xofr(r5) ; indicate. br loop ; we have finished. .sbttl input interrupt service inchar: tst r0 ; no, check incoming char. bpl noerr ; if there was an error, bit #ovr, r0 ; was it overrun? beq 1$ ; no. inc i.ovr(r3) ; yes, increment counter. 1$: bit #fra, r0 ; was it framing error? beq 2$ ; no. inc i.fra(r3) ; yes, increment counter. 2$: bit #par, r0 ; was it parity error? beq noerr ; no. inc i.par(r3) ; yes, increment counter. noerr: movb r0, r1 ; get byte. bic #177600, r1 ; mask to 7 bits. cmpb #xof, r1 ; XOF? bne 1$ ; no. tstb i.resp(r3) ; yes, respond to it? beq 2$ ; no. incb o.stat(r3) ; yes, indicate. 1$: cmpb #xon, r1 ; XON? bne 2$ ; no. clrb o.stat(r3) ; yes, indicate. 2$: tstb i.full(r3) ; is the input buffer full? beq notful ; no. inc i.rbo(r3) ; yes, increment counter. br loop ; input now complete. ; notful: mov ip.ptr(r3), r1 ; get input 'put' pointer. movb r0, (r1)+ ; save data in buffer and increment pointer. cmp r1, i.end(r5) ; if at end of buffer, bne 1$ ; then mov i.beg(r5), r1 ; point to start. 1$: mov ig.ptr(r3), r0 ; get input 'get' pointer. add a.diff(r3), r0 ; convert to our addressing. cmp r0, r1 ; are they the same? bne 2$ ; no. incb i.full(r3) ; yes, buffer now full. 2$: mov r1, ip.ptr(r3) ; update the 'put' pointer. tst i.red(r3) ; if sending XOFs beq loop ; then sub r0, r1 ; find how much bgt 3$ ; room is left add i.bufl(r3), r1 ; in the buffer. 3$: cmp i.red(r3), r1 ; if not much bge loop ; then incb i.sxof(r5) ; signal XOF wanted. bit #ie, @o.csr(r5) ; if not awaiting output interrupt bne goloop ; then ; br check ; try to send it immediately. .sbttl output interrupt service check: tst r4 ; if no queue element then beq goloop ; ignore it. tstb q$func(r4) ; is it a .WRITE request? beq outout ; yes. tstb i.exit(r3) ; has the user finished? bne getout ; yes. tstb i.sxof(r5) ; XOF required? beq 1$ ; no. clrb i.sxof(r5) ; yes, clear the required indicator. ;**** tstb i.xofs(r5) ; already sent? ;**** bne 10$ ; yes. movb #xof, r0 ; no, so incb i.xofs(r5) ; set the sent indicator and br output ; send it. ; 1$: tstb o.stat(r3) ; has an XOF been received? bne nomore ; yes. tstb i.xofs(r5) ; has an XOF been sent? beq 3$ ; no. 10$: mov ip.ptr(r3), r1 ; get input 'put' pointer, mov ig.ptr(r3), r0 ; input 'get' pointer, add a.diff(r3), r0 ; and convert it to our addressing. sub r0, r1 ; find how many bge 2$ ; chars are left add i.bufl(r3), r1 ; in the buffer. 2$: cmp i.grn(r3), r1 ; if not many ble 3$ ; then movb #xon, r0 ; we want an XON. clrb i.xofs(r5) ; clear the sent indicator and br output ; send it. ; 3$: mov (r3), r1 ; get output 'get' pointer. tstb o.full(r3) ; if the buffer is full then bne more ; there are chars for output. mov op.ptr(r3), r0 ; get output 'put' pointer, add a.diff(r3), r0 ; and convert it to our addressing. cmp r0, r1 ; if they are not the same then bne more ; there are more chars for output. nomore: clr @o.csr(r5) ; tell input there is no int being awaited. goloop: jmp loop ; check if more to do. ; more: movb (r1)+, r0 ; get the output char and increment pointer. cmp r1, o.end(r5) ; if at end of buffer bne 1$ ; then mov o.beg(r5), r1 ; reset to start. 1$: mov r1, (r3) ; save new 'get' pointer. clrb o.full(r3) ; it can't be full any longer. br output ; go to send the char. ; outout: tst q$wcnt(r4) ; if remaining char count not zero beq getout ; then tstb o.abor(r5) ; if not aborted bne getout ; then tstb o.xofr(r5) ; if no XOF received bne nomore ; then .if eq mmg$t movb @q$buff(r4), r0 ; get next inc q$buff(r4) ; byte from .iff jsr pc, @$gtbyt ; user's mov (sp)+, r0 ; buffer. .endc inc q$wcnt(r4) ; increment char count. tstb r0 ; if char isn't NUL beq outout ; then output: movb r0, @o.dbr(r5) ; output the char. br goloop ; check if more to do. ; getout: mtps #340 ; bump up priority. mov q$link(r4), (r5) ; delink element from local queue. clr q$link(r4) ; link cqe mov r4, iocqe ; into mov r4, iolqe ; monitor's queue. mtps #0 ; return to lower priority. clr @o.csr(r5) ; disable output interrupts. tst (r5) ; if port no longer busy bne notdun ; then, mtps #340 ; at high priority (to prevent bus timeout), clr @i.csr(r5) ; disable input interrupts. mtps #0 decb nports ; decrement count of active ports. .if eq tsx bne iodone ; if none left active then .ctimio timblk ; disable timeouts. bcs iodone ; timeout pending, have to wait for it. clrb flag ; indicate timer turned off. .endc br iodone ; we are done. ; notdun: bis #ie, @o.csr(r5) ; set output ints again to continue. iodone: mov pc, r4 ; jsr add #iocqe-., r4 ; version mov @#rmonb, r5 ; of jsr pc, @qcomp(r5) ; .DRFIN. br goloop .sbttl tables .if ne stats hist: .rept bufl ; histogram of number of chars in HSRB. .word 0 .endr .endc ; local tables for each port. ; <<<<<<<<<<<<<<<<<<<<<<<<<<<< lts: .word 0 ; current queue element address for port 0. .word io$csr, io$csr+2, io$csr+4, io$csr+6 ; i/o page addresses. .word 0, 0, 0 ; buffer pointers. .byte 0, 0 ; flags for XON/XOF. .if gt ports-1 .word 0 ; current queue element address for port 1. .word io$csr+10, io$csr+12, io$csr+14, io$csr+16 .word 0, 0, 0 .byte 0, 0 .if gt ports-2 .word 0 ; current queue element address for port 2. .word io$csr+20, io$csr+22, io$csr+24, io$csr+26 .word 0, 0, 0 .byte 0, 0 .if gt ports-3 .word 0 ; current queue element address for port 3. .word io$csr+30, io$csr+32, io$csr+34, io$csr+36 .word 0, 0, 0 .byte 0, 0 .if gt ports-4 .word 0 ; current queue element address for port 4. .word io$csr+40, io$csr+42, io$csr+44, io$csr+46 .word 0, 0, 0 .byte 0, 0 .if gt ports-5 .word 0 ; current queue element address for port 5. .word io$csr+50, io$csr+52, io$csr+54, io$csr+56 .word 0, 0, 0 .byte 0, 0 .if gt ports-6 .word 0 ; current queue element address for port 6. .word io$csr+60, io$csr+62, io$csr+64, io$csr+66 .word 0, 0, 0 .byte 0, 0 .if gt ports-7 .word 0 ; current queue element address for port 7. .word io$csr+70, io$csr+72, io$csr+74, io$csr+76 .word 0, 0, 0 .byte 0, 0 .endc .endc .endc .endc .endc .endc .endc .drend io .end