Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
144 lines
6.3 KiB
Rust
144 lines
6.3 KiB
Rust
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
|
|
use core::mem::MaybeUninit;
|
|
|
|
// rustfmt-compatible cfg_select/cfg_if alternative
|
|
// Note: This macro is cfg_sel!({ }), not cfg_sel! { }.
|
|
// An extra brace is used in input to make contents rustfmt-able.
|
|
macro_rules! cfg_sel {
|
|
({#[cfg(else)] { $($output:tt)* }}) => {
|
|
$($output)*
|
|
};
|
|
({
|
|
#[cfg($cfg:meta)]
|
|
{ $($output:tt)* }
|
|
$($( $rest:tt )+)?
|
|
}) => {
|
|
#[cfg($cfg)]
|
|
cfg_sel! {{#[cfg(else)] { $($output)* }}}
|
|
$(
|
|
#[cfg(not($cfg))]
|
|
cfg_sel! {{ $($rest)+ }}
|
|
)?
|
|
};
|
|
}
|
|
|
|
// <[_]>::assume_init_ref requires Rust 1.93.
|
|
/// # Safety
|
|
///
|
|
/// Calling this when the content is not yet fully initialized causes undefined
|
|
/// behavior: it is up to the caller to guarantee that every `MaybeUninit<T>` in
|
|
/// the slice really is in an initialized state.
|
|
#[allow(dead_code)]
|
|
#[inline(always)]
|
|
pub(crate) const unsafe fn slice_assume_init_ref<T>(s: &[MaybeUninit<T>]) -> &[T] {
|
|
// SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that
|
|
// `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`.
|
|
// The pointer obtained is valid since it refers to memory owned by `slice` which is a
|
|
// reference and thus guaranteed to be valid for reads.
|
|
unsafe { &*(s as *const [MaybeUninit<T>] as *const [T]) }
|
|
}
|
|
// <[_]>::assume_init_mut requires Rust 1.93.
|
|
/// # Safety
|
|
///
|
|
/// Calling this when the content is not yet fully initialized causes undefined
|
|
/// behavior: it is up to the caller to guarantee that every `MaybeUninit<T>` in the
|
|
/// slice really is in an initialized state. For instance, `.assume_init_mut()` cannot
|
|
/// be used to initialize a `MaybeUninit` slice.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn slice_assume_init_mut<T>(s: &mut [MaybeUninit<T>]) -> &mut [T] {
|
|
// SAFETY: similar to safety notes for `slice_get_ref`, but we have a
|
|
// mutable reference which is also guaranteed to be valid for writes.
|
|
unsafe { &mut *(s as *mut [MaybeUninit<T>] as *mut [T]) }
|
|
}
|
|
|
|
// This module provides core::ptr strict_provenance/exposed_provenance polyfill for pre-1.84 rustc.
|
|
pub(crate) mod ptr {
|
|
cfg_sel!({
|
|
#[cfg(not(semihosting_no_strict_provenance))]
|
|
{
|
|
pub(crate) use core::ptr::{
|
|
with_exposed_provenance, with_exposed_provenance_mut, without_provenance_mut,
|
|
};
|
|
}
|
|
#[cfg(else)]
|
|
{
|
|
#[inline(always)]
|
|
#[must_use]
|
|
pub(crate) const fn without_provenance_mut<T>(addr: usize) -> *mut T {
|
|
// An int-to-pointer transmute currently has exactly the intended semantics: it creates a
|
|
// pointer without provenance. Note that this is *not* a stable guarantee about transmute
|
|
// semantics, it relies on sysroot crates having special status.
|
|
// SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that
|
|
// pointer).
|
|
#[cfg(miri)]
|
|
unsafe {
|
|
core::mem::transmute(addr)
|
|
}
|
|
// Using transmute doesn't work with CHERI: https://github.com/kent-weak-memory/rust/blob/0c0ca909de877f889629057e1ddf139527446d75/library/core/src/ptr/mod.rs#L607
|
|
#[cfg(not(miri))]
|
|
{
|
|
addr as *mut T
|
|
}
|
|
}
|
|
#[inline(always)]
|
|
#[must_use]
|
|
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
|
|
pub(crate) fn with_exposed_provenance<T>(addr: usize) -> *const T {
|
|
addr as *const T
|
|
}
|
|
#[inline(always)]
|
|
#[must_use]
|
|
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
|
|
pub(crate) fn with_exposed_provenance_mut<T>(addr: usize) -> *mut T {
|
|
addr as *mut T
|
|
}
|
|
|
|
pub(crate) trait PtrExt<T: ?Sized>: Copy {
|
|
#[must_use]
|
|
fn addr(self) -> usize;
|
|
}
|
|
impl<T: ?Sized> PtrExt<T> for *const T {
|
|
#[inline(always)]
|
|
#[must_use]
|
|
fn addr(self) -> usize {
|
|
// A pointer-to-integer transmute currently has exactly the right semantics: it returns the
|
|
// address without exposing the provenance. Note that this is *not* a stable guarantee about
|
|
// transmute semantics, it relies on sysroot crates having special status.
|
|
// SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the
|
|
// provenance).
|
|
#[cfg(miri)]
|
|
unsafe {
|
|
core::mem::transmute(self.cast::<()>())
|
|
}
|
|
// Using transmute doesn't work with CHERI: https://github.com/kent-weak-memory/rust/blob/0c0ca909de877f889629057e1ddf139527446d75/library/core/src/ptr/mut_ptr.rs#L210
|
|
#[cfg(not(miri))]
|
|
{
|
|
self.cast::<()>() as usize
|
|
}
|
|
}
|
|
}
|
|
impl<T: ?Sized> PtrExt<T> for *mut T {
|
|
#[inline(always)]
|
|
#[must_use]
|
|
fn addr(self) -> usize {
|
|
// A pointer-to-integer transmute currently has exactly the right semantics: it returns the
|
|
// address without exposing the provenance. Note that this is *not* a stable guarantee about
|
|
// transmute semantics, it relies on sysroot crates having special status.
|
|
// SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the
|
|
// provenance).
|
|
#[cfg(miri)]
|
|
unsafe {
|
|
core::mem::transmute(self.cast::<()>())
|
|
}
|
|
// Using transmute doesn't work with CHERI: https://github.com/kent-weak-memory/rust/blob/0c0ca909de877f889629057e1ddf139527446d75/library/core/src/ptr/mut_ptr.rs#L210
|
|
#[cfg(not(miri))]
|
|
{
|
|
self.cast::<()>() as usize
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|