⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 adviopci.c

📁 pc机上经由pci连接的ata和atapi设备驱动
💻 C
📖 第 1 页 / 共 2 页
字号:

//********************************************************************
// ATA LOW LEVEL I/O DRIVER -- ADVIOPCI.C
//
// by Hale Landis (hlandis@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.
//
// Compile with one of the Borland C or C++ compilers.
//
// This C source contains the functions for executing ATA PCI bus
// mastering READ/WRITE DMA commands for ATA and ATAPI.
//********************************************************************

#include <dos.h>

#include "advio.h"

#if ADVIO_INCL_MULTI_THREAD
   ADVIO_DEFINE_YIELD;        // define yield function
#endif

#define DEBUG_PCI 0x01        // not zero for debug
                              // 0x01 trace intFlag value

//***********************************************************
//
// Some notes about PCI bus mastering DMA...
//
// ATA PCI Bus Master DMA was first described by the SFF-8038
// document.  That document is now very obsolete and generally
// difficult to obtain.  The ANSI INCITS T13 document "ATA Host
// Adapter Standards" (T13 document 1510D) has replaced
// SFF-8038.  This code supports the type of DMA described by
// sections 1 to 5 of the T13 1510D document.
//
// Note that the T13 1510D document also describes (in section 6) a
// complex DMA engine called ADMA.  While ADMA is a good idea it
// will probably never be popular or widely implemented.  This code
// does not support ADMA.
//
// The base address of the Bus Master Control Registers (BMCR) is
// found in the PCI Configuration space for the ATA controller (at
// offset 0x30 in the config space data).  This is normally an I/O
// address.
//
// The BMCR data is 16 bytes of data starting at the BMCR base
// address.  The first 8 bytes is for the primary ATA channel and
// the second 8 bytes is for the secondary ATA channel.  The 8 bytes
// contain a "command" byte and a "status" byte and a 4 byte
// (32-bit) physical memory address pointing to the Physical Region
// Descriptor (PRD) list.  Each PRD entry describes an area of
// memory or data buffer for the DMA transfer.  A region described
// by a PRD may not cross a 64K byte boundary in physical memory.
// Also, the PRD list must no cross a 64K byte boundary.
//
// This code can build a PRD list for data transfers up to 256K
// bytes.  It could be modified to handle much larger transfer
// lengths.  Note that a READ/WRITE DMA EXTENDED command can
// transfer up to 65536 sectors or about 33M bytes of data.  A 33M
// byte DMA transfer would require a PRD list with 512-514 PRD
// entries.
//
//***********************************************************

//***********************************************************
//
// set_up_xfer() -- set up the PRD entry list
//
//***********************************************************

static void set_up_xfer( int dir, long bc, unsigned int seg, unsigned int off );

static void set_up_xfer( int dir, long bc, unsigned int seg, unsigned int off )

