//********************************************************************
// ATA LOW LEVEL I/O DRIVER -- ATAIOPIO.C
//
// by Hale Landis (www.ata-atapi.com)
//
// There is no copyright and there are no restrictions on the use
// of this ATA Low Level I/O Driver code.  It is distributed to
// help other programmers understand how the ATA device interface
// works and it is distributed without any warranty.  Use this
// code at your own risk.
//
// This code is based on the ATA-2, ATA-3 and ATA-4 standards and
// on interviews with various ATA controller and drive designers.
//
// This code has been run on many ATA (IDE) drives and
// MFM/RLL controllers.  This code may be a little
// more picky about the status it sees at various times.  A real
// BIOS probably would not check the status as carefully.
//
// Compile with one of the Borland C or C++ compilers.
// This module contains inline assembler code so you'll
// also need Borland's Assembler.
//
// This C source contains the low level I/O port IN/OUT functions.
//********************************************************************

#include <dos.h>

#include "ataio.h"

//*************************************************************
//
// Host adapter base addresses.
//
//*************************************************************

unsigned int pio_base_addr1 = 0x1f0;
unsigned int pio_base_addr2 = 0x3f0;

unsigned int pio_memory_seg = 0;
int pio_memory_dt_opt = PIO_MEMORY_DT_OPT0;

unsigned int pio_bmide_base_addr = 0;

unsigned int pio_reg_addrs[10];

unsigned char pio_last_write[10];
unsigned char pio_last_read[10];

int pio_xfer_width = 16;

//*************************************************************
//
// Set the host adapter i/o base addresses.
//
//*************************************************************

void pio_set_iobase_addr( unsigned int base1,
                          unsigned int base2,
                          unsigned int base3 )

{

   pio_base_addr1 = base1;
   pio_base_addr2 = base2;
   pio_bmide_base_addr = base3;
   pio_memory_seg = 0;
   pio_reg_addrs[ CB_DATA ] = pio_base_addr1 + 0;  // 0
   pio_reg_addrs[ CB_FR   ] = pio_base_addr1 + 1;  // 1
   pio_reg_addrs[ CB_SC   ] = pio_base_addr1 + 2;  // 2
   pio_reg_addrs[ CB_SN   ] = pio_base_addr1 + 3;  // 3
   pio_reg_addrs[ CB_CL   ] = pio_base_addr1 + 4;  // 4
   pio_reg_addrs[ CB_CH   ] = pio_base_addr1 + 5;  // 5
   pio_reg_addrs[ CB_DH   ] = pio_base_addr1 + 6;  // 6
   pio_reg_addrs[ CB_CMD  ] = pio_base_addr1 + 7;  // 7
   pio_reg_addrs[ CB_DC   ] = pio_base_addr2 + 6;  // 8
   pio_reg_addrs[ CB_DA   ] = pio_base_addr2 + 7;  // 9
}

//*************************************************************
//
// Set the host adapter memory base addresses.
//
//*************************************************************

void pio_set_memory_addr( unsigned int seg )

{

   pio_base_addr1 = 0;
   pio_base_addr2 = 8;
   pio_bmide_base_addr = 0;
   pio_memory_seg = seg;
   pio_memory_dt_opt = PIO_MEMORY_DT_OPT0;
   pio_reg_addrs[ CB_DATA ] = pio_base_addr1 + 0;  // 0
   pio_reg_addrs[ CB_FR   ] = pio_base_addr1 + 1;  // 1
   pio_reg_addrs[ CB_SC   ] = pio_base_addr1 + 2;  // 2
   pio_reg_addrs[ CB_SN   ] = pio_base_addr1 + 3;  // 3
   pio_reg_addrs[ CB_CL   ] = pio_base_addr1 + 4;  // 4
   pio_reg_addrs[ CB_CH   ] = pio_base_addr1 + 5;  // 5
   pio_reg_addrs[ CB_DH   ] = pio_base_addr1 + 6;  // 6
   pio_reg_addrs[ CB_CMD  ] = pio_base_addr1 + 7;  // 7
   pio_reg_addrs[ CB_DC   ] = pio_base_addr2 + 6;  // 8
   pio_reg_addrs[ CB_DA   ] = pio_base_addr2 + 7;  // 9
}

