diff --git a/embassy/demos/picow/Cargo.toml b/embassy/demos/picow/Cargo.toml
index 18ad37f2c10a8ce009eae7cfc6799e264e0ab620..5786c99add007815c546b9d38f09378d8ee1dddf 100644
--- a/embassy/demos/picow/Cargo.toml
+++ b/embassy/demos/picow/Cargo.toml
@@ -76,6 +76,8 @@ embassy-usb-driver = { git = "https://github.com/embassy-rs/embassy", rev = "3e7
 embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "3e730aa8b06401003202bf9e21a9c83ec6b21b0e" }
 embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "3e730aa8b06401003202bf9e21a9c83ec6b21b0e" }
 
+# embedded-io = { path = "/home/matt/3rd/rs/embedded-io" }
+
 [profile.dev]
 debug = 2
 debug-assertions = true
diff --git a/embassy/demos/picow/src/main.rs b/embassy/demos/picow/src/main.rs
index 968b8f99cc114f4efeb8d41b513cb727a93b697e..c888403cb554cc8ce26ba20e8109d7817cb67451 100644
--- a/embassy/demos/picow/src/main.rs
+++ b/embassy/demos/picow/src/main.rs
@@ -16,6 +16,9 @@ pub use defmt::{debug, info, warn, panic, error, trace};
 
 use {defmt_rtt as _, panic_probe as _};
 
+use core::fmt::Write as _;
+
+
 use embassy_executor::Spawner;
 use embassy_net::Stack;
 use embassy_futures::join::join;
@@ -23,7 +26,7 @@ use embassy_futures::select::select;
 use embassy_rp::{pio::PioPeripheral, interrupt};
 use embassy_rp::peripherals::FLASH;
 use embedded_io::{asynch, Io};
-use embedded_io::asynch::Write;
+use embedded_io::asynch::Write as _;
 
 use heapless::{String, Vec};
 
