singrdk/base/Drivers/Disk/BusMasterDma.cs

365 lines
14 KiB
C#

////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: BusMasterDma.cs
//
// See "Programming Interface for Bus Master IDE Controller"
// or www.t13.org 1510D Draft "ATA/ATAPI Host Adapters Standard(ATA - Adapter)"
//
// 1. Software prepares a PRD Table in host memory.
// 2. Software provides the starting address of the PRD Table by loading the PRD Table
// Pointer Register.
// Setting of the Read/Write Control bit specifies the direction of the data transfer.
// Clearing the Interrupt and Error bits in the ATA Bus Master Status register
// to zero readies the adapter for a data transfer.
// 3. Software issues the appropriate DMA transfer command to the device.
// 4. Software initiates the bus master function by writing a one to the Start bit
// in the ATA Bus Master Command Register for the appropriate channel.
// 5. The adapter transfers data to/from host memory responding to DMA requests from the ATA device.`
//
//#define DEBUG_BUS_MASTER_DMA
using System;
using System.Text;
using System.Threading;
using System.Runtime.CompilerServices; //StructAlign attribute
using System.Runtime.InteropServices; //structLayout attribute
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Drivers.IDE;
using Microsoft.Singularity.V1.Services;
namespace Microsoft.Singularity.Drivers.IDE
{
[CLSCompliant(false)]
public class BusMasterDma
{
enum BmState
{
BmIdle = 0,
BmPrepared ,
BmArmed,
BmDisarmed,
}
public const int PRD_BYTES_PER_ENTRY = 8;
public const int PRD_ALIGNMENT = 4096;
public const int PRD_MAX_ENTRIES = 4096 / PRD_BYTES_PER_ENTRY;
// BusMaster Status Bits
private const byte BUSMASTER_STATUS_MASK_DEVICE1_DMA_OK = 0x40;
private const byte BUSMASTER_STATUS_MASK_DEVICE0_DMA_OK = 0x20;
private const byte BUSMASTER_STATUS_MASK_INTERRUPT = 0x04;
private const byte BUSMASTER_STATUS_MASK_ERROR = 0x02;
private const byte BUSMASTER_STATUS_MASK_ACTIVE = 0x01;
//BusMaster Control bits
private const byte BUSMASTER_CONTROL_MASK_WRITE = 0x08;
private const byte BUSMASTER_CONTROL_MASK_START = 0x01;
private const byte BUSMASTER_CONTROL_MASK_STOP = 0x00;
private IoPort commandPort;
private IoPort statusPort;
private IoPort prdPort;
private IoMemory prdRegion;
private IdeConfig ideConfigHandle;
private bool runningVPC;
private BmState busMasterState; // the state we think the BusMaster it is in
const byte PrdTableTerminate = 0x80;
public BusMasterDma(IdeConfig! ideConfig)
{
ideConfigHandle = ideConfig;
// Set up BusMaster ports for this controller
// <command, status, PRD table>
commandPort = ideConfig.DmaBase.PortAtOffset(0, 1, Access.ReadWrite);
statusPort = ideConfig.DmaBase.PortAtOffset(2, 1, Access.ReadWrite);
prdPort = ideConfig.DmaBase.PortAtOffset(4, 4, Access.ReadWrite);
// PRD needs to be 4-byte aligned and within one 4k page.
prdRegion =
IoMemory.AllocatePhysical(PRD_MAX_ENTRIES * PRD_BYTES_PER_ENTRY,
PRD_ALIGNMENT);
} // BusMasterInitialize
public void Uninitialize ()
{
commandPort = null; //???
}
public void Setup ()
{
}
public void BmPrepareController (IdeRequest! ideRequest)
{
// Perform steps 1 and 2 above: set up PRD, clear
// error snd interrupt
// Init. Scatter Gather List Register
uint thePrd = FillPrdTable(ideRequest);
prdPort.Write32(thePrd);
// Clear Errors
byte status = (byte) (BUSMASTER_STATUS_MASK_INTERRUPT | BUSMASTER_STATUS_MASK_ERROR);
statusPort.Write8(status);
return;
} // BmPrepareController
public void SetVirtualized()
{
runningVPC = true;
return;
}
public int Arm (IdeRequest! ideRequest)
{
byte value = BUSMASTER_CONTROL_MASK_START;
if (ideRequest.Command == IdeCmdType.Read) {
value |= BUSMASTER_CONTROL_MASK_WRITE;
}
commandPort.Write8(value); // enable BM
return 0;
} // BmArm
public byte GetStatus()
{
byte status = statusPort.Read8();
return status;
}
public bool InterruptHigh()
{
//probe the status register to see if we have been interrupted
byte status = statusPort.Read8();
if ((status & (byte) BUSMASTER_STATUS_MASK_INTERRUPT) == 0) {
return false;
}
return true;
}
public void Disarm ()
{
//According to the "Programming Interface for Bus Master IDE Controller"
// 1) reset the Start/Stop bit in command register
// 2) read controller status
// 3) read drive status
// to determine if the xfer completed successfully.
commandPort.Write8(BUSMASTER_CONTROL_MASK_STOP); // disable BM
//Tracing.Log(Tracing.Debug," stop: fullstatus ={0:x}\n",(UIntPtr)ideConfigHandle.IdeController.ReadFullStatus());
byte status = GetStatus();
if ((status & (byte)BUSMASTER_STATUS_MASK_INTERRUPT) == 0) {
Tracing.Log(Tracing.Debug,"BusMaster.Disarm: interrupt line not set {0}!\n",(UIntPtr) status);
DebugStub.WriteLine("BusMaster.Disarm: interrupt line not set {0}!\n",__arglist(status));
//DebugStub.Break();
}
if ((status & (byte)BUSMASTER_STATUS_MASK_ERROR) > 0) {
Tracing.Log(Tracing.Debug,"BusMaster.Disarm: error!!!!\n",(UIntPtr) status);
DebugStub.WriteLine("BusMaster.Disarm: error!!!!\n",__arglist(status));
//DebugStub.Break();
}
status = (byte) (BUSMASTER_STATUS_MASK_INTERRUPT | BUSMASTER_STATUS_MASK_ERROR);
statusPort.Write8( status); // clear interrupt BM
//Tracing.Log(Tracing.Debug,"cntlr status ={0:x}\n",(UIntPtr)ideConfigHandle.IdeController.ReadFullStatus());
//Tracing.Log(Tracing.Debug,"bm status : {0:x}",(UIntPtr) statusPort.Read8());
} // BmDisarm
[ System.Diagnostics.Conditional("DEBUG_BUS_MASTER_DMA") ]
public void DumpPrd()
{
for (int i = 0; i < PRD_MAX_ENTRIES; i++) {
uint address;
uint length;
bool eot;
ReadPrdEntry(i, out address, out length, out eot);
Tracing.Log(Tracing.Debug,
"prd[{0}]: {1:x8} {2:x4}\n",
(UIntPtr) i,
(UIntPtr) address,
(UIntPtr) length);
if (eot) {
Tracing.Log(Tracing.Debug, "prd[{0}]: EOT\n", (UIntPtr) i);
break;
}
}
}
private uint FillPrdTable(IdeRequest! ideRequest)
{
// given a memory address and a length generate
// a Physical Region Descriptor Table (PDRT) to be used
// in a IDE busmaster DMA transfer
//a PRDT table is an array of PRD entries, each 8 bytes in
//length. There is no count associated with this structure
// Bit 7 of the last byte of the last entry signifies the
//end of the table
// PRD (Physical Region Descriptor)
// the first 4 bytes of a PRD specify the memory address
// Bytes 5 and 6 (16 bits) specify the length.
// At most a PRD can specify a transfer of 64KB.
// The memory specified by a PRD cannot cross a 64KB boundary
// Any transfer that would cross such a boundary needs to be
// split into to separate PRDs
uint addr = (uint)((UIntPtr)ideRequest.BufferAddress + ideRequest.BufferOffset);
uint len = (uint)ideRequest.Length;
// Write a bad entry at the end
WritePrdEntry(PRD_MAX_ENTRIES - 1, 0, 0xbad1, true);
uint baseAddr = addr;
uint bytesToMap = len;
int i = 0;
while (0 != bytesToMap) {
uint did = WritePrdChunk(i, baseAddr, bytesToMap);
baseAddr += did;
bytesToMap -= did;
i++;
}
// DEBUG CHECK
uint computedLen = 0 ;
bool eotFound = false;
for (i = 0; i < PRD_MAX_ENTRIES; i++) {
uint dummy;
uint length;
bool eot;
ReadPrdEntry(i, out dummy, out length, out eot);
computedLen = computedLen + length;
if (eot) {
eotFound = true;
break;
}
}
if (computedLen != len || !eotFound) {
throw new ApplicationException("PRD length mismatch");
}
DumpPrd();
return (uint) prdRegion.PhysicalAddress.Value;
}
private UIntPtr Get64KLimit(UIntPtr a)
{
const uint count = 0x10000;
return (a.ToUInt32() + count) & ~(count - 1u);
}
// Write a descriptor for up to 64K of user data. Write stops on
// 64K boundaries or discontinuity in physical memory.
//
// Returns the number of bytes written into PRD.
private uint WritePrdChunk(int prdIndex,
UIntPtr baseVA,
uint baseVALength)
{
UIntPtr basePA;
UIntPtr bytesOnPage;
if (!DeviceService.GetDmaPhysicalAddress(baseVA,
out basePA,
out bytesOnPage)) {
throw new ApplicationException(
"Bad physical address translation"
);
}
UIntPtr basePALimit = Get64KLimit(basePA);
if (basePALimit > basePA + baseVALength) {
basePALimit = basePA + baseVALength;
}
UIntPtr expectedPA = basePA + bytesOnPage;
baseVA += bytesOnPage;
while (expectedPA < basePALimit) {
UIntPtr currentPA;
if (!DeviceService.GetDmaPhysicalAddress(baseVA,
out currentPA,
out bytesOnPage)) {
throw new ApplicationException(
"Bad physical address translation"
);
}
if (currentPA != expectedPA) {
// Physical page discontinuity
break;
}
expectedPA = currentPA + bytesOnPage;
baseVA = baseVA + bytesOnPage;
}
if (expectedPA < basePALimit) {
basePALimit = expectedPA;
}
uint done = (basePALimit - basePA).ToUInt32();
WritePrdEntry(prdIndex,
basePA.ToUInt32(),
(ushort) (done & 0xffff),
done == baseVALength);
return done;
}
private void
WritePrdEntry(int index,
uint addr,
uint length,
bool endOfTable)
requires length != 0 && length <= 0x10000;
{
// PRD structure in octets:
// - 0:3 baseAddress
// - 4:5 length
// - 6:6 reserved
// - 7:7 end of table
int baseOffset = index * PRD_BYTES_PER_ENTRY;
this.prdRegion.Write32(baseOffset,
ByteOrder.HostToLittleEndian(addr));
ushort slen = (ushort)(length & 0xffffu); // 0 == 64K
this.prdRegion.Write16(baseOffset + 4,
ByteOrder.HostToLittleEndian(slen));
this.prdRegion.Write8(baseOffset + 7,
endOfTable ? PrdTableTerminate : (byte)0);
}
private void
ReadPrdEntry(int index,
out uint addr,
out uint length,
out bool endOfTable)
{
int baseOffset = index * PRD_BYTES_PER_ENTRY;
addr = ByteOrder.LittleEndianToHost(
this.prdRegion.Read32(baseOffset)
);
ushort slen = ByteOrder.LittleEndianToHost(
this.prdRegion.Read16(baseOffset + 4)
);
length = (slen == 0) ? 0x10000u : (uint)slen;
byte end = this.prdRegion.Read8(baseOffset + 7);
endOfTable = (end == PrdTableTerminate);
}
} // BusMaster Class
}//namespace