Register listeners only with both call and kick

The vhost-user protocol doesn't impose an order for the SET_VRING_KICK
and SET_VRING_CALL messages, which implies it's valid to emit the
first before the latter.

With the current code, this means that if the VMM sends SET_VRING_KICK
before SET_VRING_CALL, and the guest has already placed a request into
the vring, we may miss signaling the guest as that request may be
processed before we have an EventFd for "call".

To fix this, delay listener registration until we have an EventFd for
both call and kick, using "VringState::Queue.ready" as an indicator
that the vring has not been initialized yet.

Signed-off-by: Sergio Lopez <slp@redhat.com>
This commit is contained in:
Sergio Lopez 2021-09-13 13:39:10 +02:00 committed by Jiang Liu
parent a577bbc868
commit 4feeb77a20

View file

@ -175,6 +175,38 @@ where
Err(VhostUserHandlerError::MissingMemoryMapping)
}
fn vring_needs_init(&self, vring: &V) -> bool {
let vring_state = vring.get_ref();
// If the vring wasn't initialized and we already have an EventFd for
// both VRING_KICK and VRING_CALL, initialize it now.
!vring_state.get_queue().ready
&& vring_state.get_call().is_some()
&& vring_state.get_kick().is_some()
}
fn initialize_vring(&self, vring: &V, index: u8) -> VhostUserResult<()> {
assert!(vring.get_ref().get_call().is_some());
assert!(vring.get_ref().get_kick().is_some());
if let Some(fd) = vring.get_ref().get_kick() {
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
let shifted_queues_mask = queues_mask >> index;
if shifted_queues_mask & 1u64 == 1u64 {
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
self.handlers[thread_index]
.register_event(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx))
.map_err(VhostUserError::ReqHandlerError)?;
break;
}
}
}
self.vrings[index as usize].set_queue_ready(true);
Ok(())
}
}
impl<S, V, B> VhostUserSlaveReqHandlerMut for VhostUserHandler<S, V, B>
@ -361,6 +393,9 @@ where
}
}
self.vrings[index as usize].set_kick(None);
self.vrings[index as usize].set_call(None);
let next_avail = self.vrings[index as usize].queue_next_avail();
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
@ -377,23 +412,8 @@ where
// such as that proposed by Rust RFC #3128.
self.vrings[index as usize].set_kick(file);
// Quote from vhost-user specification:
// Client must start ring upon receiving a kick (that is, detecting
// that file descriptor is readable) on the descriptor specified by
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
// VHOST_USER_GET_VRING_BASE.
self.vrings[index as usize].set_queue_ready(true);
if let Some(fd) = self.vrings[index as usize].get_ref().get_kick() {
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
let shifted_queues_mask = queues_mask >> index;
if shifted_queues_mask & 1u64 == 1u64 {
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
self.handlers[thread_index]
.register_event(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(evt_idx))
.map_err(VhostUserError::ReqHandlerError)?;
break;
}
}
if self.vring_needs_init(&self.vrings[index as usize]) {
self.initialize_vring(&self.vrings[index as usize], index)?;
}
Ok(())
@ -406,6 +426,10 @@ where
self.vrings[index as usize].set_call(file);
if self.vring_needs_init(&self.vrings[index as usize]) {
self.initialize_vring(&self.vrings[index as usize], index)?;
}
Ok(())
}