8.0 KiB
Drive Access and Unlock
Technical reference for how libfreemkv opens, identifies, unlocks, and reads optical drives.
Drive
Drive is the primary API. It owns the SCSI transport, the matched
drive profile, and the chipset-specific platform driver.
Opening a Drive
let mut drive = Drive::open(Path::new("/dev/sg4"))?;
open() performs: open device → send INQUIRY → match profile → instantiate
platform driver. The drive is ready for wait_ready() and init().
Drive Operations
| Method | Description |
|---|---|
wait_ready() |
Wait for disc insertion (30s timeout, TUR polling) |
init() |
Firmware upload + unlock + speed calibration |
probe_disc() |
Probe disc surface for optimal speeds |
read(lba, count, buf) |
Read sectors with built-in error recovery |
reset() |
Close/reopen device, TUR, escalate if needed |
lock_tray() |
Prevent tray ejection during rip |
unlock_tray() |
Allow tray ejection (also runs on Drop) |
eject() |
Eject disc tray |
drive_status() |
Query physical state (disc present, tray open, etc.) |
has_profile() |
Whether a bundled profile matched |
close() |
Consume Drive, cleanup (also runs via Drop) |
init() Sequence
init() orchestrates the full drive unlock:
- Platform driver
run_init()— sends vendor-specific SCSI commands - If firmware upload needed: upload, wait 10s for drive reset, retry
- Speed calibration after unlock
- Max 3 attempts before giving up
read() with Recovery
Drive::read(lba, count, buf, recovery) is the single read method. The
recovery parameter controls whether to attempt multi-phase recovery on
failure or return immediately (used by DiscStream's binary search for
single-sector probes).
On error with recovery = true:
- Phase 1 — gentle retry (5 attempts): set min speed, sleep 30s, retry. Each retry has a hard wall-clock timeout via async SG_IO.
- Phase 2 — fresh start: close transport, reset device, reopen, reinit.
- Phase 3 — gentle retry on fresh connection (5 attempts).
- If all fail: return
Err(DiscRead). DiscStream handles it (binary search, skip, zero-fill). - Stay at min speed for 500 MB after any recovery (recovery window).
SCSI Transport
Trait
pub trait ScsiTransport: Send {
fn execute(
&mut self,
cdb: &[u8],
direction: DataDirection,
data: &mut [u8],
timeout_ms: u32,
) -> Result<ScsiResult>;
}
All drive communication goes through this trait. The library never opens file
descriptors or calls ioctls outside of a ScsiTransport implementation.
Platform Backends
| Platform | Implementation | Device |
|---|---|---|
| Linux | SgIoTransport — async write/poll/read on /dev/sg* |
/dev/sg* |
| macOS | MacScsiTransport — IOKit SCSITask |
IOKit service |
| Windows | WindowsScsiTransport — SPTI |
\\.\CdRomN |
The Linux backend uses the sg driver's asynchronous interface: write() submits
the command, poll() waits with an enforceable wall-clock timeout, read()
retrieves the result. If poll() times out, the fd is abandoned (closed in a
background thread) and a fresh fd opened — the kernel's USB error recovery
cannot block us. Opens with O_RDWR | O_NONBLOCK.
On non-zero SCSI status, the transport parses sense key from the sense buffer
and returns Error::ScsiError.
CDB Builders
The scsi module provides platform-agnostic CDB constructors:
| Function | CDB | Use |
|---|---|---|
inquiry() |
INQUIRY (0x12) | Drive identification |
get_config_010c() |
GET CONFIGURATION (0x46) | Feature 010C firmware date |
build_read_buffer() |
READ BUFFER (0x3C) | All platform commands |
build_set_cd_speed() |
SET CD SPEED (0xBB) | Speed control |
build_read10_raw() |
READ(10) (0x28) with flag 0x08 | Raw sector reads |
Drive Identification
DriveId::from_drive() sends two standard SCSI commands and extracts identity
fields:
| Field | Source | SCSI Reference |
|---|---|---|
vendor_id |
INQUIRY bytes [8:16] | SPC-4 section 6.4.2 |
product_id |
INQUIRY bytes [16:32] | SPC-4 section 6.4.2 |
product_revision |
INQUIRY bytes [32:36] | SPC-4 section 6.4.2 |
vendor_specific |
INQUIRY bytes [36:43] | SPC-4 section 6.4.2 |
firmware_date |
GET CONFIGURATION Feature 010C | MMC-6 section 5.3.10 |
The match key is "VENDOR|PRODUCT|REVISION|VENDOR_SPECIFIC". Profile matching
tries all four fields first, then falls back to matching without the firmware
date for drives where Feature 010C is unavailable.
Drive Profiles
Profiles are JSON objects compiled into the binary (profiles.json).
Each profile contains:
| Field | Purpose |
|---|---|
vendor_id, product_revision, vendor_specific, firmware_date |
Matching fields |
chipset |
"mediatek" or "renesas" |
unlock_mode, unlock_buf_id |
READ BUFFER CDB parameters |
signature |
Expected 4-byte response signature |
unlock_cdb |
Pre-built unlock CDB (hex-encoded) |
register_offsets |
Offsets for hardware register reads |
capabilities |
Feature flags: bd_raw_read, dvd_all_regions, etc. |
Loading:
// Bundled (compiled-in) -- no file I/O
let profiles = profile::load_bundled()?;
// External file
let profiles = profile::load_all(Path::new("/path/to/profiles.json"))?;
Chipsets
MediaTek MT1959
Covers all LG, ASUS, and HP optical drives. Two sub-variants share identical logic with different SCSI parameters:
| Variant | READ BUFFER mode | Buffer ID |
|---|---|---|
| MT1959-A | 0x01 | 0x44 |
| MT1959-B | 0x02 | 0x77 |
The Platform trait maps to command handlers:
| Handler | Function | Description |
|---|---|---|
| 0 | unlock() |
Send READ BUFFER, verify signature + verification bytes |
| 1 | read_config() |
Read 1888-byte configuration block + 4-byte status |
| 2-3 | read_register() |
Read hardware registers at profile-specified offsets |
| 4 | calibrate() |
Probe disc surface, build 64-entry speed table |
| 5 | keepalive() |
Periodic session maintenance |
| 6 | status() |
Query current mode and feature flags |
| 7 | probe() |
Generic READ BUFFER with dynamic parameters |
| 8 | read_sectors() |
Speed lookup + SET CD SPEED + READ(10) with flag 0x08 |
| 9 | timing() |
Timing calibration |
Renesas (Planned)
RS8xxx/RS9xxx chipsets used in Pioneer and some HL-DT-ST drives.
Currently returns Error::UnsupportedDrive when a Renesas profile is matched.
Why Unlock Is Needed
Optical drive firmware restricts what applications can read from disc. Without unlock:
-
READ(10) works for unencrypted filesystem data. UDF structures, MPLS playlists, and CLPI clip info are readable without unlock. Standard READ(10) works on any drive.
-
READ(10) fails for encrypted content sectors. The drive firmware returns SCSI errors (sense key 0x05, illegal request) when an application attempts to read sectors containing encrypted m2ts content without prior AACS authentication via the bus key.
-
Raw mode bypasses firmware restrictions. After unlock, the drive accepts READ(10) with the raw read flag (CDB byte 1 = 0x08) for all sectors, regardless of encryption status.
AACS Before Unlock
AACS bus authentication uses standard MMC REPORT KEY / SEND KEY commands.
On some drives these must execute before unlock. The Disc::scan() handles
this internally — it manages the handshake/unlock ordering automatically.
Speed Control
After probe_disc(), the platform driver maintains a speed lookup table
built by probing the disc surface. On each read() call, the driver:
- Looks up the optimal speed for the target LBA.
- Issues SET CD SPEED (0xBB) if the speed differs from current.
- Performs the READ(10).
Available speeds:
| Format | Speeds |
|---|---|
| Blu-ray | 1x (4,500 KB/s) through 12x (54,000 KB/s) |
| DVD | 1x (1,385 KB/s) through 16x (22,160 KB/s) |
| Max | 0xFFFF (drive decides) |