-
-
Notifications
You must be signed in to change notification settings - Fork 169
uefi: Add safe protocol wrapper for EFI_EXT_SCSI_PASS_THRU_PROTOCOL #1589
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
Conversation
6aff09b
to
3787821
Compare
2406c3a
to
359311e
Compare
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.
Thanks for your contribution! I left a few remarks. Further, can you please add an integration test for the new functionality?
9518d32
to
ff4607d
Compare
@phip1611 @nicholasbishop Since that change, the qemu process of the aarch64 integration test runner is hard-crashing in the EDIT: Was able to fix it by leaving the second disk to the way it was before and instead adding a third disk, then located on a SCSI Controller. |
5b1f56e
to
bca67e0
Compare
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.
This is a good PR with a comprehensive improvement! Thanks! LGTM
As this PR is a little bigger than others, I'd like to ask @nicholasbishop to give a second approval
Yes, as long as it doesn't cause other issues I think that would be a good refactor. Fewer lifetimes, and avoiding internal pointers and Phantom markers feels worth it to me, to make it less likely that the code is accidentally unsound. |
grr let mut scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
// v borrows scsi_pt immutably
for device in scsi_pt.iter_devices() {
let request = ...;
let result = scsi_pt.execute_command(&device, request);
// ^ attempts to borrow scsi_pt mutably
} |
Ah right. What about collecting the iterator into a |
Yes, that works (just tested it). Although it's an additional allocation. On the other hand, we require let mut scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
let devices: Vec<_> = scsi_pt.iter_devices().collect();
for device in devices {
let request = ...;
let result = scsi_pt.execute_command(&device, request);
// ...
// profit
} One minor caveat: let device = scsi_pt.iter_devices().next().unwrap(); and then run: other_scsi_pt.execute_command(&device, request); |
@nicholasbishop
I just pushed a version without internal pointer and without |
f897dc4
to
6a1cab4
Compare
FYI, I'll return to looking at this soon -- I've been a way for a few days so lots to catch up on at work :) |
Mind pulling AlignedBuf into a separate PR? I think I'm sold on the usefulness of that addition to the API, and the need for aligned buffers comes up in lots of places, so I'd like to review that independently. |
5cb00bb
to
670362b
Compare
I think the API discussion has drifted off a bit and is somewhat widespread throughout various "threads" in this issue. 1) Intelligent Device Objects: Everything const
API ExtScsiPassThru::reset_channel(&self);
// returned ScsiDevice is owned (not a reference), but internally borrows the underlying raw protocol
ExtScsiPassThru::iter_devices(&self) -> Iterator<Item = ScsiDevice>;
ScsiDevice::reset(&self);
ScsiDevice::execute_command(&self, cmd: ScsiCommand); Usability wise (w.r.t. to flexibility for weird use-cases), probably the best option. Usage let proto: ExtScsiPassThru;
for device in proto.iter_devices() {
device.execute_command(..);
proto.reset_channel(); // << allows bus-reset mixed with device interaction
}
// also allows:
let devices = proto.iter_devices().collect();
devices[0].reset();
devices[1].reset(); 2) Intelligent Device Objects: Actions mut
API ExtScsiPassThru::reset_channel(&mut self);
// returned ScsiDevice is owned (not a reference), but internally borrows the underlying raw protocol
ExtScsiPassThru::iter_devices(&self) -> Iterator<Item = ScsiDevice>;
ScsiDevice::reset(&mut self);
ScsiDevice::execute_command(&mut self, cmd: ScsiCommand); Usability wise, very close to all-const option. Usage let proto: ExtScsiPassThru;
for mut device in proto.iter_devices() {
device.execute_command(..);
}
// also allows:
let devices = proto.iter_devices().collect();
devices[0].reset();
devices[1].reset(); 3) Device Handle: Actions at protocol mut
API ExtScsiPassThru::reset_channel(&mut self);
// Returned ScsiDevice objects are only handles passed back to ExtScsiPassThru methods
// The objects don't keep a reference to the protocol
ExtScsiPassThru::iter_devices(&self) -> Iterator<Item = ScsiDevice>;
ExtScsiPassThru::reset_device(&mut self, dev: ScsiDevice);
ExtScsiPassThru::execute_command(&mut self, dev: ScsiDevice, cmd: ScsiCommand); Usability-wise, this variant is worse than the other two, because:
However: It allows resetting the bus while ScsiDevice instances live, just like the all-const variant. Usage let proto: ExtScsiPassThru;
let proto2: ExtScsiPassThru;
for device in proto.iter_devices() {
// not possible! Iterator borrows protocol immutably, interaction requires mut
device.execute_command(device, ..);
}
// you have to allocate:
let devices = proto.iter_devices().collect();
for device in devices {
proto.execute_command(device, ..);
proto.reset_channel(); // << allows bus-reset mixed with device interaction
}
// also allows:
device.execute_command(device[0], ..);
device.execute_command(device[1], ..);
// miss-use handle at other instance:
proto2.execute_command(device[0], ..); IMHO, 2) Intelligent Device Objects: Actions mut is the best option. That's the one that is implemented at the moment. I don't want to create any pressure, but I'd be happy if we could decide on one variant of these soon (or a concrete counterproposal I can implement). |
@seijikun Thanks for the write-up. That is definitely very helpful. I'm also in favor of In case the test of time shows that this was a bad design, we can just make a breaking chance (we are still not v1.0.0). One more question: Do you see a possibility to abstract over all passthru storage device protocols to not reimplement the same API multiple times? |
I think the largest duplication effort is in the RequestBuilders, and that one can't be abstracted away because of how different these protocols are. So if at all, I think we could only put SCSI and ATA in the same abstraction - but even they differ quite a lot in their implementation. Compare e.g. the iterator implementation, which is much more contrived with ATA, because of the splitter semantics. |
No problem, that's fine. We do not need abstractions at any cost |
Thanks for the detailed writeup! And sorry for the delay in getting you my review, been quite busy recently :) I agree with your analysis, let's stick with option 2 (and as Philipp said, if we need to change it in the future we can). |
Merged, thanks for your patience! A couple suggestions to reduce delays for the future:
Hope these are helpful, and thanks again for all your work on these protocols. |
Update Latest API discussions
Added a safe wrapper for the extended SCSI Pass Thru protocol.
I did my best to design the API in a way that avoids accidentally using it incorrectly, yet allowing it to operate efficiently.
The
ScsiRequestBuilder
API is designed in a way that should easily make it possible to use it for bothEFI_EXT_SCSI_PASS_THRU_PROTOCOL
and a possible future safe protocol wrapper ofEFI_SCSI_IO_PROTOCOL
.Exemplary usage to probe all devices potentially connected to every SCSI channel in the system:
Easy variant (io/cmd buffer allocations per request):
Buffer-reuse API variant:
Checklist