From da877e495eaf24bda8a81ef4719c2e1f97389ea0 Mon Sep 17 00:00:00 2001 From: Aleksandar Andrejevic Date: Fri, 21 Jun 2013 00:47:07 +0000 Subject: [PATCH] [NTVDM] Implement Programmable Interval Timer (PIT) emulation. Fix bugs in interrupts and PIC emulation. svn path=/branches/ntvdm/; revision=59271 --- subsystems/ntvdm/bios.c | 11 +++ subsystems/ntvdm/emulator.c | 64 ++++++++++++++- subsystems/ntvdm/hardware.c | 152 +++++++++++++++++++++++++++++++++--- subsystems/ntvdm/ntvdm.h | 4 + 4 files changed, 219 insertions(+), 12 deletions(-) diff --git a/subsystems/ntvdm/bios.c b/subsystems/ntvdm/bios.c index 72f3f67bcf1..2dad12699c0 100644 --- a/subsystems/ntvdm/bios.c +++ b/subsystems/ntvdm/bios.c @@ -71,6 +71,12 @@ BOOLEAN BiosInitialize() /* Make sure the PIC is in 8086 mode */ PicWriteData(PIC_MASTER_DATA, PIC_ICW4_8086); PicWriteData(PIC_SLAVE_DATA, PIC_ICW4_8086); + + /* Clear the masks for both PICs */ + PicWriteData(PIC_MASTER_DATA, 0x00); + PicWriteData(PIC_SLAVE_DATA, 0x00); + + PitInitialize(); return TRUE; } @@ -251,4 +257,9 @@ VOID BiosVideoService() } } +VOID BiosHandleIrq(BYTE IrqNumber) +{ + PicWriteCommand(PIC_MASTER_CMD, PIC_OCW2_EOI); +} + /* EOF */ diff --git a/subsystems/ntvdm/emulator.c b/subsystems/ntvdm/emulator.c index 313067c4411..bce9afbc2c9 100644 --- a/subsystems/ntvdm/emulator.c +++ b/subsystems/ntvdm/emulator.c @@ -56,12 +56,58 @@ static VOID EmulatorWriteMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT static VOID EmulatorReadIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size) { - // TODO: NOT IMPLEMENTED! + switch (Address) + { + case PIC_MASTER_CMD: + case PIC_SLAVE_CMD: + { + *Buffer = PicReadCommand(Address); + break; + } + + case PIC_MASTER_DATA: + case PIC_SLAVE_DATA: + { + *Buffer = PicReadData(Address); + break; + } + } } static VOID EmulatorWriteIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size) { - // TODO: NOT IMPLEMENTED! + BYTE Byte = *Buffer; + + switch (Address) + { + case PIT_COMMAND_PORT: + { + PitWriteCommand(Byte); + break; + } + + case PIT_DATA_PORT(0): + case PIT_DATA_PORT(1): + case PIT_DATA_PORT(2): + { + PitWriteData(Address - PIT_DATA_PORT(0), Byte); + break; + } + + case PIC_MASTER_CMD: + case PIC_SLAVE_CMD: + { + PicWriteCommand(Address, Byte); + break; + } + + case PIC_MASTER_DATA: + case PIC_SLAVE_DATA: + { + PicWriteData(Address, Byte); + break; + } + } } static VOID EmulatorSoftwareInt(PVOID Context, BYTE Number) @@ -101,6 +147,18 @@ static VOID EmulatorSoftwareInt(PVOID Context, BYTE Number) VdmRunning = FALSE; return; } + + /* Check if this was an PIC IRQ */ + if (IntNum >= BIOS_PIC_MASTER_INT && IntNum < BIOS_PIC_MASTER_INT + 8) + { + /* It was an IRQ from the master PIC */ + BiosHandleIrq(IntNum - BIOS_PIC_MASTER_INT); + } + else if (IntNum >= BIOS_PIC_SLAVE_INT && IntNum < BIOS_PIC_SLAVE_INT + 8) + { + /* It was an IRQ from the slave PIC */ + BiosHandleIrq(IntNum - BIOS_PIC_SLAVE_INT + 8); + } switch (IntNum) { @@ -183,7 +241,7 @@ VOID EmulatorExecute(WORD Segment, WORD Offset) VOID EmulatorInterrupt(BYTE Number) { - LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress); + LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress); UINT Segment, Offset; /* Get the segment and offset */ diff --git a/subsystems/ntvdm/hardware.c b/subsystems/ntvdm/hardware.c index 751c833744b..fc36cd8b2dd 100644 --- a/subsystems/ntvdm/hardware.c +++ b/subsystems/ntvdm/hardware.c @@ -24,7 +24,64 @@ typedef struct _PIC BOOLEAN ReadIsr; } PIC, *PPIC; +typedef struct _PIT_CHANNEL +{ + BOOLEAN RateGenerator; + BOOLEAN Pulsed; + BOOLEAN FlipFlop; + BYTE AccessMode; + WORD ReloadValue; +} PIT_CHANNEL, *PPIT_CHANNEL; + static PIC MasterPic, SlavePic; +static PIT_CHANNEL PitChannels[PIT_CHANNELS]; + +static DWORD WINAPI PitThread(PVOID Parameter) +{ + LARGE_INTEGER Frequency, CurrentTime, LastTickTime; + LONGLONG Elapsed, Milliseconds, TicksNeeded; + UNREFERENCED_PARAMETER(Parameter); + + /* Get the performance counter frequency */ + if (!QueryPerformanceFrequency(&Frequency)) return EXIT_FAILURE; + if (!QueryPerformanceCounter(&LastTickTime)) return EXIT_FAILURE; + + while (VdmRunning) + { + if (!QueryPerformanceCounter(&CurrentTime)) return EXIT_FAILURE; + + /* Calculate the elapsed time, in PIT ticks */ + Elapsed = ((CurrentTime.QuadPart - LastTickTime.QuadPart) + * PIT_BASE_FREQUENCY) + / Frequency.QuadPart; + + /* A reload value of 0 indicates 65536 */ + if (PitChannels[0].ReloadValue) TicksNeeded = PitChannels[0].ReloadValue; + else TicksNeeded = 65536; + + if (Elapsed < TicksNeeded) + { + /* Get the number of milliseconds */ + Milliseconds = (Elapsed * 1000LL) / PIT_BASE_FREQUENCY; + + /* If this number is non-zero, put the thread in the waiting state */ + if (Milliseconds > 0LL) Sleep((DWORD)Milliseconds); + + continue; + } + + LastTickTime = CurrentTime; + + /* Do the IRQ */ + if (PitChannels[0].RateGenerator || !PitChannels[0].Pulsed) + { + PitChannels[0].Pulsed = TRUE; + PicInterruptRequest(0); + } + } + + return EXIT_SUCCESS; +} /* PUBLIC FUNCTIONS ***********************************************************/ @@ -165,14 +222,15 @@ VOID PicInterruptRequest(BYTE Number) { if (Number >= 0 && Number < 8) { - /* Check if the interrupt is busy or in a cascade */ + /* Check if the interrupt is busy, in a cascade or masked */ if (MasterPic.CascadeRegister & (1 << Number) - || MasterPic.InServiceRegister & (1 << Number)) + || MasterPic.InServiceRegister & (1 << Number) + || MasterPic.MaskRegister & (1 << Number)) { return; } - MasterPic.InServiceRegister |= 1 << Number; + if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << Number; EmulatorInterrupt(MasterPic.IntOffset + Number); } else if (Number >= 8 && Number < 16) @@ -189,20 +247,96 @@ VOID PicInterruptRequest(BYTE Number) return; } - /* Check the if the slave PIC is busy */ - if (MasterPic.InServiceRegister & (1 << 2)) return; + /* Check the if the slave PIC is busy or masked */ + if (MasterPic.InServiceRegister & (1 << 2) + || MasterPic.MaskRegister & (1 << 2)) return; /* Set the IRQ 2 bit in the master ISR */ - MasterPic.InServiceRegister |= 1 << 2; + if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << 2; - /* Check if the interrupt is busy or in a cascade */ + /* Check if the interrupt is busy, in a cascade or masked */ if (SlavePic.CascadeRegister & (1 << Number) - || SlavePic.InServiceRegister & (1 << Number)) + || SlavePic.InServiceRegister & (1 << Number) + || SlavePic.MaskRegister & (1 << Number)) { return; } - SlavePic.InServiceRegister |= 1 << Number; + if (!SlavePic.AutoEoi) SlavePic.InServiceRegister |= 1 << Number; EmulatorInterrupt(SlavePic.IntOffset + Number); } } + +VOID PitWriteCommand(BYTE Value) +{ + BYTE Channel = Value >> 6; + BYTE Mode = (Value >> 1) & 0x07; + + /* Set the access mode and reset flip-flop */ + // TODO: Support latch command! + PitChannels[Channel].AccessMode = (Value >> 4) & 3; + PitChannels[Channel].FlipFlop = FALSE; + + switch (Mode) + { + case 0: + case 4: + { + PitChannels[Channel].RateGenerator = FALSE; + break; + } + + case 2: + case 3: + { + PitChannels[Channel].RateGenerator = TRUE; + break; + } + } +} + +VOID PitWriteData(BYTE Channel, BYTE Value) +{ + /* Use the flip-flop for access mode 3 */ + if (PitChannels[Channel].AccessMode == 3) + { + PitChannels[Channel].AccessMode = PitChannels[Channel].FlipFlop ? 1 : 2; + PitChannels[Channel].FlipFlop = !PitChannels[Channel].FlipFlop; + } + + switch (PitChannels[Channel].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 PitInitialize() +{ + HANDLE ThreadHandle; + + /* Set up channel 0 */ + PitChannels[0].ReloadValue = 0; + PitChannels[0].RateGenerator = TRUE; + PitChannels[0].Pulsed = FALSE; + PitChannels[0].AccessMode = 3; + PitChannels[0].FlipFlop = FALSE; + + /* Create the PIT timer thread */ + ThreadHandle = CreateThread(NULL, 0, PitThread, NULL, 0, NULL); + + /* We don't need the handle */ + CloseHandle(ThreadHandle); +} diff --git a/subsystems/ntvdm/ntvdm.h b/subsystems/ntvdm/ntvdm.h index 44555c8622c..0edfb8be0f0 100644 --- a/subsystems/ntvdm/ntvdm.h +++ b/subsystems/ntvdm/ntvdm.h @@ -208,11 +208,15 @@ 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); +VOID PitWriteData(BYTE Channel, BYTE Value); VOID EmulatorSetStack(WORD Segment, WORD Offset); VOID EmulatorExecute(WORD Segment, WORD Offset); VOID EmulatorInterrupt(BYTE Number);