virtio-devices: vhost_user: Enable snapshot/restore for vhost-user-*

Enable the DEVICE_STATE protocol feature negotiation for all vhost-user
devices (block, fs, net, and generic). Restoring the state (including
the backend state if present) and vrings.

Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
Rob Bradford
2026-03-26 14:22:42 -07:00
parent 2fbb98e2c3
commit cd3334a3c2
4 changed files with 86 additions and 54 deletions

View File

@@ -72,6 +72,7 @@ impl Blk {
vu_num_queues, vu_num_queues,
config, config,
paused, paused,
vring_bases,
) = if let Some(state) = state { ) = if let Some(state) = state {
info!("Restoring vhost-user-block {id}"); info!("Restoring vhost-user-block {id}");
@@ -80,6 +81,8 @@ impl Blk {
state.acked_protocol_features, state.acked_protocol_features,
)?; )?;
vu.restore_state(&state)?;
( (
state.avail_features, state.avail_features,
state.acked_features, state.acked_features,
@@ -87,6 +90,7 @@ impl Blk {
state.vu_num_queues, state.vu_num_queues,
state.config, state.config,
true, true,
state.vring_bases,
) )
} else { } else {
// Filling device and vring features VMM supports. // Filling device and vring features VMM supports.
@@ -111,7 +115,8 @@ impl Blk {
| VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS
| VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::REPLY_ACK
| VhostUserProtocolFeatures::INFLIGHT_SHMFD | VhostUserProtocolFeatures::INFLIGHT_SHMFD
| VhostUserProtocolFeatures::LOG_SHMFD; | VhostUserProtocolFeatures::LOG_SHMFD
| VhostUserProtocolFeatures::DEVICE_STATE;
let (acked_features, acked_protocol_features) = let (acked_features, acked_protocol_features) =
vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
@@ -160,6 +165,7 @@ impl Blk {
num_queues, num_queues,
config, config,
false, false,
None,
) )
}; };
@@ -179,6 +185,7 @@ impl Blk {
acked_protocol_features, acked_protocol_features,
socket_path: vu_cfg.socket, socket_path: vu_cfg.socket,
vu_num_queues, vu_num_queues,
vring_bases,
..Default::default() ..Default::default()
}, },
id, id,

View File

@@ -101,6 +101,7 @@ impl Fs {
vu_num_queues, vu_num_queues,
config, config,
paused, paused,
vring_bases,
) = if let Some(state) = state { ) = if let Some(state) = state {
info!("Restoring vhost-user-fs {id}"); info!("Restoring vhost-user-fs {id}");
@@ -109,6 +110,8 @@ impl Fs {
state.acked_protocol_features, state.acked_protocol_features,
)?; )?;
vu.restore_state(&state)?;
( (
state.avail_features, state.avail_features,
state.acked_features, state.acked_features,
@@ -116,6 +119,7 @@ impl Fs {
state.vu_num_queues, state.vu_num_queues,
state.config, state.config,
true, true,
state.vring_bases,
) )
} else { } else {
// Filling device and vring features VMM supports. // Filling device and vring features VMM supports.
@@ -125,7 +129,8 @@ impl Fs {
| VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS
| VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::REPLY_ACK
| VhostUserProtocolFeatures::INFLIGHT_SHMFD | VhostUserProtocolFeatures::INFLIGHT_SHMFD
| VhostUserProtocolFeatures::LOG_SHMFD; | VhostUserProtocolFeatures::LOG_SHMFD
| VhostUserProtocolFeatures::DEVICE_STATE;
let (acked_features, acked_protocol_features) = let (acked_features, acked_protocol_features) =
vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
@@ -168,6 +173,7 @@ impl Fs {
num_queues, num_queues,
config, config,
false, false,
None,
) )
}; };
@@ -187,6 +193,7 @@ impl Fs {
acked_protocol_features, acked_protocol_features,
socket_path: path.to_string(), socket_path: path.to_string(),
vu_num_queues, vu_num_queues,
vring_bases,
..Default::default() ..Default::default()
}, },
id, id,

View File

