Skip to content
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

Add UringCmd opcode #133

Merged
merged 4 commits into from
Aug 31, 2022
Merged

Add UringCmd opcode #133

merged 4 commits into from
Aug 31, 2022

Conversation

albertofaria
Copy link
Contributor

Add support for IORING_SETUP_SQE128 and IORING_SETUP_CQE32, and add an UringCmd opcode corresponding to IORING_OP_URING_CMD.

These changes should be backward-compatible.

@quininer
Copy link
Member

Wow, that's pretty complicated, and frankly I don't like this.

  1. it adds some opaque types
  2. It makes applications running on normal sized io-uring have unnecessary overhead

I guess linus has the same reaction.
do you have a real scene that needs it? I would like to have time to consider if a better API design exists.

@albertofaria
Copy link
Contributor Author

Wow, that's pretty complicated

Indeed.

An alternative design would be to have separate "BigEntry" types, and parameterize IoUring with the SQE and CQE types, or maybe making IoUring a trait and providing 4 impls for the 4 small/big SQE/CQE combinations.

do you have a real scene that needs it? I would like to have time to consider if a better API design exists.

We would be using this in libblkio for NVMe passthrough (https://gitlab.com/libblkio/libblkio/-/merge_requests/117), but we can use the kernel UAPI directly for now and switch to io-uring when support is added.

@quininer
Copy link
Member

BigEntry sounds good, let's try it.

@albertofaria
Copy link
Contributor Author

v2:

  • Provide separate types for differently-sized SQEs and CQEs, and parameterize IoUring with the SQE and CQE type.
  • No longer backward-compatible, but still a simple change for existing users.
  • For SQEs there is an Entry trait that is implemented for structs Entry64 and Entry128, and analogously for CQEs.
  • There are now separate UringCmd16 and UringCmd80 opcodes that build Entry64s and Entry128s respectively.

The two Entry traits frequently need to be used from the same module to make their methods accessible, but we can't unless we use ... as _, which is slightly awkward. Should they be given differing names?

@quininer
Copy link
Member

Although generics make things a more complicated, but I still prefer v2 over v1.

I think the Entry trait is unnecessary, we can specify type explicitly to provide different methods.

impl SubmissionQueue<'_, Entry16> {
    pub unsafe fn push(&mut self, entry: &Entry16) -> Result<(), PushError> { ... }
}

and

impl IoUring<squeue::Entry64, cqueue::Entry16> {
    pub fn builder() -> Builder<squeue::Entry64, cqueue::Entry16> { ... }
}

maybe we can make cqueue::Entry16 keep the name of Entry, remove generics on methods, and make it still backward compatible.

there are currently only 4 combinations, and I think enumerating their builder methods is acceptable. Hopefully the kernel will not have more types of Entry in future.

@albertofaria
Copy link
Contributor Author

Providing separate IoUring impls for each valid SQE-CQE type combination will make it difficult for users to create generic code that can work on any IoUring<_, _>. The same goes for SubmissionQueue<_> and CompletionQueue<_>.

Perhaps we could just remove all methods from squeue::Entry and cqueue::Entry and keep them as inherent methods in the EntryN types, but still have the traits?

Also, I'm unsure whether it would be best to impl From<Entry16> for Entry32 or impl From<Entry32> for Entry16. (Implementing both is probably not a good idea because the conversion is not round-trip.) What do you think?

@quininer
Copy link
Member

This seems to work, it can be define as

pub trait EntryMarker: Copy + Sealed {}

I will consider impl From<Entry16> for Entry32, and provide a method for Entry32 like fn get(self) -> Entry16.

@albertofaria
Copy link
Contributor Author

v3:

  • Rename Entry to EntryMarker, Entry64 to Entry, and Entry16 to Entry.
  • Move methods from EntryMarker traits into the EntryN structs themselves.
  • impl From<Entry> for Entry32 instead of impl From<Entry32> for Entry.
  • This still isn't backward compatible, but required changes to user code are minimal.

@quininer
Copy link
Member

I think we can do backwards compatibility like giving the generics builder method a new name.

struct Bar<S = ()> {
    s: S
}

impl Bar<()> {
    pub fn new() -> Self {
        Bar { s: () }
    }
}

impl<S: Default> Bar<S> {
    pub fn new2() -> Self {
        Bar { s: S::default() }
    }
}


fn main() {
    let bar = Bar::new();
    let bar: Bar<u32> = Bar::new2();
}

@albertofaria
Copy link
Contributor Author

SubmissionQueue::push, SubmissionQueue::push_multiple, and CompletionQueue::fill also can't become generic if we want to keep backward compatibility.

Other than that, are we sure that adding default type parameters to IoUring, SubmissionQueue, and so on will maintain compatibility for every single possible current use of those types?

@quininer
Copy link
Member

I believe methods like push can keep generic because when we get a SubmissionQueue<S> , the type of S is deterministic.

@albertofaria
Copy link
Contributor Author

The trouble is that the compiler can no longer use the type of those methods' arguments for type inference, see for instance the changes required to io-uring-test/src/tests/queue.rs (10f20c2#diff-d0a68ef9fac5d4f6bf1873905a7b3b83bbe153fc356af15d3c3da510de186836).

