feat: wasm is working

This commit is contained in:
spacemeowx2 2021-01-09 15:44:56 +08:00
parent 7f8f042814
commit c7cf168cfb
11 changed files with 298 additions and 139 deletions

26
Cargo.lock generated
View File

@ -39,17 +39,6 @@ version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86"
[[package]]
name = "async-timer"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5fa6ed76cb2aa820707b4eb9ec46f42da9ce70b0eafab5e5e34942b38a44d5"
dependencies = [
"libc",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -103,7 +92,6 @@ dependencies = [
name = "blflash"
version = "0.3.0"
dependencies = [
"async-timer",
"byteorder",
"crc",
"deku",
@ -111,6 +99,7 @@ dependencies = [
"env_logger",
"futures",
"gloo-events",
"gloo-timers",
"hex",
"indicatif",
"js-sys",
@ -582,6 +571,19 @@ dependencies = [
"web-sys",
]
[[package]]
name = "gloo-timers"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "heck"
version = "0.3.1"

View File

@ -40,7 +40,7 @@ wasm-bindgen = "0.2"
js-sys = "0.3"
once_cell = "1.5"
wasm-bindgen-futures = "0.4"
async-timer = "0.7"
gloo-timers = { version = "0.2", features = ["futures"] }
gloo-events = "0.1"
wasm-streams = "0.1"

View File

@ -5,116 +5,209 @@ pub mod binding {
}
use crate::Error;
use async_timer::oneshot::{Oneshot, Timer};
use futures::{
channel::mpsc,
io::{AsyncRead, AsyncWrite},
io::{AsyncRead, AsyncReadExt, AsyncWrite},
ready,
sink::Sink,
stream::{BoxStream, IntoAsyncRead, Stream, StreamExt, TryStreamExt},
sink::SinkExt,
stream::{IntoAsyncRead, LocalBoxStream, StreamExt, TryStreamExt},
};
use gloo_events::EventListener;
use js_sys::Reflect;
use gloo_timers::future::TimeoutFuture;
use js_sys::{Reflect, Uint8Array};
use serial::SerialPortSettings;
use std::{
convert::TryInto,
io,
pin::Pin,
sync::Mutex,
task::{Context, Poll},
time::Duration,
};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::JsFuture;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use wasm_streams::{
readable::{IntoStream, ReadableStream},
readable::ReadableStream,
writable::{IntoSink, WritableStream},
};
use web_sys::{window, EventTarget};
#[wasm_bindgen]
extern "C" {
// #[wasm_bindgen(extends=js_sys::Object, js_name=Serial , typescript_type="Serial")]
// pub type Serial;
// #[wasm_bindgen(catch, method, structural, js_class="Serial" , js_name=requestPort)]
// pub fn requestPort(this: &Serial) -> Result<Promise, JsValue>;
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
use web_sys::window;
type Item = io::Result<Vec<u8>>;
struct Settings<'a>(&'a mut binding::SerialOptions);
impl<'a> SerialPortSettings for Settings<'a> {
fn baud_rate(&self) -> Option<serial::BaudRate> {
todo!()
}
fn char_size(&self) -> Option<serial::CharSize> {
todo!()
}
fn parity(&self) -> Option<serial::Parity> {
todo!()
}
fn stop_bits(&self) -> Option<serial::StopBits> {
todo!()
}
fn flow_control(&self) -> Option<serial::FlowControl> {
todo!()
}
fn set_baud_rate(&mut self, baud_rate: serial::BaudRate) -> serial::Result<()> {
log::trace!("set_baud_rate {:?}", baud_rate);
self.0.baud_rate(baud_rate.speed() as u32);
Ok(())
}
fn set_char_size(&mut self, char_size: serial::CharSize) {
log::trace!("set_char_size {:?}", char_size);
self.0.data_bits(match char_size {
serial::CharSize::Bits5 => 5,
serial::CharSize::Bits6 => 6,
serial::CharSize::Bits7 => 7,
serial::CharSize::Bits8 => 8,
});
}
fn set_parity(&mut self, parity: serial::Parity) {
log::trace!("set_parity {:?}", parity);
use binding::ParityType;
use serial::Parity;
self.0.parity(match parity {
Parity::ParityNone => ParityType::None,
Parity::ParityEven => ParityType::Even,
Parity::ParityOdd => ParityType::Odd,
});
}
fn set_stop_bits(&mut self, stop_bits: serial::StopBits) {
log::trace!("set_stop_bits {:?}", stop_bits);
self.0.stop_bits(match stop_bits {
serial::StopBits::Stop1 => 1,
serial::StopBits::Stop2 => 2,
});
}
fn set_flow_control(&mut self, flow_control: serial::FlowControl) {
log::trace!("set_flow_control {:?}", flow_control);
use binding::FlowControlType;
use serial::FlowControl;
self.0.flow_control(match flow_control {
FlowControl::FlowNone => FlowControlType::None,
FlowControl::FlowHardware => FlowControlType::Hardware,
_ => unreachable!(),
});
}
}
pub struct AsyncSerial {
// receiver: IntoAsyncRead<mpsc::Receiver<Item>>,
readable: IntoAsyncRead<BoxStream<'static, Item>>,
writable: IntoSink<'static>,
readable: Option<IntoAsyncRead<LocalBoxStream<'static, Item>>>,
writable: Option<IntoSink<'static>>,
port: binding::SerialPort,
signals: binding::SerialOutputSignals,
timeout: Duration,
writing: bool,
option: binding::SerialOptions,
// opened: Rc<RefCell<bool>>,
// _on_connect: EventListener,
// _on_disconnect: EventListener,
}
open: Mutex<bool>,
fn get_pipe(
port: &JsValue,
) -> crate::Result<(
Option<IntoAsyncRead<LocalBoxStream<'static, Item>>>,
Option<IntoSink<'static>>,
)> {
let readable = Reflect::get(&port, &"readable".into())
.map_err(|_| Error::WebError("Failed to get readable"))?;
let writable = Reflect::get(&port, &"writable".into())
.map_err(|_| Error::WebError("Failed to get writable"))?;
_on_connect: EventListener,
_on_disconnect: EventListener,
let readable = ReadableStream::from_raw(readable.unchecked_into());
let writable = WritableStream::from_raw(writable.unchecked_into());
let readable = readable
.into_stream()
.map(|item| {
item.map(|v| v.unchecked_into::<Uint8Array>().to_vec())
.map_err(|_| io::Error::from(io::ErrorKind::BrokenPipe))
})
.boxed_local()
.into_async_read();
Ok((Some(readable), Some(writable.into_sink())))
}
impl AsyncSerial {
pub async fn open(_port: &str) -> crate::Result<Self> {
let window = window().ok_or(Error::WebError("Failed to get window"))?;
let navigator = window
.navigator()
.dyn_into::<binding::Navigator>()
.expect("navigator");
let navigator: JsValue = window.navigator().into();
let navigator: binding::Navigator = navigator.into();
let serial = navigator.serial();
let port = Into::<JsFuture>::into(serial.request_port())
let port = JsFuture::from(serial.request_port())
.await
.expect("Failed to await request_port")
.dyn_into::<binding::SerialPort>()
.expect("SerialPort");
let target: JsValue = port.clone().into();
let target: EventTarget = target.into();
.map_err(|_| Error::WebError("Failed to await request_port"))?;
// let (sender, b) = mpsc::channel(128);
// let (c, receiver) = mpsc::channel(128);
// let target: EventTarget = port.clone().into();
let port: binding::SerialPort = port.into();
let _on_connect = EventListener::new(&target, "connect", move |_event| log("on connect!"));
let _on_disconnect =
EventListener::new(&target, "disconnect", move |_event| log("on disconnect!"));
// make sure the port is closed
if let Err(e) = JsFuture::from(port.close()).await {
log::warn!("Failed to close port {:?}", e);
}
let readable = Reflect::get(&port, &"readable".into()).expect("readable");
let writable = Reflect::get(&port, &"writable".into()).expect("writable");
let option = binding::SerialOptions::new(115200);
JsFuture::from(port.open(&option))
.await
.map_err(|_| Error::WebError("Failed to open serial"))?;
let readable = ReadableStream::from_raw(readable.unchecked_into());
let writable = WritableStream::from_raw(writable.unchecked_into());
// let opened = Rc::new(RefCell::new(true));
// let _on_connect = {
// let open = open.clone();
// EventListener::new(&target, "connect", move |_event| {
// log("on connect!");
// open.replace(true);
// })
// };
// let _on_disconnect = {
// let open = open.clone();
// EventListener::new(&target, "disconnect", move |_event| {
// log("on disconnect!");
// open.replace(false);
// })
// };
let (readable, writable) = get_pipe(&port)?;
Ok(AsyncSerial {
writable: writable.into_sink(),
readable: readable.into_stream().boxed().into_async_read(),
readable,
writable,
port,
signals: binding::SerialOutputSignals::new(),
timeout: Duration::from_secs(1),
open: Mutex::new(false),
writing: false,
_on_connect,
_on_disconnect,
// opened,
option,
// _on_connect,
// _on_disconnect,
})
}
pub async fn set_rts(&mut self, level: bool) -> serial::Result<()> {
self.signals.request_to_send(level);
Into::<JsFuture>::into(self.port.set_signals_with_signals(&self.signals))
JsFuture::from(self.port.set_signals_with_signals(&self.signals))
.await
.expect("Failed to set signals");
.map_err(|_| serial::Error::new(serial::ErrorKind::NoDevice, "Failed to set_rts"))?;
Ok(())
}
pub async fn set_dtr(&mut self, level: bool) -> serial::Result<()> {
self.signals.data_terminal_ready(level);
Into::<JsFuture>::into(self.port.set_signals_with_signals(&self.signals))
JsFuture::from(self.port.set_signals_with_signals(&self.signals))
.await
.expect("Failed to set signals");
.map_err(|_| serial::Error::new(serial::ErrorKind::NoDevice, "Failed to set_dtr"))?;
Ok(())
}
pub async fn sleep(&self, duration: Duration) {
Timer::new(duration).await;
TimeoutFuture::new(duration.as_millis().try_into().unwrap()).await;
}
pub async fn set_timeout(&mut self, timeout: Duration) -> serial::Result<()> {
self.timeout = timeout;
@ -127,7 +220,27 @@ impl AsyncSerial {
&mut self,
setup: &dyn Fn(&mut dyn SerialPortSettings) -> serial::Result<()>,
) -> serial::Result<()> {
// TODO
let mut settings = Settings(&mut self.option);
setup(&mut settings)?;
self.readable.take();
self.writable.take();
// make sure the port is closed
if let Err(e) = JsFuture::from(self.port.close()).await {
log::warn!("Failed to close port {:?}", e);
}
JsFuture::from(self.port.open(&self.option))
.await
.map_err(|_| {
serial::Error::new(serial::ErrorKind::NoDevice, "Failed to open serial")
})?;
let (readable, writable) = get_pipe(&self.port)
.map_err(|_| serial::Error::new(serial::ErrorKind::NoDevice, "Failed to get pipe"))?;
self.readable = readable;
self.writable = writable;
Ok(())
}
}
@ -138,7 +251,8 @@ impl AsyncRead for AsyncSerial {
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
AsyncRead::poll_read(Pin::new(&mut self.readable), cx, buf)
// self.readable.poll_read_unpin(cx, buf)
AsyncRead::poll_read(Pin::new(&mut self.readable.as_mut().unwrap()), cx, buf)
}
}
@ -148,23 +262,26 @@ impl AsyncWrite for AsyncSerial {
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
if !self.writing {
self.writing = true;
self.writable
.start_send(Ok(Vec::from(buf)))
.map_err(|_| Into::<io::Error>::into(io::ErrorKind::BrokenPipe))?
}
let r = ready!(self.writable.poll_ready(cx));
r.map_err(|_| Into::<io::Error>::into(io::ErrorKind::BrokenPipe))?;
self.writing = false;
let writable = self.writable.as_mut().unwrap();
ready!(writable.poll_ready_unpin(cx))
.map_err(|_| Into::<io::Error>::into(io::ErrorKind::BrokenPipe))?;
let jsbuf: JsValue = Uint8Array::from(buf).into();
writable
.start_send_unpin(jsbuf)
.map_err(|_| Into::<io::Error>::into(io::ErrorKind::BrokenPipe))?;
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
ready!(self.writable.as_mut().unwrap().poll_flush_unpin(cx))
.map_err(|_| Into::<io::Error>::into(io::ErrorKind::BrokenPipe))
.into()
}
fn poll_close(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
ready!(self.writable.as_mut().unwrap().poll_close_unpin(cx))
.map_err(|_| Into::<io::Error>::into(io::ErrorKind::BrokenPipe))
.into()
}
}

View File

@ -78,28 +78,32 @@ impl Connection {
pub async fn reset(&mut self) -> Result<(), Error> {
self.serial.set_rts(false).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
self.serial.set_dtr(true).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
self.serial.set_dtr(false).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
Ok(())
}
pub async fn reset_to_flash(&mut self) -> Result<(), Error> {
self.serial.set_rts(true).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
self.serial.set_dtr(true).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
self.serial.set_dtr(false).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
self.serial.set_rts(false).await?;
self.serial.sleep(Duration::from_millis(50)).await;
self.sleep(Duration::from_millis(50)).await;
Ok(())
}
pub async fn sleep(&self, duration: Duration) {
self.serial.sleep(duration).await
}
pub async fn timeout(&self) -> Duration {
self.serial.timeout().await
}

View File

@ -4,11 +4,11 @@ use crate::{connection::Connection, elf::RomSegment};
use indicatif::{HumanBytes, ProgressBar, ProgressStyle};
use serial::BaudRate;
use sha2::{Digest, Sha256};
use std::ops::Range;
use std::{
io::{Cursor, Read, Write},
time::{Duration, Instant},
};
use std::{ops::Range, thread::sleep};
fn get_bar(len: u64) -> ProgressBar {
let bar = ProgressBar::new(len);
@ -97,20 +97,26 @@ impl Flasher {
let mut reader = Cursor::new(&segment.data);
let mut cur = segment.addr;
#[cfg(not(target_arch = "wasm32"))]
let start = Instant::now();
log::info!("Program flash... {:x}", local_hash);
#[cfg(not(target_arch = "wasm32"))]
let pb = get_bar(segment.size() as u64);
loop {
let size = self.eflash_loader().flash_program(cur, &mut reader).await?;
// log::trace!("program {:x} {:x}", cur, size);
cur += size;
#[cfg(not(target_arch = "wasm32"))]
pb.inc(size as u64);
if size == 0 {
break;
}
}
#[cfg(not(target_arch = "wasm32"))]
pb.finish_and_clear();
#[cfg(not(target_arch = "wasm32"))]
let elapsed = start.elapsed();
#[cfg(not(target_arch = "wasm32"))]
log::info!(
"Program done {:?} {}/s",
elapsed,
@ -168,6 +174,7 @@ impl Flasher {
const BLOCK_SIZE: usize = 4096;
let mut cur = range.start;
#[cfg(not(target_arch = "wasm32"))]
let pb = get_bar(range.len() as u64);
while cur < range.end {
let data = self
@ -176,8 +183,10 @@ impl Flasher {
.await?;
writer.write_all(&data)?;
cur += data.len() as u32;
#[cfg(not(target_arch = "wasm32"))]
pb.inc(data.len() as u64);
}
#[cfg(not(target_arch = "wasm32"))]
pb.finish_and_clear();
Ok(())
@ -190,18 +199,24 @@ impl Flasher {
self.boot_rom().load_boot_header(&mut reader).await?;
self.boot_rom().load_segment_header(&mut reader).await?;
#[cfg(not(target_arch = "wasm32"))]
let start = Instant::now();
log::info!("Sending eflash_loader...");
#[cfg(not(target_arch = "wasm32"))]
let pb = get_bar(len as u64);
loop {
let size = self.boot_rom().load_segment_data(&mut reader).await?;
#[cfg(not(target_arch = "wasm32"))]
pb.inc(size as u64);
if size == 0 {
break;
}
}
#[cfg(not(target_arch = "wasm32"))]
pb.finish_and_clear();
#[cfg(not(target_arch = "wasm32"))]
let elapsed = start.elapsed();
#[cfg(not(target_arch = "wasm32"))]
log::info!(
"Finished {:?} {}/s",
elapsed,
@ -210,7 +225,7 @@ impl Flasher {
self.boot_rom().check_image().await?;
self.boot_rom().run_image().await?;
sleep(Duration::from_millis(500));
self.connection.sleep(Duration::from_millis(500)).await;
self.connection.set_baud(self.flash_speed).await?;
self.handshake().await?;
@ -239,11 +254,13 @@ impl Flasher {
let len = connection.calc_duration_length(Duration::from_millis(5));
log::trace!("5ms send count {}", len);
let data: Vec<u8> = std::iter::repeat(0x55u8).take(len).collect();
#[cfg(not(target_arch = "wasm32"))]
let start = Instant::now();
connection.write_all(&data).await?;
connection.flush().await?;
#[cfg(not(target_arch = "wasm32"))]
log::trace!("handshake sent elapsed {:?}", start.elapsed());
sleep(Duration::from_millis(200));
connection.sleep(Duration::from_millis(200)).await;
for _ in 0..5 {
if connection.read_response(0).await.is_ok() {

View File

@ -11,12 +11,12 @@ pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
pub use fs::File;
pub fn fs_read_file(path: PathBuf) -> Option<Vec<u8>> {
pub fn fs_read_file(_path: PathBuf) -> Option<Vec<u8>> {
unimplemented!("only avaliable on wasm")
}
pub fn fs_write_file(path: PathBuf, content: Vec<u8>) {
pub fn fs_write_file(_path: PathBuf, _content: Vec<u8>) {
unimplemented!("only avaliable on wasm")
}
pub fn fs_remove_file(path: PathBuf) {
pub fn fs_remove_file(_path: PathBuf) {
unimplemented!("only avaliable on wasm")
}

View File

@ -3,7 +3,6 @@
use once_cell::sync::Lazy;
use std::{
collections::HashMap,
fs,
io::{self, Cursor, Write},
path::{Path, PathBuf},
sync::Mutex,
@ -44,7 +43,11 @@ impl Drop for File {
}
pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
fs::read(path)
FS.lock()
.unwrap()
.get(&path.as_ref().to_path_buf())
.map(Clone::clone)
.ok_or(io::ErrorKind::NotFound.into())
}
pub fn fs_read_file(path: PathBuf) -> Option<Vec<u8>> {

View File

@ -9,8 +9,8 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
blflash = { version = "0.3", path = "../blflash" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
log = "0.4"
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
console_error_panic_hook = "0.1"
wasm-logger = "0.2"

View File

@ -6,5 +6,7 @@
<body>
<script type="module" src="index.js"></script>
<button id="dump">dump</button>
<button id="flash">flash</button>
<input id="file" type="file"></input>
</body>
</html>

View File

@ -1,29 +1,45 @@
import init, { dump, FS } from './pkg/libblflash.js'
import init, { dump, flash, FS } from './pkg/libblflash.js'
async function onDump() {
await dump({
port: 'port',
baud_rate: 115200,
initial_baud_rate: 115200,
output: "output.bin",
start: 0,
end: 0x100000,
})
try {
await dump({
port: 'port',
baud_rate: 1000000,
initial_baud_rate: 115200,
output: "output.bin",
start: 0,
end: 0x100000,
})
console.log('done')
debugger
const result = FS.read_file('output.bin')
console.log(result)
} catch(e) {
console.error('error during Dump', e)
}
}
async function onFlash(event) {
try {
const file = document.getElementById('file').files[0]
const content = await new Response(file).arrayBuffer()
FS.write_file('input.bin', new Uint8Array(content))
await flash({
port: 'port',
baud_rate: 1000000,
initial_baud_rate: 115200,
image: 'input.bin',
})
console.log('done')
} catch(e) {
console.error('error during Flash', e)
}
}
async function main() {
console.log('load wasm')
await init()
console.log('wasm loaded')
// const r = await dump({
// port: 'port',
// baud_rate: 115200,
// initial_baud_rate: 115200,
// output: "output.bin",
// start: 0,
// end: 0x100000,
// })
// console.log(r)
document.getElementById('dump').addEventListener('click', onDump)
document.getElementById('flash').addEventListener('click', onFlash)
}
main()
document.getElementById('dump').addEventListener('click', onDump)

View File

@ -1,31 +1,29 @@
use blflash::{Boot2Opt, Connection, Error, FlashOpt, DumpOpt, fs};
use blflash::{Error, FlashOpt, DumpOpt, fs};
use wasm_bindgen::prelude::*;
use std::path::PathBuf;
mod utils;
fn map_result(r: Result<(), Error>) -> JsValue {
match r {
Ok(_) => JsValue::UNDEFINED,
Err(e) => JsValue::from_str(&e.to_string()),
}
fn init() {
utils::set_panic_hook();
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
}
#[wasm_bindgen]
pub async fn flash(opt: JsValue) -> JsValue {
utils::set_panic_hook();
pub async fn flash(opt: JsValue) -> Result<(), JsValue> {
init();
let opt: FlashOpt = opt.into_serde().unwrap();
let result = blflash::flash(opt).await;
map_result(result)
let opt: FlashOpt = opt.into_serde().map_err(|e| e.to_string())?;
blflash::flash(opt).await.map_err(|e| e.to_string())?;
Ok(())
}
#[wasm_bindgen]
pub async fn dump(opt: JsValue) -> JsValue {
utils::set_panic_hook();
pub async fn dump(opt: JsValue) -> Result<(), JsValue> {
init();
let opt: DumpOpt = opt.into_serde().unwrap();
let result = blflash::dump(opt).await;
map_result(result)
let opt: DumpOpt = opt.into_serde().map_err(|e| e.to_string())?;
blflash::dump(opt).await.map_err(|e| e.to_string())?;
Ok(())
}
struct FS;