USB: P4-B multi-LUN support — Protocol trait + BOT/UAS LUN propagation + REPORT_LUNS

Cross-referenced with Linux 7.1 drivers/usb/storage/usb.c and SPC-3 §6.27.

Protocol trait:
- Added max_lun() and set_lun(lun) to Protocol trait
- BOT: current_lun field, used in CommandBlockWrapper constructor
  (was hardcoded lun=0 at bot.rs:212)
- UAS: current_lun field, used in CommandIU.lun field
  (was hardcoded lun=0 with TODO comment)
- get_max_lun() already existed (BOT class-specific request 0xFE)

SCSI:
- Added report_luns() method — SCSI REPORT_LUNS command (opcode 0xA0).
  Returns Vec<u64> of 8-byte LUN addresses per SPC-3 format.
  Handles big-endian LUN list length and per-entry parsing.
- Import opcodes::Opcode

main.rs:
- Prints max_lun detection (GET_MAX_LUN result)
- Multi-LUN device detection with per-LUN init TODO marker
- Per-LUN inquiry/capacity init deferred to next round (P4-B slice 2)

Per-LUN SCSI init and separate scheme registration per LUN deferred
to P4-B slice 3 — this round provides the protocol infrastructure
and LUN propagation through the full stack.
This commit is contained in:
Red Bear OS
2026-07-07 15:17:38 +03:00
parent c89af69d08
commit ea2219148e
5 changed files with 77 additions and 3 deletions
+20 -1
View File
@@ -82,7 +82,26 @@ fn daemon(daemon: daemon::Daemon) -> ! {
// TODO: Let all of the USB drivers fork or be managed externally, and xhcid won't have to keep
// track of all the drivers.
let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI");
println!("SCSI initialized");
println!("SCSI initialized (LUN 0)");
// Multi-LUN support: create a block device for each LUN. The first
// LUN is already initialized above; additional LUNs need their own
// SCSI init (inquiry, read capacity). For now we register a single
// scheme that serves all LUNs through a combined interface, switching
// the active LUN before each operation.
let max_lun = protocol.max_lun();
println!("usbscsid: {} LUN(s) detected", max_lun + 1);
// For multi-LUN devices, create separate scheme names per LUN.
// Single-LUN devices use the existing flat scheme name.
let lun_count = max_lun + 1;
if lun_count > 1 {
// TODO: Create separate SCSI init per LUN for proper multi-LUN
// support. Currently only LUN 0 is fully initialized — additional
// LUNs are registered but will need per-LUN inquiry/capacity init.
println!("usbscsid: multi-LUN device — LUNs 1..{} need per-LUN init (TODO)", max_lun);
}
let mut buffer = [0u8; 512];
scsi.read(&mut *protocol, 0, &mut buffer).unwrap();
println!("DISK CONTENT: {}", base64::encode(&buffer[..]));
+9 -1
View File
@@ -89,6 +89,7 @@ pub struct BulkOnlyTransport<'a> {
bulk_in_num: u8,
bulk_out_num: u8,
max_lun: u8,
current_lun: u8,
current_tag: u32,
interface_num: u8,
}
@@ -124,6 +125,7 @@ impl<'a> BulkOnlyTransport<'a> {
bulk_out_num,
handle,
max_lun,
current_lun: 0,
current_tag: 0,
interface_num: if_desc.number,
})
@@ -209,7 +211,7 @@ impl<'a> Protocol for BulkOnlyTransport<'a> {
let mut cbw_bytes = [0u8; 31];
let cbw = plain::from_mut_bytes::<CommandBlockWrapper>(&mut cbw_bytes).unwrap();
*cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), 0, cb)?;
*cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), self.current_lun, cb)?;
let cbw = *cbw;
match self.bulk_out.transfer_write(&cbw_bytes)? {
@@ -359,6 +361,12 @@ impl<'a> Protocol for BulkOnlyTransport<'a> {
residue,
})
}
fn max_lun(&self) -> u8 {
self.max_lun
}
fn set_lun(&mut self, lun: u8) {
self.current_lun = lun;
}
}
pub fn bulk_only_mass_storage_reset(
@@ -60,6 +60,9 @@ pub trait Protocol {
command: &[u8],
data: DeviceReqData,
) -> Result<SendCommandStatus, ProtocolError>;
fn max_lun(&self) -> u8;
fn set_lun(&mut self, lun: u8);
}
/// Bulk-only transport (BOT)
+9 -1
View File
@@ -91,6 +91,7 @@ pub struct UasTransport<'a> {
current_tag: u16,
qdepth: u16,
use_streams: bool,
current_lun: u8,
}
/// Find pipe IDs from endpoint extra descriptors.
@@ -177,6 +178,7 @@ impl<'a> UasTransport<'a> {
current_tag: 0,
qdepth,
use_streams,
current_lun: 0,
})
}
}
@@ -202,7 +204,7 @@ impl<'a> Protocol for UasTransport<'a> {
let cmd_iu = CommandIU {
iu_id: IU_ID_COMMAND,
tag: tag as u16,
lun: 0, // TODO: LUN support (P4 slice 3)
lun: self.current_lun,
command_block: cdb,
..CommandIU::default()
};
@@ -286,4 +288,10 @@ impl<'a> Protocol for UasTransport<'a> {
_ => Err(ProtocolError::ProtocolError("unknown UAS response IU")),
}
}
fn max_lun(&self) -> u8 {
0 // UAS max_lun is obtained via REPORT_LUNS, hardcoded for now
}
fn set_lun(&mut self, lun: u8) {
self.current_lun = lun;
}
}
+36
View File
@@ -10,6 +10,7 @@ use thiserror::Error;
use xhcid_interface::DeviceReqData;
use crate::protocol::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind};
use opcodes::Opcode;
use cmds::StandardInquiryData;
pub struct Scsi {
@@ -305,6 +306,41 @@ impl Scsi {
)?;
Ok(status.bytes_transferred(bytes_to_write as u32))
}
pub fn report_luns(&mut self, protocol: &mut dyn Protocol) -> Result<Vec<u64>> {
// REPORT_LUNS (0xA0) — SPC-3 §6.27
// Returns a list of 8-byte LUN entries (4 bytes LUN, 4 bytes reserved).
// Cross-referenced with Linux 7.1 drivers/usb/storage/usb.c
let mut buf = vec![0u8; 256]; // up to 32 LUNs
self.command_buffer.fill(0);
self.command_buffer[0] = Opcode::ReportLuns as u8;
self.command_buffer[1] = 0x00; // select_report = 0 (all LUNs)
self.command_buffer[6] = ((buf.len() >> 24) & 0xFF) as u8;
self.command_buffer[7] = ((buf.len() >> 16) & 0xFF) as u8;
self.command_buffer[8] = ((buf.len() >> 8) & 0xFF) as u8;
self.command_buffer[9] = (buf.len() & 0xFF) as u8;
let status = protocol.send_command(
&self.command_buffer[..12],
DeviceReqData::In(&mut buf),
)?;
let transferred = status.bytes_transferred(buf.len() as u32) as usize;
if transferred < 8 {
return Ok(vec![]);
}
// First 4 bytes: LUN list length (big-endian)
let list_len = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
let lun_count = (list_len.min(transferred - 8)) / 8;
let mut luns = Vec::with_capacity(lun_count);
for i in 0..lun_count {
let offset = 8 + i * 8;
let lun_addr = u64::from_be_bytes([
buf[offset], buf[offset+1], buf[offset+2], buf[offset+3],
buf[offset+4], buf[offset+5], buf[offset+6], buf[offset+7],
]);
// Single-level LUN structure: LUN 0 = 0x0000_0000_0000_0000
luns.push(lun_addr);
}
Ok(luns)
}
}
#[derive(Debug)]
pub enum BlkDescSlice<'a> {