diff --git a/Cargo.lock b/Cargo.lock index a9a0c8e..843b27c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,6 +931,18 @@ dependencies = [ "embassy-sync 0.2.0 (git+https://github.com/embassy-rs/embassy/?rev=ae83e6f5367197feb8361b9a28adbdedbe37e0c5)", ] +[[package]] +name = "embassy-net-w5500" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy/?rev=ae83e6f5367197feb8361b9a28adbdedbe37e0c5#ae83e6f5367197feb8361b9a28adbdedbe37e0c5" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-time", + "embedded-hal 1.0.0-alpha.10", + "embedded-hal-async", +] + [[package]] name = "embassy-rp" version = "0.1.0" @@ -1010,7 +1022,6 @@ name = "embassy-usb" version = "0.1.0" source = "git+https://github.com/embassy-rs/embassy/?rev=ae83e6f5367197feb8361b9a28adbdedbe37e0c5#ae83e6f5367197feb8361b9a28adbdedbe37e0c5" dependencies = [ - "defmt", "embassy-futures", "embassy-net-driver-channel", "embassy-sync 0.2.0 (git+https://github.com/embassy-rs/embassy/?rev=ae83e6f5367197feb8361b9a28adbdedbe37e0c5)", @@ -2736,6 +2747,7 @@ dependencies = [ "embassy-futures", "embassy-net", "embassy-net-driver", + "embassy-net-w5500", "embassy-rp", "embassy-sync 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "embassy-time", diff --git a/Cargo.toml b/Cargo.toml index dc0e73c..16907ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.2.0-alpha" edition = "2021" description = "A SSH library suitable for embedded and larger programs" repository = "https://github.com/mkj/sunset" -categories = ["network-programming", "no-std"] +categories = ["network-programming", "embedded", "no-std"] license = "MPL-2.0" keywords = ["ssh"] @@ -99,6 +99,7 @@ embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "ae8 cyw43 = { git = "https://github.com/embassy-rs/embassy/", rev = "ae83e6f5367197feb8361b9a28adbdedbe37e0c5" } cyw43-pio = { git = "https://github.com/embassy-rs/embassy/", rev = "ae83e6f5367197feb8361b9a28adbdedbe37e0c5" } +embassy-net-w5500 = { git = "https://github.com/embassy-rs/embassy/", rev = "ae83e6f5367197feb8361b9a28adbdedbe37e0c5" } bcrypt = { version = "0.14", git = "https://github.com/mkj/rust-bcrypt", branch = "noalloc" } diff --git a/embassy/demos/common/src/config.rs b/embassy/demos/common/src/config.rs index 3378b31..1585ab9 100644 --- a/embassy/demos/common/src/config.rs +++ b/embassy/demos/common/src/config.rs @@ -50,15 +50,27 @@ pub struct SSHConfig { pub wifi_net: String<32>, /// WPA2 passphrase. None is Open network. pub wifi_pw: Option>, + + pub mac: [u8; 6], +} + +fn random_mac() -> Result<[u8; 6]> { + let mut mac = [0u8; 6]; + sunset::random::fill_random(&mut mac)?; + // unicast, locally administered + mac[0] = (mac[0] & 0xfc) | 0x02; + Ok(mac) } + impl SSHConfig { /// Bump this when the format changes - pub const CURRENT_VERSION: u8 = 4; + pub const CURRENT_VERSION: u8 = 5; /// A buffer this large will fit any SSHConfig. // It can be updated by looking at - // `cargo test -- roundtrip_config --show-output` - pub const BUF_SIZE: usize = 443; + // `cargo test -- roundtrip_config` + // in the demos/common directory + pub const BUF_SIZE: usize = 449; /// Creates a new config with default parameters. /// @@ -68,6 +80,7 @@ impl SSHConfig { let wifi_net = option_env!("WIFI_NET").unwrap_or("guest").into(); let wifi_pw = option_env!("WIFI_PW").map(|p| p.into()); + let mac = random_mac()?; Ok(SSHConfig { hostkey, console_pw: None, @@ -77,6 +90,7 @@ impl SSHConfig { admin_keys: Default::default(), wifi_net, wifi_pw, + mac, }) } @@ -143,28 +157,25 @@ impl SSHEncode for SSHConfig { info!("enc si"); enc_signkey(&self.hostkey, s)?; - info!("enc pw"); enc_option(&self.console_pw, s)?; for k in self.console_keys.iter() { - info!("enc k"); enc_option(k, s)?; } self.console_noauth.enc(s)?; - info!("enc ad"); enc_option(&self.admin_pw, s)?; for k in self.admin_keys.iter() { - info!("enc ke"); enc_option(k, s)?; } - info!("enc net"); self.wifi_net.as_str().enc(s)?; - info!("enc netpw"); enc_option(&self.wifi_pw, s)?; + + self.mac.enc(s)?; + Ok(()) } } @@ -174,34 +185,29 @@ impl<'de> SSHDecode<'de> for SSHConfig { where S: SSHSource<'de>, { - info!("dec si"); let hostkey = dec_signkey(s)?; - info!("dec pw"); let console_pw = dec_option(s)?; let mut console_keys = [None, None, None]; for k in console_keys.iter_mut() { - info!("dec k"); *k = dec_option(s)?; } let console_noauth = SSHDecode::dec(s)?; - info!("dec ad"); let admin_pw = dec_option(s)?; let mut admin_keys = [None, None, None]; for k in admin_keys.iter_mut() { - info!("dec adk"); *k = dec_option(s)?; } - info!("dec wn"); let wifi_net = SSHDecode::dec(s)?; - info!("dec wp"); let wifi_pw = dec_option(s)?; + let mac = SSHDecode::dec(s)?; + Ok(Self { hostkey, console_pw, @@ -211,6 +217,7 @@ impl<'de> SSHDecode<'de> for SSHConfig { admin_keys, wifi_net, wifi_pw, + mac, }) } } @@ -289,12 +296,12 @@ mod tests { let mut buf = [0u8; 1000]; let l = sshwire::write_ssh(&mut buf, &c1).unwrap(); let v = &buf[..l]; - let c2: SSHConfig = sshwire::read_ssh(&buf, None).unwrap(); + let c2: SSHConfig = sshwire::read_ssh(v, None).unwrap(); assert_eq!(c1, c2); // All the fruit, to check BUF_SIZE. // Variable length fields are all max size. - let mut c1 = SSHConfig { + let c1 = SSHConfig { hostkey: c1.hostkey, console_pw: Some(PwHash::new("zong").unwrap()), console_keys: [ @@ -313,13 +320,21 @@ mod tests { wifi_pw: Some( core::str::from_utf8([b'f'; 63].as_slice()).unwrap().into(), ), + mac: [6,2,3,4,5,6], }; - let mut buf = [0u8; SSHConfig::BUF_SIZE]; + // test once to determine size to print + let mut buf = [0u8; 3000]; let l = sshwire::write_ssh(&mut buf, &c1).unwrap(); + let size_msg = format!("BUF_SIZE must be at least {}", l); + println!("{size_msg}"); + + // now test for real + let mut buf = [0u8; SSHConfig::BUF_SIZE]; + let l = sshwire::write_ssh(&mut buf, &c1).expect(&size_msg); println!("BUF_SIZE must be at least {}", l); let v = &buf[..l]; - let c2: SSHConfig = sshwire::read_ssh(&buf, None).unwrap(); + let c2: SSHConfig = sshwire::read_ssh(v, None).unwrap(); assert_eq!(c1, c2); } } diff --git a/embassy/demos/picow/Cargo.toml b/embassy/demos/picow/Cargo.toml index a20b656..52b9fac 100644 --- a/embassy/demos/picow/Cargo.toml +++ b/embassy/demos/picow/Cargo.toml @@ -9,11 +9,13 @@ sunset = { path = "../../.." } sunset-sshwire-derive = { version = "0.1", path = "../../../sshwire-derive" } sunset-demo-embassy-common= { path = "../common" } -cyw43 = { version = "0.1.0", features = ["defmt"]} -cyw43-pio = "0.1.0" +cyw43 = { version = "0.1.0", optional = true, features = ["defmt"] } +cyw43-pio = { version = "0.1.0", optional = true } # cyw43 = { path = "/home/matt/3rd/rs/cyw43", features = ["defmt"] } # cyw43-pio = { path = "/home/matt/3rd/rs/cyw43/cyw43-pio" } +embassy-net-w5500 = { version = "0.1.0", optional = true } + embassy-executor = { version = "0.2", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m", "nightly"] } embassy-time = { version = "0.1", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } @@ -25,7 +27,7 @@ embassy-sync = { version = "0.2.0" } embassy-futures = { version = "0.1.0" } embassy-usb = { version = "0.1.0" } atomic-polyfill = "0.1.5" -static_cell = "1.0" +static_cell = { version = "1.0", features = [ "nightly" ] } defmt = { version = "0.3", optional = true } defmt-rtt = "0.3" @@ -60,13 +62,18 @@ sha2 = { version = "0.10", default-features = false } smoltcp = { version = "0.9", default-features = false } [features] -default = ["defmt", "sunset-demo-embassy-common/defmt", "embassy-usb/defmt"] +default = ["cyw43", "defmt", "sunset-demo-embassy-common/defmt" ] defmt = ["dep:defmt", "sunset/defmt", "sunset-embassy/defmt", "smoltcp/defmt"] +# for pico w board +cyw43 = ["dep:cyw43", "dep:cyw43-pio"] +# for wiznet w5500-evb-pico board +w5500 = ["dep:embassy-net-w5500"] + # Use cyw43 firmware already on flash. This saves time when developing. # probe-rs-cli download firmware/43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 # probe-rs-cli download firmware/43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 romfw = [] -# Default console is serial +# Set default console to serial serial1 = [] diff --git a/embassy/demos/picow/src/main.rs b/embassy/demos/picow/src/main.rs index 55515b5..16d292a 100644 --- a/embassy/demos/picow/src/main.rs +++ b/embassy/demos/picow/src/main.rs @@ -41,8 +41,14 @@ mod picowmenu; mod serial; mod takepipe; mod usbserial; +#[cfg(feature = "w5500")] +mod w5500; +#[cfg(feature = "cyw43")] mod wifi; +#[cfg(not(any(feature = "cyw43", feature = "w5500")))] +compile_error!("No network device selected. Use cyw43 or w5500 feature"); + use demo_common::{SSHConfig, Shell}; use takepipe::TakePipe; @@ -53,7 +59,7 @@ pub(crate) const NUM_SOCKETS: usize = NUM_LISTENERS + 1; #[embassy_executor::main] async fn main(spawner: Spawner) { - info!("Hello World!"); + info!("Welcome to Sunset SSH"); let mut p = embassy_rp::init(Default::default()); @@ -72,20 +78,6 @@ async fn main(spawner: Spawner) { let config = &*singleton!(SunsetMutex::new(config)); - let (wifi_net, wifi_pw) = { - let c = config.lock().await; - (c.wifi_net.clone(), c.wifi_pw.clone()) - }; - // spawn the wifi stack - let (stack, wifi_control) = wifi::wifi_stack( - &spawner, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.DMA_CH0, p.PIO0, - wifi_net, wifi_pw, - ) - .await; - let stack = &*singleton!(stack); - let wifi_control = singleton!(SunsetMutex::new(wifi_control)); - spawner.spawn(net_task(&stack)).unwrap(); - let usb_pipe = { let p = singleton!(takepipe::TakePipeStorage::new()); singleton!(p.pipe()) @@ -110,27 +102,44 @@ async fn main(spawner: Spawner) { embassy_rp::watchdog::Watchdog::new(p.WATCHDOG) )); - let state = GlobalState { - usb_pipe, - serial1_pipe, - - _wifi_control: wifi_control, - config, - flash, - watchdog, - }; + let state = GlobalState { usb_pipe, serial1_pipe, config, flash, watchdog }; let state = singleton!(state); spawner.spawn(usbserial::task(p.USB, state)).unwrap(); - for _ in 0..NUM_LISTENERS { - spawner.spawn(listener(&stack, config, state)).unwrap(); + // spawn the wifi stack + #[cfg(feature = "cyw43")] + { + let stack = wifi::wifi_stack( + &spawner, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.DMA_CH0, p.PIO0, + config, + ) + .await; + + for _ in 0..NUM_LISTENERS { + spawner.spawn(cyw43_listener(&stack, config, state)).unwrap(); + } + } + + // spawn the ethernet stack + #[cfg(feature = "w5500")] + { + let stack = w5500::w5500_stack( + &spawner, p.PIN_16, p.PIN_17, p.PIN_18, p.PIN_19, p.PIN_20, p.PIN_21, + p.DMA_CH0, p.DMA_CH1, p.SPI0, config, + ) + .await; + + for _ in 0..NUM_LISTENERS { + spawner.spawn(w5500_listener(&stack, config, state)).unwrap(); + } } } // TODO: pool_size should be NUM_LISTENERS but needs a literal +#[cfg(feature = "cyw43")] #[embassy_executor::task(pool_size = 4)] -async fn listener( +async fn cyw43_listener( stack: &'static Stack>, config: &'static SunsetMutex, global: &'static GlobalState, @@ -138,12 +147,21 @@ async fn listener( demo_common::listener::<_, DemoShell>(stack, config, global).await } +#[cfg(feature = "w5500")] +#[embassy_executor::task(pool_size = 4)] +async fn w5500_listener( + stack: &'static Stack>, + config: &'static SunsetMutex, + global: &'static GlobalState, +) -> ! { + demo_common::listener::<_, DemoShell>(stack, config, global).await +} + pub(crate) struct GlobalState { // If taking multiple mutexes, lock in the order below avoid inversion. pub usb_pipe: &'static TakePipe<'static>, pub serial1_pipe: &'static TakePipe<'static>, - pub _wifi_control: &'static SunsetMutex>, pub config: &'static SunsetMutex, pub flash: &'static SunsetMutex< embassy_rp::flash::Flash<'static, FLASH, { flashconfig::FLASH_SIZE }>, @@ -312,8 +330,3 @@ impl Shell for DemoShell { session.await } } - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await -} diff --git a/embassy/demos/picow/src/picowmenu.rs b/embassy/demos/picow/src/picowmenu.rs index 5ac58f0..f54296a 100644 --- a/embassy/demos/picow/src/picowmenu.rs +++ b/embassy/demos/picow/src/picowmenu.rs @@ -174,6 +174,7 @@ pub(crate) const SETUP_MENU: Menu = Menu { // &GPIO_ITEM, &SERIAL_ITEM, &WIFI_ITEM, + &NET_ITEM, &Item { command: "reset", help: Some("Reset picow. Will log out."), @@ -390,6 +391,26 @@ const WIFI_ITEM: Item = Item { help: None, }; +const NET_ITEM: Item = Item { + command: "net", + item_type: ItemType::Menu(&Menu { + label: "net", + items: &[ + &Item { + command: "info", + item_type: ItemType::Callback { + parameters: &[], + function: do_net_info, + }, + help: None, + }, + ], + entry: None, + exit: None, + }), + help: None, +}; + // const _GPIO_ITEM: Item = Item { // command: "gpio", // item_type: ItemType::Menu(&Menu { @@ -669,6 +690,13 @@ fn do_wifi_open(_item: &Item, args: &[&str], context: &mut MenuCtx) { wifi_entry(context); } +fn do_net_info(_item: &Item, _args: &[&str], context: &mut MenuCtx) { + context.with_config(|c, out| { + let _ = write!(out, "wired mac {:x?} ", c.mac); + }); +} + + // Returns an error on EOF etc. pub(crate) async fn request_pw( tx: &mut impl asynch::Write, diff --git a/embassy/demos/picow/src/w5500.rs b/embassy/demos/picow/src/w5500.rs new file mode 100644 index 0000000..dc9600d --- /dev/null +++ b/embassy/demos/picow/src/w5500.rs @@ -0,0 +1,95 @@ +// Modified from https://github.com/embassy-rs/cyw43/ +// Copyright (c) 2019-2022 Embassy project contributors +// MIT or Apache-2.0 license + +#[allow(unused_imports)] +#[cfg(not(feature = "defmt"))] +pub use log::{debug, error, info, log, trace, warn}; + +#[allow(unused_imports)] +#[cfg(feature = "defmt")] +pub use defmt::{debug, error, info, panic, trace, warn}; + +use embassy_executor::Spawner; +use embassy_net::{Stack, StackResources}; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::*; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; + +use embassy_net_w5500::*; + +use static_cell::make_static; + +use rand::rngs::OsRng; +use rand::RngCore; + +use crate::{SSHConfig, SunsetMutex}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +pub(crate) async fn w5500_stack( + spawner: &Spawner, + p16: PIN_16, + p17: PIN_17, + p18: PIN_18, + p19: PIN_19, + p20: PIN_20, + p21: PIN_21, + dma0: DMA_CH0, + dma1: DMA_CH1, + spi0: SPI0, + config: &'static SunsetMutex, +) -> &'static embassy_net::Stack> { + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p16, p19, p18); + let spi = Spi::new(spi0, clk, mosi, miso, dma0, dma1, spi_cfg); + let cs = Output::new(p17, Level::High); + let w5500_int = Input::new(p21, Pull::Up); + let w5500_reset = Output::new(p20, Level::High); + + let mac_addr = config.lock().await.mac; + // + let state = make_static!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + spawner.spawn(ethernet_task(runner)).unwrap(); + + // Generate random seed + let seed = OsRng.next_u64(); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + make_static!(StackResources::<{ crate::NUM_SOCKETS }>::new()), + seed + )); + + // Launch network task + spawner.spawn(net_task(&stack)).unwrap(); + + stack +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} diff --git a/embassy/demos/picow/src/wifi.rs b/embassy/demos/picow/src/wifi.rs index 850a629..1a02d2e 100644 --- a/embassy/demos/picow/src/wifi.rs +++ b/embassy/demos/picow/src/wifi.rs @@ -1,4 +1,5 @@ -// Modified from https://github.com/embassy-rs/cyw43/ +// Modified from embassy +// examples/rp/src/bin/wifi_tcp_server.rs // Copyright (c) 2019-2022 Embassy project contributors // MIT or Apache-2.0 license @@ -21,12 +22,11 @@ use embassy_net::{Stack, StackResources}; use cyw43_pio::PioSpi; use static_cell::StaticCell; -use heapless::String; - use rand::rngs::OsRng; use rand::RngCore; use crate::demo_common::singleton; +use crate::{SunsetMutex, SSHConfig}; #[embassy_executor::task] async fn wifi_task( @@ -43,10 +43,10 @@ async fn wifi_task( pub(crate) async fn wifi_stack(spawner: &Spawner, p23: PIN_23, p24: PIN_24, p25: PIN_25, p29: PIN_29, dma: DMA_CH0, pio0: PIO0, - wifi_net: String<32>, wpa_password: Option>, - - ) -> (embassy_net::Stack>, cyw43::Control<'static>) + config: &'static SunsetMutex, + ) -> &'static embassy_net::Stack> { + // TODO: return `control` once it can do something useful let (fw, clm) = get_fw(); @@ -64,10 +64,15 @@ pub(crate) async fn wifi_stack(spawner: &Spawner, // control.set_power_management(cyw43::PowerManagementMode::None).await; // control.set_power_management(cyw43::PowerManagementMode::Performance).await; + let (wifi_net, wifi_pw) = { + let c = config.lock().await; + (c.wifi_net.clone(), c.wifi_pw.clone()) + }; + // TODO: this should move out of the critical path, run in the bg. // just return control before joining. for _ in 0..2 { - let status = if let Some(ref pw) = wpa_password { + let status = if let Some(ref pw) = wifi_pw { info!("wifi net {} wpa2", wifi_net); control.join_wpa2(&wifi_net, &pw).await } else { @@ -81,11 +86,6 @@ pub(crate) async fn wifi_stack(spawner: &Spawner, } } - // if let Err(e) = status { - // // wait forever - // let () = futures::future::pending().await; - // } - let config = embassy_net::Config::dhcpv4(Default::default()); let seed = OsRng.next_u64(); @@ -97,11 +97,20 @@ pub(crate) async fn wifi_stack(spawner: &Spawner, singleton!(StackResources::<{crate::NUM_SOCKETS}>::new()), seed ); - (stack, control) + + let stack = &*singleton!(stack); + spawner.spawn(net_task(&stack)).unwrap(); + + stack +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await } +// Get the WiFi firmware and Country Locale Matrix (CLM) blobs. fn get_fw() -> (&'static [u8], &'static [u8]) { - // Include the WiFi firmware and Country Locale Matrix (CLM) blobs. #[cfg(not(feature = "romfw"))] let (fw, clm) = ( include_bytes!("../firmware/43439A0.bin"),