Files
RedBear-OS/recipes/drivers/storage/usbscsid/src/protocol/bot.rs
T
vasilito 15d0707e74 feat: USB storage read/write proof + full Red Bear OS tree sync
Add redbear-usb-storage-check in-guest binary that validates USB mass
storage read and write I/O: discovers /scheme/disk/ devices, writes a
test pattern to sector 2048, reads it back, verifies match, restores
original content. Updates test-usb-storage-qemu.sh with write-proof
verification step.

Includes all accumulated Red Bear OS work: kernel patches, relibc
patches, driver infrastructure, DRM/GPU, KDE recipes, firmware,
validation tooling, build system hardening, and documentation.
2026-05-03 23:03:24 +01:00

364 lines
12 KiB
Rust

use std::num::NonZeroU32;
use std::slice;
use xhcid_interface::{
ConfDesc, DeviceReqData, EndpBinaryDirection, EndpDirection, EndpointStatus, IfDesc, Invalid,
PortReqRecipient, PortReqTy, PortTransferStatus, PortTransferStatusKind, XhciClientHandle,
XhciClientHandleError, XhciEndpHandle,
};
use super::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind};
pub const CBW_SIGNATURE: u32 = 0x43425355;
/// 0 means host to dev, 1 means dev to host
pub const CBW_FLAGS_DIRECTION_BIT: u8 = 1 << CBW_FLAGS_DIRECTION_SHIFT;
pub const CBW_FLAGS_DIRECTION_SHIFT: u8 = 7;
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
pub struct CommandBlockWrapper {
pub signature: u32,
pub tag: u32,
pub data_transfer_len: u32,
pub flags: u8, // upper nibble reserved
pub lun: u8, // bits 7:5 reserved
pub cb_len: u8,
pub command_block: [u8; 16],
}
impl CommandBlockWrapper {
pub fn new(
tag: u32,
data_transfer_len: u32,
direction: EndpBinaryDirection,
lun: u8,
cb: &[u8],
) -> Result<Self, ProtocolError> {
let mut command_block = [0u8; 16];
if cb.len() > 16 {
return Err(ProtocolError::TooLargeCommandBlock(cb.len()));
}
command_block[..cb.len()].copy_from_slice(&cb);
Ok(Self {
signature: CBW_SIGNATURE,
tag,
data_transfer_len,
flags: match direction {
EndpBinaryDirection::Out => 0,
EndpBinaryDirection::In => 1,
} << CBW_FLAGS_DIRECTION_SHIFT,
lun,
cb_len: cb.len() as u8,
command_block,
})
}
}
unsafe impl plain::Plain for CommandBlockWrapper {}
pub const CSW_SIGNATURE: u32 = 0x53425355;
#[repr(u8)]
pub enum CswStatus {
Passed = 0,
Failed = 1,
PhaseError = 2,
// the rest are reserved
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
pub struct CommandStatusWrapper {
pub signature: u32,
pub tag: u32,
pub data_residue: u32,
pub status: u8,
}
unsafe impl plain::Plain for CommandStatusWrapper {}
impl CommandStatusWrapper {
pub fn is_valid(&self) -> bool {
self.signature == CSW_SIGNATURE
}
}
pub struct BulkOnlyTransport<'a> {
handle: &'a XhciClientHandle,
bulk_in: XhciEndpHandle,
bulk_out: XhciEndpHandle,
bulk_in_num: u8,
bulk_out_num: u8,
max_lun: u8,
current_tag: u32,
interface_num: u8,
}
pub const FEATURE_ENDPOINT_HALT: u16 = 0;
impl<'a> BulkOnlyTransport<'a> {
pub fn init(
handle: &'a XhciClientHandle,
config_desc: &ConfDesc,
if_desc: &IfDesc,
) -> Result<Self, ProtocolError> {
let endpoints = &if_desc.endpoints;
let bulk_in_num = (endpoints
.iter()
.position(|endpoint| endpoint.direction() == EndpDirection::In)
.unwrap()
+ 1) as u8;
let bulk_out_num = (endpoints
.iter()
.position(|endpoint| endpoint.direction() == EndpDirection::Out)
.unwrap()
+ 1) as u8;
let max_lun = get_max_lun(handle, 0)?;
println!("BOT_MAX_LUN {}", max_lun);
Ok(Self {
bulk_in: handle.open_endpoint(bulk_in_num)?,
bulk_out: handle.open_endpoint(bulk_out_num)?,
bulk_in_num,
bulk_out_num,
handle,
max_lun,
current_tag: 0,
interface_num: if_desc.number,
})
}
fn clear_stall_in(&mut self) -> Result<(), XhciClientHandleError> {
if self.bulk_in.status()? == EndpointStatus::Halted {
self.bulk_in.reset(false)?;
self.handle.clear_feature(
PortReqRecipient::Endpoint,
u16::from(self.bulk_in_num),
FEATURE_ENDPOINT_HALT,
)?;
}
Ok(())
}
fn clear_stall_out(&mut self) -> Result<(), XhciClientHandleError> {
if self.bulk_out.status()? == EndpointStatus::Halted {
self.bulk_out.reset(false)?;
self.handle.clear_feature(
PortReqRecipient::Endpoint,
u16::from(self.bulk_out_num),
FEATURE_ENDPOINT_HALT,
)?;
}
Ok(())
}
fn reset_recovery(&mut self) -> Result<(), ProtocolError> {
bulk_only_mass_storage_reset(self.handle, self.interface_num.into())?;
self.clear_stall_in()?;
self.clear_stall_out()?;
if self.bulk_in.status()? == EndpointStatus::Halted
|| self.bulk_out.status()? == EndpointStatus::Halted
{
return Err(ProtocolError::RecoveryFailed);
}
Ok(())
}
fn read_csw_raw(
&mut self,
csw_buffer: &mut [u8; 13],
already: bool,
) -> Result<(), ProtocolError> {
match self.bulk_in.transfer_read(&mut csw_buffer[..])? {
PortTransferStatus {
kind: PortTransferStatusKind::Stalled,
..
} => {
if already {
self.reset_recovery()?;
}
println!("bulk in endpoint stalled when reading CSW");
self.clear_stall_in()?;
self.read_csw_raw(csw_buffer, true)?;
}
PortTransferStatus {
kind: PortTransferStatusKind::ShortPacket,
bytes_transferred,
} if bytes_transferred != 13 => {
panic!(
"received a short packet when reading CSW ({} != 13)",
bytes_transferred
)
}
_ => (),
}
Ok(())
}
fn read_csw(&mut self, csw_buffer: &mut [u8; 13]) -> Result<(), ProtocolError> {
self.read_csw_raw(csw_buffer, false)
}
}
impl<'a> Protocol for BulkOnlyTransport<'a> {
fn send_command(
&mut self,
cb: &[u8],
data: DeviceReqData,
) -> Result<SendCommandStatus, ProtocolError> {
self.current_tag += 1;
let tag = self.current_tag;
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)?;
let cbw = *cbw;
match self.bulk_out.transfer_write(&cbw_bytes)? {
PortTransferStatus {
kind: PortTransferStatusKind::Stalled,
..
} => {
// TODO: Error handling
panic!("bulk out endpoint stalled when sending CBW {:?}", cbw);
//self.clear_stall_out()?;
//dbg!(self.bulk_in.status()?, self.bulk_out.status()?);
}
PortTransferStatus {
bytes_transferred, ..
} if bytes_transferred != 31 => {
panic!(
"received short packet when sending CBW ({} != 31)",
bytes_transferred
);
}
_ => (),
}
let early_residue: Option<NonZeroU32> = match data {
DeviceReqData::In(buffer) => match self.bulk_in.transfer_read(buffer)? {
PortTransferStatus {
kind,
bytes_transferred,
} => match kind {
PortTransferStatusKind::Success => None,
PortTransferStatusKind::ShortPacket => {
println!(
"received short packet (len {}) when transferring data",
bytes_transferred
);
NonZeroU32::new(bytes_transferred)
}
PortTransferStatusKind::Stalled => {
panic!("bulk in endpoint stalled when reading data");
//self.clear_stall_in()?;
}
PortTransferStatusKind::Unknown => {
return Err(ProtocolError::XhciError(
XhciClientHandleError::InvalidResponse(Invalid(
"unknown transfer status",
)),
));
}
},
},
DeviceReqData::Out(buffer) => match self.bulk_out.transfer_write(buffer)? {
PortTransferStatus {
kind,
bytes_transferred,
} => match kind {
PortTransferStatusKind::Success => None,
PortTransferStatusKind::ShortPacket => {
println!(
"received short packet (len {}) when transferring data",
bytes_transferred
);
NonZeroU32::new(bytes_transferred)
}
PortTransferStatusKind::Stalled => {
panic!("bulk out endpoint stalled when reading data");
//self.clear_stall_out()?;
}
PortTransferStatusKind::Unknown => {
return Err(ProtocolError::XhciError(
XhciClientHandleError::InvalidResponse(Invalid(
"unknown transfer status",
)),
));
}
},
},
DeviceReqData::NoData => None,
};
let mut csw_buffer = [0u8; 13];
self.read_csw(&mut csw_buffer)?;
let csw = plain::from_bytes::<CommandStatusWrapper>(&csw_buffer).unwrap();
let residue = early_residue.or(NonZeroU32::new(csw.data_residue));
if csw.status == CswStatus::Failed as u8 {
println!("CSW indicated failure (CSW {:?}, CBW {:?})", csw, cbw);
}
if !csw.is_valid() || csw.tag != cbw.tag {
println!("Invald CSW {:?} (for CBW {:?})", csw, cbw);
self.reset_recovery()?;
if self.bulk_in.status()? == EndpointStatus::Halted
|| self.bulk_out.status()? == EndpointStatus::Halted
{
return Err(ProtocolError::ProtocolError(
"Reset Recovery didn't reset endpoints",
));
}
return Err(ProtocolError::ProtocolError(
"CSW invalid, but a recover was successful",
));
}
/*if self.bulk_in.status()? == EndpointStatus::Halted
|| self.bulk_out.status()? == EndpointStatus::Halted
{
println!("Trying to recover from stall");
dbg!(self.bulk_in.status()?, self.bulk_out.status()?);
}*/
Ok(SendCommandStatus {
kind: if csw.status == CswStatus::Passed as u8 {
SendCommandStatusKind::Success
} else if csw.status == CswStatus::Failed as u8 {
SendCommandStatusKind::Failed
} else {
return Err(ProtocolError::ProtocolError(
"bulk-only transport phase error, or other",
));
},
residue,
})
}
}
pub fn bulk_only_mass_storage_reset(
handle: &XhciClientHandle,
if_num: u16,
) -> Result<(), XhciClientHandleError> {
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
0xFF,
0,
if_num,
DeviceReqData::NoData,
)
}
pub fn get_max_lun(handle: &XhciClientHandle, if_num: u16) -> Result<u8, XhciClientHandleError> {
let mut lun = 0u8;
let buffer = slice::from_mut(&mut lun);
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
0xFE,
0,
if_num,
DeviceReqData::In(buffer),
)?;
Ok(lun)
}