Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 7 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ cli = [
]
# No-op: hidra's nusb backend is always built in and picked at run time.
nusb = []
# On wasm, lets an ISPDevice be driven over WebUSB as well as WebHID.
webusb = ["hidra/nusb"]

[[bin]]
name = "sinowisp"
Expand Down Expand Up @@ -66,4 +68,4 @@ stdext = "0.3.3"

# TODO: drop once hidra#8 lands and the backend-dispatch API is on crates.io.
[patch.crates-io]
hidra = { git = "https://github.com/carlossless/hidra", rev = "e11ba204159dfc791c4505ef59a0ce759d500c5b" }
hidra = { git = "https://github.com/carlossless/hidra", rev = "b1d19bc86809932f6ab9192b24ae11fb649646c4" }
8 changes: 6 additions & 2 deletions src/device_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use hidra::{
use indicatif::ProgressBar;
use itertools::Itertools;
use log::{debug, error, info};
use sinowisp::{is_expected_error, DeviceSpec, ISPDevice};
use sinowisp::{is_expected_error, DeviceSpec, ISPDevice, ISPHandle};
use thiserror::Error;

use crate::hid_tree::{DeviceNode, InterfaceNode};
Expand Down Expand Up @@ -322,7 +322,11 @@ impl DeviceSelector {
.wait()
.map_err(DeviceSelectorError::from)?;

Ok(ISPDevice::new(device_spec, cmd_handle, Some(xfer_handle)))
Ok(ISPDevice::new(
device_spec,
cmd_handle,
Some(ISPHandle::from(xfer_handle)),
))
};
}

Expand Down
55 changes: 49 additions & 6 deletions src/isp_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,60 @@ const CMD_REBOOT: u8 = 0x5a;
const XFER_READ_PAGE: u8 = 0x72;
const XFER_WRITE_PAGE: u8 = 0x77;

/// A HID handle an [`ISPDevice`] talks through.
///
/// On wasm a device whose interface declares a vendor-specific class is
/// reachable over `WebUSB` but never appears in `WebHID`, so both are accepted.
pub enum ISPHandle {
/// A `WebHID` handle natively, or a per-OS/nusb one.
Hid(HidDevice),
/// A `WebUSB` handle.
#[cfg(all(target_arch = "wasm32", feature = "webusb"))]
WebUsb(hidra::webusb::HidDevice),
}

impl From<HidDevice> for ISPHandle {
fn from(device: HidDevice) -> Self {
ISPHandle::Hid(device)
}
}

#[cfg(all(target_arch = "wasm32", feature = "webusb"))]
impl From<hidra::webusb::HidDevice> for ISPHandle {
fn from(device: hidra::webusb::HidDevice) -> Self {
ISPHandle::WebUsb(device)
}
}

impl ISPHandle {
async fn send_feature_report(&self, data: &[u8]) -> Result<(), HidError> {
match self {
ISPHandle::Hid(device) => device.send_feature_report(data).await,
#[cfg(all(target_arch = "wasm32", feature = "webusb"))]
ISPHandle::WebUsb(device) => device.send_feature_report(data).await,
}
}

async fn get_feature_report(&self, buf: &mut [u8]) -> Result<usize, HidError> {
match self {
ISPHandle::Hid(device) => device.get_feature_report(buf).await,
#[cfg(all(target_arch = "wasm32", feature = "webusb"))]
ISPHandle::WebUsb(device) => device.get_feature_report(buf).await,
}
}
}

/// One open connection to a device in ISP bootloader mode.
///
/// The methods are the individual protocol operations; they perform no
/// sequencing, delays, or progress reporting. Callers compose them into full
/// read/write cycles (and insert the settle delays after [`erase`](Self::erase)
/// and [`reboot`](Self::reboot)).
pub struct ISPDevice {
cmd_device: HidDevice,
cmd_device: ISPHandle,
/// Some platforms (Windows) expose the transfer report on a separate HID
/// handle; everywhere else it is the same handle as `cmd_device`.
xfer_device: Option<HidDevice>,
xfer_device: Option<ISPHandle>,
device_spec: DeviceSpec,
}

Expand Down Expand Up @@ -89,11 +132,11 @@ impl ISPDevice {
/// platforms that split them across HID collections (Windows).
pub fn new(
device_spec: DeviceSpec,
cmd_device: HidDevice,
xfer_device: Option<HidDevice>,
cmd_device: impl Into<ISPHandle>,
xfer_device: Option<ISPHandle>,
) -> Self {
Self {
cmd_device,
cmd_device: cmd_device.into(),
xfer_device,
device_spec,
}
Expand All @@ -104,7 +147,7 @@ impl ISPDevice {
&self.device_spec
}

fn xfer_device(&self) -> &HidDevice {
fn xfer_device(&self) -> &ISPHandle {
self.xfer_device.as_ref().unwrap_or(&self.cmd_device)
}

Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Async primitives for the Sinowealth 8051 HID ISP bootloader protocol.
//!
//! Each protocol operation ([`ISPDevice::enable_firmware`], [`ISPDevice::erase`],
//! [`ISPDevice::read_page`], ...) is an `async` method over a [`hidra::HidDevice`],
//! [`ISPDevice::read_page`], ...) is an `async` method over an [`ISPHandle`],
//! so the exact same code drives a native CLI (by calling
//! [`hidra::MaybeFuture::wait`] on the returned futures) and a web-based flasher
//! (by `.await`-ing them on `wasm32` with the WebHID backend).
//! (by `.await`-ing them on `wasm32`). On wasm the handle is a WebHID device,
//! or a WebUSB one for devices the browser will not expose as HID.
//!
//! The crate carries no orchestration, timing, or UI: composing these
//! primitives into full read/write cycles, inserting the post-erase/reboot
Expand Down