diff --git a/subsystems/ntvdm/CMakeLists.txt b/subsystems/ntvdm/CMakeLists.txt index 48c9c80c13b..7381ec49e35 100644 --- a/subsystems/ntvdm/CMakeLists.txt +++ b/subsystems/ntvdm/CMakeLists.txt @@ -5,7 +5,9 @@ list(APPEND SOURCE bios.c dos.c emulator.c - hardware.c + pic.c + timer.c + ps2.c ntvdm.c ntvdm.rc) diff --git a/subsystems/ntvdm/bios.c b/subsystems/ntvdm/bios.c index 6524910becc..88afa6d3059 100644 --- a/subsystems/ntvdm/bios.c +++ b/subsystems/ntvdm/bios.c @@ -6,10 +6,39 @@ * PROGRAMMERS: Aleksandar Andrejevic */ -#include "ntvdm.h" +/* INCLUDES *******************************************************************/ -BYTE CursorRow, CursorCol; -WORD ConsoleWidth, ConsoleHeight; +#include "bios.h" +#include "emulator.h" +#include "pic.h" +#include "timer.h" + +/* PRIVATE VARIABLES **********************************************************/ + +static BYTE CursorRow, CursorCol; +static WORD ConsoleWidth, ConsoleHeight; + +/* PRIVATE FUNCTIONS **********************************************************/ + +static COORD BiosVideoAddressToCoord(ULONG Address) +{ + COORD Result = {0, 0}; + CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; + HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); + + if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo)) + { + ASSERT(FALSE); + return Result; + } + + Result.X = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) % ConsoleInfo.dwSize.X; + Result.Y = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) / ConsoleInfo.dwSize.X; + + return Result; +} + +/* PUBLIC FUNCTIONS ***********************************************************/ BOOLEAN BiosInitialize() { @@ -83,24 +112,6 @@ BOOLEAN BiosInitialize() return TRUE; } -static COORD BiosVideoAddressToCoord(ULONG Address) -{ - COORD Result = {0, 0}; - CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; - HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); - - if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo)) - { - ASSERT(FALSE); - return Result; - } - - Result.X = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) % ConsoleInfo.dwSize.X; - Result.Y = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) / ConsoleInfo.dwSize.X; - - return Result; -} - VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress) { ULONG i; diff --git a/subsystems/ntvdm/bios.h b/subsystems/ntvdm/bios.h new file mode 100644 index 00000000000..c3e2b0175c3 --- /dev/null +++ b/subsystems/ntvdm/bios.h @@ -0,0 +1,36 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: bios.h + * PURPOSE: VDM BIOS (header file) + * PROGRAMMERS: Aleksandar Andrejevic + */ + +#ifndef _BIOS_H_ +#define _BIOS_H_ + +/* INCLUDES *******************************************************************/ + +#include "ntvdm.h" + +/* DEFINES ********************************************************************/ + +#define CONSOLE_VIDEO_MEM_START 0xB8000 +#define CONSOLE_VIDEO_MEM_END 0xBFFFF +#define ROM_AREA_START 0xC0000 +#define ROM_AREA_END 0xFFFFF +#define BIOS_PIC_MASTER_INT 0x08 +#define BIOS_PIC_SLAVE_INT 0x70 +#define BIOS_SEGMENT 0xF000 +#define VIDEO_BIOS_INTERRUPT 0x10 +#define CONSOLE_FONT_HEIGHT 8 + +/* FUNCTIONS ******************************************************************/ + +BOOLEAN BiosInitialize(); +VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress); +VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress); +VOID BiosVideoService(); +VOID BiosHandleIrq(BYTE IrqNumber); + +#endif diff --git a/subsystems/ntvdm/dos.c b/subsystems/ntvdm/dos.c index 1ba037a7fd2..ae84dd75e2f 100644 --- a/subsystems/ntvdm/dos.c +++ b/subsystems/ntvdm/dos.c @@ -6,10 +6,17 @@ * PROGRAMMERS: Aleksandar Andrejevic */ -#include "ntvdm.h" +/* INCLUDES *******************************************************************/ -WORD CurrentPsp = SYSTEM_PSP, LastError = 0; -DWORD DiskTransferArea; +#include "dos.h" +#include "emulator.h" + +/* PRIVATE VARIABLES **********************************************************/ + +static WORD CurrentPsp = SYSTEM_PSP; +static DWORD DiskTransferArea; + +/* PRIVATE FUNCTIONS **********************************************************/ static VOID DosCombineFreeBlocks(WORD StartBlock) { @@ -78,6 +85,8 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment) return DestSegment; } +/* PUBLIC FUNCTIONS ***********************************************************/ + WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable) { WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0; diff --git a/subsystems/ntvdm/dos.h b/subsystems/ntvdm/dos.h new file mode 100644 index 00000000000..b88dadb397e --- /dev/null +++ b/subsystems/ntvdm/dos.h @@ -0,0 +1,133 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: dos.h + * PURPOSE: VDM DOS Kernel (header file) + * PROGRAMMERS: Aleksandar Andrejevic + */ + +#ifndef _DOS_H_ +#define _DOS_H_ + +/* INCLUDES *******************************************************************/ + +#include "ntvdm.h" + +/* DEFINES ********************************************************************/ + +#define DOS_VERSION 0x0600 +#define DOS_CONFIG_PATH L"%SystemRoot%\\system32\\CONFIG.NT" +#define DOS_COMMAND_INTERPRETER L"%SystemRoot%\\system32\\COMMAND.COM /k %SystemRoot%\\system32\\AUTOEXEC.NT" +#define FIRST_MCB_SEGMENT 0x1000 +#define USER_MEMORY_SIZE 0x8FFFF +#define SYSTEM_PSP 0x08 +#define SYSTEM_ENV_BLOCK 0x800 +#define SEGMENT_TO_MCB(seg) ((PDOS_MCB)((ULONG_PTR)BaseAddress + TO_LINEAR((seg), 0))) +#define SEGMENT_TO_PSP(seg) ((PDOS_PSP)((ULONG_PTR)BaseAddress + TO_LINEAR((seg), 0))) + +#pragma pack(push, 1) + +typedef struct _DOS_MCB +{ + CHAR BlockType; + WORD OwnerPsp; + WORD Size; + BYTE Unused[3]; + CHAR Name[8]; +} DOS_MCB, *PDOS_MCB; + +typedef struct _DOS_FCB +{ + BYTE DriveNumber; + CHAR FileName[8]; + CHAR FileExt[3]; + WORD BlockNumber; + WORD RecordSize; + DWORD FileSize; + WORD LastWriteDate; + WORD LastWriteTime; + BYTE Reserved[8]; + BYTE BlockRecord; + BYTE RecordNumber[3]; +} DOS_FCB, *PDOS_FCB; + +typedef struct _DOS_PSP +{ + BYTE Exit[2]; + WORD MemSize; + BYTE Reserved0[6]; + DWORD TerminateAddress; + DWORD BreakAddress; + DWORD CriticalAddress; + WORD ParentPsp; + BYTE HandleTable[20]; + WORD EnvBlock; + DWORD LastStack; + WORD HandleTableSize; + DWORD HandleTablePtr; + DWORD PreviousPsp; + DWORD Reserved1; + WORD DosVersion; + BYTE Reserved2[14]; + BYTE FarCall[3]; + BYTE Reserved3[9]; + DOS_FCB Fcb; + BYTE CommandLineSize; + CHAR CommandLine[127]; +} DOS_PSP, *PDOS_PSP; + +typedef struct _DOS_SFT_ENTRY +{ + WORD ReferenceCount; + WORD Mode; + BYTE Attribute; + WORD DeviceInfo; + DWORD DriveParamBlock; + WORD FirstCluster; + WORD FileTime; + WORD FileDate; + DWORD FileSize; + DWORD CurrentOffset; + WORD LastClusterAccessed; + DWORD DirEntSector; + BYTE DirEntryIndex; + CHAR FileName[11]; + BYTE Reserved0[6]; + WORD OwnerPsp; + BYTE Reserved1[8]; +} DOS_SFT_ENTRY, *PDOS_SFT_ENTRY; + +typedef struct _DOS_SFT +{ + DWORD NextTablePtr; + WORD FileCount; + DOS_SFT_ENTRY Entry[ANYSIZE_ARRAY]; +} DOS_SFT, *PDOS_SFT; + +typedef struct _DOS_INPUT_BUFFER +{ + BYTE MaxLength, Length; + CHAR Buffer[ANYSIZE_ARRAY]; +} DOS_INPUT_BUFFER, *PDOS_INPUT_BUFFER; + +#pragma pack(pop) + +/* FUNCTIONS ******************************************************************/ + +WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable); +BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable); +BOOLEAN DosFreeMemory(WORD BlockData); +VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment); +BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock); +VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode); +CHAR DosReadCharacter(); +VOID DosPrintCharacter(CHAR Character); +VOID DosInt20h(WORD CodeSegment); +VOID DosInt21h(WORD CodeSegment); +VOID DosBreakInterrupt(); +BOOLEAN DosInitialize(); + +#endif + +/* EOF */ + diff --git a/subsystems/ntvdm/emulator.c b/subsystems/ntvdm/emulator.c index 80dfac9d1ee..67b0cd38e88 100644 --- a/subsystems/ntvdm/emulator.c +++ b/subsystems/ntvdm/emulator.c @@ -8,14 +8,21 @@ /* INCLUDES *******************************************************************/ -#include "ntvdm.h" -#include -#include +#include "emulator.h" +#include "bios.h" +#include "dos.h" +#include "pic.h" +#include "ps2.h" +#include "timer.h" -softx86_ctx EmulatorContext; -softx87_ctx FpuEmulatorContext; +/* PRIVATE VARIABLES **********************************************************/ + +static softx86_ctx EmulatorContext; +static softx87_ctx FpuEmulatorContext; static BOOLEAN A20Line = FALSE; +/* PRIVATE FUNCTIONS **********************************************************/ + static VOID EmulatorReadMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size) { /* If the A20 line is disabled, mask bit 20 */ diff --git a/subsystems/ntvdm/emulator.h b/subsystems/ntvdm/emulator.h new file mode 100644 index 00000000000..fc94305671a --- /dev/null +++ b/subsystems/ntvdm/emulator.h @@ -0,0 +1,85 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: emulator.h + * PURPOSE: Minimal x86 machine emulator for the VDM (header file) + * PROGRAMMERS: Aleksandar Andrejevic + */ + +#ifndef _EMULATOR_H_ +#define _EMULATOR_H_ + +/* INCLUDES *******************************************************************/ + +#include "ntvdm.h" +#include +#include + +/* DEFINES ********************************************************************/ + +#define EMULATOR_FLAG_CF (1 << 0) +#define EMULATOR_FLAG_PF (1 << 2) +#define EMULATOR_FLAG_AF (1 << 4) +#define EMULATOR_FLAG_ZF (1 << 6) +#define EMULATOR_FLAG_SF (1 << 7) +#define EMULATOR_FLAG_TF (1 << 8) +#define EMULATOR_FLAG_IF (1 << 9) +#define EMULATOR_FLAG_DF (1 << 10) +#define EMULATOR_FLAG_OF (1 << 11) +#define EMULATOR_FLAG_NT (1 << 14) +#define EMULATOR_FLAG_RF (1 << 16) +#define EMULATOR_FLAG_VM (1 << 17) +#define EMULATOR_FLAG_AC (1 << 18) +#define EMULATOR_FLAG_VIF (1 << 19) +#define EMULATOR_FLAG_VIP (1 << 20) +#define EMULATOR_FLAG_ID (1 << 21) +#define SPECIAL_INT_NUM 0xFF + +enum +{ + EMULATOR_EXCEPTION_DIVISION_BY_ZERO, + EMULATOR_EXCEPTION_DEBUG, + EMULATOR_EXCEPTION_NMI, + EMULATOR_EXCEPTION_BREAKPOINT, + EMULATOR_EXCEPTION_OVERFLOW, + EMULATOR_EXCEPTION_BOUND, + EMULATOR_EXCEPTION_INVALID_OPCODE, + EMULATOR_EXCEPTION_NO_FPU +}; + +typedef enum +{ + EMULATOR_REG_AX, + EMULATOR_REG_CX, + EMULATOR_REG_DX, + EMULATOR_REG_BX, + EMULATOR_REG_SI, + EMULATOR_REG_DI, + EMULATOR_REG_SP, + EMULATOR_REG_BP, + EMULATOR_REG_ES, + EMULATOR_REG_CS, + EMULATOR_REG_SS, + EMULATOR_REG_DS, +} EMULATOR_REGISTER; + +/* FUNCTIONS ******************************************************************/ + +BOOLEAN EmulatorInitialize(); +VOID EmulatorSetStack(WORD Segment, WORD Offset); +VOID EmulatorExecute(WORD Segment, WORD Offset); +VOID EmulatorInterrupt(BYTE Number); +VOID EmulatorExternalInterrupt(BYTE Number); +ULONG EmulatorGetRegister(ULONG Register); +VOID EmulatorSetRegister(ULONG Register, ULONG Value); +BOOLEAN EmulatorGetFlag(ULONG Flag); +VOID EmulatorSetFlag(ULONG Flag); +VOID EmulatorClearFlag(ULONG Flag); +VOID EmulatorStep(); +VOID EmulatorCleanup(); +VOID EmulatorSetA20(BOOLEAN Enabled); + +#endif + +/* EOF */ + diff --git a/subsystems/ntvdm/hardware.c b/subsystems/ntvdm/hardware.c deleted file mode 100644 index 904d4991292..00000000000 --- a/subsystems/ntvdm/hardware.c +++ /dev/null @@ -1,742 +0,0 @@ -/* - * COPYRIGHT: GPL - See COPYING in the top level directory - * PROJECT: ReactOS Virtual DOS Machine - * FILE: hardware.c - * PURPOSE: Minimal hardware emulation - * PROGRAMMERS: Aleksandar Andrejevic - */ - -/* INCLUDES *******************************************************************/ - -#include "ntvdm.h" - -typedef struct _PIC -{ - BOOLEAN Initialization; - BYTE MaskRegister; - BYTE InServiceRegister; - BYTE IntOffset; - BYTE ConfigRegister; - BYTE CascadeRegister; - BOOLEAN CascadeRegisterSet; - BOOLEAN AutoEoi; - BOOLEAN Slave; - BOOLEAN ReadIsr; -} PIC, *PPIC; - -enum -{ - PIT_MODE_INT_ON_TERMINAL_COUNT, - PIT_MODE_HARDWARE_ONE_SHOT, - PIT_MODE_RATE_GENERATOR, - PIT_MODE_SQUARE_WAVE, - PIT_MODE_SOFTWARE_STROBE, - PIT_MODE_HARDWARE_STROBE -}; - -typedef struct _PIT_CHANNEL -{ - WORD ReloadValue; - WORD CurrentValue; - WORD LatchedValue; - INT Mode; - BOOLEAN Pulsed; - BOOLEAN LatchSet; - BOOLEAN InputFlipFlop; - BOOLEAN OutputFlipFlop; - BYTE AccessMode; -} PIT_CHANNEL, *PPIT_CHANNEL; - -static PIC MasterPic, SlavePic; -static PIT_CHANNEL PitChannels[PIT_CHANNELS]; -static BYTE KeyboardQueue[KEYBOARD_BUFFER_SIZE]; -static BOOLEAN KeyboardQueueEmpty = TRUE; -static UINT KeyboardQueueStart = 0; -static UINT KeyboardQueueEnd = 0; -static BYTE KeyboardResponse = 0; -static BOOLEAN KeyboardReadResponse = FALSE, KeyboardWriteResponse = FALSE; -static BYTE KeyboardConfig = PS2_DEFAULT_CONFIG; - -static BOOLEAN KeyboardQueuePush(BYTE ScanCode) -{ - /* Check if the keyboard queue is full */ - if (!KeyboardQueueEmpty && (KeyboardQueueStart == KeyboardQueueEnd)) - { - return FALSE; - } - - /* Insert the value in the queue */ - KeyboardQueue[KeyboardQueueEnd] = ScanCode; - KeyboardQueueEnd++; - KeyboardQueueEnd %= KEYBOARD_BUFFER_SIZE; - - /* Since we inserted a value, it's not empty anymore */ - KeyboardQueueEmpty = FALSE; - - return TRUE; -} - -static BOOLEAN KeyboardQueuePop(BYTE *ScanCode) -{ - /* Make sure the keyboard queue is not empty */ - if (KeyboardQueueEmpty) return FALSE; - - /* Get the scan code */ - *ScanCode = KeyboardQueue[KeyboardQueueStart]; - - /* Remove the value from the queue */ - KeyboardQueueStart++; - KeyboardQueueStart %= KEYBOARD_BUFFER_SIZE; - - /* Check if the queue is now empty */ - if (KeyboardQueueStart == KeyboardQueueEnd) - { - KeyboardQueueEmpty = TRUE; - } - - return TRUE; -} - -/* PUBLIC FUNCTIONS ***********************************************************/ - -BYTE PicReadCommand(BYTE Port) -{ - PPIC Pic; - - /* Which PIC are we accessing? */ - if (Port == PIC_MASTER_CMD) Pic = &MasterPic; - else Pic = &SlavePic; - - if (Pic->ReadIsr) - { - /* Read the in-service register */ - Pic->ReadIsr = FALSE; - return Pic->InServiceRegister; - } - else - { - /* The IRR is always 0, as the emulated CPU receives the interrupt instantly */ - return 0; - } -} - -VOID PicWriteCommand(BYTE Port, BYTE Value) -{ - PPIC Pic; - - /* Which PIC are we accessing? */ - if (Port == PIC_MASTER_CMD) Pic = &MasterPic; - else Pic = &SlavePic; - - if (Value & PIC_ICW1) - { - /* Start initialization */ - Pic->Initialization = TRUE; - Pic->IntOffset = 0xFF; - Pic->CascadeRegisterSet = FALSE; - Pic->ConfigRegister = Value; - return; - } - - if (Value & PIC_OCW3) - { - /* This is an OCR3 */ - if (Value == PIC_OCW3_READ_ISR) - { - /* Return the ISR on next read from command port */ - Pic->ReadIsr = TRUE; - } - - return; - } - - /* This is an OCW2 */ - if (Value & PIC_OCW2_EOI) - { - if (Value & PIC_OCW2_SL) - { - /* If the SL bit is set, clear a specific IRQ */ - Pic->InServiceRegister &= ~(1 << (Value & PIC_OCW2_NUM_MASK)); - } - else - { - /* Otherwise, clear all of them */ - Pic->InServiceRegister = 0; - } - } -} - -BYTE PicReadData(BYTE Port) -{ - /* Read the mask register */ - if (Port == PIC_MASTER_DATA) return MasterPic.MaskRegister; - else return SlavePic.MaskRegister; -} - -VOID PicWriteData(BYTE Port, BYTE Value) -{ - PPIC Pic; - - /* Which PIC are we accessing? */ - if (Port == PIC_MASTER_DATA) Pic = &MasterPic; - else Pic = &SlavePic; - - /* Is the PIC ready? */ - if (!Pic->Initialization) - { - /* Yes, this is an OCW1 */ - Pic->MaskRegister = Value; - return; - } - - /* Has the interrupt offset been set? */ - if (Pic->IntOffset == 0xFF) - { - /* This is an ICW2, set the offset (last three bits always zero) */ - Pic->IntOffset = Value & 0xF8; - - /* Check if we are in single mode and don't need an ICW4 */ - if ((Pic->ConfigRegister & PIC_ICW1_SINGLE) - && !(Pic->ConfigRegister & PIC_ICW1_ICW4)) - { - /* Yes, done initializing */ - Pic->Initialization = FALSE; - } - return; - } - - /* Check if we are in cascade mode and the cascade register was not set */ - if (!(Pic->ConfigRegister & PIC_ICW1_SINGLE) && !Pic->CascadeRegisterSet) - { - /* This is an ICW3 */ - Pic->CascadeRegister = Value; - Pic->CascadeRegisterSet = TRUE; - - /* Check if we need an ICW4 */ - if (!(Pic->ConfigRegister & PIC_ICW1_ICW4)) - { - /* No, done initializing */ - Pic->Initialization = FALSE; - } - return; - } - - /* This must be an ICW4, we will ignore the 8086 bit (assume always set) */ - if (Value & PIC_ICW4_AEOI) - { - /* Use automatic end-of-interrupt */ - Pic->AutoEoi = TRUE; - } - - /* Done initializing */ - Pic->Initialization = FALSE; -} - -VOID PicInterruptRequest(BYTE Number) -{ - BYTE i; - - if (Number >= 0 && Number < 8) - { - /* Check if any of the higher-priorirty interrupts are busy */ - for (i = 0; i <= Number ; i++) - { - if (MasterPic.InServiceRegister & (1 << Number)) return; - } - - /* Check if the interrupt is masked */ - if (MasterPic.MaskRegister & (1 << Number)) return; - - /* Set the appropriate bit in the ISR and interrupt the CPU */ - if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << Number; - EmulatorExternalInterrupt(MasterPic.IntOffset + Number); - } - else if (Number >= 8 && Number < 16) - { - Number -= 8; - - /* - * The slave PIC is connected to IRQ 2, always! If the master PIC - * was misconfigured, don't do anything. - */ - if (!(MasterPic.CascadeRegister & (1 << 2)) - || SlavePic.CascadeRegister != 2) - { - return; - } - - /* Check if any of the higher-priorirty interrupts are busy */ - if (MasterPic.InServiceRegister != 0) return; - for (i = 0; i <= Number ; i++) - { - if (SlavePic.InServiceRegister & (1 << Number)) return; - } - - /* Check if the interrupt is masked */ - if (SlavePic.MaskRegister & (1 << Number)) return; - - /* Set the IRQ 2 bit in the master ISR */ - if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << 2; - - /* Set the appropriate bit in the ISR and interrupt the CPU */ - if (!SlavePic.AutoEoi) SlavePic.InServiceRegister |= 1 << Number; - EmulatorExternalInterrupt(SlavePic.IntOffset + Number); - } -} - -VOID PitWriteCommand(BYTE Value) -{ - BYTE Channel = Value >> 6; - BYTE Mode = (Value >> 1) & 0x07; - - /* Check if this is a counter latch command */ - if (((Value >> 4) & 3) == 0) - { - PitChannels[Channel].LatchSet = TRUE; - PitChannels[Channel].LatchedValue = PitChannels[Channel].CurrentValue; - return; - } - - /* Set the access mode and reset flip-flops */ - PitChannels[Channel].AccessMode = (Value >> 4) & 3; - PitChannels[Channel].Pulsed = FALSE; - PitChannels[Channel].LatchSet = FALSE; - PitChannels[Channel].InputFlipFlop = FALSE; - PitChannels[Channel].OutputFlipFlop = FALSE; - - switch (Mode) - { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - { - PitChannels[Channel].Mode = Mode; - break; - } - - case 6: - { - PitChannels[Channel].Mode = PIT_MODE_RATE_GENERATOR; - break; - } - - case 7: - { - PitChannels[Channel].Mode = PIT_MODE_SQUARE_WAVE; - break; - } - } -} - -BYTE PitReadData(BYTE Channel) -{ - WORD CurrentValue = PitChannels[Channel].CurrentValue; - BYTE AccessMode = PitChannels[Channel].AccessMode; - - /* Check if the value was latched */ - if (PitChannels[Channel].LatchSet) - { - CurrentValue = PitChannels[Channel].LatchedValue; - - if (AccessMode == 1 || AccessMode == 2) - { - /* The latched value was read as one byte */ - PitChannels[Channel].LatchSet = FALSE; - } - } - - /* Use the flip-flop for access mode 3 */ - if (AccessMode == 3) - { - AccessMode = PitChannels[Channel].InputFlipFlop ? 1 : 2; - PitChannels[Channel].InputFlipFlop = !PitChannels[Channel].InputFlipFlop; - - /* Check if this was the last read for the latched value */ - if (!PitChannels[Channel].InputFlipFlop) - { - /* Yes, the latch value was read as two bytes */ - PitChannels[Channel].LatchSet = FALSE; - } - } - - switch (AccessMode) - { - case 1: - { - /* Low byte */ - return CurrentValue & 0x00FF; - } - - case 2: - { - /* High byte */ - return CurrentValue >> 8; - } - } - - /* Shouldn't get here */ - return 0; -} - -VOID PitWriteData(BYTE Channel, BYTE Value) -{ - BYTE AccessMode = PitChannels[Channel].AccessMode; - - /* Use the flip-flop for access mode 3 */ - if (PitChannels[Channel].AccessMode == 3) - { - AccessMode = PitChannels[Channel].InputFlipFlop ? 1 : 2; - PitChannels[Channel].InputFlipFlop = !PitChannels[Channel].InputFlipFlop; - } - - switch (AccessMode) - { - case 1: - { - /* Low byte */ - PitChannels[Channel].ReloadValue &= 0xFF00; - PitChannels[Channel].ReloadValue |= Value; - break; - } - - case 2: - { - /* High byte */ - PitChannels[Channel].ReloadValue &= 0x00FF; - PitChannels[Channel].ReloadValue |= Value << 8; - } - } -} - -VOID PitDecrementCount() -{ - INT i; - - for (i = 0; i < PIT_CHANNELS; i++) - { - switch (PitChannels[i].Mode) - { - case PIT_MODE_INT_ON_TERMINAL_COUNT: - { - /* Decrement the value */ - PitChannels[i].CurrentValue--; - - /* Did it fall to the terminal count? */ - if (PitChannels[i].CurrentValue == 0 && !PitChannels[i].Pulsed) - { - /* Yes, raise the output line */ - if (i == 0) PicInterruptRequest(0); - PitChannels[i].Pulsed = TRUE; - } - break; - } - - case PIT_MODE_RATE_GENERATOR: - { - /* Decrement the value */ - PitChannels[i].CurrentValue--; - - /* Did it fall to zero? */ - if (PitChannels[i].CurrentValue != 0) break; - - /* Yes, raise the output line and reload */ - if (i == 0) PicInterruptRequest(0); - PitChannels[i].CurrentValue = PitChannels[i].ReloadValue; - - break; - } - - case PIT_MODE_SQUARE_WAVE: - { - /* Decrement the value by 2 */ - PitChannels[i].CurrentValue -= 2; - - /* Did it fall to zero? */ - if (PitChannels[i].CurrentValue != 0) break; - - /* Yes, toggle the flip-flop */ - PitChannels[i].OutputFlipFlop = !PitChannels[i].OutputFlipFlop; - - /* Did this create a rising edge in the signal? */ - if (PitChannels[i].OutputFlipFlop) - { - /* Yes, IRQ 0 if this is channel 0 */ - if (i == 0) PicInterruptRequest(0); - } - - /* Reload the value, but make sure it's even */ - if (PitChannels[i].ReloadValue % 2) - { - /* It's odd, reduce it by 1 */ - PitChannels[i].CurrentValue = PitChannels[i].ReloadValue - 1; - } - else - { - /* It was even */ - PitChannels[i].CurrentValue = PitChannels[i].ReloadValue; - } - - break; - } - - case PIT_MODE_SOFTWARE_STROBE: - { - // TODO: NOT IMPLEMENTED - break; - } - - case PIT_MODE_HARDWARE_ONE_SHOT: - case PIT_MODE_HARDWARE_STROBE: - { - /* These modes do not work on x86 PCs */ - break; - } - } - } -} - -BYTE KeyboardReadStatus() -{ - BYTE Status = 0; - - /* Set the first bit if the data can be read */ - if (KeyboardReadResponse || !KeyboardQueueEmpty) Status |= 1 << 0; - - /* Always set bit 2 */ - Status |= 1 << 2; - - /* Set bit 3 if the next byte goes to the controller */ - if (KeyboardWriteResponse) Status |= 1 << 3; - - return Status; -} - -VOID KeyboardWriteCommand(BYTE Command) -{ - switch (Command) - { - /* Read configuration byte */ - case 0x20: - { - KeyboardResponse = KeyboardConfig; - KeyboardReadResponse = TRUE; - - break; - } - - /* Write configuration byte */ - case 0x60: - /* Write controller output port */ - case 0xD1: - /* Write keyboard output buffer */ - case 0xD2: - /* Write mouse output buffer */ - case 0xD3: - /* Write mouse input buffer */ - case 0xD4: - { - /* These commands require a response */ - KeyboardResponse = Command; - KeyboardWriteResponse = TRUE; - - break; - } - - /* Disable mouse */ - case 0xA7: - { - // TODO: Mouse support - - break; - } - - /* Enable mouse */ - case 0xA8: - { - // TODO: Mouse support - - break; - } - - /* Test mouse port */ - case 0xA9: - { - KeyboardResponse = 0; - KeyboardReadResponse = TRUE; - - break; - } - - /* Test PS/2 controller */ - case 0xAA: - { - KeyboardResponse = 0x55; - KeyboardReadResponse = TRUE; - - break; - } - - /* Disable keyboard */ - case 0xAD: - { - // TODO: Not implemented - break; - } - - /* Enable keyboard */ - case 0xAE: - { - // TODO: Not implemented - break; - } - - /* Read controller output port */ - case 0xD0: - { - // TODO: Not implemented - break; - } - - /* CPU Reset */ - case 0xF0: - case 0xF2: - case 0xF4: - case 0xF6: - case 0xF8: - case 0xFA: - case 0xFC: - case 0xFE: - { - /* Stop the simulation */ - VdmRunning = FALSE; - - break; - } - } -} - -BYTE KeyboardReadData() -{ - BYTE Value = 0; - - /* If there was a response byte from the controller, return it */ - if (KeyboardReadResponse) - { - KeyboardReadResponse = FALSE; - return KeyboardResponse; - } - - /* Otherwise, read the data from the queue */ - KeyboardQueuePop(&Value); - - return Value; -} - -VOID KeyboardWriteData(BYTE Data) -{ - /* Check if the controller is waiting for a response */ - if (KeyboardWriteResponse) - { - KeyboardWriteResponse = FALSE; - - /* Check which command it was */ - switch (KeyboardResponse) - { - /* Write configuration byte */ - case 0x60: - { - KeyboardConfig = Data; - break; - } - - /* Write controller output */ - case 0xD1: - { - /* Check if bit 0 is unset */ - if (!(Data & (1 << 0))) - { - /* CPU disabled - end simulation */ - VdmRunning = FALSE; - } - - /* Update the A20 line setting */ - EmulatorSetA20(Data & (1 << 1)); - - break; - } - - case 0xD2: - { - /* Push the data byte to the keyboard queue */ - KeyboardQueuePush(Data); - - break; - } - - case 0xD3: - { - // TODO: Mouse support - break; - } - - case 0xD4: - { - // TODO: Mouse support - break; - } - } - - return; - } - - // TODO: Implement PS/2 device commands -} - -VOID CheckForInputEvents() -{ - PINPUT_RECORD Buffer; - HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE); - DWORD i, j, Count, TotalEvents; - BYTE ScanCode; - - /* Get the number of input events */ - if (!GetNumberOfConsoleInputEvents(ConsoleInput, &Count)) return; - if (Count == 0) return; - - /* Allocate the buffer */ - Buffer = (PINPUT_RECORD)HeapAlloc(GetProcessHeap(), 0, Count * sizeof(INPUT_RECORD)); - if (Buffer == NULL) return; - - /* Peek the input events */ - if (!ReadConsoleInput(ConsoleInput, Buffer, Count, &TotalEvents)) goto Cleanup; - - for (i = 0; i < TotalEvents; i++) - { - /* Check if this is a key event */ - if (Buffer[i].EventType != KEY_EVENT) continue; - - /* Get the scan code */ - ScanCode = Buffer[i].Event.KeyEvent.wVirtualScanCode; - - /* If this is a key release, set the highest bit in the scan code */ - if (!Buffer[i].Event.KeyEvent.bKeyDown) ScanCode |= 0x80; - - /* Push the scan code onto the keyboard queue */ - for (j = 0; j < Buffer[i].Event.KeyEvent.wRepeatCount; j++) - { - KeyboardQueuePush(ScanCode); - } - - /* Yes, IRQ 1 */ - PicInterruptRequest(1); - - /* Stop the loop */ - break; - } - -Cleanup: - HeapFree(GetProcessHeap(), 0, Buffer); -} diff --git a/subsystems/ntvdm/ntvdm.c b/subsystems/ntvdm/ntvdm.c index f20457cfed6..351352aa96c 100644 --- a/subsystems/ntvdm/ntvdm.c +++ b/subsystems/ntvdm/ntvdm.c @@ -6,7 +6,17 @@ * PROGRAMMERS: Aleksandar Andrejevic */ +/* INCLUDES *******************************************************************/ + #include "ntvdm.h" +#include "emulator.h" +#include "bios.h" +#include "dos.h" +#include "timer.h" +#include "pic.h" +#include "ps2.h" + +/* PUBLIC VARIABLES ***********************************************************/ BOOLEAN VdmRunning = TRUE; LPVOID BaseAddress = NULL; @@ -22,6 +32,8 @@ LPCWSTR ExceptionName[] = L"FPU Not Available" }; +/* PUBLIC FUNCTIONS ***********************************************************/ + VOID DisplayMessage(LPCWSTR Format, ...) { WCHAR Buffer[256]; diff --git a/subsystems/ntvdm/ntvdm.h b/subsystems/ntvdm/ntvdm.h index 3709d7dc053..7e71e2b4a33 100644 --- a/subsystems/ntvdm/ntvdm.h +++ b/subsystems/ntvdm/ntvdm.h @@ -6,6 +6,9 @@ * PROGRAMMERS: Aleksandar Andrejevic */ +#ifndef _NTVDM_H_ +#define _NTVDM_H_ + /* INCLUDES *******************************************************************/ #include @@ -23,193 +26,8 @@ #define MAX_SEGMENT 0xFFFF #define MAX_OFFSET 0xFFFF #define MAX_ADDRESS TO_LINEAR(MAX_SEGMENT, MAX_OFFSET) -#define ROM_AREA_START 0xC0000 -#define ROM_AREA_END 0xFFFFF -#define BIOS_PIC_MASTER_INT 0x08 -#define BIOS_PIC_SLAVE_INT 0x70 -#define BIOS_SEGMENT 0xF000 -#define VIDEO_BIOS_INTERRUPT 0x10 -#define SPECIAL_INT_NUM 0xFF -#define SEGMENT_TO_MCB(seg) ((PDOS_MCB)((ULONG_PTR)BaseAddress + TO_LINEAR((seg), 0))) -#define SEGMENT_TO_PSP(seg) ((PDOS_PSP)((ULONG_PTR)BaseAddress + TO_LINEAR((seg), 0))) #define STEPS_PER_CYCLE 256 -/* DOS constants */ -#define DOS_VERSION 0x0600 -#define DOS_CONFIG_PATH L"%SystemRoot%\\system32\\CONFIG.NT" -#define DOS_COMMAND_INTERPRETER L"%SystemRoot%\\system32\\COMMAND.COM /k %SystemRoot%\\system32\\AUTOEXEC.NT" -#define FIRST_MCB_SEGMENT 0x1000 -#define USER_MEMORY_SIZE 0x8FFFF -#define SYSTEM_PSP 0x08 -#define SYSTEM_ENV_BLOCK 0x800 - -/* System console constants */ -#define CONSOLE_FONT_HEIGHT 8 -#define CONSOLE_VIDEO_MEM_START 0xB8000 -#define CONSOLE_VIDEO_MEM_END 0xBFFFF - -/* Programmable interval timer (PIT) */ -#define PIT_CHANNELS 3 -#define PIT_BASE_FREQUENCY 1193182LL -#define PIT_DATA_PORT(x) (0x40 + (x)) -#define PIT_COMMAND_PORT 0x43 - -/* Programmable interrupt controller (PIC) */ -#define PIC_MASTER_CMD 0x20 -#define PIC_MASTER_DATA 0x21 -#define PIC_SLAVE_CMD 0xA0 -#define PIC_SLAVE_DATA 0xA1 -#define PIC_ICW1 0x10 -#define PIC_ICW1_ICW4 (1 << 0) -#define PIC_ICW1_SINGLE (1 << 1) -#define PIC_ICW4_8086 (1 << 0) -#define PIC_ICW4_AEOI (1 << 1) -#define PIC_OCW2_NUM_MASK 0x07 -#define PIC_OCW2_EOI (1 << 5) -#define PIC_OCW2_SL (1 << 6) -#define PIC_OCW3 (1 << 3) -#define PIC_OCW3_READ_ISR 0x0B - -/* 8042 PS/2 controller */ -#define KEYBOARD_BUFFER_SIZE 32 -#define PS2_DATA_PORT 0x60 -#define PS2_CONTROL_PORT 0x64 -#define PS2_DEFAULT_CONFIG 0x05 -#define KEYBOARD_ACK 0xFA -#define KEYBOARD_RESEND 0xFE - -#define EMULATOR_FLAG_CF (1 << 0) -#define EMULATOR_FLAG_PF (1 << 2) -#define EMULATOR_FLAG_AF (1 << 4) -#define EMULATOR_FLAG_ZF (1 << 6) -#define EMULATOR_FLAG_SF (1 << 7) -#define EMULATOR_FLAG_TF (1 << 8) -#define EMULATOR_FLAG_IF (1 << 9) -#define EMULATOR_FLAG_DF (1 << 10) -#define EMULATOR_FLAG_OF (1 << 11) -#define EMULATOR_FLAG_NT (1 << 14) -#define EMULATOR_FLAG_RF (1 << 16) -#define EMULATOR_FLAG_VM (1 << 17) -#define EMULATOR_FLAG_AC (1 << 18) -#define EMULATOR_FLAG_VIF (1 << 19) -#define EMULATOR_FLAG_VIP (1 << 20) -#define EMULATOR_FLAG_ID (1 << 21) - -enum -{ - EMULATOR_EXCEPTION_DIVISION_BY_ZERO, - EMULATOR_EXCEPTION_DEBUG, - EMULATOR_EXCEPTION_NMI, - EMULATOR_EXCEPTION_BREAKPOINT, - EMULATOR_EXCEPTION_OVERFLOW, - EMULATOR_EXCEPTION_BOUND, - EMULATOR_EXCEPTION_INVALID_OPCODE, - EMULATOR_EXCEPTION_NO_FPU -}; - -typedef enum -{ - EMULATOR_REG_AX, - EMULATOR_REG_CX, - EMULATOR_REG_DX, - EMULATOR_REG_BX, - EMULATOR_REG_SI, - EMULATOR_REG_DI, - EMULATOR_REG_SP, - EMULATOR_REG_BP, - EMULATOR_REG_ES, - EMULATOR_REG_CS, - EMULATOR_REG_SS, - EMULATOR_REG_DS, -} EMULATOR_REGISTER; - -#pragma pack(push, 1) - -typedef struct _DOS_MCB -{ - CHAR BlockType; - WORD OwnerPsp; - WORD Size; - BYTE Unused[3]; - CHAR Name[8]; -} DOS_MCB, *PDOS_MCB; - -typedef struct _DOS_FCB -{ - BYTE DriveNumber; - CHAR FileName[8]; - CHAR FileExt[3]; - WORD BlockNumber; - WORD RecordSize; - DWORD FileSize; - WORD LastWriteDate; - WORD LastWriteTime; - BYTE Reserved[8]; - BYTE BlockRecord; - BYTE RecordNumber[3]; -} DOS_FCB, *PDOS_FCB; - -typedef struct _DOS_PSP -{ - BYTE Exit[2]; - WORD MemSize; - BYTE Reserved0[6]; - DWORD TerminateAddress; - DWORD BreakAddress; - DWORD CriticalAddress; - WORD ParentPsp; - BYTE HandleTable[20]; - WORD EnvBlock; - DWORD LastStack; - WORD HandleTableSize; - DWORD HandleTablePtr; - DWORD PreviousPsp; - DWORD Reserved1; - WORD DosVersion; - BYTE Reserved2[14]; - BYTE FarCall[3]; - BYTE Reserved3[9]; - DOS_FCB Fcb; - BYTE CommandLineSize; - CHAR CommandLine[127]; -} DOS_PSP, *PDOS_PSP; - -typedef struct _DOS_SFT_ENTRY -{ - WORD ReferenceCount; - WORD Mode; - BYTE Attribute; - WORD DeviceInfo; - DWORD DriveParamBlock; - WORD FirstCluster; - WORD FileTime; - WORD FileDate; - DWORD FileSize; - DWORD CurrentOffset; - WORD LastClusterAccessed; - DWORD DirEntSector; - BYTE DirEntryIndex; - CHAR FileName[11]; - BYTE Reserved0[6]; - WORD OwnerPsp; - BYTE Reserved1[8]; -} DOS_SFT_ENTRY, *PDOS_SFT_ENTRY; - -typedef struct _DOS_SFT -{ - DWORD NextTablePtr; - WORD FileCount; - DOS_SFT_ENTRY Entry[ANYSIZE_ARRAY]; -} DOS_SFT, *PDOS_SFT; - -typedef struct _DOS_INPUT_BUFFER -{ - BYTE MaxLength, Length; - CHAR Buffer[ANYSIZE_ARRAY]; -} DOS_INPUT_BUFFER, *PDOS_INPUT_BUFFER; - -#pragma pack(pop) - /* FUNCTIONS ******************************************************************/ extern LPVOID BaseAddress; @@ -217,47 +35,7 @@ extern BOOLEAN VdmRunning; extern LPCWSTR ExceptionName[]; VOID DisplayMessage(LPCWSTR Format, ...); -BOOLEAN BiosInitialize(); -VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress); -VOID BiosPrintCharacter(CHAR Character, BYTE Attribute); -BOOLEAN DosInitialize(); -WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable); -BOOLEAN DosFreeMemory(WORD BlockData); -BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable); -BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock); -VOID DosInt20h(WORD CodeSegment); -VOID DosInt21h(WORD CodeSegment); -VOID DosBreakInterrupt(); -VOID BiosVideoService(); -VOID BiosHandleIrq(BYTE IrqNumber); -BYTE PicReadCommand(BYTE Port); -VOID PicWriteCommand(BYTE Port, BYTE Value); -BYTE PicReadData(BYTE Port); -VOID PicWriteData(BYTE Port, BYTE Value); -VOID PicInterruptRequest(BYTE Number); -VOID PitInitialize(); -VOID PitWriteCommand(BYTE Value); -BYTE PitReadData(BYTE Channel); -VOID PitWriteData(BYTE Channel, BYTE Value); -VOID PitDecrementCount(); -VOID CheckForInputEvents(); -BYTE KeyboardReadStatus(); -VOID KeyboardWriteCommand(BYTE Command); -BYTE KeyboardReadData(); -VOID KeyboardWriteData(BYTE Data); -VOID EmulatorSetStack(WORD Segment, WORD Offset); -VOID EmulatorExecute(WORD Segment, WORD Offset); -VOID EmulatorInterrupt(BYTE Number); -VOID EmulatorExternalInterrupt(BYTE Number); -ULONG EmulatorGetRegister(ULONG Register); -VOID EmulatorSetRegister(ULONG Register, ULONG Value); -VOID EmulatorSetFlag(ULONG Flag); -VOID EmulatorClearFlag(ULONG Flag); -BOOLEAN EmulatorGetFlag(ULONG Flag); -BOOLEAN EmulatorInitialize(); -VOID EmulatorStep(); -VOID EmulatorCleanup(); -VOID EmulatorHalt(); -VOID EmulatorSetA20(BOOLEAN Enabled); + +#endif /* EOF */ diff --git a/subsystems/ntvdm/pic.c b/subsystems/ntvdm/pic.c new file mode 100644 index 00000000000..1e205a31199 --- /dev/null +++ b/subsystems/ntvdm/pic.c @@ -0,0 +1,205 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: pic.c + * PURPOSE: Programmable Interrupt Controller emulation + * PROGRAMMERS: Aleksandar Andrejevic + */ + +/* INCLUDES *******************************************************************/ + +#include "pic.h" +#include "emulator.h" + +/* PRIVATE VARIABLES **********************************************************/ + +static PIC MasterPic, SlavePic; + +/* PUBLIC FUNCTIONS ***********************************************************/ + +BYTE PicReadCommand(BYTE Port) +{ + PPIC Pic; + + /* Which PIC are we accessing? */ + if (Port == PIC_MASTER_CMD) Pic = &MasterPic; + else Pic = &SlavePic; + + if (Pic->ReadIsr) + { + /* Read the in-service register */ + Pic->ReadIsr = FALSE; + return Pic->InServiceRegister; + } + else + { + /* The IRR is always 0, as the emulated CPU receives the interrupt instantly */ + return 0; + } +} + +VOID PicWriteCommand(BYTE Port, BYTE Value) +{ + PPIC Pic; + + /* Which PIC are we accessing? */ + if (Port == PIC_MASTER_CMD) Pic = &MasterPic; + else Pic = &SlavePic; + + if (Value & PIC_ICW1) + { + /* Start initialization */ + Pic->Initialization = TRUE; + Pic->IntOffset = 0xFF; + Pic->CascadeRegisterSet = FALSE; + Pic->ConfigRegister = Value; + return; + } + + if (Value & PIC_OCW3) + { + /* This is an OCR3 */ + if (Value == PIC_OCW3_READ_ISR) + { + /* Return the ISR on next read from command port */ + Pic->ReadIsr = TRUE; + } + + return; + } + + /* This is an OCW2 */ + if (Value & PIC_OCW2_EOI) + { + if (Value & PIC_OCW2_SL) + { + /* If the SL bit is set, clear a specific IRQ */ + Pic->InServiceRegister &= ~(1 << (Value & PIC_OCW2_NUM_MASK)); + } + else + { + /* Otherwise, clear all of them */ + Pic->InServiceRegister = 0; + } + } +} + +BYTE PicReadData(BYTE Port) +{ + /* Read the mask register */ + if (Port == PIC_MASTER_DATA) return MasterPic.MaskRegister; + else return SlavePic.MaskRegister; +} + +VOID PicWriteData(BYTE Port, BYTE Value) +{ + PPIC Pic; + + /* Which PIC are we accessing? */ + if (Port == PIC_MASTER_DATA) Pic = &MasterPic; + else Pic = &SlavePic; + + /* Is the PIC ready? */ + if (!Pic->Initialization) + { + /* Yes, this is an OCW1 */ + Pic->MaskRegister = Value; + return; + } + + /* Has the interrupt offset been set? */ + if (Pic->IntOffset == 0xFF) + { + /* This is an ICW2, set the offset (last three bits always zero) */ + Pic->IntOffset = Value & 0xF8; + + /* Check if we are in single mode and don't need an ICW4 */ + if ((Pic->ConfigRegister & PIC_ICW1_SINGLE) + && !(Pic->ConfigRegister & PIC_ICW1_ICW4)) + { + /* Yes, done initializing */ + Pic->Initialization = FALSE; + } + return; + } + + /* Check if we are in cascade mode and the cascade register was not set */ + if (!(Pic->ConfigRegister & PIC_ICW1_SINGLE) && !Pic->CascadeRegisterSet) + { + /* This is an ICW3 */ + Pic->CascadeRegister = Value; + Pic->CascadeRegisterSet = TRUE; + + /* Check if we need an ICW4 */ + if (!(Pic->ConfigRegister & PIC_ICW1_ICW4)) + { + /* No, done initializing */ + Pic->Initialization = FALSE; + } + return; + } + + /* This must be an ICW4, we will ignore the 8086 bit (assume always set) */ + if (Value & PIC_ICW4_AEOI) + { + /* Use automatic end-of-interrupt */ + Pic->AutoEoi = TRUE; + } + + /* Done initializing */ + Pic->Initialization = FALSE; +} + +VOID PicInterruptRequest(BYTE Number) +{ + BYTE i; + + if (Number >= 0 && Number < 8) + { + /* Check if any of the higher-priorirty interrupts are busy */ + for (i = 0; i <= Number ; i++) + { + if (MasterPic.InServiceRegister & (1 << Number)) return; + } + + /* Check if the interrupt is masked */ + if (MasterPic.MaskRegister & (1 << Number)) return; + + /* Set the appropriate bit in the ISR and interrupt the CPU */ + if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << Number; + EmulatorExternalInterrupt(MasterPic.IntOffset + Number); + } + else if (Number >= 8 && Number < 16) + { + Number -= 8; + + /* + * The slave PIC is connected to IRQ 2, always! If the master PIC + * was misconfigured, don't do anything. + */ + if (!(MasterPic.CascadeRegister & (1 << 2)) + || SlavePic.CascadeRegister != 2) + { + return; + } + + /* Check if any of the higher-priorirty interrupts are busy */ + if (MasterPic.InServiceRegister != 0) return; + for (i = 0; i <= Number ; i++) + { + if (SlavePic.InServiceRegister & (1 << Number)) return; + } + + /* Check if the interrupt is masked */ + if (SlavePic.MaskRegister & (1 << Number)) return; + + /* Set the IRQ 2 bit in the master ISR */ + if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << 2; + + /* Set the appropriate bit in the ISR and interrupt the CPU */ + if (!SlavePic.AutoEoi) SlavePic.InServiceRegister |= 1 << Number; + EmulatorExternalInterrupt(SlavePic.IntOffset + Number); + } +} + +/* EOF */ diff --git a/subsystems/ntvdm/pic.h b/subsystems/ntvdm/pic.h new file mode 100644 index 00000000000..6503dce95bd --- /dev/null +++ b/subsystems/ntvdm/pic.h @@ -0,0 +1,57 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: pic.h + * PURPOSE: Programmable Interrupt Controller emulation (header file) + * PROGRAMMERS: Aleksandar Andrejevic + */ + +#ifndef _PIC_H_ +#define _PIC_H_ + +/* INCLUDES *******************************************************************/ + +#include "ntvdm.h" + +/* DEFINES ********************************************************************/ + +#define PIC_MASTER_CMD 0x20 +#define PIC_MASTER_DATA 0x21 +#define PIC_SLAVE_CMD 0xA0 +#define PIC_SLAVE_DATA 0xA1 +#define PIC_ICW1 0x10 +#define PIC_ICW1_ICW4 (1 << 0) +#define PIC_ICW1_SINGLE (1 << 1) +#define PIC_ICW4_8086 (1 << 0) +#define PIC_ICW4_AEOI (1 << 1) +#define PIC_OCW2_NUM_MASK 0x07 +#define PIC_OCW2_EOI (1 << 5) +#define PIC_OCW2_SL (1 << 6) +#define PIC_OCW3 (1 << 3) +#define PIC_OCW3_READ_ISR 0x0B + +typedef struct _PIC +{ + BOOLEAN Initialization; + BYTE MaskRegister; + BYTE InServiceRegister; + BYTE IntOffset; + BYTE ConfigRegister; + BYTE CascadeRegister; + BOOLEAN CascadeRegisterSet; + BOOLEAN AutoEoi; + BOOLEAN Slave; + BOOLEAN ReadIsr; +} PIC, *PPIC; + +/* FUNCTIONS ******************************************************************/ + +BYTE PicReadCommand(BYTE Port); +VOID PicWriteCommand(BYTE Port, BYTE Value); +BYTE PicReadData(BYTE Port); +VOID PicWriteData(BYTE Port, BYTE Value); +VOID PicInterruptRequest(BYTE Number); + +#endif + +/* EOF */ diff --git a/subsystems/ntvdm/ps2.c b/subsystems/ntvdm/ps2.c new file mode 100644 index 00000000000..0484e7133c5 --- /dev/null +++ b/subsystems/ntvdm/ps2.c @@ -0,0 +1,313 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: ps2.c + * PURPOSE: PS/2 controller emulation + * PROGRAMMERS: Aleksandar Andrejevic + */ + +/* INCLUDES *******************************************************************/ + +#include "ps2.h" +#include "emulator.h" +#include "pic.h" + +/* PRIVATE VARIABLES **********************************************************/ + +static BYTE KeyboardQueue[KEYBOARD_BUFFER_SIZE]; +static BOOLEAN KeyboardQueueEmpty = TRUE; +static UINT KeyboardQueueStart = 0; +static UINT KeyboardQueueEnd = 0; +static BYTE KeyboardResponse = 0; +static BOOLEAN KeyboardReadResponse = FALSE, KeyboardWriteResponse = FALSE; +static BYTE KeyboardConfig = PS2_DEFAULT_CONFIG; + +/* PRIVATE FUNCTIONS **********************************************************/ + +static BOOLEAN KeyboardQueuePush(BYTE ScanCode) +{ + /* Check if the keyboard queue is full */ + if (!KeyboardQueueEmpty && (KeyboardQueueStart == KeyboardQueueEnd)) + { + return FALSE; + } + + /* Insert the value in the queue */ + KeyboardQueue[KeyboardQueueEnd] = ScanCode; + KeyboardQueueEnd++; + KeyboardQueueEnd %= KEYBOARD_BUFFER_SIZE; + + /* Since we inserted a value, it's not empty anymore */ + KeyboardQueueEmpty = FALSE; + + return TRUE; +} + +static BOOLEAN KeyboardQueuePop(BYTE *ScanCode) +{ + /* Make sure the keyboard queue is not empty */ + if (KeyboardQueueEmpty) return FALSE; + + /* Get the scan code */ + *ScanCode = KeyboardQueue[KeyboardQueueStart]; + + /* Remove the value from the queue */ + KeyboardQueueStart++; + KeyboardQueueStart %= KEYBOARD_BUFFER_SIZE; + + /* Check if the queue is now empty */ + if (KeyboardQueueStart == KeyboardQueueEnd) + { + KeyboardQueueEmpty = TRUE; + } + + return TRUE; +} + +/* PUBLIC FUNCTIONS ***********************************************************/ + +BYTE KeyboardReadStatus() +{ + BYTE Status = 0; + + /* Set the first bit if the data can be read */ + if (KeyboardReadResponse || !KeyboardQueueEmpty) Status |= 1 << 0; + + /* Always set bit 2 */ + Status |= 1 << 2; + + /* Set bit 3 if the next byte goes to the controller */ + if (KeyboardWriteResponse) Status |= 1 << 3; + + return Status; +} + +VOID KeyboardWriteCommand(BYTE Command) +{ + switch (Command) + { + /* Read configuration byte */ + case 0x20: + { + KeyboardResponse = KeyboardConfig; + KeyboardReadResponse = TRUE; + + break; + } + + /* Write configuration byte */ + case 0x60: + /* Write controller output port */ + case 0xD1: + /* Write keyboard output buffer */ + case 0xD2: + /* Write mouse output buffer */ + case 0xD3: + /* Write mouse input buffer */ + case 0xD4: + { + /* These commands require a response */ + KeyboardResponse = Command; + KeyboardWriteResponse = TRUE; + + break; + } + + /* Disable mouse */ + case 0xA7: + { + // TODO: Mouse support + + break; + } + + /* Enable mouse */ + case 0xA8: + { + // TODO: Mouse support + + break; + } + + /* Test mouse port */ + case 0xA9: + { + KeyboardResponse = 0; + KeyboardReadResponse = TRUE; + + break; + } + + /* Test PS/2 controller */ + case 0xAA: + { + KeyboardResponse = 0x55; + KeyboardReadResponse = TRUE; + + break; + } + + /* Disable keyboard */ + case 0xAD: + { + // TODO: Not implemented + break; + } + + /* Enable keyboard */ + case 0xAE: + { + // TODO: Not implemented + break; + } + + /* Read controller output port */ + case 0xD0: + { + // TODO: Not implemented + break; + } + + /* CPU Reset */ + case 0xF0: + case 0xF2: + case 0xF4: + case 0xF6: + case 0xF8: + case 0xFA: + case 0xFC: + case 0xFE: + { + /* Stop the simulation */ + VdmRunning = FALSE; + + break; + } + } +} + +BYTE KeyboardReadData() +{ + BYTE Value = 0; + + /* If there was a response byte from the controller, return it */ + if (KeyboardReadResponse) + { + KeyboardReadResponse = FALSE; + return KeyboardResponse; + } + + /* Otherwise, read the data from the queue */ + KeyboardQueuePop(&Value); + + return Value; +} + +VOID KeyboardWriteData(BYTE Data) +{ + /* Check if the controller is waiting for a response */ + if (KeyboardWriteResponse) + { + KeyboardWriteResponse = FALSE; + + /* Check which command it was */ + switch (KeyboardResponse) + { + /* Write configuration byte */ + case 0x60: + { + KeyboardConfig = Data; + break; + } + + /* Write controller output */ + case 0xD1: + { + /* Check if bit 0 is unset */ + if (!(Data & (1 << 0))) + { + /* CPU disabled - end simulation */ + VdmRunning = FALSE; + } + + /* Update the A20 line setting */ + EmulatorSetA20(Data & (1 << 1)); + + break; + } + + case 0xD2: + { + /* Push the data byte to the keyboard queue */ + KeyboardQueuePush(Data); + + break; + } + + case 0xD3: + { + // TODO: Mouse support + break; + } + + case 0xD4: + { + // TODO: Mouse support + break; + } + } + + return; + } + + // TODO: Implement PS/2 device commands +} + +VOID CheckForInputEvents() +{ + PINPUT_RECORD Buffer; + HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE); + DWORD i, j, Count, TotalEvents; + BYTE ScanCode; + + /* Get the number of input events */ + if (!GetNumberOfConsoleInputEvents(ConsoleInput, &Count)) return; + if (Count == 0) return; + + /* Allocate the buffer */ + Buffer = (PINPUT_RECORD)HeapAlloc(GetProcessHeap(), 0, Count * sizeof(INPUT_RECORD)); + if (Buffer == NULL) return; + + /* Peek the input events */ + if (!ReadConsoleInput(ConsoleInput, Buffer, Count, &TotalEvents)) goto Cleanup; + + for (i = 0; i < TotalEvents; i++) + { + /* Check if this is a key event */ + if (Buffer[i].EventType != KEY_EVENT) continue; + + /* Get the scan code */ + ScanCode = Buffer[i].Event.KeyEvent.wVirtualScanCode; + + /* If this is a key release, set the highest bit in the scan code */ + if (!Buffer[i].Event.KeyEvent.bKeyDown) ScanCode |= 0x80; + + /* Push the scan code onto the keyboard queue */ + for (j = 0; j < Buffer[i].Event.KeyEvent.wRepeatCount; j++) + { + KeyboardQueuePush(ScanCode); + } + + /* Yes, IRQ 1 */ + PicInterruptRequest(1); + + /* Stop the loop */ + break; + } + +Cleanup: + HeapFree(GetProcessHeap(), 0, Buffer); +} + +/* EOF */ + diff --git a/subsystems/ntvdm/ps2.h b/subsystems/ntvdm/ps2.h new file mode 100644 index 00000000000..2b426b1cfb1 --- /dev/null +++ b/subsystems/ntvdm/ps2.h @@ -0,0 +1,33 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: ps2.h + * PURPOSE: PS/2 controller emulation (header file) + * PROGRAMMERS: Aleksandar Andrejevic + */ + +#ifndef _PS2_H_ +#define _PS2_H_ + +/* INCLUDES *******************************************************************/ + +#include "ntvdm.h" + +/* DEFINES ********************************************************************/ + +#define KEYBOARD_BUFFER_SIZE 32 +#define PS2_DATA_PORT 0x60 +#define PS2_CONTROL_PORT 0x64 +#define PS2_DEFAULT_CONFIG 0x05 +#define KEYBOARD_ACK 0xFA +#define KEYBOARD_RESEND 0xFE + +/* FUNCTIONS ******************************************************************/ + +BYTE KeyboardReadStatus(); +VOID KeyboardWriteCommand(BYTE Command); +BYTE KeyboardReadData(); +VOID KeyboardWriteData(BYTE Data); +VOID CheckForInputEvents(); + +#endif diff --git a/subsystems/ntvdm/timer.c b/subsystems/ntvdm/timer.c new file mode 100644 index 00000000000..76adccbce80 --- /dev/null +++ b/subsystems/ntvdm/timer.c @@ -0,0 +1,235 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: timer.c + * PURPOSE: Programmable Interval Timer emulation + * PROGRAMMERS: Aleksandar Andrejevic + */ + +/* INCLUDES *******************************************************************/ + +#include "timer.h" +#include "pic.h" + +/* PRIVATE VARIABLES **********************************************************/ + +static PIT_CHANNEL PitChannels[PIT_CHANNELS]; + +/* PUBLIC FUNCTIONS ***********************************************************/ + +VOID PitWriteCommand(BYTE Value) +{ + BYTE Channel = Value >> 6; + BYTE Mode = (Value >> 1) & 0x07; + + /* Check if this is a counter latch command */ + if (((Value >> 4) & 3) == 0) + { + PitChannels[Channel].LatchSet = TRUE; + PitChannels[Channel].LatchedValue = PitChannels[Channel].CurrentValue; + return; + } + + /* Set the access mode and reset flip-flops */ + PitChannels[Channel].AccessMode = (Value >> 4) & 3; + PitChannels[Channel].Pulsed = FALSE; + PitChannels[Channel].LatchSet = FALSE; + PitChannels[Channel].InputFlipFlop = FALSE; + PitChannels[Channel].OutputFlipFlop = FALSE; + + switch (Mode) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + { + PitChannels[Channel].Mode = Mode; + break; + } + + case 6: + { + PitChannels[Channel].Mode = PIT_MODE_RATE_GENERATOR; + break; + } + + case 7: + { + PitChannels[Channel].Mode = PIT_MODE_SQUARE_WAVE; + break; + } + } +} + +BYTE PitReadData(BYTE Channel) +{ + WORD CurrentValue = PitChannels[Channel].CurrentValue; + BYTE AccessMode = PitChannels[Channel].AccessMode; + + /* Check if the value was latched */ + if (PitChannels[Channel].LatchSet) + { + CurrentValue = PitChannels[Channel].LatchedValue; + + if (AccessMode == 1 || AccessMode == 2) + { + /* The latched value was read as one byte */ + PitChannels[Channel].LatchSet = FALSE; + } + } + + /* Use the flip-flop for access mode 3 */ + if (AccessMode == 3) + { + AccessMode = PitChannels[Channel].InputFlipFlop ? 1 : 2; + PitChannels[Channel].InputFlipFlop = !PitChannels[Channel].InputFlipFlop; + + /* Check if this was the last read for the latched value */ + if (!PitChannels[Channel].InputFlipFlop) + { + /* Yes, the latch value was read as two bytes */ + PitChannels[Channel].LatchSet = FALSE; + } + } + + switch (AccessMode) + { + case 1: + { + /* Low byte */ + return CurrentValue & 0x00FF; + } + + case 2: + { + /* High byte */ + return CurrentValue >> 8; + } + } + + /* Shouldn't get here */ + return 0; +} + +VOID PitWriteData(BYTE Channel, BYTE Value) +{ + BYTE AccessMode = PitChannels[Channel].AccessMode; + + /* Use the flip-flop for access mode 3 */ + if (PitChannels[Channel].AccessMode == 3) + { + AccessMode = PitChannels[Channel].InputFlipFlop ? 1 : 2; + PitChannels[Channel].InputFlipFlop = !PitChannels[Channel].InputFlipFlop; + } + + switch (AccessMode) + { + case 1: + { + /* Low byte */ + PitChannels[Channel].ReloadValue &= 0xFF00; + PitChannels[Channel].ReloadValue |= Value; + break; + } + + case 2: + { + /* High byte */ + PitChannels[Channel].ReloadValue &= 0x00FF; + PitChannels[Channel].ReloadValue |= Value << 8; + } + } +} + +VOID PitDecrementCount() +{ + INT i; + + for (i = 0; i < PIT_CHANNELS; i++) + { + switch (PitChannels[i].Mode) + { + case PIT_MODE_INT_ON_TERMINAL_COUNT: + { + /* Decrement the value */ + PitChannels[i].CurrentValue--; + + /* Did it fall to the terminal count? */ + if (PitChannels[i].CurrentValue == 0 && !PitChannels[i].Pulsed) + { + /* Yes, raise the output line */ + if (i == 0) PicInterruptRequest(0); + PitChannels[i].Pulsed = TRUE; + } + break; + } + + case PIT_MODE_RATE_GENERATOR: + { + /* Decrement the value */ + PitChannels[i].CurrentValue--; + + /* Did it fall to zero? */ + if (PitChannels[i].CurrentValue != 0) break; + + /* Yes, raise the output line and reload */ + if (i == 0) PicInterruptRequest(0); + PitChannels[i].CurrentValue = PitChannels[i].ReloadValue; + + break; + } + + case PIT_MODE_SQUARE_WAVE: + { + /* Decrement the value by 2 */ + PitChannels[i].CurrentValue -= 2; + + /* Did it fall to zero? */ + if (PitChannels[i].CurrentValue != 0) break; + + /* Yes, toggle the flip-flop */ + PitChannels[i].OutputFlipFlop = !PitChannels[i].OutputFlipFlop; + + /* Did this create a rising edge in the signal? */ + if (PitChannels[i].OutputFlipFlop) + { + /* Yes, IRQ 0 if this is channel 0 */ + if (i == 0) PicInterruptRequest(0); + } + + /* Reload the value, but make sure it's even */ + if (PitChannels[i].ReloadValue % 2) + { + /* It's odd, reduce it by 1 */ + PitChannels[i].CurrentValue = PitChannels[i].ReloadValue - 1; + } + else + { + /* It was even */ + PitChannels[i].CurrentValue = PitChannels[i].ReloadValue; + } + + break; + } + + case PIT_MODE_SOFTWARE_STROBE: + { + // TODO: NOT IMPLEMENTED + break; + } + + case PIT_MODE_HARDWARE_ONE_SHOT: + case PIT_MODE_HARDWARE_STROBE: + { + /* These modes do not work on x86 PCs */ + break; + } + } + } +} + +/* EOF */ + diff --git a/subsystems/ntvdm/timer.h b/subsystems/ntvdm/timer.h new file mode 100644 index 00000000000..107d3a73693 --- /dev/null +++ b/subsystems/ntvdm/timer.h @@ -0,0 +1,56 @@ +/* + * COPYRIGHT: GPL - See COPYING in the top level directory + * PROJECT: ReactOS Virtual DOS Machine + * FILE: timer.h + * PURPOSE: Programmable Interval Timer emulation (header file) + * PROGRAMMERS: Aleksandar Andrejevic + */ + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +/* INCLUDES *******************************************************************/ + +#include "ntvdm.h" + +/* DEFINES ********************************************************************/ + +#define PIT_CHANNELS 3 +#define PIT_BASE_FREQUENCY 1193182LL +#define PIT_DATA_PORT(x) (0x40 + (x)) +#define PIT_COMMAND_PORT 0x43 + +enum +{ + PIT_MODE_INT_ON_TERMINAL_COUNT, + PIT_MODE_HARDWARE_ONE_SHOT, + PIT_MODE_RATE_GENERATOR, + PIT_MODE_SQUARE_WAVE, + PIT_MODE_SOFTWARE_STROBE, + PIT_MODE_HARDWARE_STROBE +}; + +typedef struct _PIT_CHANNEL +{ + WORD ReloadValue; + WORD CurrentValue; + WORD LatchedValue; + INT Mode; + BOOLEAN Pulsed; + BOOLEAN LatchSet; + BOOLEAN InputFlipFlop; + BOOLEAN OutputFlipFlop; + BYTE AccessMode; +} PIT_CHANNEL, *PPIT_CHANNEL; + +/* FUNCTIONS ******************************************************************/ + +VOID PitWriteCommand(BYTE Value); +BYTE PitReadData(BYTE Channel); +VOID PitWriteData(BYTE Channel, BYTE Value); +VOID PitDecrementCount(); + +#endif + +/* EOF */ +