Platform-agnostic SCSI transport with cfg gates
- scsi::open() returns Box<dyn ScsiTransport> on any platform - Linux SG_IO behind #[cfg(target_os = "linux")] - macOS IOKit and Windows SPTI stubs with TODO - drive.rs uses scsi::open() — no platform-specific imports - Compiles on macOS (returns "platform not yet supported")
This commit is contained in:
@@ -42,7 +42,7 @@ fn main() {
|
||||
.unwrap_or("profiles");
|
||||
|
||||
// Open SCSI transport
|
||||
let mut transport = match libfreemkv::scsi::SgIoTransport::open(device) {
|
||||
let mut transport = match libfreemkv::scsi::open(device) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Cannot open {}: {}", device.display(), e);
|
||||
@@ -51,7 +51,7 @@ fn main() {
|
||||
};
|
||||
|
||||
// INQUIRY
|
||||
let inquiry = match libfreemkv::scsi::inquiry(&mut transport) {
|
||||
let inquiry = match libfreemkv::scsi::inquiry(transport.as_mut()) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
eprintln!("Error: INQUIRY failed: {}", e);
|
||||
@@ -60,7 +60,7 @@ fn main() {
|
||||
};
|
||||
|
||||
// GET CONFIGURATION feature 0x010C
|
||||
let gc_010c = libfreemkv::scsi::get_config_010c(&mut transport).ok();
|
||||
let gc_010c = libfreemkv::scsi::get_config_010c(transport.as_mut()).ok();
|
||||
|
||||
if json_mode {
|
||||
print_json(&inquiry, &gc_010c);
|
||||
|
||||
+7
-7
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::path::Path;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::scsi::{SgIoTransport, ScsiTransport};
|
||||
use crate::scsi::ScsiTransport;
|
||||
use crate::identity::DriveId;
|
||||
use crate::profile::{self, DriveProfile, Chipset};
|
||||
use crate::platform::{Platform, DriveStatus};
|
||||
@@ -26,12 +26,12 @@ impl DriveSession {
|
||||
/// Open a drive, identify it, and find the matching profile.
|
||||
/// Uses the bundled profile database — no external files needed.
|
||||
pub fn open(device: &Path) -> Result<Self> {
|
||||
let mut transport = SgIoTransport::open(device)?;
|
||||
let mut transport = crate::scsi::open(device)?;
|
||||
let profiles = profile::load_bundled()?;
|
||||
|
||||
// Identify drive via standard SCSI commands
|
||||
// SPC-4 §6.4 (INQUIRY) + MMC-6 §5.3.10 (Feature 010Ch)
|
||||
let drive_id = DriveId::from_drive(&mut transport)?;
|
||||
let drive_id = DriveId::from_drive(transport.as_mut())?;
|
||||
|
||||
// Match drive to a profile by INQUIRY fields
|
||||
let profile = profile::find_by_drive_id(&profiles, &drive_id)
|
||||
@@ -56,7 +56,7 @@ impl DriveSession {
|
||||
};
|
||||
|
||||
Ok(DriveSession {
|
||||
scsi: Box::new(transport),
|
||||
scsi: transport,
|
||||
platform,
|
||||
profile,
|
||||
drive_id,
|
||||
@@ -65,8 +65,8 @@ impl DriveSession {
|
||||
|
||||
/// Open with an explicit profile (skip auto-detection).
|
||||
pub fn open_with_profile(device: &Path, profile: DriveProfile) -> Result<Self> {
|
||||
let mut transport = SgIoTransport::open(device)?;
|
||||
let drive_id = DriveId::from_drive(&mut transport)?;
|
||||
let mut transport = crate::scsi::open(device)?;
|
||||
let drive_id = DriveId::from_drive(transport.as_mut())?;
|
||||
|
||||
let platform: Box<dyn Platform> = match profile.chipset {
|
||||
Chipset::MediaTek => {
|
||||
@@ -82,7 +82,7 @@ impl DriveSession {
|
||||
};
|
||||
|
||||
Ok(DriveSession {
|
||||
scsi: Box::new(transport),
|
||||
scsi: transport,
|
||||
platform,
|
||||
profile,
|
||||
drive_id,
|
||||
|
||||
+56
-24
@@ -1,4 +1,9 @@
|
||||
//! SCSI/MMC command interface via Linux SG_IO.
|
||||
//! SCSI/MMC command interface.
|
||||
//!
|
||||
//! Platform backends:
|
||||
//! - Linux: SG_IO ioctl
|
||||
//! - macOS: IOKit SCSI passthrough (planned)
|
||||
//! - Windows: SPTI (planned)
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use std::path::Path;
|
||||
@@ -17,7 +22,7 @@ pub struct ScsiResult {
|
||||
pub sense: [u8; 32],
|
||||
}
|
||||
|
||||
/// Low-level SCSI transport.
|
||||
/// Low-level SCSI transport — implemented per platform.
|
||||
pub trait ScsiTransport {
|
||||
fn execute(
|
||||
&mut self,
|
||||
@@ -28,17 +33,18 @@ pub trait ScsiTransport {
|
||||
) -> Result<ScsiResult>;
|
||||
}
|
||||
|
||||
/// Linux SG_IO transport.
|
||||
pub struct SgIoTransport {
|
||||
fd: i32,
|
||||
}
|
||||
// ─── Linux: SG_IO ───────────────────────────────────────────────────────────
|
||||
|
||||
// SG_IO constants
|
||||
#[cfg(target_os = "linux")]
|
||||
const SG_IO: libc::c_ulong = 0x2285;
|
||||
#[cfg(target_os = "linux")]
|
||||
const SG_DXFER_NONE: i32 = -1;
|
||||
#[cfg(target_os = "linux")]
|
||||
const SG_DXFER_TO_DEV: i32 = -2;
|
||||
#[cfg(target_os = "linux")]
|
||||
const SG_DXFER_FROM_DEV: i32 = -3;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct sg_io_hdr {
|
||||
@@ -66,6 +72,12 @@ struct sg_io_hdr {
|
||||
info: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub struct SgIoTransport {
|
||||
fd: i32,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl SgIoTransport {
|
||||
pub fn open(device: &Path) -> Result<Self> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
@@ -82,12 +94,14 @@ impl SgIoTransport {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Drop for SgIoTransport {
|
||||
fn drop(&mut self) {
|
||||
unsafe { libc::close(self.fd); }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl ScsiTransport for SgIoTransport {
|
||||
fn execute(
|
||||
&mut self,
|
||||
@@ -142,6 +156,31 @@ impl ScsiTransport for SgIoTransport {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── macOS: IOKit (planned) ─────────────────────────────────────────────────
|
||||
|
||||
// TODO: IOKit MMC SCSI passthrough
|
||||
// Use IOSCSIPeripheralDeviceType05 (MMC device nub)
|
||||
// Send SCSITaskInterface commands via IOKit user client
|
||||
|
||||
// ─── Windows: SPTI (planned) ────────────────────────────────────────────────
|
||||
|
||||
// TODO: SCSI Pass Through Interface
|
||||
// Use CreateFile on \\.\CdRomN
|
||||
// Send IOCTL_SCSI_PASS_THROUGH_DIRECT
|
||||
|
||||
// ─── Platform-agnostic open ─────────────────────────────────────────────────
|
||||
|
||||
/// Open a SCSI transport for the given device path.
|
||||
pub fn open(device: &Path) -> Result<Box<dyn ScsiTransport>> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{ Ok(Box::new(SgIoTransport::open(device)?)) }
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{ Err(Error::DeviceNotFound { path: format!("{}: platform not yet supported (Linux only)", device.display()) }) }
|
||||
}
|
||||
|
||||
// ─── CDB builders (platform-agnostic) ───────────────────────────────────────
|
||||
|
||||
/// SCSI INQUIRY response.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InquiryResult {
|
||||
@@ -169,7 +208,7 @@ pub fn inquiry(scsi: &mut dyn ScsiTransport) -> Result<InquiryResult> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Send GET CONFIGURATION for feature 0x010C (drive serial number).
|
||||
/// Send GET CONFIGURATION for feature 0x010C (Firmware Information).
|
||||
pub fn get_config_010c(scsi: &mut dyn ScsiTransport) -> Result<Vec<u8>> {
|
||||
let cdb = [0x46, 0x02, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||
let mut buf = [0u8; 16];
|
||||
@@ -177,38 +216,31 @@ pub fn get_config_010c(scsi: &mut dyn ScsiTransport) -> Result<Vec<u8>> {
|
||||
Ok(buf.to_vec())
|
||||
}
|
||||
|
||||
/// Build a READ BUFFER (0x3C) CDB with the given mode, buffer ID, offset, and length.
|
||||
/// Build a READ BUFFER (0x3C) CDB.
|
||||
pub fn build_read_buffer(mode: u8, buffer_id: u8, offset: u32, length: u32) -> [u8; 10] {
|
||||
[
|
||||
0x3C, // READ BUFFER
|
||||
mode,
|
||||
buffer_id,
|
||||
(offset >> 16) as u8,
|
||||
(offset >> 8) as u8,
|
||||
offset as u8,
|
||||
(length >> 16) as u8,
|
||||
(length >> 8) as u8,
|
||||
length as u8,
|
||||
0x3C, mode, buffer_id,
|
||||
(offset >> 16) as u8, (offset >> 8) as u8, offset as u8,
|
||||
(length >> 16) as u8, (length >> 8) as u8, length as u8,
|
||||
0x00,
|
||||
]
|
||||
}
|
||||
|
||||
/// Build a SET CD SPEED (0xBB) CDB with the given read speed.
|
||||
/// Build a SET CD SPEED (0xBB) CDB.
|
||||
pub fn build_set_cd_speed(read_speed: u16) -> [u8; 12] {
|
||||
[
|
||||
0xBB, 0x00,
|
||||
(read_speed >> 8) as u8, read_speed as u8,
|
||||
0xFF, 0xFF, // write speed = max
|
||||
0xFF, 0xFF,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
]
|
||||
}
|
||||
|
||||
/// Build a READ(10) CDB with the raw read flag (0x08) set.
|
||||
/// Build a READ(10) CDB with the raw read flag (0x08).
|
||||
pub fn build_read10_raw(lba: u32, count: u16) -> [u8; 10] {
|
||||
[
|
||||
0x28, 0x08, // READ(10), flag=0x08 (raw)
|
||||
(lba >> 24) as u8, (lba >> 16) as u8,
|
||||
(lba >> 8) as u8, lba as u8,
|
||||
0x28, 0x08,
|
||||
(lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, lba as u8,
|
||||
0x00,
|
||||
(count >> 8) as u8, count as u8,
|
||||
0x00,
|
||||
|
||||
Reference in New Issue
Block a user