Profiles v2: chipset+variant top-level keys, minimal per-drive data
profiles.json: { "mt1959_a": [...], "mt1959_b": [...], "renesas": [] }
Each profile: identity + signature + firmware (3 fields)
Platform enum replaces Chipset — section determines variant
This commit is contained in:
+1313
-1517
File diff suppressed because it is too large
Load Diff
+29
-70
@@ -1,9 +1,5 @@
|
|||||||
//! Drive session — open, identify, and read from optical drives.
|
//! Drive session — open, identify, and read from optical drives.
|
||||||
//!
|
//!
|
||||||
//! `DriveSession` is the entry point for all drive interaction. It handles
|
|
||||||
//! device identification, profile matching, and provides both raw sector
|
|
||||||
//! reads and standard SCSI command execution.
|
|
||||||
//!
|
|
||||||
//! Three-step open:
|
//! Three-step open:
|
||||||
//! 1. `open()` — open device, identify drive. Always OEM.
|
//! 1. `open()` — open device, identify drive. Always OEM.
|
||||||
//! 2. `wait_ready()` — wait for disc to spin up. Call before reading.
|
//! 2. `wait_ready()` — wait for disc to spin up. Call before reading.
|
||||||
@@ -13,28 +9,20 @@ use std::path::Path;
|
|||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::scsi::ScsiTransport;
|
use crate::scsi::ScsiTransport;
|
||||||
use crate::identity::DriveId;
|
use crate::identity::DriveId;
|
||||||
use crate::profile::{self, DriveProfile, Chipset, ProfileMatch};
|
use crate::profile::{self, DriveProfile, ProfileMatch};
|
||||||
use crate::platform::Platform;
|
use crate::platform::PlatformDriver;
|
||||||
use crate::platform::mt1959::Mt1959;
|
use crate::platform::mt1959::Mt1959;
|
||||||
|
|
||||||
/// A drive session with identification, platform, and SCSI transport.
|
|
||||||
///
|
|
||||||
/// Created via `DriveSession::open()`.
|
|
||||||
/// All disc reading goes through this struct.
|
|
||||||
pub struct DriveSession {
|
pub struct DriveSession {
|
||||||
scsi: Box<dyn ScsiTransport>,
|
scsi: Box<dyn ScsiTransport>,
|
||||||
platform: Box<dyn Platform>,
|
driver: Box<dyn PlatformDriver>,
|
||||||
pub profile: DriveProfile,
|
pub profile: DriveProfile,
|
||||||
pub chipset: Chipset,
|
pub platform: profile::Platform,
|
||||||
pub drive_id: DriveId,
|
pub drive_id: DriveId,
|
||||||
device_path: String,
|
device_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DriveSession {
|
impl DriveSession {
|
||||||
/// Open a drive — SCSI transport + INQUIRY identify.
|
|
||||||
///
|
|
||||||
/// Pure OEM. No disc needed, no custom firmware.
|
|
||||||
/// Call `wait_ready()` before reading, `init()` for custom firmware.
|
|
||||||
pub fn open(device: &Path) -> Result<Self> {
|
pub fn open(device: &Path) -> Result<Self> {
|
||||||
let mut transport = crate::scsi::open(device)?;
|
let mut transport = crate::scsi::open(device)?;
|
||||||
let profiles = profile::load_bundled()?;
|
let profiles = profile::load_bundled()?;
|
||||||
@@ -47,20 +35,18 @@ impl DriveSession {
|
|||||||
product_revision: drive_id.product_revision.trim().to_string(),
|
product_revision: drive_id.product_revision.trim().to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let platform = create_platform(m.chipset, &m.profile)?;
|
let driver = create_driver(m.platform, &m.profile)?;
|
||||||
|
|
||||||
Ok(DriveSession {
|
Ok(DriveSession {
|
||||||
scsi: transport,
|
scsi: transport,
|
||||||
platform,
|
driver,
|
||||||
chipset: m.chipset,
|
platform: m.platform,
|
||||||
profile: m.profile,
|
profile: m.profile,
|
||||||
drive_id,
|
drive_id,
|
||||||
device_path: device.to_string_lossy().to_string(),
|
device_path: device.to_string_lossy().to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the drive to become ready (disc spun up).
|
|
||||||
/// Polls TEST UNIT READY up to 30 seconds.
|
|
||||||
pub fn wait_ready(&mut self) -> Result<()> {
|
pub fn wait_ready(&mut self) -> Result<()> {
|
||||||
let tur = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
let tur = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||||
for _ in 0..60 {
|
for _ in 0..60 {
|
||||||
@@ -77,93 +63,74 @@ impl DriveSession {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Device path this session was opened on.
|
pub fn platform_name(&self) -> &str {
|
||||||
|
self.platform.name()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn device_path(&self) -> &str {
|
pub fn device_path(&self) -> &str {
|
||||||
&self.device_path
|
&self.device_path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Activate custom firmware — unlock, upload firmware if needed, calibrate.
|
|
||||||
///
|
|
||||||
/// Optional. BD/DVD work without this (OEM, standard speed).
|
|
||||||
/// Required for UHD (AACS 2.0 bus encryption).
|
|
||||||
pub fn init(&mut self) -> Result<()> {
|
pub fn init(&mut self) -> Result<()> {
|
||||||
self.platform.init(self.scsi.as_mut())
|
self.driver.init(self.scsi.as_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if drive is initialized and ready for reads.
|
|
||||||
pub fn is_ready(&self) -> bool {
|
pub fn is_ready(&self) -> bool {
|
||||||
self.platform.is_ready()
|
self.driver.is_ready()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set read speed for a disc zone.
|
|
||||||
pub fn set_read_speed(&mut self, lba: u32) -> Result<()> {
|
pub fn set_read_speed(&mut self, lba: u32) -> Result<()> {
|
||||||
self.platform.set_read_speed(self.scsi.as_mut(), lba)
|
self.driver.set_read_speed(self.scsi.as_mut(), lba)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SCSI READ(10) for disc filesystem data (UDF, MPLS, CLPI).
|
|
||||||
pub fn read_disc(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
|
pub fn read_disc(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
|
||||||
let cdb = [
|
let cdb = [
|
||||||
crate::scsi::SCSI_READ_10, 0x00,
|
crate::scsi::SCSI_READ_10, 0x00,
|
||||||
(lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, lba as u8,
|
(lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, lba as u8,
|
||||||
0x00,
|
0x00, (count >> 8) as u8, count as u8, 0x00,
|
||||||
(count >> 8) as u8, count as u8,
|
|
||||||
0x00,
|
|
||||||
];
|
];
|
||||||
let result = self.scsi.as_mut().execute(
|
let result = self.scsi.as_mut().execute(
|
||||||
&cdb, crate::scsi::DataDirection::FromDevice, buf, 5_000)?;
|
&cdb, crate::scsi::DataDirection::FromDevice, buf, 5_000)?;
|
||||||
Ok(result.bytes_transferred)
|
Ok(result.bytes_transferred)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SCSI READ(10) for m2ts content — bulk reads with longer timeout.
|
|
||||||
pub fn read_content(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
|
pub fn read_content(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
|
||||||
let cdb = [
|
let cdb = [
|
||||||
crate::scsi::SCSI_READ_10, 0x00,
|
crate::scsi::SCSI_READ_10, 0x00,
|
||||||
(lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, lba as u8,
|
(lba >> 24) as u8, (lba >> 16) as u8, (lba >> 8) as u8, lba as u8,
|
||||||
0x00,
|
0x00, (count >> 8) as u8, count as u8, 0x00,
|
||||||
(count >> 8) as u8, count as u8,
|
|
||||||
0x00,
|
|
||||||
];
|
];
|
||||||
let result = self.scsi.as_mut().execute(
|
let result = self.scsi.as_mut().execute(
|
||||||
&cdb, crate::scsi::DataDirection::FromDevice, buf, 30_000)?;
|
&cdb, crate::scsi::DataDirection::FromDevice, buf, 30_000)?;
|
||||||
Ok(result.bytes_transferred)
|
Ok(result.bytes_transferred)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Eject the disc tray.
|
|
||||||
pub fn eject(&mut self) -> Result<()> {
|
pub fn eject(&mut self) -> Result<()> {
|
||||||
let allow_cdb = [0x1Eu8, 0, 0, 0, 0x00, 0];
|
let allow_cdb = [0x1Eu8, 0, 0, 0, 0x00, 0];
|
||||||
let mut buf = [0u8; 0];
|
let mut buf = [0u8; 0];
|
||||||
let _ = self.scsi.as_mut().execute(&allow_cdb, crate::scsi::DataDirection::None, &mut buf, 5_000);
|
let _ = self.scsi.as_mut().execute(&allow_cdb, crate::scsi::DataDirection::None, &mut buf, 5_000);
|
||||||
|
|
||||||
let eject_cdb = [0x1Bu8, 0, 0, 0, 0x02, 0];
|
let eject_cdb = [0x1Bu8, 0, 0, 0, 0x02, 0];
|
||||||
self.scsi.as_mut().execute(&eject_cdb, crate::scsi::DataDirection::None, &mut buf, 30_000)?;
|
self.scsi.as_mut().execute(&eject_cdb, crate::scsi::DataDirection::None, &mut buf, 30_000)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a raw SCSI CDB. Used by parsers and AACS handshake.
|
|
||||||
pub fn scsi_execute(
|
pub fn scsi_execute(
|
||||||
&mut self,
|
&mut self, cdb: &[u8], direction: crate::scsi::DataDirection,
|
||||||
cdb: &[u8],
|
buf: &mut [u8], timeout_ms: u32,
|
||||||
direction: crate::scsi::DataDirection,
|
|
||||||
buf: &mut [u8],
|
|
||||||
timeout_ms: u32,
|
|
||||||
) -> Result<crate::scsi::ScsiResult> {
|
) -> Result<crate::scsi::ScsiResult> {
|
||||||
self.scsi.as_mut().execute(cdb, direction, buf, timeout_ms)
|
self.scsi.as_mut().execute(cdb, direction, buf, timeout_ms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discover optical drives on the system.
|
|
||||||
pub fn find_drives() -> Vec<(String, DriveId)> {
|
pub fn find_drives() -> Vec<(String, DriveId)> {
|
||||||
let mut drives = Vec::new();
|
let mut drives = Vec::new();
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
let path = format!("/dev/sg{}", i);
|
let path = format!("/dev/sg{}", i);
|
||||||
if !std::path::Path::new(&path).exists() {
|
if !std::path::Path::new(&path).exists() { continue; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Ok(mut transport) = crate::scsi::open(std::path::Path::new(&path)) {
|
if let Ok(mut transport) = crate::scsi::open(std::path::Path::new(&path)) {
|
||||||
if let Ok(id) = DriveId::from_drive(transport.as_mut()) {
|
if let Ok(id) = DriveId::from_drive(transport.as_mut()) {
|
||||||
let profiles = match profile::load_bundled() {
|
let profiles = match profile::load_bundled() {
|
||||||
Ok(p) => p,
|
Ok(p) => p, Err(_) => continue,
|
||||||
Err(_) => continue,
|
|
||||||
};
|
};
|
||||||
if profile::find_by_drive_id(&profiles, &id).is_some() {
|
if profile::find_by_drive_id(&profiles, &id).is_some() {
|
||||||
drives.push((path, id));
|
drives.push((path, id));
|
||||||
@@ -174,12 +141,10 @@ pub fn find_drives() -> Vec<(String, DriveId)> {
|
|||||||
drives
|
drives
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the first optical drive on the system.
|
|
||||||
pub fn find_drive() -> Option<String> {
|
pub fn find_drive() -> Option<String> {
|
||||||
find_drives().into_iter().next().map(|(path, _)| path)
|
find_drives().into_iter().next().map(|(path, _)| path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a device path to the correct sg device.
|
|
||||||
pub fn resolve_device(path: &str) -> Result<(String, Option<String>)> {
|
pub fn resolve_device(path: &str) -> Result<(String, Option<String>)> {
|
||||||
if path.contains("/sg") {
|
if path.contains("/sg") {
|
||||||
if !std::path::Path::new(path).exists() {
|
if !std::path::Path::new(path).exists() {
|
||||||
@@ -187,42 +152,36 @@ pub fn resolve_device(path: &str) -> Result<(String, Option<String>)> {
|
|||||||
}
|
}
|
||||||
return Ok((path.to_string(), None));
|
return Ok((path.to_string(), None));
|
||||||
}
|
}
|
||||||
|
|
||||||
if path.contains("/sr") {
|
if path.contains("/sr") {
|
||||||
let mut sr_transport = crate::scsi::open(std::path::Path::new(path))?;
|
let mut sr_transport = crate::scsi::open(std::path::Path::new(path))?;
|
||||||
let sr_id = DriveId::from_drive(sr_transport.as_mut())?;
|
let sr_id = DriveId::from_drive(sr_transport.as_mut())?;
|
||||||
drop(sr_transport);
|
drop(sr_transport);
|
||||||
|
|
||||||
for (sg_path, sg_id) in find_drives() {
|
for (sg_path, sg_id) in find_drives() {
|
||||||
if sg_id.vendor_id == sr_id.vendor_id
|
if sg_id.vendor_id == sr_id.vendor_id
|
||||||
&& sg_id.product_id == sr_id.product_id
|
&& sg_id.product_id == sr_id.product_id
|
||||||
&& sg_id.serial_number == sr_id.serial_number
|
&& sg_id.serial_number == sr_id.serial_number
|
||||||
{
|
{
|
||||||
let warning = format!(
|
let warning = format!(
|
||||||
"{} is a block device (sr) — using {} (sg) for raw access",
|
"{} is a block device (sr) — using {} (sg) for raw access", path, sg_path
|
||||||
path, sg_path
|
|
||||||
);
|
);
|
||||||
return Ok((sg_path, Some(warning)));
|
return Ok((sg_path, Some(warning)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Ok((path.to_string(), Some(format!(
|
||||||
let warning = format!(
|
"{} is a block device (sr) — no matching sg device found", path
|
||||||
"{} is a block device (sr) — no matching sg device found, performance may be limited",
|
))));
|
||||||
path
|
|
||||||
);
|
|
||||||
return Ok((path.to_string(), Some(warning)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !std::path::Path::new(path).exists() {
|
if !std::path::Path::new(path).exists() {
|
||||||
return Err(Error::DeviceNotFound { path: path.to_string() });
|
return Err(Error::DeviceNotFound { path: path.to_string() });
|
||||||
}
|
}
|
||||||
Ok((path.to_string(), None))
|
Ok((path.to_string(), None))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_platform(chipset: Chipset, profile: &DriveProfile) -> Result<Box<dyn Platform>> {
|
fn create_driver(platform: profile::Platform, profile: &DriveProfile) -> Result<Box<dyn PlatformDriver>> {
|
||||||
match chipset {
|
match platform {
|
||||||
Chipset::MediaTek => Ok(Box::new(Mt1959::new(profile.clone()))),
|
profile::Platform::Mt1959A => Ok(Box::new(Mt1959::new(profile.clone(), false))),
|
||||||
Chipset::Renesas => Err(Error::UnsupportedDrive {
|
profile::Platform::Mt1959B => Ok(Box::new(Mt1959::new(profile.clone(), true))),
|
||||||
|
profile::Platform::Renesas => Err(Error::UnsupportedDrive {
|
||||||
vendor_id: profile.identity.vendor_id.trim().to_string(),
|
vendor_id: profile.identity.vendor_id.trim().to_string(),
|
||||||
product_id: String::new(),
|
product_id: String::new(),
|
||||||
product_revision: "Renesas not yet implemented".to_string(),
|
product_revision: "Renesas not yet implemented".to_string(),
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ pub mod keydb;
|
|||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use drive::{DriveSession, find_drive, find_drives, resolve_device};
|
pub use drive::{DriveSession, find_drive, find_drives, resolve_device};
|
||||||
pub use identity::DriveId;
|
pub use identity::DriveId;
|
||||||
pub use profile::{DriveProfile, Chipset};
|
pub use profile::DriveProfile;
|
||||||
// Platform trait is pub(crate) — callers use DriveSession, not Platform directly
|
// Platform trait is pub(crate) — callers use DriveSession, not Platform directly
|
||||||
pub use scsi::ScsiTransport;
|
pub use scsi::ScsiTransport;
|
||||||
pub use speed::DriveSpeed;
|
pub use speed::DriveSpeed;
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ use crate::scsi::ScsiTransport;
|
|||||||
/// init() — one-time initialization
|
/// init() — one-time initialization
|
||||||
/// set_read_speed() — per-zone speed during reads
|
/// set_read_speed() — per-zone speed during reads
|
||||||
/// is_ready() — state check
|
/// is_ready() — state check
|
||||||
pub(crate) trait Platform {
|
pub(crate) trait PlatformDriver {
|
||||||
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>;
|
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()>;
|
||||||
fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()>;
|
fn set_read_speed(&mut self, scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()>;
|
||||||
fn is_ready(&self) -> bool;
|
fn is_ready(&self) -> bool;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::profile::DriveProfile;
|
use crate::profile::DriveProfile;
|
||||||
use crate::scsi::{self, DataDirection, ScsiTransport};
|
use crate::scsi::{self, DataDirection, ScsiTransport};
|
||||||
use super::Platform;
|
use super::PlatformDriver;
|
||||||
|
|
||||||
const UNLOCK_RESPONSE_SIZE: u8 = 64;
|
const UNLOCK_RESPONSE_SIZE: u8 = 64;
|
||||||
|
|
||||||
@@ -29,10 +29,11 @@ pub struct Mt1959 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Mt1959 {
|
impl Mt1959 {
|
||||||
pub fn new(profile: DriveProfile) -> Self {
|
pub fn new(profile: DriveProfile, is_variant_b: bool) -> Self {
|
||||||
let (mode, buffer_id) = match profile.variant.as_str() {
|
let (mode, buffer_id) = if is_variant_b {
|
||||||
"b" => (MODE_B, BUFFER_ID_B),
|
(MODE_B, BUFFER_ID_B)
|
||||||
_ => (MODE_A, BUFFER_ID_A),
|
} else {
|
||||||
|
(MODE_A, BUFFER_ID_A)
|
||||||
};
|
};
|
||||||
Mt1959 {
|
Mt1959 {
|
||||||
profile,
|
profile,
|
||||||
@@ -125,7 +126,7 @@ impl Mt1959 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for Mt1959 {
|
impl PlatformDriver for Mt1959 {
|
||||||
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
fn init(&mut self, scsi: &mut dyn ScsiTransport) -> Result<()> {
|
||||||
if self.unlocked && self.calibrated {
|
if self.unlocked && self.calibrated {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
+23
-34
@@ -3,11 +3,13 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
/// Top-level profiles file — keyed by chipset.
|
/// Top-level profiles file — keyed by chipset + variant.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ProfilesFile {
|
pub struct ProfilesFile {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub mt1959: Vec<DriveProfile>,
|
pub mt1959_a: Vec<DriveProfile>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub mt1959_b: Vec<DriveProfile>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub renesas: Vec<DriveProfile>,
|
pub renesas: Vec<DriveProfile>,
|
||||||
}
|
}
|
||||||
@@ -29,51 +31,37 @@ pub struct Identity {
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct DriveProfile {
|
pub struct DriveProfile {
|
||||||
pub identity: Identity,
|
pub identity: Identity,
|
||||||
#[serde(default)]
|
|
||||||
pub variant: String,
|
|
||||||
#[serde(default, deserialize_with = "deserialize_hex4")]
|
#[serde(default, deserialize_with = "deserialize_hex4")]
|
||||||
pub signature: [u8; 4],
|
pub signature: [u8; 4],
|
||||||
#[serde(default, deserialize_with = "deserialize_base64")]
|
#[serde(default, deserialize_with = "deserialize_base64")]
|
||||||
pub firmware: Vec<u8>,
|
pub firmware: Vec<u8>,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub dvd_all_regions: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub bd_raw_read: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub bd_raw_metadata: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub unrestricted_speed: bool,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub drive_id: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub drive_version: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chipset — determined by which section the profile was found in.
|
/// Chipset + variant — determined by which section the profile was found in.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Chipset {
|
pub enum Platform {
|
||||||
MediaTek,
|
Mt1959A,
|
||||||
|
Mt1959B,
|
||||||
Renesas,
|
Renesas,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chipset {
|
impl Platform {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Chipset::MediaTek => "MediaTek MT1959",
|
Platform::Mt1959A => "MediaTek MT1959",
|
||||||
Chipset::Renesas => "Renesas",
|
Platform::Mt1959B => "MediaTek MT1959",
|
||||||
|
Platform::Renesas => "Renesas",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of profile lookup — includes chipset from the section it was found in.
|
/// Result of profile lookup.
|
||||||
pub struct ProfileMatch {
|
pub struct ProfileMatch {
|
||||||
pub profile: DriveProfile,
|
pub profile: DriveProfile,
|
||||||
pub chipset: Chipset,
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Hex/base64 parsing ─────────────────────────────────────────────────
|
// ── Parsing ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn parse_hex4(s: &str) -> Result<[u8; 4]> {
|
fn parse_hex4(s: &str) -> Result<[u8; 4]> {
|
||||||
if s.len() != 8 {
|
if s.len() != 8 {
|
||||||
@@ -117,7 +105,7 @@ fn load_from_str(data: &str) -> Result<ProfilesFile> {
|
|||||||
.map_err(|e| Error::ProfileParse { detail: format!("JSON: {e}") })
|
.map_err(|e| Error::ProfileParse { detail: format!("JSON: {e}") })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a profile matching a drive's INQUIRY fields. Returns profile + chipset.
|
/// Find a profile matching a drive's INQUIRY fields.
|
||||||
pub fn find_by_drive_id(
|
pub fn find_by_drive_id(
|
||||||
profiles: &ProfilesFile,
|
profiles: &ProfilesFile,
|
||||||
drive_id: &crate::identity::DriveId,
|
drive_id: &crate::identity::DriveId,
|
||||||
@@ -127,9 +115,10 @@ pub fn find_by_drive_id(
|
|||||||
let vs = drive_id.vendor_specific.trim();
|
let vs = drive_id.vendor_specific.trim();
|
||||||
let date = drive_id.firmware_date.trim();
|
let date = drive_id.firmware_date.trim();
|
||||||
|
|
||||||
for (chipset, list) in [
|
for (platform, list) in [
|
||||||
(Chipset::MediaTek, &profiles.mt1959),
|
(Platform::Mt1959A, &profiles.mt1959_a),
|
||||||
(Chipset::Renesas, &profiles.renesas),
|
(Platform::Mt1959B, &profiles.mt1959_b),
|
||||||
|
(Platform::Renesas, &profiles.renesas),
|
||||||
] {
|
] {
|
||||||
if let Some(p) = list.iter().find(|p| {
|
if let Some(p) = list.iter().find(|p| {
|
||||||
p.identity.vendor_id.trim() == v
|
p.identity.vendor_id.trim() == v
|
||||||
@@ -137,7 +126,7 @@ pub fn find_by_drive_id(
|
|||||||
&& p.identity.vendor_specific.trim() == vs
|
&& p.identity.vendor_specific.trim() == vs
|
||||||
&& p.identity.firmware_date.trim() == date
|
&& p.identity.firmware_date.trim() == date
|
||||||
}) {
|
}) {
|
||||||
return Some(ProfileMatch { profile: p.clone(), chipset });
|
return Some(ProfileMatch { profile: p.clone(), platform });
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(p) = list.iter().find(|p| {
|
if let Some(p) = list.iter().find(|p| {
|
||||||
@@ -145,7 +134,7 @@ pub fn find_by_drive_id(
|
|||||||
&& p.identity.product_revision.trim() == r
|
&& p.identity.product_revision.trim() == r
|
||||||
&& p.identity.vendor_specific.trim() == vs
|
&& p.identity.vendor_specific.trim() == vs
|
||||||
}) {
|
}) {
|
||||||
return Some(ProfileMatch { profile: p.clone(), chipset });
|
return Some(ProfileMatch { profile: p.clone(), platform });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +160,7 @@ mod tests {
|
|||||||
let id = make_drive_id("HL-DT-ST", "1.03", "NM00000", "211810241934");
|
let id = make_drive_id("HL-DT-ST", "1.03", "NM00000", "211810241934");
|
||||||
let m = find_by_drive_id(&profiles, &id).unwrap();
|
let m = find_by_drive_id(&profiles, &id).unwrap();
|
||||||
assert_eq!(m.profile.identity.vendor_id.trim(), "HL-DT-ST");
|
assert_eq!(m.profile.identity.vendor_id.trim(), "HL-DT-ST");
|
||||||
assert_eq!(m.chipset, Chipset::MediaTek);
|
assert_eq!(m.platform, Platform::Mt1959A);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user