Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSDK-8708] add coredump reader #309

Merged
merged 5 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions micro-rdk/src/common/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub static COMPONENT_NAME: &str = "generic";
pub enum GenericError {
#[error("Generic: method {0} unimplemented")]
MethodUnimplemented(&'static str),
#[error("Generic other error: {0}")]
Other(Box<dyn std::error::Error + Send + Sync>),
}
#[cfg(feature = "builtin-components")]
pub(crate) fn register_models(registry: &mut ComponentRegistry) {
Expand Down
1 change: 1 addition & 0 deletions micro-rdk/src/common/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ impl Default for ComponentRegistry {
crate::esp32::encoder::register_models(&mut r);
crate::esp32::hcsr04::register_models(&mut r);
crate::esp32::single_encoder::register_models(&mut r);
crate::esp32::coredump::register_models(&mut r);
}
}
r
Expand Down
207 changes: 207 additions & 0 deletions micro-rdk/src/esp32/coredump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};

use base64::{engine::general_purpose::STANDARD, Engine as _};
use bytes::BytesMut;
use esp_idf_svc::sys::{
esp_partition_erase_range, esp_partition_find_first, esp_partition_read,
esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_DATA_COREDUMP, esp_partition_t,
esp_partition_type_t_ESP_PARTITION_TYPE_DATA,
};

use crate::{
common::{
config::ConfigType,
generic::{DoCommand, GenericError},
registry::{ComponentRegistry, Dependency},
sensor::{GenericReadingsResult, Readings, Sensor, SensorError, SensorType},
status::Status,
},
google::protobuf::{self, value::Kind, Struct, Value},
};

pub struct Coredump {
len: usize,
coredump_partition_ptr: &'static esp_partition_t,
}

pub(crate) fn register_models(registry: &mut ComponentRegistry) {
if registry
.register_sensor("coredump", &Coredump::from_config)
.is_err()
{
log::error!("couldn't register coredump sensor");
}
}

/// Instantiate a coredump sensor if : any partition of type data and subtype coredump can be found, we will use the first found partition is more than one exists (similar to the behavior of the coredump code in esp-idf)
/// A coredump is consider available if the 4th byte of the version field is not 0xFF (flash erase value)
/// The coredump is implemented as a sensor and can be downloaded through the DoCommand interface ( since GetReading cannot return a stream of bytes object)
///
/// To download a coredump you can either call DoCommand passing {"get_nth_chunk":n} where n is the chunk number until it returns an error (no more data available) or
/// first call DoCommand passing {"sizes":null} to get the number of chunks.
/// To erase the coredump call DoCommand passing{"erase_coredump":null}
impl Coredump {
pub fn from_config(_: ConfigType, _: Vec<Dependency>) -> Result<SensorType, SensorError> {
let mut len = 0;
// if a pointer is found it will be stored as a static reference since it will remain valid for the lifetime of the program
let partition_iterator = unsafe {
esp_partition_find_first(
esp_partition_type_t_ESP_PARTITION_TYPE_DATA,
esp_partition_subtype_t_ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
std::ptr::null(),
)
.as_ref()
.ok_or(SensorError::SensorGenericError(
"no coredump partition found",
))?
};
// the first two fields of the coredump are length (uint32_t) and version (uint32_t)
let mut first_bytes = [0xFF_u8; 8];
esp_idf_svc::sys::esp!(unsafe {
esp_partition_read(partition_iterator, 0, first_bytes.as_mut_ptr() as *mut _, 8)
})?;

if first_bytes[7] != 0xFF {
len = usize::from_le_bytes(first_bytes[0..4].try_into().unwrap()); // safe because we would have read 4 bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to make sure I understand: we only need to do this once because even if someone erases the coredump via DoCommand, a coredump can only be written in a situation that what prompt a restart and result in reconfiguration? Is this correct? Or is there a situation in which something can be written to the coredump and a restart does not occur (meaning this sensor's state won't be properly reset)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coredump is only written on panic/abort whether from rust or C. So during the lifetime of the program the coredump exists until we erase it

}

Ok(Arc::new(Mutex::new(Coredump {
len,
coredump_partition_ptr: partition_iterator,
})))
}
}

