diff --git a/embassy-usb/src/class/msc/mod.rs b/embassy-usb/src/class/msc/mod.rs index 129ebbd2..50a5ec7f 100644 --- a/embassy-usb/src/class/msc/mod.rs +++ b/embassy-usb/src/class/msc/mod.rs @@ -1,12 +1,6 @@ pub mod subclass; pub mod transport; -use core::marker::PhantomData; - -use crate::driver::Driver; -use crate::types::InterfaceNumber; -use crate::Builder; - /// USB Mass Storage Class ID /// /// Section 4.3 [USB Bulk Only Transport Spec](https://www.usb.org/document-library/mass-storage-bulk-only-10) diff --git a/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs b/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs index 2f41f86d..0aeb2683 100644 --- a/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs +++ b/embassy-usb/src/class/msc/subclass/scsi/commands/control.rs @@ -8,3 +8,14 @@ // #[skip] // __: B2, // } + +use crate::packed_struct; + +packed_struct! { + pub struct Control<1> { + #[offset = 0, size = 2] + vendor_specific: u8, + #[offset = 5, size = 1] + naca: bool, + } +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs b/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs index e5a8ab85..4b10d0ba 100644 --- a/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs +++ b/embassy-usb/src/class/msc/subclass/scsi/commands/inquiry.rs @@ -15,3 +15,43 @@ // /// Control byte // pub control: Control, // } + +use super::control::Control; +use crate::class::msc::subclass::scsi::enums::{PeripheralDeviceType, PeripheralQualifier}; +use crate::packed::PackedField; +use crate::packed_struct; + +packed_struct! { + pub struct InquiryCommand<6> { + #[offset = 0, size = 8] + op_code: u8, + #[offset = 1*8, size = 1] + enable_vital_product_data: bool, + #[offset = 2*8, size = 8] + page_code: u8, + #[offset = 3*8, size = 16] + allocation_length: u16, + #[offset = 5*8, size = 8] + control: Control, + } +} + +impl InquiryCommand<[u8; InquiryCommand::SIZE]> { + pub const OPCODE: u8 = 0x12; +} + +// impl> defmt::Format for InquiryCommand { +// fn format(&self, fmt: defmt::Formatter) { +// fmt. +// } +// } + +packed_struct! { + /// 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, + } +} diff --git a/embassy-usb/src/class/msc/subclass/scsi/enums.rs b/embassy-usb/src/class/msc/subclass/scsi/enums.rs index 6df0a715..8491ff22 100644 --- a/embassy-usb/src/class/msc/subclass/scsi/enums.rs +++ b/embassy-usb/src/class/msc/subclass/scsi/enums.rs @@ -1,6 +1,6 @@ -use crate::gen_enum; +use crate::packed_enum; -gen_enum! { +packed_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. @@ -12,7 +12,7 @@ gen_enum! { } } -gen_enum! { +packed_enum! { #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum PeripheralDeviceType { /// Direct access block device (e.g., magnetic disk) diff --git a/embassy-usb/src/class/msc/subclass/scsi/mod.rs b/embassy-usb/src/class/msc/subclass/scsi/mod.rs index 18a7e571..5aec2721 100644 --- a/embassy-usb/src/class/msc/subclass/scsi/mod.rs +++ b/embassy-usb/src/class/msc/subclass/scsi/mod.rs @@ -2,10 +2,9 @@ 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::subclass::scsi::commands::inquiry::InquiryCommand; use crate::class::msc::transport::{self, CommandSetHandler}; pub struct Scsi { @@ -21,6 +20,15 @@ impl CommandSetHandler for Scsi { ) -> Result<(), transport::CommandError> { assert!(lun == 0, "LUNs are not supported"); + let op_code = cmd[0]; + match op_code { + InquiryCommand::OPCODE => { + let cmd = InquiryCommand::from_bytes(cmd); + // info!("inquiry: {:#?}", cmd); + } + _ => warn!("Unknown opcode: {}", op_code), + } + Ok(()) } diff --git a/embassy-usb/src/class/msc/subclass/scsi/packet.rs b/embassy-usb/src/class/msc/subclass/scsi/packet.rs deleted file mode 100644 index 6e176cd3..00000000 --- a/embassy-usb/src/class/msc/subclass/scsi/packet.rs +++ /dev/null @@ -1,151 +0,0 @@ -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 deleted file mode 100644 index 11b609b8..00000000 --- a/embassy-usb/src/class/msc/subclass/scsi/responses/inquiry.rs +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 6c742f8b..00000000 --- a/embassy-usb/src/class/msc/subclass/scsi/responses/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// `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/mod.rs b/embassy-usb/src/class/msc/transport/bulk_only/mod.rs index b38e4b76..d475cd95 100644 --- a/embassy-usb/src/class/msc/transport/bulk_only/mod.rs +++ b/embassy-usb/src/class/msc/transport/bulk_only/mod.rs @@ -11,7 +11,6 @@ use super::{CommandError, CommandSetHandler, DataPipeError, DataPipeIn, DataPipe use crate::class::msc::{MscProtocol, MscSubclass, USB_CLASS_MSC}; use crate::control::{ControlHandler, InResponse, Request, RequestType}; use crate::driver::Driver; -use crate::types::InterfaceNumber; use crate::Builder; const REQ_GET_MAX_LUN: u8 = 0xFE; @@ -51,7 +50,6 @@ impl ControlHandler for Control { } pub struct BulkOnlyTransport<'d, D: Driver<'d>, C: CommandSetHandler> { - msc_if: InterfaceNumber, read_ep: D::EndpointOut, write_ep: D::EndpointIn, max_packet_size: u16, @@ -77,14 +75,12 @@ impl<'d, D: Driver<'d>, C: CommandSetHandler> BulkOnlyTransport<'d, D, C> { let mut iface = func.interface(); iface.handler(control); - let msc_if = iface.interface_number(); let mut alt = iface.alt_setting(USB_CLASS_MSC, subclass as _, MscProtocol::BulkOnlyTransport as _); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); Self { - msc_if, read_ep, write_ep, max_packet_size, diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 18a92f8e..56a5be11 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -12,6 +12,7 @@ pub mod class; pub mod control; pub mod descriptor; mod descriptor_reader; +mod packed; pub mod types; use embassy_futures::select::{select, Either}; diff --git a/embassy-usb/src/packed.rs b/embassy-usb/src/packed.rs new file mode 100644 index 00000000..15dec588 --- /dev/null +++ b/embassy-usb/src/packed.rs @@ -0,0 +1,316 @@ +/// A hack to allow compile-time assertions on const parameters. +/// Gets around `can't use generic parameters from outer function` error. +/// For some reason this assert is not shown in rust-analyzer, but cargo build catches it. +macro_rules! const_assert { + ($($list:ident : $ty:ty),* => $expr:expr $(,$msg:literal)?) => {{ + struct Assert<$(const $list: usize,)*>; + impl<$(const $list: $ty,)*> Assert<$($list,)*> { + const OK: () = core::assert!($expr, $($msg)?); + } + Assert::<$($list,)*>::OK + }}; +} + +pub trait PackedField { + type Output<'a>; + + fn assert() {} + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a>; + fn set(data: &mut [u8], val: Self); +} + +impl PackedField for &[u8] { + type Output<'a> = &'a [u8]; + + fn assert() { + const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for u8 slices is not supported"); + } + + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + data + } + + fn set(data: &mut [u8], val: Self) { + data.copy_from_slice(val) + } +} + +impl PackedField for &mut [u8] { + type Output<'a> = &'a [u8]; + + fn assert() { + const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for u8 slices is not supported"); + } + + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + data + } + + fn set(data: &mut [u8], val: Self) { + data.copy_from_slice(val) + } +} + +impl PackedField for [u8; N] { + type Output<'a> = [u8; N]; + + fn assert() { + const_assert!(N: usize, SIZE: usize => SIZE == N, "Incorrect array size"); + const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for arrays is not supported"); + } + + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + data.try_into().unwrap() + } + + fn set(data: &mut [u8], val: Self) { + data.copy_from_slice(&val) + } +} + +impl PackedField for bool { + type Output<'a> = bool; + + fn assert() { + const_assert!(SIZE: usize => SIZE == 1, "bool size must equal 1"); + } + + #[inline] + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + let byte = OFFSET / 8; + let bit = OFFSET % 8; + (data[byte] & (1 << bit)) != 0 + } + + #[inline] + fn set(data: &mut [u8], val: Self) { + let byte = OFFSET / 8; + let bit = OFFSET % 8; + let mask = (val as u8) << bit; + data[byte] = mask | (data[byte] & !mask); + } +} + +impl PackedField for u8 { + type Output<'a> = u8; + + fn assert() { + const_assert!(SIZE: usize => SIZE <= 8, "u8 is not large enough"); + const_assert!(OFFSET: usize, SIZE: usize => { + let bit = OFFSET % 8; + SIZE <= (8 - bit) + }, "bit packing across byte boundary is not supported"); + } + + #[inline] + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + let byte = OFFSET / 8; + let bit = OFFSET % 8; + let mask = (0xFF >> SIZE) << bit; + (data[byte] & mask) >> bit + } + + #[inline] + fn set(data: &mut [u8], val: Self) { + let byte = OFFSET / 8; + let bit = OFFSET % 8; + let mask = (0xFF >> SIZE) << bit; + data[byte] = (val << bit) | (data[byte] & !mask); + } +} + +macro_rules! impl_packed_field_int { + ($ty:ty, $size:literal) => { + impl PackedField for $ty { + type Output<'a> = $ty; + + fn assert() { + const_assert!(SIZE: usize => SIZE <= $size, "type is not large enough"); + // most protocols only use bit packing at byte (u8) boundaries, so this is okay for now + const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for this type is not supported"); + } + + #[inline] + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + let byte = OFFSET / 8; + unsafe { *(data[byte..].as_ptr() as *const $ty)} + } + + #[inline] + fn set(data: &mut [u8], val: Self) { + let byte = OFFSET / 8; + unsafe { *(data[byte..].as_ptr() as *mut $ty) = val; } + } + } + }; +} + +impl_packed_field_int!(u16, 16); +impl_packed_field_int!(u32, 32); +impl_packed_field_int!(u64, 64); + +impl_packed_field_int!(i8, 8); +impl_packed_field_int!(i16, 16); +impl_packed_field_int!(i32, 32); +impl_packed_field_int!(i64, 64); + +#[macro_export] +macro_rules! packed_struct { + ( + $(#[$meta:meta])* + $sv:vis struct $name:ident<$size:literal> { + $( + #[offset = $offset:expr, size = $bit_size:expr] + $field:ident: $ty:ty, + )* + } + ) => { + $(#[$meta])* + $sv struct $name> { + pub data: T + } + + impl $name<[u8; $size]> { + const SIZE: usize = $size; + + pub fn new() -> Self { + Self { + data: [0u8; Self::SIZE] + } + } + } + + impl<'d, T: AsRef<[u8]> + crate::packed::PackedField = T> + 'd> $name { + pub const unsafe fn from_bytes_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::from_bytes_unchecked(buf) }) + } + } + + $( + #[inline] + pub fn $field(&self) -> <$ty as crate::packed::PackedField>::Output<'d> { + const _: () = core::assert!($offset + $bit_size <= $size * 8, "Field offset is out of range"); + <$ty as crate::packed::PackedField>::assert::<{$offset}, {$bit_size}>(); + <$ty as crate::packed::PackedField>::get::<{$offset}, {$bit_size}>(self.data.as_ref()) + } + )* + } + + impl<'d, T: AsRef<[u8]> + AsMut<[u8]> + crate::packed::PackedField = T> + 'd> $name { + $( + paste::paste! { + #[inline] + pub fn [](&mut self, val: $ty) { + const _: () = core::assert!($offset + $bit_size <= $size * 8, "Field offset is out of range"); + <$ty as crate::packed::PackedField>::assert::<{$offset}, {$bit_size}>(); + <$ty as crate::packed::PackedField>::set::<{$offset}, {$bit_size}>(self.data.as_mut(), val) + } + } + )* + } + + impl + for<'a> crate::packed::PackedField = T>> crate::packed::PackedField for $name { + type Output<'a> = Self; + + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + ::assert::(); + let val = ::get::(data); + unsafe { Self::from_bytes_unchecked(val) } + } + + fn set(data: &mut [u8], val: Self) { + ::assert::(); + ::set::(data, val.data); + } + } + + impl + for<'a> crate::packed::PackedField = T>> core::fmt::Debug for $name { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct(stringify!($name)) + $( + .field(stringify!($field), &self.$field()) + )* + .finish() + } + } + } +} + +// packed_struct!(pub struct Test<8> { +// #[offset = 8 * 6, size = 8] +// test: u8, +// }); + +// pub fn test() -> u8 { +// let t = Test::new(); +// t.test() +// } + +#[macro_export] +macro_rules! packed_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::packed::PackedField for $name { + type Output<'a> = Result; + + #[inline] + fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> { + <$ty as crate::packed::PackedField>::assert::(); + let val = <$ty as crate::packed::PackedField>::get::(data); + Self::try_from(val) + } + + #[inline] + fn set(data: &mut [u8], val: Self) { + <$ty as crate::packed::PackedField>::assert::(); + <$ty as crate::packed::PackedField>::set::(data, val.into()); + } + } + }; +} + +// gen_enum! { +// pub enum Testas { +// Hello = 0b111, +// Test = 0b1111, +// } +// }