diff --git a/subsystems/ntvdm/CMakeLists.txt b/subsystems/ntvdm/CMakeLists.txt index af24b9100b8..3145102935b 100644 --- a/subsystems/ntvdm/CMakeLists.txt +++ b/subsystems/ntvdm/CMakeLists.txt @@ -5,6 +5,7 @@ list(APPEND SOURCE bios.c dos.c emulator.c + hardware.c ntvdm.c ntvdm.rc) diff --git a/subsystems/ntvdm/bios.c b/subsystems/ntvdm/bios.c index 63f9f03e705..72f3f67bcf1 100644 --- a/subsystems/ntvdm/bios.c +++ b/subsystems/ntvdm/bios.c @@ -55,6 +55,22 @@ BOOLEAN BiosInitialize() CursorRow = ConsoleInfo.dwCursorPosition.Y; ConsoleWidth = ConsoleInfo.dwSize.X; ConsoleHeight = ConsoleInfo.dwSize.Y; + + /* Initialize the PIC */ + PicWriteCommand(PIC_MASTER_CMD, PIC_ICW1 | PIC_ICW1_ICW4); + PicWriteCommand(PIC_SLAVE_CMD, PIC_ICW1 | PIC_ICW1_ICW4); + + /* Set the interrupt offsets */ + PicWriteData(PIC_MASTER_DATA, BIOS_PIC_MASTER_INT); + PicWriteData(PIC_SLAVE_DATA, BIOS_PIC_SLAVE_INT); + + /* Tell the master PIC there is a slave at IRQ 2 */ + PicWriteData(PIC_MASTER_DATA, 1 << 2); + PicWriteData(PIC_SLAVE_DATA, 2); + + /* Make sure the PIC is in 8086 mode */ + PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086); + PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086); return TRUE; } diff --git a/subsystems/ntvdm/hardware.c b/subsystems/ntvdm/hardware.c new file mode 100644 index 00000000000..77aeee70a37 --- /dev/null +++ b/subsystems/ntvdm/hardware.c @@ -0,0 +1,202 @@ +/* + * 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; + +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) +{ + if (Number >= 0 && Number < 8) + { + /* Check if the interrupt is busy or in a cascade */ + if (MasterPic.CascadeRegister & (1 << Number) + || MasterPic.InServiceRegister & (1 << Number)) + { + return; + } + + MasterPic.InServiceRegister |= 1 << Number; + EmulatorInterrupt(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 the interrupt is busy or in a cascade */ + if (SlavePic.CascadeRegister & (1 << Number) + || SlavePic.InServiceRegister & (1 << Number)) + { + return; + } + + SlavePic.InServiceRegister |= 1 << Number; + EmulatorInterrupt(SlavePic.IntOffset + Number); + } +} diff --git a/subsystems/ntvdm/ntvdm.h b/subsystems/ntvdm/ntvdm.h index b3bfe5066a5..44555c8622c 100644 --- a/subsystems/ntvdm/ntvdm.h +++ b/subsystems/ntvdm/ntvdm.h @@ -23,6 +23,8 @@ #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 @@ -43,6 +45,28 @@ #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 + #define EMULATOR_FLAG_CF (1 << 0) #define EMULATOR_FLAG_PF (1 << 2) #define EMULATOR_FLAG_AF (1 << 4) @@ -184,6 +208,11 @@ VOID DosInt20h(WORD CodeSegment); VOID DosInt21h(WORD CodeSegment); VOID DosBreakInterrupt(); VOID BiosVideoService(); +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 EmulatorSetStack(WORD Segment, WORD Offset); VOID EmulatorExecute(WORD Segment, WORD Offset); VOID EmulatorInterrupt(BYTE Number);