diff --git a/.ci/coverage.py b/.ci/coverage.py
new file mode 100755
index 0000000000..8c17617709
--- /dev/null
+++ b/.ci/coverage.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+from lcov_cobertura import LcovCobertura
+
+LCOV_FILE = 'build/coverage/test-coverage.info'
+OUT_FILE = 'build/coverage/test-coverage.xml'
+
+with open(LCOV_FILE) as fr:
+ data = fr.read()
+
+converter = LcovCobertura(data)
+res = converter.convert()
+
+with open(OUT_FILE, 'w') as fw:
+ fw.write(res)
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..75535efd69
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,9 @@
+## Summary
+Description of the changes present in this pull request.
+
+## Test Plan
+What was done to test these changes.
+
+## Issues
+Any issues associated with this PR. Use GitHub keywords (i.e. "closes", "resolves", "fixes") with the issue number to automatically close
+the issue when this PR is merged.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000000..308aeb7bf3
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,5 @@
+# Code of Conduct
+
+Telecom Infra Project has adopted a Code of Conduct that we expect project participants to adhere to.
+Please read the [full text](https://code.fb.com/codeofconduct/)
+so that you can understand what actions will and will not be tolerated.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..5ef1546f8b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+# Contributing to OpenCellular
+We want to make contributing to this project as easy and transparent as
+possible.
+
+## Pull Requests
+We actively welcome your pull requests.
+
+1. Fork the repo and create your branch from `master`.
+2. Make sure all commits are signed and verified.
+3. If you've added code that should be tested, add tests.
+4. If you've changed APIs, update the documentation.
+5. Ensure the test suite passes.
+6. Make sure your code lints.
+
+## Issues
+We use GitHub issues to track public bugs. Please ensure your description is
+clear and has sufficient instructions to be able to reproduce the issue.
+
+## Coding Style
+See wiki
+
+## License
+By contributing to OpenCellular, you agree that your contributions will be licensed
+under the LICENSE file in the root directory of this source tree.
diff --git a/README.md b/README.md
index fc5387fd77..36b609ef8d 100644
--- a/README.md
+++ b/README.md
@@ -29,10 +29,10 @@ OpenCellular@fb.com
## Join the OpenCellular community
-* Website: oc.telecominfraproject.com
-* github: https://github.com/Telecominfraproject/OpenCellular
-* Project group: http://telecominfraproject.com/project/access-projects/opencellular-wireless-access-platform-design/
-* Mailing list:
+* Website: https://oc.telecominfraproject.com
+* GitHub: https://github.com/Telecominfraproject/OpenCellular
+* Project group: https://telecominfraproject.com/opencellular-wireless-access-platform-design/
+* Forum: https://ocforum.telecominfraproject.com/
## License
diff --git a/firmware/ec/.cproject b/firmware/ec/.cproject
index cfc755074a..752d48023d 100644
--- a/firmware/ec/.cproject
+++ b/firmware/ec/.cproject
@@ -5,11 +5,12 @@
+
-
+
@@ -76,7 +77,7 @@
-
+
@@ -122,11 +123,12 @@
+
-
+
@@ -187,7 +189,7 @@
-
+
diff --git a/firmware/ec/Makefile b/firmware/ec/Makefile
index a7d3273d21..5ea08c597a 100644
--- a/firmware/ec/Makefile
+++ b/firmware/ec/Makefile
@@ -53,6 +53,7 @@ CONFIGURO = $(XDCTOOLS_DIR)/xs --xdcpath="$(XDCPATH)" \
# Find all C source/object files.
SRC_FILE = $(shell find . -name '*.c' ! -path './test/*' ! -path './$(OUT)*')
MAIN_OBJS = $(SRC_FILE:.c=.o)
+COVERAGE_OBJS = $(SRC_FILE:.c=.gcno)
CC = $(TOOLCHAIN)/bin/arm-none-eabi-gcc
CFLAGS = -Wall -mcpu=cortex-m4 -mthumb -mabi=aapcs -mapcs-frame @$(OUT)/$(CONFIG)/compiler.opt -O3
@@ -103,10 +104,14 @@ lint:
$(LINT) $(LINT_FLAGS) $(ALL_FILE)
clean:
- -rm -rf *.o *.out *.d *.rov.xs $(OUT) $(MAIN_OBJS)
+ -rm -rf *.o *.out *.d *.rov.xs $(OUT) $(MAIN_OBJS) $(COVERAGE_OBJS)
test:
- cd test && $(MAKE)
+ cd test && $(MAKE) $(TESTFLAGS)
+
+ci: TESTFLAGS = ci
+ci: CFLAGS += -ftest-coverage
+ci: all test
.PHONY: all oc_connect1 clean test
diff --git a/firmware/ec/OpenCellular.cfg b/firmware/ec/OpenCellular.cfg
index d9a8849948..fdc9dca5e1 100644
--- a/firmware/ec/OpenCellular.cfg
+++ b/firmware/ec/OpenCellular.cfg
@@ -574,10 +574,11 @@ m3Hwi1Params.instance.name = "m3Hwi1";
Program.global.m3Hwi1 = m3Hwi.create(60, "&uDMAIntHandler", m3Hwi1Params);
*/
-var m3Hwi2Params = new m3Hwi.Params();
+/*Below configuration has some conflict with SPI DMA, doesn't work with it */
+/*var m3Hwi2Params = new m3Hwi.Params();
m3Hwi2Params.instance.name = "m3Hwi2";
m3Hwi2Params.enableInt = false;
-Program.global.m3Hwi2 = m3Hwi.create(61, "&uDMAErrorHandler", m3Hwi2Params);
+Program.global.m3Hwi2 = m3Hwi.create(61, "&uDMAErrorHandler", m3Hwi2Params);*/
/* ================ Application Specific Instances ================ */
/* ================ NDK configuration ================ */
diff --git a/firmware/ec/common/inc/global/OC_CONNECT1.h b/firmware/ec/common/inc/global/OC_CONNECT1.h
index 9a6a5e8590..ea649050a4 100644
--- a/firmware/ec/common/inc/global/OC_CONNECT1.h
+++ b/firmware/ec/common/inc/global/OC_CONNECT1.h
@@ -208,6 +208,15 @@ typedef enum OC_CONNECT1_I2CName {
OC_CONNECT1_I2CCOUNT
} OC_CONNECT1_I2CName;
+/*!
+ * @def DK_TM4C129X_SPIName
+ * @brief Enum of SPI names on the DK_TM4C129X dev board
+ */
+typedef enum DK_TM4C129X_SPIName {
+ OC_CONNECT1_SPI0 = 0,
+ OC_CONNECT1_SPICOUNT
+} OC_CONNECT1_SPIName;
+
/*!
* @def OC_CONNECT1_debugMdioName
* @brief Enum of debug MDIO names for Ethernet components
diff --git a/firmware/ec/common/inc/ocmp_wrappers/ocmp_at45db.h b/firmware/ec/common/inc/ocmp_wrappers/ocmp_at45db.h
new file mode 100644
index 0000000000..8de61aae10
--- /dev/null
+++ b/firmware/ec/common/inc/ocmp_wrappers/ocmp_at45db.h
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#ifndef COMMON_INC_OCMP_WRAPPERS_OCMP_FLASH_H_
+#define COMMON_INC_OCMP_WRAPPERS_OCMP_FLASH_H_
+
+#define FRAME_SIZE 64
+#define LAST_MSG_FLAG 0
+#define NEXT_MSG_FLAG_POS 17
+#define NEXT_MSG_FLAG 1
+#define PAYLOAD_SIZE 47
+
+#endif /* COMMON_INC_OCMP_WRAPPERS_OCMP_FLASH_H_ */
diff --git a/firmware/ec/common/inc/ocmp_wrappers/ocmp_eeprom_cat24c04.h b/firmware/ec/common/inc/ocmp_wrappers/ocmp_eeprom_cat24c04.h
index 9febbfd7ee..4e3640eb72 100644
--- a/firmware/ec/common/inc/ocmp_wrappers/ocmp_eeprom_cat24c04.h
+++ b/firmware/ec/common/inc/ocmp_wrappers/ocmp_eeprom_cat24c04.h
@@ -13,6 +13,7 @@
SCHEMA_IMPORT bool SYS_post_get_results(void **getpostResult);
SCHEMA_IMPORT bool SYS_post_enable(void **postActivate);
+SCHEMA_IMPORT const Driver_fxnTable AT45DB641E_fxnTable;
SCHEMA_IMPORT const Driver_fxnTable CAT24C04_gbc_sid_fxnTable;
SCHEMA_IMPORT const Driver_fxnTable CAT24C04_gbc_inv_fxnTable;
SCHEMA_IMPORT const Driver_fxnTable CAT24C04_sdr_inv_fxnTable;
@@ -61,4 +62,9 @@ static const Driver SYSTEMDRV = { .name = "SYSTEMDRV",
},
{} } };
+static const Driver FLASHDRV = {
+ .name = "FLASHDRV",
+ .fxnTable = &AT45DB641E_fxnTable,
+};
+
#endif /* INC_DEVICES_OCMP_EEPROM_H_ */
diff --git a/firmware/ec/inc/common/spibus.h b/firmware/ec/inc/common/spibus.h
new file mode 100644
index 0000000000..306ae58b73
--- /dev/null
+++ b/firmware/ec/inc/common/spibus.h
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+#ifndef INC_COMMON_SPIBUS_H_
+#define INC_COMMON_SPIBUS_H_
+
+/*****************************************************************************
+ * HEADER FILES
+ *****************************************************************************/
+#include "drivers/OcGpio.h"
+#include "inc/common/global_header.h"
+#include
+#include
+#include
+#include
+
+typedef struct SPI_Dev {
+ unsigned int bus;
+ OcGpio_Pin *chip_select;
+} SPI_Dev;
+
+/*****************************************************************************
+ * FUNCTION DECLARATIONS
+ *****************************************************************************/
+SPI_Handle spi_get_handle(unsigned int index);
+
+ReturnStatus spi_reg_read(SPI_Handle spiHandle, OcGpio_Pin *chip_select,
+ void *regAddress, uint8_t *data, uint32_t data_size,
+ uint32_t byte, uint8_t numofBytes);
+
+ReturnStatus spi_reg_write(SPI_Handle spiHandle, OcGpio_Pin *chip_select,
+ void *regAddress, uint8_t *data, uint32_t data_size,
+ uint32_t byte, uint8_t numofBytes);
+
+#endif /* INC_COMMON_SPIBUS_H_ */
diff --git a/firmware/ec/inc/devices/at45db.h b/firmware/ec/inc/devices/at45db.h
new file mode 100644
index 0000000000..e07f2350a6
--- /dev/null
+++ b/firmware/ec/inc/devices/at45db.h
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+#ifndef INC_DEVICES_AT45DB_H_
+#define INC_DEVICES_AT45DB_H_
+
+#include "common/inc/global/post_frame.h"
+#include "drivers/OcGpio.h"
+#include "inc/common/spibus.h"
+#include "inc/common/global_header.h"
+
+/*****************************************************************************
+ * STRUCT/ENUM DEFINITIONS
+ *****************************************************************************/
+typedef enum AT45DB_Event {
+ AT45DB_READ_EVENT = 0,
+} AT45DB_Event;
+
+typedef void (*AT45DB_CallbackFn)(AT45DB_Event evt, uint16_t value,
+ void *context);
+
+typedef struct AT45DB_Cfg {
+ SPI_Dev dev;
+ OcGpio_Pin *pin_alert;
+} AT45DB_Cfg;
+
+typedef struct AT45DB_Obj {
+ AT45DB_CallbackFn alert_cb;
+ void *cb_context;
+ AT45DB_Event evt_to_monitor;
+} AT45DB_Obj;
+
+typedef struct AT45DB_Dev {
+ const AT45DB_Cfg cfg;
+ AT45DB_Obj obj;
+} AT45DB_Dev;
+
+ePostCode at45db_probe(AT45DB_Dev *dev, POSTData *postData);
+ReturnStatus at45db_data_read(AT45DB_Dev *dev, uint8_t *data,
+ uint32_t data_size, uint32_t byte, uint32_t page);
+ReturnStatus at45db_data_write(AT45DB_Dev *dev, uint8_t *data,
+ uint32_t data_size, uint32_t byte,
+ uint32_t page);
+ReturnStatus at45db_erasePage(AT45DB_Dev *dev, uint32_t page);
+uint8_t at45db_readStatusRegister(AT45DB_Dev *dev);
+
+#endif /* INC_DEVICES_AT45DB_H_ */
diff --git a/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT1.c b/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT1.c
index a3968aa2b9..1bebc2b7f8 100644
--- a/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT1.c
+++ b/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT1.c
@@ -274,6 +274,8 @@ extern GPIO_PinConfig gpioPinConfigs[];
GPIO_PinConfig gpioPinConfigs[OC_EC_GPIOCOUNT] = {
[OC_EC_SOC_UART3_TX] =
GPIOTiva_PA_5 | GPIO_CFG_IN_NOPULL | GPIO_CFG_IN_INT_BOTH_EDGES,
+ [OC_EC_FLASH_nCS] = GPIOTiva_PB_4 | GPIO_CFG_OUT_STD |
+ GPIO_CFG_OUT_STR_HIGH | GPIO_CFG_OUT_HIGH,
[OC_EC_SDR_INA_ALERT] =
GPIOTiva_PD_2 | GPIO_CFG_IN_NOPULL | GPIO_CFG_IN_INT_FALLING,
[OC_EC_PWR_PSE_RESET] = GPIOTiva_PD_3 | GPIO_CFG_OUT_STD |
@@ -651,6 +653,66 @@ void OC_CONNECT1_initI2C(void)
I2C_init();
}
+/*
+ * =============================== SPI ===============================
+ */
+/* Place into subsections to allow the TI linker to remove items properly */
+#if defined(__TI_COMPILER_VERSION__)
+# pragma DATA_SECTION(SPI_config, ".const:SPI_config")
+# pragma DATA_SECTION(spiTivaDMAHWAttrs, ".const:spiTivaDMAHWAttrs")
+#endif
+
+#include
+#include
+
+SPITivaDMA_Object spiTivaDMAObjects[OC_CONNECT1_SPICOUNT];
+
+#if defined(__TI_COMPILER_VERSION__)
+# pragma DATA_ALIGN(spiTivaDMAscratchBuf, 32)
+#elif defined(__IAR_SYSTEMS_ICC__)
+# pragma data_alignment = 32
+#elif defined(__GNUC__)
+__attribute__((aligned(32)))
+#endif
+uint32_t spiTivaDMAscratchBuf[OC_CONNECT1_SPICOUNT];
+
+const SPITivaDMA_HWAttrs spiTivaDMAHWAttrs[OC_CONNECT1_SPICOUNT] = {
+ { .baseAddr = SSI1_BASE,
+ .intNum = INT_SSI1,
+ .intPriority = (~0),
+ .scratchBufPtr = &spiTivaDMAscratchBuf[0],
+ .defaultTxBufValue = 0,
+ .rxChannelIndex = UDMA_CHANNEL_SSI1RX,
+ .txChannelIndex = UDMA_CHANNEL_SSI1TX,
+ .channelMappingFxn = uDMAChannelAssign,
+ .rxChannelMappingFxnArg = UDMA_CH24_SSI1RX,
+ .txChannelMappingFxnArg = UDMA_CH25_SSI1TX },
+};
+
+const SPI_Config SPI_config[] = {
+ [OC_CONNECT1_SPI0] = { .fxnTablePtr = &SPITivaDMA_fxnTable,
+ .object = &spiTivaDMAObjects[OC_CONNECT1_SPI0],
+ .hwAttrs = &spiTivaDMAHWAttrs[OC_CONNECT1_SPI0] },
+ { NULL, NULL, NULL },
+};
+/*
+ * ======== OC_CONNECT1_initSPI ========
+ */
+void OC_CONNECT1_initSPI(void)
+{
+ SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI1);
+ GPIOPinConfigure(GPIO_PB5_SSI1CLK);
+ GPIOPinConfigure(GPIO_PB4_SSI1FSS);
+ GPIOPinConfigure(GPIO_PE4_SSI1XDAT0);
+ GPIOPinConfigure(GPIO_PE5_SSI1XDAT1);
+
+ GPIOPinTypeSSI(GPIO_PORTB_BASE, GPIO_PIN_4 | GPIO_PIN_5);
+ GPIOPinTypeSSI(GPIO_PORTE_BASE, GPIO_PIN_4 | GPIO_PIN_5);
+
+ OC_CONNECT1_initDMA();
+ SPI_init();
+}
+
/*
* =============================== UART ===============================
*/
@@ -914,4 +976,4 @@ void OC_CONNECT1_initWatchdog(void)
SysCtlPeripheralEnable(SYSCTL_PERIPH_WDOG0);
Watchdog_init();
-}
\ No newline at end of file
+}
diff --git a/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT_GBC.c b/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT_GBC.c
index 2f5528e1c7..12927c0e07 100644
--- a/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT_GBC.c
+++ b/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT_GBC.c
@@ -25,6 +25,8 @@
#include "inc/subsystem/power/power.h"
#include "inc/devices/eth_sw.h"
#include "inc/devices/eeprom.h"
+#include "inc/common/spibus.h"
+#include "inc/devices/at45db.h"
#include
#include
@@ -63,6 +65,19 @@ Eeprom_Cfg eeprom_gbc_inv = {
/*****************************************************************************
* SYSTEM CONFIG
*****************************************************************************/
+/* SPI AT45DB Flash Config */
+AT45DB_Dev gbc_spi_flash_memory = {
+ .cfg =
+ {
+ .dev =
+ {
+ .bus = OC_CONNECT1_SPI0,
+ .chip_select = &(OcGpio_Pin){ &ec_io, OC_EC_FLASH_nCS },
+ },
+ .pin_alert = NULL,
+ },
+ .obj = {},
+};
/* Power SubSystem Config */
// Lead Acid Temperature sensor.
SE98A_Dev gbc_pwr_lead_acid_ts = {
@@ -571,4 +586,4 @@ const INA226_Config fact_ap_3v_ps_cfg = {
const INA226_Config fact_msata_3v_ps_cfg = {
.current_lim = 1500,
-};
\ No newline at end of file
+};
diff --git a/firmware/ec/platform/oc-sdr/schema/schema.c b/firmware/ec/platform/oc-sdr/schema/schema.c
index 4b50ae7398..568e8eb6c6 100644
--- a/firmware/ec/platform/oc-sdr/schema/schema.c
+++ b/firmware/ec/platform/oc-sdr/schema/schema.c
@@ -38,6 +38,7 @@ SCHEMA_IMPORT DriverStruct eeprom_gbc_sid;
SCHEMA_IMPORT DriverStruct eeprom_gbc_inv;
SCHEMA_IMPORT DriverStruct eeprom_sdr_inv;
SCHEMA_IMPORT DriverStruct eeprom_fe_inv;
+SCHEMA_IMPORT DriverStruct gbc_spi_flash_memory;
/* Power SubSystem Configs */
SCHEMA_IMPORT DriverStruct gbc_pwr_lead_acid_ts;
SCHEMA_IMPORT DriverStruct gbc_pwr_ext_bat_charger;
@@ -214,6 +215,7 @@ SCHEMA_IMPORT bool SYNC_Init(void *driver, void *returnValue);
SCHEMA_IMPORT bool SYNC_reset(void *driver, void *params);
SCHEMA_IMPORT bool SYS_cmdReset(void *driver, void *params);
SCHEMA_IMPORT bool SYS_cmdEcho(void *driver, void *params);
+SCHEMA_IMPORT bool sys_post_init(void *driver, void *returnValue);
SCHEMA_IMPORT bool TestMod_cmdEnable(void *driver, void *params);
SCHEMA_IMPORT bool TestMod_cmdDisable(void *driver, void *params);
SCHEMA_IMPORT bool TestMod_cmdDisconnect(void *driver, void *params);
@@ -252,6 +254,11 @@ const Component sys_schema[] = {
.name = "eeprom_mac",
.driver = &Driver_MAC,
},
+ {
+ .name = "SPI_flash",
+ .driver = &FLASHDRV,
+ .driver_cfg = &gbc_spi_flash_memory,
+ },
{} },
.commands = (Command[]){ {
.name = "reset",
@@ -264,6 +271,11 @@ const Component sys_schema[] = {
{} },
},
{} },
+ .driver_cfg = &gbc_spi_flash_memory,
+ .ssHookSet =
+ &(SSHookSet){
+ .postInitFxn = (ssHook_Cb)sys_post_init,
+ },
},
{
.name = "power",
@@ -272,44 +284,42 @@ const Component sys_schema[] = {
{
.name = "comp_all",
.components =
- (Component[]){ {
- .name = "powerSource",
- .driver = &PWRSRC,
- .driver_cfg = &gbc_pwr_powerSource,
- .postDisabled = POST_DISABLED,
- },
- {} },
+ (Component[]){
+ {
+ .name = "powerSource",
+ .driver = &PWRSRC,
+ .driver_cfg = &gbc_pwr_powerSource,
+ .postDisabled = POST_DISABLED,
+ },
+ {} },
},
{ .name = "leadacid_sensor",
.components =
- (Component[]){
- {
- .name = "temp_sensor1",
- .driver = &SE98A,
- .driver_cfg = &gbc_pwr_lead_acid_ts,
- .factory_config = &fact_bc_se98a,
- },
- {} } },
+ (Component[]){ {
+ .name = "temp_sensor1",
+ .driver = &SE98A,
+ .driver_cfg = &gbc_pwr_lead_acid_ts,
+ .factory_config = &fact_bc_se98a,
+ },
+ {} } },
{ .name = "leadacid",
.components =
- (Component[]){
- {
- .name = "battery",
- .driver = <C4015,
- .driver_cfg = &gbc_pwr_ext_bat_charger,
- .factory_config = &fact_leadAcid_cfg,
- },
- {} } },
+ (Component[]){ {
+ .name = "battery",
+ .driver = <C4015,
+ .driver_cfg = &gbc_pwr_ext_bat_charger,
+ .factory_config = &fact_leadAcid_cfg,
+ },
+ {} } },
{ .name = "lion",
.components =
- (Component[]){
- {
- .name = "battery",
- .driver = <C4015,
- .driver_cfg = &gbc_pwr_int_bat_charger,
- .factory_config = &fact_lithiumIon_cfg,
- },
- {} } },
+ (Component[]){ {
+ .name = "battery",
+ .driver = <C4015,
+ .driver_cfg = &gbc_pwr_int_bat_charger,
+ .factory_config = &fact_lithiumIon_cfg,
+ },
+ {} } },
{
.name = "pse",
.driver = <C4274,
diff --git a/firmware/ec/src/Board.h b/firmware/ec/src/Board.h
index 3de01f14df..939d0743a9 100644
--- a/firmware/ec/src/Board.h
+++ b/firmware/ec/src/Board.h
@@ -48,6 +48,7 @@ extern "C" {
#define Board_initGeneral OC_CONNECT1_initGeneral
#define Board_initGPIO OC_CONNECT1_initGPIO
#define Board_initI2C OC_CONNECT1_initI2C
+#define Board_initSPI OC_CONNECT1_initSPI
#define Board_initUART OC_CONNECT1_initUART
#define Board_initUSB OC_CONNECT1_initUSB
#define Board_initWatchdog OC_CONNECT1_initWatchdog
diff --git a/firmware/ec/src/devices/at45db.c b/firmware/ec/src/devices/at45db.c
new file mode 100644
index 0000000000..401996cd4f
--- /dev/null
+++ b/firmware/ec/src/devices/at45db.c
@@ -0,0 +1,291 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * This file is used as Device layer for AT45DB641E. Mainly it contains Data
+ * read, Data write, Page erase, Status check functions, these functions are
+ * called by littlefs filesystyem in order to perform read/write operation for
+ * data using SPI interface. Also while post execution device and manufacturing
+ * id's of AT45DB641E will be verified by probe function.
+ */
+
+#include "inc/devices/at45db.h"
+#include "inc/common/spibus.h"
+#include "inc/common/global_header.h"
+#include "inc/global/OC_CONNECT1.h"
+
+#define AT45DB_DATA_WR_OPCODE_WR_COUNT 4
+#define AT45DB_DATA_RD_OPCODE_WR_COUNT 8
+#define AT45DB_DEVICE_ID 0x0028
+#define AT45DB_DEVID_RD_BYTES 2
+#define AT45DB_DEVID_RD_OPCODE 0x9F
+#define AT45DB_DEVID_OPCODE_WR_COUNT 1
+#define AT45DB_ERASE_OPCODE_WR_COUNT 4
+#define AT45DB_MANFACTURE_ID 0x1F
+#define AT45DB_PAGE_ERASE_OPCODE 0x81
+#define AT45DB_PAGE_RD_OPCODE 0xD2
+#define AT45DB_PAGE_WR_OPCODE 0x86
+#define AT45DB_READY 0x80 /* AT45DB Ready Value */
+#define AT45DB_SRAM_BUFF2_WR_OPCODE 0x87
+#define AT45DB_STATUS_OPCODE 0xD7
+#define AT45DB_STATUS_OPCODE_WR_COUNT 1
+#define AT45DB_STATUS_RD_BYTES 1
+
+#define waitForReady(dev) \
+ while (!(AT45DB_READY & at45db_readStatusRegister(dev))) \
+ ;
+
+/*****************************************************************************
+ ** FUNCTION NAME : AT45DB_read_reg
+ **
+ ** DESCRIPTION : Reads 8 bit values from at45db page or register.
+ **
+ ** ARGUMENTS : spi device configuration, cmd buffer, register value,
+ ** page offset, numOfBytes to be read, cmd write count.
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+static ReturnStatus AT45DB_read_reg(AT45DB_Dev *dev,
+ void *cmdbuffer, /* cmd or opcode buffer */
+ uint8_t *regValue, uint32_t pageOffset,
+ uint32_t NumOfbytes, uint8_t writeCount)
+{
+ ReturnStatus status = RETURN_NOTOK;
+
+ SPI_Handle at45dbHandle = spi_get_handle(dev->cfg.dev.bus);
+ if (!at45dbHandle) {
+ LOGGER_ERROR(
+ "AT45DBFLASHMEMORY:ERROR:: Failed to get SPI Bus for at45db flash memory "
+ "0x%x on bus 0x%x.\n",
+ dev->cfg.dev.chip_select, dev->cfg.dev.bus);
+ } else {
+ status = spi_reg_read(at45dbHandle, dev->cfg.dev.chip_select, cmdbuffer,
+ regValue, NumOfbytes, pageOffset, writeCount);
+ }
+ return status;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : AT45DB_write_reg
+ **
+ ** DESCRIPTION : Write 8 bit value to at45db page or register.
+ **
+ ** ARGUMENTS : spi device configuration, cmd buffer, register value,
+ ** page offset, numOfBytes to be written, cmd write count.
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+static ReturnStatus AT45DB_write_reg(AT45DB_Dev *dev,
+ void *cmdbuffer, /* cmd or opcode buffer */
+ uint8_t *regValue, uint32_t pageOffset,
+ uint32_t NumOfbytes, uint8_t writeCount)
+{
+ ReturnStatus status = RETURN_NOTOK;
+ SPI_Handle at45dbHandle = spi_get_handle(dev->cfg.dev.bus);
+ if (!at45dbHandle) {
+ LOGGER_ERROR(
+ "AT45DBFLASHMEMORY:ERROR:: Failed to get SPI Bus for at45db flash memory "
+ "0x%x on bus 0x%x.\n",
+ dev->cfg.dev.chip_select, dev->cfg.dev.bus);
+ } else {
+ status =
+ spi_reg_write(at45dbHandle, dev->cfg.dev.chip_select, cmdbuffer,
+ regValue, NumOfbytes, pageOffset, writeCount);
+ }
+ return status;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : at45db_readStatusRegister
+ **
+ ** DESCRIPTION : Reads status of at45db device whether it is ready for
+ **
+ ** r/w operation
+ **
+ ** ARGUMENTS : spi device configuration
+ **
+ ** RETURN TYPE : 8-bit status code
+ **
+ *****************************************************************************/
+uint8_t at45db_readStatusRegister(AT45DB_Dev *dev)
+{
+ uint8_t txBuffer =
+ AT45DB_STATUS_OPCODE; /* opcode for ready status of AT45DB */
+ ;
+ uint8_t status;
+
+ AT45DB_read_reg(dev, &txBuffer, &status, NULL, AT45DB_STATUS_RD_BYTES,
+ AT45DB_STATUS_OPCODE_WR_COUNT);
+
+ return (status);
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : at45db_erasePage
+ **
+ ** DESCRIPTION : Erases at45db memory page before writing data to it
+ **
+ ** ARGUMENTS : spi device configuration, page number to be erased
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+ReturnStatus at45db_erasePage(AT45DB_Dev *dev, uint32_t page)
+{
+ ReturnStatus status = RETURN_NOTOK;
+ uint8_t txBuffer[4];
+
+ waitForReady(dev);
+
+ txBuffer[0] =
+ AT45DB_PAGE_ERASE_OPCODE; /* opcode to erase main memory page */
+ txBuffer[1] =
+ (uint8_t)(page >> 7); /* Page size is 15 bits 8 in tx1 and 7 in tx2 */
+ txBuffer[2] = (uint8_t)(page << 1);
+ txBuffer[3] = 0x00;
+
+ status = AT45DB_write_reg(dev, txBuffer, NULL, NULL, NULL,
+ AT45DB_ERASE_OPCODE_WR_COUNT);
+
+ return status;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : at45db_data_read
+ **
+ ** DESCRIPTION : Reads data from at45db memory page
+ **
+ ** ARGUMENTS : spi device configuration, data pointer, data size,
+ **
+ ** page offset, page number
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+ReturnStatus at45db_data_read(AT45DB_Dev *dev, uint8_t *data,
+ uint32_t data_size, uint32_t byte, uint32_t page)
+{
+ ReturnStatus status = RETURN_NOTOK;
+ uint8_t
+ txBuffer[8]; /* last 4 bytes are needed, but have don't care values */
+
+ waitForReady(dev);
+
+ txBuffer[0] = AT45DB_PAGE_RD_OPCODE; /* opcode to read main memory page */
+ txBuffer[1] =
+ (uint8_t)(page >> 7); /* Page size is 15 bits 8 in tx1 and 7 in tx2 */
+ txBuffer[2] = (uint8_t)((page << 1));
+ txBuffer[3] = (uint8_t)(0xFF & byte);
+
+ status = AT45DB_read_reg(dev, &txBuffer, data, byte, data_size,
+ AT45DB_DATA_RD_OPCODE_WR_COUNT);
+
+ return status;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : at45db_data_write
+ **
+ ** DESCRIPTION : Writes data to at45db memory page
+ **
+ ** ARGUMENTS : spi device configuration, data pointer, data size,
+ **
+ ** page offset, page number
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+ReturnStatus at45db_data_write(AT45DB_Dev *dev, uint8_t *data,
+ uint32_t data_size, uint32_t byte, uint32_t page)
+{
+ ReturnStatus status = RETURN_NOTOK;
+ uint8_t txBuffer[4];
+
+ waitForReady(dev);
+
+ txBuffer[0] = AT45DB_SRAM_BUFF2_WR_OPCODE; /* opcode to write data to AT45DB
+ SRAM Buffer2 */
+ txBuffer[1] = 0x00;
+ txBuffer[2] = (uint8_t)(0x1 & (byte >> 8)); /* 9 bit buffer address */
+ txBuffer[3] = (uint8_t)(0xFF & byte);
+
+ status = AT45DB_write_reg(dev, &txBuffer, data, byte, data_size,
+ AT45DB_DATA_WR_OPCODE_WR_COUNT);
+
+ if (status == RETURN_OK) {
+ waitForReady(dev);
+
+ txBuffer[0] =
+ AT45DB_PAGE_WR_OPCODE; /* opcode to Push the data from AT45DB SRAM
+ Buffer2 to the page */
+ txBuffer[1] = (uint8_t)(
+ page >> 7); /* Page size is 15 bits 8 in tx1 and 7 in tx2 */
+ txBuffer[2] = (uint8_t)(page << 1);
+ txBuffer[3] = 0x00;
+
+ status = AT45DB_write_reg(dev, &txBuffer, data, byte, data_size,
+ AT45DB_DATA_WR_OPCODE_WR_COUNT);
+ }
+ return status;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : at45db_getDevID
+ **
+ ** DESCRIPTION : Reads Device id and manufacturing id of at45db device
+ **
+ ** ARGUMENTS : spi device configuration, data pointer
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+static ReturnStatus at45db_getDevID(AT45DB_Dev *dev, uint32_t *devID)
+{
+ uint8_t txBuffer = AT45DB_DEVID_RD_OPCODE; /* opcode to get device id */
+
+ return AT45DB_read_reg(dev, &txBuffer, devID, NULL, AT45DB_DEVID_RD_BYTES,
+ AT45DB_DEVID_OPCODE_WR_COUNT);
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : at45db_probe
+ **
+ ** DESCRIPTION : Compares device and manufacturing id's for post
+ **
+ ** ARGUMENTS : spi device configuration, post data pointer
+ **
+ ** RETURN TYPE : ePostCode type status, can be found in post_frame.h
+ **
+ *****************************************************************************/
+ePostCode at45db_probe(AT45DB_Dev *dev, POSTData *postData)
+{
+ uint32_t value = 0;
+ uint16_t devId = 0;
+ uint8_t manfId = 0;
+
+ if (at45db_getDevID(dev, &value) != RETURN_OK) {
+ return POST_DEV_MISSING;
+ }
+
+ devId = (value >> 8) & 0xFFFF;
+
+ if (devId != AT45DB_DEVICE_ID) {
+ return POST_DEV_ID_MISMATCH;
+ }
+
+ manfId = value & 0xFF;
+
+ if (manfId != AT45DB_MANFACTURE_ID) {
+ return POST_DEV_ID_MISMATCH;
+ }
+
+ post_update_POSTData(postData, dev->cfg.dev.bus, NULL, manfId, devId);
+
+ return POST_DEV_FOUND;
+}
diff --git a/firmware/ec/src/devices/ocmp_wrappers/ocmp_at45db.c b/firmware/ec/src/devices/ocmp_wrappers/ocmp_at45db.c
new file mode 100644
index 0000000000..53e9d62b76
--- /dev/null
+++ b/firmware/ec/src/devices/ocmp_wrappers/ocmp_at45db.c
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * This is wrapper file for at45db device contains wrapper functions like probe
+ * and function table of it. probe function calls device layer functions to
+ * complete post execution
+ */
+
+#include "common/inc/global/Framework.h"
+#include "common/inc/ocmp_wrappers/ocmp_at45db.h"
+#include "inc/devices/at45db.h"
+
+/*****************************************************************************
+ ** FUNCTION NAME : _probe
+ **
+ ** DESCRIPTION : Wrapper function for post execution
+ **
+ ** ARGUMENTS : spi device configuration, post data pointer
+ **
+ ** RETURN TYPE : ePostCode type status, can be found in post_frame.h
+ **
+ *****************************************************************************/
+static ePostCode _probe(void *driver, POSTData *postData)
+{
+ return at45db_probe(driver, postData);
+}
+
+const Driver_fxnTable AT45DB641E_fxnTable = {
+ /* Message handlers */
+ .cb_probe = _probe,
+};
diff --git a/firmware/ec/src/devices/spibus.c b/firmware/ec/src/devices/spibus.c
new file mode 100644
index 0000000000..c2adb31b0e
--- /dev/null
+++ b/firmware/ec/src/devices/spibus.c
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * This file contains SPI driver's API within spi_get_handle, spi_reg_read and
+ * spi_reg_write which ccan be called by device layer to communicate any SPI
+ * device.
+ */
+
+//*****************************************************************************
+// HANDLES DEFINITION
+//*****************************************************************************
+
+#include "Board.h"
+#include "drivers/OcGpio.h"
+#include "inc/common/spibus.h"
+#include "inc/global/OC_CONNECT1.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define PIN_LOW (0)
+#define PIN_HIGH ~(0)
+
+/*****************************************************************************
+ ** FUNCTION NAME : spi_get_handle
+ **
+ ** DESCRIPTION : Initialize SPI Bus
+ **
+ ** ARGUMENTS : SPI bus index
+ **
+ ** RETURN TYPE : SPI_Handle (NULL on failure)
+ **
+ *****************************************************************************/
+SPI_Handle spi_get_handle(uint32_t index)
+{
+ SPI_Params spiParams;
+ SPI_Handle spiHandle;
+
+ SPI_Params_init(&spiParams);
+ spiHandle = SPI_open(index, &spiParams);
+ if (spiHandle == NULL) {
+ LOGGER_ERROR("SPI_open failed\n");
+ return false;
+ }
+ return spiHandle;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : spi_reg_read
+ **
+ ** DESCRIPTION : Writing device register over SPI bus.
+ **
+ ** ARGUMENTS : SPI handle, chip select, register address, data, data
+ **
+ ** length, offset byte, numOfBytes for cmd write count
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+ReturnStatus spi_reg_read(SPI_Handle spiHandle, OcGpio_Pin *chip_select,
+ void *regAddress, uint8_t *data, uint32_t data_size,
+ uint32_t byte, uint8_t numofBytes)
+{
+ ReturnStatus status = RETURN_OK;
+ SPI_Transaction spiTransaction;
+
+ spiTransaction.count =
+ numofBytes; /* Initialize master SPI transaction structure */
+ spiTransaction.txBuf = regAddress;
+ spiTransaction.rxBuf = NULL;
+
+ OcGpio_write(chip_select, PIN_LOW); /* Initiate SPI transfer */
+
+ if (SPI_transfer(spiHandle, &spiTransaction)) {
+ status = RETURN_OK;
+ } else {
+ LOGGER_ERROR("SPIBUS:ERROR:: SPI write failed");
+ status = RETURN_NOTOK;
+ }
+
+ spiTransaction.count = data_size;
+ spiTransaction.txBuf = NULL;
+ spiTransaction.rxBuf = data;
+
+ if (SPI_transfer(spiHandle, &spiTransaction)) {
+ status = RETURN_OK;
+ } else {
+ LOGGER_ERROR("SPIBUS:ERROR:: SPI read failed");
+ status = RETURN_NOTOK;
+ }
+ OcGpio_write(chip_select, PIN_HIGH);
+
+ SPI_close(spiHandle);
+
+ return (status);
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : spi_reg_write
+ **
+ ** DESCRIPTION : Writing device register over SPI bus.
+ **
+ ** ARGUMENTS : SPI handle, chip select, register address, data, data
+ **
+ ** length, offset byte, numOfBytes for cmd write count
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+ReturnStatus spi_reg_write(SPI_Handle spiHandle, OcGpio_Pin *chip_select,
+ void *regAddress, uint8_t *data, uint32_t data_size,
+ uint32_t byte, uint8_t numofBytes)
+{
+ ReturnStatus status = RETURN_OK;
+ SPI_Transaction spiTransaction;
+
+ spiTransaction.count =
+ numofBytes; /* Initialize master SPI transaction structure */
+ spiTransaction.txBuf = regAddress;
+ spiTransaction.rxBuf = NULL;
+
+ OcGpio_write(chip_select, PIN_LOW); /* Initiate SPI transfer */
+
+ if (SPI_transfer(spiHandle, &spiTransaction)) {
+ status = RETURN_OK;
+ } else {
+ LOGGER_ERROR("SPIBUS:ERROR:: SPI write failed");
+ status = RETURN_NOTOK;
+ }
+
+ spiTransaction.count = data_size;
+ spiTransaction.txBuf = data;
+ spiTransaction.rxBuf = NULL;
+
+ if (data_size > 0) {
+ if (SPI_transfer(spiHandle, &spiTransaction)) {
+ status = RETURN_OK;
+ } else {
+ LOGGER_ERROR("SPIBUS:ERROR:: SPI write failed");
+ status = RETURN_NOTOK;
+ }
+ }
+
+ OcGpio_write(chip_select, PIN_HIGH);
+
+ SPI_close(spiHandle);
+
+ return (status);
+}
diff --git a/firmware/ec/src/filesystem/fs_wrapper.c b/firmware/ec/src/filesystem/fs_wrapper.c
new file mode 100644
index 0000000000..185c7a980d
--- /dev/null
+++ b/firmware/ec/src/filesystem/fs_wrapper.c
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * This file acts as wrapper for little filesystem, contains filesystem
+ * initialization, block read, block write, block erase as a main functions
+ * moreover provides API's like fileRead, fileWrite for external application to
+ * read and write data to at45db flash memory by using SPI interface.
+ */
+
+#include "Board.h"
+#include "common/inc/global/Framework.h"
+#include "common/inc/global/ocmp_frame.h"
+#include "inc/common/bigbrother.h"
+#include "inc/common/global_header.h"
+#include "inc/devices/at45db.h"
+#include "inc/global/OC_CONNECT1.h"
+#include "inc/utils/util.h"
+#include "src/filesystem/fs_wrapper.h"
+#include "src/filesystem/lfs.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define BLOCK_SIZE 256
+#define BLOCK_COUNT 32768
+#define FRAME_SIZE 64
+#define LOOK_AHEAD 256
+#define PAGE_SIZE 256
+#define READ_SIZE 256
+#define WRITE_SIZE 256
+
+static Queue_Struct fsRxMsg;
+static Queue_Struct fsTxMsg;
+
+lfs_t lfs;
+lfs_file_t file;
+
+/*****************************************************************************
+ ** FUNCTION NAME : block_device_read
+ **
+ ** DESCRIPTION : It is called by filesystem to read block device
+ **
+ ** ARGUMENTS : context for device configuration, block or page number,
+ **
+ ** block or page offset, data buffer, size of data to read
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+int block_device_read(const struct lfs_config *cfg, lfs_block_t block,
+ lfs_off_t off, void *buffer, lfs_size_t size)
+{
+ if (at45db_data_read(cfg->context, buffer, size, off, block) != RETURN_OK) {
+ return LFS_ERR_IO;
+ }
+
+ return LFS_ERR_OK;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : block_device_write
+ **
+ ** DESCRIPTION : it is called by filesystem to write block device
+ **
+ ** ARGUMENTS : context for device configuration, block or page number,
+ **
+ ** block or page offset, data buffer, size of data to
+ *write
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+int block_device_write(const struct lfs_config *cfg, lfs_block_t block,
+ lfs_off_t off, void *buffer, lfs_size_t size)
+{
+ if (at45db_data_write(cfg->context, buffer, size, off, block) !=
+ RETURN_OK) {
+ return LFS_ERR_IO;
+ }
+
+ return LFS_ERR_OK;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : block_device_erase
+ **
+ ** DESCRIPTION : It is called by filesystem to erase block device
+ **
+ ** ARGUMENTS : context for device configuration, block or page number,
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+int block_device_erase(const struct lfs_config *cfg, lfs_block_t block)
+{
+ if (at45db_erasePage(cfg->context, block) != RETURN_OK) {
+ return LFS_ERR_IO;
+ }
+
+ return LFS_ERR_OK;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : block_device_sync
+ **
+ ** DESCRIPTION : It is called by filesystem to sync with block device
+ **
+ ** ARGUMENTS : context for device configuration
+ **
+ ** RETURN TYPE : Success or failure
+ **
+ *****************************************************************************/
+int block_device_sync(const struct lfs_config *cfg)
+{
+ if (at45db_readStatusRegister(cfg->context) != RETURN_OK) {
+ return LFS_ERR_IO;
+ }
+
+ return LFS_ERR_OK;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : fileSize
+ **
+ ** DESCRIPTION : Returns size of saved file
+ **
+ ** ARGUMENTS : Path or file name
+ **
+ ** RETURN TYPE : file size
+ **
+ *****************************************************************************/
+int fileSize(const char *path)
+{
+ uint32_t fileSize = 0;
+
+ if (lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) == LFS_ERR_OK) {
+ LOGGER_DEBUG("FS:: File open successfully \n");
+ }
+ fileSize = lfs_file_size(&lfs, &file);
+ lfs_file_close(&lfs, &file);
+
+ return fileSize;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : fileWrite
+ **
+ ** DESCRIPTION : It write data to specified file
+ **
+ ** ARGUMENTS : Path or file name, pointer to data, data length or size
+ **
+ ** RETURN TYPE : true or flase
+ **
+ *****************************************************************************/
+bool fileWrite(const char *path, uint8_t *pMsg, uint32_t size)
+{
+ if (lfs_file_open(&lfs, &file, path,
+ LFS_O_RDWR | LFS_O_CREAT | LFS_O_APPEND) == LFS_ERR_OK) {
+ LOGGER_DEBUG("FS:: File open successfully \n");
+ }
+ if (lfs_file_write(&lfs, &file, pMsg, size) == size) {
+ LOGGER_DEBUG("FS:: File written successfully \n");
+ }
+ if (lfs_file_close(&lfs, &file) == LFS_ERR_OK) {
+ LOGGER_DEBUG("FS:: File closed successfully \n");
+ }
+
+ return true;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : fileRead
+ **
+ ** DESCRIPTION : It reads data from specified file
+ **
+ ** ARGUMENTS : Path or file name, pointer to data, data length or size
+ **
+ ** RETURN TYPE : true or flase
+ **
+ *****************************************************************************/
+bool fileRead(const char *path, UChar *buf, uint32_t size)
+{
+ if (lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) == LFS_ERR_OK) {
+ LOGGER_DEBUG("FS:: File open successfully \n");
+ }
+ if (lfs_file_read(&lfs, &file, buf, size) == size) {
+ LOGGER_DEBUG("FS:: File read successfully \n");
+ }
+ if (lfs_file_close(&lfs, &file) == LFS_ERR_OK) {
+ LOGGER_DEBUG("FS:: File closed successfully \n");
+ }
+
+ return true;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : fsMsgHandler
+ **
+ ** DESCRIPTION : It is called when data to be written
+ **
+ ** ARGUMENTS : data pointer
+ **
+ ** RETURN TYPE : true or flase
+ **
+ *****************************************************************************/
+static bool fsMsgHandler(OCMPMessageFrame *pMsg)
+{
+ char fileName[] = "logs";
+
+ fileWrite(fileName, pMsg, FRAME_SIZE);
+
+ return true;
+}
+
+/*****************************************************************************
+ ** FUNCTION NAME : fs_init
+ **
+ ** DESCRIPTION : It initializes filesystem by mounting device
+ **
+ ** ARGUMENTS : arg0 for SPI device configuration, arg1 for return
+ **
+ ** RETURN TYPE : true or flase
+ **
+ *****************************************************************************/
+void fs_init(UArg arg0, UArg arg1)
+{
+ /*configuration of the filesystem is provided by this struct */
+ const struct lfs_config cfg = {
+ .context = (void *)arg0,
+ .read = block_device_read,
+ .prog = block_device_write,
+ .erase = block_device_erase,
+ .sync = block_device_sync,
+ .read_size = READ_SIZE,
+ .prog_size = WRITE_SIZE,
+ .block_size = BLOCK_SIZE,
+ .block_count = BLOCK_COUNT,
+ .lookahead = LOOK_AHEAD,
+ };
+ int err = lfs_mount(&lfs, &cfg);
+
+ if (err) {
+ lfs_format(&lfs, &cfg);
+ lfs_mount(&lfs, &cfg);
+ }
+
+ if (!err) {
+ LOGGER_DEBUG("FS:: Filesystem mounted successfully \n");
+ }
+
+ while (true) {
+ if (Semaphore_pend(semFilesysMsg, BIOS_WAIT_FOREVER)) {
+ while (!Queue_empty(fsTxMsgQueue)) {
+ OCMPMessageFrame *pMsg =
+ (OCMPMessageFrame *)Util_dequeueMsg(fsTxMsgQueue);
+ if (pMsg != NULL) {
+ if (!fsMsgHandler(pMsg)) {
+ LOGGER_ERROR("ERROR:: Unable to route message \n");
+ free(pMsg);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/firmware/ec/src/filesystem/fs_wrapper.h b/firmware/ec/src/filesystem/fs_wrapper.h
new file mode 100644
index 0000000000..e8c342569b
--- /dev/null
+++ b/firmware/ec/src/filesystem/fs_wrapper.h
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ */
+
+#ifndef SRC_FILESYSTEM_FS_H_
+#define SRC_FILESYSTEM_FS_H_
+
+#include "common/inc/global/post_frame.h"
+
+extern Queue_Handle fsRxMsgQueue;
+extern Queue_Handle fsTxMsgQueue;
+extern Semaphore_Handle semFilesysMsg;
+
+int fileSize(const char *path);
+void fs_init(UArg arg0, UArg arg1);
+bool fileRead(const char *path, UChar *buf, uint32_t size);
+bool fileWrite(const char *path, uint8_t *pMsg, uint32_t size);
+
+#endif /* SRC_FILESYSTEM_FS_H_ */
diff --git a/firmware/ec/src/filesystem/lfs.c b/firmware/ec/src/filesystem/lfs.c
new file mode 100644
index 0000000000..0b4bf95854
--- /dev/null
+++ b/firmware/ec/src/filesystem/lfs.c
@@ -0,0 +1,2426 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "lfs.h"
+#include "lfs_util.h"
+
+#include
+#include
+#include
+
+/* Caching block device operations */
+static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t block,
+ lfs_off_t off, void *buffer, lfs_size_t size)
+{
+ uint8_t *data = buffer;
+ assert(block < lfs->cfg->block_count);
+
+ while (size > 0) {
+ if (pcache && block == pcache->block && off >= pcache->off &&
+ off < pcache->off + lfs->cfg->prog_size) {
+ /* is already in pcache? */
+ lfs_size_t diff =
+ lfs_min(size, lfs->cfg->prog_size - (off - pcache->off));
+ memcpy(data, &pcache->buffer[off - pcache->off], diff);
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ if (block == rcache->block && off >= rcache->off &&
+ off < rcache->off + lfs->cfg->read_size) {
+ /* is already in rcache? */
+ lfs_size_t diff =
+ lfs_min(size, lfs->cfg->read_size - (off - rcache->off));
+ memcpy(data, &rcache->buffer[off - rcache->off], diff);
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) {
+ /* bypass cache? */
+ lfs_size_t diff = size - (size % lfs->cfg->read_size);
+ int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
+ if (err) {
+ return err;
+ }
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ /* load to cache, first condition can no longer fail */
+ rcache->block = block;
+ rcache->off = off - (off % lfs->cfg->read_size);
+ int err = lfs->cfg->read(lfs->cfg, rcache->block, rcache->off,
+ rcache->buffer, lfs->cfg->read_size);
+ if (err) {
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t block,
+ lfs_off_t off, const void *buffer, lfs_size_t size)
+{
+ const uint8_t *data = buffer;
+
+ for (lfs_off_t i = 0; i < size; i++) {
+ uint8_t c;
+ int err = lfs_cache_read(lfs, rcache, pcache, block, off + i, &c, 1);
+ if (err) {
+ return err;
+ }
+
+ if (c != data[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t block,
+ lfs_off_t off, lfs_size_t size, uint32_t *crc)
+{
+ for (lfs_off_t i = 0; i < size; i++) {
+ uint8_t c;
+ int err = lfs_cache_read(lfs, rcache, pcache, block, off + i, &c, 1);
+ if (err) {
+ return err;
+ }
+
+ lfs_crc(crc, &c, 1);
+ }
+ return 0;
+}
+
+static int lfs_cache_flush(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache)
+{
+ if (pcache->block != 0xffffffff) {
+ int err = lfs->cfg->prog(lfs->cfg, pcache->block, pcache->off,
+ pcache->buffer, lfs->cfg->prog_size);
+ if (err) {
+ return err;
+ }
+
+ if (rcache) {
+ int res =
+ lfs_cache_cmp(lfs, rcache, NULL, pcache->block, pcache->off,
+ pcache->buffer, lfs->cfg->prog_size);
+ if (res < 0) {
+ return res;
+ }
+
+ if (!res) {
+ return LFS_ERR_CORRUPT;
+ }
+ }
+
+ pcache->block = 0xffffffff;
+ }
+ return 0;
+}
+
+static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, lfs_cache_t *rcache,
+ lfs_block_t block, lfs_off_t off, const void *buffer,
+ lfs_size_t size)
+{
+ const uint8_t *data = buffer;
+ assert(block < lfs->cfg->block_count);
+
+ while (size > 0) {
+ if (block == pcache->block && off >= pcache->off &&
+ off < pcache->off + lfs->cfg->prog_size) {
+ /* is already in pcache? */
+ lfs_size_t diff =
+ lfs_min(size, lfs->cfg->prog_size - (off - pcache->off));
+ memcpy(&pcache->buffer[off - pcache->off], data, diff);
+
+ data += diff;
+ off += diff;
+ size -= diff;
+
+ if (off % lfs->cfg->prog_size == 0) {
+ /* eagerly flush out pcache if we fill up */
+ int err = lfs_cache_flush(lfs, pcache, rcache);
+ if (err) {
+ return err;
+ }
+ }
+
+ continue;
+ }
+
+ /* pcache must have been flushed, either by programming and
+ * entire block or manually flushing the pcache
+ */
+ assert(pcache->block == 0xffffffff);
+
+ if (off % lfs->cfg->prog_size == 0 && size >= lfs->cfg->prog_size) {
+ // bypass pcache?
+ lfs_size_t diff = size - (size % lfs->cfg->prog_size);
+ int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff);
+ if (err) {
+ return err;
+ }
+
+ if (rcache) {
+ int res =
+ lfs_cache_cmp(lfs, rcache, NULL, block, off, data, diff);
+ if (res < 0) {
+ return res;
+ }
+
+ if (!res) {
+ return LFS_ERR_CORRUPT;
+ }
+ }
+
+ data += diff;
+ off += diff;
+ size -= diff;
+ continue;
+ }
+
+ /* prepare pcache, first condition can no longer fail */
+ pcache->block = block;
+ pcache->off = off - (off % lfs->cfg->prog_size);
+ }
+ return 0;
+}
+
+/* General lfs block device operations */
+static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
+ void *buffer, lfs_size_t size)
+{
+ /* if we ever do more than writes to alternating pairs,
+ * this may need to consider pcache
+ */
+ return lfs_cache_read(lfs, &lfs->rcache, NULL, block, off, buffer, size);
+}
+
+static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
+ const void *buffer, lfs_size_t size)
+{
+ return lfs_cache_prog(lfs, &lfs->pcache, NULL, block, off, buffer, size);
+}
+
+static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
+ const void *buffer, lfs_size_t size)
+{
+ return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size);
+}
+
+static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
+ lfs_size_t size, uint32_t *crc)
+{
+ return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc);
+}
+
+static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block)
+{
+ return lfs->cfg->erase(lfs->cfg, block);
+}
+
+static int lfs_bd_sync(lfs_t *lfs)
+{
+ lfs->rcache.block = 0xffffffff;
+
+ int err = lfs_cache_flush(lfs, &lfs->pcache, NULL);
+ if (err) {
+ return err;
+ }
+ return lfs->cfg->sync(lfs->cfg);
+}
+
+/* Internal operations predeclared here */
+int lfs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data);
+static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir);
+static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *parent,
+ lfs_entry_t *entry);
+static int lfs_moved(lfs_t *lfs, const void *e);
+static int lfs_relocate(lfs_t *lfs, const lfs_block_t oldpair[2],
+ const lfs_block_t newpair[2]);
+int lfs_deorphan(lfs_t *lfs);
+
+/* Block allocator */
+static int lfs_alloc_lookahead(void *p, lfs_block_t block)
+{
+ lfs_t *lfs = p;
+
+ lfs_block_t off = (((lfs_soff_t)(block - lfs->free.begin) %
+ (lfs_soff_t)(lfs->cfg->block_count)) +
+ lfs->cfg->block_count) %
+ lfs->cfg->block_count;
+
+ if (off < lfs->cfg->lookahead) {
+ lfs->free.buffer[off / 32] |= 1U << (off % 32);
+ }
+
+ return 0;
+}
+
+static int lfs_alloc(lfs_t *lfs, lfs_block_t *block)
+{
+ while (true) {
+ while (true) {
+ /* check if we have looked at all blocks since last ack */
+ if (lfs->free.begin + lfs->free.off == lfs->free.end) {
+ LFS_WARN("No more free space %ld", lfs->free.end);
+ return LFS_ERR_NOSPC;
+ }
+
+ if (lfs->free.off >=
+ lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count)) {
+ break;
+ }
+
+ lfs_block_t off = lfs->free.off;
+ lfs->free.off += 1;
+
+ if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
+ /* found a free block */
+ *block = (lfs->free.begin + off) % lfs->cfg->block_count;
+ return 0;
+ }
+ }
+
+ lfs->free.begin += lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
+ lfs->free.off = 0;
+
+ /* find mask of free blocks from tree */
+ memset(lfs->free.buffer, 0, lfs->cfg->lookahead / 8);
+ int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs);
+ if (err) {
+ return err;
+ }
+ }
+}
+
+static void lfs_alloc_ack(lfs_t *lfs)
+{
+ lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
+}
+
+/* Metadata pair and directory operations */
+static inline void lfs_pairswap(lfs_block_t pair[2])
+{
+ lfs_block_t t = pair[0];
+ pair[0] = pair[1];
+ pair[1] = t;
+}
+
+static inline bool lfs_pairisnull(const lfs_block_t pair[2])
+{
+ return pair[0] == 0xffffffff || pair[1] == 0xffffffff;
+}
+
+static inline int lfs_paircmp(const lfs_block_t paira[2],
+ const lfs_block_t pairb[2])
+{
+ return !(paira[0] == pairb[0] || paira[1] == pairb[1] ||
+ paira[0] == pairb[1] || paira[1] == pairb[0]);
+}
+
+static inline bool lfs_pairsync(const lfs_block_t paira[2],
+ const lfs_block_t pairb[2])
+{
+ return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
+ (paira[0] == pairb[1] && paira[1] == pairb[0]);
+}
+
+static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry)
+{
+ return 4 + entry->d.elen + entry->d.alen + entry->d.nlen;
+}
+
+static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir)
+{
+ /* allocate pair of dir blocks */
+ for (int i = 0; i < 2; i++) {
+ int err = lfs_alloc(lfs, &dir->pair[i]);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* rather than clobbering one of the blocks we just pretend
+ * the revision may be valid
+ */
+ int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4);
+ if (err) {
+ return err;
+ }
+
+ /* set defaults */
+ dir->d.rev += 1;
+ dir->d.size = sizeof(dir->d) + 4;
+ dir->d.tail[0] = 0xffffffff;
+ dir->d.tail[1] = 0xffffffff;
+ dir->off = sizeof(dir->d);
+
+ /* don't write out yet, let caller take care of that */
+ return 0;
+}
+
+static int lfs_dir_fetch(lfs_t *lfs, lfs_dir_t *dir, const lfs_block_t pair[2])
+{
+ /* copy out pair, otherwise may be aliasing dir */
+ const lfs_block_t tpair[2] = { pair[0], pair[1] };
+ bool valid = false;
+
+ /* check both blocks for the most recent revision */
+ for (int i = 0; i < 2; i++) {
+ struct lfs_disk_dir test;
+ int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
+ if (err) {
+ return err;
+ }
+
+ if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) {
+ continue;
+ }
+
+ if ((0x7fffffff & test.size) < sizeof(test) + 4 ||
+ (0x7fffffff & test.size) > lfs->cfg->block_size) {
+ continue;
+ }
+
+ uint32_t crc = 0xffffffff;
+ lfs_crc(&crc, &test, sizeof(test));
+ err = lfs_bd_crc(lfs, tpair[i], sizeof(test),
+ (0x7fffffff & test.size) - sizeof(test), &crc);
+ if (err) {
+ return err;
+ }
+
+ if (crc != 0) {
+ continue;
+ }
+
+ valid = true;
+
+ /* setup dir in case it's valid */
+ dir->pair[0] = tpair[(i + 0) % 2];
+ dir->pair[1] = tpair[(i + 1) % 2];
+ dir->off = sizeof(dir->d);
+ dir->d = test;
+ }
+
+ if (!valid) {
+ LFS_ERROR("Corrupted dir pair at %ld %ld", tpair[0], tpair[1]);
+ return LFS_ERR_CORRUPT;
+ }
+ return 0;
+}
+
+struct lfs_region {
+ lfs_off_t oldoff;
+ lfs_size_t oldlen;
+ const void *newdata;
+ lfs_size_t newlen;
+};
+
+static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
+ const struct lfs_region *regions, int count)
+{
+ /* increment revision count */
+ dir->d.rev += 1;
+
+ /* keep pairs in order such that pair[0] is most recent */
+ lfs_pairswap(dir->pair);
+ for (int i = 0; i < count; i++) {
+ dir->d.size += regions[i].newlen - regions[i].oldlen;
+ }
+
+ const lfs_block_t oldpair[2] = { dir->pair[0], dir->pair[1] };
+ bool relocated = false;
+
+ while (true) {
+ if (true) {
+ int err = lfs_bd_erase(lfs, dir->pair[0]);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ uint32_t crc = 0xffffffff;
+ lfs_crc(&crc, &dir->d, sizeof(dir->d));
+ err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d));
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ int i = 0;
+ lfs_off_t oldoff = sizeof(dir->d);
+ lfs_off_t newoff = sizeof(dir->d);
+ while (newoff < (0x7fffffff & dir->d.size) - 4) {
+ if (i < count && regions[i].oldoff == oldoff) {
+ lfs_crc(&crc, regions[i].newdata, regions[i].newlen);
+ int err =
+ lfs_bd_prog(lfs, dir->pair[0], newoff,
+ regions[i].newdata, regions[i].newlen);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ oldoff += regions[i].oldlen;
+ newoff += regions[i].newlen;
+ i += 1;
+ } else {
+ uint8_t data;
+ int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1);
+ if (err) {
+ return err;
+ }
+
+ lfs_crc(&crc, &data, 1);
+ err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ oldoff += 1;
+ newoff += 1;
+ }
+ }
+
+ err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ err = lfs_bd_sync(lfs);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ /* successful commit, check checksum to make sure */
+ uint32_t ncrc = 0xffffffff;
+ err = lfs_bd_crc(lfs, dir->pair[0], 0,
+ (0x7fffffff & dir->d.size) - 4, &ncrc);
+ if (err) {
+ return err;
+ }
+
+ if (ncrc != crc) {
+ goto relocate;
+ }
+ }
+
+ break;
+ relocate:
+ /* commit was corrupted */
+ LFS_DEBUG("Bad block at %ld", dir->pair[0]);
+
+ /* drop caches and prepare to relocate block */
+ relocated = true;
+ lfs->pcache.block = 0xffffffff;
+
+ /* can't relocate superblock, filesystem is now frozen */
+ if (lfs_paircmp(oldpair, (const lfs_block_t[2]){ 0, 1 }) == 0) {
+ LFS_WARN("Superblock %ld has become unwritable", oldpair[0]);
+ return LFS_ERR_CORRUPT;
+ }
+
+ /* relocate half of pair */
+ int err = lfs_alloc(lfs, &dir->pair[0]);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (relocated) {
+ /* update references if we relocated */
+ LFS_DEBUG("Relocating %ld %ld to %ld %ld", oldpair[0], oldpair[1],
+ dir->pair[0], dir->pair[1]);
+ int err = lfs_relocate(lfs, oldpair, dir->pair);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* shift over any directories that are affected */
+ for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
+ if (lfs_paircmp(d->pair, dir->pair) == 0) {
+ d->pair[0] = dir->pair[0];
+ d->pair[1] = dir->pair[1];
+ }
+ }
+ return 0;
+}
+
+static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, const lfs_entry_t *entry,
+ const void *data)
+{
+ return lfs_dir_commit(
+ lfs, dir,
+ (struct lfs_region[]){
+ { entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d) },
+ { entry->off + sizeof(entry->d), entry->d.nlen, data,
+ entry->d.nlen } },
+ data ? 2 : 1);
+}
+
+static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
+ const void *data)
+{
+ /* check if we fit, if top bit is set we do not and move on */
+ while (true) {
+ if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) {
+ entry->off = dir->d.size - 4;
+ return lfs_dir_commit(
+ lfs, dir,
+ (struct lfs_region[]){
+ { entry->off, 0, &entry->d, sizeof(entry->d) },
+ { entry->off, 0, data, entry->d.nlen } },
+ 2);
+ }
+
+ /* we need to allocate a new dir block */
+ if (!(0x80000000 & dir->d.size)) {
+ lfs_dir_t newdir;
+ int err = lfs_dir_alloc(lfs, &newdir);
+ if (err) {
+ return err;
+ }
+
+ newdir.d.tail[0] = dir->d.tail[0];
+ newdir.d.tail[1] = dir->d.tail[1];
+ entry->off = newdir.d.size - 4;
+ err = lfs_dir_commit(
+ lfs, &newdir,
+ (struct lfs_region[]){
+ { entry->off, 0, &entry->d, sizeof(entry->d) },
+ { entry->off, 0, data, entry->d.nlen } },
+ 2);
+ if (err) {
+ return err;
+ }
+
+ dir->d.size |= 0x80000000;
+ dir->d.tail[0] = newdir.pair[0];
+ dir->d.tail[1] = newdir.pair[1];
+ return lfs_dir_commit(lfs, dir, NULL, 0);
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ if (err) {
+ return err;
+ }
+ }
+}
+
+static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry)
+{
+ /* check if we should just drop the directory block */
+ if ((dir->d.size & 0x7fffffff) ==
+ sizeof(dir->d) + 4 + lfs_entry_size(entry)) {
+ lfs_dir_t pdir;
+ int res = lfs_pred(lfs, dir->pair, &pdir);
+ if (res < 0) {
+ return res;
+ }
+
+ if (pdir.d.size & 0x80000000) {
+ pdir.d.size &= dir->d.size | 0x7fffffff;
+ pdir.d.tail[0] = dir->d.tail[0];
+ pdir.d.tail[1] = dir->d.tail[1];
+ return lfs_dir_commit(lfs, &pdir, NULL, 0);
+ }
+ }
+
+ /* shift out the entry */
+ int err = lfs_dir_commit(lfs, dir,
+ (struct lfs_region[]){
+ { entry->off, lfs_entry_size(entry), NULL, 0 },
+ },
+ 1);
+ if (err) {
+ return err;
+ }
+
+ /* shift over any files/directories that are affected */
+ for (lfs_file_t *f = lfs->files; f; f = f->next) {
+ if (lfs_paircmp(f->pair, dir->pair) == 0) {
+ if (f->poff == entry->off) {
+ f->pair[0] = 0xffffffff;
+ f->pair[1] = 0xffffffff;
+ } else if (f->poff > entry->off) {
+ f->poff -= lfs_entry_size(entry);
+ }
+ }
+ }
+
+ for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
+ if (lfs_paircmp(d->pair, dir->pair) == 0) {
+ if (d->off > entry->off) {
+ d->off -= lfs_entry_size(entry);
+ d->pos -= lfs_entry_size(entry);
+ }
+ }
+ }
+ return 0;
+}
+
+static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry)
+{
+ while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size) - 4) {
+ if (!(0x80000000 & dir->d.size)) {
+ entry->off = dir->off;
+ return LFS_ERR_NOENT;
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ if (err) {
+ return err;
+ }
+
+ dir->off = sizeof(dir->d);
+ dir->pos += sizeof(dir->d) + 4;
+ }
+
+ int err =
+ lfs_bd_read(lfs, dir->pair[0], dir->off, &entry->d, sizeof(entry->d));
+ if (err) {
+ return err;
+ }
+
+ entry->off = dir->off;
+ dir->off += lfs_entry_size(entry);
+ dir->pos += lfs_entry_size(entry);
+ return 0;
+}
+
+static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
+ const char **path)
+{
+ const char *pathname = *path;
+ size_t pathlen;
+
+ while (true) {
+ nextname:
+ /* skip slashes */
+ pathname += strspn(pathname, "/");
+ pathlen = strcspn(pathname, "/");
+
+ /* skip '.' and root '..' */
+ if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) ||
+ (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) {
+ pathname += pathlen;
+ goto nextname;
+ }
+
+ /* skip if matched by '..' in name */
+ const char *suffix = pathname + pathlen;
+ size_t sufflen;
+ int depth = 1;
+ while (true) {
+ suffix += strspn(suffix, "/");
+ sufflen = strcspn(suffix, "/");
+ if (sufflen == 0) {
+ break;
+ }
+
+ if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
+ depth -= 1;
+ if (depth == 0) {
+ pathname = suffix + sufflen;
+ goto nextname;
+ }
+ } else {
+ depth += 1;
+ }
+
+ suffix += sufflen;
+ }
+
+ /* update what we've found */
+ *path = pathname;
+
+ /* find path */
+ while (true) {
+ int err = lfs_dir_next(lfs, dir, entry);
+ if (err) {
+ return err;
+ }
+
+ if (((0x7f & entry->d.type) != LFS_TYPE_REG &&
+ (0x7f & entry->d.type) != LFS_TYPE_DIR) ||
+ entry->d.nlen != pathlen) {
+ continue;
+ }
+
+ int res = lfs_bd_cmp(lfs, dir->pair[0],
+ entry->off + 4 + entry->d.elen + entry->d.alen,
+ pathname, pathlen);
+ if (res < 0) {
+ return res;
+ }
+
+ /* found match */
+ if (res) {
+ break;
+ }
+ }
+
+ /* check that entry has not been moved */
+ if (entry->d.type & 0x80) {
+ int moved = lfs_moved(lfs, &entry->d.u);
+ if (moved < 0 || moved) {
+ return (moved < 0) ? moved : LFS_ERR_NOENT;
+ }
+
+ entry->d.type &= ~0x80;
+ }
+
+ pathname += pathlen;
+ pathname += strspn(pathname, "/");
+ if (pathname[0] == '\0') {
+ return 0;
+ }
+
+ /* continue on if we hit a directory */
+ if (entry->d.type != LFS_TYPE_DIR) {
+ return LFS_ERR_NOTDIR;
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir);
+ if (err) {
+ return err;
+ }
+ }
+}
+
+/* Top level directory operations */
+int lfs_mkdir(lfs_t *lfs, const char *path)
+{
+ /* deorphan if we haven't yet, needed at most once after poweron */
+ if (!lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* fetch parent directory */
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t entry;
+ err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) {
+ return err ? err : LFS_ERR_EXIST;
+ }
+
+ /* build up new directory */
+ lfs_alloc_ack(lfs);
+
+ lfs_dir_t dir;
+ err = lfs_dir_alloc(lfs, &dir);
+ if (err) {
+ return err;
+ }
+ dir.d.tail[0] = cwd.d.tail[0];
+ dir.d.tail[1] = cwd.d.tail[1];
+
+ err = lfs_dir_commit(lfs, &dir, NULL, 0);
+ if (err) {
+ return err;
+ }
+
+ entry.d.type = LFS_TYPE_DIR;
+ entry.d.elen = sizeof(entry.d) - 4;
+ entry.d.alen = 0;
+ entry.d.nlen = strlen(path);
+ entry.d.u.dir[0] = dir.pair[0];
+ entry.d.u.dir[1] = dir.pair[1];
+
+ cwd.d.tail[0] = dir.pair[0];
+ cwd.d.tail[1] = dir.pair[1];
+
+ err = lfs_dir_append(lfs, &cwd, &entry, path);
+ if (err) {
+ return err;
+ }
+
+ lfs_alloc_ack(lfs);
+ return 0;
+}
+
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path)
+{
+ dir->pair[0] = lfs->root[0];
+ dir->pair[1] = lfs->root[1];
+
+ int err = lfs_dir_fetch(lfs, dir, dir->pair);
+ if (err) {
+ return err;
+ }
+
+ /* check for root, can only be something like '/././../.' */
+ if (strspn(path, "/.") == strlen(path)) {
+ dir->head[0] = dir->pair[0];
+ dir->head[1] = dir->pair[1];
+ dir->pos = sizeof(dir->d) - 2;
+ dir->off = sizeof(dir->d);
+ return 0;
+ }
+
+ lfs_entry_t entry;
+ err = lfs_dir_find(lfs, dir, &entry, &path);
+ if (err) {
+ return err;
+ } else if (entry.d.type != LFS_TYPE_DIR) {
+ return LFS_ERR_NOTDIR;
+ }
+
+ err = lfs_dir_fetch(lfs, dir, entry.d.u.dir);
+ if (err) {
+ return err;
+ }
+
+ /* setup head dir
+ * special offset for '.' and '..'
+ */
+ dir->head[0] = dir->pair[0];
+ dir->head[1] = dir->pair[1];
+ dir->pos = sizeof(dir->d) - 2;
+ dir->off = sizeof(dir->d);
+
+ /* add to list of directories */
+ dir->next = lfs->dirs;
+ lfs->dirs = dir;
+
+ return 0;
+}
+
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir)
+{
+ /* remove from list of directories */
+ for (lfs_dir_t **p = &lfs->dirs; *p; p = &(*p)->next) {
+ if (*p == dir) {
+ *p = dir->next;
+ break;
+ }
+ }
+ return 0;
+}
+
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info)
+{
+ memset(info, 0, sizeof(*info));
+
+ /* special offset for '.' and '..' */
+ if (dir->pos == sizeof(dir->d) - 2) {
+ info->type = LFS_TYPE_DIR;
+ strcpy(info->name, ".");
+ dir->pos += 1;
+ return 1;
+ } else if (dir->pos == sizeof(dir->d) - 1) {
+ info->type = LFS_TYPE_DIR;
+ strcpy(info->name, "..");
+ dir->pos += 1;
+ return 1;
+ }
+
+ lfs_entry_t entry;
+ while (true) {
+ int err = lfs_dir_next(lfs, dir, &entry);
+ if (err) {
+ return (err == LFS_ERR_NOENT) ? 0 : err;
+ }
+
+ if ((0x7f & entry.d.type) != LFS_TYPE_REG &&
+ (0x7f & entry.d.type) != LFS_TYPE_DIR) {
+ continue;
+ }
+
+ /* check that entry has not been moved */
+ if (entry.d.type & 0x80) {
+ int moved = lfs_moved(lfs, &entry.d.u);
+ if (moved < 0) {
+ return moved;
+ }
+
+ if (moved) {
+ continue;
+ }
+
+ entry.d.type &= ~0x80;
+ }
+
+ break;
+ }
+
+ info->type = entry.d.type;
+ if (info->type == LFS_TYPE_REG) {
+ info->size = entry.d.u.file.size;
+ }
+
+ int err = lfs_bd_read(lfs, dir->pair[0],
+ entry.off + 4 + entry.d.elen + entry.d.alen,
+ info->name, entry.d.nlen);
+ if (err) {
+ return err;
+ }
+ return 1;
+}
+
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off)
+{
+ /* simply walk from head dir */
+ int err = lfs_dir_rewind(lfs, dir);
+ if (err) {
+ return err;
+ }
+ dir->pos = off;
+
+ while (off > (0x7fffffff & dir->d.size)) {
+ off -= 0x7fffffff & dir->d.size;
+ if (!(0x80000000 & dir->d.size)) {
+ return LFS_ERR_INVAL;
+ }
+
+ int err = lfs_dir_fetch(lfs, dir, dir->d.tail);
+ if (err) {
+ return err;
+ }
+ }
+
+ dir->off = off;
+ return 0;
+}
+
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir)
+{
+ return dir->pos;
+}
+
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir)
+{
+ /* reload the head dir */
+ int err = lfs_dir_fetch(lfs, dir, dir->head);
+ if (err) {
+ return err;
+ }
+
+ dir->pair[0] = dir->head[0];
+ dir->pair[1] = dir->head[1];
+ dir->pos = sizeof(dir->d) - 2;
+ dir->off = sizeof(dir->d);
+ return 0;
+}
+
+/* File index list operations */
+static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off)
+{
+ lfs_off_t size = *off;
+ lfs_off_t b = lfs->cfg->block_size - 2 * 4;
+ lfs_off_t i = size / b;
+ if (i == 0) {
+ return 0;
+ }
+
+ i = (size - 4 * (lfs_popc(i - 1) + 2)) / b;
+ *off = size - b * i - 4 * lfs_popc(i);
+ return i;
+}
+
+static int lfs_ctz_find(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t head,
+ lfs_size_t size, lfs_size_t pos, lfs_block_t *block,
+ lfs_off_t *off)
+{
+ if (size == 0) {
+ *block = 0xffffffff;
+ *off = 0;
+ return 0;
+ }
+
+ lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){ size - 1 });
+ lfs_off_t target = lfs_ctz_index(lfs, &pos);
+
+ while (current > target) {
+ lfs_size_t skip =
+ lfs_min(lfs_npw2(current - target + 1) - 1, lfs_ctz(current));
+
+ int err = lfs_cache_read(lfs, rcache, pcache, head, 4 * skip, &head, 4);
+ if (err) {
+ return err;
+ }
+
+ assert(head >= 2 && head <= lfs->cfg->block_count);
+ current -= 1 << skip;
+ }
+
+ *block = head;
+ *off = pos;
+ return 0;
+}
+
+static int lfs_ctz_extend(lfs_t *lfs, lfs_cache_t *rcache, lfs_cache_t *pcache,
+ lfs_block_t head, lfs_size_t size, lfs_block_t *block,
+ lfs_off_t *off)
+{
+ while (true) {
+ /* go ahead and grab a block */
+ lfs_block_t nblock;
+ int err = lfs_alloc(lfs, &nblock);
+ if (err) {
+ return err;
+ }
+ assert(nblock >= 2 && nblock <= lfs->cfg->block_count);
+
+ if (true) {
+ err = lfs_bd_erase(lfs, nblock);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ if (size == 0) {
+ *block = nblock;
+ *off = 0;
+ return 0;
+ }
+
+ size -= 1;
+ lfs_off_t index = lfs_ctz_index(lfs, &size);
+ size += 1;
+
+ /* just copy out the last block if it is incomplete */
+ if (size != lfs->cfg->block_size) {
+ for (lfs_off_t i = 0; i < size; i++) {
+ uint8_t data;
+ int err =
+ lfs_cache_read(lfs, rcache, NULL, head, i, &data, 1);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_cache_prog(lfs, pcache, rcache, nblock, i, &data,
+ 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+ }
+
+ *block = nblock;
+ *off = size;
+ return 0;
+ }
+
+ /* append block */
+ index += 1;
+ lfs_size_t skips = lfs_ctz(index) + 1;
+
+ for (lfs_off_t i = 0; i < skips; i++) {
+ int err = lfs_cache_prog(lfs, pcache, rcache, nblock, 4 * i,
+ &head, 4);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ if (i != skips - 1) {
+ err = lfs_cache_read(lfs, rcache, NULL, head, 4 * i, &head,
+ 4);
+ if (err) {
+ return err;
+ }
+ }
+
+ assert(head >= 2 && head <= lfs->cfg->block_count);
+ }
+
+ *block = nblock;
+ *off = 4 * skips;
+ return 0;
+ }
+
+ relocate:
+ LFS_DEBUG("Bad block at %ld", nblock);
+
+ /* just clear cache and try a new block */
+ pcache->block = 0xffffffff;
+ }
+}
+
+static int lfs_ctz_traverse(lfs_t *lfs, lfs_cache_t *rcache,
+ const lfs_cache_t *pcache, lfs_block_t head,
+ lfs_size_t size, int (*cb)(void *, lfs_block_t),
+ void *data)
+{
+ if (size == 0) {
+ return 0;
+ }
+
+ lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){ size - 1 });
+
+ while (true) {
+ int err = cb(data, head);
+ if (err) {
+ return err;
+ }
+
+ if (index == 0) {
+ return 0;
+ }
+
+ err = lfs_cache_read(lfs, rcache, pcache, head, 0, &head, 4);
+ if (err) {
+ return err;
+ }
+
+ index -= 1;
+ }
+}
+
+/* Top level file operations */
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags)
+{
+ /* deorphan if we haven't yet, needed at most once after poweron */
+ if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* allocate entry for file if it doesn't exist */
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t entry;
+ err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ if (!(flags & LFS_O_CREAT)) {
+ return LFS_ERR_NOENT;
+ }
+
+ /* create entry to remember name */
+ entry.d.type = LFS_TYPE_REG;
+ entry.d.elen = sizeof(entry.d) - 4;
+ entry.d.alen = 0;
+ entry.d.nlen = strlen(path);
+ entry.d.u.file.head = 0xffffffff;
+ entry.d.u.file.size = 0;
+ err = lfs_dir_append(lfs, &cwd, &entry, path);
+ if (err) {
+ return err;
+ }
+ } else if (entry.d.type == LFS_TYPE_DIR) {
+ return LFS_ERR_ISDIR;
+ } else if (flags & LFS_O_EXCL) {
+ return LFS_ERR_EXIST;
+ }
+
+ // setup file struct
+ file->pair[0] = cwd.pair[0];
+ file->pair[1] = cwd.pair[1];
+ file->poff = entry.off;
+ file->head = entry.d.u.file.head;
+ file->size = entry.d.u.file.size;
+ file->flags = flags;
+ file->pos = 0;
+
+ if (flags & LFS_O_TRUNC) {
+ file->head = 0xffffffff;
+ file->size = 0;
+ }
+
+ /* allocate buffer if needed */
+ file->cache.block = 0xffffffff;
+ if (lfs->cfg->file_buffer) {
+ file->cache.buffer = lfs->cfg->file_buffer;
+ } else if ((file->flags & 3) == LFS_O_RDONLY) {
+ file->cache.buffer = malloc(lfs->cfg->read_size);
+ if (!file->cache.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ } else {
+ file->cache.buffer = malloc(lfs->cfg->prog_size);
+ if (!file->cache.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ }
+
+ /* add to list of files */
+ file->next = lfs->files;
+ lfs->files = file;
+
+ return 0;
+}
+
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file)
+{
+ int err = lfs_file_sync(lfs, file);
+
+ /* remove from list of files */
+ for (lfs_file_t **p = &lfs->files; *p; p = &(*p)->next) {
+ if (*p == file) {
+ *p = file->next;
+ break;
+ }
+ }
+
+ /* clean up memory */
+ if (!lfs->cfg->file_buffer) {
+ free(file->cache.buffer);
+ }
+ return err;
+}
+
+static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file)
+{
+relocate:
+ LFS_DEBUG("Bad block at %ld", file->block);
+
+ /* just relocate what exists into new block */
+ lfs_block_t nblock;
+ int err = lfs_alloc(lfs, &nblock);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_bd_erase(lfs, nblock);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ /* either read from dirty cache or disk */
+ for (lfs_off_t i = 0; i < file->off; i++) {
+ uint8_t data;
+ err = lfs_cache_read(lfs, &lfs->rcache, &file->cache, file->block, i,
+ &data, 1);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, nblock, i, &data,
+ 1);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+ }
+
+ /* copy over new state of file */
+ memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size);
+ file->cache.block = lfs->pcache.block;
+ file->cache.off = lfs->pcache.off;
+ lfs->pcache.block = 0xffffffff;
+
+ file->block = nblock;
+ return 0;
+}
+
+static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file)
+{
+ if (file->flags & LFS_F_READING) {
+ /* just drop read cache */
+ file->cache.block = 0xffffffff;
+ file->flags &= ~LFS_F_READING;
+ }
+
+ if (file->flags & LFS_F_WRITING) {
+ lfs_off_t pos = file->pos;
+
+ /* copy over anything after current branch */
+ lfs_file_t orig = {
+ .head = file->head,
+ .size = file->size,
+ .flags = LFS_O_RDONLY,
+ .pos = file->pos,
+ .cache = lfs->rcache,
+ };
+ lfs->rcache.block = 0xffffffff;
+
+ while (file->pos < file->size) {
+ /*
+ *copy over a byte at a time, leave it up to caching
+ * to make this efficient
+ */
+ uint8_t data;
+ lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1);
+ if (res < 0) {
+ return res;
+ }
+
+ res = lfs_file_write(lfs, file, &data, 1);
+ if (res < 0) {
+ return res;
+ }
+
+ /* keep our reference to the rcache in sync */
+ if (lfs->rcache.block != 0xffffffff) {
+ orig.cache.block = 0xffffffff;
+ lfs->rcache.block = 0xffffffff;
+ }
+ }
+
+ /* write out what we have */
+ while (true) {
+ int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ return err;
+ }
+
+ break;
+ relocate:
+ err = lfs_file_relocate(lfs, file);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* actual file updates */
+ file->head = file->block;
+ file->size = file->pos;
+ file->flags &= ~LFS_F_WRITING;
+ file->flags |= LFS_F_DIRTY;
+
+ file->pos = pos;
+ }
+ return 0;
+}
+
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file)
+{
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+
+ if ((file->flags & LFS_F_DIRTY) && !(file->flags & LFS_F_ERRED) &&
+ !lfs_pairisnull(file->pair)) {
+ /* update dir entry */
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, file->pair);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t entry = { .off = file->poff };
+ err =
+ lfs_bd_read(lfs, cwd.pair[0], entry.off, &entry.d, sizeof(entry.d));
+ if (err) {
+ return err;
+ }
+
+ if (entry.d.type != LFS_TYPE_REG) {
+ /* sanity check valid entry */
+ return LFS_ERR_INVAL;
+ }
+
+ entry.d.u.file.head = file->head;
+ entry.d.u.file.size = file->size;
+
+ err = lfs_dir_update(lfs, &cwd, &entry, NULL);
+ if (err) {
+ return err;
+ }
+
+ file->flags &= ~LFS_F_DIRTY;
+ }
+
+ return 0;
+}
+
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer,
+ lfs_size_t size)
+{
+ uint8_t *data = buffer;
+ lfs_size_t nsize = size;
+
+ if ((file->flags & 3) == LFS_O_WRONLY) {
+ return LFS_ERR_INVAL;
+ }
+
+ if (file->flags & LFS_F_WRITING) {
+ /* flush out any writes */
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (file->pos >= file->size) {
+ /* eof if past end */
+ return 0;
+ }
+
+ size = lfs_min(size, file->size - file->pos);
+ nsize = size;
+
+ while (nsize > 0) {
+ /* check if we need a new block */
+ if (!(file->flags & LFS_F_READING) ||
+ file->off == lfs->cfg->block_size) {
+ int err =
+ lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size,
+ file->pos, &file->block, &file->off);
+ if (err) {
+ return err;
+ }
+
+ file->flags |= LFS_F_READING;
+ }
+
+ /* read as much as we can in current block */
+ lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+ int err = lfs_cache_read(lfs, &file->cache, NULL, file->block,
+ file->off, data, diff);
+ if (err) {
+ return err;
+ }
+
+ file->pos += diff;
+ file->off += diff;
+ data += diff;
+ nsize -= diff;
+ }
+ return size;
+}
+
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer,
+ lfs_size_t size)
+{
+ const uint8_t *data = buffer;
+ lfs_size_t nsize = size;
+
+ if ((file->flags & 3) == LFS_O_RDONLY) {
+ return LFS_ERR_INVAL;
+ }
+
+ if (file->flags & LFS_F_READING) {
+ /* drop any reads */
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+ }
+
+ if ((file->flags & LFS_O_APPEND) && file->pos < file->size) {
+ file->pos = file->size;
+ }
+
+ if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) {
+ /* fill with zeros */
+ lfs_off_t pos = file->pos;
+ file->pos = file->size;
+
+ while (file->pos < pos) {
+ lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){ 0 }, 1);
+ if (res < 0) {
+ return res;
+ }
+ }
+ }
+
+ while (nsize > 0) {
+ /* check if we need a new block */
+ if (!(file->flags & LFS_F_WRITING) ||
+ file->off == lfs->cfg->block_size) {
+ if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
+ /* find out which block we're extending from */
+ int err = lfs_ctz_find(lfs, &file->cache, NULL, file->head,
+ file->size, file->pos - 1, &file->block,
+ &file->off);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ /* mark cache as dirty since we may have read data into it */
+ file->cache.block = 0xffffffff;
+ }
+
+ /* extend file with new blocks */
+ lfs_alloc_ack(lfs);
+ int err =
+ lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, file->block,
+ file->pos, &file->block, &file->off);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ file->flags |= LFS_F_WRITING;
+ }
+
+ /* program as much as we can in current block */
+ lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+ while (true) {
+ int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache,
+ file->block, file->off, data, diff);
+ if (err) {
+ if (err == LFS_ERR_CORRUPT) {
+ goto relocate;
+ }
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+
+ break;
+ relocate:
+ err = lfs_file_relocate(lfs, file);
+ if (err) {
+ file->flags |= LFS_F_ERRED;
+ return err;
+ }
+ }
+
+ file->pos += diff;
+ file->off += diff;
+ data += diff;
+ nsize -= diff;
+
+ lfs_alloc_ack(lfs);
+ }
+
+ file->flags &= ~LFS_F_ERRED;
+ return size;
+}
+
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off,
+ int whence)
+{
+ /* write out everything beforehand, may be noop if rdonly */
+ int err = lfs_file_flush(lfs, file);
+ if (err) {
+ return err;
+ }
+
+ /* update pos */
+ if (whence == LFS_SEEK_SET) {
+ file->pos = off;
+ } else if (whence == LFS_SEEK_CUR) {
+ if ((lfs_off_t)-off > file->pos) {
+ return LFS_ERR_INVAL;
+ }
+
+ file->pos = file->pos + off;
+ } else if (whence == LFS_SEEK_END) {
+ if ((lfs_off_t)-off > file->size) {
+ return LFS_ERR_INVAL;
+ }
+
+ file->pos = file->size + off;
+ }
+ return file->pos;
+}
+
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file)
+{
+ return file->pos;
+}
+
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file)
+{
+ lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET);
+ if (res < 0) {
+ return res;
+ }
+ return 0;
+}
+
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file)
+{
+ return lfs_max(file->pos, file->size);
+}
+
+/* General fs oprations */
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info)
+{
+ /* check for root, can only be something like '/././../.' */
+ if (strspn(path, "/.") == strlen(path)) {
+ memset(info, 0, sizeof(*info));
+ info->type = LFS_TYPE_DIR;
+ strcpy(info->name, "/");
+ return 0;
+ }
+
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t entry;
+ err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err) {
+ return err;
+ }
+
+ memset(info, 0, sizeof(*info));
+ info->type = entry.d.type;
+ if (info->type == LFS_TYPE_REG) {
+ info->size = entry.d.u.file.size;
+ }
+
+ err = lfs_bd_read(lfs, cwd.pair[0],
+ entry.off + 4 + entry.d.elen + entry.d.alen, info->name,
+ entry.d.nlen);
+ if (err) {
+ return err;
+ }
+ return 0;
+}
+
+int lfs_remove(lfs_t *lfs, const char *path)
+{
+ /* deorphan if we haven't yet, needed at most once after poweron */
+ if (!lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t entry;
+ err = lfs_dir_find(lfs, &cwd, &entry, &path);
+ if (err) {
+ return err;
+ }
+
+ lfs_dir_t dir;
+ if (entry.d.type == LFS_TYPE_DIR) {
+ /* must be empty before removal, checking size
+ * without masking top bit checks for any case where dir is not empty
+ */
+ int err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
+ if (err) {
+ return err;
+ } else if (dir.d.size != sizeof(dir.d) + 4) {
+ return LFS_ERR_INVAL;
+ }
+ }
+
+ /* remove the entry */
+ err = lfs_dir_remove(lfs, &cwd, &entry);
+ if (err) {
+ return err;
+ }
+
+ /* if we were a directory, find pred, replace tail */
+ if (entry.d.type == LFS_TYPE_DIR) {
+ int res = lfs_pred(lfs, dir.pair, &cwd);
+ if (res < 0) {
+ return res;
+ }
+
+ assert(res); /* must have pred */
+ cwd.d.tail[0] = dir.d.tail[0];
+ cwd.d.tail[1] = dir.d.tail[1];
+
+ int err = lfs_dir_commit(lfs, &cwd, NULL, 0);
+ if (err) {
+ return err;
+ }
+ }
+ return 0;
+}
+
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath)
+{
+ /* deorphan if we haven't yet, needed at most once after poweron */
+ if (!lfs->deorphaned) {
+ int err = lfs_deorphan(lfs);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* find old entry */
+ lfs_dir_t oldcwd;
+ int err = lfs_dir_fetch(lfs, &oldcwd, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t oldentry;
+ err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
+ if (err) {
+ return err;
+ }
+
+ /* allocate new entry */
+ lfs_dir_t newcwd;
+ err = lfs_dir_fetch(lfs, &newcwd, lfs->root);
+ if (err) {
+ return err;
+ }
+
+ lfs_entry_t preventry;
+ err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath);
+ if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) {
+ return err;
+ }
+
+ bool prevexists = (err != LFS_ERR_NOENT);
+ bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
+
+ /* must have same type */
+ if (prevexists && preventry.d.type != oldentry.d.type) {
+ return LFS_ERR_INVAL;
+ }
+
+ lfs_dir_t dir;
+ if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
+ /* must be empty before removal, checking size
+ * without masking top bit checks for any case where dir is not empty
+ */
+ int err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
+ if (err) {
+ return err;
+ } else if (dir.d.size != sizeof(dir.d) + 4) {
+ return LFS_ERR_INVAL;
+ }
+ }
+
+ /* mark as moving */
+ oldentry.d.type |= 0x80;
+ err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL);
+ if (err) {
+ return err;
+ }
+
+ /* update pair if newcwd == oldcwd */
+ if (samepair) {
+ newcwd = oldcwd;
+ }
+
+ /* move to new location */
+ lfs_entry_t newentry = preventry;
+ newentry.d = oldentry.d;
+ newentry.d.type &= ~0x80;
+ newentry.d.nlen = strlen(newpath);
+
+ if (prevexists) {
+ int err = lfs_dir_update(lfs, &newcwd, &newentry, newpath);
+ if (err) {
+ return err;
+ }
+ } else {
+ int err = lfs_dir_append(lfs, &newcwd, &newentry, newpath);
+ if (err) {
+ return err;
+ }
+ }
+
+ /* update pair if newcwd == oldcwd */
+ if (samepair) {
+ oldcwd = newcwd;
+ }
+
+ /* remove old entry */
+ err = lfs_dir_remove(lfs, &oldcwd, &oldentry);
+ if (err) {
+ return err;
+ }
+
+ /* if we were a directory, find pred, replace tail */
+ if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
+ int res = lfs_pred(lfs, dir.pair, &newcwd);
+ if (res < 0) {
+ return res;
+ }
+
+ assert(res); /* must have pred */
+ newcwd.d.tail[0] = dir.d.tail[0];
+ newcwd.d.tail[1] = dir.d.tail[1];
+
+ int err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
+ if (err) {
+ return err;
+ }
+ }
+ return 0;
+}
+
+/* Filesystem operations */
+static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg)
+{
+ lfs->cfg = cfg;
+
+ /* setup read cache */
+ lfs->rcache.block = 0xffffffff;
+ if (lfs->cfg->read_buffer) {
+ lfs->rcache.buffer = lfs->cfg->read_buffer;
+ } else {
+ lfs->rcache.buffer = malloc(lfs->cfg->read_size);
+ if (!lfs->rcache.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ }
+
+ /* setup program cache */
+ lfs->pcache.block = 0xffffffff;
+ if (lfs->cfg->prog_buffer) {
+ lfs->pcache.buffer = lfs->cfg->prog_buffer;
+ } else {
+ lfs->pcache.buffer = malloc(lfs->cfg->prog_size);
+ if (!lfs->pcache.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ }
+
+ /* setup lookahead, round down to nearest 32-bits */
+ assert(lfs->cfg->lookahead % 32 == 0);
+ assert(lfs->cfg->lookahead > 0);
+ if (lfs->cfg->lookahead_buffer) {
+ lfs->free.buffer = lfs->cfg->lookahead_buffer;
+ } else {
+ lfs->free.buffer = malloc(lfs->cfg->lookahead / 8);
+ if (!lfs->free.buffer) {
+ return LFS_ERR_NOMEM;
+ }
+ }
+
+ /* check that the block size is large enough to fit ctz pointers */
+ assert(4 * lfs_npw2(0xffffffff / (lfs->cfg->block_size - 2 * 4)) <=
+ lfs->cfg->block_size);
+
+ /* setup default state */
+ lfs->root[0] = 0xffffffff;
+ lfs->root[1] = 0xffffffff;
+ lfs->files = NULL;
+ lfs->dirs = NULL;
+ lfs->deorphaned = false;
+
+ return 0;
+}
+
+static int lfs_deinit(lfs_t *lfs)
+{
+ /* free allocated memory */
+ if (!lfs->cfg->read_buffer) {
+ free(lfs->rcache.buffer);
+ }
+
+ if (!lfs->cfg->prog_buffer) {
+ free(lfs->pcache.buffer);
+ }
+
+ if (!lfs->cfg->lookahead_buffer) {
+ free(lfs->free.buffer);
+ }
+ return 0;
+}
+
+int lfs_format(lfs_t *lfs, const struct lfs_config *cfg)
+{
+ int err = lfs_init(lfs, cfg);
+ if (err) {
+ return err;
+ }
+
+ /* create free lookahead */
+ memset(lfs->free.buffer, 0, lfs->cfg->lookahead / 8);
+ lfs->free.begin = 0;
+ lfs->free.off = 0;
+ lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
+
+ /* create superblock dir */
+ lfs_alloc_ack(lfs);
+ lfs_dir_t superdir;
+ err = lfs_dir_alloc(lfs, &superdir);
+ if (err) {
+ return err;
+ }
+
+ /* write root directory */
+ lfs_dir_t root;
+ err = lfs_dir_alloc(lfs, &root);
+ if (err) {
+ return err;
+ }
+
+ err = lfs_dir_commit(lfs, &root, NULL, 0);
+ if (err) {
+ return err;
+ }
+
+ lfs->root[0] = root.pair[0];
+ lfs->root[1] = root.pair[1];
+
+ /* write superblocks */
+ lfs_superblock_t superblock = {
+ .off = sizeof(superdir.d),
+ .d.type = LFS_TYPE_SUPERBLOCK,
+ .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
+ .d.nlen = sizeof(superblock.d.magic),
+ .d.version = 0x00010001,
+ .d.magic = { "littlefs" },
+ .d.block_size = lfs->cfg->block_size,
+ .d.block_count = lfs->cfg->block_count,
+ .d.root = { lfs->root[0], lfs->root[1] },
+ };
+ superdir.d.tail[0] = root.pair[0];
+ superdir.d.tail[1] = root.pair[1];
+ superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
+
+ /* write both pairs to be safe */
+ bool valid = false;
+ for (int i = 0; i < 2; i++) {
+ int err = lfs_dir_commit(
+ lfs, &superdir,
+ (struct lfs_region[]){ { sizeof(superdir.d), sizeof(superblock.d),
+ &superblock.d, sizeof(superblock.d) } },
+ 1);
+ if (err && err != LFS_ERR_CORRUPT) {
+ return err;
+ }
+
+ valid = valid || !err;
+ }
+
+ if (!valid) {
+ return LFS_ERR_CORRUPT;
+ }
+
+ /* sanity check that fetch works */
+ err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){ 0, 1 });
+ if (err) {
+ return err;
+ }
+
+ lfs_alloc_ack(lfs);
+ return lfs_deinit(lfs);
+}
+
+int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg)
+{
+ int err = lfs_init(lfs, cfg);
+ if (err) {
+ return err;
+ }
+
+ /* setup free lookahead */
+ lfs->free.begin = -lfs->cfg->lookahead;
+ lfs->free.off = lfs->cfg->lookahead;
+ lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
+
+ /* load superblock */
+ lfs_dir_t dir;
+ lfs_superblock_t superblock;
+ err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){ 0, 1 });
+ if (err && err != LFS_ERR_CORRUPT) {
+ return err;
+ }
+
+ if (!err) {
+ int err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), &superblock.d,
+ sizeof(superblock.d));
+ if (err) {
+ return err;
+ }
+
+ lfs->root[0] = superblock.d.root[0];
+ lfs->root[1] = superblock.d.root[1];
+ }
+
+ if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
+ LFS_ERROR("Invalid superblock at %ld %ld", dir.pair[0], dir.pair[1]);
+ return LFS_ERR_CORRUPT;
+ }
+
+ if (superblock.d.version > (0x00010001 | 0x0000ffff)) {
+ LFS_ERROR("Invalid version %ld.%ld",
+ 0xffff & (superblock.d.version >> 16),
+ 0xffff & (superblock.d.version >> 0));
+ return LFS_ERR_INVAL;
+ }
+ return 0;
+}
+
+int lfs_unmount(lfs_t *lfs)
+{
+ return lfs_deinit(lfs);
+}
+
+/* Littlefs specific operations */
+int lfs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data)
+{
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ /* iterate over metadata pairs */
+ lfs_dir_t dir;
+ lfs_entry_t entry;
+ lfs_block_t cwd[2] = { 0, 1 };
+
+ while (true) {
+ for (int i = 0; i < 2; i++) {
+ int err = cb(data, cwd[i]);
+ if (err) {
+ return err;
+ }
+ }
+
+ int err = lfs_dir_fetch(lfs, &dir, cwd);
+ if (err) {
+ return err;
+ }
+
+ /* iterate over contents */
+ while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size) - 4) {
+ int err = lfs_bd_read(lfs, dir.pair[0], dir.off, &entry.d,
+ sizeof(entry.d));
+ if (err) {
+ return err;
+ }
+
+ dir.off += lfs_entry_size(&entry);
+ if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
+ int err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
+ entry.d.u.file.head,
+ entry.d.u.file.size, cb, data);
+ if (err) {
+ return err;
+ }
+ }
+ }
+
+ cwd[0] = dir.d.tail[0];
+ cwd[1] = dir.d.tail[1];
+
+ if (lfs_pairisnull(cwd)) {
+ break;
+ }
+ }
+
+ /* iterate over any open files */
+ for (lfs_file_t *f = lfs->files; f; f = f->next) {
+ if (f->flags & LFS_F_DIRTY) {
+ int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->head,
+ f->size, cb, data);
+ if (err) {
+ return err;
+ }
+ }
+
+ if (f->flags & LFS_F_WRITING) {
+ int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->block,
+ f->pos, cb, data);
+ if (err) {
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir)
+{
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ /* iterate over all directory directory entries */
+ int err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){ 0, 1 });
+ if (err) {
+ return err;
+ }
+
+ while (!lfs_pairisnull(pdir->d.tail)) {
+ if (lfs_paircmp(pdir->d.tail, dir) == 0) {
+ return true;
+ }
+
+ int err = lfs_dir_fetch(lfs, pdir, pdir->d.tail);
+ if (err) {
+ return err;
+ }
+ }
+ return false;
+}
+
+static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *parent,
+ lfs_entry_t *entry)
+{
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ parent->d.tail[0] = 0;
+ parent->d.tail[1] = 1;
+
+ /* iterate over all directory directory entries */
+ while (!lfs_pairisnull(parent->d.tail)) {
+ int err = lfs_dir_fetch(lfs, parent, parent->d.tail);
+ if (err) {
+ return err;
+ }
+
+ while (true) {
+ int err = lfs_dir_next(lfs, parent, entry);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ break;
+ }
+
+ if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) &&
+ lfs_paircmp(entry->d.u.dir, dir) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static int lfs_moved(lfs_t *lfs, const void *e)
+{
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ /* skip superblock */
+ lfs_dir_t cwd;
+ int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){ 0, 1 });
+ if (err) {
+ return err;
+ }
+
+ /* iterate over all directory directory entries */
+ lfs_entry_t entry;
+ while (!lfs_pairisnull(cwd.d.tail)) {
+ int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
+ if (err) {
+ return err;
+ }
+
+ while (true) {
+ int err = lfs_dir_next(lfs, &cwd, &entry);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ break;
+ }
+
+ if (!(0x80 & entry.d.type) &&
+ memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static int lfs_relocate(lfs_t *lfs, const lfs_block_t oldpair[2],
+ const lfs_block_t newpair[2])
+{
+ /* find parent */
+ lfs_dir_t parent;
+ lfs_entry_t entry;
+ int res = lfs_parent(lfs, oldpair, &parent, &entry);
+ if (res < 0) {
+ return res;
+ }
+
+ if (res) {
+ /* update disk, this creates a desync */
+ entry.d.u.dir[0] = newpair[0];
+ entry.d.u.dir[1] = newpair[1];
+
+ int err = lfs_dir_update(lfs, &parent, &entry, NULL);
+ if (err) {
+ return err;
+ }
+ /* update internal root */
+ if (lfs_paircmp(oldpair, lfs->root) == 0) {
+ LFS_DEBUG("Relocating root %ld %ld", newpair[0], newpair[1]);
+ lfs->root[0] = newpair[0];
+ lfs->root[1] = newpair[1];
+ }
+ /* clean up bad block, which should now be a desync */
+ return lfs_deorphan(lfs);
+ }
+
+ /* find pred */
+ res = lfs_pred(lfs, oldpair, &parent);
+ if (res < 0) {
+ return res;
+ }
+
+ if (res) {
+ /* just replace bad pair, no desync can occur */
+ parent.d.tail[0] = newpair[0];
+ parent.d.tail[1] = newpair[1];
+
+ return lfs_dir_commit(lfs, &parent, NULL, 0);
+ }
+ /* couldn't find dir, must be new */
+ return 0;
+}
+
+int lfs_deorphan(lfs_t *lfs)
+{
+ lfs->deorphaned = true;
+
+ if (lfs_pairisnull(lfs->root)) {
+ return 0;
+ }
+
+ lfs_dir_t pdir = { .d.size = 0x80000000 };
+ lfs_dir_t cwd = { .d.tail[0] = 0, .d.tail[1] = 1 };
+
+ /* iterate over all directory directory entries */
+ while (!lfs_pairisnull(cwd.d.tail)) {
+ int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
+ if (err) {
+ return err;
+ }
+
+ /* check head blocks for orphans */
+ if (!(0x80000000 & pdir.d.size)) {
+ /* check if we have a parent */
+ lfs_dir_t parent;
+ lfs_entry_t entry;
+ int res = lfs_parent(lfs, pdir.d.tail, &parent, &entry);
+ if (res < 0) {
+ return res;
+ }
+
+ if (!res) {
+ /* we are an orphan */
+ LFS_DEBUG("Found orphan %ld %ld", pdir.d.tail[0],
+ pdir.d.tail[1]);
+
+ pdir.d.tail[0] = cwd.d.tail[0];
+ pdir.d.tail[1] = cwd.d.tail[1];
+
+ err = lfs_dir_commit(lfs, &pdir, NULL, 0);
+ if (err) {
+ return err;
+ }
+ break;
+ }
+
+ if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) {
+ /* we have desynced */
+ LFS_DEBUG("Found desync %ld %ld", entry.d.u.dir[0],
+ entry.d.u.dir[1]);
+
+ pdir.d.tail[0] = entry.d.u.dir[0];
+ pdir.d.tail[1] = entry.d.u.dir[1];
+
+ err = lfs_dir_commit(lfs, &pdir, NULL, 0);
+ if (err) {
+ return err;
+ }
+ break;
+ }
+ }
+
+ /* check entries for moves */
+ lfs_entry_t entry;
+ while (true) {
+ int err = lfs_dir_next(lfs, &cwd, &entry);
+ if (err && err != LFS_ERR_NOENT) {
+ return err;
+ }
+
+ if (err == LFS_ERR_NOENT) {
+ break;
+ }
+
+ /* found moved entry */
+ if (entry.d.type & 0x80) {
+ int moved = lfs_moved(lfs, &entry.d.u);
+ if (moved < 0) {
+ return moved;
+ }
+ if (moved) {
+ LFS_DEBUG("Found move %ld %ld", entry.d.u.dir[0],
+ entry.d.u.dir[1]);
+ int err = lfs_dir_remove(lfs, &cwd, &entry);
+ if (err) {
+ return err;
+ }
+ } else {
+ LFS_DEBUG("Found partial move %ld %ld", entry.d.u.dir[0],
+ entry.d.u.dir[1]);
+ entry.d.type &= ~0x80;
+ int err = lfs_dir_update(lfs, &cwd, &entry, NULL);
+ if (err) {
+ return err;
+ }
+ }
+ }
+ }
+ memcpy(&pdir, &cwd, sizeof(pdir));
+ }
+ return 0;
+}
diff --git a/firmware/ec/src/filesystem/lfs.h b/firmware/ec/src/filesystem/lfs.h
new file mode 100644
index 0000000000..f9dd3b6aad
--- /dev/null
+++ b/firmware/ec/src/filesystem/lfs.h
@@ -0,0 +1,327 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef LFS_H
+#define LFS_H
+
+#include
+#include
+
+/* Type definitions */
+typedef uint32_t lfs_size_t;
+typedef uint32_t lfs_off_t;
+
+typedef int32_t lfs_ssize_t;
+typedef int32_t lfs_soff_t;
+
+typedef uint32_t lfs_block_t;
+
+/* Max name size in bytes */
+#ifndef LFS_NAME_MAX
+# define LFS_NAME_MAX 255
+#endif
+
+/* Possible error codes, these are negative to allow
+ * valid positive return values
+ */
+enum lfs_error {
+ LFS_ERR_OK = 0, /* No error */
+ LFS_ERR_IO = -5, /* Error during device operation */
+ LFS_ERR_CORRUPT = -52, /* Corrupted */
+ LFS_ERR_NOENT = -2, /* No directory entry */
+ LFS_ERR_EXIST = -17, /* Entry already exists */
+ LFS_ERR_NOTDIR = -20, /* Entry is not a dir */
+ LFS_ERR_ISDIR = -21, /* Entry is a dir */
+ LFS_ERR_INVAL = -22, /* Invalid parameter */
+ LFS_ERR_NOSPC = -28, /* No space left on device */
+ LFS_ERR_NOMEM = -12, /* No more memory available */
+};
+
+/* File types */
+enum lfs_type {
+ LFS_TYPE_REG = 0x11,
+ LFS_TYPE_DIR = 0x22,
+ LFS_TYPE_SUPERBLOCK = 0x2e,
+};
+
+/* File open flags */
+enum lfs_open_flags {
+ /* open flags */
+ LFS_O_RDONLY = 1, /* Open a file as read only */
+ LFS_O_WRONLY = 2, /* Open a file as write only */
+ LFS_O_RDWR = 3, /* Open a file as read and write */
+ LFS_O_CREAT = 0x0100, /* Create a file if it does not exist */
+ LFS_O_EXCL = 0x0200, /* Fail if a file already exists */
+ LFS_O_TRUNC = 0x0400, /* Truncate the existing file to zero size */
+ LFS_O_APPEND = 0x0800, /* Move to end of file on every write */
+
+ /* internally used flags */
+ LFS_F_DIRTY = 0x10000, /* File does not match storage */
+ LFS_F_WRITING = 0x20000, /* File has been written since last flush */
+ LFS_F_READING = 0x40000, /* File has been read since last flush */
+ LFS_F_ERRED = 0x80000, /* An error occured during write */
+};
+
+/* File seek flags */
+enum lfs_whence_flags {
+ LFS_SEEK_SET = 0, /* Seek relative to an absolute position */
+ LFS_SEEK_CUR = 1, /* Seek relative to the current file position */
+ LFS_SEEK_END = 2, /* Seek relative to the end of the file */
+};
+
+/* Configuration provided during initialization of the filesystem */
+struct lfs_config {
+ void *context;
+
+ /* Read a region in a block */
+ int (*read)(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
+ void *buffer, lfs_size_t size);
+
+ /* Program a region in a block, function must return LFS_ERR_CORRUPT
+ * if the block should be considered bad
+ */
+ int (*prog)(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
+ const void *buffer, lfs_size_t size);
+
+ /* Erase a block, A block must be erased before being programmed */
+ int (*erase)(const struct lfs_config *c, lfs_block_t block);
+
+ /* Sync the state of the underlying block device */
+ int (*sync)(const struct lfs_config *c);
+
+ /* Minimum size of a block read. This determines the size of read buffers.
+ * This may be larger than the physical read size to improve performance
+ * by caching more of the block device
+ */
+ lfs_size_t read_size;
+
+ /* Minimum size of a block program. This determines the size of program
+ * buffers. This may be larger than the physical program size to improve
+ * performance by caching more of the block device.
+ */
+ lfs_size_t prog_size;
+
+ /* Size of an erasable block. This does not impact ram consumption and
+ * may be larger than the physical erase size. However, this should be
+ * kept small as each file currently takes up an entire block .
+ */
+ lfs_size_t block_size;
+
+ /* Number of erasable blocks on the device. */
+ lfs_size_t block_count;
+
+ /* Number of blocks to lookahead during block allocation. A larger
+ * lookahead reduces the number of passes required to allocate a block.
+ * The lookahead buffer requires only 1 bit per block so it can be quite
+ * large with little ram impact. Should be a multiple of 32.
+ */
+ lfs_size_t lookahead;
+
+ /* Optional, statically allocated read buffer. Must be read sized. */
+ void *read_buffer;
+
+ /* Optional, statically allocated program buffer. Must be program sized. */
+ void *prog_buffer;
+
+ /* Optional, statically allocated lookahead buffer. Must be 1 bit per
+ * lookahead block
+ */
+ void *lookahead_buffer;
+
+ /* Optional, statically allocated buffer for files. Must be program sized.
+ * If enabled, only one file may be opened at a time.
+ */
+ void *file_buffer;
+};
+
+/* File info structure */
+struct lfs_info {
+ /* Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR */
+ uint8_t type;
+
+ /* Size of the file, only valid for REG files */
+ lfs_size_t size;
+
+ /* Name of the file stored as a null-terminated string */
+ char name[LFS_NAME_MAX + 1];
+};
+
+/* filesystem data structures */
+typedef struct lfs_entry {
+ lfs_off_t off;
+
+ struct lfs_disk_entry {
+ uint8_t type;
+ uint8_t elen;
+ uint8_t alen;
+ uint8_t nlen;
+ union {
+ struct {
+ lfs_block_t head;
+ lfs_size_t size;
+ } file;
+ lfs_block_t dir[2];
+ } u;
+ } d;
+} lfs_entry_t;
+
+typedef struct lfs_cache {
+ lfs_block_t block;
+ lfs_off_t off;
+ uint8_t *buffer;
+} lfs_cache_t;
+
+typedef struct lfs_file {
+ struct lfs_file *next;
+ lfs_block_t pair[2];
+ lfs_off_t poff;
+
+ lfs_block_t head;
+ lfs_size_t size;
+
+ uint32_t flags;
+ lfs_off_t pos;
+ lfs_block_t block;
+ lfs_off_t off;
+ lfs_cache_t cache;
+} lfs_file_t;
+
+typedef struct lfs_dir {
+ struct lfs_dir *next;
+ lfs_block_t pair[2];
+ lfs_off_t off;
+
+ lfs_block_t head[2];
+ lfs_off_t pos;
+
+ struct lfs_disk_dir {
+ uint32_t rev;
+ lfs_size_t size;
+ lfs_block_t tail[2];
+ } d;
+} lfs_dir_t;
+
+typedef struct lfs_superblock {
+ lfs_off_t off;
+
+ struct lfs_disk_superblock {
+ uint8_t type;
+ uint8_t elen;
+ uint8_t alen;
+ uint8_t nlen;
+ lfs_block_t root[2];
+ uint32_t block_size;
+ uint32_t block_count;
+ uint32_t version;
+ char magic[8];
+ } d;
+} lfs_superblock_t;
+
+typedef struct lfs_free {
+ lfs_block_t begin;
+ lfs_block_t end;
+ lfs_block_t off;
+ uint32_t *buffer;
+} lfs_free_t;
+
+/* The filesystem type */
+typedef struct lfs {
+ const struct lfs_config *cfg;
+ const struct lfs_config cfgs;
+
+ lfs_block_t root[2];
+ lfs_file_t *files;
+ lfs_dir_t *dirs;
+
+ lfs_cache_t rcache;
+ lfs_cache_t pcache;
+
+ lfs_free_t free;
+ bool deorphaned;
+} lfs_t;
+
+/* Format a block device with the filesystem */
+int lfs_format(lfs_t *lfs, const struct lfs_config *config);
+
+/* Mounts a filesystem */
+int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
+
+/* Unmounts a filesystem */
+int lfs_unmount(lfs_t *lfs);
+
+/* Removes a file or directory */
+int lfs_remove(lfs_t *lfs, const char *path);
+
+/* Rename or move a file or directory */
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
+
+/* Find info about a file or directory */
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
+
+/* Open a file */
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags);
+
+/* Close a file */
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
+
+/* Synchronize a file on storage */
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
+
+/* Read data from file */
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer,
+ lfs_size_t size);
+
+/* Write data to file */
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer,
+ lfs_size_t size);
+
+/* Change the position of the file */
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off,
+ int whence);
+
+/* Return the position of the file */
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);
+
+/* Change the position of the file to the beginning of the file */
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);
+
+/* Return the size of the file */
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
+
+/* Create a directory */
+int lfs_mkdir(lfs_t *lfs, const char *path);
+
+/* Open a directory */
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);
+
+/* Close a directory */
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
+
+/* Read an entry in the directory */
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
+
+/* Change the position of the directory */
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);
+
+/* Return the position of the directory */
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
+
+/* Change the position of the directory to the beginning of the directory */
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
+
+/* Traverse through all blocks in use by the filesystem */
+int lfs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data);
+
+/* Prunes any recoverable errors that may have occured in the filesystem
+ * Not needed to be called by user unless an operation is interrupted
+ * but the filesystem is still mounted. This is already called on first
+ * allocation.
+ * Returns a negative error code on failure.
+ */
+int lfs_deorphan(lfs_t *lfs);
+
+#endif
diff --git a/firmware/ec/src/filesystem/lfs_util.c b/firmware/ec/src/filesystem/lfs_util.c
new file mode 100644
index 0000000000..1db14d2b19
--- /dev/null
+++ b/firmware/ec/src/filesystem/lfs_util.c
@@ -0,0 +1,24 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "lfs_util.h"
+
+void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size)
+{
+ static const uint32_t rtable[16] = {
+ 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4,
+ 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+ 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
+ };
+
+ const uint8_t *data = buffer;
+
+ for (size_t i = 0; i < size; i++) {
+ *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf];
+ *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf];
+ }
+}
diff --git a/firmware/ec/src/filesystem/lfs_util.h b/firmware/ec/src/filesystem/lfs_util.h
new file mode 100644
index 0000000000..2fffdf86fb
--- /dev/null
+++ b/firmware/ec/src/filesystem/lfs_util.h
@@ -0,0 +1,142 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef LFS_UTIL_H
+#define LFS_UTIL_H
+
+#include
+#include
+#include
+#ifdef __ICCARM__
+# include
+#endif
+
+/* Builtin functions, these may be replaced by more
+ * efficient implementations in the system
+ */
+static inline uint32_t lfs_max(uint32_t a, uint32_t b)
+{
+ return (a > b) ? a : b;
+}
+
+static inline uint32_t lfs_min(uint32_t a, uint32_t b)
+{
+ return (a < b) ? a : b;
+}
+
+static inline uint32_t lfs_ctz(uint32_t a)
+{
+#if defined(__GNUC__) || defined(__CC_ARM)
+ return __builtin_ctz(a);
+#elif defined(__ICCARM__)
+ return __CLZ(__RBIT(a));
+#else
+ uint32_t r = 32;
+ a &= -a;
+ if (a)
+ r -= 1;
+ if (a & 0x0000ffff)
+ r -= 16;
+ if (a & 0x00ff00ff)
+ r -= 8;
+ if (a & 0x0f0f0f0f)
+ r -= 4;
+ if (a & 0x33333333)
+ r -= 2;
+ if (a & 0x55555555)
+ r -= 1;
+ return r;
+#endif
+}
+
+static inline uint32_t lfs_npw2(uint32_t a)
+{
+#if defined(__GNUC__) || defined(__CC_ARM)
+ return 32 - __builtin_clz(a - 1);
+#elif defined(__ICCARM__)
+ return 32 - __CLZ(a - 1);
+#else
+ uint32_t r = 0;
+ uint32_t s;
+ s = (a > 0xffff) << 4;
+ a >>= s;
+ r |= s;
+ s = (a > 0xff) << 3;
+ a >>= s;
+ r |= s;
+ s = (a > 0xf) << 2;
+ a >>= s;
+ r |= s;
+ s = (a > 0x3) << 1;
+ a >>= s;
+ r |= s;
+ return r | (a >> 1);
+#endif
+}
+
+static inline uint32_t lfs_popc(uint32_t a)
+{
+#if defined(__GNUC__) || defined(__CC_ARM)
+ return __builtin_popcount(a);
+#else
+ a = a - ((a >> 1) & 0x55555555);
+ a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
+ return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
+#endif
+}
+
+static inline int lfs_scmp(uint32_t a, uint32_t b)
+{
+ return (int)(unsigned)(a - b);
+}
+
+/* CRC-32 with polynomial = 0x04c11db7 */
+void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
+
+/* Logging functions */
+#ifdef __MBED__
+# include "mbed_debug.h"
+#else
+# define MBED_LFS_ENABLE_INFO false
+# define MBED_LFS_ENABLE_DEBUG true
+# define MBED_LFS_ENABLE_WARN true
+# define MBED_LFS_ENABLE_ERROR true
+#endif
+
+#if MBED_LFS_ENABLE_INFO
+# define LFS_INFO(fmt, ...) printf("lfs info: " fmt "\n", __VA_ARGS__)
+#elif !defined(MBED_LFS_ENABLE_INFO)
+# define LFS_INFO(fmt, ...) debug("lfs info: " fmt "\n", __VA_ARGS__)
+#else
+# define LFS_INFO(fmt, ...)
+#endif
+
+#if MBED_LFS_ENABLE_DEBUG
+# define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__)
+#elif !defined(MBED_LFS_ENABLE_DEBUG)
+# define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__)
+#else
+# define LFS_DEBUG(fmt, ...)
+#endif
+
+#if MBED_LFS_ENABLE_WARN
+# define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__)
+#elif !defined(MBED_LFS_ENABLE_WARN)
+# define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__)
+#else
+# define LFS_WARN(fmt, ...)
+#endif
+
+#if MBED_LFS_ENABLE_ERROR
+# define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__)
+#elif !defined(MBED_LFS_ENABLE_ERROR)
+# define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__)
+#else
+# define LFS_ERROR(fmt, ...)
+#endif
+
+#endif
diff --git a/firmware/ec/src/main.c b/firmware/ec/src/main.c
index 0dde35efac..f68ae33509 100644
--- a/firmware/ec/src/main.c
+++ b/firmware/ec/src/main.c
@@ -70,6 +70,7 @@ int main(void)
Board_initGeneral();
Board_initGPIO();
Board_initI2C();
+ Board_initSPI();
Board_initUSB(Board_USBDEVICE);
Board_initUART();
ethernet_start();
diff --git a/firmware/ec/src/subsystem/sys/sys.c b/firmware/ec/src/subsystem/sys/sys.c
index e2ab5c9e8a..bdff58c30f 100644
--- a/firmware/ec/src/subsystem/sys/sys.c
+++ b/firmware/ec/src/subsystem/sys/sys.c
@@ -6,20 +6,43 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
-#include "inc/subsystem/sys/sys.h"
#include "common/inc/global/Framework.h"
+#include "helpers/memory.h"
+#include "inc/common/bigbrother.h"
+#include "inc/common/global_header.h"
#include "inc/common/post.h"
#include "inc/devices/eeprom.h"
#include "inc/subsystem/gpp/gpp.h" /* For resetting AP */
-
+#include "inc/subsystem/sys/sys.h"
+#include "inc/utils/ocmp_util.h"
+#include "src/filesystem/fs_wrapper.h"
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
-#include
-#include
+#define FRAME_SIZE 64
+#define OCFS_TASK_PRIORITY 5
+#define OCFS_TASK_STACK_SIZE 4096
-#define OC_MAC_ADDRESS_SIZE 13
+Task_Struct ocFSTask;
+Char ocFSTaskStack[OCFS_TASK_STACK_SIZE];
+
+Semaphore_Handle semFilesysMsg;
+
+Semaphore_Struct semFSstruct;
+
+static Queue_Struct fsRxMsg;
+static Queue_Struct fsTxMsg;
+
+Queue_Handle fsRxMsgQueue;
+Queue_Handle fsTxMsgQueue;
extern POSTData PostResult[POST_RECORDS];
@@ -52,7 +75,6 @@ bool SYS_cmdEcho(void *driver, void *params)
return true;
}
-/*
/*****************************************************************************
** FUNCTION NAME : SYS_post_enable
**
@@ -134,3 +156,30 @@ bool SYS_post_get_results(void **getpostResult)
memcpy(((OCMPMessageFrame *)getpostResult), postResultMsg, 64);
return status;
}
+
+bool sys_post_init(void *driver, void *returnValue)
+{
+ Semaphore_construct(&semFSstruct, 0, NULL);
+ semFilesysMsg = Semaphore_handle(&semFSstruct);
+ if (!semFilesysMsg) {
+ LOGGER_DEBUG("FS:ERROR:: Failed in Creating Semaphore");
+ return false;
+ }
+ /* Create Message Queue for RX Messages */
+ fsTxMsgQueue = Util_constructQueue(&fsTxMsg);
+ if (!fsTxMsgQueue) {
+ LOGGER_ERROR("FS:ERROR:: Failed in Constructing Message Queue for");
+ return false;
+ }
+ Task_Params taskParams;
+ Task_Params_init(&taskParams);
+ taskParams.stackSize = OCFS_TASK_STACK_SIZE;
+ taskParams.stack = &ocFSTaskStack;
+ taskParams.instance->name = "FS_TASK";
+ taskParams.priority = OCFS_TASK_PRIORITY;
+ taskParams.arg0 = (UArg)driver;
+ taskParams.arg1 = (UArg)returnValue;
+ Task_construct(&ocFSTask, fs_init, &taskParams, NULL);
+ LOGGER_DEBUG("FS:INFO:: Creating filesystem task function.\n");
+ return true;
+}
diff --git a/firmware/ec/test/Makefile b/firmware/ec/test/Makefile
index 3e514de32b..d7a36beac9 100644
--- a/firmware/ec/test/Makefile
+++ b/firmware/ec/test/Makefile
@@ -10,6 +10,7 @@ OCWARE_ROOT ?= ..
PATHT = suites/
PATHB = build/
PATHR = build/results/
+PATHC = build/coverage/
RUNNER_PATH = build/runners/
# Compiler Options
@@ -48,10 +49,17 @@ BUILD_PATHS = $(RUNNER_PATH) $(PATHB) $(PATHR)
SRCT = $(wildcard $(PATHT)*.c)
TESTS ?= $(patsubst $(PATHT)Test%.c,Test%,$(SRCT))
RESULTS = $(patsubst %,$(PATHR)%.testresults,$(TESTS))
+COVERAGE = $(patsubst %,$(PATHC)%.info,$(TESTS))
# List of standard files that every test will build (unity, test, test runner)
STD_FILES=$(PATHB)Test_%$(TARGET_EXTENSION):$(UNITY_ROOT)/src/unity.c $(PATHT)/Test_%.c build/runners/Test_%_Runner.c
+# Create the flags to add all individual coverage files
+LCOV_ADD = $(patsubst %,-a %, $(COVERAGE))
+LCOV_ADD += -a $(PATHC)test-base.info
+# The directories to ignore in coverage
+LCOV_IGNORE = "*/test/*" "*/Unity/*" "*/ti/*"
+
#We try to detect the OS we are running on, and adjust commands as needed
ifeq ($(OS),Windows_NT)
DIR_XDC ?= C:/ti/xdctools_3_32_00_06_core/packages/
@@ -82,70 +90,75 @@ endif
test: $(BUILD_PATHS) $(RESULTS)
ruby $(UNITY_ROOT)/auto/unity_test_summary.rb $(PATHR)
+junit: test
+ @ruby $(UNITY_ROOT)/auto/stylize_as_junit.rb -r $(PATHR) -o $(PATHR)/unit-test-results.xml
+
# Note: we force the rebuild of this target each run to ensure fresh results
$(PATHR)%.testresults: $(PATHB)%$(TARGET_EXTENSION) FORCE
-./$< > $@ 2>&1
$(RUNNER_PATH)Test_%_Runner.c: $(PATHT)Test_%.c
ruby $(UNITY_ROOT)/auto/generate_test_runner.rb --suite_setup="extern void suite_setUp(void); suite_setUp();" --suite_teardown="extern void suite_tearDown(void); suite_tearDown(); return num_failures;" $< $@
+
+$(PATHC)test-base.info:
+ @lcov --rc lcov_branch_coverage=1 -c -i -d ../ -o $(PATHC)test-base.info
+
+# Get coverage statistics for every test
+$(COVERAGE): $(PATHC)%.info: $(PATHC)/% $(PATHB)%$(TARGET_EXTENSION) $(PATHC) $(BUILD_PATHS) $(RESULTS)
+ @lcov --rc lcov_branch_coverage=1 -c -d $< -o $@ > /dev/null
+# Combine all test suites coverage info and remove all files that are not src
+coverage: $(COVERAGE) $(PATHC) $(PATHC)test-base.info
+ @lcov --rc lcov_branch_coverage=1 $(LCOV_ADD) -o $(PATHC)/test-coverage-all.info > /dev/null
+ @lcov --rc lcov_branch_coverage=1 --remove $(PATHC)/test-coverage-all.info $(LCOV_IGNORE) -o $(PATHC)/test-coverage.info
# Test-specific build rules (add entries here for each new test suite)
# =============================================================================
+INC_M =-lm
TEST_PCA9557_SRC=$(OCWARE_ROOT)/src/devices/pca9557.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_I2C.c
$(PATHB)Test_pca9557$(TARGET_EXTENSION): $(STD_FILES) $(TEST_PCA9557_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
TEST_SE98A_SRC=$(OCWARE_ROOT)/src/devices/se98a.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c stub/stub_GateMutex.c $(OCWARE_ROOT)/src/post/post_util.c
-$(PATHB)Test_se98a$(TARGET_EXTENSION): $(STD_FILES) $(TEST_SE98A_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
+$(PATHB)Test_se98a$(TARGET_EXTENSION): $(STD_FILES) $(TEST_SE98A_SRC) $(INC_M)
TEST_GpioPCA9557_SRC=$(OCWARE_ROOT)/src/devices/pca9557.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_I2C.c $(OCWARE_ROOT)/src/drivers/GpioPCA9557.c $(OCWARE_ROOT)/src/helpers/memory.c stub/stub_GateMutex.c
$(PATHB)Test_GpioPCA9557$(TARGET_EXTENSION): $(STD_FILES) $(TEST_GpioPCA9557_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
TEST_INA226_SRC=$(OCWARE_ROOT)/src/devices/ina226.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c $(OCWARE_ROOT)/src/post/post_util.c
$(PATHB)Test_ina226$(TARGET_EXTENSION): $(STD_FILES) $(TEST_INA226_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
TEST_SX1509_SRC=$(OCWARE_ROOT)/src/devices/sx1509.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_I2C.c
$(PATHB)Test_sx1509$(TARGET_EXTENSION): $(STD_FILES) $(TEST_SX1509_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
TEST_GpioSX1509_SRC=$(OCWARE_ROOT)/src/devices/sx1509.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_I2C.c fake/fake_ThreadedISR.c $(OCWARE_ROOT)/src/drivers/GpioSX1509.c $(OCWARE_ROOT)/src/helpers/memory.c stub/stub_GateMutex.c
$(PATHB)Test_GpioSX1509$(TARGET_EXTENSION): $(STD_FILES) $(TEST_GpioSX1509_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
-
+
TEST_LTC4015_SRC=$(OCWARE_ROOT)/src/devices/ltc4015.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c $(OCWARE_ROOT)/src/post/post_util.c
-$(PATHB)Test_ltc4015$(TARGET_EXTENSION): $(STD_FILES) $(TEST_LTC4015_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
-
+$(PATHB)Test_ltc4015$(TARGET_EXTENSION): $(STD_FILES) $(TEST_LTC4015_SRC) $(INC_M)
+
TEST_powerSource_SRC=$(OCWARE_ROOT)/src/devices/powerSource.c $(OCWARE_ROOT)/src/drivers/GpioSX1509.c $(OCWARE_ROOT)/src/devices/sx1509.c $(OCWARE_ROOT)/src/helpers/memory.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c stub/stub_GateMutex.c
-$(PATHB)Test_powerSource$(TARGET_EXTENSION): $(STD_FILES) $(TEST_powerSource_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
+$(PATHB)Test_powerSource$(TARGET_EXTENSION): $(STD_FILES) $(TEST_powerSource_SRC) $(INC_M)
TEST_EEPROM_SRC=$(OCWARE_ROOT)/src/devices/eeprom.c $(OCWARE_ROOT)/src/drivers/GpioSX1509.c $(OCWARE_ROOT)/src/devices/sx1509.c $(OCWARE_ROOT)/src/helpers/memory.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c stub/stub_GateMutex.c
-$(PATHB)Test_eeprom$(TARGET_EXTENSION): $(STD_FILES) $(TEST_EEPROM_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
+$(PATHB)Test_eeprom$(TARGET_EXTENSION): $(STD_FILES) $(TEST_EEPROM_SRC) $(INC_M)
TEST_LTC4275_SRC=$(OCWARE_ROOT)/src/devices/ltc4275.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c stub/stub_GateMutex.c
-$(PATHB)Test_ltc4275$(TARGET_EXTENSION): $(STD_FILES) $(TEST_LTC4275_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
+$(PATHB)Test_ltc4275$(TARGET_EXTENSION): $(STD_FILES) $(TEST_LTC4275_SRC) $(INC_M)
TEST_OCMP_ADT7481_SRC=$(OCWARE_ROOT)/src/devices/ocmp_wrappers/ocmp_adt7481.c $(OCWARE_ROOT)/src/devices/adt7481.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c stub/stub_GateMutex.c $(OCWARE_ROOT)/src/post/post_util.c
-$(PATHB)Test_ocmp_adt7481$(TARGET_EXTENSION): $(STD_FILES) $(TEST_OCMP_ADT7481_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
+$(PATHB)Test_ocmp_adt7481$(TARGET_EXTENSION): $(STD_FILES) $(TEST_OCMP_ADT7481_SRC) $(INC_M)
TEST_OCMP_LTC4274_SRC=$(OCWARE_ROOT)/src/devices/ocmp_wrappers/ocmp_ltc4274.c $(OCWARE_ROOT)/src/devices/ltc4274.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_GPIO.c fake/fake_I2C.c fake/fake_ThreadedISR.c stub/stub_GateMutex.c $(OCWARE_ROOT)/src/post/post_util.c
-$(PATHB)Test_ocmp_ltc4274$(TARGET_EXTENSION): $(STD_FILES) $(TEST_OCMP_LTC4274_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -lm -o $@
+$(PATHB)Test_ocmp_ltc4274$(TARGET_EXTENSION): $(STD_FILES) $(TEST_OCMP_LTC4274_SRC) $(INC_M)
TEST_PINGROUP_SRC=$(OCWARE_ROOT)/src/drivers/PinGroup.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_I2C.c fake/fake_ThreadedISR.c $(OCWARE_ROOT)/src/helpers/memory.c stub/stub_GateMutex.c $(OCWARE_ROOT)/src/drivers/GpioPCA9557.c $(OCWARE_ROOT)/src/devices/pca9557.c
$(PATHB)Test_PinGroup_driver$(TARGET_EXTENSION): $(STD_FILES) $(TEST_PINGROUP_SRC)
- $(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
TEST_OCGPIO_SRC=$(OCWARE_ROOT)/src/drivers/OcGpio.c $(OCWARE_ROOT)/src/devices/i2cbus.c fake/fake_I2C.c fake/fake_GPIO.c fake/fake_ThreadedISR.c $(OCWARE_ROOT)/src/helpers/memory.c stub/stub_GateMutex.c
$(PATHB)Test_OcGpio$(TARGET_EXTENSION): $(STD_FILES) $(TEST_OCGPIO_SRC)
+
+$(PATHB)%$(TARGET_EXTENSION):
$(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $^ -o $@
+ $(COV_CMDS)
# Dummy target to allow us to force rebuild of testresults every time
FORCE:
@@ -157,6 +170,9 @@ $(PATHB):
$(PATHR):
$(MKDIR) $(PATHR)
+$(PATHC):
+ $(MKDIR) $(PATHC)
+
$(RUNNER_PATH):
$(MKDIR) $(RUNNER_PATH)
@@ -165,7 +181,17 @@ clean:
.PHONY: clean
.PHONY: test
+.PHONY: junit
+.PHONY: coverage
.PHONY: FORCE
+# Continuous integration
ci: CFLAGS += -Werror
-ci: clean test
+ci: CFLAGS += -ftest-coverage -fprofile-arcs -fprofile-dir=$(PATHC)$*/
+ci: COV_CMDS = @mkdir -p $(PATHC)$* && mv *.gcno $(PATHC)$*
+ci: clean junit coverage
+
+# Test Coverage
+cov: CFLAGS += -ftest-coverage -fprofile-arcs -fprofile-dir=$(PATHC)$*/
+cov: COV_CMDS = @mkdir -p $(PATHC)$* && mv *.gcno $(PATHC)$*
+cov: clean test coverage