diff --git a/async/rust-toolchain.toml b/async/rust-toolchain.toml
index 5f813cc7a05c100742c6799b30c74bf6fea9cae3..097836a7c9f1b6db58d7e65be3683f13e9510dab 100644
--- a/async/rust-toolchain.toml
+++ b/async/rust-toolchain.toml
@@ -1,9 +1,5 @@
-# Before upgrading check that everything is available on all tier1 targets here:
-# https://rust-lang.github.io/rustup-components-history
-
-# 2023-04-15 has ICE building picow demo
 [toolchain]
-channel = "nightly-2023-04-08"
+channel = "nightly-2023-05-14"
 components = [ "rust-src", "rustfmt" ]
 targets = [
     "thumbv6m-none-eabi",
diff --git a/embassy/demos/common/src/server.rs b/embassy/demos/common/src/server.rs
index 5c3d64f0e722795007d439c0437897140f35408f..1e25df941f74b02b4cc799067e4ebd103ea22e5e 100644
--- a/embassy/demos/common/src/server.rs
+++ b/embassy/demos/common/src/server.rs
@@ -18,6 +18,8 @@ use embassy_futures::join::join;
 
 use embedded_io::asynch;
 
+use heapless::String;
+
 use sunset::*;
 use sunset_embassy::SSHServer;
 
@@ -43,7 +45,9 @@ macro_rules! singleton {
 
 
 // common entry point
-pub async fn listener<D: Driver, S: Shell>(stack: &'static Stack<D>, config: &SSHConfig) -> ! {
+pub async fn listener<D: Driver, S: Shell>(stack: &'static Stack<D>,
+    config: &SSHConfig,
+    init: S::Init) -> ! {
     // TODO: buffer size?
     // Does it help to be larger than ethernet MTU?
     // Should TX and RX be symmetrical? Or larger TX might fill ethernet
@@ -62,7 +66,7 @@ pub async fn listener<D: Driver, S: Shell>(stack: &'static Stack<D>, config: &SS
             continue;
         }
 
-        let r = session::<S>(&mut socket, &config).await;
+        let r = session::<S>(&mut socket, &config, &init).await;
         if let Err(_e) = r {
             // warn!("Ended with error: {:?}", e);
             warn!("Ended with error");
@@ -71,12 +75,13 @@ pub async fn listener<D: Driver, S: Shell>(stack: &'static Stack<D>, config: &SS
 }
 
 /// Run a SSH session when a socket accepts a connection
-async fn session<S: Shell>(socket: &mut TcpSocket<'_>, config: &SSHConfig) -> sunset::Result<()> {
+async fn session<S: Shell>(socket: &mut TcpSocket<'_>, config: &SSHConfig,
+    init: &S::Init) -> sunset::Result<()> {
     // OK unwrap: has been accepted
     let src = socket.remote_endpoint().unwrap();
     info!("Connection from {}:{}", src.addr, src.port);
 
-    let shell = S::default();
+    let shell = S::new(init);
 
     let app = DemoServer::new(&shell, config)?;
     let app = Mutex::<NoopRawMutex, _>::new(app);
@@ -129,8 +134,9 @@ impl<'a, S: Shell> ServBehaviour for DemoServer<'a, S> {
         Ok(&self.hostkeys)
     }
 
-    fn auth_unchallenged(&mut self, username: TextString) -> bool {
+    async fn auth_unchallenged(&mut self, username: TextString<'_>) -> bool {
         info!("Allowing auth for user {}", username.as_str().unwrap_or("bad"));
+        self.shell.authed(username.as_str().unwrap_or("")).await;
         true
     }
 
@@ -167,10 +173,14 @@ impl<'a, S: Shell> ServBehaviour for DemoServer<'a, S> {
     }
 }
 
-pub trait Shell : Default {
+pub trait Shell {
+    type Init;
+
+    fn new(init: &Self::Init) -> Self;
+
     #[allow(unused_variables)]
     // TODO: eventually the compiler should add must_use automatically?
-    fn authed(&self, handle: ChanHandle) {
+    async fn authed(&self, username: &str) {
         info!("Authenticated")
     }
 
diff --git a/embassy/demos/picow/Cargo.lock b/embassy/demos/picow/Cargo.lock
index 6b497bd21937a10a91ca4e5abe734c6af2657de8..ada84716bc5f943de4d951b0f4e189406fd8f187 100644
--- a/embassy/demos/picow/Cargo.lock
+++ b/embassy/demos/picow/Cargo.lock
@@ -315,7 +315,7 @@ dependencies = [
 [[package]]
 name = "cyw43"
 version = "0.1.0"
-source = "git+https://github.com/embassy-rs/cyw43/?rev=c19de2984751ba6fa2972ee66cfa2a6310d5f0c1#c19de2984751ba6fa2972ee66cfa2a6310d5f0c1"
+source = "git+https://github.com/embassy-rs/cyw43/?rev=6b5d9642d583bc034ee35b88c903545e7f423b4e#6b5d9642d583bc034ee35b88c903545e7f423b4e"
 dependencies = [
  "atomic-polyfill 0.1.11",
  "cortex-m",
@@ -333,10 +333,9 @@ dependencies = [
 [[package]]
 name = "cyw43-pio"
 version = "0.1.0"
-source = "git+https://github.com/embassy-rs/cyw43/?rev=c19de2984751ba6fa2972ee66cfa2a6310d5f0c1#c19de2984751ba6fa2972ee66cfa2a6310d5f0c1"
+source = "git+https://github.com/embassy-rs/cyw43/?rev=6b5d9642d583bc034ee35b88c903545e7f423b4e#6b5d9642d583bc034ee35b88c903545e7f423b4e"
 dependencies = [
  "cyw43",
- "defmt",
  "embassy-rp",
  "pio",
  "pio-proc",
@@ -692,6 +691,7 @@ name = "embassy-usb"
 version = "0.1.0"
 source = "git+https://github.com/embassy-rs/embassy?rev=3e730aa8b06401003202bf9e21a9c83ec6b21b0e#3e730aa8b06401003202bf9e21a9c83ec6b21b0e"
 dependencies = [
+ "defmt",
  "embassy-futures",
  "embassy-net-driver-channel",
  "embassy-sync 0.2.0 (git+https://github.com/embassy-rs/embassy?rev=3e730aa8b06401003202bf9e21a9c83ec6b21b0e)",
diff --git a/embassy/demos/picow/Cargo.toml b/embassy/demos/picow/Cargo.toml
index d42d89fda87c31af4132ed6a5557bff56c4510ad..f3656168dce0ccf19f6b995c1f58fa984bfb5515 100644
--- a/embassy/demos/picow/Cargo.toml
+++ b/embassy/demos/picow/Cargo.toml
@@ -12,9 +12,11 @@ sunset = { path = "../../.." }
 sunset-sshwire-derive = { version = "0.1", path = "../../../sshwire-derive" }
 sunset-demo-embassy-common= { path = "../common", features = ["defmt"] }
 
-cyw43 = { git = "https://github.com/embassy-rs/cyw43/", rev = "c19de2984751ba6fa2972ee66cfa2a6310d5f0c1", features = ["defmt"]}
-cyw43-pio = { git = "https://github.com/embassy-rs/cyw43/", rev = "c19de2984751ba6fa2972ee66cfa2a6310d5f0c1" }
-# cyw43 = { path = "/home/matt/3rd/rs/cyw43", features = ["defmt"]}
+cyw43 = { git = "https://github.com/embassy-rs/cyw43/", rev = "6b5d9642d583bc034ee35b88c903545e7f423b4e", features = ["defmt"]}
+cyw43-pio = { git = "https://github.com/embassy-rs/cyw43/", rev = "6b5d9642d583bc034ee35b88c903545e7f423b4e" }
+# cyw43 = { path = "/home/matt/3rd/rs/cyw43", features = ["defmt"] }
+# cyw43-pio = { path = "/home/matt/3rd/rs/cyw43/cyw43-pio" }
+
 embassy-executor = { version = "0.2",  features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] }
 embassy-time = { version = "0.1",  features = ["defmt", "defmt-timestamp-uptime"] }
 embassy-rp = { version = "0.1.0",  features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] }
@@ -51,7 +53,7 @@ rand = { version = "0.8", default-features = false, features = ["getrandom"] }
 sha2 = { version = "0.10", default-features = false }
 
 [features]
-default = ["defmt", "sunset-demo-embassy-common/defmt"]
+default = ["defmt", "sunset-demo-embassy-common/defmt", "embassy-usb/defmt"]
 defmt = []
 
 # Use cyw43 firmware already on flash. This saves time when developing.
diff --git a/embassy/demos/picow/rust-toolchain.toml b/embassy/demos/picow/rust-toolchain.toml
index 5f813cc7a05c100742c6799b30c74bf6fea9cae3..097836a7c9f1b6db58d7e65be3683f13e9510dab 100644
--- a/embassy/demos/picow/rust-toolchain.toml
+++ b/embassy/demos/picow/rust-toolchain.toml
@@ -1,9 +1,5 @@
-# Before upgrading check that everything is available on all tier1 targets here:
-# https://rust-lang.github.io/rustup-components-history
-
-# 2023-04-15 has ICE building picow demo
 [toolchain]
-channel = "nightly-2023-04-08"
+channel = "nightly-2023-05-14"
 components = [ "rust-src", "rustfmt" ]
 targets = [
     "thumbv6m-none-eabi",
diff --git a/embassy/demos/picow/src/flashconfig.rs b/embassy/demos/picow/src/flashconfig.rs
index 0b3828c7d167f3e60557ebdde4f9b17c956dc11d..5391ace9fea9886aec6949d7a01dd7d362cb16d4 100644
--- a/embassy/demos/picow/src/flashconfig.rs
+++ b/embassy/demos/picow/src/flashconfig.rs
@@ -51,6 +51,10 @@ pub fn load_or_create(flash: &mut Flash<'_, FLASH, FLASH_SIZE>) -> Result<SSHCon
         Err(c) => info!("Existing config bad, making new"),
     }
 
+    create(flash)
+}
+
+pub fn create(flash: &mut Flash<'_, FLASH, FLASH_SIZE>) -> Result<SSHConfig> {
     let c = SSHConfig::new()?;
     if let Err(e) = save(flash, &c) {
         warn!("Error writing config");
diff --git a/embassy/demos/picow/src/main.rs b/embassy/demos/picow/src/main.rs
index 9413ae3510db58640dfed04a779b998c0d6e40ed..b99d07704a8b23994aa350eba27fef504558ec75 100644
--- a/embassy/demos/picow/src/main.rs
+++ b/embassy/demos/picow/src/main.rs
@@ -4,10 +4,26 @@
 #![feature(async_fn_in_trait)]
 // #![allow(incomplete_features)]
 
+#[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, info, warn, panic, error, trace};
+
+use {defmt_rtt as _, panic_probe as _};
+
 use embassy_executor::Spawner;
 use embassy_net::Stack;
-use embassy_rp::pio::PioPeripheral;
-use {defmt_rtt as _, panic_probe as _};
+use embassy_futures::join::join;
+use embassy_rp::{pio::PioPeripheral, interrupt};
+use embedded_io::{asynch, Io};
+use embedded_io::asynch::Write;
+
+use heapless::{String, Vec};
 
 use static_cell::StaticCell;
 
@@ -17,8 +33,7 @@ use embassy_sync::signal::Signal;
 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
 
 use sunset::*;
-use sunset::prelude::*;
-use sunset_embassy::SSHServer;
+use sunset_embassy::{SSHServer, SunsetMutex};
 
 pub(crate) use sunset_demo_embassy_common as demo_common;
 use crate::demo_common::singleton;
@@ -26,10 +41,12 @@ use crate::demo_common::singleton;
 mod flashconfig;
 mod wifi;
 mod usbserial;
-mod switchpipe;
+mod takepipe;
 
 use demo_common::{SSHConfig, demo_menu, Shell};
 
+use takepipe::TakeBase;
+
 const NUM_LISTENERS: usize = 4;
 // +1 for dhcp. referenced directly by wifi_stack() function
 pub(crate) const NUM_SOCKETS: usize = NUM_LISTENERS+1;
@@ -45,80 +62,180 @@ async fn main(spawner: Spawner) {
 
     let mut flash = embassy_rp::flash::Flash::new(p.FLASH);
 
-    let config = flashconfig::load_or_create(&mut flash).unwrap();
+    let config = if option_env!("RESET_FLASH").is_some() {
+        flashconfig::create(&mut flash).unwrap()
+    } else {
+        flashconfig::load_or_create(&mut flash).unwrap()
+    };
 
     let ssh_config = &*singleton!(
         config
     );
 
+    let usb_pipe = singleton!(takepipe::TakePipe::new());
+    let usb_pipe = singleton!(usb_pipe.base());
+    let usb_irq = interrupt::take!(USBCTRL_IRQ);
+    spawner.spawn(usb_serial_task(p.USB, usb_irq, usb_pipe)).unwrap();
+
     let (_, sm, _, _, _) = p.PIO0.split();
     let wifi_net = ssh_config.wifi_net.as_str();
     let wifi_pw = ssh_config.wifi_pw.as_ref().map(|p| p.as_str());
-
     // spawn the wifi stack
-    let stack = wifi::wifi_stack(&spawner, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.DMA_CH0, sm,
+    let (stack, wifi_control) = wifi::wifi_stack(&spawner, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.DMA_CH0, sm,
         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 = switchpipe::SwitchPipe::new();
-    let usb_pipe = usb_pipe.base();
-    let r = usb_pipe.switch_read();
+    let init = DemoShellInit {
+        usb_pipe,
+        wifi_control,
+    };
+    let init = singleton!(init);
 
     for _ in 0..NUM_LISTENERS {
-        spawner.spawn(listener(&stack, &ssh_config)).unwrap();
+        spawner.spawn(listener(&stack, &ssh_config, init)).unwrap();
     }
 }
 
 // TODO: pool_size should be NUM_LISTENERS but needs a literal
 #[embassy_executor::task(pool_size = 4)]
-async fn listener(stack: &'static Stack<cyw43::NetDriver<'static>>, config: &'static SSHConfig) -> ! {
-    demo_common::listener::<_, DemoShell>(stack, config).await
+async fn listener(stack: &'static Stack<cyw43::NetDriver<'static>>,
+    config: &'static SSHConfig,
+    ctx: &'static DemoShellInit) -> ! {
+    demo_common::listener::<_, DemoShell>(stack, config, ctx).await
+}
+
+struct DemoShellInit {
+    usb_pipe: &'static TakeBase<'static>,
+    wifi_control: &'static SunsetMutex<cyw43::Control<'static>>,
 }
 
-#[derive(Default)]
 struct DemoShell {
     notify: Signal<NoopRawMutex, ChanHandle>,
+    ctx: &'static DemoShellInit,
+
+    // Mutex is a bit of a bodge
+    username: SunsetMutex<String<20>>,
+}
+
+impl DemoShell {
+    async fn menu<C>(&self, mut stdio: C) -> Result<()>
+        where C: asynch::Read + asynch::Write + Io<Error=sunset::Error> {
+        let mut menu_buf = [0u8; 64];
+        let menu_out = demo_menu::BufOutput::default();
+
+        let mut menu = MenuRunner::new(&demo_menu::ROOT_MENU, &mut menu_buf, menu_out);
+
+        // bodge
+        for c in "help\r\n".bytes() {
+            menu.input_byte(c);
+        }
+        menu.context.flush(&mut stdio).await?;
+
+        loop {
+            let mut b = [0u8; 20];
+            let lr = stdio.read(&mut b).await?;
+            if lr == 0 {
+                break
+            }
+            let b = &mut b[..lr];
+            for c in b.iter() {
+                menu.input_byte(*c);
+                menu.context.flush(&mut stdio).await?;
+            }
+        }
+        Ok(())
+    }
+
+    async fn serial<C>(&self, mut stdio: C) -> Result<()>
+        where C: asynch::Read+asynch::Write+Clone+Io<Error=sunset::Error> {
+
+        info!("serial top");
+
+
+        let mut s2 = stdio.clone();
+
+        info!("take await");
+        let (mut rx, mut tx) = self.ctx.usb_pipe.take().await;
+        info!("take done");
+        let r = async {
+            let mut b = [0u8; 64];
+            let mut btrans = Vec::<u8, 128>::new();
+            loop {
+                let n = rx.read(&mut b).await?;
+                let b = &mut b[..n];
+                btrans.clear();
+                for c in b {
+                    if *c == b'\n' {
+                        // OK unwrap: btrans.len() = 2*b.len()
+                        btrans.push(b'\r').unwrap();
+                    }
+                    btrans.push(*c).unwrap();
+                }
+                s2.write_all(&btrans).await?;
+            }
+            #[allow(unreachable_code)]
+            Ok::<(), sunset::Error>(())
+        };
+        let w = async {
+            let mut b = [0u8; 64];
+            loop {
+                let n = stdio.read(&mut b).await?;
+                if n == 0 {
+                    return Err(sunset::Error::ChannelEOF);
+                }
+                let b = &mut b[..n];
+                for c in b.iter_mut() {
+                    // input translate CR to LF
+                    if *c == b'\r' {
+                        *c = b'\n';
+                    }
+                }
+                tx.write_all(b).await?;
+            }
+            #[allow(unreachable_code)]
+            Ok::<(), sunset::Error>(())
+        };
+
+        join(r, w).await;
+        info!("serial task completed");
+        Ok(())
+    }
 }
 
 impl Shell for DemoShell {
+    type Init = &'static DemoShellInit;
+
+    fn new(ctx: &Self::Init) -> Self {
+        Self {
+            notify: Default::default(),
+            ctx,
+            username: SunsetMutex::new(String::new()),
+        }
+    }
+
     fn open_shell(&self, handle: ChanHandle) {
         self.notify.signal(handle);
     }
 
+    async fn authed(&self, username: &str) {
+        let mut u = self.username.lock().await;
+        *u = username.try_into().unwrap_or(String::new());
+    }
+
     async fn run<'f, S: ServBehaviour>(&self, serv: &'f SSHServer<'f, S>) -> Result<()>
     {
         let session = async {
             // wait for a shell to start
             let chan_handle = self.notify.wait().await;
-            trace!("got handle");
-
-            let mut stdio = serv.stdio(chan_handle).await?;
-
-            let mut menu_buf = [0u8; 64];
-            let menu_out = demo_menu::BufOutput::default();
-
-            let mut menu = MenuRunner::new(&demo_menu::ROOT_MENU, &mut menu_buf, menu_out);
+            let stdio = serv.stdio(chan_handle).await?;
 
-            // bodge
-            for c in "help\r\n".bytes() {
-                menu.input_byte(c);
-            }
-            menu.context.flush(&mut stdio).await?;
-
-            loop {
-                let mut b = [0u8; 20];
-                let lr = stdio.read(&mut b).await?;
-                if lr == 0 {
-                    break
-                }
-                let b = &mut b[..lr];
-                for c in b.iter() {
-                    menu.input_byte(*c);
-                    menu.context.flush(&mut stdio).await?;
-                }
+            if *self.username.lock().await == "serial" {
+                self.serial(stdio).await
+            } else {
+                self.menu(stdio).await
             }
-            Ok(())
         };
 
         session.await
@@ -133,11 +250,13 @@ async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! {
 #[embassy_executor::task]
 async fn usb_serial_task(usb: embassy_rp::peripherals::USB,
     irq: embassy_rp::interrupt::USBCTRL_IRQ,
+    pipe: &'static TakeBase<'static>,
     ) -> ! {
 
-    // let mut p = embassy_sync::pipe::Pipe(64);
+    info!("usb serial");
+    let (mut rx, mut tx) = pipe.split();
 
-    // usbserial.usb_serial(usb, irq, tx, rx).await;
+    usbserial::usb_serial(usb, irq, &mut tx, &mut rx).await;
     todo!("shoudln't exit");
 }
 
diff --git a/embassy/demos/picow/src/takepipe.rs b/embassy/demos/picow/src/takepipe.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9be610b3a3efad3fdd5b2edbc19d4b3da5cee82c
--- /dev/null
+++ b/embassy/demos/picow/src/takepipe.rs
@@ -0,0 +1,177 @@
+use embedded_io::{asynch, Io};
+
+use embassy_sync::{pipe, mutex::{MutexGuard, Mutex}, signal::Signal};
+use embassy_sync::pipe::Pipe;
+use embassy_futures::select::{select, Either};
+
+use sunset_embassy::{SunsetMutex, SunsetRawMutex};
+
+const SIZE: usize = 64;
+
+/// Allows a bidirectional pipe to be shared by many endpoints
+///
+/// One end of the pipe is fixed (attached to eg a physical/virtual
+/// uart), used with `.split()`.
+///
+/// The other end can be used by many clients, one at a time.
+/// When a subsequent client takes the pipe (with `.take()`), the existing
+/// client loses the pipe and gets EOF.
+///
+/// It works a bit like `screen -r -d`.
+pub(crate) struct TakePipe {
+	fanout: Pipe<SunsetRawMutex, SIZE>,
+    fanin: Pipe<SunsetRawMutex, SIZE>,
+    wake: Signal<SunsetRawMutex, ()>,
+}
+
+impl TakePipe {
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    pub fn base(&self) -> TakeBase {
+        TakeBase {
+            shared_read: Mutex::new(self.fanout.reader()),
+            shared_write: Mutex::new(self.fanin.writer()),
+            pipe: self,
+        }
+    }
+}
+
+impl Default for TakePipe {
+    fn default() -> Self {
+        Self {
+            fanout: Pipe::new(),
+            fanin: Pipe::new(),
+            wake: Signal::new(),
+        }
+    }
+}
+
+pub(crate) struct TakeBase<'a> {
+    shared_read: Mutex<SunsetRawMutex, pipe::Reader<'a, SunsetRawMutex, SIZE>>,
+    shared_write: Mutex<SunsetRawMutex, pipe::Writer<'a, SunsetRawMutex, SIZE>>,
+    pipe: &'a TakePipe,
+}
+
+impl<'a> TakeBase<'a> {
+    pub async fn take(&'a self) -> (TakeRead<'a>, TakeWrite<'a>) {
+        self.pipe.wake.signal(());
+        let r = self.shared_read.lock().await;
+        let w = self.shared_write.lock().await;
+        // We could .clear() the pipes, but
+        // that wouldn't deal with data that has already progressed
+        // further along out the SSH channel etc. So we leave that
+        // for high levels to deal with if needed.
+        self.pipe.wake.reset();
+
+        let r = TakeRead {
+            pipe: self.pipe,
+            shared: Some(r),
+        };
+        let w = TakeWrite {
+            pipe: self.pipe,
+            shared: Some(w),
+        };
+        (r, w)
+    }
+
+    pub fn split(&'a self) -> (TakeBaseRead<'a>, TakeBaseWrite<'a>) {
+        let r = TakeBaseRead {
+            pipe: self.pipe,
+        };
+        let w = TakeBaseWrite {
+            pipe: self.pipe,
+        };
+        (r, w)
+    }
+}
+
+pub(crate) struct TakeBaseRead<'a> {
+    pipe: &'a TakePipe,
+}
+
+pub(crate) struct TakeBaseWrite<'a> {
+    pipe: &'a TakePipe,
+}
+
+impl<'a> asynch::Read for TakeBaseRead<'a> {
+    async fn read(&mut self, buf: &mut [u8]) -> sunset::Result<usize> {
+        Ok(self.pipe.fanin.read(buf).await)
+    }
+}
+
+impl<'a> asynch::Write for TakeBaseWrite<'a> {
+    async fn write(&mut self, buf: &[u8]) -> sunset::Result<usize> {
+        Ok(self.pipe.fanout.write(buf).await)
+    }
+}
+
+impl Io for TakeBaseRead<'_> {
+    type Error = sunset::Error;
+}
+
+impl Io for TakeBaseWrite<'_> {
+    type Error = sunset::Error;
+}
+
+pub(crate) struct TakeRead<'a> {
+    pipe: &'a TakePipe,
+    shared: Option<MutexGuard<'a, SunsetRawMutex, pipe::Reader<'a, SunsetRawMutex, SIZE>>>,
+}
+
+impl asynch::Read for TakeRead<'_> {
+
+    async fn read(&mut self, buf: &mut [u8]) -> sunset::Result<usize> {
+        let p = self.shared.as_ref().ok_or(sunset::Error::ChannelEOF)?;
+
+        let r = select(
+            p.read(buf),
+            self.pipe.wake.wait(),
+        );
+
+        match r.await {
+            // read completed
+            Either::First(l) => Ok(l),
+            // lost the pipe
+            Either::Second(l) => {
+                self.shared = None;
+                Err(sunset::Error::ChannelEOF)
+            }
+        }
+    }
+}
+
+impl Io for TakeRead<'_> {
+    type Error = sunset::Error;
+}
+
+pub(crate) struct TakeWrite<'a> {
+    pipe: &'a TakePipe,
+    shared: Option<MutexGuard<'a, SunsetRawMutex, pipe::Writer<'a, SunsetRawMutex, SIZE>>>,
+}
+
+impl asynch::Write for TakeWrite<'_> {
+    async fn write(&mut self, buf: &[u8]) -> sunset::Result<usize> {
+        let p = self.shared.as_ref().ok_or(sunset::Error::ChannelEOF)?;
+
+        let r = select(
+            p.write(buf),
+            self.pipe.wake.wait(),
+        );
+
+        match r.await {
+            // write completed
+            Either::First(l) => Ok(l),
+            // lost the pipe
+            Either::Second(l) => {
+                self.shared = None;
+                Err(sunset::Error::ChannelEOF)
+            }
+        }
+    }
+}
+
+impl Io for TakeWrite<'_> {
+    type Error = sunset::Error;
+}
diff --git a/embassy/demos/picow/src/usbserial.rs b/embassy/demos/picow/src/usbserial.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91d111d537b1e2b306a263ef93ac8f099d4a822d
--- /dev/null
+++ b/embassy/demos/picow/src/usbserial.rs
@@ -0,0 +1,112 @@
+
+#[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, info, warn, panic, error, trace};
+
+use embassy_usb::{Builder};
+use embassy_rp::usb::Instance;
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
+use embassy_futures::join::join;
+
+use embedded_io::asynch;
+
+use sunset::*;
+
+pub async fn usb_serial(usb: embassy_rp::peripherals::USB,
+    irq: embassy_rp::interrupt::USBCTRL_IRQ,
+    tx: &mut impl asynch::Write,
+    rx: &mut impl asynch::Read,
+    ) {
+
+    info!("usb_serial top");
+
+    let driver = embassy_rp::usb::Driver::new(usb, irq);
+
+    let mut config = embassy_usb::Config::new(0xf055, 0x6053);
+    config.manufacturer = Some("Sunset SSH");
+    config.product = Some("picow demo");
+    config.serial_number = Some("4");
+    config.max_power = 100;
+    config.max_packet_size_0 = 64;
+
+    // Required for windows 7 compatiblity.
+    // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+    config.device_class = 0xEF;
+    config.device_sub_class = 0x02;
+    config.device_protocol = 0x01;
+    config.composite_with_iads = true;
+
+    // Create embassy-usb DeviceBuilder using the driver and config.
+    // It needs some buffers for building the descriptors.
+    let mut device_descriptor = [0; 256];
+    let mut config_descriptor = [0; 256];
+    let mut bos_descriptor = [0; 256];
+    let mut control_buf = [0; 64];
+
+    let mut state = State::new();
+
+    let mut builder = Builder::new(
+        driver,
+        config,
+        &mut device_descriptor,
+        &mut config_descriptor,
+        &mut bos_descriptor,
+        &mut control_buf,
+    );
+
+    let cdc = CdcAcmClass::new(&mut builder, &mut state, 64);
+    let (mut cdc_tx, mut cdc_rx) = cdc.split();
+
+    let mut usb = builder.build();
+
+    // Run the USB device.
+    let usb_fut = usb.run();
+
+    struct IoDone;
+
+    let io = async {
+        loop {
+            info!("usb waiting");
+            cdc_rx.wait_connection().await;
+            info!("Connected");
+
+            let io_tx = async {
+                let mut b = [0u8; 64];
+                loop {
+                    let n = cdc_rx.read_packet(&mut b).await .map_err(|_| IoDone)?;
+                    let b = &b[..n];
+                    tx.write_all(b).await.map_err(|_| IoDone)?;
+                }
+                #[allow(unreachable_code)]
+                Ok::<_, IoDone>(())
+            };
+
+            let io_rx = async {
+                let mut b = [0u8; 64];
+                loop {
+                    let n = rx.read(&mut b).await.map_err(|_| IoDone)?;
+                    if n == 0 {
+                        return Err(IoDone);
+                    }
+                    let b = &b[..n];
+                    cdc_tx.write_packet(b).await.map_err(|_| IoDone)?;
+                }
+                #[allow(unreachable_code)]
+                Ok::<_, IoDone>(())
+            };
+
+            join(io_rx, io_tx).await;
+            info!("Disconnected");
+        }
+    };
+
+    info!("usb join");
+    join(usb_fut, io).await;
+
+}
diff --git a/embassy/demos/picow/src/wifi.rs b/embassy/demos/picow/src/wifi.rs
index e01c53739907c710dd44a247b81ce3f2674454cf..180bab7837e644912f97f7d1cc1bcba5a79c910c 100644
--- a/embassy/demos/picow/src/wifi.rs
+++ b/embassy/demos/picow/src/wifi.rs
@@ -2,6 +2,16 @@
 // 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, info, warn, panic, error, trace};
+
 use embassy_rp::gpio::{Level, Output};
 use embassy_rp::pio::{PioStateMachineInstance, Sm0, Pio0};
 use embassy_rp::peripherals::*;
@@ -33,7 +43,7 @@ pub(crate) async fn wifi_stack(spawner: &Spawner,
     p23: PIN_23, p24: PIN_24, p25: PIN_25, p29: PIN_29, dma: DMA_CH0,
     sm: PioStateMachineInstance<Pio0, Sm0>,
     wifi_net: &str, wpa_password: Option<&str>,
-    ) -> embassy_net::Stack<cyw43::NetDriver<'static>>
+    ) -> (embassy_net::Stack<cyw43::NetDriver<'static>>, cyw43::Control<'static>)
     {
 
     let (fw, clm) = get_fw();
@@ -47,12 +57,15 @@ pub(crate) async fn wifi_stack(spawner: &Spawner,
     spawner.spawn(wifi_task(runner)).unwrap();
 
     control.init(clm).await;
-    // the default is PowerSave
-    control.set_power_management(cyw43::PowerManagementMode::Performance).await;
+    // the default is PowerSave. None is fastest.
+    // control.set_power_management(cyw43::PowerManagementMode::None).await;
+    // control.set_power_management(cyw43::PowerManagementMode::Performance).await;
 
     if let Some(pw) = wpa_password {
+        info!("wifi net {} pw {}", wifi_net, pw);
         control.join_wpa2(wifi_net, pw).await;
     } else {
+        info!("wifi net {} open", wifi_net);
         control.join_open(wifi_net).await;
     }
 
@@ -61,12 +74,13 @@ pub(crate) async fn wifi_stack(spawner: &Spawner,
     let seed = OsRng.next_u64();
 
     // Init network stack
-    Stack::new(
+    let stack = Stack::new(
         net_device,
         config,
         singleton!(StackResources::<{crate::NUM_SOCKETS}>::new()),
         seed
-    )
+    );
+    (stack, control)
 }
 
 fn get_fw() -> (&'static [u8], &'static [u8]) {
diff --git a/embassy/demos/std/Cargo.lock b/embassy/demos/std/Cargo.lock
index cbbcd4053855eca2396b71dd8bf117e80314724d..af0c5f15edf394e569915291afe16d1471c89263 100644
--- a/embassy/demos/std/Cargo.lock
+++ b/embassy/demos/std/Cargo.lock
@@ -766,17 +766,6 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae"
 
-[[package]]
-name = "no-panic"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f3ff7cb37ce690ee3b1bab16e77f6eae6320d91a9770ec1497f58a88c1f35c9"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "no-std-net"
 version = "0.5.0"
@@ -1066,7 +1055,6 @@ dependencies = [
  "heapless",
  "hmac",
  "log",
- "no-panic",
  "poly1305",
  "pretty-hex",
  "rand_core",
diff --git a/embassy/demos/std/rust-toolchain.toml b/embassy/demos/std/rust-toolchain.toml
index 5f813cc7a05c100742c6799b30c74bf6fea9cae3..097836a7c9f1b6db58d7e65be3683f13e9510dab 100644
--- a/embassy/demos/std/rust-toolchain.toml
+++ b/embassy/demos/std/rust-toolchain.toml
@@ -1,9 +1,5 @@
-# Before upgrading check that everything is available on all tier1 targets here:
-# https://rust-lang.github.io/rustup-components-history
-
-# 2023-04-15 has ICE building picow demo
 [toolchain]
-channel = "nightly-2023-04-08"
+channel = "nightly-2023-05-14"
 components = [ "rust-src", "rustfmt" ]
 targets = [
     "thumbv6m-none-eabi",
diff --git a/embassy/demos/std/src/main.rs b/embassy/demos/std/src/main.rs
index 43a5f5f882d264307b3eddc46bfa34b0c81c78ed..4cd28b45d30f4153a650e5701fa841e2cc5a0ad4 100644
--- a/embassy/demos/std/src/main.rs
+++ b/embassy/demos/std/src/main.rs
@@ -76,6 +76,12 @@ struct DemoShell {
 }
 
 impl Shell for DemoShell {
+    type Init = ();
+
+    fn new(init: &Self::Init) -> Self {
+        Default::default()
+    }
+
     fn open_shell(&self, handle: ChanHandle) {
         self.notify.signal(handle);
     }
@@ -123,7 +129,7 @@ impl Shell for DemoShell {
 #[embassy_executor::task(pool_size = 4)]
 async fn listener(stack: &'static Stack<TunTapDevice>, config: &'static SSHConfig) -> ! {
 
-    demo_common::listener::<_, DemoShell>(stack, config).await
+    demo_common::listener::<_, DemoShell>(stack, config, ()).await
 }
 
 
diff --git a/embassy/rust-toolchain.toml b/embassy/rust-toolchain.toml
index 5f813cc7a05c100742c6799b30c74bf6fea9cae3..097836a7c9f1b6db58d7e65be3683f13e9510dab 100644
--- a/embassy/rust-toolchain.toml
+++ b/embassy/rust-toolchain.toml
@@ -1,9 +1,5 @@
-# Before upgrading check that everything is available on all tier1 targets here:
-# https://rust-lang.github.io/rustup-components-history
-
-# 2023-04-15 has ICE building picow demo
 [toolchain]
-channel = "nightly-2023-04-08"
+channel = "nightly-2023-05-14"
 components = [ "rust-src", "rustfmt" ]
 targets = [
     "thumbv6m-none-eabi",