@@ -68,64 +68,74 @@ impl GenericVhostUser {
// Connect to the vhost-user socket. // Connect to the vhost-user socket.
let mut vu = VhostUserHandle::connect_vhost_user(false, path, num_queues as u64, false)?; let mut vu = VhostUserHandle::connect_vhost_user(false, path, num_queues as u64, false)?;
let (avail_features, acked_features, acked_protocol_features, vu_num_queues, paused) = let (
if let Some(state) = state { avail_features,
info!("Restoring generic vhost-user {id}"); acked_features,
vu.set_protocol_features_vhost_user( acked_protocol_features,
state.acked_features, vu_num_queues,
state.acked_protocol_features, paused,
)?; vring_bases,
) = if let Some(state) = state {
info!("Restoring generic vhost-user {id}");
vu.set_protocol_features_vhost_user(
state.acked_features,
state.acked_protocol_features,
)?;
( vu.restore_state(&state)?;
state.avail_features,
state.acked_features,
state.acked_protocol_features,
state.vu_num_queues,
true,
)
} else {
let avail_protocol_features = VhostUserProtocolFeatures::CONFIG
| VhostUserProtocolFeatures::MQ
| VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS
| VhostUserProtocolFeatures::REPLY_ACK
| VhostUserProtocolFeatures::INFLIGHT_SHMFD
| VhostUserProtocolFeatures::LOG_SHMFD;
let avail_features = super::DEFAULT_VIRTIO_FEATURES; (
state.avail_features,
state.acked_features,
state.acked_protocol_features,
state.vu_num_queues,
true,
state.vring_bases,
)
} else {
let avail_protocol_features = VhostUserProtocolFeatures::CONFIG
| VhostUserProtocolFeatures::MQ
| VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS
| VhostUserProtocolFeatures::REPLY_ACK
| VhostUserProtocolFeatures::INFLIGHT_SHMFD
| VhostUserProtocolFeatures::LOG_SHMFD
| VhostUserProtocolFeatures::DEVICE_STATE;
let (acked_features, acked_protocol_features) = let avail_features = super::DEFAULT_VIRTIO_FEATURES;
vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
let backend_num_queues = let (acked_features, acked_protocol_features) =
if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 { vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
vu.socket_handle()
.get_queue_num()
.map_err(Error::VhostUserGetQueueMaxNum)?
as usize
} else {
num_queues
};
if num_queues > backend_num_queues { let backend_num_queues =
error!( if acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 {
"generic vhost-user requested too many queues ({num_queues}) \ vu.socket_handle()
.get_queue_num()
.map_err(Error::VhostUserGetQueueMaxNum)? as usize
} else {
num_queues
};
if num_queues > backend_num_queues {
error!(
"generic vhost-user requested too many queues ({num_queues}) \
since the backend only supports {backend_num_queues}\n", since the backend only supports {backend_num_queues}\n",
); );
return Err(Error::BadQueueNum); return Err(Error::BadQueueNum);
} }
( (
acked_features, acked_features,
// If part of the available features that have been acked, the // If part of the available features that have been acked, the
// PROTOCOL_FEATURES bit must be already set through the VIRTIO // PROTOCOL_FEATURES bit must be already set through the VIRTIO
// acked features as we know the guest would never ack it, thus // acked features as we know the guest would never ack it, thus
// the feature would be lost. // the feature would be lost.
acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(), acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
acked_protocol_features, acked_protocol_features,
num_queues, num_queues,
false, false,
) None,
}; )
};
Ok(GenericVhostUser { Ok(GenericVhostUser {
common: VirtioCommon { common: VirtioCommon {
@@ -143,6 +153,7 @@ since the backend only supports {backend_num_queues}\n",
acked_protocol_features, acked_protocol_features,
socket_path: path.to_string(), socket_path: path.to_string(),
vu_num_queues, vu_num_queues,
vring_bases,
..Default::default() ..Default::default()
}, },
id, id,

View File

@@ -82,6 +82,7 @@ impl Net {
vu_num_queues, vu_num_queues,
config, config,
paused, paused,
vring_bases,
) = if let Some(state) = state { ) = if let Some(state) = state {
info!("Restoring vhost-user-net {id}"); info!("Restoring vhost-user-net {id}");
@@ -95,6 +96,8 @@ impl Net {
state.acked_protocol_features, state.acked_protocol_features,
)?; )?;
vu.restore_state(&state)?;
// If the control queue feature has been negotiated, let's // If the control queue feature has been negotiated, let's
// increase the number of queues. // increase the number of queues.
if state.acked_features & (1 << VIRTIO_NET_F_CTRL_VQ) != 0 { if state.acked_features & (1 << VIRTIO_NET_F_CTRL_VQ) != 0 {
@@ -108,6 +111,7 @@ impl Net {
state.vu_num_queues, state.vu_num_queues,
state.config, state.config,
true, true,
state.vring_bases,
) )
} else { } else {
// Filling device and vring features VMM supports. // Filling device and vring features VMM supports.
@@ -144,7 +148,8 @@ impl Net {
| VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS | VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS
| VhostUserProtocolFeatures::REPLY_ACK | VhostUserProtocolFeatures::REPLY_ACK
| VhostUserProtocolFeatures::INFLIGHT_SHMFD | VhostUserProtocolFeatures::INFLIGHT_SHMFD
| VhostUserProtocolFeatures::LOG_SHMFD; | VhostUserProtocolFeatures::LOG_SHMFD
| VhostUserProtocolFeatures::DEVICE_STATE;
let (mut acked_features, acked_protocol_features) = let (mut acked_features, acked_protocol_features) =
vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?; vu.negotiate_features_vhost_user(avail_features, avail_protocol_features)?;
@@ -187,6 +192,7 @@ impl Net {
vu_num_queues, vu_num_queues,
config, config,
false, false,
None,
) )
}; };
@@ -208,6 +214,7 @@ impl Net {
socket_path: vu_cfg.socket, socket_path: vu_cfg.socket,
vu_num_queues, vu_num_queues,
server, server,
vring_bases,
..Default::default() ..Default::default()
}, },
config, config,