//*************************************************************
//
// These functions do basic IN/OUT of byte and word values:
//
//    pio_inbyte()
//    pio_outbyte()
//    pio_inword()
//    pio_outword()
//
//*************************************************************

unsigned char pio_inbyte( unsigned int addr )

{
   unsigned int regAddr;
   unsigned char uc;
   unsigned char far * ucp;

   regAddr = pio_reg_addrs[ addr ];
   if ( pio_memory_seg )
   {
      ucp = (unsigned char far *) MK_FP( pio_memory_seg, regAddr );
      uc = * ucp;
   }
   else
   {
       uc = (unsigned char) inportb( regAddr );

      // READ THIS: If you get a compile error on the following
      // statement you are trying to use BASM (the assembler
      // built into Borland C). BASM can not assemble 386
      // instructions. You must use Borland TASM as is shown
      // in the EXAMPLE1.MAK or EXAMPLE2.MAK "make files".

      // asm   .386

      // asm   push  ax
      // asm   push  dx

      // asm   mov   dx,regAddr

      // asm   in    al,dx
      // asm   mov   uc,al

      // asm   pop   dx
      // asm   pop   ax

   }
   pio_last_read[ addr ] = uc;
   trc_llt( addr, uc, TRC_LLT_INB );
   return uc;
}

//*************************************************************

void pio_outbyte( unsigned int addr, unsigned char data )

{
   unsigned int regAddr;
   unsigned char far * ucp;

   regAddr = pio_reg_addrs[ addr ];
   if ( pio_memory_seg )
   {
      ucp = (unsigned char far *) MK_FP( pio_memory_seg, regAddr );
      * ucp = data;
   }
   else
   {
       outportb( regAddr, data );

      // asm   .386

      // asm   push  ax
      // asm   push  dx

      // asm   mov   dx,regAddr
      // asm   mov   al,data

      // asm   out   dx,al

      // asm   pop   dx
      // asm   pop   ax

   }
   pio_last_write[ addr ] = data;
   trc_llt( addr, data, TRC_LLT_OUTB );
}

//*************************************************************

unsigned int pio_inword( unsigned int addr )

{
   unsigned int regAddr;
   unsigned int ui;
   unsigned int far * uip;

   regAddr = pio_reg_addrs[ addr ];
   if ( pio_memory_seg )
   {
      uip = (unsigned int far *) MK_FP( pio_memory_seg, regAddr );
      ui = * uip;
   }
   else
   {
       ui = inportw( regAddr );

      // asm   .386

      // asm   push  ax
      // asm   push  dx

      // asm   mov   dx,regAddr

      // asm   in    ax,dx
      // asm   mov   ui,ax

      // asm   pop   dx
      // asm   pop   ax

   }
   trc_llt( addr, 0, TRC_LLT_INW );
   return ui;
}

//*************************************************************

void pio_outword( unsigned int addr, unsigned int data )

{
   unsigned int regAddr;
   unsigned int far * uip;

   regAddr = pio_reg_addrs[ addr ];
   if ( pio_memory_seg )
   {
      uip = (unsigned int far *) MK_FP( pio_memory_seg, regAddr );
      * uip = data;
   }
   else
   {
       outportw( regAddr, data );

      // asm   .386

      // asm   push  ax
      // asm   push  dx

      // asm   mov   dx,regAddr
      // asm   mov   ax,data

      // asm   out   dx,ax

      // asm   pop   dx
      // asm   pop   ax

   }
   trc_llt( addr, 0, TRC_LLT_OUTW );
}

//*************************************************************
//
// These functions are normally used to transfer DRQ blocks:
//
// pio_drq_block_in()
// pio_drq_block_out()
//
//*************************************************************