@quininer
Copy link
Member

This is because fill takes an extra generic parameter, which I think we can remove it.

for normal cases, it should work. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d53b9587467696272906296a7c6c1cc6

@albertofaria
Copy link
Contributor Author

v4:

  • Rename IoUring::new() to IoUring::generic_new() and IoUring::builder() to IoUring::generic_builder().
  • Add new() and builder() methods to IoUring<squeue::Entry, cqueue::Entry>.
  • Drop type parameter from methods SubmissionQueue::push, SubmissionQueue::push_multiple, and CompletionQueue::fill.

I'm not knowledgeable enough to know for sure whether adding default type parameters to IoUring, SubmissionQueue, and CompletionQueue can break existing users.

@quininer
Copy link
Member

I believe that all generics must be given default parameters, otherwise compatibility will be broken.

@albertofaria
Copy link
Contributor Author

v5:

  • Make Builder's type parameters have defaults.

@quininer
Copy link
Member

This looks good, could you add some tests to it and put the new method behind the unstable feature gate?

Signed-off-by: Alberto Faria <[email protected]>
@albertofaria
Copy link
Contributor Author

Which of these should be behind unstable?

  1. squeue::Entry128 and cqueue::Entry32;
  2. cqueue::Entry32::big_cqe;
  3. IoUring::generic_new and IoUring::generic_builder;
  4. UringCmd16 and UringCmd80.

Testing the new opcodes in CI will be difficult, as uring_cmd can only currently be used for NVMe passthrough. I can make the existing tests generic over IoUring's type parameters, though, and make io-uring-test/src/main.rs run all tests on the 4 combinations of SQE and CQE types. Does that sound good?

@quininer
Copy link
Member

I think all should be marked unstable.

We don't need to test it on ci because kernel on ci doesn't support it yet. you can use ci feature (https://github.com/tokio-rs/io-uring/blob/master/io-uring-test/Cargo.toml#L21) to skip it on ci tests.

@albertofaria
Copy link
Contributor Author

The difficulty is that NVMe passthrough requires CAP_SYS_ADMIN and an actual NVMe device, which can also be a problem for local testing.

@albertofaria
Copy link
Contributor Author

v6:

  • Rebase.
  • Make cqueue::EntryMarker extend Into<Entry> and go back to impl From<Entry32> for Entry instead of impl From<Entry> for Entry32, so that code generic over the CQE type can still use Entry::result/user_data/flags, which are inherent methods not provided by cqueue::EntryMarker.
  • Conversely make squeue::EntryMarker extend From<Entry>.
  • Run all tests against each of the four combinations of SQE and CQE types.
  • Add #[cfg(feature = "unstable")] to IoUring::generic_new, IoUring::generic_build, squeue::Entry128, cqueue::Entry32, UringCmd16, and UringCmd80.


test::<squeue::Entry, cqueue::Entry>(IoUring::new(entries)?)?;

#[cfg(feature = "unstable")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it also adds not(feature = "ci")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, fixed.

src/cqueue.rs Outdated
unsafe { std::slice::from_raw_parts_mut(entries as *mut _ as *mut E, len) }
}

fn pop(&mut self) -> E {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked unsafe and inline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs unsafe: it is just reading some bytes from a region of memory we know is valid. There is no additional contract the caller must follow to avoid undefined behavior.

Should I make it unsafe nonetheless?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's always a valid memory region, if kernel doesn't write an entry into it, it's probably uninitialized memory.

}

Ok(())
}

unsafe fn push_unchecked(&mut self, entry: &E) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked inline.

Provide separate squeue::Entry and squeue::Entry128 types for 64-byte
and 128-byte SQEs, and a sealed squeue::EntryMarker trait implemented
for both. Analogously provide cqueue::Entry, cqueue::Entry32, and
cqueue::EntryMarker for CQEs.

Parameterize IoUring with the SQE and CQE type, bound by
squeue::EntryMarker and cqueue::EntryMarker. These type parameters
default to squeue::Entry and cqueue::Entry to make the common case less
verbose.

When 128-byte SQEs and/or 32-byte CQEs are used, the IORING_SETUP_SQE128
and/or IORING_SETUP_CQE32 flags respectively are automatically set.

These changes are a necessary step to support io_uring's NVMe
passthrough feature [1], which requires 128-byte SQEs and 32-byte CQEs.

Existing opcodes build squeue::Entry values, which implement
Into<squeue::Entry128> so that less user code needs to be generic over
the two SQE types. Similarly, cqueue::Entry implements
Into<cqueue::Entry32>.

[1] https://lore.kernel.org/all/[email protected]

Signed-off-by: Alberto Faria <[email protected]>
These corresponds to IORING_OP_URING_CMD, for use with 64-byte or
128-byte io_uring SQEs respectively, and in particular enable the use of
io_uring's NVMe passthrough feature [1] introduced in Linux 5.19.

[1] https://lore.kernel.org/all/[email protected]

Signed-off-by: Alberto Faria <[email protected]>
@quininer quininer merged commit 3c63565 into tokio-rs:master Aug 31, 2022
@quininer
Copy link
Member

Thank you!

@albertofaria albertofaria deleted the uring-cmd branch August 31, 2022 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants