.mcall .modul .modul di,versio=1,commen=,audit=yes ; ATA Disk handler for RT11 V5.0x ; CHD - 25-MAY-2007 - Begun ; CHD - 26-MAR-2008 - Got it to assemble, link, and install ; CHD - 29-MAR-2008 - Got it to work as a non-system handler ; CHD - 11-APR-2008 - Got it to work XM monitor and bootable ; Portions copied from HD.MAC as distributed with E11 .mcall .drdef,.assume ; .readw= emt!375 .writw= emt!375 sysptr= 54 p1ext = 432 ; .drdef di,377,filst$!spfun$!varsz$,65535.,171000,234 ; ; These are new since V4, no idea what they are: **hd.mac** ; .drptr ; .drest class=dvc.dk ; ; Device registers: ; ;dicsr= 0 ;control/status register divec= 6 ;interrupt vector register diasta= 14 ;alternate status register (r) dictl= diasta ; disk control register (w) didata= 20 ;data register (r/w) dierrr= 22 ;error register (r) difeat= dierrr ; feature register (w) discnt= 24 ;sector count register dilba0= 26 ;LBA 0 (r/w) dilba1= 30 ;LBA 1 (r/w) dilba2= 32 ;LBA 2 (r/w) didev= 34 ;device and head (r/w) dista= 36 ;status register (r) dicomm= dista ; command register (w) ; ; Device status register bits: ; dibusy= 200 ;busy didrdy= 100 ;drive ready didrq= 10 ;data request dierrb= 1 ;error bit didi= 2 ;disable interrupts - drive diie= 100 ;enable interrupts - adapter didirq= 200 ;drive dintrq signal ; ; Device functions: ; fnread= 40 ;read sectors with retries fnwrit= 60 ;write sectors with retries fnlba= 340 ;force LBA ; ; Unit Translation Table: ; This table is defined by the .SPFUN documentation u.port= 3 u.unit= 0 u.part= 2 ; ; Macros ; ; ;Loop until drive is not busy ; reg contains CSR .macro .wnbsy reg,error,?loop .nchr nc,error .if ne nc clr drdyto ;clear timeout counter loop: dec drdyto ;count pass beq error ;branch on timeout .iff loop: nop .endc bit #dibusy,diasta(reg) ;test drive status bne loop ;repeat until not busy .endm ; ;Loop until drive is ready ; reg contains CSR .macro .wdrdy reg,error,?loop .nchr nc,error .if ne nc clr drdyto ;clear timeout counter loop: dec drdyto ;count pass beq error ;branch on timeout .iff loop: nop .endc bit #didrdy,diasta(reg) ;test drive status beq loop ;repeat until ready .endm ; ;Define unit translation table entry - .SPFUN #372 ; .macro .utent port, unit, part .word unit .byte part, port .endm ; ;Externalization code for XM monitor ; These macros only provide code when memory management is enabled. ; begin externalization .macro .extrb par,parval .if ne mmg$t mov par,parval ;PAR is in the IO q element jsr r0,@$p1ext ;copy code between here and end macro .word parval-. ; to the system stack and then exec .endc .endm ; end externalization .macro .extre parval .if ne mmg$t parval: .word 0 ;marks the end of the externaliation .endc ; and holds the buffer PAR .endm .sbttl installation code ; ; Installation Code ; .drins di nop ;non-system device entry nop ;system device entry clc ;carry clear indicates option present tst @inscsr ;verify CSR exists rts pc .sbttl set options ; ; Set Options ; .drset csr,171000,o.csr,oct .drset vector,234,o.vec,oct .drset port,1,o.port,num .drset unit,1,o.unit,num .drset part,1,o.part,num o.csr: o.vec: o.port: o.unit: o.part: br o.bad o.good: tst (pc)+ ;clc and skip sec o.bad: sec ;error indicator rts pc .assume . le 1000,<;SET area overflow> .sbttl driver entry ; ; Initiation Code ; .drbeg di .if ne mmg$t mov @#sysptr,r4 ;r4 -> monitor base mov p1ext(r4),(pc)+ ;get address of externization routine $p1ext: .word p1ext ;pointer to externalization routine .endc mov (pc)+,r4 ;pick up CSR address dicsr: .word di$csr mov (pc)+,r3 ;pick up VECTOR address vect: .word di$vec .assume . le distrt+1000,<;SET object not in block 1> mov r3,divec(r4) ;set adapter interrupt vector mov dicqe,r5 ;get current queue entry movb q$unit(r5),r2 ;rt device unit number bic #^C<7>,r2 ;isolate unit number asl r2 ;offset into table asl r2 add #diuttb-5$,r2 ;offset to table add pc,r2 ;pointer to table in r2 5$: mov u.unit(r2),r3 ;get translated unit number bic #^C<1>,r3 ;isolate lsb of unit number asl r3 ;should start at bit 4 asl r3 asl r3 asl r3 bis #fnlba,r3 ;construct dev select, LBA3 part is 0 .wnbsy r4,7$ ;wait until drive not busy 7$: mov r3,didev(r4) ;select the drive .wdrdy r4,herro2 ;spin until ready w/ timeout bit #dierrb,diasta(r4) ;check disk error bit bne herro2 ;hard error if set .if ne mmg$t mov q$par(r5),bufpar .endc mov q$blkn(r5),r3 ;get block number mov r3,dilba0(r4) ;set block number LBA0 swab r3 ;get the high byte mov r3,dilba1(r4) ;set block number LBA1 movb u.part(r2),dilba2(r4) ;partition number from unit trans tab mov q$buff(r5),bufadr ;copy address movb q$func(r5),r2 ;get function code bmi spf ;.SPFUN, yep, go mov #fnread,r3 ;assume a read mov q$wcnt(r5),r0 ;get word count beq diexi1 ;0, seek (no op with us) bpl 10$ ;read, skip neg r0 ;take absolute value mov #fnwrit,r3 ;change function code to write 10$: mov r0,wdcnt ;save word count add #377,r0 ;round up number of sectors swab r0 ;number of sectors from MSB to LSB bic #177400,r0 ;clear upper byte mov r0,discnt(r4) ;number of sectors to transfer mov r3,dicomm(r4) ;send command tst q$wcnt(r5) ;check N==1 -> write bmi fill ;write, go fill clr dictl(r4) ;enable drive interrupts mov #diie,(r4) ;enable adapter interrupts rts pc ;back on interrupt for read herro2: jmp herror ; ; Unit Translation Table ; ; port unit part diuttb: .utent 0, 0, 0 ;DI0: .utent 0, 0, 1 ;DI1: .utent 0, 0, 2 ;DI2: .utent 0, 0, 3 ;DI3: .utent 0, 1, 0 ;DI4: .utent 0, 1, 1 ;DI5: .utent 0, 1, 2 ;DI6: .utent 0, 1, 3 ;DI7: .assume . le distrt+1000,<;SET object not in block 1> .sbttl special function calls ; ; Special Functions ; spf: ; .SPFUN call cmpb r2,#373 ;cmd = get volume size? beq spfsiz ;skip if not cmpb r2,#372 ;cmd = get translation table beq spfttb diexi1: jmp diexit ;unhandled function, so go ; ;.SPFUN #373 - get volume size ; spfsiz: mov bufadr,r3 .extrb q$par(r5),parval ;externalization mov #65535.,(r3) ; number of blocks in volume .extre parval ;externalization jmp diexit ;there won't be any interrupt ; ;.SPFUN #372 - get unit translation table (same as DU handler) ; spfttb: mov bufadr,r3 ;memory buffer target mov pc,r4 add #diuttb-.,r4 ;get the address of table - PIC mov #16,r2 ;length of table .extrb q$par(r5),tparval ;externalization 35$: mov (r4)+,(r3)+ ; move table dec r2 ; bne 35$ ; .extre tparval ;externalization jmp diexit ;no interrupt, go to completion ; ; Disk Write - fill the drive buffer ; fill: mov fil.i1,xfr.i1 ;insert fill instruction mov fil.i1,xfr.i3 mov fil.i2,xfr.i2 ;insert fill disk buffer instruction jsr pc,xfer ;do the transfer loop br xfrdn fil.i1: mov (r2)+,(r4) fil.i2: mov r1,(r4) ; ; Disk Read - empty the buffer ; emp.i1: mov (r4),(r2)+ emp.i2: tst (r4) empty: mov emp.i1,xfr.i1 ;insert empty instruction mov emp.i1,xfr.i3 mov emp.i2,xfr.i2 ;insert empty disk buffer instruction jsr pc,xfer tst r1 ;done? beq diexit ;yes, goto completion xfrdn: clr dictl(r4) ;enable disk interrupts mov #diie,(r4) ;enable adapter interrupts rts pc ;back on interrupt complete ; ; Disk Buffer - fill or empty loop ; xfer: .wnbsy r4 ;spin until not busy mov diasta(r4),r3 ;get drive status bit #dierrb,r3 ;check for error bit bne herro1 ;yes? then hard error bit #didrq,r3 ;check data request flag beq herro1 ;no? then hard error mov wdcnt,r1 ;total words to transfer mov bufadr,r2 ;buffer address mov #256.,r3 ;disk buffer size in words add #didata,r4 ;r4 -> disk data register .extrb bufpar,wparval ;externalization cmp r1,r3 blo xfr55 ;wdcnt < buffer, go sub r3,r1 ;wdcnt = wdcnt - buffer xfr.i1: halt ; transfer sob r3,xfr.i1 br xfr60 xfr55: sub r1,r3 ;buffer = buffer - wdcnt xfr.i3: halt ; transfer sob r1,xfr.i3 xfr.i2: halt ; dump sob r3,xfr.i2 xfr60: nop .extre wparval ;externalization mov r1,wdcnt ;save remaining count .if ne mmg$t add #10,bufpar ;advance PAR by one sector ; to keep in 4k window .iff mov r2,bufadr ;advance buf addr by one sector .endc add #-didata,r4 ;r4 -> CSR rts pc herro1: jmp herror .sbttl data and tables ; ; Local Data and Tables ; wdcnt: .word 0 ;total words to transfer bufadr: .word 0 ;buffer address .if ne mmg$t bufpar: .word 0 ;buffer page address .endc drdyto: .word 0 ;drive ready timeout counter difblk: .word 0,0,0,0 ;.FORK block used by error log code .sbttl interrupt entry point ; ; Interrupt Entry Point ; .drast di,5 ;switch to priority 5 mov dicsr,r4 ;pick up CSR addr tst dista(r4) ;clear the interrupt flag mov #didi,dictl(r4) ;disable drive interrupts bic #diie,(r4) ;disable adapter interrupts tst wdcnt ;anything left to move bne 90$ ; yep, go bit #dierrb,diasta(r4) ;check drive error bit bne herror ;yes? then hard error bit #didrq,diasta(r4) ;check data request flag bne herror ;yes? then hard error br diexit ;we're done 90$: .fork difblk ;drop to fork level for xfers mov dicqe,r5 ;get curr queue entry tst q$wcnt(r5) ;N==0 -> read bpl empty ;continue with read jmp fill ; ; Hard error ; herror: mov dicqe,r5 ;get qel bis #hderr$,@q$csw(r5) ;set hard error flag in CSW ; ; Completion ; diexit: mov #didi,dictl(r4) ;disable drive interrupts bic #diie,(r4) ;disable adapter interrupts .drfin di ;dive into RMON .if eq 1 .drend di .iff .sbttl bootstrap driver ;+ ; ; This stuff is scattered around the boot block. For various reasons the boot ; code is entered by a NOP, two BRs and a JMP. Once we get there we just load ; in the BSTRAP secondary loader using a simple polled I/O read routine, which ; BSTRAP then calls to look at the directory and load the monitor and the ; minimum device handlers (TT: and the system device). Then it hooks up the ; real system device handler and we're on our way. ; ;- .drbot di,boot1,read .= diboot+40 boot1: jmp @#boot-diboot ;hop, skip, now jump, this is so dumb .= diboot+210 ;+ ; ; Bootstrap read routine. ; The correct unit is already selected and parameters are passed to this ; routine as: ; ; r0 starting block # ; r1 word count ; r2 bus address of buf ; ;- read: mov botcsr,r3 ;get CSR addr .wnbsy r3 ;wait until not busy .if ne 0 mov didev(r3),r4 ;get device select from register bic #^C<20>,r4 ;clear all but device bis #340,r4 ;set LBA addressing .iff mov #340,r4 .endc mov r4,didev(r3) ;reselect drive .wnbsy r3 ;wait until not busy .wdrdy r3 ;wait until drive ready mov r1,r5 ;get word count add #377,r5 ;round up word count swab r5 mov r5,discnt(r3) ;set sector count mov r0,dilba0(r3) ;set block address LBA0 swab r0 ; get upper byte mov r0,dilba1(r3) ;set block address LBA1 swab r0 ; get them back in the right order clr dilba2(r3) ;clear LBA2 ; - this would be the partition mov #fnread,dicomm(r3) ;read sectors command 30$: .wnbsy r3 ;wait until not busy bit #dierrb,dista(r3) ;check for an error bne 90$ ; yes, we're done bit #didrq,dista(r3) ;check for drq beq 90$ ; no, something isn't right mov #256.,r4 ;block size in words 40$: mov didata(r3),(r2)+ ;get a word, put it in buffer dec r1 ;count total words beq 60$ ;done with read dec r4 ;count the word bne 40$ ;loop until done with words in sector br 30$ ;loop for another sector 60$: .if ne 1 mov didata(r3),r4 ;read data buffer to empty it bit #didrq,dista(r3) ;check data request bit bne 60$ ;loop until output buffer is emptied .endc rts pc 90$: jmp bioerr ;our READ routine is too short! ; ; notes on booting: PROM bootstrap loads this from some sector on the disk ; (usually logical sector 0 or track 0, head 0, sector 1). R1 contains ; the CSR for the device that was booted. R0 may contain the unit number. ; The unit number may also be included in a device register. This ; bootstrap gets the disk (unit) number by reading the didev register ; and extracting the DEV bit. In the future, R0 may be loaded with the ; partion number so that more than 32MB of a disk can be used. ; .= diboot+500 boot: ; load secondary bootstrap from blocks 2-5 of the volume, at 001000 mov #10000,sp ;set up stack mov @(pc)+,r3 ;get CSR addr botcsr: .word di$csr ;preassembled CSR addr .if ne 0 mov didev(r3),r3 ;get boot unit # from CSR asr r3 ;right 4 bits asr r3 asr r3 asr r3 bic #177776,r3 ;isolate unit # .iff clr r3 .endc mov r3,-(sp) ;save unit on stack mov #2,r0 ;BSTRAP starts at block 2 mov #<4*400>,r1 ;4 blocks mov #1000,r2 ;load at 001000 call read ;go do it (N.B. relative addressing) ; set up BSTRAP entry conditions mov #read-diboot,@#b$read ;set pointer to standalone read mov #b$dnam,@#b$devn ;set system device name mov (sp)+,@#b$devu ;set boot unit number from stack jmp @#b$boot ;go for it .drend di ;that's it, give us BIOERR .endc .end