// Note: pio_drq_block_in() is the primary way perform PIO
// Data In transfers. It will handle 8-bit, 16-bit and 32-bit
// I/O based data transfers and 8-bit and 16-bit PCMCIA Memory
// mode transfers.

void pio_drq_block_in( unsigned int addrDataReg,
                       unsigned int bufSeg, unsigned int bufOff,
                       long wordCnt )

{
   long bCnt;
   int memDtOpt;
   unsigned int randVal;
   unsigned int dataRegAddr;
   unsigned int far * uip1;
   unsigned int far * uip2;
   unsigned char far * ucp1;
   unsigned char far * ucp2;
   unsigned long bufAddr;

   // NOTE: wordCnt is the size of a DRQ block
   // in words. The maximum value of wordCnt is normally:
   // a) For ATA, 16384 words or 32768 bytes (64 sectors,
   //    only with READ/WRITE MULTIPLE commands),
   // b) For ATAPI, 32768 words or 65536 bytes
   //    (actually 65535 bytes plus a pad byte).

   // normalize bufSeg:bufOff

   bufAddr = bufSeg;
   bufAddr = bufAddr << 4;
   bufAddr = bufAddr + bufOff;

   if ( pio_memory_seg )
   {

      // PCMCIA Memory mode data transfer.

      // set Data reg address per pio_memory_dt_opt
      dataRegAddr = 0x0000;
      memDtOpt = pio_memory_dt_opt;
      if ( pio_memory_dt_opt == PIO_MEMORY_DT_OPTR )
      {
         randVal = * (unsigned int *) MK_FP( 0x40, 0x6c );
         memDtOpt = randVal % 3;
      }
      if ( memDtOpt == PIO_MEMORY_DT_OPT8 )
         dataRegAddr = 0x0008;
      if ( memDtOpt == PIO_MEMORY_DT_OPTB )
      {
         dataRegAddr = 0x0400;
         if ( pio_memory_dt_opt == PIO_MEMORY_DT_OPTR )
            dataRegAddr = dataRegAddr | ( randVal & 0x03fe );
      }

      if ( pio_xfer_width == 8 )
      {
         // PCMCIA Memory mode 8-bit
         bCnt = wordCnt * 2L;
         ucp1 = (unsigned char far *) MK_FP( pio_memory_seg, dataRegAddr );
         for ( ; bCnt > 0; bCnt -- )
         {
            bufSeg = (unsigned int) ( bufAddr >> 4 );
            bufOff = (unsigned int) ( bufAddr & 0x0000000fL );
            ucp2 = (unsigned char far *) MK_FP( bufSeg, bufOff );
            * ucp2 = * ucp1;
            bufAddr += 1;
            if ( memDtOpt == PIO_MEMORY_DT_OPTB )
            {
               dataRegAddr += 1;
               dataRegAddr = ( dataRegAddr & 0x03ff ) | 0x0400;
               ucp1 = (unsigned char far *) MK_FP( pio_memory_seg, dataRegAddr );
            }
         }
         trc_llt( addrDataReg, 0, TRC_LLT_INSB );
      }
      else
      {
         // PCMCIA Memory mode 16-bit
         uip1 = (unsigned int far *) MK_FP( pio_memory_seg, dataRegAddr );
         for ( ; wordCnt > 0; wordCnt -- )
         {
            bufSeg = (unsigned int) ( bufAddr >> 4 );
            bufOff = (unsigned int) ( bufAddr & 0x0000000fL );
            uip2 = (unsigned int far *) MK_FP( bufSeg, bufOff );
            * uip2 = * uip1;
            bufAddr += 2;
            if ( memDtOpt == PIO_MEMORY_DT_OPTB )
            {
               dataRegAddr += 2;
               dataRegAddr = ( dataRegAddr & 0x03fe ) | 0x0400;
               uip1 = (unsigned int far *) MK_FP( pio_memory_seg, dataRegAddr );
            }
         }
         trc_llt( addrDataReg, 0, TRC_LLT_INSW );
      }
   }
   else
   {
      int pxw;
      long wc;

      // adjust pio_xfer_width - don't use DWORD if wordCnt is odd.

      pxw = pio_xfer_width;
      if ( ( pxw == 32 ) && ( wordCnt & 0x00000001L ) )
         pxw = 16;

      // Data transfer using INS instruction.
      // Break the transfer into chunks of 32768 or fewer bytes.

      while ( wordCnt > 0 )
      {
         //bufSeg = (unsigned int) ( bufAddr >> 4 );
         //bufOff = (unsigned int) ( bufAddr & 0x0000000fL );
		 bufSeg = 0;
		 bufOff = bufAddr;
         if ( wordCnt > 16384L )
            wc = 16384;
         else
            wc = wordCnt;
         if ( pxw == 8 )
         {
            // do REP INS
            pio_rep_inbyte( addrDataReg, bufSeg, bufOff, wc * 2L );
         }
         else
         if ( pxw == 32 )
         {
            // do REP INSD
            pio_rep_indword( addrDataReg, bufSeg, bufOff, wc / 2L );
         }
         else
         {
            // do REP INSW
            pio_rep_inword( addrDataReg, bufSeg, bufOff, wc );
         }
         bufAddr = bufAddr + ( wc * 2 );
         wordCnt = wordCnt - wc;
      }
   }

   return;
}

