Repository navigation
[DO NOT REVIEW] chipset: start each PIT low/high count with its low byte - #5061
Merged
Pedro Henrique Penna (ppenna) merged 1 commit intoNov 12, 2026
Conversation
In the low-then-high access mode (RW = 3), an 8254 counter takes a count as two bytes, the low byte first, through a write flip-flop that a control word for the counter resets to the low byte and that each byte then toggles, so that a guest can write one count after another without a new control word. Timer::write kept the low byte that it had buffered in cr_low once the high byte completed the count, and only set_control cleared it, so after the first count every byte was taken as a high byte with that stale low byte. Control word 0x30, then the counts 0 and 16, left counter 0 counting 65536 rather than 16 (microsoft/nvx#1063), and control word 0x34, then the counts 0x1234 and 0x5678, left the count register at 0x5634 (microsoft/nvx#1146). A guest that reloads a count without a control word, as Linux's PIT one-shot clock event device does for each event after its control word 0x38, so programmed a wrong period or one-shot length, and IRQ0 at the wrong rate. The high byte now takes the buffered low byte, which returns the flip-flop to the low byte of the next count. The low-byte-only and high-byte-only access modes, the reset by a control word, and the saved state, which already holds cr_low, are unchanged. A state that was saved without this change after such a count holds the stale low byte, which a restore keeps, as it cannot tell it from a low byte that the guest wrote: the guest's next counts then go wrong until it writes a control word for the counter, as they did before. Tests: in every mode, two counts written after a first one without a control word each take their low byte first. Through the I/O ports, as the issues reproduce it: counter 0 in mode 0, given the count 0 and then 16, raises IRQ0 on the 17th tick and not on the 16th, and counter 0 in mode 2, given 0x5678 while it counts 0x1234, holds 0x5678 at the end of the current period. Without the fix, the three tests fail with a count register of 0x5634, with IRQ0 still low after 17 ticks of a count of 65536, and with a latched count of 0x5634. The test attached to microsoft/nvx#1063 fails at the parent commit with the latched count 0xff9d of a count of 65536, and passes with the fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
requested a review
from a team
as a code owner
November 12, 2026 17:16
Pedro Henrique Penna (ppenna)
added a commit
to microsoft/nvx
that referenced
this pull request
Nov 12, 2026
In its low-then-high access mode (RW = 3), OpenVMM's emulated 8254 PIT kept the low byte that it had buffered once the high byte completed a count, so after the first count it took every byte as a high byte with that stale low byte. A guest that wrote a count without a new control word so programmed a wrong one: control word 0x30, then the counts 0 and 16, left counter 0 counting 65536 (#1063), and control word 0x34, then the counts 0x1234 and 0x5678, left the count register at 0x5634 (#1146). Pin 5d656d854a8daca0f23833666b0b71229fe2ab73, the head of microsoft/openvmm#5061, one commit on c2c21ddb2, the head of feature/microvm/dev: - 5d656d854 chipset: start each PIT low/high count with its low byte Timer::write now takes the buffered low byte with the high byte, which returns the write flip-flop to the low byte of the next count. The saved state, which already holds that byte, keeps its format. Evidence: on Windows, the revision's three new chipset tests, two of which program counter 0 through the I/O ports as the issues do, fail without the fix, with a count register of 0x5634, with IRQ0 still low after 17 ticks of a count of 65536, and with a latched count of 0x5634, and pass at the pin, with the 101 other tests of chipset. The test attached to #1063 fails at c2c21ddb2 with the latched count 0xff9d of a count of 65536, and passes at the pin. NVX's guests disable the PIT at boot and never program it again, so they never wrote the second count that the defect corrupted. NVX needs no coordinated change: the control-session vectors, the source excerpts, the contract versions, and the CPU profiles that nvx-rs copies from OpenVMM match the revision's. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
added a commit
to microsoft/nvx
that referenced
this pull request
Nov 12, 2026
The NVX pin moves to 5d656d854, position 263, 132 commits past the mapped tip. Position 263, which microsoft/openvmm#5061 proposes for feature/microvm/dev on its head, c2c21ddb2, is unassigned. The Scope also says that microsoft/openvmm#5056 merged position 262. It makes the PIT's count writes in the low-then-high access mode take each count's low byte first, in Timer::write, code that upstream main has, and adds three tests, in a file that PR10's position 022 and position 139 also change, neither of them in the code that it changes. It changes nothing that a mapped PR adds. Replaying it on upstream main applies without conflicts, and chipset type-checks there with its tests, which pass, so it can go upstream on its own. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
merged commit Nov 12, 2026
5d656d8
into
feature/microvm/dev
73 of 74 checks passed
This was referenced Nov 12, 2026
syeedali
pushed a commit
to syeedali/nvx
that referenced
this pull request
Nov 13, 2026
The NVX pin moves to 8eede133c, position 264, 133 commits past the mapped tip. Position 264, which microsoft/openvmm#5065 proposes for feature/microvm/dev on its head, 5d656d854, is unassigned. The Scope also says that microsoft/openvmm#5061 merged position 263. It keeps the part of the PIT's last tick time below VM time's 100 ns resolution, which PitDevice::evaluate dropped and counted again, in the PIT's evaluation, wakeup, reset, and saved state, code that upstream main has, and adds six tests that extend position 263's test helper, in a file that PR10's position 022 and positions 139 and 263 also change. It changes none of the code of positions 022 and 139, and nothing that a mapped PR adds. Replaying it on upstream main conflicts in that file, which lacks position 263's helper; after position 263, it conflicts only where its tests follow position 139's test, so it follows position 263 upstream. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the emulated 8254 PIT take each count of its low-then-high access mode low byte first, for microsoft/nvx#1063 and its duplicate microsoft/nvx#1146. In that mode, a counter's write flip-flop alternates between the low and the high byte of a count, and a control word for the counter resets it to the low byte, so a guest can write one count after another without a new control word, as Linux's PIT one-shot clock event device does for each event after its control word
0x38.Timer::writekept the low byte that it buffered once the high byte completed the count, so after the first count it took every byte as a high byte with that stale low byte.One commit on
c2c21ddb2, the head offeature/microvm/dev. microsoft/nvx#1551 pins this head.Start each PIT low/high count with its low byte (
5d656d854)Cause. In the
RwMode::LOW_HIGHarm ofTimer::write,if let Some(low) = self.state.cr_lowread the buffered low byte without clearing it, and onlyset_controlcleared it. Control word0x30, then the counts 0 and 16, left counter 0 counting 65536 rather than 16 (chipset: PIT LSB/MSB access mode keeps the buffered LSB after the MSB write nvx#1063), and control word0x34, then the counts0x1234and0x5678, left the count register at0x5634(openvmm: consecutive PIT low/high writes reuse the preceding count's low byte nvx#1146): a wrong period or one-shot length, and IRQ0 at the wrong rate, for a guest that reloads a count without a control word.Change.
self.state.cr_low.take(): the high byte takes the buffered low byte, which returns the flip-flop to the low byte of the next count. The low-byte-only and high-byte-only access modes, the reset by a control word, the counter latch and read-back commands, and the read flip-flop (read_high) are unchanged. The saved state already holdscr_low, so no field, format, or version changes. A state saved without this change after such a count holds the stale low byte, which a restore keeps, since it cannot tell it from a low byte that the guest wrote; the guest's next counts then go wrong until it writes a control word for the counter, as they did before.Tests.
low_high_access_takes_each_count_low_byte_first: in each of the six modes, after a control word and the count0x1234, the counts0x5678and0x9abc, written without a control word, each reach the count register.low_high_access_reloads_a_count_without_a_control_word, through the I/O ports with VM time set tick by tick (chipset: PIT LSB/MSB access mode keeps the buffered LSB after the MSB write nvx#1063): control word0x30, bytes0x00 0x00 0x10 0x00; IRQ0 is still low after 16 ticks and high after 17, the tick that loads the count and its 16 counts.low_high_access_reprograms_a_running_rate_generator, through the I/O ports (openvmm: consecutive PIT low/high writes reuse the preceding count's low byte nvx#1146): control word0x34, bytes0x34 0x12; one tick later the latched count is0x1234; bytes0x78 0x56while it counts; at the end of the current period,0x1234ticks later, the latched count is0x5678.Without the fix, the three fail: the count register holds
0x5634; IRQ0 is still low after 17 ticks, with the latched count0xfff0of a count of 65536; and the latched count is0x5634.Not in this change: in mode 0, the 8254 also stops counting and drives OUT low when the first byte of a two-byte count arrives, and loads the count on the clock after the second byte.
Timer::writeacts only on a complete count, so the previous count keeps running until the high byte arrives. That predates this change and is a separate behavior; this change does not alter what the first byte does.Validation
With Rust 1.97.1, on Windows, for
chipset:cargo check,cargo clippy --all-targets(clean apart from the environment's LNK4199 linker notices),cargo doc --no-deps, andcargo nextest run --profile agent(104 passed), thencargo xtask fmt --fix(rustfmt, lints, verify-fuzzers, and verify-flowey complete, no changes). Atc2c21ddb2, the three new tests fail as the Tests item says, and the test attached to microsoft/nvx#1063 fails with "mode 0 with count 16 did not reach terminal count after 100 ticks (latched count 0xff9d)"; with this commit, both pass.The PIT is OpenVMM's own on every backend, so this change is backend-independent; NVX's microVM tests on KVM, MSHV, and WHP run with it through the NVX pull request that pins this head.
See microsoft/nvx#1551.