[comp.lang.modula2] Floppy Format Program for XT

aubrey@rpp386.cactus.org (Aubrey McIntosh) (01/03/90)

This program works in MS-DOS under Logitech 3.x.  It is hard coded
to format a 3.5" drive in drive A: (/dev/fd0) to 720kb.  The user
must provide FAT and empty Directory (mkfs must also be run).

There is no I/O, so there is no concern with different calling conventions
between systems:  this talks directly with DMA and the NEC765.  
NO SYSTEM CALLS are made.  The ROM bios is used, but is intact during
the Minix bootup menu period.


I am looking for someone to correspond with as I translate it into C.
I know Zilch about C, and am still trying to get 1.1 --> 1.2 upgrades
from NoDak.  Actually, I'm looking for help there too.

If no one comes forth, I may cheat and post format.a in hand translation.

------------------------format.mod---------------------



(*	
 * NOTICES
 *	Aubrey McIntosh		
 *	December 4, 1989
 *	Austin, Texas
 *
 *	world!cs.utexas.edu![ rpp386.Cactus.ORG | val.COM ]!aubrey
 *
 *	Contract Programming Services available.
 *
 *	Copyright 1989  by Aubrey McIntosh.
 *	Permission granted to make derivative work, and to distribute
 *	for any reasons, provided that this comment remains intact,
 *	derivations are identified by author(s), and original works
 *	can be identified.
 *
 * BACKGROUND
 *	This file contains a study for a floppy disk format program 
 *	running under Minix.  Unfortunately I haven't written C.  However,
 *	'we' can translate a working Modula-2 program into C or .asm
 *	with some confidence.  Actually (12/28/89) this draft just
 * 	talks with the NEC765, with little regard for Minix.
 *	 
 *	Minimal guidance for identifier names and structure taken from 
 *	the floppy.c driver under Minix 1.1
 *
 *	This program (shall) run(s) under MS-DOS with the Logitech 3.x
 *	environment.  
 *
 * USAGE
 *	In MS-DOS, type 
 *	c:> format0
 *	Drive 0 will be formatted as a 720kB 3.5" floppy. (hard coded const)
 *	use Norton to write dir stock and virgin fat.
 *      --- This works. ---
 *
 *	In Minix:
 *	  Not yet ported.  See future work section.
 *
 * FUTURE WORK NEEDED
 *	
 *	Write a Modula-2 compiler for Minix.	:-)
 *	  anyone who has worked on *nix .DEF files ~please~ drop me a line,
 *	  If I do it, everyone will cuss at ~me.~~
 *
 *	Add command line switches for:
 *	1)  Drive type.
 *	2)  Which Drive.
 *	3)  Reformatting with data intact.
 *	4)  Robustness, e.g. write protected drive
 *
 *	January 2, 1990.
 *
 *        Overview of future work and program environment.
 *
 *	  This program will need to be included by build with fsck and init.
 *        I have not looked at that code to see what needs to be done.
 *        My current idea is to compile this 'by hand' or else to obtain
 *        the newly announced Modula-3 compiler which produces C output,
 *        and end up with the format0.s and executable Minix files.
 *        I further understand that the ROM bios interrupt tables and
 *        hardware initialisation is still in the default state when the
 *        boot menu is active.
 *        
 *	  An option, d Diskette format, should be added to the startup menu.
 *	  mkfs will be called by the user from the same startup menu.
 *
 *	  A decode file, roughly compatible to 'format0.asm,' is available
 *	  for the intrepid.
 *
 * REFERENCES
 *	IBM Technical Reference Manual, pn 6025008.
 *	Minix 1.1 sources, floppy.c, Prentice Hall.
 *	NEC uPD765 Data Sheet.
 *	Logitech users manual.
 *)
 
 