//*************************************************************

// Note: pio_drq_block_out() is the primary way perform PIO
// Data Out transfers. It will handle 8-bit, 16-bit and 32-bit
// I/O based data transfers and 8-bit and 16-bit PCMCIA Memory
// mode transfers.

void pio_drq_block_out( unsigned int addrDataReg,
                        unsigned int bufSeg, unsigned int bufOff,
                        long wordCnt )

{
   long bCnt;
   int memDtOpt;
   unsigned int randVal;
   unsigned int dataRegAddr;
   unsigned int far * uip1;
   unsigned int far * uip2;
   unsigned char far * ucp1;
   unsigned char far * ucp2;
   unsigned long bufAddr;

   // NOTE: wordCnt is the size of a DRQ block
   // in words. The maximum value of wordCnt is normally:
   // a) For ATA, 16384 words or 32768 bytes (64 sectors,
   //    only with READ/WRITE MULTIPLE commands),
   // b) For ATAPI, 32768 words or 65536 bytes
   //    (actually 65535 bytes plus a pad byte).

   // normalize bufSeg:bufOff

   bufAddr = bufSeg;
   bufAddr = bufAddr << 4;
   bufAddr = bufAddr + bufOff;

   if ( pio_memory_seg )
   {

      // PCMCIA Memory mode data transfer.

      // set Data reg address per pio_memory_dt_opt
      dataRegAddr = 0x0000;
      memDtOpt = pio_memory_dt_opt;
      if ( pio_memory_dt_opt == PIO_MEMORY_DT_OPTR )
      {
         randVal = * (unsigned int *) MK_FP( 0x40, 0x6c );
         memDtOpt = randVal % 3;
      }
      if ( memDtOpt == PIO_MEMORY_DT_OPT8 )
         dataRegAddr = 0x0008;
      if ( memDtOpt == PIO_MEMORY_DT_OPTB )
      {
         dataRegAddr = 0x0400;
         if ( pio_memory_dt_opt == PIO_MEMORY_DT_OPTR )
            dataRegAddr = dataRegAddr | ( randVal & 0x03fe );
      }

      if ( pio_xfer_width == 8 )
      {
         // PCMCIA Memory mode 8-bit
         bCnt = wordCnt * 2L;
         ucp2 = (unsigned char far *) MK_FP( pio_memory_seg, dataRegAddr );
         for ( ; bCnt > 0; bCnt -- )
         {
            bufSeg = (unsigned int) ( bufAddr >> 4 );
            bufOff = (unsigned int) ( bufAddr & 0x0000000fL );
            ucp1 = (unsigned char far *) MK_FP( bufSeg, bufOff );
            * ucp2 = * ucp1;
            bufAddr += 1;
            if ( memDtOpt == PIO_MEMORY_DT_OPTB )
            {
               dataRegAddr += 1;
               dataRegAddr = ( dataRegAddr & 0x03ff ) | 0x0400;
               ucp2 = (unsigned char far *) MK_FP( pio_memory_seg, dataRegAddr );
            }
         }
         trc_llt( addrDataReg, 0, TRC_LLT_OUTSB );
      }
      else
      {
         // PCMCIA Memory mode 16-bit
         uip2 = (unsigned int far *) MK_FP( pio_memory_seg, dataRegAddr );
         for ( ; wordCnt > 0; wordCnt -- )
         {
            bufSeg = (unsigned int) ( bufAddr >> 4 );
            bufOff = (unsigned int) ( bufAddr & 0x0000000fL );
            uip1 = (unsigned int far *) MK_FP( bufSeg, bufOff );
            * uip2 = * uip1;
            bufAddr += 2;
            if ( memDtOpt == PIO_MEMORY_DT_OPTB )
            {
               dataRegAddr = dataRegAddr + 2;
               dataRegAddr = ( dataRegAddr & 0x03fe ) | 0x0400;
               uip2 = (unsigned int far *) MK_FP( pio_memory_seg, dataRegAddr );
            }
         }
         trc_llt( addrDataReg, 0, TRC_LLT_OUTSW );
      }
   }
   else
   {
      int pxw;
      long wc;

      // adjust pio_xfer_width - don't use DWORD if wordCnt is odd.

      pxw = pio_xfer_width;
      if ( ( pxw == 32 ) && ( wordCnt & 0x00000001L ) )
         pxw = 16;

      // Data transfer using OUTS instruction.
      // Break the transfer into chunks of 32768 or fewer bytes.

      while ( wordCnt > 0 )
      {
         bufOff = (unsigned int) ( bufAddr & 0x0000000fL );
         bufSeg = (unsigned int) ( bufAddr >> 4 );
         if ( wordCnt > 16384L )
            wc = 16384;
         else
            wc = wordCnt;
         if ( pxw == 8 )
         {
            // do REP OUTS
            pio_rep_outbyte( addrDataReg, bufSeg, bufOff, wc * 2L );
         }
         else
         if ( pxw == 32 )
         {
            // do REP OUTSD
            pio_rep_outdword( addrDataReg, bufSeg, bufOff, wc / 2L );
         }
         else
         {
            // do REP OUTSW
            pio_rep_outword( addrDataReg, bufSeg, bufOff, wc );
         }
         bufAddr = bufAddr + ( wc * 2 );
         wordCnt = wordCnt - wc;
      }
   }

   return;
}

