Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support webusb as a backend #99

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5296d4b
WIP: Webusb
Yatekii Feb 10, 2024
bcb2753
Add webusb p1
Yatekii Nov 30, 2024
9508eed
Basic webusb backend works for flashing with probe-rs
Yatekii Dec 22, 2024
f6a6bea
Clean up some code
Yatekii Dec 22, 2024
2fe07ed
Remove unnecessary methods in webusb
Yatekii Dec 23, 2024
a15b7b7
Fix device enumeration for WebUSB
Yatekii Dec 23, 2024
45d0d8e
Some general clean up
Yatekii Dec 24, 2024
154bacc
Make x86 build again
Yatekii Dec 24, 2024
ed04a4e
Fix more build errors
Yatekii Dec 24, 2024
53875b3
Make regular platforms build again
Yatekii Dec 24, 2024
f0ebbc4
Fix more issues
Yatekii Dec 24, 2024
cc8af41
Fix more issues
Yatekii Dec 24, 2024
fe04061
Fix doctests
Yatekii Dec 24, 2024
a8b6a7d
Fix doctests more
Yatekii Dec 24, 2024
d251863
Fix doctests
Yatekii Dec 24, 2024
060e972
Fixes for windows
Yatekii Dec 24, 2024
7a054a6
Clean up some more
Yatekii Dec 25, 2024
784ca18
Clean up transfer functions
Yatekii Dec 26, 2024
92c4370
Implement clear halt and set alt
Yatekii Dec 26, 2024
f0909ca
Implement remaining functions but hotplugging
Yatekii Dec 26, 2024
1ab30cc
Implement hotplug events
Yatekii Dec 26, 2024
87da2ca
Run against wasm toolchain in CI
Yatekii Dec 26, 2024
59a7b3e
Make macOS build again
Yatekii Dec 26, 2024
0b3cd02
Fix compilation
Yatekii Dec 26, 2024
fe01b06
Remove unsafe impl of Send/Sync
Yatekii Jan 1, 2025
09505c4
Fix hotplug waker
Yatekii Jan 8, 2025
f969bea
Move webusb code to the webusb module
Yatekii Jan 8, 2025
dde4fed
Remove unwraps in enumeration
Yatekii Jan 8, 2025
d7b95cb
Make WebUSB work on WebWorkers
Yatekii Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
rustflags = "--cfg=web_sys_unstable_apis"
44 changes: 22 additions & 22 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

env:
CARGO_TERM_COLOR: always
Expand All @@ -21,31 +21,31 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: ['stable', '1.76']
rust: ["stable", "1.80"]

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v3
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v3
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

build_android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: 'aarch64-linux-android, armv7-linux-androideabi'
- name: build
run: |
cargo build --target aarch64-linux-android --all-features
cargo build --target armv7-linux-androideabi --all-features
- uses: actions/checkout@v4
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: "aarch64-linux-android, armv7-linux-androideabi"
- name: build
run: |
cargo build --target aarch64-linux-android --all-features
cargo build --target armv7-linux-androideabi --all-features
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rust-analyzer.check.targets": ["wasm32-unknown-unknown"],
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"rust-analyzer.showUnlinkedFileNotification": false
}
44 changes: 38 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,37 +1,69 @@
[package]
name = "nusb"
version = "0.1.10"
version = "0.1.12"
description = "Cross-platform low-level access to USB devices in pure Rust"
categories = ["hardware-support"]
keywords = ["usb", "hardware"]
authors = ["Kevin Mehall <[email protected]>"]
edition = "2021"
license = "Apache-2.0 OR MIT"
repository = "https://github.com/kevinmehall/nusb"
rust-version = "1.76" # keep in sync with .github/workflows/rust.yml
rust-version = "1.80" # keep in sync with .github/workflows/rust.yml

[dependencies]
atomic-waker = "1.1.2"
futures-core = "0.3.29"
log = "0.4.20"
once_cell = "1.18.0"
slab = "0.4.9"
tracing = "0.1"

