diff --git a/src/disc/encrypt.rs b/src/disc/encrypt.rs
index ab8dd4d..fda3fe6 100644
--- a/src/disc/encrypt.rs
+++ b/src/disc/encrypt.rs
@@ -76,7 +76,7 @@ impl Disc {
// DriveId first releases the immutable borrow before we hand the
// mutable transport to the registry.
let drive_id = session.drive_id.clone();
- match crate::unlock::unlocker_read_vid(session.scsi_mut(), &drive_id) {
+ match crate::unlock::unlocker_read_volume_id(session.scsi_mut(), &drive_id) {
Ok(Some(volume_id)) => {
tracing::debug!(
target: "freemkv::disc",
diff --git a/src/drive/mod.rs b/src/drive/mod.rs
index fb29613..96a6be7 100644
--- a/src/drive/mod.rs
+++ b/src/drive/mod.rs
@@ -399,6 +399,19 @@ impl Drive {
let r = match r {
Ok(Some(name)) => {
self.unlocker_name = Some(name);
+ // The matched unlocker may also be able to raise the drive to
+ // its maximum read speed. Best-effort: a failure here must NOT
+ // fail the rip — a slow drive still rips. Log and continue.
+ if let Err(e) =
+ crate::unlock::unlocker_set_max_read_speed(self.scsi.as_mut(), &self.drive_id)
+ {
+ tracing::warn!(
+ target: "freemkv::drive",
+ phase = "init",
+ error = ?e,
+ "unlocker set_max_read_speed failed; continuing at current speed"
+ );
+ }
Ok(())
}
// No unlocker matched: not an error — fall through to OEM route.
diff --git a/src/unlock.rs b/src/unlock.rs
index 098bcc5..7d25e12 100644
--- a/src/unlock.rs
+++ b/src/unlock.rs
@@ -4,8 +4,8 @@
//! supplied by an external crate (e.g. `freemkv-unlock-ld`) and registered
//! once at process start via [`register_unlocker`]. At drive-prep the
//! registry is walked in registration order; the first unlocker whose
-//! [`Unlocker::matches`] returns true is asked to [`Unlocker::unlock`] the
-//! drive by issuing its own CDBs through the raw [`ScsiTransport`].
+//! [`Unlocker::matches`] returns true is asked to [`Unlocker::unlock_drive`]
+//! the drive by issuing its own CDBs through the raw [`ScsiTransport`].
//!
//! No firmware blobs, no unlock CDBs, no drive profiles live here — only
//! the trait, the registry, and the routing. If no unlocker matches, the
@@ -17,10 +17,16 @@ use crate::identity::DriveId;
use crate::scsi::ScsiTransport;
use std::sync::RwLock;
-/// A pluggable drive unlocker.
+/// A pluggable drive-capability provider.
+///
+/// Unlockers are optional drive-capability providers. libfreemkv's AACS
+/// layer is the always-present baseline; it uses an unlocker's capabilities
+/// when one matches, and does the full cert handshake when none do.
+/// Implement only the capabilities your drive supports — the rest default
+/// to no-op.
///
/// Implementors own everything about *how* a particular drive family is
-/// unlocked: firmware upload, vendor CDBs, variant logic. libfreemkv only
+/// driven: firmware upload, vendor CDBs, variant logic. libfreemkv only
/// hands over the raw SCSI transport and the drive identity.
pub trait Unlocker: Send + Sync {
/// Stable, language-neutral identifier for this unlocker (logged).
@@ -29,27 +35,24 @@ pub trait Unlocker: Send + Sync {
/// True if this unlocker handles the given drive.
fn matches(&self, id: &DriveId) -> bool;
- /// Unlock the drive. The unlocker issues its own CDBs through `scsi`.
- /// Returns `Ok(())` once the drive is prepared for reads.
- fn unlock(&self, scsi: &mut dyn ScsiTransport, id: &DriveId) -> Result<()>;
+ /// Put the drive into extended-access mode (firmware/bootloader/whatever THIS
+ /// unlocker needs). The one required capability.
+ fn unlock_drive(&self, scsi: &mut dyn ScsiTransport, id: &DriveId) -> Result<()>;
- /// Read the AACS Volume ID via this unlocker's OEM mechanism, if it
- /// has one.
- ///
- /// An [`Unlocker`] unlocks *drive functionality*, not just the disc:
- /// `unlock` is one capability, OEM VID retrieval is another. Once the
- /// matching unlocker is identified for a drive, libfreemkv uses it for
- /// BOTH unlock and VID. The OEM path returns the VID *without* the host
- /// certificate + HRL, decoupling VID from the cert handshake.
- ///
- /// Default is a no-op: an unlocker that provides no OEM VID path (or
- /// any unlocker that doesn't override this) returns `Ok(None)`, and
- /// libfreemkv falls back to the cert-based VID read. Implementors that
- /// can serve the VID directly (e.g. a per-drive OEM CDB) return
- /// `Ok(Some(vid))`.
- fn read_vid(&self, _scsi: &mut dyn ScsiTransport, _id: &DriveId) -> Result