#![doc = include_str!("../README.md")] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] #![allow(rustdoc::bare_urls)] #![no_std] #[cfg(feature = "defmt-espflash")] pub mod defmt; #[cfg(feature = "log")] pub mod logger; /// Prints to the selected output, with a newline. #[cfg(not(feature = "no-op"))] #[macro_export] macro_rules! println { ($($arg:tt)*) => {{ { use core::fmt::Write; writeln!($crate::Printer, $($arg)*).ok(); } }}; } /// Prints to the selected output. #[cfg(not(feature = "no-op"))] #[macro_export] macro_rules! print { ($($arg:tt)*) => {{ { use core::fmt::Write; write!($crate::Printer, $($arg)*).ok(); } }}; } /// Prints to the configured output, with a newline. #[cfg(feature = "no-op")] #[macro_export] macro_rules! println { ($($arg:tt)*) => {{}}; } /// Prints to the configured output. #[cfg(feature = "no-op")] #[macro_export] macro_rules! print { ($($arg:tt)*) => {{}}; } /// Prints and returns the value of a given expression for quick and dirty /// debugging. // implementation adapted from `std::dbg` #[macro_export] macro_rules! dbg { // NOTE: We cannot use `concat!` to make a static string as a format argument // of `eprintln!` because `file!` could contain a `{` or // `$val` expression could be a block (`{ .. }`), in which case the `println!` // will be malformed. () => { $crate::println!("[{}:{}]", ::core::file!(), ::core::line!()) }; ($val:expr $(,)?) => { // Use of `match` here is intentional because it affects the lifetimes // of temporaries - https://stackoverflow.com/a/48732525/1063961 match $val { tmp => { $crate::println!("[{}:{}] {} = {:#?}", ::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp); tmp } } }; ($($val:expr),+ $(,)?) => { ($($crate::dbg!($val)),+,) }; } /// The printer that is used by the `print!` and `println!` macros. pub struct Printer; impl core::fmt::Write for Printer { fn write_str(&mut self, s: &str) -> core::fmt::Result { Printer::write_bytes(s.as_bytes()); Ok(()) } } impl Printer { /// Writes a byte slice to the configured output. pub fn write_bytes(bytes: &[u8]) { with(|token| { PrinterImpl::write_bytes_in_cs(bytes, token); PrinterImpl::flush(token); }) } } #[cfg(feature = "jtag-serial")] type PrinterImpl = serial_jtag_printer::Printer; #[cfg(feature = "uart")] type PrinterImpl = uart_printer::Printer; #[cfg(feature = "auto")] type PrinterImpl = auto_printer::Printer; #[cfg(all( feature = "auto", any( feature = "esp32c3", feature = "esp32c6", feature = "esp32h2", feature = "esp32p4", // as mentioned in 'build.rs' feature = "esp32s3" ) ))] mod auto_printer { use crate::{ serial_jtag_printer::Printer as PrinterSerialJtag, uart_printer::Printer as PrinterUart, LockToken, }; pub struct Printer; impl Printer { fn use_jtag() -> bool { // Decide if serial-jtag is used by checking SOF interrupt flag. // SOF packet is sent by the HOST every 1ms on a full speed bus. // Between two consecutive ticks, there will be at least 1ms (selectable tick // rate range is 1 - 1000Hz). // We don't reset the flag - if it was ever connected we assume serial-jtag is // used #[cfg(feature = "esp32c3")] const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32; #[cfg(feature = "esp32c6")] const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32; #[cfg(feature = "esp32h2")] const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32; #[cfg(feature = "esp32p4")] const USB_DEVICE_INT_RAW: *const u32 = unimplemented!(); #[cfg(feature = "esp32s3")] const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32; const SOF_INT_MASK: u32 = 0b10; unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 } } pub fn write_bytes_in_cs(bytes: &[u8], token: LockToken<'_>) { if Self::use_jtag() { PrinterSerialJtag::write_bytes_in_cs(bytes, token); } else { PrinterUart::write_bytes_in_cs(bytes, token); } } pub fn flush(token: LockToken<'_>) { if Self::use_jtag() { PrinterSerialJtag::flush(token); } else { PrinterUart::flush(token); } } } } #[cfg(all( feature = "auto", not(any( feature = "esp32c3", feature = "esp32c6", feature = "esp32h2", feature = "esp32p4", feature = "esp32s3" )) ))] mod auto_printer { // models that only have UART pub type Printer = crate::uart_printer::Printer; } #[cfg(all( any(feature = "jtag-serial", feature = "auto"), any( feature = "esp32c3", feature = "esp32c6", feature = "esp32h2", feature = "esp32p4", feature = "esp32s3" ) ))] mod serial_jtag_printer { use portable_atomic::{AtomicBool, Ordering}; use super::LockToken; pub struct Printer; #[cfg(feature = "esp32c3")] const SERIAL_JTAG_FIFO_REG: usize = 0x6004_3000; #[cfg(feature = "esp32c3")] const SERIAL_JTAG_CONF_REG: usize = 0x6004_3004; #[cfg(any(feature = "esp32c6", feature = "esp32h2"))] const SERIAL_JTAG_FIFO_REG: usize = 0x6000_F000; #[cfg(any(feature = "esp32c6", feature = "esp32h2"))] const SERIAL_JTAG_CONF_REG: usize = 0x6000_F004; #[cfg(feature = "esp32p4")] const SERIAL_JTAG_FIFO_REG: usize = 0x500D_2000; #[cfg(feature = "esp32p4")] const SERIAL_JTAG_CONF_REG: usize = 0x500D_2004; #[cfg(feature = "esp32s3")] const SERIAL_JTAG_FIFO_REG: usize = 0x6003_8000; #[cfg(feature = "esp32s3")] const SERIAL_JTAG_CONF_REG: usize = 0x6003_8004; /// A previous wait has timed out. We use this flag to avoid blocking /// forever if there is no host attached. static TIMED_OUT: AtomicBool = AtomicBool::new(false); fn fifo_flush() { let conf = SERIAL_JTAG_CONF_REG as *mut u32; unsafe { conf.write_volatile(0b001) }; } fn fifo_full() -> bool { let conf = SERIAL_JTAG_CONF_REG as *mut u32; unsafe { conf.read_volatile() & 0b010 == 0b000 } } fn fifo_write(byte: u8) { let fifo = SERIAL_JTAG_FIFO_REG as *mut u32; unsafe { fifo.write_volatile(byte as u32) } } fn wait_for_flush() -> bool { const TIMEOUT_ITERATIONS: usize = 50_000; // Wait for some time for the FIFO to clear. let mut timeout = TIMEOUT_ITERATIONS; while fifo_full() { if timeout == 0 { TIMED_OUT.store(true, Ordering::Relaxed); return false; } timeout -= 1; } true } impl Printer { pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) { if fifo_full() { // The FIFO is full. Let's see if we can progress. if TIMED_OUT.load(Ordering::Relaxed) { // Still wasn't able to drain the FIFO. Let's assume we won't be able to, and // don't queue up more data. // This is important so we don't block forever if there is no host attached. return; } // Give the fifo some time to drain. if !wait_for_flush() { return; } } else { // Reset the flag - we managed to clear our FIFO. TIMED_OUT.store(false, Ordering::Relaxed); } for &b in bytes { if fifo_full() { fifo_flush(); // Wait for the FIFO to clear, we have more data to shift out. if !wait_for_flush() { return; } } fifo_write(b); } } pub fn flush(_token: LockToken<'_>) { fifo_flush(); } } } #[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32"))] mod uart_printer { use super::LockToken; const UART_TX_ONE_CHAR: usize = 0x4000_9200; pub struct Printer; impl Printer { pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) { for &b in bytes { unsafe { let uart_tx_one_char: unsafe extern "C" fn(u8) -> i32 = core::mem::transmute(UART_TX_ONE_CHAR); uart_tx_one_char(b) }; } } pub fn flush(_token: LockToken<'_>) {} } } #[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32s2"))] mod uart_printer { use super::LockToken; pub struct Printer; impl Printer { pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) { // On ESP32-S2 the UART_TX_ONE_CHAR ROM-function seems to have some issues. for chunk in bytes.chunks(64) { for &b in chunk { unsafe { // write FIFO (0x3f400000 as *mut u32).write_volatile(b as u32); }; } // wait for TX_DONE while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {} unsafe { // reset TX_DONE (0x3f400010 as *mut u32).write_volatile(1 << 14); } } } pub fn flush(_token: LockToken<'_>) {} } } #[cfg(all( any(feature = "uart", feature = "auto"), not(any(feature = "esp32", feature = "esp32s2")) ))] mod uart_printer { use super::LockToken; trait Functions { const TX_ONE_CHAR: usize; const CHUNK_SIZE: usize = 32; fn tx_byte(b: u8) { unsafe { let tx_one_char: unsafe extern "C" fn(u8) -> i32 = core::mem::transmute(Self::TX_ONE_CHAR); tx_one_char(b); } } fn flush(); } struct Device; #[cfg(feature = "esp32c2")] impl Functions for Device { const TX_ONE_CHAR: usize = 0x4000_005C; fn flush() { // tx_one_char waits for empty } } #[cfg(feature = "esp32c3")] impl Functions for Device { const TX_ONE_CHAR: usize = 0x4000_0068; fn flush() { unsafe { const TX_FLUSH: usize = 0x4000_0080; const GET_CHANNEL: usize = 0x4000_058C; let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH); let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL); const G_USB_PRINT_ADDR: usize = 0x3FCD_FFD0; let g_usb_print = G_USB_PRINT_ADDR as *mut bool; let channel = if *g_usb_print { // Flush USB-JTAG 3 } else { get_channel() }; tx_flush(channel); } } } #[cfg(feature = "esp32s3")] impl Functions for Device { const TX_ONE_CHAR: usize = 0x4000_0648; fn flush() { unsafe { const TX_FLUSH: usize = 0x4000_0690; const GET_CHANNEL: usize = 0x4000_1A58; let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH); let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL); const G_USB_PRINT_ADDR: usize = 0x3FCE_FFB8; let g_usb_print = G_USB_PRINT_ADDR as *mut bool; let channel = if *g_usb_print { // Flush USB-JTAG 4 } else { get_channel() }; tx_flush(channel); } } } #[cfg(any(feature = "esp32c6", feature = "esp32h2"))] impl Functions for Device { const TX_ONE_CHAR: usize = 0x4000_0058; fn flush() { unsafe { const TX_FLUSH: usize = 0x4000_0074; const GET_CHANNEL: usize = 0x4000_003C; let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH); let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL); tx_flush(get_channel()); } } } #[cfg(feature = "esp32p4")] impl Functions for Device { const TX_ONE_CHAR: usize = 0x4FC0_0054; fn flush() { unsafe { const TX_FLUSH: usize = 0x4FC0_0074; const GET_CHANNEL: usize = 0x4FC0_0038; let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH); let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL); tx_flush(get_channel()); } } } pub struct Printer; impl Printer { pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) { for chunk in bytes.chunks(Device::CHUNK_SIZE) { for &b in chunk { Device::tx_byte(b); } Device::flush(); } } pub fn flush(_token: LockToken<'_>) {} } } #[cfg(not(feature = "critical-section"))] type LockInner<'a> = PhantomData<&'a ()>; #[cfg(feature = "critical-section")] type LockInner<'a> = critical_section::CriticalSection<'a>; #[derive(Clone, Copy)] struct LockToken<'a>(LockInner<'a>); impl LockToken<'_> { #[allow(unused)] unsafe fn conjure() -> Self { #[cfg(feature = "critical-section")] let inner = critical_section::CriticalSection::new(); #[cfg(not(feature = "critical-section"))] let inner = PhantomData; LockToken(inner) } } /// Runs the callback in a critical section, if enabled. #[inline] fn with(f: impl FnOnce(LockToken) -> R) -> R { #[cfg(feature = "critical-section")] return critical_section::with(|cs| f(LockToken(cs))); #[cfg(not(feature = "critical-section"))] f(unsafe { LockToken::conjure() }) }