zencan_node/
bootloader.rs

1//! Bootloader objects
2//!
3//!
4
5use core::sync::atomic::{AtomicBool, Ordering};
6
7use crate::object_dict::{
8    ConstByteRefField, ConstField, ObjectAccess, ProvidesSubObjects, SubObjectAccess,
9};
10use zencan_common::{
11    constants::values::BOOTLOADER_ERASE_CMD,
12    objects::{ObjectCode, SubInfo},
13    sdo::AbortCode,
14    AtomicCell,
15};
16
17/// Implements a Bootloader info (0x5500) object
18#[derive(Debug, Default)]
19pub struct BootloaderInfo<const APP: bool, const NUM_SECTIONS: u8> {
20    reset_flag: ResetField,
21}
22
23impl<const APP: bool, const NUM_SECTIONS: u8> BootloaderInfo<APP, NUM_SECTIONS> {
24    /// Create new BootloaderInfo
25    pub const fn new() -> Self {
26        Self {
27            reset_flag: ResetField::new(),
28        }
29    }
30
31    /// Read the reset_flag
32    ///
33    /// The flag is set when a reset command is written to the object, and this function can be used
34    /// by the application to determed when a reset to bootloader is commanded
35    pub fn reset_flag(&self) -> bool {
36        self.reset_flag.load()
37    }
38}
39
40#[derive(Debug, Default)]
41struct ResetField {
42    flag: AtomicBool,
43}
44
45impl ResetField {
46    pub const fn new() -> Self {
47        Self {
48            flag: AtomicBool::new(false),
49        }
50    }
51
52    pub fn load(&self) -> bool {
53        self.flag.load(Ordering::Relaxed)
54    }
55}
56
57impl SubObjectAccess for ResetField {
58    fn read(&self, _offset: usize, _buf: &mut [u8]) -> Result<usize, AbortCode> {
59        Err(AbortCode::WriteOnly)
60    }
61
62    fn read_size(&self) -> usize {
63        0
64    }
65
66    fn write(&self, data: &[u8]) -> Result<(), AbortCode> {
67        if data.len() == 4 {
68            if data == [0x42, 0x4F, 0x4F, 0x54] {
69                self.flag.store(true, Ordering::Relaxed);
70                Ok(())
71            } else {
72                Err(AbortCode::InvalidValue)
73            }
74        } else if data.len() < 4 {
75            Err(AbortCode::DataTypeMismatchLengthLow)
76        } else {
77            Err(AbortCode::DataTypeMismatchLengthHigh)
78        }
79    }
80}
81
82/// Get the value to return for the config object
83///
84/// `app` - Indicates whether the current node is running as an application, rather than a
85/// bootloader
86const fn get_config_value(app: bool) -> u32 {
87    let mut config = 1;
88    if app {
89        config |= 2;
90    }
91    config
92}
93
94impl<const APP: bool, const NUM_SECTIONS: u8> ProvidesSubObjects
95    for BootloaderInfo<APP, NUM_SECTIONS>
96{
97    fn get_sub_object(&self, sub: u8) -> Option<(SubInfo, &dyn SubObjectAccess)> {
98        match sub {
99            0 => Some((
100                SubInfo::MAX_SUB_NUMBER,
101                const { &ConstField::new(3u8.to_le_bytes()) },
102            )),
103            1 => Some((SubInfo::new_u32().ro_access(), {
104                const { &ConstField::new(get_config_value(APP).to_le_bytes()) }
105            })),
106            2 => Some((SubInfo::new_u8().ro_access(), {
107                const { &ConstField::new(NUM_SECTIONS.to_le_bytes()) }
108            })),
109            3 => Some((SubInfo::new_u32().wo_access(), &self.reset_flag)),
110            _ => None,
111        }
112    }
113
114    fn object_code(&self) -> ObjectCode {
115        ObjectCode::Record
116    }
117}
118
119/// A trait for applications to implement to provide a bootloader section access implementation
120pub trait BootloaderSectionCallbacks: Sync {
121    /// Called to erase the section
122    ///
123    /// Returns true if section is successfully erased and ready for programming
124    fn erase(&self) -> bool;
125
126    /// Write a chunk of data
127    ///
128    /// Write will be called 1 or more times after an erase with a sequence of new data to write to
129    /// the section
130    fn write(&self, data: &[u8]);
131
132    /// Finalize writing a section
133    ///
134    /// Will be called once after all data has been written to allow the storage driver to finalize
135    /// writing the data and return any errors.
136    ///
137    /// Returns true on successful write
138    fn finalize(&self) -> bool;
139}
140
141/// Implements a bootloader section object in the object dictionary
142#[allow(missing_debug_implementations)]
143pub struct BootloaderSection {
144    name: &'static str,
145    size: u32,
146    callbacks: AtomicCell<Option<&'static dyn BootloaderSectionCallbacks>>,
147}
148
149impl BootloaderSection {
150    /// Create a new bootloader section
151    pub const fn new(name: &'static str, size: u32) -> Self {
152        Self {
153            name,
154            size,
155            callbacks: AtomicCell::new(None),
156        }
157    }
158
159    /// Register the application callbacks which implement storage for this section
160    pub fn register_callbacks(&self, callbacks: &'static dyn BootloaderSectionCallbacks) {
161        self.callbacks.store(Some(callbacks));
162    }
163}
164
165impl ObjectAccess for BootloaderSection {
166    fn read(&self, sub: u8, offset: usize, buf: &mut [u8]) -> Result<usize, AbortCode> {
167        match sub {
168            0 => ConstField::new(4u8.to_le_bytes()).read(offset, buf),
169            1 => ConstField::new(1u8.to_le_bytes()).read(offset, buf),
170            2 => ConstByteRefField::new(self.name.as_bytes()).read(offset, buf),
171            3 => Err(AbortCode::WriteOnly),
172            4 => Err(AbortCode::WriteOnly),
173            _ => Err(AbortCode::NoSuchSubIndex),
174        }
175    }
176
177    fn read_size(&self, sub: u8) -> Result<usize, AbortCode> {
178        match sub {
179            0 => Ok(1),
180            1 => Ok(1),
181            2 => Ok(self.name.len()),
182            3 => Ok(0),
183            4 => Ok(0),
184            _ => Err(AbortCode::NoSuchSubIndex),
185        }
186    }
187
188    fn write(&self, sub: u8, data: &[u8]) -> Result<(), AbortCode> {
189        match sub {
190            0 => Err(AbortCode::ReadOnly),
191            1 => Err(AbortCode::ReadOnly),
192            2 => Err(AbortCode::ReadOnly),
193            3 => {
194                if data == BOOTLOADER_ERASE_CMD.to_le_bytes() {
195                    if let Some(cb) = self.callbacks.load() {
196                        if cb.erase() {
197                            Ok(())
198                        } else {
199                            Err(AbortCode::GeneralError)
200                        }
201                    } else {
202                        Err(AbortCode::ResourceNotAvailable)
203                    }
204                } else {
205                    Err(AbortCode::InvalidValue)
206                }
207            }
208            4 => {
209                if let Some(callbacks) = self.callbacks.load() {
210                    callbacks.write(data);
211                    if callbacks.finalize() {
212                        // success
213                        Ok(())
214                    } else {
215                        Err(AbortCode::GeneralError)
216                    }
217                } else {
218                    Err(AbortCode::ResourceNotAvailable)
219                }
220            }
221            _ => Err(AbortCode::NoSuchSubIndex),
222        }
223    }
224
225    fn object_code(&self) -> ObjectCode {
226        ObjectCode::Record
227    }
228
229    fn sub_info(&self, sub: u8) -> Result<SubInfo, AbortCode> {
230        match sub {
231            0 => Ok(SubInfo::MAX_SUB_NUMBER),
232            1 => Ok(SubInfo::new_u8().ro_access()),
233            2 => Ok(SubInfo::new_visibile_str(self.name.len()).ro_access()),
234            3 => Ok(SubInfo::new_u32().wo_access()),
235            4 => Ok(SubInfo {
236                size: self.size as usize,
237                data_type: zencan_common::objects::DataType::Domain,
238                access_type: zencan_common::objects::AccessType::Rw,
239                pdo_mapping: zencan_common::objects::PdoMapping::None,
240                persist: false,
241            }),
242            _ => Err(AbortCode::NoSuchSubIndex),
243        }
244    }
245}