Skip to content

Commit

Permalink
vhost_user: Add support for SHMEM_MAP/UNMAP backend requests
Browse files Browse the repository at this point in the history
Add request defintions and methods for using the new SHMEM_MAP/UNMAP
backend requests.

Note that at the time of writing this, these requests are part of this RFC
in QEMU:
https://mail.gnu.org/archive/html/qemu-devel/2024-06/msg05736.html

Co-authored-by: Albert Esteve <[email protected]>
Co-authored-by: Matej Hrica <[email protected]>

Signed-off-by: Albert Esteve <[email protected]>
Signed-off-by: Matej Hrica <[email protected]>
  • Loading branch information
aesteve-rh authored and mtjhrc committed Dec 19, 2024
1 parent c14f10b commit b482edb
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 1 deletion.
1 change: 1 addition & 0 deletions vhost/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

### Added
- [[#268]](https://github.com/rust-vmm/vhost/pull/268) Add support for `VHOST_USER_GET_SHARED_OBJECT`
- [[#251]](https://github.com/rust-vmm/vhost/pull/251) Add `SHMEM_MAP` and `SHMEM_UNMAP` support

### Changed

Expand Down
56 changes: 56 additions & 0 deletions vhost/src/vhost_user/backend_req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ impl VhostUserFrontendReqHandler for Backend {
Some(&[fd.as_raw_fd()]),
)
}

/// Forward vhost-user memory map file request to the frontend.
fn shmem_map(&self, req: &VhostUserMMap, fd: &dyn AsRawFd) -> HandlerResult<u64> {
self.send_message(BackendReq::SHMEM_MAP, req, Some(&[fd.as_raw_fd()]))
}

/// Forward vhost-user memory unmap file request to the frontend.
fn shmem_unmap(&self, req: &VhostUserMMap) -> HandlerResult<u64> {
self.send_message(BackendReq::SHMEM_UNMAP, req, None)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -269,4 +279,50 @@ mod tests {
.shared_object_add(&VhostUserSharedMsg::default())
.unwrap();
}

#[test]
fn test_shmem_map() {
let (mut fronted, backend) = frontend_backend_pair();

let (_, some_fd_to_send) = UnixStream::pair().unwrap();
let map_request = VhostUserMMap {
shmid: 0,
padding: Default::default(),
fd_offset: 0,
shm_offset: 1028,
len: 4096,
flags: (VhostUserMMapFlags::MAP_R | VhostUserMMapFlags::MAP_W).bits(),
};

backend.shmem_map(&map_request, &some_fd_to_send).unwrap();

let (hdr, request, fd) = fronted.recv_body::<VhostUserMMap>().unwrap();
assert_eq!(hdr.get_code().unwrap(), BackendReq::SHMEM_MAP);
assert!(fd.is_some());
assert_eq!({ request.shm_offset }, { map_request.shm_offset });
assert_eq!({ request.len }, { map_request.len },);
assert_eq!({ request.flags }, { map_request.flags });
}

#[test]
fn test_shmem_unmap() {
let (mut frontend, backend) = frontend_backend_pair();

let unmap_request = VhostUserMMap {
shmid: 0,
padding: Default::default(),
fd_offset: 0,
shm_offset: 1028,
len: 4096,
flags: 0,
};

backend.shmem_unmap(&unmap_request).unwrap();

let (hdr, request, fd) = frontend.recv_body::<VhostUserMMap>().unwrap();
assert_eq!(hdr.get_code().unwrap(), BackendReq::SHMEM_UNMAP);
assert!(fd.is_none());
assert_eq!({ request.shm_offset }, { unmap_request.shm_offset });
assert_eq!({ request.len }, { unmap_request.len });
}
}
104 changes: 103 additions & 1 deletion vhost/src/vhost_user/frontend_req_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ pub trait VhostUserFrontendReqHandler {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Handle shared memory region mapping requests.
fn shmem_map(&self, _req: &VhostUserMMap, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Handle shared memory region unmapping requests.
fn shmem_unmap(&self, _req: &VhostUserMMap) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}

// fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
// fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: &dyn AsRawFd);
}
Expand Down Expand Up @@ -84,6 +94,16 @@ pub trait VhostUserFrontendReqHandlerMut {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Handle shared memory region mapping requests.
fn shmem_map(&mut self, _req: &VhostUserMMap, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}

/// Handle shared memory region unmapping requests.
fn shmem_unmap(&mut self, _req: &VhostUserMMap) -> HandlerResult<u64> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}

// fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
// fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: RawFd);
}
Expand Down Expand Up @@ -111,6 +131,14 @@ impl<S: VhostUserFrontendReqHandlerMut> VhostUserFrontendReqHandler for Mutex<S>
) -> HandlerResult<u64> {
self.lock().unwrap().shared_object_lookup(uuid, fd)
}

fn shmem_map(&self, req: &VhostUserMMap, fd: &dyn AsRawFd) -> HandlerResult<u64> {
self.lock().unwrap().shmem_map(req, fd)
}

fn shmem_unmap(&self, req: &VhostUserMMap) -> HandlerResult<u64> {
self.lock().unwrap().shmem_unmap(req)
}
}

