//////////////////////////////////////////////////////////////////////////////// // // 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 // 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.Break(); } if ( (status & (byte)BUSMASTER_STATUS_MASK_ERROR) > 0 ) { Tracing.Log(Tracing.Debug,"BusMaster.Disarm: error!!!!\n",(UIntPtr) 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