Releases: jeffpar/pcjs.v1
More 80386 Improvements
A quick list of the changes:
- Fixed save/restore of A20 state for 32-bit machines
- Added support for Windows 95 non-standard video modes
- Added Debugger INT 0x41 and INT 0x68 Windows Debugger monitoring
- Added Debugger mode-specific addressing
When a machine state was saved/restored on a 24-bit (80286-based) machine, the A20 state was (in theory) properly saved/restored as well. However, 32-bit (80386-based) machines use a different A20 emulation strategy, which involves mapping/unmapping all physical memory blocks in the 1st megabyte to/from the 2nd megabyte, rather than simply masking bit 20 of all physical addresses -- because that's how DeskPro 386 systems actually worked. However, that meant that properly saving a 32-bit machine state required some additional work (ie, any unmapped blocks must be temporarily remapped for the save operation, otherwise they won't be restored).
The non-standard video mode support relates to the 320x400 graphics mode used by Windows 95 to display startup and shutdown logo screens. The PCjs video component already supported the 320x400 mode, but additional work was required to support how Windows 95 reprograms the video memory controller to mimic text mode while leaving the graphics image on-screen.
At the same time, I also fixed some other video bugs: VGA support inadvertently broke EGA graphics modes 0x0D and 0x0E (those modes should now work on both EGA and VGA), and video glitches would appear during mode changes.
There are several ways that software can (and does) disable the video signal to prevent glitches, and we honor the "normal" method used by BIOS mode changes, which involves clearing/setting bit 5 of the ATC Index register. We don't actually "blank" the screen whenever the bit is cleared (which would be a bad idea, because some IBM BIOS code toggles that bit rather frequently), but we do now prevent further updates until the bit is set again. This has the pleasing effect of eliminating odd video output during mode transitions, without causing any annoying flickering.
As for Debugger improvements, the most significant is the support for mode-specific addressing. It now tries to honor the specified address type, according to the address prefix used ('&' for real-mode or V86-mode segmented addresses, '#' for protected-mode segmented addresses, or '%' for linear addresses). "Linear" means physical if paging is not enabled, otherwise it means a paged address. This change will probably require more refinements, because addresses are used all over the place in the Debugger, but it was important to begin this transition toward improved addressing control.
Improved Fault Handling
One of the more serious PCjs bugs fixed in this release involved fault processing. Instructions that were previously not restartable now are, and instructions that should have triggered multiple faults now do. I'm sure I'll still run into more instructions that are not fully restartable after a fault, but this release takes care of all known problems so far.
For example, an inter-segment CALL requiring a stack-switch will now properly generate an NP fault if the target code segment is not present, as well as an SS fault if the target stack segment is not present. And segments with invalid descriptors will now generate a GP fault instead of an NP fault; both are legitimate, but GP fault conditions apparently take precedence over NP fault conditions (OS/2 didn't seem to really care which was generated first, but Windows definitely cared).
With these changes, IBM OS/2 1.0 runs successfully. There still appear to be some serial port issues to resolve, but overall, OS/2 support is looking much better now.
Also, support for the timer chip's "Read-Back" command has been added, which allows any/all of the counter status and/or counts to be latched. I've performed virtually zero testing of this new command, but I'm happy to report that Windows 95 Setup's hardware detection phase seems to be satisfied now, fixing the issue noted in the previous release.
Last but not least, a somewhat embarrassing bug was fixed in the LOOP opcode handlers: 16-bit LOOP operations now preserve the upper 16 bits of ECX.
More Win95 Setup Bugs Squashed
This release fixes a number bugs. The oldest and most irritating bug occurred in the Windows 95 Setup CAB decompression code. However, the problem had nothing to do with the decompression code itself; it occurred whenever a timer interrupt occurred while the decompression code was holding a 32-bit value in EAX.
A path through the interrupt handler was trashing the upper bits of EAX. The culprit: any of the MOV instructions that move an immediate value into one of the "high" 8-bit registers (AH, BH, CH, or DH). Those instructions were failing to preserve the upper 16 bits of the entire register. Further proof that the most exasperating bugs sometimes have the most mundane causes.
Also recently fixed: improper updates to the ACCESSED bit in a selector's descriptor table entry, improper error codes when a selector load generated a fault, and the RETF instruction's failure to properly restart when the return address referred to a not-present segment.
The next problem in Windows 95 Setup appears to be timer-related. When Windows 95 Setup begins its hardware analysis, it gets "stuck" in code that's reading and writing timer ports (0x40 and 0x43). Turning on timer port messages with the "m port on;m timer on"
Debugger commands reveals that the problem maybe an unsupported timer command:
chipset.outPort(0x0043,PIT1_CTRL,0xD2) @1847:DD02
PIT1_CTRL: Read-Back command not supported (yet)
To be continued.
Touching Improvements
I'd like to touch briefly on a few new features in this release:
- Conversion of touch events into mouse events
- Preventing 8-bit I/O handlers from returning more than 8 bits of data
- Reading symbolic information from 16-bit Windows binaries
The Windows 1.0 configurations should be much more usable on touch devices like the iPad. The conversion of touch events is enabled by the Video component when touchscreen="mouse"
is added the machine's <video> element. Previously, the only support for touch events was some experimental code that divided the screen into thirds and converted taps into arrow keys. That experimental code still exists and is still used by this machine, or any other machine that specifies touchscreen="keygrid"
, but keyboard emulation via touch events is not a focus right now.
Mouse emulation via touch events is a focus. However, it is still missing two critical features:
- Mouse dragging (more generally, holding a button while moving the mouse)
- Mouse right-clicking
One-finger taps are currently converted into mouse left-clicks, and one-finger strokes are converted into mouse movement.
Going forward, I'll probably convert two-finger taps into mouse right-clicks. As for dragging, I'll probably again check for two fingers, and if you hold one finger still while moving the other finger, the moving finger will dictate mouse motion while the stationary finger will dictate the mouse button that's pressed for the duration (ie, left finger for left button, right finger for right button). At least, that's my initial thinking. It sounds straightforward, but I'll need to try it first.
The 8-bit I/O fix took care of a problem that showed up in the 16-bit Windows VGA driver, when simulated hardware returned more bits than the CPU expected, resulting in register corruption. In the case of the VGA driver, it resulted in screen errors as the mouse pointer moved.
While debugging that problem, I also enhanced the Video component's "dump" command, making it much easier to dump rectangular swaths and/or specific planes of video memory. The width of the swaths can be adjusted as well, because off-screen chunks of video memory, like the mouse pointer's off-screen copy buffer, can have a logical width much less than the screen width.
From video.js (syntax as of v1.19.3):
[A]ssuming a standard VGA frame buffer with 640x480 pixels across 38400 (0x9600) memory locations, the following command will dump a vertical swath of bits from plane 0 that is 32 (0x20) rows tall and 8 columns wide, from roughly the center of the screen (0x4B00 + 0x28 - 2 = 0x4B26).
d video 4b26 l20 n8 p0
Subsequent commands that omit a starting address or offset will continue where the last dump left off; eg:
d video n8 p0
To dump a chunk of off-screen memory starting at 0x9600, where the Windows VGA driver typically stores a copy of the video memory containing the current mouse pointer:
d video 9600 l20 n5 w5 p0
Alternatively, you could use decimal values:
d video 9600 l32. n5. w5. p0.
The other important new feature affects only machines using uncompiled (COMPILED=false) code, because that's the only version with BACKTRACK support (BACKTRACK=true) enabled. With BACKTRACK enabled, all 16-bit Windows ("New Executable") binaries on a disk are read as the disk is mounted, so that when those binary files are loaded into memory later, the PCjs Debugger can provide symbolic information for entry points within the segments of those binaries. This is still experimental, and could use some obvious refinements, such as deferring symbol loading until the files are actually loaded into memory, or loading symbols only for files explicitly listed in the machine's Debugger configuration.
This Compaq DeskPro machine is where most of my attention is focused these days, and that's where you can see BACKTRACK support in action. All symbolic information displayed by the PCjs Debugger is thanks to the BACKTRACK feature. All the code that reads FAT volumes and 16-bit Windows files is in disk.js.
Windows 95 Setup Test Machine Update
The Windows 95 Setup Test Machine has been updated to include a state file, so that the machine will immediately start up ready to install, making it much faster/easier to debug an longstanding PCjs problem with CAB file decompression (which I still haven't isolated).
Since this was the first time I attempted to restore a protected-mode machine state, a few changes were required; the most important change was in the X86CPU component: the restore() method needed to restore memory state before attempting to reload segment registers, since the LDT and GDT must be accessible.
Another less critical change was made to the RAM component, to add save/restore support to RAM that uses a custom memory controller, such as regions containing memory-mapped registers. For example, on the Compaq DeskPro 386, there are memory-mapped "RAM Relocation" registers at 0x80C00000-0x80C00002 that need to be saved/restored just like an I/O port.
Most of the changes in this release occurred in the Debugger:
- Breakpoint command strings
- New commands ("if", "else", "let", print")
- Improved expression support
- More logical operators ("||", "&&", "==", "!=")
- And much, much more (well, maybe only a little more)
Fixed 32-bit Register XCHGs
The XCHG opcodes weren't upgraded to fully support 32-bit registers, so an instruction like XCHG CL,CH would inadvertently clobber all 32 bits of ECX. That has been fixed, and standard-mode Windows works much better now (Windows 3.0 running in 16-bit protected-mode would also use 32-bit registers).
I also slipped in a few Debugger improvements: properly truncate 16-bit address calculations when using commands like "db ds:si+bx" (where SI+BX produces a result greater than 0xFFFF), and display a BYTE, WORD, DWORD or FAR prefix on one-operand instructions when the size of the operand is unclear. For two-operand instructions, the size can almost always be inferred from the other operand, because it will typically be a 1, 2 or 4-byte register or immediate value.
Fix for Microsoft Edge/Windows 10
The Microsoft Edge browser dropped support for ActiveX, which meant that all IE-related hacks were no longer triggered. Which would have been fine if Edge had actually eliminated the need for any IE-related hacks. Unfortunately, there was still one hack that Edge needed: stripping the <DOCTYPE> from XSL files before loading them into the browser's XSLTProcessor.
In an effort to streamline PCjs browser support, I now strip the <DOCTYPE> from XSL files in all cases. Current versions of Chrome, Safari and Firefox seem to be OK with that change, so hopefully this is the last time I have to deal with browser-specific nonsense -- at least in the realm of XML and XSLT processing.
The PCjs loader in embed.js still contains the old ActiveX work-arounds, so PCjs should continue to run fine in IE v9 through v11.
Windows 3.0 Improvements
I've been using the Windows 95 80386-based test machine to try Windows 3.0, on the theory that one should walk before one attempts to run.
The hard disk in that machine has Windows 3.0 pre-installed. If you run Windows 3.0 in real-mode ("WIN /R"), everything appears to run fine. In standard-mode ("WIN /S"), things are less fine (there are serious problems drawing text). And finally, enhanced-mode ("WIN") runs only to a point.
Aside from the glaring text problem, standard-mode (16-bit protected-mode) actually runs pretty well. The only other problem I've noticed is that if you exit and re-run standard-mode Windows, the mouse stops working.
Enhanced-mode windows, which runs on top of a 32-bit virtual machine manager (VMM) that supports multiple DOS machines running in V86-mode, is also in better shape than it looks. PCjs is able to execute a fair bit of both 32-bit code and 16-bit code in V86-mode as it starts up -- just not yet enough code to get Windows completely up and running.
Most of the changes in v1.18.5 are related to 32-bit operation and V86-mode, including support for:
- 80386 Gates
- 80386 Task State Segments
- I/O Permission Bitmap (IOPM)
- V86-mode fault handling (eg, for software interrupts)
- 80386 bug fixes in SIB decoding and RETF instructions
- Improved Debugger expression evaluation
32-bit Instruction Fixes
A number of 80386-related fixes, including:
- Allow IOPL bits to be altered in real-mode
- Mask I/O port addresses with 0xFFFF prior to lookup
- Clear any stale stack-switch conditions prior to re-entering real-mode
- Finish 32-bit support for INC, DEC, NEG, NOT, TEST, MOVSB and MOVSW
- Treat 0x0F,0xFF as an invalid opcode (since Windows 95 requires it)
- Ignore redundant address-size and operand-size prefixes
The IOPL change fixes CPU detection code that discriminates between the 80286 and 80386. The 80286 did not allow the IOPL bits to be changed in real-mode. The 80386 does, even though altering IOPL in real-mode has no known effect. I haven't yet confirmed if the NT (Nested Task) bit can also be changed in real-mode on an 80386, but for now, it doesn't matter -- all the CPU detection code I've seen checks both IOPL and NT, and assumes an 80386 if any of those bits can be changed.
The I/O port masking change raised another question that I don't yet know the answer to: any word I/O to port 0xFFFF will effectively perform byte I/O to ports 0xFFFF and 0x0000, but what about word I/O that uses immediate 8-bit port addresses? If the first byte accesses port 0xFF, will the second byte access port 0x00 or 0x100? TBD.
Compaq DeskPro 386 Fixes
My first DeskPro 386 test configuration finally boots without errors, finally runs most of the project's VGA tests, and can finally FDISK/FORMAT a hard disk.
That's a lot of "finallies", when in fact there's nothing really "final" about this release at all. It's just another brick in the wall.
And there are definitely still some DeskPro 386 issues to investigate. For example, the Compaq BIOS beeps twice on start-up instead of what I assume should be a normal single beep. Perhaps it's trying to tell me that Compaq's equivalent of CMOS SETUP still needs to be run. However, if the original Compaq DeskPro 386 came with such a program or diskette, I don't have a copy.