@@ -79,10 +82,6 @@ async fn main(spawner: Spawner) {
         SunsetMutex::new(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 (wifi_net, wifi_pw) = {
         let c = config.lock().await;
@@ -96,6 +95,9 @@ async fn main(spawner: Spawner) {
     let wifi_control = singleton!(SunsetMutex::new(wifi_control));
     spawner.spawn(net_task(&stack)).unwrap();
 
+    let usb_pipe = singleton!(takepipe::TakePipe::new());
+    let usb_pipe = singleton!(usb_pipe.base());
+
     let state = GlobalState {
         usb_pipe,
         wifi_control,
@@ -104,6 +106,9 @@ async fn main(spawner: Spawner) {
     };
     let state = singleton!(state);
 
+    let usb_irq = interrupt::take!(USBCTRL_IRQ);
+    spawner.spawn(usb_serial_task(p.USB, usb_irq, state)).unwrap();
+
     for _ in 0..NUM_LISTENERS {
         spawner.spawn(listener(&stack, config, state)).unwrap();
     }
@@ -135,7 +140,9 @@ struct DemoShell {
     username: SunsetMutex<String<20>>,
 }
 
+// `local` is set for usb serial menus which require different auth
 async fn menu<R, W>(mut chanr: R, mut chanw: W,
+    local: bool,
     state: &'static GlobalState) -> Result<()>
     where R: asynch::Read+Io<Error=sunset::Error>,
         W: asynch::Write+Io<Error=sunset::Error> {
@@ -163,9 +170,14 @@ async fn menu<R, W>(mut chanr: R, mut chanw: W,
 
             // TODO: move this to a function or something
             if menu.context.switch_usb1 {
-                serial(chanr, chanw, state).await?;
-                // TODO we could return to the menu on serial error?
-                break 'io;
+                menu.context.switch_usb1 = false;
+                if local {
+                    writeln!(menu.context.out, "serial can't loop");
+                } else {
+                    serial(chanr, chanw, state).await?;
+                    // TODO we could return to the menu on serial error?
+                    break 'io;
+                }
             }
 
             if menu.context.need_save {
@@ -178,6 +190,8 @@ async fn menu<R, W>(mut chanr: R, mut chanw: W,
                     warn!("Error writing flash");
                 }
             }
+
+            menu.context.out.flush(&mut chanw).await?;
         }
     }
     Ok(())
@@ -264,7 +278,7 @@ impl Shell for DemoShell {
             if *self.username.lock().await == "serial" {
                 serial(stdio.clone(), stdio, self.ctx).await
             } else {
-                menu(stdio.clone(), stdio, self.ctx).await
+                menu(stdio.clone(), stdio, false, self.ctx).await
             }
         };
 
@@ -280,13 +294,10 @@ 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>,
+    global: &'static GlobalState,
     ) -> ! {
 
-    info!("usb serial");
-    let (mut rx, mut tx) = pipe.split();
-
-    usbserial::usb_serial(usb, irq, &mut tx, &mut rx).await;
+    usbserial::usb_serial(usb, irq, global).await;
     todo!("shoudln't exit");
 }
 
diff --git a/embassy/demos/picow/src/usbserial.rs b/embassy/demos/picow/src/usbserial.rs
index 272f582389979d4a1e85d1e083e8d1a5cd03f51c..5780265b9573eeaf3a57412dfda0892f9b722096 100644
--- a/embassy/demos/picow/src/usbserial.rs
+++ b/embassy/demos/picow/src/usbserial.rs
@@ -6,7 +6,7 @@ pub use log::{debug, error, info, log, trace, warn};
 #[cfg(feature = "defmt")]
 pub use defmt::{debug, error, info, panic, trace, warn};
 
-use embassy_futures::join::join;
+use embassy_futures::join::{join, join3};
 use embassy_rp::usb::Instance;
 use embassy_usb::class::cdc_acm::{self, CdcAcmClass, State};
 use embassy_usb::Builder;
@@ -17,16 +17,15 @@ use embedded_io::{asynch, Io, asynch::BufRead};
 use heapless::Vec;
 
 use sunset::*;
-use sunset_embassy::io_copy;
+use sunset_embassy::*;
 
-pub async fn usb_serial<R, W>(
+use crate::*;
+
+pub(crate) async fn usb_serial(
     usb: embassy_rp::peripherals::USB,
     irq: embassy_rp::interrupt::USBCTRL_IRQ,
-    tx: &mut W,
-    rx: &mut R,
+    global: &'static GlobalState,
 )
-    where R: asynch::Read+Io<Error=sunset::Error>,
-        W: asynch::Write+Io<Error=sunset::Error>
 {
     info!("usb_serial top");
 
@@ -53,7 +52,9 @@ pub async fn usb_serial<R, W>(
     let mut bos_descriptor = [0; 256];
     let mut control_buf = [0; 64];
 
-    let mut state = State::new();
+    // lives longer than builder
+    let mut usb_state0 = State::new();
+    let mut usb_state2 = State::new();
 
     let mut builder = Builder::new(
         driver,
@@ -64,34 +65,54 @@ pub async fn usb_serial<R, W>(
         &mut control_buf,
     );
 
-    let cdc = CdcAcmClass::new(&mut builder, &mut state, 64);
-    let (mut cdc_tx, mut cdc_rx) = cdc.split();
-    // let cdc_tx = &mut cdc_tx;
-    // let cdc_rx = &mut cdc_rx;
+    // if00
+    let cdc0 = CdcAcmClass::new(&mut builder, &mut usb_state0, 64);
+    let (mut cdc0_tx, mut cdc0_rx) = cdc0.split();
+    // if02
+    let cdc2 = CdcAcmClass::new(&mut builder, &mut usb_state2, 64);
+    let (mut cdc2_tx, mut cdc2_rx) = cdc2.split();
 
     let mut usb = builder.build();
 
+
     // Run the USB device.
     let usb_fut = usb.run();
 
-    let io = async {
+    let io0 = async {
+        let (mut chan_rx, mut chan_tx) = global.usb_pipe.split();
+        let chan_rx = &mut chan_rx;
+        let chan_tx = &mut chan_tx;
+        loop {
+            info!("usb waiting");
+            cdc0_rx.wait_connection().await;
+            info!("Connected");
+            let mut cdc0_tx = CDCWrite::new(&mut cdc0_tx);
+            let mut cdc0_rx = CDCRead::new(&mut cdc0_rx);
+
+            let io_tx = io_buf_copy(&mut cdc0_rx, chan_tx);
+            let io_rx = io_copy::<64, _, _>(chan_rx, &mut cdc0_tx);
+
+            let _ = join(io_rx, io_tx).await;
+            info!("Disconnected");
+        }
+    };
+
+    let setup = async {
         loop {
             info!("usb waiting");
-            cdc_rx.wait_connection().await;
+            cdc2_rx.wait_connection().await;
             info!("Connected");
-            let mut cdc_tx = CDCWrite::new(&mut cdc_tx);
-            let mut cdc_rx = CDCRead::new(&mut cdc_rx);
+            let cdc2_tx = CDCWrite::new(&mut cdc2_tx);
+            let cdc2_rx = CDCRead::new(&mut cdc2_rx);
 
-            let io_tx = io_copy::<64, _, _>(&mut cdc_rx, tx);
-            let io_rx = io_copy::<64, _, _>(rx, &mut cdc_tx);
+            let _ = menu(cdc2_rx, cdc2_tx, true, global).await;
 
-            join(io_rx, io_tx).await;
             info!("Disconnected");
         }
     };
 
     info!("usb join");
-    join(usb_fut, io).await;
+    join3(usb_fut, io0, setup).await;
 }
 
 pub struct CDCRead<'a, 'p, D: Driver<'a>> {
@@ -184,10 +205,7 @@ impl<'a, D: Driver<'a>> asynch::Write for CDCWrite<'a, '_, D> {
     async fn write(&mut self, buf: &[u8]) -> sunset::Result<usize> {
         // limit to 63 so we can ignore dealing with ZLPs for now
         let b = &buf[..buf.len().min(63)];
-        self.0
-            .write_packet(b)
-            .await
-            .map_err(|_| sunset::Error::ChannelEOF)?;
+        self.0.write_packet(b).await.map_err(|_| sunset::Error::ChannelEOF)?;
         Ok(b.len())
     }
 }
diff --git a/embassy/src/embassy_sunset.rs b/embassy/src/embassy_sunset.rs
index 6dd9be8fc7d7a1ca14396c193ba73c6258df071e..7746c8bad658f741cb371a3e8594e8653106d5fb 100644
--- a/embassy/src/embassy_sunset.rs
+++ b/embassy/src/embassy_sunset.rs
@@ -530,3 +530,20 @@ pub async fn io_copy<const B: usize, R, W>(r: &mut R, w: &mut W) -> Result<()>
     #[allow(unreachable_code)]
     Ok::<_, Error>(())
 }
+
+pub async fn io_buf_copy<R, W>(r: &mut R, w: &mut W) -> Result<()>
+    where R: asynch::BufRead+Io<Error=sunset::Error>,
+        W: asynch::Write+Io<Error=sunset::Error>
+{
+    loop {
+        let b = r.fill_buf().await?;
+        if b.len() == 0 {
+            return sunset::error::ChannelEOF.fail();
+        }
+        let n = b.len();
+        w.write_all(b).await?;
+        r.consume(n)
+    }
+    #[allow(unreachable_code)]
+    Ok::<_, Error>(())
+}
diff --git a/embassy/src/lib.rs b/embassy/src/lib.rs
index d5db6ebbb8d1260981bdb8a22adbb40c5a92ed3b..79c292b2a578b912fa3c49c9c00d095a20ebe4b5 100644
--- a/embassy/src/lib.rs
+++ b/embassy/src/lib.rs
@@ -19,4 +19,4 @@ pub use client::SSHClient;
 
 pub use embassy_channel::{ChanInOut, ChanIn, ChanOut};
 
-pub use embassy_sunset::{SunsetMutex, SunsetRawMutex, io_copy};
+pub use embassy_sunset::{SunsetMutex, SunsetRawMutex, io_copy, io_buf_copy};