devices: document interrupt supression.

There's some ring related wrapping math for interrupt suppression
(aka VIRTIO_F_EVENT_IDX) that had some documentation, but was still a little
tricky to follow. This CL adds some diagrams & further details.

BUG=none
TEST=n/a

Change-Id: I591661a676eb6506fbfea2afa20da28aaa1722f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3689972
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Noah Gold <nkgold@google.com>
This commit is contained in:
Noah Gold 2022-06-03 15:21:34 -07:00 committed by Chromeos LUCI
parent ded868a59f
commit 0bb3f1c3eb

View file

@ -576,14 +576,121 @@ impl Queue {
}
}
// Check Whether guest enable interrupt injection or not.
// Check whether guest enable interrupt injection or not.
//
// This function implements VIRTIO_RING_F_EVENT_IDX, otherwise known as
// interrupt suppression. The virtio spec provides the driver with a field,
// used_event, which says that once we write that descriptor (or several
// in the case of a flurry of add_used calls), we should send a
// notification. Because the values involved wrap around u16::MAX, and to
// avoid checking the condition on every add_used call, the math is a little
// complicated.
//
// The critical inequality is:
// (next_used - 1) - used_event < next_used - last_used
//
// For illustration purposes, we label it as A < B, where
// A = (next_used -1) - used_event, and B = next_used - last_used.
//
// A and B represent two distances, measured in a wrapping ring of size
// u16::MAX. In the "send intr" case, the inequality is true. In the
// "don't send intr" case, the inequality is false. We must be very careful
// in assigning a direction to the ring, so that when we
// graph the subtraction operations, we are measuring the right distance
// (similar to how DC circuits are analyzed).
//
// The two distances are as follows:
// * A is the distance between the driver's requested notification point,
// and the current position in the ring.
//
// * B is the distance between the last time we notified the guest,
// and the current position in the ring.
//
// If we graph these distances for the situation where we want to notify
// the guest, and when we don't want to notify the guest, we see that
// A < B becomes true the moment next_used - 1 passes used_event. See the
// graphs at the bottom of this comment block for a more visual explanation.
//
// Once an interrupt is sent, we have a final useful property: last_used
// moves up next_used, which causes the inequality to be false. Thus, we
// won't send notifications again until used_event is moved forward by the
// driver.
//
// Finally, let's talk about a couple of ways to write this inequality
// that don't work, and critically, explain *why*.
//
// First, a naive reading of the virtio spec might lead us to ask: why not
// just use the following inequality:
//
// next_used - 1 >= used_event
//
// because that's much simpler, right? The trouble is that the ring wraps,
// so it could be that a smaller index is actually ahead of a larger one.
// That's why we have to use distances in the ring instead.
//
// Second, one might look at the correct inequality:
// (next_used - 1) - used_event < next_used - last_used
//
// And try to simplify it to:
// last_used - 1 < used_event
//
// Functionally, this won't work because next_used isn't present at all
// anymore. (Notifications will never be sent.) But why is that? The algebra
// here *appears* to work out, but all semantic meaning is lost. There are
// two explanations for why this happens:
// * The intuitive one: the terms in the inequality are not actually
// separable; in other words, (next_used - last_used) is an inseparable
// term, so subtracting next_used from both sides of the original
// inequality and zeroing them out is semantically invalid. But why aren't
// they separable? See below.
// * The theoretical one: canceling like terms relies a vector space law:
// a + x = b + x => a = b (cancellation law). For congruences / equality
// under modulo, this law is satisfied, but for inequalities under mod, it
// is not; therefore, we cannot cancel like terms.
//
// ┌──────────────────────────────────┐
// │ │
// │ │
// │ │
// │ ┌──────────── next_used - 1
// │ │A x
// │ │ ┌────────────x────────────┐
// │ │ │ x │
// │ │ │ │
// │ │ │ │ │
// │ │ │ │ │
// │ used_event xxxx + ◄───┘ xxxxx last_used
// │ │ │ │
// │ │ Send intr │ │
// │ │ │ │
// │ └─────────────────────────┘ │
// │ │
// │ B │
// └────────────────────────────────────────────────────┘
//
// ┌───────────────────────────────────────────────────┐
// │ A │
// │ ┌────────────────────────┐ │
// │ │ │ │
// │ │ │ │
// │ │ │ │ │
// │ │ │ │ │
// used_event xxxx │ xxxxx last_used │
// │ + ◄───┘ │ │ │
// │ │ │ │
// │ Don't send intr │ │ │
// │ │ │ │
// └───────────x────────────┘ │ │
// x │ │
// next_used - 1 │ │
// │ │ B │ │
// │ └────────────────────┘ │
// │ │
// └──────────────────────────────────┘
//
fn available_interrupt_enabled(&self, mem: &GuestMemory) -> bool {
if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) != 0 {
let used_event = self.get_used_event(mem);
// if used_event >= self.last_used, driver handle interrupt quickly enough, new
// interrupt could be injected.
// if used_event < self.last_used, driver hasn't finished the last interrupt,
// so no need to inject new interrupt.
self.next_used - used_event - Wrapping(1) < self.next_used - self.last_used
} else {
!self.get_avail_flag(mem, VIRTQ_AVAIL_F_NO_INTERRUPT)