use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[cfg(feature = "schemars")]
use alloc::borrow::Cow;
#[cfg(feature = "schemars")]
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct FirmwareConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub ice: Option<IceConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fire: Option<FireConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub led: Option<LedConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub swd: Option<DebugConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_alg_params: Option<ServeAlgParams>,
}
impl FirmwareConfig {
pub fn from_bytes(buf: &[u8]) -> Result<Self, String> {
if buf.len() < 64 {
return Err(format!("Buffer too small: {} bytes", buf.len()));
}
let mut offset = 0;
let override_present = &buf[offset..offset + 8];
offset += 8;
let ice_freq = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
offset += 2;
let fire_freq = u16::from_le_bytes([buf[offset], buf[offset + 1]]);
offset += 2;
let fire_vreq = buf[offset];
offset += 1;
offset += 3;
let override_value = &buf[offset..offset + 8];
let ice_config =
if ((override_present[0] & (1 << 0)) != 0) || ((override_present[0] & (1 << 1)) != 0) {
let mut ice_config = IceConfig::default();
if (override_present[0] & (1 << 0)) != 0 {
ice_config.cpu_freq = Some(
ice_freq
.try_into()
.map_err(|_| format!("Invalid ice_freq: {}", ice_freq))?,
);
}
if (override_present[0] & (1 << 1)) != 0 {
ice_config.overclock = Some((override_value[0] & (1 << 0)) != 0);
}
Some(ice_config)
} else {
None
};
let fire_config = if ((override_present[0] & (1 << 2)) != 0)
|| ((override_present[0] & (1 << 3)) != 0)
|| ((override_present[0] & (1 << 4)) != 0)
|| ((override_present[0] & (1 << 7)) != 0)
{
let mut fire_config = FireConfig::default();
if (override_present[0] & (1 << 2)) != 0 {
fire_config.cpu_freq = Some(
fire_freq
.try_into()
.map_err(|_| format!("Invalid fire_freq: {}", fire_freq))?,
);
}
if (override_present[0] & (1 << 3)) != 0 {
fire_config.overclock = Some((override_value[0] & (1 << 1)) != 0);
}
if (override_present[0] & (1 << 4)) != 0 {
fire_config.vreg = Some(
fire_vreq
.try_into()
.map_err(|_| format!("Invalid fire_vreq: {}", fire_vreq))?,
);
}
if (override_present[0] & (1 << 7)) != 0 {
fire_config.serve_mode = Some(if (override_value[0] & (1 << 4)) != 0 {
FireServeMode::Pio
} else {
FireServeMode::Cpu
});
}
if (override_present[1] & (1 << 0)) != 0 {
fire_config.rom_dma_preload = (override_value[0] & (1 << 5)) != 0;
}
if (override_present[1] & (1 << 1)) != 0 {
fire_config.force_16_bit = (override_value[0] & (1 << 6)) != 0;
}
Some(fire_config)
} else {
None
};
let led = if (override_present[0] & (1 << 5)) != 0 {
Some(LedConfig {
enabled: (override_value[0] & (1 << 2)) != 0,
})
} else {
None
};
let swd = if (override_present[0] & (1 << 6)) != 0 {
Some(DebugConfig {
swd_enabled: (override_value[0] & (1 << 3)) != 0,
})
} else {
None
};
Ok(FirmwareConfig {
ice: ice_config,
fire: fire_config,
led,
swd,
serve_alg_params: None, })
}
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IceConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu_freq: Option<IceCpuFreq>,
#[serde(default)]
#[serde(skip_serializing_if = "is_none_or_is_false")]
pub overclock: Option<bool>,
}
fn is_none_or_is_false(v: &Option<bool>) -> bool {
if let Some(val) = v { !val } else { true }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct FireConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu_freq: Option<FireCpuFreq>,
#[serde(default, skip_serializing_if = "is_none_or_is_false")]
pub overclock: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vreg: Option<FireVreg>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serve_mode: Option<FireServeMode>,
#[serde(default = "default_true", skip_serializing_if = "is_true")]
pub rom_dma_preload: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub force_16_bit: bool,
}
fn is_true(v: &bool) -> bool {
*v
}
fn is_false(v: &bool) -> bool {
!*v
}
impl Default for FireConfig {
fn default() -> Self {
Self {
cpu_freq: None,
overclock: None,
vreg: None,
serve_mode: None,
rom_dma_preload: true,
force_16_bit: false,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum FireServeMode {
Pio,
Cpu,
}
impl core::fmt::Display for FireServeMode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FireServeMode::Pio => write!(f, "PIO"),
FireServeMode::Cpu => write!(f, "CPU"),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct LedConfig {
#[serde(default = "default_true")]
pub enabled: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct DebugConfig {
#[serde(default = "default_true")]
pub swd_enabled: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ServeAlgParams {
pub params: Vec<u8>,
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IceCpuFreq(u16);
impl IceCpuFreq {
pub const NONE: u16 = 0;
pub const STOCK: u16 = 0xFFFF;
pub const MIN_MHZ: u16 = 1;
pub const MAX_MHZ: u16 = 450;
pub fn none() -> Self {
Self(Self::NONE)
}
pub fn stock() -> Self {
Self(Self::STOCK)
}
pub fn mhz(freq: u16) -> Result<Self, InvalidFreq> {
if (Self::MIN_MHZ..=Self::MAX_MHZ).contains(&freq) {
Ok(Self(freq))
} else {
Err(InvalidFreq(freq))
}
}
pub fn is_none(&self) -> bool {
self.0 == Self::NONE
}
pub fn is_stock(&self) -> bool {
self.0 == Self::STOCK
}
pub fn get(&self) -> u16 {
self.0
}
}
impl Default for IceCpuFreq {
fn default() -> Self {
Self::stock()
}
}
impl TryFrom<u16> for IceCpuFreq {
type Error = InvalidFreq;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
Self::NONE => Ok(Self::none()),
Self::STOCK => Ok(Self::stock()),
freq if (Self::MIN_MHZ..=Self::MAX_MHZ).contains(&freq) => Ok(Self(freq)),
_ => Err(InvalidFreq(value)),
}
}
}
impl From<IceCpuFreq> for u16 {
fn from(freq: IceCpuFreq) -> u16 {
freq.0
}
}
impl serde::Serialize for IceCpuFreq {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self.0 {
Self::NONE => "None".to_string(),
Self::STOCK => "Stock".to_string(),
freq => format!("{}MHz", freq),
};
serializer.serialize_str(&s)
}
}
impl<'de> serde::Deserialize<'de> for IceCpuFreq {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"None" => Ok(Self::none()),
"Stock" => Ok(Self::stock()),
_ => {
if let Some(freq_str) = s.strip_suffix("MHz") {
let freq = freq_str.parse::<u16>().map_err(|_| {
serde::de::Error::custom(format!("Invalid frequency: {}", s))
})?;
Self::mhz(freq).map_err(|_| {
serde::de::Error::custom(format!(
"Frequency must be between {}MHz and {}MHz",
Self::MIN_MHZ,
Self::MAX_MHZ
))
})
} else {
Err(serde::de::Error::custom(format!(
"Invalid frequency format: {}",
s
)))
}
}
}
}
}
#[cfg(feature = "schemars")]
impl JsonSchema for IceCpuFreq {
fn schema_name() -> Cow<'static, str> {
"IceCpuFreq".into()
}
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"description": format!(
"CPU frequency: 'None', 'Stock', or '{{n}}MHz' where n is {}-{}",
Self::MIN_MHZ,
Self::MAX_MHZ
)
})
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FireCpuFreq(u16);
impl FireCpuFreq {
pub const NONE: u16 = 0;
pub const STOCK: u16 = 0xFFFF;
pub const MIN_MHZ: u16 = 16;
pub const MAX_MHZ: u16 = 800;
pub fn none() -> Self {
Self(Self::NONE)
}
pub fn stock() -> Self {
Self(Self::STOCK)
}
pub fn mhz(freq: u16) -> Result<Self, InvalidFreq> {
if (Self::MIN_MHZ..=Self::MAX_MHZ).contains(&freq) {
Ok(Self(freq))
} else {
Err(InvalidFreq(freq))
}
}
pub fn is_none(&self) -> bool {
self.0 == Self::NONE
}
pub fn is_stock(&self) -> bool {
self.0 == Self::STOCK
}
pub fn get(&self) -> u16 {
self.0
}
pub fn stock_value() -> Self {
Self(150u16)
}
}
impl PartialOrd for FireCpuFreq {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self.is_stock() || self.is_none() || other.is_stock() || other.is_none() {
return None;
}
self.0.partial_cmp(&other.0)
}
}
impl Default for FireCpuFreq {
fn default() -> Self {
Self::stock()
}
}
impl TryFrom<u16> for FireCpuFreq {
type Error = InvalidFreq;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
Self::NONE => Ok(Self::none()),
Self::STOCK => Ok(Self::stock()),
freq if (Self::MIN_MHZ..=Self::MAX_MHZ).contains(&freq) => Ok(Self(freq)),
_ => Err(InvalidFreq(value)),
}
}
}
impl core::fmt::Display for FireCpuFreq {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.0 {
Self::NONE => write!(f, "None"),
Self::STOCK => write!(f, "Stock"),
freq => write!(f, "{}MHz", freq),
}
}
}
#[derive(Debug)]
pub struct InvalidFreq(u16);
impl core::fmt::Display for InvalidFreq {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Invalid frequency: {}MHz (must be {}-{}MHz, 0 (None), or 0xFFFF (Stock))",
self.0,
FireCpuFreq::MIN_MHZ,
FireCpuFreq::MAX_MHZ
)
}
}
impl serde::Serialize for FireCpuFreq {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self.0 {
Self::NONE => "None".to_string(),
Self::STOCK => "Stock".to_string(),
freq => format!("{}MHz", freq),
};
serializer.serialize_str(&s)
}
}
impl<'de> serde::Deserialize<'de> for FireCpuFreq {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"None" => Ok(Self::none()),
"Stock" => Ok(Self::stock()),
_ => {
if let Some(freq_str) = s.strip_suffix("MHz") {
let freq = freq_str.parse::<u16>().map_err(|_| {
serde::de::Error::custom(format!("Invalid frequency: {}", s))
})?;
Self::mhz(freq).map_err(|_| {
serde::de::Error::custom(format!(
"Frequency must be between {}MHz and {}MHz",
Self::MIN_MHZ,
Self::MAX_MHZ
))
})
} else {
Err(serde::de::Error::custom(format!(
"Invalid frequency format: {}",
s
)))
}
}
}
}
}
#[cfg(feature = "schemars")]
impl JsonSchema for FireCpuFreq {
fn schema_name() -> Cow<'static, str> {
"FireCpuFreq".into()
}
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
schemars::json_schema!({
"type": "string",
"description": format!(
"CPU frequency: 'None', 'Stock', or '{{n}}MHz' where n is {}-{}",
Self::MIN_MHZ,
Self::MAX_MHZ
)
})
}
}
#[repr(u8)]
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum FireVreg {
#[serde(rename = "0.55V")]
V0_55 = 0x00,
#[serde(rename = "0.60V")]
V0_60 = 0x01,
#[serde(rename = "0.65V")]
V0_65 = 0x02,
#[serde(rename = "0.70V")]
V0_70 = 0x03,
#[serde(rename = "0.75V")]
V0_75 = 0x04,
#[serde(rename = "0.80V")]
V0_80 = 0x05,
#[serde(rename = "0.85V")]
V0_85 = 0x06,
#[serde(rename = "0.90V")]
V0_90 = 0x07,
#[serde(rename = "0.95V")]
V0_95 = 0x08,
#[serde(rename = "1.00V")]
V1_00 = 0x09,
#[serde(rename = "1.05V")]
V1_05 = 0x0A,
#[serde(rename = "1.10V")]
V1_10 = 0x0B,
#[serde(rename = "1.15V")]
V1_15 = 0x0C,
#[serde(rename = "1.20V")]
V1_20 = 0x0D,
#[serde(rename = "1.25V")]
V1_25 = 0x0E,
#[serde(rename = "1.30V")]
V1_30 = 0x0F,
#[serde(rename = "1.35V")]
V1_35 = 0x10,
#[serde(rename = "1.40V")]
V1_40 = 0x11,
#[serde(rename = "1.50V")]
V1_50 = 0x12,
#[serde(rename = "1.60V")]
V1_60 = 0x13,
#[serde(rename = "1.65V")]
V1_65 = 0x14,
#[serde(rename = "1.70V")]
V1_70 = 0x15,
#[serde(rename = "1.80V")]
V1_80 = 0x16,
#[serde(rename = "1.90V")]
V1_90 = 0x17,
#[serde(rename = "2.00V")]
V2_00 = 0x18,
#[serde(rename = "2.35V")]
V2_35 = 0x19,
#[serde(rename = "2.50V")]
V2_50 = 0x1A,
#[serde(rename = "2.65V")]
V2_65 = 0x1B,
#[serde(rename = "2.80V")]
V2_80 = 0x1C,
#[serde(rename = "3.00V")]
V3_00 = 0x1D,
#[serde(rename = "3.15V")]
V3_15 = 0x1E,
#[serde(rename = "3.30V")]
V3_30 = 0x1F,
#[default]
Stock = 0xFF,
}
impl TryFrom<u8> for FireVreg {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::V0_55),
0x01 => Ok(Self::V0_60),
0x02 => Ok(Self::V0_65),
0x03 => Ok(Self::V0_70),
0x04 => Ok(Self::V0_75),
0x05 => Ok(Self::V0_80),
0x06 => Ok(Self::V0_85),
0x07 => Ok(Self::V0_90),
0x08 => Ok(Self::V0_95),
0x09 => Ok(Self::V1_00),
0x0A => Ok(Self::V1_05),
0x0B => Ok(Self::V1_10),
0x0C => Ok(Self::V1_15),
0x0D => Ok(Self::V1_20),
0x0E => Ok(Self::V1_25),
0x0F => Ok(Self::V1_30),
0x10 => Ok(Self::V1_35),
0x11 => Ok(Self::V1_40),
0x12 => Ok(Self::V1_50),
0x13 => Ok(Self::V1_60),
0x14 => Ok(Self::V1_65),
0x15 => Ok(Self::V1_70),
0x16 => Ok(Self::V1_80),
0x17 => Ok(Self::V1_90),
0x18 => Ok(Self::V2_00),
0x19 => Ok(Self::V2_35),
0x1A => Ok(Self::V2_50),
0x1B => Ok(Self::V2_65),
0x1C => Ok(Self::V2_80),
0x1D => Ok(Self::V3_00),
0x1E => Ok(Self::V3_15),
0x1F => Ok(Self::V3_30),
0xFF => Ok(Self::Stock),
_ => Err(value),
}
}
}
impl core::fmt::Display for FireVreg {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Stock => write!(f, "Stock"),
_ => {
let s = serde_json::to_string(self).map_err(|_| core::fmt::Error)?;
write!(f, "{}", s.trim_matches('"'))
}
}
}
}
impl FireVreg {
pub fn is_stock(&self) -> bool {
matches!(self, Self::Stock)
}
pub fn stock_value() -> Self {
Self::V1_10
}
pub fn supported_levels() -> &'static [Self] {
&[
Self::V0_55,
Self::V0_60,
Self::V0_65,
Self::V0_70,
Self::V0_75,
Self::V0_80,
Self::V0_85,
Self::V0_90,
Self::V0_95,
Self::V1_00,
Self::V1_05,
Self::V1_10,
Self::V1_15,
Self::V1_20,
Self::V1_25,
Self::V1_30,
Self::V1_35,
Self::V1_40,
Self::V1_50,
Self::V1_60,
Self::V1_65,
Self::V1_70,
Self::V1_80,
Self::V1_90,
Self::V2_00,
Self::V2_35,
Self::V2_50,
Self::V2_65,
Self::V2_80,
Self::V3_00,
Self::V3_15,
Self::V3_30,
]
}
}
impl PartialOrd for FireVreg {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self.is_stock() || other.is_stock() {
return None;
}
(self.clone() as u8).partial_cmp(&(other.clone() as u8))
}
}