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 <manos.pitsidianakis@linaro.org>
This commit is contained in:
Manos Pitsidianakis 2025-11-12 13:44:26 +02:00 committed by Stefano Garzarella
parent 893ae1d83d
commit 7d7dd57d6f
10 changed files with 78 additions and 71 deletions

View File

@ -0,0 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
pub mod scsi;
pub mod vhu_scsi;
pub mod virtio;

View File

@ -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")]

View File

@ -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<u64> for ByteOffset {
fn from(value: u64) -> Self {
ByteOffset(value)
@ -48,7 +48,7 @@ impl Div<BlockSize> for ByteOffset {
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub(crate) struct BlockSize(NonZeroU32);
pub struct BlockSize(NonZeroU32);
impl From<BlockSize> for u32 {
fn from(value: BlockSize) -> Self {
u32::from(value.0)
@ -63,7 +63,7 @@ impl TryFrom<u32> for BlockSize {
}
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub(crate) struct BlockOffset(u64);
pub struct BlockOffset(u64);
impl From<BlockOffset> for u64 {
fn from(value: BlockOffset) -> Self {
value.0
@ -96,7 +96,7 @@ impl Mul<BlockSize> 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<BlockOffset>;
@ -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<T: BlockDeviceBackend> {
pub struct BlockDevice<T: BlockDeviceBackend> {
backend: T,
write_protected: bool,
rotation_rate: MediumRotationRate,
}
impl<T: BlockDeviceBackend> BlockDevice<T> {
pub(crate) const fn new(backend: T) -> Self {
pub const fn new(backend: T) -> Self {
Self {
backend,
write_protected: false,

View File

@ -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<u8> 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<VpdPage> 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<VpdPage>),
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<u16>))] = &[
pub const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
(CommandType::TestUnitReady, (0x0, None)),
(CommandType::RequestSense, (0x3, None)),
(CommandType::Inquiry, (0x12, None)),
@ -270,7 +270,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
];
#[derive(Debug, Clone, Copy)]
pub(crate) struct UnparsedServiceAction(u8);
pub struct UnparsedServiceAction(u8);
impl UnparsedServiceAction {
pub fn parse(self, service_action: u16) -> Option<CommandType> {
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<u32>,
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<Self, ParseError> {
pub fn parse(cdb: &[u8]) -> Result<Self, ParseError> {
let ct = CommandType::from_cdb(cdb)?;
if cdb.len() < ct.cdb_template().len() {
return Err(ParseError::TooSmall);

View File

@ -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(

View File

@ -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;

View File

@ -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(&[

View File

@ -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<Box<dyn LogicalUnit>>,
}
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<dyn LogicalUnit>) {
pub fn add_lun(&mut self, logical_unit: Box<dyn LogicalUnit>) {
self.luns.push(logical_unit);
}
pub(crate) fn luns(&self) -> impl ExactSizeIterator<Item = u16> + '_ {
pub fn luns(&self) -> impl ExactSizeIterator<Item = u16> + '_ {
// unwrap is safe: we limit LUNs at 256
self.luns
.iter()

View File

@ -35,15 +35,21 @@ const REQUEST_QUEUE: u16 = 2;
type DescriptorChainWriter = virtio::DescriptorChainWriter<GuestMemoryLoadGuard<GuestMemoryMmap>>;
type DescriptorChainReader = virtio::DescriptorChainReader<GuestMemoryLoadGuard<GuestMemoryMmap>>;
pub(crate) struct VhostUserScsiBackend {
pub struct VhostUserScsiBackend {
event_idx: bool,
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
targets: Vec<Box<dyn Target>>,
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<dyn Target>) {
pub fn add_target(&mut self, target: Box<dyn Target>) {
self.targets.push(target);
}
}

View File

@ -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<Self> {
pub fn parse(bytes: [u8; 8]) -> Option<Self> {
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,