From ea2219148e38bd997bcd12f457c83f79b64a6fda Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Tue, 7 Jul 2026 15:17:38 +0300 Subject: [PATCH] =?UTF-8?q?USB:=20P4-B=20multi-LUN=20support=20=E2=80=94?= =?UTF-8?q?=20Protocol=20trait=20+=20BOT/UAS=20LUN=20propagation=20+=20REP?= =?UTF-8?q?ORT=5FLUNS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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. --- drivers/storage/usbscsid/src/main.rs | 21 +++++++++++- drivers/storage/usbscsid/src/protocol/bot.rs | 10 +++++- drivers/storage/usbscsid/src/protocol/mod.rs | 3 ++ drivers/storage/usbscsid/src/protocol/uas.rs | 10 +++++- drivers/storage/usbscsid/src/scsi/mod.rs | 36 ++++++++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs index 5382d118e3..166921535d 100644 --- a/drivers/storage/usbscsid/src/main.rs +++ b/drivers/storage/usbscsid/src/main.rs @@ -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[..])); diff --git a/drivers/storage/usbscsid/src/protocol/bot.rs b/drivers/storage/usbscsid/src/protocol/bot.rs index 5813275add..2bd7df6919 100644 --- a/drivers/storage/usbscsid/src/protocol/bot.rs +++ b/drivers/storage/usbscsid/src/protocol/bot.rs @@ -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::(&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( diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs index edd61c065a..4ea09801a2 100644 --- a/drivers/storage/usbscsid/src/protocol/mod.rs +++ b/drivers/storage/usbscsid/src/protocol/mod.rs @@ -60,6 +60,9 @@ pub trait Protocol { command: &[u8], data: DeviceReqData, ) -> Result; + + fn max_lun(&self) -> u8; + fn set_lun(&mut self, lun: u8); } /// Bulk-only transport (BOT) diff --git a/drivers/storage/usbscsid/src/protocol/uas.rs b/drivers/storage/usbscsid/src/protocol/uas.rs index 39d18d22f4..e345544208 100644 --- a/drivers/storage/usbscsid/src/protocol/uas.rs +++ b/drivers/storage/usbscsid/src/protocol/uas.rs @@ -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; + } } diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs index 807ffb82bf..28132985e0 100644 --- a/drivers/storage/usbscsid/src/scsi/mod.rs +++ b/drivers/storage/usbscsid/src/scsi/mod.rs @@ -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> { + // 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> {