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>
271 lines
9.8 KiB
Rust
271 lines
9.8 KiB
Rust
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(transparent)]
|
|
pub(crate) struct CpuInfo(u32);
|
|
|
|
impl CpuInfo {
|
|
#[inline]
|
|
fn set(&mut self, bit: CpuInfoFlag) {
|
|
self.0 = set(self.0, bit as u32);
|
|
}
|
|
#[inline]
|
|
#[must_use]
|
|
fn test(self, bit: CpuInfoFlag) -> bool {
|
|
test(self.0, bit as u32)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
#[must_use]
|
|
fn set(x: u32, bit: u32) -> u32 {
|
|
x | (1 << bit)
|
|
}
|
|
#[inline]
|
|
#[must_use]
|
|
fn test(x: u32, bit: u32) -> bool {
|
|
x & (1 << bit) != 0
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn detect() -> CpuInfo {
|
|
use core::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
static CACHE: AtomicU32 = AtomicU32::new(0);
|
|
let mut info = CpuInfo(CACHE.load(Ordering::Relaxed));
|
|
if info.0 != 0 {
|
|
return info;
|
|
}
|
|
info.set(CpuInfoFlag::Init);
|
|
// Note: detect_false cfg is intended to make it easy for developers to test
|
|
// cases where features usually available is not available, and is not a public API.
|
|
if !cfg!(portable_atomic_test_detect_false) {
|
|
_detect(&mut info);
|
|
}
|
|
CACHE.store(info.0, Ordering::Relaxed);
|
|
info
|
|
}
|
|
|
|
macro_rules! flags {
|
|
($(
|
|
$(#[$attr:meta])*
|
|
$func:ident($name:literal, any($($cfg:ident),*)),
|
|
)*) => {
|
|
#[allow(dead_code, non_camel_case_types)]
|
|
#[derive(Clone, Copy)]
|
|
#[cfg_attr(test, derive(PartialEq, Eq, PartialOrd, Ord))]
|
|
#[repr(u32)]
|
|
enum CpuInfoFlag {
|
|
Init = 0,
|
|
$($func,)*
|
|
}
|
|
impl CpuInfo {
|
|
$(
|
|
$(#[$attr])*
|
|
#[cfg(any(test, not(any($($cfg = $name),*))))]
|
|
#[inline]
|
|
#[must_use]
|
|
pub(crate) fn $func(self) -> bool {
|
|
self.test(CpuInfoFlag::$func)
|
|
}
|
|
)*
|
|
#[cfg(test)] // for test
|
|
const ALL_FLAGS: &'static [(&'static str, CpuInfoFlag, bool)] = &[$(
|
|
($name, CpuInfoFlag::$func, cfg!(any($($cfg = $name),*))),
|
|
)*];
|
|
}
|
|
#[test]
|
|
#[cfg_attr(portable_atomic_test_detect_false, ignore = "detection disabled")]
|
|
fn test_detect() {$(
|
|
$(#[$attr])*
|
|
{
|
|
const _: u32 = 1_u32 << CpuInfoFlag::$func as u32;
|
|
assert_eq!($name.replace(|c: char| c == '-' || c == '.', "_"), stringify!($func));
|
|
if detect().$func() {
|
|
assert!(detect().test(CpuInfoFlag::$func));
|
|
} else {
|
|
assert!(!detect().test(CpuInfoFlag::$func));
|
|
}
|
|
}
|
|
)*}
|
|
};
|
|
}
|
|
|
|
// rustc definitions: https://github.com/rust-lang/rust/blob/ddaf12390d3ffb7d5ba74491a48f3cd528e5d777/compiler/rustc_target/src/target_features.rs
|
|
|
|
// LLVM definitions: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/llvm/lib/Target/AArch64/AArch64Features.td
|
|
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
|
|
flags! {
|
|
// The Armv8.1 architecture extension
|
|
// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions/The-Armv8-1-architecture-extension
|
|
// FEAT_LSE, Large System Extensions
|
|
// > This feature is supported in AArch64 state only.
|
|
// > FEAT_LSE is OPTIONAL from Armv8.0.
|
|
// > FEAT_LSE is mandatory from Armv8.1.
|
|
lse("lse", any(target_feature /* 1.61+ */, portable_atomic_target_feature)),
|
|
|
|
// The Armv8.3 architecture extension
|
|
// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions/The-Armv8-3-architecture-extension
|
|
// FEAT_LRCPC, Load-Acquire RCpc instructions
|
|
// > These instructions are added to the A64 instruction set only.
|
|
// > FEAT_LRCPC is OPTIONAL from Armv8.2.
|
|
// > FEAT_LRCPC is mandatory from Armv8.3.
|
|
#[cfg(test)] // test-only
|
|
rcpc("rcpc", any(target_feature /* 1.61+ */)),
|
|
|
|
// The Armv8.4 architecture extension
|
|
// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions/The-Armv8-4-architecture-extension
|
|
// FEAT_LSE2, Large System Extensions version 2
|
|
// > This feature is supported in AArch64 state only.
|
|
// > FEAT_LSE2 is OPTIONAL from Armv8.2.
|
|
// > FEAT_LSE2 is mandatory from Armv8.4.
|
|
#[cfg_attr(not(test), allow(dead_code))]
|
|
lse2("lse2", any(target_feature /* nightly */, portable_atomic_target_feature)),
|
|
// FEAT_LRCPC2, Load-Acquire RCpc instructions version 2
|
|
// > These instructions are added to the A64 instruction set only.
|
|
// > FEAT_LRCPC2 is OPTIONAL from Armv8.2.
|
|
// > FEAT_LRCPC2 is mandatory from Armv8.4.
|
|
// > If FEAT_LRCPC2 is implemented, then FEAT_LRCPC is implemented.
|
|
#[cfg(test)] // test-only
|
|
rcpc2("rcpc2", any(target_feature /* 1.61+ */)),
|
|
|
|
// The Armv8.9 architecture extension
|
|
// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions/The-Armv8-9-architecture-extension
|
|
// FEAT_LRCPC3, Load-Acquire RCpc instructions version 3
|
|
// > This feature is supported in AArch64 state only.
|
|
// > FEAT_LRCPC3 is OPTIONAL from Armv8.2.
|
|
// > If FEAT_LRCPC3 is implemented, then FEAT_LRCPC2 is implemented.
|
|
#[cfg_attr(not(test), allow(dead_code))]
|
|
rcpc3("rcpc3", any(target_feature /* nightly */, portable_atomic_target_feature)),
|
|
|
|
// The Armv9.4 architecture extension
|
|
// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions/The-Armv9-4-architecture-extension
|
|
// FEAT_LSE128, 128-bit Atomics
|
|
// > This feature is supported in AArch64 state only.
|
|
// > FEAT_LSE128 is OPTIONAL from Armv9.3.
|
|
// > If FEAT_LSE128 is implemented, then FEAT_LSE is implemented.
|
|
#[cfg_attr(not(test), allow(dead_code))]
|
|
lse128("lse128", any(target_feature /* nightly */, portable_atomic_target_feature)),
|
|
|
|
// The Armv9.6 architecture extension
|
|
// https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions/The-Armv9-6-architecture-extension
|
|
// FEAT_LSFE, Large System Float Extension
|
|
// > This feature is supported in AArch64 state only.
|
|
// > FEAT_LSFE is OPTIONAL from Armv9.3.
|
|
// > If FEAT_LSFE is implemented, then FEAT_FP is implemented.
|
|
#[cfg(test)] // test-only
|
|
lsfe("lsfe", any(target_feature /* N/A */, portable_atomic_target_feature)),
|
|
|
|
#[cfg(test)] // test-only
|
|
cpuid("cpuid", any(/* no corresponding target feature */)),
|
|
}
|
|
|
|
// LLVM definitions: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/llvm/lib/Target/ARM/ARMFeatures.td
|
|
#[cfg(target_arch = "arm")]
|
|
flags! {
|
|
#[cfg(test)] // test-only
|
|
lpae("lpae", any(/* no corresponding target feature */)),
|
|
}
|
|
|
|
// LLVM definitions: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/llvm/lib/Target/PowerPC/PPC.td
|
|
#[cfg(target_arch = "powerpc64")]
|
|
flags! {
|
|
// lqarx and stqcx.
|
|
quadword_atomics("quadword-atomics", any(target_feature /* nightly */, portable_atomic_target_feature)),
|
|
}
|
|
|
|
// LLVM definitions: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/llvm/lib/Target/RISCV/RISCVFeatures.td
|
|
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
|
|
flags! {
|
|
// amocas.{w,d,q}
|
|
zacas("zacas", any(target_feature /* 1.94+ */, portable_atomic_target_feature)),
|
|
#[cfg(test)] // test-only
|
|
zabha("zabha", any(target_feature /* 1.94+ */, portable_atomic_target_feature)),
|
|
#[cfg(test)] // test-only
|
|
zalasr("zalasr", any(/* no corresponding target feature */)),
|
|
}
|
|
|
|
// LLVM definitions: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/llvm/lib/Target/X86/X86.td
|
|
#[cfg(target_arch = "x86_64")]
|
|
flags! {
|
|
// avx
|
|
#[cfg(target_feature = "sse")]
|
|
avx("avx", any(target_feature)),
|
|
// cmpxchg16b
|
|
cmpxchg16b("cmpxchg16b", any(target_feature /* 1.69+ */, portable_atomic_target_feature)),
|
|
}
|
|
|
|
#[allow(
|
|
clippy::alloc_instead_of_core,
|
|
clippy::std_instead_of_alloc,
|
|
clippy::std_instead_of_core,
|
|
clippy::undocumented_unsafe_blocks,
|
|
clippy::wildcard_imports
|
|
)]
|
|
#[cfg(test)]
|
|
mod tests_common {
|
|
use std::{collections::BTreeSet, vec};
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_bit_flags() {
|
|
let mut flags = vec![("init", CpuInfoFlag::Init)];
|
|
flags.extend(CpuInfo::ALL_FLAGS.iter().map(|&(name, flag, _)| (name, flag)));
|
|
let flag_set = flags.iter().map(|(_, flag)| flag).collect::<BTreeSet<_>>();
|
|
let name_set = flags.iter().map(|(_, flag)| flag).collect::<BTreeSet<_>>();
|
|
if flag_set.len() != flags.len() {
|
|
panic!("CpuInfo flag values must be unique")
|
|
}
|
|
if name_set.len() != flags.len() {
|
|
panic!("CpuInfo flag names must be unique")
|
|
}
|
|
|
|
let mut x = CpuInfo(0);
|
|
for &(_, f) in &flags {
|
|
assert!(!x.test(f));
|
|
}
|
|
for i in 0..flags.len() {
|
|
x.set(flags[i].1);
|
|
for &(_, f) in &flags[..i + 1] {
|
|
assert!(x.test(f));
|
|
}
|
|
for &(_, f) in &flags[i + 1..] {
|
|
assert!(!x.test(f));
|
|
}
|
|
}
|
|
for &(_, f) in &flags {
|
|
assert!(x.test(f));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn print_features() {
|
|
use std::{fmt::Write as _, string::String};
|
|
|
|
let mut features = String::new();
|
|
features.push_str("\nfeatures:\n");
|
|
for &(name, flag, compile_time) in CpuInfo::ALL_FLAGS {
|
|
let run_time = detect().test(flag);
|
|
if run_time == compile_time {
|
|
let _ = writeln!(features, " {}: {}", name, run_time);
|
|
} else {
|
|
let _ = writeln!(
|
|
features,
|
|
" {}: {} (compile-time), {} (run-time)",
|
|
name, compile_time, run_time
|
|
);
|
|
}
|
|
}
|
|
test_helper::eprintln_nocapture!("{}", features);
|
|
}
|
|
|
|
// Static assertions for C type definitions.
|
|
// Assertions with core::ffi types are in crate::utils::ffi module.
|
|
#[cfg(not(any(windows, target_arch = "x86", target_arch = "x86_64")))]
|
|
const _: fn() = || {
|
|
use test_helper::sys;
|
|
let _: crate::utils::ffi::c_char = 0 as sys::c_char;
|
|
};
|
|
}
|