balbekova 7 年之前
父节点
当前提交
2d2b004fc8
共有 6 个文件被更改,包括 1151 次插入0 次删除
  1. 23 0
      modules/common/hal.c
  2. 14 0
      modules/common/hal.h
  3. 438 0
      modules/log/log.c
  4. 71 0
      modules/log/log.h
  5. 422 0
      modules/log/ringfs.c
  6. 183 0
      modules/log/ringfs.h

+ 23 - 0
modules/common/hal.c

@@ -0,0 +1,23 @@
+/*
+ * hal.c
+ *
+ *  Created on: 14.06.2017
+ *      Author: balbekova
+ */
+
+
+#include "stm32f4xx.h"
+#include "log.h"
+#include "snmp_api.h"
+#include "trap_api.h"
+#include <stddef.h>
+#include "FreeRTOS.h"
+#include "task.h"
+
+void Reboot(void) {
+	SNMP_SendUserTrap(DEVICE_REBOOTED);
+	log_event_data(LOG_SYSTEM_BOOT, "Администратор");
+	vTaskDelay(1010);
+    NVIC_SystemReset();
+}
+

+ 14 - 0
modules/common/hal.h

@@ -0,0 +1,14 @@
+/*
+ * hal.h
+ *
+ *  Created on: 14.06.2017
+ *      Author: balbekova
+ */
+
+#ifndef HAL_H_
+#define HAL_H_
+
+void Reboot(void);
+
+
+#endif /* HAL_H_ */

+ 438 - 0
modules/log/log.c

