diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 1e567bb9..6a37ead3 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -28,3 +28,6 @@ heapless = "0.7.10" # for HID usbd-hid = { version = "0.6.0", optional = true } ssmarshal = { version = "1.0", default-features = false, optional = true } + +paste = "1.0" +num_enum = { version = "0.5", default-features = false } diff --git a/embassy-usb/src/class/msc/subclass/scsi/bitfield.rs b/embassy-usb/src/class/msc/subclass/scsi/bitfield.rs new file mode 100644 index 00000000..8bed7228 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/bitfield.rs @@ -0,0 +1,132 @@ +use core::marker::PhantomData; + +pub trait Packet { + fn data(&self) -> &[u8]; + fn data_mut(&mut self) -> &mut [u8]; + + #[inline] + fn get(&self, field: &F) -> F::T { + field.get(self.data()) + } + + #[inline] + fn set(&mut self, field: &F, val: F::T) { + field.set(self.data_mut(), val) + } +} + +pub trait AsPacketField { + type T; + + fn get(&self, data: &[u8]) -> Self::T; + fn set(&self, data: &mut [u8], val: Self::T); +} + +pub struct BitField { + pub byte: usize, + pub bit: u8, + pub mask: u8, +} + +impl BitField { + pub const fn new(byte: usize, bit: u8, size: u8) -> Self { + let mask = (0xFF >> size) << bit; + Self { byte, bit, mask } + } +} + +impl AsPacketField for BitField { + type T = u8; + + #[inline] + fn get(&self, data: &[u8]) -> Self::T { + (data[self.byte] & self.mask) >> self.bit + } + + #[inline] + fn set(&self, data: &mut [u8], val: Self::T) { + data[self.byte] = (val << self.bit) | (data[self.byte] & !self.mask) + } +} + +pub struct BoolField { + pub byte: usize, + pub bit: u8, +} + +impl BoolField { + pub const fn new(byte: usize, bit: u8) -> Self { + Self { byte, bit } + } +} + +impl AsPacketField for BoolField { + type T = bool; + + #[inline] + fn get(&self, data: &[u8]) -> Self::T { + data[self.byte] & (1 << self.bit) != 0 + } + + #[inline] + fn set(&self, data: &mut [u8], val: Self::T) { + data[self.byte] = ((val as u8) << self.bit) | (data[self.byte] & !(1 << self.bit)) + } +} + +pub struct Field { + pub byte: usize, + _phantom: PhantomData, +} + +impl Field { + pub const fn new(byte: usize) -> Self { + Self { + byte, + _phantom: PhantomData, + } + } +} + +impl AsPacketField for Field { + type T = T; + + #[inline] + fn get(&self, data: &[u8]) -> Self::T { + unsafe { core::ptr::read(data.as_ptr().offset(self.byte as _) as *const T) } + } + + #[inline] + fn set(&self, data: &mut [u8], val: Self::T) { + unsafe { core::ptr::write(data.as_mut_ptr().offset(self.byte as _) as *mut T, val) } + } +} + +#[cfg(test)] +mod tests { + use super::{AsPacketField, BitField, BoolField}; + + #[test] + fn bitfield() { + let field = BitField::new(0, 4, 3); + + let mut data = [0b1111_1111]; + assert_eq!(field.get(&data), 0b111); + + field.set(&mut data, 0b000); + assert_eq!(field.get(&data), 0b000); + assert_eq!(data, [0b1000_1111]); + } + + #[test] + fn boolfield() { + let field = BoolField::new(0, 5); + + let mut data = [0b1111_1111]; + assert_eq!(field.get(&data), true); + + field.set(&mut data, false); + assert_eq!(field.get(&data), false); + assert_eq!(data, [0b1101_1111]); + } +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/block_device.rs b/embassy-usb/src/class/msc/subclass/scsi/block_device.rs new file mode 100644 index 00000000..baa99086 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/block_device.rs @@ -0,0 +1,19 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BlockDeviceError { + // TODO +} + +pub trait BlockDevice { + /// The number of bytes per block. This determines the size of the buffer passed + /// to read/write functions + fn block_size(&self) -> usize; + + /// Number of blocks in device (max LBA index) + fn num_blocks(&self) -> u32; + + /// Read the block indicated by `lba` into the provided buffer + fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError>; + + /// Write the `block` buffer to the block indicated by `lba` + fn write_block(&mut self, lba: u32, block: &[u8]) -> Result<(), BlockDeviceError>; +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs b/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs new file mode 100644 index 00000000..2f41f86d --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs @@ -0,0 +1,10 @@ +// #[bitfield(bytes = 1)] +// #[derive(BitfieldSpecifier)] +// pub struct Control { +// pub vendor_specific: B2, +// #[skip] +// __: B3, +// pub naca: B1, +// #[skip] +// __: B2, +// } diff --git a/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs b/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs new file mode 100644 index 00000000..e5a8ab85 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs @@ -0,0 +1,17 @@ +// use super::control::Control; + +// #[bitfield(bytes = 6)] +// pub struct InquiryCommand { +// /// Always 0x12 +// pub op_code: B8, +// #[skip] +// __: B7, +// /// If set, return vital data related to the page_code field +// pub enable_vital_product_data: B1, +// /// What kind of vital data to return +// pub page_code: B8, +// /// Amount of bytes allocation for data-in transfer +// pub allocation_length: B16, +// /// Control byte +// pub control: Control, +// } diff --git a/embassy-usb/src/class/msc/subclass/scsi/commands/mod.rs b/embassy-usb/src/class/msc/subclass/scsi/commands/mod.rs new file mode 100644 index 00000000..8452cb99 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/commands/mod.rs @@ -0,0 +1,5 @@ +// `bytes` in `#[bitfield(bytes = 6)]` causes a warning +#![allow(redundant_semicolons)] + +pub mod control; +pub mod inquiry; diff --git a/embassy-usb/src/class/msc/subclass/scsi/enums.rs b/embassy-usb/src/class/msc/subclass/scsi/enums.rs new file mode 100644 index 00000000..6df0a715 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/enums.rs @@ -0,0 +1,55 @@ +use crate::gen_enum; + +gen_enum! { + #[derive(Clone, Copy, Eq, PartialEq, Debug)] + pub enum PeripheralQualifier { + /// A peripheral device having the specified peripheral device type is connected to this logical unit. If the device server is unable to determine whether or not a peripheral device is connected, it also shall use this peripheral qualifier. This peripheral qualifier does not mean that the peripheral device connected to the logical unit is ready for access. + Connected = 0b000, + /// A peripheral device having the specified peripheral device type is not connected to this logical unit. However, the device server is capable of supporting the specified peripheral device type on this logical unit. + NotConnected = 0b001, + /// The device server is not capable of supporting a peripheral device on this logical unit. For this peripheral qualifier the peripheral device type shall be set to 1Fh. All other peripheral device type values are reserved for this peripheral qualifier. + Incapable = 0b011, + } +} + +gen_enum! { + #[derive(Clone, Copy, Eq, PartialEq, Debug)] + pub enum PeripheralDeviceType { + /// Direct access block device (e.g., magnetic disk) + DirectAccessBlock = 0x00, + /// Sequential-access device (e.g., magnetic tape) + SequentialAccess = 0x01, + /// Printer device + Printer = 0x02, + /// Processor device + Processor = 0x03, + /// Write-once device (e.g., some optical disks) + WriteOnce = 0x04, + /// CD/DVD device + CdDvd = 0x05, + /// Optical memory device (e.g., some optical disks) + OpticalMemory = 0x07, + /// Media changer device (e.g., jukeboxes) + MediaChanger = 0x08, + /// Storage array controller device (e.g., RAID) + StorageArrayController = 0x0C, + /// Enclosure services device + EnclosureServices = 0x0D, + /// Simplified direct-access device (e.g., magnetic disk) + SimplifiedDirectAccess = 0x0E, + /// Optical card reader/writer device + OpticaCardReaderWriter = 0x0F, + /// Bridge Controller Commands + BridgeController = 0x10, + /// Object-based Storage Device + ObjectBasedStorage = 0x11, + /// Automation/Drive Interface + AutomationInterface = 0x12, + /// Security manager device + SecurityManager = 0x13, + /// Well known logical unit + WellKnownLogicalUnit = 0x1E, + /// Unknown or no device type + UnknownOrNone = 0x1F, + } +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/mod.rs b/embassy-usb/src/class/msc/subclass/scsi/mod.rs index e69de29b..18a7e571 100644 --- a/embassy-usb/src/class/msc/subclass/scsi/mod.rs +++ b/embassy-usb/src/class/msc/subclass/scsi/mod.rs @@ -0,0 +1,37 @@ +// pub mod bitfield; +pub mod block_device; +pub mod commands; +pub mod enums; +pub mod packet; +pub mod responses; + +use self::block_device::BlockDevice; +use crate::class::msc::transport::{self, CommandSetHandler}; + +pub struct Scsi { + device: B, +} + +impl CommandSetHandler for Scsi { + async fn command_out( + &mut self, + lun: u8, + cmd: &[u8], + pipe: &mut impl transport::DataPipeOut, + ) -> Result<(), transport::CommandError> { + assert!(lun == 0, "LUNs are not supported"); + + Ok(()) + } + + async fn command_in( + &mut self, + lun: u8, + cmd: &[u8], + pipe: &mut impl transport::DataPipeIn, + ) -> Result<(), transport::CommandError> { + assert!(lun == 0, "LUNs are not supported"); + + Ok(()) + } +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/packet.rs b/embassy-usb/src/class/msc/subclass/scsi/packet.rs new file mode 100644 index 00000000..6e176cd3 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/packet.rs @@ -0,0 +1,151 @@ +pub trait BitField { + type Output; + + fn get(data: &[u8], offset: usize, size: usize) -> Self::Output; + fn set(data: &mut [u8], offset: usize, size: usize, val: Self); +} + +impl BitField for u8 { + type Output = u8; + + #[inline] + fn get(data: &[u8], offset: usize, size: usize) -> Self::Output { + let byte = offset / 8; + let bit = offset % 8; + let mask = (0xFF >> size) << bit; + (data[byte] & mask) >> bit + } + + #[inline] + fn set(data: &mut [u8], offset: usize, size: usize, val: Self) { + let byte = offset / 8; + let bit = offset % 8; + data[byte] = 0; + } +} + +#[macro_export] +macro_rules! gen_packet { + ( + $(#[$meta:meta])* + $sv:vis struct $name:ident<$size:literal> { + $( + #[offset = $offset:expr, size = $bit_size:expr] + $field:ident: $ty:ty, + )* + } + ) => { + $(#[$meta])* + $sv struct $name> { + data: T + } + + impl $name<[u8; $size]> { + const SIZE: usize = $size; + + pub fn new() -> Self { + Self { + data: [0u8; Self::SIZE] + } + } + } + + impl> $name { + pub const unsafe fn new_unchecked(data: T) -> Self { + Self { data } + } + + pub fn from_bytes(buf: T) -> Option { + if buf.as_ref().len() < $name::SIZE { + None + } else { + Some(unsafe { Self::new_unchecked(buf) }) + } + } + + $( + #[inline] + pub fn $field(&self) -> <$ty as crate::class::msc::subclass::scsi::packet::BitField>::Output { + const _: () = core::assert!($offset + $bit_size <= $size * 8, "Field offset is out of range"); + <$ty as crate::class::msc::subclass::scsi::packet::BitField>::get(self.data.as_ref(), $offset, $bit_size) + } + )* + } + + impl + AsMut<[u8]>> $name { + $( + paste::paste! { + #[inline] + pub fn [](&mut self, val: $ty) { + <$ty as crate::class::msc::subclass::scsi::packet::BitField>::set(self.data.as_mut(), $offset, $bit_size, val) + } + } + )* + } + } +} + +// gen_packet!(pub struct Test<8> { +// #[offset = 8 * 7, size = 3] +// test: u8, +// }); + +#[macro_export] +macro_rules! gen_enum { + ( + $(#[$meta:meta])* + $sv:vis enum $name:ident<$ty:ty> { + $( + $(#[$variant_meta:meta])* + $variant:ident = $variant_val:literal, + )* + } + ) => { + $(#[$meta])* + $sv enum $name { + $( + $(#[$variant_meta])* + $variant = $variant_val + ),* + } + + impl TryFrom<$ty> for $name { + type Error = $ty; + + fn try_from(value: $ty) -> Result { + match value { + $($variant_val => Ok($name::$variant),)* + _ => Err(value) + } + } + } + + impl From<$name> for $ty { + fn from(value: $name) -> $ty { + value as $ty + } + } + + impl crate::class::msc::subclass::scsi::packet::BitField for $name { + type Output = Result; + + #[inline] + fn get(data: &[u8], offset: usize, size: usize) -> Self::Output { + let val = <$ty as crate::class::msc::subclass::scsi::packet::BitField>::get(data, offset, size); + Self::try_from(val) + } + + #[inline] + fn set(data: &mut [u8], offset: usize, size: usize, val: Self) { + <$ty as crate::class::msc::subclass::scsi::packet::BitField>::set(data, offset, size, val.into()); + } + } + }; +} + +// gen_enum! { +// pub enum Testas { +// Hello = 0b111, +// Test = 0b1111, +// } +// } diff --git a/embassy-usb/src/class/msc/subclass/scsi/responses/inquiry.rs b/embassy-usb/src/class/msc/subclass/scsi/responses/inquiry.rs new file mode 100644 index 00000000..11b609b8 --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/responses/inquiry.rs @@ -0,0 +1,17 @@ +use super::super::enums::PeripheralQualifier; +use crate::class::msc::subclass::scsi::enums::PeripheralDeviceType; +use crate::gen_packet; + +gen_packet! { + /// Inquiry response can contain many extensions. We support only the minimum required 36 bytes. + pub struct InquiryResponse<36> { + #[offset = 0, size = 5] + peripheral_qualifier: PeripheralQualifier, + #[offset = 5, size = 3] + peripheral_device_type: PeripheralDeviceType, + } +} + +fn test() { + let packet = InquiryResponse::new(); +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/responses/mod.rs b/embassy-usb/src/class/msc/subclass/scsi/responses/mod.rs new file mode 100644 index 00000000..6c742f8b --- /dev/null +++ b/embassy-usb/src/class/msc/subclass/scsi/responses/mod.rs @@ -0,0 +1,4 @@ +// `bytes` in `#[bitfield(bytes = 6)]` causes a warning +#![allow(redundant_semicolons)] + +pub mod inquiry; diff --git a/embassy-usb/src/class/msc/transport/bulk_only/command_block_wrapper.rs b/embassy-usb/src/class/msc/transport/bulk_only/cbw.rs similarity index 100% rename from embassy-usb/src/class/msc/transport/bulk_only/command_block_wrapper.rs rename to embassy-usb/src/class/msc/transport/bulk_only/cbw.rs diff --git a/embassy-usb/src/class/msc/transport/bulk_only/command_status_wrapper.rs b/embassy-usb/src/class/msc/transport/bulk_only/csw.rs similarity index 100% rename from embassy-usb/src/class/msc/transport/bulk_only/command_status_wrapper.rs rename to embassy-usb/src/class/msc/transport/bulk_only/csw.rs diff --git a/embassy-usb/src/class/msc/transport/bulk_only/mod.rs b/embassy-usb/src/class/msc/transport/bulk_only/mod.rs index 845688dd..b38e4b76 100644 --- a/embassy-usb/src/class/msc/transport/bulk_only/mod.rs +++ b/embassy-usb/src/class/msc/transport/bulk_only/mod.rs @@ -1,15 +1,15 @@ -pub mod command_block_wrapper; -pub mod command_status_wrapper; +pub mod cbw; +pub mod csw; use core::mem::{size_of, MaybeUninit}; use embassy_usb_driver::{Direction, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use self::command_block_wrapper::CommandBlockWrapper; -use self::command_status_wrapper::{CommandStatus, CommandStatusWrapper}; +use self::cbw::CommandBlockWrapper; +use self::csw::{CommandStatus, CommandStatusWrapper}; use super::{CommandError, CommandSetHandler, DataPipeError, DataPipeIn, DataPipeOut}; use crate::class::msc::{MscProtocol, MscSubclass, USB_CLASS_MSC}; -use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; +use crate::control::{ControlHandler, InResponse, Request, RequestType}; use crate::driver::Driver; use crate::types::InterfaceNumber; use crate::Builder;