impl Sensor for Coredump {}

impl Readings for Coredump {
fn get_generic_readings(
&mut self,
) -> Result<crate::common::sensor::GenericReadingsResult, SensorError> {
let has_coredump = self.len > 0;
let res = GenericReadingsResult::from([(
"has_coredump".to_owned(),
Value {
kind: Some(Kind::BoolValue(has_coredump)),
},
)]);
Ok(res)
}
}

impl Status for Coredump {
fn get_status(
&self,
) -> Result<Option<crate::google::protobuf::Struct>, crate::common::status::StatusError> {
Ok(Some(protobuf::Struct {
fields: HashMap::new(),
}))
}
}

struct Null;

// Coredump would be chunked into 4096 bytes encoded in Base64
// len(bases64) = 4*(n/3) therefore n = (4096/4)*3 rounded down
// which yield n = 3072
const CORE_FRAGMENT_SIZE: usize = 3072;

impl DoCommand for Coredump {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth having a comment outlining the three commands that can be sent, their expected values, and the high-level expected behavior

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense

fn do_command(
&mut self,
command_struct: Option<protobuf::Struct>,
) -> Result<Option<protobuf::Struct>, crate::common::generic::GenericError> {
if let Some(cmd) = command_struct {
if cmd.fields.get("sizes").is_some() {
return Ok(Some(Struct {
fields: HashMap::from([
(
"nb_chunk".to_owned(),
Value {
kind: Some(Kind::NumberValue(
(self.len / CORE_FRAGMENT_SIZE) as f64,
)),
},
),
(
"len".to_owned(),
Value {
kind: Some(Kind::NumberValue(self.len as f64)),
},
),
]),
}));
}
if cmd.fields.get("erase_coredump").is_some() {
esp_idf_svc::sys::esp!(unsafe {
esp_partition_erase_range(
self.coredump_partition_ptr,
0_usize,
self.coredump_partition_ptr.size as usize,
)
})
.map_err(|e| GenericError::Other(e.into()))?;
self.len = 0;
return Ok(None);
}
if let Some(Kind::NumberValue(val)) = cmd
.fields
.get("get_nth_chunk")
.and_then(|v| v.kind.as_ref())
{
val.is_sign_positive()
.then_some(Null)
.ok_or(GenericError::Other(
"chunk requested outside of bounds".into(),
))?;

let offset = (val.floor() as usize) * CORE_FRAGMENT_SIZE;

offset
.lt(&self.len)
.then_some(Null)
.ok_or(GenericError::Other(
"chunk requested outside of bounds".into(),
))?;

let to_read: usize = CORE_FRAGMENT_SIZE.min(self.len - offset);
let mut buf = BytesMut::with_capacity(CORE_FRAGMENT_SIZE);

esp_idf_svc::sys::esp!(unsafe {
esp_partition_read(
self.coredump_partition_ptr,
offset,
buf.as_mut_ptr() as *mut _,
to_read,
)
})
.map_err(|e| GenericError::Other(e.into()))?;
unsafe {
buf.set_len(to_read);
}

let as_b64 = STANDARD.encode(buf);
return Ok(Some(Struct {
fields: HashMap::from([
(
"chunk".to_owned(),
Value {
kind: Some(Kind::NumberValue(val.floor())),
},
),
(
"payload".to_owned(),
Value {
kind: Some(Kind::StringValue(as_b64)),
},
),
]),
}));
}
}
Err(GenericError::Other("couldn't parse request".into()))
}
}
1 change: 1 addition & 0 deletions micro-rdk/src/esp32/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ pub mod nvs_storage;
pub mod provisioning {
pub mod wifi_provisioning;
}
pub mod coredump;
Loading