nvme: drop shadow doorbell registration on controller reset - #4080
Closed
smalis-msft wants to merge 1 commit into
Closed
nvme: drop shadow doorbell registration on controller reset#4080smalis-msft wants to merge 1 commit into
smalis-msft wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes an NVMe emulation correctness bug where shadow doorbell (DBBUF) and event index buffers could remain registered across a controller reset, causing the controller to read/write stale guest pages after reboot and potentially corrupting guest memory. It resets DoorbellMemory back to controller-internal storage on reset paths and adds a regression test to prevent future regressions.
Changes:
- Add
DoorbellMemory::reset()to drop shadow doorbell registration and clearevent_idx_offset. - Invoke doorbell-memory reset after controller reset completion and after device-level worker reset.
- Add an async regression test that verifies guest DBBUF pages are not written after a controller reset + re-enable without reconfiguring DBBUF.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| vm/devices/storage/nvme/src/workers/coordinator.rs | Reset shared doorbell storage when controller/device reset paths complete. |
| vm/devices/storage/nvme/src/queue.rs | Add DoorbellMemory::reset() to restore controller-internal doorbell backing and clear event index state. |
| vm/devices/storage/nvme/src/tests/shadow_doorbell_tests.rs | Add regression test ensuring DBBUF/event-idx buffers are not used after controller reset. |
| vm/devices/storage/nvme_test/src/workers/coordinator.rs | Mirror the coordinator reset behavior in the fault-injection emulator copy. |
| vm/devices/storage/nvme_test/src/queue.rs | Mirror DoorbellMemory::reset() in the fault-injection emulator copy. |
Member
|
Let's take #4011 instead. |
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.
Problem
multiarch::pcie::openvmm_linux_x64_pcie_hotplugintermittently times out(CI run 30466297922). After the test's reboot, the guest hits a
BUG: kernel NULL pointer dereferenceinnvme_probeand never reachesinit, so pipette never reconnects and petri times out after 10 minutes.
Root cause
DoorbellMemoryis shared out ofNvmeWorkersvia anArc<RwLock<..>>andwas never restored on reset. Once the guest issued Doorbell Buffer Config,
replace_memrepointed it at guest pages and nothing ever pointed it back —so after a reboot the emulator kept reading and writing the previous boot's
DBBUF pages. This violates NVMe Base 5.8, which ends the lifetime of the
Shadow Doorbell and EventIdx buffers at a Controller Level Reset.
The corruption is deterministic: on the next
CC.EN 0→1,SubmissionQueue::new(db_id 0) andCompletionQueue::new(db_id 1) eachwrite
0, which with a 4-byte doorbell stride zeroes the first 8 bytes of thestale page — a full 64-bit NULL wherever the new kernel happened to place a
pointer. Whether that crashes the guest depends on what the page was reused
for, which is where the flakiness comes from.
The failing run's
timeout_inspect_vmm.logshows the fingerprint directly:the stale doorbell page reads
[0, 0]while the stale EventIdx page stillholds boot 1's
[11, 4], because the emulator only writes EventIdx when itprobes forward.
Fix
DoorbellMemory::reset()reallocates the controller-internal doorbellstorage and clears
event_idx_offset.poll_controller_reset) and atthe end of a device-level
reset. Both paths matter: Linux's reboot usesCC.SHNand leavesCC.ENset, so only the device reset runs on a guestreboot.
nvme_testfault-injection copy of the emulator.Testing
New
test_shadow_doorbells_dropped_on_controller_resetverifies that asentinel written into the guest's DBBUF pages after a controller reset
survives a re-enable and a doorbell write. Confirmed to fail without the fix
(
left: 0, right: 0x0123456789abcdef) and stable over repeated runs.cargo nextest,clippy --all-targets,cargo doc, andcargo xtask fmtpass for
nvmeandnvme_test.