From 7d7dd57d6f0f25d54af1d14dfbffefe24eacd583 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 12 Nov 2025 13:44:26 +0200 Subject: [PATCH] scsi: split into binary and library crate Split implementation to binary crate under src/main.rs and library crate under src/lib.rs Signed-off-by: Manos Pitsidianakis --- vhost-device-scsi/src/lib.rs | 5 +++ vhost-device-scsi/src/main.rs | 18 ++++------ .../src/scsi/emulation/block_device.rs | 16 ++++----- .../src/scsi/emulation/command.rs | 34 +++++++++---------- .../src/scsi/emulation/missing_lun.rs | 2 +- vhost-device-scsi/src/scsi/emulation/mod.rs | 8 ++--- .../src/scsi/emulation/mode_page.rs | 10 +++--- .../src/scsi/emulation/target.rs | 12 +++---- vhost-device-scsi/src/vhu_scsi.rs | 14 +++++--- vhost-device-scsi/src/virtio.rs | 30 ++++++++-------- 10 files changed, 78 insertions(+), 71 deletions(-) create mode 100644 vhost-device-scsi/src/lib.rs diff --git a/vhost-device-scsi/src/lib.rs b/vhost-device-scsi/src/lib.rs new file mode 100644 index 0000000..5cf57cd --- /dev/null +++ b/vhost-device-scsi/src/lib.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +pub mod scsi; +pub mod vhu_scsi; +pub mod virtio; diff --git a/vhost-device-scsi/src/main.rs b/vhost-device-scsi/src/main.rs index 05dcb68..dcc436b 100644 --- a/vhost-device-scsi/src/main.rs +++ b/vhost-device-scsi/src/main.rs @@ -1,8 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -mod scsi; -mod vhu_scsi; -mod virtio; +use vhost_device_scsi::{ + scsi::emulation::{ + block_device::{BlockDevice, FileBackend, MediumRotationRate}, + target::EmulatedTarget, + }, + vhu_scsi::VhostUserScsiBackend, +}; use std::{ fs::File, @@ -17,14 +21,6 @@ use thiserror::Error as ThisError; use vhost_user_backend::VhostUserDaemon; use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; -use crate::{ - scsi::emulation::{ - block_device::{BlockDevice, FileBackend, MediumRotationRate}, - target::EmulatedTarget, - }, - vhu_scsi::VhostUserScsiBackend, -}; - #[derive(Debug, ThisError)] enum Error { #[error("More than 256 LUNs aren't currently supported")] diff --git a/vhost-device-scsi/src/scsi/emulation/block_device.rs b/vhost-device-scsi/src/scsi/emulation/block_device.rs index 3477c26..1a7bbfd 100644 --- a/vhost-device-scsi/src/scsi/emulation/block_device.rs +++ b/vhost-device-scsi/src/scsi/emulation/block_device.rs @@ -22,13 +22,13 @@ use super::{ }; use crate::scsi::{sense, CmdError, CmdOutput, TaskAttr}; -pub(crate) enum MediumRotationRate { +pub enum MediumRotationRate { Unreported, NonRotating, } #[derive(Clone, Copy, PartialEq, PartialOrd)] -pub(crate) struct ByteOffset(u64); +pub struct ByteOffset(u64); impl From for ByteOffset { fn from(value: u64) -> Self { ByteOffset(value) @@ -48,7 +48,7 @@ impl Div for ByteOffset { } #[derive(Clone, Copy, PartialEq, PartialOrd)] -pub(crate) struct BlockSize(NonZeroU32); +pub struct BlockSize(NonZeroU32); impl From for u32 { fn from(value: BlockSize) -> Self { u32::from(value.0) @@ -63,7 +63,7 @@ impl TryFrom for BlockSize { } #[derive(Clone, Copy, PartialEq, PartialOrd)] -pub(crate) struct BlockOffset(u64); +pub struct BlockOffset(u64); impl From for u64 { fn from(value: BlockOffset) -> Self { value.0 @@ -96,7 +96,7 @@ impl Mul for BlockOffset { } } -pub(crate) trait BlockDeviceBackend: Send + Sync { +pub trait BlockDeviceBackend: Send + Sync { fn read_exact_at(&mut self, buf: &mut [u8], offset: ByteOffset) -> io::Result<()>; fn write_exact_at(&mut self, buf: &[u8], offset: ByteOffset) -> io::Result<()>; fn size_in_blocks(&mut self) -> io::Result; @@ -104,7 +104,7 @@ pub(crate) trait BlockDeviceBackend: Send + Sync { fn sync(&mut self) -> io::Result<()>; } -pub(crate) struct FileBackend { +pub struct FileBackend { file: File, block_size: BlockSize, } @@ -142,14 +142,14 @@ impl BlockDeviceBackend for FileBackend { } } -pub(crate) struct BlockDevice { +pub struct BlockDevice { backend: T, write_protected: bool, rotation_rate: MediumRotationRate, } impl BlockDevice { - pub(crate) const fn new(backend: T) -> Self { + pub const fn new(backend: T) -> Self { Self { backend, write_protected: false, diff --git a/vhost-device-scsi/src/scsi/emulation/command.rs b/vhost-device-scsi/src/scsi/emulation/command.rs index ce2b307..995658a 100644 --- a/vhost-device-scsi/src/scsi/emulation/command.rs +++ b/vhost-device-scsi/src/scsi/emulation/command.rs @@ -19,7 +19,7 @@ use crate::scsi::emulation::mode_page::ModePage; /// One of the modes supported by SCSI's REPORT LUNS command. #[derive(PartialEq, Eq, Debug, Copy, Clone)] #[repr(u8)] -pub(crate) enum ReportLunsSelectReport { +pub enum ReportLunsSelectReport { NoWellKnown = 0x0, WellKnownOnly = 0x1, All = 0x2, @@ -46,7 +46,7 @@ impl TryFrom for ReportLunsSelectReport { /// A type of "vital product data" page returned by SCSI's INQUIRY command. #[derive(PartialEq, Eq, Debug, Copy, Clone)] -pub(crate) enum VpdPage { +pub enum VpdPage { Ascii(u8), Ata, // * BlockDeviceCharacteristics, // * @@ -78,7 +78,7 @@ pub(crate) enum VpdPage { #[derive(PartialEq, Eq, Debug, Copy, Clone)] #[repr(u8)] -pub(crate) enum ModeSensePageControl { +pub enum ModeSensePageControl { Current = 0b00, Changeable = 0b01, Default = 0b10, @@ -169,24 +169,24 @@ impl From for u8 { } #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) enum SenseFormat { +pub enum SenseFormat { Fixed, Descriptor, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) enum ModePageSelection { +pub enum ModePageSelection { AllPageZeros, Single(ModePage), } #[derive(Debug)] -pub(crate) enum LunIndependentCommand { +pub enum LunIndependentCommand { ReportLuns(ReportLunsSelectReport), } #[derive(Debug)] -pub(crate) enum LunSpecificCommand { +pub enum LunSpecificCommand { Inquiry(Option), ModeSense6 { pc: ModeSensePageControl, @@ -230,13 +230,13 @@ pub(crate) enum LunSpecificCommand { } #[derive(Debug)] -pub(crate) enum Command { +pub enum Command { LunIndependentCommand(LunIndependentCommand), LunSpecificCommand(LunSpecificCommand), } #[derive(Clone, Copy, Debug)] -pub(crate) enum CommandType { +pub enum CommandType { Inquiry, ModeSense6, Read10, @@ -251,7 +251,7 @@ pub(crate) enum CommandType { SynchronizeCache10, } -pub(crate) const OPCODES: &[(CommandType, (u8, Option))] = &[ +pub const OPCODES: &[(CommandType, (u8, Option))] = &[ (CommandType::TestUnitReady, (0x0, None)), (CommandType::RequestSense, (0x3, None)), (CommandType::Inquiry, (0x12, None)), @@ -270,7 +270,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option))] = &[ ]; #[derive(Debug, Clone, Copy)] -pub(crate) struct UnparsedServiceAction(u8); +pub struct UnparsedServiceAction(u8); impl UnparsedServiceAction { pub fn parse(self, service_action: u16) -> Option { OPCODES @@ -282,7 +282,7 @@ impl UnparsedServiceAction { /// See `parse_opcode` #[derive(Debug, Clone, Copy)] -pub(crate) enum ParseOpcodeResult { +pub enum ParseOpcodeResult { /// The opcode represents a single command. Command(CommandType), /// The opcode requires a service action. @@ -309,7 +309,7 @@ pub(crate) enum ParseOpcodeResult { /// - `ServiceAction`: the opcode is the first byte of a service action; the /// caller needs to call .parse() on the `UnparsedServiceAction` we returned /// with the service action byte. -pub(crate) fn parse_opcode(opcode: u8) -> ParseOpcodeResult { +pub fn parse_opcode(opcode: u8) -> ParseOpcodeResult { let found = OPCODES.iter().find(|(_, (x, _))| *x == opcode); match found { Some(&(ty, (_, None))) => ParseOpcodeResult::Command(ty), @@ -493,14 +493,14 @@ impl CommandType { } #[derive(Debug)] -pub(crate) struct Cdb { +pub struct Cdb { pub command: Command, pub allocation_length: Option, pub naca: bool, } #[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub(crate) enum ParseError { +pub enum ParseError { /// The opcode (specifically the first byte of the CDB) is unknown, i.e. we /// should respond with INVALID COMMAND OPERATION CODE InvalidCommand, @@ -512,7 +512,7 @@ pub(crate) enum ParseError { } #[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub(crate) enum ReportSupportedOpCodesMode { +pub enum ReportSupportedOpCodesMode { All, OneCommand(u8), OneServiceAction(u8, u16), @@ -522,7 +522,7 @@ pub(crate) enum ReportSupportedOpCodesMode { impl Cdb { // TODO: do we want to ensure reserved fields are 0? SCSI allows, but // doesn't require, us to do so. - pub(crate) fn parse(cdb: &[u8]) -> Result { + pub fn parse(cdb: &[u8]) -> Result { let ct = CommandType::from_cdb(cdb)?; if cdb.len() < ct.cdb_template().len() { return Err(ParseError::TooSmall); diff --git a/vhost-device-scsi/src/scsi/emulation/missing_lun.rs b/vhost-device-scsi/src/scsi/emulation/missing_lun.rs index 72c6760..deeb511 100644 --- a/vhost-device-scsi/src/scsi/emulation/missing_lun.rs +++ b/vhost-device-scsi/src/scsi/emulation/missing_lun.rs @@ -9,7 +9,7 @@ use super::{ }; use crate::scsi::{sense, CmdError, CmdError::DataIn, CmdOutput}; -pub(crate) struct MissingLun; +pub struct MissingLun; impl LogicalUnit for MissingLun { fn execute_command( diff --git a/vhost-device-scsi/src/scsi/emulation/mod.rs b/vhost-device-scsi/src/scsi/emulation/mod.rs index d697842..6cccfc4 100644 --- a/vhost-device-scsi/src/scsi/emulation/mod.rs +++ b/vhost-device-scsi/src/scsi/emulation/mod.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -pub(crate) mod block_device; +pub mod block_device; mod command; -pub(crate) mod missing_lun; -pub(crate) mod mode_page; +pub mod missing_lun; +pub mod mode_page; mod response_data; -pub(crate) mod target; +pub mod target; #[cfg(test)] mod tests; diff --git a/vhost-device-scsi/src/scsi/emulation/mode_page.rs b/vhost-device-scsi/src/scsi/emulation/mode_page.rs index e0c30e7..d7678a3 100644 --- a/vhost-device-scsi/src/scsi/emulation/mode_page.rs +++ b/vhost-device-scsi/src/scsi/emulation/mode_page.rs @@ -3,26 +3,26 @@ use std::io::{self, Write}; #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub(crate) enum ModePage { +pub enum ModePage { Caching, } impl ModePage { - pub(crate) const ALL_ZERO: &'static [Self] = &[Self::Caching]; + pub const ALL_ZERO: &'static [Self] = &[Self::Caching]; - pub(crate) const fn page_code(self) -> (u8, u8) { + pub const fn page_code(self) -> (u8, u8) { match self { Self::Caching => (0x8, 0), } } - pub(crate) const fn page_length(self) -> u8 { + pub const fn page_length(self) -> u8 { match self { Self::Caching => 0x12, } } - pub(crate) fn write(self, data_in: &mut impl Write) -> io::Result<()> { + pub fn write(self, data_in: &mut impl Write) -> io::Result<()> { assert_eq!(self.page_code().1, 0, "Subpages aren't supported yet."); data_in.write_all(&[ diff --git a/vhost-device-scsi/src/scsi/emulation/target.rs b/vhost-device-scsi/src/scsi/emulation/target.rs index c636d13..bbf364c 100644 --- a/vhost-device-scsi/src/scsi/emulation/target.rs +++ b/vhost-device-scsi/src/scsi/emulation/target.rs @@ -16,7 +16,7 @@ use super::{ }; use crate::scsi::{sense, CmdError, CmdOutput, Request, Target, TaskAttr}; -pub(crate) struct LunRequest { +pub struct LunRequest { pub _id: u64, pub task_attr: TaskAttr, pub crn: u8, @@ -26,7 +26,7 @@ pub(crate) struct LunRequest { } /// A single logical unit of an emulated SCSI device. -pub(crate) trait LogicalUnit: Send + Sync { +pub trait LogicalUnit: Send + Sync { /// Process a SCSI command sent to this logical unit. /// /// # Return value @@ -47,20 +47,20 @@ pub(crate) trait LogicalUnit: Send + Sync { } /// A SCSI target implemented by emulating a device within vhost-device-scsi. -pub(crate) struct EmulatedTarget { +pub struct EmulatedTarget { luns: Vec>, } impl EmulatedTarget { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { luns: Vec::new() } } - pub(crate) fn add_lun(&mut self, logical_unit: Box) { + pub fn add_lun(&mut self, logical_unit: Box) { self.luns.push(logical_unit); } - pub(crate) fn luns(&self) -> impl ExactSizeIterator + '_ { + pub fn luns(&self) -> impl ExactSizeIterator + '_ { // unwrap is safe: we limit LUNs at 256 self.luns .iter() diff --git a/vhost-device-scsi/src/vhu_scsi.rs b/vhost-device-scsi/src/vhu_scsi.rs index 628bafc..fa0adf3 100644 --- a/vhost-device-scsi/src/vhu_scsi.rs +++ b/vhost-device-scsi/src/vhu_scsi.rs @@ -35,15 +35,21 @@ const REQUEST_QUEUE: u16 = 2; type DescriptorChainWriter = virtio::DescriptorChainWriter>; type DescriptorChainReader = virtio::DescriptorChainReader>; -pub(crate) struct VhostUserScsiBackend { +pub struct VhostUserScsiBackend { event_idx: bool, mem: Option>, targets: Vec>, - pub(crate) exit_event: EventFd, + pub exit_event: EventFd, +} + +impl Default for VhostUserScsiBackend { + fn default() -> Self { + Self::new() + } } impl VhostUserScsiBackend { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { event_idx: false, mem: None, @@ -193,7 +199,7 @@ impl VhostUserScsiBackend { Ok(()) } - pub(crate) fn add_target(&mut self, target: Box) { + pub fn add_target(&mut self, target: Box) { self.targets.push(target); } } diff --git a/vhost-device-scsi/src/virtio.rs b/vhost-device-scsi/src/virtio.rs index a88ad83..2f0aa9e 100644 --- a/vhost-device-scsi/src/virtio.rs +++ b/vhost-device-scsi/src/virtio.rs @@ -20,18 +20,18 @@ use vm_memory::{Bytes, GuestAddress, GuestMemory}; /// virtio-scsi has its own format for LUNs, documented in 5.6.6.1 of virtio /// v1.1. This represents a LUN parsed from that format. #[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub(crate) enum VirtioScsiLun { +pub enum VirtioScsiLun { ReportLuns, TargetLun(u8, u16), } -pub(crate) const REPORT_LUNS: [u8; 8] = [0xc1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; +pub const REPORT_LUNS: [u8; 8] = [0xc1, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; impl VirtioScsiLun { - pub(crate) const FLAT_SPACE_ADDRESSING_METHOD: u8 = 0b0100_0000; - pub(crate) const ADDRESS_METHOD_PATTERN: u8 = 0b1100_0000; + pub const FLAT_SPACE_ADDRESSING_METHOD: u8 = 0b0100_0000; + pub const ADDRESS_METHOD_PATTERN: u8 = 0b1100_0000; - pub(crate) fn parse(bytes: [u8; 8]) -> Option { + pub fn parse(bytes: [u8; 8]) -> Option { if bytes == REPORT_LUNS { Some(Self::ReportLuns) } else if bytes[0] == 0x1 { @@ -55,7 +55,7 @@ impl VirtioScsiLun { #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ResponseCode { +pub enum ResponseCode { Ok = 0, Overrun = 1, BadTarget = 3, @@ -64,10 +64,10 @@ pub(crate) enum ResponseCode { // These are the defaults given in the virtio spec; QEMU doesn't let the driver // write to config space, so these will always be the correct values. -pub(crate) const SENSE_SIZE: usize = 96; -pub(crate) const CDB_SIZE: usize = 32; +pub const SENSE_SIZE: usize = 96; +pub const CDB_SIZE: usize = 32; -pub(crate) struct Request { +pub struct Request { pub id: u64, pub lun: VirtioScsiLun, pub prio: u8, @@ -77,7 +77,7 @@ pub(crate) struct Request { } #[derive(Debug)] -pub(crate) enum RequestParseError { +pub enum RequestParseError { CouldNotReadGuestMemory(io::Error), FailedParsingLun([u8; 8]), } @@ -107,7 +107,7 @@ impl Request { } #[derive(Debug, PartialEq, Eq)] -pub(crate) struct Response { +pub struct Response { pub response: ResponseCode, pub status: u8, pub status_qualifier: u16, @@ -316,7 +316,7 @@ where } #[cfg(test)] -pub(crate) mod tests { +pub mod tests { use virtio_bindings::virtio_scsi::{virtio_scsi_cmd_req, virtio_scsi_cmd_resp}; use virtio_queue::{desc::RawDescriptor, mock::MockSplitQueue}; use vm_memory::{ByteValued, GuestAddress, GuestMemoryMmap}; @@ -325,19 +325,19 @@ pub(crate) mod tests { #[derive(Debug, Default, Clone, Copy)] #[repr(transparent)] - pub(crate) struct VirtioScsiCmdReq(pub virtio_scsi_cmd_req); + pub struct VirtioScsiCmdReq(pub virtio_scsi_cmd_req); /// SAFETY: struct is a transparent wrapper around the request /// which can be read from a byte array unsafe impl ByteValued for VirtioScsiCmdReq {} #[derive(Debug, Default, Clone, Copy)] #[repr(transparent)] - pub(crate) struct VirtioScsiCmdResp(pub virtio_scsi_cmd_resp); + pub struct VirtioScsiCmdResp(pub virtio_scsi_cmd_resp); /// SAFETY: struct is a transparent wrapper around the response /// which can be read from a byte array unsafe impl ByteValued for VirtioScsiCmdResp {} - pub(crate) fn report_luns_command() -> VirtioScsiCmdReq { + pub fn report_luns_command() -> VirtioScsiCmdReq { VirtioScsiCmdReq(virtio_scsi_cmd_req { lun: REPORT_LUNS, tag: 0,