📄 ataiopci.c
字号:
//********************************************************************
// ATA LOW LEVEL I/O DRIVER -- ATAIOPCI.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.
//
// 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 C source contains the functions for executing ATA PCI bus
// mastering READ/WRITE DMA commands for ATA and ATAPI.
//********************************************************************
#include <dos.h>
#include "ataio.h"
#define DEBUG_PCI 0x00 // not zero for debug
// 0x01 trace the interrupt counter and flag
// 0x02 debug LARGE PRD
#if DEBUG_PCI & 0x02
#include <stdio.h>
extern unsigned char far LFB[128];
extern void prt( void );
extern void pstr( unsigned char * s );
#endif
//***********************************************************
//
// 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 not cross a 64K byte boundary.
//
// SIMPLE/COMPLEX PRD lists...
// This code can build a PRD list for data transfers up to 256K
// bytes. These types of PRD lists are built in a data area
// that is statically allocated below. These PRD lists use
// transfer the data to/from the caller's I/O buffer.
//
// LARGE PRD lists...
// This code also supports READ/WRITE DMA EXT command transfers
// up to 65536 sectors. This is done by creating a large
// PRD list in the caller's I/O buffer. This PRD list
// reuses a 64K part of the caller's I/O buffer for the data transfer.
// In this manner transfers up toe 65536 sectors (or about 33Mbytes).
// See the function dma_pci_set_max_xfer().
//
//***********************************************************
//***********************************************************
//
// pci bus master registers and PRD list buffer,
// see dma_pci_config().
//
//***********************************************************
// public data...
int dma_pci_prd_type; // type of PRD list to use
long dma_pci_largeMaxB; // max LARGE dma xfer size in bytes
long dma_pci_largeMaxS; // max LARGE dma xfer size in sectors
unsigned long far * dma_pci_largePrdBufPtr; // LARGE PRD buffer ptr
unsigned char far * dma_pci_largeIoBufPtr; // LARGE PRD I/O address
unsigned long far * dma_pci_prd_ptr; // current PRD buffer address
int dma_pci_num_prd; // current number of PRD entries
// private data...
// data used by SIMPLE/COMPLEX PRD lists
#define MAX_TRANSFER_SIZE 262144L // max transfer size (in bytes,
// should be multiple of 65536)
#define MAX_SEG ((MAX_TRANSFER_SIZE/65536L)+2L) // number physical segments
#define MAX_PRD (MAX_SEG*4L) // number of PRDs required
#define PRD_BUF_SIZE (48+(2*MAX_PRD*8)) // size of PRD list buffer
static unsigned char far prdBuf[PRD_BUF_SIZE]; // PRD buffer address
static unsigned long far * prdBufPtr; // PRD addr (seg:off) off is 0
// BMIDE data
static unsigned char statReg; // save BM status reg bits
static unsigned char rwControl; // read/write control bit setting
//***********************************************************
//
// set_up_xfer() -- set up the PRD entry list
//
// NOTE:
// dma_pci_prd_type == PCI_TYPE_LARGE uses part of the caller's
// I/O buffer to hold a large PRD list and another part of the
// caller's I/O buffer to send/receive the actual data. Each
// PRD entry uses the same memory address.
//
//***********************************************************
static int set_up_xfer( int dir, long bc, unsigned int seg, unsigned int off );
static int set_up_xfer( int dir, long bc, unsigned int seg, unsigned int off )
{
int numPrd; // number of PRD required
int maxPrd; // max number of PRD allowed
unsigned long temp;
unsigned long phyAddr; // physical memory address
unsigned long savePhyAddr; // physical memory address
unsigned long bigCnt; // complex big count
unsigned long smallCnt; // complex small count
unsigned long 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( statReg | BM_SR_MASK_INT | BM_SR_MASK_ERR );
// setup to build the PRD list...
if ( dma_pci_prd_type == PRD_TYPE_LARGE )
{
// ...set up for LARGE PRD list
// ...max PRDs allowed
maxPrd = 512;
// ...set big and small counts to max
bigCnt = smallCnt = 65536L;
// ...set the LARGE PRD buffer address
dma_pci_prd_ptr = prdPtr = dma_pci_largePrdBufPtr;
// ...convert I/O buffer address to physical memory address
phyAddr = (unsigned long) FP_SEG( dma_pci_largeIoBufPtr );
phyAddr = phyAddr << 4;
phyAddr = phyAddr + (unsigned long) FP_OFF( dma_pci_largeIoBufPtr );
phyAddr = phyAddr & 0xfffffffeL;
}
else
{
// ...set up for SIMPLE/COMPLEX PRD list
// ...max PRDs allowed
maxPrd = (int) MAX_PRD;
// ...set big and small counts to max
bigCnt = smallCnt = 65536L;
// ...adjust PRD buffer address and adjust big and small counts
prdPtr = prdBufPtr;
if ( dma_pci_prd_type == PRD_TYPE_COMPLEX )
{
temp = tmr_read_bios_timer();
prdPtr = MK_FP( FP_SEG( prdBufPtr ),
(unsigned int) ( temp & 0x0000000cL ) );
smallCnt = ( temp & 0x000000feL ) + 2L;
bigCnt = bigCnt - smallCnt - ( temp & 0x0000000eL );
}
// ...set the SIMPLE/COMPLEX PRD buffer address
dma_pci_prd_ptr = prdPtr;
// ... convert I/O buffer address to an physical memory address
phyAddr = (unsigned long) seg;
phyAddr = phyAddr << 4;
phyAddr = phyAddr + (unsigned long) off;
phyAddr = phyAddr & 0xfffffffeL;
}
savePhyAddr = phyAddr;
#if DEBUG_PCI & 0x02
pstr( "=>[prd] set_up_xfer()..." );
sprintf( LFB, "=>[prd] dir %d bc %lx seg %04x off %04x",
dir, bc, seg, off );
prt();
sprintf( LFB, "=>[prd] maxPrd %d prdPtr %Fp bigCnt %lx smallCnt %lx",
maxPrd, prdPtr, bigCnt, smallCnt );
prt();
sprintf( LFB, "=>[prd] phyAddr %08lx", phyAddr );
prt();
#endif
// build the PRD list...
// ...PRD entry format:
// +0 to +3 = memory address
// +4 to +5 = 0x0000 (not EOT) or 0x8000 (EOT)
// +6 to +7 = byte count
// ...zero number of PRDs
numPrd = 0;
// ...loop to build each PRD
while ( bc > 0 )
{
if ( numPrd >= maxPrd )
return 1;
// set this PRD's address
if ( dma_pci_prd_type == PRD_TYPE_LARGE )
phyAddr = savePhyAddr;
prdPtr[0] = phyAddr;
// set count for this PRD
if ( ( numPrd & 0x0003 ) == 0x0002 )
temp = bigCnt; // use big count (1 of 4 PRDs)
else // else
temp = smallCnt; // use small count (3 of 4 PRDs)
if ( temp > bc ) // count to large?
temp = bc; // yes - use actual count
// check if count will fit
phyAddr = phyAddr + temp;
if ( ( phyAddr & 0xffff0000L ) != ( prdPtr[0] & 0xffff0000L ) )
{
phyAddr = phyAddr & 0xffff0000L;
temp = phyAddr - prdPtr[0];
}
// set this PRD's count
prdPtr[1] = temp & 0x0000ffffL;
// update byte count
bc = bc - temp;
// set the end bit in the prd list
if ( bc < 1 )
prdPtr[1] = prdPtr[1] | 0x80000000L;
prdPtr ++ ;
prdPtr ++ ;
numPrd ++ ;
}
// return the current PRD list size and
// set the prd list address in the BMCR:
// convert PRD buffer seg:off to a physical address
// and write into BMCR PRD address registers.
dma_pci_num_prd = numPrd;
temp = FP_SEG( dma_pci_prd_ptr );
temp = temp << 4;
temp = temp + FP_OFF( dma_pci_prd_ptr );
outport( pio_bmcr_base_addr + BM_PRD_ADDR_LOW,
(unsigned int) ( temp & 0x0000ffffL ) );
outport( pio_bmcr_base_addr + BM_PRD_ADDR_HIGH,
(unsigned int) ( ( temp & 0xffff0000L ) >> 16 ) );
#if DEBUG_PCI & 0x02
{
int ndx;
unsigned long far * lfp;
pstr( "=>[prd] ----- Bus Master PRD List -----" );
sprintf( LFB, "=>[prd] PRD PhyAddr %08lX",
( ( (unsigned long) FP_SEG( dma_pci_prd_ptr ) ) << 4 )
+ ( (unsigned long) FP_OFF( dma_pci_prd_ptr ) ) );
prt();
lfp = dma_pci_prd_ptr;
ndx = 0;
while ( ndx < dma_pci_num_prd )
{
sprintf( LFB, "=>[prd] PRD %2d - PhyAddr %08lX Cnt %08lX",
ndx, * lfp, * ( lfp + 1 ) );
prt();
lfp ++ ;
if ( ( * lfp ) & 0x80000000L )
break;
lfp ++ ;
ndx ++ ;
}
}
#endif
// set the read/write control:
// PCI reads for ATA Write DMA commands,
// PCI writes for ATA Read DMA commands.
if ( dir )
rwControl = BM_CR_MASK_READ; // ATA Write DMA
else
rwControl = BM_CR_MASK_WRITE; // ATA Read DMA
sub_writeBusMstrCmd( rwControl );
return 0;
}
//***********************************************************
//
// 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.
//
//***********************************************************
int dma_pci_config( unsigned int regAddr )
{
unsigned int off;
unsigned int seg;
unsigned long lw;
// check reg address
if ( regAddr & 0x0007 ) // error if regs addr
return 1; // are not xxx0h or xxx8h
// save the base i/o address of the bus master (BMCR) regs
pio_bmcr_base_addr = regAddr;
// disable if reg address is zero
if ( ! regAddr ) // if zero,
return 0; // PCI DMA is disabled.
// 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 *) prdBuf );
off = FP_OFF( (unsigned char far *) 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 );
dma_pci_prd_ptr = prdBufPtr = MK_FP( seg, 0 );
// ... current size of the SIMPLE/COMPLEX PRD buffer
dma_pci_num_prd = 0;
// read the BM status reg and save the upper 3 bits.
statReg = sub_readBusMstrStatus() & 0x60;
// initialize the large PRD list info
dma_pci_largePrdBufPtr = (void *) 0;
dma_pci_largeIoBufPtr = (void *) 0;
dma_pci_largeMaxB = 0;
dma_pci_largeMaxS = 0;
return 0;
}
//***********************************************************
//
// determine max dma transfer for PRD type LARGE
//
// dma_pci_prd_type == PCI_TYPE_LARGE uses part of the caller's
// I/O buffer to hold a large PRD list and another part of the
// caller's I/O buffer to send/receive the actual data. Each
// PRD entry uses the same memory address.
//
//***********************************************************
void dma_pci_set_max_xfer( unsigned int seg, unsigned int off,
long bufSize )
{
long bufStart; // buffer starting physical memory address
long bufEnd; // buffer ending physical memory address (+1)
long pmaStart; // start of 64K area within buffer
long pmaEnd; // end of 64K area within buffer
// save buffer size
reg_buffer_size = bufSize;
// large DMA xfers not supported
dma_pci_largePrdBufPtr = (void *) 0;
dma_pci_largeIoBufPtr = (void *) 0;
dma_pci_largeMaxB = 0L;
dma_pci_largeMaxS = 0L;
// convert I/O buffer address from seg:off to an absolute memory address
// note: the physical address must be a word boundary (an even number).
bufStart = (unsigned long) seg;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -