mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
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:
parent
ded868a59f
commit
0bb3f1c3eb
1 changed files with 112 additions and 5 deletions
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue