From 86afe7ca0a28a9219f5afca8105ae515b80eda9c Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 27 Dec 2024 20:01:10 +0800 Subject: [PATCH 01/18] add APIs to configure PMIC IRQs --- libs/cramium-hal/src/axp2101.rs | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/libs/cramium-hal/src/axp2101.rs b/libs/cramium-hal/src/axp2101.rs index eff8764a9..e5188ea71 100644 --- a/libs/cramium-hal/src/axp2101.rs +++ b/libs/cramium-hal/src/axp2101.rs @@ -30,6 +30,38 @@ const REG_DLDO1_V: usize = 0x99; #[allow(dead_code)] const REG_DLDO2_V: usize = 0x9A; +const REG_IRQ_ENABLE0: u8 = 0x40; +const REG_IRQ_ENABLE1: u8 = 0x41; +const REG_IRQ_ENABLE2: u8 = 0x42; +const REG_IRQ_STATUS0: u8 = 0x48; +const REG_IRQ_STATUS1: u8 = 0x49; +const REG_IRQ_STATUS2: u8 = 0x4A; +const VBUS_INSERT_MASK: u8 = 0x80; +const VBUS_REMOVE_MASK: u8 = 0x40; + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum VbusIrq { + None, + Insert, + Remove, + InsertAndRemove, +} + +/// From the raw u8 read back from the register +impl From for VbusIrq { + fn from(value: u8) -> Self { + if (value & VBUS_INSERT_MASK) == 0 && (value & VBUS_REMOVE_MASK) == 0 { + VbusIrq::None + } else if (value & VBUS_INSERT_MASK) != 0 && (value & VBUS_REMOVE_MASK) == 0 { + VbusIrq::Insert + } else if (value & VBUS_INSERT_MASK) == 0 && (value & VBUS_REMOVE_MASK) != 0 { + VbusIrq::Remove + } else { + VbusIrq::InsertAndRemove + } + } +} + #[repr(u8)] #[derive(PartialEq, Eq, Clone, Copy)] pub enum WhichLdo { @@ -215,6 +247,37 @@ impl Axp2101 { } ctl } + + /// This will clear all other IRQ sources except VBUS IRQ + /// If we need to take more IRQ sources then this API will need to be refactored. + pub fn setup_vbus_irq(&mut self, i2c: &mut dyn i2c::I2cApi, mode: VbusIrq) -> Result<(), xous::Error> { + let data = match mode { + VbusIrq::None => 0u8, + VbusIrq::Insert => VBUS_INSERT_MASK, + VbusIrq::Remove => VBUS_REMOVE_MASK, + VbusIrq::InsertAndRemove => VBUS_INSERT_MASK | VBUS_REMOVE_MASK, + }; + // ENABLE1 has the code we want to target, but the rest also needs to be cleared so + // fill the values in with 0. + i2c.i2c_write(AXP2101_DEV, REG_IRQ_ENABLE0, &[0, data, 0]).map(|_| ())?; + + // clear the status bits + let mut status = [0u8; 3]; + i2c.i2c_read(AXP2101_DEV, REG_IRQ_STATUS0, &mut status, false)?; + i2c.i2c_write(AXP2101_DEV, REG_IRQ_STATUS0, &status).map(|_| ()) + } + + pub fn get_vbus_irq_status(&self, i2c: &mut dyn i2c::I2cApi) -> Result { + let mut buf = [0u8]; + i2c.i2c_read(AXP2101_DEV, REG_IRQ_STATUS1, &mut buf, false)?; + Ok(VbusIrq::from(buf[0])) + } + + /// This will clear all pending IRQs, regardless of the setup + pub fn clear_vbus_irq_pending(&mut self, i2c: &mut dyn i2c::I2cApi) -> Result<(), xous::Error> { + let data = VBUS_INSERT_MASK | VBUS_REMOVE_MASK; + i2c.i2c_write(AXP2101_DEV, REG_IRQ_STATUS1, &[data]).map(|_| ()) + } } pub fn parse_dcdc_ena(d: u8) -> ([bool; 4], bool, bool) { From 68759073ef88a163bb7e6646033612196d9dbb5f Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 27 Dec 2024 20:01:32 +0800 Subject: [PATCH 02/18] configure the IRQ input --- libs/cramium-hal/src/board/baosec.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libs/cramium-hal/src/board/baosec.rs b/libs/cramium-hal/src/board/baosec.rs index ad2787043..6b7464a9b 100644 --- a/libs/cramium-hal/src/board/baosec.rs +++ b/libs/cramium-hal/src/board/baosec.rs @@ -150,6 +150,7 @@ pub fn setup_memory_pins(iox: &dyn IoSetup) -> crate::udma::SpimChannel { crate::udma::SpimChannel::Channel1 } +/// This also sets up I2C-adjacent interrupt inputs as well pub fn setup_i2c_pins(iox: &dyn IoSetup) -> crate::udma::I2cChannel { // I2C_SCL_B[0] iox.setup_pin( @@ -173,6 +174,17 @@ pub fn setup_i2c_pins(iox: &dyn IoSetup) -> crate::udma::I2cChannel { Some(IoxEnable::Enable), Some(IoxDriveStrength::Drive2mA), ); + // PB13 -> PMIC IRQ + iox.setup_pin( + IoxPort::PB, + 13, + Some(IoxDir::Input), + Some(IoxFunction::Gpio), + Some(IoxEnable::Enable), + Some(IoxEnable::Enable), + None, + None, + ); crate::udma::I2cChannel::Channel0 } From 19780161bb57f4f5113f4868057ca7c92f3a5b93 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 27 Dec 2024 20:01:51 +0800 Subject: [PATCH 03/18] add PMIC feature to the HAL --- services/usb-cramium/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/services/usb-cramium/Cargo.toml b/services/usb-cramium/Cargo.toml index b6372f375..ebffa94ac 100644 --- a/services/usb-cramium/Cargo.toml +++ b/services/usb-cramium/Cargo.toml @@ -16,6 +16,7 @@ num-traits = { version = "0.2.14", default-features = false } cramium-hal = { path = "../../libs/cramium-hal", features = [ "std", "debug-print-usb", + "axp2101", ] } cram-hal-service = { path = "../cram-hal-service" } xous-usb-hid = { git = "https://github.com/betrusted-io/xous-usb-hid.git", branch = "main" } From 20902880095ac8eae6e71e29546c6e20e99553b4 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 27 Dec 2024 20:02:08 +0800 Subject: [PATCH 04/18] add a test loop to check the IRQ pin configuration this all seems to work as expected now, next up: hooking the interrupt handler for the IOX. Probably should put the actual IRQ router in the HAL so that multiple services can configure it, but have the handler actually invoke a function in our address space. --- services/usb-cramium/src/main.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/usb-cramium/src/main.rs b/services/usb-cramium/src/main.rs index 34080038a..a7c90b3a9 100644 --- a/services/usb-cramium/src/main.rs +++ b/services/usb-cramium/src/main.rs @@ -16,6 +16,7 @@ use std::sync::Arc; use api::{HIDReport, LogLevel, Opcode, U2fCode, U2fMsgIpc, UsbListenerRegistration}; use cram_hal_service::api::KeyMap; +use cramium_hal::axp2101::VbusIrq; use cramium_hal::usb::driver::{CorigineUsb, CorigineWrapper}; use hw::CramiumUsb; use hw::UsbIrqReq; @@ -239,6 +240,32 @@ pub(crate) fn main_hw() -> ! { } }); + std::thread::spawn({ + move || { + log::info!("Connecting to I2C"); + let mut i2c = cram_hal_service::I2c::new(); + let mut pmic = cramium_hal::axp2101::Axp2101::new(&mut i2c).expect("couldn't open PMIC"); + let iox = cram_hal_service::IoxHal::new(); + log::info!("AXP2101 config: {:?}", pmic); + let tt = ticktimer::Ticktimer::new().unwrap(); + pmic.setup_vbus_irq(&mut i2c, cramium_hal::axp2101::VbusIrq::InsertAndRemove) + .expect("couldn't setup IRQ"); + // PB13 is the IRQ input + loop { + tt.sleep_ms(500).ok(); + let status = pmic.get_vbus_irq_status(&mut i2c).unwrap(); + log::info!( + "Status: {:?}, {:?}", + status, + iox.get_gpio_pin_value(cramium_hal::iox::IoxPort::PB, 13) + ); + if status != VbusIrq::None { + pmic.clear_vbus_irq_pending(&mut i2c).expect("couldn't clear pending VBUS IRQ"); + } + } + } + }); + log::info!("Entering main loop"); let mut msg_opt = None; From 7375dc0c2fa7a60eab13e06bacb05b8ea7cc8ef4 Mon Sep 17 00:00:00 2001 From: bunnie Date: Sun, 29 Dec 2024 01:27:38 +0800 Subject: [PATCH 05/18] update core.svd based on latest code push --- utralib/cramium/core.svd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utralib/cramium/core.svd b/utralib/cramium/core.svd index cff44b124..b1b1f5133 100644 --- a/utralib/cramium/core.svd +++ b/utralib/cramium/core.svd @@ -3,7 +3,7 @@ litex SOC - + 8 32 From e66303e044060f69fcf66476b2e893a92f4ff13d Mon Sep 17 00:00:00 2001 From: bunnie Date: Thu, 2 Jan 2025 01:02:08 +0800 Subject: [PATCH 06/18] add fix discovered during sim testing --- services/cram-console/src/cmds/mbox.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/cram-console/src/cmds/mbox.rs b/services/cram-console/src/cmds/mbox.rs index 9d59319ed..98cbc4878 100644 --- a/services/cram-console/src/cmds/mbox.rs +++ b/services/cram-console/src/cmds/mbox.rs @@ -148,6 +148,9 @@ impl Mbox { } pub fn try_send(&mut self, to_cm7: MboxToCm7Pkt) -> Result<(), MboxError> { + // clear any pending bits from previous transactions + self.csr.wo(mailbox::EV_PENDING, self.csr.r(mailbox::EV_PENDING)); + if to_cm7.data.len() > MAX_PKT_LEN { Err(MboxError::TxOverflow) } else { From 0953eac2ef3c4136a685d14f029a66f9e174e1a3 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:01:15 +0800 Subject: [PATCH 07/18] cleanup warnings --- libs/cramium-hal/src/axp2101.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/cramium-hal/src/axp2101.rs b/libs/cramium-hal/src/axp2101.rs index e5188ea71..32fea0ef8 100644 --- a/libs/cramium-hal/src/axp2101.rs +++ b/libs/cramium-hal/src/axp2101.rs @@ -31,10 +31,13 @@ const REG_DLDO1_V: usize = 0x99; const REG_DLDO2_V: usize = 0x9A; const REG_IRQ_ENABLE0: u8 = 0x40; +#[allow(dead_code)] const REG_IRQ_ENABLE1: u8 = 0x41; +#[allow(dead_code)] const REG_IRQ_ENABLE2: u8 = 0x42; const REG_IRQ_STATUS0: u8 = 0x48; const REG_IRQ_STATUS1: u8 = 0x49; +#[allow(dead_code)] const REG_IRQ_STATUS2: u8 = 0x4A; const VBUS_INSERT_MASK: u8 = 0x80; const VBUS_REMOVE_MASK: u8 = 0x40; From 7f8f64e1bee6315590a6ea3365ebb21b7b75b84c Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:03:31 +0800 Subject: [PATCH 08/18] add routine to set irq pin for PMIC to board description file --- libs/cramium-hal/src/board/baosec.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/cramium-hal/src/board/baosec.rs b/libs/cramium-hal/src/board/baosec.rs index 6b7464a9b..3664ac588 100644 --- a/libs/cramium-hal/src/board/baosec.rs +++ b/libs/cramium-hal/src/board/baosec.rs @@ -1,7 +1,7 @@ // Constants that define pin locations, RAM offsets, etc. for the BaoSec board use crate::iox; -use crate::iox::IoSetup; use crate::iox::*; +use crate::iox::{IoIrq, IoSetup}; pub const I2C_AXP2101_ADR: u8 = 0x34; pub const I2C_TUSB320_ADR: u8 = 0x47; @@ -272,3 +272,7 @@ pub fn setup_kb_pins(iox: &T) -> ([(IoxPort, u8); 3], [(Iox (KB_PORT, C_PINS[2]), ]) } + +pub fn setup_pmic_irq(iox: &T, server: &str, opcode: usize) { + iox.set_irq_pin(IoxPort::PB, 13, IoxValue::Low, server, opcode); +} From fa3550a30fa75348f82551fe0977c774a3e4c669 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:04:45 +0800 Subject: [PATCH 09/18] add trait for setting IRQ on GPIO --- libs/cramium-hal/src/iox.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/cramium-hal/src/iox.rs b/libs/cramium-hal/src/iox.rs index 964a59e96..0f681a966 100644 --- a/libs/cramium-hal/src/iox.rs +++ b/libs/cramium-hal/src/iox.rs @@ -101,6 +101,14 @@ pub trait IoGpio { fn set_gpio_pin_dir(&self, port: IoxPort, pin: u8, dir: IoxDir); } +pub trait IoIrq { + /// This hooks a given port/pin to generate a message to the server specified + /// with `server` and the opcode number `usize` when an IRQ is detected on the port/pin. + /// The active state of the IRQ is defined by `active`; the transition edge from inactive + /// to active is when the event is generated. + fn set_irq_pin(&self, port: IoxPort, pin: u8, active: IoxValue, server: &str, opcode: usize); +} + pub struct Iox { pub csr: SharedCsr, } From b02112b05abfcb62c09db333118361ea9d84c4b1 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:05:51 +0800 Subject: [PATCH 10/18] fix reset conditions and straggler debug statements --- libs/cramium-hal/src/usb/driver.rs | 61 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/libs/cramium-hal/src/usb/driver.rs b/libs/cramium-hal/src/usb/driver.rs index ebae1be0d..7cc2acbc2 100644 --- a/libs/cramium-hal/src/usb/driver.rs +++ b/libs/cramium-hal/src/usb/driver.rs @@ -36,7 +36,7 @@ const CRG_EVENT_RING_NUM: usize = 1; const CRG_ERST_SIZE: usize = 1; const CRG_EVENT_RING_SIZE: usize = 128; const CRG_EP0_TD_RING_SIZE: usize = 16; -const CRG_EP_NUM: usize = 4; +pub const CRG_EP_NUM: usize = 4; const CRG_TD_RING_SIZE: usize = 512; // was 1280 in original code. not even sure we need ... 64? const CRG_UDC_MAX_BURST: u32 = 15; const CRG_UDC_ISO_INTERVAL: u8 = 3; @@ -1243,10 +1243,6 @@ impl CorigineUsb { compiler_fence(Ordering::SeqCst); // Set up storage for Endpoint contexts - #[cfg(feature = "std")] - log::trace!("Begin init_device_context"); - #[cfg(not(feature = "std"))] - println!("Begin init_device_context"); // init device context and ep context, refer to 7.6.2 self.p_epcx = AtomicPtr::new((self.ifram_base_ptr + CRG_UDC_EPCX_OFFSET) as *mut EpCxS); self.p_epcx_len = CRG_EP_NUM * 2 * size_of::(); @@ -1265,13 +1261,12 @@ impl CorigineUsb { // disable 2.0 LPM self.csr.wo(U2PORTPMSC, 0); - #[cfg(feature = "std")] - log::trace!("USB init done"); + crate::println!("USB hw init done"); } pub fn init_ep0(&mut self) { - #[cfg(feature = "std")] - log::trace!("Begin init_ep0"); + #[cfg(feature = "verbose-debug")] + crate::println!("Begin init_ep0"); let udc_ep = &mut self.udc_ep[0]; udc_ep.ep_num = 0; @@ -1311,15 +1306,7 @@ impl CorigineUsb { let cmd_param0: u32 = (udc_ep.tran_ring_info.vaddr.load(Ordering::SeqCst) as u32) & 0xFFFF_FFF0 | self.csr.ms(CMDPARA0_CMD0_INIT_EP0_DCS, udc_ep.pcs as u32); let cmd_param1: u32 = 0; - #[cfg(feature = "std")] - { - log::debug!( - "ep0 ring dma addr = {:x}", - udc_ep.tran_ring_info.vaddr.load(Ordering::SeqCst) as usize - ); - log::debug!("INIT EP0 CMD par0 = {:x} par1 = {:x}", cmd_param0, cmd_param1); - } - #[cfg(not(feature = "std"))] + #[cfg(feature = "verbose-debug")] { println!("ep0 ring dma addr = {:x}", udc_ep.tran_ring_info.vaddr.load(Ordering::SeqCst) as usize); println!("INIT EP0 CMD par0 = {:x} par1 = {:x}", cmd_param0, cmd_param1); @@ -1329,8 +1316,6 @@ impl CorigineUsb { .expect("couldn't issue ep0 init command"); self.ep0_buf = AtomicPtr::new((self.ifram_base_ptr + CRG_UDC_EP0_BUF_OFFSET) as *mut u8); - #[cfg(feature = "std")] - log::trace!("End init_ep0"); } #[cfg(feature = "std")] @@ -2168,7 +2153,7 @@ pub struct CorigineWrapper { /// Tuple is (type of endpoint, max packet size) pub ep_meta: [Option<(EpType, usize)>; CRG_EP_NUM], pub ep_out_ready: Box<[AtomicBool]>, - pub address_is_set: AtomicBool, + pub address_is_set: Arc, pub event: Option, } #[cfg(feature = "std")] @@ -2183,7 +2168,7 @@ impl CorigineWrapper { .collect::>() .into_boxed_slice(), event: None, - address_is_set: AtomicBool::new(false), + address_is_set: Arc::new(AtomicBool::new(false)), }; c } @@ -2198,7 +2183,7 @@ impl CorigineWrapper { .collect::>() .into_boxed_slice(), event: None, - address_is_set: AtomicBool::new(self.address_is_set.load(Ordering::SeqCst)), + address_is_set: self.address_is_set.clone(), }; c.ep_meta.copy_from_slice(&self.ep_meta); for (dst, src) in c.ep_out_ready.iter().zip(self.ep_out_ready.iter()) { @@ -2327,7 +2312,7 @@ impl UsbBus for CorigineWrapper { let irq_csr = { let mut hw = self.core(); // disable IRQs - hw.irq_csr.wfo(utralib::utra::irqarray1::EV_ENABLE_USBC_DUPE, 0); + hw.irq_csr.wo(utralib::utra::irqarray1::EV_ENABLE, 0); hw.reset(); hw.init(); hw.start(); @@ -2337,7 +2322,7 @@ impl UsbBus for CorigineWrapper { }; // the lock is released, now we can enable irqs irq_csr.wo(utralib::utra::irqarray1::EV_PENDING, 0xffff_ffff); // blanket clear - irq_csr.wfo(utralib::utra::irqarray1::EV_ENABLE_USBC_DUPE, 1); + irq_csr.wo(utralib::utra::irqarray1::EV_ENABLE, 3); // FIXME: hard coded value that enables CORIGINE_IRQ_MASK | SW_IRQ_MASK // TODO -- figure out what this means // self.force_reset().ok(); @@ -2530,9 +2515,11 @@ impl UsbBus for CorigineWrapper { let ret = if let Some((ptr, len)) = self.core().app_ptr.take() { self.ep_out_ready[ep_addr.index()].store(false, Ordering::SeqCst); let app_buf = unsafe { core::slice::from_raw_parts(ptr as *const u8, len) }; - if buf.len() > app_buf.len() { + if buf.len() < app_buf.len() { Err(UsbError::BufferOverflow) } else { + #[cfg(feature = "verbose-debug")] + crate::println!("copy into len {} from len {}", buf.len(), app_buf.len()); buf[..app_buf.len()].copy_from_slice(app_buf); crate::println!(" {:x?}", &buf[..app_buf.len().min(8)]); Ok(app_buf.len()) @@ -2681,9 +2668,25 @@ impl UsbBus for CorigineWrapper { fn force_reset(&self) -> Result<()> { crate::println!(" ******* force_reset"); - // This is the minimum we need to do to restart EP0, but, I think we also need to reset - // TRB pointers etc. See page 67 of the manual. - self.core().update_current_speed(); + self.address_is_set.store(false, Ordering::SeqCst); + for eor in self.ep_out_ready.iter() { + eor.store(false, Ordering::SeqCst); + } + crate::println!(" ******** reset"); + let irq_csr = { + let mut hw = self.core(); + // disable IRQs + hw.irq_csr.wo(utralib::utra::irqarray1::EV_ENABLE, 0); + hw.reset(); + hw.init(); + hw.start(); + hw.update_current_speed(); + // IRQ enable must happen without dependency on the hardware lock + hw.irq_csr.clone() + }; + // the lock is released, now we can enable irqs + irq_csr.wo(utralib::utra::irqarray1::EV_PENDING, 0xffff_ffff); // blanket clear + irq_csr.wo(utralib::utra::irqarray1::EV_ENABLE, 3); // FIXME: hard coded value that enables CORIGINE_IRQ_MASK | SW_IRQ_MASK Ok(()) } From 71ac106ee792d7abdbc06eca34fecd3248df8e53 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:06:11 +0800 Subject: [PATCH 11/18] add the CGU perclk divider for the NTO target --- loader/src/platform/cramium/cramium.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/loader/src/platform/cramium/cramium.rs b/loader/src/platform/cramium/cramium.rs index 9bfb01e2c..5c83a57e0 100644 --- a/loader/src/platform/cramium/cramium.rs +++ b/loader/src/platform/cramium/cramium.rs @@ -1542,6 +1542,11 @@ unsafe fn init_clock_asic(freq_hz: u32) -> u32 { daric_cgu.add(utra::sysctrl::SFR_CGUFD_CFGFDCR_0_4_2.offset()).write_volatile(0x1f3f); // hclk daric_cgu.add(utra::sysctrl::SFR_CGUFD_CFGFDCR_0_4_3.offset()).write_volatile(0x0f1f); // iclk daric_cgu.add(utra::sysctrl::SFR_CGUFD_CFGFDCR_0_4_4.offset()).write_volatile(0x070f); // pclk + + #[cfg(not(feature = "cramium-mpw"))] + // perclk divider - set to divide by 8 off of an 800Mhz base. Only found on NTO. + daric_cgu.add(utra::sysctrl::SFR_CGUFDPER.offset()).write_volatile(0x03_ff_ff); + // commit dividers daric_cgu.add(utra::sysctrl::SFR_CGUSET.offset()).write_volatile(0x32); } From af6289b8e475d756333487c9fa78e017ce800e24 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:06:48 +0800 Subject: [PATCH 12/18] add USB unplug/replug handling & stack resetting --- services/usb-cramium/src/api.rs | 4 +++ services/usb-cramium/src/hw.rs | 27 +++++++++++++++++ services/usb-cramium/src/main.rs | 50 ++++++++++++++++---------------- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/services/usb-cramium/src/api.rs b/services/usb-cramium/src/api.rs index 753704d04..631653be5 100644 --- a/services/usb-cramium/src/api.rs +++ b/services/usb-cramium/src/api.rs @@ -67,6 +67,10 @@ pub(crate) enum Opcode { #[cfg(feature = "mass-storage")] ResetBlockDevice = 1026, + /// Platform-specific messages for callback handlers + #[cfg(feature = "cramium-soc")] + PmicIrq = 1536, + // HIDv2 /// Read a HID report HIDReadReport = 1027, diff --git a/services/usb-cramium/src/hw.rs b/services/usb-cramium/src/hw.rs index 255d5b17d..2bfed77d1 100644 --- a/services/usb-cramium/src/hw.rs +++ b/services/usb-cramium/src/hw.rs @@ -320,4 +320,31 @@ impl<'a> CramiumUsb<'a> { self.irq_req = Some(request_type); self.irq_csr.wfo(utra::irqarray1::EV_SOFT_TRIGGER, SW_IRQ_MASK); } + + /// Process an unplug event + pub fn unplug(&mut self) { + // disable all interrupts so we can safely go through initialization routines + self.irq_csr.wo(utra::irqarray1::EV_ENABLE, 0); + + self.wrapper.hw.lock().unwrap().reset(); + self.wrapper.hw.lock().unwrap().init(); + self.wrapper.hw.lock().unwrap().start(); + self.wrapper.hw.lock().unwrap().update_current_speed(); + + // reset all shared data structures + self.device.force_reset().ok(); + self.fido_tx_queue = RefCell::new(VecDeque::new()); + self.kbd_tx_queue = RefCell::new(VecDeque::new()); + self.irq_req = None; + self.wrapper.event = None; + self.wrapper.address_is_set.store(false, Ordering::SeqCst); + self.wrapper.ep_out_ready = (0..cramium_hal::usb::driver::CRG_EP_NUM + 1) + .map(|_| core::sync::atomic::AtomicBool::new(false)) + .collect::>() + .into_boxed_slice(); + + // re-enable IRQs + self.irq_csr.wo(utra::irqarray1::EV_PENDING, 0xFFFF_FFFF); + self.irq_csr.wo(utra::irqarray1::EV_ENABLE, CORIGINE_IRQ_MASK | SW_IRQ_MASK); + } } diff --git a/services/usb-cramium/src/main.rs b/services/usb-cramium/src/main.rs index a7c90b3a9..a20401762 100644 --- a/services/usb-cramium/src/main.rs +++ b/services/usb-cramium/src/main.rs @@ -240,31 +240,16 @@ pub(crate) fn main_hw() -> ! { } }); - std::thread::spawn({ - move || { - log::info!("Connecting to I2C"); - let mut i2c = cram_hal_service::I2c::new(); - let mut pmic = cramium_hal::axp2101::Axp2101::new(&mut i2c).expect("couldn't open PMIC"); - let iox = cram_hal_service::IoxHal::new(); - log::info!("AXP2101 config: {:?}", pmic); - let tt = ticktimer::Ticktimer::new().unwrap(); - pmic.setup_vbus_irq(&mut i2c, cramium_hal::axp2101::VbusIrq::InsertAndRemove) - .expect("couldn't setup IRQ"); - // PB13 is the IRQ input - loop { - tt.sleep_ms(500).ok(); - let status = pmic.get_vbus_irq_status(&mut i2c).unwrap(); - log::info!( - "Status: {:?}, {:?}", - status, - iox.get_gpio_pin_value(cramium_hal::iox::IoxPort::PB, 13) - ); - if status != VbusIrq::None { - pmic.clear_vbus_irq_pending(&mut i2c).expect("couldn't clear pending VBUS IRQ"); - } - } - } - }); + log::info!("Registering PMIC handler to detect USB plug/unplug events"); + let iox = cram_hal_service::iox_lib::IoxHal::new(); + cramium_hal::board::setup_pmic_irq( + &iox, + api::SERVER_NAME_USB_DEVICE, + Opcode::PmicIrq.to_usize().unwrap(), + ); + let mut i2c = cram_hal_service::I2c::new(); + let mut pmic = cramium_hal::axp2101::Axp2101::new(&mut i2c).expect("couldn't open PMIC"); + pmic.setup_vbus_irq(&mut i2c, cramium_hal::axp2101::VbusIrq::Remove).expect("couldn't setup IRQ"); log::info!("Entering main loop"); @@ -275,6 +260,21 @@ pub(crate) fn main_hw() -> ! { let opcode = num_traits::FromPrimitive::from_usize(msg.body.id()).unwrap_or(Opcode::InvalidCall); log::info!("{:?}", opcode); match opcode { + Opcode::PmicIrq => match pmic.get_vbus_irq_status(&mut i2c).unwrap() { + VbusIrq::Insert => { + log::error!("VBUS insert reported by PMIC, but we didn't ask for the event!"); + } + VbusIrq::Remove => { + log::info!("VBUS removed. Resetting stack."); + cu.unplug(); + } + VbusIrq::InsertAndRemove => { + panic!("Unexpected report from vbus_irq status"); + } + VbusIrq::None => { + log::warn!("Received an interrupt but no actual event reported"); + } + }, Opcode::U2fRxDeferred => { // notify the event listener, if any if observer_conn.is_some() && observer_op.is_some() { From 96073cc12e12f2b68207e8f62e91a621025c9bf1 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:07:20 +0800 Subject: [PATCH 13/18] add Iox IRQ registration and event forwarding the Iox block can register up to 8 interrupts on any of the GPIO pins on the device. All the interrupts get OR'd together to generate a single interrupt to the CPU, which has to be disambiguated and forwarded on to the registering server as an event. Due to the protection model of Xous, the configuration of the IRQ has to be centralized, and the handling is done with a message. This means that the actual time from the interrupt to the handling can be a bit variable, up to several ms, so this method isn't suitable for hard real time. For hard real time stuff, see the implementation in the USB stack for Cramium, which has a very timing sensitive IRQ handler requirement. --- services/cram-hal-service/src/api.rs | 15 ++- services/cram-hal-service/src/iox_lib.rs | 15 ++- services/cram-hal-service/src/main.rs | 164 ++++++++++++++++++++++- 3 files changed, 189 insertions(+), 5 deletions(-) diff --git a/services/cram-hal-service/src/api.rs b/services/cram-hal-service/src/api.rs index 0db7e6785..11a4f543a 100644 --- a/services/cram-hal-service/src/api.rs +++ b/services/cram-hal-service/src/api.rs @@ -1,5 +1,5 @@ pub mod keyboard; -use cramium_hal::iox; +use cramium_hal::iox::{self, IoxPort, IoxValue}; pub use keyboard::*; /// The Opcode numbers here should not be changed. You can add new ones, @@ -47,6 +47,10 @@ pub enum Opcode { /// Peripheral reset PeriphReset = 10, + /// Configure Iox IRQ + ConfigureIoxIrq = 11, + IrqLocalHandler = 12, + /// Exit server Quit = 255, @@ -145,3 +149,12 @@ pub struct I2cTransactions { impl From> for I2cTransactions { fn from(value: Vec) -> Self { Self { transactions: value } } } + +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct IoxIrqRegistration { + pub server: String, + pub opcode: usize, + pub port: IoxPort, + pub pin: u8, + pub active: IoxValue, +} diff --git a/services/cram-hal-service/src/iox_lib.rs b/services/cram-hal-service/src/iox_lib.rs index 3bc96992a..48389e289 100644 --- a/services/cram-hal-service/src/iox_lib.rs +++ b/services/cram-hal-service/src/iox_lib.rs @@ -1,11 +1,14 @@ use core::sync::atomic::Ordering; use cramium_hal::iox::{ - IoGpio, IoSetup, IoxDir, IoxDriveStrength, IoxEnable, IoxFunction, IoxPort, IoxValue, + IoGpio, IoIrq, IoSetup, IoxDir, IoxDriveStrength, IoxEnable, IoxFunction, IoxPort, IoxValue, }; use num_traits::*; -use crate::{Opcode, SERVER_NAME_CRAM_HAL, api::IoxConfigMessage}; +use crate::{ + Opcode, SERVER_NAME_CRAM_HAL, + api::{IoxConfigMessage, IoxIrqRegistration}, +}; pub struct IoxHal { conn: xous::CID, @@ -176,3 +179,11 @@ impl Drop for IoxHal { } } } + +impl IoIrq for IoxHal { + fn set_irq_pin(&self, port: IoxPort, pin: u8, active: IoxValue, server: &str, opcode: usize) { + let msg = IoxIrqRegistration { server: server.to_owned(), opcode, port, pin, active }; + let buf = xous_ipc::Buffer::into_buf(msg).unwrap(); + buf.lend(self.conn, Opcode::ConfigureIoxIrq.to_u32().unwrap()).expect("Couldn't set up IRQ"); + } +} diff --git a/services/cram-hal-service/src/main.rs b/services/cram-hal-service/src/main.rs index 91ff6fe27..f62a02365 100644 --- a/services/cram-hal-service/src/main.rs +++ b/services/cram-hal-service/src/main.rs @@ -2,17 +2,20 @@ mod api; mod hw; use api::*; +use bitfield::*; use cramium_hal::{ - iox, + iox::{self, IoxPort, IoxValue}, udma::{EventChannel, GlobalConfig, I2cApi, PeriphId}, }; +use num_traits::*; +use utralib::CSR; #[cfg(feature = "quantum-timer")] use utralib::utra; #[cfg(feature = "quantum-timer")] use utralib::*; #[cfg(feature = "swap")] use xous::SWAPPER_PID; -use xous::sender::Sender; +use xous::{ScalarMessage, sender::Sender}; #[cfg(feature = "pio")] use xous_pio::*; @@ -37,6 +40,57 @@ fn timer_tick(_irq_no: usize, arg: *mut usize) { ptimer.irq_csr.wo(utra::irqarray18::EV_PENDING, ptimer.irq_csr.r(utra::irqarray18::EV_PENDING)); } +#[repr(u32)] +#[allow(dead_code)] +enum IntMode { + RisingEdge = 0, + FallingEdge = 1, + HighLevel = 2, + LowLevel = 3, +} +bitfield! { + #[derive(Copy, Clone, PartialEq, Eq, Default)] + pub struct IntCr(u32); + impl Debug; + pub u32, select, set_select: 6, 0; + pub u32, mode, set_mode: 8, 7; + pub enable, set_enable: 9; + pub wakeup, set_wakeup: 10; +} +fn irq_select_from_port(port: IoxPort, pin: u8) -> u32 { (port as u32) * 16 + pin as u32 } +fn find_first_none(arr: &[Option]) -> Option { arr.iter().position(|item| item.is_none()) } + +#[derive(Debug, Copy, Clone)] +#[allow(dead_code)] +struct IrqLocalRegistration { + pub cid: xous::CID, + pub opcode: usize, + pub port: IoxPort, + pub pin: u8, + pub active: IoxValue, +} + +struct IrqHandler { + pub irq_csr: CSR, + pub cid: xous::CID, +} +fn iox_irq_handler(_irq_no: usize, arg: *mut usize) { + let handler = unsafe { &mut *(arg as *mut IrqHandler) }; + let pending = handler.irq_csr.r(utralib::utra::irqarray10::EV_PENDING); + handler.irq_csr.wo(utralib::utra::irqarray10::EV_PENDING, pending); + xous::try_send_message( + handler.cid, + xous::Message::Scalar(ScalarMessage::from_usize( + Opcode::IrqLocalHandler.to_usize().unwrap(), + pending as usize, + 0, + 0, + 0, + )), + ) + .ok(); +} + fn try_alloc(ifram_allocs: &mut Vec>, size: usize, sender: Sender) -> Option { let mut size_pages = size / 4096; if size % 4096 != 0 { @@ -94,6 +148,7 @@ fn main() { let xns = xous_names::XousNames::new().unwrap(); let sid = xns.register_name(cram_hal_service::SERVER_NAME_CRAM_HAL, None).expect("can't register server"); + let self_cid = xous::connect(sid).expect("couldn't create self-connection"); let mut ifram_allocs = [Vec::new(), Vec::new()]; // code is written assuming the IFRAM blocks have the same size. Since this is fixed in @@ -229,6 +284,28 @@ fn main() { } // -------------------- end timer workaround code + // ---- "own" the Iox IRQ bank. This might need revision once NTO aliasing is available. --- + let irq_page = xous::syscall::map_memory( + xous::MemoryAddress::new(utralib::utra::irqarray10::HW_IRQARRAY10_BASE), + None, + 4096, + xous::MemoryFlags::R | xous::MemoryFlags::W, + ) + .expect("couldn't claim IRQ control page"); + let irq_csr = CSR::new(irq_page.as_mut_ptr() as *mut u32); + let mut irq = IrqHandler { irq_csr, cid: self_cid }; + xous::claim_interrupt( + utralib::utra::irqarray10::IRQARRAY10_IRQ, + iox_irq_handler, + &mut irq as *mut IrqHandler as *mut usize, + ) + .expect("couldn't claim Iox interrupt"); + irq.irq_csr.wo(utralib::utra::irqarray10::EV_PENDING, 0xFFFF_FFFF); + irq.irq_csr.wfo(utralib::utra::irqarray10::EV_ENABLE_IOXIRQ, 1); + // Up to 8 slots where we can populate interrupt mappings in the hardware + // The index of the array corresponds to the slot. + let mut irq_table: [Option; 8] = [None; 8]; + // start keyboard emulator service hw::keyboard::start_keyboard_service(); @@ -339,6 +416,89 @@ fn main() { iox.set_drive_strength(config.port, config.pin, s); } } + Opcode::ConfigureIoxIrq => { + let buf = + unsafe { xous_ipc::Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let registration = buf.to_original::().unwrap(); + log::info!("Got registration request: {:?}", registration); + if let Some(index) = find_first_none(&irq_table) { + // create the reverse-lookup registration + let local_conn = xns + .request_connection(®istration.server) + .expect("couldn't connect to IRQ registree"); + let local_reg = IrqLocalRegistration { + cid: local_conn, + opcode: registration.opcode, + port: registration.port, + pin: registration.pin, + active: registration.active, + }; + irq_table[index] = Some(local_reg); + + // now activate the hardware register + let select = irq_select_from_port(registration.port, registration.pin); + let mut int_cr = IntCr(0); + int_cr.set_select(select); + match registration.active { + IoxValue::Low => int_cr.set_mode(IntMode::FallingEdge as u32), + IoxValue::High => int_cr.set_mode(IntMode::RisingEdge as u32), + } + int_cr.set_enable(true); + // safety: the index and offset are mapped to the intended range because the index is + // bounded by the size of irq_table, and the offset comes from the generated header file. + log::debug!( + "writing {:x} to {:x} at index {}", + int_cr.0, + unsafe { + iox.csr.base().add(utralib::utra::iox::SFR_INTCR_CRINT0.offset()).add(index) + as usize + }, + index + ); + unsafe { + iox.csr + .base() + .add(utralib::utra::iox::SFR_INTCR_CRINT0.offset()) + .add(index) + .write_volatile(int_cr.0); + } + } else { + panic!("Ran out of Iox interrupt slots: maximum 8 available"); + } + } + Opcode::IrqLocalHandler => { + // Figure out which port(s) caused the IRQ + let irq_flag = iox.csr.r(utralib::utra::iox::SFR_INTFR); + // clear the set bit by writing it back + iox.csr.wo(utralib::utra::iox::SFR_INTFR, irq_flag); + let mut found = false; + for bitpos in 0..8 { + // the bit position is flipped versus register order in memory + if ((irq_flag << (bitpos as u32)) & 0x80) != 0 { + if let Some(local_reg) = irq_table[bitpos] { + found = true; + // interrupts are "Best effort" and can gracefully fail if the receiver has been + // overwhelmed by too many interrupts + xous::try_send_message( + local_reg.cid, + xous::Message::new_scalar(local_reg.opcode, 0, 0, 0, 0), + ) + .ok(); + } else { + log::warn!( + "Got IRQ on position {} but no registration was found, ignoring!", + bitpos + ); + } + } + } + if !found { + log::warn!( + "No handler was found for raw flag: {:x} (note bit order is reversed)", + irq_flag + ); + } + } Opcode::SetGpioBank => { if let Some(scalar) = msg.body.scalar_message() { let port: cramium_hal::iox::IoxPort = From e5fdd5da7781c1bc4b044644950c3b3cbd2cf79b Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:09:32 +0800 Subject: [PATCH 14/18] add bitfield crate depedency --- services/cram-hal-service/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/cram-hal-service/Cargo.toml b/services/cram-hal-service/Cargo.toml index 3e05db98c..7c149d73e 100644 --- a/services/cram-hal-service/Cargo.toml +++ b/services/cram-hal-service/Cargo.toml @@ -27,6 +27,7 @@ pio-proc = "0.2.2" pio = "0.2.1" rand_core = "0.6.4" rand_chacha = "0.3.1" +bitfield = "0.13.2" num-derive = { version = "0.4.2", default-features = false } num-traits = { version = "0.2.14", default-features = false } @@ -45,4 +46,4 @@ swap = [] mpw = ["cramium-hal/mpw"] # add this feature to enable pre-emption quantum-timer = ["utralib", "pio"] -default = ["app-uart"] +default = ["app-uart", "utralib"] From 8e693990fb5076ee608a3279e7ae518beb8265ae Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:09:40 +0800 Subject: [PATCH 15/18] update lock with bitfield crate dependency --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 9c2e6518e..592e282f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,6 +957,7 @@ dependencies = [ name = "cram-hal-service" version = "0.1.0" dependencies = [ + "bitfield", "cramium-hal", "log", "num-derive 0.4.2", From 80f0dd2ba7e49ca99b5a70101d4227379ca063d1 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:09:57 +0800 Subject: [PATCH 16/18] add reminder for where the verbose debug feature lives --- services/usb-cramium/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/services/usb-cramium/Cargo.toml b/services/usb-cramium/Cargo.toml index ebffa94ac..3af1d7e28 100644 --- a/services/usb-cramium/Cargo.toml +++ b/services/usb-cramium/Cargo.toml @@ -16,6 +16,7 @@ num-traits = { version = "0.2.14", default-features = false } cramium-hal = { path = "../../libs/cramium-hal", features = [ "std", "debug-print-usb", + # "verbose-debug", "axp2101", ] } cram-hal-service = { path = "../cram-hal-service" } From 2a8ca9248776098df2012f6a5578895208622f65 Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:10:16 +0800 Subject: [PATCH 17/18] add generated artifacts for CGU update CGU has some additional dividers to help with ATPG and other things. --- utralib/src/generated/cramium_soc.rs | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/utralib/src/generated/cramium_soc.rs b/utralib/src/generated/cramium_soc.rs index 29c2ed1b9..d9345b7f0 100644 --- a/utralib/src/generated/cramium_soc.rs +++ b/utralib/src/generated/cramium_soc.rs @@ -2674,7 +2674,7 @@ pub mod utra { } pub mod sysctrl { - pub const SYSCTRL_NUMREGS: usize = 35; + pub const SYSCTRL_NUMREGS: usize = 37; pub const SFR_CGUSEC: crate::Register = crate::Register::new(0, 0xffff); pub const SFR_CGUSEC_SFR_CGUSEC: crate::Field = crate::Field::new(16, 0, SFR_CGUSEC); @@ -2715,8 +2715,14 @@ pub mod utra { pub const SFR_CGUSEL1: crate::Register = crate::Register::new(12, 0x1); pub const SFR_CGUSEL1_SFR_CGUSEL1: crate::Field = crate::Field::new(1, 0, SFR_CGUSEL1); - pub const SFR_CGUFDPKE: crate::Register = crate::Register::new(13, 0x1ff); - pub const SFR_CGUFDPKE_SFR_CGUFDPKE: crate::Field = crate::Field::new(9, 0, SFR_CGUFDPKE); + pub const SFR_CGUFDPKE: crate::Register = crate::Register::new(13, 0xff); + pub const SFR_CGUFDPKE_SFR_CGUFDPKE: crate::Field = crate::Field::new(8, 0, SFR_CGUFDPKE); + + pub const SFR_CGUFDAORAM: crate::Register = crate::Register::new(14, 0xffff); + pub const SFR_CGUFDAORAM_SFR_CGUFDAORAM: crate::Field = crate::Field::new(16, 0, SFR_CGUFDAORAM); + + pub const SFR_CGUFDPER: crate::Register = crate::Register::new(15, 0xffffff); + pub const SFR_CGUFDPER_SFR_CGUFDPER: crate::Field = crate::Field::new(24, 0, SFR_CGUFDPER); pub const SFR_CGUFSSR_FSFREQ0: crate::Register = crate::Register::new(16, 0xffff); pub const SFR_CGUFSSR_FSFREQ0_FSFREQ0: crate::Field = crate::Field::new(16, 0, SFR_CGUFSSR_FSFREQ0); @@ -13438,6 +13444,22 @@ mod tests { baz |= sysctrl_csr.ms(utra::sysctrl::SFR_CGUFDPKE_SFR_CGUFDPKE, 1); sysctrl_csr.wfo(utra::sysctrl::SFR_CGUFDPKE_SFR_CGUFDPKE, baz); + let foo = sysctrl_csr.r(utra::sysctrl::SFR_CGUFDAORAM); + sysctrl_csr.wo(utra::sysctrl::SFR_CGUFDAORAM, foo); + let bar = sysctrl_csr.rf(utra::sysctrl::SFR_CGUFDAORAM_SFR_CGUFDAORAM); + sysctrl_csr.rmwf(utra::sysctrl::SFR_CGUFDAORAM_SFR_CGUFDAORAM, bar); + let mut baz = sysctrl_csr.zf(utra::sysctrl::SFR_CGUFDAORAM_SFR_CGUFDAORAM, bar); + baz |= sysctrl_csr.ms(utra::sysctrl::SFR_CGUFDAORAM_SFR_CGUFDAORAM, 1); + sysctrl_csr.wfo(utra::sysctrl::SFR_CGUFDAORAM_SFR_CGUFDAORAM, baz); + + let foo = sysctrl_csr.r(utra::sysctrl::SFR_CGUFDPER); + sysctrl_csr.wo(utra::sysctrl::SFR_CGUFDPER, foo); + let bar = sysctrl_csr.rf(utra::sysctrl::SFR_CGUFDPER_SFR_CGUFDPER); + sysctrl_csr.rmwf(utra::sysctrl::SFR_CGUFDPER_SFR_CGUFDPER, bar); + let mut baz = sysctrl_csr.zf(utra::sysctrl::SFR_CGUFDPER_SFR_CGUFDPER, bar); + baz |= sysctrl_csr.ms(utra::sysctrl::SFR_CGUFDPER_SFR_CGUFDPER, 1); + sysctrl_csr.wfo(utra::sysctrl::SFR_CGUFDPER_SFR_CGUFDPER, baz); + let foo = sysctrl_csr.r(utra::sysctrl::SFR_CGUFSSR_FSFREQ0); sysctrl_csr.wo(utra::sysctrl::SFR_CGUFSSR_FSFREQ0, foo); let bar = sysctrl_csr.rf(utra::sysctrl::SFR_CGUFSSR_FSFREQ0_FSFREQ0); From 5f11dadde7e702ebea73346b0dae352dade04c2b Mon Sep 17 00:00:00 2001 From: bunnie Date: Fri, 3 Jan 2025 04:12:41 +0800 Subject: [PATCH 18/18] update todo --- services/usb-cramium/src/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/usb-cramium/src/main.rs b/services/usb-cramium/src/main.rs index a20401762..015b1923c 100644 --- a/services/usb-cramium/src/main.rs +++ b/services/usb-cramium/src/main.rs @@ -37,10 +37,6 @@ enum TimeoutOp { /* TODO: - - [ ] USB stack doesn't know when the cable is unplugged. This is a hardware issue right now - with MPW hardware. Wait until NTO chips come back to try and solve this? and if the bug isn't fixed - there we can either work around this by adding a sense I/O for this purpose or we can use a timer - to poll USB connection status. - [ ] Reduce debug spew. This is left in place for now because we will definitely need it in the future and it hasn't seemed to affect the stack operation like it did in the user-space implementation. */