{
   int ndx;
   int numPrd;                      // number of PRD required for cur cmd
   unsigned long phyAddr;           // physical memory address
   unsigned long addr[MAX_PRD];     // physical address for each prd
   long count[MAX_PRD];             // byte count for each prd
   long temp;
   struct PRD far * prdPtr;         // pointer to PRD entry list

   // disable/stop the dma channel
   // clear interrupt and error bits

   sub_writeBusMstrCmd( BM_CR_MASK_STOP );
   sub_writeBusMstrStatus( ADP->statReg | BM_SR_MASK_INT | BM_SR_MASK_ERR );

   // convert transfer address from seg:off to an absolute memory address
   // note: the physical address must be an even number but that
   //       is not checked here.

   phyAddr = (unsigned long) seg;
   phyAddr = phyAddr << 4;
   phyAddr = phyAddr + (unsigned long) off;

   // the address for each PRD entry is easy to compute.

   addr[0] = phyAddr;
   for ( ndx = 1; ndx < MAX_PRD; ndx ++ )
      addr[ndx] = ( addr[ndx - 1] & 0xffff0000L ) + 0x00010000L;

   // compute the byte count for each PRD entry.
   // note: count must be an even number but that is
   //       not checked here!

   for ( ndx = 1; ndx < MAX_PRD; ndx ++ ) // zero PRD counts
      count[ndx] = 0L;
   temp = addr[1] - addr[0];        // size of 1st prd area
   if ( bc <= temp )                // does full count fit into 1st PRD?
   {
      count[0] = bc;                // yes, full count goes into 1st PRD
      bc = 0L;                      // yes, need only 1 PRD
   }
   else
   {
      count[0] = temp;              // no, update 1st PRD count
      bc = bc - temp;               // no, update remaining count
   }
   numPrd = 1;                      // always have at least 1 PRD
   while ( bc >= 65536L )           // do more PRDs (2nd, 3rd, 4th) as needed
   {
      count[numPrd] = 0L;           // these PRDs are for 64Kbytes each
      bc = bc - 65536L;
      numPrd ++ ;
   }
   if ( bc > 0L )                   // do last PRD if needed
   {
      count[numPrd] = bc;           // remaining count goes in last PRD
      numPrd ++ ;
   }

   // set the end bit in the prd list

   count[numPrd - 1] = count[numPrd - 1] | 0x80000000L;

   // copy the prd entry data to the actual prd list

   prdPtr = ADP->prdBufPtr;
   for ( ndx = 0; ndx < MAX_PRD; ndx ++ )
   {
      prdPtr -> addr  = addr[ndx];
      prdPtr -> count = count[ndx] & 0x8000ffffL;
      prdPtr ++ ;
   }

   // set the prd list address

   outport( ADP->bmcrBase + BM_PRD_ADDR_LOW, ADP->prdBufPtrLow16 );
   outport( ADP->bmcrBase + BM_PRD_ADDR_HIGH, ADP->prdBufPtrHigh16 );

   // set the read/write control:
   // PCI reads for ATA Write DMA commands,
   // PCI writes for ATA Read DMA commands.

   if ( dir )
      ADP->rwControl = BM_CR_MASK_READ;     // ATA Write DMA
   else
      ADP->rwControl = BM_CR_MASK_WRITE;    // ATA Read DMA
}

//***********************************************************
//
// dma_pci_config() - configure/setup for Read/Write DMA
//
// The caller must call this function before attempting
// to use any ATA or ATAPI commands in PCI DMA mode.
//
//***********************************************************

void dma_pci_config( unsigned int regAddr )

{
   unsigned int off;
   unsigned int seg;
   unsigned long lw;

   // save the base i/o address of the bus master (BMCR) regs
   ADP->bmcrBase = regAddr;

   // Set up the PRD entry list buffer address - the PRD entry list
   // may not span a 64KB boundary in physical memory. Space is
   // allocated (above) for this buffer such that it will be
   // aligned on a seqment boundary (off of seg:off will be 0)
   // and such that the PRD list will not span a 64KB boundary.

   // convert seg:off to physical address.
   seg = FP_SEG( (unsigned char far *) ADP->prdBuf );
   off = FP_OFF( (unsigned char far *) ADP->prdBuf );
   lw = (unsigned long) seg;
   lw = lw << 4;
   lw = lw + (unsigned long) off;
   // move up to a segment boundary.
   lw = lw + 15;
   lw = lw & 0xfffffff0L;
   // check for 64KB boundary in the first part of the PRD buffer,
   // if so just move the buffer to that boundary.
   if ( ( lw & 0xffff0000L )
        !=
        ( ( lw + ( MAX_PRD * 8L ) - 1L ) & 0xffff0000L )
      )
      lw = ( lw + ( MAX_PRD * 8L ) ) & 0xffff0000L;
   // convert back to seg:off,
   // note that off is now 0
   seg = (unsigned int) ( lw >> 4 );
   ADP->prdBufPtr = MK_FP( seg, 0 );
   // save the upper/lower parts of the physical address
   ADP->prdBufPtrLow16 = (unsigned int) ( lw & 0x0000ffffL );
   ADP->prdBufPtrHigh16 = (unsigned int) ( ( lw & 0xffff0000L ) >> 16 );

   // read the BM status reg and save bits 6 and 5
   ADP->statReg = sub_readBusMstrStatus() & 0x60;
}