@@ -0,0 +1,438 @@
+#include "log.h"
+#include "rtc.h"
+#include "ringfs.h"
+#include "spi_flash.h"
+
+#include "FreeRTOS.h"
+#include "task.h"
+#include "semphr.h"
+
+#include <string.h>
+
+const char* logsStrShortRu[] =
+{
+	"Перезагрузка контроллера",
+    "Сброс настроек",
+    "Обновление ПО",
+    "Смена пароля",
+    "Авторизация",
+    "Тест ИБП",
+    "Выключение ИБП",
+    "Авария дискр. входа 1",
+    "Состояние выхода 1",
+    "Состояние выхода 2",
+    "Авария температуры",
+    "Авария вх. напряжения",
+    "Низкий заряд АКБ",
+    "Авария нагрузки",
+    "Авария связи с ИБП",
+    "Авария отключения АКБ",
+};
+
+bool flUpdateLog = false;
+static bool fLogInit = false;  // Флаг инициализации журнала
+
+#define LOG_TIME	1000*10//*60*10
+
+#define LOG_FLASH_SECTOR_OFFSET	4
+
+#define ALARM_LOG_FLASH_SECTOR_OFFSET	258
+
+static int op_sector_erase(struct ringfs_flash_partition *flash, int address) {
+	(void)flash;
+	int ret;
+	ret = spi_flash_erase_sector(address, 0);
+	return ret;
+}
+
+static ssize_t op_program(struct ringfs_flash_partition *flash, int address, const void *data, size_t size) {
+	(void)flash;
+	int ret;
+	ret = spi_flash_write(address, data, size, 0);
+	return ret;
+}
+
+static ssize_t op_read(struct ringfs_flash_partition *flash, int address, void *data, size_t size) {
+	(void)flash;
+	int ret;
+	ret = spi_flash_read(address, data, size, 0);
+	return ret;
+}
+
+static struct ringfs_flash_partition ringfs_flash = {
+	.sector_offset = LOG_FLASH_SECTOR_OFFSET,
+
+	.sector_erase = op_sector_erase,
+	.program = op_program,
+	.read = op_read,
+};
+
+static struct ringfs fs;
+
+static struct ringfs_flash_partition ringfs_flash2 = {
+	.sector_offset = ALARM_LOG_FLASH_SECTOR_OFFSET,
+
+	.sector_erase = op_sector_erase,
+	.program = op_program,
+	.read = op_read,
+};
+
+static struct ringfs fs2;
+
+static SemaphoreHandle_t log_mutex;
+
+void log_task(void)
+{
+	for(;;){
+		flUpdateLog = true;
+		vTaskDelay(500);//LOG_TIME
+		/*log_entry_t entry;
+		log_fetch(&entry, portMAX_DELAY);*/
+	}
+}
+
+void log_init(bool format) {
+	DBG printf(">>> Event log\n");
+
+	if (!spi_flash_desc.present)
+		return;
+	ringfs_flash.sector_size = spi_flash_desc.sector_size;
+	ringfs_flash.sector_count = spi_flash_desc.sector_count/2 - LOG_FLASH_SECTOR_OFFSET;
+
+	ringfs_init(&fs, &ringfs_flash, LOG_ENTRY_VERSION, sizeof(log_entry_t));
+	if (format || ringfs_scan(&fs) != 0)
+		ringfs_format(&fs);
+
+	ringfs_flash2.sector_size = spi_flash_desc.sector_size;
+	ringfs_flash2.sector_count = spi_flash_desc.sector_count/2 - LOG_FLASH_SECTOR_OFFSET;
+
+	ringfs_init(&fs2, &ringfs_flash2, LOG_ENTRY_VERSION, sizeof(log_entry_t));
+	if (format || ringfs_scan(&fs2) != 0)
+		ringfs_format(&fs2);
+
+	fLogInit = true;
+
+	log_mutex = xSemaphoreCreateMutex();
+
+	xTaskCreate(log_task, ( char * ) "log_task", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY, NULL);
+}
+
+
+int capacity_flash = 0;
+int count_flash = 0;
+int log_test(void) {
+	int ret;
+	log_entry_t entry;
+
+	log_init(false);
+	capacity_flash = ringfs_capacity(&fs);
+	count_flash = ringfs_count_exact(&fs);
+	DBG printf("\tCapacity:   %d\n", capacity_flash);
+	DBG printf("\tCount:      %d\n", count_flash);
+
+	DBG printf("\tAppending   ");
+//	ret = log_event(LOG_SYSTEM_DEFCONFIG, 0, 0);
+	DBG printf("%s\n", ret == 0 ? "ok" : "error");
+	if (ret == 0)
+		return -1;
+
+//	ret = log_event(LOG_SYSTEM_DEFCONFIG, 0, 512);
+	entry.timestamp = 0;
+	entry.type = 0;
+	DBG printf("\tFetching    ");
+	if (log_fetch(&entry, portMAX_DELAY) == 0){
+		DBG printf("ok, time=%d, type=%d\n", entry.timestamp, entry.type);
+		log_fetch(&entry, portMAX_DELAY);
+		entry.timestamp = 0;
+			entry.type = 0;
+		log_fetch(&entry, portMAX_DELAY);
+		entry.timestamp = 0;
+					entry.type = 0;
+				log_fetch(&entry, portMAX_DELAY);
+				entry.timestamp = 0;
+							entry.type = 0;
+						log_fetch(&entry, portMAX_DELAY);
+		return 0;
+	}
+	else {
+		DBG printf("fail\n");
+		return -1;
+	}
+
+	DBG printf("\tDiscarding  ");
+	if (log_discard(&entry,portMAX_DELAY) == 0)
+		DBG printf("ok\n");
+	else {
+		DBG printf("fail\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+int log_append(log_entry_t *entry) {
+	int ret;
+	TM_RTC_t data;
+	ret = xSemaphoreTake( log_mutex, portMAX_DELAY );
+	if (ret == pdFALSE)
+		return ret;
+	if (!entry->timestamp){
+		TM_RTC_GetDateTime(&data, TM_RTC_Format_BIN);
+		entry->timestamp = data.unix;
+	}
+	if(entry->type == LOG_VALUE)
+		ringfs_append(&fs, entry);
+	else
+		ringfs_append(&fs2, entry);
+	xSemaphoreGive(log_mutex);
+	return ret;
+}
+
+int log_fetch(log_entry_t *entry, uint32_t timeout) {
+	int ret;
+	ret = xSemaphoreTake( log_mutex, (TickType_t)timeout );
+	if (ret == pdFALSE)
+		return ret;
+	if(entry->type == LOG_VALUE)
+		ret = ringfs_fetch(&fs, entry);
+	else
+		ret = ringfs_fetch(&fs2, entry);
+	xSemaphoreGive(log_mutex);
+	return ret;
+}
+
+int log_rewind(log_entry_t *entry, uint32_t timeout) {
+	int ret;
+	ret = xSemaphoreTake( log_mutex, (TickType_t)timeout );
+	if (ret == pdFALSE)
+		return ret;
+	if(entry->type == LOG_VALUE)
+		ret = ringfs_rewind(&fs);
+	else
+		ret = ringfs_rewind(&fs2);
+	xSemaphoreGive(log_mutex);
+	return ret;
+}
+
+int log_discard(log_entry_t *entry, uint32_t timeout) {
+	int ret;
+	ret = xSemaphoreTake( log_mutex, (TickType_t)timeout );
+	if (ret == pdFALSE)
+		return ret;
+	if(entry->type == LOG_VALUE)
+		ret = ringfs_discard(&fs);
+	else
+		ret = ringfs_discard(&fs2);
+	xSemaphoreGive(log_mutex);
+	return ret;
+}
+
+void log_event_data(log_type_t type, char *data)
+{
+	log_entry_t entry_data;
+
+	entry_data.timestamp = 0;
+	entry_data.type = type;
+	strncpy(entry_data.data, data, 50);
+
+	log_append(&entry_data);
+}
+
+void log_add(char *log_data)
+{
+	char buf_value[50];
+	uint8_t i, len;
+
+
+	memset(buf_value, 0, 50);
+	len = strlen(log_data);
+
+	strncpy(buf_value, log_data, len);
+	buf_value[0] = '\"';
+
+	for(i = 0; i < len; i++)
+	{
+		if(buf_value[i] == ' ')
+			buf_value[i] = ';';
+	}
+	buf_value[len - 1] = ';';
+
+	if(fs.write.slot>67)
+	{
+		log_entry_t entry_data;
+		entry_data.timestamp = 0;
+		log_event_data(LOG_VALUE, buf_value);
+	}
+	else
+	log_event_data(LOG_VALUE, buf_value);
+
+}
+
+/**
+  * @brief  Возвращает true если журнал проинициализирован
+  */
+bool LOG_IsInit()
+{
+  return fLogInit;
+}
+
+/**
+  * @brief  Возвращает общее количество страниц
+  */
+uint32_t LOG_GetPageCount(void)
+{
+  return (((ringfs_count_estimate(&fs)) / 10) + 1);
+}
+
+uint32_t LOG_GetTotalSTRCount(void)
+{
+	return ringfs_count_estimate(&fs);
+}
+
+void LOG_GetPage(char *str, uint32_t page)
+{
+	TM_RTC_t rtc_data;
+	log_entry_t entry;
+	char buf[20];
+	uint8_t i;
+	int start =LOG_GetTotalSTRCount();//(fs.write.sector*fs.slots_per_sector + fs.write.slot);
+
+	memset(buf, 0, 20);
+	for(i=0; i < 10; i++){
+		fs.cursor_position =  start - 10*(page-1) - 1 - i;
+		if(fs.cursor_position < 0)
+			break;
+		else{
+			fs.cursor.sector = (fs.read.sector + fs.cursor_position/fs.slots_per_sector)%fs.flash->sector_count;
+			fs.cursor.slot = fs.cursor_position%fs.slots_per_sector;
+		}
+		entry.type = LOG_VALUE;
+		log_fetch(&entry, portMAX_DELAY);
+		strncat(str, entry.data, strlen(entry.data));
+		TM_RTC_GetDateTimeFromUnix(&rtc_data, entry.timestamp);
+		sprintf(buf, "%02i.%02i.%02i %02i:%02i:%02i",  rtc_data.date, rtc_data.month,
+		rtc_data.year, rtc_data.hours, rtc_data.minutes, rtc_data.seconds);
+		strcat(str, buf);
+		strcat(str, "\",");
+	}
+}
+
+uint32_t LOG_GetData(int ptr, char *str, uint32_t size, bool start)
+{
+	TM_RTC_t rtc_data;
+	log_entry_t entry;
+	char buf[20];
+	uint8_t i;
+	entry.type = LOG_VALUE;
+	if(start)
+		log_rewind(&entry, portMAX_DELAY);
+
+	fs.cursor_position =  ptr/STRING_SIZE;
+	fs.cursor.sector = (fs.read.sector + fs.cursor_position/fs.slots_per_sector)%fs.flash->sector_count;
+	fs.cursor.slot = fs.cursor_position%fs.slots_per_sector;
+
+	for(i = 0; i < size/STRING_SIZE; i++)
+	{
+		log_fetch(&entry, portMAX_DELAY);
+		strncat(str, &entry.data[1], (strlen(entry.data) - 1));
+		TM_RTC_GetDateTimeFromUnix(&rtc_data, entry.timestamp);
+		sprintf(buf, "%02i.%02i.%02i %02i:%02i:%02i",  rtc_data.date, rtc_data.month,
+		rtc_data.year, rtc_data.hours, rtc_data.minutes, rtc_data.seconds);
+		strcat(str, buf);
+		strcat(str, "\n");
+	}
+	return strlen(str);
+
+}
+
+/**
+  * @brief  Возвращает общее количество страниц
+  */
+uint32_t History_GetPageCount(void)
+{
+  return (((ringfs_count_estimate(&fs2)) / 10) + 1);
+}
+
+uint32_t History_GetTotalSTRCount(void)
+{
+	return ringfs_count_estimate(&fs2);
+}
+
+void History_GetPage(char *str, uint32_t page)
+{
+	TM_RTC_t rtc_data;
+	log_entry_t entry;
+	char buf[20];
+	uint8_t i;
+	int start =History_GetTotalSTRCount();//(fs.write.sector*fs.slots_per_sector + fs.write.slot);
+
+	memset(buf, 0, 20);
+	for(i=0; i < 10; i++){
+		fs2.cursor_position =  start - 10*(page-1) - 1 - i;
+		if(fs2.cursor_position < 0)
+			break;
+		else{
+			fs2.cursor.sector = (fs2.read.sector + fs2.cursor_position/fs.slots_per_sector)%fs2.flash->sector_count;
+			fs2.cursor.slot = fs2.cursor_position%fs2.slots_per_sector;
+		}
+		entry.type = LOG_LOGIN;
+		log_fetch(&entry, portMAX_DELAY);
+		strcat(str, "\"");
+		strncat(str, logsStrShortRu[entry.type], (strlen(logsStrShortRu[entry.type]) ));
+		strcat(str, ";");
+		strncat(str, entry.data, (strlen(entry.data) ));
+		strcat(str, ";");
+		TM_RTC_GetDateTimeFromUnix(&rtc_data, entry.timestamp);
+		sprintf(buf, "%02i.%02i.%02i %02i:%02i:%02i",  rtc_data.date, rtc_data.month,
+		rtc_data.year, rtc_data.hours, rtc_data.minutes, rtc_data.seconds);
+		strcat(str, buf);
+		strcat(str, "\",");
+	}
+}
+
+uint32_t History_GetData(int ptr, char *str, uint32_t size, bool start)
+{
+	TM_RTC_t rtc_data;
+	log_entry_t entry;
+	char buf[20];
+	char temp_str[FILE_BUF_MAX_LEN];
+	uint8_t i;
+	uint16_t len;
+	entry.type = LOG_LOGIN;
+	if(start)
+		log_rewind(&entry, portMAX_DELAY);
+
+	fs2.cursor_position =  ptr/STRING_SIZE_HISTORY;
+	fs2.cursor.sector = (fs2.read.sector + fs2.cursor_position/fs2.slots_per_sector)%fs2.flash->sector_count;
+	fs2.cursor.slot = fs2.cursor_position%fs2.slots_per_sector;
+
+	for(i = 0; i < size/STRING_SIZE_HISTORY; i++)
+	{
+		memset(temp_str, 0, 100);
+		log_fetch(&entry, portMAX_DELAY);
+		strncat(temp_str, logsStrShortRu[entry.type], (strlen(logsStrShortRu[entry.type])));
+		strcat(temp_str, ";");
+		strncat(temp_str, entry.data, (strlen(entry.data)));
+		strcat(temp_str, ";");
+		TM_RTC_GetDateTimeFromUnix(&rtc_data, entry.timestamp);
+		sprintf(buf, "%02i.%02i.%02i %02i:%02i:%02i",  rtc_data.date, rtc_data.month,
+		rtc_data.year, rtc_data.hours, rtc_data.minutes, rtc_data.seconds);
+		strcat(temp_str, buf);
+
+		len = strlen(temp_str);
+
+		  if (len <= STRING_SIZE_HISTORY - 1)
+		  {
+		    memset(&temp_str[len], ' ', STRING_SIZE_HISTORY - len - 1);
+		    strcat(temp_str, "\n");
+		  }
+		  else
+		  {
+		    temp_str[STRING_SIZE - 1] = 0xa;
+		  }
+		  strncat(str, temp_str, STRING_SIZE_HISTORY);
+
+	}
+	return strlen(str);
+
+}

+ 71 - 0
modules/log/log.h

@@ -0,0 +1,71 @@
+#ifndef LOG_H
+#define LOG_H
+
+#include "main.h"
+
+#define FILE_BUF_MAX_LEN  			10*STRING_SIZE_HISTORY // Размер временного буфера для отправки/копирования Лог файла
+#define FILE_BUF_MAX_LEN_LOG 		10*STRING_SIZE // Размер временного буфера для отправки/копирования Лог файла
+#define STRING_SIZE		  			64
+#define STRING_SIZE_HISTORY		  	100
+
+char logFileBuf[FILE_BUF_MAX_LEN];
+
+typedef enum {
+	LOG_SYSTEM_BOOT = 0x00,			// device booted
+	LOG_SYSTEM_DEFCONFIG,	// default config applied
+	LOG_UPDATE_SOFT,		//
+	LOG_PSW_CHANGE,		//
+	LOG_LOGIN,		//
+	LOG_TEST_UPS,		//
+	LOG_SHUTDOWN_UPS,
+	LOG_ALARM_DIO,
+	LOG_DO0_STATE,
+	LOG_DO1_STATE,
+	LOG_ALARM_TEMP,
+	LOG_ALARM_LINE,
+	LOG_ALARM_LOW_BAT,
+	LOG_ALARM_POWER,
+	LOG_ALARM_UPS,
+	LOG_ALARM_AKB,
+	LOG_VALUE,
+	LOG_NONE,
+} __packed log_type_t;
+
+typedef struct {
+	uint32_t timestamp;
+	log_type_t type:8;
+	char data[50];
+} __packed log_entry_t;
+
+#define LOG_ENTRY_VERSION 1
+
+extern void log_init(bool format);
+extern int log_test(void);
+extern int log_append(log_entry_t *entry);
+extern int log_fetch(log_entry_t *entry, uint32_t timeout);
+extern int log_rewind(log_entry_t *entry, uint32_t timeout);
+extern int log_discard(log_entry_t *entry, uint32_t timeout);
+
+void log_add(char *log_data);
+void log_event_data(log_type_t type, char *data);
+
+/**
+  * @brief  Возвращает true если журнал проинициализирован
+  */
+bool LOG_IsInit();
+
+/**
+  * @brief  Возвращает общее количество страниц
+  */
+uint32_t LOG_GetPageCount(void);
+uint32_t LOG_GetTotalSTRCount(void);
+void LOG_GetPage(char *str, uint32_t page);
+uint32_t LOG_GetData(int ptr, char *str, uint32_t size, bool start);
+
+uint32_t History_GetPageCount(void);
+uint32_t History_GetTotalSTRCount(void);
+void History_GetPage(char *str, uint32_t page);
+uint32_t History_GetData(int ptr, char *str, uint32_t size, bool start);
+
+
+#endif /* LOG_H */

+ 422 - 0
modules/log/ringfs.c

@@ -0,0 +1,422 @@
+/*
+ * Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+/**
+ * @defgroup ringfs_impl RingFS implementation
+ * @details
+ *
+ * @{
+ */
+
+#include <ringfs.h>
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "main.h"
+
+
+/**
+ * @defgroup sector
+ * @{
+ */
+
+enum sector_status {
+    SECTOR_ERASED     = 0xFFFFFFFF, /**< Default state after NOR flash erase. */
+    SECTOR_FREE       = 0xFFFFFF00, /**< Sector erased. */
+    SECTOR_IN_USE     = 0xFFFF0000, /**< Sector contains valid data. */
+    SECTOR_ERASING    = 0xFF000000, /**< Sector should be erased. */
+    SECTOR_FORMATTING = 0x00000000, /**< The entire partition is being formatted. */
+};
+
+struct sector_header {
+    uint32_t status;
+    uint32_t version;
+};
+
+static int _sector_address(struct ringfs *fs, int sector_offset)
+{
+    return (fs->flash->sector_offset + sector_offset) * fs->flash->sector_size;
+}
+
+static int _sector_get_status(struct ringfs *fs, int sector, uint32_t *status)
+{
+    return fs->flash->read(fs->flash,
+            _sector_address(fs, sector) + offsetof(struct sector_header, status),
+            status, sizeof(*status));
+}
+
+static int _sector_set_status(struct ringfs *fs, int sector, uint32_t status)
+{
+    return fs->flash->program(fs->flash,
+            _sector_address(fs, sector) + offsetof(struct sector_header, status),
+            &status, sizeof(status));
+}
+
+static int _sector_free(struct ringfs *fs, int sector)
+{
+    int sector_addr = _sector_address(fs, sector);
+    _sector_set_status(fs, sector, SECTOR_ERASING);
+    fs->flash->sector_erase(fs->flash, sector_addr);
+    fs->flash->program(fs->flash,
+            sector_addr + offsetof(struct sector_header, version),
+            &fs->version, sizeof(fs->version));
+    _sector_set_status(fs, sector, SECTOR_FREE);
+    return 0;
+}
+
+/**
+ * @}
+ * @defgroup slot
+ * @{
+ */
+
+enum slot_status {
+    SLOT_ERASED   = 0xFFFFFFFF, /**< Default state after NOR flash erase. */
+    SLOT_RESERVED = 0xFFFFFF00, /**< Write started but not yet committed. */
+    SLOT_VALID    = 0xFFFF0000, /**< Write committed, slot contains valid data. */
+    SLOT_GARBAGE  = 0xFF000000, /**< Slot contents discarded and no longer valid. */
+};
+
+struct slot_header {
+    uint32_t status;
+};
+
+static int _slot_address(struct ringfs *fs, struct ringfs_loc *loc)
+{
+    return _sector_address(fs, loc->sector) +
+           sizeof(struct sector_header) +
+           (sizeof(struct slot_header) + fs->object_size) * loc->slot;
+}
+
+static int _slot_get_status(struct ringfs *fs, struct ringfs_loc *loc, uint32_t *status)
+{
+    return fs->flash->read(fs->flash,
+            _slot_address(fs, loc) + offsetof(struct slot_header, status),
+            status, sizeof(*status));
+}
+
+static int _slot_set_status(struct ringfs *fs, struct ringfs_loc *loc, uint32_t status)
+{
+    return fs->flash->program(fs->flash, 
+            _slot_address(fs, loc) + offsetof(struct slot_header, status),
+            &status, sizeof(status));
+}
+
+/**
+ * @}
+ * @defgroup loc
+ * @{
+ */
+
+static bool _loc_equal(struct ringfs_loc *a, struct ringfs_loc *b)
+{
+    return (a->sector == b->sector) && (a->slot == b->slot);
+}
+
+/** Advance a location to the beginning of the next sector. */
+static void _loc_advance_sector(struct ringfs *fs, struct ringfs_loc *loc)
+{
+    loc->slot = 0;
+    loc->sector++;
+    if (loc->sector >= fs->flash->sector_count)
+        loc->sector = 0;
+}
+
+/** Advance a location to the next slot, advancing the sector too if needed. */
+static void _loc_advance_slot(struct ringfs *fs, struct ringfs_loc *loc)
+{
+    loc->slot++;
+    if (loc->slot >= fs->slots_per_sector)
+        _loc_advance_sector(fs, loc);
+}
+
+/**
+ * @}
+ */
+
+/* And here we go. */
+
+int ringfs_init(struct ringfs *fs, struct ringfs_flash_partition *flash, uint32_t version, int object_size)
+{
+    /* Copy arguments to instance. */
+    fs->flash = flash;
+    fs->version = version;
+    fs->object_size = object_size;
+
+    /* Precalculate commonly used values. */
+    fs->slots_per_sector = (fs->flash->sector_size - sizeof(struct sector_header)) /
+                           (sizeof(struct slot_header) + fs->object_size);
+
+    return 0;
+}
+
+int ringfs_format(struct ringfs *fs)
+{
+    /* Mark all sectors to prevent half-erased filesystems. */
+    for (int sector=0; sector<fs->flash->sector_count; sector++)
+        _sector_set_status(fs, sector, SECTOR_FORMATTING);
+
+    /* Erase, update version, mark as free. */
+    for (int sector=0; sector<fs->flash->sector_count; sector++)
+        _sector_free(fs, sector);
+
+    /* Start reading & writing at the first sector. */
+    fs->read.sector = 0;
+    fs->read.slot = 0;
+    fs->write.sector = 0;
+    fs->write.slot = 0;
+    fs->cursor.sector = 0;
+    fs->cursor.slot = 0;
+
+    return 0;
+}
+
+int ringfs_scan(struct ringfs *fs)
+{
+    uint32_t previous_sector_status = SECTOR_FREE;
+    /* The read sector is the first IN_USE sector *after* a FREE sector
+     * (or the first one). */
+    int read_sector = 0;
+    /* The write sector is the last IN_USE sector *before* a FREE sector
+     * (or the last one). */
+    int write_sector = fs->flash->sector_count - 1;
+    /* There must be at least one FREE sector available at all times. */
+    bool free_seen = false;
+    /* If there's no IN_USE sector, we start at the first one. */
+    bool used_seen = false;
+
+    /* Iterate over sectors. */
+    for (int sector=0; sector<fs->flash->sector_count; sector++) {
+        int addr = _sector_address(fs, sector);
+
+        /* Read sector header. */
+        struct sector_header header;
+        fs->flash->read(fs->flash, addr, &header, sizeof(header));
+
+        /* Detect partially-formatted partitions. */
+        if (header.status == SECTOR_FORMATTING) {
+        	DBG printf("ringfs_scan: partially formatted partition\r\n");
+            return -1;
+        }
+
+        /* Detect and fix partially erased sectors. */
+        if (header.status == SECTOR_ERASING || header.status == SECTOR_ERASED) {
+            _sector_free(fs, addr);
+            header.status = SECTOR_FREE;
+        }
+
+        /* Detect corrupted sectors. */
+        if (header.status != SECTOR_FREE && header.status != SECTOR_IN_USE) {
+        	DBG printf("ringfs_scan: corrupted sector %d\r\n", sector);
+            return -1;
+        }
+
+        /* Detect obsolete versions. We can't do this earlier because the version
+         * could have been invalid due to a partial erase. */
+        if (header.version != fs->version) {
+            DBG printf("ringfs_scan: incompatible version 0x%08"PRIx32"\r\n", header.version);
+            return -1;
+        }
+
+        /* Record the presence of a FREE sector. */
+        if (header.status == SECTOR_FREE)
+            free_seen = true;
+
+        /* Record the presence of a IN_USE sector. */
+        if (header.status == SECTOR_IN_USE)
+            used_seen = true;
+
+        /* Update read & write sectors according to the above rules. */
+        if (header.status == SECTOR_IN_USE && previous_sector_status == SECTOR_FREE)
+            read_sector = sector;
+        if (header.status == SECTOR_FREE && previous_sector_status == SECTOR_IN_USE)
+            write_sector = sector-1;
+
+        previous_sector_status = header.status;
+    }
+
+    /* Detect the lack of a FREE sector. */
+    if (!free_seen) {
+    	DBG printf("ringfs_scan: invariant violated: no FREE sector found\r\n");
+        return -1;
+    }
+
+    /* Start writing at the first sector if the filesystem is empty. */
+    if (!used_seen) {
+        write_sector = 0;
+    }
+
+    /* Scan the write sector and skip all occupied slots at the beginning. */
+    fs->write.sector = write_sector;
+    fs->write.slot = 0;
+    while (fs->write.sector == write_sector) {
+        uint32_t status;
+        _slot_get_status(fs, &fs->write, &status);
+        if (status == SLOT_ERASED)
+            break;
+
+        _loc_advance_slot(fs, &fs->write);
+    }
+    /* If the sector was full, we're at the beginning of a FREE sector now. */
+
+    /* Position the read head at the start of the first IN_USE sector, then skip
+     * over garbage/invalid slots until something of value is found or we reach
+     * the write head which means there's no data. */
+    fs->read.sector = read_sector;
+    fs->read.slot = 0;
+    while (!_loc_equal(&fs->read, &fs->write)) {
+        uint32_t status;
+        _slot_get_status(fs, &fs->read, &status);
+        if (status == SLOT_VALID)
+            break;
+
+        _loc_advance_slot(fs, &fs->read);
+    }
+
+    /* Move the read cursor to the read head position. */
+    ringfs_rewind(fs);
+
+    return 0;
+}
+
+int ringfs_capacity(struct ringfs *fs)
+{
+    return fs->slots_per_sector * (fs->flash->sector_count - 1);
+}
+
+int ringfs_count_estimate(struct ringfs *fs)
+{
+    int sector_diff = (fs->write.sector - fs->read.sector + fs->flash->sector_count) %
+        fs->flash->sector_count;
+
+    return sector_diff * fs->slots_per_sector + fs->write.slot - fs->read.slot;
+}
+
+int ringfs_count_exact(struct ringfs *fs)
+{
+    int count = 0;
+
+    /* Use a temporary loc for iteration. */
+    struct ringfs_loc loc = fs->read;
+    while (!_loc_equal(&loc, &fs->write)) {
+        uint32_t status;
+        _slot_get_status(fs, &loc, &status);
+        
+        if (status == SLOT_VALID)
+            count++;
+
+        _loc_advance_slot(fs, &loc);
+    }
+
+    return count;
+}
+
+int ringfs_cursor_position(struct ringfs *fs) {
+    return fs->cursor_position;
+}
+
+int ringfs_append(struct ringfs *fs, const void *object)
+{
+    uint32_t status;
+
+    /*
+     * There are three sectors involved in appending a value:
+     * - the sector where the append happens: it has to be writable
+     * - the next sector: it must be free (invariant)
+     * - the next-next sector: read & cursor heads are moved there if needed
+     */
+
+    /* Make sure the next sector is free. */
+    int next_sector = (fs->write.sector+1) % fs->flash->sector_count;
+    _sector_get_status(fs, next_sector, &status);
+    if (status != SECTOR_FREE) {
+        /* Next sector must be freed. But first... */
+
+        /* Move the read & cursor heads out of the way. */
+        if (fs->read.sector == next_sector)
+            _loc_advance_sector(fs, &fs->read);
+        if (fs->cursor.sector == next_sector)
+            _loc_advance_sector(fs, &fs->cursor);
+
+        /* Free the next sector. */
+        _sector_free(fs, next_sector);
+    }
+
+    /* Now we can make sure the current write sector is writable. */
+    _sector_get_status(fs, fs->write.sector, &status);
+    if (status == SECTOR_FREE) {
+        /* Free sector. Mark as used. */
+        _sector_set_status(fs, fs->write.sector, SECTOR_IN_USE);
+    } else if (status != SECTOR_IN_USE) {
+        printf("ringfs_append: corrupted filesystem\r\n");
+        return -1;
+    }
+
+    /* Preallocate slot. */
+    _slot_set_status(fs, &fs->write, SLOT_RESERVED);
+
+    /* Write object. */
+    fs->flash->program(fs->flash,
+            _slot_address(fs, &fs->write) + sizeof(struct slot_header),
+            object, fs->object_size);
+
+    /* Commit write. */
+    _slot_set_status(fs, &fs->write, SLOT_VALID);
+
+    /* Advance the write head. */
+    _loc_advance_slot(fs, &fs->write);
+
+    return 0;
+}
+
+int ringfs_fetch(struct ringfs *fs, void *object)
+{
+    /* Advance forward in search of a valid slot. */
+    while (!_loc_equal(&fs->cursor, &fs->write)) {
+        uint32_t status;
+
+        _slot_get_status(fs, &fs->cursor, &status);
+
+        if (status == SLOT_VALID) {
+            fs->flash->read(fs->flash,
+                    _slot_address(fs, &fs->cursor) + sizeof(struct slot_header),
+                    object, fs->object_size);
+            _loc_advance_slot(fs, &fs->cursor);
+            fs->cursor_position++;
+            return 0;
+        }
+
+        _loc_advance_slot(fs, &fs->cursor);
+    }
+
+    return -1;
+}
+
+int ringfs_discard(struct ringfs *fs)
+{
+    while (!_loc_equal(&fs->read, &fs->cursor)) {
+        _slot_set_status(fs, &fs->read, SLOT_GARBAGE);
+        _loc_advance_slot(fs, &fs->read);
+    }
+    fs->cursor_position = 0;
+    return 0;
+}
+
+int ringfs_rewind(struct ringfs *fs)
+{
+    fs->cursor = fs->read;
+    fs->cursor_position = 0;
+    return 0;
+}
+
+/**
+ * @}
+ */
+
+/* vim: set ts=4 sw=4 et: */

+ 183 - 0
modules/log/ringfs.h

@@ -0,0 +1,183 @@
+/*
+ * Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+#ifndef RINGFS_H
+#define RINGFS_H
+
+/**
+ * @defgroup ringfs_api RingFS API
+ * @{
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include "tinystdio.h"
+
+/**
+ * Flash memory+parition descriptor.
+ */
+struct ringfs_flash_partition
+{
+    int sector_size;            /**< Sector size, in bytes. */
+    int sector_offset;          /**< Partition offset, in sectors. */
+    int sector_count;           /**< Partition size, in sectors. */
+
+    /**
+     * Erase a sector.
+     * @param address Any address inside the sector.
+     * @returns Zero on success, -1 on failure.
+     */
+    int (*sector_erase)(struct ringfs_flash_partition *flash, int address);
+    /**
+     * Program flash memory bits by toggling them from 1 to 0.
+     * @param address Start address, in bytes.
+     * @param data Data to program.
+     * @param size Size of data.
+     * @returns size on success, -1 on failure.
+     */
+    ssize_t (*program)(struct ringfs_flash_partition *flash, int address, const void *data, size_t size);
+    /**
+     * Read flash memory.
+     * @param address Start address, in bytes.
+     * @param data Buffer to store read data.
+     * @param size Size of data.
+     * @returns size on success, -1 on failure.
+     */
+    ssize_t (*read)(struct ringfs_flash_partition *flash, int address, void *data, size_t size);
+};
+
+/** @private */
+struct ringfs_loc {
+    int sector;
+    int slot;
+};
+
+/**
+ * RingFS instance. Should be initialized with ringfs_init() befure use.
+ * Structure fields should not be accessed directly.
+ * */
+struct ringfs {
+    /* Constant values, set once at ringfs_init(). */
+    struct ringfs_flash_partition *flash;
+    uint32_t version;
+    int object_size;
+    /* Cached values. */
+    int slots_per_sector;
+
+    /* Read/write pointers. Modified as needed. */
+    struct ringfs_loc read;
+    struct ringfs_loc write;
+    struct ringfs_loc cursor;
+
+    int cursor_position;
+};
+
+/**
+ * Initialize a RingFS instance. Must be called before the instance can be used
+ * with the other ringfs_* functions.
+ *
+ * @param fs RingFS instance to be initialized.
+ * @param flash Flash memory interface. Must be implemented externally.
+ * @param version Object version. Should be incremented whenever the object's
+ *                semantics or size change in a backwards-incompatible way.
+ * @param object_size Size of one stored object, in bytes.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_init(struct ringfs *fs, struct ringfs_flash_partition *flash, uint32_t version, int object_size);
+
+/**
+ * Format the flash memory.
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_format(struct ringfs *fs);
+
+/**
+ * Scan the flash memory for a valid filesystem.
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_scan(struct ringfs *fs);
+
+/**
+ * Calculate maximum RingFS capacity.
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Maximum capacity on success, -1 on failure.
+ */
+int ringfs_capacity(struct ringfs *fs);
+
+/**
+ * Calculate approximate object count.
+ * Runs in O(1).
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Estimated object count on success, -1 on failure.
+ */
+int ringfs_count_estimate(struct ringfs *fs);
+
+/**
+ * Calculate exact object count.
+ * Runs in O(n).
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Exact object count on success, -1 on failure.
+ */
+int ringfs_count_exact(struct ringfs *fs);
+
+/**
+ * Get current cursor position.
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Current cursor position, -1 on failure.
+ */
+int ringfs_cursor_position(struct ringfs *fs);
+
+/**
+ * Append an object at the end of the ring. Deletes oldest objects as needed.
+ *
+ * @param fs Initialized RingFS instance.
+ * @param object Object to be stored.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_append(struct ringfs *fs, const void *object);
+
+/**
+ * Fetch next object from the ring, oldest-first. Advances read cursor.
+ *
+ * @param fs Initialized RingFS instance.
+ * @param object Buffer to store retrieved object.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_fetch(struct ringfs *fs, void *object);
+
+/**
+ * Discard all fetched objects up to the read cursor.
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_discard(struct ringfs *fs);
+
+/**
+ * Rewind the read cursor back to the oldest object.
+ *
+ * @param fs Initialized RingFS instance.
+ * @returns Zero on success, -1 on failure.
+ */
+int ringfs_rewind(struct ringfs *fs);
+
+/**
+ * @}
+ */
+
+#endif
+
+/* vim: set ts=4 sw=4 et: */