//*************************************************************
//
// These functions do REP INS/OUTS data transfers
// (PIO data transfers in I/O mode):
//
// pio_rep_inbyte()
// pio_rep_outbyte()
// pio_rep_inword()
// pio_rep_outword()
// pio_rep_indword()
// pio_rep_outdword()
//
// These functions can be called directly but usually they
// are called by the pio_drq_block_in() and pio_drq_block_out()
// functions to perform I/O mode transfers. See the
// pio_xfer_width variable!
//
//*************************************************************

void pio_rep_inbyte( unsigned int addrDataReg,
                     unsigned int bufSeg, unsigned int bufOff,
                     long byteCnt )

{
   unsigned short dataRegAddr = pio_reg_addrs[ addrDataReg ];
   unsigned int bCnt = (unsigned int) byteCnt;

   // Warning: Avoid calling this function with
   // byteCnt > 32768 (transfers 32768 bytes).
   // bufSeg and bufOff should be normalized such
   // that bufOff is a value between 0 and 15 (0xf).

   //asm   .386

   asm(
   "cld\n\t"
   "rep insb\n\t"
   ::"D"(bufOff),"c"(byteCnt),"d"(dataRegAddr));

   trc_llt( addrDataReg, 0, TRC_LLT_INSB );
}

//*************************************************************