/// Server to handle service requests from backends from the backend communication channel.
Expand Down Expand Up @@ -241,6 +269,18 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
.shared_object_lookup(&msg, &files.unwrap()[0])
.map_err(Error::ReqHandlerError)
}
Ok(BackendReq::SHMEM_MAP) => {
let msg = self.extract_msg_body::<VhostUserMMap>(&hdr, size, &buf)?;
self.backend
.shmem_map(&msg, &files.unwrap()[0])
.map_err(Error::ReqHandlerError)
}
Ok(BackendReq::SHMEM_UNMAP) => {
let msg = self.extract_msg_body::<VhostUserMMap>(&hdr, size, &buf)?;
self.backend
.shmem_unmap(&msg)
.map_err(Error::ReqHandlerError)
}
_ => Err(Error::InvalidMessage),
};

Expand Down Expand Up @@ -278,7 +318,7 @@ impl<S: VhostUserFrontendReqHandler> FrontendReqHandler<S> {
files: &Option<Vec<File>>,
) -> Result<()> {
match hdr.get_code() {
Ok(BackendReq::SHARED_OBJECT_LOOKUP) => {
Ok(BackendReq::SHARED_OBJECT_LOOKUP | BackendReq::SHMEM_MAP) => {
// Expect a single file is passed.
match files {
Some(files) if files.len() == 1 => Ok(()),
Expand Down Expand Up @@ -366,12 +406,14 @@ mod tests {

struct MockFrontendReqHandler {
shared_objects: HashSet<Uuid>,
shmem_mappings: HashSet<(u64, u64)>,
}

impl MockFrontendReqHandler {
fn new() -> Self {
Self {
shared_objects: HashSet::new(),
shmem_mappings: HashSet::new(),
}
}
}
Expand All @@ -395,6 +437,16 @@ mod tests {
}
Ok(1)
}

fn shmem_map(&mut self, req: &VhostUserMMap, _fd: &dyn AsRawFd) -> HandlerResult<u64> {
assert_eq!({ req.shmid }, 0);
Ok(!self.shmem_mappings.insert((req.shm_offset, req.len)) as u64)
}

fn shmem_unmap(&mut self, req: &VhostUserMMap) -> HandlerResult<u64> {
assert_eq!({ req.shmid }, 0);
Ok(!self.shmem_mappings.remove(&(req.shm_offset, req.len)) as u64)
}
}

#[test]
Expand Down Expand Up @@ -436,6 +488,13 @@ mod tests {
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);

// Testing shmem map/unmap messages.
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 0);
});

backend.set_shared_object_flag(true);
Expand All @@ -456,6 +515,24 @@ mod tests {
.is_ok());
assert!(backend.shared_object_remove(&shobj_msg).is_ok());
assert!(backend.shared_object_remove(&shobj_msg).is_ok());

let (_, some_fd_to_map) = UnixStream::pair().unwrap();
let map_request1 = VhostUserMMap {
shm_offset: 0,
len: 4096,
..Default::default()
};
let map_request2 = VhostUserMMap {
shm_offset: 4096,
len: 8192,
..Default::default()
};
backend.shmem_map(&map_request1, &some_fd_to_map).unwrap();
backend.shmem_unmap(&map_request2).unwrap();
backend.shmem_map(&map_request2, &some_fd_to_map).unwrap();
backend.shmem_unmap(&map_request2).unwrap();
backend.shmem_unmap(&map_request1).unwrap();

// Ensure that the handler thread did not panic.
assert!(frontend_handler.join().is_ok());
}
Expand Down Expand Up @@ -485,6 +562,13 @@ mod tests {
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);

