IMD 86-DOS version 1.14 Copyright 1980,81 Seattle Computer Products, Inc. Note: Originally designed for Tarbell DD, patched to support the Cromemco 4FDC.  3؎м0 uX=240$ t 0W4u3_$uǀJu@0 4t0$i&3мz3ۊù8P@vz!یȎغ! u/3@'!tێÎӼ\3P!SP˺ !88 Error in loading Command Interpreter $COMMAND COM j$ QRCZYC'53ɋѰ    Ò DPX$$t$P$tXP$tX$tP$tX˴.:ku0$ t@r!QYr -rQYr ˳.).t@ .k:t 13ċ.l4 u.pt.1:t' HEX2BIN COMEINIT COMFINIT ASMHn3DOSIO ASMbKMON ASMCPMTAB ASM0BOOT ASM) NEWS DOCx~ READTHISDOCEOSIO HEX WSCVNTAWBUHHHRMDDP@QL 86-DOS version 1.14 Copyright 1980,81 Seattle Computer Products, Inc. $-vXX.P.6$wUWVRQSP.&.̎Լܷ ~..&.FX[YZ^_]mQLU/#<0"tc u -X----a S2Ê<t<t<t <t< tS)@ÊPX@;^ w89s ӁPrXË?s ?þ]tu˹ uxrR_krȎs=r?t] t }?t^sÂ~t&h"$ttáU@;Fs^U3҂~tҋ^ڊf;)tSE[VË>UG;~s >U ~u;r RZ@r:tv t;^ w -so2|rwirlrgtb]t]i 棢R[ÃuZС^;sK"FNtuH3&]v3[á t +F+s3ۣ3v&]&U t+s3&] s܇BAJË;#u%:FtJ2& tRU.'!#]Zu3#H%!RZ#F%.'>66!62wsá@;wUs&t':Fvsv,>ì< u < u 3 uG 6>u >&M> tu<6 u>tQW_Y<u 33xot&E&]+ru t;s' uĉ>t>tr^WPRS|_Xr%>&t+#؋N6!sY[ns볋6+3&L;t" t+3s@ȋ&E&EIAá t*r6 ttt <t;< <t)>떋%s>y&Et&EIvNP&E&Uv t@X+t QXrȋBItw>t  t@) r #%WP5Y[ء t+ʋ>&;E&Mr&E&US t=-vNr݋>&E&E3'1F3&] tRSF*‹эvȂ;sIC;tK+Rf6ƣXZ+[.9#r96#r PF:%Xt+*̈&QNJJ V YË=uE U ؊ֶ4RQSËC;^ ~)=)[\X+ZBŠVB+w3t H~ɓtӋ⳺F[YZ uˋ>&]3t=rpsrJtNU&E>:u hF3~u 󫥥ù &E&]>3 3 rREUh_qBr߃!&M u33҂tFFD~uP Zt@uBª@s&%..ÌȎ؋.2^FV JFN6\TLL.2.6\L% t@h&#! 4QV^Y..2FE!U#}@su$ö:s㋗42ÌȎ t܊:v8 t݊J..VRP<t}<ty< t9< tY<tZ<t:s\ u:sFW_.&J_uƌŌێÎݾRð / *돰\ ^.j tN&< s< tC u tNYWOΰ Sv &} t .**ˀ[_tO +*.:t:t w:tF)*tItWG_u*]@F_W^!A.< s< t P^X @< rq<t.PX @.t @Ú@t@<u@<t)<t,<uް~..&X[YZ^_]#..< t <t%< u. Qȵ kY.k.c@t PEXltÊZ:t:Ɗu&N &f&F&.&F &F&f^&-&-&:sT&&!W3؎fr jqq@ʎ42ʻAtيЈ:Јt.3Ɏ^t%4GGξ0+΋ xIAWX&f&F&.&+F &N@@&vH31ðrww t t wƻ:°r+鸵-*G COMMAND v. 1.10 $Ȏм\O !5 tH !!$_ tk3 @  щڴ&! uE! '!Ⱥ u!ێÎӼ@PSP!. ˺A ! ! '!    +Љ Ӌ;Ѻ w> t+ȋً3Ҵ! '!) tuۺ    !> t"' ! uҋ> Ŏ&- uޡ ŎЋ& . غ!. ..   3 >] u ?]  !\!t \ !!N6 > M !! t*ñ$-!$-!P u ! u  (! tʺ !! !E t ! > u޴ ! '! <u 5 ìtN< t<=t<,t< ún !!. $. 3.RrZA ĀuWt v ״ !! ! !* ] tȎ؎мz>3t,Ar<w .TqȎ؎мz !븴 !" FL. \?V 5 .PR. rZ+AXFZXt mЭË.؋VYV1ZItƨt Ru-2QƋ+‹ù3+&ˬ$<t< s.Y&6&g;QPR^[ێY;rIAIQPR_Y;پ"s+ˇW^QPRK_Y+˾"uSW_[uOZG3rxFIrm ,0r< s$_,< r<r CÊ<'t<"tF< t":u:$uFC"s"tuN!imDt>eHEu>gX.teEXu>gEt]ش!\t ! uj'!~ú!tj~(\! uڴ\!ú&tȺ!\!j u'!}j+ЉӋ;Ѻw5>t+ȋً3Ҵ!\'!)tuۺ1}j!>t"'\! uҋ>Ŏ&-uޡţţÉ&t!\! u|j\3.<:uDȵ9;6+(;vρu !<t <t u.~ s1JQPR^YIAtNO&G22Ћ. 3;u66Pp6 tN6X/[C6'e $dW&*Ę6 |e$$&û) 2q,u $,Š$'@'< uû,Њ+ y-ܪH,<>uL$\$,  ,>t3[G<u>tT<vS<tO<v<u BPs+s t1DI t <t $ڰ]ð+|BXSI$,22,[3,2$KLøL,X3$ Q>t,$7DXû">t B,tW,CLИ؋û t <r22<r<stL,hALCLDLBLAHCHDHBHAXCXDXBXSPBPSIDIESCSSSDSfF fF f f f f Sc Lc uF uF u u u u Sc Lc iF iF i i i i Sc Lc oF oF o o o o Sc Lc wF wF w w w w 1  lF lF l l l l 1 rF rF r r r r 1 zF zF z z z z 1 p p p p p p p p p p p p p p p p Sp Sp Sp Sp Sp Sp Sp Sp Lp Lp Lp Lp Lp Lp Lp Lp d d d d d d d d d d d d d d d d //////////////// #     }F }F } } }t  } L >RRRRRRR WOt}_}_}t}t04  {}}}}}}}}}}}}}}}}d d k k  } } d d kk   d */%/!//  D H BB /DH7 d f7 b7   ##DADADSUSBXOOANCMMOAAAAAAAACALCBCLUDCMCMPCMPCWDADADEDIESHLIDIIMUINININININTIREJJNJJBJCXJJJGJJLJMJNJPJPJJNJNJJLAHLDLELELOCLODLODLOOLOOPLOOPNMOVMOVMUNENONOOUTOUTPOPOPPUSPUSHRCRCREPREPNREROROSAHSASCASCASHSHSTDOWESTOSTOTESWAIXCHXLASE??nq\_xfuiowlrzA;8SAXBXCXDXSPBPSIDIDSESSSCSIPPCOVDNEINGZRACPECYNVUPDIPLNZNAPONC Program terminated normally $Invalid drive or file name $File not found $No room in disk directory $Insufficient space on disk $Hard Disk Error $Insufficient memory $^ Error Error in EXE/HEX file $EXE/HEX file cannot be written $ZP 00 207772697474656E0D0A2474 :1A167A0000000000000000005A0000000000000000000000000000000001FB :051694u Seattle Computer Products 8086 Assembler Version 2.40 Copyright 1979,80,81 by Seattle Computer Products, Inc. $* !m[!e t\V!)uñu! U!:ű#жu27%t&uڊ< 't_! uU! :u:u:t[P!XV!àU!< t< u V!<$tV! <{s uڋ wϠV!<)uƶ2û!?sttC{!tXSN>{!uË>{!u8t`xtPY<;tL< tH<u\@u0 uNzJRPq<,u?h<u6X[4$ t u&4 PDXu$ Q<uX utu$ |ڊ$  x $ $8 C.+ <u@$t P F<t" t6" -Yިt~!]!~!C]! >]!>]!QRS Q@RS u t t  t)ŀtid[ZYÊÊQSYŀtFQRS:$<t $t:t-:t+:Ʊu,<t3 t ڊ t :tPY<t, ±u `[P|<uŋ ñu X tKtt-t> $t0ڋ ޞ։ ڰڋ ñ"t)C7É "  uڋ Ø;ÇuȵȵQ<t<tY<t$t%t$ c $ Y$8  K$8 @ȵQe u<t<uwPJX$ ȵQ-V!<,uR , u:ZYQR&$P XuP <t3<t<t<uX$8 Š. t 'u$-V!<,tX!%u PQRmZYX>!_PX뭀x!@&x! !p !SCڋ t[C(\!s ,آ\!|Cu CCSGG>\!t I\![ uâo!SQPRrZb!<u puXcY h![ tr!P XCvwt}) xCЀÂ> ZtPj!h!j!Bl!f!egl!m! !!!+Ϻ (! u%Ë>!>!ËϺ!+t! (! tM @$ 0<:rdd nd"dc aa 7as ?amadlign~ mp :lc ld li mc bw wd allmpb mpw mpsb mpsw bwmsD i ecIiv\0aa 'as /own i quD scJndndifylt alt n fD ncAnb nw ntmul\(div\8ret nto pztetl|brawgopsxcrmpnzuneunl}ge}nbsaesncsng~le~navbevpeznp{po{noqnsympscxznge|naernbewnleea ds es oopodb odw ahf ock oopeoopzodsb odsw oopneoopnzov/ ul\ ovb ovw ovsb ovsw ot eg op r ut rgD utb utw oputD ush opf ushf etnep olorclcrepz epe epnz epne ub *bb bc tc td ti hl hr(al ar8eg&cab caw tob tow ahf casb casw tosb tosw estp ait or2chg lat xmdddbl st0ohomhhompleiiv0ivivr8ecstisnre adlsu stsmudi0sub(div8cocomncstni dddpdl2dl2dlgdlndc(den uuloretaatandinsto tuubububrqrcalav0tc8ten0ts8saicAatracl2l2xpJKJoJJJJJJJwJJJJJJJJJJJJJJJJ JJ!6JJOxJJJJJJJJ"?JP[hJJwJJJJJJJJJJJJJJJ7VJJJsJJzJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJAuJJJJRegister not allowed in VALUIndex or base register must be BP, BX, SI, or DOnly one base register (BX, BP) alloweOnly one index register (SI or DI) alloweOnly addition allowed on register or undefined labeOnly one undefined label per expression alloweIllegal digit in hexadecimal numbeIllegal digit in decimal numbe Illegal character in label or opcod Label defined twic Opcode not recognizeInvalid operan"," and second operand expecteRegister mismatcImmediate operand not allowe"]" expecteTwo memory operands not alloweDestination must not be immediate valuBoth operands must not be registerOperand must be segment registeFirst operand must be registeUndefined label not alloweValue out of rang Error in operand size fla!Must have label on same lin#Zero-length string illega$ENDIF without I%One-character strings onl&Illegal expressio'End of string not foundUndefined labeeValue out of range (forward***** ERROR: No directory space $ Insufficient memory $ File not found $ Disk full $ Bad disk specifier $ Error Count Symbol Table ASMendififre HEX PRN20202050524E0000  <t\r!!!G.!!G!! #u!'!<;uy3ҋS%r_[\3ҋ&rH! t! Yt1!3@(! mu !  ! HARD DISK ERROR$Invalid drive specification$No 86DOS.SYS on drive A$Error in 86DOS.SYS on drive A$No place for system$No room for system$???????????86DOS SYS00000000000000002A :0000000000 :1A02A800FZ !,!::.u ! !3ɋѳ:+t$t ̳ t-! u  !Zrtv < t:u Ê< t,0r< rF 00ĒP!X Invalid time$Current time is $ Enter new time: $ \! uQ|Z !*! --ldrd ! ,!.< t2G,?$7l< t d&Ĵȴ+! u ] t۾etB\!& t !\  !$ ! t C  3}! # @j !I@ & u& < ȋс > \'! >B D   : 8 ( & u #%!*D ! * % F Y( C<,uFNI* 3NWQY+Ϟ_& uB >( u;> s!@ +t~\'!& Qu& 3ҋ( u;> v >> +ϻ8Et O@ GGJY+χ>B )>};u& ú !& u' u( u< >B +vځ v3һkCCϺ +t! (! u >: B +AO>B 8 ô!a ! 8 >: ;tw t  ;t֋B +ϰ B;uS l:d*[;8 t V3й''Š$t0*( u 8 w]u>* G+wB +t1.8 QhY< s< t< t < t P^X @< COuKÿ B<  tB< uOu64 +, . сwp2 RZ0 64 +IOVF . D^6, ʃ7u*~>0 OF , . I0 AJ)6 s6 Bu Jú ;lu32 64 :t+>0 6 u>0 >4 6 2 tͺ !á4 : 2 8 % t !!PX< t0 >4 2 * +0 +, rA6 6 >0 uыߋ, I :ʋu6 ω>0 >4 +ϰ 2 BtJ2 4 23ɬ<t< tA( u8 %uSW* uC_+v 8 >: 3Uj( u8 C8 6: u;6B t, 6: ! ` ?, >: ;t.VWQB +;@ sB +;wAY_^ú !]#%%!( u8 UB +A>@ Gډ>: 8 .B ^   ! <t%LB;s󤤰 CȌȎ؎м .B >: F@ +O>B !!$_wOP,H! t62P !Z ! !X6~P ! !XFVQIAr6 t@;tB3v~Ճ"!F(!!! !XPa !Y,A;w3Fu pt3@EEF t!.!6+63IW+qyv+º` &9tV&] t[\u52\m= u] l u!@H:+t2hx\'sSV  m m u m ?xlr C 374'+: u    ls ð R!ZË VWS[_^)t :~vu)FE =r5D"FL)Ft$F tN!3^,PSW>_[Z, W$t }?t_ f"Į_tIKxr͋@빋3ҋ^F u!^îйߠ+U%X]sj^ HARD DISK ERROR on CP/M drive $Source and destination drives must not be the same$Drive not available for CP/M reading$Insufficient disk space$No room in directory to create file$Source file name missing$Source file not found$File transfer complete$DIR  p""""?3     R?^     addresses of the parameter block for each of 16 drives. ;Note that 16 entries are ALWAYS required\#H!#! uk\! u_3}DF@j1#'! u?ḤD\'H! <u(#! u!! ! RDCPM file bad$Insufficient disk space to write file$RDCPM.COM not found on specified drive$Specified file not found$Specified file too long$RDCPM COMDF@j1#'! u?ḤD\'H! <u(#! u!UU h2Cu|\׋ޢ"AS M \ t} ts  2b   b ' #U <;u Y;T<t J< u빵=u0 ( b< t<t<;t< t <:t<,t< $'@'SRQP$ C u  uXYZ[SRQ u\<t<t C  `t Xu:w KttsC;u<:u 2"t<(u [<)u ȟ] <'t#s8CTu< tCF<;t<'u<0r<:s<@r$_s/;vɺ ! Cu !<t4<t3  ,0r< r,r<sq !6peCOgM\! u)3}@j3!ʹ(\!p! dHEXCOMError in HEX file--conversion aborted$File not found$Address out of range--conversion aborted$Disk directory full$3\! uQ| Disk INIT version 1.1 $ !6!\ !!< u $_,Ar!:sڊƘɋxuqȋCCK4 2Ҋ‹-ƋCD:s ZOWRuZV t 謈Cu^Ê 40 4r 30 4t0"tѴ !Completely re-formats any bad disk - destroying its contents, of course! $ Initialize disk in which drive? $ ERROR - Not ready or write protected $1248MMMMPPPP.6; Disk initialization routine for 1771/1793 type disk controllers. ; Runs on 8086 under 86-DOS. Revised 11-24-81. ; ; Translated from the Z80 on 12-19-80 and subsequently upgraded to handle ; all of the following controllers. Set switch to one to select. ; ; Note: tables for the SCP controller or the Cromemco 16FDC with small ; drives do not exist yet. Do not select these combinations. ; SCP: EQU 0 ; Seattle Computer Products controller. TARBELLSINGLE: EQU 0 ; Tarbell single-density controller . TARBELLDOUBLE: EQU 0 ; Tarbell double-density controller. CROMEMCO4FDC: EQU 1 ; Cromemco 4FDC. CROMEMCO16FDC: EQU 0 ; Cromemco 16FDC. LARGE: EQU 1 ; All 8-inch disks. COMBIN: EQU 0 ; Some 8-inch and some 5.25-inch. SMALL: EQU 0 ; All 5.25-inch disks. CUSTOM: EQU 0 ; Custom drive configuration. LARGEDS:EQU 1 ; Set to 1 if large disks are double-sided. SMALLDS:EQU 0 ; Set to 1 if small disks are double-sided. STPSPD: EQU 2 ;******************************************************************** TARBELL: EQU TARBELLSINGLE + TARBELLDOUBLE CROMEMCO: EQU CROMEMCO4FDC + CROMEMCO16FDC IF SCP DISK: EQU 0E0H DONEBIT:EQU 01H SMALLBIT:EQU 10H BACKBIT:EQU 04H DDENBIT:EQU 08H ENDIF IF TARBELL DISK: EQU 78H DONEBIT:EQU 80H BACKBIT:EQU 40H DDENBIT:EQU 08H ENDIF IF CROMEMCO DISK: EQU 30H DONEBIT:EQU 1 SMALLBIT:EQU 10H BACKBIT:EQU 0FDH ; Send this to port 4 to select back. DDENBIT:EQU 40H ENDIF CONIN: EQU 1 OUTSTR: EQU 9 SELDRV: EQU 14 ORG 100H PUT 100H  JP INIT HEADER: DB 13,10,'Disk INIT version 1.1',13,10,'$',26 INIT: MOV AH,OUTSTR MOV DX,HEADER INT 33 MOV DX,WARNING INT 33 EACH: MOV SP,5CH MOV AH,OUTSTR MOV DX,DRVMES INT 33 MOV AH,CONIN INT 33 ; Get drive letter from console. CMP AL,13 ; Return to 86-DOS if CR. JNE CHECKDRIVE INT 32 CHECKDRIVE: AND AL,5FH ; Force upper case. SUB AL,'A' JC EACH ; Check if valid drive. MOV DH,AL MOV DL,-1 MOV AH,SELDRV INT 33 ; Get number of drives. CMP DH,AL  JAE EACH ; Jump if drive number too big. MOV AL,DH ; Set DI to drive number. CBW MOV DI,AX MOV SI,AX ; SI = twice drive number to point to words. SAL SI ; Create basic pattern for a track. MOV BX,PATTERN MOV DX,[SI+INDEXT] ; DX points to index pattern. CALL MAKE ; Make pattern for index mark and one sector MOV CL,[DI+SECCNT] ; Get sector count for this sector. DEC CL ; Repeat sector pattern for remaining sectors. MAKSEC: MOV DX,[SI+SECTAB] ; DX point to sector pattern. CALL MAKE DEC CL JNZ MAKSEC CALL MAKE ; Fill out rest of track. ; Put in sequential sector numbers. MOV AL,1 ; Start with sector number 1 MOV CL,AL ; Add one to each succeeding sector number MOV BX,[SI+TRKNUM] ; Get offset from beginning of pattern to first INC BX ; track number. Increment to side, INC BX ; then sector. ADD BX,PATTERN ; Compute actual memory address. CALL PUTSEC ; ; Select drive and restore head to track 0. ; IF CROMEMCO16FDC IN DISK+4 ; Get "disk flags". TEST AL,08H ; See if motor on. ENDIF MOV AL,[DI+DRVTAB] ; Get drive-select byte. OUT DISK+4 ; Send it out. IF CROMEMCO16FDC JNZ MOTORSON ; If motors already on, don't wait. MOV CX,43716 ; Loop count, 1 second for loop below. MOTORDELAY: ; (8 MHz, 16-bit memory). AAM ; 83 clocks. AAM ; 83 clocks. LOOP MOTORDELAY ; 17 clocks. MOTORSON: ENDIF  MOV AL,08H+STPSPD ; Restore without verify MOV CH,0D0H ; Accept "not ready", "write protect", & "seek CALL DCOM ; error" errors. XOR DL,DL ; Start with track 0. TRACKLOOP: MOV AL,DL ; Get track number. MOV BX,[SI+TRKNUM] ; Get offset from beginning of pattern to first ADD BX,PATTERN ; track number and add address of pattern. MOV CL,0 CALL PUTSEC ; Put tra ck number in each sector. IF SCP+TARBELLDOUBLE MOV AL,[DI+DRVTAB] ; Get drive-select byte. OUT DISK+4 ; Select side. AND AL,BACKBIT ; Compute side number. JZ GOTSIDE MOV AL,1 ; If not zero, then 1. GOTSIDE: MOV DH,AL ; Save side number in DH. ENDIF IF CROMEMCO16FDC MOV AL,0FFH ; Select front side if 16FDC. OUT 04H ENDIF IF TARBELLSINGLE+CROMEMCO MOV DH,0 ; These controllers always start with side 0. ENDIF SIDELOOP: MOV AL,DH ; Get side number. MOV BX,[SI+TRKNUM] ; Get offset from beginning of pattern to first INC BX ; track number. Increment to side. ADD BX,PATTERN ; Compute actual memory address. MOV CL,0 CALL PUTSEC ; Put side byte in each sector ; ; Write a track. ; CALL TRACK MOV SI,DI  ; Fix up SI (TRACK messed it up). SAL SI IF (SCP+CROMEMCO16FDC)*(LARGEDS+SMALLDS)+TARBELLDOUBLE*LARGEDS MOV AL,[DI+DRVTAB] ; Get drive-select byte. TEST AL,DDENBIT ; See if double-density. JZ NEXTRACK ; Jump if not. ENDIF IF SCP*LARGEDS*(SMALLDS-1)+CROMEMCO16FDC*SMALLDS*(LARGEDS-1) TEST AL,SMALLBIT ; Check for small disk. JNZ NEXTRACK ; Jump if small because SMALLDS is off or, ENDIF ; jump if large because LARGEDS is off. IF SCP*(LARGEDS-1)*SMALLDS+CROMEMCO16FDC*(SMALLDS-1)*LARGEDS TEST AL,SMALLBIT ; Check for large disk. JZ NEXTRACK ; Jump if large because LARGEDS is off or, ENDIF ; jump if small because SMALLDS is off. IF (SCP+CROMEMCO16FDC)*(LARGEDS+SMALLDS)+TARBELLDOUBLE*LARGEDS INC DH ; Next side. CMP DH,2 ; See if too big. JAE NEXTRACK ; Finished this track, on to the next. IF SCP+TARBELL OR AL,BACKBIT ; Select back side. OUT DISK+4 ENDIF IF CROMEMCO MOV AL,BACKBIT ; Select back side. OUT 04H ENDIF JP SIDELOOP ; Do the back side. NEXTRACK: ENDIF INC DL ; Next track. CMP DL,[DI+TRKCNT] ; See if done. JAE FINI MOV AL,58H+STPSPD ; Step in to next track MOV CH,0C0H ; Accept "not ready" or "write protect" errors. CALL DCOM JP TRACKLOOP FINI: JMP EACH PUTSEC: PUSH DX MOV CH,[DI+SECCNT] ; CH = number of sectors. MOV DX,[SI+SECSIZ] ; DX = number of bytes in sector pattern. SEC:  MOV [BX],AL ; Poke number in sector ID. ADD BX,DX ADD AL,CL ; Increment sector, side, or track number. DEC CH JNZ SEC POP DX RET MAKE: PUSH SI MOV SI,DX MAKELOOP: UP LODB ; Get byte count. OR AL,AL ; Return if zero. JZ MAKERETURN MOV CH,AL ; Count to CH. LODB ; Get byte for pattern. PUTPAT: MOV [BX],AL ; Put byte in pattern. INC BX DEC CH JNZ PUTPAT JP MAKELOOP MAKERETURN: POP SI RET TRACK: IF CROMEMCO MOV AL,[DI+DRVTAB] ; Get drive-select byte. OR AL,80H ; Turn on auto-wait. OUT DISK+4 ENDIF MOV AL,0F4H OUT DISK MOV SI,PATTERN MOV CH,0E4H ; Accept "not ready", "write protect", "write ; fault", & "lost data" errors. AAM ; Delay 10 microseconds INTRQ to go off. WRTLP: IF SCP IN DISK+5 ; Wait for DRQ or INTRQ. ENDIF IF TARBELL+CROMEMCO IN DISK+4 ENDIF IF TARBELL SHL AL JNC WAIT ENDIF IF SCP+CROMEMCO SHR AL JC WAIT ENDIF LODB OUT DISK+3 JP WRTLP DCOM: OUTB DISK AAM  ;10 Microsecond delay WAIT: INB DISK+4 TEST AL,DONEBIT IF SCP+CROMEMCO JZ WAIT ENDIF IF TARBELL JNZ WAIT E NDIF IN DISK ; Get status from disk. AND AL,CH JZ RET MOV AH,OUTSTR MOV DX,ERRMES INT 33 JMP EACH WARNING:DB "Completely re-formats any bad disk - " DB "destroying its contents, of course!",13,10,"$" DRVMES: DB 13,10,"Initialize disk in which drive? $" ERRMES: DB 13,10,10,"ERROR - Not ready or write protected",13,10,"$" ; ; How the tables below work: ; DRVTAB is a bunch of bytes which are sent to the disk controller as ; drive-select bytes, they are used it to specify which physical drive ; the logical drive number refers to. The first entry is for logical ; drive 0, the next for logical drive 1, etc. The drive select byte ; must also have the correct bits set for density (single or double), ; size (5.25-inch or 8-inch), and side (if initializing one side only; ; logical drives which are double-density double-sided MUST have the ; drive-select byte select side zero). Exactly which bit does what ; depends on the actual disk controller used. ; ; SECCNT indicates how many sectors per track for each drive. ; (Each entry is one byte - use DB). ; 5.25-inch single-density -> 18 ; 5.25-inch double-density -> 8 ; 8-inch single-density -> 26 ; 8-inch double-density -> 8 ; ; TRKCNT indicates how many tracks are on each drive. ; (1 byte each entry - use DB). ; ; TRKNUM indicates how many bytes from the beginning of the track ; pattern the first sector ID number is. ; (Each entry is two bytes - use DW). ; 5.25-inch single-density -> 12 ; 5.25-inch double-density -> don't know yet for sure ; 8-inch single-density -> 80 ; 8-inch double-density -> 162 ; ; SECSIZ indicates how many bytes are in each the pattern for each ; sector. (Each entry is two bytes - use DW). ; ; INDEXT points to the index pattern to be used for each drive. ; (Each entry is two bytes - use DW). ; 5.25-inch single-density -> SSINDEX ; 5.25-inch double-density -> SDINDEX ; 8-inch single-density -> LSINDEX ; 8-inch double-density -> LDINDEX ; ; SECTAB points to the sector pattern to be used for each drive. ; (Each entry is two bytes - use DW). ; 5.25-inch single-density -> SSSECTOR ; 5.25-inch double-density -> SDSECTOR ; 8-inch single-density -> LSSECTOR ; 8-inch double-density -> LDSECTOR ; IF TARBELLDOUBLE*(LARGEDS-1) DRVTAB: DB 0,10H,08H,18H,28H,38H SECCNT: DB 26,26,8,8,8,8 TRKCNT: DB 77,77,77,77,77,77 TRKNUM: DW 80,80,162,162,162,162 SECSIZ: DW 186,186,1138,1138,1138,1138 INDEXT: DW LSINDEX,LSINDEX,LDINDEX,LDINDEX,LDINDEX,LDINDEX SECTAB: DW LSSECTOR,LSSECTOR,LDSECTOR,LDSECTOR,LDSECTOR,LDSECTOR ENDIF IF TARBELLDOUBLE*LARGEDS DRVTAB: DB 0,40H,10H,50H,08H,18H SECCNT: DB 26,26,26,26,8,8 TRKCNT: DB 77,77,77,77,77,77 TRKNUM: DW 80,80,80,80,162,162 SECSIZ: DW 186,186,186,186,1138,1138 INDEXT: DW LSINDEX,LSINDEX,LSINDEX,LSINDEX,LDINDEX,LDINDEX SECTAB: DW LSSECTOR,LSSECTOR,LSSECTOR,LSSECTOR,LDSECTOR,LDSECTOR ENDIF  IF TARBELLSINGLE DRVTAB: DB 0F2H,0E2H,0D2H,0C0H SECCNT: DB 26,26,26,26 TRKCNT: DB 77,77,77,77 TRKNUM: DW 80,80,80,80 SECSIZ: DW 186,186,186,186 INDEXT: DW LSINDEX,LSINDEX,LSINDEX,LSINDEX SECTAB: DW LSSECTOR,LSSECTOR,LSSECTOR,LSSECTOR ENDIF IF CROMEMCO4FDC*LARGE DRVTAB: DB 31H,32H,34H,38H SECCNT: DB 26,26,26,26 TRKCNT: DB 77,77,77,77 TRKNUM: DW 80,80,80,80 SECSIZ:  DW 186,186,186,186 INDEXT: DW LSINDEX,LSINDEX,LSINDEX,LSINDEX SECTAB: DW LSSECTOR,LSSECTOR,LSSECTOR,LSSECTOR ENDIF IF CROMEMCO4FDC*SMALL DRVTAB: DB 21H,22H,24H,28H SECCNT: DB 18,18,18,18 TRKCNT: DB 40,40,40,40 TRKNUM: DW 12,12,12,12 SECSIZ: DW 165,165,165,165 INDEXT: DW SSINDEX,SSINDEX,SSINDEX,SSINDEX SECTAB: DW SSSECTOR,SSSECTOR,SSSECTOR,SSSECTOR ENDIF IF CROMEMCO4FDC*COMBIN DRVTAB: DB 31H,32H,24H SECCNT: DB 26,26,18 TRKCNT: DB 77,77,40 TRKNUM: DW 80,80,12 SECSIZ: DW 186,186,165  INDEXT: DW LSINDEX,LSINDEX,SSINDEX SECTAB: DW LSSECTOR,LSSECTOR,SSSECTOR ENDIF IF CROMEMCO16FDC*LARGE DRVTAB: DB 31H,32H,71H,72H,74H,78H SECCNT: DB 26,26,8,8,8,8 TRKCNT: DB 77,77,77,77,77,77 TRKNUM: DW 80,80,162,162,162,162 SECSIZ: DW 186,186,1138,1138,1138,1138 INDEXT: DW LSINDEX,LSINDEX,LDINDEX,LDINDEX,LDINDEX,LDINDEX SECTAB: DW LSSECTOR,LSSECTOR,LDSECTOR,LDSECTOR,LDSECTOR,LDSECTOR ENDIF IF CROMEMCO16FDC*COMBIN DRVTAB: DB 31H,32H,24H,71H,72H,64H SECCNT: DB 26,26,18,8,8,8 TRKCNT: DB 77,77,40,77,77,40 TRKNUM: DW 80,80,12,162,162,? SECSIZ: DW 186,186,165,1138,1138,? INDEXT: DW LSINDEX,LSINDEX,SSINDEX,LDINDEX,LDINDEX,SDINDEX SECTAB: DW LSSECTOR,LSSECTOR,SSSECTOR,LDSECTOR,LDSECTOR,SDSECTOR ENDIF IF (SCP+CROMEMCO16FDC)*(LARGE+COMBIN)+TARBELLDOUBLE LDINDEX: ; Pattern for 8-inch double-density. DB 80,4EH DB 12,0 DB 3,0F6H DB 1,0FCH DB 50,4EH LDSECTOR: DB 12,0 DB 3,0F5H DB 1,0FEH DB 3,0 ;Track, side, and sector DB 1,3 ;Sector size=1024 DB 1,0F7H DB 22,4EH DB 12,0 DB 3,0F5H DB 1,0FBH DB 255,0E5H DB 255,0E5H DB 255,0E5H DB 255,0E5H DB 4,0E5H DB 1,0F7H DB 54,4EH DB 0 DB 255,4EH DB 255,4EH DB 255,4EH DB 0 ENDIF IF (SCP+CROMEMCO16FDC)*(LARGE+COMBIN)+TARBELLDOUBLE LSINDEX: ; Pattern for 8-inch single-density with 1793. DB 40,-1 DB 6,0 DB 1,0FCH DB 26,-1 LSSECTOR: DB 6,0 DB 1,0FEH DB 4,0 DB 1,0F7H DB 11,-1 DB 6,0 DB 1,0FBH DB 128,0E5H DB 1,0F7H DB 27,-1 DB 0 DB 255,-1 DB 255,-1 DB 0  ENDIF IF CROMEMCO4FDC*(LARGE+COMBIN)+TARBELLSINGLE LSINDEX: ; Pattern for 8-inch single-density with 1771. DB 46,0 DB 1,0FCH DB 26,0 LSSECTOR: DB 6,0 DB 1,0FEH DB 4,0 DB 1,0F7H DB 17,0 DB 1,0FBH DB 128,0E5H DB 1,0F7H DB 27,0 DB 0 DB 255,0 DB 255,0 DB 0 ENDIF IF CROMEMCO4FDC*(SMALL+COMBIN) SSINDEX: ; Pattern for 5.25-inch single-density w/1771. SSSECTOR: ; No index mark on small disk DB 7,-1 DB 4,0 DB 1,0FEH DB 4,0 DB 1,0F7H DB 11,-1 DB 6,0 DB 1,0FBH DB 128,0E5H DB 1,0F7H DB 1,-1 DB 0 DB 255,0 DB 255,0 DB 0 ENDIF PATTERN: 05 :1; I/O System for 86-DOS version 1.10 and later. Revised 12-3-81. ; Assumes a CPU Support card at F0 hex for character I/O, ; with disk drivers for Tarbell, Cromemco, or North Star controllers. ; Select whether the auxiliary port is the Support Card parallel port ; or on channel 1 of a Multiport Serial card addressed at 10H. PARALLELAUX: EQU 1 SERIALAUX: EQU 0 ; Select whether the printer is connected to the Support card parallel ; output port (standard) or channel 0 of a Multiport Serial card   ; addressed at 10H. PARALLELPRN: EQU 1 SERIALPRN: EQU 0 ; If the Multiport Serial was chosen for either the auxiliary or the ; printer, select the baud rate here. Refer to Multiport Serial manual ; page 11 to pick the correct value for a given baud rate. PRNBAUD:EQU 7 ; 1200 baud AUXBAUD:EQU 0FH ; 19200 baud ; Select disk controller here. SCP: EQU 0 TARBELLSD: EQU 0 TARBELLDD: EQU 0 CROMEMCO4FDC: EQU 1 CROMEMCO16FDC: EQU 0 NORTHSTARSD: EQU 0 TARBELL:EQU TARBELLSD+TARBELLDD CROMEMCO:EQU CROMEMCO4FDC+CROMEMCO16FDC ; If North Star controller is selected, stop here. If 1771/1793-type ; controller is selected, configuration options must be selected below: IF SCP+TARBELL+CROMEMCO ; Select disk configuration: LARGE: EQU 1 ; Four large drives. COMBIN: EQU 0 ; Two 8-inch and one 5.25-inch. SMALL: EQU 0 ; Three 5.25-inch drives. CUSTOM: EQU 0 ; User defined. ; If 8-inch drives are PerSci, select FASTSEEK here: ; Fastseek with Tarbell controllers doesn't work yet. FASTSEEK: EQU 0 ; For double-density controllers, select double-sided operation of ; 8-inch disks in double-density mode. LARGEDS: EQU 1 ; For double-density controllers, select double-sided operation of ; 5.25-inch disks in double-density mode. SMALLDS: EQU 0 ; Use table below to select head step speed. Step times for 5" drives ; are double that shown in the table. Times for Fast Seek mode (using ; PerSci drives) is very small - 200-400 microseconds. ; Step value 1771 1791 ; 0 6ms 3ms ; 1 6ms 6ms ; 2 10ms 10ms ; 3 20ms 15ms STPSPD: EQU 2 ;********************************************************************** IF LARGE+COMBIN ; Drive A is 8-inch. DOSLEN: EQU 56 ; Length of 86-DOS in 128-byte sectors. DOSSECT:EQU 52+6+6+16 ; Allow for reserved tracks, 2 FATs, directory ENDIF IF SMALL ; Drive A is 5.25-inch. DOSLEN: EQU 56 DOSSECT:EQU 54+4+4+16 ENDIF IF CUSTOM ; Drive A is custom. DOSLEN: EQU 0 ; Specify DOS length in sectors. DOSSECT:EQU 0 ; Specify beginning sector on disk. ENDIF ENDIF IF NORTHSTARSD DOSLEN: EQU 28 DOSSECT:EQU 30+2+2+8 ENDIF ORG 0 PUT 100H BASE: EQU 0F0H SIOBASE:EQU 10H STAT: EQU BASE+7 DATA: EQU BASE+6 DAV: EQU 2 TBMT: EQU 1 SERIAL: EQU SERIALPRN+SERIALAUX STCDATA:EQU BASE+4 ; Ports for 9513 Timer chip. STCCOM: EQU BASE+5 IF SERIALAUX AUXSTAT:EQU SIOBASE+3 AUXDATA:EQU SIOBASE+2 ENDIF IF PARALLELAUX AUXSTAT:EQU BASE+13 AUXDATA:EQU BASE+12 ENDIF IF SERIALPRN PRNSTAT:EQU SIOBASE+1 PRNDATA:EQU SIOBASE+0 ENDIF IF PARALLELPRN PRNSTAT:EQU BASE+13 PRNDATA:EQU BASE+12 ENDIF JMP INIT JMP STATUS JMP INP JMP OUTP JMP PRINT JMP AUXIN JMP AUXOUT JMP READ JMP WRITE JMP DSKCHG JMP SETDATE JMP SETTIME JMP GETTIME INIT: MOV AL,0FFH ;Mask all interrupts OUT BASE+3 ;Send mask to slave XOR AX,AX MOV SS,AX MOV SP,400H ;Set stack just below I/O system PUSH CS POP DS ; Initialize time-of-day clock. MOV SI,STCTAB MOV CX,4 ;Initialize 4 registers INITSTC: LODB OUT STCCOM ;Select register to initialize LODB OUT STCDATA LODB OUT STCDATA LOOP INITSTC IF SERIAL MOV CX,4 SERINIT: LODB OUT SIOBASE+1 OUT SIOBASE+3 LOOP SERINIT LODB ;Baud rate for chann el 0 OUT SIOBASE+8 LODB ;Baud rate for channel 1 OUT SIOBASE+9 ENDIF ; Load 86-DOS PUSH DS MOV AX,DOSSEG ; Set segment register for loading DOS. MOV DS,AX XOR BX,BX ; Offset in DOSSEG is zero. MOV AL,BL ; Drive 0. MOV CX,DOSLEN MOV DX,DOSSECT CALL READ,40H POP DS MOV SI,INITTAB CALL 0,DOSSEG MOV DX,100H MOV AH,26 ;Set DMA address INT 21H MOV CX,[6] ;Get size of segment MOV BX,DS ;Save segment for later ;DS must be set to CS so we can point to the FCB MOV AX,CS MOV DS,AX MOV DX,FCB ;File Control Block for COMMAND.COM MOV AH,15 INT 21H ;Open COMMAND.COM OR AL,AL JNZ COMERR ;Error if file not found XOR AX,AX MOV [FCB+33],AX ;Set 4-byte Random Record field to MOV [FCB+35],AX ; beginning of file INC AX MOV [FCB+14],AX ;Set record length field MOV AH,39 ;Block read (CX already set) INT 21H JCXZ COMERR ;Error if no records read TEST AL,1 JZ COMERR ;Error if not end-of-file ;Make all segment registers the same MOV DS,BX MOV ES,BX MOV SS,BX MOV SP,5CH ;Set stack to standard value XOR AX,AX PUSH AX ;Put zero on top of stack for return MOV DX,80H MOV AH,26 INT 21H ;Set default transfer address (DS:0080) PUSH BX ;Put segment on stack MOV AX,100H PUSH AX ;Put address to execute within segment on stack RET L ;Jump to COMMAND COMERR: MOV DX,BADCOM MOV AH,9 ;Print string INT 21H EI STALL: JP STALL STCTAB: DB 17H ;Select master mode register DW 84F3H ;Enable time-of-day DB 1 ;Counter 1 mode register DW 0138H DB 2 DW 0038H DB 3 DW 0008H ;Set counter 3 to count days IF SERIAL DB 0B7H, 77H, 4EH, 37H, PRNBAUD, AUXBAUD ENDIF BADCOM: DB 13,10,"Error in loading Command Interpreter",13,10,"$" FCB: DB 1,"COMMAND COM" DS 25 GETTIME: MOV AL,0A7H ;Save counters 1,2,3 OUT STCCOM MOV AL,0E0H ;Enable data pointer sequencing OUT STCCOM MOV AL,19H ;Select hold 1 / hold cycle OUT STCCOM CALL STCTIME ;Get seconds & 1/100's XCHG AX,DX CALL STCTIME ;Get hours & minutes XCHG AX,CX IN STCDATA MOV AH,AL IN STCDATA XCHG AL,AH ;Count of days JP POINTSTAT STCTIME:  CALL STCBYTE MOV CL,AH STCBYTE: IN STCDATA MOV AH,AL SHR AH SHR AH SHR AH SHR AH AND AL,0FH ;Unpack BCD digits AAD ;Convert to binary MOV AH,AL MOV AL,CL RET SETTIME: PUSH CX PUSH DX CALL LOAD0 ;Put 0 into load registers to condition timer MOV AL,43H ;Load counters 1 & 2 OUT STCCOM POP DX POP CX CALL LOAD MOV AL,43H OUT STCCOM ;Load counters 1&2 CALL LOAD0 MOV AL,27H ;Arm counters 1,2,3 OUT STCCOM JP POINTSTAT LOAD0: XOR CX,CX MOV DX,CX  LOAD: MOV AL,09 ;Counter 1 load register CALL OUTDX MOV AL,0AH ;Counter 2 load register MOV DX,CX OUTDX: OUT STCCOM ;Select a load register MOV AL,DL CALL OUTBCD MOV AL,DH OUTBCD: AAM ;Convert binary to unpacked BCD SHL AH SHL AH SHL AH SHL AH OR AL,AH ;Packed BCD OUT STCDATA RET SETDATE: XCHG AX,DX ;Put date in DX MOV AL,0BH ;Select Counter 3 load register OUT STCCOM XCHG AX,DX OUT STCDATA MOV AL,AH OUT STCDATA MOV AL,44H ;Load counter 3 OUT STCCOM POINTSTAT: PUSH AX MOV AL,1FH ;Point to status register OUT STCCOM ; so power-off glitches won't hurt POP AX  RET L STATUS: IN STAT AND AL,DAV RET L INP: IN STAT AND AL,DAV JZ INP IN DATA AND AL,7FH RET L OUTP: PUSH AX OUTLP: IN STAT AND AL,TBMT JZ OUTLP POP AX OUT DATA RET L PRINT: PUSH AX PRINLP: IN PRNSTAT AND AL,TBMT JZ PRINLP POP AX OUT PRNDATA RET L AUXIN: IN AUXSTAT AND AL,DAV JZ AUXIN IN AUXDATA RET L AUXOUT: PUSH AX AUXLP: IN AUXSTAT AND AL,TBMT JZ AUXLP POP AX OUT AUXDATA RET L ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * IF SCP+TARBELL+CROMEMCO WD1791: EQU SCP+TARBELLDD+CROMEMCO16FDC WD1771: EQU TARBELLSD+CROMEMCO4FDC IF WD1791 READCOM:EQU 80H WRITECOM:EQU 0A0H ENDIF IF WD1771 READCOM:EQU 88H WRITECOM:EQU 0A8H ENDIF IF SCP SMALLBIT:EQU 10H BACKBIT:EQU 04H DDENBIT:EQU 08H DONEBIT:EQU 01H DISK: EQU 0E0H DLYTIM: EQU 22 ENDIF IF TARBELL BACKBIT:EQU 40H DDENBIT:EQU 08H DONEBIT:EQU 80H DISK: EQU 78H DLYTIM: EQU 10 ; 24 usec delay after force interrupt ENDIF  IF CROMEMCO SMALLBIT:EQU 10H BACKBIT:EQU 0FDH ; Send this to port 4 to select back. DDENBIT:EQU 40H DONEBIT:EQU 01H DISK: EQU 30H DLYTIM: EQU 22 ; 52 usec delay after force interrupt ENDIF IF SMALLDS-1 SMALLDDSECT: EQU 8 ENDIF IF SMALLDS SMALLDDSECT: EQU 16 ENDIF IF LARGEDS-1 LARGEDDSECT: EQU 8 ENDIF IF LARGEDS LARGEDDSECT: EQU 16 ENDIF ; ; I/O system disk change function. ; AL = disk drive number. ; Return AH = -1 if disk is changed. ; AH = 0 if don't know. ; AH = 1 if not changed. ; DSKCHG: MOV AH,0 SEG CS CMP AL,[CURDRV] JNZ RETL IN DISK AND AL,20H ; Look at head load bit JZ RETL MOV AH,1 RETL: RET L READ: CALL SEEK ;Position head JC ERROR RDLP: PUSH CX CALL READSECT ;Perform sector read POP CX JC ERROR INC DH ;Next sector number LOOP RDLP ;Read each sector requested OR AL,AL RET L WRITE: CALL SEEK ;Position head JC ERROR WRTLP: PUSH CX CALL WRITESECT ;Perform sector write POP CX JC ERROR INC DH ;Bump sector counter LOOP WRTLP ;Write CX sectors OR AL,AL RET L ERROR: MOV BL,-1 SEG CS MOV [DI],BL MOV SI,ERRTAB GETCOD: INC BL SEG CS LODB TEST AH,AL JZ GETCOD MOV AL,BL SHL AL STC RET L ERRTAB: DB 40H ;Write protect error DB 80H ;Not ready error DB 8 ;CRC error DB 2 ;Seek error DB 10H ;Sector not found DB 20H ;Write fault DB 7 ;Data error SEEK: ; Inputs: ; AL = Drive number ; BX = Disk transfer address in DS ; CX = Number of sectors to transfer ; DX = Logical record number of transfer ; Function: ; Seeks to proper track. ; Outputs: ; AH = Drive select byte ; DL = Track number ; DH = Sector number ; SI = Disk transfer address in DS ; DI = pointer to drive's track counter in CS ; CX unchanged. MOV AH,AL SEG CS XCHG AL,[CURDRV] CMP AL,AH ;Changing drives? JZ SAMDRV ;If changing drives, unload head so the head load delay one-shot ;will fire again. Do it by seeking to same track the H bit reset. IN DISK+1 ;Get current track number OUT DISK+3 ;Make it the track to seek to MOV AL,10H ;Seek and unload head CALL DCOM MOV AL,AH ;Restore current drive number SAMDRV: MOV SI,BX ; Save transfer address CBW MOV BX,AX ; Prepare to index  on drive number IF CROMEMCO16FDC IN DISK+4 ; See if the motor is on. TEST AL,08H ENDIF SEG CS MOV AL,[BX+DRVTAB] ; Get drive-select byte. OUT DISK+4 ; Select drive IF CROMEMCO16FDC JNZ MOTORSON ; No delay if motors already on. PUSH AX PUSH CX MOV CX,43716 ; Loop count for 1 second. MOTORDELAY: ; (8 MHz, 16-bit memory). AAM ; 83 clocks. AAM  ; 83 clocks. LOOP MOTORDELAY ; 17 clocks. POP CX POP AX MOTORSON: ENDIF IF CROMEMCO OR AL,80H ; Set auto-wait bit ENDIF MOV AH,AL ; Save drive-select byte in AH. XCHG AX,DX ; AX = logical sector number. MOV DL,26 ; 26 sectors/track unless changed below IF SCP TEST DH,SMALLBIT ; Check if small disk. JZ BIGONE ; Jump if big disk. MOV DL,18 ; Assume 18 sectors on small track. TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,SMALLDDSECT ; Number of sectors on small DD track. JP HAVSECT BIGONE: TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,LARGEDDSECT ; Number of sectors on big DD track. ENDIF IF TARBELLDD ; Tarbell DD controller. TEST DH,DDENBIT ; Check for double-density. JZ HAVSECT MOV DL,LARGEDDSECT ; Number of sectors on DD track. ENDIF IF CROMEMCO4FDC TEST DH,SMALLBIT ; Check if small disk. JNZ HAVSECT ; Jump if not. MOV DL,18 ; 18 sectors on small disk track. ENDIF IF CROMEMCO16FDC TEST DH,SMALLBIT ; Check if small disk. JNZ BIGONE ; Jump if big disk. MOV DL,18 ; Assume 18 sectors on small track. TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,SMALLDDSECT ; Number of sectors on small DD track. JP HAVSECT BIGONE: TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,LARGEDDSECT ; Number of sectors on big DD track. ENDIF HAVSECT: DIV AL,DL ; AL = track, AH = sector. XCHG AX,DX ; AH has drive-select byte, DX = track & sector. INC DH ; Sectors start at one, not zero. SEG CS MOV BL,[BX+TRKPT] ; Get this drive's displacement into track table. ADD BX,TRKTAB ; BX now points to track counter for this drive. MOV DI,BX MOV AL,DL ; Move new track number into AL. SEG CS XCHG AL,[DI] ; Xchange current track with desired track OUT DISK+1 ; Inform controller chip of current track CMP AL,DL ; See if we're at the right track. JZ RET MOV BH,2 ; Seek retry count CMP AL,-1 ; Head position known? JNZ NOHOME ; If not, home head TRYSK: CALL HOME JC SEEKERR NOHOME: MOV AL,DL ; AL = new track number. OUT DISK+3 MOV AL,1CH+STPSPD ; Seek command. CALL MOVHEAD AND AL,98H ; Accept not ready, seek, & CRC error bits. JZ RET JS SEEKERR ; No retries if not ready DEC BH JNZ TRYSK SEEKERR: MOV AH,AL ; Put status in AH. TEST AL,80H ; See if it was a Not Ready error. STC JNZ RET ; Status is OK for Not Ready error. MOV AH,2 ; Everything else is seek error. RET SETUP: MOV BL,DH ; Move sector number to BL to play with IF SCP+CROMEMCO16FDC TEST AH,DDENBIT ; Check for double density. JZ CHECKSMALL ; Not DD, check size for SD. ENDIF IF TARBELLDD TEST AH,DDENBIT ; Check for double density. JZ CHECK26 ; Not DD. ENDIF IF WD1791 IF (SCP+TARBELL)*LARGEDS+SCP*SMALLDS MOV AL,AH ; Select front  side of disk. OUT DISK+4 ENDIF IF CROMEMCO*(LARGEDS+SMALLDS) MOV AL,0FFH ; Select front side of disk. OUT 04H ENDIF CMP BL,8 ; See if legal DD sector number. JBE PUTSEC ; Jump if ok. IF (LARGEDS-1)*((SMALLDS*(SCP+CROMEMCO))-1) JP STEP ; If only SS drives, we gotta step. ENDIF IF SCP*LARGEDS*(SMALLDS-1) TEST AH,SMALLBIT ; Check for 5.25 inch disk. JNZ STEP ; Jump if small because SMALLDS is off. ENDIF IF SCP*SMALLDS*(LARGEDS-1) TEST AH,SMALLBIT ; Check for 8 inch disk. JZ STEP ; Jump if large because LARGEDS is off. ENDIF IF CROMEMCO16FDC*LARGEDS*(SMALLDS-1) TEST AH,SMALLBIT ; Check for 5.25 inch disk. JZ STEP ; Jump if small because SMALLDS is off. ENDIF IF CROMEMCO16FDC*SMALLDS*(LARGEDS-1) TEST AH,SMALLBIT ; Check for 8 inch disk. JNZ STEP ; Jump if large because LARGEDS is off. ENDIF IF LARGEDS+SMALLDS*(SCP+CROMEMCO) SUB BL,8 ; Find true sector for back side. CMP BL,8 ; See if ok now. JA STEP ; Have to step if still too big. IF SCP+TARBELLDD MOV AL,AH ; Move drive select byte into AL. OR AL,BACKBIT ; Select back side. OUT DISK+4 ENDIF IF CROMEMCO16FDC MOV AL,BACKBIT ; Select back side. OUT 04H ENDIF JP PUTSEC ENDIF ENDIF IF SCP CHECKSMALL: TEST AH,SMALLBIT ; See if big disk. JZ CHECK26 ; Jump if big. ENDIF IF CROMEMCO CHECKSMALL: TEST AH,SMALLBIT ; See if big disk. JNZ CHECK26 ; Jump if big. ENDIF IF SCP+CROMEMCO CMP BL,18 ; See if legal small SD/SS sector.  JA STEP ; Jump if not. ENDIF CHECK26: CMP BL,26 ; See if legal large SD/SS sector. JBE PUTSEC ; Jump if ok. STEP: INC DL ; Increment track number. MOV AL,58H ; Step in with update. CALL DCOM SEG CS INC B,[DI] ; Increment the track pointer. MOV DH,1 ; After step, do first sector. MOV BL,DH ; Fix temporary sector number also. PUTSEC: MOV AL,BL ; Output sector number to controller. OUT DISK+2 DI ; Interrupts not allowed until I/O done IN DISK ; Get head load bit  NOT AL AND AL,20H ; Check head load status JZ RET MOV AL,4 RET READSECT: CALL SETUP MOV BL,10 RDAGN: OR AL,READCOM OUT DISK IF CROMEMCO MOV AL,AH ; Turn on auto-wait. OUT DISK+4 ENDIF MOV BP,SI RLOOP: IF SCP IN DISK+5 ; Wait for DRQ or INTRQ. ENDIF IF TARBELL+CROMEMCO IN DISK+4 ENDIF IF TARBELL SHL AL JNC RDONE ENDIF  IF SCP+CROMEMCO SHR AL JC RDONE ENDIF IN DISK+3 MOV [SI],AL INC SI JP RLOOP RDONE: EI ; Interrupts OK now CALL GETSTAT AND AL,9CH JZ FORCINT MOV SI,BP MOV BH,AL ;Save error status for report MOV AL,0 DEC BL JNZ RDAGN MOV AH,BH ; Put error status in AH. STC FORCINT: MOV AL,0D0H ;Force Interrupt command for type I status OUT DISK  MOV AL,DLYTIM INTDLY: DEC AL ;Does not affect carry JNZ INTDLY ;Minimum loop time (19 clocks)=2.375 usec RET WRITESECT: CALL SETUP MOV BL,10 WRTAGN: OR AL,WRITECOM OUT DISK IF CROMEMCO MOV AL,AH ; Turn on auto-wait. OUT DISK+4 ENDIF MOV BP,SI WRLOOP: IF SCP IN DISK+5 ; Wait for DRQ or INTRQ. ENDIF IF TARBELL+CROMEMCO IN DISK+4 ENDIF IF TARBELL SHL AL JNC WRDONE ENDIF IF SCP+CROMEMCO SHR AL JC WRDONE ENDIF LODB OUT DISK+3 JP  WRLOOP WRDONE: EI CALL GETSTAT AND AL,0FCH JZ FORCINT MOV SI,BP MOV BH,AL MOV AL,0 DEC BL JNZ WRTAGN MOV AH,BH ; Error status to AH. STC JP FORCINT IF SCP+(FASTSEEK-1)*TARBELL+CROMEMCO HOME: ENDIF IF FASTSEEK*SCP TEST AH,SMALLBIT ; Check for big disk. JZ RESTORE ; Big disks are PerSci. ENDIF IF FASTSEEK*CROMEMCO TEST AH,SMALLBIT ; Check for large disk. JNZ RESTORE ; Big disks are fast seek PerSci. ENDIF MOV BL,3 TRYHOM: MOV AL,0CH+STPSPD CALL DCOM AND AL,98H JZ RET JS HOMERR ; No retries if not ready MOV AL,58H+STPSPD ; Step in with update CALL DCOM DEC BL  JNZ TRYHOM HOMERR: STC RET IF SCP+(FASTSEEK-1)*TARBELL+CROMEMCO MOVHEAD: ENDIF IF CROMEMCO TEST AH,SMALLBIT ; Check for PerSci. JNZ FASTSK ENDIF DCOM: OUT DISK PUSH AX AAM ;Delay 10 microseconds POP AX GETSTAT: IN DISK+4 TEST AL,DONEBIT IF TARBELL JNZ GETSTAT ENDIF IF SCP+CROMEMCO JZ GETSTAT ENDIF IN DISK RET ; ; RESTORE for PerSci drives. ; Doesn't exist yet for Tarbell controllers. ; Cromemco 4FDC restore is used for 16FDC which isn't real efficient ; but it works. Some ambitious person could fix this. ; IF FASTSEEK*SCP RESTORE: MOV AL,AH ; Get drive-select byte. OR AL,80H ; Turn on restore. OUTB DISK+4 SKWAIT: INB DISK+4 ; Wait for seek complete. TEST AL,40H JZ SKWAIT MOV AL,AH ; Turn off restore. OUTB DISK+4 XOR AL,AL ; Tell 1793 we're on track 0. OUTB DISK+1 RET ENDIF IF FASTSEEK*TARBELL HOME: RESTORE: RET ENDIF IF FASTSEEK*CROMEMCO RESTORE: MOV AL,0C4H ;READ ADDRESS command to keep head loaded OUT DISK MOV AL,77H OUT 4 CHKRES: IN 4 AND AL,40H JZ RESDONE IN DISK+4 TEST AL,DONEBIT JZ CHKRES IN DISK JP RESTORE ;Reload head RESDONE: MOV AL,7FH OUT 4 CALL GETSTAT MOV AL,0 OUT DISK+1 ;Tell 1771 we're now on track 0 RET ENDIF ; ; Fast seek code for PerSci drives. ; Tarbell not installed yet. ; IF FASTSEEK*TARBELL  MOVHEAD: FASTSK: RET ENDIF IF FASTSEEK*CROMEMCO FASTSK: MOV AL,6FH OUT 4 MOV AL,18H CALL DCOM SKWAIT: IN 4 TEST AL,40H JNZ SKWAIT MOV AL,7FH OUT 4 MOV AL,0 RET ENDIF CURDRV: DS 1 ; ; Explanation of tables below. ;  ; DRVTAB is a table of bytes which are sent to the disk controller as ; drive-select bytes to choose which physical drive is selected for ; each logical drive. It also selects whether the disk is 5.25-inch or ; 8-inch, single-density or double-density, to use side 0 or side 1 for ; single-sided operation. (Note: always select side 0 in the drive- ; select byte if double-sided double-density operation is desired). ; There should be one entry in the DRVTAB table for each logical drive. ; Exactly which bits in the drive-select byte do what depends on which ; disk controller is used. ; ; TRKTAB is a table of bytes used to store which track the read/write ; head of each drive is on. Each physical drive should have its own ; entry in TRKTAB. ; ; TRKPT is a table of bytes which indicates which TRKTAB entry each ; logical drive should use. Since each physical drive may be used for ; more than one logical drive, more than one entry in TRKPT may point ; to the same entry in TRKTAB. Drives such a s PerSci 277s which use ; the same head positioner for more than one drive should share entrys ; in TRKTAB. ; ; INITTAB is the initialization table for 86-DOS as described in the ; 86-DOS Programer's Manual under "Customizing the I/O System." ; IF SCP*COMBIN*FASTSEEK DRVTAB: DB 00H,01H,10H,08H,09H,18H TRKPT: DB 0,0,1,0,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW SSDRIVE DW LDDRIVE DW LDDRIVE DW SDDRIVE DW 0 DW 30 ENDIF IF SCP*LARGEDS*(FASTSEEK-1) ; Drive A is drive 0, side 0, single-density. ; Drive B is drive 0, side 1, single-density. ; Drive C is drive 1, side 0, single-density. ; Drive D is drive 1, side 1, single-density. ; Drive E is drive 0, both sides, double density. ; Drive F is drive 1, both sides, double density. DRVTAB: DB 00H,04H,01H,05H,08H,09H TRKPT: DB 0,0,1,1,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW 0 ; Reserved buffer space. DW 30 ; Reserved stack space. ENDIF IF TARBELLDD*(LARGEDS-1) ;Drive A is drive 0, single density ;Drive B is drive 1, single density ;Drives C to F are drive 0 to 3, double density DRVTAB: DB 0,10H,8,18H,28H,38H TRKPT: DB 0,1,0,1,2,3 TRKTAB: DB -1,-1,-1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW 0 DW 30 ENDIF IF TARBELLDD*LARGEDS ;Drive A is drive 0, side 0, single density ;Drive B is drive 0, side 1, single density ;Drive C is drive 1, side 0, single density ;Drive D is drive 1, side 1, single density ;Drive E is drive 0, both sides, double density ;Drive F is drive 1, both sides, double density DRVTAB: DB 0,40H,10H,50H,8H,18H TRKPT: DB 0,0,1,1,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW 0 DW 30 ENDIF IF TARBELLSD DRVTAB: DB 0F2H,0E2H,0D2H,0C0H  TRKPT: DB 0,1,2,3 TRKTAB: DB -1,-1,-1,-1 INITTAB:DB 4 DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW 0 DW 30 ENDIF ; Cromemco drive select byte is derived as follows: ; Bit 7 = 0 ; Bit 6 = 1 if double density (if 16FDC) ; Bit 5 = 1 (motor on) ; Bit 4 = 0 for 5", 1 for 8" drives ; Bit 3 = 1 for drive 3 ; Bit 2 = 1 for drive 2 ; Bit 1 = 1 for drive 1 ; Bit 0 = 1 for drive 0 IF CROMEMCO4FDC*LARGE ; Table for four large drives DRVTAB: DB 31H,32H,34H,38H TRKPT: DB 0,0,1,1 TRKTAB: DB -1,-1 INITTAB:DB 4 ;Number of drives DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW 0 DW 30 ENDIF IF CROMEMCO4FDC*COMBIN ; Table for two large drives and one small one DRVTAB: DB 31H,32H,24H TRKPT: DB 0,0,1 TRKTAB: DB -1,-1 INITTAB:DB 3 ;Number of drives DW LSDRIVE DW LSDRIVE DW SSDRIVE DW 0 DW 30 ENDIF IF CROMEMCO4FDC*SMALL ; Table for 3 small drives DRVTAB: DB 21H,22H,24H TRKPT: DB 0,1,2 TRKTAB: DB -1,-1,-1 INITTAB:DB 3 DW SSDRIVE DW SSDRIVE DW SSDRIVE DW 0 DW 30 ENDIF IF CUSTOM ; Table for 2 large drives without fast seek and a Cromemco 4FDC. DRVTAB: DB 31H,32H TRKPT: DB 0,1 TRKTAB: DB -1,-1 INITTAB:DB 2 DW LCDRIVE DW LSDRIVE DW 0 DW 30 ENDIF IF CROMEMCO16FDC*SMALL ; Table for three small drives. ; A, B, & C are single density, D, E, & F are double density. DRVTAB: DB 21H,22H,24H,61H,62H,64H   TRKPT: DB 0,1,2,0,1,2 TRKTAB: DB -1,-1,-1 INITTAB:DB 6 DW SSDRIVE DW SSDRIVE DW SSDRIVE DW SDDRIVE DW SDDRIVE DW SDDRIVE DW 0 DW 30 ENDIF IF CROMEMCO16FDC*COMBIN ; Table for 2 large drives (a PerSci 277 or 299), and one small. ; Drives A & B are the PerSci single density, ; C is the small drive single density, ; D & E are the PerSci double density, ; and F is the small drive double density. DRVTAB: DB 31H,32H,24H,71H,72H,64H TRKPT: DB 0,0,1,0,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW SSDRIVE DW LDDRIVE DW LDDRIVE DW SDDRIVE DW 0 DW 30 ENDIF IF CROMEMCO16FDC*LARGE ; Table for four large drives (2 PerSci 277s or 299s). ; Drives A - B are single density, C - F are double density. DRVTAB: DB 31H,32H,71H,72H,74H,78H TRKPT: DB 0,0,0,0,1,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW 0 DW 30 ENDIF IF SMALL+COMBIN SSDRIVE: DW 128 ; Sector size in bytes. DB 2  ; Sector per allocation unit. DW 54 ; Reserved sectors. DB 2 ; Number of allocation tables. DW 64 ; Number of directory entrys. DW 720 ; Number of sectors on the disk. IF SMALLDS-1 SDDRIVE: ; This is the IBM Personal Computer DW 512 ; disk format. DB 1 DW 1 DB 2 DW 64 DW 320 ENDIF IF SMALLDS SDDRIVE: DW 512 DB 2 DW 1 DB 2 DW 64 ; 64 directory entrys for small DDDS? DW 640 ENDIF ENDIF ; End of small drive DPTs. IF COMBIN+LARGE LSDRIVE: DW 128 DB 4 DW 52 DB 2 DW 64 DW 2002 IF LARGEDS-1 LDDRIVE: DW 1024 DB 1 DW 1 DB 2 DW 96 DW 616 ENDIF  IF LARGEDS LDDRIVE: DW 1024 DB 1 DW 1 DB 2 DW 128 DW 1232 ENDIF ENDIF ; End of large drive DPTs. ENDIF ; End of 1771/1793 disk drivers. ; * * * * * * * * * * * * * * * * * * * * * * * IF NORTHSTARSD ; North Star disk controller addresses. ; DSKSEG: EQU 0FE80H ; `F' is for extended address modification. WRTADR: EQU 200H CMMND: EQU 300H DRVSEL: EQU 1H WRTSEC: EQU 4H STPOFF: EQU 8H STPON: EQU 9H NOP: EQU 10H RSETSF: EQU 14H STPOUT: EQU 1CH STPIN: EQU 1DH BSTAT: EQU 20H RDBYTE: EQU 40H MOTOR: EQU 80H ; ; Status bits. ; TK0: EQU 01H ; Track 0 bit. WP: EQU 02H ; Write protect bit. BDY: EQU 04H ; Data body (sync byte) found. WRT: EQU 08H ; Write bytes status flag. MO: EQU 10H ; Motor on. SF: EQU 80H ; Indicates sector hole was detected. ; ; Delay times in sectors for various disk functions. ; MOTORD: EQU 31 ; Motor up-to-speed time (1 second). HEADD: EQU 14 ; Head-load settle time. Actually, the head ; doesn't require this much time to settle, ; but this much time is required to ; synchronize the sector counter. STEPD: EQU 2 ; Step time. One or two only. ; 1 -> 20mS, 2 -> 40mS. ; ; Various numbers of things. ; NSECT: EQU 10 ; 10 North Star sectors per track. NTRACK: EQU 35 ; 35 tracks on standard SA-400 drive. ERRLIM: EQU 10 ; Number of soft errors. ; ; READ and WRITE functions.  ; AL = drive number. ; CX = Number of sectors to transfer. ; DX = Logical record number. ; DS:BX = Transfer address. ; READ: MOV AH,1 ; AH = 1 to read. JP READWRITE WRITE: MOV AH,0 ; AH = 0 to write. READWRITE: CMP DX,350 ; See if too l arge a sector number is requested JB SECTOROK ; Jump if OK. MOV AL,0CH ; Error type C, "data error". STC ; Set CY flag to indicate error. RET L ; Quit immediatly. SECTOROK: MOV SI,BX ; Transfer address to SI & DI. MOV DI,BX UP ; Set direction flag for autoincrement. PUSH ES ; Store extra segment. MOV BX,DS ; Put data segment in extra segment. MOV ES,BX PUSH DS ; Save data segment. MOV BX,DSKSEG ; DS is North Star controller segment. MOV DS,BX PUSH AX ; Store read/write flag. CBW ; Drive number is sixteen bits. MOV BX,AX ; Put in BX. MOV AX,DX ; Compute track & sector. MOV DL,NSECT ; Ten sectors/track. DIV AL,DL ; AL = track number, AH = sector number. MOV CH,AH ; Sector number to CH. PUSH CX ; Save sector number & number of sectors. MOV DH,AL ; Put track number in DH. SEG CS ; TRACKTAB is in the code segment. MOV AH,[BX+TRACKTAB] ; Find out what the current track is. SEG CS MOV [BX+TRACKTAB],DH ; Update TRACKTAB. MOV BP,CMMND+MOTOR+STPIN ; Assume step direction is in. MOV CL,DH ; Put track number in CL. SUB CL,AH ; Calculate how many steps required. JAE DIRECTION ; Direction is correct if >= 0. DEC BP ; Direction is out (STPOUT = STPIN-1). NEG CL ; Make number of steps positive. DIRECTION: IF STEPD-1 ; Multiply number of steps by two if step delay SAL CL ; is 40mS per step. ENDIF TEST B,[CMMND+MOTOR+NOP],MO ; Turn motors on & check MO status. JZ MOTORS ; If motors were off, wait for them to start. SEG CS ; OLDDRIVE is in the code segment. CMP BL,[OLDDRIVE] ; See if the correct drive is selected. JNZ SELECT ; If wrong drive is selected, select right one. JP SEEK ; Motors on, drive selected, go and step. MOTORS: MOV DL,MOTORD ; Wait for motors to come up to speed. CALL WSECTOR SELECT: CALL ONESECT ; Wait for write gate to go off. MOV AL,[BX+CMMND+MOTOR+DRVSEL] ; Select new drive. SEG CS ; OLDDRIVE is in code segment. MOV [OLDDRIVE],BL ; Update OLDDRIVE. MOV DL,HEADD-1 ; Full head load delay (-1 because waiting for ; the correct sector delays at least one more) MOV AL,CL ; See if we've ever used the drive before. IF STEPD-1 ; Compute the actual number of steps if 40mS SAR AL ; step delay is used. ENDIF CMP AL,NTRACK ; If the number of steps is >= NTRACK, we can't JAE HEADDELAY ; count on step time for head load delay. SUB DL,CL ; Subtract stepping time. JB SEEK ; Don't wait if we'll step long enough for the ; head to settle & the sector counter to sync. HEADDELAY: CALL WSECTOR SEEK: IF STEPD-1 ; Convert back to the actual number of steps SAR CL ; rather than step time if the step time ENDIF ; is 40mS per step. XOR CH,CH ; CX = CL. JCXZ SEEKCOMPLETE ; Jump if we're already there. SEG DS ; BP normally uses stack segment. MOV AL,[BP] ; Set the step direction. CALL ONESECT ; Wait for the write gate to turn off. ; ; Step routine. Step direction has already been given to the disk ; controller. DH has destination track number.  ; CX has number of sectors to step, >= 1. ; If track zero is ever reached, the head position is recalibrated using DH. ; STEP: MOV AL,[CMMND+MOTOR+NOP] ; Get `A' status. ROR AL ; Track 0 bit to CF. JNC STEPOK ; Recalibrate if track zero. MOV  CL,DH ; Track # to step count. JCXZ SEEKCOMPLETE ; If destinination = 0, we're there. MOV AL,[CMMND+MOTOR+STPIN] ; Set direction. STEPOK: MOV AL,[CMMND+MOTOR+STPON] AAM ; Waste time for > 10 uS. MOV AL,[CMMND+MOTOR+STPOFF] MOV DL,STEPD ; Step time (sectors). CALL WSECTOR LOOP STEP ; Loop till we get there. SEEKCOMPLETE: POP CX ; Restore sector number & number of sectors. MOV BP,BX ; Put drive number in BP. SECTORLOOP: MOV DH,ERRLIM ; Soft error limit. ERRORRETRY: DI ; Interrupts illegal till after read, write, WAITSECTOR: ; or error. CALL ONESECT ; Wait for next sector to come by. MOV AL,[CMMND+MOTOR+BSTAT+NOP] ; Get `B' status. AND AL,0FH ; Mask to sector number. CMP AL,CH JNE WAITSECTOR ; Wait till the one we want comes by. POP AX ; Get function. PUSH AX ; Back on the stack for next time. AND AH,AH ; AH = 1 -> read, AH = 0 -> write. JZ WRITESECTOR ; Jump if write. ; READSECTOR: MOV SI,CMMND+MOTOR+RDBYTE+NOP PUSH CX ; Save sector number and number of sectors. MOV CX,352 ; Time limit for sync byte. 352 passes through ; the loop @ 35 clocks/pass = 24 byte times. RSYNCLP: TEST B,[CMMND+MOTOR+NOP],BDY LOOPZ RSYNCLP ; Test for sync byte. Loop till sync or timeout JNZ READSECT ; Found sync byte, read data bytes. MOV AL,8 ; Error number 8, "Record Not Found". JP ERROR READSECT: MOV CX,256 ; Byte count. MOV DL,CL ; CRC = 0. READLOOP: AAD ; Waste time >= 7.5 uS. MOV AL,[SI] ; Read a byte. STOB ; Store byte.  XOR DL,AL ; Compute CRC. ROL DL LOOP READLOOP ; Loop for 256 bytes. AAD ; Waste time >= 7.5 uS. MOV AL,[SI] ; Get CRC from disk. CMP AL,DL ; Same as computed? JE NEXTSECTOR ; Jump if sucessful read. SUB DI,256 ; Back-up the index for retry. MOV AL,4 ; Error number 4, "CRC Error". ERROR: EI ; Interrupts OK now. POP CX ; Get sector number & number of sectors. DEC DH ; Decrement error count. JNZ ERRORRETRY ; Wait for the sector to come by again. POP BX ; Pop junk off the stack. POP DS ; Pop segment registers. POP ES XOR CH,CH ; CX is number of sectors left to read. STC ; Set CY flag to indicate error. RET L ; Return. ; WRITESECTOR: TEST B,[CMMND+MOTOR+NOP],WP JZ NOTPROT ; Jump if not protected. EI  ; Interrupts OK now. POP AX ; Pop junk off the stack. POP DS POP ES XOR CH,CH ; CX = number of sectors left to write. MOV AL,CH ; AL = 0 to indicate write protect. STC ; Set CY flag to indicate error. RET L NOTPROT: PUSH CX ; Save sector number and number of sectors. MOV AL,[CMMND+MOTOR+WRTSEC] WWRT: TEST B,[CMMND+MOTOR+NOP],WRT JZ WWRT ; Loop till WRT bit goes high. MOV CX,15 ; Number of zeros to write. MOV BX,WRTADR ; Address to write zeros. WRTZERO: MOV AL,[BX] ; Write a zero. AAD ; Waste time for >= 7.5 uS. LOOP WRTZERO ; Write 15 of them. MOV BL,0FBH ; Sync byte. MOV AL,[BX] ; Write sync byte. AAD ; Waste time for >= 7.5 uS. MOV CX,256 ; Byte count. MOV DL,CL ; CRC = 0. WRTBYTE: SEG ES  ; Data is in extra segment. LODB ; Get write data. MOV BL,AL ; Data to BL to write. MOV AL,[BX] ; Write it. AAD ; Waste time for >= 7.5 uS. XOR DL,BL ; Compute CRC. ROL DL LOOP WRTBYTE ; Write 256 bytes. MOV BL,DL ; Write CRC byte . MOV AL,[BX] ; NEXTSECTOR: EI ; Interrupts OK now. POP CX ; Get sector count. DEC CL ; Decrement sector count. JZ OKRETURN ; Return if done. INC CH ; Increment sector number. CMP CH,10 ; Compare with number of sectors on track. JAE NEEDSTEP JMP SECTORLOOP ; Read another sector from same track. NEEDSTEP: MOV CH,0 ; Reset sector number. CALL ONESECT ; Wait for write gate to go off. MOV AL,[CMMND+MOTOR+STPIN] MOV AL,[CMMND+MOTOR+STPON] AAM ; Wait > 10 uS for step pulse width. MOV AL,[CMMND+MOTOR+STPOFF] SEG CS ; BP normally uses stack segment. INC B,[BP+TRACKTAB] ; Increment the track table. ; We don't have to wait for STEPD because ; waiting for the write gate to go off caused ; us to blow the sector and we have to wait ; a whole revolution anyway. JMP SECTORLOOP ; Read a sector from the new track. OKRETURN: POP AX ; Get function, AH=0 -> write, AH=1 -> read. POP DS ; Get original data & extra segments. POP ES CLC ; No errors. RET L ; ; Wait for sector routine. ONESECT waits for the next sector. ; WSECTOR waits the number of sectors given by DL. ; ONESECT: MOV DL,1 ; Wait for next sector. WSECTOR: MOV AL,[CMMND+MOTOR+RSETSF] SECTLOOP: MOV AL,[CMMND+MOTOR+NOP]  TEST AL,SF ; Check sector flag. JZ SECTLOOP ; Loop till new sector. DEC DL ; Decrement sector count. JNZ WSECTOR ; Loop till zero. RET ; DSKCHG: MOV AH,0 ; AH = 0 in case we don't know. SEG CS CMP AL,[OLDDRIVE] ; See if that's the last drive used. JNE RETL ; Return if not. PUSH DS ; See if the motors are still on. PUSH BX MOV BX,DSKSEG MOV DS,BX TEST B,[CMMND+NOP],MO POP BX POP DS JZ RETL ; Motors off, disk could be changed. MOV AH,1 ; If motors on, assume disk not changed. RETL: RET L ; ; Disk initialization tables. ; INITTAB: DB 3 ; Three drives. DW DPT,DPT,DPT ; Address of disk parameter tables. DW 0 ; Minimum buffer space DW 30 ; Stack space ; DPT: DW 256 ; Sector size. DB 1 ; One sector per allocation unit. DW 30 ; Number of sectors allocated to system. DB 2 ; Two allocation tables. DW 64 ; Number of directory entries. DW 350 ; Number of sectors on the disk. ; ; Storage locations for the disk drivers. ; OLDDRIVE: DB 0 ; Old drive will be number 0 after boot. TRACKTAB: DB 2 ; Drive 0 will be on track 2 after boot. DB NTRACK-1+NTRACK-1+24 ; Number of steps to restore the head DB NTRACK-1+NTRACK-1+24 ; if never used before. ENDIF ; End North Star disk drivers DOSSEG: EQU ($+15)/16+40H ; Compute segment to use for 86-DOS.  ; Address to write zeros. WRTZERO: MOV AL,[BX] ;; Seattle Computer Products 8086 Monitor version 1.5 4/24/80 ; by Tim Paterson ; This software is not copyrighted. ;To select a disk boot, set one of the following equates ;to 1, the rest to 0. CROMEMCO4FDC: EQU 1 ;1 for 4FDC, 0 for others N ORTHSTARSD: EQU 0 ;North Star single density? TARBELL: EQU 0 ;Tarbell (single or double)? OTHER: EQU 0 ;User-defined disk PUTBASE:EQU 100H LOAD: EQU 200H ORG 7F0H PUT PUTBASE+7F0H JMP 0,0FF80H ;Power-on jump to monitor ;Baud Rate Table. The 9513 divides 2MHz by these values. ;They are for 9600, 1200, 300, 150, 110 baud BAUD: DW 13,104,416,832,1144 ORG 100H ;RAM area base address ;System Equates BASE: EQU 0F0H ;CPU Support base port address STAT: EQU BASE+7 ;UART status port DATA: EQU BASE+6 ;UART data port DAV: EQU 2 ;UART data available bit TBMT: EQU 1 ;UART transmitter ready bit BUFLEN: EQU 80 ;Maximum length of line input buffer BPMAX: EQU 10 ;Maximum number of breakpoints BPLEN: EQU BPMAX+BPMAX ;Length of breakpoint table REGTABLEN:EQU 14 ;Number of registers SEGDIF: EQU 800H ;-0FF800H (ROM address) PROMPT: EQU ">" CAN: EQU "@"  ;RAM area. BRKCNT: DS 2 ;Number of breakpoints TCOUNT: DS 2 ;Number of steps to trace BPTAB: DS BPLEN ;Breakpoint table LINEBUF:DS BUFLEN+1 ;Line input buffer ALIGN DS 50 ;Working stack area STACK: ;Register save area AXSAVE: DS 2 BXSAVE: DS 2 CXSAVE: DS 2 DXSAVE: DS 2 SPSAVE: DS 2 BPSAVE: DS 2 SISAVE: DS 2 DISAVE: DS 2 DSSAVE: DS 2 ESSAVE: DS 2 RSTACK: ;Stack set here so registers can be saved by pushing SSSAVE: DS 2 CSSAVE: DS 2 IPSAVE: DS 2 FSAVE: DS 2 ;Start of Monitor code ORG 0 PUT PUTBASE ;One-time initialization UP XOR AX,AX MOV SS,AX MOV DS,AX MOV ES,AX MOV DI,AXSAVE MOV CX,14 REP STOW ;Set register images to zero OR B,[FSAVE+1],2 ;Enable interrupts MOV CL,4 MOV AL,40H MOV DI,DSSAVE REP STOW ;Set segment reg. images to 40H MOV B [SPSAVE+1],0CH ;Set user stack to 400H+0C00H MOV SP,STACK  ;Prepare 9513 MOV AL,17H OUT BASE+5 ;Select Master Mode register MOV AL,0F3H OUT BASE+4 ;Low byte of Master Mode MOV AX,584H ;Output 84H to BASE+4 OUTW BASE+4 ;and 05H to BASE+5 ;Master Mode now set to 84F3H: ; Scaler set to BCD division ; Enable data pointer increment ; 8-bit data bus ; FOUT=100Hz, dividing F5 by 4 (F5=4MHz/10000) ; Both alarm comparators disabled ; Time-of-day enabled ;Counter 5 selected ;Initialize loop. Ports BASE through BASE+7 are initialized ;from table. Each table entry has number of bytes followed by ;data. MOV SI,INITTABLE ;Initialization table MOV DX,BASE ;DX has (variable) port no. INITPORT: SEG CS LODB ;Get byte count MOV CL,AL JCXZ NEXTPORT ;No init. for some ports INITBYTE: SEG CS LODB ;Get init. data OUT DX ;Send to port LOOP INITBYTE ;As many bytes as required NEXTPORT: INC DX ;Prepare for next port CMP DL,BASE+8 ;Check against limit JNZ INITPORT ;Initialization complete except for determining baud rate. ;Both 8259As are ready to accept interrupts, the 9513 is ;providing 19.2k baud X 16 to the 8251A which is set for ;16X clock and one stop bit. CALL CHECKB ;Check for correct baud rate ;CHECKB does not return if baud rate is correct ;Intial baud rate (19.2k) was wrong, so run auto-baud routine INITBAUD: MOV SI,BAUD ;First set up 9513 for slower baud rates (<=9600).  ;Counter 5 mode register has already been selected. MOV AX,0E823H ;Output 23H to BASE+4 OUTW BASE+4 ;and 0E8H to BASE+5 ; 23H to BASE+4 sets lower half of Counter 5 mode register. ;Reload from Load, count down repetively in binary, ;toggle output. ;0E8H to BASE+5 disables data pointer sequencing MOV AL,0DH OUT BASE+5 ;Select Counter 5 load reg. INITB: SEG CS LODW ;Get divisor OUT BASE+4 ;Output low byte MOV AL,AH OUT BASE+4 ;Output high byte CALL CHECKB ;Check if baud rate correct JP INITB ;Try next rate if not CHECKB: CALL IN ;First byte could be messed up CALL IN ;Get carriage return CMP AL,13 ;Correct? JZ MONITOR ;Don't return if correct RET ;Didn't get it yet ;Initialization complete, including baud rate. MONITOR: ; Do auto boot if sense switch 0 is on. MOV DI,LINEBUF MOV B,[DI],13 ;No breakpoints after boot IN BASE+0FH ;Sense switch port TEST AL,1 JZ DOMON JMP BOOT DOMON: MOV SI,HEADER CALL PRINTMES COMMAND: ;Re-establish initial conditions UP XOR AX,AX MOV DS,AX MOV ES,AX MOV SP,STACK MOV [64H],INT ;Set UART interrupt vector MOV [66H],CS MOV AL,PROMPT CALL OUT CALL INBUF ;Get command line ;From now and throughout command line processing, DI points ;to next character in command line to be processed. CALL SCANB ;Scan off leading blanks JZ COMMAND ;Null command? MOV AL,[DI] ;AL=first non-blank character ;Prepare command letter for table lookup SUB AL,"B" ;Low end range check JC ERR1 CMP AL,"T"+1-"B" ;Upper end range check JNC ERR1 INC DI SHL AL ;Times two CBW ;Now a 16-bit quantity XCHG BX,AX ;In BX we can address with it SEG CS CALL [BX+COMTAB] ;Execute command JP COMMAND ;Get next command ERR1: JMP ERROR ;Get input line INBUF: MOV DI,LINEBUF ;Next empty buffer location XOR CX,CX ;Character count GETCH: CALL IN ;Get input character CMP AL,20H ;Check for control characters JC CONTROL CMP AL,7FH ;RUBOUT is a backspace JZ BACKSP CALL OUT ;Echo character CMP AL,CAN ;Cancel line? JZ KILL STOB ;Put in input buffer INC CX ;Bump character count CMP CX,BUFLEN ;Buffer full? JBE GETCH ;Drop in to backspace if full BACKSP: JCXZ GETCH ;Can't backspace over nothing DEC DI ;Drop pointer DEC CX ;and character count CALL BACKUP ;Send physical backspace JP GETCH ;Get next char. CONTROL: CMP AL,8 ;Check for backspace JZ BACKSP CMP AL,13 ;Check for carriage return JNZ GETCH ;Ignore all other control char. STOB ;Put the car. ret. in buffer MOV DI,LINEBUF ;Set up DI for command processing ;Output CR/LF sequence CRLF: MOV AL,13 CALL OUT MOV AL,10 JP OUT ;Cancel input line KILL: CALL CRLF JP COMMAND ;Character input routine IN: DI ;Poll, don't interrupt INB STAT TEST AL,DAV JZ IN ;Loop until ready INB DATA AND AL,7FH ;Only 7 bits EI ;Interrupts OK now RET ;Physical backspace - blank, backspace, blank BACKUP: MOV SI,BACMES ;Print ASCII message. Last char has bit 7 set PRINTMES: SEG CS LODB ;Get char to print CALL OUT SHL AL ;High bit set? JNC PRINTMES RET ;Scan for parameters of a command SCANP: CALL SCANB ;Get first non-blank CMP B,[DI],"," ;One comma between params OK JNE EOLCHK ;If not comma, we found param INC DI ;Skip over comma ;Scan command line for next non-blank character SCANB: MOV  AL," " PUSH CX ;Don't disturb CX MOV CL,-1 ;but scan as many as necessary REPE SCAB DEC DI ;Back up to first non-blank POP CX EOLCHK: CMP B,[DI],13 RET ;Print the 5-digit hex address of SI and DS OUTSI: MOV DX,DS ;Put DS where we can work with it MOV AH,0 ;Will become high bits of DS CALL SHIFT4 ;Shift DS four bits ADD DX,SI ;Compute absolute address JP OUTADD ;Finish below ;Print 5-digit hex address of DI and ES ;Same as OUTSI above OUTDI: MOV DX,ES MOV AH,0 CALL SHIFT4 ADD DX,DI ;Finish OUTSI here too OUTADD: ADC AH,0 ;Add in carry to high bits CALL HIDIG ;Output hex value in AH ;Print out 16-bit value in DX in hex OUT16: MOV AL,DH ;High-order byte first CALL HEX MOV AL,DL ;Then low-order byte ;Output byte in AL as two hex digits HEX: MOV AH,AL ;Save for second digit ;Shift high digit into low 4 bits PUSH CX MOV CL,4 SHR AL,CL POP CX CALL DIGIT ;Output first digit HIDIG: MOV AL,AH ;Now do digit saved in AH DIGIT: AND AL,0FH ;Mask to 4 bits ;Trick 6-byte hex conversion works on 8086 too. ADD AL,90H DAA ADC AL,40H DAA  ;Console output of character in AL OUT: PUSH AX ;Character to output on stack OUT1: INB STAT AND AL,TBMT JZ OUT1 ;Wait until ready POP AX OUTB DATA RET ;Output one space BLANK: MOV AL," " JP OUT ;Output the number of blanks in CX TAB: CALL BLANK LOOP TAB RET ;Command Table. Command letter indexes into table to get ;address of command. PERR prints error for no such command. COMTAB: DW BOOT ;B DW PERR ;C DW DUMP ;D DW ENTER ;E DW FILL ;F DW GO ;G DW PERR ;H DW INPUT ;I DW PERR ;J DW PERR ;K DW PERR ;L DW MOVE ;M DW PERR ;N DW OUTPUT ;O DW PERR ;P DW PERR ;Q DW REG ;R DW SEARCH ;S DW TRACE ;T ;Given 20-bit address in AH:DX, breaks it down to a segment ;number in AX and a displacement in DX. Displacement is ;always zero except for least significant 4 bits. GETSEG: MOV AL,DL AND AL,0FH ;AL has least significant 4 bits CALL SHIFT4 ;4-bit left shift of AH:DX MOV DL,AL ;Restore lowest 4 bits MOV AL,DH ;Low byte of segment number XOR DH,DH ;Zero high byte of displacement RET ;Shift AH:DX left 4 bits SHIFT4: SHL DX RCL AH ;1 SHL DX RCL AH ;2 SHL DX RCL AH ;3 SHL DX RCL AH ;4 RET2: RET ;RANGE - Looks for parameters defining an address range. ;The first parameter is a hex number of 5 or less digits ;which specifies the starting address. The second parameter ;may specify the ending address, or it may be preceded by ;"L" and specify a length (4 digits max), or it may be ;omitted and a length of 128 bytes is assumed. Returns with ;segment no. in AX and displacement (0-F) in DX. RANGE: MOV CX,5 ;5 digits max CALL GETHEX ;Get hex number PUSH AX ;Save high 4 bits PUSH DX ;Save low 16 bits CALL SCANP ;Get to next parameter CMP B,[DI],"L" ;Length indicator? JE GETLEN MOV DX,128 ;Default length CALL HEXIN ;Second parameter present? JC RNGRET ;If not, use default MOV CX,5 ;5 hex digits CALL GETHEX ;Get ending address MOV CX,DX ;Low 16 bits of ending addr. POP DX ;Low 16 bits of starting addr. POP BX ;BH=hi 4 bits of start addr. SUB CX,DX ;Compute   range SBB AH,BH ;Finish 20-bit subtract JNZ RNGERR ;Range must be less than 64K XCHG AX,BX ;AH=starting, BH=ending hi 4 bits INC CX ;Range must include ending location JP RNGCHK ;Finish range testing and return GETLEN: INC DI ;Skip over "L" to length MOV CX,4 ;Length may have 4 digits CALL GETHEX ;Get the range RNGRET: MOV CX,DX ;Length POP DX ;Low 16 bits of starting addr. POP AX ;AH=hi 4 bits of starting addr. ;RNGCHK verifies that the range lies entirely within one segment. ;CX=0 means count=10000H. Range is within one segment only if ;adding the low 4 bits of the starting address to the count is ;<=10000H, because segments can start only on 16-byte boundaries. RNGCHK: MOV BX,DX ;Low 16 bits of start addr.  AND BX,0FH ;Low 4 bits of starting addr. JCXZ MAXRNG ;If count=10000H then BX must be 0 ADD BX,CX ;Must be <=10000H JNC GETSEG ;OK if strictly < MAXRNG: ;If here because of JCXZ MAXRNG, we are testing if low 4 bits ;(in BX) are zero. If we dropped straight in, we are testing ;for BX+CX=10000H (=0). Either way, zero flag set means ;withing range. JZ GETSEG RNGERR: MOV AX,4700H+"R" ;RG ERROR JMP ERR ;Dump an area of memory in both hex and ASCII DUMP: CALL RANGE ;Get range to dump PUSH AX ;Save segment CALL GETEOL ;Check for errors POP DS ;Set segment MOV SI,DX ;SI has displacement in segment ROW: CALL OUTSI ;Print address at start of line PUSH SI ;Save address for ASCII dump BYTE: CALL BLANK ;Space between bytes BYTE1: LODB ;Get byte to dump CALL HEX ;and display it POP DX ;DX has start addr. for ASCII dump DEC CX ;Drop loop count JZ ASCII ;If through do ASCII dump MOV AX,SI TEST AL,0FH ;On 16-byte boundary? JZ ENDROW PUSH DX ;Didn't need ASCII addr. yet TEST AL,7 ;On 8-byte boundary? JNZ BYTE MOV AL,"-" ;Mark every 8 bytes CALL OUT JP BYTE1 ENDROW: CALL ASCII ;Show it in ASCII JP ROW ;Loop until count is zero ASCII: PUSH CX ;Save byte count MOV AX,SI ;Current dump address MOV SI,DX ;ASCII dump address SUB AX,DX ;AX=length of ASCII dump ;Compute tab length. ASCII dump always appears on right side ;screen regardless of how many bytes were dumped. Figure 3 ;characters for each byte dumped and subtract from 51, which ;allows a minimum of 3 blanks after the last byte dumped. MOV BX,AX SHL AX ;Length times 2 ADD AX,BX ;Length times 3 MOV CX,51 SUB CX,AX ;Amount to tab in CX CALL TAB MOV CX,BX ;ASCII dump length back in CX ASCDMP: LODB ;Get ASCII byte to dump AND AL,7FH ;ASCII uses 7 bits CMP AL,7FH ;Don't try to print RUBOUT JZ NOPRT CMP AL," "  ;Check for control characters JNC PRIN NOPRT: MOV AL,"." ;If unprintable character PRIN: CALL OUT ;Print ASCII character LOOP ASCDMP ;CX times POP CX ;Restore overall dump length JMP CRLF ;Print CR/LF and return ;Block move one area of memory to another. Overlapping moves ;are performed correctly, i.e., so that a source byte is not ;overwritten until after it has been moved. MOVE: CALL RANGE ;Get range of source area PUSH CX ;Save length PUSH AX ;Save segment MOV SI,DX ;Set source displacement MOV CX,5 ;Allow 5 digits CALL GETHEX ;in destination address CALL GETEOL ;Check for errors !  CALL GETSEG ;Convert dest. to seg/disp MOV DI,DX ;Set dest. displacement POP BX ;Source segment MOV DS,BX MOV ES,AX  ;Destination segment POP CX ;Length CMP DI,SI ;Check direction of move SBB AX,BX ;Extend the CMP to 32 bits JB COPYLIST ;Move forward into lower mem. ;Otherwise, move backward. Figure end of source and destination ;areas and flip direction flag. DEC CX ADD SI,CX ;End of source area ADD DI,CX ;End of destination area DOWN ;Reverse direction INC CX COPYLIST: MOVB ;Do at least 1 - Range is 1-10000H not 0-FFFFH DEC CX REP MOVB ;Block move RET ;Fill an area of memory with a list values. If the list ;is bigger than the area, don't use the whole list. If the ;list is smaller, repeat it as many times as necessary. FILL: CALL RANGE ;Get range to fill PUSH CX ;Save length PUSH AX ;Save segment number PUSH DX  ;Save displacement CALL LIST ;Get list of values to fill with POP DI ;Displacement in segment POP ES ;Segment POP CX ;Length CMP BX,CX ;BX is length of fill list MOV SI,LINEBUF ;List is in line buffer JCXZ BIGRNG JAE COPYLIST ;If list is big, copy part of it BIGRNG: SUB CX,BX ;How much bigger is area than list? XCHG CX,BX ;CX=length of list PUSH DI ;Save starting addr. of area REP MOVB ;Move list into area POP SI ;The list has been copied into the beginning of the ;specified area of memory. SI is the first address ;of that area, DI is the end of the copy of the list ;plus one, which is where the list will begin to repeat. ;All we need to do now is copy [SI] to [DI] until the ;end of the memory area is reached. This will cause the ;list to repeat as many times as necessary. MOV CX,BX ;Length of area minus list PUSH ES ;Different index register POP DS ;requires different segment reg. JP COPYLIST ;Do the block move ;Search a specified area of memory for given list of bytes. ;Print address of first byte of each match. SEARCH: CALL RANGE ;Get area to be searched PUSH CX ;Save count PUSH AX ;Save segment number PUSH DX ;Save displacement CALL LIST ;Get search list DEC BX ;No. of bytes in list-1 POP DI ;Displacement within segment POP ES ;Segment POP CX ;Length to be searched SUB CX,BX ; minus length of list SCAN: MOV SI,LINEBUF ;List kept in line buffer LODB ;Bring first byte into AL DOSCAN: SCAB ;Search for first byte LOOPNE DOSCAN ;Do at least once by using LOOP JNZ RET ;Exit if not found PUSH BX ;Length of list minus 1 XCHG BX,CX PUSH DI ;Will resume search here REPE CMPB ;Compare rest of string MOV CX,BX ;Area length back in CX POP DI ;Next search location POP BX ;Restore list length JNZ TEST ;Continue search if no match DEC DI ;Match address CALL OUTDI ;Print it INC DI ;Restore search address CALL CRLF TEST: JCXZ RET JP SCAN ;Look for next occurrence ;Get the next parameter, which must be a hex number. ;CX is maximum number of digits the number may have. GETHEX: CALL SCANP ;Scan to next parameter GETHEX1: XOR DX,DX ;Initialize the number MOV AH,DH CALL HEXIN ;Get a hex digit JC ERROR ;Must be one valid digit MOV DL,AL ;First 4 bits in position GETLP: INC DI ;Next char in buffer DEC CX ;Digit count CALL"  HEXIN ;Get another hex digit? JC RET ;All done if no more digits JCXZ ERROR ;Too many digits? CALL SHIFT4 ;Multiply by 16 OR DL,AL ;and combine new digit JP GETLP ;Get more digits ;Check if next character in the input buffer is a hex digit ;and convert it to binary if it is. Carry set if not. HEXIN: MOV AL,[DI] ;Check if AL has a hex digit and convert it to binary if it ;is. Carry set if not. HEXCHK: SUB AL,"0" ;Kill ASCII numeric bias JC RET CMP AL,10 CMC JNC RET ;OK if 0-9 SUB AL,7 ;Kill A-F bias CMP AL,10 JC RET CMP AL,16 CMC RET: RET ;Process one parameter when a list of bytes is ;required. Carry set if parameter bad. Called by LIST LISTITEM: CALL SCANP ;Scan to parameter CALL HEXIN ;Is it in hex? JC STRINGCHK ;If not, could be a string MOV CX,2 ;Only 2 hex digits for bytes CALL GETHEX ;Get the byte value MOV [BX],DL ;Add to list INC BX GRET: CLC ;Parameter was OK RET STRINGCHK: MOV AL,[DI] ;Get first character of param CMP AL,"'" ;String? JZ STRING CMP AL,'"' ;Either quote is all right JZ STRING STC ;Not string, not hex - bad RET STRING: MOV AH,AL ;Save for closing quote INC DI STRNGLP: MOV AL,[DI] ;Next char of string INC DI CMP AL,13 ;Check for end of line JZ ERROR ;Must find a close quote CMP AL,AH ;Check for close quote JNZ STOSTRG ;Add new character to list CMP AH,[DI] ;Two quotes in a row? JNZ GRET ;If not, we're done INC DI ;Yes - skip second one STOSTRG: MOV [BX],AL ;Put new char in list INC BX JP STRNGLP ;Get more characters ;Get a byte list for ENTER, FILL or SEARCH. Accepts any number ;of 2-digit hex values or character strings in either single ;(') or double (") quotes. LIST: MOV BX,LINEBUF ;Put byte list in the line buffer LISTLP: CALL LISTITEM ;Process a parameter JNC LISTLP ;If OK, try for more SUB BX,LINEBUF ;BX now has no. of bytes in list JZ ERROR ;List must not be empty ;Make sure there is nothing more on the line except for ;blanks and carriage return. If there is, it is an ;unrecognized parameter and an error. GETEOL: CALL SCANB ;Skip blanks JNZ ERROR ;Better be a RETURN RET ;Command error. DI has been incremented beyond the ;command letter so it must decremented for the ;error pointer to work. PERR: DEC DI ;Syntax error. DI points to character in the input buffer ;which caused error. By subtracting from start of buffer, ;we will know how far to tab over to appear directly below ;it on the terminal. Then print "^ Error". ERROR: SUB DI,LINEBUF-1 ;How many char processed so far? MOV CX,DI ;Parameter for TAB in CX CALL TAB ;Directly below bad char MOV SI,SYNERR ;Error message ;Print error message and abort to command level PRINT: CALL PRINTMES JMP COMMAND ;Short form of ENTER command. A list of values from the ;command line are put into memory without using normal ;ENTER mode. GETLIST: CALL LIST ;Get the bytes to enter POP DI ;Displacement within segment POP ES ;Segment to enter into MOV SI,LINEBUF ;List of bytes is in line buffer MOV CX,BX ;Count of bytes REP MOVB ;Enter that byte list RET ;Enter values into memory at a specified address. If the ;line contains nothing but the address we go#  into "enter ;mode", where the address and its current value are printed ;and the user may change it if desired. To change, type in ;new value in hex. Backspace works to correct errors. If ;an illegal hex digit or too many digits are typed, the ;bell is sounded but it is otherwise ignored. To go to the ;next byte (with or without change), hit space bar. To ;back up to a previous address, type "-". On ;every 8-byte boundary a new line is started and the address ;is printed. To terminate command, type carriage return. ; Alternatively, the list of bytes to be entered may be ;included on the original command line immediately following ;the address. This is in regular LIST format so any number ;of hex values or strings in quotes may be entered. ENTER: MOV CX,5 ;5 digits in address CALL GETHEX ;Get ENTER address CALL GETSEG ;Convert to seg/disp format ;Adjust segment and displacement so we are in the middle ;of the segment instead of the very bottom. This allows ;backing up a long way. SUB AH,8 ;Adjust segment 32K down ADD DH,80H ; and displacement 32K up PUSH AX ;Save for later PUSH DX CALL SCANB  ;Any more parameters? JNZ GETLIST ;If not end-of-line get list POP DI ;Displacement of ENTER POP ES ;Segment GETROW:  CALL OUTDI ;Print address of entry CALL BLANK ;Leave a space GETBYTE: SEG ES MOV AL,[DI] ;Get current value CALL HEX ;And display it MOV AL,"." CALL OUT ;Prompt for new value MOV CX,2 ;Max of 2 digits in new value MOV DX,0 ;Intial new value GETDIG: CALL IN ;Get digit from user MOV AH,AL ;Save CALL HEXCHK ;Hex digit? XCHG AH,AL ;Need original for echo JC NOHEX ;If not, try special command CALL OUT ;Echo to console MOV DH,DL ;Rotate new value MOV DL,AH ;And include new digit LOOP GETDIG ;At most 2 digits ;We have two digits, so all we will accept now is a command. WAIT: CALL IN ;Get command character NOHEX: CMP AL,8 ;Backspace JZ BS CMP AL,7FH ;RUBOUT JZ BS CMP AL,"-" ;Back up to previous address JZ PREV CMP AL,13 ;All done with command? JZ EOL CMP AL," " ;Go to next address JZ NEXT ;If we got here, character was invalid. Sound bell. MOV AL,7 CALL OUT JCXZ WAIT ;CX=0 means no more digits JP GETDIG ;Don't have 2 digits yet BS: CMP CL,2 ;CX=2 means nothing typed yet JZ GETDIG ;Can't back up over nothing INC CL ;Accept one more character MOV DL,DH ;Rotate out last digit MOV DH,CH ;Zero this digit CALL BACKUP ;Physical backspace JP GETDIG ;Get more digits ;If new value has been entered, convert it to binary and ;put into memory. Always bump pointer to next location STORE: CMP CL,2 ;CX=2 means nothing typed yet JZ NOSTO ;So no new value to store ;Rotate DH left 4 bits to combine with DL and make a byte value PUSH CX MOV CL,4 SHL DH,CL POP CX OR DL,DH ;Hex is now converted to binary SEG ES MOV [DI],DL ;Store new value NOSTO: INC DI ;Prepare for next location RET EOL: CALL STORE ;Enter the new value JMP CRLF ;CR/LF and terminate NEXT: CALL STORE ;Enter new value INC CX ;Leave a space plus two for INC CX ; each digit not entered CALL TAB MOV AX,DI ;Next memory address AND AL,7 ;Check for 8-byte boundary JNZ GETBYTE ;Take 8 per line NEWROW: $ CALL CRLF ;Terminate line JMP GETROW ;Print address on new line PREV: CALL STORE ;Enter the new value ;DI has been bumped to next byte. Drop it 2 to go to previous addr DEC DI DEC DI JP NEWROW ;Terminate line after backing up ;Perform register dump if no parameters or set register if a ;register designation is a parameter. REG: CALL SCANP JZ DISPREG MOV DL,[DI] INC DI MOV DH,[DI] CMP DH,13 JZ FLAG INC DI CALL GETEOL CMP DH," " JZ FLAG MOV DI,REGTAB XCHG AX,DX PUSH CS POP ES MOV CX,REGTABLEN REPNZ SCAW JNZ BADREG OR CX,CX JNZ NOTPC DEC DI DEC DI SEG CS MOV AX,[DI-2] NOTPC: CALL OUT MOV AL,AH CALL OUT CALL BLANK PUSH DS POP ES LEA BX,[DI+REGDIF-2] MOV DX,[BX] CALL OUT16 CALL CRLF MOV AL,":" CALL OUT CALL INBUF CALL SCANB JZ RET3 MOV CX,4 CALL GETHEX1 CALL GETEOL MOV [BX],DX RET3: RET BADREG: MOV AX,5200H+"B" ;BR ERROR JMP ERR DISPREG: MOV SI,REGTAB MOV BX,AXSAVE MOV CX,8 CALL DISPREGLINE CALL CRLF MOV CX,5 CALL DISPREGLINE CALL BLANK CALL DISPFLAGS JMP CRLF FLAG: CMP DL,"F" JNZ BADREG CALL DISPFLAGS MOV AL,"-" CALL OUT CALL INBUF CALL SCANB XOR BX,BX MOV DX,[FSAVE] GETFLG: MOV SI,DI LODW CMP AL,13 JZ SAVCHG CMP AH,13 JZ FLGERR MOV DI,FLAGTAB MOV CX,32 PUSH CS POP ES REPNE SCAW JNZ FLGERR MOV CH,CL AND CL,0FH MOV AX,1 ROL AX,CL TEST AX,BX JNZ REPFLG OR BX,AX OR DX,AX TEST CH,16 JNZ NEXFLG XOR DX,AX NEXFLG: MOV DI,SI PUSH DS POP ES CALL SCANP JP GETFLG DISPREGLINE: SEG CS LODW CALL OUT MOV AL,AH CALL OUT MOV AL,"=" CALL OUT MOV DX,[BX] INC BX INC BX CALL OUT16 CALL BLANK CALL BLANK LOOP DISPREGLINE RET REPFLG: MOV AX,4600H+"D" ;DF ERROR FERR: CALL SAVCHG ERR: CALL OUT MOV AL,AH CALL OUT MOV SI,ERRMES JMP PRINT SAVCHG: MOV [FSAVE],DX RET FLGERR: MOV AX,4600H+"B" ;BF ERROR JP FERR DISPFLAGS: MOV SI,FLAGTAB MOV CX,16 MOV DX,[FSAVE] DFLAGS:  SEG CS LODW SHL DX JC FLAGSET SEG CS MOV AX,[SI+30] FLAGSET: OR AX,AX JZ NEXTFLG CALL OUT MOV AL,AH CALL OUT CALL BLANK NEXTFLG: LOOP DFLAGS RET ;Trace 1 instruction or the number of instruction specified ;by the parameter using 8086 trace mode. Registers are all ;set according to values in save area TRACE: CALL SCANP CALL HEXIN MOV DX,1  JC STOCNT MOV CX,4 CALL GETHEX STOCNT: MOV [TCOUNT],DX CALL GETEOL STEP: MOV [BRKCNT],0 OR B,[FSAVE+1],1 EXIT:  MOV [12],BREAKFIX MOV [14],CS MOV [4],REENTER MOV [6],CS DI MOV [64H],REENTER MOV [66H],CS MOV SP,STACK POP AX  POP BX POP CX POP DX POP BP POP BP POP SI POP DI POP ES POP ES POP SS MOV SP,[SPSAVE] PUSH [FSAVE] PUSH [CSSAVE] PUSH [IPSAVE] MOV DS,[DSSAVE] IRET STEP1: JP STEP ;Re-entry point from breakpoint. Need to decrement instruction ;pointer so it points to location where breakpoint actually ;occured. BREAKFIX: XCHG SP,BP DEC [BP] XCHG SP,BP  ;Re-entry point from trace mode or interrupt during ;execution. All registers are saved so they can be ;displayed or modified. REENTER: SEG CS MOV [SPSAVE+SEGDIF],SP SEG CS MOV [SSSAVE+SEGDIF],SS XOR SP,SP MOV SS,SP MOV SP,RSTACK PU% SH ES PUSH DS PUSH DI PUSH SI PUSH BP DEC SP DEC SP PUSH DX PUSH CX PUSH BX PUSH AX PUSH SS POP DS MOV SP,[SPSAVE] MOV SS,[SSSAVE] POP [IPSAVE] POP [CSSAVE] POP AX AND AH,0FEH MOV [FSAVE],AX MOV [SPSAVE],SP PUSH DS POP ES PUSH DS POP SS MOV SP,STACK MOV [64H],INT MOV AL,20H OUT BASE+2 EI UP CALL CRLF CALL DISPREG DEC [TCOUNT] JNZ STEP1 ENDGO: MOV SI,BPTAB MOV CX,[BRKCNT] JCXZ COMJMP CLEARBP: MOV DX,[SI+BPLEN] LODW PUSH AX CALL GETSEG MOV ES,AX MOV DI,DX POP AX STOB LOOP CLEARBP COMJMP: JMP COMMAND ;Input from the specified port and display result INPUT: MOV CX,4 ;Port may have 4 digits CALL GETHEX ;Get port number in DX INB DX ;Variable port input CALL HEX ;And display JMP CRLF ;Output a value to specified port. OUTPUT: MOV CX,4 ;Port may have 4 digits CALL GETHEX ;Get port number PUSH DX ;Save while we get data MOV CX,2 ;Byte output only CALL GETHEX ;Get data to output XCHG AX,DX ;Output data in AL POP DX ;Port in DX OUTB DX ;Variable port output RET ;Jump to program, setting up registers according to the ;save area. Up to 10 breakpoint addresses may be specified. GO: MOV BX,LINEBUF XOR SI,SI GO1: CALL SCANP JZ EXEC MOV CX,5 CALL GETHEX MOV [BX],DX MOV [BX-BPLEN+1],AH INC BX INC BX INC SI CMP SI,BPMAX+1 JNZ GO1 MOV AX,5000H+"B" ;BP ERROR JMP ERR EXEC: MOV [BRKCNT],SI CALL GETEOL MOV CX,SI JCXZ NOBP MOV SI,BPTAB SETBP: MOV DX,[SI+BPLEN] LODW CALL GETSEG MOV DS,AX MOV DI,DX MOV AL,[DI] MOV B,[DI],0CCH PUSH ES POP DS MOV [SI-2],AL LOOP SETBP NOBP: MOV [TCOUNT],1 JMP EXIT ;Console input interrupt handler. Used to interrupt commands ;or programs under execution (if they have interrupts ;enabled). Control-S causes a loop which waits for any other ;character to be typed. Control-C causes abort to command ;mode. All other characters are ignored. INT: PUSH AX ;Don't destroy accumulator ;Output End-of-Interrupt commands to slave 8259A. This ;wouldn't be necessary if Automatic End of Interrupt mode ;worked like it was supposed to! MOV AL,20H OUT BASE+2 IN DATA ;Get interrupting character AND AL,7FH ;ASCII has only 7 bits CMP AL,"S"-"@" ;Check for Control-S JNZ NOSTOP CALL IN ;Wait for continue character NOSTOP: CMP AL,"C"-"@" ;Check for Control-C JZ BREAK ;Just ignore interrupt - restore AX and return POP AX IRET BREAK: CALL CRLF JMP COMMAND REGTAB: DB "AXBXCXDXSPBPSIDIDSESSSCSIPPC" REGDIF: EQU AXSAVE-REGTAB ;Flags are ordered to correspond with the bits of the flag ;register, most significant bit first, zero if bit is not ;a flag. First 16 entries are for bit set, second 16 for ;bit reset. FLAGTAB: DW 0 DW 0 DW 0 DW 0 DB "OV" DB "DN" DB "EI" DW 0 DB "NG" DB "ZR" DW 0 DB "AC" DW 0 DB "PE" DW 0 DB "CY" DW 0 DW 0 DW 0 DW 0 DB "NV" DB "UP" DB "DI" DW 0 DB "PL" DB "NZ" DW 0 DB "NA" DW 0 DB "PO" DW 0 DB "NC" ;Initialization table. First byte of each entry is no. ;of bytes to output to the corresponding port. That ;many initialization bytes follow. INITTABLE: ;Port BASE+0 - Master 8259A. Intialization Command Word (ICW) ;One sets l& evel-triggered mode, multiple 8259As, require ;ICW4. DB 1 DB 19H ;Port BASE+1 - Master 8259A. ICW2 sets vector base to 10H ;ICW3 sets a slave on interrupt input 1; ICW4 sets buffered ;mode, as a master, with Automatic End of Interrupt, 8086 ;vector; Operation Command Word (OCW) One sets interrupt ;mask to enable line 1 (slave 8259A) only. DB 4 DB 10H,2,0FH,0FDH ;Port BASE+2 - Slave 8259A. ICW1 sets level-triggered mode, ;multiple 8259As, require ICW4. DB 1 DB 19H ;Port BASE+3 - Slave 8259A. ICW2 sets vector base to 18H ;ICW3 sets slave address as 1; ICW4 sets buffered mode, ;as slave, with Automatic End of Interrupt (which doesn't ;work in slaves), 8086 vector; OCW1 sets interrupt mask ;to enable line 1 (serial receive) only. DB 4  DB 18H,1,0BH,0FDH ;Port Base+4 - 9513 Data. 9513 has previously been set ;up for Counter 5 mode register with auto increment. Thus ;mode is set to 0B63H, which is no gating, count source is ;F1 (4 MHz), reload from load or hold, count down repetitively ;in binary, with output toggle. Load register is set to ;0007H, and Hold register is set to 0006H. Thus we ;alternately divide by 7 and 6, which is divided by 2 by ;the output toggle, thus providing a square wave of ;4 MHz/13 = 307.7 kHz, which divided by 16 in the 8251A ;provides 19,230 baud (0.16% high). DB 6 DB 63H,0BH,7,0,6,0 ;Port BASE+5 - 9513 Control. Load and arm counter 5, ;enabling baud rate generation. Then select counter ;5 mode register, in case baud rate wasn't right. DB 2 DB 70H,5 ;Port BASE+6 - 8251A Data. No initialization to this port. DB 0 ;Port BASE+7 - 8251A Control. Since it is not possible to ;know whether the 8251A next expects a Mode Instruction or ;a Command Instruction, a dummy byte is sent which could ;safely be interpreted as either but guarantees it is now ;expecting a Command. The command sent is Internal Reset ;which causes it to start expecting a mode. The mode sent ;is for 2 stop bits, no parity, 8 data bits, 16X clock. ;This is followed by the command to error reset, enable ;transmitter and receiver, set RTS and DTR to +12V. DB 4 DB 0B7H,77H,0CEH,37H HEADER: DM 13,10,10,"SCP 8086 Monitor 1.5",13,10 SYNERR: DB '^' ERRMES: DM " Error",13,10 BACMES: DM 8,32,8 ;Disk boot. Select one of the following routines by ;setting the equates at the start of this program. BOOT: PUSH DI ;************************************************ ;Boot for Cromemco 4FDC disk controller with either ;large or small disks. Loads track 0, sector 1 into LOAD. IF CROMEMCO4FDC DISK: EQU 30H MOV AL,1 OUT 2 ;Reset 4FDC serial I/O MOV AL,84H OUT 0 ;and set for 300 baud MOV AL,7FH OUT 4 MOV DL,21H RETRY: MOV AL,0D0H OUTB DISK READY: INB DISK ROR AL JC READY XOR DL,10H MOV AL,DL OUTB DISK+4 MOV DI,LOAD MOV AL,12 OUTB DISK HOME: INB DISK+4 ROR AL JNC HOME INB DISK AND AL,98H JNZ RETRY MOV AL,1 OUTB DISK+2 MOV CX,80H MOV AL,DL OR AL,80H OUTB DISK+4 MOV AL,8CH OUTB DISK READ: INB DISK+4 ROR AL JC DONE INB DISK+3 STOB LOOP READ WSTAT: INB DISK+4 ROR AL JNC WSTAT DONE: INB DISK AND AL,9CH JNZ RETRY ENDIF ;*************************************************** ;Boot for North Star disk, single density. ;Loads ' track 0, sector 0 into address LOAD ;Bug in North Star boot fixed 5-26-81. IF NORTHSTARSD ;Disk command equates SEL: EQU 1 STP1: EQU 9 STP2: EQU 8 NOP: EQU 10H SEC: EQU 14H STPOUT: EQU 1CH RD: EQU 40H BST: EQU 20H PUSH DS MOV AX,0FEB8H MOV DS,AX MOV AL,[SEL] MOV CX,20 MOTOR: CALL SECTOR LOOP MOTOR CHKTRK: TEST B,[STPOUT],1 JNZ ONTRACK MOV AL,[STP1] AAM MOV AL,[STP2] CALL SECTOR CALL SECTOR JP CHKTRK SECTOR: MOV AL,[SEC] ; Reset sector flag. SECLP: TEST B,[NOP],80H ; Wait for sector flag. JZ SECLP RET ONTRACK: MOV DI,LOAD MOV CX,280 MOV BX,RD+NOP GETSEC: CALL SECTOR TEST B,[BST+NOP],0FH ; Test for sector zero. JNZ GETSEC GETSYNC: TEST B,[NOP],4 LOOPZ GETSYNC JZ ONTRACK MOV CX,100H XOR DL,DL AAD READ: MOV AL,[BX] STOB ;Uses ES XOR DL,AL ROL DL AAD LOOP READ MOV AL,[BX] CMP AL,DL  JNZ ONTRACK POP DS ENDIF ;************************************************ ;Boot for Tarbell disk controllers. Load track 0, ;sector 1 into LOAD. IF TARBELL DISK: EQU 78H JP RETRY DCOM: OUTB DISK MOV AL,50 HOLD: DEC AL JNZ HOLD RET RETRY: MOV AL,0D0H CALL DCOM READY: INB DISK ROR AL JC READY MOV DI,LOAD MOV AL,0EH ;Home command @ 10ms/track CALL DCOM INB DISK+4 INB DISK AND AL,98H JNZ RETRY MOV AL,1 OUTB DISK+2 MOV CX,80H MOV AL,8CH CALL DCOM READ: INB DISK+4 ROL AL JNC DONE INB DISK+3 STOB LOOP READ WSTAT: INB DISK+4 ROL AL JC WSTAT DONE: INB DISK AND AL,9CH JNZ RETRY ENDIF ;*************************************************** IF OTHER ;User may insert customized disk boot here. All ;registers are available, stack pointer is valid ;and interrupts are enabled. Stack should be at ;same level on fall-through to code below. Last ;address available is 07DF hex. ORG 7E0H ;Simulate boot of maximum length ENDIF ;*************************************************** ;Successful read MOV [CSSAVE],0 MOV [IPSAVE],LOAD POP DI JMP GO O nSV2.תNuYɀ?0uC{wQS#S[t;Source code for drive tables used by RDCPM ORG 0 PUT 100H DW END ;Address of first free byte ;Table of addresses of the parameter block for each of 16 drives. ;Note that 16 entries are ALWAYS required, with unused drives 0. DW IBM,IBM,IBM,IBM DW 0,0,0,0 DW 0,0,0,0 DW 0,0,0,0 ;Below is the definition for standard single-density 8" drives IBM: DW 26 ;Sectors per track DB 3 ;Block shift DB 7 ;Block mask DB 0 ;Extent mask DW 242 ;Disk size - 1 DW 63 ;Directory entries - 1 DS 4 ;Not used DW 2 ;Tracks to skip DW MOD6 ;Modulo-6 sector translate table MOD6: DB 0,6,12,18,24 DB 4,10,16,22 DB 2,8,14,20 DB 1,7,13,19,25 DB 5,11,17,23 DB 3,9,15,21 ;This is the table for Cromemco 5" drives. SMALL: DW 18 ;Sectors per track DB 3 ;Block shift DB 7 ;Block mask DB 0 ;Extent mask DW 82 ;Disk size - 1 DW 63 ;Directory entries - 1 DS 4 ;Not used DW 3 ;Tracks to skip DW MOD5 ;Modulo-5 sector translate table MOD5: DB 0,5,10,15 DB 2,7,12,17 (  DB 4,9,14 DB 1,6,11,16 DB 3,8,13 END: ;This is a disk boot routine for the 1771/1791 type disk ;controllers. It would normally reside on track 0, ;sector 1, to be loaded by the "B" command of the ;monitor at address 200H. By changing the equates ;below, it may be configured to load any size of ;program at any address. The program is assumed to ;occupy consecutive sectors starting at track 0, sector ;2, and will begin exection at its load address (which ;must be a 16-byte boundary) with the Instruction ;Pointer set to zero. ; Variations are available for the Cromemco 4FDC with ; large disks, the 4FDC with small disks, the Tarbell ; single-density controller, and the Tarbell double- ; density controller. Select one. CROMEMCOSMALL: EQU 0 CROMEMCOLARGE: EQU 1 TARBELLSINGLE: EQU 0 TARBELLDOUBLE: EQU 0 LOAD: EQU 400H ;Address to load program SEG: EQU 40H ;LOAD/10H SECTOR: EQU 8 ;No. of 128-byte sectors to load BOOTER: EQU 200H ;"B" command puts booter here ;**************************************************************  CROMEMCO: EQU CROMEMCOLARGE+CROMEMCOSMALL TARBELL: EQU TARBELLSINGLE+TARBELLDOUBLE WD1771: EQU CROMEMCO+TARBELLSINGLE WD1791: EQU TARBELLDOUBLE SMALL: EQU CROMEMCOSMALL LARGE: EQU CROMEMCOLARGE+TARBELL IF SMALL MAXSECT:EQU 18 ENDIF IF LARGE MAXSECT:EQU 26 ENDIF IF TARBELL DONEBIT:EQU 80H DISK: EQU 78H ENDIF IF CROMEMCO DONEBIT:EQU 1 DISK: EQU 30H ENDIF IF WD1771 READCOM:EQU 88H ENDIF IF WD1791 READCOM:EQU 80H ENDIF IF CROMEMCOLARGE WAITBYTE:EQU 0B1H ENDIF IF CROMEMCOSMALL WAITBYTE:EQU 0A1H ENDIF ORG BOOTER PUT 100H XOR AX,AX MOV DS,AX MOV ES,AX MOV SS,AX MOV SP,BOOTER ;For debugging purposes UP MOV DI,LOAD MOV DX,SECTOR MOV BL,2 SECT: MOV AL,0D0H ;Force Interrupt command OUT DISK ;To force Type I status AAM CMP BL,MAXSECT+1 JNZ NOSTEP MOV AL,58H ;Step in with update CALL DCOM MOV BL,1 NOSTEP: MOV AL,BL OUTB DISK+2 IF CROMEMCO MOV AL,WAITBYTE OUT DISK+4 ;Turn on hardware wait ENDIF INB DISK ;Get head load status NOT AL AND AL,20H JZ OUTCOM MOV AL,4 OUTCOM: OR AL,READCOM OUTB DISK MOV CX,128 PUSH DI READ: INB DISK+4 TEST AL,DONEBIT IF TARBELL JZ DONE ENDIF IF CROMEMCO JNZ DONE ENDIF INB DISK+3 STOB LOOP READ DONE: POP DI CALL GETSTAT AND AL,9CH JNZ SECT ADD DI,128 INC BL DEC DX JNZ SECT JMP 0,SEG DCOM: OUT DISK AAM GETSTAT: INB DISK+4 TEST AL,DONEBIT IF TARBELL JNZ GETSTAT ENDIF IF CROMEMCO  JZ GETSTAT ENDIF IN DISK RET ndififre HEX PRN! uQ|) The documentation for this release of 86-DOS is not complete yet, we have enclosed the documentation for version 1.0 and a few sheets explaining new features which are necessary to know to use the system. We hope to make a new addendum which will replace the 1.0 addendum and all the loose sheet in a couple of weeks. Microsoft has purchased 86-DOS from us and now owns all the rights to it including the responsibility to document it. When the final manuals from Microsoft are available you will get copies. The file CPMTAB.ASM is the source file for the tables used by RDCPM and is provided as an example of how new tables may be prepared. These tables allow reading 8" or 5" single-density CP/M disks. If new tables are desired for reading disks of a different CP/M 2 format, follow these steps: 1. Prepare a source file of the new tables (see MAKRDCPM in manual).  2. Assemble the new tables. 3. Convert the new tables to binary with HEX2BIN. 4. Run MAKRDCPM. The files INIT.ASM and INIT.COM are the source and object code for a disk initialization routine. Any raw or blown disk may be formatted by this routine to an empty disk with the soft-sector information necessary for your controller. Note that CLEAR must be run after formatting to use the disk with 86-DOS. The program prints a warning message and asks which drive to format before initializing begins. An important note about RDCPM and INIT: These two programs bypass the operating system and either use the I/O system (in the case of RDCPM) or talk directly to the disk controller (in the case of INIT) to access the disk. Therefore, if the I/O system is changed so that the number and arrangment of drives is different, RDCPM and INIT may have to be changed so that they respect the new drive configuration. The disk selections in INIT.ASM are very similar to those in DOSIO.ASM, so if you change one, make appropriate changes in the other. Since INIT moves the disk drive heads around without the knowledge of the I/O system, after initializing disks the heads will probably be on a different track than the I/O system thinks they should be on. This usually results in a long delay in accessing any drive used after a disk has been initialized on it because the I/O system must decide it doesn't know where the head is, restore it to track zero, and put it back where it's supposed to be. s  Addendum for 86-DOS 1.1 This disk contains 86-DOS version 1.1 which is necessary to run the Microsoft high-level language compiler or MACRO-86 assembler which you have purchased. It also contains the newer versions of the system utilities which go with 86-DOS 1.1. Sometime in December SCP will begin distribution of an even later version of 86-DOS which will be fully documented and include all the features of Microsoft's MS-DOS, IBM's Personal Computer DOS, and Lifeboat's S* B-86. The later version will be available for a service charge (probably $25.00). Differences between 86-DOS version 1.0 and 86-DOS version 1.1 (included on this disk), which the user must know about in order to use the new software: * 86-DOS no longer fits on the space reserved at the beginning of the disk. 86-DOS resides in an invisible file which is always the first file on the disk. The boot sequence is now as follows: 1> the boot ROM loads the first sector from the disk and jumps to the code loaded from that sector. 2> the code from the first sector loads in the I/O system which follows the first sector in the reserved area and is typically eight 128-byte sectors in length. A jump is made to the  initialization program in the I/O system. 3> the initialization program in the I/O system uses the disk read function of the I/O system to load 86-DOS from the invisible file. 4> the initialization code then uses 86-DOS to load in the command interpreter, COMMAND.COM, from the disk and then jumps to it. * The SYS utility which copys 86-DOS from one disk to another also copies the invisible file, 86DOS.SYS, which contains 86-DOS. Because this file must be the first file on the disk (so the I/O system can find it when 86-DOS is booted up), SYS can only transfer the operating system to a disk which is freshly CLEARed or already has a file (invisible or not) called 86DOS.SYS which is large enough to hold 86-DOS. * The CHKDSK utility prints the total number of files on the disk including invisible files. This allows the user to check for the presence of invisible files such as 86DOS.SYS since the total number of files reported by CHKDSK will be greater that that reported by the DIR command. Note: be sure to use this new version of CHKDSK with 86-DOS version 1.1 because old versions of CHKDSK run under 86-DOS version 1.1 can't "see" the invisible files and will free the disk space allocated to these files thus destroying them. * The COMMAND interpreter (COMMAND.COM) can now load executable machine  language files with the extension .EXE (for EXEcute). These are similar to .COM files but have a 512-byte preface which contains information on how to patch inter-segment jumps and calls within the file allowing multiple-segment files to be loaded. * DEBUG also can load .EXE files so that they can be debugged. * The assembler has had some rather significant features added to it. The expression analyser can now perform multiplication and division as well as addition and subtraction. Use "*" to multiply and "/" to divide. Multiplications and divisions are performed before additions and subtractions. Parentheses may be used to change this normal order of precedence. Errors messages are sent directly to the console as well as put in the .PRN print file so that it is not necessary to make a print file to find out what errors are in the source code. Two more options have been added to the third letter of the drive assignment extention which tells the assembler what to do with the print file. A-O send print file to disk drive A-O P send print file directly to + printer (tabs are NOT expanded) X send print file to console Y force a second pass through the source file so that error messages sent to the console include the text of the source file line on which the error occured Z no print file. Error messages sent to the console will indicate what error occured and on which line, but will not include the text of the line in which the error occured The Z option is normally only used if you are assembling a file which probably has no errors in it (perhaps you've assembled it before and know it has no errors), so that incomplete error messages are of no consequence. Using the Z option results in faster assembly since the assembler doesn't have to make a second pass through source file. Line numbers have been added to the print file to make it easier to find particular lines using the editor. The assembler now handles 8087 opcodes. Complete documentation on this feature will be provided with the new release in December. * The disk initialization program (INIT.COM) has been changed so that the drive letters and disk formats correspond to those used by the operating system. For example, with the Tarbell double-density :1A010000E92400E99B01E99D01E9A501E9AD01E9B501E9BB01E9D501E9E5D5 :1A011A0001E9BD01E96901E92601E9EB00B0FFE6F333C08ED0BC00040E1F26 :1A013400BEB800B90400ACE6F5ACE6F4ACE6F4E2F51EB87B008ED833DB8AC5 :1A014E00C3B93800BA50009AED0140001FBE7F039A00007B00BA0001B41A14 :1A016800CD218B0E06008CDB8CC88ED8BAED00B40FCD210AC0752F33C0A373 :1A0182000E01A3100140A3FB00B427CD21E31DA80174198EDB8EC38ED3BCEC :1A019C005C0033C050BA8000B41ACD2153B8000150CBBAC400B409CD21FB69 :1A01B600EBFE17F3840138010238000308000D0A4572726F7220696E206C95 :1A01D0006F6164696E6720436F6D6D616E6420496E74657270726574657210 :0F01EA000D0A2401434F4D4D414E4420434F4DCC :1A021200B0A7E6F5B0E0E6F5B019E6F5E80F0092E80B0091E4F48AE0E4F46A :1A022C0086C4EB6AE802008ACCE4F48AE0D0ECD0ECD0ECD0EC240FD50A8A0B :1A024600E08AC1C35152E81600B043E6F55A59E81100B043E6F5E80600B029 :1A02600027E6F5EB3533C98BD1B009E80400B00A8BD1E6F58AC2E802008AB4 :1A027A00C6D40AD0E4D0E4D0E4D0E40AC4E6F4C392B00BE6F592E6F48AC4A9 :1A029400E6F4B044E6F550B01FE6F558CBE4F72402CBE4F7240274FAE4F675 :1A02AE00247FCB50E4F7240174FA58E6F6CB50E4FD240174FA58E6FCCBE45E :1A02C800FD240274FAE4FCCB50E4FD240174FA58E6FCCBB4002E3A06700382 :1A02E2007508E47824207402B401CBE84000722151E8EE0059721AFEC6E282 :1A02FC00F50AC0CBE82D00720E51E81301597207FEC6E2F50AC0CBB3FF2E9A :1A031600881DBE2902FEC32EAC84E074F88AC3D0E0F9CB4080080210200712 :1A0330008AE02E860670033AC4740BE479E67BB010E81D018AC48BF3988B2C :1A034A00D82E8A877103E67C8AE092B21AF6C6087402B210F6F292FEC62E7C :1A0364008A9F770381C37D038BFB8AC22E8605E6793AC27427B7023CFF752E :1A037E0005E8C70072138AC2E67BB01EE8D4002498740F7804FECF75E88A86 :1A039800E0A880F97502B402C38ADEF6C40874198AC4E67C82FB0876238253 :1A03B200EB0882FB08770D8AC40C40E67CEB1382FB1A760EFEC2B058E894DC :1A03CC00002EFE05B6018ADE8AC3E67AFAE478F6D0242074BFB004C3E8BA6E :1A03E600FFB30A0C80E6788BEEE47CD0E07307E47B880446EBF3FBE86700FB :1A040000249C740D8BF58AF8B000FECB75DB8AE7F9B0D0E678B00AFEC87599 :1A041A00FCC3E882FFB30A0CA0E6788BEEE47CD0E07305ACE67BEBF5FBE808 :1A043400310024FC74D78BF58AF8B000FECB75DD8AE7F9EBC8B303, B00EE8CC :1A044E001100249874C77809B05AE80600FECB75ECF9C3E67850D40A58E465 :080468007CA88075FAE478C35A :1A047100004010500818000001010001FFFF0690039003900390039A039A27 :19048B000300001E008000043400024000D2070004010100028000D00408 :0000000000 0500818000ECT:EQU 52+6+6+16 ; Allow for reserved tracks, 2 FATs, directory ENDIF IF SMALL ; Drive A is 5.25-inch. DOSLEN: EQU 56 DOSSECT:EQU 54+4+4+16 ENDIF IF CUSTOM ; Drive A is custom. DOSLEN: EQU 0 ; Specify DOS length in sectors. DOSSECT:EQU 0 ; Specify beginning sector on disk. ENDIF ENDIF IF NORTHSTARSD DOSLEN: EQU 28 DOSSECT:EQU 30+2+2+8 ENDIF ORG 0 PUT 100H BASE: EQU 0F0H SIOBASE:EQU 10H STAT: EQU BASE+7 DATA: EQU BASE+6 DAV: EQU 2 TBMT: EQU 1 SERIAL: EQU SERIALPRN+SERIALAUX STCDATA:EQU BASE+4 ; Ports for 9513 Timer chip. STCCOM: EQU BASE+5 IF SERIALAUX AUXSTAT:EQU SIOBASE+3 AUXDATA:EQU SIOBASE+2 ENDIF IF PARALLELAUX AUXSTAT:EQU BASE+13 AUXDATA:EQU BASE+12 ENDIF IF SERIALPRN PRNSTAT:EQU SIOBASE+1 PRNDATA:EQU SIOBASE+0 ENDIF IF PARALLELPRN PRNSTAT:EQU BASE+13 PRNDATA:EQU BASE+12 ENDIF JMP INIT JMP STATUS JMP INP JMP OUTP JMP PRINT JMP AUXIN JMP AUXOUT JMP READ JMP WRITE JMP DSKCHG JMP SETDATE JMP SETTIME JMP GETTIME INIT: MOV AL,0FFH ;Mask all interrupts OUT BASE+3 ;Send mask to slave XOR AX,AX MOV SS,AX MOV SP,400H ;Set stack just below I/O system PUSH CS POP DS ; Initialize time-of-day clock. MOV SI,STCTAB MOV CX,4 ;Initialize 4 registers INITSTC: LODB OUT STCCOM ;Select register to initialize LODB OUT STCDATA LODB OUT STCDATA LOOP INITSTC IF SERIAL MOV CX,4 SERINIT: LODB OUT SIOBASE+1 OUT SIOBASE+3 LOOP SERINIT LODB ;Baud rate for channel 0 OUT SIOBASE+8 LODB ;Baud rate for channel 1 OUT SIOBASE+9 ENDIF ; Load 86-DOS PUSH DS MOV AX,DOSSEG ; Set segment register for loading DOS. MOV DS,AX XOR BX,BX ; Offset in DOSSEG is zero. MOV AL,BL ; Drive 0. MOV CX,DOSLEN MOV DX,DOSSECT CALL READ,40H POP DS MOV SI,INITTAB CALL 0,DOSSEG MOV DX,100H MOV AH,26 ;Set DMA address INT 21H MOV CX,[6] ;Get size of segment MOV BX,DS ;Save segment for later ;DS must be set to CS so we can point to the FCB MOV AX,CS MOV DS,AX MOV DX,FCB ;File Control Block for COMMAND.COM MOV AH,15 INT 21H ;Open COMMAND.COM OR AL,AL JNZ COMERR ;Error if file not found XOR AX,AX MOV [FCB+33],AX ;Set 4-byte Random Record field to MOV [FCB+35],AX ; beginning of file INC AX MOV [FCB+14],AX ;Set record length field MOV AH,39 ;Block read (CX already set) INT 21H JCXZ COMERR ;Error if no records read TEST AL,1 JZ COMERR ;Error if not end-of-file ;Make all segment registers the same MOV DS,BX MOV ES,BX MOV SS,BX MOV SP,5CH ;Set stack to standard value XOR AX,AX PUSH AX ;Put zero on top of stack for return MOV DX,80H MOV AH,26 INT 21H ;Set default transfer address (DS:0080) PUSH BX ;Put segment on stack MOV AX,100H PUSH AX ;Put address to execute within segment on stack RET L ;Jump to COMMAND COMERR: MOV DX,BADCOM MOV AH,9 ;Print string INT 21H EI STALL: JP STALL STCTAB: DB 17H ;Select master mode register DW 84F3H ;Enable time-of-day DB 1 ;C- ounter 1 mode register DW 0138H DB 2 DW 0038H DB 3 DW 0008H ;Set counter 3 to count days IF SERIAL DB 0B7H, 77H, 4EH, 37H, PRNBAUD, AUXBAUD ENDIF BADCOM: DB 13,10,"Error in loading Command Interpreter",13,10,"$" FCB: DB 1,"COMMAND CO controller there are no longer two different initialization programs INIT and DINIT for single and double-density disks.  The new INIT will format a double-density double-sided disk in drives E or F if if you have double-sided drives. See the warning about changing DOSIO and INIT in the file NEWS.DOC. { And print it again } END. M" DS 25 GETTIME: MOV AL,0A7H ;Save counters 1,2,3 OUT STCCOM MOV AL,0E0H ;Enable data pointer sequencing OUT STCCOM MOV AL,19H ;Select hold 1 / hold cycle OUT STCCOM CALL STCTIME ;Get seconds & 1/100's XCHG AX,DX CALL STCTIME ;Get hours & minutes XCHG AX,CX IN STCDATA MOV AH,AL IN STCDATA XCHG AL,AH ;Count of days JP POINTSTAT STCTIME:  CALL STCBYTE MOV CL,AH STCBYTE: IN STCDATA MOV AH,AL SHR AH SHR AH SHR AH SHR AH AND AL,0FH ;Unpack BCD digits AAD ;Convert to binary MOV AH,AL MOV AL,CL RET SETTIME: PUSH CX PUSH DX CALL LOAD0 ;Put 0 into load registers to condition timer MOV AL,43H ;Load counters 1 & 2 OUT STCCOM POP DX POP CX CALL LOAD MOV AL,43H OUT STCCOM ;Load counters 1&2 CALL LOAD0 MOV AL,27H ;Arm counters 1,2,3 OUT STCCOM JP POINTSTAT LOAD0: XOR CX,CX MOV DX,CX  LOAD: MOV AL,09 ;Counter 1 load register CALL OUTDX MOV AL,0AH ;Counter 2 load register MOV DX,CX OUTDX: OUT STCCOM ;Select a load register MOV AL,DL CALL OUTBCD MOV AL,DH OUTBCD: AAM ;Convert binary to unpacked BCD SHL AH SHL AH SHL AH SHL AH OR AL,AH ;Packed BCD OUT STCDATA RET SETDATE: XCHG AX,DX ;Put date in DX MOV AL,0BH ;Select Counter 3 load register OUT STCCOM XCHG AX,DX OUT STCDATA MOV AL,AH OUT STCDATA MOV AL,44H ;Load counter 3 OUT STCCOM POINTSTAT: PUSH AX MOV AL,1FH ;Point to status register OUT STCCOM ; so power-off glitches won't hurt POP AX RET L STATUS: IN STAT AND AL,DAV RET L INP: IN STAT AND AL,DAV JZ INP IN DATA AND AL,7FH RET L OUTP: PUSH AX OUTLP: IN STAT AND AL,TBMT JZ OUTLP POP AX OUT DATA RET L PRINT: PUSH AX PRINLP: IN PRNSTAT AND AL,TBMT JZ PRINLP POP AX OUT PRNDATA RET L AUXIN: IN AUXSTAT AND AL,DAV JZ AUXIN IN AUXDATA RET L AUXOUT: PUSH AX AUXLP: IN AUXSTAT AND AL,TBMT JZ AUXLP POP AX OUT AUXDATA RET L ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * IF SCP+TARBELL+CROMEMCO WD1791: EQU SCP+TARBELLDD+CROMEMCO16FDC WD1771: EQU TARBELLSD+CROMEMCO4FDC IF WD1791 READCOM:EQU 80H WRITECOM:EQU 0A0H ENDIF IF WD1771 READCOM:EQU 88H WRITECOM:EQU 0A8H ENDIF IF SCP SMALLBIT:EQU 10H BACKBIT:EQU 04H DDENBIT:EQU 08H DONEBIT:EQU 01H DISK: EQU 0E0H DLYTIM: EQU 22 ENDIF IF TARBELL BACKBIT:EQU 40H DDENBIT:EQU 08H DONEBIT:EQU 80H DISK: EQU 78H DLYTIM: EQU 10 ; 24 usec delay after force interrupt ENDIF .  IF CROMEMCO SMALLBIT:EQU 10H BACKBIT:EQU 0FDH ; Send this to port 4 to select back. DDENBIT:EQU 40H DONEBIT:EQU 01H DISK: EQU 30H DLYTIM: EQU 22 ; 52 usec delay after force interrupt ENDIF IF SMALLDS-1 SMALLDDSECT: EQU 8 ENDIF IF SMALLDS SMALLDDSECT: EQU 16 ENDIF IF LARGEDS-1 LARGEDDSECT: EQU 8 ENDIF IF LARGEDS LARGEDDSECT: EQU 16 ENDIF ; ; I/O system disk change function. ; AL = disk drive number. ; Return AH = -1 if disk is changed. ; AH = 0 if don't know. ; AH = 1 if not changed. ; DSKCHG: MOV AH,0 SEG CS CMP AL,[CURDRV] JNZ RETL IN DISK AND AL,20H ; Look at head load bit JZ RETL MOV AH,1 RETL: RET L READ: CALL SEEK ;Position head JC ERROR RDLP: PUSH CX CALL READSECT ;Perform sector read POP CX JC ERROR INC DH ;Next sector number LOOP RDLP ;Read each sector requested OR AL,AL RET L WRITE: CALL SEEK ;Position head JC ERROR WRTLP: PUSH CX CALL WRITESECT ;Perform sector write POP CX JC ERROR INC DH ;Bump sector counter LOOP WRTLP ;Write CX sectors OR AL,AL RET L ERROR: MOV BL,-1 SEG CS MOV [DI],BL MOV SI,ERRTAB GETCOD: INC BL SEG CS LODB TEST AH,AL JZ GETCOD MOV AL,BL SHL AL STC RET L ERRTAB: DB 40H ;Write protect error DB 80H ;Not ready error DB 8 ;CRC error DB 2 ;Seek error DB 10H ;Sector not found DB 20H ;Write fault DB 7 ;Data error SEEK: ; Inputs: ; AL = Drive number ; BX = Disk transfer address in DS ; CX = Number of sectors to transfer ; DX = Logical record number of transfer ; Function: ; Seeks to proper track. ; Outputs: ; AH = Drive select byte ; DL = Track number ; DH = Sector number ; SI = Disk transfer address in DS ; DI = pointer to drive's track counter in CS ; CX unchanged. MOV AH,AL SEG CS XCHG AL,[CURDRV] CMP AL,AH ;Changing drives? JZ SAMDRV ;If changing drives, unload head so the head load delay one-shot ;will fire again. Do it by seeking to same track the H bit reset. IN DISK+1 ;Get current track number OUT DISK+3 ;Make it the track to seek to MOV AL,10H ;Seek and unload head CALL DCOM MOV AL,AH ;Restore current drive number SAMDRV: MOV SI,BX ; Save transfer address CBW MOV BX,AX ; Prepare to index on drive number IF CROMEMCO16FDC IN DISK+4 ; See if the motor is on. TEST AL,08H ENDIF SEG CS MOV AL,[BX+DRVTAB] ; Get drive-select byte. OUT DISK+4 ; Select drive IF CROMEMCO16FDC JNZ MOTORSON ; No delay if motors already on. PUSH AX PUSH CX MOV CX,43716 ; Loop count for 1 second. MOTORDELAY: ; (8 MHz, 16-bit memory). AAM ; 83 clocks. AAM  ; 83 clocks. LOOP MOTORDELAY ; 17 clocks. POP CX POP AX MOTORSON: ENDIF IF CROMEMCO OR AL,80H ; Set auto-wait bit ENDIF MOV AH,AL ; Save drive-select byte in AH. XCHG AX,DX ; AX = logical sector number. MOV DL,26 ; 26 sectors/track unless changed below IF SCP TEST DH,SMALLBIT ; Check if small disk. JZ BIGONE ; Jump if big disk. MOV DL,18 ; Assume 18 sectors on small track. TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,SMALLDDSECT ; Number of sectors on small DD track. JP HAVSECT BIGONE: TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump/  if not. MOV DL,LARGEDDSECT ; Number of sectors on big DD track. ENDIF IF TARBELLDD ; Tarbell DD controller. TEST DH,DDENBIT ; Check for double-density. JZ HAVSECT MOV DL,LARGEDDSECT ; Number of sectors on DD track. ENDIF IF CROMEMCO4FDC TEST DH,SMALLBIT ; Check if small disk. JNZ HAVSECT ; Jump if not. MOV DL,18 ; 18 sectors on small disk track. ENDIF IF CROMEMCO16FDC TEST DH,SMALLBIT ; Check if small disk. JNZ BIGONE ; Jump if big disk. MOV DL,18 ; Assume 18 sectors on small track. TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,SMALLDDSECT ; Number of sectors on small DD track. JP HAVSECT BIGONE: TEST DH,DDENBIT ; Check if double-density. JZ HAVSECT ; Jump if not. MOV DL,LARGEDDSECT ; Number of sectors on big DD track. ENDIF HAVSECT: DIV AL,DL ; AL = track, AH = sector. XCHG AX,DX ; AH has drive-select byte, DX = track & sector. INC DH ; Sectors start at one, not zero. SEG CS MOV BL,[BX+TRKPT] ; Get this drive's displacement into track table. ADD BX,TRKTAB ; BX now points to track counter for this drive. MOV DI,BX MOV AL,DL ; Move new track number into AL. SEG CS XCHG AL,[DI] ; Xchange current track with desired track OUT DISK+1 ; Inform controller chip of current track CMP AL,DL ; See if we're at the right track. JZ RET MOV BH,2 ; Seek retry count CMP AL,-1 ; Head position known? JNZ NOHOME ; If not, home head TRYSK: CALL HOME JC SEEKERR NOHOME: MOV AL,DL ; AL = new track number. OUT DISK+3 MOV AL,1CH+STPSPD ; Seek command. CALL MOVHEAD AND AL,98H ; Accept not ready, seek, & CRC error bits. JZ RET JS SEEKERR ; No retries if not ready DEC BH JNZ TRYSK SEEKERR: MOV AH,AL ; Put status in AH. TEST AL,80H ; See if it was a Not Ready error. STC JNZ RET ; Status is OK for Not Ready error. MOV AH,2 ; Everything else is seek error. RET SETUP: MOV BL,DH ; Move sector number to BL to play with IF SCP+CROMEMCO16FDC TEST AH,DDENBIT ; Check for double density. JZ CHECKSMALL ; Not DD, check size for SD. ENDIF IF TARBELLDD TEST AH,DDENBIT ; Check for double density. JZ CHECK26 ; Not DD. ENDIF IF WD1791 IF (SCP+TARBELL)*LARGEDS+SCP*SMALLDS MOV AL,AH ; Select front side of disk. OUT DISK+4 ENDIF IF CROMEMCO*(LARGEDS+SMALLDS) MOV AL,0FFH ; Select front side of disk. OUT 04H ENDIF CMP BL,8 ; See if legal DD sector number. JBE PUTSEC ; Jump if ok. IF (LARGEDS-1)*((SMALLDS*(SCP+CROMEMCO))-1) JP STEP ; If only SS drives, we gotta step. ENDIF IF SCP*LARGEDS*(SMALLDS-1) TEST AH,SMALLBIT ; Check for 5.25 inch disk. JNZ STEP ; Jump if small because SMALLDS is off. ENDIF IF SCP*SMALLDS*(LARGEDS-1) TEST AH,SMALLBIT ; Check for 8 inch disk. JZ STEP ; Jump if large because LARGEDS is off. ENDIF IF CROMEMCO16FDC*LARGEDS*(SMALLDS-1) TEST AH,SMALLBIT ; Check for 5.25 inch disk. JZ STEP ; Jump if small because SMALLDS is off. ENDIF IF CROMEMCO16FDC*SMALLDS*(LARGEDS-1) TEST AH,SMALLBIT ; Check for 8 inch disk. JNZ STEP ; Jump if large because LARGEDS is off. ENDIF IF LARGEDS+SMALLDS*(SCP+CROMEMCO) SUB BL,8 ; Find true sector for back side. CMP BL,8 ; See if ok now. JA STEP ; Have to step if still too0  big. IF SCP+TARBELLDD MOV AL,AH ; Move drive select byte into AL. OR AL,BACKBIT ; Select back side. OUT DISK+4 ENDIF IF CROMEMCO16FDC MOV AL,BACKBIT ; Select back side. OUT 04H ENDIF JP PUTSEC ENDIF ENDIF IF SCP CHECKSMALL: TEST AH,SMALLBIT ; See if big disk. JZ CHECK26 ; Jump if big. ENDIF IF CROMEMCO CHECKSMALL: TEST AH,SMALLBIT ; See if big disk. JNZ CHECK26 ; Jump if big. ENDIF IF SCP+CROMEMCO CMP BL,18 ; See if legal small SD/SS sector.  JA STEP ; Jump if not. ENDIF CHECK26: CMP BL,26 ; See if legal large SD/SS sector. JBE PUTSEC ; Jump if ok. STEP: INC DL ; Increment track number. MOV AL,58H ; Step in with update. CALL DCOM SEG CS INC B,[DI] ; Increment the track pointer. MOV DH,1 ; After step, do first sector. MOV BL,DH ; Fix temporary sector number also. PUTSEC: MOV AL,BL ; Output sector number to controller. OUT DISK+2 DI ; Interrupts not allowed until I/O done IN DISK ; Get head load bit  NOT AL AND AL,20H ; Check head load status JZ RET MOV AL,4 RET READSECT: CALL SETUP MOV BL,10 RDAGN: OR AL,READCOM OUT DISK IF CROMEMCO MOV AL,AH ; Turn on auto-wait. OUT DISK+4 ENDIF MOV BP,SI RLOOP: IF SCP IN DISK+5 ; Wait for DRQ or INTRQ. ENDIF IF TARBELL+CROMEMCO IN DISK+4 ENDIF IF TARBELL SHL AL JNC RDONE ENDIF  IF SCP+CROMEMCO SHR AL JC RDONE ENDIF IN DISK+3 MOV [SI],AL INC SI JP RLOOP RDONE: EI ; Interrupts OK now CALL GETSTAT AND AL,9CH JZ FORCINT MOV SI,BP MOV BH,AL ;Save error status for report MOV AL,0 DEC BL JNZ RDAGN MOV AH,BH ; Put error status in AH. STC FORCINT: MOV AL,0D0H ;Force Interrupt command for type I status OUT DISK  MOV AL,DLYTIM INTDLY: DEC AL ;Does not affect carry JNZ INTDLY ;Minimum loop time (19 clocks)=2.375 usec RET WRITESECT: CALL SETUP MOV BL,10 WRTAGN: OR AL,WRITECOM OUT DISK IF CROMEMCO MOV AL,AH ; Turn on auto-wait. OUT DISK+4 ENDIF MOV BP,SI WRLOOP: IF SCP IN DISK+5 ; Wait for DRQ or INTRQ. ENDIF IF TARBELL+CROMEMCO IN DISK+4 ENDIF IF TARBELL SHL AL JNC WRDONE ENDIF IF SCP+CROMEMCO SHR AL JC WRDONE ENDIF LODB OUT DISK+3 JP WRLOOP WRDONE: EI CALL GETSTAT AND AL,0FCH JZ FORCINT MOV SI,BP MOV BH,AL MOV AL,0 DEC BL JNZ WRTAGN MOV AH,BH ; Error status to AH. STC JP FORCINT IF SCP+(FASTSEEK-1)*TARBELL+CROMEMCO HOME: ENDIF IF FASTSEEK*SCP TEST AH,SMALLBIT ; Check for big disk. JZ RESTORE ; Big disks are PerSci. ENDIF IF FASTSEEK*CROMEMCO TEST AH,SMALLBIT ; Check for large disk. JNZ RESTORE ; Big disks are fast seek PerSci. ENDIF MOV BL,3 TRYHOM: MOV AL,0CH+STPSPD CALL DCOM AND AL,98H JZ RET JS HOMERR ; No retries if not ready MOV AL,58H+STPSPD ; Step in with update CALL DCOM DEC BL  JNZ TRYHOM HOMERR: STC RET IF SCP+(FASTSEEK-1)*TARBELL+CROMEMCO MOVHEAD: ENDIF IF CROMEMCO TEST AH,SMALLBIT ; Check for PerSci. JNZ FASTSK ENDIF DCOM: OUT DISK PUSH AX AAM ;Delay 10 microseconds POP AX GETSTAT: IN DISK+4 TEST AL,DONEBIT IF TARBELL JNZ GETSTAT ENDIF IF SCP+CROMEMCO JZ GETSTAT ENDIF IN DISK RET ; ; R1 ESTORE for PerSci drives. ; Doesn't exist yet for Tarbell controllers. ; Cromemco 4FDC restore is used for 16FDC which isn't real efficient ; but it works. Some ambitious person could fix this. ; IF FASTSEEK*SCP RESTORE: MOV AL,AH ; Get drive-select byte. OR AL,80H ; Turn on restore. OUTB DISK+4 SKWAIT: INB DISK+4 ; Wait for seek complete. TEST AL,40H JZ SKWAIT MOV AL,AH ; Turn off restore. OUTB DISK+4 XOR AL,AL ; Tell 1793 we're on track 0. OUTB DISK+1 RET ENDIF IF FASTSEEK*TARBELL HOME: RESTORE: RET ENDIF IF FASTSEEK*CROMEMCO RESTORE: MOV AL,0C4H ;READ ADDRESS command to keep head loaded OUT DISK MOV AL,77H OUT 4 CHKRES: IN 4 AND AL,40H JZ RESDONE IN DISK+4 TEST AL,DONEBIT JZ CHKRES IN DISK JP RESTORE ;Reload head RESDONE: MOV AL,7FH OUT 4 CALL GETSTAT MOV AL,0 OUT DISK+1 ;Tell 1771 we're now on track 0 RET ENDIF ; ; Fast seek code for PerSci drives. ; Tarbell not installed yet. ; IF FASTSEEK*TARBELL  MOVHEAD: FASTSK: RET ENDIF IF FASTSEEK*CROMEMCO FASTSK: MOV AL,6FH OUT 4 MOV AL,18H CALL DCOM SKWAIT: IN 4 TEST AL,40H JNZ SKWAIT MOV AL,7FH OUT 4 MOV AL,0 RET ENDIF CURDRV: DS 1 ; ; Explanation of tables below. ;  ; DRVTAB is a table of bytes which are sent to the disk controller as ; drive-select bytes to choose which physical drive is selected for ; each logical drive. It also selects whether the disk is 5.25-inch or ; 8-inch, single-density or double-density, to use side 0 or side 1 for ; single-sided operation. (Note: always select side 0 in the drive- ; select byte if double-sided double-density operation is desired). ; There should be one entry in the DRVTAB table for each logical drive. ; Exactly which bits in the drive-select byte do what depends on which ; disk controller is used. ; ; TRKTAB is a table of bytes used to store which track the read/write ; head of each drive is on. Each physical drive should have its own ; entry in TRKTAB. ; ; TRKPT is a table of bytes which indicates which TRKTAB entry each ; logical drive should use. Since each physical drive may be used for ; more than one logical drive, more than one entry in TRKPT may point ; to the same entry in TRKTAB. Drives such as PerSci 277s which use ; the same head positioner for more than one drive should share entrys ; in TRKTAB. ; ; INITTAB is the initialization table for 86-DOS as described in the ; 86-DOS Programer's Manual under "Customizing the I/O System." ; IF SCP*COMBIN*FASTSEEK DRVTAB: DB 00H,01H,10H,08H,09H,18H TRKPT: DB 0,0,1,0,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW SSDRIVE DW LDDRIVE DW LDDRIVE DW SDDRIVE DW 0 DW 30 ENDIF IF SCP*LARGEDS*(FASTSEEK-1) ; Drive A is drive 0, side 0, single-density. ; Drive B is drive 0, side 1, single-density. ; Drive C is drive 1, side 0, single-density. ; Drive D is drive 1, side 1, single-density. ; Drive E is drive 0, both sides, double density. ; Drive F is drive 1, both sides, double density. DRVTAB: DB 00H,04H,01H,05H,08H,09H TRKPT: DB 0,0,1,1,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW 0 ; Reserved buffer space. DW 30 ; Reserved stack space.2  ENDIF IF TARBELLDD*(LARGEDS-1) ;Drive A is drive 0, single density ;Drive B is drive 1, single density ;Drives C to F are drive 0 to 3, double density DRVTAB: DB 0,10H,8,18H,28H,38H TRKPT: DB 0,1,0,1,2,3 TRKTAB: DB -1,-1,-1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW 0 DW 30 ENDIF IF TARBELLDD*LARGEDS ;Drive A is drive 0, side 0, single density ;Drive B is drive 0, side 1, single density ;Drive C is drive 1, side 0, single density ;Drive D is drive 1, side 1, single density ;Drive E is drive 0, both sides, double density ;Drive F is drive 1, both sides, double density DRVTAB: DB 0,40H,10H,50H,8H,18H TRKPT: DB 0,0,1,1,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW 0 DW 30 ENDIF IF TARBELLSD DRVTAB: DB 0F2H,0E2H,0D2H,0C0H  TRKPT: DB 0,1,2,3 TRKTAB: DB -1,-1,-1,-1 INITTAB:DB 4 DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW 0 DW 30 ENDIF ; Cromemco drive select byte is derived as follows: ; Bit 7 = 0 ; Bit 6 = 1 if double density (if 16FDC) ; Bit 5 = 1 (motor on) ; Bit 4 = 0 for 5", 1 for 8" drives ; Bit 3 = 1 for drive 3 ; Bit 2 = 1 for drive 2 ; Bit 1 = 1 for drive 1 ; Bit 0 = 1 for drive 0 IF CROMEMCO4FDC*LARGE ; Table for four large drives DRVTAB: DB 31H,32H,34H,38H TRKPT: DB 0,0,1,1 TRKTAB: DB -1,-1 INITTAB:DB 4 ;Number of drives DW LSDRIVE DW LSDRIVE DW LSDRIVE DW LSDRIVE DW 0 DW 30 ENDIF IF CROMEMCO4FDC*COMBIN ; Table for two large drives and one small one DRVTAB: DB 31H,32H,24H TRKPT: DB 0,0,1 TRKTAB: DB -1,-1 INITTAB:DB 3 ;Number of drives DW LSDRIVE DW LSDRIVE DW SSDRIVE DW 0 DW 30 ENDIF IF CROMEMCO4FDC*SMALL ; Table for 3 small drives DRVTAB: DB 21H,22H,24H TRKPT: DB 0,1,2 TRKTAB: DB -1,-1,-1 INITTAB:DB 3 DW SSDRIVE DW SSDRIVE DW SSDRIVE DW 0 DW 30 ENDIF IF CUSTOM ; Table for 2 large drives without fast seek and a Cromemco 4FDC. DRVTAB: DB 31H,32H TRKPT: DB 0,1 TRKTAB: DB -1,-1 INITTAB:DB 2 DW LCDRIVE DW LSDRIVE DW 0 DW 30 ENDIF IF CROMEMCO16FDC*SMALL ; Table for three small drives. ; A, B, & C are single density, D, E, & F are double density. DRVTAB: DB 21H,22H,24H,61H,62H,64H  TRKPT: DB 0,1,2,0,1,2 TRKTAB: DB -1,-1,-1 INITTAB:DB 6 DW SSDRIVE DW SSDRIVE DW SSDRIVE DW SDDRIVE DW SDDRIVE DW SDDRIVE DW 0 DW 30 ENDIF IF CROMEMCO16FDC*COMBIN ; Table for 2 large drives (a PerSci 277 or 299), and one small. ; Drives A & B are the PerSci single density, ; C is the small drive single density, ; D & E are the PerSci double density, ; and F is the small drive double density. DRVTAB: DB 31H,32H,24H,71H,72H,64H TRKPT: DB 0,0,1,0,0,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW SSDRIVE DW LDDRIVE DW LDDRIVE DW SDDRIVE DW 0 DW 30 ENDIF IF CROMEMCO16FDC*LARGE ; Table for four large drives (2 PerSci 277s or 299s). ; Drives A - B are single density, C - F are double density. DRVTAB: DB 31H,32H,71H,72H,74H,78H TRKPT: DB 0,0,0,0,1,1 TRKTAB: DB -1,-1 INITTAB:DB 6 DW LSDRIVE DW LSDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW LDDRIVE DW 0 DW 30 ENDIF IF SMALL+COMBIN SSDRIVE: DW 128 ; Sector size in bytes. DB 2 3  ; Sector per allocation unit. DW 54 ; Reserved sectors. DB 2 ; Number of allocation tables. DW 64 ; Number of directory entrys. DW 720 ; Number of sectors on the disk. IF SMALLDS-1 SDDRIVE: ; This is the IBM Personal Computer DW 512 ; disk format. DB 1 DW 1 DB 2 DW 64 DW 320 ENDIF IF SMALLDS SDDRIVE: DW 512 DB 2 DW 1 DB 2 DW 64 ; 64 directory entrys for small DDDS? DW 640 ENDIF ENDIF ; End of small drive DPTs. IF COMBIN+LARGE LSDRIVE: DW 128 DB 4 DW 52 DB 2 DW 64 DW 2002 IF LARGEDS-1 LDDRIVE: DW 1024 DB 1 DW 1 DB 2 DW 96 DW 616 ENDIF  IF LARGEDS LDDRIVE: DW 1024 DB 1 DW 1 DB 2 DW 128 DW 1232 ENDIF ENDIF ; End of large drive DPTs. ENDIF ; End of 1771/1793 disk drivers. ; * * * * * * * * * * * * * * * * * * * * * * * IF NORTHSTARSD ; North Star disk controller addresses. ; DSKSEG: EQU 0FE80H ; `F' is for extended address modification. WRTADR: EQU 200H CMMND: EQU 300H DRVSEL: EQU 1H WRTSEC: EQU 4H STPOFF: EQU 8H STPON: EQU 9H NOP: EQU 10H RSETSF: EQU 14H STPOUT: EQU 1CH STPIN: EQU 1DH BSTAT: EQU 20H RDBYTE: EQU 40H MOTOR: EQU 80H ; ; Status bits. ; TK0: EQU 01H ; Track 0 bit. WP: EQU 02H ; Write protect bit. BDY: EQU 04H ; Data body (sync byte) found. WRT: EQU 08H ; Write bytes status flag. MO: EQU 10H ; Motor on. SF: EQU 80H ; Indicates sector hole was detected. ; ; Delay times in sectors for various disk functions. ; MOTORD: EQU 31 ; Motor up-to-speed time (1 second). HEADD: EQU 14 ; Head-load settle time. Actually, the head ; doesn't require this much time to settle, ; but this much time is required to ; synchronize the sector counter. STEPD: EQU 2 ; Step time. One or two only. ; 1 -> 20mS, 2 -> 40mS. ; ; Various numbers of things. ; NSECT: EQU 10 ; 10 North Star sectors per track. NTRACK: EQU 35 ; 35 tracks on standard SA-400 drive. ERRLIM: EQU 10 ; Number of soft errors. ; ; READ and WRITE functions.  ; AL = drive number. ; CX = Number of sectors to transfer. ; DX = Logical record number. ; DS:BX = Transfer address. ; READ: MOV AH,1 ; AH = 1 to read. JP READWRITE WRITE: MOV AH,0 ; AH = 0 to write. READWRITE: CMP DX,350 ; See if too large a sector number is requested JB SECTOROK ; Jump if OK. MOV AL,0CH ; Error type C, "data error". STC ; Set CY flag to indicate error. RET L ; Quit immediatly. SECTOROK: MOV SI,BX ; Transfer address to SI & DI. MOV DI,BX UP ; Set direction flag for autoincrement. PUSH ES ; Store extra segment. MOV BX,DS ; Put data segment in extra segment. MOV ES,BX PUSH DS ; Save data segment. MOV BX,DSKSEG ; DS is North Star controller segment. MOV DS,BX PUSH AX ; Store read/write flag. CBW ; Drive number is sixteen bits. MOV BX,AX ; Put in BX. MOV AX,DX ; Compute track & sector. MOV DL,NSECT ; Ten sectors/track. DIV AL,DL ; AL = track number, AH = sector number. MOV CH,AH ; Sector number to CH. PUSH CX ; Save sector number & number of sectors. MOV DH,AL ; Put track number in DH. SEG CS ; TRACKTAB is in the code segment. MOV AH,[BX+TRACKTAB] ; Find out what the current track is. SEG CS MOV [BX+TRACKTAB],DH ; Update TRACKTAB. MOV BP,CMMND+MOTOR+STP4 IN ; Assume step direction is in. MOV CL,DH ; Put track number in CL. SUB CL,AH ; Calculate how many steps required. JAE DIRECTION ; Direction is correct if >= 0. DEC BP ; Direction is out (STPOUT = STPIN-1). NEG CL ; Make number of steps positive. DIRECTION: IF STEPD-1 ; Multiply number of steps by two if step delay SAL CL ; is 40mS per step. ENDIF TEST B,[CMMND+MOTOR+NOP],MO ; Turn motors on & check MO status. JZ MOTORS ; If motors were off, wait for them to start. SEG CS ; OLDDRIVE is in the code segment. CMP BL,[OLDDRIVE] ; See if the correct drive is selected. JNZ SELECT ; If wrong drive is selected, select right one. JP SEEK ; Motors on, drive selected, go and step. MOTORS: MOV DL,MOTORD ; Wait for motors to come up to speed. CALL WSECTOR SELECT: CALL ONESECT ; Wait for write gate to go off. MOV AL,[BX+CMMND+MOTOR+DRVSEL] ; Select new drive. SEG CS ; OLDDRIVE is in code segment. MOV [OLDDRIVE],BL ; Update OLDDRIVE. MOV DL,HEADD-1 ; Full head load delay (-1 because waiting for ; the correct sector delays at least one more) MOV AL,CL ; See if we've ever used the drive before. IF STEPD-1 ; Compute the actual number of steps if 40mS SAR AL ; step delay is used. ENDIF CMP AL,NTRACK ; If the number of steps is >= NTRACK, we can't JAE HEADDELAY ; count on step time for head load delay. SUB DL,CL ; Subtract stepping time. JB SEEK ; Don't wait if we'll step long enough for the ; head to settle & the sector counter to sync. HEADDELAY: CALL WSECTOR SEEK: IF STEPD-1 ; Convert back to the actual number of steps SAR CL ; rather than step time if the step time ENDIF ; is 40mS per step. XOR CH,CH ; CX = CL. JCXZ SEEKCOMPLETE ; Jump if we're already there. SEG DS ; BP normally uses stack segment. MOV AL,[BP] ; Set the step direction. CALL ONESECT ; Wait for the write gate to turn off. ; ; Step routine. Step direction has already been given to the disk ; controller. DH has destination track number.  ; CX has number of sectors to step, >= 1. ; If track zero is ever reached, the head position is recalibrated using DH. ; STEP: MOV AL,[CMMND+MOTOR+NOP] ; Get `A' status. ROR AL ; Track 0 bit to CF. JNC STEPOK ; Recalibrate if track zero. MOV CL,DH ; Track # to step count. JCXZ SEEKCOMPLETE ; If destinination = 0, we're there. MOV AL,[CMMND+MOTOR+STPIN] ; Set direction. STEPOK: MOV AL,[CMMND+MOTOR+STPON] AAM ; Waste time for > 10 uS. MOV AL,[CMMND+MOTOR+STPOFF] MOV DL,STEPD ; Step time (sectors). CALL WSECTOR LOOP STEP ; Loop till we get there. SEEKCOMPLETE: POP CX ; Restore sector number & number of sectors. MOV BP,BX ; Put drive number in BP. SECTORLOOP: MOV DH,ERRLIM ; Soft error limit. ERRORRETRY: DI ; Interrupts illegal till after read, write, WAITSECTOR: ; or error. CALL ONESECT ; Wait for next sector to come by. MOV AL,[CMMND+MOTOR+BSTAT+NOP] ; Get `B' status. AND AL,0FH ; Mask to sector number. CMP AL,CH JNE WAITSECTOR ; Wait till the one we want comes by. POP AX ; Get function. PUSH AX ; Back on the stack for next time. AND AH,AH ; AH = 1 -> read, AH = 0 -> write. JZ WRITESECTOR ; Jump if write. ; READSECTOR: MOV SI,CMMND+MOTOR+RDBYTE+NOP PUSH CX ; Save sector number an5 d number of sectors. MOV CX,352 ; Time limit for sync byte. 352 passes through ; the loop @ 35 clocks/pass = 24 byte times. RSYNCLP: TEST B,[CMMND+MOTOR+NOP],BDY LOOPZ RSYNCLP ; Test for sync byte. Loop till sync or timeout JNZ READSECT ; Found sync byte, read data bytes. MOV AL,8 ; Error number 8, "Record Not Found". JP ERROR READSECT: MOV CX,256 ; Byte count. MOV DL,CL ; CRC = 0. READLOOP: AAD ; Waste time >= 7.5 uS. MOV AL,[SI] ; Read a byte. STOB ; Store byte.  XOR DL,AL ; Compute CRC. ROL DL LOOP READLOOP ; Loop for 256 bytes. AAD ; Waste time >= 7.5 uS. MOV AL,[SI] ; Get CRC from disk. CMP AL,DL ; Same as computed? JE NEXTSECTOR ; Jump if sucessful read. SUB DI,256 ; Back-up the index for retry. MOV AL,4 ; Error number 4, "CRC Error". ERROR: EI ; Interrupts OK now. POP CX ; Get sector number & number of sectors. DEC DH ; Decrement error count. JNZ ERRORRETRY ; Wait for the sector to come by again. POP BX ; Pop junk off the stack. POP DS ; Pop segment registers. POP ES XOR CH,CH ; CX is number of sectors left to read. STC ; Set CY flag to indicate error. RET L ; Return. ; WRITESECTOR: TEST B,[CMMND+MOTOR+NOP],WP JZ NOTPROT ; Jump if not protected. EI  ; Interrupts OK now. POP AX ; Pop junk off the stack. POP DS POP ES XOR CH,CH ; CX = number of sectors left to write. MOV AL,CH ; AL = 0 to indicate write protect. STC ; Set CY flag to indicate error. RET L NOTPROT: PUSH CX ; Save sector number and number of sectors. MOV AL,[CMMND+MOTOR+WRTSEC] WWRT: TEST B,[CMMND+MOTOR+NOP],WRT JZ WWRT ; Loop till WRT bit goes high. MOV CX,15 ; Number of zeros to write. MOV BX,WRTADR ; Address to write zeros. WRTZERO: MOV AL,[BX] ; Write a zero. AAD ; Waste time for >= 7.5 uS. LOOP WRTZERO ; Write 15 of them. MOV BL,0FBH ; Sync byte. MOV AL,[BX] ; Write sync byte. AAD ; Waste time for >= 7.5 uS. MOV CX,256 ; Byte count. MOV DL,CL ; CRC = 0. WRTBYTE: SEG ES  ; Data is in extra segment. LODB ; Get write data. MOV BL,AL ; Data to BL to write. MOV AL,[BX] ; Write it. AAD ; Waste time for >= 7.5 uS. XOR DL,BL ; Compute CRC. ROL DL LOOP WRTBYTE ; Write 256 bytes. MOV BL,DL ; Write CRC byte. MOV AL,[BX] ; NEXTSECTOR: EI ; Interrupts OK now. POP CX ; Get sector count. DEC CL ; Decrement sector count. JZ OKRETURN ; Return if done. INC CH ; Increment sector number. CMP CH,10 ; Compare with number of sectors on track. JAE NEEDSTEP JMP SECTORLOOP ; Read another sector from same track. NEEDSTEP: MOV CH,0 ; Reset sector number. CALL ONESECT ; Wait for write gate to go off. MOV AL,[CMMND+MOTOR+STPIN] MOV AL,[CMMND+MOTOR+STPON] AAM ; Wait > 10 uS for step pulse width. MOV AL,[CMMND+MOTOR+STPOFF] SEG CS ; BP normally uses stack segment. INC B,[BP+TRACKTAB] ; Increment the track table. ; We don't have to wait for STEPD because ; waiting for the write gate to go off caused ; us to blow the sector and we have to wait ; a whole revolution anyway. JMP SECTORLOOP ; Read a sector from the new track. OKRETURN: POP AX ; Get function, AH=0 -> write, AH=1 -> read. POP DS ; Get original data & extra segments. POP ES CLC ; No errors6 . RET L ; ; Wait for sector routine. ONESECT waits for the next sector. ; WSECTOR waits the number of sectors given by DL. ; ONESECT: MOV DL,1 ; Wait for next sector. WSECTOR: MOV AL,[CMMND+MOTOR+RSETSF] SECTLOOP: MOV AL,[CMMND+MOTOR+NOP]  TEST AL,SF ; Check sector flag. JZ SECTLOOP ; Loop till new sector. DEC DL ; Decrement sector count. JNZ WSECTOR ; Loop till zero. RET ; DSKCHG: MOV AH,0 ; AH = 0 in case we don't know. SEG CS CMP AL,[OLDDRIVE] ; See if that's the last drive used. JNE RETL ; Return if not. PUSH DS ; See if the motors are still on. PUSH BX MOV BX,DSKSEG MOV DS,BX TEST B,[CMMND+NOP],MO POP BX POP DS JZ RETL ; Motors off, disk could be changed. MOV AH,1 ; If motors on, assume disk not changed. RETL: RET L ; ; Disk initialization tables. ; INITTAB: DB 3 ; Three drives. DW DPT,DPT,DPT ; Address of disk parameter tables. DW 0 ; Minimum buffer space DW 30 ; Stack space ; DPT: DW 256 ; Sector size. DB 1 ; One sector per allocation unit. DW 30 ; Number of sectors allocated to system. DB 2 ; Two allocation tables. DW 64 ; Number of directory entries. DW 350 ; Number of sectors on the disk. ; ; Storage locations for the disk drivers. ; OLDDRIVE: DB 0 ; Old drive will be number 0 after boot. TRACKTAB: DB 2 ; Drive 0 will be on track 2 after boot. DB NTRACK-1+NTRACK-1+24 ; Number of steps to restore the head DB NTRACK-1+NTRACK-1+24 ; if never used before. ENDIF ; End North Star disk drivers DOSSEG: EQU ($+15)/16+40H ; Compute segment to use for 86-DOS.  ; Address to write zeros. WRTZERO: MOV AL,[BX] ;7 8 9 : ; < = > ? @ A B C D E F G H I J K L