void pio_rep_outbyte( unsigned int addrDataReg,
                      unsigned int bufSeg, unsigned int bufOff,
                      long byteCnt )

{
   unsigned int dataRegAddr = pio_reg_addrs[ addrDataReg ];
   unsigned int bCnt = (unsigned int) byteCnt;

   // Warning: Avoid calling this function with
   // byteCnt > 32768 (transfers 32768 bytes).
   // bufSeg and bufOff should be normalized such
   // that bufOff is a value between 0 and 15 (0xf).

   //asm(".386");

   asm(
   "cld\n\t"
   "rep outsb\n\t"
   ::"S"(bufOff),"c"(bCnt),"d"(dataRegAddr));

   trc_llt( addrDataReg, 0, TRC_LLT_OUTSB );
}

//*************************************************************

void pio_rep_inword( unsigned int addrDataReg,
                     unsigned int bufSeg, unsigned int bufOff,
                     long wordCnt )

{
   unsigned int dataRegAddr = pio_reg_addrs[ addrDataReg ];
   unsigned int wCnt = (unsigned int) wordCnt;

   // Warning: Avoid calling this function with
   // wordCnt > 16384 (transfers 32768 bytes).
   // bufSeg and bufOff should be normalized such
   // that bufOff is a value between 0 and 15 (0xf).

   //asm(".386");


   asm(
   "cld\n\t"
   "rep insw\n\t"
   ::"D"(bufOff),"c"(wCnt),"d"(dataRegAddr));
   trc_llt( addrDataReg, 0, TRC_LLT_INSW );
}

//*************************************************************

void pio_rep_outword( unsigned int addrDataReg,
                      unsigned int bufSeg, unsigned int bufOff,
                      long wordCnt )

{
   unsigned int dataRegAddr = pio_reg_addrs[ addrDataReg ];
   unsigned int wCnt = (unsigned int) wordCnt;

   // Warning: Avoid calling this function with
   // wordCnt > 16384 (transfers 32768 bytes).
   // bufSeg and bufOff should be normalized such
   // that bufOff is a value between 0 and 15 (0xf).

   //asm(".386");

   asm(
   "cld\n\t"
   "rep outsw\n\t"
   ::"S"(bufOff),"c"(wCnt),"d"(dataRegAddr));

   trc_llt( addrDataReg, 0, TRC_LLT_OUTSW );
}

//*************************************************************

void pio_rep_indword( unsigned int addrDataReg,
                      unsigned int bufSeg, unsigned int bufOff,
                      long dwordCnt )

{
   unsigned int dataRegAddr = pio_reg_addrs[ addrDataReg ];
   unsigned int dwCnt = (unsigned int) dwordCnt;

   // Warning: Avoid calling this function with
   // dwordCnt > 8192 (transfers 32768 bytes).
   // bufSeg and bufOff should be normalized such
   // that bufOff is a value between 0 and 15 (0xf).

   //asm(".386");

   asm(
   "cld\n\t"
   "rep insd\n\t" //"rep   insd;"
   ::"D"(bufOff),"c"(dwCnt),"d"(dataRegAddr));

   trc_llt( addrDataReg, 0, TRC_LLT_INSD );
}

//*************************************************************

void pio_rep_outdword( unsigned int addrDataReg,
                       unsigned int bufSeg, unsigned int bufOff,
                       long dwordCnt )

{
   unsigned int dataRegAddr = pio_reg_addrs[ addrDataReg ];
   unsigned int dwCnt = (unsigned int) dwordCnt;

   // Warning: Avoid calling this function with
   // dwordCnt > 8192 (transfers 32768 bytes).
   // bufSeg and bufOff should be normalized such
   // that bufOff is a value between 0 and 15 (0xf).

   //asm(".386");


   asm(
   "cld;\n\t"
   "rep outsd\n\t" // "rep   outsd;"
   ::"S"(bufOff),"c"(dwCnt),"d"(dataRegAddr));

   trc_llt( addrDataReg, 0, TRC_LLT_OUTSD );
}

// end ataiopio.c
