-
Notifications
You must be signed in to change notification settings - Fork 28
NVMe admin commands should better mind their PRPs #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,7 @@ | ||
| use std::cmp::min; | ||
| use std::mem::size_of; | ||
|
|
||
| use crate::common::GuestAddr; | ||
| use crate::common::PAGE_SIZE; | ||
| use crate::common::{GuestAddr, GuestRegion, PAGE_SIZE}; | ||
| use crate::vmm::MemCtx; | ||
|
|
||
| use super::bits::*; | ||
|
|
@@ -186,14 +185,19 @@ impl NvmeCtrl { | |
| cmd: &cmds::GetLogPageCmd, | ||
| mem: &MemCtx, | ||
| ) -> cmds::Completion { | ||
| assert!((cmd.len as usize) < PAGE_SIZE); | ||
| let buf = cmd | ||
| if let Some(regions) = cmd | ||
| .data(mem) | ||
| .next() | ||
| .expect("missing prp entry for log page response"); | ||
| // TODO: actually keep a log that we can write back instead of all zeros | ||
| assert!(mem.write_byte(buf.0, 0, cmd.len as usize)); | ||
| cmds::Completion::success() | ||
| .map(|r| mem.writable_region(&r)) | ||
| .collect::<Option<Vec<_>>>() | ||
| { | ||
| // TODO: Keep a log to write back instead of 0s | ||
| for region in regions { | ||
| let _ = region.write_byte(0, region.len()); | ||
| } | ||
| cmds::Completion::success() | ||
| } else { | ||
| cmds::Completion::generic_err(STS_DATA_XFER_ERR) | ||
| } | ||
| } | ||
|
|
||
| /// Service Identify command. | ||
|
|
@@ -208,12 +212,16 @@ impl NvmeCtrl { | |
| IDENT_CNS_NAMESPACE => match cmd.nsid { | ||
| 1 => { | ||
| assert!(size_of::<IdentifyNamespace>() <= PAGE_SIZE); | ||
| let buf = cmd | ||
| .data(mem) | ||
| .next() | ||
| .expect("missing prp entry for ident response"); | ||
| assert!(mem.write(buf.0, &self.ns_ident)); | ||
| cmds::Completion::success() | ||
| match Self::write_admin_result( | ||
| cmd.data(mem), | ||
| &self.ns_ident, | ||
| mem, | ||
| ) { | ||
| Some(_) => cmds::Completion::success(), | ||
| None => { | ||
| cmds::Completion::generic_err(STS_DATA_XFER_ERR) | ||
| } | ||
| } | ||
| } | ||
| // 0 is not a valid NSID (See NVMe 1.0e, Section 6.1 Namespaces) | ||
| // We also don't currently support namespace management | ||
|
|
@@ -224,12 +232,15 @@ impl NvmeCtrl { | |
| }, | ||
| IDENT_CNS_CONTROLLER => { | ||
| assert!(size_of::<IdentifyController>() <= PAGE_SIZE); | ||
| let buf = cmd | ||
| .data(mem) | ||
| .next() | ||
| .expect("missing prp entry for ident response"); | ||
| assert!(mem.write(buf.0, &self.ctrl_ident)); | ||
| cmds::Completion::success() | ||
|
|
||
| match Self::write_admin_result( | ||
| cmd.data(mem), | ||
| &self.ctrl_ident, | ||
| mem, | ||
| ) { | ||
| Some(_) => cmds::Completion::success(), | ||
| None => cmds::Completion::generic_err(STS_DATA_XFER_ERR), | ||
| } | ||
| } | ||
| // We currently present NVMe version 1.0 in which CNS is a 1-bit field | ||
| // and hence only need to support the NAMESPACE and CONTROLLER cases | ||
|
|
@@ -282,4 +293,58 @@ impl NvmeCtrl { | |
| } | ||
| } | ||
| } | ||
|
|
||
| /// Write result data from an admin command into host memory | ||
| /// | ||
| /// The `data` type must be `repr(packed(1))` | ||
| /// | ||
| /// Returns `Some(())` if successful, else None | ||
| fn write_admin_result<T: Copy>( | ||
| prp: cmds::PrpIter, | ||
| data: &T, | ||
| mem: &MemCtx, | ||
| ) -> Option<()> { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not especially fond of using |
||
| let bufs: Vec<GuestRegion> = prp.collect(); | ||
| if size_of::<T>() > bufs.iter().map(|r| r.1).sum::<usize>() { | ||
| // Not enough space | ||
| return None; | ||
| } | ||
| let regions = bufs | ||
| .into_iter() | ||
| .map(|r| mem.writable_region(&r)) | ||
| .collect::<Option<Vec<_>>>()?; | ||
| if regions.len() == 1 { | ||
| // Can be copied to one contiguous page | ||
| regions[0].write(data).ok()?; | ||
| Some(()) | ||
| } else { | ||
| // Split across multiple pages | ||
|
|
||
| // Safety: | ||
| // | ||
| // We expect and demand that the resulting structs written through | ||
| // this function are packed, such that there is no padding to risk | ||
| // UB through the [u8] slice creation. | ||
| let mut raw = unsafe { | ||
| std::slice::from_raw_parts( | ||
| data as *const T as *const u8, | ||
| size_of::<T>(), | ||
| ) | ||
| }; | ||
| let mut copied = 0; | ||
| for region in regions { | ||
| let write_len = usize::min(region.len(), raw.len()); | ||
|
|
||
| let to_copy; | ||
| (to_copy, raw) = raw.split_at(write_len); | ||
| copied += region.write_bytes(&to_copy).ok()?; | ||
|
|
||
| if raw.is_empty() { | ||
| break; | ||
| } | ||
| } | ||
| assert_eq!(copied, size_of::<T>()); | ||
| Some(()) | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be neat to enforce the packed-ness of T here, but the most promising looking crate I found to do so (
repr-trait) doesn't quite look to me like it would drop into this code neatly (reading through its derives I'm not convinced it will handle therepr(C, packed(1))repr that NVMe's structs use). Oh well--maybe another time.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, a
write_from_ptr()interface would keep us clear of the potential UB from the slice creation. This should work for now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to the comment regarding the
repr()expectations, so it's at least a bit more visible in the mean time.