use core::cell::RefCell;
use core::fmt::Write as _;
use embassy_sync::blocking_mutex::Mutex as BlockingMutex;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embedded_io_async::Write as _;
use heapless::String;
#[cfg(feature = "fire27")]
mod imp {
use esp_hal::{
Async, Blocking,
gpio::AnyPin,
peripherals::UART0,
uart::{Config, Uart, UartRx, UartTx},
};
pub type ConsoleRx<'d> = UartRx<'d, Blocking>;
pub type ConsoleTx<'d> = UartTx<'d, Blocking>;
pub type ConsoleTxAsync<'d> = UartTx<'d, Async>;
pub fn setup(
uart: UART0<'static>,
tx_pin: AnyPin<'static>,
rx_pin: AnyPin<'static>,
) -> (ConsoleRx<'static>, ConsoleTx<'static>) {
Uart::new(uart, Config::default().with_baudrate(1_000_000))
.expect("UART0 console init")
.with_tx(tx_pin)
.with_rx(rx_pin)
.split()
}
const UART0_FIFO_REG: *mut u32 = 0x3FF4_0000 as *mut u32;
const UART0_STATUS_REG: *const u32 = 0x3FF4_001C as *const u32;
const TX_FIFO_DEPTH: u32 = 128;
const PANIC_SPIN_PER_BYTE: u32 = 1_000_000;
pub fn boot_panic_write(bytes: &[u8]) {
for &b in bytes {
let mut budget = PANIC_SPIN_PER_BYTE;
while unsafe { (UART0_STATUS_REG.read_volatile() >> 16) & 0xFF } >= TX_FIFO_DEPTH - 2 {
budget -= 1;
if budget == 0 {
return; }
core::hint::spin_loop();
}
unsafe { UART0_FIFO_REG.write_volatile(b as u32) };
}
}
}
#[cfg(feature = "cores3")]
mod imp {
use esp_hal::{
Async,
peripherals::USB_DEVICE,
usb_serial_jtag::{UsbSerialJtag, UsbSerialJtagRx, UsbSerialJtagTx},
};
pub type ConsoleRx<'d> = UsbSerialJtagRx<'d, Async>;
pub type ConsoleTx<'d> = UsbSerialJtagTx<'d, Async>;
pub type ConsoleTxAsync<'d> = UsbSerialJtagTx<'d, Async>;
pub fn setup(usb: USB_DEVICE<'static>) -> (ConsoleRx<'static>, ConsoleTx<'static>) {
UsbSerialJtag::new(usb).into_async().split()
}
const SERIAL_JTAG_FIFO_REG: *mut u32 = 0x6003_8000 as *mut u32;
const SERIAL_JTAG_CONF_REG: *mut u32 = 0x6003_8004 as *mut u32;
const PANIC_SPIN_PER_FILL: u32 = 5_000_000;
#[inline]
fn fifo_full() -> bool {
unsafe { SERIAL_JTAG_CONF_REG.read_volatile() & 0b010 == 0 }
}
pub fn boot_panic_write(bytes: &[u8]) {
for &b in bytes {
if fifo_full() {
unsafe { SERIAL_JTAG_CONF_REG.write_volatile(0b001) }; let mut budget = PANIC_SPIN_PER_FILL;
while fifo_full() {
budget -= 1;
if budget == 0 {
return;
}
core::hint::spin_loop();
}
}
unsafe { SERIAL_JTAG_FIFO_REG.write_volatile(b as u32) };
}
unsafe { SERIAL_JTAG_CONF_REG.write_volatile(0b001) }; }
}
pub use imp::{ConsoleRx, ConsoleTx, ConsoleTxAsync, setup};
use imp::boot_panic_write;
const RING_SIZE: usize = 4096;
const LINE_CAP: usize = 352;
struct Ring {
buf: [u8; RING_SIZE],
head: usize, tail: usize, full: bool, dropped: u32,
}
impl Ring {
const fn new() -> Self {
Self { buf: [0; RING_SIZE], head: 0, tail: 0, full: false, dropped: 0 }
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.buf[self.head] = b;
self.head = (self.head + 1) % RING_SIZE;
if self.full {
self.tail = (self.tail + 1) % RING_SIZE;
self.dropped = self.dropped.saturating_add(1);
} else if self.head == self.tail {
self.full = true;
}
}
}
fn take_dropped(&mut self) -> u32 {
core::mem::take(&mut self.dropped)
}
fn is_empty(&self) -> bool {
!self.full && self.head == self.tail
}
fn used(&self) -> usize {
if self.full {
RING_SIZE
} else if self.head >= self.tail {
self.head - self.tail
} else {
RING_SIZE - self.tail + self.head
}
}
fn free(&self) -> usize {
RING_SIZE - self.used()
}
fn read_and_consume(&mut self, dst: &mut [u8]) -> usize {
if self.is_empty() {
return 0;
}
let n_contig = if self.head > self.tail {
self.head - self.tail
} else {
RING_SIZE - self.tail
};
let n = n_contig.min(dst.len());
let end = self.tail + n;
dst[..n].copy_from_slice(&self.buf[self.tail..end]);
self.tail = end % RING_SIZE;
if n > 0 {
self.full = false;
}
n
}
}
static RING: BlockingMutex<CriticalSectionRawMutex, RefCell<Ring>> =
BlockingMutex::new(RefCell::new(Ring::new()));
static DRAIN_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
static SPACE_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
fn format_line(level: &str, args: core::fmt::Arguments<'_>) -> String<LINE_CAP> {
let now = embassy_time::Instant::now();
let mut line: String<LINE_CAP> = String::new();
let _ = write!(
line,
"[{:05}.{:03} {:<5}] {}",
now.as_secs(),
now.as_millis() % 1000,
level,
args
);
while line.len() + 2 > LINE_CAP {
let _ = line.pop();
}
let _ = line.push_str("\r\n");
line
}
fn push_line(level: &str, args: core::fmt::Arguments<'_>) {
let line = format_line(level, args);
RING.lock(|r| r.borrow_mut().write(line.as_bytes()));
DRAIN_SIGNAL.signal(());
}
struct ConsoleLogger;
impl log::Log for ConsoleLogger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
let lvl = match record.level() {
log::Level::Error => "ERROR",
log::Level::Warn => "WARN ",
log::Level::Info => "INFO ",
log::Level::Debug => "DEBUG",
log::Level::Trace => "TRACE",
};
push_line(lvl, *record.args());
}
fn flush(&self) {}
}
static LOGGER: ConsoleLogger = ConsoleLogger;
pub fn init() {
let _ = log::set_logger(&LOGGER);
log::set_max_level(log::LevelFilter::Info);
}
pub fn enable_async() {}
pub async fn send_line(args: core::fmt::Arguments<'_>) {
let line = format_line("INFO ", args);
loop {
let wrote = RING.lock(|r| {
let mut r = r.borrow_mut();
if r.free() >= line.len() {
r.write(line.as_bytes());
true
} else {
false
}
});
if wrote {
DRAIN_SIGNAL.signal(());
return;
}
SPACE_SIGNAL.wait().await;
}
}
#[embassy_executor::task]
pub async fn drain_task(mut tx: ConsoleTxAsync<'static>) {
let mut scratch = [0u8; 256];
let mut drop_accum: u32 = 0;
let mut last_drop_report = embassy_time::Instant::now();
loop {
let (n, dropped) = RING.lock(|r| {
let mut r = r.borrow_mut();
let n = r.read_and_consume(&mut scratch);
(n, r.take_dropped())
});
drop_accum = drop_accum.saturating_add(dropped);
if drop_accum > 0 {
let now = embassy_time::Instant::now();
if n == 0 || (now - last_drop_report).as_millis() >= 250 {
let mut mark: String<48> = String::new();
let _ = write!(mark, "\r\n[CONSOLE-DROP {}B]\r\n", drop_accum);
let _ = tx.write_all(mark.as_bytes()).await;
drop_accum = 0;
last_drop_report = now;
}
}
if n == 0 {
DRAIN_SIGNAL.wait().await;
continue;
}
SPACE_SIGNAL.signal(());
let _ = tx.write_all(&scratch[..n]).await;
}
}
pub fn on_panic(info: &core::panic::PanicInfo<'_>) -> ! {
let mut line: String<256> = String::new();
let _ = write!(line, "\r\n[PANIC] {}\r\n", info);
RING.lock(|r| r.borrow_mut().write(line.as_bytes()));
loop {
let mut chunk = [0u8; 64];
let n = RING.lock(|r| r.borrow_mut().read_and_consume(&mut chunk));
if n == 0 {
break;
}
boot_panic_write(&chunk[..n]);
}
loop {
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}