First post, by superfury
I've written some DMA controller emulation for my emulator I'm making. Anyone knows if this is correct? Atm I'm not sure about the final lines (pointed in the code with 'not entirely sure about this').
delay(us) delays the thread a specified time, allowing other threads (core CPU/EMU thread and timer thread (handling VGA and other timers)).
DMA_Thread is the main processing thread.
DMA_Read/WriteIO handle IN and OUT instructions by the CPU (80(1)86 CPU atm, 286+ unfinished, 8086 still seems to have some little bugs).
Information sources:
IBM documentation: http://pdos.csail.mit.edu/6.828/2004/readings … dware/8237A.pdf
OSDev documentation: http://wiki.osdev.org/DMA
/*
DMA Controller (8237A)
*/
#include "headers/types.h" //Basic types!
#include "headers/hardware/ports.h" //Port support!
typedef void (*DMAWriteBHandler)(byte *EOP, byte data); //Write handler to DMA hardware!
typedef byte (*DMAReadBHandler)(byte *EOP); //Read handler from DMA hardware!
typedef void (*DMAWriteWHandler)(byte *EOP, word data); //Write handler to DMA hardware!
typedef word (*DMAReadWHandler)(byte *EOP); //Read handler from DMA hardware!
typedef struct
{
DMAModeRegister ModeRegister; //Our mode register!
word CurrentAddressRegister; //Start address register!
word BaseAddressRegister; //Start address base register when resetting (base)! Written together with the start address!
word CurrentCountRegister; //Count register set by the user and counting!
word BaseCountRegister; //Current count register when resetting (base)! Written together with the counter!
byte PageAddressRegister; //Upper 8 bits of the 24-bit transfer memory address!
DMAWriteBHandler WriteBHandler; //Write handler 8-bit!
DMAReadBHandler ReadBHandler; //Read handler 8-bit
DMAWriteWHandler WriteWHandler; //Write handler 16-bit!
DMAReadWHandler ReadWHandler; //Read handler 16-bit!
} DMAChannelTYPE; //Contains all info about an DMA channel!
typedef union
{
struct
{
byte SEL : 2; //Which channel are we?
byte TransferType : 2; //00=Self test, 1=Write to memory, 2=Read from memory, 3=Invalid.
byte Auto : 1; //After the transfer has completed, Reset to the address and count values originally programmed.
byte Down : 1; //Top-down processing if set, else increment addresses.
byte Mode : 2; //Mode: 0=Transfer on Demand, 1=Single DMA Transfer, 2=Block DMA transfer, 3=Cascade Mode (Used to cascade another DMA controller).
}; //The mode register!
byte ModeRegisterB; //Mode register for all 4 channels!
} DMAModeRegister;
typedef struct
{
//Public registers
DMAChannelTYPE DMAChannel[4]; //4 DMA Channels per Controller!
union
{
struct
{
byte TransferComplete : 4; //Transfer complete for 4 channels (high to low, bit 0=TC0.)
byte RequestPending : 4; //Request pending for 4 channels (high to low, bit 0=REQ0.)
};
byte StatusRegister; //Status register for a DMA controller!
}; //A Status register!
byte CommandRegister; //Command Register
byte RequestRegister; //Request Register!
byte MultiChannelMaskRegister; //MultiChennel Mask Register, bit0-3=channel 0-3; 0=unmask (enabled), 1=mask (disabled)
//Internal registers!
byte FlipFlop; //Current flipflop: Cannot be accessed by software!
byte IntermediateRegister; //Intermediate register!
//Master reset register doesn't store!
//MaskResetRegister doesn't store!
} DMAControllerTYPE; //Contains a DMA Controller's registers!
DMAControllerTYPE DMAController[2]; //We have 2 DMA Controllers!
void initDMAControllers() //Init function for BIOS!
{
memset(&DMAController[0],0,sizeof(DMAController)); //Init DMA Controller channels 0-3 (0 unused: for DRAM Refresh)
memset(&DMAController[1],0,sizeof(DMAController)); //Init DMA Controller channels 4-7 (4 unused: for DMA Controller coupling)
}
//Easy sets of high and low nibbles (word data)!
#define SETHIGH(b,v) b=((b&0xFF)|(v<<8))
#define SETLOW(b,v) b = ((b&0xFF00)|v)
void DMA_WriteIO(word port, byte value) //Handles OUT instructions to I/O ports.
{
byte controller = (port>=0xC0)?1:0; //What controller?
byte reg = port; //What register is selected, default to 1:1 mapping?
if (controller) //16-bit register (second DMA controller)?
{
reg -= 0xC0; //Take the base!
reg >>= 1; //Every port is on a offset of 2!
//Now reg is on 1:1 mapping too!
}
byte channel; //Which channel to use?
switch (port) //What port?
{
//Extra 8 bits for addresses:
case 0x87: //
DMAController[0].DMAChannel[0].PageAddressRegister = value; //Set!
break;
case 0x83: //
DMAController[0].DMAChannel[1].PageAddressRegister = value; //Set!
break;
case 0x81: //
DMAController[0].DMAChannel[2].PageAddressRegister = value; //Set!
break;
case 0x82: //
DMAController[0].DMAChannel[3].PageAddressRegister = value; //Set!
break;
//Extra 8 bits for addresses:
case 0x8F: //
DMAController[1].DMAChannel[0].PageAddressRegister = value; //Set!
break;
case 0x8B: //
DMAController[1].DMAChannel[1].PageAddressRegister = value; //Set!
break;
case 0x89: //
DMAController[1].DMAChannel[2].PageAddressRegister = value; //Set!
break;
case 0x8A: //
DMAController[1].DMAChannel[3].PageAddressRegister = value; //Set!
break;
default: //Unknown port?
break;
default: //Non-page register!
switch (reg) //What register is selected?
{
case 0x00:
case 0x02:
case 0x04:
case 0x06: //Address register?
channel = port>>1; //What channel?
if (DMAController[controller].FlipFlop) //High?
{
SETHIGH(DMAController[controller].DMAChannel[channel].CurrentAddressRegister,value); //Set high nibble!
SETHIGH(DMAController[controller].DMAChannel[channel].BaseAddressRegister,value); //Set high nibble!
}
else //Low?
{
SETLOW(DMAController[controller].DMAChannel[channel].CurrentAddressRegister,value); //Set low nibble!
SETLOW(DMAController[controller].DMAChannel[channel].BaseAddressRegister,value); //Set low nibble!
}
DMAController[controller].FlipFlop = !DMAController[controller].FlipFlop; //Flipflop!
break;
case 0x01:
case 0x03:
case 0x05:
case 0x07: //Count register?
channel = (port-1)>>1; //What channel?
if (DMAController[controller].FlipFlop) //High?
{
SETHIGH(DMAController[controller].DMAChannel[channel].CurrentCountRegister,value); //Set high nibble!
SETHIGH(DMAController[controller].DMAChannel[channel].BaseCountRegister,value); //Set high nibble!
}
else //Low?
{
SETLOW(DMAController[controller].DMAChannel[channel].CurrentCountRegister,value); //Set low nibble!
SETLOW(DMAController[controller].DMAChannel[channel].BaseCountRegister,value); //Set low nibble!
}
DMAController[controller].FlipFlop = !DMAController[controller].FlipFlop; //Flipflop!
break;
case 0x08: //Command register!
DMAController[controller].CommandRegister = value; //Set!
break;
case 0x09: //Request register!
DMAController[controller].RequestRegister = value; //Set!
break;
case 0x0A: //Single Channel Mask Register!
DMAController[controller].MultiChannelMaskRegister &= ~(1<<(value&3)); //Turn off the channel!
DMAController[controller].MultiChannelMaskRegister |= ((value&4)>>2)<<(value&3)); //Mask it if needed!
break;
case 0x0B: //Mode Register!
DMAController[controller].ModeRegisters[value&0x3].ModeRegister = value; //Set!
break;
case 0x0C: //Flip-Flop Reset Register!
DMAController[controller].FlipFlop = 0; //Reset!
break;
case 0x0D: //Master Reset Register!
DMAController[controller].FlipFlop = 0; //Reset!
DMAController[controller].StatusRegister = 0; //Reset!
DMAController[controller].MultiChannelMaskRegister |= 0xF; //Set the masks!
break;
case 0x0E: //Mask Reset Register!
DMAController[controller].MultiChannelMaskRegister &= ~0xF; //Clear the masks!
break;
case 0x0F: //MultiChannel Mask Register!
DMAController[controller].MultiChannelMaskRegister = value; //Set!
break;
}
break;
}
}
byte DMA_ReadIO(word port) //Handles IN instruction from CPU I/O ports
{
byte result; //To hold the result!
byte controller = (port>=0xC0)?1:0; //What controller?
byte reg = port; //What register is selected, default to 1:1 mapping?
if (controller) //16-bit register (second DMA controller)?
{
reg -= 0xC0; //Take the base!
reg >>= 1; //Every port is on a offset of 2!
//Now reg is on 1:1 mapping too!
}
switch (port) //What port?
{
//Extra 8 bits for addresses:
case 0x87: //
return DMAController[0].DMAChannel[0].PageAddressRegister; //Get!
break;
case 0x83: //
return DMAController[0].DMAChannel[1].PageAddressRegister; //Get!
break;
case 0x81: //
return DMAController[0].DMAChannel[2].PageAddressRegister; //Get!
break;
case 0x82: //
return DMAController[0].DMAChannel[3].PageAddressRegister; //Get!
break;
//Extra 8 bits for addresses:
case 0x8F: //
return DMAController[1].DMAChannel[0].PageAddressRegister; //Get!
break;
case 0x8B: //
return DMAController[1].DMAChannel[1].PageAddressRegister; //Get!
break;
case 0x89: //
return DMAController[1].DMAChannel[2].PageAddressRegister; //Get!
break;
case 0x8A: //
return DMAController[1].DMAChannel[3].PageAddressRegister; //Get!
break;
default: //Non-page register!
switch (reg) //What register is selected?
{
//Controller 0!
case 0x08: //Status Register!
result = DMAController[0].StatusRegister; //Get!
DMAController[0].StatusRegister &= ~0xF; //Clear TC bits!
return result; //Get!
break;
case 0x0D: //Intermediate Register!
return DMAController[0].IntermediateRegister; //Get!
break;
case 0x0F: //MultiChannel Mask Register!
return DMAController[0].MultiChannelMaskRegister; //Get!
break;
default: //Unknown port?
break;
}
break;
}
return ~0; //Unknown port!
}
void DMA_autoinit(byte controller, byte channel) //Autoinit functionality.
{
DMAControllers[controller].DMAChannel[channel].CurrentAddressRegister = DMAControllers[controller].DMAChannel[channel].CurrentAddressRegisterReset; //Reset address register!
DMAControllers[controller].DMAChannel[channel].CurrentCountRegister = DMAControllers[controller].DMAChannel[channel].CurrentCountRegisterReset; //Reset count register!
}
//Flags for different responses that might need to be met.
#define FLAG_TC 1
#define FLAG_EOP 2
/* Main DMA Controller processing thread */
void DMA_Thread()
{
byte current = 0; //Current channel in total (0-7)
nextcycle: //Next cycle to process!
byte controller = ((current&4)>>2); //Init controller
byte channel = (current&3); //Channel to use! Channels 0 are unused (DRAM memory refresh (controller 0) and cascade DMA controller (controller 1))
if (!(DMAControllers[controller].CommandRegister&4) && channel) //Controller not disabled and valid channel to transfer?
{
DMAModeRegister moderegister;
moderegister.ModeRegisterB = DMAControllers[controller].ModeRegisters[channel].ModeRegisterB; //Read the mode register to use!
if (moderegister.Mode==3) goto nextchannel; //Skip channel: invalid!
if (DMAControllers[controller].RequestPending&(1<<channel) && (DMAControllers[controller].TransferComplete&(1<<channel))) //Request pending and doing nothing atm?
{
DMAControllers[controller].TransferComplete &= ~(1<<channel); //Clear transfer complete: we're pending!
DMAControllers[controller].RequestPending &= ~(1<<channel); //Clear pending: we're not pending anymore!
}
if (!DMAControllers[controller].MultiChannelMaskRegister&(1<<channel)) //Channel not masked off?
{
if (!(DMAControllers[controller].TransferComplete&(1<<channel))) //Transfer not yet complete? We're running the transfer a bit!
{
byte processed = 0; //Default: nothing going on!
/*
processed bits:
bit 0: TC (Terminal Count) occurred.
bit 1: External EOP encountered.
*/
uint_32 address; //The address to use!
if (controller) //16-bits transfer has a special addressing scheme?
{
address = DMAControllers[controller].DMAChannel[channel].StartAddress; //Load the start address!
address <<= 1; //Shift left by 1 to obtain a multiple of 2!
address &= 0xFFFF; //Clear the overflowing bit, if any!
}
else //8-bit has normal addressing!
{
address = DMAControllers[controller].DMAChannel[channel].StartAddress; //Normal addressing!
}
address |= (DMAControllers[controller].DMAChannel[channel].PageAddressRegister<<16); //Apply page address to get the full address!
byte EOP = 0; //EOP detected?
switch (moderegister.TransferType)
{
case 0: //???
break;
case 1: //Writing to memory? (Reading from device)
if (controller) //16-bits?
{
if (DMAControllers[controller].DMAChannel[channel].ReadWHandler) //Valid handler?
{
MMU_directww(address,DMAControllers[controller].DMAChannel[channel].ReadWHandler(&EOP)); //Read using handler!
}
}
else //8-bits?
{
if (DMAControllers[controller].DMAChannel[channel].ReadBHandler) //Valid handler?
{
MMU_directwb(address,DMAControllers[controller].DMAChannel[channel].ReadBHandler(&EOP)); //Read using handler!
}
}
break;
case 2: //Reading from memory? (Writing to device)
if (controller) //16-bits?
{
if (DMAControllers[controller].DMAChannel[channel].WriteWHandler) //Valid handler?
{
DMAControllers[controller].DMAChannel[channel].WriteWHandler(&EOP,MMU_directrw(address)); //Read using handler!
}
}
else //8-bits?
{
if (DMAControllers[controller].DMAChannel[channel].WriteBHandler) //Valid handler?
{
DMAControllers[controller].DMAChannel[channel].WriteBHandler(&EOP,MMU_directrb(address)); //Read using handler!
}
}
break;
default: //Invalid?
break;
}
if (EOP)
{
processed |= FLAG_EOP; //Set EOP flag!
}
//Process the address counter step: we've been processed and ready to move on!
if (moderegister.Down) //Decrease address?
{
--DMAControllers[controller].DMAChannel[channel].CurrentAddressRegister; //Decrease counter!
}
else //Increase counter?
{
++DMAControllers[controller].DMAChannel[channel].CurrentAddressRegister; //Decrease counter!
}
--DMAControllers[controller].DMAChannel[channel].CurrentCountRegister; //Next step calculated!
if (DMAControllers[controller].DMAChannel[channel].CurrentCountRegister==0xFFFF) //Finished when overflows below 0!
{
processed |= FLAG_TC; //Set flag: terminal count occurred!
DMAControllers[controller].TransferComplete |= (1<<channel); //Transfer complete! }
//Process all flags that has occurred!
//*** Not entirely sure about this ****
switch (moderegister.Mode) //What mode are we processing in?
{
case 0: //Single Transfer Mode
if (moderegister.Auto && (processed&FLAG_TC)) //AutoInit on TC!
{
DMA_AutoInit(controller,channel); //Perform utoInit!
}
break;
case 1: //Block Transfer Mode
if (moderegister.Auto && (processed&(FLAG_TC|FLAG_EOP))) //AutoInit on TC/EOP!
{
DMA_AutoInit(controller,channel); //Perform autoinit!
}
break;
case 2: //Demand Transfer Mode
if (moderegister.Auto && (processed&(FLAG_TC|FLAG_EOP))) //AutoInit on TC/EOP!
{
DMA_AutoInit(controller,channel); //Perform autoinit!
}
break;
}
}
}
}
nextchannel: //Skipping this channel (used by cascade mode channels)
if (++current&(~0x7)) //Last controller finished (overflow channel counter)?
{
current = 0; //Reset controller!
delay(1); //Wait a bit to give other threads some time!
}
goto nextcycle; //Next cycle!
}
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io