📄 adviopci.c
字号:
//********************************************************************
// 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 + -