[dev-dependencies]
env_logger = "0.10.0"
futures-lite = "1.13.0"
pollster = { version = "0.4", features = ["macro"] }

[target.'cfg(any(target_os="linux", target_os="android"))'.dependencies]
rustix = { version = "0.38.17", features = ["fs", "event", "net"] }
libc = "0.2.155"
# [target.'cfg(all(any(target_os="linux", target_os="android"), not(target_arch="wasm32")))'.dependencies]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with the conditional it fails to compile. Not sure how to do this one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok consider me confused. Having this last time I tried made it break. It works fine now.

# rustix = { version = "0.38.17", features = ["fs", "event", "net"] }
# libc = "0.2.155"

[target.'cfg(target_os="windows")'.dependencies]
windows-sys = { version = "0.48.0", features = ["Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Com"] }
windows-sys = { version = "0.48.0", features = [
"Win32_Devices_Usb",
"Win32_Devices_DeviceAndDriverInstallation",
"Win32_Foundation",
"Win32_Devices_Properties",
"Win32_Storage_FileSystem",
"Win32_Security",
"Win32_System_IO",
"Win32_System_Registry",
"Win32_System_Com",
] }

[target.'cfg(target_os="macos")'.dependencies]
core-foundation = "0.9.3"
core-foundation-sys = "0.8.4"
io-kit-sys = "0.4.0"

[target.'cfg(target_arch="wasm32")'.dependencies]
web-sys = { version = "*", features = [
"Document",
"Window",
"Navigator",
"Usb",
"UsbDevice",
"UsbDeviceRequestOptions",
"UsbConfiguration",
"UsbInterface",
"UsbAlternateInterface",
"UsbControlTransferParameters",
"UsbRequestType",
"UsbRecipient",
"UsbInTransferResult",
"UsbTransferStatus",
"UsbOutTransferResult",
] }
wasm-bindgen-futures = { version = "*" }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
8 changes: 5 additions & 3 deletions examples/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ use std::time::Duration;

use nusb::transfer::{Control, ControlType, Recipient};

fn main() {
#[pollster::main]
async fn main() {
env_logger::init();
let di = nusb::list_devices()
.await
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");

println!("Device info: {di:?}");

let device = di.open().unwrap();
let device = di.open().await.unwrap();

// Linux can make control transfers without claiming an interface
#[cfg(any(target_os = "linux", target_os = "macos"))]
Expand Down Expand Up @@ -49,7 +51,7 @@ fn main() {
}

// but we also provide an API on the `Interface` to support Windows
let interface = device.claim_interface(0).unwrap();
let interface = device.claim_interface(0).await.unwrap();

let result = interface.control_out_blocking(
Control {
Expand Down
15 changes: 9 additions & 6 deletions examples/bulk.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use futures_lite::future::block_on;
use nusb::transfer::RequestBuffer;

fn main() {
#[pollster::main]
async fn main() {
env_logger::init();
let di = nusb::list_devices()
.await
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");

println!("Device info: {di:?}");

let device = di.open().unwrap();
let interface = device.claim_interface(0).unwrap();
let device = di.open().await.unwrap();
let interface = device.claim_interface(0).await.unwrap();

block_on(interface.bulk_out(0x02, Vec::from([1, 2, 3, 4, 5])))
interface
.bulk_out(0x02, Vec::from([1, 2, 3, 4, 5]))
.await
.into_result()
.unwrap();

Expand All @@ -23,7 +26,7 @@ fn main() {
while queue.pending() < 8 {
queue.submit(RequestBuffer::new(256));
}
let result = block_on(queue.next_complete());
let result = queue.next_complete().await;
println!("{result:?}");
if result.status.is_err() {
break;
Expand Down
73 changes: 41 additions & 32 deletions examples/control.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,70 @@
use futures_lite::future::block_on;
use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient};

fn main() {
#[pollster::main]
async fn main() {
env_logger::init();
let di = nusb::list_devices()
.await
.unwrap()
.find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23)
.expect("device should be connected");

println!("Device info: {di:?}");

let device = di.open().unwrap();
let device = di.open().await.unwrap();

// Linux can make control transfers without claiming an interface
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
let result = block_on(device.control_out(ControlOut {
let result = device
.control_out(ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: 0x81,
value: 0x9999,
index: 0x9999,
data: &[1, 2, 3, 4],
})
.await;
println!("{result:?}");

let result = device
.control_in(ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: 0x81,
value: 0x9999,
index: 0x9999,
length: 256,
})
.await;
println!("{result:?}");
}

// but we also provide an API on the `Interface` to support Windows
let interface = device.claim_interface(0).await.unwrap();

let result = interface
.control_out(ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: 0x81,
value: 0x9999,
index: 0x9999,
data: &[1, 2, 3, 4],
}));
println!("{result:?}");
})
.await;
println!("{result:?}");

let result = block_on(device.control_in(ControlIn {
let result = interface
.control_in(ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: 0x81,
value: 0x9999,
index: 0x9999,
length: 256,
}));
println!("{result:?}");
}

// but we also provide an API on the `Interface` to support Windows
let interface = device.claim_interface(0).unwrap();

let result = block_on(interface.control_out(ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: 0x81,
value: 0x9999,
index: 0x9999,
data: &[1, 2, 3, 4],
}));
println!("{result:?}");

let result = block_on(interface.control_in(ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Device,
request: 0x81,
value: 0x9999,
index: 0x9999,
length: 256,
}));
})
.await;
println!("{result:?}");
}
15 changes: 8 additions & 7 deletions examples/descriptors.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use nusb::DeviceInfo;

