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");
|
.unwrap_or("profiles");
|
||||||
|
|
||||||
// Open SCSI transport
|
// Open SCSI transport
|
||||||
let mut transport = match libfreemkv::scsi::SgIoTransport::open(device) {
|
let mut transport = match libfreemkv::scsi::open(device) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error: Cannot open {}: {}", device.display(), e);
|
eprintln!("Error: Cannot open {}: {}", device.display(), e);
|
||||||
@@ -51,7 +51,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// INQUIRY
|
// INQUIRY
|
||||||
let inquiry = match libfreemkv::scsi::inquiry(&mut transport) {
|
let inquiry = match libfreemkv::scsi::inquiry(transport.as_mut()) {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error: INQUIRY failed: {}", e);
|
eprintln!("Error: INQUIRY failed: {}", e);
|
||||||
@@ -60,7 +60,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// GET CONFIGURATION feature 0x010C
|
// 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 {
|
if json_mode {
|
||||||
print_json(&inquiry, &gc_010c);
|
print_json(&inquiry, &gc_010c);
|
||||||
|
|||||||
+7
-7
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::scsi::{SgIoTransport, ScsiTransport};
|
use crate::scsi::ScsiTransport;
|
||||||
use crate::identity::DriveId;
|
use crate::identity::DriveId;
|
||||||
use crate::profile::{self, DriveProfile, Chipset};
|
use crate::profile::{self, DriveProfile, Chipset};
|
||||||
use crate::platform::{Platform, DriveStatus};
|
use crate::platform::{Platform, DriveStatus};
|
||||||
@@ -26,12 +26,12 @@ impl DriveSession {
|
|||||||
/// Open a drive, identify it, and find the matching profile.
|
/// Open a drive, identify it, and find the matching profile.
|
||||||
/// Uses the bundled profile database — no external files needed.
|
/// Uses the bundled profile database — no external files needed.
|
||||||
pub fn open(device: &Path) -> Result<Self> {
|
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()?;
|
let profiles = profile::load_bundled()?;
|
||||||
|
|
||||||
// Identify drive via standard SCSI commands
|
// Identify drive via standard SCSI commands
|
||||||
// SPC-4 §6.4 (INQUIRY) + MMC-6 §5.3.10 (Feature 010Ch)
|
// 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
|
// Match drive to a profile by INQUIRY fields
|
||||||
let profile = profile::find_by_drive_id(&profiles, &drive_id)
|
let profile = profile::find_by_drive_id(&profiles, &drive_id)
|
||||||
@@ -56,7 +56,7 @@ impl DriveSession {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(DriveSession {
|
Ok(DriveSession {
|
||||||
scsi: Box::new(transport),
|
scsi: transport,
|
||||||
platform,
|
platform,
|
||||||
profile,
|
profile,
|
||||||
drive_id,
|
drive_id,
|
||||||
@@ -65,8 +65,8 @@ impl DriveSession {
|
|||||||
|
|
||||||
/// Open with an explicit profile (skip auto-detection).
|
/// Open with an explicit profile (skip auto-detection).
|
||||||
pub fn open_with_profile(device: &Path, profile: DriveProfile) -> Result<Self> {
|
pub fn open_with_profile(device: &Path, profile: DriveProfile) -> Result<Self> {
|
||||||
let mut transport = SgIoTransport::open(device)?;
|
let mut transport = crate::scsi::open(device)?;
|
||||||
let drive_id = DriveId::from_drive(&mut transport)?;
|
let drive_id = DriveId::from_drive(transport.as_mut())?;
|
||||||
|
|
||||||
let platform: Box<dyn Platform> = match profile.chipset {
|
let platform: Box<dyn Platform> = match profile.chipset {
|
||||||
Chipset::MediaTek => {
|
Chipset::MediaTek => {
|
||||||
@@ -82,7 +82,7 @@ impl DriveSession {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(DriveSession {
|
Ok(DriveSession {
|
||||||
scsi: Box::new(transport),
|
scsi: transport,
|
||||||
platform,
|
platform,
|
||||||
profile,
|
profile,
|
||||||
drive_id,
|
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 crate::error::{Error, Result};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -17,7 +22,7 @@ pub struct ScsiResult {
|
|||||||
pub sense: [u8; 32],
|
pub sense: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Low-level SCSI transport.
|
/// Low-level SCSI transport — implemented per platform.
|
||||||
pub trait ScsiTransport {
|
pub trait ScsiTransport {
|
||||||
fn execute(
|
fn execute(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -28,17 +33,18 @@ pub trait ScsiTransport {
|
|||||||
) -> Result<ScsiResult>;
|
) -> Result<ScsiResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Linux SG_IO transport.
|
// ─── Linux: SG_IO ───────────────────────────────────────────────────────────
|
||||||
pub struct SgIoTransport {
|
|
||||||
fd: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SG_IO constants
|
#[cfg(target_os = "linux")]
|
||||||
const SG_IO: libc::c_ulong = 0x2285;
|
const SG_IO: libc::c_ulong = 0x2285;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
const SG_DXFER_NONE: i32 = -1;
|
const SG_DXFER_NONE: i32 = -1;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
const SG_DXFER_TO_DEV: i32 = -2;
|
const SG_DXFER_TO_DEV: i32 = -2;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
const SG_DXFER_FROM_DEV: i32 = -3;
|
const SG_DXFER_FROM_DEV: i32 = -3;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
struct sg_io_hdr {
|
struct sg_io_hdr {
|
||||||
@@ -66,6 +72,12 @@ struct sg_io_hdr {
|
|||||||
info: u32,
|
info: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub struct SgIoTransport {
|
||||||
|
fd: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
impl SgIoTransport {
|
impl SgIoTransport {
|
||||||
pub fn open(device: &Path) -> Result<Self> {
|
pub fn open(device: &Path) -> Result<Self> {
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
@@ -82,12 +94,14 @@ impl SgIoTransport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
impl Drop for SgIoTransport {
|
impl Drop for SgIoTransport {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { libc::close(self.fd); }
|
unsafe { libc::close(self.fd); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
impl ScsiTransport for SgIoTransport {
|
impl ScsiTransport for SgIoTransport {
|
||||||
fn execute(
|
fn execute(
|
||||||
&mut self,
|
&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.
|
/// SCSI INQUIRY response.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InquiryResult {
|
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>> {
|
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 cdb = [0x46, 0x02, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00];
|
||||||
let mut buf = [0u8; 16];
|
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())
|
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] {
|
pub fn build_read_buffer(mode: u8, buffer_id: u8, offset: u32, length: u32) -> [u8; 10] {
|
||||||
[
|
[
|
||||||
0x3C, // READ BUFFER
|
0x3C, mode, buffer_id,
|
||||||
mode,
|
(offset >> 16) as u8, (offset >> 8) as u8, offset as u8,
|
||||||
buffer_id,
|
(length >> 16) as u8, (length >> 8) as u8, length as u8,
|
||||||
(offset >> 16) as u8,
|
|
||||||
(offset >> 8) as u8,
|
|
||||||
offset as u8,
|
|
||||||
(length >> 16) as u8,
|
|
||||||
(length >> 8) as u8,
|
|
||||||
length as u8,
|
|
||||||
0x00,
|
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] {
|
pub fn build_set_cd_speed(read_speed: u16) -> [u8; 12] {
|
||||||
[
|
[
|
||||||
0xBB, 0x00,
|
0xBB, 0x00,
|
||||||
(read_speed >> 8) as u8, read_speed as u8,
|
(read_speed >> 8) as u8, read_speed as u8,
|
||||||
0xFF, 0xFF, // write speed = max
|
0xFF, 0xFF,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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] {
|
pub fn build_read10_raw(lba: u32, count: u16) -> [u8; 10] {
|
||||||
[
|
[
|
||||||
0x28, 0x08, // READ(10), flag=0x08 (raw)
|
0x28, 0x08,
|
||||||
(lba >> 24) as u8, (lba >> 16) as u8,
|
(lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, lba as u8,
|
||||||
(lba >> 8) as u8, lba as u8,
|
|
||||||
0x00,
|
0x00,
|
||||||
(count >> 8) as u8, count as u8,
|
(count >> 8) as u8, count as u8,
|
||||||
0x00,
|
0x00,
|
||||||
|
|||||||
Reference in New Issue
Block a user