// Testing shmem map/unmap messages.
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 1);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 0);
assert_eq!(handler.handle_request().unwrap(), 0);
});

backend.set_reply_ack_flag(true);
Expand All @@ -506,6 +590,24 @@ mod tests {
.is_err());
assert!(backend.shared_object_remove(&shobj_msg).is_ok());
assert!(backend.shared_object_remove(&shobj_msg).is_err());

let (_, some_fd_to_map) = UnixStream::pair().unwrap();
let map_request1 = VhostUserMMap {
shm_offset: 0,
len: 4096,
..Default::default()
};
let map_request2 = VhostUserMMap {
shm_offset: 4096,
len: 8192,
..Default::default()
};
backend.shmem_map(&map_request1, &some_fd_to_map).unwrap();
backend.shmem_unmap(&map_request2).unwrap_err();
backend.shmem_map(&map_request2, &some_fd_to_map).unwrap();
backend.shmem_unmap(&map_request2).unwrap();
backend.shmem_unmap(&map_request1).unwrap();

// Ensure that the handler thread did not panic.
assert!(frontend_handler.join().is_ok());
}
Expand Down
46 changes: 46 additions & 0 deletions vhost/src/vhost_user/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ enum_value! {
SHARED_OBJECT_REMOVE = 7,
/// Lookup for a virtio shared object.
SHARED_OBJECT_LOOKUP = 8,
/// Map memory into guest address space
SHMEM_MAP = 9,
/// Unmap memory from guest address space
SHMEM_UNMAP = 10,
}
}

Expand Down Expand Up @@ -990,6 +994,48 @@ impl VhostUserMsgValidator for VhostUserTransferDeviceState {
}
}

// Bit mask for flags in VhostUserMMap struct
bitflags! {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
/// Flags specifying access permissions of memory mapping of a file
pub struct VhostUserMMapFlags: u64 {
/// Empty permission.
const EMPTY = 0x0;
/// Read permission.
const MAP_R = 0x1;
/// Write permission.
const MAP_W = 0x2;
}
}

/// Backend request to mmap a file-backed buffer into guest memory
#[repr(C, packed)]
#[derive(Debug, Copy, Clone, Default)]
pub struct VhostUserMMap {
/// Shared memory region ID.
pub shmid: u8,
/// Struct padding.
pub padding: [u8; 7],
/// File offset.
pub fd_offset: u64,
/// Offset into the shared memory region.
pub shm_offset: u64,
/// Size of region to map.
pub len: u64,
/// Flags for the mmap operation
pub flags: u64,
}

// SAFETY: Safe because all fields of VhostUserBackendMapMsg are POD.
unsafe impl ByteValued for VhostUserMMap {}

impl VhostUserMsgValidator for VhostUserMMap {
fn is_valid(&self) -> bool {
(self.flags & !VhostUserMMapFlags::all().bits()) == 0
&& self.fd_offset.checked_add(self.len).is_some()
&& self.shm_offset.checked_add(self.len).is_some()
}
}
/// Inflight I/O descriptor state for split virtqueues
#[repr(C, packed)]
#[derive(Clone, Copy, Default)]
Expand Down

0 comments on commit b482edb

Please sign in to comment.