use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use anyhow::{bail, Result};
use clap::Parser;
use crate::ScsiBus::{aio_complete_cb, ScsiCompleteCb};
use crate::{Device, DeviceBase};
use block_backend::{create_block_backend, BlockDriverOps, BlockProperty};
use machine_manager::config::{valid_id, DriveConfig, DriveFile, VmConfig};
use machine_manager::event_loop::EventLoop;
use util::aio::{Aio, AioEngine, WriteZeroesState};
use util::gen_base_func;
use super::bus::ScsiBus;
pub const SCSI_TYPE_DISK: u32 = 0x00;
pub const SCSI_TYPE_TAPE: u32 = 0x01;
pub const SCSI_TYPE_PRINTER: u32 = 0x02;
pub const SCSI_TYPE_PROCESSOR: u32 = 0x03;
pub const SCSI_TYPE_WORM: u32 = 0x04;
pub const SCSI_TYPE_ROM: u32 = 0x05;
pub const SCSI_TYPE_SCANNER: u32 = 0x06;
pub const SCSI_TYPE_MOD: u32 = 0x07;
pub const SCSI_TYPE_MEDIUM_CHANGER: u32 = 0x08;
pub const SCSI_TYPE_STORAGE_ARRAY: u32 = 0x0c;
pub const SCSI_TYPE_ENCLOSURE: u32 = 0x0d;
pub const SCSI_TYPE_RBC: u32 = 0x0e;
pub const SCSI_TYPE_OSD: u32 = 0x11;
pub const SCSI_TYPE_ZBC: u32 = 0x14;
pub const SCSI_TYPE_WLUN: u32 = 0x1e;
pub const SCSI_TYPE_NOT_PRESENT: u32 = 0x1f;
pub const SCSI_TYPE_INACTIVE: u32 = 0x20;
pub const SCSI_TYPE_NO_LUN: u32 = 0x7f;
pub const SCSI_DISK_F_REMOVABLE: u32 = 0;
pub const SCSI_DISK_F_DPOFUA: u32 = 1;
pub const SECTOR_SHIFT: u8 = 9;
pub const DEFAULT_SECTOR_SIZE: u32 = 1_u32 << SECTOR_SHIFT;
pub const SCSI_DISK_DEFAULT_BLOCK_SIZE_SHIFT: u32 = 9;
pub const SCSI_DISK_DEFAULT_BLOCK_SIZE: u32 = 1 << SCSI_DISK_DEFAULT_BLOCK_SIZE_SHIFT;
pub const SCSI_CDROM_DEFAULT_BLOCK_SIZE_SHIFT: u32 = 11;
pub const SCSI_CDROM_DEFAULT_BLOCK_SIZE: u32 = 1 << SCSI_CDROM_DEFAULT_BLOCK_SIZE_SHIFT;
const SCSI_MAX_CHANNEL: i64 = 0;
const SCSI_MAX_TARGET: i64 = 255;
const SUPPORT_SCSI_MAX_LUN: i64 = 255;
#[derive(Parser, Clone, Debug, Default)]
#[command(no_binary_name(true))]
pub struct ScsiDevConfig {
#[arg(long, value_parser = ["scsi-cd", "scsi-hd"])]
pub classtype: String,
#[arg(long, value_parser = valid_id)]
pub id: String,
#[arg(long, value_parser = valid_scsi_bus)]
pub bus: String,
#[arg(long, default_value = "0", value_parser = clap::value_parser!(u8).range(..=SCSI_MAX_CHANNEL))]
pub channel: u8,
#[arg(long, alias = "scsi-id", value_parser = clap::value_parser!(u8).range(..=SCSI_MAX_TARGET))]
pub target: u8,
#[arg(long, value_parser = clap::value_parser!(u16).range(..=SUPPORT_SCSI_MAX_LUN))]
pub lun: u16,
#[arg(long)]
pub drive: String,
#[arg(long)]
pub serial: Option<String>,
#[arg(long)]
pub bootindex: Option<u8>,
}
fn valid_scsi_bus(bus: &str) -> Result<String> {
let strs = bus.split('.').collect::<Vec<&str>>();
if strs.len() != 2 || strs[1] != "0" {
bail!("Invalid scsi bus {}", bus);
}
Ok(bus.to_string())
}
#[derive(Clone, Default)]
pub struct ScsiDevState {
pub features: u32,
pub vendor: String,
pub product: String,
pub device_id: String,
pub version: String,
pub serial: String,
}
impl ScsiDevState {
fn new() -> Self {
ScsiDevState {
features: 0,
vendor: "STRA".to_string(),
product: "".to_string(),
device_id: "".to_string(),
version: "".to_string(),
serial: "".to_string(),
}
}
}
impl Device for ScsiDevice {
gen_base_func!(device_base, device_base_mut, DeviceBase, base);
fn realize(mut self) -> Result<Arc<Mutex<Self>>> {
match self.scsi_type {
SCSI_TYPE_DISK => {
self.block_size = SCSI_DISK_DEFAULT_BLOCK_SIZE;
self.state.product = "STRA HARDDISK".to_string();
}
SCSI_TYPE_ROM => {
self.block_size = SCSI_CDROM_DEFAULT_BLOCK_SIZE;
self.state.product = "STRA CDROM".to_string();
}
_ => {
bail!("Scsi type {} does not support now", self.scsi_type);
}
}
if let Some(serial) = &self.dev_cfg.serial {
self.state.serial = serial.clone();
}
let drive_files = self.drive_files.lock().unwrap();
let file = VmConfig::fetch_drive_file(&drive_files, &self.drive_cfg.path_on_host)?;
let alignments = VmConfig::fetch_drive_align(&drive_files, &self.drive_cfg.path_on_host)?;
self.req_align = alignments.0;
self.buf_align = alignments.1;
let drive_id = VmConfig::get_drive_id(&drive_files, &self.drive_cfg.path_on_host)?;
drop(drive_files);
let mut thread_pool = None;
if self.drive_cfg.aio != AioEngine::Off {
thread_pool = Some(EventLoop::get_ctx(None).unwrap().thread_pool.clone());
}
let aio = Aio::new(Arc::new(aio_complete_cb), self.drive_cfg.aio, thread_pool)?;
let conf = BlockProperty {
id: drive_id,
format: self.drive_cfg.format,
iothread: self.iothread.clone(),
direct: self.drive_cfg.direct,
req_align: self.req_align,
buf_align: self.buf_align,
discard: false,
write_zeroes: WriteZeroesState::Off,
l2_cache_size: self.drive_cfg.l2_cache_size,
refcount_cache_size: self.drive_cfg.refcount_cache_size,
};
let backend = create_block_backend(file, aio, conf)?;
let disk_size = backend.lock().unwrap().disk_size()?;
self.block_backend = Some(backend);
self.disk_sectors = disk_size >> SECTOR_SHIFT;
let dev = Arc::new(Mutex::new(self));
Ok(dev)
}
}
#[macro_export]
macro_rules! SCSI_DEVICE {
($trait_device:expr, $lock_device: ident, $struct_device: ident) => {
convert_device_ref!($trait_device, $lock_device, $struct_device, ScsiDevice);
};
}
#[derive(Default)]
pub struct ScsiDevice {
pub base: DeviceBase,
pub dev_cfg: ScsiDevConfig,
pub drive_cfg: DriveConfig,
pub state: ScsiDevState,
pub block_backend: Option<Arc<Mutex<dyn BlockDriverOps<ScsiCompleteCb>>>>,
pub req_align: u32,
pub buf_align: u32,
pub disk_sectors: u64,
pub block_size: u32,
pub scsi_type: u32,
drive_files: Arc<Mutex<HashMap<String, DriveFile>>>,
pub aio: Option<Arc<Mutex<Aio<ScsiCompleteCb>>>>,
pub iothread: Option<String>,
}
unsafe impl Send for ScsiDevice {}
unsafe impl Sync for ScsiDevice {}
impl ScsiDevice {
pub fn new(
dev_cfg: ScsiDevConfig,
drive_cfg: DriveConfig,
drive_files: Arc<Mutex<HashMap<String, DriveFile>>>,
iothread: Option<String>,
scsi_bus: Arc<Mutex<ScsiBus>>,
) -> ScsiDevice {
let scsi_type = match dev_cfg.classtype.as_str() {
"scsi-hd" => SCSI_TYPE_DISK,
_ => SCSI_TYPE_ROM,
};
let mut scsi_dev = ScsiDevice {
base: DeviceBase::new(dev_cfg.id.clone(), false, None),
dev_cfg,
drive_cfg,
state: ScsiDevState::new(),
req_align: 1,
buf_align: 1,
scsi_type,
drive_files,
iothread,
..Default::default()
};
scsi_dev.set_parent_bus(scsi_bus);
scsi_dev
}
}
#[cfg(test)]
mod tests {
use super::*;
use machine_manager::config::str_slip_to_clap;
#[test]
fn test_scsi_device_cmdline_parser() {
let cmdline1 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0,serial=123456,bootindex=1";
let config =
ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline1, true, false)).unwrap();
assert_eq!(config.id, "scsi0-0-0-0");
assert_eq!(config.bus, "scsi0.0");
assert_eq!(config.target, 0);
assert_eq!(config.lun, 0);
assert_eq!(config.drive, "drive-0-0-0-0");
assert_eq!(config.serial.unwrap(), "123456");
assert_eq!(config.bootindex.unwrap(), 1);
let cmdline2 = "scsi-cd,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let config =
ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline2, true, false)).unwrap();
assert_eq!(config.channel, 0);
assert_eq!(config.serial, None);
assert_eq!(config.bootindex, None);
let cmdline3 = "scsi-hd,bus=scsi0.0,scsi-id=256,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false));
assert!(result.is_err());
let cmdline3 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=256,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false));
assert!(result.is_err());
let cmdline3 = "illegal,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline3, true, false));
assert!(result.is_err());
let cmdline4 = "scsi-hd,scsi-id=0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false));
assert!(result.is_err());
let cmdline4 = "scsi-hd,bus=scsi0.0,lun=0,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false));
assert!(result.is_err());
let cmdline4 = "scsi-hd,bus=scsi0.0,scsi-id=0,drive=drive-0-0-0-0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false));
assert!(result.is_err());
let cmdline4 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=0,id=scsi0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false));
assert!(result.is_err());
let cmdline4 = "scsi-hd,bus=scsi0.0,scsi-id=0,lun=0,drive=drive-0-0-0-0";
let result = ScsiDevConfig::try_parse_from(str_slip_to_clap(cmdline4, true, false));
assert!(result.is_err());
}
}