📄 dma.c
字号:
//
// Permedia3 Sample Display Driver
// dma.c
//
// Copyright (c) 2000 Microsoft Corporation. All rights reserved.
//
// This module manages the Permedia3's use of DMA. This includes the
// command buffering, which the card DMAs, as well as DMA transfers
// of data (bitmaps mostly) from system meory.
#include "pch.h" // Precompiled header support.
#include "debug.h"
#include "register.h"
#include "const.h"
#include "struct.h"
#include "global.h"
#include "proto.h"
#include <ceddk.h>
// This is the current position in a DMA buffer that we are writing to.
static ULONG * l_CurrentPosition;
// This points to the last address that we wrote a command to in the previous
// command bundle.
static ULONG * l_WriterPosition;
// These pointers identify the physical and virtual addresses of the
// beginning and the end of the DMA buffer.
static ULONG * l_VirtualDMABuffer;
static ULONG * l_VirtualDMABufferEnd;
static ULONG l_PhysicalDMABuffer;
static ULONG l_PhysicalDMABufferEnd;
// This is the current Hostin ID number. We attach unique IDs to each DMA
// packet. This allows us to monitor the progress of the chip in the
// command stream.
static ULONG l_HostinId;
// This variable indicates if we need to monitor the DMA status from the
// vertical retrace interrupt.
static BOOL l_NeedToCheckDMA;
// This event is set by the DMA interrupt to signify that it is idle.
static HANDLE l_DMADone;
// Internal prototypes. These functions are not exposed outside this module.
static void RestartDMA();
static PVOID AllocatePhysicalMemory(UINT cbSize, PULONG ppaBuffer);
static BOOL FreePhysicalMemory(PVOID lpMemory, UINT cbSize);
BOOL
InitializeDMA()
{
// InitializeDMA
// This function initializes the DMA command buffering scheme. This function
// is the only one in this module which does not access shared variables
// with Interlocked* functions. (Interrupts are enbaled after DMA in the
// intialization scheme. See init.c.)
// Local variables.
ULONG DMAMemoryControl;
BOOL FnRetVal = FALSE; // Return value for this function.
// We assme the DMA buffer is some number of ULONGSs. We use this
// assumption later.
Assert(SIZEOF_DMA_BUFFER % 4 == 0);
Enter(L"InitializeDMA");
// Set initial Hostin ID.
l_HostinId = 0;
// Enable bus-mastering on the PCI bus.
g_Config.PciCommonConfig.Command |= b_CFGCommand_BusMasterEnable;
if (HalSetBusDataByOffset(PCIConfiguration,
g_Config.PciBusNumber,
g_Config.PciSlotNumber.u.AsULONG,
&g_Config.PciCommonConfig.Command,
r_CFGCommand,
sizeof(ULONG)) == sizeof(ULONG)) {
// Setup the appropriate DMA control registers.
WaitForInputFIFO(3);
DMAMemoryControl = 0;
DMAMemoryControl |= 1 << 2; // Align host-in DMA to 64 bit boundaries
DMAMemoryControl |= 1 << 31; // Align host-out DMA to 64 bit boundaries
// !TODO! Burst size?
//DMAMemoryControl |= (0 & 0x1f) << 24; // burst size n == (1 << 7+n)? Spec indicates n * 128
WriteRegUlong(r_DMAMemoryControl, DMAMemoryControl);
// Allocate buffers for DMA and setup pointers. Use special CE
// API to get contigous physical memory buffers, suitable for DMA.
// !TODO! Should we enable caching on DMA buffers?
l_VirtualDMABuffer = AllocatePhysicalMemory(SIZEOF_DMA_BUFFER, &l_PhysicalDMABuffer);
if (l_VirtualDMABuffer != NULL) {
l_DMADone = CreateEvent(NULL, // No security descriptors in CE
FALSE, // Auto reset
FALSE, // Initially not signaled
NULL); // No name
if (l_DMADone != NULL) {
l_PhysicalDMABufferEnd = l_PhysicalDMABuffer + SIZEOF_DMA_BUFFER;
l_VirtualDMABufferEnd = (ULONG *)((BYTE *)l_VirtualDMABuffer + SIZEOF_DMA_BUFFER);
RestartDMA();
FnRetVal = TRUE;
Message(L"DMA Command stream enabled.\n");
}
else {
Error(L"Unable to create event for DMA completion.\n");
}
}
else {
Error(L"Unable to allocate contiguous memory for DMA buffer.\n");
}
}
else {
Error(L"Unable to enable bus mastering on PCI bus.\n");
}
Exit(L"InitializeDMA");
return FnRetVal;
}
ULONG
RegisterToTag(
ULONG Register
)
{
// RegisterToTag
// This function converts a register address (16 bits) to a DMA register
// tag. No Enter/Exit semantics for inlined functions.
return (Register >> 3);
}
void
WaitForDMASpace(
ULONG Ulongs
)
{
// WaitForDMASpace
// This function should be called before writing a set of commands into the
// DMA buffer system. It verifies that enough space is available for the
// commands.
// Check parameters.
Assert(Ulongs < SIZEOF_DMA_BUFFER);
Enter(L"WaitForDMASpace");
// Compute the number of Ulongs left in the DMA buffer. If we do not have
// enough space left, we wait for the current DMA to complete to restart
// the buffer. Note the use of the +2 to allow us to prepend a HostIdTag
// to the coming buffer.
if ((ULONG)(l_VirtualDMABufferEnd - l_CurrentPosition) < Ulongs + 2) {
WaitNotBusy();
// Restart the buffer. Also sets host in tag for this packet.
RestartDMA();
}
else {
// Write in the new HostInId.
QueueDMATag(r_HostinID, l_HostinId++);
}
Exit(L"WaitForDMASpace");
}
void
QueueDMATag(
ULONG Register,
ULONG Data
)
{
// QueueDMATag
// This function adds a tag and data pair to the command stream. There is
// no Enter/Exit commands becuase this function is intended to be inlined.
// This function assumes that there is enough space in the current DMA
// buffer for the data passed.
*l_CurrentPosition++ = RegisterToTag(Register);
*l_CurrentPosition++ = Data;
}
void
QueueDMAHold(
ULONG Register,
ULONG Count
)
{
// QueueDMAHold
// This function adds a 'hold' packet to the command stream. A 'hold' packet
// tells the the DMA engine to hold the tag constant for a certain number
// of consecutive data elements. Useful for consecutively writing different
// values to the same register. No Enter/Exit semantics for inlines.
// This function assumes that there is enough space in the current DMA
// buffer for the data passed.
*l_CurrentPosition++ = (RegisterToTag(Register) | ((Count - 1) << 16));
}
void
QueueDMAIncrement(
ULONG Register,
ULONG Count
)
{
// QueueDMAIncrement
// This function adds and 'increment' packet to the command stream. This
// packet instructs the DMA engine to increment the tag value for each
// consecutive data element. No Enter/Exit semantics.
// This function assumes that there is enough space in the current DMA
// buffer for the data passed.
*l_CurrentPosition++ = (RegisterToTag(Register) | ((Count - 1) << 16) | (1 << 14));
}
void
QueueDMAIndex(
ULONG Register,
ULONG Mask
)
{
// QueueDMAIndex
// This function adds a DMA 'index' packet to the command stream. This packet
// identifies an 11 bit group, and then holds a 16 bit mask that identifies
// which of the registers in the group are to be written. The data following the
// index packet is paired up to the tags, starting at the low bit of the mask.
// This function assumes that there is enough space in the current DMA
// buffer for the data passed.
*l_CurrentPosition++ = ((RegisterToTag(Register) & 0xFF0) | (Mask << 16) | (2 << 14));
}
void
QueueDMAUlong(
ULONG Data
)
{
// QueueDMAUlong
// Add a Ulong (32 bit value) to the command stream. No Enter/Exit semantics
// for inlined functions.
// This function assumes that there is enough space in the current DMA
// buffer for the data passed.
*l_CurrentPosition++ = Data;
}
void
EndDMA()
{
// EndDMA
// This function is called to signify that an entire meaningful group of
// commands have been written into the current buffer, and that we should
// DMA them. No Enter/Exit semantics for inlined functions.
WriteRegUlong(r_DMAContinue, l_CurrentPosition - l_WriterPosition);
l_WriterPosition = l_CurrentPosition;
}
void
DMAInterrupt()
{
// DMAInterrupt
// This function is called to process a DMA interrupt. It mearly sets the
// DMA done event if someone is listening (l_NeedToCheckDMA) and if the
// DMA engine is idle.
Enter(L"DMAInterrupt");
if (l_NeedToCheckDMA) {
if (!IsBusy()) {
SetEvent(l_DMADone);
}
}
Exit(L"DMAInterrupt");
}
void
RestartDMA()
{
// RestartDMA
// This function kicks the DMA buffer off by loading the DMAAddr and
// DMACount registers. It resets the Writer and Current position
// pointers to the start of the buffer as well. Assumes that the
// current DMA is done.
Enter(L"RestartDMA");
l_WriterPosition = l_VirtualDMABuffer;
l_CurrentPosition = l_WriterPosition;
// Start a DMA operation in the first buffer. We will use DMA continues
// each time a packet is completed.
// Write in the new HostInId. No need to check for space or call EndDMA.
QueueDMATag(r_HostinID, l_HostinId++);
WriteRegUlong(r_DMAAddr, l_PhysicalDMABuffer);
WriteRegUlong(r_DMACount, l_CurrentPosition - l_WriterPosition);
l_WriterPosition = l_CurrentPosition;
Exit(L"RestartDMA");
}
void
WaitNotBusy()
{
// WaitNotBusy
// This function will cause the current thread to sleep until the DMA buffer
// is empty. That's to say, all pending operations are complete.
Enter(L"WaitNotBusy");
l_NeedToCheckDMA = TRUE;
WaitForSingleObject(l_DMADone, INFINITE);
l_NeedToCheckDMA = FALSE;
Exit(L"WaitNotBusy");
}
BOOL
IsBusy()
{
// IsBusy
// This function will check the chip against our local HostIn ID to
// determine if all pending DMA operations are complete. Returns TRUE if
// there are still pending operations.
// Local variables.
ULONG CurrentHostInId;
ULONG BufferHostInId;
BOOL FnRetVal; // This is the value returned from this function.
Enter(L"IsBusy");
CurrentHostInId = ReadRegUlong(r_HostinID);
BufferHostInId = l_HostinId - 1;
FnRetVal = (BOOL)(CurrentHostInId != BufferHostInId);
Exit(L"IsBusy");
return FnRetVal;
}
BOOL
FreePhysicalMemory(PVOID lpMemory, UINT cbSize)
{
UnlockPages(lpMemory, cbSize);
VirtualFree(lpMemory,0, MEM_RELEASE);
return(TRUE);
}
PVOID
AllocatePhysicalMemory(UINT cbSize, PULONG ppaBuffer)
{
LPVOID pBuffer = NULL;
LPVOID pLastBuffer = NULL;
LPVOID pNextBuffer = NULL;
ULONG paBuffer = 0;
BOOL fDone = FALSE;
BOOL fResult = FALSE;
UINT i = 0;
//let's first find the maximum # of pages that could be locked
UINT cPages = ADDRESS_AND_SIZE_TO_SPAN_PAGES(NULL, cbSize);
PULONG aPages = NULL;
//allocate an array for the maximum # of pages
aPages = SystemAlloc(sizeof(ULONG) * cPages);
if (!aPages)
{
Error(L"ddi_perm3:AllocatePhysicalMemory error allocating memory.\r\n");
return(NULL);
}
// we need to allocate at least 1 byte
if(cbSize == 0)
cbSize = 1;
do
{
fDone = TRUE;
pLastBuffer = pBuffer;
pBuffer = VirtualAlloc(NULL, cbSize,
MEM_COMMIT, PAGE_NOCACHE | PAGE_READWRITE);
// our allocated block must be on a page boundary!
ASSERT(((DWORD)pBuffer & (PAGE_SIZE - 1)) == 0);
Message(L"Allocated possibly continuous memory block: %%08X\r\n", pBuffer);
if(pBuffer)
{
fResult = LockPages(pBuffer, cbSize, aPages, LOCKFLAG_WRITE);
if(fResult)
{
PULONG pPage;
ASSERT(cPages ==
ADDRESS_AND_SIZE_TO_SPAN_PAGES(pBuffer, cbSize));
pPage = aPages;
// We want page numbers and the kernel gives us the physical address
// of each page, adjust the list. Except on MIPS where the kernel
// gives us the physical address shifted right 6 bits.
for(i = 0 ; i < cPages ; ++i)
{
#if defined(MIPS)
#if defined(R4000) | defined(R5230)
#if defined(R4100)
*pPage >>= PAGE_SHIFT - 4;
#else
*pPage >>= PAGE_SHIFT - 6;
#endif
#else
*pPage >>= PAGE_SHIFT;
#endif
#elif defined(SHx) | defined(x86) | defined(PPC) | defined(ARM)
*pPage >>= PAGE_SHIFT;
#else
#error Unsupported Processor
#endif
++pPage;
}
pPage = aPages;
ASSERT(cPages > 0);
for(i = 0 ; i < cPages - 1 && fDone ; ++i)
{
if(*pPage + 1 != *(pPage + 1))
fDone = FALSE;
++pPage;
}
if(fDone)
paBuffer = *aPages << PAGE_SHIFT;
}
if(!fDone)
*(PPVOID)pBuffer = pLastBuffer;
}
} while(!fDone);
while(pLastBuffer)
{
pNextBuffer = *(PPVOID)pLastBuffer;
UnlockPages(pLastBuffer, cbSize);
VirtualFree(pLastBuffer,0, MEM_RELEASE);
pLastBuffer = pNextBuffer;
}
// our allocated block must be on a page boundary or we will miss
// part of the physical address and the interrupt table will not be aligned!
ASSERT(((DWORD)pBuffer & (PAGE_SIZE - 1)) == 0);
ASSERT(ppaBuffer);
*ppaBuffer = paBuffer;
ASSERT((*ppaBuffer & (PAGE_SIZE - 1)) == 0);
if(aPages)
SystemFree(aPages);
Message(L"ddi_perm3:AllocatePhysicalMemory Continuous memory block VA: %08X PA:%08X \r\n", pBuffer, *ppaBuffer);
return(pBuffer);
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -