Skip to main content

cu_sdlogger/
logger.rs

1use crate::sdmmc::{Block, BlockCount, BlockDevice, BlockIdx};
2use alloc::sync::Arc;
3use bincode::config::standard;
4use bincode::enc::write::Writer as BincodeWriter;
5use bincode::error::EncodeError;
6use bincode::{Encode, encode_into_slice, encode_into_writer};
7use core::cell::UnsafeCell;
8use cu29::prelude::*;
9
10const BLK: usize = 512;
11
12/// Wrapper to share a block device behind `Arc` without adding locking.
13///
14/// # Safety
15/// This type provides unsynchronized interior mutability. Only use it when
16/// all access is externally serialized (single-threaded execution or a
17/// higher-level mutex).
18pub struct ForceSyncSend<T>(UnsafeCell<T>);
19impl<T> ForceSyncSend<T> {
20    pub const fn new(inner: T) -> Self {
21        Self(UnsafeCell::new(inner))
22    }
23    #[inline]
24    fn inner(&self) -> &T {
25        // SAFETY: Callers must serialize access to avoid mutable aliasing.
26        unsafe { &*self.0.get() }
27    }
28    #[inline]
29    #[allow(clippy::mut_from_ref)]
30    fn inner_mut(&self) -> &mut T {
31        // SAFETY: Callers must ensure exclusive access (no concurrent use).
32        unsafe { &mut *self.0.get() }
33    }
34}
35// SAFETY: This wrapper does not synchronize access; callers must serialize use.
36unsafe impl<T: Send> Send for ForceSyncSend<T> {}
37// SAFETY: Sharing across threads is only safe if access is externally serialized.
38unsafe impl<T: Send> Sync for ForceSyncSend<T> {}
39
40impl<B: BlockDevice> BlockDevice for ForceSyncSend<B> {
41    type Error = B::Error;
42
43    #[cfg(all(feature = "eh02", not(feature = "eh1")))]
44    fn read(&self, blocks: &mut [Block], start: BlockIdx, reason: &str) -> Result<(), Self::Error> {
45        self.inner_mut().read(blocks, start, reason)
46    }
47
48    #[cfg(feature = "eh1")]
49    fn read(&self, blocks: &mut [Block], start: BlockIdx) -> Result<(), Self::Error> {
50        self.inner_mut().read(blocks, start)
51    }
52
53    fn write(&self, blocks: &[Block], start: BlockIdx) -> Result<(), Self::Error> {
54        self.inner_mut().write(blocks, start)
55    }
56    fn num_blocks(&self) -> Result<BlockCount, Self::Error> {
57        self.inner().num_blocks()
58    }
59}
60
61/// Implements a bincode `Writer` for linear logging over a block device.
62pub struct SdBlockWriter<BD: BlockDevice> {
63    bd: Arc<ForceSyncSend<BD>>,
64    current_blk: BlockIdx, // absolute block number for payload
65    position_blk: usize,   // 0..512
66    capacity_bytes: usize, // payload capacity for this section
67    written: usize,        // payload bytes written so far
68    buffer: Block,         // RMW buffer for current block
69}
70
71impl<BD: BlockDevice> SdBlockWriter<BD> {
72    pub fn new(bd: Arc<ForceSyncSend<BD>>, start_block: BlockIdx, capacity_bytes: usize) -> Self {
73        Self {
74            bd,
75            current_blk: start_block,
76            position_blk: 0,
77            capacity_bytes,
78            written: 0,
79            buffer: Block::new(),
80        }
81    }
82
83    #[inline]
84    fn flush_full(&mut self) -> Result<(), EncodeError> {
85        self.bd
86            .write(core::slice::from_ref(&self.buffer), self.current_blk)
87            .expect("write failed on full block");
88        self.current_blk += BlockCount(1);
89        self.position_blk = 0;
90        self.buffer = Block::new();
91        Ok(())
92    }
93
94    /// Force-flush the current tail block if partially filled.
95    pub fn flush_tail(&mut self) -> Result<(), EncodeError> {
96        if self.position_blk != 0 {
97            self.bd
98                .write(core::slice::from_ref(&self.buffer), self.current_blk)
99                .expect("write failed on flush");
100            // Advance to the next block, start fresh at the boundary.
101            self.current_blk += BlockCount(1);
102            self.position_blk = 0;
103            self.buffer = Block::new();
104        }
105        Ok(())
106    }
107}
108
109impl<BD: BlockDevice> BincodeWriter for SdBlockWriter<BD> {
110    fn write(&mut self, mut bytes: &[u8]) -> Result<(), EncodeError> {
111        if self
112            .written
113            .checked_add(bytes.len())
114            .is_none_or(|w| w > self.capacity_bytes)
115        {
116            return Err(EncodeError::UnexpectedEnd);
117        }
118
119        if self.position_blk != 0 {
120            let take = core::cmp::min(BLK - self.position_blk, bytes.len());
121            self.buffer.as_mut()[self.position_blk..self.position_blk + take]
122                .copy_from_slice(&bytes[..take]);
123            self.position_blk += take;
124            self.written += take;
125            bytes = &bytes[take..];
126            if self.position_blk == BLK {
127                self.flush_full()?;
128            }
129        }
130
131        while bytes.len() >= BLK {
132            let mut blk = Block::new();
133            blk.as_mut().copy_from_slice(&bytes[..BLK]);
134            self.bd
135                .write(core::slice::from_ref(&blk), self.current_blk)
136                .expect("write failed");
137            self.current_blk += BlockCount(1);
138            self.written += BLK;
139            bytes = &bytes[BLK..];
140        }
141
142        if !bytes.is_empty() {
143            let n = bytes.len();
144            self.buffer.as_mut()[self.position_blk..self.position_blk + n].copy_from_slice(bytes);
145            self.position_blk += n;
146            self.written += n;
147            if self.position_blk == BLK {
148                self.flush_full()?;
149            }
150        }
151
152        Ok(())
153    }
154}
155
156pub struct EMMCSectionStorage<BD: BlockDevice> {
157    bd: Arc<ForceSyncSend<BD>>,
158    start_block: BlockIdx,
159    content_writer: SdBlockWriter<BD>,
160}
161
162impl<BD: BlockDevice> EMMCSectionStorage<BD> {
163    fn new(bd: Arc<ForceSyncSend<BD>>, start_block: BlockIdx, data_capacity: usize) -> Self {
164        // data_capacity is the space left in that section minus the header.
165        let content_writer =
166            SdBlockWriter::new(bd.clone(), start_block + BlockCount(1), data_capacity); // +1 to skip the header
167        Self {
168            bd,
169            start_block,
170            content_writer,
171        }
172    }
173}
174
175impl<BD: BlockDevice + Send> SectionStorage for EMMCSectionStorage<BD> {
176    fn initialize<E: Encode>(&mut self, header: &E) -> Result<usize, EncodeError> {
177        self.post_update_header(header)?;
178        Ok(SECTION_HEADER_COMPACT_SIZE as usize)
179    }
180
181    fn post_update_header<E: Encode>(&mut self, header: &E) -> Result<usize, EncodeError> {
182        // Re-encode header and write again to header blocks.
183        let mut block = Block::new();
184        let wrote = encode_into_slice(header, block.as_mut(), standard())?;
185        self.bd
186            .write(&[block], self.start_block)
187            .map_err(|_| EncodeError::UnexpectedEnd)?;
188        Ok(wrote)
189    }
190
191    fn append<E: Encode>(&mut self, entry: &E) -> Result<usize, EncodeError> {
192        let bf = self.content_writer.written;
193        encode_into_writer(entry, &mut self.content_writer, standard())
194            .map_err(|_| EncodeError::UnexpectedEnd)?;
195        Ok(self.content_writer.written - bf)
196    }
197
198    fn flush(&mut self) -> CuResult<usize> {
199        let bf = self.content_writer.written;
200        self.content_writer
201            .flush_tail()
202            .map_err(|_| CuError::from("flush failed"))?;
203        Ok(self.content_writer.written - bf)
204    }
205}
206
207pub struct EMMCLogger<BD: BlockDevice> {
208    bd: Arc<ForceSyncSend<BD>>,
209    next_block: BlockIdx,
210    last_block: BlockIdx,
211    temporary_end_marker: Option<BlockIdx>,
212}
213
214impl<BD: BlockDevice> EMMCLogger<BD> {
215    pub fn new(bd: BD, start: BlockIdx, size: BlockCount) -> CuResult<Self> {
216        let main_header = MainHeader {
217            magic: MAIN_MAGIC,
218            format_version: UNIFIED_LOG_FORMAT_VERSION,
219            first_section_offset: BLK as u16,
220            page_size: BLK as u16,
221        };
222        let mut block: Block = Block::new();
223
224        encode_into_slice(&main_header, block.as_mut(), standard())
225            .map_err(|_| CuError::from("Could not encode the main header"))?;
226
227        bd.write(&[block], start)
228            .map_err(|_| CuError::from("Could not write main header"))?;
229
230        let next_block = start + BlockCount(1); // +1 to skip the main header
231        let last_block = start + size;
232
233        Ok(Self {
234            bd: Arc::new(ForceSyncSend::new(bd)),
235            next_block,
236            last_block,
237            temporary_end_marker: None,
238        })
239    }
240
241    // Allocate a section in this logger and return the start block index.
242    fn alloc_section(&mut self, size: BlockCount) -> CuResult<BlockIdx> {
243        let start = self.next_block;
244        self.next_block += size;
245        if self.next_block > self.last_block {
246            return Err(CuError::from("out of space"));
247        }
248        Ok(start)
249    }
250
251    fn clear_temporary_end_marker(&mut self) {
252        if let Some(marker) = self.temporary_end_marker.take() {
253            self.next_block = marker;
254        }
255    }
256
257    fn write_end_marker(&mut self, temporary: bool) -> CuResult<()> {
258        let block_size = SECTION_HEADER_COMPACT_SIZE as usize;
259        let blocks_needed = 1; // header only
260        let start_block = self.next_block;
261        let end_block = start_block + BlockCount(blocks_needed as u32);
262        if end_block > self.last_block {
263            return Err(CuError::from("out of space"));
264        }
265
266        let header = SectionHeader {
267            magic: SECTION_MAGIC,
268            block_size: SECTION_HEADER_COMPACT_SIZE,
269            entry_type: UnifiedLogType::LastEntry,
270            offset_to_next_section: (blocks_needed * block_size) as u32,
271            used: 0,
272            is_open: temporary,
273        };
274
275        let mut header_block = Block::new();
276        encode_into_slice(&header, header_block.as_mut(), standard())
277            .map_err(|_| CuError::from("Could not encode end-of-log header"))?;
278        self.bd
279            .write(&[header_block], start_block)
280            .map_err(|_| CuError::from("Could not write end-of-log header"))?;
281
282        self.temporary_end_marker = Some(start_block);
283        self.next_block = end_block;
284        Ok(())
285    }
286}
287
288impl<BD> UnifiedLogWrite<EMMCSectionStorage<BD>> for EMMCLogger<BD>
289where
290    BD: BlockDevice + Send + Sync + 'static,
291{
292    fn add_section(
293        &mut self,
294        entry_type: UnifiedLogType,
295        requested_section_size: usize,
296    ) -> CuResult<SectionHandle<EMMCSectionStorage<BD>>> {
297        self.clear_temporary_end_marker();
298        let block_size = SECTION_HEADER_COMPACT_SIZE; // 512
299        if block_size != 512 {
300            panic!("EMMC: only 512 byte blocks supported");
301        }
302
303        let section_header = SectionHeader {
304            magic: SECTION_MAGIC,
305            block_size,
306            entry_type,
307            offset_to_next_section: requested_section_size as u32,
308            used: 0,
309            is_open: true,
310        };
311
312        let section_size_in_blks: u32 = (requested_section_size / block_size as usize) as u32 + 1; // always round up
313        let start_block = self.alloc_section(BlockCount(section_size_in_blks))?;
314
315        let storage = EMMCSectionStorage::new(
316            Arc::clone(&self.bd),
317            start_block,
318            ((section_size_in_blks - 1) * block_size as u32) as usize,
319        );
320
321        // Create handle (this will call `storage.initialize(header)`).
322        let handle = SectionHandle::create(section_header, storage)?;
323        self.write_end_marker(true)?;
324        Ok(handle)
325    }
326
327    fn flush_section(&mut self, section: &mut SectionHandle<EMMCSectionStorage<BD>>) {
328        section.mark_closed();
329        // and the end of the stream is ok.
330        section
331            .get_storage_mut()
332            .flush()
333            .expect("EMMC: flush failed");
334        // and be sure the header is up-to-date
335        section
336            .post_update_header()
337            .expect("EMMC: post update header failed");
338    }
339
340    fn status(&self) -> UnifiedLogStatus {
341        UnifiedLogStatus {
342            total_used_space: (self.next_block.0 as usize) * BLK,
343            total_allocated_space: (self.next_block.0 as usize) * BLK,
344        }
345    }
346}
347
348impl<BD: BlockDevice> Drop for EMMCLogger<BD> {
349    fn drop(&mut self) {
350        self.clear_temporary_end_marker();
351        if let Err(e) = self.write_end_marker(false) {
352            panic!("Failed to flush the unified logger: {}", e);
353        }
354    }
355}