//***********************************************************
//
// exec_pci_ata_cmd() - PCI Bus Master for ATA R/W DMA commands
//
//***********************************************************

static int exec_pci_ata_cmd( unsigned int seg, unsigned int off,
                             long numSect );

static int exec_pci_ata_cmd( unsigned int seg, unsigned int off,
                             long numSect )

{
   unsigned int cntr;
   unsigned char status;

   // mark start of a R/W DMA command in low level trace

   trc_llt( 0, 0, TRC_LLT_S_RWD );

   // Quit now if the command is incorrect.

   if (    ( ADP->cmd != CMD_READ_DMA )
        && ( ADP->cmd != CMD_READ_DMA_EXT )
        && ( ADP->cmd != CMD_WRITE_DMA )
        && ( ADP->cmd != CMD_WRITE_DMA_EXT ) )
   {
      ADP->ec = 77;
      trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
      sub_trace_command();
      trc_llt( 0, 0, TRC_LLT_E_RWD );
      return 1;
   }

   // Quit now if no dma channel set up
   // or interrupts are not enabled.

   if ( ( ! ADP->bmcrBase ) || ( ! ADP->irqActive ) )
   {
      ADP->ec = 70;
      trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
      sub_trace_command();
      trc_llt( 0, 0, TRC_LLT_E_RWD );
      return 1;
   }

   // Quit now if 1) I/O buffer overrun possible.
   // or 2) DMA can't handle the transfer size.

   if ( ( numSect > 512L ) || ( ( numSect * 512L ) > ADP->reg_buffer_size ) )
   {
      ADP->ec = 61;
      trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
      sub_trace_command();
      trc_llt( 0, 0, TRC_LLT_E_PID );
      return 1;
   }

   // set up the dma transfer

   set_up_xfer( ( ADP->cmd == CMD_WRITE_DMA )
                ||
                ( ADP->cmd == CMD_WRITE_DMA_EXT ),
                numSect * 512L, seg, off );

   // Set command time out.

   tmr_set_timeout();

   // Select the drive - call the sub_select function.
   // Quit now if this fails.

   if ( sub_select() )
   {
      sub_trace_command();
      trc_llt( 0, 0, TRC_LLT_E_RWD );
      return 1;
   }

   // Set up all the registers except the command register.

   sub_setup_command();

   // Zero the interrupt flag.

   int_on();

   // Start the command by setting the Command register.  The drive
   // should immediately set BUSY status.

   pio_outbyte( CB_CMD, ADP->cmd );

   // The drive should start executing the command including any
   // data transfer.

   // Data transfer...
   // read the BMIDE regs
   // enable/start the dma channel.
   // read the BMIDE regs again

   sub_readBusMstrCmd();
   sub_readBusMstrStatus();
   sub_writeBusMstrCmd( ADP->rwControl | BM_CR_MASK_START );
   sub_readBusMstrCmd();
   sub_readBusMstrStatus();

   // Data transfer...
   // the device and dma channel transfer the data here while we start
   // checking for command completion...
   // wait for the PCI BM Interrupt=1 (see ADVIOINT.C)...

   trc_llt( 0, 0, TRC_LLT_WINT );
   cntr = 0;
   while ( 1 )
   {
      cntr ++ ;
      if ( ! ( cntr & 0x1fff ) )
      {
         sub_readBusMstrStatus();         // read BM status (for trace)
         if ( ! ( ADP->incompat_flags & REG_INCOMPAT_DMA_POLL ) )
            pio_inbyte( CB_ASTAT );       // poll Alt Status
      }
      if ( int_check() )                  // interrupt ?
      {
         trc_llt( 0, 0, TRC_LLT_INTRQ );  // yes
         trc_llt( 0, ADP->bmStatus, TRC_LLT_R_BM_SR );
         trc_llt( CB_STAT, ADP->ataStatus, TRC_LLT_INB );
         trc_llt( 0, 0x04, TRC_LLT_W_BM_SR );
         break;
      }
      if ( tmr_chk_timeout() )            // time out ?
      {
         trc_llt( 0, 0, TRC_LLT_TOUT );   // yes
         ADP->to = 1;
         ADP->ec = 73;
         trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
         break;
      }

      #if ADVIO_INCL_MULTI_THREAD
         // in a multi-thread application
         // yield to other threads here,
         // maybe one of them can do something
         // on a different ATA channel?
         ADVIO_CALL_YIELD;    // BM DMA not done yet, yield
      #endif

   }

   if ( ADP->incompat_flags & REG_INCOMPAT_DMA_DELAY )
      delay( 1 );    // delay for buggy controllers

   // End of command...
   // disable/stop the dma channel

   status = ADP->bmStatus;                   // read BM status
   status &= ~ BM_SR_MASK_ACT;               // ignore Active bit
   sub_writeBusMstrCmd( BM_CR_MASK_STOP );   // shutdowm DMA
   sub_readBusMstrCmd();                     // read BM cmd (just for trace)
   status |= sub_readBusMstrStatus();        // read BM status again

   if ( ADP->incompat_flags & REG_INCOMPAT_DMA_DELAY )
      delay( 1 );    // delay for buggy controllers

   if ( ADP->ec == 0 )
   {
      if ( status & BM_SR_MASK_ERR )            // bus master error?
      {
         ADP->ec = 78;                          // yes
         trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
      }
   }
   if ( ADP->ec == 0 )
   {
      if ( status & BM_SR_MASK_ACT )            // end of PRD list?
      {
         ADP->ec = 71;                          // yes
         trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
      }
   }

   #if DEBUG_PCI & 0x01
      trc_llt( 0, int_check(), TRC_LLT_DEBUG );  // for debugging
   #endif

   // End of command...
   // If no error use the Status register value that was read
   // by the interrupt handler. If there was an error
   // read the Status register because it may not have been
   // read by the interrupt handler.

   if ( ADP->ec )
      status = pio_inbyte( CB_STAT );
   else
      status = ADP->ataStatus;
   int_off();

   // Final status check...
   // if no error, check final status...
   // Error if BUSY, DEVICE FAULT, DRQ or ERROR status now.

   if ( ADP->ec == 0 )
   {
      if ( status & ( CB_STAT_BSY | CB_STAT_DF | CB_STAT_DRQ | CB_STAT_ERR ) )
      {
         ADP->ec = 74;
         trc_llt( 0, ADP->ec, TRC_LLT_ERROR );
      }
   }

   // Final status check...
   // if any error, update total bytes transferred.

   if ( ADP->ec == 0 )
      ADP->totalBytesXfer = numSect * 512L;
   else
      ADP->totalBytesXfer = 0L;

   // Done...
   // Read the output registers and trace the command.

   sub_trace_command();

   // Done...
   // mark end of a R/W DMA command in low level trace

   trc_llt( 0, 0, TRC_LLT_E_RWD );

   // All done.  The return values of this function are described in
   // ADVIO.H.

   if ( ADP->ec )
      return 1;
   return 0;
}

//***********************************************************
//
// dma_pci_chs() - PCI Bus Master for ATA R/W DMA commands
//
//***********************************************************

int dma_pci_chs( int cmd,
                 unsigned int fr, unsigned int sc,
                 unsigned int cyl, unsigned int head, unsigned int sect,
                 unsigned int seg, unsigned int off,
                 long numSect )

{

   // Setup current command information.

   sub_zero_return_data();
   ADP->flg = TRC_FLAG_ATA;
   ADP->ct  = TRC_TYPE_ADMAI;
   if ( ( cmd == CMD_WRITE_DMA ) || ( cmd == CMD_WRITE_DMA_EXT ) )
      ADP->ct  = TRC_TYPE_ADMAO;
   ADP->cmd = cmd;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -