Fix read speed: detect kernel max transfer size, fix phantom streams
Root cause: READ_10 requested 510 sectors (1MB) but kernel sg driver limits to max_hw_sectors_kb=120 (60 sectors). All bulk reads errored, error handler fell to 3-sector reads → 4.8 MB/s instead of 12+ MB/s. - detect_max_batch_sectors() now resolves sg→block device via sysfs and uses 80% of kernel limit for safety margin - Default fallback reduced from 510 to 48 sectors - ContentReader uses per-device detected max, not hardcoded constant - Filter out coding_type 0x00 (empty/padding) stream entries - Add MPLS stream_type 5/6/7 attribute parsing (secondary audio/video) Tested on BU40N: 4.8 → 12.5 MB/s sustained, 23.7 MB/s peak.
This commit is contained in:
+39
-13
@@ -672,6 +672,8 @@ impl Disc {
|
|||||||
|
|
||||||
// Build streams from STN table
|
// Build streams from STN table
|
||||||
let streams: Vec<Stream> = parsed.streams.iter().filter_map(|s| {
|
let streams: Vec<Stream> = parsed.streams.iter().filter_map(|s| {
|
||||||
|
// Skip empty/padding entries (coding_type 0x00)
|
||||||
|
if s.coding_type == 0 { return None; }
|
||||||
let codec = Codec::from_coding_type(s.coding_type);
|
let codec = Codec::from_coding_type(s.coding_type);
|
||||||
match s.stream_type {
|
match s.stream_type {
|
||||||
1 | 6 | 7 => Some(Stream::Video(VideoStream {
|
1 | 6 | 7 => Some(Stream::Video(VideoStream {
|
||||||
@@ -765,6 +767,8 @@ pub struct ContentReader<'a> {
|
|||||||
buf_len: usize,
|
buf_len: usize,
|
||||||
/// Current batch size in sectors (adapts on errors)
|
/// Current batch size in sectors (adapts on errors)
|
||||||
batch_sectors: u16,
|
batch_sectors: u16,
|
||||||
|
/// Maximum batch size detected from kernel limits
|
||||||
|
max_batch_sectors: u16,
|
||||||
/// Consecutive successful batch reads
|
/// Consecutive successful batch reads
|
||||||
ok_streak: u32,
|
ok_streak: u32,
|
||||||
/// Consecutive errors at current position
|
/// Consecutive errors at current position
|
||||||
@@ -804,6 +808,9 @@ impl Disc {
|
|||||||
let mut dummy = [0u8; 0];
|
let mut dummy = [0u8; 0];
|
||||||
let _ = session.scsi_execute(&speed_cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000);
|
let _ = session.scsi_execute(&speed_cdb, crate::scsi::DataDirection::None, &mut dummy, 5_000);
|
||||||
|
|
||||||
|
// Detect kernel max transfer size for this device
|
||||||
|
let max_batch = detect_max_batch_sectors(session.device_path());
|
||||||
|
|
||||||
Ok(ContentReader {
|
Ok(ContentReader {
|
||||||
session,
|
session,
|
||||||
aacs: self.aacs.as_ref(),
|
aacs: self.aacs.as_ref(),
|
||||||
@@ -811,10 +818,11 @@ impl Disc {
|
|||||||
current_extent: 0,
|
current_extent: 0,
|
||||||
current_offset: 0,
|
current_offset: 0,
|
||||||
unit_key_idx: 0,
|
unit_key_idx: 0,
|
||||||
read_buf: Vec::with_capacity(MAX_BATCH_SECTORS as usize * 2048),
|
read_buf: Vec::with_capacity(max_batch as usize * 2048),
|
||||||
buf_pos: 0,
|
buf_pos: 0,
|
||||||
buf_len: 0,
|
buf_len: 0,
|
||||||
batch_sectors: MAX_BATCH_SECTORS,
|
batch_sectors: max_batch,
|
||||||
|
max_batch_sectors: max_batch,
|
||||||
ok_streak: 0,
|
ok_streak: 0,
|
||||||
error_streak: 0,
|
error_streak: 0,
|
||||||
speed_tier: 0,
|
speed_tier: 0,
|
||||||
@@ -826,30 +834,48 @@ impl Disc {
|
|||||||
|
|
||||||
/// Detect the maximum transfer size in sectors for a device.
|
/// Detect the maximum transfer size in sectors for a device.
|
||||||
/// Reads /sys/block/<dev>/queue/max_hw_sectors_kb on Linux.
|
/// Reads /sys/block/<dev>/queue/max_hw_sectors_kb on Linux.
|
||||||
|
/// For sg devices, resolves the corresponding block device via sysfs.
|
||||||
/// Returns a value aligned to 3 sectors (one aligned unit).
|
/// Returns a value aligned to 3 sectors (one aligned unit).
|
||||||
fn detect_max_batch_sectors(device_path: &str) -> u16 {
|
fn detect_max_batch_sectors(device_path: &str) -> u16 {
|
||||||
// Extract block device name: /dev/sr0 → sr0
|
|
||||||
let dev_name = device_path.rsplit('/').next().unwrap_or("");
|
let dev_name = device_path.rsplit('/').next().unwrap_or("");
|
||||||
if !dev_name.is_empty() {
|
if dev_name.is_empty() {
|
||||||
let sysfs_path = format!("/sys/block/{}/queue/max_hw_sectors_kb", dev_name);
|
return DEFAULT_BATCH_SECTORS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For sg devices, find the corresponding block device name
|
||||||
|
let block_name = if dev_name.starts_with("sg") {
|
||||||
|
let block_dir = format!("/sys/class/scsi_generic/{}/device/block", dev_name);
|
||||||
|
std::fs::read_dir(&block_dir).ok()
|
||||||
|
.and_then(|mut entries| entries.next())
|
||||||
|
.and_then(|e| e.ok())
|
||||||
|
.map(|e| e.file_name().to_string_lossy().to_string())
|
||||||
|
} else {
|
||||||
|
Some(dev_name.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(bname) = block_name {
|
||||||
|
let sysfs_path = format!("/sys/block/{}/queue/max_hw_sectors_kb", bname);
|
||||||
if let Ok(content) = std::fs::read_to_string(&sysfs_path) {
|
if let Ok(content) = std::fs::read_to_string(&sysfs_path) {
|
||||||
if let Ok(kb) = content.trim().parse::<u32>() {
|
if let Ok(kb) = content.trim().parse::<u32>() {
|
||||||
// Convert KB to sectors (1 sector = 2 KB on disc = 2048 bytes)
|
// Convert KB to sectors (1 sector = 2 KB = 2048 bytes)
|
||||||
let sectors = (kb / 2) as u16;
|
let sectors = (kb / 2) as u16;
|
||||||
// Align down to 3 (one aligned unit) and cap at a reasonable max
|
// Stay well under kernel limit (80% of max) to avoid edge cases
|
||||||
let aligned = (sectors / 3) * 3;
|
let safe = (sectors * 4 / 5).max(MIN_BATCH_SECTORS);
|
||||||
|
// Align down to 3 (one aligned unit)
|
||||||
|
let aligned = (safe / 3) * 3;
|
||||||
if aligned >= MIN_BATCH_SECTORS {
|
if aligned >= MIN_BATCH_SECTORS {
|
||||||
return aligned.min(MAX_BATCH_SECTORS);
|
return aligned.min(MAX_BATCH_SECTORS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback: conservative default
|
// Fallback: safe default well under typical kernel limits
|
||||||
MAX_BATCH_SECTORS
|
DEFAULT_BATCH_SECTORS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read strategy constants
|
/// Read strategy constants
|
||||||
const MAX_BATCH_SECTORS: u16 = 510; // 170 aligned units ≈ 1MB (kernel caps to hw limit)
|
const MAX_BATCH_SECTORS: u16 = 510; // absolute max (170 aligned units ≈ 1MB)
|
||||||
|
const DEFAULT_BATCH_SECTORS: u16 = 48; // safe fallback (96KB, under typical 120KB kernel limit)
|
||||||
const MIN_BATCH_SECTORS: u16 = 3; // 1 aligned unit = 6KB (error recovery)
|
const MIN_BATCH_SECTORS: u16 = 3; // 1 aligned unit = 6KB (error recovery)
|
||||||
const RAMP_BATCH_AFTER: u32 = 5; // successes before doubling batch size
|
const RAMP_BATCH_AFTER: u32 = 5; // successes before doubling batch size
|
||||||
const RAMP_SPEED_AFTER: u32 = 50; // successes at max batch before restoring speed
|
const RAMP_SPEED_AFTER: u32 = 50; // successes at max batch before restoring speed
|
||||||
@@ -1009,9 +1035,9 @@ impl<'a> ContentReader<'a> {
|
|||||||
|
|
||||||
// Ramp up: batch size first, then disc speed
|
// Ramp up: batch size first, then disc speed
|
||||||
self.ok_streak += 1;
|
self.ok_streak += 1;
|
||||||
if self.batch_sectors < MAX_BATCH_SECTORS {
|
if self.batch_sectors < self.max_batch_sectors {
|
||||||
if self.ok_streak >= RAMP_BATCH_AFTER {
|
if self.ok_streak >= RAMP_BATCH_AFTER {
|
||||||
self.batch_sectors = (self.batch_sectors * 2).min(MAX_BATCH_SECTORS);
|
self.batch_sectors = (self.batch_sectors * 2).min(self.max_batch_sectors);
|
||||||
self.ok_streak = 0;
|
self.ok_streak = 0;
|
||||||
}
|
}
|
||||||
} else if self.speed_tier > 0 && self.ok_streak >= RAMP_SPEED_AFTER {
|
} else if self.speed_tier > 0 && self.ok_streak >= RAMP_SPEED_AFTER {
|
||||||
|
|||||||
+21
@@ -263,6 +263,27 @@ fn parse_stream_entry(item: &[u8], pos: usize, stream_type: u8) -> Option<(Strea
|
|||||||
language = String::from_utf8_lossy(&sa[1..4]).to_string();
|
language = String::from_utf8_lossy(&sa[1..4]).to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5 => {
|
||||||
|
// Secondary audio: same as primary audio
|
||||||
|
if sa.len() >= 2 {
|
||||||
|
audio_format = (sa[1] >> 4) & 0x0F;
|
||||||
|
audio_rate = sa[1] & 0x0F;
|
||||||
|
}
|
||||||
|
if sa.len() >= 5 {
|
||||||
|
language = String::from_utf8_lossy(&sa[2..5]).to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
6 | 7 => {
|
||||||
|
// Secondary video: same as primary video
|
||||||
|
if sa.len() >= 2 {
|
||||||
|
video_format = (sa[1] >> 4) & 0x0F;
|
||||||
|
video_rate = sa[1] & 0x0F;
|
||||||
|
}
|
||||||
|
if coding_type == 0x24 && sa.len() > 2 {
|
||||||
|
dynamic_range = (sa[2] >> 4) & 0x0F;
|
||||||
|
color_space_val = sa[2] & 0x0F;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user