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 9de84e0a6f..10cb9d312f 100644 --- a/firmware/ec/common/inc/global/OC_CONNECT1.h +++ b/firmware/ec/common/inc/global/OC_CONNECT1.h @@ -204,6 +204,16 @@ 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..f4c521b54d --- /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 ddc36d3fd0..23b87aa742 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; @@ -75,4 +76,9 @@ static const Driver 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..db514d2e25 --- /dev/null +++ b/firmware/ec/inc/common/spibus.h @@ -0,0 +1,50 @@ +/** +* 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..8720f3beae --- /dev/null +++ b/firmware/ec/inc/devices/at45db.h @@ -0,0 +1,52 @@ +/** +* 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 414e63c2dd..83ca0f46b7 100644 --- a/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT1.c +++ b/firmware/ec/platform/oc-sdr/cfg/OC_CONNECT1.c @@ -278,6 +278,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] = @@ -686,6 +688,70 @@ 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 =============================== */ @@ -965,4 +1031,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 02a4c1dcda..16e41e0253 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,17 @@ 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 = { @@ -565,4 +578,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 d877f1b26a..18f0eb05e1 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); @@ -250,6 +252,11 @@ const Component sys_schema[] = { .name = "eeprom_mac", .driver = &Driver_MAC, }, + { + .name = "SPI_flash", + .driver = &FLASHDRV, + .driver_cfg = &gbc_spi_flash_memory, + }, {} }, .commands = (Command[]){ @@ -266,6 +273,10 @@ const Component sys_schema[] = { }, {} }, + .driver_cfg = &gbc_spi_flash_memory, + .ssHookSet = &(SSHookSet) { + .postInitFxn = (ssHook_Cb)sys_post_init, + }, }, { .name = "power", diff --git a/firmware/ec/src/Board.h b/firmware/ec/src/Board.h index 7a8842ee49..8da425355a 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..5953d4cc3b --- /dev/null +++ b/firmware/ec/src/devices/at45db.c @@ -0,0 +1,283 @@ +/** +* 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..519a802490 --- /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..4c4d692b19 --- /dev/null +++ b/firmware/ec/src/devices/spibus.c @@ -0,0 +1,167 @@ +/** + * 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..5c1ce5ce4b --- /dev/null +++ b/firmware/ec/src/filesystem/fs_wrapper.c @@ -0,0 +1,271 @@ +/** +* 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..611ff0e34e --- /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..7f4c391c4b --- /dev/null +++ b/firmware/ec/src/filesystem/lfs.c @@ -0,0 +1,2373 @@ +/* + * 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..8668d3908b --- /dev/null +++ b/firmware/ec/src/filesystem/lfs.h @@ -0,0 +1,330 @@ +/* + * 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..542fc736db --- /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..cda950742f --- /dev/null +++ b/firmware/ec/src/filesystem/lfs_util.h @@ -0,0 +1,124 @@ +/* + * 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 1f3c3e9f2e..d2e8a5b11d 100644 --- a/firmware/ec/src/main.c +++ b/firmware/ec/src/main.c @@ -62,6 +62,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 b57f3c1ac9..8a2381ad9f 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]; @@ -54,7 +77,6 @@ bool SYS_cmdEcho(void *driver, void *params) return true; } -/* /***************************************************************************** ** FUNCTION NAME : SYS_post_enable ** @@ -137,3 +159,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; +}