MODULE Format0;
 
  IMPORT DebugPMD, RTSMain, SYSTEM;
 
  
  MODULE NEC765;

    IMPORT 	SYSTEM, RTSMain,
    		panic, Relinquish,
    		seekStatus, STATUSport, DATAport, RATE;
    		
    EXPORT
    		DoRecalibrate, 
    		DoSeek, 
    		DoSenseInterruptStatus, 
    		DoFormatATrack,
    		
    		(* *)
    		    		
    		SR0Bits, HD;
    		
    		
    (*  Page 6-4 of 1984 NEC data book.
     *  Primitive I/O support for the 765.
     *
     *  This module should support anything that the NEC765 will do,
     *  and could be made into a separate module and submitted to a
     *  library.
     *)
     
     
    TYPE
      MainStatus 	= ( D0B,  D1B,  D2B, D3B,   CB, EXM, DIO,   RQM );
      SR0		= ( US0,  US1,  HD,  NR,    EC, SE,  D6,    D7 );
      SR1		= ( MA,   NW,   ND,  res13, or, DE,  res16, EN );
      SR2		= ( MD,   BC,   SN,  SH,    WC, DD,  CM,    res27);
      SR3		= ( US03, US13, HD3, TS,    T0, RY,  WP,    FT);
      
      MainStatusBits 	= SET OF MainStatus;
      SR0Bits		= SET OF SR0;
      SR1Bits		= SET OF SR1;
      SR2Bits		= SET OF SR2;
      SR3Bits		= SET OF SR3;
      
      
    PROCEDURE DoRecalibrate( (* fdn : CARDINAL *) );
    
      VAR
        results : SR0Bits;
        PCN : CARDINAL;

    BEGIN
      WriteDataRegisterFile( Recalibrate ); 
      WriteDataRegisterFile( SR0Bits { } (* Drive 0 *) ); 
      REPEAT
      UNTIL  7 IN seekStatus;
      EXCL( seekStatus, 7 );
      DoSenseInterruptStatus( results, PCN )    
    END  DoRecalibrate;

    
    PROCEDURE DoSeek( Head, NCN : CARDINAL  );
    
      VAR
        results : SR0Bits;
        PCN     : CARDINAL;

    BEGIN
      WriteDataRegisterFile( Seek ); 
      IF Head = 0
      THEN
        WriteDataRegisterFile( SR0Bits {    } (* Head 0 *) )
      ELSE
        WriteDataRegisterFile( SR0Bits { HD } (* Head 1 *) )
      END; 
      WriteDataRegisterFile( NCN ); 
    END  DoSeek;

    PROCEDURE DoSenseInterruptStatus(
      	       VAR st0 : SR0Bits;
      	       VAR PCN : CARDINAL);
    BEGIN
      WriteDataRegisterFile( SenseInterruptStatus ); 
      ReadDataRegisterFile( st0 );
      ReadDataRegisterFile( PCN )
    END DoSenseInterruptStatus;
    
    
    PROCEDURE DoFormatATrack( Head : CARDINAL;
                              Bytes,
                              Sectors : CARDINAL );
      VAR
        aux : CARDINAL;
        
    (*The DMA should be armed before this is called.*)
    BEGIN
      WriteDataRegisterFile( MFM + FormatATrack ); 
      IF Head = 0
      THEN
        WriteDataRegisterFile( SR0Bits {    } (* Head 0 *) )
      ELSE
        WriteDataRegisterFile( SR0Bits { HD } (* Head 1 *) )
      END; 
      WriteDataRegisterFile( Bytes );
      WriteDataRegisterFile( Sectors );
      IF (Bytes = 3) AND (Sectors = 5)
      THEN (* 3.5" p. 6-11 in Nec Book for Sony drive *)
        WriteDataRegisterFile( 74H )
      ELSE
        WriteDataRegisterFile( 54H )
      END;
      WriteDataRegisterFile( 0H );
      
      REPEAT (*relinquish*) UNTIL  7 IN seekStatus;
      EXCL( seekStatus, 7 );
      
      ReadDataRegisterFile( aux );
      ReadDataRegisterFile( aux );
      ReadDataRegisterFile( aux );
      (*Manditory read of meaningless (sic) results...*)
      ReadDataRegisterFile( aux );
      ReadDataRegisterFile( aux );
      ReadDataRegisterFile( aux );
      ReadDataRegisterFile( aux );
    END DoFormatATrack;

    
    
    PROCEDURE ReadMainStatus(): MainStatusBits;
    
    (*  Wait 12 usec or longer for the chip to know what it did and update
     *  the registers, then read the port.  As I read the data sheets,
     *  it will not do to loop poll the chip -- although that is what
     *  Minix 1.1 floppy.c in fact does do.
     *)
     
      VAR
        result	: MainStatusBits;
        
    BEGIN
      Relinquish( 12 ); 
      SYSTEM.INBYTE ( STATUSport, result );
      RETURN result
    END ReadMainStatus;
    
    
    PROCEDURE ReadDataRegisterFile( VAR file : ARRAY OF SYSTEM.WORD );
      VAR
        count : CARDINAL;
        
    BEGIN
      REPEAT 
        (* This should always conclude. *) 
        (* Jan 2, 1990.
         * To be safe, I should actually do more testing on the 
         * status bits to be sure I'm not here while the NEC765 is
         * waiting for the last byte of a command.  But this works
         * if the next higher level is correct, and I'm gonna release it.
         *)
      UNTIL (ReadMainStatus ()
            * MainStatusBits{ RQM, DIO })
              =  MainStatusBits{ RQM, DIO };
      count := 0;
      
      WHILE (ReadMainStatus () 
         * MainStatusBits{ RQM, DIO} = MainStatusBits { RQM, DIO })
         AND ( count <= HIGH(file) )
      DO
        SYSTEM.INBYTE( DATAport, file[ count ] );
        INC( count )
      END
      (* Jan 2, 1990
       * This procedure was designed to be called with a packet parameter.
       * Then I broke the higher level up during debugging.
       * I stripped out all the packet data definitions.
       * After I put them back, I would 
       *    assert( count = HIGH(file));
       *)
    END ReadDataRegisterFile;
    
    
    
    PROCEDURE WriteDataRegisterFile( file : ARRAY OF SYSTEM.WORD );
      VAR
        count : CARDINAL;
        
    BEGIN
      REPEAT 
        (*wait. This should always conclude. *) 
      UNTIL (ReadMainStatus () 
            * MainStatusBits{ RQM, DIO })
              =  MainStatusBits{ RQM };
      count := 0;
      WHILE (ReadMainStatus ()
         * MainStatusBits{ RQM, DIO} = MainStatusBits { RQM })
         AND ( count <= HIGH(file) )
      DO
        SYSTEM.OUTBYTE( DATAport, file[ count ] );
        INC( count )
      END
    END WriteDataRegisterFile;
    
    
    
    CONST
      MT		= 80H; (* The multi track bit command (PD7265 only) *)
      MFM    		= 40H;
      SK		= 20H; (* Skip Deleted sector, read next *)
      
      (*These are listed here in the order that they are introduced
       *on page 6-6 in the NEC literature.  I don't see the pattern.
       *)
       
      ReadData 			= 06H;  (* 00110 *)
      ReadDeleted 		= 0CH;  (* 01100 *)
      WriteData			= 05H;  (* 00101 *)
      WriteDeleted		= 09H;  (* 01001 *)
      ReadATrack		= 02H;	(* 00010 *)
      ReadId			= 0AH;  (* 01010 *)
      FormatATrack		= 0DH;  (* 01101 *)
      ScanEqual			= 11H;	(* 10001 *)
      ScanLowerEqual		= 19H;	(* 11001 *)
      ScanHighEqual		= 1DH;  (* 11101 *)
      Recalibrate		= 07H;  (* 00111 *)
      SenseInterruptStatus	= 08H;  (* 01000 *)
      Specify			= 03H;  (* 00011 *)
      SenseDriveStatus		= 04H;  (* 00100 *)
      Seek			= 0FH;  (* 01111 *)
      
  END NEC765;
      		
   
  (* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *)
  
  
  MODULE DiskAdapter;
  
  (*  
   *  This is board designer stuff, and the nec765 module (should be|
   *  is purely) applicable to the NEC765 and has no board designer
   *  modifications in it.
   *  
   *  I read the schematics in the IBM Technical Reference,
   *  pn 6025008, Revised Edition, July 1982.  The various values
   *  agree with that schematic, the NEC 765 Data sheet, and floppy.c
   *
   *  This is not intended to be complete enough for a separate
   *  module in a library, but just 'nuf to get the code out the door.
   *
   *)
   
    IMPORT SYSTEM;
    EXPORT
      STATUSport, DATAport, RATE, seekStatus,
      MotorOn, MotorOff;
      
      
    CONST
      (*  IBM-PC magic I/O addresses.
       *  Resolved with floppy.c, BIOS, and schematic.
       *)
       
      DOR	= 03F2H;	
      STATUSport= 03F4H;
      DATAport	= 03F5H;
      RATE	= 03F7H;	(* not on XT *)
      
      ENABLEINT = 0CH;
      MOTORMASK = 0F0H;
      
    TYPE
      ControlDOR = ( 
      		     DriveSelect0, DriveSelect1, RST765, DMA2Enable,
      		     Motor1, Motor2, Motor3, Motor4 ,
      		     res0, res1, res2, res3,	(*A packed byte*)
      		     res4, res5, res6, res7
      		   );
                     
      DORBits    = SET OF ControlDOR;
      
    VAR
      (*ROM bios accomodation*)
      seekStatus [ 0040H:003EH ] : BITSET;
      status     [ 0040H:003FH ] : DORBits;
      BitImage   [ 0040H:003FH ] : SYSTEM.BYTE;
      
    PROCEDURE MotorOn;
    BEGIN
      status := status  + DORBits { Motor1 } 
      	 		- DORBits { Motor2, Motor3, Motor4}
      			(*Hard coded for drive 0*)
      			- DORBits { DriveSelect0, DriveSelect1 }
      			(*Set watchdog timer count at 40:40*)
      			+ DORBits { res0..res7 };
      SYSTEM.OUTBYTE( DOR, BitImage )
    END MotorOn;

      
    PROCEDURE MotorOff;
    BEGIN
      status := status - DORBits { DriveSelect0, DriveSelect1 }
                       - DORBits { Motor1 .. Motor4 };
      SYSTEM.OUTBYTE( DOR, status )
    END MotorOff;

  BEGIN
  (*
    (*Hmm, if the machine booted, the controller has been
     *initialized ok!
     *)
    status := DORBits{ DMA2Enable  };
    SYSTEM.OUTBYTE( DOR, status )
    INCL( status, RST765 );
   *)
    status := DORBits{ RST765  , DMA2Enable  };
    SYSTEM.OUTBYTE( DOR, status );
   
   
  END DiskAdapter;
  
      		
   
  (* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *)
  
  
  
  MODULE OtherSysStuff;
  (*These are suggestions for other goodies in the library.
   *)
   
    IMPORT SYSTEM;
    EXPORT panic, Relinquish, SetupDMA;
    
    
    PROCEDURE panic( msg : ARRAY OF CHAR);
    BEGIN
      (* I set a breakpoint in the RTD.
       * Again, this is prototype activity.
       *)
    END panic;
    
    
    
    PROCEDURE Relinquish( time : CARDINAL );
    
    (* On a blindingly fast machine, this would do a call
     * into the scheduler or message system, and let other
     * work be done while 12 micro seconds crawled by.
     *)
     
      CONST
        usec 	 = 5; 		(* I don't know a correct value. *)
        overhead = 0 * usec;	(* This'un either *)
        
      VAR
        ix	: CARDINAL;

    BEGIN
      FOR ix := overhead TO time * usec DO     (*nothing*) 
        (*Can You say optimizer interference?*)
      END;
    END Relinquish;
    
    
    
    PROCEDURE SetupDMA( 
    			    dmaout : BOOLEAN;
    			VAR buffer : ARRAY OF SYSTEM.WORD);
        (*nb  
         **************************************************
         * 
         * WARNING:  ESCAPED HACKER IN YOUR VICINITY.
         * RESIDENTS ARE URGED TO ADOPT EXTREEM CAUTION.
         * HE IS REPORTED TO EXHIBIT CUTE CODE WITHOUT PROVOCATION.
         * ARTIFACTS MAY BECOME DANGEROUS WHEN MOVED TO NEW ENVIRONMENTS.
         * 
         **************************************************
         * Not Portable.  Uses knowledge of compiler output.
         * Be wary of optimizers.
         * PROC is almost guaranteed to be the wrong size.
         * Be careful if you move variables,
         *    or even change compiler switches. :-)
         **************************************************
         * I guess folks do this all the time in C without warnings, and
         * call it 'close to the machine' huh?
         *)
         
      TYPE
        ParamStackPtr = POINTER TO ParamStack;
        ParamStack =  
          RECORD
            itself : ParamStackPtr;
            BP	   : CARDINAL;
            return : PROC;
            Offset : CARDINAL;
            Segment: CARDINAL;
            Size   : CARDINAL
          END;
          
      VAR
        top  : CARDINAL;
        aux  : CARDINAL;
        count: CARDINAL;
        hook : ParamStackPtr;   
         
      CONST
        DMAWRITE  = 04AH;  
        DMAREAD   = 046H;
        DMAStatus = 00BH;
        DMATOP    = 081H;
        DMAADDR   = 004H;
        DMACOUNT  = 005H;
        DMAINIT   = 00AH;
      
    BEGIN
      hook := SYSTEM.ADR( hook );
      WITH hook^ DO
        IF Size # HIGH( buffer )
        THEN
          panic( 'Stack structure error in SetupDMA.' )
        END;
        aux := (Offset DIV 16) + Segment;
        top := aux DIV 1000H;
        Offset := (Offset MOD 16) + 16 * (aux MOD 1000H);
        IF 0FFFFH - SYSTEM.SIZE(buffer) <= Offset
        THEN panic ( "Can't set up DMA." )
        END;
        IF dmaout 
        (*For some reason Minix sends the status command to
         *two DMA channels.  So does the ROM bios listing.
         *I don't comprehend why.  I don't do it.  The code works.
         *
         *What if I'm using DMA for some other device also?
         *)
        THEN   
          SYSTEM.OUTBYTE( DMAStatus, DMAWRITE )
        ELSE
          SYSTEM.OUTBYTE( DMAStatus, DMAREAD )
        END;
        SYSTEM.OUTBYTE( DMAADDR, Offset MOD 256 );
        SYSTEM.OUTBYTE( DMAADDR, Offset DIV 256 );
        SYSTEM.OUTBYTE( DMATOP, top );
        count := ((1 + HIGH( buffer )) * 2) - 1; (* Byte Count - 1 *)
        SYSTEM.OUTBYTE( DMACOUNT, count MOD 256 );
        SYSTEM.OUTBYTE( DMACOUNT, count DIV 256 );
        SYSTEM.OUTBYTE( DMAINIT, 2 )
      END
    END SetupDMA;


  END OtherSysStuff;
  
      
      		
   
  (* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *)
  
  
    
  
  
  
  
  CONST
  (*
    Sectors = 5;
    Bytes   = 3 (* 512 -- Note: this is ln2(size)-7 NOT size/128*);
   *)
   
    Sectors = 9;
    Bytes   = 2 (* 512 -- Note: this is ln2(size)-7 NOT size/128*);
    Tracks  = 80;
    Sides   = 2;
    Out	    = TRUE;
    
  VAR
    track    : ARRAY [ 0 .. 9 * 512-1 ] OF CARDINAL;
    continue : BOOLEAN;
    tracks   : CARDINAL;
    sectors  : CARDINAL;
    sides    : CARDINAL;
    CHRN     : (*This is byte packed -- 4 bytes long*)
      ARRAY [ 0..Sectors-1 ] OF
        RECORD
          CH,
          RN  :  CARDINAL
        END;
    hackTrack    : PROCEDURE( BOOLEAN, VAR ARRAY OF SYSTEM.WORD );
    results : SR0Bits;
    PCN     : CARDINAL;

          
BEGIN
  hackTrack := SetupDMA;	(* ... defeat a compiler optimisation...*)
  
  WITH CHRN[0] DO
    CH := 0;
    RN := Bytes * 100H
  END;
  FOR sectors := 0 TO Sectors - 1 DO
    CHRN[ sectors ] := CHRN [0];
    CHRN[ sectors ].RN := (sectors + 1) + 100H * Bytes
  END;
  continue := TRUE;   
  (* I've got an RTD, 'continue' changes by magic;  this is prototyping! *)
  WHILE continue DO
    MotorOn;
    DoRecalibrate;
    FOR tracks := 0 TO Tracks - 1 DO
      MotorOn;
      FOR sides := 0 TO Sides - 1 DO
        DoSeek( sides, tracks );
        FOR sectors := 0 TO Sectors - 1 DO
          CHRN[ sectors ].CH := tracks + 100H * sides
        END;
        
        REPEAT (*wait*)
        UNTIL  7 IN seekStatus;
        EXCL( seekStatus, 7 );
        DoSenseInterruptStatus( results, PCN );
        SetupDMA( Out, CHRN ); 
        DoFormatATrack( sides, Bytes, Sectors )
      END
    END;
    continue := FALSE	(*A breakpoint hook.*)
  END;
  MotorOff
END Format0.
-- 
Aubrey McIntosh                  Freelance using Modula-2
                                 Real time, embedded, instruments.
Austin, TX 78723                 Enquiries welcome
1-(512)-452-1540                 aubrey%rpp386.Cactus.org@cs.utexas.edu