202 lines
9.9 KiB
Markdown
202 lines
9.9 KiB
Markdown
# Ring-0 Interrupt Handling -- Notes for Later
|
|
|
|
KPCOMM.DRV handles UART interrupts in a ring-3, protected-mode handler hooked
|
|
through DPMI. The stock Windows 3.1 COMM.DRV runs its interrupt code at
|
|
ring 0 in enhanced mode. This file records what is known about that, when
|
|
switching would be worth it, and how to do it, so the work can be picked up
|
|
later without redoing the research.
|
|
|
|
**Decision (2026-10-12): not now.** Measure first; the cheap steps below
|
|
may make it unnecessary. If it is ever needed, a small VxD is a better
|
|
design than copying the stock driver's hook.
|
|
|
|
Each statement below is marked **verified** (checked against a source) or
|
|
**recollection** (believed true, not yet checked).
|
|
|
|
|
|
## The current path and its cost
|
|
|
|
In 386 enhanced mode a UART interrupt currently goes:
|
|
|
|
1. Hardware IRQ to the VMM's interrupt table at ring 0, then to VPICD (the
|
|
virtual interrupt controller).
|
|
2. VPICD gives the IRQ to the VM that owns it (the system VM, via VCD's
|
|
virtualization of the COM IRQ) and requests a virtual interrupt there.
|
|
3. If the system VM is not running, the scheduler has to switch to it.
|
|
VPICD boosts the VM's priority, but delivery still waits for the switch
|
|
and for the VM's virtual interrupt flag to be set.
|
|
4. The DPMI host enters KPCOMM's protected-mode handler (`isrIrqNN`) at
|
|
ring 3 on its locked stack.
|
|
5. Every `in`/`out` on the UART may trap to VCD, if VCD is still trapping
|
|
the ports for the owning VM -- unknown, see step 2 of the plan. EOI on
|
|
port 20h is virtualized by VPICD.
|
|
6. IRET back down to ring 0, then resume whatever was running.
|
|
|
|
Two costs follow from this:
|
|
|
|
- **Latency.** The time from the IRQ to our handler includes VM scheduling.
|
|
It is normally small, but under load (a busy DOS box, DOS file I/O running
|
|
in V86 mode, long CLI periods elsewhere) it can approach or exceed the FIFO
|
|
budget. At 115200 baud a byte arrives every 87 us; with RX trigger 1 the
|
|
16-byte FIFO leaves about 1.3 ms of slack, with trigger 8 about 0.7 ms.
|
|
- **CPU.** Each interrupt pays several ring transitions. A full-speed
|
|
115200 stream can mean about 2,900 interrupts per second at the default RX
|
|
trigger of 4, and about 11,500 at trigger 1. A guess of 10-30 us per
|
|
reflected interrupt on a 486 would put trigger 1 at 10-35% of the CPU and
|
|
trigger 4 at roughly a quarter of that. These are recollection-level
|
|
estimates, not measurements.
|
|
|
|
Ring-0 handling would remove the VM scheduling and the ring transitions (and
|
|
any I/O trapping): the handler runs at hardware-interrupt time whichever VM
|
|
is active. It does not help when something else keeps interrupts disabled
|
|
for too long, such as floppy access in enhanced mode; only FIFO depth covers
|
|
that.
|
|
|
|
|
|
## Measure first
|
|
|
|
1. **Measure headroom (debug build).**
|
|
- Record the most bytes drained in a single `handleRx` call. That is
|
|
the FIFO fill level when the handler finally ran, which measures
|
|
latency directly.
|
|
- Also count interrupts per second and `CE_OVERRUN` occurrences.
|
|
- Log the three at close with `dbgHex16`.
|
|
- Run a 115200 download while copying files in File Manager, dragging
|
|
windows, reading a floppy, and running a DOS box in the background
|
|
(and full-screen).
|
|
- Reading it: a peak that stays well under ~12 bytes with no overruns
|
|
means ring 0 buys nothing you would notice. A peak at 14-16 means
|
|
overruns are near.
|
|
2. **Check for I/O trapping.** Time a few thousand `_inp` calls on the
|
|
UART's LSR against BIOS ticks. About 1 us each is untrapped ISA I/O;
|
|
several microseconds or more means VCD traps the port. If it traps,
|
|
acquiring the port through VCD's API (option C) may be enough on its own.
|
|
3. **Tune the trigger.** The default `COMnRxTRIGGER=4` keeps about 1 ms of
|
|
slack at a quarter of trigger 1's interrupt rate.
|
|
- **Overruns:** try 1 (about 1.3 ms of slack).
|
|
- **CPU:** try 8 (about 0.7 ms of slack, half the interrupts of 4).
|
|
|
|
The 16550A's character timeout delivers stragglers within 4 character
|
|
times at any level. If bytes below the trigger level stall until more
|
|
data arrives, the UART or emulator is not raising the timeout interrupt;
|
|
use 1 there and note which hardware it was. (An old commit message
|
|
claimed this happened at trigger 8. It was never verified.)
|
|
|
|
Measure on real hardware: emulator UART and interrupt timing is not
|
|
representative.
|
|
|
|
|
|
## Option A -- the stock driver's VCD hook
|
|
|
|
**Verified** (KB Q80901, Windows 3.1 application compatibility):
|
|
|
|
> "COMM.DRV provides its own interrupt handling in enhanced mode."
|
|
> "a new function allows COMM.DRV to register its own ring-0 handler"
|
|
|
|
The same article says `COMMDRV30=TRUE` in `[386Enh]` restores the Windows
|
|
3.0 behavior. Both COMM.DRV and VCD read `COMxBASE` and `COMxIRQ`.
|
|
|
|
**Recollection** (to be confirmed from the DDK or disassembly):
|
|
|
|
- **What changed in 3.1.** In 3.0, VCD's own ring-0 code serviced the UART
|
|
for COMM.DRV. In 3.1, COMM.DRV hands VCD the address of its own ISR, and
|
|
VCD calls it at hardware-interrupt time through ring-0 aliases of
|
|
COMM.DRV's 16-bit code and data segments. That way all comm logic stays
|
|
in COMM.DRV.
|
|
- **The protocol.** COMM.DRV reaches VCD through its protected-mode API
|
|
entry point: INT 2Fh AX=1684h with BX=000Eh (VCD's device ID) returns
|
|
ES:DI. Acquire, release, and ISR-registration functions are called
|
|
through that entry point.
|
|
- **Sources.** The Windows 3.1 DDK shipped sample sources for COMM.DRV
|
|
(`ibmcom.asm`, `ibmint.asm`, ...) and probably for VCD; the exact protocol
|
|
is there.
|
|
|
|
**How to recover the protocol:**
|
|
|
|
- From the 3.1 DDK sources, if a copy can be found.
|
|
- From the CyberCom disassembly already in hand: look for INT 2Fh with
|
|
AX=1684h and BX=000Eh, then trace what it calls through the returned
|
|
entry point. If CyberCom does it, it is the template.
|
|
- From the stock COMM.DRV: same search.
|
|
|
|
**Constraints on KPCOMM's interrupt code if it runs at ring 0:**
|
|
|
|
- **No Windows API calls.** `checkNotify` calls `PostMessage`; that must
|
|
become a deferred event (VCD/VMM schedules a callback in the system VM,
|
|
which then posts), or a flag polled from application-context calls.
|
|
- **No DPMI.** `chainIsr` (DPMI chaining) and `maskIrq` go away; VPICD owns
|
|
masking, EOI (`VPICD_Phys_EOI`), and sharing. `serviceIrq`'s
|
|
EOI/chain/spurious logic is replaced by whatever VCD expects as a return.
|
|
- **Segments and stack.** The code runs with VCD-provided ring-0 aliases.
|
|
DS must be the alias passed in, not `seg ports`. Far pointers to
|
|
GlobalAlloc buffers carry ring-3 selectors; ring 0 may load DPL-3 data
|
|
selectors, but check how VCD expects buffers to be addressed. The stack
|
|
is VCD's; 16-bit code on a 32-bit stack needs care with SP versus ESP.
|
|
- **Two paths.** Standard mode has no VxDs, so the current DPMI path must
|
|
remain as a fallback, and both must share `servicePort`/`handleRx`/
|
|
`handleTx` without duplicating them.
|
|
- **Undocumented.** The interface is undocumented for third-party drivers
|
|
and specific to Windows 3.1 (and possibly differs in WfW 3.11).
|
|
|
|
The parts of `isr.c` that are already ring-safe are `servicePort`,
|
|
`handleRx`, `handleTx`, `handleLsr`, `handleMsr`, `updateRxFlow`,
|
|
`releaseRxFlow`, `updateTxHold`, `startTx`, `writeIer`, and `writeMcr`.
|
|
They use only port I/O and the port state. `raiseEvents` is safe;
|
|
`checkNotify` is not.
|
|
|
|
|
|
## Option B -- a KPCOMM VxD
|
|
|
|
The cleaner design: a small 32-bit virtual device that owns the UART at
|
|
ring 0, as Windows for Workgroups 3.11's SERIAL.386 does for its COMM.DRV
|
|
(recollection: WfW 3.11 shipped SERIAL.386, which takes over from VCD's comm
|
|
handling).
|
|
|
|
- **Interrupts.** `VPICD_Virtualize_IRQ` for the port's IRQ. The VxD's
|
|
hardware-interrupt callback services the UART and fills ring buffers in
|
|
locked memory, then calls `VPICD_Phys_EOI`.
|
|
- **Talking to COMM.DRV.** COMM.DRV gets a PM API entry point (INT 2Fh
|
|
1684h with the VxD's ID) to open and close ports, and to map the shared
|
|
buffers and ComDEB-like state into its address space. The ring-3
|
|
COMM.DRV keeps the Windows API and DCB logic, and the buffers stay
|
|
single-producer/single-consumer as now.
|
|
- **Notifications.** `Call_Priority_VM_Event` or a VMM event into the
|
|
system VM, then `PostMessage` from there.
|
|
- **Port ownership.** Either cooperate with VCD (acquire the port through
|
|
VCD so DOS boxes get the contention dialog) or replace VCD entirely with
|
|
`device=` in `[386Enh]`, as TurboCom-style products did (recollection).
|
|
Replacing VCD means taking on DOS-box virtualization too.
|
|
- **Toolchain.** Windows 3.1 DDK (VxD includes, `vmm.inc`/`vpicd.inc`), MASM
|
|
5.10b or 6.x, LINK386, and ADDHDR or equivalent. Debug with WDEB386 on a
|
|
serial terminal. MSVC 1.52 cannot build VxDs.
|
|
- **Cost.** The largest of the options. A bug hangs the machine, and
|
|
standard mode still needs the DPMI path.
|
|
|
|
|
|
## Option C -- VCD port acquisition only
|
|
|
|
If step 2 of the plan shows VCD trapping UART I/O for the system VM,
|
|
acquiring the port through VCD's PM API may stop the trapping while keeping
|
|
the ring-3 handler. It is cheaper than A or B and addresses only the
|
|
per-I/O cost, not scheduling latency. It needs the same VCD API
|
|
information as option A (recollection-level today).
|
|
|
|
|
|
## Summary
|
|
|
|
| Option | Fixes latency | Fixes CPU cost | Effort | Risk |
|
|
|---|---|---|---|---|
|
|
| Tune trigger level | No | Yes, up to 4-8x fewer interrupts | Trivial | Less slack per interrupt |
|
|
| C: VCD acquisition | No | Partly, only if I/O is trapped | Small-medium | Undocumented API |
|
|
| A: VCD ring-0 hook | Yes | Yes | Medium-large | Undocumented; restructured ISR; two paths |
|
|
| B: KPCOMM VxD | Yes | Yes | Large | New toolchain; ring-0 debugging; two paths |
|
|
|
|
|
|
## References
|
|
|
|
- KB Q80901, Windows 3.1 application compatibility (COMM.DRV ring-0
|
|
handler, `COMMDRV30`): https://jeffpar.github.io/kbarchive/kb/080/Q80901/
|
|
- KB Q83449, Windows 3.1 serial communications Q&A
|
|
- Windows 3.1 DDAG, communication drivers:
|
|
https://dos-help.soulsphere.org/ddag31qh.hlp/Communication-Driver_Functions.html
|
|
- `README.md` in this directory, for the current architecture
|