/////////////////////////////////////
// Demo of how to access a floppy  //
// drive without of a BIOS stuff   //
//                                 //
// Program uses DMA data transferr //
/////////////////////////////////////

////////////////////////////////
// Compiler Borland C/C++ 3.1 //
////////////////////////////////

#ifdef __cplusplus
    #define __CPPARGS ...
#else
    #define __CPPARGS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>

#define CYL 0

typedef struct _DPT_
{
  unsigned char srt_hut;
  unsigned char dma_hlt;
  unsigned char motor_w;
  unsigned char sec_size;
  unsigned char eot;
  unsigned char gap_rw;
  unsigned char dtl;
  unsigned char gap_f;
  unsigned char fill_char;
  unsigned char hst;
  unsigned char mot_start;
} DPT;

DPT     far *get_dpt (void);
void    fdc_out (unsigned char byte);
int     fdc_inp (void);
void    int_wait (void);
void    dma_init (void far *);
void    tdelay (int cnt);
void interrupt IRQ6 (__CPPARGS);
void interrupt (*oldIRQ6) (__CPPARGS);

char    buffer[512];

static  int IRQ=0;

void main (void)
{
  unsigned i;
  long  l;
  char  status[7], main_status;
  DPT _far *fdpt;
  FILE  *sect;

  printf ("\n\nWork with Floppy Drive Controller\n");

  oldIRQ6 = _dos_getvect (8+6);
  _dos_setvect (8+6, IRQ6);

  // We're opening a file in order to store in it
  // the very first sector of the diskette
  sect = fopen ("!sector.dat", "wb+");

  // Getting Diskette Parameter Table pointer
  fdpt = get_dpt();

  // Turning on the motor in the "A:" drive
  // Enabling interrupts before actual turning on
  _enable();
  outp (0x3F2, 0x1C);

  // Waiting while motor speeds up
  tdelay (18);

  // Displaying contents of the controller state register
  printf ("Motor is on.\t\t");
  printf ("State: %02.2X\n", inp(0x3F4));

  // recalibrate
  fdc_out (7);
  fdc_out (0);
  int_wait();


  // We need to move drive head to the CYL track

  // "Seek" command
  fdc_out (0xf);

  // The "Seek" command needs 2 parameters:
  // a Head/Drive number and a Track number.
  // Since we're working with "A:" drive and 0 head,
  // first parameter is 0, second parameter is CYL
  fdc_out (0);
  fdc_out (CYL);

  // Displaying contents of the controller state register
  printf ("\n<<<Seeking>>> \t\t");
  printf ("State: %02.2X\n", inp(0x3F4));

  // Interrupt notifies us about operation end
  int_wait();

  // Delay for head positioning
  tdelay (5);

  // In order to check the result of the "Seek" command
  // we're sending "Read Interrupt State" command

  // Displaying ST0 register and number of a track after
  // "Seek" command execution PCN
  fdc_out (0x8);
  printf ("Interrupt state:\t");
  printf (" ST0: %02.2X, \t", fdc_inp());
  printf ("PCN: %02.2X\n", fdc_inp());

  // For more detailed info of FDC state
  // we're sending "Read Media/Drive State" command,
  // displaying ST3 register
  fdc_out (4);
  fdc_out (0);
  printf ("Media/Drive state:\t ST3: %02.2X\n", fdc_inp());

  // Setting speed of data transfer to 500 KB/sec
  outp (0x3F7, 0);

  // DMA initialization
  dma_init ((void far *)buffer);

  // "Read Data" command
  fdc_out (0x66);
  fdc_out (0x0);        // drive 0, head 0

  fdc_out (CYL);        // track CYL
  fdc_out (0);          // head 0
  fdc_out (1);          // sector no 1

  // Sending some technical info to FDC.
  // This info may be obtained form the Diskette Parameter Table.
  // Parameters are:
  //    - sector size;
  //    - last sector on a track;
  //    - gap length;
  //    - number of bytes to be read/write
  fdc_out (fdpt->sec_size);
  fdc_out (fdpt->eot);
  fdc_out (fdpt->gap_rw);
  fdc_out (fdpt->dtl);

  // Waiting for interrupt (end of operation)
  int_wait();

  // Getting and displaying results of
  // the "Read Data" command
  printf ("\n<<<Reading a sector>>> \n");
  printf ("   State bytes (ST0,ST1,ST2,C,H,R,N):\n");

  for(i=0; i<7; i++) printf("%02.2X\t", (char) fdc_inp());
  printf("\n");

  // Saving sector to the file
  for(i=0; i<512; i++) fputc (buffer[i],sect);
  fclose (sect);

  // Turning motor off
  outp (0x3F2, 0xC);
lll:
  _dos_setvect (8+6, oldIRQ6);
}

// Writes a byte to FDC
void fdc_out (unsigned char parm)
{
  asm mov   dx,3F4h
loop_fdc_out:

  asm in    al,dx
  asm test  al,80h      // Is controller ready?
  asm jz loop_fdc_out   //   No, waiting...

  asm inc   dx
  asm mov   al, parm    // Writing the byte
  asm out   dx, al
}

// Reads a byte from FDC
int fdc_inp (void)
{
  asm mov   dx,3F4h
loop_fdc_inp:
  asm in    al,dx
  asm test  al,80h      // Is controller ready?
  asm jz loop_fdc_inp   //   No, waiting...

  asm inc   dx
  asm in    al, dx      // Reading a byte
  return _AL;
}

// Waits for an interrupt generated by FDC
void int_wait (void) {
  _enable();
  while (IRQ==0) {};
  IRQ = 0;
}

void interrupt IRQ6 (__CPPARGS) {
  IRQ = 1;
  outportb (0x20, 0x20);
}

// DMA initialization routine
void dma_init (void far *buf)
{
  unsigned long f_adr;
  unsigned sg, of;

  // Computing 24-bit address for the data buffer
  f_adr = ((unsigned long)FP_SEG(buf) << 4)
    + (unsigned long)FP_OFF(buf);

  // Splitting the address into a page number
  // and an offset
  sg = (f_adr >> 16) & 0xff;
  of = f_adr & 0xffff;

  // Disabling ints during DMA programming
  _disable();
  asm mov   al,46h      // FDC read data command

  asm out   12,al       // We're working with 16-bit ports.
		        // Next byte sent to 16-bit port is less significiant

  asm out   11,al       // DMA mode

  asm mov   ax,of       // Buffer offset LSB
  asm out   4,al
  asm mov   al,ah       // Buffer offset MSB
  asm out   4,al

  asm mov   ax,sg       // Page number
  asm out   81h,al

  asm mov   ax,511      // Data length
  asm out   5,al
  asm mov   al,ah
  asm out   5,al

  asm mov   al,2        // channel 2 enabled
  asm out   10,al

  // It's now safe to enable ints
  _enable();
}

// This routine returns a Diskette Parameter Table address
DPT far *get_dpt(void)
{
  void far * far *ptr;

  ptr = (void far * far *)MK_FP(0x0, 0x78);
  return (DPT far*)(*ptr);
}

// This routine waits for cnt timer ticks.
// Timer frequency is 18.2 Hz
void tdelay (int cnt)
{
  asm push bx
  asm push dx
  asm push si

  asm mov si, cnt
  asm mov ah, 0
  asm int 1ah
  asm mov bx, dx
  asm add bx, si

delay_loop:
  asm int 1ah
  asm cmp dx, bx
  asm jne delay_loop

  asm pop si
  asm pop dx
  asm pop bx
}
