├── .gitignore ├── command-components ├── enumerate-devices-go │ ├── .gitignore │ ├── go.mod │ ├── build.sh │ ├── main.go │ └── api │ │ ├── bindings_types.go │ │ └── bindings.h ├── mass-storage │ ├── .cargo │ │ └── config.toml │ ├── src │ │ ├── bulk_only │ │ │ ├── mod.rs │ │ │ ├── wasm.rs │ │ │ └── native.rs │ │ ├── main.rs │ │ ├── lib.rs │ │ └── mass_storage.rs │ └── Cargo.toml ├── wasi_snapshot_preview1.command.wasm ├── lsusb │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── control │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── enumerate-devices-rust │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── xbox │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── xbox-maze │ ├── Cargo.toml │ └── src │ │ └── main.rs └── ping │ ├── Cargo.toml │ └── src │ └── main.rs ├── perf.data ├── perf.data.old ├── wit ├── deps │ └── usb │ │ ├── imports.wit │ │ ├── types.wit │ │ ├── descriptors.wit │ │ └── device.wit └── bindings.wit ├── usb-wasm-bindings ├── src │ ├── lib.rs │ └── traits.rs ├── Cargo.toml ├── README.md └── regenerate-bindings.sh ├── usb-wasm ├── src │ ├── error.rs │ ├── host.rs │ └── lib.rs └── Cargo.toml ├── wasmtime-usb ├── Cargo.toml └── src │ ├── lib.rs │ └── bin │ └── wasmtime-usb.rs ├── Cargo.toml ├── README.md ├── Justfile └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | out/ 3 | .vscode/ 4 | artifacts/ -------------------------------------------------------------------------------- /command-components/enumerate-devices-go/.gitignore: -------------------------------------------------------------------------------- 1 | out/main.wasm -------------------------------------------------------------------------------- /perf.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlab-discover/usb-wasm/HEAD/perf.data -------------------------------------------------------------------------------- /command-components/enumerate-devices-go/go.mod: -------------------------------------------------------------------------------- 1 | module example.com 2 | 3 | go 1.22.2 4 | -------------------------------------------------------------------------------- /command-components/mass-storage/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # target = "wasm32-wasi" -------------------------------------------------------------------------------- /perf.data.old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlab-discover/usb-wasm/HEAD/perf.data.old -------------------------------------------------------------------------------- /wit/deps/usb/imports.wit: -------------------------------------------------------------------------------- 1 | package wadu436:usb@0.0.1; 2 | 3 | world imports { 4 | import device; 5 | } -------------------------------------------------------------------------------- /wit/bindings.wit: -------------------------------------------------------------------------------- 1 | package wadu436:usb-bindings; 2 | 3 | world bindings { 4 | include wadu436:usb/imports@0.0.1; 5 | } -------------------------------------------------------------------------------- /usb-wasm-bindings/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod bindings; 2 | 3 | #[cfg(feature = "traits")] 4 | mod traits; 5 | 6 | pub use bindings::wadu436::usb::*; 7 | -------------------------------------------------------------------------------- /command-components/wasi_snapshot_preview1.command.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlab-discover/usb-wasm/HEAD/command-components/wasi_snapshot_preview1.command.wasm -------------------------------------------------------------------------------- /usb-wasm/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum UsbWasmError { 5 | #[error("rusb error")] 6 | RusbError(#[from] rusb::Error), 7 | #[error("device not opened")] 8 | DeviceNotOpened, 9 | } 10 | -------------------------------------------------------------------------------- /command-components/mass-storage/src/bulk_only/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_arch = "wasm32"))] 2 | mod native; 3 | #[cfg(not(target_arch = "wasm32"))] 4 | pub use native::*; 5 | 6 | #[cfg(target_arch = "wasm32")] 7 | mod wasm; 8 | #[cfg(target_arch = "wasm32")] 9 | pub use wasm::*; 10 | -------------------------------------------------------------------------------- /usb-wasm-bindings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb-wasm-bindings" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | wit-bindgen = "0.24.0" 10 | 11 | [features] 12 | traits = [] -------------------------------------------------------------------------------- /usb-wasm-bindings/README.md: -------------------------------------------------------------------------------- 1 | # usb-wasm-bindings 2 | 3 | Rust bindings for the USB WASM Component 4 | 5 | Based on the [WASI 0.2 Rust bindings](https://github.com/bytecodealliance/wasi) 6 | 7 | Run `./regenerate-bindings.sh` to regenerate the bindings (Alternatively, run `just regenerate-bindings` from the root crate). -------------------------------------------------------------------------------- /command-components/lsusb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lsusb" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | usb-wasm-bindings = { workspace = true } -------------------------------------------------------------------------------- /command-components/control/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "control" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | usb-wasm-bindings = { workspace = true, features = ["traits"]} -------------------------------------------------------------------------------- /command-components/enumerate-devices-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "enumerate-devices-rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | usb-wasm-bindings = { workspace = true } -------------------------------------------------------------------------------- /command-components/xbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xbox" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | byteorder = "1.5.0" 10 | colored = "2.0.4" 11 | anyhow = { workspace = true } 12 | usb-wasm-bindings = { workspace = true, features = ["traits"]} -------------------------------------------------------------------------------- /usb-wasm-bindings/regenerate-bindings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Based on the regenerate script from the Rust WASI 0.2 bindings 4 | # https://github.com/bytecodealliance/wasi/blob/main/ci/regenerate.sh 5 | 6 | set -ex 7 | 8 | cargo install --locked wit-bindgen-cli@0.24.0 9 | wit-bindgen rust ../wit --out-dir ./src --std-feature --type-section-suffix rust-wasi-from-crates-io 10 | cargo fmt -------------------------------------------------------------------------------- /command-components/xbox-maze/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xbox-maze" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | byteorder = "1.5.0" 10 | colored = "2.0.4" 11 | anyhow = { workspace = true } 12 | usb-wasm-bindings = { workspace = true, features = ["traits"] } 13 | -------------------------------------------------------------------------------- /command-components/enumerate-devices-go/build.sh: -------------------------------------------------------------------------------- 1 | mkdir -p out 2 | go generate 3 | tinygo build -target=wasi -o ./out/main.wasm main.go 4 | wasm-tools component embed --world bindings ../../wit ./out/main.wasm -o ./out/main.embed.wasm # create a component 5 | wasm-tools component new ./out/main.embed.wasm --adapt ../wasi_snapshot_preview1.command.wasm -o ./out/main.component.wasm 6 | wasm-tools validate ./out/main.component.wasm --features component-model -------------------------------------------------------------------------------- /usb-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rusb = "0.9.3" 10 | thiserror = "1.0.58" 11 | tracing = "0.1.40" 12 | wasmtime = { workspace = true, features = ["component-model"] } 13 | wasmtime-wasi = { workspace = true } 14 | 15 | [dev-dependencies] 16 | anyhow = "1.0.44" 17 | -------------------------------------------------------------------------------- /command-components/ping/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ping" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = { workspace = true } 10 | 11 | [target.'cfg(target_arch = "wasm32")'.dependencies] 12 | usb-wasm-bindings = { workspace = true, features = ["traits"] } 13 | 14 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 15 | rusb = "0.9.4" -------------------------------------------------------------------------------- /command-components/enumerate-devices-rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use usb_wasm_bindings::device::UsbDevice; 2 | 3 | pub fn main() -> anyhow::Result<()> { 4 | for device in UsbDevice::enumerate() { 5 | let descriptor = device.descriptor(); 6 | println!( 7 | "{:#04x}:{:#04x} - {} {}", 8 | descriptor.vendor_id, 9 | descriptor.product_id, 10 | descriptor.manufacturer_name.unwrap_or("N/A".to_owned()), 11 | descriptor.product_name.unwrap_or("N/A".to_owned()), 12 | ); 13 | } 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /wasmtime-usb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasmtime-usb-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | anyhow = { workspace = true } 12 | clap = { version = "4.5.4", features = ["derive"] } 13 | serde = { version = "1.0.193", features = ["derive"] } 14 | tracing = "0.1.40" 15 | tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } 16 | usb-wasm = { workspace = true } 17 | wasmtime = { workspace = true } 18 | wasmtime-wasi = { workspace = true } 19 | 20 | -------------------------------------------------------------------------------- /usb-wasm-bindings/src/traits.rs: -------------------------------------------------------------------------------- 1 | // Some additional convenience trait implementations for types from the WIT interfaces 2 | use crate::bindings::wadu436::usb::types::Filter; 3 | 4 | // We implement this here instead because the bindings.rs file is autogenerated 5 | #[allow(clippy::derivable_impls)] 6 | impl Default for Filter { 7 | fn default() -> Self { 8 | Self { 9 | vendor_id: Default::default(), 10 | product_id: Default::default(), 11 | class_code: Default::default(), 12 | subclass_code: Default::default(), 13 | protocol_code: Default::default(), 14 | serial_number: Default::default(), 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "command-components/control", 4 | "command-components/lsusb", 5 | "command-components/ping", 6 | "command-components/xbox", 7 | "command-components/xbox-maze", 8 | "command-components/mass-storage", 9 | "command-components/enumerate-devices-rust", 10 | "wasmtime-usb", 11 | "usb-wasm", 12 | "usb-wasm-bindings", 13 | ] 14 | default-members = ["wasmtime-usb"] 15 | resolver = "2" 16 | 17 | [workspace.dependencies] 18 | usb-wasm = { path = "usb-wasm" } 19 | usb-wasm-bindings = { path = "usb-wasm-bindings" } 20 | 21 | anyhow = "1.0.75" 22 | wasmtime = "20.0.1" 23 | wasmtime-wasi = "20.0.1" 24 | 25 | [profile.release] 26 | strip = true 27 | 28 | [profile.dev.package."*"] 29 | debug = false 30 | opt-level = 3 31 | -------------------------------------------------------------------------------- /command-components/enumerate-devices-go/main.go: -------------------------------------------------------------------------------- 1 | // my-component.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | api "example.com/api" 8 | ) 9 | 10 | //go:generate wit-bindgen tiny-go ../../wit --out-dir=api 11 | func main() { 12 | devices := api.StaticUsbDeviceEnumerate() 13 | for _, device := range devices { 14 | descriptor := device.Descriptor() 15 | vendorId := descriptor.VendorId 16 | productId := descriptor.ProductId 17 | productName := "N/A" 18 | if descriptor.ProductName.IsSome() { 19 | productName = descriptor.ProductName.Unwrap() 20 | } 21 | manufacturerName := "N/A" 22 | if descriptor.ManufacturerName.IsSome() { 23 | manufacturerName = descriptor.ManufacturerName.Unwrap() 24 | } 25 | fmt.Printf("%04x:%04x - %s %s\n", vendorId, productId, manufacturerName, productName) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /command-components/mass-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mass-storage" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | 10 | [[bin]] 11 | name = "mass-storage" 12 | 13 | [dependencies] 14 | ahash = "0.8.11" 15 | anyhow = { workspace = true } 16 | bytes = "1.5.0" 17 | chrono = "0.4.37" 18 | clap = { version = "4.5.4", features = ["derive"] } 19 | fatfs = "0.3.6" 20 | fscommon = "0.1.1" 21 | mbrman = "0.5.2" 22 | rand = "0.8.5" 23 | thiserror = "1.0.58" 24 | tracing = "0.1.40" 25 | tracing-subscriber = "0.3.18" 26 | uluru = "3.0.0" 27 | 28 | [target.'cfg(target_arch = "wasm32")'.dependencies] 29 | usb-wasm-bindings = { workspace = true, features = ["traits"] } 30 | 31 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 32 | rusb = "0.9.3" 33 | -------------------------------------------------------------------------------- /command-components/control/src/main.rs: -------------------------------------------------------------------------------- 1 | use usb_wasm_bindings::{ 2 | device::UsbDevice, 3 | types::{ControlSetup, ControlSetupRecipient, ControlSetupType, Filter}, 4 | }; 5 | 6 | use anyhow::anyhow; 7 | 8 | pub fn main() -> anyhow::Result<()> { 9 | let arduino_usb = UsbDevice::request_device(&Filter { 10 | vendor_id: Some(0x2341), 11 | product_id: Some(0x8057), 12 | ..Default::default() 13 | }) 14 | .ok_or(anyhow!("Arduino not found."))?; 15 | 16 | // Open device 17 | arduino_usb.open(); 18 | 19 | // GET_DESCRIPTOR request https://www.beyondlogic.org/usbnutshell/usb6.shtml 20 | let response = arduino_usb.read_control( 21 | ControlSetup { 22 | request_type: ControlSetupType::Standard, 23 | request_recipient: ControlSetupRecipient::Device, 24 | request: 0x06, 25 | value: 0x0100, 26 | index: 0, 27 | }, 28 | 64, 29 | ); 30 | 31 | println!("Device Descriptor: {:?}", response); 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /wasmtime-usb/src/lib.rs: -------------------------------------------------------------------------------- 1 | use wasmtime_wasi::{DirPerms, FilePerms, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; 2 | 3 | pub struct HostState { 4 | wasi_ctx: WasiCtx, 5 | wasi_table: ResourceTable, 6 | } 7 | 8 | impl HostState { 9 | pub fn new(args: &[impl AsRef], preopen: Option) -> Self { 10 | let mut wasi_ctx = WasiCtxBuilder::new(); 11 | wasi_ctx.inherit_stdio().args(args); 12 | 13 | if let Some(preopen) = preopen { 14 | wasi_ctx.preopened_dir( 15 | preopen.as_str(), 16 | preopen.as_str(), 17 | DirPerms::all(), 18 | FilePerms::all(), 19 | ).unwrap(); 20 | } 21 | 22 | let wasi_table = ResourceTable::new(); 23 | Self { 24 | wasi_ctx: wasi_ctx.build(), 25 | wasi_table, 26 | } 27 | } 28 | } 29 | 30 | impl WasiView for HostState { 31 | fn table(&mut self) -> &mut ResourceTable { 32 | &mut self.wasi_table 33 | } 34 | 35 | fn ctx(&mut self) -> &mut WasiCtx { 36 | &mut self.wasi_ctx 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /wit/deps/usb/types.wit: -------------------------------------------------------------------------------- 1 | package wadu436:usb@0.0.1; 2 | 3 | // TODO: Create an error type 4 | 5 | interface types { 6 | type version = tuple; 7 | 8 | // Record for passing into the request-device convenience function. 9 | record filter { 10 | vendor-id: option, 11 | product-id: option, 12 | class-code: option, 13 | subclass-code: option, 14 | protocol-code: option, 15 | serial-number: option, 16 | } 17 | 18 | // Endpoint transfer types 19 | enum transfer-type { 20 | control, 21 | isochronous, 22 | bulk, 23 | interrupt, 24 | } 25 | 26 | // Endpoint direction 27 | enum direction { 28 | out, 29 | in, 30 | } 31 | 32 | // Device speed 33 | enum speed { 34 | unknown, // OS doesn't know which speed the device is using 35 | low, // 1.5 Mbit/s 36 | full, // 12 Mbit/s 37 | high, // 480 Mbit/s 38 | super, // 5 Gbit/s 39 | superplus, // 10 Gbit/s 40 | } 41 | 42 | // Setup type for control transfers 43 | enum control-setup-type { 44 | standard, 45 | class, 46 | vendor, 47 | // reserved? 48 | } 49 | 50 | // Recipient for control transfers 51 | enum control-setup-recipient { 52 | device, 53 | %interface, // interface, but that's a keyword 54 | endpoint, 55 | // other? 56 | } 57 | 58 | // Control setup packet 59 | record control-setup { 60 | request-type: control-setup-type, 61 | request-recipient: control-setup-recipient, 62 | request: u8, // bRequest 63 | value: u16, // wValue 64 | index: u16, // wIndex 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /wit/deps/usb/descriptors.wit: -------------------------------------------------------------------------------- 1 | package wadu436:usb@0.0.1; 2 | 3 | interface descriptors { 4 | use types.{version, direction, transfer-type}; 5 | 6 | // Contains the fields of the device descriptor as defined in the USB 2.0 spec 7 | record device-descriptor { 8 | product-name: option, 9 | manufacturer-name: option, 10 | serial-number: option, 11 | 12 | usb-version: version, 13 | 14 | vendor-id: u16, 15 | product-id: u16, 16 | device-version: version, 17 | 18 | device-class: u8, 19 | device-subclass: u8, 20 | device-protocol: u8, 21 | 22 | max-packet-size: u8, 23 | } 24 | 25 | // Contains the fields of the configuration descriptor 26 | record configuration-descriptor { 27 | number: u8, 28 | description: option, 29 | self-powered: bool, 30 | remote-wakeup: bool, 31 | max-power: u16, // in milliAmps !NOTE: this is not the same as the USB spec, which uses 2mA units 32 | } 33 | 34 | // Contains the fields of the interface descriptor 35 | record interface-descriptor { 36 | interface-number: u8, 37 | alternate-setting: u8, 38 | interface-class: u8, 39 | interface-subclass: u8, 40 | interface-protocol: u8, 41 | interface-name: option, 42 | } 43 | 44 | // Contains the fields of the endpoint descriptor 45 | record endpoint-descriptor { 46 | endpoint-number: u8, // 0-15, lower 4 bits of the bEndpointAddress field 47 | direction: direction, // corresponds to bit 7 of the bEndpointAddress field 48 | transfer-type: transfer-type, // corresponds to bits 0-1 of the bmAttributes field 49 | max-packet-size: u16, 50 | interval: u8, // for interrupt and isochronous endpoints 51 | } 52 | } -------------------------------------------------------------------------------- /command-components/mass-storage/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use mass_storage::{benchmark, benchmark_raw_speed, cat, ls, tree}; 3 | use tracing::Level; 4 | 5 | use anyhow::anyhow; 6 | 7 | #[derive(Parser, Debug)] 8 | #[command(version, about, long_about = None)] 9 | struct Args { 10 | /// Number of times to greet 11 | #[clap(subcommand)] 12 | command: Command, 13 | } 14 | 15 | #[derive(Subcommand, Debug)] 16 | enum Command { 17 | #[clap(about = "Print a tree of the filesystem")] 18 | Tree { path: Vec }, 19 | #[clap(about = "List files in the filesystem")] 20 | Ls { path: Vec }, 21 | #[clap(about = "Read a file from the filesystem")] 22 | Cat { path: Vec }, 23 | #[clap(about = "Benchmark speed of writing/reading to/from the filesystem")] 24 | Benchmark { megabytes: usize }, 25 | // THIS WILL WRITE BLOCKS DIRECTLY TO THE DEVICE AND WILL DESTROY YOUR PARTITION 26 | #[clap( 27 | about = "Benchmark raw speed of mass storage device. WARNING: THIS WILL WRITE BLOCKS DIRECTLY TO THE DEVICE AND WILL DESTROY YOUR PARTITION" 28 | )] 29 | RawBenchmark { 30 | seq_megabytes: usize, 31 | rnd_megabytes: usize, 32 | samples: usize, 33 | }, 34 | // TODO: Copy 35 | } 36 | 37 | fn vec_to_opt_str(vec: Vec) -> Option { 38 | if vec.is_empty() { 39 | None 40 | } else { 41 | Some(vec.join(" ")) 42 | } 43 | } 44 | 45 | pub fn main() -> anyhow::Result<()> { 46 | // Set up logging 47 | tracing_subscriber::fmt().with_max_level(Level::INFO).init(); 48 | 49 | let args = Args::parse(); 50 | 51 | match args.command { 52 | Command::Tree { path } => tree(vec_to_opt_str(path))?, 53 | Command::Ls { path } => ls(vec_to_opt_str(path))?, 54 | Command::Cat { path } => cat(vec_to_opt_str(path).ok_or(anyhow!("No file specified"))?)?, 55 | Command::Benchmark { megabytes } => benchmark(megabytes)?, 56 | Command::RawBenchmark { 57 | seq_megabytes, 58 | rnd_megabytes, 59 | samples, 60 | } => benchmark_raw_speed(1, seq_megabytes, rnd_megabytes, samples)?, 61 | } 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /command-components/enumerate-devices-go/api/bindings_types.go: -------------------------------------------------------------------------------- 1 | package bindings 2 | 3 | // inspired from https://github.com/moznion/go-optional 4 | 5 | type optionKind int 6 | 7 | const ( 8 | none optionKind = iota 9 | some 10 | ) 11 | 12 | type Option[T any] struct { 13 | kind optionKind 14 | val T 15 | } 16 | 17 | // IsNone returns true if the option is None. 18 | func (o Option[T]) IsNone() bool { 19 | return o.kind == none 20 | } 21 | 22 | // IsSome returns true if the option is Some. 23 | func (o Option[T]) IsSome() bool { 24 | return o.kind == some 25 | } 26 | 27 | // Unwrap returns the value if the option is Some. 28 | func (o Option[T]) Unwrap() T { 29 | if o.kind != some { 30 | panic("Option is None") 31 | } 32 | return o.val 33 | } 34 | 35 | // Set sets the value and returns it. 36 | func (o *Option[T]) Set(val T) T { 37 | o.kind = some 38 | o.val = val 39 | return val 40 | } 41 | 42 | // Unset sets the value to None. 43 | func (o *Option[T]) Unset() { 44 | o.kind = none 45 | } 46 | 47 | // Some is a constructor for Option[T] which represents Some. 48 | func Some[T any](v T) Option[T] { 49 | return Option[T]{ 50 | kind: some, 51 | val: v, 52 | } 53 | } 54 | 55 | // None is a constructor for Option[T] which represents None. 56 | func None[T any]() Option[T] { 57 | return Option[T]{ 58 | kind: none, 59 | } 60 | } 61 | 62 | type ResultKind int 63 | 64 | const ( 65 | resultOk ResultKind = iota 66 | resultErr 67 | ) 68 | 69 | type Result[T any, E any] struct { 70 | kind ResultKind 71 | resultOk T 72 | resultErr E 73 | } 74 | 75 | // IsOk returns true if the result is Ok. 76 | func (r Result[T, E]) IsOk() bool { 77 | return r.kind == resultOk 78 | } 79 | 80 | // IsErr returns true if the result is Err. 81 | func (r Result[T, E]) IsErr() bool { 82 | return r.kind == resultErr 83 | } 84 | 85 | // Unwrap returns the value if the result is Ok. 86 | func (r Result[T, E]) Unwrap() T { 87 | if r.kind != resultOk { 88 | panic("Result is Err") 89 | } 90 | return r.resultOk 91 | } 92 | 93 | // UnwrapErr returns the value if the result is Err. 94 | func (r Result[T, E]) UnwrapErr() E { 95 | if r.kind != resultErr { 96 | panic("Result is Ok") 97 | } 98 | return r.resultErr 99 | } 100 | 101 | // Set sets the value and returns it. 102 | func (r *Result[T, E]) Set(val T) T { 103 | r.kind = resultOk 104 | r.resultOk = val 105 | return val 106 | } 107 | 108 | // SetErr sets the value and returns it. 109 | func (r *Result[T, E]) SetErr(val E) E { 110 | r.kind = resultErr 111 | r.resultErr = val 112 | return val 113 | } 114 | 115 | // Ok is a constructor for Result[T, E] which represents Ok. 116 | func Ok[T any, E any](v T) Result[T, E] { 117 | return Result[T, E]{ 118 | kind: resultOk, 119 | resultOk: v, 120 | } 121 | } 122 | 123 | // Err is a constructor for Result[T, E] which represents Err. 124 | func Err[T any, E any](v E) Result[T, E] { 125 | return Result[T, E]{ 126 | kind: resultErr, 127 | resultErr: v, 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USB-WASM 2 | 3 | Prototype design and implementation of an interface for accessing and communicating with USB devices from a WebAssembly Component. This project forms part of my Master's thesis. 4 | 5 | ## Repository structure 6 | 7 | - [`./wit/`](./wit/) contains the interface definition in the WIT IDL. 8 | - [`./usb-wasm`](./usb-wasm/) contains the host implementation of the interface for Wasmtime. 9 | - [`./usb-wasm-bindings`](./usb-wasm-bindings/) contains the automatically generated Rust bindings. This crate is used in the example/test command components. 10 | - [`./command-components`](./command-components/) contains a couple of command components implemented using the interface. 11 | - [`./wasmtime-usb`](./wasmtime-usb/) implements a Component Model-enabled Wasmtime CLI application to run command components that use the interface. 12 | 13 | ## Requirements 14 | 15 | - Rust 16 | - [wasm-tools](https://github.com/bytecodealliance/wasm-tools) 17 | - [just](https://just.systems/) 18 | 19 | ## Building 20 | 21 | We use `just` as build system. You can see all supported commands by running `just --list`. You can build the modified wasmtime runtime with the following command. 22 | 23 | ```shell 24 | just build-runtime 25 | ``` 26 | 27 | You can also use `just` to build any of the guest components (command components). For example, to build the xbox pacman demo, run the following. 28 | 29 | ```shell 30 | just build-xbox-maze 31 | ``` 32 | 33 | Afterwards, you can run this demo using the following command. 34 | 35 | ```shell 36 | sudo ./target/debug/wasmtime-usb out/xbox-maze.wasm 37 | ``` 38 | 39 | ## Building components manually 40 | 41 | Instead of using `just`, you can also build the components manually using the following method. 42 | 43 | Because Rust can only target WASI preview 1 right now, the compiled WASM binaries first need to be transformed into command components before they can be run by the Component Model-enabled Wasmtime CLI. 44 | 45 | `wasm-tools` is used to 'adapt' the WASM binaries compiled by rustc into command components. 46 | 47 | For example, to run the `lsusb` command component, the following commands have to be executed: 48 | 49 | ```shell 50 | > cargo build -p lsusb --target=wasm32-wasi 51 | > wasm-tools component new ./target/wasm32-wasi/debug/lsusb.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/lsusb.wasm 52 | > cargo run -- ./out/lsusb.wasm 53 | ``` 54 | 55 | If you have the `just` command runner installed, you can just run `just lsusb`, which will perform all these steps for you. 56 | 57 | ## Best xbox pacman performance 58 | 59 | Since the game uses CLI output, it needs to write this line per line. This might cause some flickering. For best performance, use Alacritty with a font size of 30 and in fullscreen. 60 | 61 | ## Funding information 62 | 63 | This work has been partially supported by the ELASTIC project, which received funding from the Smart Networks and Services Joint Undertaking (SNS JU) under the European Union’s Horizon Europe research and innovation programme under Grant Agreement No 101139067. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union. Neither the European Union nor the granting authority can be held responsible for them. 64 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | lsusb: 2 | just build-lsusb 3 | cargo run -- ./out/lsusb.wasm 4 | 5 | xbox: 6 | just build-xbox 7 | cargo run -- ./out/xbox.wasm 8 | 9 | ping *arg: 10 | just build-ping 11 | cargo run --release -- --dir=. ./out/ping.wasm -- {{arg}} 12 | 13 | control: 14 | just build-control 15 | cargo run -- ./out/control.wasm 16 | 17 | mass-storage *arg: 18 | just build-mass-storage 19 | cargo run --release -- --dir=. ./out/mass-storage.wasm -- {{arg}} 20 | 21 | enumerate-devices-go: 22 | just build-enumerate-devices-go 23 | cargo run -- ./command-components/enumerate-devices-go/out/main.component.wasm 24 | 25 | enumerate-devices-rust: 26 | just build-enumerate-devices-rust 27 | cargo run -- ./out/enumerate-devices-rust.wasm 28 | 29 | flamegraph-mass-storage: 30 | just build-mass-storage 31 | cargo flamegraph --no-inline --bin wasmtime-usb -- ./out/mass-storage.wasm benchmark 32 | 33 | perf-mass-storage: 34 | just build-mass-storage 35 | cargo build --release 36 | perf record --call-graph dwarf -k mono ./target/release/wasmtime-usb --profile ./out/mass-storage.wasm benchmark 37 | 38 | build-runtime: 39 | cargo build 40 | 41 | build-lsusb: 42 | just regenerate-bindings 43 | cargo build -p lsusb --target=wasm32-wasip1 44 | wasm-tools component new ./target/wasm32-wasip1/debug/lsusb.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/lsusb.wasm 45 | 46 | build-xbox: 47 | just regenerate-bindings 48 | cargo build -p xbox --target=wasm32-wasip1 49 | wasm-tools component new ./target/wasm32-wasip1/debug/xbox.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/xbox.wasm 50 | 51 | build-xbox-maze: 52 | just regenerate-bindings 53 | cargo build -p xbox-maze --target=wasm32-wasip1 54 | wasm-tools component new ./target/wasm32-wasip1/debug/xbox-maze.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/xbox-maze.wasm 55 | 56 | build-ping: 57 | just regenerate-bindings 58 | cargo build -p ping --release --target=wasm32-wasip1 59 | wasm-tools component new ./target/wasm32-wasip1/release/ping.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/ping.wasm 60 | 61 | build-control: 62 | just regenerate-bindings 63 | cargo build -p control --target=wasm32-wasip1 64 | wasm-tools component new ./target/wasm32-wasip1/debug/control.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/control.wasm 65 | 66 | build-mass-storage: 67 | just regenerate-bindings 68 | cargo build -p mass-storage --release --target=wasm32-wasip1 69 | wasm-tools component new ./target/wasm32-wasip1/release/mass-storage.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/mass-storage.wasm 70 | 71 | build-enumerate-devices-rust: 72 | just regenerate-bindings 73 | cargo build -p enumerate-devices-rust --release --target=wasm32-wasip1 74 | wasm-tools component new ./target/wasm32-wasip1/release/enumerate-devices-rust.wasm --adapt ./command-components/wasi_snapshot_preview1.command.wasm -o out/enumerate-devices-rust.wasm 75 | 76 | build-enumerate-devices-go: 77 | cd command-components/enumerate-devices-go && ./build.sh 78 | 79 | verify: 80 | wit-bindgen markdown wit/ --out-dir ./out/wit-md/ 81 | 82 | regenerate-bindings: 83 | cd usb-wasm-bindings && ./regenerate-bindings.sh 84 | -------------------------------------------------------------------------------- /wasmtime-usb/src/bin/wasmtime-usb.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use clap::Parser; 3 | use tracing_subscriber::EnvFilter; 4 | use usb_wasm::error::UsbWasmError; 5 | use wasmtime::component::{Component, Linker}; 6 | use wasmtime::{Config, Engine, Store}; 7 | use wasmtime_usb_cli::HostState; 8 | use wasmtime_wasi::{I32Exit, WasiView}; 9 | 10 | #[derive(Parser, Debug)] 11 | #[command(version, about, long_about = None)] 12 | struct Args { 13 | #[clap(short, long)] 14 | dir: Option, 15 | #[clap(short, long)] 16 | profile: bool, 17 | command: String, 18 | #[clap(trailing_var_arg = true, allow_hyphen_values = true)] 19 | command_args: Vec, 20 | } 21 | 22 | fn main() -> anyhow::Result<()> { 23 | // Set up logging 24 | tracing_subscriber::fmt() 25 | .with_env_filter(EnvFilter::from_default_env()) 26 | .init(); 27 | 28 | let args = Args::parse(); 29 | 30 | let command_component_path = std::path::Path::new(args.command.as_str()); 31 | 32 | // Configure an `Engine` and link in all the host components (Wasi preview 2 and our USB component) 33 | 34 | let config = { 35 | let mut config = Config::new(); 36 | config.wasm_component_model(true); 37 | if args.profile { 38 | config.profiler(wasmtime::ProfilingStrategy::PerfMap); 39 | } 40 | config 41 | }; 42 | let engine = Engine::new(&config)?; 43 | let mut linker: Linker = wasmtime::component::Linker::new(&engine); 44 | register_host_components(&mut linker)?; 45 | 46 | // Set up the Store with the command line arguments 47 | let mut command_args = args.command_args; 48 | command_args.insert(0, args.command.clone()); 49 | let mut store = Store::new(&engine, HostState::new(&command_args, args.dir)); 50 | 51 | // Load the component (should be an instance of the wasi command component) 52 | let component = Component::from_file(&engine, command_component_path)?; 53 | 54 | let (command, _instance) = 55 | wasmtime_wasi::bindings::sync::Command::instantiate(&mut store, &component, &linker)?; 56 | let result = command.wasi_cli_run().call_run(&mut store); 57 | 58 | match result { 59 | Ok(Ok(())) => Ok(()), 60 | Ok(Err(())) => Err(anyhow!("inner error")), // IDK HOW THIS IS CAUSED 61 | Err(e) => { 62 | if let Some(source) = e.source() { 63 | if let Some(exit_code) = source.downcast_ref::() { 64 | std::process::exit(exit_code.process_exit_code()); 65 | // return Err(exit_code.into()); 66 | } 67 | if let Some(error) = source.downcast_ref::() { 68 | match error { 69 | UsbWasmError::RusbError(err) => { 70 | println!("{}", err); 71 | } 72 | _ => { 73 | println!("{}", error); 74 | } 75 | } 76 | // return Err(exit_code.into()); 77 | } 78 | println!("Source: {}", source); 79 | } 80 | println!("e: {}", e); 81 | Ok(()) 82 | } 83 | } 84 | } 85 | 86 | fn register_host_components(linker: &mut Linker) -> anyhow::Result<()> { 87 | wasmtime_wasi::add_to_linker_sync(linker)?; 88 | usb_wasm::add_to_linker(linker)?; 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /wit/deps/usb/device.wit: -------------------------------------------------------------------------------- 1 | package wadu436:usb@0.0.1; 2 | 3 | // Open questions/TODOs: 4 | // TODO: Hotplug support? How to write a decent interface for that? Maybe wait for async primitives in preview 3? 5 | // TODO: how to handle timeouts? WebUSB doesn't have them either. see https://stackoverflow.com/questions/65424624/does-webusb-support-timeouts and https://github.com/WICG/webusb/issues/25 6 | // TODO: async support? (probably wait for preview 3) 7 | // TODO: should transfer functions, ... take a resource to the endpoint/interface/configuration, or a raw u8? See README for more info 8 | 9 | interface device { 10 | use descriptors.{device-descriptor, configuration-descriptor, interface-descriptor, endpoint-descriptor}; 11 | use types.{speed, filter, control-setup-type, control-setup-recipient, control-setup}; 12 | 13 | // Main resource representing a USB device. Any communication with the device happens through this resource. 14 | resource usb-device { 15 | // Main entry point for the API. 16 | // Returns all the USB devices currently connected to the system (or if access control is implemented by the runtime, only the ones the component has access to) 17 | enumerate: static func() -> list; 18 | 19 | // Convenience funtion, equivalent to calling enumerate(), applying the provided filters to the list, and returning the first element 20 | request-device: static func(filter: filter) -> option; 21 | 22 | // Returns the device descriptor of the device 23 | descriptor: func() -> device-descriptor; 24 | 25 | // Returns the USB Speed of the device (Low, Full, High, ...) 26 | speed: func() -> speed; 27 | 28 | // Returns all the configurations the device supports 29 | configurations: func() -> list; 30 | // Returns the currently active configuration 31 | active-configuration: func() -> usb-configuration; 32 | 33 | // Opens the device. This is required before any transfers can be made. 34 | open: func() -> (); 35 | // Returns whether the device is currently open. 36 | opened: func() -> bool; 37 | // Resets the device. 38 | reset: func() -> (); 39 | // Closes the device. 40 | close: func() -> (); 41 | 42 | // Selects the active configuration. The device must first be opened. 43 | select-configuration: func(configuration: borrow) -> (); 44 | 45 | // Claims an interface for exclusive use. Also selects the alternate interface, as the usb-interface resource actually represents an alternate interface. 46 | claim-interface: func(%interface: borrow) -> (); 47 | // Releases an interface. 48 | release-interface: func(%interface: borrow) -> (); 49 | 50 | // Clears a halt on a specific endpoint. 51 | clear-halt: func(endpoint: borrow) -> (); 52 | 53 | // Read control data from the device. The endpoint is always EP0. 54 | read-control: func(request: control-setup, length: u16) -> list; 55 | // Write control data to the device. The endpoint is always EP0. The return value is the number of bytes written. 56 | write-control: func(request: control-setup, data: list) -> u64; 57 | 58 | // Read data from an interrupt endpoint. The endpoint must be an interrupt endpoint. 59 | read-interrupt: func(endpoint: borrow, length: u64) -> list; 60 | // Write data to an interrupt endpoint. The endpoint must be an interrupt endpoint. The return value is the number of bytes written. 61 | write-interrupt: func(endpoint: borrow, data: list) -> u64; 62 | 63 | // Read data from a bulk endpoint. The endpoint must be a bulk endpoint. 64 | read-bulk: func(endpoint: borrow, length: u64) -> list; 65 | // Write data to a bulk endpoint. The endpoint must be a bulk endpoint. The return value is the number of bytes written. 66 | write-bulk: func(endpoint: borrow, data: list) -> u64; 67 | 68 | // TODO: support sending/receiving multiple packets at once for isochronous endpoints? 69 | // Read data from an isochronous endpoint. The endpoint must be an isochronous endpoint. 70 | read-isochronous: func(endpoint: borrow) -> list; 71 | // Write data to an isochronous endpoint. The endpoint must be an isochronous endpoint. The return value is the number of bytes written. 72 | write-isochronous: func(endpoint: borrow, data: list) -> u64; 73 | } 74 | 75 | // Represents a USB configuration. A device can have multiple configurations, but only one can be active at a time. 76 | // Must be dropped before parent device is dropped 77 | resource usb-configuration { 78 | descriptor: func() -> configuration-descriptor; 79 | interfaces: func() -> list; 80 | } 81 | 82 | // Represents a USB interface. 83 | // This resource actually represents an *alternate* interface. An interface can have multiple alternates, but only one can be active at a time. 84 | // Must be dropped before parent configuration is dropped 85 | resource usb-interface { 86 | descriptor: func() -> interface-descriptor; 87 | endpoints: func() -> list; 88 | } 89 | 90 | // Represents a USB endpoint. 91 | // Must be dropped before parent interface is dropped 92 | resource usb-endpoint { 93 | descriptor: func() -> endpoint-descriptor; 94 | } 95 | } -------------------------------------------------------------------------------- /command-components/xbox/src/main.rs: -------------------------------------------------------------------------------- 1 | use usb_wasm_bindings::device::UsbDevice; 2 | use usb_wasm_bindings::types::Filter; 3 | 4 | use std::io; 5 | use std::io::Write; 6 | 7 | use anyhow::anyhow; 8 | use byteorder::ByteOrder; 9 | use colored::Colorize; 10 | 11 | #[derive(Copy, Clone, Debug, Default)] 12 | pub struct XboxControllerState { 13 | a: bool, 14 | b: bool, 15 | x: bool, 16 | y: bool, 17 | start: bool, 18 | select: bool, 19 | 20 | up: bool, 21 | down: bool, 22 | left: bool, 23 | right: bool, 24 | lb: bool, 25 | rb: bool, 26 | lstick: bool, 27 | rstick: bool, 28 | 29 | lt: f32, 30 | rt: f32, 31 | lstick_x: f32, 32 | lstick_y: f32, 33 | rstick_x: f32, 34 | rstick_y: f32, 35 | } 36 | 37 | macro_rules! render_pressed { 38 | ($f:expr, $text:expr, $condition:expr) => { 39 | if $condition { 40 | write!($f, " {}", $text.green().bold()) 41 | } else { 42 | write!($f, " {}", $text.red().bold()) 43 | } 44 | }; 45 | } 46 | 47 | impl std::fmt::Display for XboxControllerState { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | // Print sticks 50 | write!( 51 | f, 52 | "LSTICK X: {:>4.0}% LSTICK Y: {:>4.0}% RSTICK X: {:>4.0}% RSTICK Y: {:>4.0}% LT: {:>3.0}% RT: {:>3.0}%", 53 | 100. * self.lstick_x, 54 | 100. * self.lstick_y, 55 | 100. * self.rstick_x, 56 | 100. * self.rstick_y, 57 | 100. * self.lt, 58 | 100. * self.rt, 59 | )?; 60 | render_pressed!(f, "A", self.a)?; 61 | render_pressed!(f, "B", self.b)?; 62 | render_pressed!(f, "X", self.x)?; 63 | render_pressed!(f, "Y", self.y)?; 64 | 65 | render_pressed!(f, "Up", self.up)?; 66 | render_pressed!(f, "Down", self.down)?; 67 | render_pressed!(f, "Left", self.left)?; 68 | render_pressed!(f, "Right", self.right)?; 69 | 70 | render_pressed!(f, "Start", self.start)?; 71 | render_pressed!(f, "Select", self.select)?; 72 | render_pressed!(f, "LB", self.lb)?; 73 | render_pressed!(f, "RB", self.rb)?; 74 | render_pressed!(f, "LS", self.lstick)?; 75 | render_pressed!(f, "RS", self.rstick)?; 76 | Ok(()) 77 | } 78 | } 79 | 80 | pub fn parse_xbox_controller_data(data: &[u8]) -> XboxControllerState { 81 | assert!(data.len() >= 18, "data is too short"); 82 | let lt = byteorder::LittleEndian::read_u16(&data[6..]) as f32 / 1023.0; 83 | let rt = byteorder::LittleEndian::read_u16(&data[8..]) as f32 / 1023.0; 84 | 85 | let lstick_x = (byteorder::LittleEndian::read_i16(&data[10..]) as f32 + 0.5) / 32767.5; 86 | let lstick_y = (byteorder::LittleEndian::read_i16(&data[12..]) as f32 + 0.5) / 32767.5; 87 | let rstick_x = (byteorder::LittleEndian::read_i16(&data[14..]) as f32 + 0.5) / 32767.5; 88 | let rstick_y = (byteorder::LittleEndian::read_i16(&data[16..]) as f32 + 0.5) / 32767.5; 89 | XboxControllerState { 90 | a: (data[4] & 0x10) != 0, 91 | b: (data[4] & 0x20) != 0, 92 | x: (data[4] & 0x40) != 0, 93 | y: (data[4] & 0x80) != 0, 94 | start: (data[4] & 0x08) != 0, 95 | select: (data[4] & 0x04) != 0, 96 | 97 | up: (data[5] & 0x01) != 0, 98 | down: (data[5] & 0x02) != 0, 99 | left: (data[5] & 0x04) != 0, 100 | right: (data[5] & 0x08) != 0, 101 | lb: (data[5] & 0x10) != 0, 102 | rb: (data[5] & 0x20) != 0, 103 | lstick: (data[5] & 0x40) != 0, 104 | rstick: (data[5] & 0x80) != 0, 105 | 106 | lt, 107 | rt, 108 | lstick_x, 109 | lstick_y, 110 | rstick_x, 111 | rstick_y, 112 | } 113 | } 114 | 115 | pub fn main() -> anyhow::Result<()> { 116 | let xbox_controller = UsbDevice::request_device(&Filter { 117 | vendor_id: Some(0x045e), 118 | product_id: Some(0x02ea), 119 | ..Default::default() 120 | }) 121 | .ok_or(anyhow!("No Xbox Controller found!"))?; 122 | 123 | // Select interface 124 | let configuration = xbox_controller 125 | .configurations() 126 | .into_iter() 127 | .find(|c| c.descriptor().number == 1) 128 | .ok_or(anyhow!("Could not find configuration"))?; 129 | let interface = configuration 130 | .interfaces() 131 | .into_iter() 132 | .find(|i| { 133 | i.descriptor().interface_number == 0x00 && i.descriptor().alternate_setting == 0x00 134 | }) 135 | .ok_or(anyhow!("Could not find interface"))?; 136 | let endpoint = interface 137 | .endpoints() 138 | .into_iter() 139 | .find(|e| { 140 | e.descriptor().direction == usb_wasm_bindings::types::Direction::In 141 | && e.descriptor().endpoint_number == 0x02 142 | }) 143 | .ok_or(anyhow!("Could not find IN endpoint"))?; 144 | let endpoint_out = interface 145 | .endpoints() 146 | .into_iter() 147 | .find(|e| { 148 | e.descriptor().direction == usb_wasm_bindings::types::Direction::Out 149 | && e.descriptor().endpoint_number == 0x02 150 | }) 151 | .ok_or(anyhow!("Could not find OUT endpoint"))?; 152 | 153 | // Open device 154 | xbox_controller.open(); 155 | // xbox_controller.select_configuration(&configuration); 156 | xbox_controller.claim_interface(&interface); 157 | 158 | // Set up the device 159 | xbox_controller.write_interrupt(&endpoint_out, &[0x05, 0x20, 0x00, 0x01, 0x00]); 160 | 161 | println!("Connected to Xbox Controller"); 162 | let mut previous_length = 0; 163 | 164 | print!("\r{} ", XboxControllerState::default()); //Print empty values first untill we get our first communication 165 | io::stdout().flush()?; 166 | 167 | loop { 168 | let data = 169 | xbox_controller.read_interrupt(&endpoint, endpoint.descriptor().max_packet_size as u64); 170 | if data.len() == 18 { 171 | let state = parse_xbox_controller_data(&data[0..18]); 172 | let state_str = state.to_string(); 173 | if state_str.len() < previous_length { 174 | print!( 175 | "\r{}{} ", 176 | state, 177 | " ".repeat(previous_length - state_str.len()) 178 | ); 179 | } else { 180 | print!("\r{} ", state); 181 | } 182 | io::stdout().flush()?; 183 | previous_length = state_str.len(); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /command-components/lsusb/src/main.rs: -------------------------------------------------------------------------------- 1 | use usb_wasm_bindings::{ 2 | device::{UsbConfiguration, UsbDevice, UsbEndpoint, UsbInterface}, 3 | types::{Direction, TransferType}, 4 | }; 5 | 6 | fn class_to_string(class: u8) -> &'static str { 7 | match class { 8 | 0x00 => "Defined at Interface Level", 9 | 0x01 => "Audio", 10 | 0x02 => "Communications and CDC Control", 11 | 0x03 => "Human Interface Device", 12 | 0x05 => "Physical", 13 | 0x06 => "Image", 14 | 0x07 => "Printer", 15 | 0x08 => "Mass Storage", 16 | 0x09 => "Hub", 17 | 0x0a => "CDC-Data", 18 | 0x0b => "Smart Card", 19 | 0x0d => "Content Security", 20 | 0x0e => "Video", 21 | 0x0f => "Personal Healthcare", 22 | 0x10 => "Audio/Video Devices", 23 | 0x11 => "Billboard Device", 24 | 0x12 => "USB Type-C Bridge", 25 | 0x13 => "USB Bulk Display Protocol Device Class", 26 | 0x14 => "MCTP over USB Protocol Endpoint Device Class", 27 | 0x3c => "I3C Device Class", 28 | 0xdc => "Diagnostic Device", 29 | 0xe0 => "Wireless Controller", 30 | 0xef => "Miscellaneous", 31 | 0xfe => "Application Specific", 32 | 0xff => "Vendor Specific", 33 | _ => "Unknown", 34 | } 35 | } 36 | 37 | struct Section { 38 | name: String, 39 | content: Vec<(&'static str, String, Option)>, 40 | } 41 | 42 | impl Section { 43 | fn new(name: &'static str) -> Self { 44 | Self { 45 | name: name.to_owned(), 46 | content: Vec::new(), 47 | } 48 | } 49 | 50 | fn add(&mut self, name: &'static str, value: String, comment: Option) { 51 | self.content.push((name, value, comment)); 52 | } 53 | } 54 | 55 | fn print_section(section: Section, indent_level: usize) { 56 | let indent = "\t".repeat(indent_level); 57 | 58 | println!("{indent}{}", section.name); 59 | 60 | let (first_column_width, second_column_width) = section 61 | .content 62 | .iter() 63 | .map(|(name, value, _)| (name.len(), value.len())) 64 | .reduce(|a, b| (a.0.max(b.0), a.1.max(b.1))) 65 | .unwrap_or((0, 0)); 66 | 67 | for (name, value, comment) in section.content { 68 | println!( 69 | "{indent}\t{:second_column_width$} {}", 70 | name, 71 | value, 72 | comment.unwrap_or_default() 73 | ); 74 | } 75 | } 76 | 77 | fn device_section(device: &UsbDevice) -> Section { 78 | let descriptor = device.descriptor(); 79 | let mut section = Section::new("Device Descriptor"); 80 | section.add( 81 | "USB Version", 82 | format!( 83 | "{}.{}{}", 84 | descriptor.usb_version.0, descriptor.usb_version.1, descriptor.usb_version.2 85 | ), 86 | None, 87 | ); 88 | section.add( 89 | "Device Class", 90 | format!("{:#04x}", descriptor.device_class,), 91 | Some(class_to_string(descriptor.device_class).to_owned()), 92 | ); 93 | section.add( 94 | "Subclass", 95 | format!("{:#04x}", descriptor.device_subclass), 96 | None, 97 | ); 98 | section.add( 99 | "Protocol", 100 | format!("{:#04x}", descriptor.device_protocol), 101 | None, 102 | ); 103 | section.add("Vendor ID", format!("{:#06x}", descriptor.vendor_id), None); 104 | section.add( 105 | "Product ID", 106 | format!("{:#06x}", descriptor.product_id), 107 | None, 108 | ); 109 | section.add( 110 | "Device Version", 111 | format!( 112 | "{}.{}{}", 113 | descriptor.device_version.0, descriptor.device_version.1, descriptor.device_version.2 114 | ), 115 | None, 116 | ); 117 | section.add( 118 | "Manufacturer", 119 | String::default(), 120 | descriptor.manufacturer_name, 121 | ); 122 | section.add("Product", String::default(), descriptor.product_name); 123 | section.add("Serial Number", String::default(), descriptor.serial_number); 124 | section 125 | } 126 | 127 | fn configuration_section(configuration: &UsbConfiguration) -> Section { 128 | let descriptor = configuration.descriptor(); 129 | let mut section = Section::new("Configuration Descriptor"); 130 | section.add( 131 | "Configuration Value", 132 | format!("{:#04x}", descriptor.number), 133 | None, 134 | ); 135 | section.add( 136 | "Configuration Description", 137 | String::default(), 138 | descriptor.description, 139 | ); 140 | section.add( 141 | "Self Powered", 142 | (if descriptor.self_powered { 143 | "✓" 144 | } else { 145 | "✗" 146 | }) 147 | .to_owned(), 148 | None, 149 | ); 150 | section.add( 151 | "Remote Wakeup", 152 | (if descriptor.remote_wakeup { 153 | "✓" 154 | } else { 155 | "✗" 156 | }) 157 | .to_owned(), 158 | None, 159 | ); 160 | section.add("Max Power", format!("{}mA", descriptor.max_power), None); 161 | section 162 | } 163 | 164 | fn interface_section(interface: &UsbInterface) -> Section { 165 | let mut section = Section::new("Interface Descriptor"); 166 | let descriptor = interface.descriptor(); 167 | 168 | section.add( 169 | "Interface Number", 170 | format!("{:#04x}", descriptor.interface_number), 171 | None, 172 | ); 173 | section.add( 174 | "Alternate Setting", 175 | format!("{:#04x}", descriptor.alternate_setting), 176 | None, 177 | ); 178 | section.add( 179 | "Interface Class", 180 | format!("{:#04x}", descriptor.interface_class), 181 | Some(class_to_string(descriptor.interface_class).to_owned()), 182 | ); 183 | section.add( 184 | "Interface Subclass", 185 | format!("{:#04x}", descriptor.interface_subclass), 186 | None, 187 | ); 188 | section.add( 189 | "Interface Protocol", 190 | format!("{:#04x}", descriptor.interface_protocol), 191 | None, 192 | ); 193 | section.add( 194 | "Interface Name", 195 | String::default(), 196 | descriptor.interface_name, 197 | ); 198 | 199 | section 200 | } 201 | 202 | fn endpoint_section(endpoint: &UsbEndpoint) -> Section { 203 | let mut section = Section::new("Endpoint Descriptor"); 204 | let descriptor = endpoint.descriptor(); 205 | 206 | section.add( 207 | "Endpoint Number", 208 | format!("{:#04x}", descriptor.endpoint_number), 209 | None, 210 | ); 211 | 212 | section.add( 213 | "Direction", 214 | match descriptor.direction { 215 | Direction::In => "In", 216 | Direction::Out => "Out", 217 | } 218 | .to_owned(), 219 | None, 220 | ); 221 | 222 | section.add( 223 | "Transfer Type", 224 | match descriptor.transfer_type { 225 | TransferType::Control => "Control", 226 | TransferType::Isochronous => "Isochronous", 227 | TransferType::Bulk => "Bulk", 228 | TransferType::Interrupt => "Interrupt", 229 | } 230 | .to_owned(), 231 | None, 232 | ); 233 | 234 | // section.add( 235 | // "Synchronization Type", 236 | // match descriptor.synchronization_type { 237 | // usb::SynchronizationType::None => "None", 238 | // usb::SynchronizationType::Asynchronous => "Asynchronous", 239 | // usb::SynchronizationType::Adaptive => "Adaptive", 240 | // usb::SynchronizationType::Synchronous => "Synchronous", 241 | // } 242 | // .to_owned(), 243 | // None, 244 | // ); 245 | 246 | // section.add( 247 | // "Usage Type", 248 | // match descriptor.usage_type { 249 | // usb::UsageType::Data => "Data", 250 | // usb::UsageType::Feedback => "Feedback", 251 | // usb::UsageType::ImplicitFeedbackData => "Implicit Feedback Data", 252 | // } 253 | // .to_owned(), 254 | // None, 255 | // ); 256 | 257 | section.add( 258 | "Max Packet Size", 259 | format!("{:#04x}", descriptor.max_packet_size), 260 | None, 261 | ); 262 | 263 | section 264 | } 265 | 266 | pub fn main() -> anyhow::Result<()> { 267 | let mut first = true; 268 | for device in UsbDevice::enumerate() { 269 | if !first { 270 | println!(); 271 | } 272 | first = false; 273 | let descriptor = device.descriptor(); 274 | println!( 275 | "ID {:#04x}:{:#04x} - {} {} ({})", 276 | descriptor.vendor_id, 277 | descriptor.product_id, 278 | descriptor.manufacturer_name.unwrap_or("N/A".to_owned()), 279 | descriptor.product_name.unwrap_or("N/A".to_owned()), 280 | class_to_string(descriptor.device_class), 281 | ); 282 | print_section(device_section(&device), 0); 283 | for configuration in device.configurations() { 284 | print_section(configuration_section(&configuration), 1); 285 | 286 | for interface in configuration.interfaces() { 287 | print_section(interface_section(&interface), 2); 288 | for endpoint in interface.endpoints() { 289 | print_section(endpoint_section(&endpoint), 3); 290 | } 291 | } 292 | } 293 | } 294 | 295 | Ok(()) 296 | } 297 | -------------------------------------------------------------------------------- /command-components/mass-storage/src/bulk_only/wasm.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 2 | 3 | use thiserror::Error; 4 | 5 | use tracing::trace; 6 | 7 | use usb_wasm_bindings::{ 8 | device::{UsbConfiguration, UsbDevice, UsbEndpoint, UsbInterface}, 9 | types::{ControlSetup, ControlSetupRecipient, ControlSetupType, Direction, TransferType}, 10 | }; 11 | 12 | #[derive(Debug, Error)] 13 | pub enum BulkOnlyTransportError { 14 | #[error("Invalid LUN")] 15 | InvalidLUN, 16 | #[error("The device responded with a differnt tag than was expected")] 17 | IncorrectTag, 18 | } 19 | 20 | // Implementation of the base Bulk Only Transfer protocol 21 | pub struct BulkOnlyTransportDevice { 22 | pub(crate) bulk_in: UsbEndpoint, 23 | pub(crate) bulk_out: UsbEndpoint, 24 | pub(crate) _interface: UsbInterface, // We need to keep these alive because of the endpoint resources 25 | pub(crate) _configuration: UsbConfiguration, // We need to keep these alive because of the endpoint resources 26 | pub(crate) device: UsbDevice, 27 | pub(crate) current_tag: u32, 28 | pub(crate) selected_lun: u8, 29 | pub(crate) max_lun: u8, 30 | } 31 | 32 | impl BulkOnlyTransportDevice { 33 | // Also opens the device, selects the configuration, and claims the interface 34 | pub fn new( 35 | device: UsbDevice, 36 | configuration: UsbConfiguration, 37 | interface: UsbInterface, 38 | ) -> Self { 39 | device.open(); 40 | device.reset(); 41 | if device.active_configuration().descriptor().number != configuration.descriptor().number { 42 | device.select_configuration(&configuration); 43 | }; 44 | device.claim_interface(&interface); 45 | 46 | // Find endpoints 47 | let (bulk_in, bulk_out) = { 48 | ( 49 | interface 50 | .endpoints() 51 | .into_iter() 52 | .find(|ep| { 53 | ep.descriptor().direction == Direction::In 54 | && ep.descriptor().transfer_type == TransferType::Bulk 55 | }) 56 | .unwrap(), 57 | interface 58 | .endpoints() 59 | .into_iter() 60 | .find(|ep| { 61 | ep.descriptor().direction == Direction::Out 62 | && ep.descriptor().transfer_type == TransferType::Bulk 63 | }) 64 | .unwrap(), 65 | ) 66 | }; 67 | 68 | // Get Max LUN 69 | let max_lun = { 70 | let lun_data = device.read_control( 71 | ControlSetup { 72 | request_type: ControlSetupType::Class, 73 | request_recipient: ControlSetupRecipient::Interface, 74 | request: 0xFE, 75 | value: 0, 76 | index: interface.descriptor().interface_number as u16, 77 | }, 78 | 1, 79 | ); 80 | lun_data[0] 81 | }; 82 | 83 | BulkOnlyTransportDevice { 84 | device, 85 | _configuration: configuration, 86 | _interface: interface, 87 | 88 | bulk_in, 89 | bulk_out, 90 | 91 | current_tag: 0, 92 | 93 | selected_lun: 0, 94 | max_lun, 95 | } 96 | } 97 | 98 | pub fn max_lun(&self) -> u8 { 99 | self.max_lun 100 | } 101 | 102 | pub fn selected_lun(&self) -> u8 { 103 | self.selected_lun 104 | } 105 | 106 | pub fn select_lun(&mut self, lun: u8) -> Result<(), BulkOnlyTransportError> { 107 | if self.max_lun > lun { 108 | return Err(BulkOnlyTransportError::InvalidLUN); 109 | } 110 | self.selected_lun = lun; 111 | Ok(()) 112 | } 113 | 114 | // Device to Host 115 | pub fn command_in( 116 | &mut self, 117 | command_block: BulkOnlyTransportCommandBlock, 118 | ) -> Result<(CommandStatusWrapper, Vec), BulkOnlyTransportError> { 119 | let tag = self.get_tag(); 120 | let cbw = CommandBlockWrapper { 121 | tag, 122 | transfer_length: command_block.transfer_length, 123 | direction: Direction::In, 124 | lun: self.selected_lun, 125 | cbwcb: command_block.command_block, 126 | }; 127 | 128 | trace!( 129 | tag, 130 | lun = self.selected_lun, 131 | transfer_length = command_block.transfer_length, 132 | "Sending command to device {:02x?}", 133 | cbw.cbwcb 134 | ); 135 | let cbw_bytes = cbw.to_bytes(); 136 | self.device.write_bulk(&self.bulk_out, &cbw_bytes); 137 | 138 | // TODO: implement proper error recovery 139 | // First, implement errrors in the WIT interface though 140 | // then, see section 5.3.3 and Figure 2 of the USB Mass Storage Class – Bulk Only Transport document 141 | 142 | // TODO: data stage 143 | let transfer_length = cbw.transfer_length as usize; 144 | // Receive data 145 | let data = self.device.read_bulk(&self.bulk_in, transfer_length as u64); 146 | 147 | let csw_bytes = self.device.read_bulk(&self.bulk_in, 13); 148 | let csw = CommandStatusWrapper::from_bytes(csw_bytes); 149 | 150 | if csw.tag != tag { 151 | return Err(BulkOnlyTransportError::IncorrectTag); 152 | } 153 | 154 | trace!("Received Command Status: {:?}", csw); 155 | Ok((csw, data)) 156 | } 157 | 158 | pub fn command_out( 159 | &mut self, 160 | command_block: BulkOnlyTransportCommandBlock, 161 | data: Option<&[u8]>, 162 | ) -> Result { 163 | let tag = self.get_tag(); 164 | let cbw = CommandBlockWrapper { 165 | tag, 166 | transfer_length: command_block.transfer_length, 167 | direction: Direction::Out, 168 | lun: self.selected_lun, 169 | cbwcb: command_block.command_block, 170 | }; 171 | 172 | trace!( 173 | tag, 174 | lun = self.selected_lun, 175 | transfer_length = command_block.transfer_length, 176 | "Sending command to device {:02x?}", 177 | cbw.cbwcb 178 | ); 179 | let cbw_bytes = cbw.to_bytes(); 180 | trace!("CBW Bytes: {:?}", cbw_bytes); 181 | self.device.write_bulk(&self.bulk_out, &cbw_bytes); 182 | 183 | // TODO: implement proper error recovery 184 | // First, implement errrors in the WIT interface though 185 | // then, see section 5.3.3 and Figure 2 of the USB Mass Storage Class – Bulk Only Transport document 186 | 187 | if let Some(data) = data { 188 | self.device.write_bulk(&self.bulk_out, data); 189 | } 190 | 191 | let csw_bytes = self.device.read_bulk(&self.bulk_in, 13); 192 | let csw = CommandStatusWrapper::from_bytes(csw_bytes); 193 | 194 | if csw.tag != tag { 195 | return Err(BulkOnlyTransportError::IncorrectTag); 196 | } 197 | 198 | trace!("Received Command Status: {:?}", csw); 199 | Ok(csw) 200 | } 201 | 202 | pub(crate) fn get_tag(&mut self) -> u32 { 203 | let tag = self.current_tag; 204 | self.current_tag += 1; 205 | tag 206 | } 207 | } 208 | 209 | pub struct BulkOnlyTransportCommandBlock { 210 | pub command_block: Vec, 211 | pub transfer_length: u32, 212 | } 213 | 214 | #[derive(Debug)] 215 | pub(crate) struct CommandBlockWrapper { 216 | pub(crate) tag: u32, 217 | pub(crate) transfer_length: u32, 218 | pub(crate) direction: Direction, 219 | pub(crate) lun: u8, 220 | pub(crate) cbwcb: Vec, 221 | } 222 | 223 | impl CommandBlockWrapper { 224 | pub(crate) fn to_bytes(&self) -> Vec { 225 | let mut cbw = BytesMut::with_capacity(31); 226 | 227 | assert!(self.lun < 16, "Invalid LUN"); 228 | assert!( 229 | !self.cbwcb.is_empty() && self.cbwcb.len() <= 16, 230 | "Invalid CBWCB length" 231 | ); 232 | 233 | cbw.put_u32_le(0x43425355); 234 | cbw.put_u32_le(self.tag); 235 | cbw.put_u32_le(self.transfer_length); 236 | cbw.put_u8(match self.direction { 237 | Direction::Out => 0b00000000, 238 | Direction::In => 0b10000000, 239 | }); 240 | cbw.put_u8(self.lun); 241 | cbw.put_u8(self.cbwcb.len() as u8); 242 | cbw.put_slice(&self.cbwcb); 243 | cbw.put_bytes(0, 16 - self.cbwcb.len()); 244 | 245 | cbw.to_vec() 246 | } 247 | } 248 | 249 | // TODO: make this private after implementing struct CommandStatus {} 250 | #[derive(Debug)] 251 | pub struct CommandStatusWrapper { 252 | pub(crate) tag: u32, 253 | pub data_residue: u32, 254 | pub status: CommandStatusWrapperStatus, 255 | } 256 | 257 | #[derive(Debug, PartialEq)] 258 | pub enum CommandStatusWrapperStatus { 259 | CommandPassed, // Good 260 | CommandFailed, 261 | PhaseError, 262 | ReservedObsolete, 263 | Reserved, 264 | } 265 | 266 | impl CommandStatusWrapper { 267 | pub(crate) fn from_bytes(bytes: Vec) -> Self { 268 | assert!(bytes.len() == 13, "CSW incorrect length"); 269 | let mut bytes = Bytes::from(bytes); 270 | 271 | let signature = bytes.get_u32_le(); 272 | assert!(signature == 0x53425355, "invalid CSW signature"); 273 | 274 | let tag = bytes.get_u32_le(); 275 | let data_residue = bytes.get_u32_le(); 276 | let status = match bytes.get_u8() { 277 | 0 => CommandStatusWrapperStatus::CommandPassed, 278 | 1 => CommandStatusWrapperStatus::CommandFailed, 279 | 2 => CommandStatusWrapperStatus::PhaseError, 280 | 3..=4 => CommandStatusWrapperStatus::ReservedObsolete, 281 | _ => CommandStatusWrapperStatus::Reserved, 282 | }; 283 | 284 | CommandStatusWrapper { 285 | tag, 286 | data_residue, 287 | status, 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /command-components/mass-storage/src/bulk_only/native.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 4 | 5 | use rusb::{request_type, Device, DeviceHandle, Direction, GlobalContext, TransferType}; 6 | use thiserror::Error; 7 | 8 | use tracing::trace; 9 | 10 | const TIMEOUT: Duration = Duration::from_secs(20); 11 | 12 | #[derive(Debug, Error)] 13 | pub enum BulkOnlyTransportError { 14 | #[error("Invalid LUN")] 15 | InvalidLUN, 16 | #[error("The device responded with a differnt tag than was expected")] 17 | IncorrectTag, 18 | } 19 | 20 | // Implementation of the base Bulk Only Transfer protocol 21 | pub struct BulkOnlyTransportDevice { 22 | pub(crate) bulk_in: u8, 23 | pub(crate) bulk_out: u8, 24 | pub(crate) _device: Device, 25 | pub(crate) handle: DeviceHandle, 26 | pub(crate) current_tag: u32, 27 | pub(crate) selected_lun: u8, 28 | pub(crate) max_lun: u8, 29 | } 30 | 31 | impl BulkOnlyTransportDevice { 32 | // Also opens the device, selects the configuration, and claims the interface 33 | pub fn new(device: Device, configuration: u8, interface: u8) -> Self { 34 | let config_descriptor = device.config_descriptor(configuration).unwrap(); 35 | 36 | let handle = device.open().unwrap(); 37 | let _ = handle.set_auto_detach_kernel_driver(true); 38 | handle.reset().unwrap(); 39 | if handle.active_configuration().unwrap() != config_descriptor.number() { 40 | handle.set_active_configuration(configuration).unwrap(); 41 | }; 42 | handle.claim_interface(interface).unwrap(); 43 | 44 | println!("configuration: {:?}", configuration); 45 | 46 | let interface_descriptor = config_descriptor 47 | .interfaces() 48 | .find(|i| i.number() == interface) 49 | .unwrap() 50 | .descriptors() 51 | .next() 52 | .unwrap(); 53 | 54 | // Find endpoints 55 | let (bulk_in, bulk_out) = { 56 | let mut endpoints = interface_descriptor.endpoint_descriptors(); 57 | ( 58 | endpoints 59 | .find(|ep| { 60 | ep.direction() == Direction::In && ep.transfer_type() == TransferType::Bulk 61 | }) 62 | .unwrap() 63 | .address(), 64 | endpoints 65 | .find(|ep| { 66 | ep.direction() == Direction::Out && ep.transfer_type() == TransferType::Bulk 67 | }) 68 | .unwrap() 69 | .address(), 70 | ) 71 | }; 72 | 73 | // Get Max LUN 74 | let max_lun = { 75 | let mut data = [0u8; 1]; 76 | handle 77 | .read_control( 78 | request_type( 79 | Direction::In, 80 | rusb::RequestType::Class, 81 | rusb::Recipient::Interface, 82 | ), 83 | 0xFE, 84 | 0, 85 | interface as u16, 86 | &mut data[..], 87 | TIMEOUT, 88 | ) 89 | .unwrap(); 90 | 91 | data[0] 92 | }; 93 | 94 | BulkOnlyTransportDevice { 95 | _device: device, 96 | handle, 97 | 98 | bulk_in, 99 | bulk_out, 100 | 101 | current_tag: 0, 102 | 103 | selected_lun: 0, 104 | max_lun, 105 | } 106 | } 107 | 108 | pub fn max_lun(&self) -> u8 { 109 | self.max_lun 110 | } 111 | 112 | pub fn selected_lun(&self) -> u8 { 113 | self.selected_lun 114 | } 115 | 116 | pub fn select_lun(&mut self, lun: u8) -> Result<(), BulkOnlyTransportError> { 117 | if self.max_lun > lun { 118 | return Err(BulkOnlyTransportError::InvalidLUN); 119 | } 120 | self.selected_lun = lun; 121 | Ok(()) 122 | } 123 | 124 | // Device to Host 125 | pub fn command_in( 126 | &mut self, 127 | command_block: BulkOnlyTransportCommandBlock, 128 | ) -> Result<(CommandStatusWrapper, Vec), BulkOnlyTransportError> { 129 | let tag = self.get_tag(); 130 | let cbw = CommandBlockWrapper { 131 | tag, 132 | transfer_length: command_block.transfer_length, 133 | direction: Direction::In, 134 | lun: self.selected_lun, 135 | cbwcb: command_block.command_block, 136 | }; 137 | 138 | trace!( 139 | tag, 140 | lun = self.selected_lun, 141 | transfer_length = command_block.transfer_length, 142 | "Sending command to device {:02x?}", 143 | cbw.cbwcb 144 | ); 145 | let cbw_bytes = cbw.to_bytes(); 146 | self.handle 147 | .write_bulk(self.bulk_out, &cbw_bytes, TIMEOUT) 148 | .unwrap(); 149 | 150 | // TODO: implement proper error recovery 151 | // First, implement errrors in the WIT interface though 152 | // then, see section 5.3.3 and Figure 2 of the USB Mass Storage Class – Bulk Only Transport document 153 | 154 | // TODO: data stage 155 | let transfer_length = cbw.transfer_length as usize; 156 | // Receive data 157 | let mut data = vec![0u8; transfer_length]; 158 | self.handle 159 | .read_bulk(self.bulk_in, &mut data, TIMEOUT) 160 | .unwrap(); 161 | 162 | let mut csw_bytes = [0u8; 13]; 163 | self.handle 164 | .read_bulk(self.bulk_in, &mut csw_bytes, TIMEOUT) 165 | .unwrap(); 166 | let csw = CommandStatusWrapper::from_bytes(&csw_bytes); 167 | 168 | if csw.tag != tag { 169 | return Err(BulkOnlyTransportError::IncorrectTag); 170 | } 171 | 172 | trace!("Received Command Status: {:?}", csw); 173 | Ok((csw, data)) 174 | } 175 | 176 | pub fn command_out( 177 | &mut self, 178 | command_block: BulkOnlyTransportCommandBlock, 179 | data: Option<&[u8]>, 180 | ) -> Result { 181 | let tag = self.get_tag(); 182 | let cbw = CommandBlockWrapper { 183 | tag, 184 | transfer_length: command_block.transfer_length, 185 | direction: Direction::Out, 186 | lun: self.selected_lun, 187 | cbwcb: command_block.command_block, 188 | }; 189 | 190 | trace!( 191 | tag, 192 | lun = self.selected_lun, 193 | transfer_length = command_block.transfer_length, 194 | "Sending command to device {:02x?}", 195 | cbw.cbwcb 196 | ); 197 | let cbw_bytes = cbw.to_bytes(); 198 | trace!("CBW Bytes: {:?}", cbw_bytes); 199 | self.handle 200 | .write_bulk(self.bulk_out, &cbw_bytes, TIMEOUT) 201 | .unwrap(); 202 | 203 | // TODO: implement proper error recovery 204 | // First, implement errrors in the WIT interface though 205 | // then, see section 5.3.3 and Figure 2 of the USB Mass Storage Class – Bulk Only Transport document 206 | 207 | if let Some(data) = data { 208 | self.handle 209 | .write_bulk(self.bulk_out, data, TIMEOUT) 210 | .unwrap(); 211 | } 212 | 213 | let mut csw_bytes = [0_u8; 13]; 214 | self.handle 215 | .read_bulk(self.bulk_in, &mut csw_bytes, TIMEOUT) 216 | .unwrap(); 217 | let csw = CommandStatusWrapper::from_bytes(&csw_bytes); 218 | 219 | if csw.tag != tag { 220 | return Err(BulkOnlyTransportError::IncorrectTag); 221 | } 222 | 223 | trace!("Received Command Status: {:?}", csw); 224 | Ok(csw) 225 | } 226 | 227 | pub(crate) fn get_tag(&mut self) -> u32 { 228 | let tag = self.current_tag; 229 | self.current_tag += 1; 230 | tag 231 | } 232 | } 233 | 234 | pub struct BulkOnlyTransportCommandBlock { 235 | pub command_block: Vec, 236 | pub transfer_length: u32, 237 | } 238 | 239 | #[derive(Debug)] 240 | pub(crate) struct CommandBlockWrapper { 241 | pub(crate) tag: u32, 242 | pub(crate) transfer_length: u32, 243 | pub(crate) direction: Direction, 244 | pub(crate) lun: u8, 245 | pub(crate) cbwcb: Vec, 246 | } 247 | 248 | impl CommandBlockWrapper { 249 | pub(crate) fn to_bytes(&self) -> Vec { 250 | let mut cbw = BytesMut::with_capacity(31); 251 | 252 | assert!(self.lun < 16, "Invalid LUN"); 253 | assert!( 254 | !self.cbwcb.is_empty() && self.cbwcb.len() <= 16, 255 | "Invalid CBWCB length" 256 | ); 257 | 258 | cbw.put_u32_le(0x43425355); 259 | cbw.put_u32_le(self.tag); 260 | cbw.put_u32_le(self.transfer_length); 261 | cbw.put_u8(match self.direction { 262 | Direction::Out => 0b00000000, 263 | Direction::In => 0b10000000, 264 | }); 265 | cbw.put_u8(self.lun); 266 | cbw.put_u8(self.cbwcb.len() as u8); 267 | cbw.put_slice(&self.cbwcb); 268 | cbw.put_bytes(0, 16 - self.cbwcb.len()); 269 | 270 | cbw.to_vec() 271 | } 272 | } 273 | 274 | // TODO: make this private after implementing struct CommandStatus {} 275 | #[derive(Debug)] 276 | pub struct CommandStatusWrapper { 277 | pub(crate) tag: u32, 278 | pub data_residue: u32, 279 | pub status: CommandStatusWrapperStatus, 280 | } 281 | 282 | #[derive(Debug, PartialEq)] 283 | pub enum CommandStatusWrapperStatus { 284 | CommandPassed, // Good 285 | CommandFailed, 286 | PhaseError, 287 | ReservedObsolete, 288 | Reserved, 289 | } 290 | 291 | impl CommandStatusWrapper { 292 | pub(crate) fn from_bytes(bytes: &[u8]) -> Self { 293 | assert!(bytes.len() == 13, "CSW incorrect length"); 294 | let mut bytes = Bytes::copy_from_slice(bytes); 295 | 296 | let signature = bytes.get_u32_le(); 297 | assert!(signature == 0x53425355, "invalid CSW signature"); 298 | 299 | let tag = bytes.get_u32_le(); 300 | let data_residue = bytes.get_u32_le(); 301 | let status = match bytes.get_u8() { 302 | 0 => CommandStatusWrapperStatus::CommandPassed, 303 | 1 => CommandStatusWrapperStatus::CommandFailed, 304 | 2 => CommandStatusWrapperStatus::PhaseError, 305 | 3..=4 => CommandStatusWrapperStatus::ReservedObsolete, 306 | _ => CommandStatusWrapperStatus::Reserved, 307 | }; 308 | 309 | CommandStatusWrapper { 310 | tag, 311 | data_residue, 312 | status, 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /command-components/xbox-maze/src/main.rs: -------------------------------------------------------------------------------- 1 | use usb_wasm_bindings::device::UsbDevice; 2 | use usb_wasm_bindings::types::Filter; 3 | 4 | use std::io; 5 | use std::io::Write; 6 | 7 | use anyhow::anyhow; 8 | use byteorder::ByteOrder; 9 | use colored::Colorize; 10 | 11 | #[derive(Copy, Clone, Debug, Default)] 12 | pub struct XboxControllerState { 13 | a: bool, 14 | b: bool, 15 | x: bool, 16 | y: bool, 17 | start: bool, 18 | select: bool, 19 | 20 | up: bool, 21 | down: bool, 22 | left: bool, 23 | right: bool, 24 | lb: bool, 25 | rb: bool, 26 | lstick: bool, 27 | rstick: bool, 28 | 29 | lt: f32, 30 | rt: f32, 31 | lstick_x: f32, 32 | lstick_y: f32, 33 | rstick_x: f32, 34 | rstick_y: f32, 35 | } 36 | 37 | macro_rules! render_pressed { 38 | ($f:expr, $text:expr, $condition:expr) => { 39 | if $condition { 40 | write!($f, "{} ", $text.green().bold()) 41 | } else { 42 | write!($f, "{} ", $text.red()) 43 | } 44 | }; 45 | } 46 | 47 | impl std::fmt::Display for XboxControllerState { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | // Print sticks 50 | writeln!( 51 | f, 52 | "LS X: {:>3.0}%\tLS Y: {:>3.0}%\nRS X: {:>3.0}%\tRS Y: {:>3.0}%\nLT : {:>3.0}%\tRT : {:>3.0}%\n", 53 | 100. * self.lstick_x, 54 | 100. * self.lstick_y, 55 | 100. * self.rstick_x, 56 | 100. * self.rstick_y, 57 | 100. * self.lt, 58 | 100. * self.rt, 59 | )?; 60 | 61 | render_pressed!(f, "A", self.a)?; 62 | render_pressed!(f, "B", self.b)?; 63 | render_pressed!(f, "X", self.x)?; 64 | render_pressed!(f, "Y", self.y)?; 65 | 66 | render_pressed!(f, "Up", self.up)?; 67 | render_pressed!(f, "Down", self.down)?; 68 | render_pressed!(f, "Left", self.left)?; 69 | render_pressed!(f, "Right", self.right)?; 70 | 71 | render_pressed!(f, "Start", self.start)?; 72 | render_pressed!(f, "Select", self.select)?; 73 | render_pressed!(f, "LB", self.lb)?; 74 | render_pressed!(f, "RB", self.rb)?; 75 | render_pressed!(f, "LS", self.lstick)?; 76 | render_pressed!(f, "RS", self.rstick)?; 77 | 78 | writeln!(f, "\n")?; 79 | 80 | if self.up { 81 | writeln!(f, " {}", "↑".green().bold())?; 82 | } else { 83 | writeln!(f, " {}", "↑".red())?; 84 | } 85 | 86 | if self.left && !self.right { 87 | writeln!(f, "{} {}", "←".green().bold(), "→".red())?; 88 | } else if !self.left && self.right { 89 | writeln!(f, "{} {}", "←".red(), "→".green().bold())?; 90 | } else if self.left && self.right { 91 | writeln!(f, "{} {}", "←".green().bold(), "→".green().bold())?; 92 | } else { 93 | writeln!(f, "{} {}", "←".red(), "→".red())?; 94 | } 95 | 96 | if self.down { 97 | writeln!(f, " {}", "↓".green().bold())?; 98 | } else { 99 | writeln!(f, " {}", "↓".red())?; 100 | } 101 | 102 | Ok(()) 103 | } 104 | } 105 | 106 | pub fn parse_xbox_controller_data(data: &[u8]) -> XboxControllerState { 107 | assert!(data.len() >= 18, "data is too short"); 108 | let lt = byteorder::LittleEndian::read_u16(&data[6..]) as f32 / 1023.0; 109 | let rt = byteorder::LittleEndian::read_u16(&data[8..]) as f32 / 1023.0; 110 | 111 | let lstick_x = (byteorder::LittleEndian::read_i16(&data[10..]) as f32 + 0.5) / 32767.5; 112 | let lstick_y = (byteorder::LittleEndian::read_i16(&data[12..]) as f32 + 0.5) / 32767.5; 113 | let rstick_x = (byteorder::LittleEndian::read_i16(&data[14..]) as f32 + 0.5) / 32767.5; 114 | let rstick_y = (byteorder::LittleEndian::read_i16(&data[16..]) as f32 + 0.5) / 32767.5; 115 | XboxControllerState { 116 | a: (data[4] & 0x10) != 0, 117 | b: (data[4] & 0x20) != 0, 118 | x: (data[4] & 0x40) != 0, 119 | y: (data[4] & 0x80) != 0, 120 | start: (data[4] & 0x08) != 0, 121 | select: (data[4] & 0x04) != 0, 122 | 123 | up: (data[5] & 0x01) != 0, 124 | down: (data[5] & 0x02) != 0, 125 | left: (data[5] & 0x04) != 0, 126 | right: (data[5] & 0x08) != 0, 127 | lb: (data[5] & 0x10) != 0, 128 | rb: (data[5] & 0x20) != 0, 129 | lstick: (data[5] & 0x40) != 0, 130 | rstick: (data[5] & 0x80) != 0, 131 | 132 | lt, 133 | rt, 134 | lstick_x, 135 | lstick_y, 136 | rstick_x, 137 | rstick_y, 138 | } 139 | } 140 | 141 | const WALL: &str = "\x1B[38;5;75m▓\x1B[0m"; 142 | const FOOD: &str = "\x1B[38;5;214m•\x1B[0m"; 143 | const EMPTY: &str = " "; 144 | const PACMAN: &str = "\x1B[38;5;226m◉\x1B[0m"; 145 | const GHOST: &str = "\x1B[38;5;160m⭑\x1B[0m"; 146 | 147 | fn print_maze(maze: &[[&str; 30]; 14]) { 148 | for row in maze.iter() { 149 | for cell in row.iter() { 150 | print!("{}", cell); 151 | } 152 | println!(); 153 | } 154 | } 155 | 156 | pub fn main() -> anyhow::Result<()> { 157 | let xbox_controller = UsbDevice::request_device(&Filter { 158 | vendor_id: Some(0x045e), 159 | product_id: Some(0x02ea), 160 | ..Default::default() 161 | }) 162 | .ok_or(anyhow!("No Xbox Controller found!"))?; 163 | 164 | // Select interface 165 | let configuration = xbox_controller 166 | .configurations() 167 | .into_iter() 168 | .find(|c| c.descriptor().number == 1) 169 | .ok_or(anyhow!("Could not find configuration"))?; 170 | let interface = configuration 171 | .interfaces() 172 | .into_iter() 173 | .find(|i| { 174 | i.descriptor().interface_number == 0x00 && i.descriptor().alternate_setting == 0x00 175 | }) 176 | .ok_or(anyhow!("Could not find interface"))?; 177 | let endpoint = interface 178 | .endpoints() 179 | .into_iter() 180 | .find(|e| { 181 | e.descriptor().direction == usb_wasm_bindings::types::Direction::In 182 | && e.descriptor().endpoint_number == 0x02 183 | }) 184 | .ok_or(anyhow!("Could not find IN endpoint"))?; 185 | let endpoint_out = interface 186 | .endpoints() 187 | .into_iter() 188 | .find(|e| { 189 | e.descriptor().direction == usb_wasm_bindings::types::Direction::Out 190 | && e.descriptor().endpoint_number == 0x02 191 | }) 192 | .ok_or(anyhow!("Could not find OUT endpoint"))?; 193 | 194 | // Open device 195 | xbox_controller.open(); 196 | xbox_controller.claim_interface(&interface); 197 | 198 | // Set up the device (https://github.com/quantus/xbox-one-controller-protocol) 199 | xbox_controller.write_interrupt(&endpoint_out, &[0x05, 0x20, 0x00, 0x01, 0x00]); 200 | 201 | let mut maze = [ 202 | [WALL; 30], 203 | [ 204 | WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 205 | WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 206 | FOOD, WALL, 207 | ], 208 | [ 209 | WALL, FOOD, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, WALL, WALL, FOOD, 210 | WALL, WALL, FOOD, WALL, WALL, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, 211 | FOOD, WALL, 212 | ], 213 | [ 214 | WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 215 | FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 216 | FOOD, WALL, 217 | ], 218 | [ 219 | WALL, FOOD, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, 220 | WALL, WALL, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, 221 | FOOD, WALL, 222 | ], 223 | [ 224 | WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, 225 | WALL, WALL, GHOST, FOOD, FOOD, FOOD, FOOD, WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, 226 | FOOD, WALL, 227 | ], 228 | [ 229 | WALL, WALL, WALL, FOOD, FOOD, WALL, WALL, WALL, WALL, WALL, WALL, PACMAN, EMPTY, EMPTY, 230 | EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL, WALL, WALL, WALL, WALL, WALL, FOOD, FOOD, 231 | WALL, WALL, WALL, 232 | ], 233 | [ 234 | WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, 235 | FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, 236 | GHOST, WALL, 237 | ], 238 | [ 239 | WALL, FOOD, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, 240 | WALL, WALL, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, 241 | FOOD, WALL, 242 | ], 243 | [ 244 | WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 245 | WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 246 | FOOD, WALL, 247 | ], 248 | [ 249 | WALL, FOOD, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, WALL, WALL, FOOD, 250 | FOOD, FOOD, FOOD, WALL, WALL, WALL, WALL, WALL, WALL, FOOD, WALL, WALL, WALL, WALL, 251 | FOOD, WALL, 252 | ], 253 | [ 254 | WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 255 | WALL, WALL, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, FOOD, 256 | FOOD, WALL, 257 | ], 258 | [ 259 | WALL, FOOD, WALL, WALL, WALL, WALL, FOOD, GHOST, FOOD, FOOD, WALL, WALL, FOOD, FOOD, 260 | FOOD, FOOD, FOOD, FOOD, WALL, WALL, FOOD, FOOD, FOOD, FOOD, WALL, WALL, WALL, WALL, 261 | FOOD, WALL, 262 | ], 263 | [WALL; 30], 264 | ]; 265 | 266 | // (y, x) 267 | let mut current_pos = (6, 11); 268 | let mut button_down = false; 269 | 270 | // Clear screen and home cursor 271 | print!("\x1B[2J"); 272 | print!("\x1B[H"); 273 | 274 | // println!("Connected to Xbox Controller\n"); 275 | // println!("{}", XboxControllerState::default()); // Print empty values first until we get our first communication 276 | print_maze(&maze); 277 | io::stdout().flush()?; 278 | 279 | loop { 280 | // Clear screen and home cursor 281 | print!("\x1B[2J"); 282 | print!("\x1B[H"); 283 | 284 | let data = 285 | xbox_controller.read_interrupt(&endpoint, endpoint.descriptor().max_packet_size as u64); 286 | if data.len() == 18 { 287 | let state = parse_xbox_controller_data(&data[0..18]); 288 | 289 | // println!("Connected to Xbox Controller\n"); 290 | // println!("{}", state); 291 | print_maze(&maze); 292 | 293 | if !button_down { 294 | if state.right { 295 | button_down = true; 296 | 297 | if maze[current_pos.0][current_pos.1 + 1] != WALL { 298 | maze[current_pos.0][current_pos.1] = EMPTY; 299 | current_pos.1 += 1; 300 | maze[current_pos.0][current_pos.1] = PACMAN; 301 | } 302 | } 303 | 304 | if state.left { 305 | button_down = true; 306 | 307 | if maze[current_pos.0][current_pos.1 - 1] != WALL { 308 | maze[current_pos.0][current_pos.1] = EMPTY; 309 | current_pos.1 -= 1; 310 | maze[current_pos.0][current_pos.1] = PACMAN; 311 | } 312 | } 313 | 314 | if state.up { 315 | button_down = true; 316 | 317 | if maze[current_pos.0 - 1][current_pos.1] != WALL { 318 | maze[current_pos.0][current_pos.1] = EMPTY; 319 | current_pos.0 -= 1; 320 | maze[current_pos.0][current_pos.1] = PACMAN; 321 | } 322 | } 323 | 324 | if state.down { 325 | button_down = true; 326 | 327 | if maze[current_pos.0 + 1][current_pos.1] != WALL { 328 | maze[current_pos.0][current_pos.1] = EMPTY; 329 | current_pos.0 += 1; 330 | maze[current_pos.0][current_pos.1] = PACMAN; 331 | } 332 | } 333 | } else if !state.up && !state.down && !state.left && !state.right { 334 | button_down = false; 335 | } 336 | 337 | io::stdout().flush()?; 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | --- LLVM Exceptions to the Apache 2.0 License ---- 205 | 206 | As an exception, if, as a result of your compiling your source code, portions 207 | of this Software are embedded into an Object form of such source code, you 208 | may redistribute such embedded portions in such Object form without complying 209 | with the conditions of Sections 4(a), 4(b) and 4(d) of the License. 210 | 211 | In addition, if you combine or link compiled forms of this Software with 212 | software that is licensed under the GPLv2 ("Combined Software") and if a 213 | court of competent jurisdiction determines that the patent provision (Section 214 | 3), the indemnity provision (Section 9) or other Section of the License 215 | conflicts with the conditions of the GPLv2, you may retroactively and 216 | prospectively choose to deem waived or otherwise exclude such Section(s) of 217 | the License, but only in their entirety and only with respect to the Combined 218 | Software. 219 | -------------------------------------------------------------------------------- /usb-wasm/src/host.rs: -------------------------------------------------------------------------------- 1 | use wasmtime_wasi::WasiView; 2 | 3 | use crate::wadu436::usb::device::*; 4 | use crate::wadu436::usb::types::{ControlSetupRecipient, ControlSetupType}; 5 | 6 | fn host_control_setup_to_rusb( 7 | setup: &crate::wadu436::usb::device::ControlSetup, 8 | ) -> crate::ControlSetup { 9 | let request_type = match setup.request_type { 10 | ControlSetupType::Standard => rusb::RequestType::Standard, 11 | ControlSetupType::Class => rusb::RequestType::Class, 12 | ControlSetupType::Vendor => rusb::RequestType::Vendor, 13 | }; 14 | let request_recipient = match setup.request_recipient { 15 | ControlSetupRecipient::Device => rusb::Recipient::Device, 16 | ControlSetupRecipient::Interface => rusb::Recipient::Interface, 17 | ControlSetupRecipient::Endpoint => rusb::Recipient::Endpoint, 18 | }; 19 | 20 | crate::ControlSetup { 21 | request_type, 22 | request_recipient, 23 | request: setup.request, 24 | value: setup.value, 25 | index: setup.index, 26 | } 27 | } 28 | 29 | impl HostUsbDevice for T { 30 | fn enumerate(&mut self) -> wasmtime::Result>> { 31 | let table = self.table(); 32 | 33 | Ok(UsbDevice::enumerate()? 34 | .into_iter() 35 | .map(|device| table.push(device)) 36 | .collect::>()?) 37 | } 38 | 39 | #[doc = "Convenience funtion, equivalent to calling enumerate(), applying the provided filters to the list, and returning the first element"] 40 | fn request_device( 41 | &mut self, 42 | filter: Filter, 43 | ) -> wasmtime::Result>> { 44 | let table = self.table(); 45 | let device = UsbDevice::enumerate()?.into_iter().find(|device| { 46 | let descriptor = &device.descriptor; 47 | let vendor_id = filter 48 | .vendor_id 49 | .map_or(true, |vendor_id| vendor_id == descriptor.vendor_id); 50 | let product_id = filter 51 | .product_id 52 | .map_or(true, |product_id| product_id == descriptor.product_id); 53 | let class_code = filter 54 | .class_code 55 | .map_or(true, |class_code| class_code == descriptor.device_class); 56 | let subclass_code = filter.subclass_code.map_or(true, |subclass_code| { 57 | subclass_code == descriptor.device_subclass 58 | }); 59 | let protocol_code = filter.protocol_code.map_or(true, |protocol_code| { 60 | protocol_code == descriptor.device_protocol 61 | }); 62 | let serial_number = filter.serial_number.as_ref().map_or(true, |serial_number| { 63 | descriptor 64 | .serial_number 65 | .as_ref() 66 | .map_or(false, |device_serial_number| { 67 | serial_number == device_serial_number 68 | }) 69 | }); 70 | 71 | vendor_id && product_id && class_code && subclass_code && protocol_code && serial_number 72 | }); 73 | 74 | Ok(device.map(|device| table.push(device)).transpose()?) 75 | } 76 | 77 | fn descriptor( 78 | &mut self, 79 | rep: wasmtime::component::Resource, 80 | ) -> wasmtime::Result { 81 | let device = self.table().get(&rep)?; 82 | Ok(device.descriptor.clone()) 83 | } 84 | 85 | fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { 86 | self.table().delete(rep)?; 87 | Ok(()) 88 | } 89 | 90 | fn configurations( 91 | &mut self, 92 | rep: wasmtime::component::Resource, 93 | ) -> wasmtime::Result>> { 94 | let table = self.table(); 95 | let device = table.get(&rep)?; 96 | 97 | Ok(device 98 | .get_configurations() 99 | .into_iter() 100 | .map(|configuration| table.push_child(configuration, &rep)) 101 | .collect::, _>>()?) 102 | } 103 | 104 | fn open(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { 105 | let device = self.table().get_mut(&rep)?; 106 | device.open()?; 107 | Ok(()) 108 | } 109 | 110 | fn reset(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { 111 | let device = self.table().get_mut(&rep)?; 112 | device.reset()?; 113 | Ok(()) 114 | } 115 | 116 | fn close(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { 117 | let device = self.table().get_mut(&rep)?; 118 | device.close(); 119 | Ok(()) 120 | } 121 | 122 | fn opened(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result { 123 | let device = self.table().get(&rep)?; 124 | Ok(device.opened()) 125 | } 126 | 127 | fn select_configuration( 128 | &mut self, 129 | rep: wasmtime::component::Resource, 130 | configuration: wasmtime::component::Resource, 131 | ) -> wasmtime::Result<()> { 132 | let table = self.table(); 133 | let configuration = table.get(&configuration)?; 134 | let configuration_value = configuration.descriptor.number; 135 | let device = table.get_mut(&rep)?; 136 | device.select_configuration(configuration_value)?; 137 | Ok(()) 138 | } 139 | 140 | fn claim_interface( 141 | &mut self, 142 | rep: wasmtime::component::Resource, 143 | interface: wasmtime::component::Resource, 144 | ) -> wasmtime::Result<()> { 145 | let table = self.table(); 146 | let interface = table.get(&interface)?; 147 | let interface_number = interface.descriptor.interface_number; 148 | let interface_setting = interface.descriptor.alternate_setting; 149 | let device = table.get_mut(&rep)?; 150 | device.claim_interface(interface_number).unwrap(); 151 | device 152 | .set_alternate_setting(interface_number, interface_setting) 153 | .unwrap(); 154 | Ok(()) 155 | } 156 | 157 | fn release_interface( 158 | &mut self, 159 | rep: wasmtime::component::Resource, 160 | interface: wasmtime::component::Resource, 161 | ) -> std::result::Result<(), wasmtime::Error> { 162 | let table = self.table(); 163 | let interface = table.get(&interface)?.descriptor.interface_number; 164 | let device = table.get_mut(&rep)?; 165 | device.release_interface(interface)?; 166 | Ok(()) 167 | } 168 | 169 | fn clear_halt( 170 | &mut self, 171 | rep: wasmtime::component::Resource, 172 | endpoint: wasmtime::component::Resource, 173 | ) -> wasmtime::Result<()> { 174 | let table = self.table(); 175 | let endpoint = table.get(&endpoint)?; 176 | let endpoint_address = endpoint.get_endpoint_number(); 177 | let device = table.get_mut(&rep)?; 178 | device.clear_halt(endpoint_address)?; 179 | Ok(()) 180 | } 181 | 182 | fn read_interrupt( 183 | &mut self, 184 | rep: wasmtime::component::Resource, 185 | endpoint: wasmtime::component::Resource, 186 | length: u64, 187 | ) -> wasmtime::Result> { 188 | let table = self.table(); 189 | let ep = table.get(&endpoint)?; 190 | let address = ep.get_endpoint_number(); 191 | let device = table.get_mut(&rep)?; 192 | let data = device 193 | .interrupt_transfer_in(address, length as usize) 194 | .unwrap(); 195 | Ok(data) 196 | } 197 | 198 | fn write_interrupt( 199 | &mut self, 200 | rep: wasmtime::component::Resource, 201 | endpoint: wasmtime::component::Resource, 202 | data: Vec, 203 | ) -> wasmtime::Result { 204 | let table = self.table(); 205 | let ep = table.get(&endpoint)?; 206 | let address = ep.get_endpoint_number(); 207 | let device = table.get_mut(&rep)?; 208 | let bytes_written = device.interrupt_transfer_out(address, &data).unwrap(); 209 | Ok(bytes_written as _) 210 | } 211 | 212 | fn read_bulk( 213 | &mut self, 214 | rep: wasmtime::component::Resource, 215 | endpoint: wasmtime::component::Resource, 216 | length: u64, 217 | ) -> wasmtime::Result> { 218 | let table = self.table(); 219 | let ep = table.get(&endpoint)?; 220 | let address = ep.descriptor.endpoint_number 221 | + match ep.descriptor.direction { 222 | crate::wadu436::usb::types::Direction::Out => 0x00, 223 | crate::wadu436::usb::types::Direction::In => 0x80, 224 | }; 225 | let device = table.get_mut(&rep)?; 226 | let data = device.bulk_transfer_in(address, length as usize).unwrap(); 227 | Ok(data) 228 | } 229 | 230 | fn write_bulk( 231 | &mut self, 232 | rep: wasmtime::component::Resource, 233 | endpoint: wasmtime::component::Resource, 234 | data: Vec, 235 | ) -> wasmtime::Result { 236 | let table = self.table(); 237 | let ep = table.get(&endpoint)?; 238 | let address = ep.descriptor.endpoint_number 239 | + match ep.descriptor.direction { 240 | crate::wadu436::usb::types::Direction::Out => 0x00, 241 | crate::wadu436::usb::types::Direction::In => 0x80, 242 | }; 243 | let device = table.get_mut(&rep)?; 244 | let bytes_written = device.bulk_transfer_out(address, &data).unwrap(); 245 | Ok(bytes_written as _) 246 | } 247 | 248 | fn read_isochronous( 249 | &mut self, 250 | rep: wasmtime::component::Resource, 251 | endpoint: wasmtime::component::Resource, 252 | ) -> wasmtime::Result> { 253 | let table = self.table(); 254 | let ep = table.get(&endpoint)?; 255 | let address = ep.descriptor.endpoint_number 256 | + match ep.descriptor.direction { 257 | crate::wadu436::usb::types::Direction::Out => 0x00, 258 | crate::wadu436::usb::types::Direction::In => 0x80, 259 | }; 260 | let buffer_size = ep.descriptor.max_packet_size; 261 | let device = table.get_mut(&rep)?; 262 | let mut data = device 263 | .iso_transfer_in(address, 1, buffer_size.into()) 264 | .unwrap(); 265 | 266 | Ok(data.swap_remove(0)) 267 | } 268 | 269 | fn write_isochronous( 270 | &mut self, 271 | rep: wasmtime::component::Resource, 272 | endpoint: wasmtime::component::Resource, 273 | data: Vec, 274 | ) -> wasmtime::Result { 275 | let table = self.table(); 276 | let ep = table.get(&endpoint)?; 277 | let address = ep.descriptor.endpoint_number 278 | + match ep.descriptor.direction { 279 | crate::wadu436::usb::types::Direction::Out => 0x00, 280 | crate::wadu436::usb::types::Direction::In => 0x80, 281 | }; 282 | let device = table.get_mut(&rep)?; 283 | let bytes_written = device.iso_transfer_out(address, &[data]).unwrap(); 284 | Ok(bytes_written) 285 | } 286 | 287 | fn active_configuration( 288 | &mut self, 289 | rep: wasmtime::component::Resource, 290 | ) -> std::result::Result, wasmtime::Error> { 291 | let table = self.table(); 292 | let device = table.get_mut(&rep)?; 293 | let configuration = device.active_configuration()?; 294 | 295 | Ok(table.push(configuration)?) 296 | } 297 | 298 | fn speed( 299 | &mut self, 300 | rep: wasmtime::component::Resource, 301 | ) -> std::result::Result { 302 | let table = self.table(); 303 | let device = table.get_mut(&rep)?; 304 | let speed = device.speed(); 305 | Ok(match speed { 306 | rusb::Speed::Unknown => Speed::Unknown, 307 | rusb::Speed::Low => Speed::Low, 308 | rusb::Speed::Full => Speed::Full, 309 | rusb::Speed::High => Speed::High, 310 | rusb::Speed::Super => Speed::Super, 311 | rusb::Speed::SuperPlus => Speed::Superplus, 312 | _ => Speed::Unknown, 313 | }) 314 | } 315 | 316 | fn read_control( 317 | &mut self, 318 | rep: wasmtime::component::Resource, 319 | request: ControlSetup, 320 | length: u16, 321 | ) -> wasmtime::Result> { 322 | let table = self.table(); 323 | let device = table.get_mut(&rep)?; 324 | let setup = host_control_setup_to_rusb(&request); 325 | let data = device.control_transfer_in(setup, length).unwrap(); 326 | Ok(data) 327 | } 328 | 329 | fn write_control( 330 | &mut self, 331 | rep: wasmtime::component::Resource, 332 | request: ControlSetup, 333 | data: Vec, 334 | ) -> wasmtime::Result { 335 | let table = self.table(); 336 | let device = table.get_mut(&rep)?; 337 | let setup = host_control_setup_to_rusb(&request); 338 | let bytes_written = device.control_transfer_out(setup, &data).unwrap(); 339 | Ok(bytes_written as _) 340 | } 341 | } 342 | 343 | impl HostUsbConfiguration for T { 344 | fn descriptor( 345 | &mut self, 346 | rep: wasmtime::component::Resource, 347 | ) -> wasmtime::Result { 348 | let configuration = self.table().get(&rep)?; 349 | Ok(configuration.descriptor.clone()) 350 | } 351 | 352 | fn drop( 353 | &mut self, 354 | rep: wasmtime::component::Resource, 355 | ) -> wasmtime::Result<()> { 356 | self.table().delete(rep)?; 357 | Ok(()) 358 | } 359 | 360 | fn interfaces( 361 | &mut self, 362 | rep: wasmtime::component::Resource, 363 | ) -> wasmtime::Result>> { 364 | let table = self.table(); 365 | let configuration = table.get(&rep)?; 366 | 367 | Ok(configuration 368 | .get_interfaces() 369 | .into_iter() 370 | .map(|interface| table.push_child(interface, &rep)) 371 | .collect::>()?) 372 | } 373 | } 374 | 375 | impl HostUsbInterface for T { 376 | fn descriptor( 377 | &mut self, 378 | rep: wasmtime::component::Resource, 379 | ) -> wasmtime::Result { 380 | let interface = self.table().get(&rep).unwrap(); 381 | 382 | Ok(interface.descriptor.clone()) 383 | } 384 | 385 | fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { 386 | self.table().delete(rep)?; 387 | Ok(()) 388 | } 389 | 390 | fn endpoints( 391 | &mut self, 392 | rep: wasmtime::component::Resource, 393 | ) -> wasmtime::Result>> { 394 | let table = self.table(); 395 | let interface = table.get(&rep).unwrap(); 396 | 397 | Ok(interface 398 | .get_endpoints() 399 | .into_iter() 400 | .map(|endpoint| table.push_child(endpoint, &rep)) 401 | .collect::>()?) 402 | } 403 | } 404 | 405 | impl HostUsbEndpoint for T { 406 | fn descriptor( 407 | &mut self, 408 | rep: wasmtime::component::Resource, 409 | ) -> wasmtime::Result { 410 | let endpoint = self.table().get(&rep).unwrap(); 411 | 412 | Ok(endpoint.descriptor) 413 | } 414 | 415 | fn drop(&mut self, rep: wasmtime::component::Resource) -> wasmtime::Result<()> { 416 | self.table().delete(rep)?; 417 | Ok(()) 418 | } 419 | } 420 | 421 | impl Host for T {} 422 | -------------------------------------------------------------------------------- /command-components/ping/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::{fs::File, time::Duration}; 3 | 4 | #[cfg(target_arch = "wasm32")] 5 | use usb_wasm_bindings::{device::UsbDevice, types::Filter}; 6 | 7 | use anyhow::anyhow; 8 | 9 | pub fn main() -> anyhow::Result<()> { 10 | let data = std::env::args() 11 | .nth(1) 12 | .ok_or(anyhow!("Usage: ping "))?; 13 | 14 | #[cfg(target_arch = "wasm32")] 15 | let arduino_usb = UsbDevice::request_device(&Filter { 16 | vendor_id: Some(0x2341), 17 | product_id: Some(0x8057), 18 | ..Default::default() 19 | }) 20 | .ok_or(anyhow!("Arduino not found."))?; 21 | 22 | #[cfg(not(target_arch = "wasm32"))] 23 | let arduino_usb = rusb::devices()? 24 | .iter() 25 | .find(|d| { 26 | if let Ok(device_descriptor) = d.device_descriptor() { 27 | device_descriptor.vendor_id() == 0x2341 && device_descriptor.product_id() == 0x8057 28 | } else { 29 | false 30 | } 31 | }) 32 | .ok_or(anyhow!("Arduino not found."))?; 33 | 34 | // Select interface 35 | #[cfg(target_arch = "wasm32")] 36 | let configuration = arduino_usb 37 | .configurations() 38 | .into_iter() 39 | .find(|c: &usb_wasm_bindings::device::UsbConfiguration| c.descriptor().number == 1) 40 | .ok_or(anyhow!("Could not find configuration"))?; 41 | #[cfg(target_arch = "wasm32")] 42 | let interface = configuration 43 | .interfaces() 44 | .into_iter() 45 | .find(|i| { 46 | i.descriptor().interface_number == 0x00 && i.descriptor().alternate_setting == 0x00 47 | }) 48 | .ok_or(anyhow!("Could not find interface"))?; 49 | #[cfg(target_arch = "wasm32")] 50 | let endpoint_out = interface 51 | .endpoints() 52 | .into_iter() 53 | .find(|e| { 54 | e.descriptor().direction == usb_wasm_bindings::types::Direction::Out 55 | && e.descriptor().endpoint_number == 0x01 56 | }) 57 | .ok_or(anyhow!("Could not find out endpoint"))?; 58 | #[cfg(target_arch = "wasm32")] 59 | let endpoint_in = interface 60 | .endpoints() 61 | .into_iter() 62 | .find(|e| { 63 | e.descriptor().direction == usb_wasm_bindings::types::Direction::In 64 | && e.descriptor().endpoint_number == 0x02 65 | }) 66 | .ok_or(anyhow!("Could not find in endpoint"))?; 67 | 68 | #[cfg(target_arch = "wasm32")] 69 | { 70 | // Open device 71 | arduino_usb.open(); 72 | arduino_usb.reset(); 73 | // arduino_usb.select_configuration(&configuration); 74 | arduino_usb.claim_interface(&interface); 75 | } 76 | #[cfg(not(target_arch = "wasm32"))] 77 | let handle = arduino_usb.open()?; 78 | #[cfg(not(target_arch = "wasm32"))] 79 | let binding = arduino_usb.active_config_descriptor()?; 80 | #[cfg(not(target_arch = "wasm32"))] 81 | let interface_descriptor = binding 82 | .interfaces() 83 | .next() 84 | .ok_or(anyhow!("Could not find interface"))? 85 | .descriptors() 86 | .next() 87 | .ok_or(anyhow!("Could not find interface"))?; 88 | #[cfg(not(target_arch = "wasm32"))] 89 | { 90 | handle.reset()?; 91 | let _ = handle.set_auto_detach_kernel_driver(true); 92 | handle.claim_interface(0x00)?; 93 | } 94 | 95 | println!("Connected to Arduino"); 96 | 97 | let data_raw = data.as_bytes(); 98 | 99 | let mut latencies: Vec = Vec::new(); 100 | const REPEATS: usize = 100; 101 | 102 | #[cfg(target_arch = "wasm32")] 103 | match interface.descriptor().interface_protocol { 104 | 0x01 => { 105 | println!("Using protocol 0x01 (Bulk)"); 106 | for _ in 0..REPEATS { 107 | println!("Sending {} bytes (bulk): {:?}", data_raw.len(), data); 108 | let start = std::time::Instant::now(); 109 | arduino_usb.write_bulk(&endpoint_out, data_raw); 110 | let data = arduino_usb.read_bulk( 111 | &endpoint_in, 112 | endpoint_in.descriptor().max_packet_size as u64, 113 | ); 114 | let end = std::time::Instant::now(); 115 | latencies.push(end.duration_since(start)); 116 | let buf_utf8 = String::from_utf8_lossy(&data); 117 | println!("Received {} bytes (bulk): {:?}", data.len(), buf_utf8); 118 | } 119 | } 120 | 0x02 => { 121 | println!("Using protocol 0x02 (Interrupt)"); 122 | for _ in 0..REPEATS { 123 | println!("Sending {} bytes (interrupt): {:?}", data_raw.len(), data); 124 | let start = std::time::Instant::now(); 125 | arduino_usb.write_interrupt(&endpoint_out, data_raw); 126 | let data = arduino_usb.read_interrupt( 127 | &endpoint_in, 128 | endpoint_in.descriptor().max_packet_size as u64, 129 | ); 130 | let end = std::time::Instant::now(); 131 | latencies.push(end.duration_since(start)); 132 | let buf_utf8 = String::from_utf8_lossy(&data); 133 | println!("Received {} bytes (interrupt): {:?}", data.len(), buf_utf8); 134 | } 135 | } 136 | 0x03 => { 137 | println!("Using protocol 0x03 (Isochronous)"); 138 | for _ in 0..REPEATS { 139 | println!("Sending {} bytes (isochronous): {:?}", data_raw.len(), data); 140 | let start = std::time::Instant::now(); 141 | arduino_usb.write_isochronous(&endpoint_out, data_raw); 142 | let data = arduino_usb.read_isochronous(&endpoint_in); 143 | let end = std::time::Instant::now(); 144 | latencies.push(end.duration_since(start)); 145 | let buf_utf8 = String::from_utf8_lossy(&data); 146 | println!( 147 | "Received {} bytes (isochronous): {:?}", 148 | data.len(), 149 | buf_utf8 150 | ); 151 | } 152 | } 153 | _ => { 154 | println!("Unknown protocol number"); 155 | } 156 | } 157 | #[cfg(not(target_arch = "wasm32"))] 158 | match interface_descriptor.protocol_code() { 159 | 0x01 => { 160 | println!("Using protocol 0x01 (Bulk)"); 161 | for _ in 0..REPEATS { 162 | println!("Sending {} bytes (bulk): {:?}", data_raw.len(), data); 163 | let start = std::time::Instant::now(); 164 | handle.write_bulk(0x01, data_raw, Duration::from_secs(60))?; 165 | let mut data = vec![0; 64]; 166 | let bytes_read = handle.read_bulk(0x82, &mut data, Duration::from_secs(60))?; 167 | let end = std::time::Instant::now(); 168 | latencies.push(end.duration_since(start)); 169 | let buf_utf8 = String::from_utf8_lossy(&data[..bytes_read]); 170 | println!("Received {} bytes (bulk): {:?}", bytes_read, buf_utf8); 171 | } 172 | } 173 | 0x02 => { 174 | println!("Using protocol 0x02 (Interrupt)"); 175 | for _ in 0..REPEATS { 176 | println!("Sending {} bytes (interrupt): {:?}", data_raw.len(), data); 177 | let start = std::time::Instant::now(); 178 | handle.write_interrupt(0x01, data_raw, Duration::from_secs(60))?; 179 | let mut data = vec![0; 512]; 180 | let bytes_read = handle.read_interrupt(0x82, &mut data, Duration::from_secs(60))?; 181 | let end = std::time::Instant::now(); 182 | latencies.push(end.duration_since(start)); 183 | let buf_utf8 = String::from_utf8_lossy(&data[..bytes_read]); 184 | println!("Received {} bytes (interrupt): {:?}", bytes_read, buf_utf8); 185 | } 186 | } 187 | 0x03 => { 188 | println!("Using protocol 0x03 (Isochronous)"); 189 | for _ in 0..REPEATS { 190 | println!("Sending {} bytes (isochronous): {:?}", data_raw.len(), data); 191 | let start = std::time::Instant::now(); 192 | iso_transfer_out(&handle, 0x01, data_raw)?; 193 | let data = iso_transfer_in(&handle, 0x82, 512)?; 194 | let end = std::time::Instant::now(); 195 | latencies.push(end.duration_since(start)); 196 | let buf_utf8 = String::from_utf8_lossy(&data); 197 | println!( 198 | "Received {} bytes (isochronous): {:?}", 199 | data.len(), 200 | buf_utf8 201 | ); 202 | } 203 | } 204 | _ => { 205 | println!("Unknown protocol number"); 206 | } 207 | } 208 | 209 | latencies.sort(); 210 | 211 | println!("Latencies:"); 212 | for latency in latencies.iter() { 213 | println!("{:?}", latency); 214 | } 215 | 216 | println!( 217 | "Average latency: {:?}", 218 | latencies.iter().sum::() / REPEATS as _ 219 | ); 220 | println!( 221 | "Median latency: {:?}", 222 | latencies.iter().nth(latencies.len() / 2).unwrap() 223 | ); 224 | println!("Max latency: {:?}", latencies.iter().max().unwrap()); 225 | println!("Min latency: {:?}", latencies.iter().min().unwrap()); 226 | 227 | let mut file = File::create("latencies_in_ms.txt")?; 228 | for latency in latencies.iter() { 229 | writeln!(file, "{:?}", 1000. * latency.as_secs_f64())?; 230 | } 231 | 232 | Ok(()) 233 | } 234 | 235 | #[cfg(not(target_arch = "wasm32"))] 236 | extern "system" fn libusb_transfer_cb(transfer: *mut rusb::ffi::libusb_transfer) { 237 | unsafe { 238 | *((*transfer).user_data as *mut i32) = 1; 239 | } 240 | } 241 | 242 | #[cfg(not(target_arch = "wasm32"))] 243 | fn error_from_libusb(err: i32) -> rusb::Error { 244 | match err { 245 | rusb::ffi::constants::LIBUSB_ERROR_IO => rusb::Error::Io, 246 | rusb::ffi::constants::LIBUSB_ERROR_INVALID_PARAM => rusb::Error::InvalidParam, 247 | rusb::ffi::constants::LIBUSB_ERROR_ACCESS => rusb::Error::Access, 248 | rusb::ffi::constants::LIBUSB_ERROR_NO_DEVICE => rusb::Error::NoDevice, 249 | rusb::ffi::constants::LIBUSB_ERROR_NOT_FOUND => rusb::Error::NotFound, 250 | rusb::ffi::constants::LIBUSB_ERROR_BUSY => rusb::Error::Busy, 251 | rusb::ffi::constants::LIBUSB_ERROR_TIMEOUT => rusb::Error::Timeout, 252 | rusb::ffi::constants::LIBUSB_ERROR_OVERFLOW => rusb::Error::Overflow, 253 | rusb::ffi::constants::LIBUSB_ERROR_PIPE => rusb::Error::Pipe, 254 | rusb::ffi::constants::LIBUSB_ERROR_INTERRUPTED => rusb::Error::Interrupted, 255 | rusb::ffi::constants::LIBUSB_ERROR_NO_MEM => rusb::Error::NoMem, 256 | rusb::ffi::constants::LIBUSB_ERROR_NOT_SUPPORTED => rusb::Error::NotSupported, 257 | rusb::ffi::constants::LIBUSB_ERROR_OTHER => rusb::Error::Other, 258 | _ => rusb::Error::Other, 259 | } 260 | } 261 | 262 | #[cfg(not(target_arch = "wasm32"))] 263 | pub fn iso_transfer_in( 264 | handle: &rusb::DeviceHandle, 265 | endpoint: u8, 266 | buffer_size: usize, 267 | ) -> anyhow::Result> { 268 | use rusb::{ 269 | constants::LIBUSB_TRANSFER_TYPE_ISOCHRONOUS, 270 | ffi::{libusb_alloc_transfer, libusb_handle_events_completed, libusb_submit_transfer}, 271 | UsbContext, 272 | }; 273 | 274 | let transfer = unsafe { libusb_alloc_transfer(1) }; 275 | let transfer_ref = unsafe { &mut *transfer }; 276 | 277 | let mut completed = 0_i32; 278 | let completed_ptr = (&mut completed) as *mut i32; 279 | 280 | let mut buffer = vec![0; buffer_size as usize]; 281 | 282 | transfer_ref.dev_handle = handle.as_raw(); 283 | transfer_ref.endpoint = endpoint; 284 | transfer_ref.transfer_type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; 285 | transfer_ref.timeout = Duration::from_secs(60).as_millis() as _; 286 | transfer_ref.buffer = buffer.as_mut_slice().as_ptr() as *mut _; 287 | transfer_ref.length = buffer.len() as _; 288 | transfer_ref.num_iso_packets = 1; 289 | transfer_ref.user_data = completed_ptr as *mut _; 290 | 291 | let iso_packet_descs = unsafe { 292 | std::slice::from_raw_parts_mut(transfer_ref.iso_packet_desc.as_mut_ptr(), 1 as usize) 293 | }; 294 | 295 | let entry = iso_packet_descs.get_mut(0).unwrap(); 296 | entry.length = buffer_size as _; 297 | entry.status = 0; 298 | entry.actual_length = 0; 299 | 300 | transfer_ref.callback = libusb_transfer_cb; 301 | let err = unsafe { libusb_submit_transfer(transfer) }; 302 | if err != 0 { 303 | return Err(anyhow!( 304 | "Error submitting transfer: {:?}", 305 | error_from_libusb(err) 306 | )); 307 | } 308 | 309 | let mut err = 0; 310 | unsafe { 311 | while (*completed_ptr) == 0 { 312 | err = libusb_handle_events_completed(handle.context().as_raw(), completed_ptr); 313 | } 314 | }; 315 | if err != 0 { 316 | return Err(error_from_libusb(err).into()); 317 | } 318 | 319 | let entry = iso_packet_descs.get_mut(0).unwrap(); 320 | if entry.status == 0 { 321 | Ok(buffer[0..entry.actual_length as usize].to_vec()) 322 | } else { 323 | Err(anyhow!( 324 | "Error transferring data: {:?}", 325 | error_from_libusb(entry.status) 326 | )) 327 | // TODO: handle errors here 328 | // Status code meanings 329 | // https://libusb.sourceforge.io/api-1.0/group__libusb__asyncio.html#ga9fcb2aa23d342060ebda1d0cf7478856 330 | } 331 | } 332 | 333 | #[cfg(not(target_arch = "wasm32"))] 334 | pub fn iso_transfer_out( 335 | handle: &rusb::DeviceHandle, 336 | endpoint: u8, 337 | _buffer: &[u8], 338 | ) -> anyhow::Result { 339 | use rusb::{ 340 | constants::LIBUSB_TRANSFER_TYPE_ISOCHRONOUS, 341 | ffi::{libusb_alloc_transfer, libusb_handle_events_completed, libusb_submit_transfer}, 342 | UsbContext, 343 | }; 344 | 345 | let transfer = unsafe { libusb_alloc_transfer(1) }; 346 | let transfer_ref = unsafe { &mut *transfer }; 347 | 348 | let mut completed = 0_i32; 349 | let completed_ptr = (&mut completed) as *mut i32; 350 | 351 | // reorder the buffers so they're continuous in memory 352 | let buffer: Vec = _buffer.iter().copied().collect::>(); 353 | 354 | transfer_ref.dev_handle = handle.as_raw(); 355 | transfer_ref.endpoint = endpoint; 356 | transfer_ref.transfer_type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; 357 | transfer_ref.timeout = Duration::from_secs(60).as_millis() as _; 358 | transfer_ref.buffer = buffer.as_ptr() as *mut _; 359 | transfer_ref.length = buffer.len() as _; 360 | transfer_ref.num_iso_packets = 1; 361 | // It should be okay to pass in this (stack) variable, as this function will not return untill after the transfer is complete. 362 | transfer_ref.user_data = completed_ptr as *mut _; 363 | 364 | let iso_packet_descs = unsafe { 365 | std::slice::from_raw_parts_mut(transfer_ref.iso_packet_desc.as_mut_ptr(), _buffer.len()) 366 | }; 367 | 368 | let entry = iso_packet_descs.get_mut(0).unwrap(); 369 | entry.length = buffer.len() as _; 370 | entry.status = 0; 371 | entry.actual_length = 0; 372 | 373 | transfer_ref.callback = libusb_transfer_cb; 374 | 375 | let err = unsafe { libusb_submit_transfer(transfer) }; 376 | if err != 0 { 377 | return Err(error_from_libusb(err).into()); 378 | } 379 | 380 | let mut err = 0; 381 | unsafe { 382 | while (*completed_ptr) == 0 { 383 | err = libusb_handle_events_completed(handle.context().as_raw(), completed_ptr); 384 | } 385 | }; 386 | if err != 0 { 387 | return Err(error_from_libusb(err).into()); 388 | } 389 | 390 | let mut bytes_written: u64 = 0; 391 | for i in 0.._buffer.len() { 392 | let entry = iso_packet_descs.get_mut(i).unwrap(); 393 | if entry.status == 0 { 394 | bytes_written += entry.actual_length as u64; 395 | } else { 396 | // TODO: handle errors here 397 | // Status code meanings 398 | // https://libusb.sourceforge.io/api-1.0/group__libusb__asyncio.html#ga9fcb2aa23d342060ebda1d0cf7478856 399 | } 400 | } 401 | 402 | Ok(bytes_written) 403 | } 404 | -------------------------------------------------------------------------------- /command-components/mass-storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read, Seek, Write}; 2 | 3 | use bulk_only::BulkOnlyTransportDevice; 4 | 5 | use chrono::{DateTime, Local}; 6 | use fatfs::{Dir, FileSystem, FsOptions, ReadWriteSeek}; 7 | use mass_storage::MassStorageDevice; 8 | use rand::{Fill, Rng}; 9 | use tracing::{debug, info}; 10 | #[cfg(target_arch = "wasm32")] 11 | use usb_wasm_bindings::device::UsbDevice; 12 | 13 | use anyhow::anyhow; 14 | 15 | pub mod bulk_only; 16 | pub mod mass_storage; 17 | 18 | pub fn tree(path: Option) -> anyhow::Result<()> { 19 | fn _tree(dir: Dir<'_, impl ReadWriteSeek>, depth: usize) -> Result, io::Error> { 20 | debug!(depth, "build_fs_tree_"); 21 | if depth > 10 { 22 | return Ok(vec![]); 23 | } 24 | 25 | let mut lines: Vec = Vec::new(); 26 | for entry in dir.iter() { 27 | let entry = entry.unwrap(); 28 | if entry.file_name().starts_with('.') { 29 | continue; 30 | } 31 | lines.push(format!("{}|_ {}", " ".repeat(depth), entry.file_name())); 32 | if entry.is_dir() { 33 | lines.extend(_tree(entry.to_dir(), depth + 1)?); 34 | } 35 | } 36 | Ok(lines) 37 | } 38 | 39 | let fs = get_filesystem()?; 40 | let root_dir = fs.root_dir(); 41 | let dir = match path { 42 | None => root_dir, 43 | Some(ref path) if path == "." => root_dir, 44 | Some(ref path) => root_dir.open_dir(path)?, 45 | }; 46 | 47 | let lines = [vec!["\\.".to_string()], _tree(dir, 0)?].concat(); 48 | println!("{}", lines.join("\n")); 49 | Ok(()) 50 | } 51 | 52 | pub fn ls(dir: Option) -> anyhow::Result<()> { 53 | fn format_file_size(size: u64) -> String { 54 | const KB: u64 = 1024; 55 | const MB: u64 = 1024 * KB; 56 | const GB: u64 = 1024 * MB; 57 | if size < KB { 58 | format!("{}B", size) 59 | } else if size < MB { 60 | format!("{}KB", size / KB) 61 | } else if size < GB { 62 | format!("{}MB", size / MB) 63 | } else { 64 | format!("{}GB", size / GB) 65 | } 66 | } 67 | 68 | let fs = get_filesystem()?; 69 | let root_dir = fs.root_dir(); 70 | let dir = match dir { 71 | None => root_dir, 72 | Some(ref path) if path == "." => root_dir, 73 | Some(ref path) => root_dir.open_dir(path)?, 74 | }; 75 | for r in dir.iter() { 76 | let e = r?; 77 | let modified = DateTime::::from(e.modified()) 78 | .format("%Y-%m-%d %H:%M:%S") 79 | .to_string(); 80 | println!( 81 | "{:4} {} {}", 82 | format_file_size(e.len()), 83 | modified, 84 | e.file_name() 85 | ); 86 | } 87 | Ok(()) 88 | } 89 | 90 | pub fn cat(file: String) -> anyhow::Result<()> { 91 | let fs = get_filesystem()?; 92 | let root_dir = fs.root_dir(); 93 | let mut file = root_dir.open_file(&file)?; 94 | let mut buf = vec![]; 95 | file.read_to_end(&mut buf)?; 96 | print!("{}", String::from_utf8_lossy(&buf)); 97 | Ok(()) 98 | } 99 | 100 | pub fn write(path: &str, contents: &[u8]) -> anyhow::Result<()> { 101 | let fs = get_filesystem()?; 102 | let mut file = fs.root_dir().create_file(path)?; 103 | file.truncate()?; 104 | file.write_all(contents)?; 105 | Ok(()) 106 | } 107 | 108 | #[cfg(target_arch = "wasm32")] 109 | fn get_mass_storage_device() -> anyhow::Result { 110 | // Find device 111 | let msd = { 112 | let mut mass_storage_devices: Vec = Vec::new(); 113 | for device in UsbDevice::enumerate().into_iter() { 114 | let configuration = device.configurations().remove(0); 115 | let interface = configuration.interfaces().into_iter().find(|interface| { 116 | let if_descriptor = interface.descriptor(); 117 | if_descriptor.interface_class == 0x08 && if_descriptor.interface_protocol == 0x50 118 | }); 119 | if let Some(interface) = interface { 120 | let bulk_only_transport = 121 | BulkOnlyTransportDevice::new(device, configuration, interface); 122 | mass_storage_devices.push(MassStorageDevice::new(bulk_only_transport).unwrap()); 123 | } 124 | } 125 | 126 | if mass_storage_devices.is_empty() { 127 | return Err(anyhow!("No mass storage devices found. Exiting.")); 128 | } 129 | 130 | if mass_storage_devices.len() == 1 { 131 | mass_storage_devices.remove(0) 132 | } else { 133 | let mut input = String::new(); 134 | println!("Please select a device:"); 135 | for (i, msd) in mass_storage_devices.iter().enumerate() { 136 | let properties = msd.get_properties(); 137 | 138 | println!( 139 | "{}. {} ({})", 140 | i, 141 | properties.name, 142 | human_readable_file_size(properties.capacity, 2) 143 | ); 144 | } 145 | io::stdin().read_line(&mut input)?; 146 | let i: usize = input.trim().parse()?; 147 | mass_storage_devices.remove(i) 148 | } 149 | }; 150 | { 151 | let properties = msd.get_properties(); 152 | info!( 153 | "Selected: {} ({})", 154 | properties.name, 155 | human_readable_file_size(properties.capacity, 2) 156 | ); 157 | } 158 | Ok(msd) 159 | } 160 | 161 | #[cfg(not(target_arch = "wasm32"))] 162 | fn get_mass_storage_device() -> anyhow::Result { 163 | // Find device 164 | 165 | use rusb::UsbContext; 166 | let msd = { 167 | let mut mass_storage_devices: Vec = Vec::new(); 168 | 169 | for device in rusb::GlobalContext::default().devices()?.iter() { 170 | let configuration = device.config_descriptor(0)?; 171 | let interface = configuration.interfaces().find(|interface| { 172 | let if_descriptor = interface.descriptors().next().unwrap(); 173 | if_descriptor.class_code() == 0x08 && if_descriptor.protocol_code() == 0x50 174 | }); 175 | if let Some(interface) = interface { 176 | let bulk_only_transport = 177 | BulkOnlyTransportDevice::new(device, 0, interface.number()); 178 | mass_storage_devices.push(MassStorageDevice::new(bulk_only_transport).unwrap()); 179 | } 180 | } 181 | 182 | if mass_storage_devices.is_empty() { 183 | return Err(anyhow!("No mass storage devices found. Exiting.")); 184 | } 185 | 186 | if mass_storage_devices.len() == 1 { 187 | mass_storage_devices.remove(0) 188 | } else { 189 | let mut input = String::new(); 190 | println!("Please select a device:"); 191 | for (i, msd) in mass_storage_devices.iter().enumerate() { 192 | let properties = msd.get_properties(); 193 | 194 | println!( 195 | "{}. {} ({})", 196 | i, 197 | properties.name, 198 | human_readable_file_size(properties.capacity, 2) 199 | ); 200 | } 201 | io::stdin().read_line(&mut input)?; 202 | let i: usize = input.trim().parse()?; 203 | mass_storage_devices.remove(i) 204 | } 205 | }; 206 | { 207 | let properties = msd.get_properties(); 208 | info!( 209 | "Selected: {} ({})", 210 | properties.name, 211 | human_readable_file_size(properties.capacity, 2) 212 | ); 213 | } 214 | Ok(msd) 215 | } 216 | 217 | fn get_filesystem() -> anyhow::Result> { 218 | let mut msd = get_mass_storage_device().unwrap(); 219 | // let mut msd = 220 | // BufStream::with_capacities(24576, 24576, get_mass_storage_device().unwrap()); 221 | let mbr = mbrman::MBR::read_from(&mut msd, 512)?; 222 | let (_, partition) = mbr.iter().next().ok_or(anyhow!("No partition found"))?; 223 | let starting_lba = partition.starting_lba; 224 | let sectors = partition.sectors; 225 | let sector_size = mbr.sector_size; 226 | 227 | println!("starting_lba: {}", starting_lba); 228 | println!("sectors: {}", sectors); 229 | 230 | let fat_slice = fscommon::StreamSlice::new( 231 | msd, 232 | (starting_lba * sector_size).into(), 233 | (starting_lba + sectors) as u64 * sector_size as u64, 234 | ) 235 | .unwrap(); 236 | 237 | debug!("Initialized Filesystem"); 238 | Ok(FileSystem::new(fat_slice, FsOptions::new())?) 239 | } 240 | 241 | // WARNING: This will probably break your filesystem, as this function just writes random blocks to the device 242 | // Breaks the USB when test_count > 1 for some reason? 243 | pub fn benchmark_raw_speed( 244 | test_count: usize, 245 | seq_test_size_mb: usize, 246 | rnd_test_size_mb: usize, 247 | samples: usize, 248 | ) -> anyhow::Result<()> { 249 | let mut msd = get_mass_storage_device()?; 250 | let properties = msd.get_properties(); 251 | 252 | let mut rng = rand::thread_rng(); 253 | 254 | let seq_test_size = seq_test_size_mb * 1024 * 1024; 255 | let rnd_test_size = seq_test_size_mb * 1024 * 1024; 256 | 257 | struct Report { 258 | sequential_write_speed: f64, 259 | sequential_read_speed: f64, 260 | random_write_speed: f64, 261 | random_read_speed: f64, 262 | } 263 | 264 | info!("Starting benchmark"); 265 | info!( 266 | "Seq Test Data: {}, Rnd Test Data: {}, ", 267 | human_readable_file_size(seq_test_size_mb as u64 * 1024 * 1024, 2), 268 | human_readable_file_size(rnd_test_size_mb as u64 * 1024 * 1024, 2), 269 | ); 270 | const NUM_BLOCKS: u32 = 2048; 271 | let mut reports = vec![]; 272 | for i in 0..samples { 273 | info!("Starting iteration {}", i); 274 | let mut report = Report { 275 | sequential_write_speed: 0.0, 276 | sequential_read_speed: 0.0, 277 | random_write_speed: 0.0, 278 | random_read_speed: 0.0, 279 | }; 280 | 281 | let seq_num_repetitions: u32 = 282 | ((seq_test_size / (NUM_BLOCKS * 512) as usize) as u32).max(1); 283 | 284 | // Benchmark SEQUENTIAL reads and writes: 285 | for _ in 0..test_count { 286 | let mut data = vec![0_u8; NUM_BLOCKS as usize * 512]; 287 | data[..].try_fill(&mut rng)?; 288 | 289 | let address = 8192; 290 | // rng.gen_range(0..properties.total_number_of_blocks - NUM_REPETITIONS * NUM_BLOCKS); 291 | let start_write = std::time::Instant::now(); 292 | for i in 0..seq_num_repetitions { 293 | msd.write_blocks(address + i * NUM_BLOCKS, NUM_BLOCKS as u16, &data); 294 | } 295 | let end_write = std::time::Instant::now(); 296 | let write_time = end_write - start_write; 297 | report.sequential_write_speed += seq_test_size as f64 / write_time.as_secs_f64(); 298 | } 299 | report.sequential_write_speed /= test_count as f64; 300 | 301 | for _ in 0..test_count { 302 | let address = 8192; 303 | // rng.gen_range(0..properties.total_number_of_blocks - NUM_REPETITIONS * NUM_BLOCKS); 304 | let start_read = std::time::Instant::now(); 305 | for i in 0..seq_num_repetitions { 306 | msd.read_blocks(address + i * NUM_BLOCKS, NUM_BLOCKS as u16); 307 | } 308 | let end_read = std::time::Instant::now(); 309 | let read_time = end_read - start_read; 310 | report.sequential_read_speed += seq_test_size as f64 / read_time.as_secs_f64(); 311 | } 312 | report.sequential_read_speed /= test_count as f64; 313 | 314 | let rnd_num_repetitions: u32 = 315 | ((rnd_test_size / (NUM_BLOCKS * 512) as usize) as u32).max(1); 316 | // Benchmark RANDOM reads and writes: 317 | for _ in 0..test_count { 318 | let mut data = vec![0_u8; NUM_BLOCKS as usize * 512]; 319 | data[..].try_fill(&mut rng)?; 320 | 321 | let addresses: Vec = (0..rnd_num_repetitions) 322 | .map(|_| rng.gen_range(8192..properties.total_number_of_blocks - NUM_BLOCKS)) 323 | .collect(); 324 | let start_write = std::time::Instant::now(); 325 | for address in addresses { 326 | msd.write_blocks(address, NUM_BLOCKS as u16, &data); 327 | } 328 | let end_write = std::time::Instant::now(); 329 | let write_time = end_write - start_write; 330 | report.random_write_speed += seq_test_size as f64 / write_time.as_secs_f64(); 331 | } 332 | report.random_write_speed /= test_count as f64; 333 | 334 | for _ in 0..test_count { 335 | let addresses: Vec = (0..rnd_num_repetitions) 336 | .map(|_| rng.gen_range(8192..properties.total_number_of_blocks - NUM_BLOCKS)) 337 | .collect(); 338 | let start_read = std::time::Instant::now(); 339 | for address in addresses { 340 | msd.read_blocks(address, NUM_BLOCKS as u16); 341 | } 342 | let end_read = std::time::Instant::now(); 343 | let read_time = end_read - start_read; 344 | report.random_read_speed += seq_test_size as f64 / read_time.as_secs_f64(); 345 | } 346 | report.random_read_speed /= test_count as f64; 347 | 348 | info!( 349 | "SEQ WRITE: {}/s, SEQ READ: {}/s, RND WRITE: {}/s, RND READ: {}/s", 350 | human_readable_file_size(report.sequential_write_speed as u64, 2), 351 | human_readable_file_size(report.sequential_read_speed as u64, 2), 352 | human_readable_file_size(report.random_write_speed as u64, 2), 353 | human_readable_file_size(report.random_read_speed as u64, 2), 354 | ); 355 | 356 | reports.push(report); 357 | 358 | if i < samples - 1 { 359 | info!("Waiting 5 seconds to let the USB drive cool down..."); 360 | std::thread::sleep(std::time::Duration::from_secs(5)); 361 | } 362 | } 363 | 364 | let mut file = std::fs::File::create("raw_benchmark_report.csv")?; 365 | writeln!(file, "SEQ WRITE,SEQ READ,RND WRITE,RND READ")?; 366 | for report in reports { 367 | writeln!( 368 | file, 369 | "{},{},{},{}", 370 | report.sequential_write_speed, 371 | report.sequential_read_speed, 372 | report.random_write_speed, 373 | report.random_read_speed 374 | )?; 375 | } 376 | // for report in reports { 377 | 378 | // } 379 | 380 | Ok(()) 381 | } 382 | 383 | pub fn benchmark(seq_test_size_mb: usize) -> anyhow::Result<()> { 384 | let fs = get_filesystem()?; 385 | 386 | let root_dir = fs.root_dir(); 387 | let mut temp_file = root_dir.create_file("temp.bin")?; 388 | temp_file.truncate()?; 389 | 390 | let mut rng = rand::thread_rng(); 391 | 392 | let seq_test_size = seq_test_size_mb * 1024 * 1024; 393 | 394 | struct Report { 395 | sequential_write_speed: f64, 396 | sequential_read_speed: f64, 397 | } 398 | 399 | println!("Starting benchmark"); 400 | println!( 401 | "Seq Test Data: {}", 402 | human_readable_file_size(seq_test_size as u64, 2), 403 | ); 404 | 405 | let mut report = Report { 406 | sequential_write_speed: 0.0, 407 | sequential_read_speed: 0.0, 408 | }; 409 | 410 | // Benchmark SEQUENTIAL reads and writes: 411 | { 412 | let mut data = vec![0_u8; seq_test_size]; 413 | data[..].try_fill(&mut rng)?; 414 | 415 | temp_file.seek(io::SeekFrom::Start(0))?; 416 | let start_write = std::time::Instant::now(); 417 | temp_file.write_all(&data)?; 418 | let end_write = std::time::Instant::now(); 419 | let write_time = end_write - start_write; 420 | report.sequential_write_speed = seq_test_size as f64 / write_time.as_secs_f64(); 421 | } 422 | 423 | { 424 | let mut data = Vec::new(); 425 | temp_file.seek(io::SeekFrom::Start(0))?; 426 | let start_read = std::time::Instant::now(); 427 | temp_file.read_to_end(&mut data)?; 428 | let end_read = std::time::Instant::now(); 429 | let read_time = end_read - start_read; 430 | report.sequential_read_speed = data.len() as f64 / read_time.as_secs_f64(); 431 | } 432 | 433 | println!( 434 | "SEQ WRITE: {}/s, SEQ READ: {}/s", 435 | human_readable_file_size(report.sequential_write_speed as u64, 2), 436 | human_readable_file_size(report.sequential_read_speed as u64, 2), 437 | ); 438 | temp_file.flush()?; 439 | std::mem::drop(temp_file); 440 | root_dir.remove("temp.bin")?; 441 | 442 | Ok(()) 443 | } 444 | 445 | fn human_readable_file_size(size_in_bytes: u64, decimal_places: usize) -> String { 446 | let units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; 447 | 448 | let mut size = size_in_bytes as f64; 449 | 450 | let mut i = 0; 451 | 452 | while i < (units.len() - 1) && size >= 1024.0 { 453 | size /= 1024.0; 454 | i += 1; 455 | } 456 | 457 | format!("{:.1$} {2}", size, decimal_places, units[i]) 458 | } 459 | -------------------------------------------------------------------------------- /command-components/enumerate-devices-go/api/bindings.h: -------------------------------------------------------------------------------- 1 | // Generated by `wit-bindgen` 0.24.0. DO NOT EDIT! 2 | #ifndef __BINDINGS_BINDINGS_H 3 | #define __BINDINGS_BINDINGS_H 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef struct bindings_string_t { 12 | uint8_t*ptr; 13 | size_t len; 14 | } bindings_string_t; 15 | 16 | typedef struct wadu436_usb_types_version_t { 17 | uint8_t f0; 18 | uint8_t f1; 19 | uint8_t f2; 20 | } wadu436_usb_types_version_t; 21 | 22 | typedef struct bindings_option_u16_t { 23 | bool is_some; 24 | uint16_t val; 25 | } bindings_option_u16_t; 26 | 27 | typedef struct bindings_option_u8_t { 28 | bool is_some; 29 | uint8_t val; 30 | } bindings_option_u8_t; 31 | 32 | typedef struct bindings_option_string_t { 33 | bool is_some; 34 | bindings_string_t val; 35 | } bindings_option_string_t; 36 | 37 | // Record for passing into the request-device convenience function. 38 | typedef struct wadu436_usb_types_filter_t { 39 | bindings_option_u16_t vendor_id; 40 | bindings_option_u16_t product_id; 41 | bindings_option_u8_t class_code; 42 | bindings_option_u8_t subclass_code; 43 | bindings_option_u8_t protocol_code; 44 | bindings_option_string_t serial_number; 45 | } wadu436_usb_types_filter_t; 46 | 47 | // Endpoint transfer types 48 | typedef uint8_t wadu436_usb_types_transfer_type_t; 49 | 50 | #define WADU436_USB_TYPES_TRANSFER_TYPE_CONTROL 0 51 | #define WADU436_USB_TYPES_TRANSFER_TYPE_ISOCHRONOUS 1 52 | #define WADU436_USB_TYPES_TRANSFER_TYPE_BULK 2 53 | #define WADU436_USB_TYPES_TRANSFER_TYPE_INTERRUPT 3 54 | 55 | // Endpoint direction 56 | typedef uint8_t wadu436_usb_types_direction_t; 57 | 58 | #define WADU436_USB_TYPES_DIRECTION_OUT 0 59 | #define WADU436_USB_TYPES_DIRECTION_IN 1 60 | 61 | // Device speed 62 | typedef uint8_t wadu436_usb_types_speed_t; 63 | 64 | #define WADU436_USB_TYPES_SPEED_UNKNOWN 0 65 | // OS doesn't know which speed the device is using 66 | #define WADU436_USB_TYPES_SPEED_LOW 1 67 | // 1.5 Mbit/s 68 | #define WADU436_USB_TYPES_SPEED_FULL 2 69 | // 12 Mbit/s 70 | #define WADU436_USB_TYPES_SPEED_HIGH 3 71 | // 480 Mbit/s 72 | #define WADU436_USB_TYPES_SPEED_SUPER 4 73 | // 5 Gbit/s 74 | #define WADU436_USB_TYPES_SPEED_SUPERPLUS 5 75 | 76 | // Setup type for control transfers 77 | typedef uint8_t wadu436_usb_types_control_setup_type_t; 78 | 79 | #define WADU436_USB_TYPES_CONTROL_SETUP_TYPE_STANDARD 0 80 | #define WADU436_USB_TYPES_CONTROL_SETUP_TYPE_CLASS 1 81 | #define WADU436_USB_TYPES_CONTROL_SETUP_TYPE_VENDOR 2 82 | 83 | // Recipient for control transfers 84 | typedef uint8_t wadu436_usb_types_control_setup_recipient_t; 85 | 86 | #define WADU436_USB_TYPES_CONTROL_SETUP_RECIPIENT_DEVICE 0 87 | #define WADU436_USB_TYPES_CONTROL_SETUP_RECIPIENT_INTERFACE 1 88 | // interface, but that's a keyword 89 | #define WADU436_USB_TYPES_CONTROL_SETUP_RECIPIENT_ENDPOINT 2 90 | 91 | // Control setup packet 92 | typedef struct wadu436_usb_types_control_setup_t { 93 | wadu436_usb_types_control_setup_type_t request_type; 94 | wadu436_usb_types_control_setup_recipient_t request_recipient; 95 | uint8_t request; 96 | // bRequest 97 | uint16_t value; 98 | // wValue 99 | uint16_t index; 100 | } wadu436_usb_types_control_setup_t; 101 | 102 | typedef wadu436_usb_types_version_t wadu436_usb_descriptors_version_t; 103 | 104 | typedef wadu436_usb_types_direction_t wadu436_usb_descriptors_direction_t; 105 | 106 | typedef wadu436_usb_types_transfer_type_t wadu436_usb_descriptors_transfer_type_t; 107 | 108 | // Contains the fields of the device descriptor as defined in the USB 2.0 spec 109 | typedef struct wadu436_usb_descriptors_device_descriptor_t { 110 | bindings_option_string_t product_name; 111 | bindings_option_string_t manufacturer_name; 112 | bindings_option_string_t serial_number; 113 | wadu436_usb_descriptors_version_t usb_version; 114 | uint16_t vendor_id; 115 | uint16_t product_id; 116 | wadu436_usb_descriptors_version_t device_version; 117 | uint8_t device_class; 118 | uint8_t device_subclass; 119 | uint8_t device_protocol; 120 | uint8_t max_packet_size; 121 | } wadu436_usb_descriptors_device_descriptor_t; 122 | 123 | // Contains the fields of the configuration descriptor 124 | typedef struct wadu436_usb_descriptors_configuration_descriptor_t { 125 | uint8_t number; 126 | bindings_option_string_t description; 127 | bool self_powered; 128 | bool remote_wakeup; 129 | uint16_t max_power; 130 | } wadu436_usb_descriptors_configuration_descriptor_t; 131 | 132 | // Contains the fields of the interface descriptor 133 | typedef struct wadu436_usb_descriptors_interface_descriptor_t { 134 | uint8_t interface_number; 135 | uint8_t alternate_setting; 136 | uint8_t interface_class; 137 | uint8_t interface_subclass; 138 | uint8_t interface_protocol; 139 | bindings_option_string_t interface_name; 140 | } wadu436_usb_descriptors_interface_descriptor_t; 141 | 142 | // Contains the fields of the endpoint descriptor 143 | typedef struct wadu436_usb_descriptors_endpoint_descriptor_t { 144 | uint8_t endpoint_number; 145 | // 0-15, lower 4 bits of the bEndpointAddress field 146 | wadu436_usb_descriptors_direction_t direction; 147 | // corresponds to bit 7 of the bEndpointAddress field 148 | wadu436_usb_descriptors_transfer_type_t transfer_type; 149 | // corresponds to bits 0-1 of the bmAttributes field 150 | uint16_t max_packet_size; 151 | uint8_t interval; 152 | } wadu436_usb_descriptors_endpoint_descriptor_t; 153 | 154 | typedef wadu436_usb_descriptors_device_descriptor_t wadu436_usb_device_device_descriptor_t; 155 | 156 | typedef wadu436_usb_descriptors_configuration_descriptor_t wadu436_usb_device_configuration_descriptor_t; 157 | 158 | typedef wadu436_usb_descriptors_interface_descriptor_t wadu436_usb_device_interface_descriptor_t; 159 | 160 | typedef wadu436_usb_descriptors_endpoint_descriptor_t wadu436_usb_device_endpoint_descriptor_t; 161 | 162 | typedef wadu436_usb_types_speed_t wadu436_usb_device_speed_t; 163 | 164 | typedef wadu436_usb_types_filter_t wadu436_usb_device_filter_t; 165 | 166 | typedef wadu436_usb_types_control_setup_type_t wadu436_usb_device_control_setup_type_t; 167 | 168 | typedef wadu436_usb_types_control_setup_recipient_t wadu436_usb_device_control_setup_recipient_t; 169 | 170 | typedef wadu436_usb_types_control_setup_t wadu436_usb_device_control_setup_t; 171 | 172 | typedef struct wadu436_usb_device_own_usb_device_t { 173 | int32_t __handle; 174 | } wadu436_usb_device_own_usb_device_t; 175 | 176 | typedef struct wadu436_usb_device_borrow_usb_device_t { 177 | int32_t __handle; 178 | } wadu436_usb_device_borrow_usb_device_t; 179 | 180 | typedef struct wadu436_usb_device_own_usb_configuration_t { 181 | int32_t __handle; 182 | } wadu436_usb_device_own_usb_configuration_t; 183 | 184 | typedef struct wadu436_usb_device_borrow_usb_configuration_t { 185 | int32_t __handle; 186 | } wadu436_usb_device_borrow_usb_configuration_t; 187 | 188 | typedef struct wadu436_usb_device_own_usb_interface_t { 189 | int32_t __handle; 190 | } wadu436_usb_device_own_usb_interface_t; 191 | 192 | typedef struct wadu436_usb_device_borrow_usb_interface_t { 193 | int32_t __handle; 194 | } wadu436_usb_device_borrow_usb_interface_t; 195 | 196 | typedef struct wadu436_usb_device_own_usb_endpoint_t { 197 | int32_t __handle; 198 | } wadu436_usb_device_own_usb_endpoint_t; 199 | 200 | typedef struct wadu436_usb_device_borrow_usb_endpoint_t { 201 | int32_t __handle; 202 | } wadu436_usb_device_borrow_usb_endpoint_t; 203 | 204 | typedef struct wadu436_usb_device_list_own_usb_device_t { 205 | wadu436_usb_device_own_usb_device_t *ptr; 206 | size_t len; 207 | } wadu436_usb_device_list_own_usb_device_t; 208 | 209 | typedef struct wadu436_usb_device_option_own_usb_device_t { 210 | bool is_some; 211 | wadu436_usb_device_own_usb_device_t val; 212 | } wadu436_usb_device_option_own_usb_device_t; 213 | 214 | typedef struct wadu436_usb_device_list_own_usb_configuration_t { 215 | wadu436_usb_device_own_usb_configuration_t *ptr; 216 | size_t len; 217 | } wadu436_usb_device_list_own_usb_configuration_t; 218 | 219 | typedef struct bindings_list_u8_t { 220 | uint8_t *ptr; 221 | size_t len; 222 | } bindings_list_u8_t; 223 | 224 | typedef struct wadu436_usb_device_list_own_usb_interface_t { 225 | wadu436_usb_device_own_usb_interface_t *ptr; 226 | size_t len; 227 | } wadu436_usb_device_list_own_usb_interface_t; 228 | 229 | typedef struct wadu436_usb_device_list_own_usb_endpoint_t { 230 | wadu436_usb_device_own_usb_endpoint_t *ptr; 231 | size_t len; 232 | } wadu436_usb_device_list_own_usb_endpoint_t; 233 | 234 | // Imported Functions from `wadu436:usb/device@0.0.1` 235 | // Main entry point for the API. 236 | // Returns all the USB devices currently connected to the system (or if access control is implemented by the runtime, only the ones the component has access to) 237 | extern void wadu436_usb_device_static_usb_device_enumerate(wadu436_usb_device_list_own_usb_device_t *ret); 238 | // Convenience funtion, equivalent to calling enumerate(), applying the provided filters to the list, and returning the first element 239 | extern void wadu436_usb_device_static_usb_device_request_device(wadu436_usb_device_filter_t *filter, wadu436_usb_device_option_own_usb_device_t *ret); 240 | // Returns the device descriptor of the device 241 | extern void wadu436_usb_device_method_usb_device_descriptor(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_device_descriptor_t *ret); 242 | // Returns the USB Speed of the device (Low, Full, High, ...) 243 | extern wadu436_usb_device_speed_t wadu436_usb_device_method_usb_device_speed(wadu436_usb_device_borrow_usb_device_t self); 244 | // Returns all the configurations the device supports 245 | extern void wadu436_usb_device_method_usb_device_configurations(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_list_own_usb_configuration_t *ret); 246 | // Returns the currently active configuration 247 | extern wadu436_usb_device_own_usb_configuration_t wadu436_usb_device_method_usb_device_active_configuration(wadu436_usb_device_borrow_usb_device_t self); 248 | // Opens the device. This is required before any transfers can be made. 249 | extern void wadu436_usb_device_method_usb_device_open(wadu436_usb_device_borrow_usb_device_t self); 250 | // Returns whether the device is currently open. 251 | extern bool wadu436_usb_device_method_usb_device_opened(wadu436_usb_device_borrow_usb_device_t self); 252 | // Resets the device. 253 | extern void wadu436_usb_device_method_usb_device_reset(wadu436_usb_device_borrow_usb_device_t self); 254 | // Closes the device. 255 | extern void wadu436_usb_device_method_usb_device_close(wadu436_usb_device_borrow_usb_device_t self); 256 | // Selects the active configuration. The device must first be opened. 257 | extern void wadu436_usb_device_method_usb_device_select_configuration(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_configuration_t configuration); 258 | // Claims an interface for exclusive use. Also selects the alternate interface, as the usb-interface resource actually represents an alternate interface. 259 | extern void wadu436_usb_device_method_usb_device_claim_interface(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_interface_t interface); 260 | // Releases an interface. 261 | extern void wadu436_usb_device_method_usb_device_release_interface(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_interface_t interface); 262 | // Clears a halt on a specific endpoint. 263 | extern void wadu436_usb_device_method_usb_device_clear_halt(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint); 264 | // Read control data from the device. The endpoint is always EP0. 265 | extern void wadu436_usb_device_method_usb_device_read_control(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_control_setup_t *request, uint16_t length, bindings_list_u8_t *ret); 266 | // Write control data to the device. The endpoint is always EP0. The return value is the number of bytes written. 267 | extern uint64_t wadu436_usb_device_method_usb_device_write_control(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_control_setup_t *request, bindings_list_u8_t *data); 268 | // Read data from an interrupt endpoint. The endpoint must be an interrupt endpoint. 269 | extern void wadu436_usb_device_method_usb_device_read_interrupt(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint, uint64_t length, bindings_list_u8_t *ret); 270 | // Write data to an interrupt endpoint. The endpoint must be an interrupt endpoint. The return value is the number of bytes written. 271 | extern uint64_t wadu436_usb_device_method_usb_device_write_interrupt(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint, bindings_list_u8_t *data); 272 | // Read data from a bulk endpoint. The endpoint must be a bulk endpoint. 273 | extern void wadu436_usb_device_method_usb_device_read_bulk(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint, uint64_t length, bindings_list_u8_t *ret); 274 | // Write data to a bulk endpoint. The endpoint must be a bulk endpoint. The return value is the number of bytes written. 275 | extern uint64_t wadu436_usb_device_method_usb_device_write_bulk(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint, bindings_list_u8_t *data); 276 | // TODO: support sending/receiving multiple packets at once for isochronous endpoints? 277 | // Read data from an isochronous endpoint. The endpoint must be an isochronous endpoint. 278 | extern void wadu436_usb_device_method_usb_device_read_isochronous(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint, bindings_list_u8_t *ret); 279 | // Write data to an isochronous endpoint. The endpoint must be an isochronous endpoint. The return value is the number of bytes written. 280 | extern uint64_t wadu436_usb_device_method_usb_device_write_isochronous(wadu436_usb_device_borrow_usb_device_t self, wadu436_usb_device_borrow_usb_endpoint_t endpoint, bindings_list_u8_t *data); 281 | extern void wadu436_usb_device_method_usb_configuration_descriptor(wadu436_usb_device_borrow_usb_configuration_t self, wadu436_usb_device_configuration_descriptor_t *ret); 282 | extern void wadu436_usb_device_method_usb_configuration_interfaces(wadu436_usb_device_borrow_usb_configuration_t self, wadu436_usb_device_list_own_usb_interface_t *ret); 283 | extern void wadu436_usb_device_method_usb_interface_descriptor(wadu436_usb_device_borrow_usb_interface_t self, wadu436_usb_device_interface_descriptor_t *ret); 284 | extern void wadu436_usb_device_method_usb_interface_endpoints(wadu436_usb_device_borrow_usb_interface_t self, wadu436_usb_device_list_own_usb_endpoint_t *ret); 285 | extern void wadu436_usb_device_method_usb_endpoint_descriptor(wadu436_usb_device_borrow_usb_endpoint_t self, wadu436_usb_device_endpoint_descriptor_t *ret); 286 | 287 | // Helper Functions 288 | 289 | void bindings_option_u16_free(bindings_option_u16_t *ptr); 290 | 291 | void bindings_option_u8_free(bindings_option_u8_t *ptr); 292 | 293 | void bindings_option_string_free(bindings_option_string_t *ptr); 294 | 295 | void wadu436_usb_types_filter_free(wadu436_usb_types_filter_t *ptr); 296 | 297 | void wadu436_usb_descriptors_device_descriptor_free(wadu436_usb_descriptors_device_descriptor_t *ptr); 298 | 299 | void wadu436_usb_descriptors_configuration_descriptor_free(wadu436_usb_descriptors_configuration_descriptor_t *ptr); 300 | 301 | void wadu436_usb_descriptors_interface_descriptor_free(wadu436_usb_descriptors_interface_descriptor_t *ptr); 302 | 303 | void wadu436_usb_device_device_descriptor_free(wadu436_usb_device_device_descriptor_t *ptr); 304 | 305 | void wadu436_usb_device_configuration_descriptor_free(wadu436_usb_device_configuration_descriptor_t *ptr); 306 | 307 | void wadu436_usb_device_interface_descriptor_free(wadu436_usb_device_interface_descriptor_t *ptr); 308 | 309 | void wadu436_usb_device_filter_free(wadu436_usb_device_filter_t *ptr); 310 | 311 | extern void wadu436_usb_device_usb_device_drop_own(wadu436_usb_device_own_usb_device_t handle); 312 | 313 | extern wadu436_usb_device_borrow_usb_device_t wadu436_usb_device_borrow_usb_device(wadu436_usb_device_own_usb_device_t handle); 314 | 315 | extern void wadu436_usb_device_usb_configuration_drop_own(wadu436_usb_device_own_usb_configuration_t handle); 316 | 317 | extern wadu436_usb_device_borrow_usb_configuration_t wadu436_usb_device_borrow_usb_configuration(wadu436_usb_device_own_usb_configuration_t handle); 318 | 319 | extern void wadu436_usb_device_usb_interface_drop_own(wadu436_usb_device_own_usb_interface_t handle); 320 | 321 | extern wadu436_usb_device_borrow_usb_interface_t wadu436_usb_device_borrow_usb_interface(wadu436_usb_device_own_usb_interface_t handle); 322 | 323 | extern void wadu436_usb_device_usb_endpoint_drop_own(wadu436_usb_device_own_usb_endpoint_t handle); 324 | 325 | extern wadu436_usb_device_borrow_usb_endpoint_t wadu436_usb_device_borrow_usb_endpoint(wadu436_usb_device_own_usb_endpoint_t handle); 326 | 327 | void wadu436_usb_device_list_own_usb_device_free(wadu436_usb_device_list_own_usb_device_t *ptr); 328 | 329 | void wadu436_usb_device_option_own_usb_device_free(wadu436_usb_device_option_own_usb_device_t *ptr); 330 | 331 | void wadu436_usb_device_list_own_usb_configuration_free(wadu436_usb_device_list_own_usb_configuration_t *ptr); 332 | 333 | void bindings_list_u8_free(bindings_list_u8_t *ptr); 334 | 335 | void wadu436_usb_device_list_own_usb_interface_free(wadu436_usb_device_list_own_usb_interface_t *ptr); 336 | 337 | void wadu436_usb_device_list_own_usb_endpoint_free(wadu436_usb_device_list_own_usb_endpoint_t *ptr); 338 | 339 | // Transfers ownership of `s` into the string `ret` 340 | void bindings_string_set(bindings_string_t *ret, const char*s); 341 | 342 | // Creates a copy of the input nul-terminate string `s` and 343 | // stores it into the component model string `ret`. 344 | void bindings_string_dup(bindings_string_t *ret, const char*s); 345 | 346 | // Deallocates the string pointed to by `ret`, deallocating 347 | // the memory behind the string. 348 | void bindings_string_free(bindings_string_t *ret); 349 | 350 | #ifdef __cplusplus 351 | } 352 | #endif 353 | #endif 354 | -------------------------------------------------------------------------------- /command-components/mass-storage/src/mass_storage.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 2 | use std::io::{self, Read, Seek, Write}; 3 | use thiserror::Error; 4 | use tracing::{debug, trace}; 5 | 6 | use crate::bulk_only::{ 7 | BulkOnlyTransportCommandBlock, BulkOnlyTransportDevice, CommandStatusWrapperStatus, 8 | }; 9 | use uluru::LRUCache; 10 | 11 | const CACHE_SIZE: usize = 128; 12 | 13 | #[derive(Debug, Error)] 14 | pub enum MassStorageDeviceError { 15 | #[error("Incompatible device")] 16 | IncompatibleDevice, 17 | #[error("Device is not ready yet")] 18 | NotReady, 19 | } 20 | 21 | #[derive(Debug, Default, Clone)] 22 | pub struct MassStorageDeviceProperties { 23 | pub name: String, 24 | pub capacity: u64, 25 | pub total_number_of_blocks: u32, 26 | pub block_size: u32, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | struct CacheEntry { 31 | block: u32, 32 | data: [u8; 512], 33 | dirty: bool, 34 | } 35 | 36 | impl CacheEntry { 37 | fn new(block: u32, data: [u8; 512], dirty: bool) -> Self { 38 | Self { block, data, dirty } 39 | } 40 | 41 | fn from_vec(block: u32, data: &[u8], dirty: bool) -> Self { 42 | let mut entry = Self::new(block, [0; 512], dirty); 43 | entry.data.copy_from_slice(data); 44 | entry 45 | } 46 | } 47 | 48 | // Implementation of a Mass Storage USB Device using SCSI commands on top of a Bulk Only Transport USB device 49 | pub struct MassStorageDevice { 50 | device: BulkOnlyTransportDevice, 51 | properties: MassStorageDeviceProperties, 52 | 53 | cache: LRUCache, // Block -> Data 54 | reads: usize, 55 | blocks_read: usize, 56 | writes: usize, 57 | blocks_written: usize, 58 | 59 | cursor: u64, 60 | } 61 | 62 | impl MassStorageDevice { 63 | pub fn new(device: BulkOnlyTransportDevice) -> Result { 64 | let mut mass_storage_device = MassStorageDevice { 65 | device, 66 | properties: Default::default(), 67 | cache: LRUCache::default(), 68 | cursor: 0, 69 | reads: 0, 70 | blocks_read: 0, 71 | writes: 0, 72 | blocks_written: 0, 73 | }; 74 | 75 | // Inquiry properties 76 | if !mass_storage_device.test_unit_ready() { 77 | return Err(MassStorageDeviceError::NotReady); 78 | } 79 | 80 | let inquiry = mass_storage_device.inquiry(); 81 | let capacity = mass_storage_device.read_capacity(); 82 | 83 | if inquiry.peripheral_qualifier != 0 && inquiry.peripheral_device_type != 0 { 84 | return Err(MassStorageDeviceError::IncompatibleDevice); 85 | } 86 | 87 | let name = format!("{} {}", inquiry.vendor_id, inquiry.product_id); 88 | 89 | let properties = MassStorageDeviceProperties { 90 | name, 91 | capacity: capacity.block_length_in_bytes as u64 92 | * capacity.returned_logical_block_address as u64, 93 | block_size: capacity.block_length_in_bytes, 94 | total_number_of_blocks: capacity.returned_logical_block_address, 95 | }; 96 | mass_storage_device.properties = properties; 97 | 98 | Ok(mass_storage_device) 99 | } 100 | 101 | pub fn get_properties(&self) -> MassStorageDeviceProperties { 102 | self.properties.clone() 103 | } 104 | 105 | pub fn flush_cache(&mut self) { 106 | let mut entries_to_write = Vec::<(u32, [u8; 512])>::new(); 107 | self.cache.iter().for_each(|entry| { 108 | if entry.dirty { 109 | entries_to_write.push((entry.block, entry.data)); 110 | } 111 | }); 112 | 113 | for (block, data) in entries_to_write { 114 | self.write_blocks(block, 1, &data); 115 | } 116 | 117 | self.cache.clear(); 118 | } 119 | 120 | // SCSI commands 121 | pub fn test_unit_ready(&mut self) -> bool { 122 | // We'll assume LUN 0 123 | let cbw = BulkOnlyTransportCommandBlock { 124 | command_block: vec![0x00; 6], 125 | transfer_length: 0, 126 | }; 127 | 128 | let csw = self.device.command_out(cbw, None).unwrap(); 129 | 130 | csw.status == CommandStatusWrapperStatus::CommandPassed 131 | } 132 | 133 | pub fn inquiry(&mut self) -> InquiryResponse { 134 | let cbw = BulkOnlyTransportCommandBlock { 135 | command_block: vec![0x12, 0x00, 0x00, 0x00, 36, 0x00], 136 | transfer_length: 36, 137 | }; 138 | 139 | let (csw, data) = self.device.command_in(cbw).unwrap(); 140 | if csw.status != CommandStatusWrapperStatus::CommandPassed { 141 | todo!("Handle command failure") 142 | } 143 | 144 | InquiryResponse::from_bytes(&data) 145 | } 146 | 147 | pub fn read_capacity(&mut self) -> ReadCapacityResponse { 148 | let cbw = BulkOnlyTransportCommandBlock { 149 | command_block: vec![0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 150 | transfer_length: 8, 151 | }; 152 | 153 | let (csw, data) = self.device.command_in(cbw).unwrap(); 154 | if csw.status != CommandStatusWrapperStatus::CommandPassed { 155 | todo!("Handle command failure") 156 | } 157 | 158 | ReadCapacityResponse::from_bytes(&data) 159 | } 160 | 161 | pub fn read_blocks(&mut self, address: u32, blocks: u16) -> Vec { 162 | self.blocks_read += blocks as usize; 163 | self.reads += 1; 164 | // println!("Reading {} block(s) starting at block {}", blocks, address); 165 | let mut command_block = BytesMut::new(); 166 | command_block.put_u8(0x28); // OPCODE 167 | command_block.put_u8(0); // Fields I don't care about 168 | command_block.put_u32(address); // Logical block address 169 | command_block.put_u8(0); // Fields I don't care about 170 | command_block.put_u16(blocks); // Number of blocks to transfer 171 | command_block.put_u8(0); // CONTROL 172 | let command_block = command_block.to_vec(); 173 | 174 | let cbw = BulkOnlyTransportCommandBlock { 175 | command_block, 176 | transfer_length: blocks as u32 * self.properties.block_size, 177 | }; 178 | 179 | let (csw, data) = self.device.command_in(cbw).unwrap(); 180 | if csw.status != CommandStatusWrapperStatus::CommandPassed { 181 | todo!("Handle command failure") 182 | } 183 | 184 | data 185 | } 186 | 187 | pub fn write_blocks(&mut self, address: u32, blocks: u16, data: &[u8]) { 188 | self.blocks_written += blocks as usize; 189 | self.writes += 1; 190 | // println!("Writing {} blocks at address {:x}", blocks, address); 191 | let mut command_block = BytesMut::new(); 192 | command_block.put_u8(0x2A); // OPCODE 193 | command_block.put_u8(0); // Fields I don't care about 194 | command_block.put_u32(address); // Logical block address 195 | command_block.put_u8(0); // Fields I don't care about 196 | command_block.put_u16(blocks); // Number of blocks to transfer 197 | command_block.put_u8(0); // CONTROL 198 | let command_block = command_block.to_vec(); 199 | 200 | let cbw = BulkOnlyTransportCommandBlock { 201 | command_block, 202 | transfer_length: blocks as u32 * self.properties.block_size, 203 | }; 204 | 205 | let csw = self.device.command_out(cbw, Some(data)).unwrap(); 206 | if csw.status != CommandStatusWrapperStatus::CommandPassed { 207 | self.request_sense(); 208 | } 209 | } 210 | 211 | pub fn request_sense(&mut self) { 212 | let mut command_block = BytesMut::new(); 213 | command_block.put_u8(0x03); 214 | command_block.put_u8(0x00); 215 | command_block.put_u8(0x00); 216 | command_block.put_u8(0x00); 217 | command_block.put_u8(252); 218 | let command_block = command_block.to_vec(); 219 | 220 | let cbw = BulkOnlyTransportCommandBlock { 221 | command_block, 222 | transfer_length: 252, 223 | }; 224 | 225 | let (csw, data) = self.device.command_in(cbw).unwrap(); 226 | if csw.status != CommandStatusWrapperStatus::CommandPassed { 227 | todo!("Handle command failure") 228 | } 229 | 230 | let mut bytes: bytes::Bytes = data.into(); 231 | let valid_and_response_code = bytes.get_u8(); 232 | let valid = valid_and_response_code & 0b10000000; 233 | let response_code = valid_and_response_code & 0b01111111; 234 | 235 | bytes.advance(1); 236 | 237 | let sense_key = bytes.get_u8() & 0b00001111; 238 | let information = bytes.get_u32(); 239 | let additional_sense_length = bytes.get_u8(); 240 | 241 | let command_specific_information = bytes.get_u32(); 242 | 243 | let additional_sense_code = bytes.get_u8(); 244 | let additional_sense_code_qualifier = bytes.get_u8(); 245 | 246 | println!("valid: {}", valid); 247 | println!("response_code: {:x?}", response_code); 248 | println!("sense_key: {:x?}", sense_key); 249 | println!("information: {:x?}", information); 250 | println!("additional_sense_length: {:x?}", additional_sense_length); 251 | println!( 252 | "command_specific_information: {:x?}", 253 | command_specific_information 254 | ); 255 | println!("additional_sense_code: {:x?}", additional_sense_code); 256 | println!( 257 | "additional_sense_code_qualifier: {:x?}", 258 | additional_sense_code_qualifier 259 | ); 260 | } 261 | } 262 | 263 | impl Seek for MassStorageDevice { 264 | fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { 265 | match pos { 266 | std::io::SeekFrom::Start(offset) => self.cursor = offset, 267 | std::io::SeekFrom::End(offset) => { 268 | if offset > 0 { 269 | self.cursor = self.properties.capacity + offset as u64 270 | } else { 271 | self.cursor = self.properties.capacity - (-offset) as u64 272 | } 273 | } 274 | std::io::SeekFrom::Current(offset) => { 275 | if offset > 0 { 276 | self.cursor += offset as u64 277 | } else { 278 | self.cursor -= (-offset) as u64 279 | } 280 | } 281 | } 282 | Ok(self.cursor) 283 | } 284 | } 285 | 286 | impl Read for MassStorageDevice { 287 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 288 | trace!("Reading {} bytes at address {:x}", buf.len(), self.cursor); 289 | 290 | let start_address = self.cursor as usize; 291 | let end_address = (self.cursor + buf.len() as u64).min(self.properties.capacity) as usize; // Not-inclusive 292 | let num_bytes = end_address.saturating_sub(start_address); 293 | 294 | if num_bytes == 0 { 295 | // End of disk 296 | return Ok(0); 297 | } 298 | 299 | // First find which blocks we need to read 300 | let start_block = (start_address / self.properties.block_size as usize) as u32; 301 | let offset_in_start_block = start_address % self.properties.block_size as usize; 302 | let end_block = ((end_address - 1) / self.properties.block_size as usize) as u32; // Because end_address is not inclusive 303 | let num_blocks = (end_block - start_block + 1) as usize; 304 | let offset_in_end_block = ((end_address - 1) % self.properties.block_size as usize) + 1; 305 | 306 | trace!( 307 | "Reading {} block(s) starting at block {}", 308 | num_blocks, 309 | start_block 310 | ); 311 | 312 | let mut new_range: Option = None; 313 | let mut ranges: Vec<(u32, u32)> = Vec::new(); 314 | 315 | for block in start_block..end_block + 1 { 316 | if let Some(entry) = self.cache.find(|item| item.block == block) { 317 | // Found in cache 318 | if block == start_block && block == end_block { 319 | buf[..] 320 | .copy_from_slice(&entry.data[offset_in_start_block..offset_in_end_block]); 321 | } else if block == start_block { 322 | buf[..512 - offset_in_start_block] 323 | .copy_from_slice(&entry.data[offset_in_start_block..]); 324 | } else if block == end_block { 325 | buf[(512 - offset_in_start_block) + (num_blocks - 2) * 512..] 326 | .copy_from_slice(&entry.data[..offset_in_end_block]); 327 | } else { 328 | buf[(512 - offset_in_start_block) + ((start_block - block) as usize) * 512 329 | ..(512 - offset_in_start_block) 330 | + ((start_block - block) as usize) * 512 331 | + 512] 332 | .copy_from_slice(&entry.data); 333 | } 334 | 335 | if let Some(new_range) = new_range { 336 | ranges.push((new_range, block)); 337 | } 338 | } else if new_range.is_none() { 339 | // We're at the start of the range 340 | new_range = Some(block); 341 | } 342 | } 343 | if let Some(new_range) = new_range { 344 | ranges.push((new_range, end_block + 1)); 345 | } 346 | 347 | tracing::trace!( 348 | start_address, 349 | end_address, 350 | start_block, 351 | offset_in_start_block, 352 | end_block, 353 | num_blocks, 354 | num_bytes, 355 | buf_len = buf.len(), 356 | "reading" 357 | ); 358 | 359 | for (start, end) in ranges { 360 | let data = self.read_blocks(start_block as _, (end - start) as _); 361 | // Put the data into the cache 362 | let range = if data.len() > 512 * CACHE_SIZE { 363 | data.len() - (512 * CACHE_SIZE)..data.len() 364 | } else { 365 | 0..data.len() 366 | }; 367 | for (i, chunk) in data[range].chunks(512).enumerate() { 368 | let block = start + i as u32; 369 | 370 | if let Some(item) = self.cache.find(|item| item.block == block) { 371 | item.data.copy_from_slice(chunk); 372 | } else { 373 | let value = CacheEntry::from_vec(block, chunk, false); 374 | if let Some(evicted_entry) = self.cache.insert(value) { 375 | if evicted_entry.dirty { 376 | println!("Flushing block {}", block); 377 | self.write_blocks(block, 1, &evicted_entry.data); 378 | } 379 | } 380 | } 381 | } 382 | // Copy the data into the buffer 383 | if start == start_block && end == end_block + 1 { 384 | buf[..].copy_from_slice( 385 | &data[offset_in_start_block..(num_blocks - 1) * 512 + offset_in_end_block], 386 | ); 387 | } else if start == start_block { 388 | buf[..(512 - offset_in_start_block) + ((end - start_block - 1) as usize) * 512] 389 | .copy_from_slice(&data[offset_in_start_block..(num_blocks - 1) * 512]); 390 | } else if end == end_block + 1 { 391 | buf[(512 - offset_in_start_block) + ((start - start_block - 1) as usize) * 512..] 392 | .copy_from_slice(&data[..(num_blocks - 1) * 512 + offset_in_end_block]); 393 | } else { 394 | // General case 395 | buf[(512 - offset_in_start_block) + ((start - start_block - 1) as usize) * 512 396 | ..(512 - offset_in_start_block) + ((end - start_block - 1) as usize) * 512] 397 | .copy_from_slice(&data[..]); 398 | } 399 | } 400 | 401 | self.cursor += num_bytes as u64; 402 | 403 | Ok(num_bytes) 404 | } 405 | } 406 | 407 | impl Write for MassStorageDevice { 408 | fn write(&mut self, buf: &[u8]) -> io::Result { 409 | // println!("Writing {} bytes at address {:x}", buf.len(), self.cursor); 410 | 411 | let start_address = self.cursor as usize; 412 | let end_address = (self.cursor + buf.len() as u64).min(self.properties.capacity) as usize; // Not-inclusive 413 | let num_bytes = end_address.saturating_sub(start_address); 414 | 415 | if num_bytes == 0 { 416 | // End of disk 417 | return Ok(0); 418 | } 419 | 420 | // First find which blocks we need to read 421 | let start_block = (start_address / self.properties.block_size as usize) as u32; 422 | let offset_in_start_block = start_address % self.properties.block_size as usize; 423 | let end_block = ((end_address - 1) / self.properties.block_size as usize) as u32; // Because end_address is not inclusive 424 | let num_blocks: u16 = (end_block - start_block + 1) as _; 425 | 426 | tracing::trace!( 427 | start_address, 428 | end_address, 429 | start_block, 430 | offset_in_start_block, 431 | end_block, 432 | num_blocks, 433 | num_bytes, 434 | buf_len = buf.len(), 435 | "writing" 436 | ); 437 | 438 | debug!( 439 | "Writing {} block(s) starting at block {}", 440 | num_blocks, start_block 441 | ); 442 | 443 | if num_blocks == 1 { 444 | let mut data = vec![0_u8; 512]; 445 | if let Some(block) = self.cache.find(|item| item.block == start_block) { 446 | data.copy_from_slice(&block.data); 447 | } else { 448 | let original_data = self.read_blocks(start_block, 1); 449 | data.copy_from_slice(&original_data); 450 | } 451 | data[offset_in_start_block..offset_in_start_block + buf.len()].copy_from_slice(buf); 452 | // Update the cache 453 | if let Some(item) = self.cache.find(|item| item.block == start_block) { 454 | item.data.copy_from_slice(&data); 455 | item.dirty = true; 456 | } else { 457 | let value = CacheEntry::from_vec(start_block, &data, false); 458 | if let Some(evicted_entry) = self.cache.insert(value) { 459 | if evicted_entry.dirty { 460 | self.write_blocks(start_block, 1, &evicted_entry.data); 461 | } 462 | } 463 | } 464 | } else { 465 | let mut data = vec![0_u8; num_blocks as usize * 512]; 466 | let data_len = data.len(); 467 | // First block 468 | if let Some(block) = self.cache.find(|item| item.block == start_block) { 469 | data[0..512].copy_from_slice(&block.data); 470 | } else { 471 | let original_data = self.read_blocks(start_block, 1); 472 | data[0..512].copy_from_slice(&original_data); 473 | } 474 | 475 | // Last block 476 | if let Some(block) = self.cache.find(|item| item.block == end_block) { 477 | data[data_len - 512..data_len].copy_from_slice(&block.data); 478 | } else { 479 | let original_data = self.read_blocks(end_block, 1); 480 | data[data_len - 512..data_len].copy_from_slice(&original_data); 481 | } 482 | data[offset_in_start_block..offset_in_start_block + buf.len()].copy_from_slice(buf); 483 | 484 | self.write_blocks(start_block, num_blocks, &data); 485 | 486 | // Update the cache 487 | for i in 0..num_blocks { 488 | let key = start_block + i as u32; 489 | // If the evicted block was dirty, we don't need to write it back because we already wrote it back just above 490 | if let Some(item) = self.cache.find(|item| item.block == key) { 491 | item.data.copy_from_slice(&data); 492 | } else { 493 | let value = CacheEntry::from_vec( 494 | key, 495 | &data[i as usize * 512..(i + 1) as usize * 512], 496 | false, 497 | ); 498 | if let Some(evicted_entry) = self.cache.insert(value) { 499 | if evicted_entry.dirty { 500 | self.write_blocks(key, 1, &evicted_entry.data); 501 | } 502 | } 503 | } 504 | } 505 | } 506 | 507 | self.cursor += num_bytes as u64; 508 | 509 | Ok(num_bytes) 510 | } 511 | 512 | fn flush(&mut self) -> io::Result<()> { 513 | self.flush_cache(); 514 | // We don't buffer anything ourselves so we don't need to flush 515 | Ok(()) 516 | } 517 | } 518 | 519 | impl Drop for MassStorageDevice { 520 | fn drop(&mut self) { 521 | self.flush_cache(); 522 | println!("Read Ops: {}", self.reads); 523 | println!("Read {} blocks", self.blocks_read); 524 | println!("Write Ops: {}", self.writes); 525 | println!("Wrote {} blocks", self.blocks_written); 526 | } 527 | } 528 | 529 | #[derive(Debug)] 530 | pub struct InquiryResponse { 531 | pub peripheral_qualifier: u8, 532 | pub peripheral_device_type: u8, 533 | pub removable_media: bool, 534 | pub version: u8, 535 | // normaca: bool, 536 | // hisup: bool, 537 | pub response_data_format: u8, 538 | // sccs: bool, 539 | // acc: bool, 540 | // tpgs: u8, 541 | // _3pc: bool, 542 | // protect: bool, 543 | // encserv: bool, 544 | // vs: bool, 545 | // multip: bool, 546 | // cmdque: bool, 547 | // vs2: bool, 548 | pub vendor_id: String, 549 | pub product_id: String, 550 | pub product_revision: String, 551 | } 552 | 553 | impl InquiryResponse { 554 | fn from_bytes(data: &[u8]) -> Self { 555 | let mut data = Bytes::copy_from_slice(data); 556 | let peripheral = data.get_u8(); 557 | let peripheral_qualifier = (peripheral & 0b11100000) >> 5; 558 | let peripheral_device_type = peripheral & 0b00011111; 559 | 560 | let removable_media = (data.get_u8() & 0b10000000) != 0; 561 | 562 | let version = data.get_u8(); 563 | 564 | let response_data_format = data.get_u8() & 0b00001111; 565 | 566 | // Skip a couple bytes 567 | data.advance(4); 568 | 569 | let vendor_id = String::from_utf8(data[0..8].to_vec()) 570 | .unwrap() 571 | .trim() 572 | .to_owned(); 573 | let product_id = String::from_utf8(data[8..24].to_vec()) 574 | .unwrap() 575 | .trim() 576 | .to_owned(); 577 | let product_revision = String::from_utf8(data[24..28].to_vec()) 578 | .unwrap() 579 | .trim() 580 | .to_owned(); 581 | 582 | InquiryResponse { 583 | peripheral_qualifier, 584 | peripheral_device_type, 585 | removable_media, 586 | version, 587 | response_data_format, 588 | vendor_id, 589 | product_id, 590 | product_revision, 591 | } 592 | } 593 | } 594 | 595 | #[derive(Debug)] 596 | pub struct ReadCapacityResponse { 597 | pub returned_logical_block_address: u32, 598 | pub block_length_in_bytes: u32, 599 | pub capacity_in_bytes: u64, 600 | } 601 | 602 | impl ReadCapacityResponse { 603 | fn from_bytes(data: &[u8]) -> Self { 604 | let mut data = Bytes::copy_from_slice(data); 605 | let returned_logical_block_address = data.get_u32(); 606 | let block_length_in_bytes = data.get_u32(); 607 | let capacity_in_bytes: u64 = 608 | returned_logical_block_address as u64 * block_length_in_bytes as u64; 609 | 610 | Self { 611 | block_length_in_bytes, 612 | returned_logical_block_address, 613 | capacity_in_bytes, 614 | } 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /usb-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | wasmtime::component::bindgen!({ 2 | world: "wadu436:usb/imports", 3 | path: "../wit/deps/usb", 4 | 5 | with: { 6 | "wadu436:usb/device/usb-device": UsbDevice, 7 | "wadu436:usb/device/usb-configuration": UsbConfiguration, 8 | "wadu436:usb/device/usb-interface": UsbInterface, 9 | "wadu436:usb/device/usb-endpoint": UsbEndpoint, 10 | } 11 | }); 12 | 13 | use error::UsbWasmError; 14 | use rusb::{ 15 | constants::LIBUSB_TRANSFER_TYPE_ISOCHRONOUS, 16 | ffi::{libusb_alloc_transfer, libusb_handle_events_completed, libusb_submit_transfer}, 17 | GlobalContext, Recipient, RequestType, Speed, UsbContext, 18 | }; 19 | use std::{error::Error, sync::Arc, time::Duration}; 20 | use wadu436::usb::{self, types::Direction}; 21 | 22 | use wasmtime_wasi::WasiView; 23 | 24 | pub mod error; 25 | mod host; 26 | 27 | const TIMEOUT: Duration = Duration::from_secs(20); 28 | 29 | pub struct UsbDevice { 30 | device: rusb::Device, 31 | handle: Option>, 32 | language: Option, 33 | descriptor: usb::device::DeviceDescriptor, 34 | } 35 | 36 | pub struct ControlSetup { 37 | pub request_type: RequestType, 38 | pub request_recipient: Recipient, 39 | pub request: u8, 40 | pub value: u16, 41 | pub index: u16, 42 | } 43 | 44 | fn error_from_libusb(err: i32) -> rusb::Error { 45 | match err { 46 | rusb::ffi::constants::LIBUSB_ERROR_IO => rusb::Error::Io, 47 | rusb::ffi::constants::LIBUSB_ERROR_INVALID_PARAM => rusb::Error::InvalidParam, 48 | rusb::ffi::constants::LIBUSB_ERROR_ACCESS => rusb::Error::Access, 49 | rusb::ffi::constants::LIBUSB_ERROR_NO_DEVICE => rusb::Error::NoDevice, 50 | rusb::ffi::constants::LIBUSB_ERROR_NOT_FOUND => rusb::Error::NotFound, 51 | rusb::ffi::constants::LIBUSB_ERROR_BUSY => rusb::Error::Busy, 52 | rusb::ffi::constants::LIBUSB_ERROR_TIMEOUT => rusb::Error::Timeout, 53 | rusb::ffi::constants::LIBUSB_ERROR_OVERFLOW => rusb::Error::Overflow, 54 | rusb::ffi::constants::LIBUSB_ERROR_PIPE => rusb::Error::Pipe, 55 | rusb::ffi::constants::LIBUSB_ERROR_INTERRUPTED => rusb::Error::Interrupted, 56 | rusb::ffi::constants::LIBUSB_ERROR_NO_MEM => rusb::Error::NoMem, 57 | rusb::ffi::constants::LIBUSB_ERROR_NOT_SUPPORTED => rusb::Error::NotSupported, 58 | rusb::ffi::constants::LIBUSB_ERROR_OTHER => rusb::Error::Other, 59 | _ => rusb::Error::Other, 60 | } 61 | } 62 | 63 | extern "system" fn libusb_transfer_cb(transfer: *mut rusb::ffi::libusb_transfer) { 64 | unsafe { 65 | *((*transfer).user_data as *mut i32) = 1; 66 | } 67 | } 68 | 69 | impl UsbDevice { 70 | pub fn enumerate() -> Result, UsbWasmError> { 71 | let devices = rusb::devices()?; 72 | 73 | let mut devices_ = Vec::with_capacity(devices.len()); 74 | 75 | // Poor man's try_block 76 | fn read_device( 77 | device: rusb::Device, 78 | ) -> Result { 79 | let handle = device.open()?; 80 | 81 | // First get all the information needed to apply the filters 82 | let descriptor = device.device_descriptor()?; 83 | 84 | let (language, product_name, manufacturer_name, serial_number) = 85 | if let Ok(languages) = handle.read_languages(TIMEOUT) { 86 | let language = languages[0]; 87 | 88 | let product_name = handle 89 | .read_product_string(language, &descriptor, TIMEOUT) 90 | .ok(); 91 | let manufacturer_name = handle 92 | .read_manufacturer_string(language, &descriptor, TIMEOUT) 93 | .ok(); 94 | let serial_number = handle 95 | .read_serial_number_string(language, &descriptor, TIMEOUT) 96 | .ok(); 97 | ( 98 | Some(language), 99 | product_name, 100 | manufacturer_name, 101 | serial_number, 102 | ) 103 | } else { 104 | (None, None, None, None) 105 | }; 106 | 107 | let device_version = descriptor.device_version(); 108 | let usb_version = descriptor.usb_version(); 109 | 110 | let descriptor = usb::device::DeviceDescriptor { 111 | vendor_id: descriptor.vendor_id(), 112 | product_id: descriptor.product_id(), 113 | device_class: descriptor.class_code(), 114 | device_subclass: descriptor.sub_class_code(), 115 | device_protocol: descriptor.protocol_code(), 116 | manufacturer_name, 117 | product_name, 118 | serial_number, 119 | device_version: ( 120 | device_version.major(), 121 | device_version.minor(), 122 | device_version.sub_minor(), 123 | ), 124 | usb_version: ( 125 | usb_version.major(), 126 | usb_version.minor(), 127 | usb_version.sub_minor(), 128 | ), 129 | max_packet_size: descriptor.max_packet_size(), 130 | }; 131 | 132 | Ok(UsbDevice { 133 | device, 134 | handle: None, 135 | language, 136 | descriptor, 137 | }) 138 | } 139 | 140 | for device in devices.iter() { 141 | match read_device(device) { 142 | Ok(device) => devices_.push(device), 143 | Err(e) => eprintln!("Error while reading a device: {:?}", e), 144 | } 145 | } 146 | 147 | Ok(devices_) 148 | } 149 | 150 | pub fn open(&mut self) -> Result<(), UsbWasmError> { 151 | if self.handle.is_none() { 152 | let handle = self.device.open()?; 153 | let _ = handle.set_auto_detach_kernel_driver(true); 154 | 155 | self.handle = Some(handle); 156 | } 157 | 158 | Ok(()) 159 | } 160 | 161 | pub fn active_configuration(&mut self) -> Result { 162 | let configuration = self.device.active_config_descriptor()?; 163 | let description = self.language.and_then(|language| { 164 | if let Some(handle) = &self.handle { 165 | handle 166 | .read_configuration_string(language, &configuration, TIMEOUT) 167 | .ok() 168 | } else { 169 | self.device 170 | .open() 171 | .unwrap() 172 | .read_configuration_string(language, &configuration, TIMEOUT) 173 | .ok() 174 | } 175 | }); 176 | 177 | // Find the index of this configuration 178 | let config_index = (0..self.device.device_descriptor()?.num_configurations()) 179 | .find(|i| { 180 | self.device 181 | .config_descriptor(*i) 182 | .ok() 183 | .map_or(false, |descriptor| { 184 | descriptor.number() == configuration.number() 185 | }) 186 | }) 187 | .unwrap(); 188 | 189 | Ok(UsbConfiguration { 190 | language: self.language, 191 | index: config_index, 192 | descriptor: usb::device::ConfigurationDescriptor { 193 | number: configuration.number(), 194 | description, 195 | self_powered: configuration.self_powered(), 196 | remote_wakeup: configuration.remote_wakeup(), 197 | max_power: configuration.max_power(), 198 | }, 199 | device: self.device.clone(), 200 | }) 201 | } 202 | 203 | pub fn speed(&mut self) -> Speed { 204 | self.device.speed() 205 | } 206 | 207 | pub fn close(&mut self) { 208 | if let Some(handle) = self.handle.take() { 209 | std::mem::drop(handle); 210 | } 211 | } 212 | 213 | pub fn reset(&mut self) -> Result<(), UsbWasmError> { 214 | if let Some(handle) = &mut self.handle { 215 | Ok(handle.reset()?) 216 | } else { 217 | Err(rusb::Error::InvalidParam.into()) 218 | } 219 | } 220 | 221 | pub fn opened(&self) -> bool { 222 | self.handle.is_some() 223 | } 224 | 225 | pub fn clear_halt(&mut self, endpoint: u8) -> Result<(), UsbWasmError> { 226 | if let Some(handle) = &mut self.handle { 227 | handle.clear_halt(endpoint)?; 228 | } 229 | Ok(()) 230 | } 231 | 232 | pub fn select_configuration(&mut self, config: u8) -> Result<(), UsbWasmError> { 233 | if let Some(handle) = &mut self.handle { 234 | handle.set_active_configuration(config).unwrap(); 235 | } 236 | Ok(()) 237 | } 238 | 239 | pub fn claim_interface(&mut self, interface: u8) -> Result<(), UsbWasmError> { 240 | if let Some(handle) = &mut self.handle { 241 | handle.claim_interface(interface)?; 242 | } 243 | Ok(()) 244 | } 245 | 246 | pub fn release_interface(&mut self, interface: u8) -> Result<(), UsbWasmError> { 247 | if let Some(handle) = &mut self.handle { 248 | handle.release_interface(interface)?; 249 | } 250 | Ok(()) 251 | } 252 | 253 | pub fn set_alternate_setting( 254 | &mut self, 255 | interface: u8, 256 | setting: u8, 257 | ) -> Result<(), UsbWasmError> { 258 | if let Some(handle) = &mut self.handle { 259 | handle.set_alternate_setting(interface, setting)?; 260 | } 261 | Ok(()) 262 | } 263 | 264 | pub fn interrupt_transfer_in( 265 | &mut self, 266 | endpoint: u8, 267 | buffer_size: usize, 268 | ) -> Result, UsbWasmError> { 269 | if let Some(handle) = &mut self.handle { 270 | let mut buffer = vec![0; buffer_size]; 271 | let _bytes_read = handle 272 | .read_interrupt(endpoint, &mut buffer, TIMEOUT) 273 | .unwrap(); 274 | buffer.resize(_bytes_read, 0); 275 | Ok(buffer) 276 | } else { 277 | Err(UsbWasmError::DeviceNotOpened) 278 | } 279 | } 280 | 281 | pub fn interrupt_transfer_out( 282 | &mut self, 283 | endpoint: u8, 284 | buffer: &[u8], 285 | ) -> Result { 286 | if let Some(handle) = &mut self.handle { 287 | let bytes_written = handle.write_interrupt(endpoint, buffer, TIMEOUT).unwrap(); 288 | Ok(bytes_written) 289 | } else { 290 | Err(UsbWasmError::DeviceNotOpened) 291 | } 292 | } 293 | 294 | pub fn bulk_transfer_in( 295 | &mut self, 296 | endpoint: u8, 297 | buffer_size: usize, 298 | ) -> Result, UsbWasmError> { 299 | if let Some(handle) = &mut self.handle { 300 | let mut buffer = vec![0; buffer_size]; 301 | let _bytes_read = handle.read_bulk(endpoint, &mut buffer, TIMEOUT).unwrap(); 302 | buffer.resize(_bytes_read, 0); 303 | Ok(buffer) 304 | } else { 305 | Err(UsbWasmError::DeviceNotOpened) 306 | } 307 | } 308 | 309 | pub fn bulk_transfer_out( 310 | &mut self, 311 | endpoint: u8, 312 | buffer: &[u8], 313 | ) -> Result { 314 | if let Some(handle) = &mut self.handle { 315 | let bytes_written = handle.write_bulk(endpoint, buffer, TIMEOUT).unwrap(); 316 | Ok(bytes_written) 317 | } else { 318 | Err(UsbWasmError::DeviceNotOpened) 319 | } 320 | } 321 | 322 | pub fn iso_transfer_in( 323 | &mut self, 324 | endpoint: u8, 325 | num_packets: i32, 326 | buffer_size: usize, 327 | ) -> Result>, UsbWasmError> { 328 | if num_packets < 0 { 329 | // Error 330 | return Err(rusb::Error::InvalidParam.into()); 331 | } 332 | if let Some(handle) = &mut self.handle { 333 | let transfer = unsafe { libusb_alloc_transfer(num_packets) }; 334 | let transfer_ref = unsafe { &mut *transfer }; 335 | 336 | let mut completed = 0_i32; 337 | let completed_ptr = (&mut completed) as *mut i32; 338 | 339 | let mut buffer = vec![0; buffer_size * num_packets as usize]; 340 | 341 | transfer_ref.dev_handle = handle.as_raw(); 342 | transfer_ref.endpoint = endpoint; 343 | transfer_ref.transfer_type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; 344 | transfer_ref.timeout = 1000; 345 | transfer_ref.buffer = buffer.as_mut_slice().as_ptr() as *mut _; 346 | transfer_ref.length = buffer.len() as _; 347 | transfer_ref.num_iso_packets = num_packets; 348 | transfer_ref.user_data = completed_ptr as *mut _; 349 | 350 | let iso_packet_descs = unsafe { 351 | std::slice::from_raw_parts_mut( 352 | transfer_ref.iso_packet_desc.as_mut_ptr(), 353 | num_packets as usize, 354 | ) 355 | }; 356 | for i in 0..num_packets as usize { 357 | let entry = iso_packet_descs.get_mut(i).unwrap(); 358 | entry.length = buffer_size as _; 359 | entry.status = 0; 360 | entry.actual_length = 0; 361 | } 362 | 363 | transfer_ref.callback = libusb_transfer_cb; 364 | let err = unsafe { libusb_submit_transfer(transfer) }; 365 | if err != 0 { 366 | return Err(error_from_libusb(err).into()); 367 | } 368 | 369 | let mut err = 0; 370 | unsafe { 371 | while (*completed_ptr) == 0 { 372 | err = libusb_handle_events_completed(handle.context().as_raw(), completed_ptr); 373 | } 374 | }; 375 | if err != 0 { 376 | return Err(error_from_libusb(err).into()); 377 | } 378 | 379 | let mut output_data = Vec::with_capacity(num_packets as usize); 380 | for i in 0..num_packets as usize { 381 | let entry = iso_packet_descs.get_mut(i).unwrap(); 382 | if entry.status == 0 { 383 | output_data.push( 384 | buffer[i * buffer_size..i * buffer_size + entry.actual_length as usize] 385 | .to_vec(), 386 | ); 387 | } else { 388 | // TODO: handle errors here 389 | // Status code meanings 390 | // https://libusb.sourceforge.io/api-1.0/group__libusb__asyncio.html#ga9fcb2aa23d342060ebda1d0cf7478856 391 | } 392 | } 393 | 394 | Ok(output_data) 395 | } else { 396 | Err(UsbWasmError::DeviceNotOpened) 397 | } 398 | } 399 | 400 | pub fn iso_transfer_out( 401 | &mut self, 402 | endpoint: u8, 403 | buffers: &[Vec], 404 | ) -> Result { 405 | if let Some(handle) = &mut self.handle { 406 | let transfer = unsafe { libusb_alloc_transfer(buffers.len() as _) }; 407 | let transfer_ref = unsafe { &mut *transfer }; 408 | 409 | let mut completed = 0_i32; 410 | let completed_ptr = (&mut completed) as *mut i32; 411 | 412 | // reorder the buffers so they're continuous in memory 413 | let buffer: Vec = buffers.iter().flatten().copied().collect::>(); 414 | 415 | transfer_ref.dev_handle = handle.as_raw(); 416 | transfer_ref.endpoint = endpoint; 417 | transfer_ref.transfer_type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; 418 | transfer_ref.timeout = TIMEOUT.as_millis() as _; 419 | transfer_ref.buffer = buffer.as_ptr() as *mut _; 420 | transfer_ref.length = buffer.len() as _; 421 | transfer_ref.num_iso_packets = buffers.len() as _; 422 | // It should be okay to pass in this (stack) variable, as this function will not return untill after the transfer is complete. 423 | transfer_ref.user_data = completed_ptr as *mut _; 424 | 425 | let iso_packet_descs = unsafe { 426 | std::slice::from_raw_parts_mut( 427 | transfer_ref.iso_packet_desc.as_mut_ptr(), 428 | buffers.len(), 429 | ) 430 | }; 431 | for (i, buffer) in buffers.iter().enumerate() { 432 | let entry = iso_packet_descs.get_mut(i).unwrap(); 433 | entry.length = buffer.len() as _; 434 | entry.status = 0; 435 | entry.actual_length = 0; 436 | } 437 | 438 | transfer_ref.callback = libusb_transfer_cb; 439 | 440 | let err = unsafe { libusb_submit_transfer(transfer) }; 441 | if err != 0 { 442 | return Err(error_from_libusb(err).into()); 443 | } 444 | 445 | let mut err = 0; 446 | unsafe { 447 | while (*completed_ptr) == 0 { 448 | err = libusb_handle_events_completed(handle.context().as_raw(), completed_ptr); 449 | } 450 | }; 451 | if err != 0 { 452 | return Err(error_from_libusb(err).into()); 453 | } 454 | 455 | let mut bytes_written: u64 = 0; 456 | for i in 0..buffers.len() { 457 | let entry = iso_packet_descs.get_mut(i).unwrap(); 458 | if entry.status == 0 { 459 | bytes_written += entry.actual_length as u64; 460 | } else { 461 | // TODO: handle errors here 462 | // Status code meanings 463 | // https://libusb.sourceforge.io/api-1.0/group__libusb__asyncio.html#ga9fcb2aa23d342060ebda1d0cf7478856 464 | } 465 | } 466 | 467 | Ok(bytes_written) 468 | } else { 469 | // TODO: fix a proper error here 470 | Err(UsbWasmError::DeviceNotOpened) 471 | } 472 | } 473 | 474 | pub fn control_transfer_in( 475 | &mut self, 476 | setup: ControlSetup, 477 | length: u16, 478 | ) -> Result, UsbWasmError> { 479 | if let Some(handle) = &self.handle { 480 | let request_type = rusb::request_type( 481 | rusb::Direction::In, 482 | setup.request_type, 483 | setup.request_recipient, 484 | ); 485 | // Low speed: 8 bytes 486 | // High and Full speed: 64 bytes 487 | // Super speed: 512 bytes 488 | // Not sure for super speed plus? 489 | // let mut buffer = match self.device.speed() { 490 | // rusb::Speed::Low => vec![0; 8], 491 | // rusb::Speed::High | rusb::Speed::Full => vec![0; 64], 492 | // rusb::Speed::Super | rusb::Speed::SuperPlus | rusb::Speed::Unknown => { 493 | // vec![0; 512] 494 | // } // Assume highest buffer needed for unknown speed 495 | // _ => { 496 | // vec![0; 512] 497 | // } // Assume highest buffer needed for non-exhaustive checks 498 | // }; 499 | let mut buffer = vec![0; length as usize]; 500 | let bytes_read = handle.read_control( 501 | request_type, 502 | setup.request, 503 | setup.value, 504 | setup.index, 505 | &mut buffer, 506 | TIMEOUT, 507 | )?; 508 | buffer.truncate(bytes_read); 509 | Ok(buffer) 510 | } else { 511 | Err(UsbWasmError::DeviceNotOpened) 512 | } 513 | } 514 | 515 | pub fn control_transfer_out( 516 | &mut self, 517 | setup: ControlSetup, 518 | data: &[u8], 519 | ) -> Result { 520 | if let Some(handle) = &self.handle { 521 | let request_type = rusb::request_type( 522 | rusb::Direction::Out, 523 | setup.request_type, 524 | setup.request_recipient, 525 | ); 526 | let bytes_written = handle.write_control( 527 | request_type, 528 | setup.request, 529 | setup.value, 530 | setup.index, 531 | data, 532 | TIMEOUT, 533 | )?; 534 | Ok(bytes_written as u64) 535 | } else { 536 | Err(UsbWasmError::DeviceNotOpened) 537 | } 538 | } 539 | 540 | pub fn get_configurations(&self) -> Vec { 541 | let mut configurations = Vec::new(); 542 | 543 | for i in 0..self 544 | .device 545 | .device_descriptor() 546 | .unwrap() 547 | .num_configurations() 548 | { 549 | let configuration = self.device.config_descriptor(i).unwrap(); 550 | 551 | let description = self.language.and_then(|language| { 552 | if let Some(handle) = &self.handle { 553 | handle 554 | .read_configuration_string(language, &configuration, TIMEOUT) 555 | .ok() 556 | } else { 557 | self.device 558 | .open() 559 | .unwrap() 560 | .read_configuration_string(language, &configuration, TIMEOUT) 561 | .ok() 562 | } 563 | }); 564 | configurations.push(UsbConfiguration { 565 | language: self.language, 566 | index: i, 567 | descriptor: usb::device::ConfigurationDescriptor { 568 | number: configuration.number(), 569 | description, 570 | self_powered: configuration.self_powered(), 571 | remote_wakeup: configuration.remote_wakeup(), 572 | max_power: configuration.max_power(), 573 | }, 574 | device: self.device.clone(), 575 | }); 576 | } 577 | 578 | configurations 579 | } 580 | } 581 | 582 | pub struct UsbConfiguration { 583 | device: rusb::Device, 584 | index: u8, 585 | language: Option, 586 | descriptor: usb::device::ConfigurationDescriptor, 587 | } 588 | 589 | impl UsbConfiguration { 590 | pub fn get_interfaces(&self) -> Vec { 591 | let handle = self.device.open().unwrap(); 592 | let config_descriptor = Arc::new(self.device.config_descriptor(self.index).unwrap()); 593 | config_descriptor 594 | .interfaces() 595 | .flat_map(|interface| { 596 | interface.descriptors().map(|interface_descriptor| { 597 | let interface_name = self.language.and_then(|language| { 598 | handle 599 | .read_interface_string(language, &interface_descriptor, TIMEOUT) 600 | .ok() 601 | }); 602 | 603 | UsbInterface { 604 | config_descriptor: config_descriptor.clone(), 605 | descriptor: usb::device::InterfaceDescriptor { 606 | interface_number: interface_descriptor.interface_number(), 607 | alternate_setting: interface_descriptor.setting_number(), 608 | interface_class: interface_descriptor.class_code(), 609 | interface_subclass: interface_descriptor.sub_class_code(), 610 | interface_protocol: interface_descriptor.protocol_code(), 611 | interface_name, 612 | }, 613 | } 614 | }) 615 | }) 616 | .collect() 617 | } 618 | } 619 | 620 | pub struct UsbInterface { 621 | descriptor: usb::device::InterfaceDescriptor, 622 | config_descriptor: Arc, 623 | } 624 | 625 | impl UsbInterface { 626 | pub fn get_endpoints(&self) -> Vec { 627 | let interface_descriptor = self 628 | .config_descriptor 629 | .interfaces() 630 | .find(|interface| interface.number() == self.descriptor.interface_number) 631 | .unwrap() 632 | .descriptors() 633 | .find(|descriptor| descriptor.setting_number() == self.descriptor.alternate_setting) 634 | .unwrap(); 635 | interface_descriptor 636 | .endpoint_descriptors() 637 | .map(|endpoint| UsbEndpoint { 638 | descriptor: usb::device::EndpointDescriptor { 639 | endpoint_number: endpoint.number(), 640 | direction: match endpoint.direction() { 641 | rusb::Direction::In => usb::types::Direction::In, 642 | rusb::Direction::Out => usb::types::Direction::Out, 643 | }, 644 | transfer_type: match endpoint.transfer_type() { 645 | rusb::TransferType::Control => usb::types::TransferType::Control, 646 | rusb::TransferType::Isochronous => usb::types::TransferType::Isochronous, 647 | rusb::TransferType::Bulk => usb::types::TransferType::Bulk, 648 | rusb::TransferType::Interrupt => usb::types::TransferType::Interrupt, 649 | }, 650 | max_packet_size: endpoint.max_packet_size(), 651 | interval: endpoint.interval(), 652 | }, 653 | }) 654 | .collect() 655 | } 656 | } 657 | 658 | pub struct UsbEndpoint { 659 | descriptor: usb::device::EndpointDescriptor, 660 | } 661 | 662 | impl UsbEndpoint { 663 | pub fn get_endpoint_number(&self) -> u8 { 664 | self.descriptor.endpoint_number 665 | + match self.descriptor.direction { 666 | Direction::Out => 0x00, 667 | Direction::In => 0x80, 668 | } 669 | } 670 | } 671 | 672 | #[derive(Debug)] 673 | pub enum UsbError { 674 | NotFound, 675 | AlreadyDropped, 676 | } 677 | 678 | impl std::fmt::Display for UsbError { 679 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 680 | match self { 681 | UsbError::NotFound => write!(f, "Not found"), 682 | UsbError::AlreadyDropped => write!(f, "Already dropped"), 683 | } 684 | } 685 | } 686 | 687 | impl Error for UsbError {} 688 | 689 | pub fn add_to_linker( 690 | linker: &mut wasmtime::component::Linker, 691 | ) -> wasmtime::Result<()> { 692 | wadu436::usb::device::add_to_linker(linker, |s| s) 693 | } 694 | 695 | #[cfg(test)] 696 | mod test { 697 | use super::*; 698 | 699 | #[test] 700 | fn test_enumeration() -> anyhow::Result<()> { 701 | let devices = UsbDevice::enumerate()?; 702 | 703 | for device in devices { 704 | for configuration in device.get_configurations() { 705 | for interface in configuration.get_interfaces() { 706 | for endpoint in interface.get_endpoints() { 707 | println!("{:?}", endpoint.descriptor); 708 | } 709 | } 710 | } 711 | } 712 | 713 | Ok(()) 714 | } 715 | } 716 | --------------------------------------------------------------------------------