fn main() {
#[pollster::main]
async fn main() {
env_logger::init();
for dev in nusb::list_devices().unwrap() {
inspect_device(dev);
for dev in nusb::list_devices().await.unwrap() {
inspect_device(dev).await;
}
}

fn inspect_device(dev: DeviceInfo) {
async fn inspect_device(dev: DeviceInfo) {
println!(
"Device {:03}.{:03} ({:04x}:{:04x}) {} {}",
dev.bus_id(),
Expand All @@ -17,7 +18,7 @@ fn inspect_device(dev: DeviceInfo) {
dev.manufacturer_string().unwrap_or(""),
dev.product_string().unwrap_or("")
);
let dev = match dev.open() {
let dev = match dev.open().await {
Ok(dev) => dev,
Err(e) => {
println!("Failed to open device: {}", e);
Expand All @@ -33,6 +34,6 @@ fn inspect_device(dev: DeviceInfo) {
for config in dev.configurations() {
println!("{config:#?}");
}
println!("");
println!("");
println!();
println!();
}
7 changes: 5 additions & 2 deletions examples/detach.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! Detach the kernel driver for an FTDI device and then reattach it.
use std::{thread::sleep, time::Duration};
fn main() {

#[pollster::main]
async fn main() {
env_logger::init();
let di = nusb::list_devices()
.await
.unwrap()
.find(|d| d.vendor_id() == 0x0403 && d.product_id() == 0x6001)
.expect("device should be connected");

let device = di.open().unwrap();
let device = di.open().await.unwrap();
device.detach_kernel_driver(0).unwrap();
sleep(Duration::from_secs(10));
device.attach_kernel_driver(0).unwrap();
Expand Down
7 changes: 5 additions & 2 deletions examples/detach_claim.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
//! Detach the kernel driver for an FTDI device, claim the USB interface, and
//! then reattach it.
use std::{thread::sleep, time::Duration};
fn main() {

#[pollster::main]
async fn main() {
env_logger::init();
let di = nusb::list_devices()
.await
.unwrap()
.find(|d| d.vendor_id() == 0x0403 && d.product_id() == 0x6010)
.expect("device should be connected");

let device = di.open().unwrap();
let device = di.open().await.unwrap();
let interface = device.detach_and_claim_interface(0).unwrap();
sleep(Duration::from_secs(1));
drop(interface);
Expand Down
Loading
Loading