* test ci * fix ci with new compiler * god bless clippy fix * refmuts * clippy * tests work * use new compiler * use new compiler * bump MSRV of esp-hal and crates that depend on esp-hal * fix eyesore * clippy again * remove hardcoded compiler version * bump rust-version as well * note MSRV bump in changelog
502 lines
14 KiB
Rust
502 lines
14 KiB
Rust
#![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<R>(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() })
|
|
}
|