px-fwlib 0.10.0
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers generated with Doxygen 1.9.2
px_log_fs.h
1#ifndef __PX_LOG_FS_H__
2#define __PX_LOG_FS_H__
3/* =============================================================================
4 ____ ___ ____ ___ _ _ ___ __ __ ___ __ __ TM
5 | _ \ |_ _| / ___| / _ \ | \ | | / _ \ | \/ | |_ _| \ \/ /
6 | |_) | | | | | | | | | | \| | | | | | | |\/| | | | \ /
7 | __/ | | | |___ | |_| | | |\ | | |_| | | | | | | | / \
8 |_| |___| \____| \___/ |_| \_| \___/ |_| |_| |___| /_/\_\
9
10 Copyright (c) 2019 Pieter Conradie <https://piconomix.com>
11
12 License: MIT
13 https://github.com/piconomix/px-fwlib/blob/master/LICENSE.md
14
15 Title: px_log_fs.h : Record-based file system for Serial Flash
16 Author(s): Pieter Conradie
17 Creation Date: 2019-09-09
18
19============================================================================= */
20
21/**
22 * @ingroup DATA
23 * @defgroup PX_LOG_FS px_log_fs.h : Record-based file system for Serial Flash
24 *
25 * Record-based file system for Serial Flash.
26 *
27 * File(s):
28 * - data/inc/px_log_fs.h
29 * - data/inc/px_log_fs_cfg_template.h
30 * - data/src/px_log_fs.c
31 * - data/inc/px_log_fs_glue.h
32 * - data/src/px_log_fs_glue_at25s.c
33 * - data/src/px_log_fs_glue_at45d.c
34 *
35 * 1. Introduction
36 * ===============
37 *
38 * <b>px_log_fs</b> is a basic but **resilient** record-based file system to
39 * store sequential log data on Serial Flash, for example
40 * [Adesto AT25S Serial Flash](https://www.adestotech.com/products/standard-serial-flash).
41 * It is designed with the following fundamental characteristics in mind:
42 * - The Serial Flash is evenly divided up into pages
43 * - A block of pages is erased at a time (less is better)
44 * - An erase will reset all bits in a block back to ones (1)
45 * - A write selectively set bits in a page to zero (0)
46 * - A bit that is zero can only be restored to one with a block erase
47 *
48 * File system features:
49 * - Basic wear levelling by spreading block erases and page writes
50 * sequentially over the whole Serial Flash
51 * - Serial Flash bit fault tolerance without complete file corruption
52 * (only one record or extreme worst case one page will be lost)
53 * - Power disruption or unplanned reset tolerance (only one record may be lost)
54 * - Oldest records can be marked as archived.
55 *
56 * A file is defined as a collection of sequential records that contain
57 * fixed-size log data. The records can be navigated forwards or backwards to
58 * read data, while still being able to append new data. New records will
59 * overwrite oldest records when the file is full
60 * (#PX_LOG_FS_CFG_STOP_WR_WHEN_FULL = 0) or record writing will stop when the
61 * file is full (#PX_LOG_FS_CFG_STOP_WR_WHEN_FULL = 1).
62 *
63 * The size of each record is configured with #PX_LOG_FS_CFG_REC_DATA_SIZE.
64 * See 'px_log_fs_cfg_template.h'
65 *
66 * The erase block size (number of pages) is configurable with
67 * #PX_LOG_FS_CFG_ERASE_BLOCK_SIZE. For example a single page of the Adesto
68 * [AT45DB041E](https://www.adestotech.com/products/data-flash/) can be erased
69 * at a time (very convenient), but the
70 * [AT25SF041](https://www.adestotech.com/products/standard-serial-flash/) has
71 * a minimum erase block size of 4 KB or 8 pages (a much cheaper option).
72 *
73 * @warn_s
74 * The file system must start at the beginning of an erase block and end at the
75 * end of an erase block. It must be at least two erase blocks in size, but
76 * more is better.
77 * @warn_e
78 *
79 * <i>First a code example to demonstrate the API, followed by implementation
80 * details...</i>
81 *
82 * 2. Code example
83 * ===============
84 *
85 * @include data/test/px_log_fs_test.c
86 *
87 * 3. Implementation details
88 * =========================
89 *
90 * The Serial Flash pages are treated as one big
91 * [circular buffer](http://en.wikipedia.org/wiki/Circular_buffer).
92 * Records are stored sequentially and record writing will stop (or wrap to
93 * overwrite oldest records) when the start of the oldest record is reached.
94 * This simplified scheme implicitly allows for
95 * [wear leveling](https://en.wikipedia.org/wiki/Wear_leveling), because each
96 * Serial Flash page is erased once per pass through the circular buffer.
97 *
98 * The file system leverages a characteristic of Serial Flash. When a block of
99 * pages are erased, all the data bits are reset to 1. When writing a byte,
100 * bits can be cleared (0), but not reset to 1 again. The following 8-bit
101 * marker values are used:
102 *
103 * ![8-bit markers](log_fs/markers.png)
104 *
105 * @tip_s
106 * By inspecting the bits, it can be observed that a marker can be changed from
107 * FREE to PAGE to PAGE+A to BAD, but not the other way around without erasing
108 * the whole block.
109 * @tip_e
110 *
111 * Each written page starts with a header (px_log_fs_header_t) which
112 * consists of an 8-bit marker, a 16-bit incrementing number and an 8-bit CRC.
113 * Only the first 4 bytes of each page is read to quickly index the whole file
114 * system during px_log_fs_init(). The incrementing number is used to figure
115 * out which is the oldest page and which is the newest during
116 * px_log_fs_init().
117 *
118 * ![Page header structure](log_fs/page_header.png)
119 *
120 * Pages containing records are marked with PAGE (0x5F). Each record
121 * (px_log_fs_record_t) contains an 8-bit RECORD marker (0xAF), the record data
122 * (fixed size set with #PX_LOG_FS_CFG_REC_DATA_SIZE) and an 8-bit CRC.
123 *
124 * ![Record structure](log_fs/record.png)
125 *
126 * @warn_s
127 * <b>A record cannot span across pages</b>, so there may be unused space at
128 * the end of each page. For example, if a page is 256 bytes and the page
129 * header takes up 4 bytes, then there are 252 bytes left over for records. If
130 * 8 data bytes are stored in each record, then the total record size is 10
131 * bytes (including overhead). This means that 25 records can be stored in each
132 * page and 2 bytes will be unused (252 - 25*10 = 2).
133 * @warn_e
134 *
135 * 4. How to find the FIRST and LAST page
136 * ======================================
137 *
138 * To figure out which page contains the oldest records (FIRST page) and
139 * which page contains the newest records (LAST page), a rolling (or wrapping)
140 * number is assigned to each page. This means that each consecutive page
141 * is labelled with an incrementing number. If the number exceeds the maximum,
142 * it starts again at zero (rolls over).
143 *
144 * @tip_s
145 * To simplify these examples, the rolling number starts at 00, increments up
146 * to 99 and then rolls over to 00.
147 * @tip_e
148 *
149 * Each page's rolling number is compared with the next page's rolling number.
150 * The largest difference in the rolling numbers indicates that the LAST page
151 * has been found. The next valid page is per implication the FIRST.
152 *
153 * To be exact, modulo unsigned integer arithmetic is used:
154 *
155 * Diff = (rolling_number_next - rolling_number) mod 100
156 *
157 * Here are a few select test cases to demonstrate that it works under various
158 * conditions:
159 *
160 * ![1. Page 5 is LAST and page 0 is FIRST (largest diff is 95)](log_fs/first_last_01.png)
161 *
162 * ![2. Page 9 is LAST and page 4 is FIRST (largest diff = 95)](log_fs/first_last_02.png)
163 *
164 * ![3. Page 10 is LAST and page 12 is FIRST (largest diff = 86)](log_fs/first_last_03.png)
165 *
166 * ![4. Page 14 is LAST and page 0 is FIRST (largest diff = 86)](log_fs/first_last_04.png)
167 *
168 * ![5. Page 2 is LAST and page 3 is FIRST (largest diff = 85)](log_fs/first_last_05.png)
169 *
170 * ![6. Page 3 is LAST and page 4 is FIRST (largest diff = 85)](log_fs/first_last_06.png)
171 *
172 * ![7. Page 6 is LAST and page 7 is FIRST (largest diff = 85)](log_fs/first_last_07.png)
173 *
174 * 5. Archiving records
175 * ====================
176 *
177 * Oldest records can be marked as archived (RECORD+A marker = 0xAA) for
178 * example when the record has been succesfully uploaded to a server. This
179 * makes it possible to find the next (unarchived) record that must be uploaded
180 * to the server after a reset or power interruption.
181 *
182 * If all the records on a page is marked as archived then the page is marked
183 * as archived too (PAGE+A marker = 0x55). This makes it faster to find the
184 * first unarchived record, because each page marker is inspected until the
185 * first unarchived page (PAGE marker = 0x5F) is found. Then each record on
186 * that page is inspected until the first unarchived record is found
187 * (RECORD marker =0xAF).
188 *
189 * @{
190 */
191
192/* _____STANDARD INCLUDES____________________________________________________ */
193
194/* _____PROJECT INCLUDES_____________________________________________________ */
195#include "px_defs.h"
196
197// Include project specific configuration. See "px_log_fs_cfg_template.h"
198#include "px_log_fs_cfg.h"
199
200/*
201 * New log_fs with capability to mark records as archived is not backwards
202 * compatible with an existing file system as CRC calculation now excludes
203 * marker byte.
204 *
205 * Compiler error is given to create awareness that logged information may be
206 * lost.
207 */
208#if (PX_LOG_FS_CFG_VER < 2)
209#error "New log_fs not backwards compatible with existing file system"
210#endif
211
212// Check that all project specific options have been specified in "px_log_fs_cfg.h"
213#if ( !defined(PX_LOG_FS_CFG_VER ) \
214 || !defined(PX_LOG_FS_CFG_PAGE_SIZE ) \
215 || !defined(PX_LOG_FS_CFG_ERASE_BLOCK_SIZE ) \
216 || !defined(PX_LOG_FS_CFG_REC_DATA_SIZE ) \
217 || !defined(PX_LOG_FS_CFG_STOP_WR_WHEN_FULL) )
218#error "One or more options not defined in 'px_log_fs_cfg.h'"
219#endif
220
221#if (PX_LOG_FS_CFG_ERASE_BLOCK_SIZE == 0)
222#error "PX_LOG_FS_CFG_ERASE_BLOCK_SIZE cannot be zero"
223#endif
224#if(!PX_VAL_IS_PWR_OF_TWO(PX_LOG_FS_CFG_ERASE_BLOCK_SIZE))
225#error "PX_LOG_FS_CFG_ERASE_BLOCK_SIZE must be a multiple of two, e.g. 1, 2, 4, 8, 16, ..."
226#endif
227
228#ifdef __cplusplus
229extern "C" {
230#endif
231/* _____DEFINITIONS__________________________________________________________ */
232
233/* _____TYPE DEFINITIONS_____________________________________________________ */
234/// Error codes
235typedef enum
236{
237 PX_LOG_FS_ERR_NONE = 0, ///< No error
238 PX_LOG_FS_ERR_EMPTY, ///< File is empty
239 PX_LOG_FS_ERR_FULL, ///< File is full
240 PX_LOG_FS_ERR_WRITE_FAIL, ///< Failed to write
241 PX_LOG_FS_ERR_NO_RECORD, ///< No record found
242 PX_LOG_FS_ERR_FATAL, ///< Fatal file system error
244
245/// Specification of data address in Serial Flash
246typedef struct
247{
248 uint16_t page; ///< Page
249 uint16_t offset; ///< Offset inside page
251
252typedef struct
253{
254 uint16_t fs_page_start; ///< First page in file system (must be on the start of an erase block)
255 uint16_t fs_page_end; ///< Last page in file system (must be on the end of an erase block)
256 uint16_t page_first; ///< First page with records (PX_LOG_FS_PAGE_INVALID if empty)
257 uint16_t page_last; ///< Last page with records (PX_LOG_FS_PAGE_INVALID if empty)
258 uint16_t page_nr_next; ///< Next page number to use (starts at 0)
259 px_log_fs_adr_t adr_wr; ///< Next write address (open position)
260 px_log_fs_adr_t adr_rd; ///< Current read address
261 bool archive_flag; ///< Flag is set if current read record is archived
263
264/* _____GLOBAL VARIABLES_____________________________________________________ */
265
266/* _____GLOBAL FUNCTION DECLARATIONS_________________________________________ */
267/**
268 * Initialise log file system.
269 *
270 * @param handle Pointer to file system handle structure
271 * @param fs_page_start First page in file system (must be on the start of an erase block)
272 * @param fs_page_end Last page in file system (must be on the end of an erase block)
273 *
274 * @retval PX_LOG_FS_ERR_NONE File system was succesfully indexed
275 */
277 uint16_t fs_page_start,
278 uint16_t fs_page_end);
279
280/**
281 * Reset log file system.
282 *
283 * @param handle Pointer to file system handle structure
284 * @param fs_page_start First page in file system (must be on the start of an erase block)
285 * @param fs_page_end Last page in file system (must be on the end of an erase block)
286 *
287 * @retval PX_LOG_FS_ERR_NONE File system was succesfully resetted
288 */
290 uint16_t fs_page_start,
291 uint16_t fs_page_end);
292/**
293 * Read first (oldest) record.
294 *
295 * @param handle Pointer to file system handle structure
296 * @param data Pointer to buffer where record data must be
297 * copied to
298 * @param nr_of_bytes Number of bytes to copy from record
299 *
300 * @retval PX_LOG_FS_ERR_NONE Valid record data found and copied into
301 * structure
302 * @retval PX_LOG_FS_ERR_NO_RECORD No record found
303 */
305 void * data,
306 size_t nr_of_bytes);
307
308/**
309 * Read first (oldest) unarchived record.
310 *
311 * @param handle Pointer to file system handle structure
312 * @param data Pointer to buffer where record data must be
313 * copied to
314 * @param nr_of_bytes Number of bytes to copy from record
315 *
316 * @retval PX_LOG_FS_ERR_NONE Valid record data found and copied into
317 * structure
318 * @retval PX_LOG_FS_ERR_NO_RECORD No record found
319 */
321 void * data,
322 size_t nr_of_bytes);
323/**
324 * Read next (newer) record.
325 *
326 * @param handle Pointer to file system handle structure
327 * @param data Pointer to buffer where record data must be
328 * copied to
329 * @param nr_of_bytes Number of bytes to copy from record
330 *
331 * @retval PX_LOG_FS_ERR_NONE Valid record data found and copied into
332 * structure
333 * @retval PX_LOG_FS_ERR_NO_RECORD No record found
334 */
336 void * data,
337 size_t nr_of_bytes);
338
339/**
340 * Read last (newest) record.
341 *
342 * @param handle Pointer to file system handle structure
343 * @param data Pointer to buffer where record data must be
344 * copied to
345 * @param nr_of_bytes Number of bytes to copy from record
346 *
347 * @retval PX_LOG_FS_ERR_NONE Valid record data found and copied into
348 * structure
349 * @retval PX_LOG_FS_ERR_NO_RECORD No record found
350 */
352 void * data,
353 size_t nr_of_bytes);
354
355/**
356 * Read previous (older) record.
357 *
358 * @param handle Pointer to file system handle structure
359 * @param data Pointer to buffer where record data must be
360 * copied to
361 * @param nr_of_bytes Number of bytes to copy from record
362 *
363 * @retval PX_LOG_FS_ERR_NONE Valid record data found and copied into
364 * structure
365 * @retval PX_LOG_FS_ERR_NO_RECORD No record found
366 */
368 void * data,
369 size_t nr_of_bytes);
370
371/**
372 * See if currently read record is archived or not.
373 *
374 * NB! Record *MUST* have been succesfully read first.
375 *
376 * @param handle Pointer to file system handle structure
377 *
378 * @retval true record is marked as archived
379 * @retval false record is NOT marked as archived
380 *
381 */
383{
384 return handle->archive_flag;
385}
386
387/**
388 * Set currently read record as archived.
389 *
390 * NB! Record *MUST* have been succesfully read first.
391 *
392 * As a side effect, if this is the last record in the page, then the page
393 * marker is set as archived for quick indexing.
394 *
395 * It is important that the records are marked as archived starting from the
396 * oldest to the newest in sequence otherwise the algorithm that mark a whole
397 * page as archived will not work correctly.
398 *
399 * @param handle Pointer to file system handle structure
400 *
401 * @retval PX_LOG_FS_ERR_NONE Success. Record is set as archived.
402 * @retval PX_LOG_FS_ERR_WRITE_FAIL Failed to set record as archived.
403 */
405
406/**
407 * Write a record to the file.
408 *
409 * A new record is appended to the end of the list of records. The maximum
410 * number of bytes that can be stored in the record is specified by
411 * PX_LOG_FS_CFG_DATA_SIZE.
412 *
413 * @param handle Pointer to file system handle structure
414 * @param data Pointer to buffer containing data that must
415 * be stored in the record
416 * @param nr_of_bytes Number of bytes that must be written
417 *
418 * @retval PX_LOG_FS_ERR_NONE Success
419 * @retval PX_LOG_FS_ERR_FULL File (or file system) is full
420 * @retval PX_LOG_FS_ERR_WRITE_FAIL Failed to write record (verify failed)
421 */
423 const void * data,
424 size_t nr_of_bytes);
425
426/**
427 * Report log file system info.
428 *
429 * @param handle Pointer to file system handle structure
430 */
432
433/* _____MACROS_______________________________________________________________ */
434
435#ifdef __cplusplus
436}
437#endif
438
439/// @}
440#endif
uint16_t fs_page_start
First page in file system (must be on the start of an erase block)
Definition: px_log_fs.h:254
uint16_t page
Page.
Definition: px_log_fs.h:248
uint16_t offset
Offset inside page.
Definition: px_log_fs.h:249
uint16_t page_nr_next
Next page number to use (starts at 0)
Definition: px_log_fs.h:258
bool archive_flag
Flag is set if current read record is archived.
Definition: px_log_fs.h:261
px_log_fs_adr_t adr_rd
Current read address.
Definition: px_log_fs.h:260
uint16_t page_last
Last page with records (PX_LOG_FS_PAGE_INVALID if empty)
Definition: px_log_fs.h:257
uint16_t page_first
First page with records (PX_LOG_FS_PAGE_INVALID if empty)
Definition: px_log_fs.h:256
px_log_fs_adr_t adr_wr
Next write address (open position)
Definition: px_log_fs.h:259
uint16_t fs_page_end
Last page in file system (must be on the end of an erase block)
Definition: px_log_fs.h:255
px_log_fs_err_t px_log_fs_reset(px_log_fs_handle_t *handle, uint16_t fs_page_start, uint16_t fs_page_end)
Reset log file system.
Definition: px_log_fs.c:550
px_log_fs_err_t px_log_fs_init(px_log_fs_handle_t *handle, uint16_t fs_page_start, uint16_t fs_page_end)
Initialise log file system.
Definition: px_log_fs.c:452
px_log_fs_err_t px_log_fs_rd_next(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read next (newer) record.
Definition: px_log_fs.c:675
px_log_fs_err_t px_log_fs_rd_first(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read first (oldest) record.
Definition: px_log_fs.c:607
px_log_fs_err_t px_log_fs_rd_previous(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read previous (older) record.
Definition: px_log_fs.c:755
px_log_fs_err_t px_log_fs_wr(px_log_fs_handle_t *handle, const void *data, size_t nr_of_bytes)
Write a record to the file.
Definition: px_log_fs.c:897
px_log_fs_err_t
Error codes.
Definition: px_log_fs.h:236
px_log_fs_err_t px_log_fs_rd_first_unarchived(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read first (oldest) unarchived record.
Definition: px_log_fs.c:624
void px_log_fs_dbg_report_info(px_log_fs_handle_t *handle)
Report log file system info.
Definition: px_log_fs.c:1000
bool px_log_fs_rd_rec_is_archived(px_log_fs_handle_t *handle)
See if currently read record is archived or not.
Definition: px_log_fs.h:382
px_log_fs_err_t px_log_fs_rd_rec_set_archive(px_log_fs_handle_t *handle)
Set currently read record as archived.
Definition: px_log_fs.c:818
px_log_fs_err_t px_log_fs_rd_last(px_log_fs_handle_t *handle, void *data, size_t nr_of_bytes)
Read last (newest) record.
Definition: px_log_fs.c:739
@ PX_LOG_FS_ERR_NONE
No error.
Definition: px_log_fs.h:237
@ PX_LOG_FS_ERR_WRITE_FAIL
Failed to write.
Definition: px_log_fs.h:240
@ PX_LOG_FS_ERR_NO_RECORD
No record found.
Definition: px_log_fs.h:241
@ PX_LOG_FS_ERR_EMPTY
File is empty.
Definition: px_log_fs.h:238
@ PX_LOG_FS_ERR_FATAL
Fatal file system error.
Definition: px_log_fs.h:242
@ PX_LOG_FS_ERR_FULL
File is full.
Definition: px_log_fs.h:239
Specification of data address in Serial Flash.
Definition: px_log_fs.h:247